struct sockaddr_nl 结构体 由来、含义以及使用——获取Linux路由表

本文详细介绍Linux下AF_NETLINK套接字的应用,特别是路由套接字(Routing Socket)的创建与使用方法,包括生成套接字、绑定sockaddr结构、发送与接收数据的过程,并以读取IPv4路由表信息为例进行说明。
Linux 用户态与内核态的交互
  在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通 读,也在最新版本中改变为netlink,无疑,它将是Linux用户态与内核态交流的主要方法之一。它的通信依据是一个对应于进程的标识,一般定为该进 程的 ID。当通信的一端处于中断过程时,该标识为 0。当使用 netlink 套接字进行通信,通信的双方都是用户态进程,则使用方法类似于消息队列。但通信双方有一端是中断过程,使用方法则不同。netlink 套接字的最大特点是对中断过程的支持,它在内核空间接收用户空间数据时不再需要用户自行启动一个内核线程,而是通过另一个软中断调用用户事先指定的接收函 数。
《UNIX Network Programming Volume 1 - 3rd Edition》第18章
讲到BSD UNIX系统中routing socket的应用,这种套接字是按下面方式生成的:
rt_socket = socket(AF_ROUTE, SOCK_RAW, 0);
然 后就可以用它跟内核交互,进行网络环境管理的操作,如读取/设置/删除路由表信息,更改网关等等,但书中所列代码只在4.3BSD及以后版本的原始 UNIX系统下可用,Linux虽然实现了AF_ROUTE族套接字,但用法却完全不同。由于网上这方面知识的资料想对匮乏,现对Linux下 routing socket的使用做一介绍。
由于我现在在Magic Linux1.0下工作,所以以下的讲解全部基于2.4.10内核。Linux从v2.2开始引入这一机制,因此可以肯定从v2.2到v2.4的内核都是适用的,更新的v2.6我没有试过。

Linux下虽然也有AF_ROUTE族套接字,但是这个定义只是个别名,请看
/usr/include/linux/socket.h, line 145:
#define AF_ROUTE AF_NETLINK /* Alias to emulate 4.4BSD */
可 见在Linux内核当中真正实现routing socket的是AF_NETLINK族套接字。AF_NETLINK族套接字像一个连接用户空间和内核的双工管道,通过它,用户进程可以修改内核运行参 数、读取和设置路由信息、控制特定网卡的up/down状态等等,可以说是一个管理网络资源的绝佳途径。

1 生成所需套接字,并绑定一个sockaddr结构

先来看如何生成一个AF_NETLINK族套接字:
sockfd = socket(AF_NETLINK, socket_type, netlink_faimly);
这里socket_type可选SOCK_DGRAM或SOCK_RAW;因为AF_NETLINK族是面向数据报的套接字,所以不能使用SOCK_STREAM。
netlink_family指定要和内核中的哪个子系统进行交互,目前支持:
NETLINK_ROUTE 与路由信息相关,包括查询、设置和删除路由表中的条目等。待会儿我们将以这类family举个实际的例子;
NETLINK_FIREWALL 接收由IPv4防火墙代码发送的包;
NETLINK_ARPD 可以在用户空间进行arp缓存的管理;
NETLINK_ROUTE6 在用户空间发送和接收路由表信息更新;
还有几种虽然没有实现,但已经有了定义,为以后扩展做好了准备。

接下来要给该套接字绑定一个sockaddr结构,实际上是一个sockaddr_nl结构:
struct sockaddr_nl {
sa_family_t nl_family; /*AF_NETLINK*/
unsigned short nl_pad; /* 0 */
pid_t nl_pid; /* 进程pid */
u_32 nl_groups; /* 多播组掩码*/
}nl;
这个结构一般按照注释填好就可以了,nl_groups我也不知道怎么用,一般填零了,表示没有多播。绑定:
bind(sockfd, (struct sockaddr*) &nl, sizeof(nl));

2 填充所需数据结构,并通过sendmsg()/send()等函数写到套接字里去

到 此为止,与内核通信的准备工作就完成了,下面要做的工作是,选取适当的数据结构进行填充,并作为sendmsg()的参数发送出去,并recv()收到的 消息。这个数据结构就是nlmsghdr,它只是一个信息头,后面可以接任意长的数据,这些数据实际上又是针对某一需求所采用的特定数据结构。先来看 nlmsghdr:
struct nlmsghdr {
_u32 nlmsg_len; /* Length of msg including header */
_u32 nlmsg_type; /* 操作命令 */
_u16 nlmsg_flags; /* various flags */
_u32 nlmsg_seq; /* Sequence number */
_u32 nlmsg_pid; /* 进程PID */
};
/* 紧跟着是实际要发送的数据,长度可以任意 */

nlmsg_type 决定这次要执行的操作,如查询当前路由表信息,所使用的就是RTM_GETROUTE。标准nlmsg_type包括:NLMSG_NOOP, NLMSG_DONE, NLMSG_ERROR等。根据采用的nlmsg_type不同,还要选取不同的数据结构来填充到nlmsghdr后面:
操作 数据结构
RTM_NEWLINK ifinfomsg
RTM_DELLINK
RTM_GETLINK
RTM_NEWADDR ifaddrmsg
RTM_DELADDR
RTM_GETADDR
RTM_NEWROUTE rtmsg
RTM_DELROUTE
RTM_GETROUTE
RTM_NEWNEIGH ndmsg/nda_chcheinfo
RTM_DELNEIGH
RTM_GETNEIGH
RTM_NEWRULE rtmsg
RTM_DELRULE
RTM_GETRULE
RTM_NEWQDISC tcmsg
RTM_DELQDISC
RTM_GETQDISC
RTM_NEWTCLASS tcmsg
RTM_DELTCLASS
RTM_GETTCLASS
RTM_NEWTFILTER tcmsg
RTM_DELTFILTER
RTM_GETTFILTER
由于情形众多,我从现在开始将用一个特定的例子来说明问题。我们的目的是从内核读取IPV4路由表信息。从上面表看,nlmsg_type一定使用RTM_xxxROUTE操作,对应的数据结构是rtmsg。既然是读取,那么应该是RTM_GETROUTE了。
struct rtmsg {
unsigned char rtm_family; /* 路由表地址族 */
unsigned char rtm_dst_len; /* 目的长度 */
unsigned char rtm_src_len; /* 源长度 */ (2.4.10头文件的注释标反了?)
unsigned char rtm_tos; /* TOS */

unsigned char rtm_table; /* 路由表选取 */
unsigned char rtm_protocol; /* 路由协议 */
unsigned char rtm_scope;
unsigned char rtm_type;

unsigned int rtm_flags;
};
对于RTM_GETROUTE操作来说,我们只需指定两个成员:rtm_family:AF_INET, rtm_table: RT_TABLE_MAIN。其他成员都初始化为0即可。将这个结构体跟nlmsghdr结合起来,得到我们自己的新结构体:
struct {
struct nlmsghdr nl;
struct rtmsg rt;
}req;
填充好rt结构之后,还要调整nl结构相应成员的值。Linux定义了多个宏来处理nlmsghdr成员的值,我们这里用到的是NLMSG_LENGTH(size_t len);
req.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
这将计算nlmsghdr长度与rtmsg长度的和(其中包括了将rtmsg进行4字节边界对齐的调整),并存储到nlmsghdr的nlmsg_len成员中。接下来要做的就是将这个新结构体req放到sendmsg()函数的msghdr.iov处,并调用函数。
sendmsg(sockfd, &msg, 0);

3 接收数据,并进行分析

接下来的操作是recv()操作,从该套接字读取内核返回的数据,并进行分析处理。
recv(sockfd, p, sizeof(buf) - nll, 0);
其中p是指向一个缓冲区buf的指针,nll是已接收到的nlmsghdr数据的长度。
由于内核返回信息是一个字节流,需要调用者检查消息结尾。这是通过检查返回的nlmsghdr的nlmsg_type是否等于NLMSG_DONE来完成的。返回的数据格式如下:
-----------------------------------------------------------
| nlmsghdr+route entry | nlmsghdr+route entry | ......... 
-----------------------------------------------------------
| 解出route entry
V
-----------------------------------------------------------
| dst_addr | gateway | Output interface| ...............
-----------------------------------------------------------
可 以看出,返回消息由多个(nlmsghdr + route entry)组成,当某个nlmsghdr的nlmsg_type == NLMSG_DONE时就表示信息输出已经完毕。而每一个route entry由多个rtattr结构体组成,每个结构体表示该路由项的某个属性,如目的地址,网关等等。根据这个示意图我们就能够轻松解析需要的数据了。
非常非常详细的给我这段代码的注释,详细到每一行,每一个参数,都要对他们进行拓展的知识,我是初学者,但我要非常了解这段代码的流程 原理 /**添加------------------------------------------------- * system_if_add_gateway_route - 添加设备指定的gateway */ /** * system_if_add_gateway_route_ex - 添加网关路由并指定优先级 * @dev: 设备结构体指针 * @gateway: 网关IP地址(网络字节序) * @metric: 路由优先级值(越小优先级越高) * 返回:成功返回OK,失败返回ERROR */ S32 system_if_add_gateway_route_ex(struct device *dev, U32 gateway, int metric) { struct rtentry rt; struct sockaddr_in sin; int ret = 0; char ifname[IFNAMSIZ] = {0}; if (!valid_ip_addr(gateway)) { NIFC_ERROR("Invalid gateway address"); return ERROR; } // 获取接口名称(关键) if (dev && dev->ifname) { strncpy(ifname, dev->ifname, IFNAMSIZ-1); } else { NIFC_ERROR("Device name missing"); return ERROR; } memset(&rt, 0, sizeof(rt)); memset(&sin, 0, sizeof(sin)); // 设置目标网络(默认路由) sin.sin_family = AF_INET; sin.sin_addr.s_addr = INADDR_ANY; memcpy(&rt.rt_dst, &sin, sizeof(struct sockaddr_in)); // 设置网关 sin.sin_addr.s_addr = gateway; memcpy(&rt.rt_gateway, &sin, sizeof(struct sockaddr_in)); // 设置子网掩码 sin.sin_addr.s_addr = INADDR_ANY; memcpy(&rt.rt_genmask, &sin, sizeof(struct sockaddr_in)); // 设置关键参数 rt.rt_flags = RTF_GATEWAY | RTF_UP; // 网关路由+激活状态 rt.rt_metric = metric; // 优先级设置 rt.rt_dev = ifname; // 绑定到指定接口 // 创建路由 if ((ret = ioctl(sock_ioctl, SIOCADDRT, &rt)) < 0) { NIFC_ERROR("Add route failed: %s (metric=%d)", strerror(errno), metric); return ERROR; } NIFC_DEBUG("Added gateway "IP_PRINT_FMT" metric=%d on %s", IP_PRINT(gateway), metric, ifname); return OK; }
最新发布
11-08
详细添加注释 /** * system_if_add_gateway_route - 添加设备指定的gateway */ S32 system_if_add_gateway_route(struct device *dev, U32 gateway) { struct rtentry rt; struct sockaddr_in sin; int ret = 0; if (!valid_ip_addr(gateway)) { NIFC_ERROR("Parameter wrong."); return ERROR; } memset(&rt, 0, sizeof(struct rtentry)); memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = 0; sin.sin_addr.s_addr = gateway; memcpy (&rt.rt_gateway, &sin, sizeof(struct sockaddr_in)); sin.sin_addr.s_addr = INADDR_ANY; /* 默认网关的dst是0.0.0.0 */ memcpy (&rt.rt_dst, &sin, sizeof(struct sockaddr_in)); rt.rt_flags = RTF_GATEWAY; /* 添加路由类型为GATEWAY */ if ((ret = ioctl(sock_ioctl, SIOCADDRT, &rt)) < 0) { NIFC_ERROR("Ioctl SIOCADDRT error, ret code is %d ,error info:%s ", ret, strerror(errno)); return ERROR; } NIFC_DEBUG("Add gateway route %08x successfully.", gateway); return OK; } /** * system_if_add_host_route - 添加设备指定的host主机路由 */ S32 system_if_add_host_route(struct device *dev, U32 host) { struct rtentry rt; struct sockaddr_in sin; int ret = 0; if (NULL == dev) { return ERROR; } if (!valid_ip_addr(host)) { NIFC_ERROR("Parameter wrong."); return ERROR; } memset(&rt, 0, sizeof(struct rtentry)); memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = 0; sin.sin_addr.s_addr = host; memcpy (&rt.rt_dst, &sin, sizeof(struct sockaddr_in)); rt.rt_dev = dev->ifname; /* 需指定需要配置HOST路由的设备名称 */ rt.rt_flags = RTF_HOST; /* 添加路由类型为HOST */ if ((ret = ioctl(sock_ioctl, SIOCADDRT, &rt)) < 0) { NIFC_ERROR("Ioctl SIOCADDRT error, ret code is %d ,error info:%s ", ret, strerror(errno)); return ERROR; } NIFC_DEBUG("Add host route %08x successfully.", host); return OK; } /** * system_if_del_gateway_route - 删除设备指定的gateway */ S32 system_if_del_gateway_route(struct device *dev, U32 gateway) { struct rtentry rt; struct sockaddr_in sin; int ret = 0; if (!valid_ip_addr(gateway)) { NIFC_ERROR("Parameter wrong."); return ERROR; } memset(&rt, 0, sizeof(struct rtentry)); memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = 0; sin.sin_addr.s_addr = INADDR_ANY; /* 默认网关的dst是0.0.0.0 */ memcpy (&rt.rt_dst, &sin, sizeof(struct sockaddr_in)); sin.sin_addr.s_addr = gateway; memcpy (&rt.rt_gateway, &sin, sizeof(struct sockaddr_in)); rt.rt_flags = RTF_GATEWAY; if ((ret = ioctl(sock_ioctl, SIOCDELRT, &rt)) < 0) { NIFC_ERROR("Ioctl SIOCDELRT error, ret code is %d, dst = %08x, gateway = %08x, error info:%s.", ret, INADDR_ANY, gateway, strerror(errno)); return ERROR; } NIFC_DEBUG("Delete gateway route %08x successfully.", gateway); return OK; } /** * system_if_del_host_route - 删除设备指定的host主机路由 */ S32 system_if_del_host_route(struct device *dev, U32 host) { struct rtentry rt; struct sockaddr_in sin; int ret = 0; if (NULL == dev) { return ERROR; } if (!valid_ip_addr(host)) { NIFC_ERROR("Parameter wrong."); return ERROR; } memset(&rt, 0, sizeof(struct rtentry)); memset(&sin, 0, sizeof(struct sockaddr_in)); sin.sin_family = AF_INET; sin.sin_port = 0; sin.sin_addr.s_addr = host; memcpy (&rt.rt_dst, &sin, sizeof(struct sockaddr_in)); rt.rt_dev = dev->ifname; /* 需指定需要删除HOST路由的设备名称 */ rt.rt_flags = RTF_HOST; if ((ret = ioctl(sock_ioctl, SIOCDELRT, &rt)) < 0) { NIFC_ERROR("Ioctl SIOCDELRT error, ret code is %d, host = %08x ,error info:%s ", ret, host, strerror(errno)); return ERROR; } NIFC_DEBUG("Delete host route %08x successfully.", host); return OK; } S32 system_if_set_net(struct device *dev, U32 mtu, U32 ipaddr, U32 mask, U32 gateway) { S32 error = OK; if (NULL == dev) { return ERROR; } #ifdef CONFIG_MOBILE_ACCESS_SET_SUPPORT LTE_CONFIG_INFO_DATA lte_config = {0}; ds_read(LTE_INFO_DATA_PATH, &lte_config, sizeof(LTE_CONFIG_INFO_DATA)); if(lte_config.internet_wired_enable == 0 || (lte_config.auto_switch_wired == 0 && lte_config.internet_wired_enable == 2)) { /* 设置IP地址和子网掩码 */ if (ERROR == system_if_set_ip_mask(dev, ipaddr, mask)) { error = ERROR; } /* 设置MTU */ if (ERROR == system_if_set_mtu(dev, mtu)) { error = ERROR; } }else #endif { /* 先删除之前的gateway路由,否则设置IP地址会将该条目自动删除 */ if (dev->gateway_route) { system_if_del_gateway_route(dev, dev->gateway_route); /* 不管是否删除成功,置空 */ dev->gateway_route = 0; } /* 删除之前的host路由(如果之前网关和IP不在同一网段会存在该条目) */ if (dev->host_route) { system_if_del_host_route(dev, dev->host_route); /* 不管是否删除成功,置空 */ dev->host_route = 0; } /* 设置IP地址和子网掩码 */ if (ERROR == system_if_set_ip_mask(dev, ipaddr, mask)) { error = ERROR; } /* 设置MTU */ if (ERROR == system_if_set_mtu(dev, mtu)) { error = ERROR; } /* 设置网关,如果网关和IP不在同一个网段,先添加目的地为gateway的主机路由,否则无法添加网关 */ if ((ipaddr & mask) != (gateway & mask)) { if (ERROR == system_if_add_host_route(dev, gateway)) { error = ERROR; } else { dev->host_route = gateway; } } /* 添加路由网关 */ if (ERROR == system_if_add_gateway_route(dev, gateway)) { error = ERROR; } else { dev->gateway_route = gateway; } } return error; }
11-07
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值