UNIX网络编程——ioctl 函数的用法详解
1.介绍
Linux网络程序与内核交互的方法是通过ioctl来实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口。并且还能够查看,修改,删除ARP高速缓存的信息,所以,我们有必要了解一下ioctl函数的具体实现。
2.相关结构体与相关函数
-
#include <sys/ioctl.h>
-
int ioctl(int d,int request,....);
参数:
d-文件描述符,这里是对网络套接字操作,显然是套接字描述符。
request-请求码
省略的部分对应不同的内存缓冲区,而具体的内存缓冲区是由请求码request来决定的,下面看一下具体都有哪些相关缓冲区。
(1)网络接口请求结构ifreq
-
struct ifreq
-
{
-
#define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC
-
union{
-
char ifrn_name[IFNAMESIZ];//网络接口名称
-
}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;//本地MAC地址
-
short ifru_flags;//网络接口标记
-
int ifru_ivalue;//不同的请求含义不同
-
struct ifmap ifru_map;//网卡地址映射
-
int ifru_mtu;//最大传输单元
-
char ifru_slave[IFNAMSIZ];//占位符
-
char ifru_newname[IFNAMSIZE];//新名称
-
void __user* ifru_data;//用户数据
-
struct if_settings ifru_settings;//设备协议设置
-
}ifr_ifru;
-
}
-
#define ifr_name ifr_ifrn.ifrn_name;//接口名称
-
#define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC
-
#define ifr_addr ifr_ifru.ifru_addr;//本地IP
-
#define ifr_dstaddr ifr_ifru.dstaddr;//目标IP
-
#define ifr_broadaddr ifr_ifru.broadaddr;//广播IP
-
#define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码
-
#define ifr_flags ifr_ifru.ifru_flags;//标志
-
#define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度
-
#define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元
-
#define ifr_map ifr_ifru.ifru_map;//设备地址映射
-
#define ifr_slave ifr_ifru.ifru_slave;//副设备
-
#define ifr_data ifr_ifru.ifru_data;//接口使用
-
#define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号
-
#define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽
-
#define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度
-
#define ifr_newname ifr_ifru.ifru_newname;//新名称
-
#define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置
如果想获得网络接口的相关信息,就传入ifreq结构体。
(2)网卡设备属性ifmap
-
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)网络配置接口ifconf
-
struct ifconf{//网络配置结构体是一种缓冲区
-
int ifc_len;//缓冲区ifr_buf的大小
-
union{
-
char__user *ifcu_buf;//绘冲区指针
-
struct ifreq__user* ifcu_req;//指向ifreq指针
-
}ifc_ifcu;
-
};
-
#define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址
-
#define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址
(4)ARP高速缓存操作arpreq
-
/* ARP高速缓存操作,包含IP地址和硬件地址的映射表,操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录 */
-
struct arpreq{
-
struct sockaddr arp_pa;//协议地址
-
struct sockaddr arp_ha;//硬件地址
-
int arp_flags;//标记
-
struct sockaddr arp_netmask;//协议地址的子网掩码
-
char arp_dev[16];//查询网络接口的名称
-
}
3. 请求码request
类别 |
Request |
说明 |
数据类型 |
套 接 口 |
SIOCATMARK SIOCSPGRP SIOCGPGRP |
是否位于带外标记 设置套接口的进程ID或进程组ID 获取套接口的进程ID或进程组ID |
int int int |
文
件
|
FIONBIN FIOASYNC FIONREAD FIOSETOWN FIOGETOWN
|
设置/清除非阻塞I/O标志 设置/清除信号驱动异步I/O标志 获取接收缓存区中的字节数 设置文件的进程ID或进程组ID 获取文件的进程ID或进程组ID |
int int int int int |
接 口
|
SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFFLAGS SIOCGIFFLAGS SIOCSIFDSTADDR SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFBRDADDR SIOCGIFNETMASK SIOCSIFNETMASK SIOCGIFMETRIC SIOCSIFMETRIC SIOCGIFMTU SIOCxxx |
获取所有接口的清单 设置接口地址 获取接口地址 设置接口标志 获取接口标志 设置点到点地址 获取点到点地址 获取广播地址 设置广播地址 获取子网掩码 设置子网掩码 获取接口的测度 设置接口的测度 获取接口MTU (还有很多取决于系统的实现) |
struct ifconf struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq |
ARP |
SIOCSARP SIOCGARP SIOCDARP |
创建/修改ARP表项 获取ARP表项 删除ARP表项 |
struct arpreq struct arpreq struct arpreq |
路 由 |
SIOCADDRT SIOCDELRT |
增加路径 删除路径 |
struct rtentry struct rtentry |
流 |
I_xxx |
|
|
4. 相关例子
(1)网络接口信息
选项获取填充struct ifreq的ifr_name:
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <string.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <linux/if.h>
-
#include <arpa/inet.h>
-
#include <linux/sockios.h>
-
/**
-
ioctl函数是与内核交互的一种方法,使用ioctl函数与内核协议栈进行交互
-
ioctl函数可操作I/O请求,文件请求与网络接口请求
-
网络接口请求的几个结构体:
-
struct ifreq{
-
#define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC
-
union{
-
char ifrn_name[IFNAMESIZ];//网络接口名称
-
}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;//本地MAC地址
-
short ifru_flags;//网络接口标记
-
int ifru_ivalue;//不同的请求含义不同
-
struct ifmap ifru_map;//网卡地址映射
-
int ifru_mtu;//最大传输单元
-
char ifru_slave[IFNAMSIZ];//占位符
-
char ifru_newname[IFNAMSIZE];//新名称
-
void __user* ifru_data;//用户数据
-
struct if_settings ifru_settings;//设备协议设置
-
}ifr_ifru;
-
}
-
#define ifr_name ifr_ifrn.ifrn_name;//接口名称
-
#define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC
-
#define ifr_addr ifr_ifru.ifru_addr;//本地IP
-
#define ifr_dstaddr ifr_ifru.dstaddr;//目标IP
-
#define ifr_broadaddr ifr_ifru.broadaddr;//广播IP
-
#define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码
-
#define ifr_flags ifr_ifru.ifru_flags;//标志
-
#define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度
-
#define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元
-
#define ifr_map ifr_ifru.ifru_map;//设备地址映射
-
#define ifr_slave ifr_ifru.ifru_slave;//副设备
-
#define ifr_data ifr_ifru.ifru_data;//接口使用
-
#define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号
-
#define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽
-
#define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度
-
#define ifr_newname ifr_ifru.ifru_newname;//新名称
-
#define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置
-
struct ifmap{//网卡设备的映射属性
-
unsigned long mem_start;//开始地址
-
unsigned long mem_end;//结束地址
-
unsigned short base_addr;//基地址
-
unsigned char irq;//中断号
-
unsigned char dma;//DMA
-
unsigned char port;//端口
-
}
-
struct ifconf{//网络配置结构体是一种缓冲区
-
int ifc_len;//缓冲区ifr_buf的大小
-
union{
-
char__user *ifcu_buf;//绘冲区指针
-
struct ifreq__user* ifcu_req;//指向ifreq指针
-
}ifc_ifcu;
-
};
-
#define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址
-
#define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址
-
(1)获得配置选项SIOCGIFCONF获得网络接口的配置情况 需要填充struct ifreq中ifr_name变量
-
(2)其它选项获取填充struct ifreq的ifr_name
-
**/
-
int main(int argc,char*argv[]){
-
int s;
-
int err;
-
s=socket(AF_INET,SOCK_DGRAM,0);
-
if(s<0){
-
perror("socket error");
-
return;
-
}
-
//传入网络接口序号,获得网络接口的名称
-
struct ifreq ifr;
-
ifr.ifr_ifindex=2;//获得第2个网络接口的名称
-
err=ioctl(s,SIOCGIFNAME,&ifr);
-
if(err)
-
perror("index error");
-
else
-
printf("the %dst interface is:%s\n",ifr.ifr_ifindex,ifr.ifr_name);
-
//传入网络接口名称,获得标志
-
memcpy(ifr.ifr_name,"eth0",5);
-
err=ioctl(s,SIOCGIFFLAGS,&ifr);
-
if(!err)
-
printf("SIOCGIFFLAGS:%d\n",ifr.ifr_flags);
-
//获得MTU和MAC
-
err=ioctl(s,SIOCGIFMTU,&ifr);
-
if(!err)
-
printf("SIOCGIFMTU:%d\n",ifr.ifr_mtu);
-
//获得MAC地址
-
err=ioctl(s,SIOCGIFHWADDR,&ifr);
-
if(!err){
-
unsigned char* hw=ifr.ifr_hwaddr.sa_data;
-
printf("SIOCGIFHWADDR:%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);
-
}
-
//获得网卡映射参数 命令字SIOCGIFMAP
-
err=ioctl(s,SIOCGIFMAP,&ifr);
-
if(!err)
-
printf("SIOCGIFMAP,mem_start:%d,mem_end:%d,base_addr:%d,ifr_map:%d,dma:%d,port:%d\n",ifr.ifr_map.mem_start,ifr.ifr_map.mem_end,ifr.ifr_map.base_addr,ifr.ifr_map.irq,ifr.ifr_map.dma,ifr.ifr_map.port);
-
//获得网卡序号
-
err=ioctl(s,SIOCGIFINDEX,&ifr);
-
if(!err)
-
printf("SIOCGIFINDEX:%d\n",ifr.ifr_ifindex);
-
//获取发送队列的长度
-
err=ioctl(s,SIOCGIFTXQLEN,&ifr);
-
if(!err)
-
printf("SIOCGIFTXQLEN:%d\n",ifr.ifr_qlen);
-
//获取网络接口IP
-
struct sockaddr_in *sin=(struct sockaddr_in*)&ifr.ifr_addr;//保存的是二进制IP
-
char ip[16];//字符数组,存放字符串
-
memset(ip,0,16);
-
err=ioctl(s,SIOCGIFADDR,&ifr);
-
if(!err){
-
inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);//转换的字符串保存到ip数组中,第二个参数是要转换的二进制IP指针,第三个参数是转换完成存放IP的缓冲区,最后一个参数是缓冲区的长度
-
printf("SIOCGIFADDR:%s\n",ip);
-
}
-
//查询目标IP地址
-
err=ioctl(s,SIOCGIFDSTADDR,&ifr);
-
if(!err){
-
inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);
-
printf("SIOCGIFDSTADDR:%s\n",ip);
-
}
-
//查询子网掩码
-
err=ioctl(s,SIOCGIFNETMASK,&ifr);
-
if(!err){
-
inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);
-
printf("SIOCGIFNETMASK:%s\n",ip);
-
}
-
//设置IP地址,设置网络接口
-
inet_pton(AF_INET,"222.27.253.108",&sin->sin_addr.s_addr);//将字符串IP转换成二进制
-
err=ioctl(s,SIOCSIFADDR,&ifr);//发送设置本机ip地址请求命令
-
if(!err){
-
printf("check IP-----");
-
memset(&ifr,0,sizeof(ifr));
-
memcpy(ifr.ifr_name,"eth0",5);
-
ioctl(s,SIOCGIFADDR,&ifr);
-
inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);
-
printf("%s\n",ip);
-
}
-
//得到接口的广播地址
-
memset(&ifr,0,sizeof(ifr));
-
memcpy(ifr.ifr_name,"eth0",5);
-
ioctl(s,SIOCGIFBRDADDR,&ifr);
-
struct sockaddr_in *broadcast=(struct sockaddr_in*)&ifr.ifr_broadaddr;
-
//转换成字符串
-
inet_ntop(AF_INET,&broadcast->sin_addr.s_addr,ip,16);//inet_ntop将二进制IP转换成点分十进制的字符串
-
printf("BROADCAST IP:%s\n",ip);
-
close(s);
-
}
运行结果:
(2)查看arp高速缓存信息
-
#include <stdio.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
#include <net/if_arp.h>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <linux/sockios.h>
-
/**
-
ARP高速缓存操作,包含IP地址和硬件地址的映射表
-
操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录
-
struct arpreq{
-
struct sockaddr arp_pa;//协议地址
-
struct sockaddr arp_ha;//硬件地址
-
int arp_flags;//标记
-
struct sockaddr arp_netmask;//协议地址的子网掩码
-
char arp_dev[16];//查询网络接口的名称
-
}
-
**/
-
//根据IP地址查找硬件地址
-
int main(int argc,char*argv[]){
-
int s;
-
int err;
-
struct arpreq arpreq;
-
struct sockaddr_in *addr=(struct sockaddr_in*)&arpreq.arp_pa;//IP地址
-
s=socket(AF_INET,SOCK_DGRAM,0);
-
if(s<0)
-
perror("socket error");
-
addr->sin_family=AF_INET;
-
addr->sin_addr.s_addr=inet_addr(argv[1]);//转换成二进制IP
-
if(addr->sin_addr.s_addr==INADDR_NONE)
-
printf("IP地址格式错误\n");
-
strcpy(arpreq.arp_dev,"eth0");
-
err=ioctl(s,SIOCGARP,&arpreq);
-
if(err==-1){
-
perror("arp");
-
return -1;
-
}
-
unsigned char* hw=(unsigned char*)&arpreq.arp_ha.sa_data;//硬件地址
-
printf("%s\n",argv[1]);
-
printf("%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);
-
close(s);
-
return 0;
-
}
运行结果:
查看网关的MAC。在查看ARP高速缓存时要传入IP地址与接口信息。而获得接口信息要传入接口名ifr_name,如eth0。
总结:
本文主要介绍了获得网络接口请求信息,获得网卡设备映射属性、配置网络接口、获得ARP高速缓存等。其它ioctl函数还能对操作文件,操作I/O、操作路由等。最后对于网络接口的操作与ARP高速缓存的操作分别给出了实例。