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;
}
Post
Replies
Boosts
Views
Activity
Thanks for the thought, but... I don't think connect does anything except tell the socket what address to send to. Supporting this theory, I did switch to skip calling connect(), followed by using sendto() with the destination address instead. No change in behavior, both platforms act the same as before.
It still kinda feels like somewhere there's some kind of OS filtering maybe. I did turn off the System Settings->Network->Firewall switch and run again, but no change. Might the OS filter inbound ICMP packets at a low level in some way? Or maybe the outbound ones, actually, it is possible it never got sent.
Thanks for the idea on getaddrinfo(), will change that (haven't tried yet tho). But it feels tangential to whatever is actually going on.
I have kind of figured out the problem, but I don't "understand" it.
I switched the code from making a select() followed by recv(), to just calling recvfrom(). The select() call always times out. But recvfrom() works!
However, my understanding is the semantics of recvfrom() are to block until there is something to read. But if I send a ping and it doesn't work for whatever of many possible reasons, I need to not wait forever and time out after a few seconds of waiting.
Key semantic question: why does select not wake up when there is (or should be?) a data packet there? I inserted some sleep() time before the recvfrom() just to make sure it would receive something even if it was buffered somewhere.
Also just FWIW, I confirmed that the desired packets were being received on wire by using the following in a separate shell window:
$ sudo tcpdump 'icmp'
This saw my packets get sent and received, and led me to try other things.
OK, egg on my face, I have figured this out.
Having not worked on Unix-y systems much, I neglected to understand what the first argument to select() was. In Windows, it turns out to be basically ignored. In Unix, it is the max number of file descriptors passed in the FD_SET. So, basically, s+1 where s is the file descriptor of the socket. Obviously rookie mistake.
DOH. Thanks for the help and while it took me some personal head scratching I learned something about unix. :-)