ioctl与ifreq、ifconf的使用

本文介绍了一个使用ioctl系统调用来模拟ifconfig命令功能的小程序。通过ioctl可以获取和设置网络接口的各种属性,如IP地址、MAC地址、广播地址、掩码等,并提供了启动和关闭接口的功能。

#include<unistd.h>
int ioctl( int fd, int request, .../* void *arg */ ); 返回0——成功, -1——出错
第一个参数 fd 指示某个文件描述符(当然也包括 套接口描述符)
第二个参数 request 指示要ioctl执行的操作
第三个参数 总是某种指针,具体的指向类型依赖于request 参数
--------------------------------------------------------------------------------
我们可以把和网络相关的请求(request)划分为6 类:
套接口操作
文件操作
接口操作
ARP 高速缓存操作
路由表操作
流系统

下表列出了网络相关ioctl 请求的request 参数以及arg 地址必须指向的数据类型:
类别    request    说明    (第三个参数)数据类型
--------------------------------------------------------------------------------
SIOCATMARK 是否位于带外标记 int
套接口 SIOCSPGRP 设置套接口的进程ID或组ID int
SIOCGPGRP 获取套接口的进程ID或组ID int
--------------------------------------------------------------------------------
文件 FIONBIO 设置/清除非阻塞IO标识 int
FIOASYNC 设置/清除信号驱动异步IO标识 int
FIONREAD 获取接收缓冲区中的字节数 int
……
--------------------------------------------------------------------------------
SIOCGIFCONF 获取所有接口的清单 struct ifconf
接口 SIOCSIFADDR 设置接口的ip地址 struct ifreq
SIOCGIFADDR 获取接口地址 struct ifreq
SIOCSIFFLAGS 设置接口标识 struct ifreq
SIOCGIFFLAGS 获取接口标识 struct ifreq
SIOCSIFDSTADDR 设置点到点地址 struct ifreq
SIOCGIFDSTADDR 获取点到点地址 struct ifreq
SICSIFBRDADDR 设置广播地址 struct ifreq
SICGIFBRDADDR 获取广播地址 struct ifreq
……
--------------------------------------------------------------------------------
SIOCSARP 创建/修改ARP表项 struct arpreq
ARP SIOCGARP 获取ARP表项 struct arpreq
SIOCDARP 删除ARP表项 struct arpreq
--------------------------------------------------------------------------------

上面的表中,用ioctl执行接口操作的请求时,要用到结构体 struct ifconf 与 struct ifreq
ifconf 结构包含了 ifreq 结构指针, 在使用ifconf之前,我们得先为其ifcu_buf指针(或者说ifcu_req指针)分配缓冲区

struct ifconf
{ 
	int ifc_len; // 缓冲区ifcu_buf的大小 
	union
	{ 
		caddr_t ifcu_buf; // 其实就是char *类型。
		struct ifreq *ifcu_req; // 为ifconf分配空间时我们用ifcu_buf指针;当要取得或设置该缓冲区中的ifreq类型时,则用这个指针
	}ifc_ifcu; 
}; 
#define ifc_buf ifc_ifcu.ifcu_buf //buffer address 
#define ifc_req ifc_ifcu.ifcu_req //array of structures returned

struct ifreq 
{
	char ifrn_name[IFNAMSIZ]; /* if name, e.g. "eth0" */ 
	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]; /* Just fits the size */
		char ifru_newname[IFNAMSIZ];
		void * 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*/

/***************************************************/
/**** 一个用ioctl实现的类似ifconfig命令的小程序 ****/
/***************************************************/
#include <stdio.h> //printf() 
#include <unistd.h> //ioctl() 
#include <sys/ioctl.h> //ioctl 
#include <sys/socket.h> //socket() 
#include <net/if.h> //struct ifconf{} & struct ifreq{} 
#include <string.h> //strcpy() 
#include <arpa/inet.h> //inet_ntoa() 
#include <stdlib.h> //malloc() & free() 

int print_if_addr(int fd, char *interface_name); //打印接口的ip地址 
int print_if_mac(int fd, char *interface_name); //打印接口的mac地址 
int print_if_broadaddr(int fd, char *interface_name); //打印接口的广播地址 
int print_if_mask(int fd, char *interface_name); //打印接口的掩码 
int print_if_mtu(int fd, char *interface_name); //打印接口的mtu 
int print_all_interface(); //打印所有接口的基本信息 
int print_if_addr6(char *interface_name); //打印接口的ipv6地址 
int print_interface_info(char *interface_name); //打印接口的以上所有信息 
int set_if_up(char *interface_name); //启动接口 
int set_if_down(char *interface_name); //关闭接口 
int set_if_ip(char *interface_name, char *ip_str); //设置接口的ip地址 
void usage(); //打印该程序的使用手册 

int main(int argc, char **argv) 
{ 
	int sockfd; 
	printf("/n **********funway:用ioctl函数来实现ifconfig命令的效果**********/n"); 
	switch(argc) 
	{ 
		case 1: 
			print_all_interface(); 
			break; 
		case 2: 
			print_interface_info(argv[1]); 
			break; 
		case 3: 
			if(strcmp(argv[2], "up") == 0) 
				set_if_up(argv[1]); 
			else if(strcmp(argv[2], "down") == 0) 
				set_if_down(argv[1]); 
			else 
			set_if_ip(argv[1], argv[2]); 
				break; 
		default: 
			usage(); 
			break; 
	} 

	return 0; 
} 

void usage() 
{ 
	printf("usage: ./myifconfig [interface [down|up|ip]]/n"); 
} 

int print_if_addr(int fd, char *if_name) 
{ 
	struct sockaddr_in *ip; 
	struct ifreq ifr; 

	strcpy(ifr.ifr_name, if_name); 

	if(ioctl(fd, SIOCGIFADDR, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFADDR error"); 
		return -1; 
	} 

	ip = (struct sockaddr_in *)&ifr.ifr_addr; //获得ipv4地址 
	printf(" IP: %s/n", inet_ntoa(ip->sin_addr)); //将ipv4地址转换为主机字节序的字符串并输出 
	return 0; 
} 

int print_if_broadaddr(int fd, char *if_name) 
{ 
	struct sockaddr_in *ip; 
	struct ifreq ifr; 

	strcpy(ifr.ifr_name, if_name); 

	if(ioctl(fd, SIOCGIFBRDADDR, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFBRDADDR error"); 
		return -1; 
	} 

	ip = (struct sockaddr_in *)&ifr.ifr_broadaddr; //获得广播地址 
	printf(" Broadcast: %s/n", inet_ntoa(ip->sin_addr)); 
	return 0; 
} 

int print_if_mask(int fd, char *if_name) 
{ 
	struct sockaddr_in *ip; 
	struct ifreq ifr; 

	strcpy(ifr.ifr_name, if_name); 

	if(ioctl(fd, SIOCGIFNETMASK, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFNETMASK error"); 
		return -1; 
	} 

	ip = (struct sockaddr_in *)&ifr.ifr_ifru.ifru_netmask; //获得子网掩码。注意!我们仍放在struct aockaddr_in结构中返回 
	printf(" Mask: %s/n", inet_ntoa(ip->sin_addr)); 
	return 0; 
} 

int print_if_mac(int fd, char *if_name) 
{ 
	unsigned char *p; //这里要用unsigned char,因为char要对[1xxx xxxx]这样的数进行补码运算的,但mac地址是不需要符号的数值 
	struct ifreq ifr; 

	strcpy(ifr.ifr_name, if_name); 

	if(ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFHWADDR error"); 
		return -1; 
	} 

	p = (char *)&ifr.ifr_ifru.ifru_hwaddr.sa_data[0]; //获得接口的MAC地址,用字符串指针返回 
	printf(" MAC: %02x:%02x:%02x:%02x:%02x:%02x/n", *p, *(p+1), *(p+2), *(p+3), *(p+4), *(p+5)); 
	return 0; 
} 

int print_if_mtu(int fd, char *if_name) 
{ 
	unsigned int mtu; 
	struct ifreq ifr; 

	strcpy(ifr.ifr_name, if_name); 

	if(ioctl(fd, SIOCGIFMTU, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFMTU error"); 
		return -1; 
	} 

	mtu = ifr.ifr_ifru.ifru_mtu; //获得子网掩码。注意!我们仍放在struct aockaddr_in结构中返回 
	printf(" MTU: %d/n", mtu); 
	return 0; 
} 

int print_if_addr6(char *if_name) 
{ 
	unsigned int mtu; 
	struct ifreq ifr; 
	int sockfd; 

	if((sockfd = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) 
	{ 
		perror("Socket error"); 
		return -1; 
	} // 创建用来检查网络接口的套接字 

	/* strcpy(ifr.ifr_name, if_name); 

	if(ioctl(fd, SIOCGIFMTU, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFMTU error"); 
		return -1; 
	} 

	mtu = ifr.ifr_ifru.ifru_mtu; //获得子网掩码。注意!我们仍放在struct aockaddr_in结构中返回 
	printf(" ipv6: %d/n", mtu); 
	*/ 
	//未写完,不知道怎么获得ipv6地址。。。 
	return 0; 
} 

int print_all_interface() 
{ 
	struct ifconf ifc; 
	struct ifreq *ifr_p; 
	int sockfd, len, old_len = 0, i; 
	char *buf; 

	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
	{ 
		perror("Socket error"); 
		return -1; 
	} // 创建用来检查网络接口的套接字 

	len = 10 * sizeof(struct ifreq); 
	for( ; ; ) 
	{ 
		if((buf = malloc(len)) == NULL) 
		{ 
			perror("malloc error"); 
			return -1; 
		} 
		ifc.ifc_len = len; 
		ifc.ifc_buf = buf; 
		if(ioctl(sockfd, SIOCGIFCONF, &ifc) < 0) 
		{ 
			perror("ioctl SIOCGIFCONF error"); 
			return -1; 
		} 
		if(ifc.ifc_len == old_len) 
		break; 
		old_len = ifc.ifc_len; 
		len += 10 * sizeof(struct ifreq); 
		free(buf); 
	} 
	printf("we have %d interfaces/n", ifc.ifc_len / sizeof(struct ifreq)); 

	for(i = 0; i < ifc.ifc_len / sizeof(struct ifreq); i++) 
	{ 
		ifr_p = &ifc.ifc_req[i]; 
		printf("/ninterface [%s]:/n", ifr_p->ifr_name); 

		print_if_addr(sockfd, ifr_p->ifr_name); 
		print_if_broadaddr(sockfd, ifr_p->ifr_name); 
		print_if_mask(sockfd, ifr_p->ifr_name); 
		print_if_mac(sockfd, ifr_p->ifr_name); 
		print_if_mtu(sockfd, ifr_p->ifr_name); 
	} 
	close(sockfd); 
	return 0; 
} 

int print_interface_info(char *if_name) 
{ 
	int sockfd; 
	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
	{ 
		perror("Socket error"); 
		return -1; 
	} // 创建用来检查网络接口的套接字 

	printf("%s:/n", if_name); 
	print_if_addr(sockfd, if_name); 
	print_if_broadaddr(sockfd, if_name); 
	print_if_mask(sockfd, if_name); 
	print_if_mac(sockfd, if_name); 
	print_if_mtu(sockfd, if_name); 
	close(sockfd); 
	return 0; 
} 

int set_if_up(char *if_name) //启动接口 
{ 
	struct ifreq ifr; 
	int sockfd; 

	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
	{ 
		perror("Socket error"); 
		return -1; 
	} // 创建用来检查网络接口的套接字 

	strcpy(ifr.ifr_name, if_name); 
	if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFFLAGS error"); 
		return -1; 
	} 
	ifr.ifr_flags |= IFF_UP; 
	if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) 
	{ 
		perror("ioctl SIOCSIFFLAGS error"); 
		return -1; 
	} 
	return 0; 
} 

int set_if_down(char *if_name) //关闭接口 
{ 
	struct ifreq ifr; 
	int sockfd; 

	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
	{ 
		perror("Socket error"); 
		return -1; 
	} // 创建用来检查网络接口的套接字 

	strcpy(ifr.ifr_name, if_name); 
	if(ioctl(sockfd, SIOCGIFFLAGS, &ifr) < 0) 
	{ 
		perror("ioctl SIOCGIFFLAGS error"); 
		return -1; 
	} 
	ifr.ifr_flags &= ~IFF_UP; //将IIF_UP取反后与原来的标志进行 与运算。 
	if(ioctl(sockfd, SIOCSIFFLAGS, &ifr) < 0) 
	{ 
		perror("ioctl SIOCSIFFLAGS error"); 
		return -1; 
	} 
	return 0; 
} 

int set_if_ip(char *if_name, char *ip_str) //设置接口的ip地址 
{ 
	struct ifreq ifr; 
	struct sockaddr_in ip_addr; 
	int sockfd; 

	if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) 
	{ 
		perror("Socket error"); 
		return -1; 
	} // 创建用来检查网络接口的套接字 

	ip_addr.sin_family = AF_INET; 
	if(inet_pton(AF_INET, ip_str, &ip_addr.sin_addr) < 1) 
	{ 
		perror("error ipv4 addr:"); 
		return -1; 
	} 

	strcpy(ifr.ifr_name, if_name); 
	memcpy(&ifr.ifr_addr, &ip_addr, sizeof(struct sockaddr_in)); 
	if(ioctl(sockfd, SIOCSIFADDR, &ifr) < 0) 
	{ 
		perror("ioctl SIOCSIFADDR error"); 
		return -1; 
	} 
	return 0; 
} 


 

使用 `ioctl` 获取内核网络接口信息是 Linux 网络编程中一种传统方式,例如通过 `SIOCGIFCONF`、`SIOCGIFADDR` 等命令读取网卡名称、IP 地址、MTU 等。虽然它在很多旧代码中广泛使用,但现代的 **Netlink 接口(如 rtnetlink)相比存在诸多限制**。 --- ## ✅ 一、`ioctl` 获取接口信息的典型用法 ```c #include <sys/socket.h> #include <sys/ioctl.h> #include <net/if.h> int sock = socket(AF_INET, SOCK_DGRAM, 0); struct ifconf ifc; char buf[1024]; ifc.ifc_len = sizeof(buf); ifc.ifc_buf = buf; if (ioctl(sock, SIOCGIFCONF, &ifc) < 0) { perror("ioctl"); return -1; } struct ifreq *ifr = ifc.ifc_req; for (int i = 0; i < ifc.ifc_len / sizeof(struct ifreq); ++i) { printf("Interface: %s\n", ifr[i].ifr_name); } ``` 上述代码可以获取所有接口名和部分地址信息。 --- ## ❌ 二、`ioctl` 的主要限制( Netlink 对比) | 限制类别 | 具体问题 | 说明 | |--------|---------|------| | ### 1. 数据结构固定且不灵活 | 固定大小的 `struct ifreq` | `ifreq` 是一个 union 结构,字段有限,难以扩展新属性(如 IPv6 地址、VLAN ID、隧道参数等)。 | | | 最大支持 ~1000 个接口 | `ifconf.buf` 是用户指定缓冲区,不能动态分页;如果接口太多会截断。 | | | 不支持嵌套属性 | 无法表达复杂结构(如策略路由、QoS 规则),而 Netlink 使用 TLV(Type-Length-Value)格式完美支持。 | | ### 2. 协议族耦合严重 | 主要面向 IPv4 | `SIOCGIFADDR` 等只能获取 AF_INET 地址,IPv6 需要额外调用或不可用。 | | | 缺乏统一语义 | 每个协议族有自己的 ioctl 命令,缺乏通用性。Netlink 使用 `RTM_GETADDR` + `ifa_family=AF_INET6` 统一处理。 | | ### 3. 安全性和权限模型弱 | 所有操作都在同一个 socket 上 | 没有消息级别的权限控制、序列号、PID 区分;容易被伪造请求。 | | | 无审计能力 | 无法追踪谁发起了哪个请求。Netlink 支持 netns、capabilities、audit logging。 | | ### 4. 不支持异步事件通知 | 只能轮询 | 无法监听“接口状态变化”、“IP 添加/删除”等事件。 | | | Netlink 支持多播组订阅 | 如加入 `RTNLGRP_LINK` 或 `RTNLGRP_IPV4_IFADDR` 实时接收变更。 | | ### 5. 不支持原子操作和事务 | 多个 ioctl 调用之间无一致性保证 | 例如:修改 MTU 和 flags 分两步执行,中间可能被其他进程打断。 | | | Netlink 支持批量消息(NLMSG_BATCH) | 可在一个 Netlink 消息包中包含多个操作,实现原子更新。 | | ### 6. 不支持命名空间(netns)感知 | 行为依赖当前进程的网络命名空间 | 虽然能工作,但没有明确的命名空间标识。 | | | Netlink 天然支持跨 netns 查询 | 结合 `genl_ctrl_resolve()` 和 family name 更清晰。 | | ### 7. 错误处理机制简陋 | 返回 `-1` + `errno` | 无法携带丰富的错误信息(比如哪个属性出错、建议值等)。 | | | Netlink 使用 `NLMSG_ERROR` 消息 | 可返回详细错误码和原始请求拷贝,便于调试。 | | ### 8. 性能瓶颈 | 一次性复制整个接口列表到用户空间 | 即使只关心一个接口也要遍历全部。 | | | Netlink 支持按需查询(dumpit + filter) | 内核可在 dump 过程中根据条件过滤,减少数据拷贝。 | --- ## ✅ 三、什么时候还能用 `ioctl`? 尽管有这些限制,`ioctl` 在以下场景仍可用: | 场景 | 是否推荐 | 说明 | |------|----------|------| | 快速获取本机 IPv4 地址或接口名 | ✅ 小项目可用 | 简单脚本、兼容老系统。 | | 嵌入式设备,资源受限 | ✅ 权衡后可用 | Netlink 库较重,`ioctl` 更轻量。 | | 修改单一参数(如 MTU、flags) | ⚠️ 可用但建议迁移到 Netlink | `SIOCSIFMTU` 依然有效。 | | 生产级网络管理工具开发 | ❌ 不推荐 | 应使用 `libmnl` + `rtnetlink` 或 `iproute2` 架构。 | --- ## ✅ 四、对比总结表 | 特性 | `ioctl(SIOCGIFCONF)` | Netlink (rtnetlink) | |------|------------------------|------------------------| | 支持 IPv4/IPv6 | ❌ 仅 IPv4 主力 | ✅ 完全支持 | | 支持超过 1000 个接口 | ❌ 缓冲区限制 | ✅ 动态分页 dump | | 支持实时事件监听 | ❌ 否 | ✅ 支持多播订阅 | | 属性扩展能力 | ❌ 固定结构 | ✅ TLV 格式无限扩展 | | 权限控制 | ❌ 弱 | ✅ CAP_NET_ADMIN + audit | | 原子操作 | ❌ 否 | ✅ 批量消息支持 | | 跨网络命名空间 | ⚠️ 依赖上下文 | ✅ 显式支持 | | 错误反馈丰富度 | ❌ errno | ✅ 详细错误消息 | | 用户空间库成熟度 | ✅ 简单易用 | ✅ `libmnl`, `libnl` 成熟 | --- ## ✅ 五、结论 > 🔴 **`ioctl` 已被视为过时(legacy)机制**,尤其在需要高性能、可扩展、事件驱动的网络配置场景下。 > > 🟢 **Netlink 是现代 Linux 网络管理的标准接口**,被 `ip`, `ss`, `bridge`, `tc` 等工具广泛采用。 ### ✔ 推荐迁移路径: ``` 旧代码:ioctl(SIOCGIFCONF) → 替代方案:Netlink RTM_GETLINK / RTM_GETADDR 旧代码:ioctl(SIOCGIFFLAGS) → 替代方案:Netlink RTM_GETLINK + IFLA_LINKINFO ``` ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值