libpcap可以列出所有網卡deivce、地址資訊。
先看一下自己主機上有哪些device:
~ % ifconfig -l lo0 gif0 stf0 en0 en1 en2 p2p0 awdl0 bridge0目前可用的網卡有這些,不同系統上有不同的名稱,以Ethernet來說:Mac OS X是
en*,Linux是eth*,而Windows是\Device\NPF_{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}。那來寫程式吧。
include的header
#include <stdio.h> #include <stdlib.h> #include <pcap.h> #include <sys/socket.h> #include <arpa/inet.h> #ifndef __linux #include <net/if.h> #include <netinet/in.h> #include <net/if_dl.h> #include <net/ethernet.h> #else /* if BSD */ #include <linux/if_ether.h> #include <netpacket/packet.h> #include <linux/if_link.h> #include <netinet/ether.h> #endif /* if linux */
#ifndef __linux ... #else ... #endif是判斷核心是BSD還是linux,Mac OS X核心算是BSD來的,CentOS核心算是linux的,當為linux會定義__linux,所以只要用一些macro就可以判斷了。有些比較底層的部分會因為核心不同實作上也會不同,所以偶爾需要靠macro的方法判斷;這程式會需要解析link層的地址,像這個就需要用這技巧了。
pcap_if_t *devices = NULL; char errbuf[PCAP_ERRBUF_SIZE];
宣告一下變數,
pcap_if_t *是主要儲存所有device資訊的結構體,本身就是一個link list的結構;char errbuf[]在一些libpcap初始化時候會需要的儲存錯誤訊息字串。結構
pcap_if_t宣告:typedef struct pcap_if pcap_if_t;
/*
* Item in a list of interfaces.
*/
struct pcap_if {
struct pcap_if *next;
char *name; /* name to hand to "pcap_open_live()" */
char *description; /* textual description of interface, or NULL */
struct pcap_addr *addresses;
bpf_u_int32 flags; /* PCAP_IF_ interface flags */
};
函數
pcap_findalldevs()能夠取得所有可用的device。//get all devices
if(-1 == pcap_findalldevs(&devices, errbuf)) {
fprintf(stderr, "pcap_findalldevs(): %s\n", errbuf);
exit(1);
}//end if
函數
pcap_findalldevs()原型:int pcap_findalldevs(pcap_if_t **alldevsp, char *errbuf);
- 返回值:成功傳回0,失敗傳回-1,錯誤訊息在errbuf。
- 參數:alldevsp介面的鏈結指標的指標。errbuf錯誤訊息。
- 功能:用來取得目前可用的device。
成功取得後就用迴圈來走訪link list吧。
//list all device
for(pcap_if_t *d = devices ; d ; d = d->next) {
printf("Device: %s\n", d->name);
if(d->description) {
printf(" Description: %s\n", d->description);
}//end if
printf(" Loopback: %s\n",(d->flags & PCAP_IF_LOOPBACK) ? "yes" : "no");
#ifdef PCAP_IF_UP
printf(" Up: %s\n",(d->flags & PCAP_IF_UP) ? "yes" : "no");
#endif /* if PCAP_IF_UP */
#ifdef PCAP_IF_RUNNING
printf(" Running: %s\n",(d->flags & PCAP_IF_RUNNING) ? "yes" : "no");
#endif /* if PCAP_IF_RUNNING */
pcap_if_t *的name成員是device的名稱,description是描述,成員flags可以用來判斷是不是loopback,在一些系統上還有PCAP_IF_UP和PCAP_IF_RUNNING可以使用。成員addresses也是一個link list結構,所以一樣用迴圈走訪。
//list all address
for(struct pcap_addr *a = d->addresses ; a ; a = a->next) {
結構
struct pcap_addr宣告:/*
* Representation of an interface address.
*/
struct pcap_addr {
struct pcap_addr *next;
struct sockaddr *addr; /* address */
struct sockaddr *netmask; /* netmask for that address */
struct sockaddr *broadaddr; /* broadcast address for that address */
struct sockaddr *dstaddr; /* P2P destination address for that address */
};
宣告一下會用到變數。
sa_family_t family = a->addr->sa_family;
char ntop_buf[256];
sa_family_t是地址類型家族,常見的有AF_INET(PF_INET)、AF_INET6(PF_INET6)、AF_LINK、AF_PACKET(PF_PACKET)以及AF_UNIX(PF_UNIX),先用變數family來記錄目前地址的類型。 if(family == AF_INET || family == AF_INET6) {
if(a->addr) {
printf(" Address: %s\n",
inet_ntop(family, &((struct sockaddr_in *)a->addr)->sin_addr, ntop_buf, sizeof(ntop_buf)));
}//end if
if(a->netmask) {
printf(" Netmask: %s\n",
inet_ntop(family, &((struct sockaddr_in *)a->netmask)->sin_addr, ntop_buf, sizeof(ntop_buf)));
}//end if
if(a->broadaddr) {
printf(" Broadcast Address: %s\n",
inet_ntop(family, &((struct sockaddr_in *)a->broadaddr)->sin_addr, ntop_buf, sizeof(ntop_buf)));
}//end if
if(a->dstaddr) {
printf(" Destination Address: %s\n",
inet_ntop(family, &((struct sockaddr_in *)a->dstaddr)->sin_addr, ntop_buf, sizeof(ntop_buf)));
}//end if
}//end else
當family為
AF_INET(表示IPv4)或AF_INET6(表示IPv6)時候用函數inet_ntop()來將網路地址格式(一個無號的32bit整數)轉成字串格式。函數
inet_ntop()原型:const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
- 返回值:成功傳回dst參數,失敗傳回NULL。
- 參數:af只有AF_INET或AF_INET6表示不是IPv4就是IPv6。src網路順序地址。dst結果字串。size結果字串陣列長度。
- 功能:將網路順序的地址轉成字串,dst陣列大小如果是IPv4可以用INET_ADDRSTRLEN,IPv6則是INET6_ADDRSTRLEN這兩個macros。
#ifdef AF_LINK
else if(family == AF_LINK && a->addr) {
struct sockaddr_dl *sdl = (struct sockaddr_dl *)a->addr;
if (sdl->sdl_family == AF_LINK && sdl->sdl_alen == ETHER_ADDR_LEN) {
printf(" Link address: %s\n", ether_ntoa((const struct ether_addr *)(sdl->sdl_data + sdl->sdl_nlen)));
//printf(" Link address: %s\n", ether_ntoa((const struct ether_addr *)LLADDR(sdl)));
}//end if
}//end if
#elif AF_PACKET
else if(family == AF_PACKET && a->addr) {
struct sockaddr_ll *sll = (struct sockaddr_ll *)a->addr;
if (sll->sll_family == AF_PACKET && sll->sll_halen == ETHER_ADDR_LEN) {
printf(" Link address: %s\n", ether_ntoa((const struct ether_addr *)sll->sll_addr));
}//end if
}//end if
#endif
函數
pcap_findalldevs()可以取得link層的地址,但是解析方法BSD和linux不太相同,BSD定義了AF_LINK而linux定義了AF_PACKET表示link層的類型,所以這邊用這兩個marcos來判斷屬於哪種類型。當是
AF_LINK類型時,宣告struct sockaddr_dl *表示link層地址,當成員sdl_family為AF_LINK且成員sdl_alen長度為6(ETHER_ADDR_LEN),表示可能為Ethernet地址,所以使用函數ether_ntoa()來將十六進位格式(Binary)轉成字串格式。但是成員sdl_data包含了device name,所以必須加上成員sdl_nlen(device name長度)才會指向地址開始的位置(或是可以使用註解起來的macros LLADDR)。函數
ether_ntoa()原型:char *ether_ntoa(const struct ether_addr *n);
- 返回值:MAC address字串,失敗傳回NULL。
- 參數:n要轉換的網路封包用的地址結構指標。
- 功能:將網路封包用的地址結構指標轉成字串。
巨集
LLADDR定義:#define LLADDR(s) ((caddr_t)((s)->sdl_data + (s)->sdl_nlen))
如果是
AF_PACKET情況,則宣告struct sockaddr_ll *,當成員sll_family為AF_PACKET且成員ssl_halen長度為6(ETHER_ADDR_LEN),就以Ethernet地址方式解析,成員sll_addr就已經是地址位置了。最後要釋放取得的device link list。
//free pcap_freealldevs(devices);
函數
pcap_freealldevs()原型:void pcap_freealldevs(pcap_if_t *alldevsp);
- 參數:alldevsp介面的鏈結指標。
- 功能:用來釋放從pcap_findalldevs()取得的device link list指標。
編譯方法用
gcc編譯:
libpcap % gcc -I/usr/local/opt/libpcap/include -Wall -std=gnu99 -L/usr/local/opt/libpcap/lib -lpcap list-devices.c -o list-devices
- -I:include header搜尋路徑;
homebrew的libpcap預設安裝在/usr/local/opt/libpcap - -L:library搜尋路徑。
- -l:要link的lib,libpcap的lib名稱為libpcap.a(或libpcap.so),所以指定-lpcap。
- -o:輸出檔名。
- -W:警告訊息,-Wall顯示所有警告。
- -std:使用的規範。
-std請使用gnu99別用c99,不然在CentOS上libpcap會無法編譯。執行結果(Mac OS X):
libpcap % ./list-devices
Device: en0
Loopback: no
Up: yes
Running: yes
Link address: 6c:40:8:bc:ae:98
Address: 0:0:fe80::6e40:8ff
Netmask: ::ffff:ffff:ffff:ffff:0:0
Address: 192.168.1.50
Netmask: 255.255.255.0
Broadcast Address: 192.168.1.255
...
執行結果(CentOS):
[root@tutu libpcap]# ./list-devices
Device: eth0
Loopback: no
Link address: 0:c:29:b0:81:f7
Address: 192.168.1.67
Netmask: 255.255.255.0
Broadcast Address: 192.168.1.255
Address: 0:0:fe80::20c:29ff
Netmask: ::ffff:ffff:ffff:ffff:0:0
...
在CentOS底下記得以root權限跑程式,因為要存取的link層需要有root權限(其實在Mac OS X上也需要,不過可以透過修改
/dev/bpf*的權限讓一般使用者也可以透過libpcap存取到link層)。Source code on Github
謝謝!寫的很好!
回覆刪除