Netlink IPC 數據結構

#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 /* Unused number, formerly ip_queue */ #define NETLINK_SOCK_DIAG 4 /* 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 NETLINK_CRYPTO 21 /* Crypto layer */ #define NETLINK_INET_DIAG NETLINK_SOCK_DIAG #define MAX_LINKS 32
/* 其中(1)nl_family始終為AF_NETLINK; (2)nl_pad始終為0; (3)nl_pid為netlink套接字的單播地址,在發送消息時用於表示目的套接字的地址, 在用戶空間綁定時可以指定為當前進程的PID號(對於內核來說這個值為0)或者干脆不設置(在綁定bind時由內核調用netlink_autobind() 設置為當前進程的PID),但需要注意的是當用戶同一個進程中需要創建多個netlink套接字時則必須保證這個值是唯一的 (一般在多線程中可以使用”pthread_self() << 16 | getpid()“這樣的方法進行設置); (4)nl_groups表示組播組。在發送消息時用於表示目的多播組,在綁定地址時用於表示加入的多播組。這里nl_groups為一個32位無符號數, 其中的每一位表示一個 多播組, 一個netlink套接字可以加入多個多播組用以接收多個多播組的多播消息(最多支持32個)。 */ struct sockaddr_nl { __kernel_sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID */ __u32 nl_groups; /* multicast groups mask */ };
netlink消息同IP消息一樣,也需要遵循協議要求的格式,每個netlink消息的開頭是固定長度的netlink報頭,報頭后才是實際的載荷。netlink報頭一共占16個字節。
/* netlink消息同IP消息一樣,也需要遵循協議要求的格式,每個netlink消息的開頭是固定長度的netlink報頭, 報頭后才是實際的載荷。netlink報頭一共占16個字節,具體內容即同struct nlmsghdr中定義的一樣。 */ struct nlmsghdr { __u32 nlmsg_len; /* Length of message including header */ __u16 nlmsg_type; /* Message content nlmsg_type:消息狀態, 內核在include/uapi/linux/netlink.h中定義了以下4種通用的消息類型,它們分別是NLMSG_NOOP NLMSG_ERROR so on 除了這4種類型的消息以外,不同的netlink協議也可以自行添加自己所特有的消息類型, 但是內核定義了類型保留宏(#define NLMSG_MIN_TYPE 0x10),即小於該值的消息類型值由內核保留,不可用。*/ __u16 nlmsg_flags; /* Additional flags NLM_F_REQUEST so on */ __u32 nlmsg_seq; /* Sequence number 消息序列號,用以將消息排隊,有些類似TCP協議中的序號*/ __u32 nlmsg_pid; /* Sending process port ID 發送端口的ID號,對於內核來說該值就是0,對於用戶進程來說就是其socket所綁定的ID號*/ };
消息類型:

nlmsg_type:消息狀態其取值有:
NLMSG_NOOP:不執行任何動作,必須將該消息丟棄;
NLMSG_ERROR:消息發生錯誤;
NLMSG_DONE:標識分組消息的末尾;
NLMSG_OVERRUN:緩沖區溢出,表示某些消息已經丟失。
/* 除了這4種類型的消息以外,不同的netlink協議也可以自行添加自己所特有的消息類型, 但是內核定義了類型保留宏(#define NLMSG_MIN_TYPE 0x10),即小於該值的消息類型值由內核保留,不可用。 */ #define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */
Netlink消息處理宏
消息處理的宏定義

#define NLMSG_ALIGNTO 4U #define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) ) /* 對len執行4字節對齊 */ #define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) /* netlink消息頭長度 */ #define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN) /* netlink消息載荷len加上消息頭 */ #define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len)) /* 對netlink消息全長執行字節對齊 */ #define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0))) /* 獲取netlink消息實際載荷位置 */ #define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \ (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))/* 取得下一個消息的首地址,同時len也減少為剩余消息的總長度 */ #define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \ (nlh)->nlmsg_len <= (len)) /* 驗證消息的長度 */ #define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len))) /* 返回PAYLOAD的長度 */
/* netlink的消息頭后面跟着的是消息的有效載荷部分,它采用的是格式為“類型——長度——值”,簡寫TLV。 其中類型和長度使用屬性頭nlattr來表示。其中nla_len表示屬性長度;nla_type表示屬性類型, 它可以取值為以下幾種類型(定義在include\net\netlink.h中): ======================================================================== * Netlink Messages and Attributes Interface (As Seen On TV) * ------------------------------------------------------------------------ * Messages Interface * ------------------------------------------------------------------------ * * Message Format: * <--- nlmsg_total_size(payload) ---> * <-- nlmsg_msg_size(payload) -> * +----------+- - -+-------------+- - -+-------- - - * | nlmsghdr | Pad | Payload | Pad | nlmsghdr * +----------+- - -+-------------+- - -+-------- - - * nlmsg_data(nlh)---^ ^ * nlmsg_next(nlh)-----------------------+ * * Payload Format: * <---------------------- nlmsg_len(nlh) ---------------------> * <------ hdrlen ------> <- nlmsg_attrlen(nlh, hdrlen) -> * +----------------------+- - -+--------------------------------+ * | Family Header | Pad | Attributes | * +----------------------+- - -+--------------------------------+ * nlmsg_attrdata(nlh, hdrlen)---^ * * Data Structures: * struct nlmsghdr netlink message header * * Message Construction: * nlmsg_new() create a new netlink message * nlmsg_put() add a netlink message to an skb * nlmsg_put_answer() callback based nlmsg_put() * nlmsg_end() finalize netlink message * nlmsg_get_pos() return current position in message * nlmsg_trim() trim part of message * nlmsg_cancel() cancel message construction * nlmsg_free() free a netlink message * * Message Sending: * nlmsg_multicast() multicast message to several groups * nlmsg_unicast() unicast a message to a single socket * nlmsg_notify() send notification message * * Message Length Calculations: * nlmsg_msg_size(payload) length of message w/o padding * nlmsg_total_size(payload) length of message w/ padding * nlmsg_padlen(payload) length of padding at tail * * Message Payload Access: * nlmsg_data(nlh) head of message payload * nlmsg_len(nlh) length of message payload * nlmsg_attrdata(nlh, hdrlen) head of attributes data * nlmsg_attrlen(nlh, hdrlen) length of attributes data * * Message Parsing: * nlmsg_ok(nlh, remaining) does nlh fit into remaining bytes? * nlmsg_next(nlh, remaining) get next netlink message * nlmsg_parse() parse attributes of a message * nlmsg_find_attr() find an attribute in a message * nlmsg_for_each_msg() loop over all messages * nlmsg_validate() validate netlink message incl. attrs * nlmsg_for_each_attr() loop over all attributes * * Misc: * nlmsg_report() report back to application? * * ------------------------------------------------------------------------ * Attributes Interface * ------------------------------------------------------------------------ * * Attribute Format: * <------- nla_total_size(payload) -------> * <---- nla_attr_size(payload) -----> * +----------+- - -+- - - - - - - - - +- - -+-------- - - * | Header | Pad | Payload | Pad | Header * +----------+- - -+- - - - - - - - - +- - -+-------- - - * <- nla_len(nla) -> ^ * nla_data(nla)----^ | * nla_next(nla)-----------------------------' * * Data Structures: * struct nlattr netlink attribute header * * Attribute Construction: * nla_reserve(skb, type, len) reserve room for an attribute * nla_reserve_nohdr(skb, len) reserve room for an attribute w/o hdr * nla_put(skb, type, len, data) add attribute to skb * nla_put_nohdr(skb, len, data) add attribute w/o hdr * nla_append(skb, len, data) append data to skb * * Attribute Construction for Basic Types: * nla_put_u8(skb, type, value) add u8 attribute to skb * nla_put_u16(skb, type, value) add u16 attribute to skb * nla_put_u32(skb, type, value) add u32 attribute to skb * nla_put_u64_64bits(skb, type, * value, padattr) add u64 attribute to skb * nla_put_s8(skb, type, value) add s8 attribute to skb * nla_put_s16(skb, type, value) add s16 attribute to skb * nla_put_s32(skb, type, value) add s32 attribute to skb * nla_put_s64(skb, type, value, * padattr) add s64 attribute to skb * nla_put_string(skb, type, str) add string attribute to skb * nla_put_flag(skb, type) add flag attribute to skb * nla_put_msecs(skb, type, jiffies, * padattr) add msecs attribute to skb * nla_put_in_addr(skb, type, addr) add IPv4 address attribute to skb * nla_put_in6_addr(skb, type, addr) add IPv6 address attribute to skb * * Nested Attributes Construction: * nla_nest_start(skb, type) start a nested attribute * nla_nest_end(skb, nla) finalize a nested attribute * nla_nest_cancel(skb, nla) cancel nested attribute construction * * Attribute Length Calculations: * nla_attr_size(payload) length of attribute w/o padding * nla_total_size(payload) length of attribute w/ padding * nla_padlen(payload) length of padding * * Attribute Payload Access: * nla_data(nla) head of attribute payload * nla_len(nla) length of attribute payload * * Attribute Payload Access for Basic Types: * nla_get_u8(nla) get payload for a u8 attribute * nla_get_u16(nla) get payload for a u16 attribute * nla_get_u32(nla) get payload for a u32 attribute * nla_get_u64(nla) get payload for a u64 attribute * nla_get_s8(nla) get payload for a s8 attribute * nla_get_s16(nla) get payload for a s16 attribute * nla_get_s32(nla) get payload for a s32 attribute * nla_get_s64(nla) get payload for a s64 attribute * nla_get_flag(nla) return 1 if flag is true * nla_get_msecs(nla) get payload for a msecs attribute * * Attribute Misc: * nla_memcpy(dest, nla, count) copy attribute into memory * nla_memcmp(nla, data, size) compare attribute with memory area * nla_strlcpy(dst, nla, size) copy attribute to a sized string * nla_strcmp(nla, str) compare attribute with string * * Attribute Parsing: * nla_ok(nla, remaining) does nla fit into remaining bytes? * nla_next(nla, remaining) get next netlink attribute * nla_validate() validate a stream of attributes * nla_validate_nested() validate a stream of nested attributes * nla_find() find attribute in stream of attributes * nla_find_nested() find attribute in nested attributes * nla_parse() parse and validate stream of attrs * nla_parse_nested() parse nested attribuets * nla_for_each_attr() loop over all attributes * nla_for_each_nested() loop over the nested attributes *========================================================================= */
socket 發送netlink消息

#define TEST_DATA_LEN 16 #define TEST_DATA "netlink send test" /* 僅作為示例,內核NETLINK_ROUTE套接字無法解析 */ struct sockaddr_nl nladdr; struct msghdr msg; struct nlmsghdr *nlhdr; struct iovec iov; /* 填充目的地址結構 */ memset(&nladdr, 0, sizeof(nladdr)); nladdr.nl_family = AF_NETLINK; nladdr.nl_pid = 0; /* 地址為內核 */ nladdr.nl_groups = 0; /* 單播 */ /* 填充netlink消息頭 */ nlhdr = (struct nlmsghdr *)malloc(NLMSG_SPACE(TEST_DATA_LEN)); nlhdr->nlmsg_len = NLMSG_LENGTH(TEST_DATA_LEN); nlhdr->nlmsg_flags = NLM_F_REQUEST; nlhdr->nlmsg_pid = get_pid(); /* 當前套接字所綁定的ID號(此處為本進程的PID) */ nlhdr->nlmsg_seq = 0; /* 填充netlink消息實際載荷 */ strcpy(NLMSG_DATA(nlhdr), TEST_DATA); iov.iov_base = (void *)nlhdr; iov.iov_len = nlhdr->nlmsg_len; /* 填充數據消息結構 */ memset(&msg, 0, sizeof(msg)); msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; /* 發送netlink消息 */ sendmsg (sock, &msg, 0); /* sock 為NETLINK_ROUTE類型套接字 */ 這里調用了socket所綁定協議特有的數據發送鈎子函數,其中最后一個參數為msg->msg_iter->count, 即消息實際載荷的總長度。在前一篇文章中已經看到了對於netlink類型的套接字來說該函數被注冊為netlink_sendmsg(), 下面來分析這個函數,這個函數較長,分段分析:
根據socket 層代碼分析可知:
send(fd......) 最后回調socket所綁定協議特有的數據發送鈎子函數,其中最后一個參數為msg->msg_iter->count,即消息實際載荷的總長度。
對於netlink類型的套接字來說該函數被注冊為netlink_sendmsg()
static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) { struct sock *sk = sock->sk; struct netlink_sock *nlk = nlk_sk(sk); DECLARE_SOCKADDR(struct sockaddr_nl *, addr, msg->msg_name); u32 dst_portid; u32 dst_group; struct sk_buff *skb; int err; struct scm_cookie scm; u32 netlink_skb_flags = 0; if (msg->msg_flags&MSG_OOB) return -EOPNOTSUPP; /*這里定義了一個struct sockaddr_nl *addr指針,它指向了msg->msg_name表示消息的目的地址(會做地址長度檢查); 然后調用scm_send()發送消息輔助數據 */err = scm_send(sock, msg, &scm, true); if (err < 0) return err; /* 如果用戶指定了netlink消息的目的地址,則對其進行校驗,然后判斷當前netlink協議的NL_CFG_F_NONROOT_SEND標識是否設置, 如果設置了該標識則允許非root用戶發送組播,對於NETLINK_ROUTE類型的netlink套接字, 並沒有設置該標識,表明非root用戶不能發送組播消息; 然后設置NETLINK_SKB_DST標識。如果用戶沒有指定netlink消息的目的地址, 則使用netlink套接字默認的(該值默認為0,會在調用connect系統調用時在netlink_connect()中被賦值為用戶設置的值)。 注意這里dst_group經過ffs的處理后轉化為組播地址位數(找到最低有效位)。 ? */ if (msg->msg_namelen) { err = -EINVAL; if (addr->nl_family != AF_NETLINK) goto out; dst_portid = addr->nl_pid; dst_group = ffs(addr->nl_groups);//用於查找一個整數中的第一個置位值(也就是bit為1的位) err = -EPERM; if ((dst_group || dst_portid) && !netlink_allowed(sock, NL_CFG_F_NONROOT_SEND)) goto out; netlink_skb_flags |= NETLINK_SKB_DST; } else {//沒有指定netlink 消息接收地址 使用默認的 dst_portid = nlk->dst_portid; dst_group = nlk->dst_group; } if (!nlk->bound) {//來判斷當前的netlink套接字是否被綁定過,如果沒有綁定過這里調用netlink_autobind()進行動態綁定 err = netlink_autobind(sock); if (err) goto out; } else { /* Ensure nlk is hashed and visible. */ smp_rmb(); } //接下來判斷需要發送的數據是否過長(長於發送緩存大小),然后通過netlink_alloc_large_skb分配skb結構(傳入的參數為消息載荷的長度以及組播地址)。 err = -EMSGSIZE; if (len > sk->sk_sndbuf - 32) goto out; err = -ENOBUFS; skb = netlink_alloc_large_skb(len, dst_group); if (skb == NULL) goto out; /* 在成功創建skb結構之后,這里就開始初始化它,這里使用到了skb中的擴展cb字段(char cb[48] __aligned(8), 一共48個字節用於存放netlink的地址和標識相關的附加信息足夠了),同時使用宏NETLINK_CB來操作這些字段。 netlink將skb的cb字段強制定義為struct netlink_skb_parms結構: */ NETLINK_CB(skb).portid = nlk->portid; NETLINK_CB(skb).dst_group = dst_group; NETLINK_CB(skb).creds = scm.creds; NETLINK_CB(skb).flags = netlink_skb_flags; /* 其中portid表示原端套接字所綁定的id,dst_group表示消息目的組播地址,flag為標識,sk指向原端套接字的sock結構。 這里首先將套接字綁定的portid賦值到skb得cb字段中、同時設置組播地址的數量以及netlink_skb標識(這里是已經置位NETLINK_SKB_DST)。 接下來調用最關鍵的調用memcpy_from_msg拷貝數據,它首先調用skb_put調整skb->tail指針, 然后執行copy_from_iter(data, len, &msg->msg_iter)將數據從msg->msg_iter中傳輸到skb->data中 (這是第一次內存拷貝動作!將用戶空間數據直接拷貝到內核skb中)。 */ err = -EFAULT; if (memcpy_from_msg(skb_put(skb, len), msg, len)) { kfree_skb(skb); goto out; } err = security_netlink_send(sk, skb); if (err) { kfree_skb(skb); goto out; } /* 如果是組播發送則調用netlink_broadcast()發送消息,否則調用netlink_unicast()發送單播消息 */ if (dst_group) { atomic_inc(&skb->users); netlink_broadcast(sk, skb, dst_portid, dst_group, GFP_KERNEL); } err = netlink_unicast(sk, skb, dst_portid, msg->msg_flags&MSG_DONTWAIT); out: scm_destroy(&scm); return err; }
單播發送函數netlink_unicast()
/* 內核可以通過nlmsg_unicast()函數向應用層發送單播消息,由各個netlink協議負責調用,也有的協議是直接調用netlink_unicast()函數, 其實nlmsg_unicast()也僅是netlink_unicast()的一個封裝而已: */ int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 portid, int nonblock) { struct sock *sk; int err; long timeo; /* 這里首先調用netlink_trim()重新裁剪skb的數據區的大小,這可能會clone出一個新的skb結構同時重新分配skb->data的內存空間 (這就出現了第三次的內存拷貝動作!),當然如果原本skb中多余的內存數據區非常小或者該內存空間是在vmalloc空間中的就不會執行上述操作, 我們現在跟隨的情景上下文中就是后一種情況,並不會重新分配空間。 接下來記下發送超時等待時間,如果已經設置了MSG_DONTWAIT標識,則等待時間為0,否則返回sk->sk_sndtimeo (該值在sock初始化時由sock_init_data()函數賦值為MAX_SCHEDULE_TIMEOUT)。 */ skb = netlink_trim(skb, gfp_any()); timeo = sock_sndtimeo(ssk, nonblock); retry: sk = netlink_getsockbyportid(ssk, portid);//根據目的portid號和原端sock結構查找目的端的sock結構 if (IS_ERR(sk)) { kfree_skb(skb); return PTR_ERR(sk); } if (netlink_is_kernel(sk))//目的地址是內核空間,則調用netlink_unicast_kernel向內核進行單播,入參是目的sock、數據skb 原端sock。 return netlink_unicast_kernel(sk, skb, ssk); //這里首先sk_filter執行防火牆的過濾,確保可以發送以后調用netlink_attachskb將要發送的skb綁定到netlink sock上 if (sk_filter(sk, skb)) { err = skb->len; kfree_skb(skb); sock_put(sk); return err; } /* 目的sock的接收緩沖區剩余的的緩存大小小於已經提交的數據量,或者標志位已經置位了阻塞標識NETLINK_CONGESTED, 這表明數據不可以立即的送到目的端的接收緩存中。因此, 在原端不是內核socket且沒有設置非阻塞標識的情況下會定義一個等待隊列並等待指定的時間並返回1, 否則直接丟棄該skb數據包並返回失敗。 若目的端的接收緩存區空間足夠,就會調用netlink_skb_set_owner_r進行綁定。 回到netlink_unicast()函數中,可以看到若執行netlink_attachskb()的返回值為1,就會再次嘗試發送操作。 最后調用netlink_sendskb()執行發送操作 */ err = netlink_attachskb(sk, skb, &timeo, ssk); if (err == 1) goto retry; if (err) return err; return netlink_sendskb(sk, skb);
/* 內核可以通過nlmsg_unicast()函數向應用層發送單播消息,由各個netlink協議負責調用,也有的協議是直接調用netlink_unicast()函數, 其實nlmsg_unicast()也僅是netlink_unicast()的一個封裝而已: */ int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 portid, int nonblock) { struct sock *sk; int err; long timeo; /* 這里首先調用netlink_trim()重新裁剪skb的數據區的大小,這可能會clone出一個新的skb結構同時重新分配skb->data的內存空間 (這就出現了第三次的內存拷貝動作!),當然如果原本skb中多余的內存數據區非常小或者該內存空間是在vmalloc空間中的就不會執行上述操作, 我們現在跟隨的情景上下文中就是后一種情況,並不會重新分配空間。 接下來記下發送超時等待時間,如果已經設置了MSG_DONTWAIT標識,則等待時間為0,否則返回sk->sk_sndtimeo (該值在sock初始化時由sock_init_data()函數賦值為MAX_SCHEDULE_TIMEOUT)。 */ skb = netlink_trim(skb, gfp_any()); timeo = sock_sndtimeo(ssk, nonblock); retry: sk = netlink_getsockbyportid(ssk, portid);//根據目的portid號和原端sock結構查找目的端的sock結構 if (IS_ERR(sk)) { kfree_skb(skb); return PTR_ERR(sk); } if (netlink_is_kernel(sk))//目的地址是內核空間,則調用netlink_unicast_kernel向內核進行單播,入參是目的sock、數據skb 原端sock。 return netlink_unicast_kernel(sk, skb, ssk); //這里首先sk_filter執行防火牆的過濾,確保可以發送以后調用netlink_attachskb將要發送的skb綁定到netlink sock上 if (sk_filter(sk, skb)) { err = skb->len; kfree_skb(skb); sock_put(sk); return err; } /* 目的sock的接收緩沖區剩余的的緩存大小小於已經提交的數據量,或者標志位已經置位了阻塞標識NETLINK_CONGESTED, 這表明數據不可以立即的送到目的端的接收緩存中。因此, 在原端不是內核socket且沒有設置非阻塞標識的情況下會定義一個等待隊列並等待指定的時間並返回1, 否則直接丟棄該skb數據包並返回失敗。 若目的端的接收緩存區空間足夠,就會調用netlink_skb_set_owner_r進行綁定。 回到netlink_unicast()函數中,可以看到若執行netlink_attachskb()的返回值為1,就會再次嘗試發送操作。 最后調用netlink_sendskb()執行發送操作 */ err = netlink_attachskb(sk, skb, &timeo, ssk); if (err == 1) goto retry; if (err) return err; return netlink_sendskb(sk, skb); } EXPORT_SYMBOL(netlink_unicast)
單播發送消息:

static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb) { int len = skb->len; netlink_deliver_tap(skb); //這里的sk_data_ready()鈎子函數在初始化netlink函數sock_init_data()中被注冊為sock_def_readable(),進入分析一下 //對於內核的netlink來說內核netlink的創建函數中已經將其注冊為 netlink_data_ready 所以內核不能收到組播 /* static void netlink_data_ready(struct sock *sk) { BUG(); } */ skb_queue_tail(&sk->sk_receive_queue, skb); sk->sk_data_ready(sk);//里喚醒目的接收端socket的等待隊列,這樣應用層套接字就可以接收並處理消息了。 return len; }
對於根據portid 查找sk
首先調用netlink_lookup執行查找,查找的命名空間和協議號同原端sock,它會從nl_table[protocol]的哈希表中找到已經注冊的目的端sock套接字。找到以后執行校驗,如若找到的socket已經connect了,則它的目的portid必須是原端的portid。
接下來判斷目的的netlink socket是否是內核的netlink socket
static int netlink_unicast_kernel(struct sock *sk, struct sk_buff *skb, struct sock *ssk) { int ret; struct netlink_sock *nlk = nlk_sk(sk); /* 目標netlink套接字是否注冊了netlink_rcv()接收函數,如果沒有則直接丟棄該數據包,否則繼續發送流程,這里首先設置一些標識: skb->sk = sk; 將目的sock賦值給skb->sk指針 skb->destructor = netlink_skb_destructor; 注冊destructor鈎子函數 NETLINK_CB(skb).sk = ssk; 將原端的sock保存早skb的cb擴展字段中 最后就調用了nlk->netlink_rcv(skb)函數將消息送到內核中的目的netlink套接字中了 在內核注冊netlink套接字的時候已經將其接收函數注冊到了netlink_rcv中: struct sock * __netlink_kernel_create(struct net *net, int unit, struct module *module, struct netlink_kernel_cfg *cfg) { ...... if (cfg && cfg->input) nlk_sk(sk)->netlink_rcv = cfg->input;
對於NETLINK_ROUTE類型的套接字來說就是rtnetlink_rcv了,netlink_rcv()鈎子函數會接收並解析用戶傳下來的數據 */ ret = -ECONNREFUSED; if (nlk->netlink_rcv != NULL) { ret = skb->len; netlink_skb_set_owner_r(skb, sk); NETLINK_CB(skb).sk = ssk; netlink_deliver_tap_kernel(sk, ssk, skb); nlk->netlink_rcv(skb); consume_skb(skb); } else { kfree_skb(skb); } sock_put(sk); return ret; } /* 內核可以通過nlmsg_unicast()函數向應用層發送單播消息,由各個netlink協議負責調用,也有的協議是直接調用netlink_unicast()函數, 其實nlmsg_unicast()也僅是netlink_unicast()的一個封裝而已: */
Netlink發送廣播消息:
int netlink_broadcast_filtered(struct sock *ssk, struct sk_buff *skb, u32 portid, u32 group, gfp_t allocation, int (*filter)(struct sock *dsk, struct sk_buff *skb, void *data), void *filter_data) { struct net *net = sock_net(ssk); struct netlink_broadcast_data info; struct sock *sk; skb = netlink_trim(skb, allocation); info.exclude_sk = ssk; info.net = net; info.portid = portid; info.group = group; info.failure = 0; info.delivery_failure = 0; info.congested = 0; info.delivered = 0; info.allocation = allocation; info.skb = skb; info.skb2 = NULL; info.tx_filter = filter; info.tx_data = filter_data; /* While we sleep in clone, do not allow to change socket list */ netlink_lock_table(); /* 初始化netlink組播數據結構netlink_broadcast_data,其中info.group中保存了目的組播地址, 然后從nl_table[ssk->sk_protocol].mc_list里邊查找加入組播組的socket,並調用do_one_broadcast()函數依次發送組播數據: */ sk_for_each_bound(sk, &nl_table[ssk->sk_protocol].mc_list) do_one_broadcast(sk, &info); consume_skb(skb); netlink_unlock_table(); if (info.delivery_failure) { kfree_skb(info.skb2); return -ENOBUFS; } consume_skb(info.skb2); if (info.delivered) { if (info.congested && gfpflags_allow_blocking(allocation)) yield(); return 0; } return -ESRCH; }

static void do_one_broadcast(struct sock *sk, struct netlink_broadcast_data *p) { struct netlink_sock *nlk = nlk_sk(sk); int val; if (p->exclude_sk == sk) return; //這里會確保原端sock和目的端sock不是同一個,它們屬於同一個網絡命名空間,目的的組播地址為發送的目的組播地址等等 if (nlk->portid == p->portid || p->group - 1 >= nlk->ngroups || !test_bit(p->group - 1, nlk->groups)) return; if (!net_eq(sock_net(sk), p->net)) { if (!(nlk->flags & NETLINK_F_LISTEN_ALL_NSID)) return; if (!peernet_has_id(sock_net(sk), p->net)) return; if (!file_ns_capable(sk->sk_socket->file, p->net->user_ns, CAP_NET_BROADCAST)) return; } if (p->failure) { netlink_overrun(sk); return; } sock_hold(sk); if (p->skb2 == NULL) { if (skb_shared(p->skb)) { p->skb2 = skb_clone(p->skb, p->allocation); } else { p->skb2 = skb_get(p->skb); /* * skb ownership may have been set when * delivered to a previous socket. */ skb_orphan(p->skb2); } } if (p->skb2 == NULL) { netlink_overrun(sk); /* Clone failed. Notify ALL listeners. */ p->failure = 1; if (nlk->flags & NETLINK_F_BROADCAST_SEND_ERROR) p->delivery_failure = 1; goto out; } if (p->tx_filter && p->tx_filter(sk, p->skb2, p->tx_data)) { kfree_skb(p->skb2); p->skb2 = NULL; goto out; } if (sk_filter(sk, p->skb2)) { kfree_skb(p->skb2); p->skb2 = NULL; goto out; } NETLINK_CB(p->skb2).nsid = peernet2id(sock_net(sk), p->net); NETLINK_CB(p->skb2).nsid_is_set = true; /* 內核netlink套接字是無論如何也不應該接收到組播消息的。 但是對於應用層netlink套接字,該sk_data_ready()鈎子函數在初始化netlink函數sock_init_data()中被注冊為sock_def_readable(), 這個函數后面再分析。 */ val = netlink_broadcast_deliver(sk, p->skb2);//netlink_broadcast_deliver()函數對目的sock發送數據skb: if (val < 0) { netlink_overrun(sk); if (nlk->flags & NETLINK_F_BROADCAST_SEND_ERROR) p->delivery_failure = 1; } else { p->congested |= val; p->delivered = 1; p->skb2 = NULL; } out: sock_put(sk); }
內核netlink套接字是無論如何也不應該接收到組播消息的。但是對於應用層netlink套接字,該sk_data_ready()鈎子函數在初始化netlink函數sock_init_data()中被注冊為sock_def_readable()
static int __netlink_sendskb(struct sock *sk, struct sk_buff *skb) { int len = skb->len; netlink_deliver_tap(skb); //這里的sk_data_ready()鈎子函數在初始化netlink函數sock_init_data()中被注冊為sock_def_readable(),進入分析一下 //對於內核的netlink來說內核netlink的創建函數中已經將其注冊為 netlink_data_ready 所以內核不能收到組播 /* static void netlink_data_ready(struct sock *sk) { BUG(); } */ skb_queue_tail(&sk->sk_receive_queue, skb); sk->sk_data_ready(sk);//里喚醒目的接收端socket的等待隊列,這樣應用層套接字就可以接收並處理消息了。 return len; }