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的地址,所以變數hintsai_family成員指定成AF_UNSPEC,表示不特別指定要哪種地址,也能夠設定成AF_INETAF_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地址與網路順序地址作互相轉換這是撰寫封包的基本功,所以要好好地去瞭解每個細節,並選擇時機使用適當的函數。

results matching ""

    No results matching ""