路由套接字
一、概述
AF_ROUTE
域对访问内核中的路由子系统的接口做了清理。在路由域中支持的唯一一种套接字是原始套接字。路由套接字支持三种类型的操作。
- 进程通过写到路由套接字向内核发送消息(路径的增加和删除采用这种方式实现)。
- 进程通过读入路由套接字接收来自内核的消息(内核采用这种操作通知进程已收到并处理一个ICMP重定向消息,或请求外部路由进程解析一个路径)。
- 进程调用
sysctl
函数获取路由表或列出所有已配置的接口。
前两种操作可以复合使用。内核可以通过写一个路由套接字往内核发送一个消息,请求内核关于某个特定路径的所有消息,又可以通过路由套接字接受内核的应答。
二、数据链路套接字地址结构
struct sockaddr_dl {
uint8_t sdl_len;
sa_family_t sdl_family; /* AF_LINK */
uint16_t sdl_index; /* system assigned index, if > 0 */
uint8_t sdl_type; /* IFT_ETHER, etc. from <net/if_types.h> */
uint8_t sdl_nlen; /* name length, starting in sdl_data[0] */
uint8_t sdl_alen; /* link-layer address length */
uint8_t sdl_slen; /* link-layer selector length */
char sdl_data[12]; /* minimum work area, can be larger;
contains i/f name and link-layer address */
};
#define LLADDR(s) ((caddr_t)((s)->sdl_data + (s)->sdl_nlen))
//sdl_data成员含有名字和链路层地址(例如以太网接口的48位MAC地址)。名字从sdl_data[0]开始,而且不以空字符结尾。链路层地址从sdl_data[sdl_nlen]开始。以上宏定义返回链路层地址的指针。
//链路层套接字地址结构可变。如果地址和名字总长度超出12字节,结构体将大于20字节。32位系统上通常向上舍入到下一个4字节的倍数。IF_RECVIF套接字选项返回的本结构中,所有3个长度成员都为0。
三、读和写
创建路由套接字后,与其他套接字一样,可以调用 read() 或 write() 函数进行读写,但是由于系统内核是根据应用程序写入的消息来完成对路由信息的提供和修改的,因此,与其他套接字不同,写入和读出的数据都是有固定的格式。消息定义在<net/route.h>
中。
路由套接字交换的结构有5个类型:rt_msghdr、if_msghdr、ifa_msghdr、ifma_msghdr和if_announcemsghdr
。每个结构有相同的前3个成员:本消息的长度、版本和类型。类型成员是上图第一列中的常值之一。
//<net/route.h>
struct rt_msghdr {
u_short rtm_msglen;
u_char rtm_version;
u_char rtm_type;
u_long rtm_index;
int rtm_flags;
int rtm_addrs;
pid_t rtm_pid;
int rtm_seq;
int rtm_errno;
int rtm_use;
u_long rtm_inits;
u_long rtm_table;
struct rt_metrics rtm_rmx;
};
//<net/if.h>
struct if_msghdr{
u_short ifm_msglen;
u_char ifm_version;
u_char ifm_type;
int ifm_addrs;
int ifm_flags;
u_short ifm_index;
struct if_data ifm_data;
};
struct ifa_msghdr{
u_short ifam_msglen;
u_char ifam_version;
u_char ifam_type;
int ifam_addrs;
int ifam_flags;
u_short ifam_index;
int ifam_metric;
};
struct ifma_msghdr{
u_short ifam_msglen;
u_char ifmam_version;
u_char ifmam_type;
int ifmam_addrs;
int ifmam_flags;
u_short ifmam_index;
};
struct if_announcemsghdr{
u_short ifan_msglen;
u_char ifan_version;
u_char ifan_type;
u_short ifan_index;
char ifan_name[IFNAMSIZ];
u_short ifan_what;
}
rtm_addrs、ifm_addrs、ifam_addrs
这三个成员是数位掩码,指明本消息后跟的套接字地址结构是8个可能选择中的哪几个。在<net/route.h>
头文件定义。存在多个套接字地址结构时,按表中所示的顺序排列。
1.获取并输出一个路由表项
- 首先命令行取代IPv4点分十进制数地址。
- 将地址向内核发送RTM_GET消息。
- 内核在IPv4路由表查询地址,将RTM_GET消息返回相应路由表项的信息。
采用默认路径:
指定主机第二个以太网接口所在子网为目的的地址:
目的地址为网络本身,网关是外出接口,作为sockaddr_dl结构返回,结构索引为2.
如下图所示写到路由套接字的信息和由内核返回的信息。
#include "unproute.h"
#define BUFLEN (sizeof(struct rt_msghdr) + 512)
#define SEQ 9999
int main(int argc,char **argv)
{
int sockfd;
char* buf; pid_t pid;
ssize_t n;
struct rt_msghdr* rtm;
struct sockaddr* sa, *rti_info[RTAX_MAX];
struct sockaddr_in* sin;
if(argc !=2)
err_quit("usage:getrt<IPaddress>");
//创建套接字
sockfd = socket(AF_ROUTE, SOCK_RAW, 0);
//填写rt_msghdr结构
buf = calloc(1, BUFLEN); //分配缓冲区初始化为0
rtm = (struct rt_msghdr*)buf;
rtm->rtm_msglen = sizeof(struct rt_msghdr) + sizeof(struct sockaddr_in);
rtm->rtm_version = RTM_VERSION;
rtm->rtm_type = RTM_GET;
rtm->rtm_addrs = RTA_DST;
rtm->rtm_pid = pid = getpid();
rtm->rtm_seq = SEQ;
//以目的地址填写网际网套接字地址结构
sin = (struct sockaddr_in*)(rtm + 1);
sin->sin_len = sizeof(struct sockaddr_in);
sin->sin_family = AF_INET;
inet_pton(AF_INET, argv[1], &sin->sin_addr);
//
write(sockfd, rtm, rtm->rtm_msglen);
do {
n = read(sockfd, rtm, BUFLEN);
} while (rtm->rtm_type != RTM_GET || rtm->rtm_seq != SEQ || rtm->rtm_pid != pid);
//rtm指向rt_msghdr结构,sa指向接在其后的第一个套接字地址结构
rtm = (struct rt_msghdr*)buf;
sa = (struct sockaddr*)(rtm + 1);
//一掩码和指向第一个套接字地址结构的指针为参数(sa),在rti_info数组中填入指向相应套接字地址结构的指针
get_rtaddr(rtm->rtm_addrs, sa, rti_info);
//若存在则逐个显示4个肯能的地址,sock_ntop_host:显示目的地址和网关地址;sock_masktop:显示掩码
if ((sa = rti_info[RTAX_DST]) != NULL)
printf("dest:%s\n", sock_ntop_host(sa, sa->sa_len));
if ((sa = rti_info[RTAX_GATEWAY]) != NULL)
printf("dest:%s\n", sock_ntop_host(sa, sa->sa_len));
if ((sa = rti_info[RTAX_NETMASK]) != NULL)
printf("genmask:%s\n", sock_masktop(sa, sa->sa_len));
if ((sa = rti_info[RTAX_GENMASK]) != NULL)
printf("netmask:%s\n", sock_masktop(sa, sa->sa_len));
exit(0)