How to have two multicast sockets listen to two multicast channels with same port

1.7k views Asked by At

I have broadly the following multicast socket code. It works fine. Now I need to join two multicast channels on the same machine like 224.10.13.18 - 55001 224.10.13.34 - 55001

and depending on which ip address it came from, I need to treat the message differently.

The question is to how to use create two sockets for multicast channels where the port values are same, such that each socket only returns read on data that is sent to that channel.

  /* create socket to join multicast group on */
  socket_file_descriptor_ = socket ( AF_INET, SOCK_DGRAM, IPPROTO_UDP );
  if ( socket_file_descriptor_ < 0 ) 
    { // fprintf ( stderr, "cannot open socket \n");
      exit(1);
    }

  /* set reuse port to on to allow multiple binds per host */ 
  {
    int flag_on = 1;
    if ( ( setsockopt ( socket_file_descriptor_, SOL_SOCKET, SO_REUSEADDR, &flag_on,
                        sizeof(flag_on) ) ) < 0 ) 
      { // fprintf ( stderr, "MulticastReceiverSocket setsockopt() SOL_SOCKET SO_REUSEADDR failed\n");
        exit(1);
      }
  } 

  struct ip_mreq mc_req;
  inet_pton ( AF_INET, listen_ip_.c_str(), &(mc_req.imr_multiaddr.s_addr) ); 

  mc_req.imr_interface.s_addr = htonl(INADDR_ANY);

  /* send an ADD MEMBERSHIP message via setsockopt */
  if ( ( setsockopt ( socket_file_descriptor_, IPPROTO_IP, IP_ADD_MEMBERSHIP, 
                      (void*) &mc_req, sizeof(mc_req))) < 0) 
  { // std::cerr << "setsockopt() failed in IP_ADD_MEMBERSHIP " << listen_ip_ << ": "<< listen_port_ << std::endl;
      exit(1);
  } 


  /* construct a multicast address structure */
  struct sockaddr_in mcast_Addr;
  bzero ( &mcast_Addr, sizeof(mcast_Addr) );
  mcast_Addr.sin_family = AF_INET;
  mcast_Addr.sin_addr.s_addr = htonl(INADDR_ANY);
  mcast_Addr.sin_port = htons ( listen_port_ );
  /* bind to specified port onany interface */
  if ( bind ( socket_file_descriptor_, (struct sockaddr *) &mcast_Addr, sizeof ( struct sockaddr_in ) ) < 0 ) 
  { // fprintf ( stderr, "%s cannot bind %s:%d \n", "MulticastReceiverSocket", listen_ip_.c_str(), listen_port_ ) ;
     exit(1);
  } 
1

There are 1 answers

1
dbush On

You only need one socket for this. If you set the IP_PKTINFO option on a call to setsockopt, you can use recvmsg to get a struct in_pktinfo, which will contain the destination IP address. Then you can choose how to process the packet based on that.

Borrowing from https://stackoverflow.com/a/5309155/1687119 (error checking removed for brevity):

// sock is bound AF_INET socket, usually SOCK_DGRAM
// include struct in_pktinfo in the message "ancilliary" control data
setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt));
// the control data is dumped here
char cmbuf[0x100];
// the remote/source sockaddr is put here
struct sockaddr_in peeraddr;
// if you want access to the data you need to init the msg_iovec fields
struct msghdr mh = {
    .msg_name = &peeraddr,
    .msg_namelen = sizeof(peeraddr),
    .msg_control = cmbuf,
    .msg_controllen = sizeof(cmbuf),
};
recvmsg(sock, &mh, 0);
for ( // iterate through all the control headers
    struct cmsghdr *cmsg = CMSG_FIRSTHDR(&mh);
    cmsg != NULL;
    cmsg = CMSG_NXTHDR(&mh, cmsg))
{
    // ignore the control headers that don't match what we want
    if (cmsg->cmsg_level != IPPROTO_IP ||
        cmsg->cmsg_type != IP_PKTINFO)
    {
        continue;
    }
    struct in_pktinfo *pi = CMSG_DATA(cmsg);
    // at this point, peeraddr is the source sockaddr
    // pi->ipi_spec_dst is the destination in_addr
    // pi->ipi_addr is the receiving interface in_addr
}