1. 程式人生 > >UNIX網路程式設計——ioctl 函式的用法詳解

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快取記憶體的操作分別給出了例項。