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

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);
}

Related

Programmatically creating an ad-hoc network in Big Sur

Before Mac OS Big Sur, one could create an ad-hoc network by calling the startIBSSModeWithSSID:security:channel:password:error: function of a CWInterface obtained from a CWWifiClient. It seems that after an update to Big Sur, the above function is deprecated and throws a kCWOperationNotPermittedErr (-3930) error every time.
I tried launching the application from root, and it still refused to create an ad-hoc network. Meanwhile, using the "Create Network" option in the WiFi dropdown menu works with an administrator password.
A previous answer on this site I have come across is outdated and the code does not work anymore. There is a post on the Apple Developer forums created 5 months ago but it remains unanswered, with the "solution" being to file a tech support incident.
This is the code I am using:
#import <Foundation/Foundation.h>
#import <CoreWLAN/CoreWLAN.h>
#import <SecurityFoundation/SFAuthorization.h>
#import <objc/message.h>
int main(int argc, const char * argv[]) {
#autoreleasepool {
bool success = 0;
CWWiFiClient* wifiClient = [CWWiFiClient sharedWiFiClient];
CWInterface* interface = [wifiClient interface];
NSString* namestr = #"very_creative_ssid";
NSData* name = [namestr dataUsingEncoding:NSUTF8StringEncoding];
NSString* pass = #"very_cruel_framework"; // not used
NSError* err = nil;
success = [interface startIBSSModeWithSSID:name
security:kCWIBSSModeSecurityNone
channel:11
password:nil
error:&err];
if (!success) {
NSLog(#"%#", err);
return 1;
}
[NSRunLoop.currentRunLoop run];
}
return 0;
}
Is there a way to programmatically create an ad-hoc network in Big Sur without throwing an error?
Edit: Here is the console output (1 line):
2022-01-12 05:25:03.723 cwlantest[15305:448617] Error Domain=com.apple.coreWLAN.error Code=-3930 "(null)"
I'm going to put this as an answer, if anyone finds anything new or Apple adds this feature in the future, I'll be very happy to be wrong.
TLDR: Not anymore!
Since Apple removed the "Create network..." option from the wifi menubar, the only way to create an ad-hoc network is through Network Sharing. I followed https://www.makeuseof.com/how-to-create-a-secure-ad-hoc-network-in-macos/ under the How to Create a Secure Ad Hoc Network section to make a network:
sudo networksetup -createnetworkservice AdHoc lo0
sudo networksetup -setmanual AdHoc 192.168.1.88 255.255.255.255
And in System Preferences, share your network connection from AdHoc over WiFi.
With that on, I checked the CWInterface.interfaceMode() and it was in HostAP mode. Pure speculation, but I think IBSS was removed completely, it's marked as Deprecated in the developer documentation. -3930 is kCWOperationNotPermittedErr, so I'm not 100% sure that's accurate, but it's possible.
There are private interfaces to set HostAP mode in CoreWLAN:
https://github.com/onmyway133/Runtime-Headers/blob/master/macOS/10.13/CoreWLAN.framework/CWInterface.h https://medium.com/swlh/calling-ios-and-macos-hidden-api-in-style-1a924f244ad1 https://gist.github.com/wolever/4418079
After replacing objc_msgsend with NSInvocation in the last link since objc_msgsend seems to have been removed:
#import <CoreWLAN/CoreWLAN.h>
#import <objc/message.h>
int main(int argc, char* argv[]) {
#autoreleasepool {
int ch;
NSString *ssid = nil, *password = nil;
while((ch = getopt(argc, argv, "s:p:h")) != -1) {
switch(ch) {
case 's':
ssid = [NSString stringWithUTF8String:optarg];
break;
case 'p':
password = [NSString stringWithUTF8String:optarg];
break;
case '?':
case 'h':
default:
printf("USAGE: %s [-s ssid] [-p password] [-h] command\n", argv[0]);
printf("\nOPTIONS:\n");
printf(" -s ssid SSID\n");
printf(" -p password WEP password\n");
printf(" -h Print help\n");
printf("\nCOMMAND:\n");
printf(" status Print interface mode\n");
printf(" start Start Host AP mode\n");
printf(" stop Stop Host AP mode\n");
return 0;
}
}
NSString *command = nil;
if(argv[optind]) {
command = [NSString stringWithUTF8String:argv[optind]];
}
CWInterface *iface = [[CWWiFiClient sharedWiFiClient] interface];
if(!command || [command isEqualToString:#"status"]) {
NSString *mode = nil;
switch(iface.interfaceMode) {
case kCWInterfaceModeStation:
mode = #"Station";
break;
case kCWInterfaceModeIBSS:
mode = #"IBSS";
break;
case kCWInterfaceModeHostAP:
mode = #"HostAP";
break;
case kCWInterfaceModeNone:
default:
mode = #"None";
}
printf("%s\n", [mode UTF8String]);
} else if([command isEqualToString:#"stop"]) {
// Stop Host AP mode
if(getuid() != 0) {
printf("this may need root (trying anyway)...\n");
}
SEL selector = #selector(stopHostAPMode);
NSMethodSignature *signature = [iface methodSignatureForSelector: selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = iface;
invocation.selector = selector;
[invocation invoke];
printf("Done?");
//objc_msgSend(iface, #selector(stopHostAPMode));
} else if([command isEqualToString:#"start"]) {
if(!ssid) {
printf("error: an ssid must be specified\n");
return 1;
}
// known security types:
// 2: no securiry
// 16: wep
// Note: values [-127..127] have been tried, and all but these return errors.
unsigned long long securityType = 2;
if(password) {
if([password length] < 10) {
printf("error: password too short (must be >= 10 characters)\n");
return 1;
}
securityType = 16;
}
NSSet *chans = [iface supportedWLANChannels];
//printf("chan count: %lu\n", [chans count]);
NSEnumerator *enumerator = [chans objectEnumerator];
CWChannel *channel;
while ((channel = [enumerator nextObject])) {
//printf("channel: %lu\n", [channel channelNumber]);
if ([channel channelNumber] == 11)
break;
}
printf("Found Channel: %d\n", channel.channelNumber);
// Start Host AP mode
NSError *error = nil;
NSError **errorptr = &error;
SEL selector = #selector(startHostAPModeWithSSID:securityType:channel:password:error:);
NSMethodSignature *signature = [iface methodSignatureForSelector: selector];
NSInvocation *invocation =
[NSInvocation invocationWithMethodSignature:signature];
invocation.target = iface;
invocation.selector = selector;
NSString * ssidstr = #"Test";
NSString * pass = #"barbarbarr";
NSData * ssidArg = [ssidstr dataUsingEncoding:NSUTF8StringEncoding];
[invocation setArgument: &ssidArg atIndex:2];
[invocation setArgument: &securityType atIndex:3];
[invocation setArgument: &channel atIndex:4];
[invocation setArgument: &pass atIndex:5];
[invocation setArgument: &errorptr atIndex:6];
[invocation invoke];
BOOL success;
[invocation getReturnValue:&success];
if (!success) {
printf("startHostAPModeWithSSID error: %s\n", [(*errorptr).localizedDescription UTF8String]);
return 1;
} else {
printf("Success?\n");
return 0;
}
}
return 0;
}
}
./hostap stop does successfully kick me out of hostap mode started from network sharing, but ./hostap start fails with -3903 kCWNotSupportedErr.
Also, using startHostAPMode: without other settings does succeed, but the wifi menu shows WiFi: Internet Sharing, so I think this is a private api meant specifically for network sharing and will likely need other configuration to get working. You could potentially continue down that road, but it didn't look very promising. The best bet is to just use network sharing or potentially look into scripting System Preferences with AppleScript if you really want a scripted approach.

Calling CFRelease for a CFHostRef will sometimes crash

This is a secondary question that arose out of a post I made earlier today. I have the method below, which works fine for what I need, but sometimes crashes when I call CFRelease on the hostRef variable. I think it may have to do with the resource being used elsewhere when I'm trying to release it, but as far as I can tell, I'm synchronously resolving the host and I'm not accessing it from another thread.
I tried calling CFHostCancelInfoResolution before CFRelease, but that didn't change the frequency of the crashes. I thought I would post this here to see if there are some assumptions or misconceptions I have that aren't true.
+ (NSArray *) addressesForHostname: (NSString *)hostname {
CFMutableArrayRef ipAddresses = nil;
DLog(#"Getting addresses for host name %#", hostname);
CFHostRef hostRef = CFHostCreateWithName(kCFAllocatorDefault, (__bridge CFStringRef)(hostname));
CFStreamError error;
BOOL didResolve = CFHostStartInfoResolution(hostRef, kCFHostAddresses, &error); // synchronously get the host.
if (didResolve) {
CFArrayRef responseObjects = CFHostGetAddressing(hostRef, NULL);
long numberOfResponses = CFArrayGetCount(responseObjects);
ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault, numberOfResponses, &kCFTypeArrayCallBacks);
for ( int i = 0 ; i < numberOfResponses; ++i ) {
char * ipAddress = NULL;
CFDataRef responseObject = CFArrayGetValueAtIndex(responseObjects, i);
struct sockaddr * currentAddress = (struct sockaddr *) CFDataGetBytePtr(responseObject); // Unwrap the CFData wrapper aound the sockaddr struct
switch (currentAddress->sa_family) {
case AF_INET: { // Internetworking AKA IPV4
DLog(#"Extracting IPV4 address");
struct sockaddr_in * socketAddress = (struct sockaddr_in *) currentAddress;
ipAddress = malloc(sizeof(INET_ADDRSTRLEN));
inet_ntop(AF_INET,
&(socketAddress->sin_addr),
ipAddress,
INET_ADDRSTRLEN);
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);
break;
}
case AF_INET6: { // IPV6
DLog(#"Extracting IPV6 address");
struct sockaddr_in6 * socketAddress = (struct sockaddr_in6 *) currentAddress;
ipAddress = malloc(sizeof(INET6_ADDRSTRLEN));
inet_ntop(AF_INET6,
&(socketAddress->sin6_addr),
ipAddress,
INET6_ADDRSTRLEN);
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);
break;
}
default:
DLog(#"Unsupported addressing protocol encountered. Gracefully ignoring and continuing.");
break;
}
if(ipAddress != NULL) {
free(ipAddress);
}
}
CFRelease(responseObjects);
}
CFRelease(hostRef);
return (__bridge_transfer NSArray *) ipAddresses;
}
removing CFRelease(responseObjects); shall fix it
You must follow the Create Rule for Core Foundation objects. If you received the object by calling a function with the words Create or Copy in their names (or if you call CFRetain explicitly), then you must release (CFRelease) the object when you're done with it. If you did not receive the object this way, then you must not release the object.
There are several mistakes in your code. First, the one you're finding, which is around responseObjects. You fetch this object using:
CFDataRef responseObject = CFArrayGetValueAtIndex(responseObjects, i);
The function does not have Create or Copy in its name. You must not call CFRelease on it.
However, you also call this:
ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault, numberOfResponses, &kCFTypeArrayCallBacks);
and in a couple of places:
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
You must call CFRelease on these objects before they go out of scope or you will leak them.
I think CFStreamError error; may cause this issue.
Try to declare error to null?
// synchronously get the host.
BOOL didResolve = CFHostStartInfoResolution(hostRef, kCFHostAddresses, NULL);

Threading and Sockets in Objective-C

NOTE: I've edited my question. I've got it to connect and perform the first callback, but subsequent callbacks don't go through at all.
This is my first time writing Objective-C (with GNUstep; it's for a homework assignment). I've got the solution working, but I am trying to add something more to it. The app is a GUI client that connects to a server and gets data from it. Multiple clients can connect to the same server. If any one of the clients changes data that is residing on the server, the server sends a callback to all registered clients. This solution was originally implemented in Java (both client and server) and for the latest assignment, the professor wanted us to write an Objective-C client for it. He said that we don't need to handle callbacks, but I wanted to try anyway.
I am using NSThread and I wrote something that looks like this:
CallbackInterceptorThread.h
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#interface CallbackInterceptorThread : NSThread {
#private
NSString* clientPort;
AppDelegate* appDelegate;
}
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate;
- (void) main;
#end
CallbackInterceptorThread.m
#import <Foundation/Foundation.h>
#import "CallbackInterceptorThread.h"
#define MAXDATASIZE 4096
#implementation CallbackInterceptorThread
- (id) initWithClientPort: (NSString*) aClientPort
appDelegate: (AppDelegate*) anAppDelegate {
if((self = [super init])) {
[clientPort autorelease];
clientPort = [aClientPort retain];
[appDelegate autorelease];
appDelegate = [anAppDelegate retain];
}
return self;
}
- (void) main {
GSRegisterCurrentThread();
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
char* buffer = malloc(MAXDATASIZE);
Cst420ServerSocket* socket = [[Cst420ServerSocket alloc] initWithPort: clientPort];
[socket retain];
NSString* returnString;
while(YES) {
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
if([socket accept]) {
printf("Connection accepted!\n");
while(YES) {
printf("Inner loop\n");
sleep(1);
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
printf("Going to sleep now\n");
sleep(1);
}
[socket close];
}
}
}
#end
Cst420ServerSocket has been provided to us by the instructor. It looks like this:
#import "Cst420Socket.h"
#define PORT "4444"
/**
* Cst420Socket.m - objective-c class for manipulating stream sockets.
* Purpose: demonstrate stream sockets in Objective-C.
* These examples are buildable on MacOSX and GNUstep on top of Windows7
*/
// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa){
if (sa->sa_family == AF_INET) {
return &(((struct sockaddr_in*)sa)->sin_addr);
}
return &(((struct sockaddr_in6*)sa)->sin6_addr);
}
#implementation Cst420ServerSocket
- (id) initWithPort: (NSString*) port{
self = [super init];
int ret = 0;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // use my IP
const char* portStr = [port UTF8String];
if ((rv = getaddrinfo(NULL, portStr, &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
ret = 1;
}else{
for(p = servinfo; p != NULL; p = p->ai_next) {
if ((sockfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP))==-1){
perror("server: socket create error");
continue;
}
if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
perror("server: bind error");
continue;
}
break;
}
if (p == NULL) {
fprintf(stderr, "server: failed to bind\n");
ret = 2;
}else{
freeaddrinfo(servinfo); // all done with this structure
if (listen(sockfd, BACKLOG) == -1) {
perror("server: listen error");
ret = 3;
}
}
if (ret == 0){
return self;
} else {
return nil;
}
}
}
- (BOOL) accept {
BOOL ret = YES;
#if defined(WINGS)
new_fd = accept(sockfd, NULL, NULL);
#else
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size);
#endif
if (new_fd == -1) {
perror("server: accept error");
ret = NO;
}
connected = ret;
return ret;
}
- (int) sendBytes: (char*) byteMsg OfLength: (int) msgLength Index: (int) at{
int ret = send(new_fd, byteMsg, msgLength, 0);
if(ret == -1){
NSLog(#"error sending bytes");
}
return ret;
}
- (NSString* ) receiveBytes: (char*) byteMsg
maxBytes: (int) max
beginAt: (int) at {
int ret = recv(new_fd, byteMsg, max-1, at);
if(ret == -1){
NSLog(#"server error receiving bytes");
}
byteMsg[ret+at] = '\0';
NSString * retStr = [NSString stringWithUTF8String: byteMsg];
return retStr;
}
- (BOOL) close{
#if defined(WINGS)
closesocket(new_fd);
#else
close(new_fd);
#endif
connected = NO;
return YES;
}
- (void) dealloc {
#if defined(WINGS)
closesocket(sockfd);
#else
close(sockfd);
#endif
[super dealloc];
}
#end
Our professor also provided us an example of a simple echo server and client (the server just spits back whatever the client sent it) and I've used the same pattern in the thread.
My initial problem was that my callback interceptor thread didn't accept any (callback) connections from the server. The server said that it could not connect back to the client (ConnectException from Java; it said "Connection refused"). I was able to fix this by changing my instructor's code. In the connect function (not shown), he had set the hints to use AF_UNSPEC instead of AF_INET. So Java was seeing my localhost IP come through as 0:0:0:0:0:0:0:1 (in IPv6 format). When Java tried to connect back to send a callback, it received an exception (not sure why it cannot connect to an IPv6 address).
After fixing this problem, I tried out my app again and this time the callback from the server was received by my client. However, subsequent callbacks fail to work. After receiving the first callback, the busy-loop keeps running (as it should). But when the server sends a second callback, it looks like the client cannot read it in. On the server side I can see that it sent the callback to the client successfully. It's just that the client is having trouble reading in the data. I added some print statements (see above) for debugging and this is what I get:
Client waiting for callbacks on port 2020
Connection accepted!
Inner loop
Received from Server |A callback from server to 127.0.0.1:2020|
Got a callback from server
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
Received from Server ||
Going to sleep now
Inner loop
... (and it keeps going regardless of the second callback being sent)
Here is how I am starting the thread (from the GUI):
CallbackInterceptorThread* callbackInterceptorThread = [[CallbackInterceptorThread alloc] initWithClientPort: clientPort appDelegate: self];
[callbackInterceptorThread start];
I think I've got it working. So from the Java side (the server), this was what I was doing:
Socket socket = new Socket(clientAddress, clientPort);
BufferedOutputStream out = new BufferedOutputStream(socket.getOutputStream());
out.write(("A callback from server to " + clientAddress + ":" + clientPort).getBytes());
out.flush();
out.close();
I put some debugging print-statements in my professor's code and noticed that in receiveBytes, recv was returning 0. The return value of recv is the length of the message that it received. So it received a zero-length string. But a return value of 0 also means that the peer closed the connection properly (which is exactly what I had done from the Java side with out.close()). So I figured that if I needed to respond to the second callback, I would need to accept the connection again. So I changed my busy loop to this:
printf("Client waiting for callbacks on port %s\n", [clientPort cString]);
while([socket accept]) {
printf("Connection accepted!\n");
returnString = [socket receiveBytes: buffer maxBytes: MAXDATASIZE beginAt: 0];
printf("Received from Server |%s|\n", [returnString cString]);
if([returnString length] > 0) {
printf("Got a callback from server\n");
[appDelegate populateGui];
}
}
[socket close];
and that seemed to do the trick. I am not sure if this is the right way to do it, so I am open to suggestions for improvement!

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.

Help with Sending/ Receiving UDP packets - C Sockets

Ok, if you look at some of my previous questions, I've been working on getting a simple connection up and running with C sockets (I'm still fairly new to the whole networking aspect of an program, but everyone has to start somewhere, right?). I've included the code below that I have so far and when I execute it, I get no errors, but at the same time, I don't get the packet on the other end. By the way, I'm programming multicast sockets in objective-C and "msgStatus" is just a label in my GUI (it's hooked up correctly, so there's no problem there). I'm just not seeing where I'm going wrong. Could someone possibly help me or point me in the right direction? Thanks!
#define MAX_LEN 1024 /* maximum string size to send */
#define MIN_PORT 1024 /* minimum port allowed */
#define MAX_PORT 65535 /* maximum port allowed */
#define MYPORT 5673 /* port we will be using for our multicast socket */
-(void)broadcastMessage {//(NSString*)msg {
NSLog(#"broadcastMessage - Stage 1");
NSString *msg = #"From Master";
mc_ttl = 3; // number of node hops the message is allowed to travel across the network
// define the port we will be using
mc_port = MYPORT;
/* create a socket for sending to the multicast address */
if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: broadcastMessage - socket() failed");
return;
}
mc_addr.sin_family = AF_INET;
mc_addr.sin_addr.s_addr = inet_addr("225.0.0.37");
mc_addr.sin_port = htons(mc_port);
if (bind(sock, (struct sockaddr *) &mc_addr, sizeof(struct sockaddr_in)) < 0) {
NSLog(#"ERROR: bind not successful");
return;
}
NSLog(#"broadcastMessage - Stage 2");
/* set the TTL (time to live/hop count) for the send */
if ((setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*) &mc_ttl, sizeof(mc_ttl))) < 0) {
NSLog(#"ERROR: broadcastMessage - setsockopt() failed");
return;
}
NSLog(#"broadcastMessage - Stage 3");
/* construct a multicast address structure - erase everything in the structure first*/
memset(&mc_addr, 0, sizeof(mc_addr));
// prepare the message to be sent
char send_str[MAX_LEN];
/* clear send buffer */
memset(send_str, 0, sizeof(send_str));
// convert the message to a C string to send
[msg getCString:send_str maxLength:MAX_LEN encoding:NSASCIIStringEncoding];
//while (fgets(send_str, MAX_LEN, stdin)) {
NSLog(#"broadcastMessage - Stage 4");
// send_len = strlen(send_str);
/* send string to multicast address */
if ((sendto(sock, send_str, sizeof(send_str), 0, (struct sockaddr *) &mc_addr, sizeof(mc_addr))) != sizeof(send_str)) {
NSLog(#"ERROR: broadcastMessage - sendto() sent incorrect number of bytes");
return;
}
NSLog(#"broadcastMessage - Stage 5");
/* clear send buffer */
memset(send_str, 0, sizeof(send_str));
NSLog(#"broadcastMessage - Stage 6");
close(sock);
}
-(void)listenForPackets {
listeningFlag_on = 1;
NSLog(#"listenForPackets - Stage 1");
if ((listeningSock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
NSLog(#"ERROR: listenForPackets - socket() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
// set reuse port to on to allow multiple binds per host
if ((setsockopt(listeningSock, SOL_SOCKET, SO_REUSEADDR, &listeningFlag_on, sizeof(listeningFlag_on))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 2");
// construct a multicast address structure after erasing anything in the listeningmc_addr structure
memset(&listeningmc_addr, 0, sizeof(listeningmc_addr));
listeningmc_addr.sin_family = AF_INET;
listeningmc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
listeningmc_addr.sin_port = htons(mc_port);
// bind multicast address to socket
if ((bind(listeningSock, (struct sockaddr *) &listeningmc_addr, sizeof(listeningmc_addr))) < 0) {
NSLog(#"ERROR: listenForPackets - bind() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
//******************************************************************************************************************************
//******************************************************************************************************************************
NSString *ipAddress = [[NSString alloc] initWithString:self.getIPAddress];
const char *tmp = [ipAddress UTF8String];
listeningMc_addr_str = tmp;
printf("%s\n", listeningMc_addr_str);
listeningMc_req.imr_multiaddr.s_addr = inet_addr("225.0.0.37");
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
// send an ADD MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
int err = errno;
NSLog(#"errno - %i", err);
NSLog(#"Error = %s", strerror(err));
perror("ERROR");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 3");
for (;;) { // loop forever
// clear the receive buffers & structs
memset(listeningRecv_str, 0, sizeof(listeningRecv_str));
listeningFrom_len = sizeof(listeningFrom_addr);
memset(&listeningFrom_addr, 0, listeningFrom_len);
// block waiting to receive a packet
if ((listeningRecv_len = recvfrom(listeningSock, listeningRecv_str, MAX_LEN, 0, (struct sockaddr*)&listeningFrom_addr, &listeningFrom_len)) < 0) {
NSLog(#"ERROR: listenForPackets - recvfrom() failed");
return; // make the method return an int instead of void and use this statement to check for errors
}
NSLog(#"listenForPackets - Stage 4");
NSString *tmpy = [[NSString alloc] initWithCString:listeningRecv_str encoding:NSASCIIStringEncoding];
msgStatus.text = tmpy;
NSLog(#"ERROR");
}
// received string
printf("Received %d bytes from %s: ", listeningRecv_len, inet_ntoa(listeningFrom_addr.sin_addr));
printf("%s", listeningRecv_str);
}
// send a DROP MEMBERSHIP message via setsockopt
if ((setsockopt(listeningSock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &listeningMc_req, sizeof(listeningMc_req))) < 0) {
NSLog(#"ERROR: listenForPackets - setsockopt() failed");
//return 1; // make the method return an int instead of void and use this statement to check for errors
}
close(listeningSock);
NSLog(#"listenForPackets - Stage 5 - Complete");
}
Here is the code I'm using to extract my IP Address.
-(NSString *)getIPAddress {
NSString *address = #"error";
struct ifaddrs *interfaces; // = NULL;
struct ifaddrs *temp_addr; // = NULL;
int success = 0;
// retrieve the current interfaces - returns 0 on success
success = getifaddrs(&interfaces);
if (success == 0)
{
// Loop through linked list of interfaces
temp_addr = interfaces;
while(temp_addr != NULL)
{
if(temp_addr->ifa_addr->sa_family == AF_INET)
{
// Check if interface is en0 which is the wifi connection on the iPhone
if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:#"en0"])
{
// Get NSString from C String
address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)];
}
}
temp_addr = temp_addr->ifa_next;
}
}
// Free memory
freeifaddrs(interfaces);
return address;
}
In the listener, I think you need to set
listeningMc_req.imr_interface.s_addr = htonl(INADDR_ANY);
... since that is also the interface on which you bind the socket. Depending on whether you run everything on a single host, you might need to consider the loopback interface and binding to INADDR_ANY will do that.
Is there a router between you and your destination? If so, there's some work that needs to be done to tell the router that you want to subscribe to the feed as well as tell the router you will be sending the feed.
I would start by tcpdump-ing the connection to make sure the packet is leaving your machine first.