Netlink与RTNetlink的简单使用
近期项目要求通过程序去配置下发网口路由,所以去了解了一下netlink机制。
一、netlink通信机制
netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。
目前,用户应用程序和内核通信的常用方式如下:
- 系统调用:常见的有 write、read、ioctl 等等,它需要应用程序主动向内核写入或读取数据,是一种同步的单工数据传输方式
- /proc文件系统:同 系统调用 类似
- 异步IO:可以通过编写驱动代码,使得内核在某些时刻主动向应用程序发送信号,但无法传输大量数据。
- netlink:是一种同步或者异步的数据传输方式,使用 netlink 在应用程序和内核建立起连接后即可进行双向数据发送。
1、netlink的优点
- 支持全双工、异步通信
- 用户空间使用标准的socket接口即可进行通信
- 支持多播
- 在内核端可用于进程上下文与中断上下文
2、netlink的应用
目前 linux内核 已经使用 netlink 实现了多种功能应用,关于功能使用后面会有部分讲解。下面罗列出几个常用的功能,如下:
- 获取或修改路由信息
- 监听TCP协议数据报文
- 防火墙
- netfilter子系统
- 内核事件向用户态通知
按照笔者的理解,netlink 很像网络编程,因为它有一定的格式要求,是通过发送 消息 来完成数据传输的。那么它就需要 消息格式 和 协议流程。说得简单一些,就是 netlink 是一种通信方式,是内核提供的一种功能。另一方面则是因为 netlink 用就是 socket套接字 那一套编程接口,所以十分像是网络协议。
二、netlink接口与数据接口
用户态应用使用标准的 socket API有sendto(),recvfrom(); sendmsg(), recvmsg()
netlink 可以直接使用 socket套接字 的标准接口直接进行代码编写,也就是说 socket 、 bind 等接口都可以直接使用。
而按照笔者理解,netlink 的编程与 UDP 编程类似,但在网络编程中我们是使用 IP地址 + 端口号 进行寻址的,而 netlink 则是通过 协议类型+进程ID 进行寻址的,其中 协议类型 会在调用 socket接口 的时候指定。
netlink 基本的编程步骤如下:
- 使用 socket 声明套接字
- 使用 bind 绑定 本地地址 到套接字
- 构造 消息
- 发送或接收 消息,而在 netlink 中有两套发送及接收数据的接口,分别是 sendto和recvfrom、sendmsg和recvmsg
1、netlink用户态常用的数据结构
struct sockaddr_nl 是netlink通信地址,跟普通socket struct sockaddr_in类似。
(1)、struct sockaddr_nl结构
struct sockaddr_nl {
__kernel_sa_family_t nl_family; //一般为AF_NETLINK
unsigned short nl_pad; //无需填充
__u32 nl_pid; //与内核通信的进程的进程ID,0 则代表地址为内核
__u32 nl_groups; //多播组号,netlink支持多播
};
(2)、struct nlmsghd 结构
/* struct nlmsghd 是netlink消息头*/
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 */
};
1)nlmsg_len:整个netlink消息的长度(包含消息头);
2)nlmsg_type:消息状态,内核在include/uapi/linux/netlink.h中定义了以下4种通用的消息类型,它们分别是:
#define NLMSG_NOOP 0x1 /* Nothing. */
#define NLMSG_ERROR 0x2 /* Error */
#define NLMSG_DONE 0x3 /* End of a dump */
#define NLMSG_OVERRUN 0x4 /* Data lost */
#define NLMSG_MIN_TYPE 0x10 /* < 0x10: reserved control messages */
/*NLMSG_NOOP:不执行任何动作,必须将该消息丢弃;
NLMSG_ERROR:消息发生错误;
NLMSG_DONE:标识分组消息的末尾;
NLMSG_OVERRUN:缓冲区溢出,表示某些消息已经丢失。
NLMSG_MIN_TYPEK:预留 */
3)nlmsg_flags:消息标记,它们用以表示消息的类型,如下:
/* Flags values */
#define NLM_F_REQUEST 0x01 /* It is request message. */
#define NLM_F_MULTI 0x02 /* Multipart message, terminated by NLMSG_DONE */
#define NLM_F_ACK 0x04 /* Reply with ack, with zero or error code */
#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 */
#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 */
4)nlmsg_seq:消息序列号,用以将消息排队,有些类似TCP协议中的序号(不完全一样),但是netlink的这个字段是可选的,不强制使用;
5)nlmsg_pid:发送端口的ID号,对于内核来说该值就是0,对于用户进程来说就是其socket所绑定的ID号。
(3)、struct msghdr 结构体
struct iovec { /* Scatter/gather array items */
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 data buffer 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: 消息标识
*/
参考:linux netlink通信机制 - zhangwju - 博客园 (cnblogs.com)
三、RTnetlink接口与数据接口
netlink 是 Linux 内核中用于内核与用户空间之间进行通信的一种机制。它允许用户空间程序与内核模块进行通信,用于配置网络参数、查询状态信息、接收通知等。Netlink 使用一种类似于套接字的 API 进行通信,提供了一种可靠的、高效的机制来传输大量的异步数据,比如路由表、网络接口信息、套接字选项等。Netlink 被广泛应用于 Linux 系统中的网络配置、路由管理、网络监控等方面。
而 RTNetlink 则是 Netlink 的一个子协议,用于处理与路由相关的信息。RTNetlink 可以通过 Netlink 机制来获取、配置和管理路由表、路由策略、邻居信息等路由相关的数据。它是 Linux 系统中管理网络路由的标准接口,通过 RTNetlink,用户空间程序可以实现动态地添加、删除、修改路由表中的路由条目,以及监听路由表的变化。
总的来说,Netlink 是 Linux 内核与用户空间进行通信的机制,而 RTNetlink 是 Netlink 的一个子协议,用于处理与路由相关的信息。通过 RTNetlink,用户空间程序可以与内核进行高效地路由管理。
1、RTNetlink用户态常用数据结构
(1)、struct sockaddr_nl结构
与Netlink相同。
(2)、struct nlmsghd 结构
其与Netlink主要的区别在于nlmsg_type有很多其它可选消息类型,在rtnetlink.h中包含了很多消息类型:
/* Types of messages */
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,
#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,
#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)
};
(3)、struct rtmsg结构体
struct rtmsg
{
unsigned char rtm_family; // 路由项所属协议族,如AF_INET、AF_INET6;
// 路由项目的地址掩码长度,特别注意是掩码长度,和属性中的RTA_DST配合表示路由项的目的网络
unsigned char rtm_dst_len;
unsigned char rtm_src_len; // 源地址掩码长度
unsigned char rtm_tos; // 路由项的服务质量,对应IP首部的服务字段
// 路由项所述路由表ID,由于只有1个字节,所以取值最大为255,因此用户态能够配置256个路由表,
// 但是内核实际上并不是只能保存256个路由表,内核对此是没有限制的;
unsigned char rtm_table; /* 路由表选取 */
unsigned char rtm_protocol; // 路由协议,含义见下方
unsigned char rtm_scope; // 路由项的作用域,含义见下方
unsigned char rtm_type; // 路由项类型,含义见下方
unsigned rtm_flags;// 路由标记
};
1)rtm_table:路由表ID,内核有如下规划,从表ID的定义可以看出,当支持策略路由时(多路由表),用户态程序能够新建的表的ID范围为1~252。
RT_TABLE_UNSPEC 0 //未知,当操作该路由表时,内核默认会操作main表
RT_TABLE_DEFAULT 253 //default表,用于策略路由的默认路由表
RT_TABLE_MAIN 254 //main表,主路由表,不指定具体要操作的表时就是main表
RT_TABLE_LOCAL 255 //local表,该表维护了本机IP地址相关的路由,由内核自己维护,用户态无法操作
2)rtm_protocol:路由协议,表示的是路由项是如何产生的,可取的值如下,其中常见的是RTPROT_KERNEL和RTPROT_STAIC。
RTPROT_UNSPEC //未知
RTPROT_REDIRECT //该路由项是通过ICMP重定向消息添加的
RTPROT_KERNEL //该路由项是由内核自己添加的,local表的路由大多数属于这一类
RTPROT_BOOTUP //该路由项是在启动时通过boot协议添加的
RTPROT_STAIC //该路由项是由管理员静态添加的,用户态配置的路由属于这一类
3)rtm_scope:路由项的作用域,**表示的是本机距离该路由项表示的目的地址的距离。**如下,值(0, 200)之间的数字是可以由管理员自己配置的。值越大,距离本机越近。所以叫它作用域其实是不利于理解的。
RT_SCOPE_UNIVERSE 0 //表示目的地和本机距离超过了1跳
RT_SCOPE_SITE 200
RT_SCOPE_LINK 253 //表示目的地位于和本机直接相连的网络,如同一局域网内的其它主机地址
RT_SCOPE_HOST 254 //表示目的地是本地地址
RT_SCOPE_NOWHERE 255 //表示目的地不存在,这是个保留值
4)rtm_type:路由表项的类型,它一定程度上决定了数据包匹配该路由表项后应该采取的动作。
enum {
RTN_UNSPEC,
RTN_UNICAST, /* Gateway or direct route */
RTN_LOCAL, /* Accept locally */
RTN_BROADCAST, /* Accept locally as broadcast,
send as broadcast */
RTN_ANYCAST, /* Accept locally as broadcast,
but send as unicast */
RTN_MULTICAST, /* Multicast route */
RTN_BLACKHOLE, /* Drop */
RTN_UNREACHABLE, /* Destination is unreachable */
RTN_PROHIBIT, /* Administratively prohibited */
RTN_THROW, /* Not in this table */
RTN_NAT, /* Translate this address */
RTN_XRESOLVE, /* Use external resolver */
__RTN_MAX
};
RTN_UNSPEC //一个未初始化的值
RTN_LOCAL //该路由项的目的地址是主机本地地址
RTN_UNICAST //该路由是一条到直连或者非直连目的地址的单播路由,添加路由时如果不指定路由类型,那么默认取该值
RTN_MULTICAST //该该路由项的目的地址是一个多播地址
RTN_BROADCAST //该该路由项的目的地址是一个广播地址
RTN_ANYCAST //IPv4中不使用
RTN_UNREACHABLE //路由结果是不可达,会给源地址回复主机不可达ICMP报文
RTN_BLACKHOLE //路由结果是不可达,会悄悄的丢弃数据包,不做任何回应
RTN_PROHIBIT //路由结果为不可达,会给源地址回复"communication administratively prohibited"类型的ICMP报文
RTN_THROW //路由结果为不可达,会给源地址回复网络不可达ICMP报文
(4)、struct rtattr结构体
struct rtattr {
unsigned short rta_len;
unsigned short rta_type;
/* Data follows */
};
所包含的结构体还有很多,具体结构体的使用根据nlmsg_type消息类型进行选择的,但基本都是以rtattr结构体结尾,所以RTNetlink的基本消息构成为:
nlmsghdr + option_msg + rtattr
-
nlmsghdr 选择对应的消息类型;
-
option_msg就是根据nlmsghdr 中nlmsg_type消息类型来确定使用的消息头;
-
rtattr主要是具体的消息内容。
2、RTNetlink消息类型
(1)、RTM_NEWLINK, RTM_DELLINK, RTM_GETLINK
创建或者删除一个特定的网络接口,或者从一个特定的网络接口上获得信息。这些消息含有一个ifinfomsg类型的结构,紧跟在后面的是一系列的rtattr结构。
/*****************************************************************
* Link layer specific messages.
****/
/* struct ifinfomsg
* passes link level specific information, not dependent
* on network protocol.
*/
struct ifinfomsg {
unsigned char ifi_family; /* AF_UNSPEC */
unsigned short ifi_type; /* Device type */
int ifi_index; /* Interface index */
unsigned int ifi_flags; /* Device flags */
unsigned int ifi_change; /* change mask */
};
/*
* ifi_family: 接口地址类型
* ifi_type: 设备类型
* ifi_index: 是结构唯一的索引
* ifi_flags: 设备标志,可以看netdevice 结构
* ifi_change: 保留值,通常设置为0xFFFFFFFF
*/
/*
ifi_type代表硬件设备的类型:
ARPHRD_ETHER 10M以太网
ARPHRD_PPP PPP拨号
ARPHRDLOOPBACK 环路设备
ifi_flags包含设备的一些标志:
IFF_UP 接口正在运行
IFF_BROADCAST 有效的广播地址集
IFF_DEBUG 内部调试标志
IFF_LOOPBACK 这是自环接口
IFF_POINTOPOINT 这是点到点的链路设备
IFF_RUNNING 资源已分配
IFF_NOARP 无arp协议,没有设置第二层目的地址
IFF_PROMISC 接口为杂凑(promiscuous)模式
IFF_NOTRAILERS 避免使用trailer
IFF_ALLMULTI 接收所有组播(multicast)报文
IFF_MASTER 主负载平衡群(bundle)
IFF_SLAVE 从负载平衡群(bundle)
IFF_MULTICAST 支持组播(multicast)
IFF_PORTSEL 可以通过ifmap选择介质(media)类型
IFF_AUTOMEDIA 自动选择介质
IFF_DYNAMIC 接口关闭时丢弃地址
Routing attributes(rtattr部分属性,rta_type)
rta_type value type description
──────────────────────────────────────────────────────────
IFLA_UNSPEC - 未说明,未指定的数据
IFLA_ADDRESS hardware address L2硬件地址
IFLA_BROADCAST hardware address L2广播地址.
IFLA_IFNAME asciiz string char型设备名.
IFLA_MTU unsigned int MTU of the device.
IFLA_LINK int Link type.
IFLA_QDISC asciiz string Queueing discipline.
IFLA_STATS see below struct rtnl_link_stats的设备信息
//用来获取ifinfomsg后面的rtattr结构
#define IFLA_RTA(r) ((struct rtattr*)(((char*)(r)) + NLMSG_ALIGN(sizeof(struct ifinfomsg))))
*/
(2)、RTM_NEWADDR, RTM_DELADDR, RTM_GETADDR
添加、删除或者接收一个和接口相关的IP地址的信息。这些消息含有一个ifaddrmsg类型的结构,紧跟在后面的是一系列的rtattr结构。
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: 接口索引与接口地址关联
*/
/*
Attributes (rtattr部分属性,rta_type)
rta_type value type description
─────────────────────────────────────────────────────────────
IFA_UNSPEC - unspecified.
IFA_ADDRESS raw protocol address 接口地址 interface address
IFA_LOCAL raw protocol address 本地地址 local address
IFA_LABEL asciiz string 接口名称 name of the interface
IFA_BROADCAST raw protocol address 广播 broadcast address.
IFA_ANYCAST raw protocol address anycast address
IFA_CACHEINFO struct ifa_cacheinfo Address information.
*/
(3)、RTM_NEWROUTE, RTM_DELROUTE, RTM_GETROUTE
创建,删除或者获取网络设备的路由信息;这些消息包含一个rtmsg结构,其后跟数目可选的rtattr结构。
对于RTM_GETROUTE,设置rtm_dst_len以及rtm_src_len为0表示获取指定路由表的所有条目(entries)。
其它的成员,除了rtm_table、rtm_protocol,0是通配符。
struct rtmsg结构体参考上面内容。
对应的rta_type如下:
rta_type value type description
──────────────────────────────────────────────────────────────
RTA_UNSPEC - ignored.
RTA_DST protocol address Route destination address. /* 目的 */
RTA_SRC protocol address Route source address. /* 源地址 */
RTA_IIF int Input interface index. /* 输入设备 index */
RTA_OIF int Output interface index.
RTA_GATEWAY protocol address The gateway of the route /* 网关 */
RTA_PRIORITY int Priority of route. /* 优先级 */
RTA_PREFSRC
RTA_METRICS int Route metric /* 路由metric 值*/
RTA_MULTIPATH
RTA_PROTOINFO
RTA_FLOW
RTA_CACHEINFO
RTA_SESSION, /* no longer used */
RTA_MP_ALGO, /* no longer used */
RTA_TABLE,
RTA_MARK,
RTA_MFC_STATS,
RTA_VIA,
RTA_NEWDST,
RTA_PREF,
RTA_ENCAP_TYPE,
RTA_ENCAP,
RTA_EXPIRES,
RTA_PAD,
RTA_UID,
RTA_TTL_PROPAGATE,
RTA_IP_PROTO,
RTA_SPORT,
RTA_DPORT,
RTA_NH_ID,
3、RTNetlink的具体使用实例
/*********************************************************
* Filename: nl_netinfo.c
* Author: zhangwj
* Date:
* Descripte:
* Email:
* Warnning:
**********************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <linux/route.h>
#include <errno.h>
#define EPOLL_LISTEN_MAX_CNT 256
#define EPOLL_LISTEN_TIMEOUT 500
int g_nlfd = -1;
int g_epollfd = -1;
void parse_rtattr(struct rtattr **tb, int max, struct rtattr *attr, int len)
{
for (; RTA_OK(attr, len); attr = RTA_NEXT(attr, len)) {
if (attr->rta_type <= max) {
tb[attr->rta_type] = attr;
}
}
}
void nl_netroute_handle(struct nlmsghdr *nlh)
{
int len;
struct rtattr *tb[RTA_MAX + 1];
struct rtmsg *rt;
char tmp[256];
bzero(tb, sizeof(tb));
rt = NLMSG_DATA(nlh);
len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*rt));
parse_rtattr(tb, RTA_MAX, RTM_RTA(rt), len);
printf("%s: ", (nlh->nlmsg_type==RTM_NEWROUTE)?"NEWROUT":"DELROUT");
if (tb[RTA_DST] != NULL) {
inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_DST]), tmp, sizeof(tmp));
printf("DST: %s ", tmp);
}
if (tb[RTA_SRC] != NULL) {
inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_SRC]), tmp, sizeof(tmp));
printf("SRC: %s ", tmp);
}
if (tb[RTA_GATEWAY] != NULL) {
inet_ntop(rt->rtm_family, RTA_DATA(tb[RTA_GATEWAY]), tmp, sizeof(tmp));
printf("GATEWAY: %s ", tmp);
}
printf("\n");
}
void nl_netifinfo_handle(struct nlmsghdr *nlh)
{
int len;
struct rtattr *tb[IFLA_MAX + 1];
struct ifinfomsg *ifinfo;
bzero(tb, sizeof(tb));
ifinfo = NLMSG_DATA(nlh);
len = nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifinfo));
parse_rtattr(tb, IFLA_MAX, IFLA_RTA (ifinfo), len);
printf("%s: %s ", (nlh->nlmsg_type==RTM_NEWLINK) ? "NEWLINK" : "DELLINK", (ifinfo->ifi_flags & IFF_UP) ? "up" : "down");
if(tb[IFLA_IFNAME]) {
printf("%s", RTA_DATA(tb[IFLA_IFNAME]));
}
printf("\n");
}
void nl_netifaddr_handle(struct nlmsghdr *nlh)
{
int len;
struct rtattr *tb[IFA_MAX + 1];
struct ifaddrmsg *ifaddr;
char tmp[256];
bzero(tb, sizeof(tb));
ifaddr = NLMSG_DATA(nlh);
len =nlh->nlmsg_len - NLMSG_SPACE(sizeof(*ifaddr));
parse_rtattr(tb, IFA_MAX, IFA_RTA (ifaddr), len);
printf("%s ", (nlh->nlmsg_type == RTM_NEWADDR)? "NEWADDR":"DELADDR");
if (tb[IFA_LABEL] != NULL) {
printf("%s ", RTA_DATA(tb[IFA_LABEL]));
}
if (tb[IFA_ADDRESS] != NULL) {
inet_ntop(ifaddr->ifa_family, RTA_DATA(tb[IFA_ADDRESS]), tmp, sizeof(tmp));
printf("%s ", tmp);
}
printf("\n");
}
void nl_netlink_handle(int fd)
{
int r_size;
socklen_t len = 0;
char buff[2048] = {0};
struct sockaddr_nl addr;
struct nlmsghdr *nlh;
while(1)
{
len = sizeof(addr);
r_size = recvfrom(fd, (void *)buff, sizeof(buff), 0, (struct sockaddr *)&addr, &len);
nlh = (struct nlmsghdr *)buff;
for(; NLMSG_OK(nlh, r_size); nlh = NLMSG_NEXT(nlh, r_size))
{
switch(nlh->nlmsg_type) {
case NLMSG_DONE:
case NLMSG_ERROR:
break;
case RTM_NEWLINK: /* */
case RTM_DELLINK:
nl_netifinfo_handle(nlh);
break;
case RTM_NEWADDR:
case RTM_DELADDR: /* */
nl_netifaddr_handle(nlh);
break;
case RTM_NEWROUTE:
case RTM_DELROUTE: /* */
nl_netroute_handle(nlh);
break;
default:
break;
}
}
}
}
void epoll_event_handle(void)
{
int i = 0;
int fd_cnt = 0;
int sfd;
struct epoll_event events[EPOLL_LISTEN_MAX_CNT];
memset(events, 0, sizeof(events));
while(1)
{
/* wait epoll event */
fd_cnt = epoll_wait(g_epollfd, events, EPOLL_LISTEN_MAX_CNT, EPOLL_LISTEN_TIMEOUT);
for(i = 0; i < fd_cnt; i++)
{
sfd = events[i].data.fd;
if(events[i].events & EPOLLIN)
{
if (sfd == g_nlfd)
{
nl_netlink_handle(sfd);
}
}
}
}
}
int epoll_add_fd(int fd)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = EPOLLIN | EPOLLET;
if (epoll_ctl(g_epollfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
perror("epoll add fd error");
return -1;
}
printf("epoll add fd[%d] success\n", fd);
return 0;
}
int init_epoll_fd()
{
int epollfd = -1;
epollfd = epoll_create(EPOLL_LISTEN_MAX_CNT);
if (epollfd < 0) {
perror("epoll create failure!...");
return -1;
}
g_epollfd = epollfd;
printf("epool create fd [%d] success\n", epollfd);
return g_epollfd;
}
int init_nl_sockfd()
{
int ret = 0;
int nlfd = -1;
struct sockaddr_nl sa;
/* open a netlink fd */
nlfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (nlfd < 0) {
perror("create netlink socket failure");
return -1;
}
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_groups = RTMGRP_LINK | RTMGRP_IPV4_IFADDR | RTMGRP_IPV4_ROUTE | RTMGRP_IPV6_IFADDR | RTMGRP_IPV6_ROUTE;
/* bind netlink */
ret = bind(nlfd, (struct sockaddr *)&sa, sizeof(sa));
if (ret < 0) {
perror("bind nlfd error");
close(nlfd);
return -1;
}
if (epoll_add_fd(nlfd)) {
close(nlfd);
return -1;
}
g_nlfd = nlfd;
printf("netlink create fd [%d] success\n", nlfd);
return nlfd;
}
int main(int argc, char **argv)
{
if (init_epoll_fd() < 0) { /* 创建epoll 监听fd */
return -1;
}
if (init_nl_sockfd() < 0) { /* 创建netlink */
return -1;
}
/* 循环接收处理 */
epoll_event_handle();
return 0;
}
四、RTNetlink添加路由的实例
vnet_netlink_add_ip4_route_vrf (void *dst, u8 dst_len, void *gw, u32 o_if, u32 vrf_id)
{
vnet_netlink_msg_t m;
struct rtmsg rtm = { 0 };
u8 dflt[4] = { 0 };
clib_error_t *err = 0;
rtm.rtm_family = AF_INET;
rtm.rtm_table = RT_TABLE_MAIN;
rtm.rtm_type = RTN_UNICAST;
rtm.rtm_dst_len = dst_len;
//初始化nlmsghd,填充消息类型为RTM_NEWROUTE
vnet_netlink_msg_init (&m, RTM_NEWROUTE,
NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE,
&rtm, sizeof (struct rtmsg));
if (gw != NULL)
{
//add包含网关信息的rta
vnet_netlink_msg_add_rtattr (&m, RTA_GATEWAY, gw, 4);
}
if (o_if != ~0)
{
//add包含出口设备的网口索引
vnet_netlink_msg_add_rtattr (&m, RTA_OIF, &o_if, 4);
}
if (vrf_id != ~0)
{
//add包含路由表的rta
vnet_netlink_msg_add_rtattr (&m, RTA_TABLE, &vrf_id, 4);
}
//add包含路由地址的rta
vnet_netlink_msg_add_rtattr (&m, RTA_DST, dst ? dst : dflt, 4);
err = vnet_netlink_msg_send (&m, NULL);
if (err)
err = clib_error_return (0, "add ip4 route %U", format_clib_error, err);
return err;
}
通过netlink下发其它网口配置可以参考iproute2源码。