Manipulating the network environment using RTNETLINK

How to use RTNETLINK to develop applications that control networking. NETLINK is a facility in the Linux operating system for user-space applications to communicate with the kernel. NETLINK is an extension of the standard socket implementation. Using NETLINK, an application can send/receive information to/from different kernel features, such as networking, to check current status and control them. In this article, I describe how a programmer can use the networking environment manipulation capability of NETLINK known as RTNETLINK. I discuss some areas of use of RTNETLINK, the relevant socket operations, the functionality, how RTNETLINK messages are formed and finally, provide a set of sample code that uses RTNETLINK. RTNETLINK for the IP version 4 environment is referred to as NETLINK_ROUTE, and for the IP version 6 environment, it is referred to as NETLINK_ROUTE6. The explanations given here are applicable for both IP versions 4 and 6. Developers of network layer protocol handlers can use RTNETLINK to modify and monitor different components of networking, such as the routing table and network interfaces. There are many existing and upcoming protocol standards at the Internet Engineering Task Force (IETF) that can be implemented in user space. These implementations will require manipulating the routing and knowing what is being modified by other processes. Some of these protocol categories are as follows: Dynamic routing protocols: protocols of this category, including the Routing Information Protocol (RIP), Open Shortest Path First (OSPF) and Exterior Gateway Protocol (EGP) actively manage the routing environment of a host while communicating with other equally capable hosts or routers in the network or Internet. Mobility protocols: hosts that are mobile and connect to different networks at different times use protocols such as Mobile IP (MIP), Session Initiation Protocol (SIP) and Network Mobility (NEMO) to manage routing to maintain connectivity and continuity of communications. Ad hoc networking protocols: hosts that are mobile and located in places where there is no networking infrastructure, such as routers and WLAN access points, require peer-to-peer communications with differently configured hosts. Mobile computers of rescue workers in an earthquake-struck area or other such emergencies can use ad hoc networking protocols. These protocols, such as the Ad hoc On-demand Distance Vector (AODV) and Optimized Link State Routing (OLSR), require managing the routing to find and communicate with other hosts using neighboring hosts as routers and gateways. It helps reduce the complexity of the kernel code if you implement these protocols in user space. Further, it simplifies the development and testing of these protocols because of the availability of many user-space development tools. Problems, such as kernel crashes, that are likely with kernel-based code when testing or when used by end users will not occur in a user-space protocol handler. Manipulating the Networking Environment Using RTNETLINK http://0-delivery.acm. org.innopac. lib.ryerson. ca/10.1145/1140000/11341. 2 of 12 6/18/2006 9:15 PM Socket Operations The socket implementation of Linux allows two end points to communicate. The socket API provides a standard set of functions and data structures. With RTNETLINK, the two end points in communication are user space and kernel space. The following sequence of socket calls have to be made when manipulating the networking environment through RTNETLINK: Open socket. 1. Bind socket to local address (using process ID). 2. Send message to the other end point. 3. Receive message from the other end point. 4. Close socket. 5. The socket() function opens an unattached end point to communicate with the kernel. The function prototype of this call is as follows: int socket(int domain, int type, int protocol); The domain refers to what type of socket is being used. For RTNETLINK, we use AF_NETLINK (PF_NETLINK). type refers to the type of protocol used when communicating. This can be raw (SOCK_RAW) or datagram (SOCK_DGRAM). This is not relevant for RTNETLINK sockets and either can be used. protocol refers to the exact NETLINK capability that we use; in our case, it is NETLINK_ROUTE. This function returns an integer with a positive number called the socket descriptor, if the socket opening was successful. This descriptor will be used in all the future RTNETLINK calls until the socket is closed. If there was a failure, a negative value is returned, and the system error variable errno included in errno.h is set to the appropriate error code. The following is an example of a call to open an RTNETLINK socket: int fd; . fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); Once the socket is opened, it has to be bound to a local address. The user application can use a unique 32-bit ID to identify the local address. The function prototype of bind is as follows: int bind(int fd, struct sockaddr *my_addr, socklen_t addrlen); To bind, the caller must provide a local address using the sockaddr_nl structure. This structure in the linux/netlink.h #include file has the following format: struct sockaddr_nl { sa_family_t nl_family; // AF_NETLINK unsigned short nl_pad; // zero Manipulating the Networking Environment Using RTNETLINK http://0-delivery.acm. org.innopac. lib.ryerson. ca/10.1145/1140000/11341. 3 of 12 6/18/2006 9:15 PM __u32 nl_pid; // process pid __u32 nl_groups; // multicast grps mask }; The nl_pid must contain a unique ID, which can be created using the return of the getpid() function. This function returns the process ID of the current user process that opened the RTNETLINK socket. But, if our process consists of multiple threads with each thread opening different RTNETLINK sockets, a modified process ID can be used. Once this structure is filled, the binding can be done. The bind function returns zero if the operation succeeded. A negative number is returned in the case of failure, and the system error variable is set. The following is an example of calling bind: struct sockaddr_nl la; . bzero(&la, sizeof(la)); la. nl_family = AF_NETLINK; la. nl_pad = 0; la. nl_pid = getpid(); la. nl_groups = 0; rtn = bind(fd, (struct sockaddr*) &la, sizeof(la)); If the operation you require is multicast-based, you must set nl_groups to join the multicast group associated with the required RTNETLINK operation. For example, if you want to be notified of the changes to the routing table by other processes, you must OR (|) the RTMGRP_IPV4_ROUTE and RTMGRP_NOTIFY. Sending routing RTNETLINK messages to the kernel is done through the use of the standard sendmsg() function of the socket interface. The following is the prototype of this function: ssize_t sendmsg(int fd, const struct msghdr *msg, int flags); msg is a pointer to a msghdr structure. The following is the format of this structure: struct msghdr { void *msg_name; //Address to send to socklen_t msg_namelen; //Length of address data struct iovec *msg_iov; //Vector of data to send size_t msg_iovlen; //Number of iovec entries void *msg_control; //Ancillary data size_t msg_controllen; //Ancillary data buf len int msg_flags; //Flags on received msg }; The msg_name is a pointer to a variable of the type struct sockaddr_nl. This is the destination address of the sendmsg() function. Because this message is directed to the kernel, all variables of sockaddr_nl will be initialized to zero, except the nl_family member variable. The field msg_namelen should contain the size of a struct sockaddr_nl. Manipulating the Networking Environment Using RTNETLINK http://0-delivery.acm. org.innopac. lib.ryerson. ca/10.1145/1140000/11341. 4 of 12 6/18/2006 9:15 PM msg_iov should contain a pointer to a struct iovec, which is filled with the RTNETLINK message relevant to the request being made. The caller is allowed to place multiple RTNETLINK requests, if required. msg_iovlen points to the number of struct iovec structures that were placed in msg_iov. The rest of the variables are initialized to zero. To receive RTNETLINK messages, the recv() function is used. Here is the prototype of this function: ssize_t recv(int fd, void *buf, size_t len, int flags); The second and third variables are a pointer to a buffer to place the bytes read and the length of this buffer, respectively. For RTNETLINK, the buffer will contain a set of RTNETLINK messages that have to be read one after the other using a set of macros provided in the netlink.h and rtnetlink.h #include files. flags is a set of flags to indicate how the receive should be performed. For RTNETLINK, this simply can be initialized to zero. Once the socket communications are complete, the socket has to be closed using the close() function. Here's the prototype of this function: