【Linux网络编程】ioctl函数在网络编程中的应用(C语言实现一个ifconfig)



1 前言

  linux系统下查看、配置网络相关的信息,如物理地址、ip地址、网关等信息,我们通常是使用ifconfigroute命令操作;亦或者直接修改网络相关的配置文件达到修改的目的。在程序中修改网络相关信息,虽然可以通过 exec函数簇、system函数、popen函数调用ifconfigroute shell命令,通过解析命令返回值获取信息,但很显然这不是理想的方法。

  linux系统用户态与内核态(包括设备、驱动)进行非数据流的相关控制信息交互,一般是通过ioctl函数实现。相同的,用户态与网络设备相关的控制信息也是通过ioctl实现。实质上,ifconfigroute底层最终是通过调用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);这里使用IPv4
  • type,套接字类型,常用有原始套接字( SOCK_RAW )、流格式套接字(SOCK_STRAAM)、数据报套接字(SOCK_DGRAM);这里可以填三者任意值
  • protocol,传输协议,常用有TCP协议(IPPROTO_TCP)和UDP协议(IPPROTO_UDP);这里可以填“0”,系统会根据套接字类型选择相应的传输协议

【2】初始化struct ifreqstruct ifconf数据结构

【3】调用ioctl,传入指定请求码,访问套接字fd

【4】解析struct ifreqstruct 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)

猜你喜欢

转载自blog.csdn.net/qq_20553613/article/details/107189278
今日推荐