2014/09/16

Netlink

1. Introduction

Netlink 提供 userspace & kernel 之間的溝通, 它定義在 RFC 3549 "Linux Netlink as an IP Services Protocol", 相較於其它溝通方式, 它有這些優點:

  • 不用 polling
  • 可以從 kernel 當作訊息發起端
  • 提供 multicast
kernel 裡相關的檔案:

  • Netlink core files
    • net/netlink/af_netlink.c
    • include/linux/netlink.h
    • net/netlink/genetlink.c
  • Route netlink files
    • net/core/rtnetlink.c
    • include/linux/rtnetlink.h
  • Generic netlink files
    • net/netlink/genetlink.c
    • include/linux/genetlink.h

2. Create netlink socket

userspace & kernel 在create socket 時, 最後都會 call __netlink_create(), 唯一的差別是, kernel會加上屬性 NETLINK_KERNEL_SOCKET


2.1 create netlink socket in userspace


底下是sample code

int sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);

struct sockaddr_nl addr;
bzero (&addr, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_groups = RTMGRP_IPV4_ROUTE;

bind(sockfd,(struct sockaddr *)&addr,sizeof(addr);

其中 sockaddr_nl 的定義是

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 */
};
include/linux/netlink.h)

其中
nl_family 填 AF_NETLINK
nl_pad 通常得填 0
nl_pid 代表 netlink 的 unicast address, 如果是 kernel space 就填 0, 如果是 userspace, 在bind的時候就會被填上 user process 的 pid, 但如果 user process 跳過 bind, 那麼就得自行管理這個欄位的值
nl_groups 填這個 socket 所屬的 multicast group


2.2 create netlink socket in kernel

kernel 的 netlink 是在 init 的時候就會呼叫, 不同的group它的init的點也不一樣, 底下是ROUTE的init的點

static int __net_init rtnetlink_net_init(struct net *net)
{
    struct sock *sk;
    sk = netlink_kernel_create(net, NETLINK_ROUTE, RTNLGRP_MAX,
                               rtnetlink_rcv, &rtnl_mutex, THIS_MODULE);
    if (!sk)
        return -ENOMEM;
    net->rtnl = sk;
    return 0;
}
( net/core/rtnetlink.c )

其中 rtnetlink_rcv 是callback function, 負責處理從userspace來的data
NETLINK_ROUTE 是  netlink protocol, 定義在 include/linux/netlink.h
RTNLGRP_MAX 是 route multicast group, 定義在 include/linux/rtnetlink.h

netlink_kernel_create 實作在這裡

netlink_kernel_create(struct net *net, int unit, unsigned int groups,
                      void (*input)(struct sk_buff *skb),
                      struct mutex *cb_mutex, struct module *module)
( net/netlink/af_netlink.c )

netlink protocol 除了 route 之外還有很多, 比如說 kobject 也有自己 init 的點

static int uevent_net_init(struct net *net) {
        struct uevent_sock *ue_sk;

        ue_sk = kzalloc(sizeof(*ue_sk), GFP_KERNEL);
        if (!ue_sk)
                return -ENOMEM;

        ue_sk->sk = netlink_kernel_create(net, NETLINK_KOBJECT_UEVENT,
                                          1, NULL, NULL, THIS_MODULE);
        if (!ue_sk->sk) {
                printk(KERN_ERR
                       "kobject_uevent: unable to create netlink socket!\n");
                kfree(ue_sk);
                return -ENODEV;
        }
        mutex_lock(&uevent_sock_mutex);
        list_add_tail(&ue_sk->list, &uevent_sock_list);
        mutex_unlock(&uevent_sock_mutex);
        return 0;
}
( lib/kobject_uevent.c )

可以看到它的 netlink protocol 是 NETLINK_KOBJECT_UEVENT, 因為 kobject 沒有 multicast group, 所以 group 的欄位填 1, callback填null因為它不需要處理從 userspace 來的 data, mutex 填null 表示使用 default 的 mutex

2.3 maintain netlink socket


許多不同 netlink protocol 的 kernel socket 用 netlink_table 來maintain, 它是個link list
struct netlink_table {
    struct nl_pid_hash hash;
    struct hlist_head mc_list;
    struct listeners __rcu *listeners;
    unsigned int nl_nonroot;
    unsigned int groups;
    struct mutex *cb_mutex;
    struct module *module;
    int registered;
};

要操作這張 table, 有底下幾個 operation

  • nl_table_lock()
  • netlink_lookup()
  • netlink_insert()
比如說, 當 kobject netlink socket 開起來之後, 就會被放進這張 table 裡

2.4 register maintain netlink socket

在開完 socket 之後, 就要註冊在不同的message要什麼callback

rtnetlink_init() {
// ......
    rtnl_register(PF_UNSPEC, RTM_GETLINK, rtnl_getlink, rtnl_dump_ifinfo, rtnl_calcit);
    rtnl_register(PF_UNSPEC, RTM_SETLINK, rtnl_setlink, NULL, NULL);
// ......

rtnl_register 定義如下

rtnl_register(int protocol, int msgtype, // protocol = protocol family, ref include/linux/socket.h
    rtnl_doit_func doit,     // for addition/deletion/modification
    rtnl_dumpit_func dumpit, // for retrieving information
    rtnl_calcit_func calcit) // for calculation of buffer size

從上面例子可以看到 RTM_GETLINK 有填對應的 callback, RTM_SETLINK 有些則填 null

而實際上, rtnl_register 用 array 存對應的callback

static struct rtnl_link *rtnl_msg_handlers[RTNL_FAMILY_MAX + 1];
struct rtnl_link {
    rtnl_doit_func    doit;
    rtnl_dumpit_func  dumpit;
    rtnl_calcit_func  calcit;
};

3. sending rtnetlink message

舉個例子, 當netwrok dev oepn之後, 要通知userspace


3.1 netlink message format

netlink message 前面有 nlmsghdr 可供判斷這段message用的 netlink protocol 是什麼, 接著它的payload 則會依照不同的 netlink protocl 而有不同的format, 但通常都使用TLV的格式


通常一段 netlink message, 可以包含多份 message, 每份 message 前面都有 nlmsghdr, 裡面有一些資訊像是 len 表示這段 message 的長度, type 表示netlink protocol, 也可以利用nlmsghdr和macro幫忙指到對的位置

裡面的 payload, 如果是 route netlink protocol, 它的 format 長這樣

可以看到它的 payload 前面有個 rtmsg 的標頭, 進一步提供 route 相關的 message, 其後接著 TLV 格式的資訊

底下有個例子, 假如收到一包 route 相關 message, 以 buffer 表示其中每段資料的內容


4. Generic Netlink protocol

因為 netlink protocol 只用1 byte來儲存, 所以只有32種 netlink protocol  family 可以用, 如果加新的 netlink protocol family, 很快就不夠用, 再加上每次加上新的, header檔就得重新定義, 所以提出 generic netlink protocol

它的作法就是定義 generic netlink protocol 在 netlink protocol family, 再利用mux來區分

5. libnl.so

在實作上, 拆開每包 netlink message 還是很麻煩, 可以使用 libnl.so
它提供了
  • Connecting/disconnectng of sockets
  • Sending/receiving of data
  • Construction/parsing of messages
  • provides a customizeable receiving state machine
  • provides a abstract data type framework
這些功能對應到這些 library
  • generic netlink family (libnl-genl)
  • routing family (libnl-route)
  • netfilter family (libnl-nf)
底下是libnl的官網架構圖:


底下是它的實作範例



1 則留言:

  1. 太赞了!网上找了好多资料,还是老兄这篇讲得明白;特别是那几张format的图,解释得很清晰!

    回覆刪除