查看: 1122|回复: 0

[技术交流] 内核通信之 Netlink 源码分析和实例分析(3)

[复制链接]

185

主题

204

帖子

596

积分

利尔达员工

Rank: 9Rank: 9Rank: 9

积分
596
发表于 2020-7-29 16:27:45 | 显示全部楼层 |阅读模式
netlink 关键数据结构和函数sockaddr_nl 协议套接字
netlink 的地址表示由 sockaddr_nl 负责
struct sockaddr_nl {
    __kernel_sa_family_t    nl_family;    /* AF_NETLINK    */
    unsigned short          nl_pad;        /* zero        */
    __u32                   nl_pid;        /* port ID 这个一般是进程id */
    __u32                   nl_groups;    /* multicast groups mask */
};
nl_family 制定了协议族,netlink 有自己独立的值:AF_NETLINK,nl_pid 一般取为进程 pid。nl_groups 用以多播,当不需要多播时,该字段为 0。
nlmsghdr 消息体
netlink 消息是作为套接字缓冲区 sk_buff 的数据部分传递的,其消息本身又分为头部和数据。头部为:
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 port ID */
};
nlmsg_len 为消息的长度,包含该头部在内。nlmsg_pid 为发送进程的端口 ID,这个用户可以自定义,一般也是使用进程 pid。
msghdr 用户态系发送消息体
使用 sendmsg 和 recvmsg 函数进行发送和接收消息,使用的消息体是这个样子的。
struct iovec {                    /* Scatter/gather array items */
    void  *iov_base;              /* Starting address */
    size_t iov_len;               /* Number of bytes to transfer */
};
/*
iov_base: iov_base 指向数据包缓冲区,即参数 buff,iov_len 是 buff 的长度。
msghdr 中允许一次传递多个 buff,以数组的形式组织在 msg_iov 中,msg_iovlen 就记录数组的长度 (即有多少个buff)
*/
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 int      msg_flags;
};
/*
   msg_name:数据的目的地址,网络包指向 sockaddr_in, netlink 则指向 sockaddr_nl;
   msg_namelen: msg_name 所代表的地址长度
   msg_iov: 指向的是缓冲区数组
   msg_iovlen: 缓冲区数组长度
   msg_control: 辅助数据,控制信息(发送任何的控制信息)
   msg_controllen: 辅助信息长度
   msg_flags: 消息标识
*/
逻辑结构如下:
socket 也是一种特殊的文件,通过 VFS 的接口同样可以对其进行使用管理。socket 本身就需要实现文件系统的相应接口,有自己的操作方法集。
netlink 常用宏#define NLMSG_ALIGNTO   4U/* 宏 NLMSG_ALIGN(len) 用于得到不小于len且字节对齐的最小数值 */
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
/* Netlink 头部长度 */
#define NLMSG_HDRLEN     ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/* 计算消息数据 len 的真实消息长度(消息体 + 消息头)*/
#define NLMSG_LENGTH(len) ((len) + NLMSG_HDRLEN)
/* 宏 NLMSG_SPACE(len) 返回不小于 NLMSG_LENGTH(len) 且字节对齐的最小数值 */
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
/* 宏 NLMSG_DATA(nlh) 用于取得消息的数据部分的首地址,设置和读取消息数据部分时需要使用该宏 */
#define NLMSG_DATA(nlh)  ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/* 宏 NLMSG_NEXT(nlh,len) 用于得到下一个消息的首地址, 同时 len 变为剩余消息的长度 */
#define NLMSG_NEXT(nlh,len)  ((len) -= NLMSG_ALIGN((nlh)->nlmsg_len), \                  (struct nlmsghdr*)(((char*)(nlh)) + NLMSG_ALIGN((nlh)->nlmsg_len)))
/* 判断消息是否 >len */
#define NLMSG_OK(nlh,len) ((len) >= (int)sizeof(struct nlmsghdr) && \               (nlh)->nlmsg_len >= sizeof(struct nlmsghdr) && \               (nlh)->nlmsg_len <= (len))
/* NLMSG_PAYLOAD(nlh,len) 用于返回 payload 的长度*/
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))netlink 内核常用函数netlink_kernel_create
这个内核函数用于创建内核 socket,以提供和用户态通信
static inline struct sock *
netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
/*
    net: 指向所在的网络命名空间, 默认传入的是 &init_net (不需要定义);   net_namespace.c(extern struct net init_net);
    unit: netlink
    cfg:  cfg netlink
*/

/* optional Netlink kernel configuration parameters */
struct netlink_kernel_cfg {
    unsigned int    groups;
    unsigned int    flags;
    void        (*input)(struct sk_buff *skb); /* input 回调函数 */
    struct mutex    *cb_mutex;
    void        (*bind)(int group);
    bool        (*compare)(struct net *net, struct sock *sk);
};
单播函数 netlink_unicast() 和多播函数 netlink_broadcast()/* 来发送单播消息 */
extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock);
/* ssk: netlink socket
   skb: skb buff 指针
   portid:通信的端口号
   nonblock:表示该函数是否为非阻塞,如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为 0,该函数在没有接收缓存可利用 定时睡眠
*/

/* 用来发送多播消息 */
extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,
                 __u32 group, gfp_t allocation);
/* ssk: 同上(对应 netlink_kernel_create 返回值)、
   skb: 内核 skb buff
   portid:端口id
   group: 是所有目标多播组对应掩码的"OR"操作的。
   allocation: 指定内核内存分配方式,通常 GFP_ATOMIC 用于中断上下文,而 GFP_KERNEL 用于其他场合。
                这个参数的存在是因为该 API 可能需要分配一个或多个缓冲区来对多播消息进行 clone。
*/

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

快速回复 返回顶部 返回列表