0

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!

Josh Bradley
  • 4,630
  • 13
  • 54
  • 79
  • A **NSString** is an *immutable* string. The compiler is complaining because you are assigning the result of the conversion (also immutable) to a **char***, and the string pointed to by that **char*** may validly be modified. You should declare mc_addr_str as **const char *** to make the warning go away. – Inshallah Jul 23 '09 at 02:02
  • Also, try: mc_add_str = [ipAddress UTF8String] – Inshallah Jul 23 '09 at 02:22
  • I'm still getting an error at the setsockopt method call...Is there any way I can find out what the error is exactly? – Josh Bradley Jul 23 '09 at 05:09
  • Essentially, what **const** does is tell the compiler that the contents of the string can't be modified, and if you try to do so anyway, it will give you a warning (different from the one you are getting). If you don't use **const**, then the compiler must assume that somewhere down the road you *will* be modifying that string. So to save you headaches trying to find out why your progam segfaulted, it'll warn you when you try to assign a **const** string, that musn't be modified, to a **non-const** string, that probably will get modified. – Inshallah Jul 23 '09 at 06:02
  • Other than looking at perror() output, you should also make sure that you have a valid address: printf("%s\n", mc_addr_str); – Inshallah Jul 23 '09 at 06:22
  • Well, errno gives me the int error of 22 and strerror(err) says "Invalid Argument." – Josh Bradley Jul 23 '09 at 07:55
  • perror also returns "Invalid Argument"...I'm not sure what I'm doing wrong exactly. – Josh Bradley Jul 23 '09 at 09:16
  • Well, specifically "129.74.153.224" is what is in mc_addr_str. It just holds the IP Address as a string. – Josh Bradley Jul 23 '09 at 09:18

2 Answers2

1

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

Marco Mustapic
  • 3,879
  • 1
  • 21
  • 20
  • 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 Jul 23 '09 at 04:11
  • @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 Jul 23 '09 at 05:53
  • 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 Jul 23 '09 at 08:02
1

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
  • 4,804
  • 28
  • 24