Netlink 初识

Introduction

Inter Process Communication (IPC)

  • Development in kernel is complex.
  • Only core functionalities of operating system and performance critical codes are in kernel.
  • User space applications and kernel space code.
  • Through IPC user space application can communicate with kernel space program.Two kernel module can communicate with each other.
  • Widely used and famous IPCs : system call, IOCTL, proc filesystem, Netlink socket
  • User space application set/get information to/from kernel using IOTCL.

    ifconfig、arp、 route 内部都是使用ioctl , ioctl对于每个操作都必须有一个唯一的ioctl编号。

  • ioctl cannot send asynchronous message from kernel to user space.

  • Non-trivial task to add IOCTL for new feature.

    新添加ioctl 并非易事,因为需要同时修改 user space和kernel

  • Adding a protocol type in netlink.h is sufficient(足够) to support new type of Netlink socket. Socket APIs are compatible with BSD socket API. Thus easy to implement in user space application.

  • Netlink is asynchronous

  • Netlink socket supports multicast.

Basic Functionalities

  • Special IPC for transferring information between kernel and user space processes.
  • Two kernel processes can communication with each other through netlink.
  • Full duplex communication
  • Use standard socket APIs in user space process.
  • Support multiple protocol types. User can define his own type in netlink.h
    • NETLINK_ROUTE
    • NETLINK_FIREWALL
    • NETLINK_ARPD

User space packages

  • iproute2 uses Netlink
    • ip: For management of network tables and network interfaces
    • tc: For traffic control management
    • bridge: For management of bridge addresses and devices
  • net-tools still uses IOCTL
    • ifconfig
    • arp
    • route
    • netstat

API

socket

  • int socket(int domain, int type, int protocol)
    • socket domain (address family): AF_NETLINK
    • type: SOCK_RAW or SOCK_DGRAM
    • protocol: NETLINK_ROUTE, NETLINK_FIREWALL, NETLINK_ARPD

bind

  • the netlink bind() API associates a local (source) socket address with the opened socket
1
2
3
4
5
6
7
8
//netlink.h
struct sockaddr_nl
{
    sa_family_t nl_family; /* AF_NETLINK */
    unsigned short nl_pad;    /* zero */
    __u32    nl_pid;    /* process pid */
    __u32    nl_groups;    /* mcast groups mask */
} nladdr;
  • nl _pid: filled with the calling process’ own pid

  • nl_groups: 0 for unicast

  • bind(fd, (struct sockaddr*)&nladdr, sizeof(nladdr)):


linux-learn-demo-code/netlink at main · charles-7777/linux-learn-demo-code

route show demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
    /* Create netlink socket */
    fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
    if (fd < 0) {
        printf("Error creating socket: %s\n", strerror(errno));
        return -1;
    }

    /* Set up local address and bind */
    memset(&local, 0, sizeof(local));
    local.nl_family = AF_NETLINK;
    local.nl_pid = pid;
    local.nl_groups = RTMGRP_LINK;  /* Subscribe to link notifications */

    if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) {
        perror("Cannot bind - are you root? Check netlink/rtnetlink support");
        close(fd);
        return -1;
    }

    printf("=== Network Information ===\n\n");

    /* Request and display network interfaces if requested */
    if (show_all || show_interfaces) {
        printf("--- Network Interfaces ---\n");
        if (send_netlink_request(fd, RTM_GETLINK, AF_UNSPEC) == 0) {
            process_netlink_responses(fd, 0, 1);
        }
        printf("\n");
    }

    /* Request and display routing table if requested */
    if (show_all || show_routes) {
        printf("--- Routing Table ---\n");
        if (send_netlink_request(fd, RTM_GETROUTE, AF_INET) == 0) {
            process_netlink_responses(fd, 1, 0);
        }
        printf("\n");
    }

route monitor demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
charles@ubuntu24:~/netlink$ ./route_montor
Route Monitor Tool
==================
Monitoring IPv4 route changes...
Press Ctrl+C to exit

[2026-07-11 03:44:10] DELETED route: 10.0.2.15/32 proto 2 src 10.0.2.15 table 255          
[2026-07-11 03:44:37] ADDED route: 10.0.2.15/32 proto 2 src 10.0.2.15 table 255
[2026-07-11 03:44:37] ADDED route: 10.0.2.255/32 proto 2 src 10.0.2.15 table 255
[2026-07-11 03:44:37] ADDED route: 10.0.2.0/24 proto 2 src 10.0.2.15
[2026-07-11 03:44:37] ADDED route: 10.0.2.2/32 proto 16 src 10.0.2.15
[2026-07-11 03:44:37] ADDED route: N/A/0 proto 16 via 10.0.2.2 src 10.0.2.15
[2026-07-11 03:44:37] ADDED route: 192.168.1.1/32 proto 16 via 10.0.2.2 src 10.0.2.15
^C
Received signal 2, shutting down...

Route monitor stopped.

charles@ubuntu24:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.56.0    0.0.0.0         255.255.255.0   U     100    0        0 enp0s8
charles@ubuntu24:~$ sudo ifconfig enp0s3 up
charles@ubuntu24:~$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.2.2        0.0.0.0         UG    100    0        0 enp0s3
10.0.2.0        0.0.0.0         255.255.255.0   U     100    0        0 enp0s3
10.0.2.2        0.0.0.0         255.255.255.255 UH    100    0        0 enp0s3
192.168.1.1     10.0.2.2        255.255.255.255 UGH   100    0        0 enp0s3
192.168.56.0    0.0.0.0         255.255.255.0   U     100    0        0 enp0s8
charles@ubuntu24:~$
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    /* Create netlink socket */
    if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
        ERR_RET("socket");
    }

    /* Set socket options to receive notifications */
    addr.nl_family = AF_NETLINK;
    addr.nl_groups = RTMGRP_IPV4_ROUTE;  /* Subscribe to IPv4 route changes */

    /* Bind socket */
    if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        close(sock);
        ERR_RET("bind");
    }

    /* Main monitoring loop */
    while (running) {
        fd_set fds;
        struct timeval tv;
        int ret;

        FD_ZERO(&fds);
        FD_SET(sock, &fds);
        tv.tv_sec = 1;
        tv.tv_usec = 0;

        /* Wait for data with timeout */
        ret = select(sock + 1, &fds, NULL, NULL, &tv);
        if (ret < 0) {
            if (errno == EINTR) {
                continue;  /* Interrupted by signal */
            }
            perror("select");
            break;
        }

        if (ret > 0 && FD_ISSET(sock, &fds)) {
            if (loop(sock, &addr) < 0) {
                break;
            }
        }
    }
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[312348.806576] Entering: hello_init
[312348.808862] Netlink module initialized successfully!
[312453.904532] Entering: hello_nl_recv_msg
[312453.904781] Netlink received msg payload: Hello from user space!
[312453.905008] Reply sent successfully to PID: 128016


charles@ubuntu24:~/netlink$ sudo insmod netlinkKernel.ko
charles@ubuntu24:~/netlink$ ./netlinkUser
Sending message to kernel: Hello from user space!
Waiting for message from kernel...
Received message from kernel: Hello from kernel
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
//netlinkUser.c
   // 创建 Netlink socket
    sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);

    // 绑定源地址(用户进程自身)
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid();  // 使用进程 PID 作为端口号
    src_addr.nl_groups = 0;      // 不加入多播组

    if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
    }

    // 设置目标地址(内核)
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;        // 内核的 PID 为 0
    dest_addr.nl_groups = 0;     // 单播

    // 分配 Netlink 消息缓冲区
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
    if (!nlh) {
    }

    memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));

    // 填充 Netlink 消息头
    nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
    nlh->nlmsg_pid = getpid();   // 发送方 PID
    nlh->nlmsg_flags = 0;        // 无特殊标志

    // 拷贝用户数据到消息负载
    char *data = NLMSG_DATA(nlh);
    strcpy(data, "Hello from user space!");

    // 设置 I/O 向量
    iov.iov_base = (void *)nlh;
    iov.iov_len = nlh->nlmsg_len;

    // 设置消息头
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    // 发送消息到内核
    printf("Sending message to kernel: %s\n", (char *)NLMSG_DATA(nlh));
    ret = sendmsg(sock_fd, &msg, 0);
    if (ret < 0) {
    }

    // 等待并接收内核回复
    printf("Waiting for message from kernel...\n");
    memset(&msg, 0, sizeof(msg));
    msg.msg_name = (void *)&dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;

    ret = recvmsg(sock_fd, &msg, 0);
    if (ret < 0) {
    }

    // 打印内核回复的内容
    printf("Received message from kernel: %s\n", (char *)NLMSG_DATA(nlh))
        
        
//NetlinkKernel.c

#define NETLINK_USER 31

struct sock *nl_sk = NULL;

// 接收来自用户空间消息的回调函数
static void hello_nl_recv_msg(struct sk_buff *skb)
{
    struct nlmsghdr *nlh;
    int pid;
    struct sk_buff *skb_out;
    int msg_size;
    char *msg = "Hello from kernel";
    int res;

    printk(KERN_INFO "Entering: %s\n", __func__);

    // 获取 Netlink 消息头
    nlh = (struct nlmsghdr *)skb->data;
    printk(KERN_INFO "Netlink received msg payload: %s\n", (char *)nlmsg_data(nlh));

    // 获取发送进程的 PID
    pid = nlh->nlmsg_pid;

    // 准备回复消息
    msg_size = strlen(msg) + 1;  // +1 for null terminator

    // 分配新的 skb 用于回复
    skb_out = nlmsg_new(msg_size, GFP_KERNEL);
    if (!skb_out) {
        printk(KERN_ERR "Failed to allocate new skb\n");
        return;
    }

    // 构造 Netlink 消息
    nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, msg_size, 0);
    if (!nlh) {
        printk(KERN_ERR "nlmsg_put failed\n");
        nlmsg_free(skb_out);
        return;
    }

    // 设置目标组(不加入多播组)
    NETLINK_CB(skb_out).dst_group = 0;

    // 拷贝消息数据
    strncpy(nlmsg_data(nlh), msg, msg_size);

    // 单播发送回复给用户进程
    res = nlmsg_unicast(nl_sk, skb_out, pid);
    if (res < 0) {
        printk(KERN_INFO "Error while sending back to user: %d\n", res);
    } else {
        printk(KERN_INFO "Reply sent successfully to PID: %d\n", pid);
    }
}

// 模块初始化函数
static int __init hello_init(void)
{
    struct netlink_kernel_cfg cfg = {
        .input = hello_nl_recv_msg,
    };

    printk(KERN_INFO "Entering: %s\n", __func__);

    // 创建 Netlink socket
    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if (!nl_sk) {
        printk(KERN_ALERT "Error creating Netlink socket.\n");
        return -ENOMEM;
    }

    printk(KERN_INFO "Netlink module initialized successfully!\n");
    return 0;
}

// 模块退出函数
static void __exit hello_exit(void)
{
    printk(KERN_INFO "Exiting hello module\n");

    // 释放 Netlink socket
    if (nl_sk) {
        netlink_kernel_release(nl_sk);
        nl_sk = NULL;
    }
}
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计