用以实现用户进程与内核进程通信
netlink 套接字的最大特点是对中断过程的支持
内核与用户数据交换
sysfs、sysctl、netlink、procfs、seq_file、debugfs和relayfs
优点
- Netlink通过socket API;而ioctl和proc文件系统均需要通过程序加入相应的设备或文件
- Netlink使用socket缓存队列,是一种异步通信机制,而ioctl是同步通信机制,如果传输的数据量较大,会影响系统性能
- Netlink支持多播,属于一个Netlink组的模块和进程都能获得该多播消息
- 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(); //发送者的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,表示当前模块