1. 程式人生 > >《Linux網路程式設計》: libnet 詳解

《Linux網路程式設計》: libnet 詳解

1 . 概述

通過《Linux網路程式設計》: 原始套接字傳送UDP報文 的學習,我們組 UDP 資料包時常考慮位元組流順序、校驗和計算等問題,有時候會比較繁瑣,那麼,有沒有一種更簡單的方法呢?答案是:藉助 libnet 函式庫

libnet 是一個小型的介面函式庫,主要用 C 語言寫成,提供了低層網路資料包的構造、處理和傳送功能。

libnet 的開發目的是:建立一個簡單統一的網路程式設計介面以遮蔽不同作業系統底層網路程式設計的差別,使得程式設計師將精力集中在解決關鍵問題上。

libnet 庫提供的介面函式包含 15 種資料包生成器和兩種資料包傳送器(IP 層和資料鏈路層)。 

提供的介面函式包括:

1)記憶體管理(分配和釋放)函式

2)地址解析函式

3)各種協議型別的資料包建構函式

4)資料包傳送函式(IP層和鏈路層)

5)一些輔助函式,如產生隨機數、錯誤報告、埠列表管理等

2. libnet 的安裝

3. 流程

利用libnet函式庫開發應用程式的基本步驟:

1)資料包記憶體初始化

2)構造資料包

3)傳送資料

4)釋放資源

以傳送 UDP 資料包為例,流程圖如下:

這裡需要注意的是組包的順序,由上層再到底層,這裡為 udp -> ip -> mac,不能反過來。

 

4. 常用函式介紹

《Linux網路程式設計》: libnet 函式列表

需要包含標頭檔案: libnet.h

libnet_t *libnet_init(int injection_type, char *device, char *err_buf);

功能:資料包記憶體初始化及環境建立

引數:

injection_type:構造的型別

LIBNET_LINK,鏈路層

LIBNET_RAW4,網路介面層(網路層)

LIBNET_LINK_ADV,鏈路層高階版本

LIBNET_RAW4_ADV,   網路層高階版本

device:網路介面,如 "eth0",或 IP 地址,亦可為 NULL (自動查詢搜尋)  

err_buf:存放出錯的資訊 

返回值:

成功:一個 libnet * 型別的指標,後面的操作都得使用這個指標 

失敗:NULL

void libnet_destroy(libnet_t *l);

功能:釋放資源

引數:

l:libnet_init() 返回的 libnet * 指標

返回值:

char* libnet_addr2name4(u_int32_t in, u_int8_t use_name);

功能:將網路位元組序轉換成點分十進位制數串

引數:

in:網路位元組序的 ip 地址        

use_name:

LIBNET_RESOLVE,  對應主機名

LIBNET_DONT_RESOLVE,對應點分十進位制 IPv4 地址

返回值:

成功:點分十進位制 ip 地址 

失敗:NULL

u_int32_t libnet_name2addr4(libnet_t *l,  char *host_name,  u_int8_t use_name);

功能:將點分十進位制數串轉換為網路位元組序 ip 地址

引數:

l:libnet_init() 返回的 libnet * 指標

host_name:

LIBNET_RESOLVE,  對應主機名

LIBNET_DONT_RESOLVE,對應點分十進位制 IPv4 地址

返回值:

成功:網路位元組序 ip 地址

失敗:-1

u_int32_t libnet_get_ipaddr4(libnet_t *l);

功能:獲取介面裝置 ip 地址

引數:

l:libnet_init() 返回的 libnet * 指標

返回值:

成功:網路位元組序的 ip 地址

失敗:-1

struct libnet_ether_addr* libnet_get_hwaddr(libnet_t *l);

功能:獲取介面裝置硬體地址

引數:

l:libnet_init() 返回的 libnet * 指標

返回值:

成功:指向 MAC 地址的指標

失敗:NULL

libnet_ptag_t libnet_build_udp(u_int16_t sp, u_int16_t dp,u_int16_t len, u_int16_t sum,u_int8_t *payload, u_int32_t payload_s,libnet_t *l, libnet_ptag_t ptag);

功能:構造 udp 資料包

引數:

sp: 源埠號

dp:目的埠號

len:udp 包總長度

sum:校驗和,設為 0,libnet 自動填充

payload:負載,為給應用程式傳送的文字內容,沒有內容時可設定為 NULL

payload_s:負載長度,給應用程式傳送文字內容的長度,或為 0

l:libnet_init() 返回的 libnet * 指標

ptag:協議標記,第一次組新的傳送包時,這裡寫 0,同一個應用程式,下一次再組包時,這個位置的值寫此函式的返回值。

返回值:

成功:協議標記

失敗:-1

libnet_ptag_t libnet_build_tcp(u_int16_t sp, u_int16_t dp,u_int32_t seq, u_int32_t ack,u_int8_t control, u_int16_t win,u_int16_t sum, u_int16_t urg,u_int16_t len, u_int8_t *payload,u_int32_t payload_s, libnet_t *l,libnet_ptag_t ptag );

功能:構造 tcp 資料包

引數:

sp:源埠號

dp:目的埠號

seq:序號

ack:ack 標記

control:控制標記

win:視窗大小

sum:校驗和,設為 0,libnet 自動填充

urg:緊急指標

len:tcp包長度

payload:負載,為給應用程式傳送的文字內容,可設定為 NULL

payload_s:負載長度,或為 0

l:libnet_init() 返回的 libnet * 指標

ptag:協議標記,第一次組新的傳送包時,這裡寫 0,同一個應用程式,下一次再組包時,這個位置的值寫此函式的返回值。

返回值:

成功:協議標記

失敗:-1

libnet_ptag_t libnet_build_tcp_options(u_int8_t *options,  u_int32_t options_s,libnet_t *l, libnet_ptag_t ptag );

功能:構造 tcp 選項資料包

引數:

options:tcp 選項字串

options_s:選項長度

l:libnet 控制代碼,libnet_init() 返回的 libnet * 指標

ptag:協議標記,第一次組新的傳送包時,這裡寫 0,同一個應用程式,下一次再組包時,這個位置的值寫此函式的返回值。

返回值:

成功:協議標記

失敗:-1

libnet_ptag_t libnet_build_ipv4(u_int16_t ip_len, u_int8_t tos,u_int16_t id, u_int16_t flag,u_int8_t ttl, u_int8_t prot,u_int16 sum, u_int32_t src,u_int32_t dst, u_int8_t *payload,u_int32_t payload_s,libnet_t *l,libnet_ptag_t ptag );

功能:構造一個 IPv4 資料包

引數:

ip_len:ip 包總長

tos:服務型別

id:ip 標識

flag:片偏移

ttl:生存時間

prot:上層協議

sum:校驗和,設為 0,libnet 自動填充

src:源 ip 地址

dst:目的ip地址

payload:負載,可設定為 NULL(這裡通常寫 NULL)

payload_s:負載長度,或為 0(這裡通常寫 0 )

l:libnet 控制代碼,libnet_init() 返回的 libnet * 指標

ptag:協議標記,第一次組新的傳送包時,這裡寫 0,同一個應用程式,下一次再組包時,這個位置的值寫此函式的返回值。

返回值:

成功:協議標記  

失敗:-1

libnet_ptag_t libnet_build_ipv4_options(u_int8_t*options, u_int32_t options,libnet_t*l, libnet_ptag_t ptag);

功能:構造 IPv4 選項資料包

引數:

options:tcp 選項字串

options_s:選項長度

l:libnet 控制代碼,libnet_init() 返回的 libnet * 指標

ptag:協議標記,若為 0,建立一個新的協議

返回值:

成功:協議標記

失敗:-1

libnet_ptag_t libnet_build_arp(u_int16_t hrd, u_int16_t pro,u_int8_t hln, u_int8_t pln,u_int16_t op, u_int8_t *sha,u_int8_t *spa, u_int8_t *tha,u_int8_t *tpa, u_int8_t *payload,u_int32_t payload_s, libnet_t *l,libnet_ptag_t ptag );

功能:構造 arp 資料包

引數:

hrd:硬體地址格式,ARPHRD_ETHER(乙太網)

pro:協議地址格式,ETHERTYPE_IP( IP協議)

hln:硬體地址長度

pln:協議地址長度

op:ARP協議操作型別(1:ARP請求,2:ARP迴應,3:RARP請求,4:RARP迴應)

sha:傳送者硬體地址

spa:傳送者協議地址

tha:目標硬體地址

tpa:目標協議地址

payload:負載,可設定為 NULL(這裡通常寫 NULL)

payload_s:負載長度,或為 0(這裡通常寫 0 )

l:libnet 控制代碼,libnet_init() 返回的 libnet * 指標

ptag:協議標記,第一次組新的傳送包時,這裡寫 0,同一個應用程式,下一次再組包時,這個位置的值寫此函式的返回值。

返回值:

成功:協議標記

失敗:-1

libnet_ptag_t libnet_build_ethernet(u_int8_t*dst, u_int8_t *src,u_int16_ttype, u_int8_t*payload,u_int32_tpayload_s, libnet_t*l,libnet_ptag_t ptag );

功能:構造一個乙太網資料包

引數: 

dst:目的 mac

src:源 mac

type:上層協議型別

payload:負載,即附帶的資料,可設定為 NULL(這裡通常寫 NULL)

payload_s:負載長度,或為 0(這裡通常寫 0 )

l:libnet 控制代碼,libnet_init() 返回的 libnet * 指標

ptag:協議標記,第一次組新的傳送包時,這裡寫 0,同一個應用程式,下一次再組包時,這個位置的值寫此函式的返回值。

返回值:

成功:協議標記

失敗:-1

int libnet_write(libnet_t * l);

功能:傳送資料包

引數:

l:libnet 控制代碼,libnet_init() 返回的 libnet * 指標

返回值:

成功:傳送資料包的長度

失敗:返回 -1

使用例項

這裡是在 ubuntu 下通過原始套接字組一個 udp 資料包,給 PC 機的網路除錯助手傳送資訊(對比:《Linux網路程式設計》: 原始套接字傳送UDP報文):

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <libnet.h>
 
int main(int argc, char *argv[])
{
	char send_msg[1000] = "";
	char err_buf[100] = "";
	libnet_t *lib_net = NULL;
	int lens = 0;
	libnet_ptag_t lib_t = 0;
	unsigned char src_mac[6] = {0x00,0x0c,0x29,0x97,0xc7,0xc1};//傳送者網絡卡地址00:0c:29:97:c7:c1
	unsigned char dst_mac[6] = {0x74,0x27,0xea,0xb5,0xff,0xd8};//接收者網絡卡地址‎74-27-EA-B5-FF-D8
    char *src_ip_str = "192.168.31.163"; //源主機IP地址
    char *dst_ip_str = "192.168.31.248"; //目的主機IP地址
	unsigned long src_ip,dst_ip = 0;
 
	lens = sprintf(send_msg, "%s", "this is for the udp test");
 
 	lib_net = libnet_init(LIBNET_LINK_ADV, "eth0", err_buf);	//初始化
	if(NULL == lib_net)
	{
		perror("libnet_init");
		exit(-1);
	}
 
	src_ip = libnet_name2addr4(lib_net,src_ip_str,LIBNET_RESOLVE);	//將字串型別的ip轉換為順序網路位元組流
	dst_ip = libnet_name2addr4(lib_net,dst_ip_str,LIBNET_RESOLVE);
 
	lib_t = libnet_build_udp(	//構造udp資料包
								8080,
								8080,
								8+lens,
								0,
								send_msg,
								lens,
								lib_net,
								0
							);
 
	lib_t = libnet_build_ipv4(	//構造ip資料包
								20+8+lens,
								0,
								500,
								0,
								10,
								17,
								0,
								src_ip,
								dst_ip,
								NULL,
								0,
								lib_net,
								0
							);
 
	lib_t = libnet_build_ethernet(	//構造乙太網資料包
									(u_int8_t *)dst_mac,
									(u_int8_t *)src_mac,
									0x800, // 或者,ETHERTYPE_IP
									NULL,
									0,
									lib_net,
									0
								);
	int res = 0;
	res = libnet_write(lib_net);	//傳送資料包
	if(-1 == res)
	{
		perror("libnet_write");
		exit(-1);
	}
 
	libnet_destroy(lib_net);	//銷燬資源
	
	printf("----ok-----\n");
	return 0;
 }

編譯程式碼時,需要加上 -lnet: