add and remove IP addresses to an interface using ioctl or netlink

12,661

Solution 1

With some help from avahi project. Here is the code:

$ cat netlink-test.cc

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <asm/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <malloc.h>
#include <string.h>
#include <iostream>

using namespace std;

struct rtnl_handle
{
    int fd;
    struct sockaddr_nl local;
    struct sockaddr_nl peer;
    __u32 seq;
    __u32 dump;
};

typedef struct
{
    __u8 family;
    __u8 bytelen;
    __s16 bitlen;
    __u32 flags;
    __u32 data[8];
} inet_prefix;

/* This uses a non-standard parsing (ie not inet_aton, or inet_pton)
 * because of legacy choice to parse 10.8 as 10.8.0.0 not 10.0.0.8
 */
static int get_addr_ipv4(__u8 *ap, const char *cp)
{
    int i;

    for (i = 0; i < 4; i++) {
        unsigned long n;
        char *endp;

        n = strtoul(cp, &endp, 0);
        if (n > 255)
            return -1;      /* bogus network value */

        if (endp == cp) /* no digits */
            return -1;

        ap[i] = n;

        if (*endp == '\0')
            break;

        if (i == 3 || *endp != '.')
            return -1;      /* extra characters */
        cp = endp + 1;
    }

    return 1;
}

// This function is to open the netlink socket as the name suggests.
int netlink_open(struct rtnl_handle* rth)
{
    int addr_len;
    memset(rth, 0, sizeof(rth));

    // Creating the netlink socket of family NETLINK_ROUTE

    rth->fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (rth->fd < 0)
    {
        perror("cannot open netlink socket");
        return -1;
    }
    memset(&rth->local, 0, sizeof(rth->local));
    rth->local.nl_family = AF_NETLINK;
    rth->local.nl_groups = 0;

    // Binding the netlink socket
    if (bind(rth->fd, (struct sockaddr*)&rth->local, sizeof(rth->local)) < 0)
    {
        perror("cannot bind netlink socket");
        return -1;
    }
    addr_len = sizeof(rth->local);
    if (getsockname(rth->fd, (struct sockaddr*)&rth->local, (socklen_t*) &addr_len) < 0)
    {
        perror("cannot getsockname");
        return -1;
    }
    if (addr_len != sizeof(rth->local))
    {
        fprintf(stderr, "wrong address lenght %d\n", addr_len);
        return -1;
    }
    if (rth->local.nl_family != AF_NETLINK)
    {
        fprintf(stderr, "wrong address family %d\n", rth->local.nl_family);
        return -1;
    }
    rth->seq = time(NULL);
    return 0;
}

// This function does the actual reading and writing to the netlink socket
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n, pid_t peer,
        unsigned groups, struct nlmsghdr *answer)
{
    int status;
    struct nlmsghdr *h;
    struct sockaddr_nl nladdr;
    // Forming the iovector with the netlink packet.
    struct iovec iov = { (void*)n, n->nlmsg_len };
    char buf[8192];
    // Forming the message to be sent.
    struct msghdr msg = { (void*)&nladdr, sizeof(nladdr), &iov, 1, NULL, 0, 0 };
    // Filling up the details of the netlink socket to be contacted in the
    // kernel.
    memset(&nladdr, 0, sizeof(nladdr));
    nladdr.nl_family = AF_NETLINK;
    nladdr.nl_pid = peer;
    nladdr.nl_groups = groups;
    n->nlmsg_seq = ++rtnl->seq;
    if (answer == NULL)
        n->nlmsg_flags |= NLM_F_ACK;
    // Actual sending of the message, status contains success/failure
    status = sendmsg(rtnl->fd, &msg, 0);
    if (status < 0)
        return -1;
}




// This is the utility function for adding the parameters to the packet.
int addattr_l(struct nlmsghdr *n, int maxlen, int type, void *data, int alen)
{
    int len = RTA_LENGTH(alen);
    struct rtattr *rta;

    if (NLMSG_ALIGN(n->nlmsg_len) + len > maxlen)
        return -1;
    rta = (struct rtattr*)(((char*)n) + NLMSG_ALIGN(n->nlmsg_len));
    rta->rta_type = type;
    rta->rta_len = len;
    memcpy(RTA_DATA(rta), data, alen);
    n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + len;
    return 0;
}


int get_addr_1(inet_prefix *addr, const char *name, int family)
{
    memset(addr, 0, sizeof(*addr));

    if (strcmp(name, "default") == 0 ||
            strcmp(name, "all") == 0 ||
            strcmp(name, "any") == 0) {
        if (family == AF_DECnet)
            return -1;
        addr->family = family;
        addr->bytelen = (family == AF_INET6 ? 16 : 4);
        addr->bitlen = -1;
        return 0;
    }

    if (strchr(name, ':')) {
        addr->family = AF_INET6;
        if (family != AF_UNSPEC && family != AF_INET6)
            return -1;
        if (inet_pton(AF_INET6, name, addr->data) <= 0)
            return -1;
        addr->bytelen = 16;
        addr->bitlen = -1;
        return 0;
    }


    addr->family = AF_INET;
    if (family != AF_UNSPEC && family != AF_INET)
        return -1;

    if (get_addr_ipv4((__u8 *)addr->data, name) <= 0)
        return -1;

    addr->bytelen = 4;
    addr->bitlen = -1;
    return 0;
}

int get_prefix(inet_prefix *dst, char *arg, int family)
{
    int err;
    unsigned plen;

    memset(dst, 0, sizeof(*dst));

    if (strcmp(arg, "default") == 0 ||
            strcmp(arg, "any") == 0 ||
            strcmp(arg, "all") == 0) {
        if (family == AF_DECnet)
            return -1;
        dst->family = family;
        dst->bytelen = 0;
        dst->bitlen = 0;
        return 0;
    }

    err = get_addr_1(dst, arg, family);
    if (err == 0) {
        switch(dst->family) {
            case AF_INET6:
                dst->bitlen = 128;
                break;
            case AF_DECnet:
                dst->bitlen = 16;
                break;
            default:
            case AF_INET:
                dst->bitlen = 32;
        }
    }
    return err;
}


int add_IP_Address(char * IP, struct rtnl_handle * rth)
{

    inet_prefix lcl;
    // structure of the netlink packet. 
    struct {
        struct nlmsghdr     n;
        struct ifaddrmsg    ifa;
        char            buf[1024];
    } req;

    memset(&req, 0, sizeof(req));
    req.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
    req.n.nlmsg_type = RTM_NEWADDR;
    req.n.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST;


//  req.n.nlmsg_type = RTM_DELADDR;
//  req.n.nlmsg_flags = NLM_F_REQUEST;

    req.ifa.ifa_family = AF_INET ;
    req.ifa.ifa_prefixlen = 32 ;
    req.ifa.ifa_index = 1 ; // get the loopback index
    req.ifa.ifa_scope = 0 ;

    get_prefix(&lcl, IP, req.ifa.ifa_family);
    if (req.ifa.ifa_family == AF_UNSPEC)
        req.ifa.ifa_family = lcl.family;
    addattr_l(&req.n, sizeof(req), IFA_LOCAL, &lcl.data, lcl.bytelen);

    if (rtnl_talk(rth, &req.n, 0, 0, NULL) < 0)
        return -2;
}

int main(int argc, char **argv)
{
    struct rtnl_handle * rth;
    netlink_open(rth);
    char * ip = "1.2.3.4";
    return add_IP_Address(ip,rth);
}

For deleting, you only need to uncomment:

    req.n.nlmsg_type = RTM_DELADDR;
    req.n.nlmsg_flags = NLM_F_REQUEST;

and comment:

    req.n.nlmsg_type = RTM_NEWADDR;
    req.n.nlmsg_flags = NLM_F_CREATE | NLM_F_EXCL | NLM_F_REQUEST;

Solution 2

First of all, this response is only for linux. The networking/interface/routing configuration is not defined in any standard, so each OS implements his own way to configure the network stuff.

In Linux, the network configuration is performed via NETLINK sockets (not ioctl's, may be in other OS's I don't know), wich are an special family of sockets to communicate kernel and userland. To be more accurate, it's configured by NETLINK_ROUTE protocol over a NETLINK socket. In the strace that you posted you can view how it's created the NETLIK socket:

socket(PF_NETLINK, SOCK_RAW, 0)         = 3

And the

sendto(3, "\24\0\0\0\22\0\1\3\214;\367P\0\0\0\0\0\0\0\0", 20, 0, NULL, 0) = 20

I'm sure that it's a NETLINK_ROUTE request with a RTM_NEWADDR message. The rest of the strace could be interpretated as NETLINK_ROUTE protocol message comunication.

You can learn more about NETLINK_ROUTE here, and find examples here and Here, and as @alk has reccommended, reading ip source could be something interesting to know what is exactly happenning.

Share:
12,661
Amir
Author by

Amir

Updated on June 30, 2022

Comments

  • Amir
    Amir almost 2 years

    Is there anyway to add and remove IP addresses from an interface (like loopback) in C?

    I found ioctl and a few documents explaining how to do that (e.g. this link), however they are all for setting addresses not adding and removing?

    Following the suggestions I ran strace for adding a new loopback and here is the results:

    $ sudo strace ip addr add 1.2.3.4 dev lo
    execve("/sbin/ip", ["ip", "addr", "add", "1.2.3.4", "dev", "lo"], [/* 17 vars */]) = 0
    brk(0)                                  = 0x1bab000
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f221ed04000
    access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    fstat(3, {st_mode=S_IFREG|0644, st_size=109414, ...}) = 0
    mmap(NULL, 109414, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f221ece9000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/x86_64-linux-gnu/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
    read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\340\r\0\0\0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0644, st_size=14768, ...}) = 0
    mmap(NULL, 2109704, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f221e8e0000
    mprotect(0x7f221e8e2000, 2097152, PROT_NONE) = 0
    mmap(0x7f221eae2000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x2000) = 0x7f221eae2000
    close(3)                                = 0
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\200\30\2\0\0\0\0\0"..., 832) = 832
    fstat(3, {st_mode=S_IFREG|0755, st_size=1811128, ...}) = 0
    mmap(NULL, 3925208, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f221e521000
    mprotect(0x7f221e6d6000, 2093056, PROT_NONE) = 0
    mmap(0x7f221e8d5000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b4000) = 0x7f221e8d5000
    mmap(0x7f221e8db000, 17624, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f221e8db000
    close(3)                                = 0
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f221ece8000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f221ece7000
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f221ece6000
    arch_prctl(ARCH_SET_FS, 0x7f221ece7700) = 0
    mprotect(0x7f221e8d5000, 16384, PROT_READ) = 0
    mprotect(0x7f221eae2000, 4096, PROT_READ) = 0
    mprotect(0x638000, 4096, PROT_READ)     = 0
    mprotect(0x7f221ed06000, 4096, PROT_READ) = 0
    munmap(0x7f221ece9000, 109414)          = 0
    socket(PF_NETLINK, SOCK_RAW, 0)         = 3
    setsockopt(3, SOL_SOCKET, SO_SNDBUF, [32768], 4) = 0
    setsockopt(3, SOL_SOCKET, SO_RCVBUF, [1048576], 4) = 0
    bind(3, {sa_family=AF_NETLINK, pid=0, groups=00000000}, 12) = 0
    getsockname(3, {sa_family=AF_NETLINK, pid=6804, groups=00000000}, [12]) = 0
    sendto(3, "\24\0\0\0\22\0\1\3\214;\367P\0\0\0\0\0\0\0\0", 20, 0, NULL, 0) = 20
    recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\344\3\0\0\20\0\2\0\214;\367P\224\32\0\0\0\0\4\3\1\0\0\0I\0\1\0\0\0\0\0"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 2000
    brk(0)                                  = 0x1bab000
    brk(0x1bcc000)                          = 0x1bcc000
    recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"\24\0\0\0\3\0\2\0\214;\367P\224\32\0\0\0\0\0\0\1\0\0\0I\0\1\0\0\0\0\0"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 20
    sendmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"(\0\0\0\24\0\5\6\215;\367P\0\0\0\0\2 \0\0\1\0\0\0\10\0\2\0\1\2\3\4"..., 40}], msg_controllen=0, msg_flags=0}, 0) = 40
    recvmsg(3, {msg_name(12)={sa_family=AF_NETLINK, pid=0, groups=00000000}, msg_iov(1)=[{"$\0\0\0\2\0\0\0\215;\367P\224\32\0\0\0\0\0\0(\0\0\0\24\0\5\6\215;\367P"..., 16384}], msg_controllen=0, msg_flags=0}, 0) = 36
    exit_group(0)                           = ?