iproute 作为网络接口的设置工具
具备我们大部分需要的功能。
以设置ipv6 地址为例来分析一下它的源码
它的实质其实是与内核建立一个socket通信,通过建立的fd进行网络接口的设置和信息读取。
简单来说,就四步:
建立与内核的连接-> 发送数据到内核 -> 从内核读取数据 -> 关闭连接
- 建立与内核的连接
int rtnl_open(struct rtnl_handle *rth, unsigned int subscriptions)
{
return rtnl_open_byproto(rth, subscriptions, NETLINK_ROUTE);
}
其中rth结构体如下
struct rtnl_handle {
int fd; // fd是socket 打开的值
struct sockaddr_nl local; // local 是本地地址
struct sockaddr_nl peer; // peer 是邻居地址
__u32 seq; // seq 是32位序列号
__u32 dump; // dump 是32位
int proto; // proto 是协议号
FILE *dump_fp; // dump_fp 是文件名
int flags; // flags 是 标志字段
};
seq:
TCP会话的每一端都包含一个32位(bit)的序列号,该序列号被用来跟踪该端发送的数据量。每一个包中都包含序列号,在接收端则通过确认号用来通知发送端数据成功接收
proto:
具体定义:
#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 /* Unused number, formerly ip_queue */
#define NETLINK_SOCK_DIAG 4 /* 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 NETLINK_SMC 22 /* SMC monitoring */
#define NETLINK_INET_DIAG NETLINK_SOCK_DIAG
指定要和内核中的哪个子系统进行交互,目前支持:
NETLINK_ROUTE 与路由信息相关,包括查询、设置和删除路由表中的条目等。待会儿我们将以这类family举个实际的例子;
NETLINK_FIREWALL 接收由IPv4防火墙代码发送的包;
NETLINK_ARPD 可以在用户空间进行arp缓存的管理;
NETLINK_ROUTE6 在用户空间发送和接收路由表信息更新;
还有几种虽然没有实现,但已经有了定义,为以后扩展做好了准备。
dump 和dump_fd作用还待研究
- 发送数据到内核
先总结再细说
struct {
struct nlmsghdr n;
struct ifaddrmsg ifa;
char buf[256];
} req
req的基地址(req第一个数据存放的就是struct nlmsghdr n;传n的地址相当于传req的基地址)传给 iov的iov_base
struct iovec iov = {
.iov_base = n,
.iov_len = n->nlmsg_len
};
再把iov这个结构体地址传给msg_iov = &iov。
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
再发送数据给到内核
具体分析如下:
static int ipaddr_modify(int cmd, int flags, int argc, char **argv)
struct {
struct nlmsghdr n;
struct ifaddrmsg ifa;
char buf[256];
} req = {
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
.n.nlmsg_flags = NLM_F_REQUEST | flags,
.n.nlmsg_type = cmd,
.ifa.ifa_family = preferred_family,
};
在ipaddr_modify初始化req结构体
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 */
};
字段 nlmsg_len 指定消息的总长度,包括紧跟该结构的数据部分长度以及该结构的大小
#define NLMSG_ALIGNTO 4U
#define NLMSG_ALIGN(len) ( ((len)+NLMSG_ALIGNTO-1) & ~(NLMSG_ALIGNTO-1) )
这样返回值在len大于零时,都是以4的倍数对齐(1-4=4;5-8=8;如此类推)
struct ifaddrmsg {
unsigned char ifa_family; /* Address type */
unsigned char ifa_prefixlen; /* Prefixlength of address */
unsigned char ifa_flags; /* Address flags */
unsigned char ifa_scope; /* Address scope */
int ifa_index; /* Interface index */
};
ifa_family: 地址类型(通常为AF_INET or AF_INET6))
ifa_prefixlen: 地址的地址掩码长度,如果改地址定义在这个family
ifa_flags:
ifa_scope: 地址的作用域
ifa_index: 接口索引与接口地址关联
ifa_prefixlen: 就是掩码64,128...
ifa_index: 利用if_nametoindex(name);函数把设备名转换为接口index,这个
值对应的网络设备是固定不变的。
.n.nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)),
初始化是nlmsghdr和ifaddrmsg两个结构体长度24。
具体的数据内容是通过addattr_l来添加的
int addattr_l(struct nlmsghdr *n, int maxlen, int type, const void *data,
int alen)
{
int len = RTA_LENGTH(alen);
struct rtattr *rta;
if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len) > maxlen) {
fprintf(stderr,
"addattr_l ERROR: message exceeded bound of %d\n",
maxlen);
return -1;
}
//printf("%s[%d] NLMSG_ALIGN(n->nlmsg_len)[%d]\n",__func__,__LINE__,NLMSG_ALIGN(n->nlmsg_len));
rta = NLMSG_TAIL(n);
rta->rta_type = type;
rta->rta_len = len;
if (alen)
memcpy(RTA_DATA(rta), data, alen);
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
return 0;
}
跳过nlmsg_len,把数据考到req.buf中。
#define NLMSG_TAIL(nmsg) \
((struct rtattr *) (((void *) (nmsg)) + NLMSG_ALIGN((nmsg)->nlmsg_len)))
memcpy(RTA_DATA(rta), data, alen);
再更新数据长度
n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(len);
这样下次还有其他数据要加入时,就会在上一个数据末尾加入。
所以想看看自己发的内容对不对,可以查看buf里面的内容。
而这个buf分为rta和data;
struct rtattr {
unsigned short rta_len;
unsigned short rta_type;
};
前面存的时rta结构体,接着存的是实际数据
像设置ipv6地址的话就是存一个inet_prefix *addr结构体
inet_pton(AF_INET6, name, addr->data)
将点分文本的IP地址转换为“二进制网络字节序”的IP地址
最后利用rtnl_talk -> __rtnl_talk 把数据发送到内核
static int __rtnl_talk(struct rtnl_handle *rtnl, struct nlmsghdr *n,
struct nlmsghdr *answer, size_t maxlen,
bool show_rtnl_err, nl_ext_ack_fn_t errfn)
{
int status;
unsigned int seq;
struct nlmsghdr *h;
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
struct iovec iov = {
.iov_base = n,
.iov_len = n->nlmsg_len
};
struct msghdr msg = {
.msg_name = &nladdr,
.msg_namelen = sizeof(nladdr),
.msg_iov = &iov,
.msg_iovlen = 1,
};
...
n->nlmsg_seq = seq = ++rtnl->seq;
status = sendmsg(rtnl->fd, &msg, 0);
if (status < 0) {
perror("Cannot talk to rtnetlink");
return -1;
}
发送struct msghdr msg这个结构体到内核中去
struct iovec { /* Scatter/gather arrayitems */
void *iov_base; /*Starting address */
size_t iov_len; /* Number of bytes to transfer*/
};
/* iov_base: iov_base指向数据包缓冲区,即参数buff,iov_len是buff的长度。msghdr中允许一次传递多个buff,
以数组的形式组织在 msg_iov中,msg_iovlen就记录数组的长度(即有多少个buff)
*/
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary databuffer len */
int msg_flags; /* flags on received message */
};
/* msg_name:数据的目的地址,网络包指向sockaddr_in, netlink则指向sockaddr_nl;
msg_namelen: msg_name 所代表的地址长度
msg_iov: 指向的是缓冲区数组
msg_iovlen: 缓冲区数组长度
msg_control: 辅助数据,控制信息(发送任何的控制信息)
msg_controllen: 辅助信息长度
msg_flags: 消息标识
*/
- 从内核读取数据
收数据的话,利用
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
status = recvmsg(rtnl->fd, &msg, 0);
for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); )
具体的解析内容就没有分析,可以根据发送来反推。
- 关闭连接
最后利用rth_close来关闭连接的通道。