參考文檔:
https://www.linuxjournal.com/article/7356 Linux Journey: Why and how to use Netlink socket
https://www.linuxjournal.com/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.infradead.org/~tgr/libnl/ 功能較全
http://netfilter.org/projects/libmnl/ mini的libnetlink 庫
以下流程用來對網絡環境進行修改:
step 1. 打開套接字
step 2. 在本地 bind 套接字
step 3. 發送消息到目的地
step 4. 在目的地接收消息
step 5. 關閉套接字
/* 添加頭文件 */
#include <bits/sockaddr.h> #include <asm/types.h> #include <linux/rtnetlink.h> #include <sys/socket.h>
1. 創建socket
netlink套接字調用原型 include/linux/netlink.h
#include <sys/socket.h> #include <linux/netlink.h>
int socket(int domain, int type, int protocol);
第一個參數 doamin 表示什么樣的套接字類型,使用RTNETLINK, 使用AF_NETLINK
第二個參數 type 表示什么方法 RAW 或 DGRAM ,對於RTNETLINK 都可以使用
第三個參數 protocol , 為了修改路由表,我們使用NETLINK_ROUTE
如果成功,返回一個正數整形
如果失敗,返回一個負數
從用戶態創建socket
/* 示例代碼 */
int sock_fd; sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
2. bind
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,失敗返回負數
/* 示例代碼 */
int rtn; struct sockaddr_nl la; ... bzero(&la, sizeof(la)); la.nl_family = AF_NETLINK; la.nl_pad = 0; la.nl_pid = getpid(); la.nl_groups = 0; rtn = bind(fd, (struct sockaddr*) &la, sizeof(la));
libnetlink中代碼: libnetlink.c 中rtnl_open函數解析
#include <asm/types.h> #include <libnetlink.h> #include <linux/netlink.h> #include <linux/rtnetlink.h>
// 打開rtnetlink接口,將handle信息保存在rth結構中 int rtnl_open(struct rtnl_handle *rth, unsigned subscriptions)
成功返回0,失敗返回-1
// 定義在頭文件 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
// 成功返回0, 失敗返回-1 int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions) { return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE); }
// libnetlink.c 頭文件中rtnl_open_byproto函數
// 成功返回0, 失敗返回-1
int rtnl_open_byproto(struct rtnl_handle *rth, unsigned int subscriptions, int protocol) { socklen_t addr_len; int sndbuf = 32768; int one = 1;
// 第一步:初值 memset(rth, 0, sizeof(*rth));
// 第二上:填充rth結構 rth->proto = protocol; // 協議為NETLINK_ROUTE
// 第三步:打開套接字接口 rth->fd = socket(AF_NETLINK, SOCK_RAW | SOCK_CLOEXEC, protocol); // 調用socket函數 if (rth->fd < 0) { perror("Cannot open netlink socket"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) < 0) { perror("SO_SNDBUF"); return -1; } if (setsockopt(rth->fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) < 0) { perror("SO_RCVBUF"); return -1; } /* Older kernels may no support extended ACK reporting */ setsockopt(rth->fd, SOL_NETLINK, NETLINK_EXT_ACK, &one, sizeof(one));
// 將本地地址初始化為0 && 初值 memset(&rth->local, 0, sizeof(rth->local)); rth->local.nl_family = AF_NETLINK; rth->local.nl_groups = subscriptions;
// 調用bind if (bind(rth->fd, (struct sockaddr *)&rth->local, sizeof(rth->local)) < 0) { perror("Cannot bind netlink socket"); return -1; } addr_len = sizeof(rth->local); if (getsockname(rth->fd, (struct sockaddr *)&rth->local, &addr_len) < 0) { perror("Cannot getsockname"); return -1; } if (addr_len != sizeof(rth->local)) { fprintf(stderr, "Wrong address length %d\n", addr_len); return -1; } if (rth->local.nl_family != AF_NETLINK) { fprintf(stderr, "Wrong address family %d\n", rth->local.nl_family); return -1; }
// 序列號為當前時間 rth->seq = time(NULL); return 0; }
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;
msg.msg_name = (void *)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1;
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 頭文件
void rtnl_close(struct rtnl_handle *rth);
void rtnl_close(struct rtnl_handle *rth) { if (rth->fd >= 0) { close(rth->fd); rth->fd = -1; } }
二、內核態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
ip route del 192.168.2.11
ip monitor route
這條命令從用戶態程序中發送netlink 消息(RTM_NEWROUTE)調用rtlinknet套接字來創建路由條目。
消息被內核態rtnetlink套接字的rtnetlink_rcv()方法接收。