1.基础概念:
内核空间:Linux 系统包含内核空间和用户空间,一般将比较重要的内容放置内核空间运行。
用户空间:把GUI ,管理,控制程序放置用户空间运行。
IPC: 内核空间和用户空间之间需要进行通信,如何通信呢?这就需要IPC. 常用的IPC 有ioctl,系统调用,netlink socket.
2. netlink socket 基本原理:
- netlink socket 是IPC 中的一种,是一种异步通信机制。发送的消息只暂存在socket 接收缓存中,不需要等待接收者立即处理。
- 使用netlink socket 实现用户空间和内核空间的通信。用户空间只调用统一的socket API 来使用netlink 通信,内核空间使用统一的内核API 也可使用netlink 通信,从而实现用户空间和内核空间的通信。
- Netlink协议基于BSD socket和AF_NETLINK地址簇,使用32位的端口号寻址(PID),每个Netlink协议通常与一个或一组内核服务/组件相关联,如常用的NETLINK_ROUTE用于获取和设置路由与链路信息。
3. 用户空间常用socket APIs:
1.socket():
函数作用: 创建一个socket
函数格式:int socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
参数介绍:
-
第一个参数表示地址族,AF_NETLINK 表示使用netlink.
-
第二个参数必须是SOCK_RAW 或SOCK_DGRAM
-
第三个参数是指netlink 的协议类型。 该协议类型可以是内核空间定义好的,也可以是用户空间定义的。 比如NETLINK_ROUTE 是内核已定义好的,表示路由的协议; NETLINK_GENERIC 是一个通用协议类型,专门为用户使用,用户不必定义添加新的协议类型。(当然用户也可自己添加)。
- 所有内核定义的协议类型均在 linux/netlink.h (如下所示:)
#define NETLINK_ROUTE 0 /* Routing/device hook */ #define NETLINK_UNUSED 1 /* Unused number */ #define NETLINK_USERSOCK 2 /* Reserved for user mode socket protocols */ #define NETLINK_FIREWALL 3 /* Firewalling hook */ #define NETLINK_INET_DIAG 4 /* INET socket monitoring */ #define NETLINK_NFLOG 5 /* netfilter/iptables ULOG */ #define NETLINK_XFRM 6 /* ipsec */ #define NETLINK_SELINUX 7 /* SELinux event notifications */ #define NETLINK_ISCSI 8 /* Open-iSCSI */ #define NETLINK_AUDIT 9 /* auditing */ #define NETLINK_FIB_LOOKUP 10 #define NETLINK_CONNECTOR 11 #define NETLINK_NETFILTER 12 /* netfilter subsystem */ #define NETLINK_IP6_FW 13 #define NETLINK_DNRTMSG 14 /* DECnet routing messages */ #define NETLINK_KOBJECT_UEVENT 15 /* Kernel messages to userspace */ #define NETLINK_GENERIC 16 /* leave room for NETLINK_DM (DM Events) */ #define NETLINK_SCSITRANSPORT 18 /* SCSI Transports */ #define NETLINK_ECRYPTFS 19 #define NETLINK_RDMA 20 #define NETLINK_CRYPTO 21 /* Crypto layer */ #define MAX_LINKS 32
2. bind(): 函数
函数作用: socket 套接字文件描述符获取成功后,需要对套接字进行地址端口的绑定,之后才能通过该socket 进行数据接收和操作。该函数就是将地址与socketfd绑定。
函数格式: bind(int sockfd, const struct sockaddr *local_addr,socklen_t addr_len);
参数介绍:
- 第一个参数是前面通过socket() 获取的文件描述符。
- 第二个参数为要与socket绑定的本地地址,netlink 有专门的结构体地址。具体如下
- 第三个参数为sockaddr 的长度。
返回值:
- 返回0 : bind 绑定成功
- 返回-1: bind 绑定失败
- errno: 错误值
参考网址:https://blog.youkuaiyun.com/xc_tsao/article/details/44132965
相关补充知识:
struct sockaddr{
sa_family_t sa_family; /* 地址族 AF_XXX */
char sa_data[14]; /* 协议地址 */
};
struct sockaddr_nl{
sa_family_t nl_family; /*nelink 地址族,这里通常为AF_NETLINK*/
unsigned short nl_pad; /* 通常设置为0 */
unsigned int nl_pid; /* 接受消息的进程ID,如果为0 表示接收方为内核或多播组,如果多个进程可以使用pthread_self()<<16|getpid();*/
unsigned int nl_groups; /* 接收消息的多播组, 是一系列的掩码,一个进程可以存在多个多播组中,消息发送者指定多播组,这样消息将发给多个接收者*/
};
/* 举例 */
int sock= socket(AF_NETLINK,SOCK_RAW,NETLINK_ROUTINE);
struct sockaddr_nl snl;
memset(&snl,0, sizeof(snl));
snl.nl_family = AF_NETLINK;
snl.nl_groups = RTMGRP_LINK | RTM_NEWNEIGH;
if(bind(sock,(struct sockaddr*(&snl)),sizeof(snl))<0)
printf("socket bind error\n");
3. sendmsg():
函数作用:
函数格式: ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
参数介绍:
- sookfd : sock()获取的套接字描述符
- msghdr*: 信息封装在msghdr 中,具体的数据在iovec 中
- flags: 一般设置为0
类似函数: ssize_t send(int sockfd, const void *buf, size_t len,int flags)
相关补充知识:
1). msghdr 介绍:
/*---------------------iovec-------------------------*/
struct iovec {
void *iov_base; /* 基本数据的地址*/
_kernel_size_t iov_len; /* 单位是size_t */
};
/*----------------msghdr 消息头结构------------------- */
struct msghdr{
void* msg_name; /* Socket name, 指向sockaddr_nl 结构的指针。在非连接的UDP中,发送者要指定接收方地址,需要该项。在TCP或连接的UDP中(如通过bind 已连接),此值一般设置为NULL*/
int msg_namelen; /* msg_name 的长度*/
struct iovec * msg_iov; /* 多io(输入、输出)缓冲区的地址 ,其实是我们要传输的数据块(基址+长度)*/
_kernel_size_t msg_iovlen; /* 缓冲区的个数 */
void* msg_control; /* 辅助数据的地址*/
_kernel_size_t msg_controllen; /* 辅助数据的长度*/
unsigned msg_flags; /* 接受消息的标识*/
};
2). nlmsghdr 介绍:
/*-----------nlmsghdr---------------------*/
//struct nlmsghdr 是netlink socket 自己的消息头,被称为netlink的控制块, 应用层向内核发送netlink消息时,必须提供该控制头。
struct nlmsghdr{
u32 nlmsg_len; /* msg 的总长度,包括该信息头长度*/
u16 nlmsg_type; /* 应用内部定义的消息类型,大部分情况设置为0*/
u16 nlmsg_flags; /* 额外的flag,用于设置消息标志*/
u32 nlmsg_seq; /* 序列号用于追踪消息*/
u32 nlmsg_pid; /* 来源进程ID 用于追踪消息*/
};
- nlmsg_type: 消息的类型,位于inlucde/linux/rtnetlink.h 中
/*------nlmsg_type 所支持的控制消息------*/
1. NLMSG_NOOP-空消息,什么也不做;
2. NLMSG_ERROR-指明该消息中包含一个错误;
3. NLMSG_DONE-如果内核通过Netlink队列返回了多个消息,那么队列的最后一条消息的类型为NLMSG_DONE,其余所有消息的nlmsg_flags属性都被设置NLM_F_MULTI位有效。
4. NLMSG_OVERRUN-暂时没用到
/*---------nlmsg_type 所用的常用消息------*/
enum {
RTM_BASE = 16,
#define RTM_BASE RTM_BASE
RTM_NEWLINK = 16, /*增加特定网络接口的相关信息*/
#define RTM_NEWLINK RTM_NEWLINK
RTM_DELLINK, /*删除特定网络接口的相关信息*/
#define RTM_DELLINK RTM_DELLINK
RTM_GETLINK, /* 获取特定网络接口的相关信息*/
#define RTM_GETLINK RTM_GETLINK
RTM_SETLINK,
#define RTM_SETLINK RTM_SETLINK
RTM_NEWADDR = 20,
#define RTM_NEWADDR RTM_NEWADDR
RTM_DELADDR, /*删除一个与网络接口相关的IP地址*/
#define RTM_DELADDR RTM_DELADDR
RTM_GETADDR,
#define RTM_GETADDR RTM_GETADDR
RTM_NEWROUTE = 24,
#define RTM_NEWROUTE RTM_NEWROUTE
RTM_DELROUTE,
#define RTM_DELROUTE RTM_DELROUTE
RTM_GETROUTE,
#define RTM_GETROUTE RTM_GETROUTE
RTM_NEWNEIGH = 28,
#define RTM_NEWNEIGH RTM_NEWNEIGH
RTM_DELNEIGH, /*删除有关邻里表条目,如ARP entry (地址解析协议条目)*/
#define RTM_DELNEIGH RTM_DELNEIGH
RTM_GETNEIGH,
#define RTM_GETNEIGH RTM_GETNEIGH
RTM_NEWRULE = 32,
#define RTM_NEWRULE RTM_NEWRULE
RTM_DELRULE,
#define RTM_DELRULE RTM_DELRULE
RTM_GETRULE,
#define RTM_GETRULE RTM_GETRULE
RTM_NEWQDISC = 36,
#define RTM_NEWQDISC RTM_NEWQDISC
RTM_DELQDISC,
#define RTM_DELQDISC RTM_DELQDISC
RTM_GETQDISC,
#define RTM_GETQDISC RTM_GETQDISC
RTM_NEWTCLASS = 40,
#define RTM_NEWTCLASS RTM_NEWTCLASS
RTM_DELTCLASS,
#define RTM_DELTCLASS RTM_DELTCLASS
RTM_GETTCLASS,
#define RTM_GETTCLASS RTM_GETTCLASS
RTM_NEWTFILTER = 44,
#define RTM_NEWTFILTER RTM_NEWTFILTER
RTM_DELTFILTER,
#define RTM_DELTFILTER RTM_DELTFILTER
RTM_GETTFILTER,
#define RTM_GETTFILTER RTM_GETTFILTER
RTM_NEWACTION = 48,
#define RTM_NEWACTION RTM_NEWACTION
RTM_DELACTION,
#define RTM_DELACTION RTM_DELACTION
RTM_GETACTION,
#define RTM_GETACTION RTM_GETACTION
RTM_NEWPREFIX = 52,
#define RTM_NEWPREFIX RTM_NEWPREFIX
RTM_GETMULTICAST = 58,
#define RTM_GETMULTICAST RTM_GETMULTICAST
RTM_GETANYCAST = 62,
#define RTM_GETANYCAST RTM_GETANYCAST
RTM_NEWNEIGHTBL = 64,
#define RTM_NEWNEIGHTBL RTM_NEWNEIGHTBL
RTM_GETNEIGHTBL = 66,
#define RTM_GETNEIGHTBL RTM_GETNEIGHTBL
RTM_SETNEIGHTBL,
#define RTM_SETNEIGHTBL RTM_SETNEIGHTBL
RTM_NEWNDUSEROPT = 68,
#define RTM_NEWNDUSEROPT RTM_NEWNDUSEROPT
RTM_NEWADDRLABEL = 72,
#define RTM_NEWADDRLABEL RTM_NEWADDRLABEL
RTM_DELADDRLABEL,
#define RTM_DELADDRLABEL RTM_DELADDRLABEL
RTM_GETADDRLABEL,
#define RTM_GETADDRLABEL RTM_GETADDRLABEL
RTM_GETDCB = 78,
#define RTM_GETDCB RTM_GETDCB
RTM_SETDCB,
#define RTM_SETDCB RTM_SETDCB
RTM_NEWNETCONF = 80,
#define RTM_NEWNETCONF RTM_NEWNETCONF
RTM_DELNETCONF,
#define RTM_DELNETCONF RTM_DELNETCONF
RTM_GETNETCONF = 82,
#define RTM_GETNETCONF RTM_GETNETCONF
RTM_NEWMDB = 84,
#define RTM_NEWMDB RTM_NEWMDB
RTM_DELMDB = 85,
#define RTM_DELMDB RTM_DELMDB
RTM_GETMDB = 86,
#define RTM_GETMDB RTM_GETMDB
RTM_NEWNSID = 88,
#define RTM_NEWNSID RTM_NEWNSID
RTM_DELNSID = 89,
#define RTM_DELNSID RTM_DELNSID
RTM_GETNSID = 90,
#define RTM_GETNSID RTM_GETNSID
RTM_NEWSTATS = 92,
#define RTM_NEWSTATS RTM_NEWSTATS
RTM_GETSTATS = 94,
#define RTM_GETSTATS RTM_GETSTATS
RTM_NEWCACHEREPORT = 96,
#define RTM_NEWCACHEREPORT RTM_NEWCACHEREPORT
RTM_NEWCHAIN = 100,
#define RTM_NEWCHAIN RTM_NEWCHAIN
RTM_DELCHAIN,
#define RTM_DELCHAIN RTM_DELCHAIN
RTM_GETCHAIN,
#define RTM_GETCHAIN RTM_GETCHAIN
RTM_NEWNEXTHOP = 104,
#define RTM_NEWNEXTHOP RTM_NEWNEXTHOP
RTM_DELNEXTHOP,
#define RTM_DELNEXTHOP RTM_DELNEXTHOP
RTM_GETNEXTHOP,
#define RTM_GETNEXTHOP RTM_GETNEXTHOP
RTM_NEWLINKPROP = 108,
#define RTM_NEWLINKPROP RTM_NEWLINKPROP
RTM_DELLINKPROP,
#define RTM_DELLINKPROP RTM_DELLINKPROP
RTM_GETLINKPROP,
#define RTM_GETLINKPROP RTM_GETLINKPROP
RTM_NEWVLAN = 112,
#define RTM_NEWNVLAN RTM_NEWVLAN
RTM_DELVLAN,
#define RTM_DELVLAN RTM_DELVLAN
RTM_GETVLAN,
#define RTM_GETVLAN RTM_GETVLAN
RTM_NEWNEXTHOPBUCKET = 116,
#define RTM_NEWNEXTHOPBUCKET RTM_NEWNEXTHOPBUCKET
RTM_DELNEXTHOPBUCKET,
#define RTM_DELNEXTHOPBUCKET RTM_DELNEXTHOPBUCKET
RTM_GETNEXTHOPBUCKET,
#define RTM_GETNEXTHOPBUCKET RTM_GETNEXTHOPBUCKET
__RTM_MAX,
#define RTM_MAX (((__RTM_MAX + 3) & ~3) - 1)
};
- nlmsg_flags :msg的标志定义在include/linux/netlink.h
/* Flags values */
#define NLM_F_REQUEST 0x01 /* It is request message. 消息是一个请求,所有应用首先发起的消息都应该设置该标志*/
#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE 表示该消息是一个多部分消息的一部分,后续的消息通过NLMSG_NEXT 获取*/
#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code 表示该消息是一个请求消息的响应,顺序号和进程ID可以把请求和响应关联起来 */
#define NLM_F_ECHO 0x08 /* Echo this request 表示该消息是一个相关的包的回传 */
#define NLM_F_DUMP_INTR 0x10 /* Dump was inconsistent due to sequence change */
#define NLM_F_DUMP_FILTERED 0x20 /* Dump was filtered as requested */
/* Modifiers to GET request */
#define NLM_F_ROOT 0x100 /* specify tree root 请求的数据表应当整体返回用户应用,而不是一个条目一个条目的返回,被许多netlink协议的各种数据获取操作使用*/
#define NLM_F_MATCH 0x200 /* return all matching 表示该协议特定的请求只需要一个数据子集,数据子集由指定的协议特定的过滤器来匹配*/
#define NLM_F_ATOMIC 0x400 /* atomic GET */
#define NLM_F_DUMP (NLM_F_ROOT|NLM_F_MATCH)
/* Modifiers to NEW request */
#define NLM_F_REPLACE 0x100 /* Override existing 用于取代数据表中的现有条目 */
#define NLM_F_EXCL 0x200 /* Do not touch, if it exists */
#define NLM_F_CREATE 0x400 /* Create, if it does not exist */
#define NLM_F_APPEND 0x800 /* Add to end of list */
/* Modifiers to DELETE request */
#define NLM_F_NONREC 0x100 /* Do not delete recursively */
/* Flags for ACK message */
#define NLM_F_CAPPED 0x100 /* request was capped */
#define NLM_F_ACK_TLVS 0x200 /* extended ACK TVLs were included */
3). 举例说明:
#include <sys/types.h>
#include <sys/socket.h>
//sendmsg test
int main()
{
struct nlmsghdr *n
struct iovec iov = { (void*) n, n->nlmsg_len };
struct sockaddr_nl snl;
struct msghdr msg = {(void*) &snl, sizeof snl, &iov, 1, NULL, 0, 0};
status = sendmsg(sock, &msg, 0);
return 0;
}
4. send()
函数作用:类似sendmsg(), 发送消息到sock 对列
函数格式:size_t send(int sockfd, const void * buf, size_t len,int flag);
5. recvmsg():
函数作用: 接收来自套接字的信息message。
函数格式: recvmsg(int sockfd, struct msghdr * msg, int flags);
参数介绍:
- sockfd: 套接字文件描述符
- msghdr: 减少函数参数的数量而设计的结构
- flag: 设置为0
举例:
int res;
char buf[1024];
struct iovec iov = {buf, sizeof(buf)};
struct sockaddr_nl snl;
struct msghdr msg = { (void*)&snl, sizeof(snl), &iov, 1, NULL, 0, 0};
struct nlmsghdr *h;
res = recvmsg (sock, &msg, 0);
6. close()
函数作用: 关闭套接字
函数格式: close(int sockfd)
参数介绍:
- sockfd: 套接字文件描述符
4. netlink 中的相关宏介绍:
netlink 中定义的相关宏主要是为了计算方便,方便得到数据的位置,方便得到下一个消息。
#define NLMSG_ALIGNTO 4
#define NLMSG_ALLIGN(len) (((len)+NLMSG_ALIGNTO-1)& ~(NLMSG_ALIGNTO-1))
/*上述NLMSG_ALLIGN(len)得到不小于len且字节对齐的最小数值*/
#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr)))
/*得到不小于nlmsghd且字节对齐的值 */
#define NLMSG_LENGTH(len) ((len)+NLMSG_ALIGN(NLMSG_HDRLEN))
/*NLMSG_LENGTH(len)得到数据部分长度为len时实际的消息长度,它一般用于分配消息缓存*/
#define NLMSG_SPACE(len) NLMSG_ALIGN(NLMSG_LENGTH(len))
#define NLMSG_DATA(nlh) ((void*)(((char*)nlh) + NLMSG_LENGTH(0)))
/*获取数据部分的首地址*/
#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_OK(nlh,len)用于判断消息是否有len这么长*/
#define NLMSG_PAYLOAD(nlh,len) ((nlh)->nlmsg_len - NLMSG_SPACE((len)))
/*宏NLMSG_PAYLOAD(nlh,len)用于返回payload的长度*/
温馨提示: 主要用于学习和笔记记载,如发现错误欢迎提出。