Search code examples
cnetwork-programmingioctlifconfig

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


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)                           = ?

Solution

  • 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;