netlink

用以实现用户进程与内核进程通信
netlink 套接字的最大特点是对中断过程的支持

内核与用户数据交换
sysfs、sysctl、netlink、procfs、seq_file、debugfs和relayfs

优点

  1. Netlink通过socket API;而ioctl和proc文件系统均需要通过程序加入相应的设备或文件
  2. Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能
  3. Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息
  4. Netlink允许内核发起会话,而ioctl和系统调用只能由用户空间进程发起

用户空间

int socket(int domain, int type, int protocol);

domain:PF_NETLINK

struct sockaddr_nl
{
    sa_family_t    nl_family;
    unsigned short nl_pad;
    __u32          nl_pid;
    __u32          nl_groups;
};

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

nl_pid:接收或发送消息的进程ID,如果希望内核处理消息或多播消息,就把该字段设置为 0,否则设置为处理消息的进程ID(线程使用pthread_self() << 16 | getpid())
nl_groups:指定多播组。bind 函数把调用进程加入到该字段指定的多播组,如果设置为 0,表示调用者不加入任何多播组

//netlink消息头
struct nlmsghdr
{
  __u32 nlmsg_len;   /* Length of message *///包含头
  __u16 nlmsg_type;  /* Message type*/
  __u16 nlmsg_flags; /* Additional flags */
  __u32 nlmsg_seq;   /* Sequence number */
  __u32 nlmsg_pid;   /* Sending process PID *///源端口
};

/*总长度*/
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*字节对齐*/
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )

nlmsg_len:消息的总长度(包含消息头)。一般地,使用NLMSG_LENGTH来计算对齐后的总长度
nlmsg_type:应用内部定义的消息类型

#define NLMSG_NOOP              0x1     /* Nothing. */
#define NLMSG_ERROR             0x2     /* Error */
#define NLMSG_DONE              0x3     /* End of a dump */
#define NLMSG_OVERRUN           0x4     /* Data lost */

nlmsg_flags:附加在消息上的额外说明信息
nlmsg_seq 和 nlmsg_pid:用于应用追踪消息,前者表示顺序号,后者为消息来源进程 ID

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

举例

#include <linux/types.h>
#include <linux/netlink.h>

#define NL_IMP2 31
#define IMP2_U_PID 0 //消息类型

static int skfd;
skfd = socket(PF_NETLINK, SOCK_RAW, NL_IMP2);
if(skfd < 0)
{
    printf("can not create a netlink socket
");
    exit(0);
}

struct sockaddr_nl local;
memset(&local, 0, sizeof(local));
local.nl_family = AF_NETLINK;
local.nl_pid = 0; //向内核发送
local.nl_groups = 0;

if(bind(skfd, (struct sockaddr*)&local, sizeof(local)) != 0)
{
    printf("bind() error
");
    return -1;
}

struct nlmsghdr hdr;
memset(&hdr, 0, sizeof(hdr));
hdr.nlmsg_len = NLMSG_LENGTH(0); //没有多余的数据
hdr.nlmsg_flags = 0;
hdr.nlmsg_type = NL_T_XX; //自定义消息类型
hdr.nlmsg_pid = getpid(); //发送者的PID

sendto(skfd, &hdr, hdr.nlmsg_len, 0, (struct sockaddr*)&local, sizeof(local));
//接收
typedef struct
{
    struct nlmsghdr hdr;
    struct packet_info icmp_info;
}info;

while(1)
{
    len = sizeof(struct sockaddr_nl);
    rcvlen = recvfrom(skfd, &info, sizeof(info), 0, (struct sockaddr*)&local, &len);
}

内核空间

struct sock *netlink_kernel_create(int unit, void (*input)(struct sock *sk, int len));

unit:netlink协议类型
input:当有消息到达,会被引用
sk:和返回值一致

在input中,你可以直接处理收到的数据,也可以不处理,在大量数据传输的情况下,在input中处理是不明智的,正确的方式应该是建立一个内核线程专门接收数据,没有数据的时候该内核线程睡眠,一旦有了数据,input回调函数唤醒这个内核线程就是了

内核空间发送数据使用独立创建的sk_buff缓冲区

#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(sizeof(struct nlmsghdr)))
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))

NLMSG_ALIGN(len):得到不小于len且字节对齐的最小数值
NLMSG_LENGTH(len):计算数据部分长度为len时实际的消息长度
NLMSG_SPACE(len):返回不小于NLMSG_LENGTH(len)且字节对齐的最小数值

#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))

取得消息的数据部分首地址

#define NETLINK_CB(skb) (*(struct netlink_skb_parms*)&((skb)->cb))

NETLINK_CB(skb).pid = 0;
NETLINK_CB(skb).dst_pid = 0;
NETLINK_CB(skb).dst_group = 1;

pid:发送者进程ID,对于内核,它为0
dst_pid:接收者进程ID,如果目标为组或内核,它为0
dst_group:目标组地址,如果目标为某一进程或内核,它为0

netlink_unicast()发布单播消息

int netlink_unicast(struct sock *sk, struct sk_buff *skb, u32 pid, int nonblock);

pid:接收消息进程的pid
nonblock:该函数是否为非阻塞。如果为1,该函数将在没有接收缓存可利用时立即返回,而如果为0,该函数在没有接收缓存可利用时睡眠

void netlink_broadcast(struct sock *sk, struct sk_buff *skb, u32 pid, u32 group, int allocation);

group:接收消息的多播组。如果发送给多个多播组,就把该参数设置为多个多播组组ID的位或
allocation:GFP_ATOMIC用于原子的上下文(即不可以睡眠);GFP_KERNEL用于非原子上下文

#define NLMSG_PUT(skb, pid, seq, type, len)
({ if (skb_tailroom(skb) < (int)NLMSG_SPACE(len)) goto nlmsg_failure; __nlmsg_put(skb, pid, seq, type, len); })

static __inline__ struct nlmsghdr *__nlmsg_put(struct sk_buff *skb, u32 pid, u32 seq, int type, int len)
{
    struct nlmsghdr *nlh;
    int size = NLMSG_LENGTH(len);
    nlh = (struct nlmsghdr*)skb_put(skb, NLMSG_ALIGN(size));
    nlh->nlmsg_type = type;
    nlh->nlmsg_len = size;
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_pid = pid;
    nlh->nlmsg_seq = seq;
    return nlh;
}

程序中应该定义nlmsg_failure标签

举例

#include <linux/netfilter_ipv4.h>  
#include <linux/netlink.h>
#include <net/sock.h>

nlfd = netlink_kernel_create(NL_IMP2, kernel_receive);
if(!nlfd)
{
    printk("can not create a netlink socket/n");
    return -1;
}

static void kernel_receive(struct sock *sk, int len)
{
    do
    {
        struct sk_buff *skb;
        while((skb = skb_dequeue(&sk->receive_queue)) != NULL)
        {
            struct nlmsghdr *nlh = NULL;
            if(skb->len >= sizeof(struct nlmsghdr)
            {
                nlh = (struct nlmsghdr *)skb->data;
                if((nlh->nlmsg_len >= sizeof(struct nlmsghdr)) && (skb->len >= nlh->nlmsg_len))
                {
                    //自定义消息
                    if(nlh->nlmsg_type == IMP2_U_PID)       
                    {
                        ;
                    }
                    //socket关闭
                    else if(nlh->nlmsg_type == IMP2_CLOSE)
                    {
                        ;
                    }
                }
            }
        }
        kfree_skb(skb);
    }while(nlfd && nlfd->receive_queue.qlen);
}

if(nlfd)
{
    sock_release(nlfd->socket);
}
//发送
struct sk_buff *skb;
struct nlmsghdr *nlh;
struct packet_info *packet;

/*计算消息总长*/
size = NLMSG_SPACE(sizeof(*info));

/*分配一个新的套接字缓存*/
skb = alloc_skb(size, GFP_ATOMIC);

/*初始化一个netlink消息首部*/
nlh = NLMSG_PUT(skb, 0, 0, NL_T_XX, size-sizeof(*nlh));
nlh->nlmsg_len = size;

/*跳过消息首部,指向数据区*/
packet = NLMSG_DATA(nlh);

/*初始化数据区*/
memset(packet, 0, sizeof(struct packet_info));

/*填充待发送的数据*/
packet->src = info->src;
packet->dest = info->dest;

/*设置控制字段*/
NETLINK_CB(skb).dst_groups = 0;

/*发送数据到目的pid*/
ret = netlink_unicast(nlfd, skb, pid, MSG_DONTWAIT);

2.6内核netlink
从2.6.24开始,linux内部对netlink的实现机制和调用接口进行了很大的调整,特别是函数 netlink_kernel_create(),最新的参数有6个之多

extern struct sock *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:使用&init_net,定义在linux/net/core/net_namespace.c 中,此外在linux/include/net/net_namespace.h中也有外部定义,直接作为参数使用即可
unit:同上
input:同上,但不需要自己调用调度的方法,让内核进程阻塞以便等待消息到来了
module:THIS_MODULE,定义在module.h,表示当前模块

原文地址:https://www.cnblogs.com/zhangxuechao/p/11709821.html