本文只讨论ioctl 在 linux网络编程中的应用

#include<unistd.h>

int ioctl( int fd, int request, .../* void *arg */ ); 

第一个参数 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


路由                  ……


有很多request请求没有列出,而且不同的系统所提供的request也有所不同,比如linux就不提供SIOCGSIZIFCONF请求)

上面的表中,用ioctl执行接口操作的请求时,要用到结构体 struct ifconf 与 struct ifreq

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*/

ifconf 结构包含了 ifreq 结构指针, 在使用ifconf之前,我们得先为其ifcu_buf指针(或者说ifcu_req指针)分配缓冲区

其他不知道该怎么说。 

贴代码吧。  一个用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!因为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));
	//printf(" MAC:%02x:%02x:%02x:%02x:%02x:%02x\n", *p++, *p++, *p++, *p++, *p++, *p++);
		//这么写会导致输出为倒序。这并不是p指针有什么问题,不信你可以用
		// for(;;) 
		//	printf(p++);
		//来试验就是正确的,我猜倒序的原因是编译器的优化问题吧
	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;	
}