Linux网络接口操作之ioctl-1_接口参数

Linux 提供了一系列网络接口操作相关的命令集,其中,一些传统的工具,如 net-tools 软件包中的 ifconfig(8),arp(8),route(8) 等都是通过 ioctl(2) 系统调用实现。

本篇介绍使用 ioctl(2) 进行网络接口参数的获取与设置。

1 函数介绍

头文件:

#include <sys/ioctl.h>

函数原型:

int ioctl(int fd, int request, ...);

函数参数:

  • 第一个参数 fd 指定一个由 open(2)/socket(2) 创建的文件描述符;
  • 第二个参数 request 指定操作的类型,即对该文件描述符执行何种操作;
  • 第三个参数为一块内存区域,通常依赖于 request 指定的操作类型。

2 接口参数与操作的相关定义

内核版本:2.6.32.5

ioctl(2) 使用 struct ifreq 与/或 struct ifconf 结构执行网络接口相关的操作,这两个结构的地址作为 ioctl(2) 的第三个参数。如下:

/* include/linux/if.h */

#define    IFNAMSIZ    16
#define    IFALIASZ    256

struct ifreq 
{
#define IFHWADDRLEN    6
    union {
        char      ifrn_name[IFNAMSIZ];
    } ifr_ifrn;
    
    union {
        struct    sockaddr ifru_addr;
        struct    sockaddr ifru_dstaddr;
        struct    sockaddr ifru_broadaddr;
        struct    sockaddr ifru_netmask;
        struct    sockaddr ifru_hwaddr;
        short     ifru_flags;
        int       ifru_ivalue;
        int       ifru_mtu;
        struct    ifmap ifru_map;
        char      ifru_slave[IFNAMSIZ];
        char      ifru_newname[IFNAMSIZ];
        void __user *    ifru_data;
        struct    if_settings ifru_settings;
    } ifr_ifru;
};

#define ifr_name		ifr_ifrn.ifrn_name        	/* interface name */
#define ifr_hwaddr		ifr_ifru.ifru_hwaddr    	/* MAC address */
#define ifr_addr    	ifr_ifru.ifru_addr        	/* address */
#define ifr_dstaddr		ifr_ifru.ifru_dstaddr    	/* other end of p-p lnk */
#define ifr_broadaddr 	ifr_ifru.ifru_broadaddr    	/* broadcast address */
#define ifr_netmask   	ifr_ifru.ifru_netmask    	/* interface net mask */
#define ifr_flags    	ifr_ifru.ifru_flags        	/* flags */
#define ifr_metric    	ifr_ifru.ifru_ivalue    	/* metric */
#define ifr_mtu        	ifr_ifru.ifru_mtu        	/* mtu */
#define ifr_map        	ifr_ifru.ifru_map        	/* device map */
#define ifr_slave    	ifr_ifru.ifru_slave        	/* slave device */
#define ifr_data    	ifr_ifru.ifru_data        	/* for use by interface */
#define ifr_ifindex    	ifr_ifru.ifru_ivalue    	/* interface index */
#define ifr_bandwidth	ifr_ifru.ifru_ivalue    	/* link bandwidth */
#define ifr_qlen        ifr_ifru.ifru_ivalue    	/* Queue length */
#define ifr_newname    	ifr_ifru.ifru_newname    	/* New name */
#define ifr_settings    ifr_ifru.ifru_settings    	/* Device/proto settings */

struct ifconf 
{
    int    ifc_len;
    union {
        char __user *ifcu_buf;
        struct ifreq __user *ifcu_req;
    } ifc_ifcu;
};

#define    ifc_buf    ifc_ifcu.ifcu_buf        /* buffer address        */
#define    ifc_req    ifc_ifcu.ifcu_req        /* array of structures    */

操作类型,ioctl(2) 的第二个参数,如下:

/* include/linux/sockios.h */

/* Socket configuration controls. */
#define SIOCGIFNAME        	0x8910        /* get iface name        */
#define SIOCSIFLINK        	0x8911        /* set iface channel    */
#define SIOCGIFCONF        	0x8912        /* get iface list        */
#define SIOCGIFFLAGS    	0x8913        /* get flags            */
#define SIOCSIFFLAGS    	0x8914        /* set flags            */
#define SIOCGIFADDR        	0x8915        /* get PA address        */
#define SIOCSIFADDR        	0x8916        /* set PA address        */
#define SIOCGIFDSTADDR    	0x8917        /* get remote PA address    */
#define SIOCSIFDSTADDR    	0x8918        /* set remote PA address    */
#define SIOCGIFBRDADDR    	0x8919        /* get broadcast PA address    */
#define SIOCSIFBRDADDR    	0x891a        /* set broadcast PA address    */
#define SIOCGIFNETMASK    	0x891b        /* get network PA mask        */
#define SIOCSIFNETMASK    	0x891c        /* set network PA mask        */
#define SIOCGIFMETRIC    	0x891d        /* get metric            */
#define SIOCSIFMETRIC    	0x891e        /* set metric            */
#define SIOCGIFMEM        	0x891f        /* get memory address (BSD)    */
#define SIOCSIFMEM        	0x8920        /* set memory address (BSD)    */
#define SIOCGIFMTU        	0x8921        /* get MTU size        */
#define SIOCSIFMTU        	0x8922        /* set MTU size        */
#define SIOCSIFNAME        	0x8923        /* set interface name */
#define SIOCSIFHWADDR    	0x8924        /* set hardware address */
#define SIOCGIFENCAP    	0x8925        /* get/set encapsulations */
#define SIOCSIFENCAP    	0x8926        
#define SIOCGIFHWADDR    	0x8927        /* Get hardware address        */
#define SIOCGIFSLAVE    	0x8929        /* Driver slaving support    */
#define SIOCSIFSLAVE    	0x8930
#define SIOCADDMULTI    	0x8931        /* Multicast address lists    */
#define SIOCDELMULTI    	0x8932
#define SIOCGIFINDEX    	0x8933        /* name -> if_index mapping    */
#define SIOGIFINDEX    		SIOCGIFINDEX  /* misprint compatibility :-)    */
#define SIOCSIFPFLAGS    	0x8934        /* set/get extended flags set    */
#define SIOCGIFPFLAGS    	0x8935
#define SIOCDIFADDR        	0x8936        /* delete PA address        */
#define SIOCSIFHWBROADCAST	0x8937    	  /* set hardware broadcast addr    */
#define SIOCGIFCOUNT    	0x8938        /* get number of devices */
...
#define SIOCETHTOOL     	0x8946    	  /* Ethtool interface     */

3 一般步骤

通过 ioctl(2) 执行网络接口参数的获取/设置的一般步骤为:

  1. 通过 socket(2) 创建 IP 套接字;由于 ioctl(2) 此时是与内核通信,因此对套接字的通信域与类型没有强制要求,通信域可以为 AF_INET/AF_LOCAL,类型可以为 SOCK_DGRAM/SOCK_STREAM/SOCK_RAW 等;
  2. 初始化 struct ifconf 与/或 struct ifreq 结构;
  3. 对套接字描述符调用 ioctl(2),执行相应类型的 SIO 操作;
  4. 获取返回至 truct ifconf 与/或 struct ifreq 结构中的相关信息。

4 示例程序

本地网络接口信息:eth0 网线已连接且已配置 IPv4 地址,eth1 网线未连接且未配置 IPv4 地址:

# ip l
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:ed:9d:28 brd ff:ff:ff:ff:ff:ff
3: eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000
    link/ether 00:0c:29:ed:9d:32 brd ff:ff:ff:ff:ff:ff
    
# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:ed:9d:28 brd ff:ff:ff:ff:ff:ff
    inet 192.168.56.139/24 brd 192.168.56.255 scope global eth0
    inet6 fe80::20c:29ff:feed:9d28/64 scope link 
       valid_lft forever preferred_lft forever
3: eth1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc pfifo_fast state DOWN qlen 1000
    link/ether 00:0c:29:ed:9d:32 brd ff:ff:ff:ff:ff:ff

4.1 通过 SIOCGIFCONF 操作获取系统中所有的网络接口

	/* list_network_interfaces_ioctl.c */
	
	#include <stdio.h>
	#include <stdlib.h>
	#include <sys/socket.h>
	#include <sys/types.h>
	#include <arpa/inet.h>
	#include <sys/ioctl.h>
	#include <net/if.h>
	#include <string.h>
	
	#define BUFSIZE 1024
	
	int main(int argc, char *argv[])
	{
	    int sfd, if_count, i;
	    struct ifconf ifc;
	    struct ifreq ifr[10];
	    char ipaddr[INET_ADDRSTRLEN] = {'\0'};
	
	    memset(&ifc, 0, sizeof(struct ifconf));
	
	    sfd = socket(AF_INET, SOCK_DGRAM, 0);
	
	    ifc.ifc_len = 10 * sizeof(struct ifreq);
	    ifc.ifc_buf = (char *)ifr;
	
	    /* SIOCGIFCONF is IP specific. see netdevice(7) */
	    ioctl(sfd, SIOCGIFCONF, (char *)&ifc);
	
	    if_count = ifc.ifc_len / (sizeof(struct ifreq));
	    for (i = 0; i < if_count; i++) {
	        printf("Interface %s : ", ifr[i].ifr_name);    
	        inet_ntop(AF_INET, &(((struct sockaddr_in *)&(ifr[i].ifr_addr))->sin_addr), ipaddr, INET_ADDRSTRLEN);
	        printf("%s\n", ipaddr);
	    }
	    
	    close(sfd);
	
	    exit(EXIT_SUCCESS);
	}

编译并运行:

# gcc list_network_interfaces_ioctl.c -g -o list_network_interfaces_ioctl

# ./list_network_interfaces_ioctl
Interface lo : 127.0.0.1
Interface eth0 : 192.168.56.139

4.2 通过 SIOCGIFADDR 操作获取指定网络接口的 IPv4 地址

/* get_interface_ip_address_ioctl.c */

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

static char *get_ipaddr(const char *);

int main(int argc, char *argv[])
{

    if (argc != 2) {
        fprintf(stderr, "Usage: %s [network interface name]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char ifname[IFNAMSIZ] = {'\0'};
    strncpy(ifname, argv[1], IFNAMSIZ-1);

    char *ip = get_ipaddr(ifname);

    printf("Interface %s : %s\n", ifname, ip);
    
    return 0;
}

static char *get_ipaddr(const char *dev)
{
    int sfd, saved_errno, ret;
    struct ifreq ifr;
    char *ipaddr;

    ipaddr = (char *)malloc(INET_ADDRSTRLEN);
    memset(&ifr, 0, sizeof(ifr));
    ifr.ifr_addr.sa_family = AF_INET;
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    sfd = socket(AF_INET, SOCK_DGRAM, 0);

    errno = saved_errno;
    ret = ioctl(sfd, SIOCGIFADDR, &ifr);
    if (ret == -1) {
        if (errno == 19) {
            fprintf(stderr, "Interface %s : No such device.\n", dev);
            exit(EXIT_FAILURE);
        }
        if (errno == 99) {
            fprintf(stderr, "Interface %s : No IPv4 address assigned.\n", dev);
            exit(EXIT_FAILURE);
        }
    }
    saved_errno = errno;

    inet_ntop(AF_INET, &(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr), ipaddr, INET_ADDRSTRLEN);
    
    close(sfd);
    return ipaddr;
}

编译并运行:

# gcc get_interface_ip_address_ioctl.c -g -o get_interface_ip_address_ioctl
#
# ./get_interface_ip_address_ioctl eth0
Interface eth0 : 192.168.56.139
#
# ./get_interface_ip_address_ioctl eth1
Interface eth1 : No IPv4 address assigned.
#
# ./get_interface_ip_address_ioctl eth2
Interface eth2 : No such device.

SIOCGIFADDR 操作使用 struct ifreq 中的 ifr_ifru.ifru_addr 字段;ifr_ifrn.ifrn_name 指定为网络接口名称并调用 ioctl(SIOCGIFADDR),返回后将 ifr_ifru.ifru_addr 转换为 IPv4 套接字地址结构,IPv4 地址保存在该结构中的 sin_addr 字段中。

SIOCGIFCONF 与 SIOCGIFADDR 属于 IPv4 特定的操作,对于未配置 IPv4 地址的网络接口,ioctl(SIOCGIFCONF) 返回时不会分配 struct ifreq 结构,因而不会返回该接口的名称,而 ioctl(SIOCGIFADDR) 将以 errno 值99(Cannot assign requested address)而调用失败;若指定了系统中不存在的网络接口,则 errno 的值为19(No such device)。

SIOCGIFCONF 与 SIOCGIFADDR 无法获取网络接口的 IPv6 地址,ioctl 的内核源码中通过读取 /proc/net/if_inet6 获取。

4.3 通过 SIOCGIFHWADDR 操作获取指定网络接口的 MAC 地址

/* get_interface_mac_address_ioctl.c */

#include <stdio.h>
#include <stdlib.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <netinet/if_ether.h>
#include <net/if_arp.h>

static unsigned char *get_if_mac(const char *);

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s [network interface name]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char ifname[IFNAMSIZ] = {'\0'};
    strncpy(ifname, argv[1], IFNAMSIZ-1);

    unsigned char *mac = get_if_mac(ifname);
    
    printf("Interface %s : %.2x:%.2x:%.2x:%.2x:%.2x:%.2x\n",
    ifname, *mac, *(mac+1), *(mac+2), *(mac+3), *(mac+4), *(mac+5));

    return 0;
}

static unsigned char *get_if_mac(const char *dev)
{
    int sfd, ret, saved_errno, i;
    unsigned char *mac_addr;
    struct ifreq ifr;

    mac_addr = (unsigned char *)malloc(ETH_ALEN);

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    saved_errno = errno;
    ret = ioctl(sfd, SIOCGIFHWADDR, &ifr);
    if (ret == -1 && errno == 19) {
        fprintf(stderr, "Interface %s : No such device.\n", dev);
        exit(EXIT_FAILURE);
    }
    errno = saved_errno;

    if (ifr.ifr_addr.sa_family == ARPHRD_LOOPBACK) {
        printf("Interface %s : A Loopback device.\n", dev);
        printf("MAC address is always 00:00:00:00:00:00\n");
        exit(EXIT_SUCCESS);
    }

    if (ifr.ifr_addr.sa_family != ARPHRD_ETHER) {
        fprintf(stderr, "Interface %s : Not an Ethernet device.\n", dev);
        exit(EXIT_FAILURE);
    }

    memcpy(mac_addr, ifr.ifr_hwaddr.sa_data, ETH_ALEN);

    return (unsigned char *)mac_addr;
}

编译并运行:

# gcc get_interface_mac_address_ioctl.c -g -o get_interface_mac_address_ioctl
#
# ./get_interface_mac_address_ioctl lo
Interface lo : A Loopback device.
MAC address is always 00:00:00:00:00:00
#
# ./get_interface_mac_address_ioctl eth0
Interface eth0 : 00:0c:29:ed:9d:28
#
# ./get_interface_mac_address_ioctl eth1
Interface eth1 : 00:0c:29:ed:9d:32
#
# ./get_interface_mac_address_ioctl eth2
Interface eth2 : No such device.

SIOCGIFHWADDR 操作使用 struct ifreq 中的 ifr_ifru.ifru_hwaddr 字段,在 ifr_ifrn.ifrn_name 中填充指定的网络接口名称后,该接口的 mac 地址按顺序返回到 ifr_ifru.ifru_hwaddr.sa_data 数组的前6个字节中。

4.4 通过 SIOCGIFFLAGS 操作获取指定网络接口的标志

/* get_interface_flags_ioctl.c */

#include <stdio.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <netinet/if_ether.h>
#include <net/if_arp.h>

static short get_if_flags(int, char *);

int main(int argc, char *argv[])
{
    if (argc != 2) {
        fprintf(stderr, "Usage: %s [network interface name]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sfd;
    short flags;
    char ifname[IFNAMSIZ] = {'\0'};
    strncpy(ifname, argv[1], IFNAMSIZ-1);

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    flags = get_if_flags(sfd, ifname);

    printf("Interface %s : ", ifname);
    if (flags & IFF_UP)
        printf("UP ");

    if (flags & IFF_RUNNING)
        printf("RUNNING ");

    if (flags & IFF_LOOPBACK)
        printf("LOOPBACK ");

    if (flags & IFF_BROADCAST)
        printf("BROADCAST ");

    if (flags & IFF_MULTICAST)
        printf("MULTICAST ");

    if (flags & IFF_PROMISC)
        printf("PROMISC");

#ifndef IFF_LOWER_UP
#define IFF_LOWER_UP 0x10000
    if (flags & IFF_LOWER_UP)
        printf("LOWER_UP");
#endif

    printf("\n");

    close(sfd);
    exit(EXIT_SUCCESS);
}

static short get_if_flags(int s, char *dev)
{
    int saved_errno, ret;
    short if_flags;
    struct ifreq ifr;

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    saved_errno = errno;
    ret = ioctl(s, SIOCGIFFLAGS, &ifr);
    if (ret == -1 && errno == 19) {
        fprintf(stderr, "Interface %s : No such device.\n", dev);
        exit(EXIT_FAILURE);
    }
    errno = saved_errno;
    if_flags = ifr.ifr_flags;

    return if_flags;
}

编译并运行:

# gcc get_interface_flags_ioctl.c -g -o get_interface_flags_ioctl
#
# ./get_interface_flags_ioctl lo
Interface lo : UP RUNNING LOOPBACK
#
# ./get_interface_flags_ioctl eth0
Interface eth0 : UP RUNNING BROADCAST MULTICAST
#
# ./get_interface_flags_ioctl eth1
Interface eth1 : UP BROADCAST MULTICAST
#
# ./get_interface_flags_ioctl eth2
Interface eth2 : No such device.

ifr_ifrn.ifrn_name 指定为网络接口名称后,ioctl(SIOCGIFFLAGS) 调用将标志返回到 ifr_ifru.ifru_flags 字段

  • IFF_RUNNING 表示该接口已被激活,且可以正常传输数据;
  • IFF_UP 表示 giant 接口已被激活,但可能无法正常传输数据,如网线未连接的情况;
  • IFF_LOWER_UP 表示网络的物理连接已就绪,即网线连接正常;由于 struct ifreq 的 ifr_ifru.ifru_flags 类型为 short,用16进制表示仅为4位,因而无法获取与设置5位16进制的 IFF_LOWER_UP 标志(0x10000)。

4.5 通过 SIOCSIFADDR 操作设置指定网络接口的 IPv4 地址

/* set_interface_ip_address_ioctl.c */

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

static void set_ipaddr(const char *, const char *);

int main(int argc, char *argv[])
{

    if (argc != 3) {
        fprintf(stderr, "Usage: %s [network interface name] [ip address]\n",
        argv[0]);
        exit(EXIT_FAILURE);
    }

    char ifname[IFNAMSIZ] = {'\0'};
    strncpy(ifname, argv[1], IFNAMSIZ-1);
    char ipaddr[INET_ADDRSTRLEN] = {'\0'};
    strncpy(ipaddr, argv[2], INET_ADDRSTRLEN);

    set_ipaddr(ifname, ipaddr);

    printf("Interface %s : ip address is set to %s\n", ifname, ipaddr);
    
    return 0;
}

static void set_ipaddr(const char *dev, const char *ip)
{
    int sfd, saved_errno, ret;
    struct ifreq ifr;
    struct sockaddr_in sin;

    sfd = socket(AF_INET, SOCK_DGRAM, 0);

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &(sin.sin_addr));

    memcpy(&ifr.ifr_addr, &sin, sizeof(struct sockaddr));

    errno = saved_errno;
    ret = ioctl(sfd, SIOCSIFADDR, &ifr);
    if (ret == -1) {
        if (errno == 19) {
            fprintf(stderr, "Interface %s : No such device.\n", dev);
            exit(EXIT_FAILURE);
        }
        if (errno == 99) {
            fprintf(stderr, "Interface %s : No IPv4 address assigned.\n", dev);
            exit(EXIT_FAILURE);
        }
    }
    saved_errno = errno;

    close(sfd);
}

编译并运行:

# gcc set_interface_ip_address_ioctl.c -g -o set_interface_ip_address_ioctl
#
# ./set_interface_ip_address_ioctl eth1 10.0.0.1
Interface eth1 : ip address is set to 10.0.0.1
#
# ./get_interface_ip_address_ioctl eth1
Interface eth1 : 10.0.0.1
#
# ./set_interface_ip_address_ioctl eth1 10.0.0.2
Interface eth1 : ip address is set to 10.0.0.2
#
# ./get_interface_ip_address_ioctl eth1
Interface eth1 : 10.0.0.2

与 ifconfig(8) 相同,多次指定同一网络接口名称设置 IP 地址时,最后的设置将覆盖先前的设置而生效。

4.6 通过 SIOCGIFFLAGS 操作设置指定网络接口的标志

使用 ifconfig(8) 将 eth1 设置为混杂模式,并关闭该接口,然后在程序中关闭混杂模式,并开启该接口:

# ifconfig eth1 promisc
#
# ifconfig eth1 down
#
# ./get_interface_flags_ioctl eth1
Interface eth1 : BROADCAST MULTICAST PROMISC
/* set_interface_flags_ioctl.c */

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

static short get_if_flags(int, struct ifreq*);
static void set_if_flags(int, struct ifreq*);

int main(int argc, char *argv[])
{

    if (argc != 2) {
        fprintf(stderr, "Usage: %s [network interface name]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int sfd;
    short flags;
    struct ifreq ifr;

    char ifname[IFNAMSIZ] = {'\0'};
    strncpy(ifname, argv[1], IFNAMSIZ-1);

    sfd = socket(AF_INET, SOCK_DGRAM, 0);
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
    flags = get_if_flags(sfd, &ifr);

    ifr.ifr_flags = flags;

    /* set IFF_UP if cleared */
    if (!(flags & IFF_UP)) {
        ifr.ifr_flags |= IFF_UP;
        set_if_flags(sfd, &ifr);
        printf("Interface %s : UP set.\n", ifname);
    }

    flags = ifr.ifr_flags;

    /* clear IFF_PROMISC if set */
    if (flags & IFF_PROMISC) {
        ifr.ifr_flags &= ~IFF_PROMISC;
        set_if_flags(sfd, &ifr);
        printf("Interface %s : PROMISC cleared.\n", ifname);
    }

    close(sfd);

    exit(EXIT_SUCCESS);
}

static short get_if_flags(int s, struct ifreq *ifr)
{
    int ret, saved_errno;
    short if_flags;

    saved_errno = errno;
    ret = ioctl(s, SIOCGIFFLAGS, ifr);
    if (ret == -1 && errno == 19) {
        fprintf(stderr, "Interface %s : No such device.\n", ifr->ifr_name);
        exit(EXIT_FAILURE);
    }
    errno = saved_errno;

    if_flags = ifr->ifr_flags;
    return if_flags;
}

static void set_if_flags(int s, struct ifreq *ifr)
{
    int ret, saved_errno;
    saved_errno = errno;
    ret = ioctl(s, SIOCSIFFLAGS, ifr);
    if (ret == -1) {
        fprintf(stderr, "Interface %s : %s\n", ifr->ifr_name, strerror(errno));
        exit(EXIT_FAILURE);
    }
    errno = saved_errno;
}

4.7 通过 SIOCSIFNAME 操作更改网络接口的名称

/* change_ifname_ioctl.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>

static void change_ifname(char *, char *);
static void shutdown_if_up(char *);

int main(int argc, char *argv[])
{
    if (argc != 3) {
        fprintf(stderr, "%s [old ifname] [new ifname]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    char old_ifname[IFNAMSIZ] = {'\0'};
    strncpy(old_ifname, argv[1], IFNAMSIZ);

    char new_ifname[IFNAMSIZ] = {'\0'};
    strncpy(new_ifname, argv[2], IFNAMSIZ);

    change_ifname(old_ifname, new_ifname);
    printf("Interface name %s has been changed to %s\n", old_ifname, new_ifname);

    return 0;
}

void change_ifname(char *old_dev, char *new_dev)
{
    int sfd, ret, saved_errno;
    struct ifreq ifr;

    shutdown_if_up(old_dev);

    sfd = socket(AF_INET, SOCK_DGRAM, 0);

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, old_dev, IFNAMSIZ);
    strncpy(ifr.ifr_newname, new_dev, IFNAMSIZ);

    saved_errno = errno;
    ret = ioctl(sfd, SIOCSIFNAME, &ifr);
    if (ret == -1) {
        fprintf(stderr, "Interface %s : %s\n", dev, strerror(errno));
        exit(EXIT_FAILURE);
    }
    errno = saved_errno;
}

static void shutdown_if_up(char *dev)
{
    int sfd, ret, saved_errno;
    short flags;
    struct ifreq ifr;

    sfd = socket(AF_INET, SOCK_DGRAM, 0);

    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, dev, IFNAMSIZ);

    saved_errno = errno;
    ret = ioctl(sfd, SIOCGIFFLAGS, &ifr);
    if (ret == -1) {
        fprintf(stderr, "Interface %s : %s\n", dev, strerror(errno));
        exit(EXIT_FAILURE);
    }
    errno = saved_errno;

    flags = ifr.ifr_flags;
    if (flags & IFF_UP) {
        ifr.ifr_flags &= ~IFF_UP;
        saved_errno = errno;
        ret = ioctl(sfd, SIOCSIFFLAGS, &ifr);
        if (ret == -1) {
            fprintf(stderr, "Interface %s : %s\n",dev, strerror(errno));
            exit(EXIT_FAILURE);
        }
        errno = saved_errno;
    }
}

将 struct ifreq 的 ifr_ifrn.ifrn_name 指定为网络接口名称后,ioctl(SIOCSIFNAME) 将指定的新名称写入到 ifr_ifru.ifru_newname 中;该操作要求网络接口为关闭状态,即 (~IFF_UP)。

4.8 通过 SIOCSIFHWADDR 操作设置指定网络接口的 MAC 地址

static char Set_MacAddr(const char *pIfName, const unsigned char *pMacAddr)
{
	int fd;
	short flag;
	struct ifreq ifr;

	if (pIfName == NULL || pMacAddr == NULL)
	{
		printf("Input Param is Error!\n");
		return 0;
	}

	fd = socket(AF_INET, SOCK_DGRAM, 0);
 
	memset(&ifr, 0, sizeof(struct ifreq));
	strcpy(ifr.ifr_name, pIfName);
 
	if (ioctl(fd, SIOCGIFFLAGS, &ifr) < 0)
	{
		printf("ioctl [SIOCGIFFLAGS] error!\n");
		goto ERROR;
    }
 
	flag = ifr.ifr_flags;
 
	/* 如果网卡是启动的,设置MAC地址需要先停止网卡 */
	if (flag & IFF_UP)
	{
		ifr.ifr_flags &= ~IFF_UP;
		if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
		{
			printf("ioctl [SIOCSIFFLAGS] error!\n");
			goto ERROR;
		}
	}
 
	/* 配置MAC地址 */
	ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
	memcpy(ifr.ifr_hwaddr.sa_data, pMacAddr, MAC_ADDR_LEN);
	if (ioctl(fd, SIOCSIFHWADDR, &ifr) < 0)
	{
		printf("ioctl [SIOCSIFHWADDR] error!\n");
		goto ERROR;
    }
 
	/* 恢复网卡的工作状态 */
	if (flag & IFF_UP)
	{
		ioctl(fd, SIOCGIFFLAGS, &ifr);
		ifr.ifr_flags |= IFF_UP;
		
		if (ioctl(fd, SIOCSIFFLAGS, &ifr) < 0)
		{
			printf("ioctl [SIOCSIFFLAGS] error!\n");
			goto ERROR;
		}
	}

	close(fd);

	return 1;

ERROR:
	if (errno == 19)
	{
		printf("Interface %s : No such device.\n", pIfName);
	}
	else if (errno == 99)
	{
		printf("Interface %s : No IPv4 address assigned.\n", pIfName);
	}

	close(fd);
	
	return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值