Wednesday, March 10, 2010

Code Snippet: SIOCGIFCONF

A little while ago in this space we discussed SO_BINDTODEVICE, the socket option to control which physical interface will be used for packet ingress/egress. Recently in the comments of that post a question was posed: if you know the IP address of the interface, how do you programmatically find its name?

If there is a direct way to pass in an IP address and get back the interface name, I don't know it. The mechanism I know of is to retrieve the interface list from the kernel and walk through it until you find the IP address you're looking for. The code snippet below demonstrates the technique: the first use of SIOCGIFCONF determines the amount of memory we need, the second retrieves the interface list.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <linux/if.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main()
{
   struct ifreq *ifr;
   struct ifconf ifc;
   int s, i;
   int numif;

   // find number of interfaces.
   memset(&ifc, 0, sizeof(ifc));
   ifc.ifc_ifcu.ifcu_req = NULL;
   ifc.ifc_len = 0;

   if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
     perror("socket");
     exit(1);
   }

   if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
     perror("ioctl");
     exit(2);
   }

   if ((ifr = malloc(ifc.ifc_len)) == NULL) {
     perror("malloc");
     exit(3);
   }
   ifc.ifc_ifcu.ifcu_req = ifr;

   if (ioctl(s, SIOCGIFCONF, &ifc) < 0) {
     perror("ioctl2");
     exit(4);
   }
   close(s);

   numif = ifc.ifc_len / sizeof(struct ifreq);
   for (i = 0; i < numif; i++) {
     struct ifreq *r = &ifr[i];
     struct sockaddr_in *sin = (struct sockaddr_in *)&r->ifr_addr;

     printf("%-8s : %s\n", r->ifr_name, inet_ntoa(sin->sin_addr));
   }

   free(ifr);
   exit(0);
}

Updates: Mike Ditto points out that the number of interfaces can change between the first call to SIOCGIFCONF and the second, as some workloads result in frequent netdev creation. He advises "ifc.ifc_len = ifc.ifc_len * 2;" before calling malloc. Michael Reed notes that unistd.h is required. It worked for me without it, but only because one of the other includes was pulling it in.