views:

1222

answers:

2

Ok, I'm fairly new to C sockets but I just need to do some simple sendto() and recvfrom() calls to get a string out across a network, using multicast sockets. After looking around and reading several guides (including Beej's), I found the code below which does the job of listening for messages sent over a multicast socket (which is what I need). The program runs fine when it is in main, but my problem arises when I put it in a method (i.e. a method called "listenForPackets") elsewhere in my project and attempt to run it in another thread on a runloop. After debugging though, the problem comes down to the variable "mc_addr_str" which is assigned to equal argv[1] in the main method.

#include <sys/types.h>  // for type definitions
#include <sys/socket.h> // for socket API calls
#include <netinet/in.h> // for address structs
#include <arpa/inet.h>  // for sockaddr_in
#include <stdio.h>      // for printf() and fprintf()
#include <stdlib.h>     // for atoi()
#include <string.h>     // for strlen()
#include <unistd.h>     // for close()

#define MAX_LEN  1024   // maximum receive string size
#define MIN_PORT 1024   // minimum port allowed
#define MAX_PORT 65535  // maximum port allowed

int main(int argc, char *argv[]) {

    int sock;                     // socket descriptor
    int flag_on = 1;              // socket option flag
    struct sockaddr_in mc_addr;   // socket address structure
    char recv_str[MAX_LEN+1];     // buffer to receive string
    int recv_len;                 // length of string received
    struct ip_mreq mc_req;        // multicast request structure
    char* mc_addr_str;            // multicast IP address
    unsigned short mc_port;       // multicast port
    struct sockaddr_in from_addr; // packet source
    unsigned int from_len;        // source addr length

    // validate number of arguments
    if (argc != 3) {
     fprintf(stderr, 
       "Usage: %s <Multicast IP> <Multicast Port>\n", 
       argv[0]);
     exit(1);
    }

    mc_addr_str = argv[1];      // arg 1: multicast ip address
    mc_port = atoi(argv[2]);    // arg 2: multicast port number

    // validate the port range
    if ((mc_port < MIN_PORT) || (mc_port > MAX_PORT)) {
     fprintf(stderr, "Invalid port number argument %d.\n",
       mc_port);
     fprintf(stderr, "Valid range is between %d and %d.\n",
       MIN_PORT, MAX_PORT);
     exit(1);
    }

    // create socket to join multicast group on
    if ((sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
     perror("socket() failed");
     exit(1);
    }

    // set reuse port to on to allow multiple binds per host
    if ((setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag_on, sizeof(flag_on))) < 0) {
     perror("setsockopt() failed");
     exit(1);
    }

    // construct a multicast address structure
    memset(&mc_addr, 0, sizeof(mc_addr));
    mc_addr.sin_family      = AF_INET;
    mc_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    mc_addr.sin_port        = htons(mc_port);

    // bind multicast address to socket
    if ((bind(sock, (struct sockaddr *) &mc_addr, sizeof(mc_addr))) < 0) {
     perror("bind() failed");
     exit(1);
    }

    // construct an IGMP join request structure
    mc_req.imr_multiaddr.s_addr = inet_addr(mc_addr_str);
    mc_req.imr_interface.s_addr = htonl(INADDR_ANY);

    // send an ADD MEMBERSHIP message via setsockopt
    if ((setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*) &mc_req, sizeof(mc_req))) < 0) {
     perror("setsockopt() failed");
     exit(1);
    }

    for (;;) {          // loop forever

     // clear the receive buffers & structs
     memset(recv_str, 0, sizeof(recv_str));
     from_len = sizeof(from_addr);
     memset(&from_addr, 0, from_len);

     // block waiting to receive a packet
     if ((recv_len = recvfrom(sock, recv_str, MAX_LEN, 0, (struct sockaddr*)&from_addr, &from_len)) < 0) {
      perror("recvfrom() failed");
      exit(1);
     }

     // output received string
     printf("Received %d bytes from %s: ", recv_len, inet_ntoa(from_addr.sin_addr));
     printf("%s", recv_str);
    }

    // send a DROP MEMBERSHIP message via setsockopt
    if ((setsockopt(sock, IPPROTO_IP, IP_DROP_MEMBERSHIP, (void*) &mc_req, sizeof(mc_req))) < 0) {
     perror("setsockopt() failed");
     exit(1);
    }

    close(sock);  
}

Now, via help from another SO member, I have a method that will return the IPaddress to me as a NSString (I'm using it elsewhere in my program also, so I need to keep it returning NSString).

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

I thought I could just do a simple little conversion

mc_addr_str = (someConversion)getIPAddress;

What I have so far is:

NSString *ipAddress = [[NSString alloc] initWithString:self.getIPAddress];

mc_addr_str = [ipAddress cStringUsingEncoding:[NSString defaultCStringEncoding]];

When I do this, the program makes it to the setsockopt call and then fails with an error code of -1 (I assume that's a general error code that lets the program know something bad happened and needs to abort). Also, when I am assigning mc_addr_str in the previous statement, I get

warning: assignment discards qualifiers from pointer target type

I'm not sure where my problem is arising from now. Do I have a casting error during the assignment to mc_addr_str or did I use the wrong encoding? Any input is appreciated!

A: 

cStringUsingEncoding returns a const char *, and your mc_add_str is a char *. That's why the compiler is issuing a warning.

Marco Mustapic
Ok, maybe I just don't understand what you're saying but I'm confused. Isn't this what I want? Declare a pointer of type char to point to [ipAddress UTF8String]; which will return a char*...thus, my char pointer is pointing to a char data type???
Josh Bradley
@Josh Bradley: **const char ***! don't forget the **const**. That's what gives you the error. Also see my comment above, it explains in more detail.
Inshallah
oh ok, got it. Sorry, I didn't see that. That fixed the warning error. Now I just have to figure out why the setsockopt is failing when it is adding membership.
Josh Bradley
+1  A: 

The address you get from getIPAddresss() is the address of the interface, not of the multicast group. Here is how the address is being used in your example, can you spot the problem?

// construct an IGMP join request structure
mc_req.imr_multiaddr.s_addr = inet_addr(mc_addr_str);
mc_req.imr_interface.s_addr = htonl(INADDR_ANY)

You set the multicast address to the address in mc_addr_str, which holds the interface address. That doesn't seem right does it? ;)

Here's what you should do:

// construct an IGMP join request structure
mc_req.imr_multiaddr.s_addr = inet_addr("225.0.0.37");
mc_req.imr_interface.s_addr = inet_addr(mc_addr_str);

The address range from 224.0.0.0 to 239.255.255.255 is reserved for multicast addresses. Don't use 224.0.0.0 to 224.0.0.255 though since these are reserved for multicast routing information. If you use any other addresses your setsockopt() will fail the way it does in your example.

Inshallah