首先介绍netlink套接字以及它支持的协议族的不同类型。然后详细介绍netlink套接字在启动时如何注册。另外,还将分别阐述如何创建内核和用户netlink套接字、netlink数据结构的细节以及netlink报文的格式。最后,本博客还会详细说明netlink用户如何与内核套接字进行交互。
netlink是一种在内核模块与用户空间进程之间传输数据的双向通信方法。其功能由为用户空间进程提供的标准套接字API和为内核模块提供的内部内核API组成。
Linux支持的netlink族如下:
为什么选择netlink套接字?
启动时,当加载netlink模块(net/netlink/af_netlink.c)时,module_init会调用netlink_proto_init初始化例程:
static int __init netlink_proto_init(void)
{
struct sk_buff *dummy_skb;
if (sizeof(struct netlink_skb_parms) > sizeof(dummy_skb->cb)) {
printk(KERN_CRIT "netlink_init: panic\n");
return -1;
}
sock_register(&netlink_family_ops);
#ifdef CONFIG_PROC_FS
create_proc_read_entry("net/netlink", 0, 0, netlink_read_proc, NULL);
#endif
return 0;
}
在netlink_proto_init例程中,以参数netlink_family_ops调用sock_register。
netlink_family_ops是一种net_proto_family结构,如果是netlink协议,则其定义如下所示,其中PF_NETLINK是协议类型族。netlink_create是PF_NETLINK套接字的创建函数。
struct net_proto_family netlink_family_ops = {
PF_NETLINK,
netlink_create
};
函数sock_register的主要目的是通知协议处理程序的地址族,并购将其链接到套接字模块。
/*
* This function is called by a protocol handler that wants to
* advertise its address family, and have it linked into the
* SOCKET module.
*/
int sock_register(struct net_proto_family *ops)
{
int err;
if (ops->family >= NPROTO) {
printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family, NPROTO);
return -ENOBUFS;
}
net_family_write_lock();
err = -EEXIST;
if (net_families[ops->family] == NULL) {
net_families[ops->family]=ops;
err = 0;
}
net_family_write_unlock();
return err;
}
sock_register在1614行检查net_families表中的套接字系统的协议族表项,然后在1615行将协议族表项插入到net_families表中(在这种情况下,它是netlink协议)。
表net_families是net_proto_family指针的数组,其中所有的协议族都已经注册。表net_families的定义如下所示,其中NPROTO是允许注册的最大协议数目。在内核中其值被设置为32。
static struct net_proto_family *net_families[NPROTO];
在Linux启动时,CPU子系统启动并运行,内存和进程管理开始工作,函数do_basic_setup通过在第713行调用函数sock_init进行网络初始化,如下源码所示:
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init do_basic_setup(void)
{
......
/* Networking initialization needs a process context */
sock_init();
......
}
函数sock_init在第1657行和1658行初始化所有的地址(协议)族。这里,我们对协议模块的初始化感兴趣,尤其时netlink协议。为了初始化netlink协议,在第1697行调用了rtnetlink_init函数来初始化和创建内核netlink套接字。
void __init sock_init(void)
{
......
for (i = 0; i < NPROTO; i++)
net_families[i] = NULL;
......
#ifdef CONFIG_RTNETLINK
rtnetlink_init();
#endif
#ifdef CONFIG_NETLINK_DEV
init_netlink();
#endif
#ifdef CONFIG_NETFILTER
netfilter_init();
#endif
}
rtnetlink_init在内核中创建一个netlink套接字,以处理用户请求。在第529行以NETLINK_ROUTE和rtnetlink_rcv函数指针为参数调用例程netlink_kernel_create。
void __init rtnetlink_init(void)
{
#ifdef RTNL_DEBUG
printk("Initializing RT netlink socket\n");
#endif
rtnl = netlink_kernel_create(NETLINK_ROUTE, rtnetlink_rcv);
......
}
函数netlink_kernel_create首先在第699行调用sock_alloc来分配一个套接字。然后在第702行将套接字类型初始化为SOCK_RAW。
/*
* We export these functions to other modules. They provide a
* complete set of kernel non-blocking support for message
* queueing.
*/
struct sock *
netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len))
{
struct socket *sock;
struct sock *sk;
if (unit<0 || unit>=MAX_LINKS)
return NULL;
if (!(sock = sock_alloc()))
return NULL;
sock->type = SOCK_RAW;
if (netlink_create(sock, unit) < 0) {
sock_release(sock);
return NULL;
}
sk = sock->sk;
sk->data_ready = netlink_data_ready;
if (input)
sk->protinfo.af_netlink->data_ready = input;
netlink_insert(sk, 0);
return sk;
}
在第704行调用函数netlink_create创建内核netlink套接字,然后在第724行初始化sock结构指针sk为指向套接字结构的套接字对象,这个对象在netlink_create函数中被动态分配的。还初始化sock结构的data_ready函数指针为指向netlink_data_ready函数,然后检查是否向本函数输入了第二个参数;如果是,则在第711行初始化af_netlink->data_ready函数指针为第二个输入参数,该参数是用于netlink协议的rtnetlink_rcv。最后,在第713行调用例程netlink_insert在nl_table中添加该套接字的表项。
用户空间netlink套接字是通过socket系统调用创建的,例如:
fd = socket(AF_NETLINK, SOCK_RAW, protocol);
其中AF_NETLINK是地址族,SOCK_RAW是套接字类型。
netlink套接字支持如下的协议族。
这里讨论NETLINK_ROUTE协议,NETLINK_ROUTE协议用于更新路由表:连接欸设置网络接口的参数;解决网络接口设置IP地址、排队规则、流量分类、流量分类的过滤器、邻居设置以及路由规则设置。它控制了Linux的网络路由系统。
例如,对于NETLINK_ROUTE协议,使用netlink套接字更新路由表的用户命令是ip,排队规则和流量分配的用户命令是tc。
LINK参数消息 link消息允许NETLINK_ROUTE协议用户设置和检索系统的网络接口信息。它由如下的消息类型组成:
addr消息 addr消息允许NETLINK_ROUTE协议用户设置、取消系统的网络接口上的IP地址。它由如下的消息类型组成:
ROUTE消息 ROUTE消息允许NETLINK_ROUTE协议用户更新路由表。它由如下消息类型组成:
QDISC消息 QDISC消息允许NETLINK_ROUTE协议用户在系统的排队规则中添加、删除qdisc。它由如下的消息类型组成:
CLASS消息 CLASS消息允许NETLINK_ROUTE协议用户从系统的排队规则的qdisc中添加和删除一个类别。它由如下的消息类型组成:
FILTER消息 FILTER消息允许NETLINK_ROUTE协议用户从系统的排队规则的qdisc的类中添加和删除一个过滤器。它由如下的消息类型组成:
socket系统调用,它随后由内核执行。socket调用sys_socketcall,该函数然后调用sys_socket,sys_socket又调用sock_create,在本示例中其协议族为netlink,因此sock_create再调用netlink_create。该函数创建套机子并初始化对于套接字的协议操作。初始化dock->ops为netlink_ops,其中netlink_ops是一列将再netlink套接字上进行的各种操作的函数指针。
struct proto_ops netlink_ops = {
family: PF_NETLINK,
release: netlink_release,
bind: netlink_bind,
connect: netlink_connect,
socketpair: sock_no_socketpair,
accept: sock_no_accept,
getname: netlink_getname,
poll: datagram_poll,
ioctl: sock_no_ioctl,
listen: sock_no_listen,
shutdown: sock_no_shutdown,
setsockopt: sock_no_setsockopt,
getsockopt: sock_no_getsockopt,
sendmsg: netlink_sendmsg,
recvmsg: netlink_recvmsg,
mmap: sock_no_mmap,
};
内核数据结构:
nl_table是指向sock结构的指针数组(链接的套接字列表)。其大小设置为MAX_LINKS(32)。再内核中的定义如下。
static struct sock *nl_table[MAX_LINKS];
nl_table数组中每个元素表示一个NETLINK协议族--例如,NETLINK_ROUTE、NETLINK_FIREWALL等。如下图所示,每个netlink协议族包含一个指向套接字(struct sock)链接列表的指针。当用户空间和内核空间netlink套接字要通信时,需要根据协议查找nl_table;并且根据协议,可以在套接字链接列表中搜索与当前进程有相同进程ID的sock。一旦在nl_table的该协议对应的sock列表中找到了某个sock,则将skbuff(包含netlink报文)加入sock接收队列中。
rtnetlink_links定义为指向rtnetlink_link数据结构的指针数组。
struct rtnetlink_link * rtnetlink_links[NPROTO];
每个rtnetlink_link数据结构对应一个rtnetlink命令--例如,RTM_NEWQDISC,该命令用于添加一个新的qdisc。这里rtnetlink_link如下:
struct rtnetlink_link
{
int (*doit)(struct sk_buff *, struct nlmsghdr*, void *attr);
int (*dumpit)(struct sk_buff *, struct netlink_callback *cb);
};
rtnetlink_links表中的每个表项都对应一个具体的族,例如AF_NETLINK。
在排队规则的情况下,rtnetlink_links在pktsched_init中进行初始化,代码如下:
int __init pktsched_init(void)
{
#ifdef CONFIG_RTNETLINK
struct rtnetlink_link *link_p;
#endif
#if PSCHED_CLOCK_SOURCE == PSCHED_CPU
if (psched_calibrate_clock() < 0)
return -1;
#elif PSCHED_CLOCK_SOURCE == PSCHED_JIFFIES
psched_tick_per_us = HZ<
rtnetlink_links是一个二维数组,这里对PF_UNSPEC下标的元素(一维数组)进行设置对应的doit和dumpit的函数地址。
类似地,在函数tc_filter_init中初始化用于在类中添加过滤器的排队规则过滤函数指针。代码如下:
int __init tc_filter_init(void)
{
#ifdef CONFIG_RTNETLINK
struct rtnetlink_link *link_p = rtnetlink_links[PF_UNSPEC];
/* Setup rtnetlink links. It is made here to avoid
exporting large number of public symbols.
*/
if (link_p) {
link_p[RTM_NEWTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
link_p[RTM_DELTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
link_p[RTM_GETTFILTER-RTM_BASE].doit = tc_ctl_tfilter;
link_p[RTM_GETTFILTER-RTM_BASE].dumpit = tc_dump_tfilter;
}
#endif
......
对于路由,定义该表为inet_rtnetlink_table,且在函数devinet_init中初始化。inet_rtnetlink_table的定义如下:
static struct rtnetlink_link inet_rtnetlink_table[RTM_MAX-RTM_BASE+1] =
{
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ inet_rtm_newaddr, NULL, },
{ inet_rtm_deladdr, NULL, },
{ NULL, inet_dump_ifaddr, },
{ NULL, NULL, },
{ inet_rtm_newroute, NULL, },
{ inet_rtm_delroute, NULL, },
{ inet_rtm_getroute, inet_dump_fib, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
#ifdef CONFIG_IP_MULTIPLE_TABLES
{ inet_rtm_newrule, NULL, },
{ inet_rtm_delrule, NULL, },
{ NULL, inet_dump_rules, },
{ NULL, NULL, },
#else
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
{ NULL, NULL, },
#endif
};
nlmsghdr是一个标准的消息头,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消息头一致。
nlmsg_flags定义不同种类的控制标志。
nlmsg_seq由创建netlink请求消息并将这些请求与对应的响应关联的进程使用。
nlmsg_pid是发送进程的进程ID。
msghdr数据结构包含将传输到内核的netlink消息中。
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;
};
msg_iov是一个iovec类型的指针,定义如下:
struct iovec
{
void *iov_base; /* BSD uses caddr_t (1003.1g requires void *) */
__kernel_size_t iov_len; /* Must be size_t (1003.1g) */
};
iovec结构由两个元素组成:数据指针和数据长度。
iov_base指向netlink保温(netlink消息头加上数据)。
iov_len包含将要传输到内核的报文长度。
在排队规则情况下的netlink套接字格式如下所示。在将netlink套接字传递到内核之前,其参数必须按照上述格式进行填充。根据填充的参数,特定的内核模块才能采取恰当的行动。
对于路由表的情况,只将结构tcmsg替换成rtmsg。因此,netlink关于排队规则的报文由如下部分组成。
这里将介绍netlink套接字是如何用于tc命令实现的,如tc qdisc add dev eth0 root handle 1:0 cbq bandwidth 10mbit
下图展示了tc命令用户空间流程图并列出详细介绍tc命令用户空间流程代码。图中可以清楚的看出request和msghdr结构是如何分配的。分配这些结构后,根据request和msghdr结构的内容调用sendmsg 系统调用进入内核模式。
对应的代码链接:
tc源码
main
int main(int argc, char **argv)
{
......
ret = do_cmd(argc-1, argv+1);
Exit:
rtnl_close(&rth);
if (use_names)
cls_names_uninit();
return ret;
}
main=>do_cmd
static int do_cmd(int argc, char **argv)
{
if (matches(*argv, "qdisc") == 0)
return do_qdisc(argc-1, argv+1);
if (matches(*argv, "class") == 0)
return do_class(argc-1, argv+1);
if (matches(*argv, "filter") == 0)
return do_filter(argc-1, argv+1);
if (matches(*argv, "chain") == 0)
return do_chain(argc-1, argv+1);
if (matches(*argv, "actions") == 0)
return do_action(argc-1, argv+1);
if (matches(*argv, "monitor") == 0)
return do_tcmonitor(argc-1, argv+1);
if (matches(*argv, "exec") == 0)
return do_exec(argc-1, argv+1);
if (matches(*argv, "help") == 0) {
usage();
return 0;
}
fprintf(stderr, "Object \"%s\" is unknown, try \"tc help\".\n",
*argv);
return -1;
}
main=>do_cmd=>do_qdisc
int do_qdisc(int argc, char **argv)
{
if (argc < 1)
return tc_qdisc_list(0, NULL);
if (matches(*argv, "add") == 0)
return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_EXCL|NLM_F_CREATE, argc-1, argv+1);
if (matches(*argv, "change") == 0)
return tc_qdisc_modify(RTM_NEWQDISC, 0, argc-1, argv+1);
if (matches(*argv, "replace") == 0)
return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_CREATE|NLM_F_REPLACE, argc-1, argv+1);
if (matches(*argv, "link") == 0)
return tc_qdisc_modify(RTM_NEWQDISC, NLM_F_REPLACE, argc-1, argv+1);
if (matches(*argv, "delete") == 0)
return tc_qdisc_modify(RTM_DELQDISC, 0, argc-1, argv+1);
if (matches(*argv, "list") == 0 || matches(*argv, "show") == 0
|| matches(*argv, "lst") == 0)
return tc_qdisc_list(argc-1, argv+1);
if (matches(*argv, "help") == 0) {
usage();
return 0;
}
fprintf(stderr, "Command \"%s\" is unknown, try \"tc qdisc help\".\n", *argv);
return -1;
}
main=>do_cmd=>do_qdisc=>tc_qdisc_modify
static int tc_qdisc_modify(int cmd, unsigned int flags, int argc, char **argv)
{
struct qdisc_util *q = NULL;
struct tc_estimator est = {};
struct {
struct tc_sizespec szopts;
__u16 *data;
} stab = {};
char d[IFNAMSIZ] = {};
char k[FILTER_NAMESZ] = {};
struct {
struct nlmsghdr n;
struct tcmsg t;
char buf[TCA_BUF_MAX];
} req = {
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg)),
.n.nlmsg_flags = NLM_F_REQUEST | flags,
.n.nlmsg_type = cmd,
.t.tcm_family = AF_UNSPEC,
};
......
if (rtnl_talk(&rth, &req.n, NULL) < 0)
return 2;
return 0;
}
main=>do_cmd=>do_qdisc=>tc_qdisc_modify=>rtnl_talk
int rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
struct nlmsghdr **answer)
{
return __rtnl_talk(rtnl, n, answer, true, NULL);
}
main=>do_cmd=>do_qdisc=>tc_qdisc_modify=>rtnl_talk=>__rtnl_talk
static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
struct nlmsghdr **answer,
bool show_rtnl_err, nl_ext_ack_fn_t errfn)
{
struct iovec iov = {
.iov_base = n,
.iov_len = n->nlmsg_len
};
return __rtnl_talk_iov(rtnl, &iov, 1, answer, show_rtnl_err, errfn);
}
main=>do_cmd=>do_qdisc=>tc_qdisc_modify=>rtnl_talk=>__rtnl_talk=>__rtnl_talk_iov
static int __rtnl_talk_iov(struct rtnl_handle *rtnl, struct iovec *iov,
size_t iovlen, struct nlmsghdr **answer,
bool show_rtnl_err, nl_ext_ack_fn_t errfn)
{
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
struct iovec riov;
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = iov,
.msg_iovlen = iovlen,
};
unsigned int seq = 0;
struct nlmsghdr *h;
int i, status;
char *buf;
for (i = 0; i < iovlen; i++) {
h = iov[i].iov_base;
h->nlmsg_seq = seq = ++rtnl->seq;
if (answer == NULL)
h->nlmsg_flags |= NLM_F_ACK;
}
status = sendmsg(rtnl->fd, &msg, 0);
......
}
sys_sendmsg 系统调用sendmsg在内核空间中调用本函数。sys_sendmsg的主要参数是struct msghdr *msg。msg结构包含指向netlink报文的指针(struct req)。sys_sendmsg创建一个与用户空间中struct msghdr *msg结构体相同的新数据结构,新数据结构在第1334行定义为msg_sys。
然后在第1338行使用copy_from_user,将用户空间msg结构中的每个元素都复制到内核空间新的数据结构msg_sys中。msg_sys的iovec元素包含一个指向netlink套接字的指针,在第1376行调用函数verify_iovec来对其进行验证和赋值。最后,以msg_sys为参数调用sock_sendmsg。
sys_sendmsg
asmlinkage long sys_sendmsg(int fd, struct msghdr *msg, unsigned flags)
{
struct socket *sock;
char address[MAX_SOCK_ADDR];
......
if (copy_from_user(&msg_sys,msg,sizeof(struct msghdr)))
goto out;
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
......
/* This will also move the address data into kernel space */
err = verify_iovec(&msg_sys, iov, address, VERIFY_READ);
......
err = sock_sendmsg(sock, &msg_sys, total_len);
......
}
sock_sendmsg sock_sendmsg创建了一个scm_cookie类型的数据结构对象。其主要用途是保存套接字控制消息的信息(进程的uid、gid和pid等)。在第506行调用函数scm_send来初始化scm_cookie数据结构。最后,在第508行调用函数指针sendmsg。这里操作指针指向netlink_ops数据结构,而netlink_ops中的sendmsg指向netlink_sendmsg,从而调用netlink_sendmsg。
sys_sendmsg=>sock_sendmsg
int sock_sendmsg(struct socket *sock, struct msghdr *msg, int size)
{
int err;
struct scm_cookie scm;
err = scm_send(sock, msg, &scm);
if (err >= 0) {
err = sock->ops->sendmsg(sock, msg, size, &scm);
scm_destroy(&scm);
}
return err;
}
sys_sendmsg=>sock_sendmsg=>netlink_sendmsg
static int netlink_sendmsg(struct socket *sock, struct msghdr *msg, int len,
struct scm_cookie *scm)
{
......
skb = alloc_skb(len, GFP_KERNEL);
if (skb==NULL)
goto out;
......
if (memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len)) {
kfree_skb(skb);
goto out;
}
......
if (dst_groups) {
atomic_inc(&skb->users);
netlink_broadcast(sk, skb, dst_pid, dst_groups, GFP_KERNEL);
}
err = netlink_unicast(sk, skb, dst_pid, msg->msg_flags&MSG_DONTWAIT);
out:
return err;
}
netlink_unicast在第396行从sock结构中获取套接字的协议(作为参数ssk->protocol传递)。然后调用函数netlink_lookup来从全局netlink表(如nl_table)中查找相应的链表。随后根据套接字创建时定义的模式,调用add_wait_queue将当前进程键入到套接字的等待队列中,并将该进程的状态设置为TASK_INTERRUPTIBLE。之后会重复检查是否可以运行当前进程;如果没有负载,则在第434行将当前进程的状态改变为TASK_RUNNING。最后,在第447行将sk_buff加入到套接字的接收队列中,并在第448行调用函数sk->data_ready(sk, len)。该函数指针为netlink_data_ready函数。
sys_sendmsg=>sock_sendmsg=>netlink_sendmsg=>netlink_unicast
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)
{
......
int protocol = ssk->protocol;
......
sk = netlink_lookup(protocol, pid);
......
__set_current_state(TASK_INTERRUPTIBLE);
add_wait_queue(&sk->protinfo.af_netlink->wait, &wait);
......
remove_wait_queue(&sk->protinfo.af_netlink->wait, &wait);
......
skb_queue_tail(&sk->receive_queue, skb);
sk->data_ready(sk, len);
sock_put(sk);
return len;
no_dst:
kfree_skb(skb);
return -ECONNREFUSED;
}
sys_sendmsg=>sock_sendmsg=>netlink_sendmsg=>netlink_unicast=>netlink_data_ready
void netlink_data_ready(struct sock *sk, int len)
{
if (sk->protinfo.af_netlink->data_ready)
sk->protinfo.af_netlink->data_ready(sk, len);
......
}
sys_sendmsg=>sock_sendmsg=>netlink_sendmsg=>netlink_unicast=>netlink_data_ready=>rtnetlink_rcv
static void rtnetlink_rcv(struct sock *sk, int len)
{
do {
struct sk_buff *skb;
if (rtnl_shlock_nowait())
return;
while ((skb = skb_dequeue(&sk->receive_queue)) != NULL) {
if (rtnetlink_rcv_skb(skb)) {
if (skb->len)
skb_queue_head(&sk->receive_queue, skb);
else
kfree_skb(skb);
break;
}
kfree_skb(skb);
}
up(&rtnl_sem);
} while (rtnl && rtnl->receive_queue.qlen);
}
rtnetlink_rcv_skb rtnetlink_rcv_skb在第411行将skb->data指针强制转换成结构nlmsghdr类型,nlmsghdr结构是netlink报文头结构,这个skb->data是netlink报文的起始地址。然后rtnetlink_rcv_skb在第417行以netlink报文头结构作为参数之一调用函数rtnetlink_rcv_msg。
sys_sendmsg=>sock_sendmsg=>netlink_sendmsg=>netlink_unicast=>netlink_data_ready=>rtnetlink_rcv=>rtnetlink_rcv_skb
extern __inline__ int rtnetlink_rcv_skb(struct sk_buff *skb)
{
int err;
struct nlmsghdr * nlh;
while (skb->len >= NLMSG_SPACE(0)) {
u32 rlen;
nlh = (struct nlmsghdr *)skb->data;
if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
return 0;
rlen = NLMSG_ALIGN(nlh->nlmsg_len);
if (rlen > skb->len)
rlen = skb->len;
if (rtnetlink_rcv_msg(skb, nlh, &err)) {
/* Not error, but we must interrupt processing here:
* Note, that in this case we do not pull message
* from skb, it will be processed later.
*/
if (err == 0)
return -1;
netlink_ack(skb, nlh, err);
} else if (nlh->nlmsg_flags&NLM_F_ACK)
netlink_ack(skb, nlh, 0);
skb_pull(skb, rlen);
}
return 0;
}
rtnetlink_rcv_msg rtnetlink_rcv_msg首先在第295行和第305行从作为输入参数传递给本地函数的netlink报文(nlh)中获取netlink套接字的类型和族。doit和dunmpit函数指针存储在rtnetlink_links表中的rtnetlink_link中。设置族和类型在tc中(tc的用户空间代码)。最后,根据族的行和类型的列,在第384行调用doit函数。这里是以添加一个qdisc为例,因此调用的函数时tc_qdisc_modify。
sys_sendmsg=>sock_sendmsg=>netlink_sendmsg=>netlink_unicast=>netlink_data_ready=>rtnetlink_rcv=>rtnetlink_rcv_skb=>rtnetlink_rcv_msg
/* Process one rtnetlink message. */
extern __inline__ int
rtnetlink_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, int *errp)
{
......
type = nlh->nlmsg_type;
......
type -= RTM_BASE;
......
family = ((struct rtgenmsg*)NLMSG_DATA(nlh))->rtgen_family;
......
link_tab = rtnetlink_links[family];
if (link_tab == NULL)
link_tab = rtnetlink_links[PF_UNSPEC];
link = &link_tab[type];
......
if (link->doit == NULL)
link = &(rtnetlink_links[PF_UNSPEC][type]);
if (link->doit == NULL)
goto err_inval;
err = link->doit(skb, nlh, (void *)&rta);
......
}
内核空间的tc命令流程如下图。