作者:diaoyf | 文章來源:http://programmerdigest.cn
實際環境和特殊需求往往會将簡單問題複雜化,比如計算機IP位址,對于一個連接配接中socket,可以直接獲得本端和對端的IP、端口資訊。但在一些特殊場合我們可能需要更多的資訊,比如系統中有幾塊網卡,他們的Mac位址是多少,每塊網卡配置設定了幾個IP(一個網卡對應多個IP)等等。
這些資訊往往需要通過ifconfig指令來獲得,對于程式員來說,在代碼中調用外部的shell指令可不是個最佳方案,因為沒人能保障不同平台、不同版本的ifconfig指令輸出的格式是一緻的。本篇文章中将介紹通過ioctl函數實作上述需求。
#include <sys/ioctl.h> int ioctl(int fd, int request, … /* void *arg */); 傳回:成功傳回0,失敗傳回-1
ioctl函數的參數隻有3個,但卻是Unix中少有的幾個“家族類”複雜函數,這裡摘錄一段《Unix網絡程式設計》一書中對ioctl函數的描述:
在傳統上ioctl函數是用于那些普遍使用、但不适合歸入其他類别的任何特殊的系統接口……網絡程式(一般是伺服器程式)中ioctl常用于在程式啟動時獲得主機上所有接口的資訊:接口的位址、接口是否支援廣播、是否支援多點傳播,等等。
ioctl函數的第一個參數fd,可以表示一個打開的檔案(檔案句柄)或網絡套接字,第二個和第三個參數展現了函數的家族特色,參數二request根據函數功能分類定義了多組宏,而參數三總是一個指針,指針的類型依賴于參數二request。因為ioctl的種類實在太多,這裡隻列出和本文相關的幾個參數定義:
分類
參數二(宏)
參數三
描述
接口
SIOCGIFCONF
struct ifconf
獲得所有接口清單
SIOCGIFADDR
struct ifreq
獲得接口位址
SIOCGIFFLAGS
獲得接口标志
SIOCGIFBRDADDR
獲得廣播位址
SIOCGIFNETMASK
獲得子網路遮罩
上表中列出了兩個相關的結構體:struct ifconf 和 struct ifreq,要了解ioctl函數的具體運用,首先要了解這兩個結構:
/* net/if.h */
struct ifconf
{
int ifc_len; /* Size of buffer. */
union
{
__caddr_t ifcu_buf;
struct ifreq *ifcu_req;
} ifc_ifcu;
};
struct ifreq
# define IFHWADDRLEN 6
# define IFNAMSIZ IF_NAMESIZE
char ifrn_name[IFNAMSIZ]; /* Interface name, e.g. "en0". */
} ifr_ifrn;
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short int ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
__caddr_t ifru_data;
} ifr_ifru;
struct ifconf的第二個元素ifc_ifcu是一個聯合,是指向struct ifreq結構的位址,通常是一組struct ifreq結構空間(每一個描述一個接口),struct ifconf的第一個元素ifc_len描述了struct ifreq結構空間的大小;結構struct ifreq也有兩個元素,第一個元素ifr_ifrn内含一個字元串,用來描述接口的名稱,比如“eth0″、”wlan0”等,第二個元素是聯合,比較複雜,用來描述套接口的位址結構。
struct ifconf 和 struct ifreq的關系可以參考下圖:
ioctl函數中的struct ifconf 和 struct ifreq結構關系
通常運用ioctl函數的第一步是從核心擷取系統的所有接口,然後再針對每個接口擷取其位址資訊。擷取所有接口通過SIOCGIFCONF請求來實作:
獲得了接口清單,就可以通過struct ifconf結構中*ifcu_req的指針得到struct ifreq結構數組的位址,通過周遊獲得每隔接口的詳細位址資訊:
printf("接口名稱:%s\n", ifrs[n].ifr_name); /* 接口名稱 */
/* 獲得IP位址 */
ioctl(fd, SIOCGIFADDR, (char *) &ifrs[n]);
printf("IP位址:%s\n",
(char*)inet_ntoa(((struct sockaddr_in*) (&ifrs[n].ifr_addr))->sin_addr));
/* 獲得子網路遮罩 */
ioctl(fd, SIOCGIFNETMASK, (char *) &ifrs[n]);
printf("子網路遮罩:%s\n",
/* 獲得廣播位址 */
ioctl(fd, SIOCGIFBRDADDR, (char *) &ifrs[n]);
printf("廣播位址:%s\n",
/* 獲得MAC位址 */
ioctl(fd, SIOCGIFHWADDR, (char *) &ifrs[n]);
printf("MAC位址:%02x:%02x:%02x:%02x:%02x:%02x\n",
(unsigned char) ifrs[n].ifr_hwaddr.sa_data[0],
(unsigned char) ifrs[n].ifr_hwaddr.sa_data[1],
(unsigned char) ifrs[n].ifr_hwaddr.sa_data[2],
(unsigned char) ifrs[n].ifr_hwaddr.sa_data[3],
(unsigned char) ifrs[n].ifr_hwaddr.sa_data[4],
(unsigned char) ifrs[n].ifr_hwaddr.sa_data[5]);
最後,給出一個參考程式代碼。
ioctl函數沒有納入POXIS規範,各系統對ioctl的實作也不盡相同,下面的代碼在我的Ubuntu10.04 linux上可執行通過,但在其他Unix系統上不一定能夠通過編譯,例如在Power AIX 5.3上需要将獲得MAC位址的那段代碼注釋掉。
#include <arpa/inet.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>
#define MAXINTERFACES 16 /* 最大接口數 */
int fd; /* 套接字 */
int if_len; /* 接口數量 */
struct ifreq buf[MAXINTERFACES]; /* ifreq結構數組 */
struct ifconf ifc; /* ifconf結構 */
int main(argc, argv)
/* 建立IPv4的UDP套接字fd */
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
perror("socket(AF_INET, SOCK_DGRAM, 0)");
return -1;
}
/* 初始化ifconf結構 */
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = (caddr_t) buf;
/* 獲得接口清單 */
if (ioctl(fd, SIOCGIFCONF, (char *) &ifc) == -1)
perror("SIOCGIFCONF ioctl");
if_len = ifc.ifc_len / sizeof(struct ifreq); /* 接口數量 */
printf("接口數量:%d\n\n", if_len);
while (if_len– > 0) /* 周遊每個接口 */
printf("接口:%s\n", buf[if_len].ifr_name); /* 接口名稱 */
/* 獲得接口标志 */
if (!(ioctl(fd, SIOCGIFFLAGS, (char *) &buf[if_len])))
{
/* 接口狀态 */
if (buf[if_len].ifr_flags & IFF_UP)
{
printf("接口狀态: UP\n");
}
else
printf("接口狀态: DOWN\n");
}
else
char str[256];
sprintf(str, "SIOCGIFFLAGS ioctl %s", buf[if_len].ifr_name);
perror(str);
/* IP位址 */
if (!(ioctl(fd, SIOCGIFADDR, (char *) &buf[if_len])))
printf("IP位址:%s\n",
(char*)inet_ntoa(((struct sockaddr_in*) (&buf[if_len].ifr_addr))->sin_addr));
sprintf(str, "SIOCGIFADDR ioctl %s", buf[if_len].ifr_name);
/* 子網路遮罩 */
if (!(ioctl(fd, SIOCGIFNETMASK, (char *) &buf[if_len])))
printf("子網路遮罩:%s\n",
/* 廣播位址 */
if (!(ioctl(fd, SIOCGIFBRDADDR, (char *) &buf[if_len])))
printf("廣播位址:%s\n",
/*MAC位址 */
if (!(ioctl(fd, SIOCGIFHWADDR, (char *) &buf[if_len])))
printf("MAC位址:%02x:%02x:%02x:%02x:%02x:%02x\n\n",
(unsigned char) buf[if_len].ifr_hwaddr.sa_data[0],
(unsigned char) buf[if_len].ifr_hwaddr.sa_data[1],
(unsigned char) buf[if_len].ifr_hwaddr.sa_data[2],
(unsigned char) buf[if_len].ifr_hwaddr.sa_data[3],
(unsigned char) buf[if_len].ifr_hwaddr.sa_data[4],
(unsigned char) buf[if_len].ifr_hwaddr.sa_data[5]);
sprintf(str, "SIOCGIFHWADDR ioctl %s", buf[if_len].ifr_name);
}//–while end
//關閉socket
close(fd);
return 0;
}
在我的系統上,程式輸出:
接口數量:4 接口:wlan0 接口狀态: UP IP位址:192.168.1.142 子網路遮罩:255.255.255.0 廣播位址:192.168.1.255 MAC位址:00:14:a5:65:47:57 接口:eth0:0 IP位址:192.168.4.113 廣播位址:192.168.4.255 MAC位址:00:14:c2:e5:45:57 接口:eth0 IP位址:192.168.4.111 接口:lo IP位址:127.0.0.1 子網路遮罩:255.0.0.0 廣播位址:0.0.0.0 MAC位址:00:00:00:00:00:00
從輸出可以看出,系統有4個接口,”wlan0″表示第一塊無線網卡接口,”eth0″(IP位址:192.168.4.111)表示第一塊連線網卡接口(我們最長用的RJ45連接配接口網卡),”lo”是回路位址接口(我們常用的127.0.0.1)。
注意:”eth0:0″(IP位址:192.168.4.113)是有線網卡的别名******網卡綁定多個IP),這是為了測試這個參考程式特意在eth0上添加的一個IP位址。
參考資料:《Unix網絡程式設計》第16章 ioctl操作