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
謝謝!寫的很好!
回覆刪除