Hi Quinn, thanks for the kind attention and example. I looked it over, and while I'm not too up on Swift I don't quite know why mine wouldn't work as I think(?) the basic network calls are similar. Though... there are some higher level wrappers that I'm not highly familiar with.
I boiled my C++ (but barely) code down to its minimum to illustrate the issue. I compile and run this on MacOS with g++, as well as Windows with g++ (using mingw64 tools). Works fine on Windows, both on Win11/Parallels on my Mac as well as my 12+ yr old Win10 machine.
I've also pasted in snapshots of the output. On MacOS it always times out in the select(), i.e. never receives anything.
MacOS output (terminal)
~/source/test % g++ pingtest.cpp ~/source/test % a.out www.yahoo.com connecting to www.yahoo.com (69.147.88.8)
Timed out with no reply received
~/source/test %
Windows output (cmd), built on Win11/Parallels but same output when the exe is copied to and run on old Win10
M:\source\test>g++ -D_WINDOWS pingtest.cpp -lws2_32
M:\source\test>a.exe www.yahoo.com
connecting to www.yahoo.com (69.147.88.8)
Reply was received, 0.013000 secs
M:\source\test>
And the C++ source code
// pingtest.cpp: MacOS times out on select() (line 165)
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <assert.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#ifndef _WINDOWS
// Compiling on MacOS needs these for various definitions
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
// windows-y terms for compiling in unix land
typedef int SOCKET;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
// SOCK_DGRAM means no need to run as sudo on MacOS
// (but select() times out either way, still illustrating the problem)
#define MY_SOCK_TYPE SOCK_DGRAM
#else // _WINDOWS
#include <winsock.h>
// create a RAW socket b/c get error WSAPROTONOSUPPORT w/SOCK_DGRAM
#define MY_SOCK_TYPE SOCK_RAW
#endif // _WINDOWS
#define ICMP_ECHO 8
#define ICMP_ECHOREPLY 0
// The IP header
struct IpHeader {
unsigned char h_len_version; // Whoa, g++ doesn't always pack bit fields
// unsigned int h_len:4; // length of the header
// unsigned int version:4; // Version of IP
unsigned char tos; // Type of service
unsigned short total_len; // total length of the packet
unsigned short ident; // unique identifier
unsigned short frag_and_flags; // flags
unsigned char ttl;
unsigned char proto; // protocol (TCP, UDP etc)
unsigned short checksum; // IP checksum
unsigned int sourceIP;
unsigned int destIP;
};
//
// ICMP header
//
struct IcmpHeader {
unsigned char i_type;
unsigned char i_code; // type sub code
unsigned short i_cksum;
unsigned short i_id;
unsigned short i_seq;
// timestamp as a payload
unsigned int timestamp;
};
const int ICMP_LEN=32; // actual total length of IcmpHeader
static SOCKET m_s=INVALID_SOCKET;
// simple error handling for test program - just print a message and terminate
static void FAIL(const char* msg)
{
printf("%s: %s\n",msg,strerror(errno));
exit(1);
}
static void init_socket(const char* szHost)
{
assert(szHost != NULL); // valid string needed
assert(m_s == INVALID_SOCKET); // only call once, please
m_s = socket(AF_INET,MY_SOCK_TYPE,IPPROTO_ICMP);
if(m_s == INVALID_SOCKET)
FAIL("socket()");
sockaddr_in dest;
memset(&dest,0,sizeof(dest));
dest.sin_family = AF_INET;
// convert host string from either x.x.x.x or hostname to ip address
if((dest.sin_addr.s_addr = inet_addr(szHost)) == INADDR_NONE) {
hostent* hp = gethostbyname(szHost);
if(!hp) FAIL("Unknown host");
if(hp->h_addrtype != AF_INET) FAIL("Host is not an IP address");
assert(hp->h_length == sizeof(dest.sin_addr.s_addr));
memcpy(&(dest.sin_addr),hp->h_addr,hp->h_length);
}
// quick message confirming hostname translated
printf("connecting to %s (%s)\n",szHost,inet_ntoa(dest.sin_addr));
if(connect(m_s,(sockaddr*)&dest,sizeof(dest)) != 0)
FAIL("connect()");
}
static unsigned short checksum(const unsigned short* buf,int size)
{
unsigned int cksum=0;
while(size >= sizeof(unsigned short)) {
cksum += *buf++;
size -= sizeof(unsigned short);
}
const unsigned char* bbuf = (unsigned char*)buf;
while(size-- > 0)
cksum += *buf++;
cksum = (cksum >> 16) + (cksum & 0xffff);
cksum += (cksum >> 16);
return (unsigned short)(~cksum);
}
static void do_ping(void)
{
static unsigned short m_seq=100; // start non-zero just for grins
char buf[1024]; // just a byte buffer for send/recv
IcmpHeader* icmp = (IcmpHeader*)buf;
icmp->i_type = ICMP_ECHO;
icmp->i_code = 0;
icmp->i_id = (unsigned short)getpid();
icmp->i_cksum = 0;
icmp->i_seq = m_seq++;
icmp->timestamp = clock(); // arbitrary thing to pass around
memset(icmp+1,'E',ICMP_LEN-sizeof(IcmpHeader)); // fill with some junk
icmp->i_cksum = checksum((const unsigned short*)icmp,ICMP_LEN);
//
// Send the ICMP request
//
size_t n = send(m_s,buf,ICMP_LEN,0);
if(n == SOCKET_ERROR)
FAIL("send()");
assert(n == ICMP_LEN);
//
// Wait for the ICMP response
//
fd_set fd;
timeval tmo;
FD_ZERO(&fd);
FD_SET(m_s,&fd);
tmo.tv_sec = 10; // timeout in secs
tmo.tv_usec = 0;
n = select(m_s,&fd,0,0,&tmo);
if(n == SOCKET_ERROR) // error?
FAIL("select()");
if(n == 0) { // timed out waiting
//
// THIS IS THE PROBLEM ON MACOS - NEVER GETS PAST HERE, ALWAYS TIMES OUT
//
printf("Timed out with no reply received\n");
return;
}
assert(n == 1 && FD_ISSET(m_s,&fd));
//
// Receive the ICMP response
//
n = recv(m_s,buf,sizeof(buf),0);
if(n == SOCKET_ERROR) // error?
FAIL("recv()");
if(n < sizeof(IpHeader))
FAIL("Received invalid IP packet");
IpHeader* iphdr = (IpHeader*)buf;
int h_len = (iphdr->h_len_version & 0x0F); // g++ bit packing hack
if(n < (int)(h_len*4+sizeof(IcmpHeader)))
FAIL("Received too few ICMP bytes in reply");
icmp = (IcmpHeader*)(buf + h_len*4);
if(icmp->i_type != ICMP_ECHOREPLY) {
printf("Received ICMP non-echo type %d\n",icmp->i_type);
return;
}
if(icmp->i_id != (unsigned short)getpid())
printf("warning: received someone else's packet!\n");
if((icmp->i_seq+1) != m_seq)
printf("warning: received reply out of sequence\n");
printf("Reply was received, %f secs\n",(double)(clock() - icmp->timestamp)/CLOCKS_PER_SEC);
}
static void close_socket(void)
{
if(m_s != INVALID_SOCKET)
close(m_s);
m_s = INVALID_SOCKET;
}
int main(int argc, char* argv[])
{
#ifdef _WINDOWS
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,1),&wsaData) != 0)
printf("WSAStartup failed: 0x%08x\n",::GetLastError());
#endif
if(argc < 2) {
printf("Usage: %s host\n",argv[0]);
return 0;
}
int n = -1;
init_socket(argv[1]);
do_ping();
close_socket();
return 0;
}