p-net icon indicating copy to clipboard operation
p-net copied to clipboard

pnal_udp_open does not bind to the network device on linux

Open arneboe opened this issue 5 months ago • 0 comments

I have a use case where I have two network interfaces in the same subnet. I am running two instances of the p-net stack, one for each interface.

For this use case, it is important to bind the udp socket to the device using the device name. If I don't do this bind, the p-net stack will randomly use the wrong interface to send udp messages.

To fix this, I have added a reverse lookup loop to pnal_udp_open in the linux port. It searches for the network interface with the given ip and sets the SO_BINDTODEVICE socket option to bind to the device by name. For this to work, pf_udp_open needs to be modified as well. Instead of handing PNAL_IPADDR_ANY it should use the ip from net->fspm_cfg.

As far as I know, binding to the device by name does not have any downsides and I would like you to include this feature in your next release.

Below is my proof of concept implementation:

int pf_udp_open (pnet_t * net, pnal_ipport_t port)
{
   const uint32_t ip_addr =
      (((uint32_t)net->fspm_cfg.if_cfg.ip_cfg.ip_addr.a) << 24) |
      (((uint32_t)net->fspm_cfg.if_cfg.ip_cfg.ip_addr.b) << 16) |
      (((uint32_t)net->fspm_cfg.if_cfg.ip_cfg.ip_addr.c) << 8) |
      ((uint32_t)net->fspm_cfg.if_cfg.ip_cfg.ip_addr.d);

   return pnal_udp_open (ip_addr, port);
}
int pnal_udp_open (pnal_ipaddr_t addr, pnal_ipport_t port)
{
   struct sockaddr_in local;
   int id;
   const int enable = 1;

   id = socket (PF_INET, SOCK_DGRAM, IPPROTO_UDP);
   if (id == -1)
   {
      return -1;
   }

   if (setsockopt(id, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) != 0)
   {
      goto error;
   }

   /* Find the network interface with the matching IP address */
   struct ifaddrs *ifaddr, *ifa;
   int found_interface = 0;

   if (getifaddrs (&ifaddr) == 0)
   {
      /* Walk through linked list, looking for matching IP */
      for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
      {
         if (ifa->ifa_addr == NULL)
            continue;

         if (ifa->ifa_addr->sa_family == AF_INET)
         {
            struct sockaddr_in * ifaddr_in =
               (struct sockaddr_in *)ifa->ifa_addr;
            if (ntohl (ifaddr_in->sin_addr.s_addr) == addr)
            {
               /* Found matching interface, bind socket to it */
               if (
                  setsockopt (
                     id,
                     SOL_SOCKET,
                     SO_BINDTODEVICE,
                     ifa->ifa_name,
                     strlen (ifa->ifa_name)) == 0)
               {
                  printf ("Socket bound to interface: %s\n", ifa->ifa_name);
                  found_interface = 1;
                  break;
               }
            }
         }
      }

      freeifaddrs (ifaddr);

      if (!found_interface)
      {
         printf (
            "Warning: Could not find interface with IP address 0x%08x\n",
            addr);
      }
   }

   /* set IP and port number */
   local = (struct sockaddr_in){
      .sin_family = AF_INET,
      .sin_addr.s_addr = htonl (addr),
      .sin_port = htons (port),
      .sin_zero = {0},
   };

   if (bind (id, (struct sockaddr *)&local, sizeof (local)) != 0)
   {
      goto error;
   }

   return id;

error:
   close (id);
   return -1;
}

arneboe avatar Jul 23 '25 16:07 arneboe