更多文章請多關注個人網站:http://www.readbk.net,謝謝瀏覽!
機制原理:
Netlink 是一種特殊的 socket,它是 Linux 所特有的,由於傳送的消息是暫存在socket接收緩存中,並不被接收者立即處理,所以netlink是一種異步通信機制。 系統調用和 ioctl 則是同步通信機制。
用戶空間進程可以通過標準socket API來實現消息的發送、接收,在Linux中,有很多用戶空間和內核空間的交互都是通過Netlink機制完成的,在Linux3.0的內核版本中定義了下面的21個用於Netlink通信的宏,其中默認的最大值爲32.我這裏重點關注的是IPv6路由部分的通信過程。
在include/linux/`netlink.h文件中定義了下面的用於通信的宏!
#define NETLINK_ROUTE 0 /* Routing/device hook */
#define NETLINK_UNUSED 1 /* Unused number */
#define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */
#define NETLINK_FIREWALL 3 /* Firewalling hook */
#define NETLINK_INET_DIAG 4 /* INET socket monitoring */
#define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */
#define NETLINK_XFRM 6 /* ipsec */
#define NETLINK_SELINUX 7 /* SELinux event notifications */
#define NETLINK_ISCSI 8 /* Open-iSCSI */
#define NETLINK_AUDIT 9 /* auditing */
#define NETLINK_FIB_LOOKUP 10
#define NETLINK_CONNECTOR 11
#define NETLINK_NETFILTER 12 /* netfilter subsystem */
#define NETLINK_IP6_FW 13
#define NETLINK_DNRTMSG 14 /* DECnet routing messages */
#define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */
#define NETLINK_GENERIC 16
/* leave room for NETLINK_DM (DM Events) */
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define MAX_LINKS 32
用戶態可以使用標準的socket APIs, socket(), bind(), sendmsg(), recvmsg() 和 close() 等函數就能很容易地使用 netlink socket,我們在用戶空間可以直接通過socket函數來使用Netlink通信,例如可以通過下面的方式:
sock = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
說明:第一個參數必須是 AF_NETLINK 或 PF_NETLINK,在 Linux 中,它們倆實際爲一個東西,它表示要使用netlink,第二個參數必須是SOCK_RAW或SOCK_DGRAM, 第三個參數指定netlink協議類型,可以是自己在netlink.h中定義的,也可以是內核中已經定義好的。上面的例子使用主要是路由的Netlink協議。也可以是上面21中協議類型的其中之一。
NETLINK_GENERIC是一個通用的協議類型,它是專門爲用戶使用的,因此,用戶可以直接使用它,而不必再添加新的協議類型。
對於每一個netlink協議類型,可以使用多播的概念,最多可以有 32個多播組,每一個多播組用一個位表示,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 */
};
說明:sa_family_t nl_family; //一般爲AF_NETLINK,
unsigned short nl_pad; //字段 nl_pad 當前沒有使用,因此要總是設置爲 0,
__u32 nl_pid; //綁定時用於指定綁定者的進程號,發送消息時用於指定接收進程號,如果希望內核處理多播消息,就把該字段設置爲 0,否則設置爲處理消息的進程 ID。傳遞給 bind 函數的地址的 nl_pid 字段應當設置爲本進程的進程 ID,這相當於 netlink socket 的本地地址。但是,對於一個netlink socket 的情況,字段 nl_pid 則可以設置爲其它的值,如:pthread_self() << 16 | getpid();因此字段 nl_pid 實際上未必是進程 ID,它只是用於區分不同的接收者或發送者的一個標識,用戶可以根據自己需要設置該字段。
__u32 nl_groups; //綁定時用於指定綁定者所要加入的多播組,這樣綁定者就可以接收多播消息,發送 消息時可以用於指定多播組,這樣就可以將消息發給多個接收者。這裏nl_groups 爲32位的無符號整形,所以可以指定32個多播組,每個進程可以加入多個多播組, 因爲多播組是通過“或”操作,如果設置爲 0,表示調用者不加入任何多播組。這裏就是Netlink多播的概念!和通信中的多播概念有點類似。
Bind的調用方式如下!
struct sockaddr_nl snl;
memset (&snl, 0, sizeof snl);
snl.nl_family = AF_NETLINK;
snl.nl_groups = groups;
ret = bind (sock, (struct sockaddr *) &snl, sizeof snl);
其中sock爲前面的 socket 調用返回的文件描述符,參數snl爲 struct
sockaddr_nl 類型的地址。 爲了發送一個 netlink 消息給內核或其他用戶態應用,需要填充目標 netlink
socket 地址 ,此時,字段 nl_pid 和 nl_groups 分別表示接收消息者的進程 ID 與多播組。如果字段 nl_pid 設置爲 0,表示消息接收者爲內核或多播組,如果 nl_groups爲 0,表示該消息爲單播消息,否則表示多播消息。用戶態使用函數 sendmsg 發送 netlink 消息時還需要引用結構 struct
msghdr、struct
nlmsghdr 和 struct
iovec,結構 struct
msghdr 的定義如下:
struct msghdr {
void * msg_name; /* Socket name */
int msg_namelen; /* Length of name */
struct iovec * msg_iov; /* Data blocks */
__kernel_size_t msg_iovlen; /* Number of blocks */
void * msg_control; /* Per protocol magic (eg BSD file descriptor passing) */
__kernel_size_t msg_controllen; /* Length of cmsg list */
unsigned msg_flags;
};
使用方法如下:
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&(snl);
msg.msg_namelen = sizeof(snl);
struct nlmsghdr 爲 netlink socket 自己的消息頭,因此它也被稱爲netlink 控制塊。應用層在向內核發送 netlink 消息時必須提供該控制頭。
消息頭。
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 PID */
};
字段 nlmsg_len 指定消息的總長度,包括緊跟該結構的數據部分長度以及該結構的大小,字段 nlmsg_type 用於應用內部定義消息的類型,它對netlink 內核實現是透明的,因此大部分情況下設置爲 0,字段 nlmsg_seq 和 nlmsg_pid 用於應用追蹤消息,前者表示順序號,後者爲消息來源進程 ID。字段 nlmsg_flags 用於設置消息標誌,可用的標誌包括下面的宏定義:kernel/include/linux/netlink.c
/* Flags values */
#define NLM_F_REQUEST 1 /* It is request message. */
#define NLM_F_MULTI 2 /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK 4 /* Reply with ack, with zero or error code */
#define NLM_F_ECHO 8 /* Echo this request */
/* Modifiers to GET request */
#define NLM_F_ROOT 0x100 /* specify tree root */
#define NLM_F_MATCH 0x200 /* return all matching */
#define NLM_F_ATOMIC 0x400 /* atomic GET */
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
/* Modifiers to NEW request */
#define NLM_F_REPLACE 0x100 /* Override existing */
#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */
#define NLM_F_CREATE 0x400 /* Create, if it does not exist */
#define NLM_F_APPEND 0x800 /* Add to end of list */
標誌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 指示在表末尾添加新的條目。
內核需要讀取和修改這些標誌,對於一般的使用,用戶把它設置爲 0 就可以,只是一些高級應用(如 netfilter 和路由 daemon 需要它進行一些設置),
下面是在調用sendmsg函數之前的各個結構體的賦值操作:
struct nlmsghdr *n
struct iovec iov = { (void*) n, n->nlmsg_len };
struct msghdr msg = {(void*) &snl, sizeof snl, &iov, 1, NULL, 0, 0};
其中snl爲 struct
sockaddr_nl snl;在結構體struct
msghdr中包含有struct
iovec結構,其實就是我們要傳輸的數據塊,它爲一個指針,定義了數據塊的基址和長度。
struct iovec
{
void __user *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
上面的數據結構全部初始化以後就可以調用sendmsg函數進行發送操作了。
status = sendmsg (sock, &msg, 0);
其中sock就是我們創建的sock套接字,msg就是上面結構體struct
msghdr的實例。如果我們需要返回一個ACK消息,可以對flags標誌進行設置如下:
/* Request an acknowledgement by setting NLM_F_ACK */
n->nlmsg_flags |= NLM_F_ACK;
使用下面的函數進行接收處理時,status;爲返回的狀態,這裏可能的結果爲:
-
#define NLMSG_NOOP 0x1 /* Nothing. */ #define NLMSG_ERROR 0x2 /* Error */ #define NLMSG_DONE 0x3 /* End of a dump */ #define NLMSG_OVERRUN 0x4 /* Data lost int status; char buf[4096]; struct iovec iov = { buf, sizeof buf }; struct sockaddr_nl snl; struct msghdr msg = { (void*)&snl, sizeof snl, &iov, 1, NULL, 0, 0}; struct nlmsghdr *h; status = recvmsg (sock, &msg, 0);
在linux/netlink.h中定義了一些方便對消息進行處理的宏,這些宏包括:
#define NLMSG_ALIGNTO 4
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
/*宏NLMSG_ALIGN(len)用於得到不小於len且字節對齊的最小數值*/
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN))
/*宏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的長度*/
在/kernel/net/netlink/af_netlink.c文件中定義了netlink套接字的結構體,
在af_netlink.c文件中我們可以看到netlink協議的註冊struct netlink_sock { /* struct sock has to be the first member of netlink_sock */ struct sock sk; u32 pid; //內核自己的pid,=0 u32 dst_pid; u32 dst_group;//目的組 u32 flags; u32 subscriptions; u32 ngroups;// 組數量 unsigned long *groups; //組號 unsigned long state; wait_queue_head_t wait;// 進程在接收數據包時等待隊列 struct netlink_callback *cb; spinlock_t cb_lock; void (*data_ready)(struct sock *sk, int bytes); ///內核態接收到用戶態信息後的處理函數 struct module *module; };
-
在static int __init netlink_proto_init(void)函數中會調用註冊協議的函數,對netlink協議進行註冊,其中,netlink_proto就是上面的struct proto netlink_proto協議。static struct proto netlink_proto = { .name = "NETLINK", .owner = THIS_MODULE, .obj_size = sizeof(struct netlink_sock), };
-
在內核中接收的數據和存儲發送的數據都是放在了skb_buff的結構體中int err = proto_register(&netlink_proto, 0);
-
使用下面的宏獲取skb_bff中的數據部分struct netlink_skb_parms { struct ucred creds; /* Skb credentials */ __u32 pid; __u32 dst_pid; __u32 dst_group; kernel_cap_t eff_cap; __u32 loginuid; /* Login (audit) uid */ };
-
#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))
實例:
這裏我以路由中的netlink爲例,看一下內核中的處理流程是怎麼樣的!在/kernel/net/core/rtnetlink.c文件中,有一個接收從用戶空間過來的Netlink消息的函數。 -
上面的內核函數就是用來接收用戶路由方面Netlink消息的,當我們使用route命令添加一條路由時,就會調用該函數接收。該函數是再netlink的初始化是註冊的。同樣在rtnetlink.c文件中。static void rtnetlink_rcv(struct sock *sk, int len) { unsigned int qlen = 0; do { rtnl_lock(); netlink_run_queue(sk, &qlen, &rtnetlink_rcv_msg); up(&rtnl_sem); netdev_run_todo(); } while (qlen); }
-
在netlink_kernel_create函數中,可以看到內核接收用戶空間傳過來的消息的接收函數,void __init rtnetlink_init(void) { int i; rtattr_max = 0; for (i = 0; i < ARRAY_SIZE(rta_max); i++) if (rta_max[i] > rtattr_max) rtattr_max = rta_max[i]; rta_buf = kmalloc(rtattr_max * sizeof(struct rtattr *), GFP_KERNEL); if (!rta_buf) panic("rtnetlink_init: cannot allocate rta_buf\n"); rtnl = netlink_kernel_create(NETLINK_ROUTE, RTNLGRP_MAX, rtnetlink_rcv, THIS_MODULE);//在創建內核的netlink時,註冊了路由netlink的接收函數,rtnetlink_rcv. if (rtnl == NULL) panic("rtnetlink_init: cannot initialize rtnetlink\n"); netlink_set_nonroot(NETLINK_ROUTE, NL_NONROOT_RECV); register_netdevice_notifier(&rtnetlink_dev_notifier); rtnetlink_links[PF_UNSPEC] = link_rtnetlink_table; rtnetlink_links[PF_PACKET] = link_rtnetlink_table; }
-
到此,內核創建netlink到接收用戶空間發送過來消息整個流程就清晰了。那當我們添加一條新路由時,在接收函數rtnetlink_rcv中的循環中,會從一個隊列中調用實際的接收處理函數,這裏爲rtnetlink_rcv_msg函數。struct sock * netlink_kernel_create(int unit, unsigned int groups, void (*input)(struct sock *sk, int len), struct module *module) { struct socket *sock; struct sock *sk; struct netlink_sock *nlk; if (!nl_table) return NULL; if (unit<0 || unit>=MAX_LINKS) return NULL; if (sock_create_lite(PF_NETLINK, SOCK_DGRAM, unit, &sock)) return NULL; if (__netlink_create(sock, unit) < 0) goto out_sock_release; sk = sock->sk; sk->sk_data_ready = netlink_data_ready; if (input) nlk_sk(sk)->data_ready = input;//設置內核接收Netlink消息的函數,這裏就是前面的rtnetlink_rcv函數 if (netlink_insert(sk, 0)) goto out_sock_release; nlk = nlk_sk(sk); //取得sock嵌入的netlink_sock結構體 nlk->flags |= NETLINK_KERNEL_SOCKET; netlink_table_grab(); nl_table[unit].groups = groups < 32 ? 32 : groups; nl_table[unit].module = module; nl_table[unit].registered = 1;// 更新netlink_table結構體信息,每中協議對應一個netlink_ table結構 netlink_table_ungrab(); return sk; out_sock_release: sock_release(sock); return NULL; }
-
/** * nelink_run_queue - Process netlink receive queue. * @sk: Netlink socket containing the queue * @qlen: Place to store queue length upon entry * @cb: Callback function invoked for each netlink message found * * Processes as much as there was in the queue upon entry and invokes * a callback function for each netlink message found. The callback * function may refuse a message by returning a negative error code * but setting the error pointer to 0 in which case this function * returns with a qlen != 0. * * qlen must be initialized to 0 before the initial entry, afterwards * the function may be called repeatedly until qlen reaches 0. */ void netlink_run_queue(struct sock *sk, unsigned int *qlen, int (*cb)(struct sk_buff *, struct nlmsghdr *, int *)) { struct sk_buff *skb; if (!*qlen || *qlen > skb_queue_len(&sk->sk_receive_queue)) *qlen = skb_queue_len(&sk->sk_receive_queue); for (; *qlen; (*qlen)--) { skb = skb_dequeue(&sk->sk_receive_queue); if (netlink_rcv_skb(skb, cb)) { if (skb->len) skb_queue_head(&sk->sk_receive_queue, skb); else { kfree_skb(skb); (*qlen)--; } break; } kfree_skb(skb); } }
下面是rtnetlink_rcv_msg()函數的實現,對netlink消息進行相應的處理。其中有一個數據結構
struct rtnetlink_link *link; 其定義如下:是兩個不同的處理函數
inet6_rtm_newroute函數通過下面的數組進行了相應的註冊處理,所以上面的link->doit(skb, nlh, (void *)&rta_buf[0])就是根據下面的這個調用的。struct rtnetlink_link { int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr); int (*dumpit)(struct sk_buff *, struct netlink_callback *cb); }; /* Process one rtnetlink message. */ static __inline__ int rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp) { struct rtnetlink_link *link; struct rtnetlink_link *link_tab; int sz_idx, kind; int min_len; int family; int type; int err; /* Only requests are handled by kernel now */ if (!(nlh->nlmsg_flags&NLM_F_REQUEST)) return 0; type = nlh->nlmsg_type; /* A control message: ignore them */ if (type < RTM_BASE) return 0; /* Unknown message: reply with EINVAL */ if (type > RTM_MAX) goto err_inval; type -= RTM_BASE; /* All the messages must have at least 1 byte length */ if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(struct rtgenmsg))) return 0; family = ((struct rtgenmsg*)NLMSG_DATA(nlh))->rtgen_family; if (family >= NPROTO) { *errp = -EAFNOSUPPORT; return -1; } link_tab = rtnetlink_links[family];//根據用戶空間傳過來的不同德family類型,調用不同的處理函數,這裏以路由爲例的話爲AF_ROUTE或者AF_NETLINK if (link_tab == NULL) link_tab = rtnetlink_links[PF_UNSPEC]; link = &link_tab[type]; //根據不同的type調用不同的處理函數。這裏的type爲RTM_NEWROUTE sz_idx = type>>2; kind = type&3; if (kind != 2 && security_netlink_recv(skb)) { *errp = -EPERM; return -1; } if (kind == 2 && nlh->nlmsg_flags&NLM_F_DUMP) { if (link->dumpit == NULL) link = &(rtnetlink_links[PF_UNSPEC][type]); if (link->dumpit == NULL) goto err_inval; if ((*errp = netlink_dump_start(rtnl, skb, nlh, link->dumpit, NULL)) != 0) { return -1; } netlink_queue_skip(nlh, skb); return -1; } memset(rta_buf, 0, (rtattr_max * sizeof(struct rtattr *))); min_len = rtm_min[sz_idx]; if (nlh->nlmsg_len < min_len) goto err_inval; if (nlh->nlmsg_len > min_len) { int attrlen = nlh->nlmsg_len - NLMSG_ALIGN(min_len); struct rtattr *attr = (void*)nlh + NLMSG_ALIGN(min_len); while (RTA_OK(attr, attrlen)) { unsigned flavor = attr->rta_type; if (flavor) { if (flavor > rta_max[sz_idx]) goto err_inval; rta_buf[flavor-1] = attr; } attr = RTA_NEXT(attr, attrlen); } } if (link->doit == NULL) link = &(rtnetlink_links[PF_UNSPEC][type]); if (link->doit == NULL) goto err_inval; err = link->doit(skb, nlh, (void *)&rta_buf[0]);//此處調用RTM_NEWROUTE,對應的route處理函數,也就是下面的inet6_rtm_newroute函數。 *errp = err; return err; err_inval: *errp = -EINVAL; return -1; } int inet6_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg) { struct rtmsg *r = NLMSG_DATA(nlh); struct in6_rtmsg rtmsg; if (inet6_rtm_to_rtmsg(r, arg, &rtmsg)) return -EINVAL; return ip6_route_add(&rtmsg, nlh, arg, &NETLINK_CB(skb)); }
-
static struct rtnetlink_link inet6_rtnetlink_table[RTM_NR_MSGTYPES] = { [RTM_GETLINK - RTM_BASE] = { .dumpit = inet6_dump_ifinfo, }, [RTM_NEWADDR - RTM_BASE] = { .doit = inet6_rtm_newaddr, }, [RTM_DELADDR - RTM_BASE] = { .doit = inet6_rtm_deladdr, }, [RTM_GETADDR - RTM_BASE] = { .dumpit = inet6_dump_ifaddr, }, [RTM_GETMULTICAST - RTM_BASE] = { .dumpit = inet6_dump_ifmcaddr, }, [RTM_GETANYCAST - RTM_BASE] = { .dumpit = inet6_dump_ifacaddr, }, [RTM_NEWROUTE - RTM_BASE] = { .doit = inet6_rtm_newroute, }, [RTM_DELROUTE - RTM_BASE] = { .doit = inet6_rtm_delroute, }, [RTM_GETROUTE - RTM_BASE] = { .doit = inet6_rtm_getroute, .dumpit = inet6_dump_fib, }, };
相關的結構體:
內核中所有的netlink套接字存儲在一個全局的哈新表中,該結構定義如下
static struct netlink_table *nl_table;其中每個協議對應一個哈希表,所有的同一種協議的數
據報散列在同哈希表中
下面爲一種協議所連接的哈希表結構:struct netlink_table {
struct nl_pid_hash hash; // 根據pid進行HASH的netlink sock鏈表, 相當於客戶端鏈表
struct hlist_head mc_list; // 多播的sock鏈表
unsigned int nl_nonroot; // 監聽者標誌
unsigned int groups; // 每個netlink的協議類型可以定義多個組, 8的倍數,最小是32
struct module *module;
int registered;
};最大可有MAX_LINKS(32)個表,處理不同協議類型的netlink套接口, 注意由於是自身的通信, 本機
同時作爲服務器和客戶端, 服務端需要一個套接口對應, 每個客戶端也要有一個套接口對應, 多個客戶端的套接口形成一個鏈表.
在kernel/include/linux/Net.h中struct hlist_head *table; // 鏈表節點,每個桶中協議的sock連入其中,根據哈希值可得確定 的sock unsigned long rehash_time; // 重新計算HASH的時間間隔 unsigned int mask; unsigned int shift; unsigned int entries; // 鏈表節點數 unsigned int max_shift; // 最大冪值 u32 rnd; // 隨機數 };
-
struct proto_ops { int family; struct module *owner; int (*release) (struct socket *sock); int (*bind) (struct socket *sock, struct sockaddr *myaddr, int sockaddr_len); int (*connect) (struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags); int (*socketpair)(struct socket *sock1, struct socket *sock2); int (*accept) (struct socket *sock, struct socket *newsock, int flags); int (*getname) (struct socket *sock, struct sockaddr *addr, int *sockaddr_len, int peer); unsigned int (*poll) (struct file *file, struct socket *sock, struct poll_table_struct *wait); int (*ioctl) (struct socket *sock, unsigned int cmd, unsigned long arg); int (*listen) (struct socket *sock, int len); int (*shutdown) (struct socket *sock, int flags); int (*setsockopt)(struct socket *sock, int level, int optname, char __user *optval, int optlen); int (*getsockopt)(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen); int (*sendmsg) (struct kiocb *iocb, struct socket *sock,//netlink套接字實際的發送與接收函數 struct msghdr *m, size_t total_len); int (*recvmsg) (struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len, int flags); int (*mmap) (struct file *file, struct socket *sock, struct vm_area_struct * vma); ssize_t (*sendpage) (struct socket *sock, struct page *page, int offset, size_t size, int flags); };
下面我們看看,當我們使用route命令添加一個新的路由是,這個函數的調用順序是怎麼樣的。下面是主要的函數;
Dput()
sys_sendmsg()//內核的接受函數
new_inode()
netlink_sendmsg//內核態接收用戶態發送的數據
rtnetlink_rcv()
netlink_run_queue()
rtnetlink_rcv_msg()
inet6_rtm_newroute()
在kernel/net/netlink/af_netlink.c文件中,內核態接收用戶態發送的數據,在netlink_sendskb函數中調用sock的隊列,執行相應的netlink接收函數
static int netlink_sendmsg(struct kiocb *kiocb, struct socket *sock, struct msghdr *msg, size_t len) { struct sock_iocb *siocb = kiocb_to_siocb(kiocb); struct sock *sk = sock->sk; struct netlink_sock *nlk = nlk_sk(sk); struct sockaddr_nl *addr=msg->msg_name; u32 dst_pid; u32 dst_group; struct sk_buff *skb; int err; struct scm_cookie scm; if (msg->msg_flags&MSG_OOB) return -EOPNOTSUPP; if (NULL == siocb->scm) siocb->scm = &scm; err = scm_send(sock, msg, siocb->scm); if (err < 0) return err; if (msg->msg_namelen) { if (addr->nl_family != AF_NETLINK) return -EINVAL; dst_pid = addr->nl_pid; dst_group = ffs(addr->nl_groups); if (dst_group && !netlink_capable(sock, NL_NONROOT_SEND)) return -EPERM; } else { dst_pid = nlk->dst_pid; dst_group = nlk->dst_group; } if (!nlk->pid) { err = netlink_autobind(sock); if (err) goto out; } err = -EMSGSIZE; if (len > sk->sk_sndbuf - 32) goto out; err = -ENOBUFS; skb = alloc_skb(len, GFP_KERNEL);// 分配一個sk_buff結構,將msghdr結構轉化爲sk_buff結構 if (skb==NULL) goto out; NETLINK_CB(skb).pid = nlk->pid;//填寫本地的pid信息 NETLINK_CB(skb).dst_pid = dst_pid; NETLINK_CB(skb).dst_group = dst_group; NETLINK_CB(skb).loginuid = audit_get_loginuid(current->audit_context); memcpy(NETLINK_CREDS(skb), &siocb->scm->creds, sizeof(struct ucred)); /* What can I do? Netlink is asynchronous, so that we will have to save current capabilities to check them, when this message will be delivered to corresponding kernel module. --ANK (980802) */ err = -EFAULT; //數據拷貝進sk_buff中 if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) { kfree_skb(skb); goto out; } err = security_netlink_send(sk, skb); if (err) { kfree_skb(skb); goto out; } if (dst_group) { atomic_inc(&skb->users); netlink_broadcast(sk, skb, dst_pid, dst_group, GFP_KERNEL); } err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT); out: return err; }
-