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\n
,RFC 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篇可以比較看看哪種方式比較方便。