如何新加netlink

如何在Linux内核中添加一个新的Netlink指令(命令)

Linux内核中Netlink有两种常见方式添加新指令:

  1. Generic Netlink(genl,推荐方式) 现代、灵活、动态分配family ID、无需修改内核头文件,广泛用于nl80211、ethtool、taskstats等子系统。
  2. 原生Netlink(legacy/raw Netlink,不推荐新开发) 旧式方式,需要固定协议号(NETLINK_XXX),命令通过nlmsg_type字段区分。早期子系统(如NETLINK_ROUTE、NETLINK_USERSOCK)使用此方式。

下面分别详细说明两种方式的完整实现步骤,并以相同功能为例:实现一个命令MY_CMD_ECHO,用户空间发送一个字符串,内核回显该字符串。


一、Generic Netlink(推荐)

1. 定义命令、属性和family名称
复制代码
#include <net/genetlink.h>

/* 命令枚举 */
enum my_commands {
    MY_CMD_UNSPEC,      /* 必须有 */
    MY_CMD_ECHO,
    __MY_CMD_MAX,
};
#define MY_CMD_MAX (__MY_CMD_MAX - 1)

/* 属性枚举 */
enum my_attrs {
    MY_ATTR_UNSPEC,
    MY_ATTR_MSG,        /* 携带的字符串 */
    __MY_ATTR_MAX,
};
#define MY_ATTR_MAX (__MY_ATTR_MAX - 1)

/* family 定义 */
#define MY_FAMILY_NAME    "MYFAMILY"
#define MY_FAMILY_VERSION 1
2. 属性验证策略(推荐)
复制代码
static const struct nla_policy my_genl_policy[MY_ATTR_MAX + 1] = {
    [MY_ATTR_MSG] = { .type = NLA_STRING, .len = 256 }, /* 最大256字节字符串 */
};
3. 命令处理函数
复制代码
static int my_echo_doit(struct sk_buff *skb, struct genl_info *info)
{
    struct sk_buff *rep;
    void *reply_hdr;
    char *msg;

    if (!info->attrs[MY_ATTR_MSG])
        return -EINVAL;

    msg = nla_data(info->attrs[MY_ATTR_MSG]);

    /* 分配回复skb */
    rep = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
    if (!rep)
        return -ENOMEM;

    /* 开始构造回复消息 */
    reply_hdr = genlmsg_put_reply(rep, info, info->snd_portid, info->snd_seq, 
                                  info->nlhdr, 0, MY_CMD_ECHO);
    if (!reply_hdr)
        goto nla_put_failure;

    /* 把原消息回显回去 */
    if (nla_put_string(rep, MY_ATTR_MSG, msg))
        goto nla_put_failure;

    genlmsg_end(rep, reply_hdr);

    /* 单播发送回复 */
    return genlmsg_reply(rep, info);

nla_put_failure:
    genlmsg_cancel(rep, reply_hdr);
    kfree_skb(rep);
    return -EMSGSIZE;
}
4. 操作(ops)定义
复制代码
static const struct genl_ops my_genl_ops[] = {
    {
        .cmd    = MY_CMD_ECHO,
        .doit   = my_echo_doit,
        .policy = my_genl_policy,
        .flags  = GENL_CMD_CAP_DO,           /* 可选:需要CAP_NET_ADMIN用 GENL_ADMIN_PERM */
    },
};
5. family注册
复制代码
static struct genl_family my_genl_family = {
    .name      = MY_FAMILY_NAME,
    .version   = MY_FAMILY_VERSION,
    .maxattr   = MY_ATTR_MAX,
    .ops       = my_genl_ops,
    .n_ops     = ARRAY_SIZE(my_genl_ops),
    .module    = THIS_MODULE,
    /* .mcgrps = ... 如果需要多播组 */
};

static int __init my_genl_init(void)
{
    int ret = genl_register_family(&my_genl_family);
    if (ret) {
        pr_err("genl_register_family failed: %d\n", ret);
        return ret;
    }
    pr_info("MYFAMILY registered, family id: %d\n", my_genl_family.id);
    return 0;
}

static void __exit my_genl_exit(void)
{
    genl_unregister_family(&my_genl_family);
}

module_init(my_genl_init);
module_exit(my_genl_exit);
6. 用户空间调用方式(使用libnl)
复制代码
int family = genl_ctrl_resolve(sock, "MYFAMILY");  // 动态获取family id

struct nl_msg *msg = nlmsg_alloc();
genlmsg_put(msg, NL_AUTO_PORT, NL_AUTO_SEQ, family, 0, 0, MY_CMD_ECHO, MY_FAMILY_VERSION);
nla_put_string(msg, MY_ATTR_MSG, "Hello from userspace!");
nl_send_auto(sock, msg);

优点:无需修改uapi头文件、动态ID、支持多命令、多属性、权限控制、多播等现代特性。


二、原生Netlink(Legacy方式,不推荐新项目)

1. 定义协议号(需修改内核头文件)

在 include/uapi/linux/netlink.h 添加:

复制代码
#define NETLINK_MYPROTO  31   /* 找一个未使用的数字(最大31) */

注意:协议号0-31固定,超过需要修改MAX_LINKS。新内核不鼓励添加新协议。

2. 定义命令(通过nlmsg_type区分)
复制代码
enum my_netlink_commands {
    MY_NL_CMD_UNSPEC,
    MY_NL_CMD_ECHO,
    __MY_NL_CMD_MAX,
};
3. 消息接收处理函数
复制代码
static int my_netlink_rcv(struct sk_buff *skb, struct nlmsghdr *nlh, struct netlink_ext_ack *extack)
{
    char *msg;
    int len;
    struct sk_buff *rep;
    struct nlmsghdr *rep_nlh;

    if (nlh->nlmsg_type != MY_NL_CMD_ECHO)
        return -EINVAL;

    /* 解析payload(简单假设是null-terminated string) */
    msg = nlmsg_data(nlh);
    len = nlmsg_len(nlh);

    /* 分配回复 */
    rep = nlmsg_new(len, GFP_KERNEL);
    if (!rep)
        return -ENOMEM;

    rep_nlh = nlmsg_put(rep, NETLINK_CB(skb).portid, nlh->nlmsg_seq,
                        MY_NL_CMD_ECHO, len, 0);
    if (!rep_nlh) {
        kfree_skb(rep);
        return -EMSGSIZE;
    }

    memcpy(nlmsg_data(rep_nlh), msg, len);

    /* 发送单播回复 */
    netlink_unicast(nl_sk, rep, NETLINK_CB(skb).portid, 0);

    return 0;
}

static void my_netlink_rcv_skb(struct sk_buff *skb)
{
    netlink_rcv_skb(skb, &my_netlink_rcv);
}
复制代码
static struct netlink_kernel_cfg my_nl_cfg = {
    .input = my_netlink_rcv_skb,
    /* .flags = NL_CFG_F_NONROOT_RECV 可选权限控制 */
};

static struct sock *nl_sk;

static int __init my_raw_init(void)
{
    nl_sk = netlink_kernel_create(&init_net, NETLINK_MYPROTO, &my_nl_cfg);
    if (!nl_sk) {
        pr_err("netlink_kernel_create failed\n");
        return -ENOMEM;
    }
    pr_info("Raw netlink MYPROTO created\n");
    return 0;
}

static void __exit my_raw_exit(void)
{
    netlink_kernel_release(nl_sk);
}

module_init(my_raw_init);
module_exit(my_raw_exit);
5. 用户空间调用(原始socket)
复制代码
int sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_MYPROTO);

struct nlmsghdr *nlh = ...;
nlh->nlmsg_type = MY_NL_CMD_ECHO;
strcpy(NLMSG_DATA(nlh), "Hello");

send(sock, nlh, nlh->nlmsg_len, 0);

缺点

  • 需要修改uapi头文件(难以进入主线)
  • 协议号固定且稀缺
  • 没有内置属性解析(需手动解析TLV)
  • 权限、多播等功能支持较弱
  • 已被Generic Netlink取代

总结对比

特性 Generic Netlink (推荐) 原生Netlink (Legacy)
是否需要修改uapi头 是(添加NETLINK_XXX)
family ID分配 动态分配 固定协议号
属性解析 内置nla_* API + policy验证 手动解析
多命令支持 自然支持(genl_ops数组) 通过nlmsg_type手动区分
权限控制 支持GENL_ADMIN_PERM等 有限
多播组 支持 支持但较复杂
现代子系统使用情况 绝大多数(如nl80211、mac80211) 极少数旧协议(如route)
推荐程度 强烈推荐新开发 不推荐,仅维护旧代码
相关推荐
济6171 天前
linux(第十二期)--裸机实验(C 语言版 LED 灯实验)-- Ubuntu20.04
linux·c语言
羑悻的小杀马特1 天前
【Linux篇章】穿越网络迷雾:揭开 HTTP 应用层协议的终极奥秘!从请求响应到实战编程,从静态网页到动态交互,一文带你全面吃透并征服 HTTP 协议,打造属于你的 Web 通信利刃!
linux·运维·网络·http·操作系统·网络通信
网安CILLE1 天前
Linux 命令大全(网络安全常用)
linux·运维·服务器·网络安全
CodeOfCC1 天前
flutter-elinux 编译linux arm64程序
linux·flutter
米高梅狮子1 天前
7. Linux RAID 存储技术
linux·运维·服务器
麻辣长颈鹿Sir1 天前
CMAKE指令集
linux·运维·windows·cmake·cmake指令集
座山雕~1 天前
Linux的超全,命令
linux
AllFiles1 天前
Linux流量控制神器TC完全指南:原理详解与实践指南
linux·命令行
lbb 小魔仙1 天前
Linux 服务器安全配置:iptables + SELinux 防御策略全解析
linux·服务器·安全