使用 ioctl 或 netlink 向接口添加和删除 IP 地址

Posted

技术标签:

【中文标题】使用 ioctl 或 netlink 向接口添加和删除 IP 地址【英文标题】:add and remove IP addresses to an interface using ioctl or netlink 【发布时间】:2013-01-16 22:25:16 【问题描述】:

是否可以在 C 中从接口(如环回)添加和删除 IP 地址?

我找到了 ioctl 和一些解释如何做到这一点的文档(例如 this link),但是它们都是用于设置地址而不是添加和删除?

按照我运行 strace 以添加新环回的建议,结果如下:

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

【问题讨论】:

你为什么不“strace ifconfig 相关参数”看看ifconfig 做了什么来添加它们。或者看看 ifconfig 的源代码,也许? 看来它根本没有为此使用ioctl...hmm 您可能想在这里查看ip 的来源:git.kernel.org/?p=linux/kernel/git/shemminger/…,也可以在这里阅读:linuxfoundation.org/collaborate/workgroups/networking/netlink 更奇怪的是,如果您使用 ioctl 为IPv4 设置地址(替换旧地址),但为IPv6 添加地址(让旧地址到位)。跨度> @alk 您的链接现在已损坏。 【参考方案1】:

在 avahi 项目的帮助下。 这是代码:

$ 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);

删除只需要取消注释即可:

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

和评论:

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

【讨论】:

【参考方案2】:

首先,此响应仅适用于 linux。网络/接口/路由配置没有在任何标准中定义,因此每个操作系统都实现了自己的方式来配置网络。

在 Linux 中,网络配置是通过 NETLINK 套接字(不是 ioctl,可能在我不知道的其他操作系统中)执行的,这是一个特殊的套接字系列,用于通信内核和用户空间。更准确地说,它是由 NETLINK_ROUTE 协议通过 NETLINK 套接字配置的。在您发布的 strace 中,您可以查看它是如何创建 NETLIK 套接字的:

socket(PF_NETLINK, SOCK_RAW, 0)         = 3

还有

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

我确定这是一个带有 RTM_NEWADDR 消息的 NETLINK_ROUTE 请求。 strace 的其余部分可以解释为 NETLINK_ROUTE 协议消息通信。

您可以了解有关 NETLINK_ROUTE here 的更多信息,并查找示例 here 和 Here,正如 @alk 所建议的那样,阅读 ip 源代码可能会很有趣,以了解究竟发生了什么。

【讨论】:

以上是关于使用 ioctl 或 netlink 向接口添加和删除 IP 地址的主要内容,如果未能解决你的问题,请参考以下文章

linux 内核中Netlink

linux下netlink的使用简介

Linux内核通信之netlink机制

使用 Python 向网络驱动程序发送 IOCTL 调用

如何在 Free Pascal 上为接口列表或其他 ioctl 调用 ioctl?

Linux用户与内核空间交互—ioctl