netlink中用到的結構體和宏

一、結構體

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 用於設置消息標誌,可用的標誌包括:

複製代碼
#define NLM_F_REQUEST 1 #define NLM_F_MULTI 2 #define NLM_F_ACK 4 #define NLM_F_ECHO 8 #define NLM_F_ROOT 0x100 #define NLM_F_MATCH 0x200 #define NLM_F_ATOMIC 0x400 #define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH) #define NLM_F_REPLACE 0x100 #define NLM_F_EXCL 0x200 #define NLM_F_CREATE 0x400 #define NLM_F_APPEND 0x800
複製代碼

 

標誌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 */  
};  

struct iovec定義了一個向量元素。通常,這個結構用作一個多元素的數組。對於每一個傳輸的元素,指針成員iov_base指向一個緩衝區,這個緩衝區是存放的是readv所接收的數據或是writev將要發送的數據。成員iov_len在各種情況下分別確定了接收的最大長度以及實際寫入的長度。

例子:

int readv(int fd, const struct iovec *vector, int count);  
int writev(int fd, const struct iovec *vector, int count);  
     

  1. <span style="white-space:pre">  </span>#include <stdio.h>  
  2.       2 #include <sys/uio.h>  
  3.       3   
  4.       4 int main()  
  5.       5 {  
  6.       6         static char part2[] = "THIS IS FROM WRITEV";  
  7.       7         static int  part3 = 65;  
  8.       8         static char part1[] = "[";  
  9.       9   
  10.      10         struct iovec iov[3];  
  11.      11   
  12.      12         iov[0].iov_base = part1;  
  13.      13         iov[0].iov_len = strlen(part1);  
  14.      14   
  15.      15         iov[1].iov_base = part2;  
  16.      16         iov[1].iov_len = strlen(part2);  
  17.      17   
  18.      18         iov[2].iov_base = &part3;  
  19.      19         iov[2].iov_len = sizeof(int);  
  20.      20   
  21.      21         writev(1, iov, 3);  
  22.      22   
  23.      23         return 0;  
  24.      24   
  25.      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 {
    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_buffdata指針也會不斷減小。

對於輸入包而言,存儲區的內容是不變的。但我們在分析包時,在每層都會剝除該層的頭。爲了保留我們的剝除結果,結構中會有三個指針分別指向以太,網絡和傳輸三層的協議頭。這三個指針依次分別是union {…} macunion {…} nhunion{…} h。三個成員都是聯合,其中的內容是該層可能的協議類型的指針。

還有一組成員表示存儲區中數據的長度。len表示存儲區的數據長度和分片長度之和。data_len表示分片長度。mac_len表示mac頭的長度。truesize表示存儲區總長度(即end-head)和sk_buff本身長度之和。

下面的內容是關於如何維護存儲區。

alloc_skbdev_alloc_skb用來分配sk_buff和存儲區,kfree_skbdev_kfree_skb則釋放sk_buff和其對應的存儲區。剛初始化完的存儲區,headdatatail都指向存儲區的開頭,end指向結尾。下面這些函數是修改datatail指針的指向。

skb_reserve是在存儲區的開頭保留一段空間。其有兩個作用,對於接收到的報文而言,可以保持字節對齊;對於發送的報文而言,可以保留空間用於存放各層的協議頭。畢竟我們在高層協議就會分配該存儲區,所以需要預留下層的協議頭的空間。該函數的作用會導致datatail兩個指針都下移,在head和它們之間留出空間。

skb_put則是將tail下移,即增加了真正空間的長度。

skb_push是將data上移,也增加了真正空間的長度。

skb_pulldata下移,減少了真正空間的長度。

下面是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 csumipsum:校驗和。

unsigned char cloned

unsigned char pkt_type:包類型,可以是單發給該主機的包,廣播包,多播包,轉發包等。

unsigned short protocol:表示以太層的上層協議,可以是IPIPv6ARP

下面是關於skb_shared_info結構的。當我們使用alloc_skb分配空間時,會在存儲區的後面緊接着一個該結構的實例。如下圖所示:

其中存儲了關於網絡塊的附加信息。不知爲何不也放在sk_buff中。其中維護了存儲塊的引用計數,IP分片數據的信息。

對於sk_buff和存儲塊,有時是需要拷貝的。分爲三種情況,一是隻拷貝sk_buff,存儲塊複用,此時使用skb_clone函數;二是拷貝sk_buff和存儲塊的主體,但存儲塊後的skb_shared_infofrags數組指向的分片數據不拷貝,這使用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。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章