天天看點

獲得Unix/Linux系統中的IP、MAC位址等資訊

作者: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的關系可以參考下圖:

獲得Unix/Linux系統中的IP、MAC位址等資訊

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操作