Monday, March 15, 2010

Howto get a list of network interfaces in Linux, part 3

Here we are, this is the ultimate post from these series. Thanks both previous posts, we are now able to get a list of interfaces using any language from shell script to whatever language which can speak ''sockets''.

However, as we could realize earlier, the ioctl-based method does not work exactly the way we want, because it does only report IP enabled devices. So if you want to deal with network interfaces before they start running, you are stuck with the /proc method. There must be another way. Remember that man netdevice we did last week. They mentioned netlink... What are those ?

Netlink sockets are sockets from the AF_NETLINK family. Being sockets, they are used to communicate between two addresses. The fun part is, netlink communications are local. And they usually (in the most simple scenario) link a user process with the kernel, especially the networking stack of the kernel.

Oh great, so we could query the kernel directly, to find out the contents of the global list of interfaces ? Indeed, we will. And since this tutorial is the most tricky of all three, let's have a detailed look at the source code. You can always grab the full source by following the link at the bottom of this post.

First we will open a socket :

int fd;
(...)
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

We already discussed about the socket family, the type should be set to SOCK_RAW (the only one that makes sense here anyway) and the protocol is NETLINK_ROUTE. Why _ROUTE ? There are a few netlink protocols, each dealing with a specific part of the networking stack. NETLINK_XFRM deals with the transform layer used by security and mobility stacks, for example. And the protocol we chose deals with both routes and links.

Now we have to bind that socket. Netlink addresses are based on process ID, so let's do that :

struct sockaddr_nl local;
(...)
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = pid; /* can be obtained using getpid*/
local.nl_groups = 0;

if (bind(fd, (struct sockaddr *) &local, sizeof(local)) < 0)
... error handling ...

If an error occurs it might mean that you are not running the program as super-user or with sufficient rights, or that netlink support is missing in the kernel. And if the bind system call is successful, we can now start sending requests. The request we will be sending, is the one that makes the kernel answer by sending a list of all the interfaces, whatever their state.

typedef struct nl_req_s nl_req_t;

struct nl_req_s {
  struct nlmsghdr hdr;
  struct rtgenmsg gen;
};
(...)
struct sockaddr_nl kernel;
struct msghdr rtnl_msg;
struct iovec io;
nl_req_t req;
(...)
memset(&rtnl_msg, 0, sizeof(rtnl_msg));
memset(&kernel, 0, sizeof(kernel));
memset(&req, 0, sizeof(req));
(...)
kernel.nl_family = AF_NETLINK; /* fill-in kernel address (destination) */

req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_seq = 1;
req.hdr.nlmsg_pid = pid;
req.gen.rtgen_family = AF_PACKET; /*  no preferred AF, we will get *all* interfaces */

io.iov_base = &req;
io.iov_len = req.hdr.nlmsg_len;
rtnl_msg.msg_iov = &io;
rtnl_msg.msg_iovlen = 1;
rtnl_msg.msg_name = &kernel;
rtnl_msg.msg_namelen = sizeof(kernel);

sendmsg(fd, (struct msghdr *) &rtnl_msg, 0);

Okay, so this is the longest piece of code we will be quoting here. First we need to define a structure that can hold our specific message, since netlink can encapsulate so many things, we need to give some structure for this encapsulation. This the the nl_req_t type we just defined.

Then we prepare the destination address for our message. As expected, it is the ''kernel'' address which is specified by all zeroes except the family that is set to AF_NETLINK.

Now we prepare our request. It has a header, and a single message. The header follows the struct nlmsghdr format. In this structure, we will fill information about :
  • the message type : RTM_GETLINK, obviously used to get information about links.
  • the message length. For this, we use the NLMSG_LENGTH macro that makes some alignment calculation and also accounts for the header length. We thus have to pass the size of the payload only, and the result will be the aligned size of the whole netlink message. Our payload is a struct rtgenmsg structure, that is used to specify which address family we are interested in.
  • the message flags : NLM_F_REQUEST because we are sending a request (i.e. requiring an answer) and NLM_F_DUMP.
  • the message sequence number : not very important here because we will only send one message. Note that this number must be monotonically increasing.
  • the message pid : this is our the pid of the sending process, which as we said earlier, is related to the netlink address.
We also fill the rtgen_family field of our payload structure to make sure that we receive information about ALL interfaces, not only IP-related ones. Then we prepare the regular sendmsg stuff using struct msghdr and struct iovec friends.

The message is then complete, and is sent through a sendmsg call. And now, let's get ready for the answer.

int end = 0;
char reply[IFLIST_REPLY_BUFFER]; /* a large buffer */
(...)
  while (!end)
    {
      int len;
      struct nlmsghdr *msg_ptr;    /* pointer to current part */
      struct msghdr rtnl_reply;    /* generic msghdr structure */
      struct iovec io_reply;

      memset(&io_reply, 0, sizeof(io_reply));
      memset(&rtnl_reply, 0, sizeof(rtnl_reply));

      io.iov_base = reply;
      io.iov_len = IFLIST_REPLY_BUFFER;
      rtnl_reply.msg_iov = &io;
      rtnl_reply.msg_iovlen = 1;
      rtnl_reply.msg_name = &kernel;
      rtnl_reply.msg_namelen = sizeof(kernel);

      len = recvmsg(fd, &rtnl_reply, 0); /* read lots of data */

This is the first part of the loop that contains most of the initialization logic. It is relying on a end flag that will be updated upon receiving the message that defines an end of answer. Then recvmsg structures are prepared to read a very large message. This netlink message itself can have several message in it's payload, this is why we are going to need a nested loop :

  if (len)
    {
      for (msg_ptr = (struct nlmsghdr *) reply; NLMSG_OK(msg_ptr, len); msg_ptr = NLMSG_NEXT(msg_ptr, len))
        {
          switch(msg_ptr->nlmsg_type)
          {
        case 3:        /* this is the NLMSG_DONE end msg */
          end++;
          break;
        case 16:    /* this is the RTM_NEWLINK msg */
          rtnl_print_link(msg_ptr);
          break;
        default:    /* for education only,
                   should not happen here */
          printf("message type %d, length %d\n", msg_ptr->nlmsg_type, msg_ptr->nlmsg_len);
          break;
          }
        }
    }

Here we use a few of those NLMSG macros, to walk the big netlink message. Basically, NLMSG_OK checks that the message specified by the parameter pointer is indeed a valid netlink message, while NLMSG_NEXT steps over a single iteration, moving from the parameter pointer current message to the next message in the netlink payload, while the global length is specified to avoid buffer overrun. Upon receiving NLMSG_DONE message, we just update our end of loop condition. And we use a separated function to print out new link information. Here it is :

void
rtnl_print_link(struct nlmsghdr *h)
{
  struct ifinfomsg *iface;
  struct rtattr *attribute;
  int len;

  iface = NLMSG_DATA(h);
  len = h->nlmsg_len - NLMSG_LENGTH(sizeof(*iface));

  for (attribute = IFLA_RTA(iface); RTA_OK(attribute, len); attribute = RTA_NEXT(attribute, len))
    {
      switch(attribute->rta_type)
      {
        case IFLA_IFNAME:
          printf("Interface %d : %s\n", iface->ifi_index, (char *) RTA_DATA(attribute));
          break;
        default:
          break;
      }
    }
}

Very simple function made of mostly a big loop. Here we can find some new macros that look a lot like the NLMSG ones. They have an equivalent behavior but are handling different data. NLMSG_ macros are handling the netlink message and allows to parse all parts of a multi-part query or request. Then, each part itself is made of several attributes (think of route information here, routes have lots of attributes, and so do links). So RTA_ stands for route attribute, and the effect of the RTA macros is identical to NLMSG ones. This allows us to loop over ALL attributes of a link.

Also, in order to catch the first of those RTAs, we need to know where they start. This depends on the kind of message we received, but as far as this function is concerned, the message type is known to be RTM_NEWLINK. So IFLA_RTA allows us to get a pointer to the first attribute in the RTM_NEWLINK message.

For the sake of this exemple, we will only consider the interface name, but there is much more information (MTU, hardware address, link type, flags, state, etc...). You can check out for more information, like the list of possible attributes.

And now, let's run our example :

Interface 1 : lo
Interface 2 : eth1
Interface 3 : wlan1
Interface 4 : pan0

Where we can see that they are all returned, including the down ones. Victory ! By the way, the number we are printing here is the interface index, that is very useful for subsequent actions, you'd better store it next to the interface name.

I hope this was an interesting (long) read. Again, feel free to use this code provided you give proper credits.

PS: for the lazy people who want to try it right away, here is a link to the full source.

8 comments:

Unknown said...

nl80211 is a cool interface. You might also consider reading /proc/net/dev as well.

Maverick said...

Hey Jean...I am writing module to access information of TAP interface,the information would be bridge related ,like STP port state and others.
I would like to know whether bridge related information can I get using RTM_GETLINK

werewolf said...

if we have eth0 and eth0:1 will there name have seperate index

Anonymous said...

Thank you Jean!

Vicky said...

How will we display only a particular interface by some matching criteria using NLM_F_MATCH?

Unknown said...

Thx for good post.

P.S.
Link to full project is dead.

Unknown said...

Thanks for your post. I have question, is there any sample program for collecting the survey data,?
NL80211_CMD_GET_SURVEY ??

Unknown said...

Thanks for the nice post.
Unfortunately, the link to the sources is down, could you update it?