概述
通過《原始套接字執行個體:發送 UDP 資料包》的學習,我們組 UDP 資料包時常考慮位元組流順序、校驗和計算等問題,有時候會比較繁瑣,那麼,有沒有一種更簡單的方法呢?答案是:借助 libnet 函數庫。
libnet 是一個小型的接口函數庫,主要用 C 語言寫成,提供了低層網絡資料包的構造、處理和發送功能。
libnet 的開發目的是:建立一個簡單統一的網絡程式設計接口以屏蔽不同作業系統底層網絡程式設計的差别,使得程式員将精力集中在解決關鍵問題上。
libnet 庫提供的接口函數包含 15 種資料包生成器和兩種資料包發送器(IP 層和資料鍊路層)。
提供的接口函數包括:
1)記憶體管理(配置設定和釋放)函數
2)位址解析函數
3)各種協定類型的資料包構造函數
4)資料包發送函數(IP層和鍊路層)
5)一些輔助函數,如産生随機數、錯誤報告、端口清單管理等
詳情請看《libnet 函數清單》。
libnet 的安裝
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiQDOxEzX3xCZlhXam9VbsUmepNXZy9CXwJWZ3xCdh1mcvZ2Lc1zaHRGcWdUYuVzVa9GczoVdG1mWfVGc5RHLwIzX39GZhh2csATMflHLwEzX4xSZz91ZsAzMfRHLGZkRGZkRfJ3bs92YskmNhVTYykVNQJVMRhXVEF1X0hXZ0xiNx8VZ6l2cssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL1cTNxgjMkhDOmNDNxQzMzYzX5MDMzcTM4IzLclDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
流程
利用libnet函數庫開發應用程式的基本步驟:
1)資料包記憶體初始化
2)構造資料包
3)發送資料
4)釋放資源
以發送 UDP 資料包為例,流程圖如下:
這裡需要注意的是組包的順序,由上層再到底層,這裡為 udp -> ip -> mac,不能反過來。
常用函數介紹
以下函數的使用需要包含頭檔案: 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 機的網絡調試助手發送資訊(對比:《原始套接字執行個體:發送 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;
}