The original UNIX socket implementation had no way to communicate errors asynchronously (the whole API is synchronous). So if a socket ran into an asynchronous error (an error not a direct consequence of calling send()
or recv()
), it tagged itself as having data to read and when the program was trying to read the data, the read call would fail and the set error would be the asynchronous error.
In your case the error is asynchronous, as the send call did not fail. The packet was correctly sent out to the destination. Yet the destination refused to accept that packet and replied with an ICMP message, that the port is not available. This message arrives asynchronously at your host, long after your send call has already returned.
So when a socket signals you that you can read data, then there are two possibilities:
- There is really data available to read.
- An asynchronous error took place and the read call gives you the error code.
When using kCFSocketDataCallBack
, you cannot distinguish these two cases, since this the API will always try to directly read data when the socket says it there is data available for reading and if that read fails, you will get an empty CFDataRef
object.
See CFSocket
implementation:
if (__CFSocketReadCallBackType(s) == kCFSocketDataCallBack) {
// ... <snip> ...
if (__CFSocketIsConnectionOriented(s)) {
buffer = bufferArray;
recvlen = recvfrom(s->_socket, buffer, MAX_CONNECTION_ORIENTED_DATA_SIZE, 0, (struct sockaddr *)name, (socklen_t *)&namelen);
} else {
buffer = malloc(MAX_DATA_SIZE);
if (buffer) recvlen = recvfrom(s->_socket, buffer, MAX_DATA_SIZE, 0, (struct sockaddr *)name, (socklen_t *)&namelen);
// ... <snip> ...
if (0 >= recvlen) {
//??? should return error if <0
/* zero-length data is the signal for perform to invalidate */
data = CFRetain(zeroLengthData);
} else {
data = CFDataCreate(CFGetAllocator(s), buffer, recvlen);
}
Source: https://opensource.apple.com/source/CF/CF-476.18/CFSocket.c.auto.html
The line //??? should return error if <0
says it all. Yes, this should return an error, yet it just returns an empty data blob.
Instead you have to use kCFSocketReadCallBack
. Now your callback is performed whenever the socket says that it has data available for reading, yet no data has been read so far. You will then have to read the data yourself using POSIX socket API:
int rawSocket = CFSocketGetNative(socket);
if (rawSocket < 0) { /* ... handle invalid socket descriptor error ... */ }
CFDataRef data = NULL;
CFErrorRef error = NULL;
for (;;) {
char buffer[MAX_PACKET_SIZE];
ssize_t bytesRead = read(rawSocket, &buffer, MAX_PACKET_SIZE);
if (bytesRead < 0) {
int errorCode = errno;
if (errorCode == EINTR) {
// Read call was just interrupted; that's uncritical, just try again
continue;
}
error = CFErrorCreate(NULL, kCFErrorDomainPOSIX, errorCode, NULL);
break;
}
data = CFDataCreate(NULL, buffer, bytesRead);
break;
}
// Either data or error is not NULL