Sunday, November 7, 2010

Getting/Setting the netmask in Linux/BSD

Recently I have been wrestling with a method for generating multiple aliases for one interface, as in the last post. Part of this was finding suitable ip-addresses to act as aliases. My first idea was to choose free ip-addresses on the local subnet, depending on how big it was, then pinging all possible addresses to find out which ones were already taken. This turned out much more complex than I had thought, unreliable and slow.

Then I had the idea of just expanding the interface's netmask up a level, ignoring all the addresses of the existing subnet without testing them, and generating alias ips that couldn't possibly conflict with any already on the network. To do that I needed to get and set the current netmask programmatically. I didn't find much on the setting (you won't find it in Steven's book, for example). So I wrote my own two routines get_netmask and set_netmask and put them into an example program that even works on BSD:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <string.h>

/**
 * @param skfd an open socket
 * @param intf the interface name, e.g. eth0
 * @return the netmask as a string
 */
char *get_netmask( int skfd, char *intf )
{
    struct ifreq ifr;
    ifr.ifr_addr.sa_family = AF_INET;
    strncpy( ifr.ifr_name, intf, IFNAMSIZ-1 );
    if ( ioctl(skfd,SIOCGIFNETMASK,&ifr) == -1 )
    {
        printf("could not read interface %s\n",intf);
        exit( 0 );
    }
    /* display result */
    return inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr);
}
/**
 * @param skfd an open socket
 * @param intf the interface name, e.g. eth0
 * @param newmask the new mask a a string e.g. "255.255.0.0"
 */
void set_netmask( int skfd, char *intf, char *newmask )
{
    struct ifreq ifr;
    unsigned int dst;
    struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
    memset(&ifr, 0, sizeof(ifr));
    sin->sin_family = AF_INET;
    if ( !inet_pton(AF_INET, newmask, &sin->sin_addr) )
    {
        printf("failed to convert netmask\n");
        exit( 0 );
    }
    strncpy( ifr.ifr_name, intf, IFNAMSIZ-1 );
    if ( ioctl(skfd,SIOCSIFNETMASK,&ifr) == -1 )
    {
        printf("could not read interface %s\n",intf);
        exit( 0 );
    }
}
/**
 * main entry routine
 * accepts 2 arguments: the interface name and the new netmask
 * e.g. ./intf eth0 255.255.0.0
 */
int main( int argc, char **argv )
{
    char *str;
    int skfd = socket( AF_INET, SOCK_DGRAM, 0);
    if ( skfd == -1 )
    {
        printf("Couldn't open socket\n");
        exit(0);
    }
    printf("netmask was: %s\n", get_netmask(skfd,argv[1]) );
    set_netmask( skfd, argv[1], argv[2] );
    printf("netmask is now: %s\n", get_netmask(skfd,argv[1]) );
    close( skfd );
    return 0;
}