iproute2 ipv6地址设置源码分析

iproute 作为网络接口的设置工具
具备我们大部分需要的功能。

以设置ipv6 地址为例来分析一下它的源码

它的实质其实是与内核建立一个socket通信,通过建立的fd进行网络接口的设置和信息读取。

简单来说,就四步:
建立与内核的连接-> 发送数据到内核 -> 从内核读取数据 -> 关闭连接

  1. 建立与内核的连接
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作用还待研究
  1. 发送数据到内核
先总结再细说
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: 就是掩码64128...
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: 消息标识
  */

  1. 从内核读取数据
    收数据的话,利用
 		iov.iov_base = buf;
 		iov.iov_len = sizeof(buf);
		status = recvmsg(rtnl->fd, &msg, 0);
for (h = (struct nlmsghdr *)buf; status >= sizeof(*h); ) 
具体的解析内容就没有分析,可以根据发送来反推。
  1. 关闭连接
    最后利用rth_close来关闭连接的通道。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值