linux socket 添加路由,Linux下routing socket使用介绍

Linux下routing socket使用介绍

发布时间:2006-09-23 00:59:37来源:红联作者:wuhu911

作者:王贵豹(daiban2@tom.com)

您可以自由转载,但如果在商业报刊上刊登,请先征得作者同意。

《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结构体组成,每个结构体表示该路由项的某个属性,如目的地址,网关等等。根据这个示意图我们就能够轻松解析需要的数据了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值