一、結構體
struct sockaddr_nl
{
sa_family_t nl_family; /* AF_NETLINK */
unsigned short nl_pad; /* zero */
__u32 nl_pid; /* port ID */
__u32 nl_groups; /* multicast groups mask */
};
字段 nl_family 必須設置爲 AF_NETLINK 或着 PF_NETLINK,字段 nl_pad 當前沒有使用,因此要總是設置爲 0,字段 nl_pid 爲接收或發送消息的進程的 ID,如果希望內核處理消息或多播消息,就把該字段設置爲 0,否則設置爲處理消息的進程 ID。字段 nl_groups 用於指定多播組,bind 函數用於把調用進程加入到該字段指定的多播組,如果設置爲 0,表示調用者不加入任何多播組。傳遞給 bind 函數的地址的 nl_pid 字段應當設置爲本進程的進程 ID,這相當於 netlink socket 的本地地址。
struct
nlmsghdr
{
__u32 nlmsg_len; /* Length of message including header */
__u16 nlmsg_type; /* Message content */
__u16 nlmsg_flags; /* Additional flags */
__u32 nlmsg_seq; /* Sequence number */
__u32 nlmsg_pid; /* Sending process port ID */
};
字段 nlmsg_len 指定消息的總長度,包括緊跟該結構的數據部分長度以及該結構的大小,字段 nlmsg_type 用於應用內部定義消息的類型,它對 netlink 內核實現是透明的,因此大部分情況下設置爲 0,字段 nlmsg_flags 用於設置消息標誌,可用的標誌包括:
標誌NLM_F_REQUEST用於表示消息是一個請求,所有應用首先發起的消息都應設置該標誌。
標誌NLM_F_MULTI 用於指示該消息是一個多部分消息的一部分,後續的消息可以通過宏NLMSG_NEXT來獲得。
宏NLM_F_ACK表示該消息是前一個請求消息的響應,順序號與進程ID可以把請求與響應關聯起來。
標誌NLM_F_ECHO表示該消息是相關的一個包的回傳。
標誌NLM_F_ROOT 被許多 netlink 協議的各種數據獲取操作使用,該標誌指示被請求的數據表應當整體返回用戶應用,而不是一個條目一個條目地返回。有該標誌的請求通常導致響應消息設置NLM_F_MULTI標誌。注意,當設置了該標誌時,請求是協議特定的,因此,需要在字段 nlmsg_type 中指定協議類型。
標誌 NLM_F_MATCH 表示該協議特定的請求只需要一個數據子集,數據子集由指定的協議特定的過濾器來匹配。
標誌 NLM_F_ATOMIC 指示請求返回的數據應當原子地收集,這預防數據在獲取期間被修改。
標誌 NLM_F_DUMP 未實現。
標誌 NLM_F_REPLACE 用於取代在數據表中的現有條目。
標誌 NLM_F_EXCL_ 用於和 CREATE 和 APPEND 配合使用,如果條目已經存在,將失敗。
標誌 NLM_F_CREATE 指示應當在指定的表中創建一個條目。
標誌 NLM_F_APPEND 指示在表末尾添加新的條目。
struct iovec {
ptr_t iov_base; /* Starting address */
size_t iov_len; /* Length in bytes */
};
例子:
int
readv(int fd, const struct iovec *vector, int count);
int writev(int fd, const struct iovec *vector, int count);
- <span style="white-space:pre"> </span>#include <stdio.h>
- 2 #include <sys/uio.h>
- 3
- 4 int main()
- 5 {
- 6 static char part2[] = "THIS IS FROM WRITEV";
- 7 static int part3 = 65;
- 8 static char part1[] = "[";
- 9
- 10 struct iovec iov[3];
- 11
- 12 iov[0].iov_base = part1;
- 13 iov[0].iov_len = strlen(part1);
- 14
- 15 iov[1].iov_base = part2;
- 16 iov[1].iov_len = strlen(part2);
- 17
- 18 iov[2].iov_base = &part3;
- 19 iov[2].iov_len = sizeof(int);
- 20
- 21 writev(1, iov, 3);
- 22
- 23 return 0;
- 24
- 25 }
output:
[THIS IS FROM WRITEVA65
struct netlink_skb_parms
{
struct ucred creds; /* Skb credentials */
__u32 pid;
__u32 dst_group;
kernel_cap_t eff_cap;
__u32 loginuid; /* Login (audit) uid */
__u32 sessionid; /* Session id (audit) */
__u32 sid; /* SELinux security id */
};
struct sk_buff * next;
struct sk_buff * prev;
struct sk_buff_head * list;
struct sock *sk;
struct timeval stamp;
struct net_device *dev;
struct net_device *real_dev;
union
{
struct tcphdr *th;
struct udphdr *uh;
struct icmphdr *icmph;
struct igmphdr *igmph;
struct iphdr *ipiph;
struct spxhdr *spxh;
unsigned char *raw;
} h;
union
{
struct iphdr *iph;
struct ipv6hdr *ipv6h;
struct arphdr *arph;
struct ipxhdr *ipxh;
unsigned char *raw;
} nh;
union
{
struct ethhdr *ethernet;
unsigned char *raw;
} mac;
struct dst_entry *dst;
char cb[48];
unsigned int len;
unsigned int data_len;
unsigned int csum;
unsigned char __unused,
cloned, pkt_type,
ip_summed; __u32 priority; atomic_t users;
unsigned short protocol;
unsigned short security;
unsigned int truesize;
unsigned char *head;
unsigned char *data;
unsigned char *tail;
unsigned char *end;
void (*destructor)(struct sk_buff *);
#ifdef CONFIG_NETFILTER
unsigned long nfmark;
__u32 nfcache;
struct nf_ct_info *nfct;
#ifdef CONFIG_NETFILTER_DEBUG
unsigned int nf_debug;
#endif
#endif
#if defined(CONFIG_HIPPI)
union{
__u32 ifield;
} private;
#endif
#ifdef CONFIG_NET_SCHED
__u32 tc_index;
#endif
};
該結構維護一個收到的或者要發送的網絡包。但其本身並不包含存放網絡包的數據的存儲區。存儲區是另外單獨分配的內存空間,但該結構說明了如何訪問存儲區空間,如何維護多個存儲區空間以及存儲網絡包解析的成果。
所有的sk_buff是通過一個雙向鏈表進行維護的。需要說明的是該雙向鏈表中的一個元素是struct sk_buff_head類型。它相當於該雙向鏈表的表頭,其中有鎖,鏈表元素個數等維護鏈表的相關信息。鏈表中其它的元素都是sk_buff類型。
鏈表結構圖如下:
而對於每個sk_buff而言,如前所述,其本身並不維護網絡包的存儲區。該結構跟存儲區的關係如下圖所示:
圖的左邊表示sk_buff結構,右側表示網絡包的存儲區。可以看到,sk_buff中有四個指針指向存儲區。其中head一定指向存儲區的開頭,end一定指向存儲區的結尾。data指向實際內容的開頭,tail指向實際內容的結尾。這樣做有兩個原因,一是在分配空間的時候我們尚不知道具體需要多大的空間,只能按照最大可能空間來分配;二是爲了滿足字節對齊的需要。
圖中Data部分的內容包括網絡包的所有內容。對於輸入包而言,其就是從當前層向上的所有層的頭和最後的負載,每解析掉一層的頭,該協議的協議頭對應的數據就不再繼續處理,所以data指針在每層會逐漸增大。對於輸出包而言,其每向下傳輸一層,都會添加一層的頭,所以sk_buff的data指針也會不斷減小。
對於輸入包而言,存儲區的內容是不變的。但我們在分析包時,在每層都會剝除該層的頭。爲了保留我們的剝除結果,結構中會有三個指針分別指向以太,網絡和傳輸三層的協議頭。這三個指針依次分別是union {…} mac,union {…} nh和union{…} h。三個成員都是聯合,其中的內容是該層可能的協議類型的指針。
還有一組成員表示存儲區中數據的長度。len表示存儲區的數據長度和分片長度之和。data_len表示分片長度。mac_len表示mac頭的長度。truesize表示存儲區總長度(即end-head)和sk_buff本身長度之和。
下面的內容是關於如何維護存儲區。
alloc_skb和dev_alloc_skb用來分配sk_buff和存儲區,kfree_skb和dev_kfree_skb則釋放sk_buff和其對應的存儲區。剛初始化完的存儲區,head,data,tail都指向存儲區的開頭,end指向結尾。下面這些函數是修改data和tail指針的指向。
skb_reserve是在存儲區的開頭保留一段空間。其有兩個作用,對於接收到的報文而言,可以保持字節對齊;對於發送的報文而言,可以保留空間用於存放各層的協議頭。畢竟我們在高層協議就會分配該存儲區,所以需要預留下層的協議頭的空間。該函數的作用會導致data和tail兩個指針都下移,在head和它們之間留出空間。
skb_put則是將tail下移,即增加了真正空間的長度。
skb_push是將data上移,也增加了真正空間的長度。
skb_pull將data下移,減少了真正空間的長度。
下面是sk_buff的其它成員:
struct sock *sk:表示該網絡包所屬的socket。對於該主機發送的和接收的網絡包,自然對應着一個socket套接字。對於轉發的網絡包,則該成員爲NULL。
atomic_t users:表示該sk_buff結構實例被引用的計數。這個是結構本身的引用計數,而不是其對應的存儲區的引用計數。
void (*destructor)():析構函數。
struct timeval stamp:對於接受包,表示該包接收到的時間。
struct net_device *dev:接收或者發送包的網卡接口。
struct net_device *input_dev:接收到包的網卡接口。
struct net_device *real_dev:在某些機器中,多個物理網卡接口可以被看作一個虛擬設備。這中情況下real_dev表示物理結構。
char cb[40]:一個用於對該網絡包進行處理的通用緩衝區。可以存放一些在不同層之間傳輸的數據。
unsigned int csum,ipsum:校驗和。
unsigned char cloned:
unsigned char pkt_type:包類型,可以是單發給該主機的包,廣播包,多播包,轉發包等。
unsigned short protocol:表示以太層的上層協議,可以是IP,IPv6和ARP。
下面是關於skb_shared_info結構的。當我們使用alloc_skb分配空間時,會在存儲區的後面緊接着一個該結構的實例。如下圖所示:
其中存儲了關於網絡塊的附加信息。不知爲何不也放在sk_buff中。其中維護了存儲塊的引用計數,IP分片數據的信息。
對於sk_buff和存儲塊,有時是需要拷貝的。分爲三種情況,一是隻拷貝sk_buff,存儲塊複用,此時使用skb_clone函數;二是拷貝sk_buff和存儲塊的主體,但存儲塊後的skb_shared_info中frags數組指向的分片數據不拷貝,這使用pskb_copy函數;三是上述的全都拷貝,使用skb_copy函數。在不同情況下不同使用。
二、宏
在linux/netlink.h中定義了一些方便對消息進行處理的宏,這些宏包括:
#define NLMSG_ALIGNTO 4 #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) |
宏NLMSG_ALIGN(len)用於得到不小於len且字節對齊的最小數值。
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr))) |
宏NLMSG_LENGTH(len)用於計算數據部分長度爲len時實際的消息長度。它一般用於分配消息緩存。
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) |
宏NLMSG_SPACE(len)返回不小於NLMSG_LENGTH(len)且字節對齊的最小數值,它也用於分配消息緩存。
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) |
宏NLMSG_DATA(nlh)用於取得消息的數據部分的首地址,設置和讀取消息數據部分時需要使用該宏。
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), / (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len))) |
宏NLMSG_NEXT(nlh,len)用於得到下一個消息的首地址,同時len也減少爲剩餘消息的總長度,該宏一般在一個消息被分成幾個部分發送或接收時使用。
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && / (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && / (nlh)->nlmsg_len <= (len)) |
宏NLMSG_OK(nlh,len)用於判斷消息是否有len這麼長。
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) |
宏NLMSG_PAYLOAD(nlh,len)用於返回payload的長度。
函數close用於關閉打開的netlink socket。