通过libnl
能够很快的编写一个netlink程序框架,隐藏了socket,bind,send/recv等复杂调用,
但是数据的构造和解析还是很头疼的,尤其对于我这样的初学者来说,下面按TCP/IP分层模型来学学Netlink消息格式。
下面以netlink第一、二、三层类比TCP/IP模型中的网络层,传输层和应用层
Netlink消息第一层netlink protocol
,有下面这20种
#define NETLINK_ROUTE 0 /* Routing/device hook */
#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
#define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */
#define NETLINK_ECRYPTFS 19
#define NETLINK_RDMA 20
#define NETLINK_CRYPTO 21 /* Crypto layer */
Netlink消息第二层,以NETLINK_ROUTE
为例
• LINKS
• ADDRESSES
• ROUTES
• NEIGHBORS
• RULES
• DISCIPLINES
• CLASSES
• FILTERS
Netlink消息第三层,以ROUTES
为例
封装的ROUTE的属性头部及数据
netlink 数据结构可形象的表示为
<----- NLMSG_HDRLEN -----> <-------- RTM_PAYOAD(rtm) --->
<RTA_PAYLOAD(r)>
+------------------+- - -+---------------+- - -+--------------+--------+ - -+
| Netlink Header | Pad | Family Header | Pad | Attributes | rtattr | Pad|
| struct nlmsghdr | | struct rtmsg | | stuct rtattr | data | |
+------------------+- - -+---------------+- - -+--------------+--------+ - -+
^ ^ ^ ^ ^
nlh | | | |
NLMSG_DATA(nlh) --------^ | | |
RTM_RTA(rtm)-----------------------------------^ | |
RTA_DATA(rta)-------------------------------------------------^ RTA_NEXT(rta)
Pad
表示为字节对齐所填充的数据。
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 */
};
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
#define NLMSG_NEXT(nlh,len) ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \
(struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_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)))
NLMSG_ALIGNTO
字节对齐的值,这里按4字节对齐,4U的意思就是 (unsigned int)4。
NLMSG_ALIGN(len)
按4字节对齐的长度,返回字节对齐后的值align_len,len=< align_len <=len+4。
NLMSG_HDRLEN
struct nlmsghdr 所占内存大小的对齐后值
NLMSG_LENGTH(len)
nlmsghdr长度加上len
NLMSG_SPACE(len)
定义如上,作用参见下面的RTM_PAYLOAD(n)
NLMSG_DATA(nlh)
从nlh首地址向后移动到data起始位置
NLMSG_NEXT(nlh, len)
使用了逗号表达式完成两件事,先调整剩余长度len,减去当前nlmsg的总长度;再定位到下一个nlmsg的起始位置。
NLMSG_OK(nlh, len)
检查nlmsg是否合法,剩余数据长度len要大于nlmsg头部长度,nlh->nlmsg_len 要大于nlmsg头部长度并且小于剩余长度len。
NLMSG_PAYLOAD(nlh, len)
nlmsg去掉头部的数据长度。
struct rtmsg {
unsigned char rtm_family;
unsigned char rtm_dst_len;
unsigned char rtm_src_len;
unsigned char rtm_tos;
unsigned char rtm_table; /* Routing table id */
unsigned char rtm_protocol; /* Routing protocol; see below */
unsigned char rtm_scope; /* See below */
unsigned char rtm_type; /* See below */
unsigned rtm_flags;
};
#define RTM_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct rtmsg))))
#define RTM_PAYLOAD(n) NLMSG_PAYLOAD(n,sizeof(struct rtmsg))
RTM_RTA(r)
输入route message指针 struct rtmsg* r,返回route第一个属性首地址
RTM_PAYLOAD(n)
- RTM_PAYLOAD(n)
- NLMSG_PAYLOAD(n,sizeof(struct rtmsg))
- ((n)->nlmsg_len - NLMSG_SPACE(sizeof(struct rtmsg)))
- ((n)->nlmsg_len - NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct rtmsg))))
- ((n)->nlmsg_len - NLMSG_ALIGN((sizeof(struct rtmsg) + NLMSG_HDRLEN)))
- ((n)->nlmsg_len - NLMSG_ALIGN((sizeof(struct rtmsg) + ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))))))
去除字节对齐,,将n替换成 nlmsghdr*nlh
可以简化成如下
((nlh)->nlmsg_len - sizeof(struct rtmsg) - sizeof(struct nlmsghdr))
即rtmsg层封装的数据长度,相当于TCP数据包去掉IP报头和TCP报头长度得到TCP数据部分长度。
struct rtattr {
unsigned short rta_len; //整个属性部分的长度,包括头部和数据
unsigned short rta_type; //
};
/* Macros to handle rtattributes */
#define RTA_ALIGNTO 4
#define RTA_ALIGN(len) ( ((len)+RTA_ALIGNTO-1) & ~(RTA_ALIGNTO-1) )
#define RTA_OK(rta,len) ((len) >= (int)sizeof(struct rtattr) && \
(rta)->rta_len >= sizeof(struct rtattr) && \
(rta)->rta_len <= (len))
#define RTA_NEXT(rta,attrlen) ((attrlen) -= RTA_ALIGN((rta)->rta_len), \
(struct rtattr*)(((char*)(rta)) + RTA_ALIGN((rta)->rta_len)))
#define RTA_LENGTH(len) (RTA_ALIGN(sizeof(struct rtattr)) + (len))
#define RTA_SPACE(len) RTA_ALIGN(RTA_LENGTH(len))
#define RTA_DATA(rta) ((void*)(((char*)(rta)) + RTA_LENGTH(0)))
#define RTA_PAYLOAD(rta) ((int)((rta)->rta_len) - RTA_LENGTH(0))
RTA_ALIGNTO
字节对齐的值,这里按4字节对齐。
RTA_ALIGN(len)
输入一个长度len,返回字节对齐后的值align_len,len=< align_len <=len+RTA_ALIGNTO。
RTA_OK(rta,len)
输入一个struct rtattr* rta,和整个rtmsg剩余长度len,返回一个bool值,判断一个属性rta是否正确,条件有3个。
RTA_NEXT(rta,attrlen)
用了逗号表达式,先对attrlen减去rta属性内容的全部长度,然后返回下一个rtattr的首地址。
RTA_LENGTH(len)
struct rtattr 对齐后内存大小 加上 len。
RTA_SPACE
定义如下,目前在代码中没找到用处。
RTA_DATA(rta)
返回rta数据的起始位置,即rta位置向后移动一个 对齐的struct rtattr大小。
RTA_PAYLOAD(rta)
返回有效数据的长度。