Updating IP routes programmatically

Published 12-10-2012 00:00:00

ioctl(2) is a special system call used when no other system call can handle a specific I/O operation. For exemple, the generic write(2) syscall is used for writing to a specified file descriptor but there is neither syscall to open the CD-tray nor syscall to manage routing tables: ioctl is here as a fit-all syscall.

From the man page:

NAME
       ioctl - control device

SYNOPSIS
       #include <sys/ioctl.h>

       int ioctl(int d, int request, void *args);

DESCRIPTION
       The  ioctl() function manipulates the underlying device parameters of special files.

fd: The special file descriptor of an IP datagram socket

request: Identifier of the request to execute (see sys/ioctl.h)

args: Request-specific pointer arguments

Add and remove route C example

We are going to use the SIOCADDRT and SIOCDELRT requests.

First, create a unix datagram socket:

int fd;
fd = socket(AF_INET, SOCK_DGRAM, 0);

Then we need to feed the rtentry structure (from /usr/include/net/route.h).

struct rtentry
  {
    unsigned long int rt_pad1;
    struct sockaddr rt_dst;             /* Target address.  */
    struct sockaddr rt_gateway;         /* Gateway addr (RTF_GATEWAY).  */
    struct sockaddr rt_genmask;         /* Target network mask (IP).  */
    unsigned short int rt_flags;
    short int rt_pad2;
    unsigned long int rt_pad3;
    unsigned char rt_tos;
    unsigned char rt_class;
#if __WORDSIZE == 64
    short int rt_pad4[3];
#else
    short int rt_pad4;
#endif
    short int rt_metric;                /* +1 for binary compatibility!  */
    char *rt_dev;                       /* Forcing the device at add.  */
    unsigned long int rt_mtu;           /* Per route MTU/Window.  */
    unsigned long int rt_window;        /* Window clamping.  */
    unsigned short int rt_irtt;         /* Initial RTT.  */
  };

Not all fields are mandatory:

#define RTDST "192.168.10.0"
#define RTGATEWAY "192.168.2.246"
#define RTGENMASK "255.255.255.0"

struct rtentry route;
struct sockaddr_in *addr;

memset(&route, 0, sizeof(route));

addr = (struct sockaddr_in*)&route.rt_dst;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr(RTDST);

addr = (struct sockaddr_in*)&route.rt_gateway;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr(RTGATEWAY);

addr = (struct sockaddr_in*)&route.rt_genmask;
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr(RTGENMASK);

route.rt_flags = RTF_UP | RTF_GATEWAY;

And the final call:

/* Add the route */
ioctl(fd, SIOCADDRT, route);

/* Delete the route */
ioctl(fd, SIOCDELRT, route);

Do not forget to run this program as root to have the right privileges.

Updating routing tables in erlang

Erlang does not provide raw access to the ioctl system call.

procket is an NIF used to extend the Erlang runtime to make the various system
calls needed for low level socket manipulation using socket/3, ioctl/3,
setsockopt/4, ...

Thanks to the procket library the translation to Erlang code is straightforward. The only difficulty is to translate the rtentry structure to an erlang binary term.

Add and remove Erlang example

Fist define some constants taken from c headers files:

-define(AF_INET, 2).

-define(SIOCADDRT, 16#890B). %% #define SIOCADDRT 0x890B /* add routing table entry    */
-define(SIOCDELRT, 16#890C). %% #define SIOCDELRT 0x890C /* delete routing table entry */

-define(RTF_UP,      16#0001). %% #define RTF_UP     0x0001  /* Route usable.  */
-define(RTF_GATEWAY, 16#0002). %% #define RTF_GATEWAY 0x0002 /* Destination is a gateway. */

Then create the low level socket:

{ok, Socket} = procket:socket(inet, dgram, 0).

The tricky part is to create the right erlang binary term from the rtentry structure. Look at the C defined structure for documentation.

For example the rt_dst member is of type struct sockaddr, its size is 16 bytes: family is a 16 bits integer, port is an unused 16 bits integer, address is 4 bytes long and the padding is 8 bytes long.

So, for each structure member, one need to know its exact size in memory.

-define(RTDST,     192:8, 168:8,  10:8,   0:8).
-define(RTGATEWAY, 192:8, 168:8,   2:8, 246:8).
-define(RTGENMASK, 255:8, 255:8, 255:8,   0:8).

RTEntry = <<0:?UINT64,

            ?AF_INET:?UINT16,
            0:?UINT16, % Port is not used
            ?RTDST,
            0:64,

            ?AF_INET:?UINT16,
            0:?UINT16, % Port is not used
            ?RTGATEWAY,
            0:64,

            ?AF_INET:?UINT16,
            0:?UINT16, % Port is not used
            ?RTGENMASK,
            0:64,

            (?RTF_UP bor ?RTF_GATEWAY):?UINT16,

            0:16, 0:64, 0:8, 0:8, 0:16, 0:16, 0:16, 0:16, 0:64, 0:64, 0:64, 0:16
          >>.

And the final calls:

{ok, Arg, _} = procket:alloc([RTEntry]).

procket:ioctl(Socket, ?SIOCADDRT, Arg).
procket:ioctl(Socket, ?SIOCDELRT, Arg).