ioctl( )函數
本函數影響由fd參數引用的一個打開的文件。
#include<unistd.h>
int ioctl( int fd, int request, .../* void *arg */ );
返回0:成功 -1:出錯
第三個參數總是一個指針,但指針的類型依賴於request參數。
我們可以把和網絡相關的請求划分為6類:
套接口操作
文件操作
接口操作
ARP高速緩存操作
路由表操作
流系統
下表列出了網絡相關ioctl請求的request參數以及arg地址必須指向的數據類型:
類別 |
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 |
|
|
套接口操作:
明確用於套接口操作的ioctl請求有三個,它們都要求ioctl的第三個參數是指向某個整數的一個指針。
SIOCATMARK: 如果本套接口的的度指針當前位於帶外標記,那就通過由第三個參數指向的整數返回一個非0值;否則返回一個0值。POSIX以函數sockatmark替換本請求。
SIOCGPGRP: 通過第三個參數指向的整數返回本套接口的進程ID或進程組ID,該ID指定針對本套接口的SIGIO或SIGURG信號的接收進程。本請求和fcntl的F_GETOWN命令等效,POSIX標准化的是fcntl函數。
SIOCSPGRP: 把本套接口的進程ID或者進程組ID設置成第三個參數指向的整數,該ID指定針對本套接口的SIGIO或SIGURG信號的接收進程,本請求和fcntl的F_SETOWN命令等效,POSIX標准化的是fcntl操作。
文件操作:
以下5個請求都要求ioctl的第三個參數指向一個整數。
FIONBIO: 根據ioctl的第三個參數指向一個0或非0值分別清除或設置本套接口的非阻塞標志。本請求和O_NONBLOCK文件狀態標志等效,而該標志通過fcntl的F_SETFL命令清除或設置。
FIOASYNC: 根據iocl的第三個參數指向一個0值或非0值分別清除或設置針對本套接口的信號驅動異步I/O標志,它決定是否收取針對本套接口的異步I/O信號(SIGIO)。本請求和O_ASYNC文件狀態標志等效,而該標志可以通過fcntl的F_SETFL命令清除或設置。
FIONREAD: 通過由ioctl的第三個參數指向的整數返回當前在本套接口接收緩沖區中的字節數。本特性同樣適用於文件,管道和終端。
FIOSETOWN: 對於套接口和SIOCSPGRP等效。
FIOGETOWN: 對於套接口和SIOCGPGRP等效。
接口配置:
得到系統中所有接口由SIOCGIFCONF請求完成,該請求使用ifconf結構,ifconf又使用ifreq
結構,如下所示:
Struct ifconf{
int ifc_len; // 緩沖區的大小
union{
caddr_t ifcu_buf; // input from user->kernel
struct ifreq *ifcu_req; // return of structures returned
}ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf //buffer address
#define ifc_req ifc_ifcu.ifcu_req //array of structures returned
#define IFNAMSIZ 16
struct ifreq{
char ifr_name[IFNAMSIZ]; // interface name, e.g., “le0”
union{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
short ifru_flags;
int ifru_metric;
caddr_t ifru_data;
}ifr_ifru;
};
#define ifr_addr ifr_ifru.ifru_addr // address
#define ifr_dstaddr ifr_ifru.ifru_dstaddr // otner end of p-to-p link
#define ifr_broadaddr ifr_ifru.ifru_broadaddr // broadcast address
#define ifr_flags ifr_ifru.ifru_flags // flags
#define ifr_metric ifr_ifru.ifru_metric // metric
#define ifr_data ifr_ifru.ifru_data // for use by interface
再調用ioctl前我們必須先分撇一個緩沖區和一個ifconf結構,然后才初始化后者。如下圖
展示了一個ifconf結構的初始化結構,其中緩沖區的大小為1024,ioctl的第三個參數指向
這樣一個ifconf結構。
ifc_len |
Ifc_buf |
1024
--------------------->緩存
假設內核返回2個ifreq結構,ioctl返回時通過同一個ifconf結構緩沖區填入了那2個ifreq結構,ifconf結構的ifc_len成員也被更新,以反映存放在緩沖區中的信息量。
接口操作:
SIOCGIFCONF請求為每個已配置的接口返回其名字以及一個套接口地址結構。我們接着可以發出多個接口類其他請求以設置或獲取每個接口的其他特征。這些請求的獲取(get)版本(SIOCGxxx)通常由netstat程序發出,設置(set)版本(SIGOCSxxx)通常由ifconfig程序發出。任何用戶都可以獲取接口信息,設置接口信息卻要求有超級用戶權限。
這些請求汲取或返回一個一個ifreq結構中的信息,而這個結構的地址則作為ioctl調用的第三個參數制定。接口總是以其名標志,在ifreq結構的ifr_name成員中指定,如le0,lo0,ppp0等。
這些請求中有許多使用套接口地址結構在應用進程和內核之間指定或返回具體接口的IP地址或地址掩碼。對於IPV4,這個地址或掩碼放在一個網際套接口地址結構的sin_addr成員中;對於IPV6,它是一個IPV6套接口地址結構的sin6_addr成員。
SIOCGIFADDR: 在ifr_addr成員中返回單播地址。
SIOCSIFADDR:用ifr_addr成員設置接口地址,這個接口的初始化函數也被調用。
SIOCGIFFLAGS:在ifr_flags成員中返回接口標志。這些接口標志的名字格式為IFF_XXX,在<net/if.h>頭文件中定義。舉例來說,這些標志指示接口是否處於UP即在工狀態(IFF_UP),是否為一個點到點接口(IFF_POINTOPOINT),是否支持廣播(IFF_BROADCAST),等等。
SIOCSIFFLAGS:用ifr_flags成員設置接口標志。
SIOCGIFDSTADDR:在ifr_dstaddr成員中返回點到點地址。
SIOCSIFDSTADDR: 在ifr_dstaddr成員中設置點到點地址
SIOCGIFBRDADDR: 在ifr_broadaddr成員中返回廣播地址。應用進程必須首先獲取接口標志,然后發出正確的請求;對於廣播接口為SIOCGIFBRDADDR,對於點到點接口為SIOCGIFDSTADDR
SIOCSIFBRDADDR:用ifr_broadaddr成員設置廣播地址。
SIOCGIFNETMASK:在ifr_addr成員中返回子網掩碼。
SIOCSIFNETMASK:在ifr_addr成員中設置子網掩碼。
SIOCGIFMETRIC:用ifr_metric成員返回接口測度。接口測度由內核為每個接口維護,不過使用他的是路由守護進程routed。接口測度被routed加到跳數上。
SIOCSIFMETRIC:用ifr_metric成員設置接口的路由測度。
ARP高速緩存操作
ARP告訴緩存也通過ioctl函數操縱。使用路由域套接口的系統往往改用路由套接口訪問
ARP高速緩存。這些請求使用如下的arpreq結構,定義在<net/if_arp.h>
struct arpreq {
struct sockaddr arp_pa;
struct sockaddr arp_ha;
int arp_flags;
};
#define ATF_INUSE 0x01 //entry in use
#define ATF_COM 0x02 //completed entry (hardware addr valid)
#define ATF_PERM 0x04 // permanent entry
#define ATF_PUBL 0x08 // published entry (respond for other host )
Ioctl的第三個必須指向某個arpreq結構,操縱ARP高速緩存的ioctl請求有以下三個:
SIOCSARP: 把一個新的表項加入ARP告訴緩存中區,或者修改其中已經存在的一個表項,其中arp_pa是一個含有IP地址的網際套接口地址結構,arp_ha則是一個通用套接口地址結構,他的sa_family值為AF_unspec,sa_data中含有硬件地址(例如6直接的以太網地址)。ATF_PERM和ATF_PUBL這兩個標志也可以由應用進程指定。另外兩個標志(ATF_INUSE和ATF_COM)則由內核設置。
SIOCDARP: 從ARP告訴緩存中刪除一個表項。調用者指定要刪除表項的網際地址。
SIOCGARP: 從ARP高速緩存中獲取一個表項。調用者指定網際地址,相應的硬件地址(例外以太網地址)隨標志一起返回。
只有超級用戶才能增加或刪除表項。這三個請求通常由arp程序發出。
注意ioctl沒有辦法列出ARP高速緩存中的所有表項。當指定-a標志執行arp命令時,大多
數版本的arp程序通過讀取內核的內存( /dev/kmem )獲得ARP高速緩存的當前內容。
路由表操作
有些系統提供2個用於操縱路由表的ioctl請求。這2個請求要求ioctl的第三個參數是指向
某個rtentry結構的一個指針,該結構定義在<net/route.h>頭文件中。這些請求通常由route
程序發出。只有超級用戶才能發出這些請求。
SIOCADDRT:往路由表中增加一個表項
SIOCDELRT:從路由表中刪除一個表項
Ioctl沒有辦法列出路由表中的所有表項。這個操作通常由netstat程序在指定-r標志自行四
完成。netstat程序通過讀取內核的內存 (/dev/kmem)獲得整個路由表。用sysctl同樣可
以做到。
如下是chinaunix論壇飛灰橙給出的一個例子。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
void err_sys(const char *errmsg);
int main(void)
{
int i, sockfd;
struct ifreq ifr;
struct arpreq arpr;
strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));
if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
err_sys("socket");
/* get ip address */
if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1)
err_sys("1-ioctl");
/* get hardware address */
ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1)
err_sys("2-ioctl");
/* output hardware address */
for (i = 0; i < 6; i++) {
unsigned char *mac = (unsigned char *) ifr.ifr_hwaddr.sa_data;
printf("%x", (int) mac[i]);
if (i != 5)
printf("%c", ':');
}
exit(0);
}
void err_sys(const char *errmsg)
{
perror(errmsg);
exit(1);
}