libnet/ssdp.c

SSDP

SSDP(Simple Service Discovery Protocol),一種應用層協定,是隨插即用(UPnP,Universal Plug and Play)技術核心協定之一,使用UDP,Port1900。

SSDP應用在現代的路由器、電視、印表機...等,可以說是幾乎身邊的電器3C產品都有這項技術。在封包層面支援UPnP的主機利用群播到239.255.255.250的方式,告訴整個區域網路下的主機們有這項服務。

我們可以群播出一個SSDP Discover封包,只要是支援UPnP服務的主機會傳回它所提供的服務。

在2013-2015年間,SSDP變成全世界DDoS(Distributed Denial of Service)攻擊方式排行第一名,SSDP能夠被放大30倍左右,雖然低於NTP(Network Time Protocol)很多(NTP能夠被放大500倍以上),但是由於現代很多網路設備都有UPnP技術,所以數量上SSDP能夠佔不少優勢,講白話就是你可能會被你家的冰箱DDoS。

這個程式會發出一個SSDP Discover封包,並且接收傳回的封包,最後會計算被放大多少倍。程式同時使用了libnet和libpcap,所以編譯的時候記得要加上libpcap。

Source Code

//
//  ssdp.c
//  功能:送出SSDP Discover封包並等待回覆。
//  Created by 聲華 陳 on 2016/03/06.
//

#define __FAVOR_BSD
#include <stdio.h>
#include <stdlib.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <pcap.h>
#include <libnet.h>

void usage(const char *cmd);
u_int32_t ip_aton(const char *ip_address);
char *ip_ntoa(u_int32_t i);
int datalink_length(int dlt);
pcap_t *open_pcap_handle(char *device, int timeout, char *filter_expression);
libnet_t *open_libnet_handle(char *device);

int main(int argc, const char * argv[]) {

    int c;
    char *device = NULL;
    pcap_t *pcap_handle = NULL;
    libnet_t *libnet_handle = NULL;
    int timeout = 200; //0.2s
    libnet_ptag_t tag;
    u_int32_t src_ip = 0;
    u_int32_t dst_ip = ip_aton("239.255.255.250");
    u_int16_t sport;
    int send_length = 0, recv_length = 0;
    struct pcap_pkthdr *header = NULL;
    const u_char *content = NULL;
    int dlt;
    int dlt_length;
    char ssdp[] =
    "M-SEARCH * HTTP/1.1\r\n"
    "HOST: 239.255.255.250:1900\r\n"
    "MAN: \"ssdp:discover\"\r\n"
    "MX: 2\r\n"
    "ST: ssdp:all\r\n"
    "\r\n";
    int ssdp_length = strlen(ssdp);

    //parse argument
    opterr = 0; //don't show default error message
    while((c = getopt(argc, (char * const *)argv, "i:s:d:t:")) != EOF) {
        switch (c) {
            case 'i':
                device = optarg;
                break;

            case 's':
                src_ip = ip_aton(optarg);
                break;

            case 'd':
                dst_ip = ip_aton(optarg);
                break;

            case 't':
                timeout = atoi(optarg);
                if(timeout == 0) {
                    fprintf(stderr, "Invalid timeout value: %s\n", optarg);
                    exit(1);
                }//end if
                break;

            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
        }//end switch
    }//end while

    if(!device) {
        usage(argv[0]);
    }//end if


    //init pcap
    pcap_handle = open_pcap_handle(device, timeout, "udp src port 1900");
    if(!pcap_handle) {
        goto BYE;
    }//end if

    //get data-link length
    dlt = pcap_datalink(pcap_handle);
    dlt_length = datalink_length(dlt);
    if(dlt_length == -1) {
        fprintf(stderr, "No support datalink type: %s(%s)\n",
                pcap_datalink_val_to_name(dlt), pcap_datalink_val_to_description(dlt));
        goto BYE;
    }//end if

    //init libnet
    libnet_handle = open_libnet_handle(device);
    if(!libnet_handle) {
        goto BYE;
    }//end if

    //if source ip address is 0, assign current ip address
    if(src_ip == 0) {
        src_ip = libnet_get_ipaddr4(libnet_handle);
        if(src_ip == -1) {
            fprintf(stderr, "libnet_get_ipaddr4: %s\n", libnet_geterror(libnet_handle));
            goto BYE;
        }//end if fail
    }//end if

    //init seed
    libnet_seed_prand(libnet_handle);
    sport = libnet_get_prand(LIBNET_PRu16) % (65535-49152+1)+(49152);

    //build udp with ssdp
    tag = libnet_build_udp(sport,
                           //source port
                           1900,
                           //destination port
                           LIBNET_UDP_H + ssdp_length,
                           //length
                           0,
                           //checksum
                           (u_char *)ssdp,
                           ssdp_length,
                           libnet_handle,
                           LIBNET_PTAG_INITIALIZER);

    if(tag == -1) {
        fprintf(stderr, "libnet_build_udp: %s\n", libnet_geterror(libnet_handle));
        goto BYE;
    }//end if

    //build ip
    tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_UDP_H + ssdp_length,
                            //total length, 28 + payload length
                            0,
                            //type of service
                            libnet_get_prand(LIBNET_PRu16),
                            //id, rand
                            0,
                            //fragment offset
                            64,
                            //time to live
                            IPPROTO_UDP,
                            //procotol, 17
                            0,
                            //checksum, auto calculate
                            src_ip,
                            //source ip address
                            dst_ip,
                            //destination ip address
                            NULL,
                            0,
                            libnet_handle,
                            LIBNET_PTAG_INITIALIZER);

    if(tag == -1) {
        fprintf(stderr, "libnet_autobuild_ipv4: %s\n", libnet_geterror(libnet_handle));
        goto BYE;
    }//end if

    //send ssdp
    send_length = libnet_write(libnet_handle);
    if(send_length == -1) {
        fprintf(stderr, "libnet_write: %s\n", libnet_geterror(libnet_handle));
        goto BYE;
    }//end if


    //now listen response
    while(pcap_next_ex(pcap_handle, &header, &content) == 1) {
        struct ip *ip = (struct ip *)(content + dlt_length);
        char *src_ip = ip_ntoa(ip->ip_src.s_addr);
        struct udphdr *udp = (struct udphdr *)(content + dlt_length + (ip->ip_hl << 2));
        int ssdp_length = ntohs(udp->uh_ulen) - LIBNET_UDP_H;
        //int ssdp_length = header->caplen - dlt_length - (ip->ip_hl << 2) - LIBNET_UDP_H;
        char *ssdp_data = (char *)(content + dlt_length + (ip->ip_hl << 2) + LIBNET_UDP_H);
        char buffer[65535] = {};

        //check if is not udp
        if(ip->ip_p != IPPROTO_UDP) {
            continue;
        }//end if

        //copy ssdp data
        memcpy(buffer, ssdp_data, ssdp_length);
        buffer[ssdp_length] = '\0';

        //print result
        printf("Source IP Address: %s\n", src_ip);
        printf("SSDP Response:\n%s", buffer);

        //add receive length
        recv_length += header->caplen - dlt_length;
    }//end while read packet


    //stats
    printf("\nSent: %d bytes, Received: %d bytes\n"
           "Increased: %.2f times\n",
           send_length, recv_length, recv_length/(float)send_length);


    //free
    if(pcap_handle) {
        pcap_close(pcap_handle);
    }//end if
    if(libnet_handle) {
        libnet_destroy(libnet_handle);
    }//end if

    return 0;

BYE:
    //free
    if(pcap_handle) {
        pcap_close(pcap_handle);
    }//end if
    if(libnet_handle) {
        libnet_destroy(libnet_handle);
    }//end if

    return 1;
}

void usage(const char *cmd) {
    printf("Usage: %s <-i Interface> [-s Source IP Address] [-d Destination IP Address] [-t Timeout ms]\n", cmd);
    exit(1);
}//end usage

u_int32_t ip_aton(const char *ip_address) {
    u_int32_t ip_integer;
    if(1 != inet_pton(AF_INET, ip_address, &ip_integer)) {
        fprintf(stderr, "Invalid IP address: %s\n", ip_address);
        exit(1);
    }//end if
    return ip_integer;
}//end if

char *ip_ntoa(u_int32_t i) {
#define FUNCTION_BUFFER 256
    static char str[FUNCTION_BUFFER][INET_ADDRSTRLEN];
    static int which = -1;

    which = (which + 1 == FUNCTION_BUFFER ? 0 : which + 1);

    memset(str[which], 0, sizeof(str[which]));

    inet_ntop(AF_INET, &i, str[which], sizeof(str[which]));

    return str[which];
}//end ip_ntoa

int datalink_length(int dlt) {
    switch (dlt) {
        case DLT_EN10MB: return 14;
#ifdef DLT_IPNET
        case DLT_IPNET: return 24;
#endif
#ifdef DLT_PPI
        case DLT_PPI: return 8;
#endif
#ifdef DLT_NULL
        case DLT_NULL: return 4;
#endif
#ifdef DLT_LOOP
        case DLT_LOOP: return 4;
#endif
#ifdef DLT_RAW
        case DLT_RAW: return 0;
#endif
#ifdef DLT_IPV4
        case DLT_IPV4: return 0;
#endif
#ifdef DLT_IPV6
        case DLT_IPV6: return 0;
#endif
#ifdef DLT_FDDI
        case DLT_FDDI: return 13;
#endif
        default: return -1;
    }//end switch
}//end datalink_length

pcap_t *open_pcap_handle(char *device, int timeout, char *filter_expression) {
    pcap_t *handle = NULL;
    char errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 net, mask;
    struct bpf_program fcode;

    handle = pcap_open_live(device, 65535, 1, timeout, errbuf);
    if(!handle) {
        fprintf(stderr, "pcap_open_live: %s\n", errbuf);
        return NULL;
    }//end if

    if(-1 == pcap_lookupnet(device, &net, &mask, errbuf)) {
        fprintf(stderr, "pcap_lookupnet: %s\n", errbuf);
        pcap_close(handle);
        return NULL;
    }//end if

    if(-1 == pcap_compile(handle, &fcode, filter_expression, 1, mask)) {
        fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        return NULL;
    }//end if

    //set filter
    if(-1 == pcap_setfilter(handle, &fcode)) {
        fprintf(stderr, "pcap_pcap_setfilter: %s\n", pcap_geterr(handle));
        pcap_freecode(&fcode);
        pcap_close(handle);
        return NULL;
    }//end if

    //free code
    pcap_freecode(&fcode);

    return handle;
}//end open_pcap_handle

libnet_t *open_libnet_handle(char *device) {
    libnet_t *handle = NULL;
    char errbuf[LIBNET_ERRBUF_SIZE];

    handle = libnet_init(LIBNET_RAW4, device, errbuf);
    if(!handle) {
        fprintf(stderr, "libnet_init: %s\n", errbuf);
        return NULL;
    }//end if

    return handle;
}//end open_libnet_handle

結果

libnet % ./ssdp       
Usage: ./ssdp <-i Interface> [-s Source IP Address] [-d Destination IP Address] [-t Timeout ms]
libnet % sudo ./ssdp -i en0
Source IP Address: 192.168.1.1
SSDP Response:
HTTP/1.1 200 OK
CACHE-CONTROL: max-age=100
DATE: Mon, 07 Mar 2016 20:20:10 GMT
EXT:
LOCATION: http://192.168.1.1:1900/igd.xml
SERVER: ipos/7.0 UPnP/1.0 TL-WR841N/8.0
ST: upnp:rootdevice
USN: uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx::upnp:rootdevice
...略
USN: uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx::urn:schemas-wifialliance-org:service:WFAWLANConfig:1


Sent: 122 bytes, Received: 4312 bytes
Increased: 35.34 times

送出122 bytes,接收的卻有4312 bytes,總共被放大了35.34倍。

參數

-i #封包送出及接收的Interface
-s #來源IP Address,預設自己
-d #目的IP Address,預設239.255.255.250
-t #等到封包回覆時間,單位毫秒,預設200毫秒

分析

#define __FAVOR_BSD

在程式最上方#define __FAVOR_BSD這個保留字,因為在非BSD核心的Linux系統,有些封包表頭定義不太一樣,利用定義這個保留字讓它能夠正確使用我們想要的表頭定義。


void usage(const char *cmd);
u_int32_t ip_aton(const char *ip_address);
char *ip_ntoa(u_int32_t i);
int datalink_length(int dlt);
pcap_t *open_pcap_handle(char *device, int timeout, char *filter_expression);
libnet_t *open_libnet_handle(char *device);

函數宣告有這些:函數datalink_length()用來取得不同Data-link Layer的表頭長度。函數open_pcap_handle()用來打開一個libpcap handle。函數open_libnet_handle()用來打開一個libnet handle。


void usage(const char *cmd) {
    printf("Usage: %s <-i Interface> [-s Source IP Address] [-d Destination IP Address] [-t Timeout ms]\n", cmd);
    exit(1);
}//end usage

程式主要讀入Interface就好了。


    //parse argument
    opterr = 0; //don't show default error message
    while((c = getopt(argc, (char * const *)argv, "i:s:d:t:")) != EOF) {
        switch (c) {
            case 'i':
                device = optarg;
                break;

            case 's':
                src_ip = ip_aton(optarg);
                break;

            case 'd':
                dst_ip = ip_aton(optarg);
                break;

            case 't':
                timeout = atoi(optarg);
                if(timeout == 0) {
                    fprintf(stderr, "Invalid timeout value: %s\n", optarg);
                    exit(1);
                }//end if
                break;

            case '?':
            case 'h':
            default:
                usage(argv[0]);
                break;
        }//end switch
    }//end while

    if(!device) {
        usage(argv[0]);
    }//end if

讀入參數一樣沒什麼特別的。


    //init pcap
    pcap_handle = open_pcap_handle(device, timeout, "udp src port 1900");

首先先打開一個libpcap handle,參數分別為Interface(變數device) 、等待封包回覆時間(變數timeout)以及過濾器表達式(常數"udp src port 1900"),所以擷取到的封包會是UDP且Source Port為1900。


接著進入函數open_pcap_handle()

pcap_t *open_pcap_handle(char *device, int timeout, char *filter_expression) {
    pcap_t *handle = NULL;
    char errbuf[PCAP_ERRBUF_SIZE];
    bpf_u_int32 net, mask;
    struct bpf_program fcode;

    handle = pcap_open_live(device, 65535, 1, timeout, errbuf);
    if(!handle) {
        fprintf(stderr, "pcap_open_live: %s\n", errbuf);
        return NULL;
    }//end if

    if(-1 == pcap_lookupnet(device, &net, &mask, errbuf)) {
        fprintf(stderr, "pcap_lookupnet: %s\n", errbuf);
        pcap_close(handle);
        return NULL;
    }//end if

    if(-1 == pcap_compile(handle, &fcode, filter_expression, 1, mask)) {
        fprintf(stderr, "pcap_compile: %s\n", pcap_geterr(handle));
        pcap_close(handle);
        return NULL;
    }//end if

    //set filter
    if(-1 == pcap_setfilter(handle, &fcode)) {
        fprintf(stderr, "pcap_pcap_setfilter: %s\n", pcap_geterr(handle));
        pcap_freecode(&fcode);
        pcap_close(handle);
        return NULL;
    }//end if

    //free code
    pcap_freecode(&fcode);

    return handle;
}//end open_pcap_handle

函數就打開一個libpcap handle,編譯上過濾器表達式,成功後就傳回,這部分在libpcap篇介紹過了。


...略
    if(!pcap_handle) {
        goto BYE;
    }//end if

當函數傳回NULL,就會goto BYE,所以先找到標籤BYE

BYE:
    //free
    if(pcap_handle) {
        pcap_close(pcap_handle);
    }//end if
    if(libnet_handle) {
        libnet_destroy(libnet_handle);
    }//end if

    return 1;

標籤BYE在底下,會裡會釋放完資源後return 1結束程式。

所以到這裡要講為何要用goto,一開始在學C語言的時候都會說不要使用goto,的確使用goto會造成程式閱讀性降低很多,但是如果單純去看goto功能會發現,在一些很多層的迴圈內要跳出就會很方便,而且在不少library也都會使用goto。但是goto並不是隨便使用,最重要的原則就是:只能往下跳,以我的習慣是在如果當發生錯誤時,要結束函數或程式之前,如果要有些資源要釋放或是變數需要設定,因為這些動作都一樣,所以我就會使用goto


    //get data-link length
    dlt = pcap_datalink(pcap_handle);
    dlt_length = datalink_length(dlt);
    if(dlt_length == -1) {
        fprintf(stderr, "No support datalink type: %s(%s)\n",
                pcap_datalink_val_to_name(dlt), pcap_datalink_val_to_description(dlt));
        goto BYE;
    }//end if

接著我們要取得Data-link Layer協定表頭的長度,先用函數pcap_datalink()取得Data-link類型,接著再呼叫我們寫的函數datalink_length()根據Data-link類型傳回長度。


int datalink_length(int dlt) {
    switch (dlt) {
        case DLT_EN10MB: return 14;
#ifdef DLT_IPNET
        case DLT_IPNET: return 24;
#endif
#ifdef DLT_PPI
        case DLT_PPI: return 8;
#endif
#ifdef DLT_NULL
        case DLT_NULL: return 4;
#endif
#ifdef DLT_LOOP
        case DLT_LOOP: return 4;
#endif
#ifdef DLT_RAW
        case DLT_RAW: return 0;
#endif
#ifdef DLT_IPV4
        case DLT_IPV4: return 0;
#endif
#ifdef DLT_IPV6
        case DLT_IPV6: return 0;
#endif
#ifdef DLT_FDDI
        case DLT_FDDI: return 13;
#endif
        default: return -1;
    }//end switch
}//end datalink_length

這個函數我挑了幾個常見的協定,來傳回表頭長度,例如DLT_EN10MB是Ethernet,而Ethernet表頭長度是14 bytes。


...略
    if(dlt_length == -1) {
        fprintf(stderr, "No support datalink type: %s(%s)\n",
                pcap_datalink_val_to_name(dlt), pcap_datalink_val_to_description(dlt));
        goto BYE;
    }//end if

如果傳回-1,就是Data-link類型我沒有列舉到,所以顯示錯誤訊息並goto BYE

為何要取得Data-link協定表頭的長度,因為等等要分析封包的時候要使用,可能會想說為何不寫14就好,因為我們開啟的Interface不見得都會是Ethernet。


    //init libnet
    libnet_handle = open_libnet_handle(device);
    if(!libnet_handle) {
        goto BYE;
    }//end if

接著呼叫函數open_libnet_handle()來傳回一個libnet handle。


libnet_t *open_libnet_handle(char *device) {
    libnet_t *handle = NULL;
    char errbuf[LIBNET_ERRBUF_SIZE];

    handle = libnet_init(LIBNET_RAW4, device, errbuf);
    if(!handle) {
        fprintf(stderr, "libnet_init: %s\n", errbuf);
        return NULL;
    }//end if

    return handle;
}//end open_libnet_handle

這部分很簡單,呼叫函數libnet_init()並且使用LIBNET_RAW4打開一個libnet handle,這邊特別的是有傳入Interface。

前面有講過打開Network Layer的Raw socket,送出封包的時候會交由Route table決定要從哪個網卡送出,但是我們libpcap是打開特定的Interface,如果libnet自己決定別張網卡送出的話,我們就聽不到傳回的封包了。函數libnet_init()如果使用LIBNET_RAW4並且有給Interface,就會指定從特定網卡送出。

這部分libnet原始碼在src/libnet_raw.c的135行到140行。


    //if source ip address is 0, assign current ip address
    if(src_ip == 0) {
        src_ip = libnet_get_ipaddr4(libnet_handle);
        if(src_ip == -1) {
            fprintf(stderr, "libnet_get_ipaddr4: %s\n", libnet_geterror(libnet_handle));
            goto BYE;
        }//end if fail
    }//end if

如果傳入參數的時候沒有指定Source IP Address,我們就抓自己的IP Address。


    //init seed
    libnet_seed_prand(libnet_handle);
    sport = libnet_get_prand(LIBNET_PRu16) % (65535-49152+1)+(49152);

然後就來亂數產生一個Port使用。至於為何要這樣計算,是為了要產生49152-65535這個範圍內的亂數。

libnet/tcp_syn_flood.c有解釋囉。


    //build udp with ssdp
    tag = libnet_build_udp(sport,
                           //source port
                           1900,
                           //destination port
                           LIBNET_UDP_H + ssdp_length,
                           //length
                           0,
                           //checksum
                           (u_char *)ssdp,
                           ssdp_length,
                           libnet_handle,
                           LIBNET_PTAG_INITIALIZER);

    if(tag == -1) {
        fprintf(stderr, "libnet_build_udp: %s\n", libnet_geterror(libnet_handle));
        goto BYE;
    }//end if

因為libnet沒有建構SSDP的函數,所以我們把它當成UDP的Payload傳入就好。函數libnet_build_udp()來建構,第二個參數Destination Port要用1900,因為SSDP預設Port使用1900。第三個參數長度記得要加上SSDP的長度。第四個參數Checksum填入0表示自動算。第五和第六個參數傳入SSDP部分以及SSDP長度。


那來看傳入的SSDP是什麼。

    char ssdp[] =
    "M-SEARCH * HTTP/1.1\r\n"
    "HOST: 239.255.255.250:1900\r\n"
    "MAN: \"ssdp:discover\"\r\n"
    "MX: 2\r\n"
    "ST: ssdp:all\r\n"
    "\r\n";
    int ssdp_length = strlen(ssdp);

SSDP跟HTTP使用一樣的格式叫做Internet Message Format,第一行為Request line或Status line,接著格式為:欄位:[空格]資料CRLF,最後結束時以CRLF表示結束,CRLF以C語言表示就是\r\nRFC 2822

SSDP Discover封包使用:

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 2
ST: ssdp:all

其中ST如果使用ssdp:all會傳回該裝置所支援所有UPnP的服務,所以會回覆很多封包,如果是要知道有哪些裝置有提供UPnP服務(就傳回一個封包),就使用ST: upnp:rootdevice


    //build ip
    tag = libnet_build_ipv4(LIBNET_IPV4_H + LIBNET_UDP_H + ssdp_length,
                            //total length, 28 + payload length
                            0,
                            //type of service
                            libnet_get_prand(LIBNET_PRu16),
                            //id, rand
                            0,
                            //fragment offset
                            64,
                            //time to live
                            IPPROTO_UDP,
                            //procotol, 17
                            0,
                            //checksum, auto calculate
                            src_ip,
                            //source ip address
                            dst_ip,
                            //destination ip address
                            NULL,
                            0,
                            libnet_handle,
                            LIBNET_PTAG_INITIALIZER);

    if(tag == -1) {
        fprintf(stderr, "libnet_autobuild_ipv4: %s\n", libnet_geterror(libnet_handle));
        goto BYE;
    }//end if

IP表頭部分就用函數libnet_build_ipv4()建構。


    //send ssdp
    send_length = libnet_write(libnet_handle);
    if(send_length == -1) {
        fprintf(stderr, "libnet_write: %s\n", libnet_geterror(libnet_handle));
        goto BYE;
    }//end if

建構完封包後就可以直接送出了,函數libnet_write()傳回值是送出了多少bytes,因為我們的libnet handle開啟的時候使用LIBNET_RAW4,所以傳回的長度並不包含Data-link的表頭長度。


    //now listen response
    while(pcap_next_ex(pcap_handle, &header, &content) == 1) {
        struct ip *ip = (struct ip *)(content + dlt_length);
        char *src_ip = ip_ntoa(ip->ip_src.s_addr);
        struct udphdr *udp = (struct udphdr *)(content + dlt_length + (ip->ip_hl << 2));
        int ssdp_length = ntohs(udp->uh_ulen) - LIBNET_UDP_H;
        //int ssdp_length = header->caplen - dlt_length - (ip->ip_hl << 2) - LIBNET_UDP_H;
        char *ssdp_data = (char *)(content + dlt_length + (ip->ip_hl << 2) + LIBNET_UDP_H);
        char buffer[65535] = {};

        //check if is not udp
        if(ip->ip_p != IPPROTO_UDP) {
            continue;
        }//end if

        //copy ssdp data
        memcpy(buffer, ssdp_data, ssdp_length);
        buffer[ssdp_length] = '\0';

        //print result
        printf("Source IP Address: %s\n", src_ip);
        printf("SSDP Response:\n%s", buffer);

        //add receive length
        recv_length += header->caplen - dlt_length;
    }//end while read packet

接著就來等待回覆並且分析一下封包。

    //now listen response
    while(pcap_next_ex(pcap_handle, &header, &content) == 1) {
...略

迴圈while只會當有讀到封包時才會繼續,所以其他情況就直接忽略吧。

...略
        struct ip *ip = (struct ip *)(content + dlt_length);
        char *src_ip = ip_ntoa(ip->ip_src.s_addr);
...略

封包我們直接從IP表頭下手,直接加上剛剛取得的變數dlt_length,就會是IP表頭部分,然後使用函數ip_ntoa()來將Source IP Address從網路地址格式轉成字串。

...略
        struct udphdr *udp = (struct udphdr *)(content + dlt_length + (ip->ip_hl << 2));
        int ssdp_length = ntohs(udp->uh_ulen) - LIBNET_UDP_H;
        //int ssdp_length = header->caplen - dlt_length - (ip->ip_hl << 2) - LIBNET_UDP_H;
        char *ssdp_data = (char *)(content + dlt_length + (ip->ip_hl << 2) + LIBNET_UDP_H);
...略

接著處理UDP表頭,加上dlt_length和IP表頭長度就是UDP表頭了。接著先計算SSDP的長度,由UDP封包內的Length欄位扣除UDP表頭長度就是SSDP的長度了。

SSDP資料開始的位置是dlt_length、IP表頭長度和UDP表頭長度的總和。

實際上SSDP長度應該要用註解內的方法,因為可能會有人偽造封包,把UDP長度那欄位的資訊填入不對的長度。

...略
        //check if is not udp
        if(ip->ip_p != IPPROTO_UDP) {
            continue;
        }//end if
...略

雖然我們已經用了過濾器表達式過濾封包了,不過可能會因為我們自己的邏輯錯誤,把不要的封包還是給抓取下來,所以用簡單的if稍微過濾一下,這裡也可以寫其他的表達式再進一步濾掉不要的封包。

...略
        //copy ssdp data
        memcpy(buffer, ssdp_data, ssdp_length);
        buffer[ssdp_length] = '\0';
...略

然後我們把SSDP的資料部分複製到變數buffer內,雖然我們有長度這項資訊了,但是我們等等要用函數printf()列印出SSDP的資料,而函數printf()列印字串方式就是列印到有空字元'\0'為止,我們的封包指標是指向libpcap給的指標,結束的地方不見得會有空字元,應該要說為何要有空字元?封包內本來就會出現0這個數值,只是SSDP的資料剛好都是字串。所以我們先用函數memcpy()複製到一個合法的空間,再在最後面的位置給空字元'\0'

空字元'\0'的ASCII code就是0喔。

...略
        //print result
        printf("Source IP Address: %s\n", src_ip);
        printf("SSDP Response:\n%s", buffer);
...略

然後就列印Source IP Address和SSDP的資料了。

        //add receive length
        recv_length += header->caplen - dlt_length;
    }//end while read packet
...略

最後我們統計一下接收到長度,因為我們前面送出去的長度並不包含Data-link的表頭長度,所以我們也扣掉這個長度。

記得封包在網路上轉送(Forward)的時候,Data-link Layer部分協定會一直修改,而不會被修改的部分是Network Layer以上,所以我們要計算資訊的時候,通常不會考慮Data-link Layer


    //stats
    printf("\nSent: %d bytes, Received: %d bytes\n"
           "Increased: %.2f times\n",
           send_length, recv_length, recv_length/(float)send_length);

最後就把送出和接收的資訊給列印出來。


    //free
    if(pcap_handle) {
        pcap_close(pcap_handle);
    }//end if
    if(libnet_handle) {
        libnet_destroy(libnet_handle);
    }//end if

最後記得釋放資源。

結語

程式同時使用了libnet和libpcap,個人偏好Raw socket偶爾搭配libpcap方式,之後等到了Raw socket篇可以比較看看哪種方式比較方便。

results matching ""

    No results matching ""