libnet/address_conversion.c
在正式開始撰寫封包前,必須先瞭解一些地址轉換函數,在libpcap篇已經有用到inet_ntop()
將網路格式的地址轉成字串,在撰寫封包時需要將字串轉成網路格式,有時候甚至需要將主機名稱轉成IP地址,所以這裡要來講兩種方式,一個是本身系統提供的方式以及libnet的方式。
Source Code
//
// address_conversion.c
// 功能:使用系統提供的函數轉換地址以及libnet所提供的地址轉換函數。
// Created by 聲華 陳 on 2016/01/21.
//
#include <stdio.h>
#include <stdlib.h>
#include <libnet.h>
#include <netdb.h>
int main(int argc, const char * argv[]) {
struct addrinfo hints, *res;
char ntop_buf[256];
int status;
char *hostname = "www.facebook.com";
char *ip_address = "31.13.78.35";
struct sockaddr_in addr;
printf("====== System ======\n");
//hostname to ip address
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
if((status = getaddrinfo(hostname, NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
}//end if
else {
printf("Using getaddrinfo()\n");
printf("Hostname: %s\n", hostname);
for(struct addrinfo *p = res ; p ; p = p->ai_next) {
void *addr = NULL;
char *type = "";
if(p->ai_family == AF_INET) {
addr = &((struct sockaddr_in *)p->ai_addr)->sin_addr;
type = "IPv4";
}//end if
else if(p->ai_family == AF_INET6) {
addr = &((struct sockaddr_in6 *)p->ai_addr)->sin6_addr;
type = "IPv6";
}//end if
else {
continue;
}//end else
inet_ntop(p->ai_family, addr, ntop_buf, sizeof(ntop_buf));
printf("%s: %s\n", type, ntop_buf);
}//end for
freeaddrinfo(res);
}//end else
printf("\n");
//ip address to hostname
memset(&addr, 0, sizeof(addr));
inet_pton(AF_INET, ip_address, &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_len = sizeof(addr);
addr.sin_port = htons(443);
char host[1024];
char service[20];
if((status = getnameinfo((struct sockaddr *)&addr,
sizeof(addr),
host, sizeof(host),
service, sizeof(service),
NI_NOFQDN)) != 0) {
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(status));
}//end if
else {
printf("Using getnameinfo()\n");
printf("IP address: %s\n", ip_address);
printf("Hostname: %s\n", host);
printf("Service: %s\n", service);
}//end else
printf("\n====== libnet ======\n");
char errbuf[LIBNET_ERRBUF_SIZE];
libnet_t *handle = NULL;
u_int32_t temp_addr;
char *temp_ptr = NULL;
struct libnet_in6_addr ip6;
char ip6_address[INET6_ADDRSTRLEN];
handle = libnet_init(LIBNET_NONE, NULL, errbuf);
if(!handle) {
fprintf(stderr, "libnet_init: %s\n", errbuf);
exit(1);
}//end if
//resolve hostname and convert to integer
printf("libnet_name2addr4() with LIBNET_RESOLVE\n");
temp_addr = libnet_name2addr4(handle, hostname, LIBNET_RESOLVE);
printf("Convert %s to %#010x\n\n", hostname, temp_addr);
//ip address integer to hostname
printf("libnet_addr2name4() with LIBNET_RESOLVE\n");
temp_ptr = libnet_addr2name4(temp_addr, LIBNET_RESOLVE);
printf("Convert %#010x to %s\n\n", temp_addr, temp_ptr);
//convert ip address string to integer
printf("libnet_name2addr4() with LIBNET_DONT_RESOLVE\n");
temp_addr = libnet_name2addr4(handle, ip_address, LIBNET_DONT_RESOLVE);
printf("Convert %s to %#010x\n\n", ip_address, temp_addr);
//ip address integer to hostname
printf("libnet_addr2name4() with LIBNET_DONT_RESOLVE\n");
temp_ptr = libnet_addr2name4(temp_addr, LIBNET_DONT_RESOLVE);
printf("Convert %#010x to %s\n\n", temp_addr, temp_ptr);
printf("Current addresses :\n");
//get current ipv4 address
if((temp_addr = libnet_get_ipaddr4(handle)) != -1) {
temp_ptr = libnet_addr2name4(temp_addr, LIBNET_DONT_RESOLVE);
printf("IPv4: %s\n", temp_ptr);
inet_ntop(AF_INET, &temp_addr, ntop_buf, sizeof(ntop_buf));
printf("IPv4: %s\n", ntop_buf);
}//end if
//get current ipv6 address
ip6 = libnet_get_ipaddr6(handle);
libnet_addr2name6_r(ip6, LIBNET_DONT_RESOLVE, ip6_address, sizeof(ip6_address));
printf("IPv6: %s\n", ip6_address);
inet_ntop(AF_INET6, &ip6, ntop_buf, sizeof(ntop_buf));
printf("IPv6: %s\n", ntop_buf);
printf("MAC address: ");
struct libnet_ether_addr *mac_address =
libnet_get_hwaddr(handle);
for(int i = 0 ; i < 6; i++) {
if(i != 0) {
printf(":");
}
printf("%02x", mac_address->ether_addr_octet[i]);
}//end for
printf("\n\nUsing libnet_hex_aton()\n");
int len;
u_int8_t *s = libnet_hex_aton("12:34:56:78:90:ab:cd:ef", &len);
for(int i = 0 ; i < len ; i++) {
if(i != 0) {
printf(":");
}
printf("%02x", s[i]);
}//end for
if(s) {
free(s);
}//end if
//current choose interface
printf("\n\nlibnet choose: %s\n", libnet_getdevice(handle));
//free
libnet_destroy(handle);
return 0;
}
結果
libnet % sudo ./address_conversion
====== System ======
Using getaddrinfo()
Hostname: www.facebook.com
IPv4: 31.13.78.35
IPv4: 31.13.78.35
IPv6: 2a03:2880:f00c:114:face:b00c::25de
IPv6: 2a03:2880:f00c:114:face:b00c::25de
Using getnameinfo()
IP address: 31.13.78.35
Hostname: edge-star-mini-shv-01-sin4.facebook.com
Service: https
====== libnet ======
libnet_name2addr4() with LIBNET_RESOLVE
Convert www.facebook.com to 0x234e0d1f
libnet_addr2name4() with LIBNET_RESOLVE
Convert 0x234e0d1f to edge-star-mini-shv-01-sin4.facebook.com
libnet_name2addr4() with LIBNET_DONT_RESOLVE
Convert 31.13.78.35 to 0x234e0d1f
libnet_addr2name4() with LIBNET_DONT_RESOLVE
Convert 0x234e0d1f to 31.13.78.35
Current addresses :
IPv4: 192.168.1.100
IPv4: 192.168.1.100
IPv6: fe80::6e40:8ff:febc:ae98
IPv6: fe80::6e40:8ff:febc:ae98
MAC address: 6c:40:08:bc:ae:98
Using libnet_hex_aton()
12:34:56:78:90:ab:cd:ef
libnet choose: en0
分析
程式分成兩部份,一個是系統部分一個是libnet,先來看系統提供的部分。
主機名稱轉IP地址。
//hostname to ip address
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
if((status = getaddrinfo(hostname, NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
}//end if
...略
我們希望可以從主機名稱取得IPv4或IPv6的地址,所以變數hints
的ai_family
成員指定成AF_UNSPEC
,表示不特別指定要哪種地址,也能夠設定成AF_INET
或AF_INET6
來指定IPv4或IPv6。
函數getaddrinfo()
主要是將主機名稱轉成IP地址或是準備好連線對方主機的前置作業,因為我們不需要查詢服務訊息,所以第二個參數設定NULL就好。
...略
else {
printf("Using getaddrinfo()\n");
printf("Hostname: %s\n", hostname);
for(struct addrinfo *p = res ; p ; p = p->ai_next) {
void *addr = NULL;
char *type = "";
if(p->ai_family == AF_INET) {
addr = &((struct sockaddr_in *)p->ai_addr)->sin_addr;
type = "IPv4";
}//end if
else if(p->ai_family == AF_INET6) {
addr = &((struct sockaddr_in6 *)p->ai_addr)->sin6_addr;
type = "IPv6";
}//end if
else {
continue;
}//end else
inet_ntop(p->ai_family, addr, ntop_buf, sizeof(ntop_buf));
printf("%s: %s\n", type, ntop_buf);
}//end for
freeaddrinfo(res);
}//end else
當函數返回正確後,就來將取回的地址資訊列印出來,變數res
是取回的資訊鏈結串列指標,所以利用一個迴圈將每個結構都跑一遍。
利用結構內的成員ai_family
就可以知道每個結構的地址類型,利用變數addr
指向地址開頭位置後,就可以使用函數inet_ntop()
來轉換成字串並列印出來。
IP地址轉主機名稱。
//ip address to hostname
memset(&addr, 0, sizeof(addr));
inet_pton(AF_INET, ip_address, &addr.sin_addr);
addr.sin_family = AF_INET;
addr.sin_len = sizeof(addr);
addr.sin_port = htons(443);
char host[1024];
char service[20];
if((status = getnameinfo((struct sockaddr *)&addr,
sizeof(addr),
host, sizeof(host),
service, sizeof(service),
NI_NOFQDN)) != 0) {
fprintf(stderr, "getnameinfo: %s\n", gai_strerror(status));
}//end if
else {
printf("Using getnameinfo()\n");
printf("IP address: %s\n", ip_address);
printf("Hostname: %s\n", host);
printf("Service: %s\n", service);
}//end else
我們要取得主機名稱前,要先把Socket地址的成員給填寫完,變數addr
是IPv4的地址,成員sin_family
表示他是IPv4的地址,成員sin_port
是用來等等取回來的服務名稱(可有可無)。
函數getnameinfo()
前兩個參數就給Socket地址的訊息,第三四參數是主機名稱的部分,第五六參數是服務名稱的部分,Flags當是0的時候,如果找不到主機名稱則變數host
是空的,而設定成NI_NOFQDN
時,當找不到主機名稱的時候,變數host
會填入字串的IP地址。
接著進入libnet部分。
handle = libnet_init(LIBNET_NONE, NULL, errbuf);
if(!handle) {
fprintf(stderr, "libnet_init: %s\n", errbuf);
exit(1);
}//end if
首先使用函數libnet_init()
先開啟一個libnet handle,第一個參數使用LIBNET_NONE
,因為我們沒有藥送出封包,單純使用地址轉換函數,第二個參數我們一樣不指定Interface。
接下來依照LIBNET_RESOLVE
以及LIBNET_DONT_RESOLVE
分開講解。
主機名稱轉成網路順序地址。
//resolve hostname and convert to integer
printf("libnet_name2addr4() with LIBNET_RESOLVE\n");
temp_addr = libnet_name2addr4(handle, hostname, LIBNET_RESOLVE);
printf("Convert %s to %#010x\n\n", hostname, temp_addr);
網路連線時通常會拿到主機名稱,但我們需要取得網路順序的IPv4地址才能夠連線,所以呼叫函數libnet_name2addr4()
將主機名稱轉成IPv4地址,第二個參數給要轉換的主機名稱,第三個參數使用LIBNET_RESOLVE
表示需要解析。函數回傳值就是網路順序的地址,實際上就是一個unsigned的32位元int。
網路順序地址查詢主機名稱。
//ip address integer to hostname
printf("libnet_addr2name4() with LIBNET_RESOLVE\n");
temp_ptr = libnet_addr2name4(temp_addr, LIBNET_RESOLVE);
printf("Convert %#010x to %s\n\n", temp_addr, temp_ptr);
如果要透過網路順序的地址來查詢主機名稱,可以使用函數libnet_addr2name4()
完成,一樣需要解析主機名稱所以第二個參數要給LIBNET_RESOLVE
。
IPv4地址轉成網路順序地址。
//convert ip address string to integer
printf("libnet_name2addr4() with LIBNET_DONT_RESOLVE\n");
temp_addr = libnet_name2addr4(handle, ip_address, LIBNET_DONT_RESOLVE);
printf("Convert %s to %#010x\n\n", ip_address, temp_addr);
有時候拿到的主機訊息已經是IP地址了,這次的libnet_name2addr4()
的第三個參數只要LIBNET_DONT_RESOLVE
就可以直接轉成網路順序地址,效果等同於函數inet_pton()
。
網路順序地址轉成查詢主機名稱(但不解析)。
//ip address integer to hostname
printf("libnet_addr2name4() with LIBNET_DONT_RESOLVE\n");
temp_ptr = libnet_addr2name4(temp_addr, LIBNET_DONT_RESOLVE);
printf("Convert %#010x to %s\n\n", temp_addr, temp_ptr);
函數libnet_addr2name()
第二個參數如果給LIBNET_DONT_RESOLVE
效果等同於將網路順序地址轉成IP地址,與函數inet_ntop()
相同。
轉換地址部分主要就那些函數,接著是取得本地的地址訊息。
if((temp_addr = libnet_get_ipaddr4(handle)) != -1) {
temp_ptr = libnet_addr2name4(temp_addr, LIBNET_DONT_RESOLVE);
printf("IPv4: %s\n", temp_ptr);
inet_ntop(AF_INET, &temp_addr, ntop_buf, sizeof(ntop_buf));
printf("IPv4: %s\n", ntop_buf);
}//end if
函數libnet_get_ipaddr4()
可以取得變數handle
所開啟Interface的IPv4地址,但是在呼叫libnet_init()
時並沒有指定Interface,所以它會自動抓取一個Interface。
取得網路順序地址後使用函數libnet_addr2name4()
和函數inet_ntop()
來將網路順序地址轉換成字串,可以比較差別。
//get current ipv6 address
ip6 = libnet_get_ipaddr6(handle);
libnet_addr2name6_r(ip6, LIBNET_DONT_RESOLVE, ip6_address, sizeof(ip6_address));
printf("IPv6: %s\n", ip6_address);
inet_ntop(AF_INET6, &ip6, ntop_buf, sizeof(ntop_buf));
printf("IPv6: %s\n", ntop_buf);
除了IPv4地址外,也能夠抓取IPv6地址,函數libnet_get_ipaddr6()
可以取得變數handle
所開啟Interface的IPv6地址。一樣使用函數libnet_addr2name6_r()
和函數inet_ntop()
來將網路順序地址轉換成字串,而函數libnet_addr2name6_r()
用法與函數libnet_addr2name4()
類似,就不再另外解釋。
printf("MAC address: ");
struct libnet_ether_addr *mac_address =
libnet_get_hwaddr(handle);
for(int i = 0 ; i < 6; i++) {
if(i != 0) {
printf(":");
}
printf("%02x", mac_address->ether_addr_octet[i]);
}//end for
libnet也能夠透過函數libnet_get_hwaddr()
取得乙太網路地址(MAC Address),這邊就直接列印出來,當然也可以用函數ether_ntoa()
或libpcap/dump_ethernet.c所用的函數mac_ntoa()
來轉換,再列印出來。
printf("\n\nUsing libnet_hex_aton()\n");
int len;
u_int8_t *s = libnet_hex_aton("12:34:56:78:90:ab:cd:ef", &len);
for(int i = 0 ; i < len ; i++) {
if(i != 0) {
printf(":");
}
printf("%02x", s[i]);
}//end for
if(s) {
free(s);
}//end if
libnet也提供將MAC Address字串轉成網路封包可以用地址指標(二進位),使用函數libnet_hex_aton()
轉換,這邊比較特別的是這個地址長度並沒有限制,只要用分號:
隔開即可,所以第二個參數就是傳回幾個byte。
使用完後一定要呼叫free()
釋放掉空間。
//current choose interface
printf("\n\nlibnet choose: %s\n", libnet_getdevice(handle));
前面呼叫過函數libnet_get_ipaddr4()
,所以libnet已經自動抓取一個Interface,呼叫函數libnet_getdevice()
可以取得Interface。
//free
libnet_destroy(handle);
最後記得要釋放掉資源。
結語
會將主機名稱或是IP地址與網路順序地址作互相轉換這是撰寫封包的基本功,所以要好好地去瞭解每個細節,並選擇時機使用適當的函數。