netlink

来源:互联网 发布:网络进度计划波浪线 编辑:程序博客网 时间:2024/05/21 14:42

用以实现用户进程与内核进程通信
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\n");    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\n");    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(); //发送者的PIDsendto(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,表示当前模块

1 0
原创粉丝点击