参考文档: https://www./article/7356 Linux Journey: Why and how to use Netlink socket https://www./article/8498?page=0,1
用户空间与内核通信的几种方法: * system call * procfs(/proc文件系统) * sysctl(/proc/sys目录) * sysfs(/sys文件系统) * ioctl 系统调用 * netlink套接字(RFC 3549) net/netlink目录 - af_netlink.c - af_netlink.h - genetlink.c - diag.c
af_netlink 模块提供了内核态API genetlink 模块提供了更新的 内核API, 更容易发送数据信息 diag 模块提供了netlink接口监控信息
一、用户态netlink API用户态的netlink库有两个: http://www./~tgr/libnl/ 功能较全 http:///projects/libmnl/ mini的libnetlink 库
以下流程用来对网络环境进行修改: step 1. 打开套接字 step 2. 在本地 bind 套接字 step 3. 发送消息到目的地 step 4. 在目的地接收消息 step 5. 关闭套接字
/* 添加头文件 */
1. 创建socket netlink套接字调用原型 include/linux/netlink.h #include <sys/socket.h> #include <linux/netlink.h> 第一个参数 doamin 表示什么样的套接字类型,使用RTNETLINK, 使用AF_NETLINK 第二个参数 type 表示什么方法 RAW 或 DGRAM ,对于RTNETLINK 都可以使用 第三个参数 protocol , 为了修改路由表,我们使用NETLINK_ROUTE 如果成功,返回一个正数整形 如果失败,返回一个负数
从用户态创建socket /* 示例代码 */
int bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); 第一个参数是 socket创建的点 第二个参数 struct sockaddr* 是本地地址,结构如下: 数据结构 sockaddr_nl 表示一个netlink套接字地址 include/uapi/linux/netlink.h struct sockaddr_nl { __kernel_sa_family_t nl_family; /* AF_NETLINK */ unsigned short nl_pad; /* zero */ __u32 nl_pid; /* port ID */ __u32 nl_groups; /* multicast groups mask */ }; nl_family 总是 AF_NETLINK nl_pad 总是为0 nl_pid 当地址是内核套接字时,值为0 用户空间程序会将nl_pid的值设为他们的进程ID nl_groups: 组播值为0表示单播,值从0-31
bind 函数成功,返回0,失败返回负数 /* 示例代码 */
libnetlink中代码: libnetlink.c 中rtnl_open函数解析#include <asm/types.h> #include <libnetlink.h> #include <linux/netlink.h> #include <linux/rtnetlink.h>
// 定义在头文件 libnetlink.h struct rtnl_handle { int fd; // 套接字描述符 struct sockaddr_nl local; // 本地地址 struct sockaddr_nl peer; // 目的地址 __u32 seq; // 序列号 __u32 dump; int proto; // 协议 FILE *dump_fp; int flags; // 标志字段 };
// libnetlink.c
// libnetlink.c 头文件中rtnl_open_byproto函数
3. 发送netlink消息 如果消息是发送给内核的,nl_pid 与 nl_groups 的值都为0 如果消息是单播消息发送给另一个进程的, nl_pid是另一个进程的pid值,nl_groups的值为0 如果消息是组播发送的,需要定义nl_groups的值。 ssize_t sendmsg(int fd, const struct msghdr *msg, int flags);
第一个参数 是socket创建点 第二个参数是需要发送消息的首地址,需要如下定义 struct msghdr msg; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len;
struct msghdr { void *msg_name; //Address to send to socklen_t msg_namelen; //Length of address data struct iovec *msg_iov; //Vector of data to send size_t msg_iovlen; //Number of iovec entries void *msg_control; //Ancillary data size_t msg_controllen; //Ancillary data buf len int msg_flags; //Flags on received msg };
netlink消息头部 include/uapi/linux/netlink.h 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 */ };
0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Length | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Type | Flags | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Sequence Number | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | Process ID (PID) | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 32位的nlmsg_len 表示整个netlink消息的数据包长度 16位的nlmsg_type是从应用定义 NLMSG_NOOP 无任何操作,消息必须丢弃 NLMSG_ERROR 错误发生了 NLMSG_DONE 其他消息结束了 NLMSG_OVERRUN 错误,数据丢失了
4. 接收消息 接收的应用必须分配足够的内存空间来获取消息头部与payloads. ssize_t recv(int fd, void *buf, size_t len, int flags);
struct sockaddr_nl nladdr; struct msghdr msg; struct iovec iov; iov.iov_base = (void *)nlh; iov.iov_len = MAX_NL_MSG_LEN; msg.msg_name = (void *)&(nladdr); msg.msg_namelen = sizeof(nladdr); msg.msg_iov = &iov; msg.msg_iovlen = 1; recvmsg(fd, &msg, 0);
5. 关闭 int close(int fd);
libnetlink库中rtnl_close函数 // libnetlink.h 头文件
二、内核态netlink 接口net/core/af_netlink.c
1. 创建socket接口 struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg)
第一个参数 net 表示网络命令空间 第二个参数 是netlink协议如, uapi/linux/netlink.h NETLINK_ROUTE NETLINK_XFRM NETLINK_AUDIT ... 第三个参数 是可选项 include/linux/netlink.h /* optional Netlink kernel configuration parameters */ struct netlink_kernel_cfg { unsigned int groups; unsigned int flags; void (*input)(struct sk_buff *skb); struct mutex *cb_mutex; int (*bind)(struct net *net, int group); void (*unbind)(struct net *net, int group); bool (*compare)(struct net *net, struct sock *sk); }; groups 表示组播号 flags字段可以是 NL_CFG_F_NONROOT_RECV NL_CFG_F_NONROOT_SEND input是一个回调函数 rtnetlink_rcv() cb_mutex 是可选项,
netlink_kernel_create 在内核 nl_table 创建一个点。
2. 内核注册 extern void rtnl_register(int protocol, int msgtype, rtnl_doit_func, rtnl_dumpit_func, rtnl_calcit_func );
3. 发送消息 rtmsg_ifinfo()
2. 在内核通过netlink发送消息 int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);
3. 在内核关闭netlink sock_release(nl_sk->socket);
三、在路由表中添加或删除一条路由ip route add 192.168.1.11 via 192.168.2.20
这条命令从用户态程序中发送netlink 消息(RTM_NEWROUTE)调用rtlinknet套接字来创建路由条目。 消息被内核态rtnetlink套接字的rtnetlink_rcv()方法接收。
|
|
来自: just_person > 《待分类》