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).