文章目录
1 前言
linux系统下查看、配置网络相关的信息,如物理地址、ip地址、网关等信息,我们通常是使用ifconfig
、route
命令操作;亦或者直接修改网络相关的配置文件达到修改的目的。在程序中修改网络相关信息,虽然可以通过 exec函数簇、system函数、popen函数调用ifconfig
、route
shell命令,通过解析命令返回值获取信息,但很显然这不是理想的方法。
linux系统用户态与内核态(包括设备、驱动)进行非数据流的相关控制信息交互,一般是通过ioctl
函数实现。相同的,用户态与网络设备相关的控制信息也是通过ioctl
实现。实质上,ifconfig
、route
底层最终是通过调用ioctl
函数实现其功能的。
2 网络编程下的ioctl
网络模块下的ioctl
控制信息包括网卡设备映射属性、网络接口信息、网络接口配置信息。
2.1 函数原型
#include <sys/ioctl.h>
int ioctl(int fd, int request, ...);
-
fd
,socket文件描述符,通过open
或者socket
创建 -
request
,请求操作命令码,与具体操作相关 -
参数3,具体数据结构,依赖于请求码
request
指定的操作类型
2.2 请求操作命令码
linux 4.4内核,网络模块编程相关请求码位于"/include/uapi/inux/sockios.h"
中定义。更低版本内核可能位于"/include/inux/sockios.h"
中定义,命令码命名有特定固有的命名格式。
-
删除类型命令码:
SIOCDxxx
,xxx表示具体操作命名 -
获取类型命令码:
SIOCGxxx
,xxx表示具体操作命名 -
设置类型命令码:
SIOCSxxx
,xxx表示具体操作命名
下面只列出常用的请求命令码,更多命令码宏参考sockios.h
定义。
请求操作码 | 值 | 数据类型 | 含义 |
---|---|---|---|
SIOCGIFCONF | 0x8912 | struct ifconf | 获取所有网络接口 |
SIOCGIFFLAGS | 0x8913 | struct ifreq | 获取网络接口标识 |
SIOCSIFFLAGS | 0x8914 | struct ifreq | 设置网络接口标识 |
SIOCGIFADDR | 0x8915 | struct ifreq | 获取本地ip地址 |
SIOCSIFADDR | 0x8916 | struct ifreq | 设置本地ip地址 |
SIOCGIFBRDADDR | 0x8919 | struct ifreq | 获取广播地址 |
SIOCSIFBRDADDR | 0x891a | struct ifreq | 设置广播地址 |
SIOCGIFNETMASK | 0x891b | struct ifreq | 获取本地ip子网掩码 |
SIOCSIFNETMASK | 0x891c | struct ifreq | 设置本地ip子网掩码 |
SIOCGIFMETRIC | 0x891d | struct ifreq | 获取metric值 |
SIOCSIFMETRIC | 0x891e | struct ifreq | 设置metric值 |
SIOCGIFMTU | 0x8921 | struct ifreq | 获取最大传输单元 |
SIOCSIFMTU | 0x8922 | struct ifreq | 设置最大传输单元 |
SIOCGIFTXQLEN | 0x8942 | struct ifreq | 获取发送队列大小 |
SIOCSIFTXQLEN | 0x8943 | struct ifreq | 设置发送队列大小 |
SIOCDARP | 0x8953 | struct arpreq | 删除ARP表项 |
SIOCGARP | 0x8954 | struct arpreq | 获取ARP表项 |
SIOCSARP | 0x8955 | struct arpreq | 设置ARP表项 |
2.3 数据结构
linux 4.4内核,网卡设备映射属性、网络接口信息、网络接口配置信息数据结构位于"/include/uapi/inux/if.h"
中定义。更低版本内核可能位于"/include/inux/if.h"
中定义。
2.3.1 网卡设备映射属性
struct ifmap {
unsigned long mem_start; /* 映射起始地址 */
unsigned long mem_end; /* 映射结束地址 */
unsigned short base_addr; /* 基地址 */
unsigned char irq; /* 中断号 */
unsigned char dma; /* 网卡DMA映射 */
unsigned char port; /* 端口号 */
/* 3 bytes spare */
};
2.3.3 网络接口信息
struct ifreq {
#define IFHWADDRLEN 6 /* 物理地址长度 */
union
{
char ifrn_name[IFNAMSIZ]; /* 网卡名称 */
} ifr_ifrn;
union {
struct sockaddr ifru_addr; /* 本地ip */
struct sockaddr ifru_dstaddr; /* 目标ip */
struct sockaddr ifru_broadaddr; /* 广播ip */
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;
};
-
ifru_flags
,接口标识,表示网卡的工作状态、运行模式、数据传输模式等,具体标识含义在if.h
中定义,常见标识如下。标识 值 含义 IFF_BROADCAST 1<<1 广播传输 IFF_DEBUG 1<<2 调试模式 IFF_LOOPBACK 1<<3 回环传输 IFF_UP 1<<0 接口已开启,但可能无法正常传输数据,如未接网线 IFF_RUNNING 1<<6 接口已开启,数据可正常传输 IFF_LOWER_UP 1<<16 接口物理连接已就绪
如果要获取网络接口信息,则ioctl
第三个参数传入struct ifreq
数据结构地址。ifr_ifru
成员参数是一个联合体,linux内核已定义了常用的信息宏,可以通过这些宏获取参数信息。
#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 ifreq ifr = {
0};
ifr.ifr_name = "eth0";
2.3.4 网络接口配置信息
struct ifconf {
int ifc_len; /* 配置缓冲区大小 */
union {
char __user *ifcu_buf; /* char类型缓冲区 */
struct ifreq __user *ifcu_req;/* struct ifreq类型缓冲区 */
} ifc_ifcu;
};
2.3.5 网络接口ARP高速缓存
ARP高速缓存信息数据结构位于"/include/uapi/inux/if_arp.h"
中定义。更低版本内核可能位于"/include/inux/if_arp.h"
中定义。
struct arpreq {
struct sockaddr arp_pa; /* protocol address */
struct sockaddr arp_ha; /* hardware address */
int arp_flags; /* flags */
struct sockaddr arp_netmask; /* netmask (only for proxy arps) */
char arp_dev[16];
};
3 ioctl应用
网络编程中的ioctl
函数获取、设置网口属性应用步骤:
【1】通过socket
函数创建文件描述符fd(套接字)
#include <sys/socket.h>
int socket(int af, int type, int protocol);
af
,地址族(Address Family),ip地址类型,分为IPv4(AF_INET
)和IPv6(AF_INET6);这里使用IPv4type
,套接字类型,常用有原始套接字(SOCK_RAW
)、流格式套接字(SOCK_STRAAM
)、数据报套接字(SOCK_DGRAM
);这里可以填三者任意值protocol
,传输协议,常用有TCP协议(IPPROTO_TCP
)和UDP协议(IPPROTO_UDP
);这里可以填“0”,系统会根据套接字类型选择相应的传输协议
【2】初始化struct ifreq
和struct ifconf
数据结构
【3】调用ioctl
,传入指定请求码,访问套接字fd
【4】解析struct ifreq
和struct ifconf
数据结构返回值
3.1 获取指定网卡ip
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <net/if.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int fd = 0;
struct ifreq ifr = {
0};
struct ifconf ifc = {
0};
int ret = 0;
int i;
if (argc < 2)
{
printf("parameter invalid,usage: [%s if_name]\n", argv[0]);
return -1;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_addr.sa_family = AF_INET;
strncpy(ifr.ifr_name, argv[1], IFNAMSIZ); /* 网卡名称 */
/* 创建socket描述符 */
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd <= 0)
{
printf("create socket fd failed,%s\n", strerror(errno));
return -1;
}
ret = ioctl(fd, SIOCGIFADDR, &ifr);
if (ret < 0)
{
printf("ioctl failed,%s\n", strerror(errno));
goto __exit;
}
printf("%s ip addr: %s\n", argv[1], inet_ntoa(((struct sockaddr_in *)(&ifr.ifr_addr))->sin_addr));
__exit:
if (fd != 0)
{
close(fd);
}
return ret;
}
执行结果
acuity@ubuntu:/mnt/hgfs/LSW/STHB/TCP/ioctl$ gcc getip.c -o getip
acuity@ubuntu:/mnt/hgfs/LSW/STHB/TCP/ioctl$ ./getip ens33
ens33 ip addr: 192.168.0.24
3.2 实现ifconfig功能
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <net/if.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int fd = 0;
struct ifreq ifr_buf[10]; /* 最大查询10个网口信息 */
struct ifconf ifc = {
0};
int if_num = 0;
int ret = 0;
int i= 0;
char buf[128] = {
0};
/* 创建socket描述符 */
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd <= 0)
{
printf("create socket fd failed,%s\n", strerror(errno));
return -1;
}
ifc.ifc_len = sizeof(ifr_buf);
ifc.ifc_buf = (caddr_t)ifr_buf;
ret = ioctl(fd, SIOCGIFCONF, (char*)&ifc);
if(ret)
{
printf("ioctl failed,%s\n", strerror(errno));
close(fd);
return -1;
}
if_num = ifc.ifc_len/sizeof(struct ifreq); /* 实际网口个数 */
while(if_num--)
{
printf("%s", ifr_buf[if_num].ifr_name);
/* 获取网卡状态标识 */
ret = ioctl(fd, SIOCGIFFLAGS, &ifr_buf[if_num]);
if(ret != 0)
{
printf("ioctl SIOCGIFFLAGS failed,%s\n", strerror(errno));
continue;
}
if (ifr_buf[if_num].ifr_flags&IFF_LOOPBACK)
{
sprintf(buf,"%s", "Link encap:Local Loopback");
}
else
{
sprintf(buf,"%s", "Link encap:Ethernet");
}
/* 获取物理地址 */
ret = ioctl(fd, SIOCGIFHWADDR, &ifr_buf[if_num]);
if(ret != 0)
{
printf("ioctl SIOCGIFHWADDR failed,%s\n", strerror(errno));
continue;
}
sprintf(buf+strlen(buf)," HWaddr %02x:%02x:%02x:%02x:%02x:%02x",
(unsigned char)ifr_buf[if_num].ifr_hwaddr.sa_data[0],
(unsigned char)ifr_buf[if_num].ifr_hwaddr.sa_data[1],
(unsigned char)ifr_buf[if_num].ifr_hwaddr.sa_data[2],
(unsigned char)ifr_buf[if_num].ifr_hwaddr.sa_data[3],
(unsigned char)ifr_buf[if_num].ifr_hwaddr.sa_data[4],
(unsigned char)ifr_buf[if_num].ifr_hwaddr.sa_data[5]);
for (i=0; i<(10-strlen(ifr_buf[if_num].ifr_name)); i++)
{
printf(" ");
}
printf("%s\n", buf);
/* 获取本地ip */
ret = ioctl(fd, SIOCGIFADDR, &ifr_buf[if_num]);
if(ret)
{
printf("ioctl SIOCGIFADDR failed,%s\n", strerror(errno));
continue;
}
sprintf(buf,"inet addr:%s", inet_ntoa(((struct sockaddr_in *)(&ifr_buf[if_num].ifr_addr))->sin_addr));
/* 获取广播ip */
ret = ioctl(fd, SIOCGIFBRDADDR, &ifr_buf[if_num]);
if(ret)
{
printf("ioctl SIOCGIFBRDADDR failed,%s\n", strerror(errno));
continue;
}
sprintf(buf+strlen(buf), " Bcast:%s", inet_ntoa(((struct sockaddr_in *)(&ifr_buf[if_num].ifr_broadaddr))->sin_addr));
/* 获取子网掩码 */
ret = ioctl(fd, SIOCGIFNETMASK, &ifr_buf[if_num]);
if(ret)
{
printf("ioctl SIOCGIFNETMASK failed,%s\n", strerror(errno));
continue;
}
sprintf(buf+strlen(buf), " Mask:%s", inet_ntoa(((struct sockaddr_in *)(&ifr_buf[if_num].ifr_netmask))->sin_addr));
printf(" %s\n", buf);
/* 获取网卡状态标识 */
ret = ioctl(fd, SIOCGIFFLAGS, &ifr_buf[if_num]);
if(ret != 0)
{
printf("ioctl SIOCGIFFLAGS failed,%s\n", strerror(errno));
continue;
}
if (ifr_buf[if_num].ifr_flags&IFF_UP)
{
sprintf(buf, "%s","UP");
}
if (ifr_buf[if_num].ifr_flags&IFF_LOOPBACK)
{
sprintf(buf+strlen(buf), " %s", "LOOPBACK");
}
else
{
sprintf(buf+strlen(buf), " %s", "BROADCAST");
}
if (ifr_buf[if_num].ifr_flags&IFF_RUNNING)
{
sprintf(buf+strlen(buf), " %s", "RUNNING");
}
if (ifr_buf[if_num].ifr_flags&IFF_MULTICAST)
{
sprintf(buf+strlen(buf), " %s", "MULTICAST");
}
/* 获取最大传输MTU */
ret = ioctl(fd, SIOCGIFMTU, &ifr_buf[if_num]);
if(ret != 0)
{
printf("ioctl SIOCGIFMTU failed,%s\n", strerror(errno));
continue;
}
sprintf(buf+strlen(buf), " MTU:%d", ifr_buf[if_num].ifr_mtu);
/* 获取Metric */
ret = ioctl(fd, SIOCGIFMETRIC, &ifr_buf[if_num]);
if(ret != 0)
{
printf("ioctl SIOCGIFMETRIC failed,%s\n", strerror(errno));
continue;
}
sprintf(buf+strlen(buf), " Metric:%d", ifr_buf[if_num].ifr_metric);
printf(" %s\n", buf);
/* 获取发送队列大小 */
ret = ioctl(fd, SIOCGIFTXQLEN, &ifr_buf[if_num]);
if(ret != 0)
{
printf("ioctl SIOCGIFTXQLEN failed,%s\n", strerror(errno));
continue;
}
sprintf(buf, "txqueuelen:%d", ifr_buf[if_num].ifr_qlen);
printf(" %s\n", buf);
printf("\n");
}
close(fd);
return 0;
}
执行结果
/* 程序执行 */
acuity@ubuntu:/mnt/hgfs/LSW/STHB/TCP/ioctl$ gcc ifconfig.c -o ifconfig
acuity@ubuntu:/mnt/hgfs/LSW/STHB/TCP/ioctl$ ./ifconfig
ens33 Link encap:Ethernet HWaddr 00:0c:29:99:a4:35
inet addr:192.168.0.24 Bcast:192.168.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
txqueuelen:1000
lo Link encap:Local Loopback HWaddr 00:00:00:00:00:00
inet addr:127.0.0.1 Bcast:0.0.0.0 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
txqueuelen:1000
/* ifconfig命令 */
acuity@ubuntu:/mnt/hgfs/LSW/STHB/TCP/ioctl$ ifconfig
ens33 Link encap:Ethernet HWaddr 00:0c:29:99:a4:35
inet addr:192.168.0.24 Bcast:192.168.0.255 Mask:255.255.255.0
inet6 addr: fe80::1513:8861:4aa:c9c4/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:88317 errors:0 dropped:0 overruns:0 frame:0
TX packets:22411 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:59721319 (59.7 MB) TX bytes:2169702 (2.1 MB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:138 errors:0 dropped:0 overruns:0 frame:0
TX packets:138 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:12635 (12.6 KB) TX bytes:12635 (12.6 KB)