netlink 是 Go 和核心模組之間優秀的通訊兵

Huayra發表於2021-07-03

netlink 是 Linux 系統裡使用者態程式、核心模組之間的一種 IPC 方式,特別是使用者態程式和核心模組之間的 IPC 通訊。比如在 Linux 終端裡常用的 ip 命令,就是使用 netlink 去跟核心進行通訊的。

TL,DR 使用 Go 通過 netlink 向核心模組傳送訊息,核心模組響應一條訊息,原始碼:核心模組Go 程式

核心模組

註冊 netlink

insmod custom-netlink.ko 載入核心模組時,註冊自定義的 netlink 處理函式。在 rmmod custom_netlink 解除安裝核心模組時,則登出自定義的 netlink 處理函式。

#define NETLINK_CUSTOM 31

static int __init custom_nl_init(void)
{
    //This is for 3.6 kernels and above.
    struct netlink_kernel_cfg cfg = {
        .input = custom_nl_recv_msg,
    };

    nl_sk = netlink_kernel_create(&init_net, NETLINK_CUSTOM, &cfg);
    ......
    return 0;
}

static void __exit custom_nl_exit(void)
{

    netlink_kernel_release(nl_sk);
    printk(KERN_INFO "[-] unregistered custom_netlink module.\n");
}

module_init(custom_nl_init);
module_exit(custom_nl_exit);

接收 netlink 訊息

訊息格式如下:

0       4       8
+-+-+-+-+-+-+-+-+
|msg len|  data |
+-+-+-+-+-+-+-+-+
|     data      |
+-+-+-+-+-+-+-+-+

前 4 個位元組代表後面的資料長度,後跟著一段字串。為了防止在列印字串的時候記憶體越界了,將字串的最後一個字元置為 \0

列印字串後,將該字元響應回去。

enum
{
    nlresp_result_unspec,
    nlresp_result_ok,
    nlresp_result_invalid
};

static void custom_nl_recv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh;
    unsigned char *nl_data;
    unsigned char *msg;
    __u32 msg_size;

    nlh = nlmsg_hdr(skb);
    nl_data = (unsigned char *)NLMSG_DATA(nlh);
    msg_size = *(__u32*)nl_data;
    if (msg_size > 1024) {
        custom_nl_send_msg(nlh, nlresp_result_invalid, NULL, 0);
        return;
    }

    msg = nl_data+4;
    msg[msg_size-1] = '\0';
    printk(KERN_INFO "[Y] [custom netlink] receive msg from user: %s\n", msg);

    custom_nl_send_msg(nlh, nlresp_result_ok, msg, msg_size);
}

響應 netlink 訊息

typedef struct
{
    __u32 result;
    unsigned char data[0];
} custom_nl_resp_data_t;

static int custom_nl_send_msg(struct nlmsghdr *nlh, __u32 result, const unsigned char *data, __u32 data_size)
{
    custom_nl_resp_data_t *resp;
    struct nlmsghdr *nlh_resp;
    struct sk_buff *skb;
    int pid = nlh->nlmsg_pid, res = -1;
    const unsigned char *resp_msg = "Echo from kernel: ";

    data_size += 4+18; // 4 是 result 的長度,18 是 resp_msg 的長度
    resp = kzalloc(data_size + 18, GFP_KERNEL);
    memcpy(resp->data, resp_msg, 18); // 複製 resp_msg
    memcpy(resp->data + 18, data, data_size-4); // 複製 Go 程式傳送過來的字串
    resp->result = result;

    skb = nlmsg_new(data_size, GFP_KERNEL); // 核心自動釋放記憶體,不需要手動釋放
    if (!skb)
        goto out;

    nlh_resp = nlmsg_put(skb, pid, nlh->nlmsg_seq, NLMSG_DONE, data_size, 0);
    memcpy(NLMSG_DATA(nlh_resp), resp, data_size); // 填充 netlink 訊息內容
    res = nlmsg_unicast(nl_sk, skb, pid); // 將訊息響應給指定 pid 的程式

out:
    kfree(resp); // 用後釋放記憶體
    return res;
}

Go 程式

發起 netlink 連線

使用跟核心模組一致的 netlink family 發起連線。

const (
    netlinkCustom = 31
)

conn, err := netlink.Dial(netlinkCustom, nil)

傳送 netlink 訊息

封裝 netlink 訊息,並將提供的字串複製到訊息中。

// msg 是前面提供的字串
  data := make([]byte, 4+len(msg)+1)
  nlenc.PutUint32(data[:4], uint32(len(msg)+1))
  copy(data[4:], msg)

  fmt.Println("Send to kernel:", msg)

  var nlmsg netlink.Message
  nlmsg.Data = data

  msgs, err := conn.Execute(nlmsg)

接收 netlink 訊息

按如下格式接收訊息:

0       4       8
+-+-+-+-+-+-+-+-+
| result|  data |
+-+-+-+-+-+-+-+-+
|     data      |
+-+-+-+-+-+-+-+-+

前 4 個位元組是核心模組響應的結果,後跟著一段字串。

msgs, err := conn.Execute(nlmsg)
  ......

  nlmsg = msgs[0]
  ......

  fmt.Println(string(nlmsg.Data[4:]))

實驗效果

➜  kernel-module-fun make
make -C /lib/modules/4.19.0/build M=/root/Projects/kernel-module-fun modules
make[1]: Entering directory '/usr/src/linux-headers-4.19.0'
  CC [M]  /root/Projects/kernel-module-fun/custom-netlink.o
  Building modules, stage 2.
  MODPOST 4 modules
  CC      /root/Projects/kernel-module-fun/custom-netlink.mod.o
  LD [M]  /root/Projects/kernel-module-fun/custom-netlink.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.19.0'
➜  kernel-module-fun insmod custom-netlink.ko
➜  kernel-module-fun ./custom-netlink/custom-netlink
Send to kernel: Hello from Go
Echo from kernel: Hello from Go
➜  kernel-module-fun rmmod custom_netlink
➜  kernel-module-fun dmesg
[2424642.636765] [+] registered custom_netlink module!
[2424644.929387] [Y] [custom netlink] receive msg from user: Hello from Go
[2424647.822184] [-] unregistered custom_netlink module.

小結

使用 netlink 進行使用者態程式和核心模組的 IPC 通訊,能夠動態地變更核心模組的配置,甚至動態地去開啟、關閉核心模組裡的功能。比如在收到配置後,再掛載 netfilter hook

使用 netlink 動態傳配置的功能,可以取代 insmod 傳參這一笨拙的傳配置方式。

P.S. go-iproute:使用 Go 基於 netlink 實現的 iproute2 工具庫

更多原創文章乾貨分享,請關注公眾號
  • netlink 是 Go 和核心模組之間優秀的通訊兵
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章