Multicasting in C: Binary does not receive when using addrinfo

430 views Asked by At

I have this funny little problem in two nearly identical programs. What I am trying to do is send some data on Multicast socket and receive it. For now, I am okay if the sender receives the message (I'll set the option to not receive later).

I have two implementation cases. In the first approach, I am using the traditional way of initializing a sockaddr structure and then binding to, and also joining a multicast group on the same socket. This, however, is IPv4/IPv6 dependent and to circumvent that, I tried to use addrinfo structures in the second variant of the program. Both programs are given below.

The problem is, the messages are being received in the first use case, where I am using the regular sockaddr while, there is no message being received/socket descriptor being set in the second case. Could somebody help me out and explain why is this happening?

Variant 1 (with sockaddr)

#include<stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>       /* for nonblocking */
#include <netinet/tcp.h>

fd_set hm_tprt_conn_set;

main()
{
    struct ip_mreq mreq;
    struct sockaddr_in mc_addr;
    int sock_fd ;
    int val;
    int reuse = 1;

    struct sockaddr_in ip;
    struct sockaddr_in src_addr;

    int total_bytes_rcvd=0;
    unsigned int length;
    unsigned char buf[50]; 
    int op_complete = 0;
    int os_error;

    struct timeval select_timeout;
    fd_set read_set;
    int32_t nready; //Number of ready descriptors

    time_t time_val;

    length = sizeof (src_addr);

    sock_fd = socket(AF_INET, SOCK_DGRAM,0);
    if(sock_fd == -1)
    {
        printf("\n Error Opening UDP MCAST socket");
        perror("\n Cause is ");
        exit(0);
    }

    printf("\n Setting the socket to non-blocking mode");
    val = fcntl(sock_fd, F_GETFL , 0);
    val = fcntl(sock_fd, F_SETFL, val | O_NONBLOCK);

    if (val == -1)
    {
            printf("\n Error while setting socket to non-blocking mode");
            perror("Cause is ");
            sock_fd = -1;
            exit(0);
    } //end if val == -1

    if (setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        fprintf(stderr, "setsockopt: %d\n", errno);
        perror("Cause is ");
            exit(0);
    }

    FD_SET(sock_fd, &hm_tprt_conn_set);

    printf("\n Construct a mcast address structure");
    /* 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(4936);

    memset(&ip, 0, sizeof(ip));
    ip.sin_family = AF_INET;
    ip.sin_addr.s_addr = inet_addr("224.0.0.203")/*htonl(INADDR_ANY)*/;
    ip.sin_port = htons(4936);

    printf("\n Bind the multicast address structure and port to the recieving socket ");
    if (bind( sock_fd, (struct sockaddr*) &mc_addr, sizeof(mc_addr)) == -1)
    {
        fprintf(stderr, "bind: %d\n", errno);
            perror("\n Cause is ");
        exit(0);
     }

    mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.203");
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

        if(setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq)) == -1)
    {
        fprintf(stderr, "setsockopt: %d\n", errno);
        perror("\n Cause is ");
            exit(0);
    }

    printf("\nCreated Recv Socket: %d", sock_fd);   


    fflush(stdout);
    memset(&src_addr, 0, sizeof(mc_addr));
    while(1){
    /* Send a multicast */  
    time_val = time(NULL);
    snprintf(buf, sizeof(buf), "Hello: %s", ctime(&time_val));
        total_bytes_rcvd = sendto(sock_fd,
                        buf,
                        sizeof(buf),
                           0,
                          (struct sockaddr *)&ip,
                         length );
    printf("\n%d bytes sent.", total_bytes_rcvd);

    /* perform select */                         
        select_timeout.tv_sec = 0;
        select_timeout.tv_usec = 5000000;

        read_set = hm_tprt_conn_set;

        nready = select(sock_fd+1, &read_set, NULL, NULL, &select_timeout);
        if(nready == 0)
        {
            /***************************************************************************/
            /* No descriptors are ready                                                */
            /***************************************************************************/
            continue;
        }
        else if(nready == -1)
        {
            perror("Error Occurred on select() call.");
            continue;
        }

    if(FD_ISSET(sock_fd, &read_set))
        {
        printf("\n Recv the data"); 
        total_bytes_rcvd = recvfrom(sock_fd,
                                         buf,
                                      sizeof(buf),
                                       0,
                                  (struct sockaddr *)&src_addr,
                                     &length );

        printf("%s: message = \" %s \"\n", inet_ntoa(src_addr.sin_addr), buf);
        printf("\n total byte recieved %d", total_bytes_rcvd);

            /***************************************************************************/
            /* If select returned 1, and it was a listen socket, it makes sense to poll*/
            /* again by breaking out and use select again.                             */
            /***************************************************************************/
            if(--nready <=0)
            {
                printf("\nNo more incoming requests.");
                continue;
            }
        }//end select on listenfd
    }
}

Variant 2 (with addrinfo)

#include<stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/param.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>       /* for nonblocking */
#include <netinet/tcp.h>
#include <netdb.h>      /* AI_PASSIVE and other Macros for getaddrinfo() */

fd_set hm_tprt_conn_set;

main()
{

  struct addrinfo hints, *res, *ressave;
  char target[128] = "127.0.0.1";
  char service[128] = "4936";

    struct ip_mreq mreq;

    int sock_fd ;
    int val;
    int reuse = 1;

    struct sockaddr_in ip;
    struct sockaddr_in src_addr;

    int total_bytes_rcvd=0;
    unsigned int length;
    unsigned char buf[50]; 
    int op_complete = 0;
    int os_error;

    struct timeval select_timeout;
    fd_set read_set;
    int32_t nready; //Number of ready descriptors

    time_t time_val;

    length = sizeof (src_addr);

  sock_fd = socket(AF_INET, SOCK_DGRAM,0);
  if(sock_fd == -1)
  {
      printf("\n Error Opening UDP MCAST socket");
      perror("\n Cause is ");
      exit(0);
  }

  printf("\n Setting the socket to non-blocking mode");
  val = fcntl(sock_fd, F_GETFL , 0);
  val = fcntl(sock_fd, F_SETFL, val | O_NONBLOCK);

  if (val == -1)
  {
          printf("\n Error while setting socket to non-blocking mode");
          perror("Cause is ");
          sock_fd = -1;
          exit(0);
  } //end if val == -1

  if (setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
  {
      fprintf(stderr, "setsockopt: %d\n", errno);
        perror("Cause is ");
            exit(0);
    }

  FD_SET(sock_fd, &hm_tprt_conn_set);

    printf("\n Construct a mcast address structure");
    /* construct a multicast address structure */

  hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

  if((os_error = getaddrinfo(target, service, &hints, &res)) !=0)
  {
      printf("\n%s",gai_strerror(os_error));
      exit(0);
  }

  ressave = res;

  if(bind(sock_fd, res->ai_addr, res->ai_addrlen) != 0)
    {
        perror("Error binding to port");
        close(sock_fd);
        sock_fd = -1;
    }

    mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.203");
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);

    if(setsockopt(sock_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq)) == -1)
    {
        fprintf(stderr, "setsockopt: %d\n", errno);
        perror("Cause is ");
        exit(0);
    }

  /* Set Destination address */
    memset(&ip, 0, sizeof(ip));
    ip.sin_family = AF_INET;
    ip.sin_addr.s_addr = inet_addr("224.0.0.203")/*htonl(INADDR_ANY)*/;
    ip.sin_port = htons(4936);

    /* Set to zero address where addresses of sender will be received */
    memset(&src_addr, 0, sizeof(src_addr));

    while(1){

    /* Send a multicast */  
    time_val = time(NULL);
    snprintf(buf, sizeof(buf), "Hello: %s", ctime(&time_val));
        total_bytes_rcvd = sendto(sock_fd,
                        buf,
                        sizeof(buf),
                          0,
                          (struct sockaddr *)&ip,
                        length );

    printf("\n%d bytes sent.", total_bytes_rcvd);

    /* perform select */                         
        select_timeout.tv_sec = 0;
        select_timeout.tv_usec = 5000000;

        read_set = hm_tprt_conn_set;

        nready = select(sock_fd+1, &read_set, NULL, NULL, &select_timeout);
        if(nready == 0)
        {
            /***************************************************************************/
            /* No descriptors are ready                                                */
            /***************************************************************************/
            continue;
        }
        else if(nready == -1)
        {
            perror("Error Occurred on select() call.");
            continue;
        }

    if(FD_ISSET(sock_fd, &read_set))
        {
        printf("\n Recv the data"); 
        total_bytes_rcvd = recvfrom(sock_fd,
                                  buf,
                                  sizeof(buf),
                                    0,
                                  (struct sockaddr *)&src_addr,
                                  &length );

        printf("%s: message = \" %s \"\n", inet_ntoa(src_addr.sin_addr), buf);
        printf("\n total byte recieved %d", total_bytes_rcvd);

            /***************************************************************************/
            /* If select returned 1, and it was a listen socket, it makes sense to poll*/
            /* again by breaking out and use select again.                             */
            /***************************************************************************/
            if(--nready <=0)
            {
                printf("\nNo more incoming requests.");
                continue;
            }
        }//end select on listenfd
    }
}
2

There are 2 answers

0
dbush On BEST ANSWER

The difference is that in the first variant you're binding to INADDR_ANY, while in the second variant you're binding to 127.0.0.1. Failing to bind to INADDR_ANY means you won't receive any multicast data.

You can fix this with the following:

hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;

if((os_error = getaddrinfo(NULL, service, &hints, &res)) !=0)
{
   printf("\n%s",gai_strerror(os_error));
   exit(0);
}

From the man page for getaddrinfo regarding AI_PASSIVE:

If node is NULL, the network address in each socket structure is initialized according to the AI_PASSIVE flag, which is set in hints.ai_flags. The network address in each socket structure will be left unspecified if AI_PASSIVE flag is set. This is used by server applications, which intend to accept client connections on any network address. The network address will be set to the loopback interface address if the AI_PASSIVE flag is not set. This is used by client applications, which intend to connect to a server running on the same network host.

While in this case you are sending to the same host, multicast data does not go out on the localhost interface by default. You would need to call setsockopt with the IP_MULTICAST_IF option to set the outgoing multicast interface.

With this change, I was able to send and receive with the second variant.

2
alvits On

Before you can bind() you need to have a working socket. You will need to cycle through all the results. Here's what's missing on your code.

ressave = res;

sock = socket(ressave->ai_family, ressave->ai_socktype, ressave->ai_protocol);
while(ressave != NULL && (sock < 0 || connect(sock, ressave->ai_addr, ressave->ai_addrlen) < 0)) {
    close(sock);
    if((ressave = ressave->ai_next) != NULL)
        sock = socket(ressave->ai_family, ressave->ai_socktype, ressave->ai_protocol);
}

At this point you have either found a working socket sock or not. When ressave is not NULL then the value of socket sock is valid.