Sending UDP packets in Objective C: packet arriving without any data - objective-c

I'm trying to send UDP packets in objective C. More specifically, building with xcode targeting the iphone 6.1 simulator.
I can't seem to actually receive the data I send. Weirdly, I do get a data event... the data's just been truncated to length 0.
I've cut it down as much as I can, to make a dead simple test I think should pass.
#import "UdpSocketTest.h"
#include <arpa/inet.h>
#import <CoreFoundation/CFSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#implementation UdpSocketTest
static int receivedByteCount = 0;
void onReceive(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info);
void onReceive(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
// this gets called once, but CFDataGetLength(data) == 0
receivedByteCount += CFDataGetLength(data);
}
-(void) testUdpSocket {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(5000); // <-- doesn't really matter, not sending from receiver
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
CFSocketContext socketContext = { 0, (__bridge void*)self, CFRetain, CFRelease, NULL };
// prepare receiver
CFSocketRef receiver = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP ,kCFSocketDataCallBack, (CFSocketCallBack)onReceive, &socketContext);
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFSocketCreateRunLoopSource(NULL, receiver, 0), kCFRunLoopCommonModes);
CFSocketConnectToAddress(receiver, CFDataCreate(NULL, (unsigned char *)&addr, sizeof(addr)), -1);
// point sender at receiver
CFSocketRef sender = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, (CFSocketCallBack)onReceive, &socketContext);
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFSocketCreateRunLoopSource(NULL, sender, 0), kCFRunLoopCommonModes);
CFSocketConnectToAddress(sender, CFSocketCopyAddress(receiver), -1);
// send data of sixty zeroes, allow processing to occur
CFSocketSendData(sender, NULL, (__bridge CFDataRef)[NSMutableData dataWithLength:60], 2.0);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
// did the data arrive?
STAssertTrue(receivedByteCount > 0, #"");
// nope
}
#end
What am I doing wrong? I know it's something obvious, but I can't see it.

I'm surprised you're getting any callbacks at all—you're never actually binding your receiver to your local port. You're creating two sockets and telling both of them "I went to send data to 127.0.0.1:5000", but nowhere are you saying "I want to receive data on 127.0.0.1:5000.
In order to do that, you should be calling CFSocketSetAddress on the receiver socket, not CFSocketConnectToAddress. This is equivalent to calling the bind(2) system call on the underlying native BSD socket.

Related

Apportable gives compile error "arithmetic on a pointer to an incomplete type"

Apportable error
arithmetic on a pointer to an incomplete type 'struct if_msghdr'
socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1);
Here is the code. The code is working fine on ios/xcode but gives above error to interfaceMsgStruct with apportable. I have get this code from google and it is widely used so i dont think the code might have error. If it does then please correct me.
#import "MFMacAddress.h"
#implementation MFMacAddress
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_dl.h>
+ (NSString *)getMacAddress
{
int mgmtInfoBase[6];
char *msgBuffer = NULL;
size_t length;
unsigned char macAddress[6];
struct if_msghdr *interfaceMsgStruct;
struct sockaddr_dl *socketStruct;
NSString *errorFlag = NULL;
// Setup the management Information Base (mib)
mgmtInfoBase[0] = CTL_NET; // Request network subsystem
mgmtInfoBase[1] = AF_ROUTE; // Routing table info
mgmtInfoBase[2] = 0;
mgmtInfoBase[3] = AF_LINK; // Request link layer information
mgmtInfoBase[4] = NET_RT_IFLIST; // Request all configured interfaces
// With all configured interfaces requested, get handle index
if ((mgmtInfoBase[5] = if_nametoindex("en0")) == 0)
errorFlag = #"if_nametoindex failure";
else
{
// Get the size of the data available (store in len)
if (sysctl(mgmtInfoBase, 6, NULL, &length, NULL, 0) < 0)
errorFlag = #"sysctl mgmtInfoBase failure";
else
{
// Alloc memory based on above call
if ((msgBuffer = malloc(length)) == NULL)
errorFlag = #"buffer allocation failure";
else
{
// Get system information, store in buffer
if (sysctl(mgmtInfoBase, 6, msgBuffer, &length, NULL, 0) < 0)
errorFlag = #"sysctl msgBuffer failure";
}
}
}
// Befor going any further...
if (errorFlag != NULL)
{
NSLog(#"Error: %#", errorFlag);
return errorFlag;
}
// Map msgbuffer to interface message structure
interfaceMsgStruct = (struct if_msghdr *) msgBuffer;
// Map to link-level socket structure
socketStruct = (struct sockaddr_dl *) (interfaceMsgStruct + 1);
// Copy link layer address data in socket structure to an array
memcpy(&macAddress, socketStruct->sdl_data + socketStruct->sdl_nlen, 6);
// Read from char array into a string object, into traditional Mac address format
NSString *macAddressString = [NSString stringWithFormat:#"%02X:%02X:%02X:%02X:%02X:%02X",
macAddress[0], macAddress[1], macAddress[2],
macAddress[3], macAddress[4], macAddress[5]];
NSLog(#"Mac Address: %#", macAddressString);
// Release the buffer memory
free(msgBuffer);
return macAddressString;
}
#end
To get the mac address, you can do
[[UIDevice currentDevice] macAddress]
which returns an NSString *
The compile error you're seeing is the result of a different system headers between IOS and Android. We're working on a more transparent solution, but in the meantime the solution above is an easy workaround.
Often you get this error when the definition of struct if_msghdr is not 'visible'.
Check if you need to #include other header files, or if different conditional compilation options locked it out.

What is wrong with my sockets code?

I'm working on an iOS version of my Android application which constantly sends some binary data to server over regular TCP connection. And while Java code works fine, my code in plain C doesn't - the connection breaks after the first bunch of data is sent to server. I managed to reimplement Java solution using CFNetwork framework, however my C code is still a problem...
C code (error checking and uninteresting code is removed):
mSocket = socket(AF_INET, SOCK_STREAM, 0);
//...
connect(mSocket, (struct sockaddr *)&sin, sizeof(sin));
int keepalive = 1;
int res = setsockopt(mSocket, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive,
sizeof(int));
int set = 1;
setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
unsigned char buffer* = //...;
int ret = send(mSocket, buffer, bufferLen, 0); //handshake with server, runs fine
while(/*there is new data*/) {
buffer = new_data(); //some fake method
ret = send(mSocket, buffer, bufferLen, 0); //On the second iteration
//write returns -1
}
However, this Objective-C code based on CFNetwork works just fine...
Objective-C code:
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
host,
port,
&readStream,
&writeStream);
CFWriteStreamOpen(mServerWriteStream);
while(/*there is new data*/) {
NSData* data = //....
int wroteBytes = CFWriteStreamWrite(mServerWriteStream, [data bytes], [data length]);
}
So I'm puzzled with what am I doing wrong in C code and will appreciate any hints
Thank you
If you need a regular TCP connection you need to create the socket with such protocol. Instead of:
mSocket = socket(AF_INET, SOCK_STREAM, 0);
You should create it as:
#include <netinet/in.h>
mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
On my system IPPROTO_TCP is defined as 6 and not 0. I suspect you have something similar.
The setsockopt() has to execute before connect() or it is ignored.

UDP client prints received data twice [i.e. received data is appended on itself twice]

GOAL: I'm making a UDP socket to send and receive data.
I am testing this on my laptop, so I have a server running on the background that listens to incoming messages and echoing this back.
PROBLEM: I am seeing that the server receives one string, and when it echoes back, the client reads the string 2 times instead of ONE and adds gibberish.How to solve this?
Output from the code is: HelloHello09[btw i als get some questionsmarks that are upside down in front of the 09 and behind it, but I cannot paste it, lolz]
Code:
#define BUFLEN 5
#define PORT 12345
#import <Foundation/Foundation.h>
#define srvr_IP "127.0.0.1"
void errorSig(char *);
int main (int argc, const char * argv[])
{
int sockSend = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in si_other;
char buf[BUFLEN] = "Hello";
char bufrec[BUFLEN];
#autoreleasepool {
si_other.sin_family = AF_INET;
si_other.sin_port = htons(PORT);
inet_pton(AF_INET, srvr_IP, &si_other.sin_addr);
memset(&si_other.sin_zero, 0, sizeof(si_other.sin_zero));
int size = sizeof(si_other);
sendto(sockSend, buf, BUFLEN, 0,
(struct sockaddr *)&si_other, size);
recvfrom(sockSend, bufrec, BUFLEN, 0, (struct sockaddr *)&si_other, (unsigned int*)&size);
NSString *test = [[NSString alloc]initWithUTF8String:bufrec];
NSLog(#" data is: %#", test);
close(sockSend);
}
return 0;
}
I've just seen that you did in fact define BUFLEN as 5 (sorry I missed that when I wrote my comment above). As I suspected, you have a NULL termination issue. The length of a string in C is one more than the number of characters you want to store, to make room for the NULL terminator indicating the end of the string.
Change your BUFLEN definition to 6 and you should find it works much better.

How to get read/write streams after dns_sd DNSServiceResolve in iOS

The goal is to get read/write streams after a service is successfully resolved by with the dns_sd API. I started with Apple's DNSSDObjects sample project and I'm editing the DNSSDService.m file to get read and write streams after the service is resolved.
Here's what I've got so far. It seems like it should work, but it does't :(
I got this far by following the code on this thread, though I'm not entirely sure that it's how this should be done.
EDIT: Apple's documentation here confirms that this is how it should be done..."So, once you've resolved the service using DNSServiceResolve, you should pass the service's DNS name (the hosttarget parameter to your DNSServiceResolveReply callback) to a connect-by-name API (like CFStreamCreatePairWithSocketToHost)."
// Called by DNS-SD when something happens with the resolve operation.
static void ResolveReplyCallback(
DNSServiceRef sdRef,
DNSServiceFlags flags,
uint32_t interfaceIndex,
DNSServiceErrorType errorCode,
const char * fullname,
const char * hosttarget,
uint16_t port,
uint16_t txtLen,
const unsigned char * txtRecord,
void * context
)
{
CFStringRef host = CFStringCreateWithCString(kCFAllocatorDefault,
hosttarget,
kCFStringEncodingUTF8);
DNSSDService * obj;
#pragma unused(interfaceIndex)
assert([NSThread isMainThread]); // b/c sdRef dispatches to the main queue
obj = (__bridge DNSSDService *) context;
assert([obj isKindOfClass:[DNSSDService class]]);
assert(sdRef == obj->sdRef_);
#pragma unused(sdRef)
#pragma unused(flags)
#pragma unused(fullname)
#pragma unused(txtLen)
#pragma unused(txtRecord)
if (errorCode == kDNSServiceErr_NoError) {
[obj resolveReplyWithTarget:[NSString stringWithUTF8String:hosttarget]
port:ntohs(port)];
} else {
[obj stopWithError:[NSError errorWithDomain:NSNetServicesErrorDomain
code:errorCode
userInfo:nil]
notify:YES];
}
//now let's get read&write streams?
CFReadStreamRef readStream = NULL;
CFWriteStreamRef writeStream = NULL;
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
host,
port,
&readStream,
&writeStream
);
if (readStream && writeStream) {
CFReadStreamSetProperty(readStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
CFWriteStreamSetProperty(writeStream,
kCFStreamPropertyShouldCloseNativeSocket,
kCFBooleanTrue);
obj.inputStream = (__bridge_transfer NSInputStream *) readStream;
obj.outputStream = (__bridge_transfer NSOutputStream *) writeStream;
}
CFRelease(host);
}

How to use CFNetwork to get byte array from sockets?

I'm working in a project for the iPad, it is a small program and I need it to communicate with another software that runs on windows and act like a server; so the application that I'm creating for the iPad will be the client.
I'm using CFNetwork to do sockets communication, this is the way I'm establishing the connection:
char ip[] = "192.168.0.244";
NSString *ipAddress = [[NSString alloc] initWithCString:ip];
/* Build our socket context; this ties an instance of self to the socket */
CFSocketContext CTX = { 0, self, NULL, NULL, NULL };
/* Create the server socket as a TCP IPv4 socket and set a callback */
/* for calls to the socket's lower-level connect() function */
TCPClient = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketDataCallBack, (CFSocketCallBack)ConnectCallBack, &CTX);
if (TCPClient == NULL)
return;
/* Set the port and address we want to listen on */
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr([ipAddress UTF8String]);
CFDataRef connectAddr = CFDataCreate(NULL, (unsigned char *)&addr, sizeof(addr));
CFSocketConnectToAddress(TCPClient, connectAddr, -1);
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, TCPClient, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
CFRelease(sourceRef);
CFRunLoopRun();
And this is the way I sent the data, which basically is a byte array
/* The native socket, used for various operations */
// TCPClient is a CFSocketRef member variable
CFSocketNativeHandle sock = CFSocketGetNative(TCPClient);
Byte byteData[3];
byteData[0] = 0;
byteData[1] = 4;
byteData[2] = 0;
send(sock, byteData, strlen(byteData)+1, 0);
Finally, as you may have noticed, when I create the server socket, I registered a callback for the kCFSocketDataCallBack type, this is the code.
void ConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
// SocketsViewController is the class that contains all the methods
SocketsViewController *obj = (SocketsViewController*)info;
UInt8 *unsignedData = (UInt8 *) CFDataGetBytePtr(data);
char *value = (char*)unsignedData;
NSString *text = [[NSString alloc]initWithCString:value length:strlen(value)];
[obj writeToTextView:text];
[text release];
}
Actually, this callback is being invoked in my code, the problem is that I don't know how can I get the data that the windows client sent me, I'm expecting to receive an array of bytes, but I don't know how can I get those bytes from the data param.
If anyone can help me to find a way to do this, or maybe me point me to another way to get the data from the server in my client application I would really appreciate it.
Thanks.
In your callback, the data parameter is indeed a CFDataRef value for the kCFSocketDataCallBack callback type.
CFDataRef dataRef = (CFDataRef) data;
Byte *array = new Byte[CFDataGetLength(dataRef)]; // Or use a fixed length
CFDataGetBytes(dataRef, CFRangeMake(0, CFDataGetLength(theData)), array);
The array will then contains the array of byte.