路由套接字

Posted tianzeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了路由套接字相关的知识,希望对你有一定的参考价值。

  在创建套接字时,可以通过指定参数 AF_ROUTE 域创建路由套接字(它只能支持原始套接字,只有超级用户才能创建这个套接字),路由套接字可以访问内核中路由子系统的接口信息。

  1. 进程通过写到路由套接字向内核发送消息(路径的增加和删除采用这种方式实现)
  2. 进程通过读入路由套接字接收来自内核的消息(内核采用这种操作通知进程已收到并处理一个ICMP重定向消息,或请求外部路由进程解析一个路径)
  3. 进程调用 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() 函数进行读写,但是由于系统内核是根据应用程序写入的消息来完成对路由信息的提供和修改的,因此,与其他套接字不同,写入和读出的数据都是有固定的格式。

技术图片

技术图片

  通过路由套接字交换的结构有5个类型:rt_msghdr、if_msghdr、ifa_msghdr、ifma_msghdr和if_announcemsghdr。每个结构有相同的前3个成员:本消息的长度、版本和类型。类型成员是上图第一列中的常值之一。

  rtm_addrs、ifm_addrs、ifam_addrs这三个成员是数位掩码,指明本消息后跟的套接字地址结构是8个可能选择中的哪几个。

技术图片

技术图片
#include    "unproute.h"
#define    BUFLEN    (sizeof(struct rt_msghdr) + 512)
                   /* sizeof(struct sockaddr_in6) * 8 = 192 */
#define    SEQ        9999


/*
 * Round up ‘a‘ to next multiple of ‘size‘, which must be a power of 2
 */
#define ROUNDUP(a, size) (((a) & ((size)-1)) ? (1 + ((a) | ((size)-1))) : (a))
/*
 * Step to next socket address structure;
 * if sa_len is 0, assume it is sizeof(u_long).
 */
#define NEXT_SA(ap)    ap = (SA *) 
    ((caddr_t) ap + (ap->sa_len ? ROUNDUP(ap->sa_len, sizeof (u_long)) :                                     sizeof(u_long)))
void get_rtaddrs(int addrs, SA *sa, SA **rti_info)
{
    int        i;

    for (i = 0; i < RTAX_MAX; i++) {
        if (addrs & (1 << i)) {
            rti_info[i] = sa;
            NEXT_SA(sa);
        } else
            rti_info[i] = NULL;
    }
}


const char * sock_masktop(SA *sa, socklen_t salen)
{
    static char        str[INET6_ADDRSTRLEN];
    unsigned char    *ptr = &sa->sa_data[2];

    if (sa->sa_len == 0)
        return("0.0.0.0");
    else if (sa->sa_len == 5)
        snprintf(str, sizeof(str), "%d.0.0.0", *ptr);
    else if (sa->sa_len == 6)
        snprintf(str, sizeof(str), "%d.%d.0.0", *ptr, *(ptr+1));
    else if (sa->sa_len == 7)
        snprintf(str, sizeof(str), "%d.%d.%d.0", *ptr, *(ptr+1), *(ptr+2));
    else if (sa->sa_len == 8)
        snprintf(str, sizeof(str), "%d.%d.%d.%d",
                 *ptr, *(ptr+1), *(ptr+2), *(ptr+3));
    else
        snprintf(str, sizeof(str), "(unknown mask, len = %d, family = %d)",
                 sa->sa_len, sa->sa_family);
    return(str);
}

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);    /* need superuser privileges */

    buf = Calloc(1, BUFLEN);    /* and initialized to 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 = (struct rt_msghdr *) buf;
    sa = (struct sockaddr *) (rtm + 1);
    get_rtaddrs(rtm->rtm_addrs, sa, rti_info);
    if ( (sa = rti_info[RTAX_DST]) != NULL)
        printf("dest: %s
", Sock_ntop_host(sa, sa->sa_len));

    if ( (sa = rti_info[RTAX_GATEWAY]) != NULL)
        printf("gateway: %s
", Sock_ntop_host(sa, sa->sa_len));

    if ( (sa = rti_info[RTAX_NETMASK]) != NULL)
        printf("netmask: %s
", Sock_masktop(sa, sa->sa_len));

    if ( (sa = rti_info[RTAX_GENMASK]) != NULL)
        printf("genmask: %s
", Sock_masktop(sa, sa->sa_len));

    exit(0);
}
View Code

sysctl操作

  我们对路由套接字的主要兴趣点在于使用sysctl函数检查路由表和接口列表。创建一个路由套接字(AF_ROUTE)需要超级用户权限。使用sysctl检查路由表和接口列表的进程不限用户权限。

#include <sys/param.h>
#include <sys/sysctl.h>
 
int sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
//返回:若成功为0,若出错-1

  这个函数使用类似SNMP(简单网络管理协议)MIB(管理信息库)的名字。

  name参数是指定名字的一个整数数组,namelen参数指定该数组中的元素数目。该数组中的第一个元素指定本请求定向到内核的哪个子系统,第二个及其后元素逐次系话指定该子系统的某个部分。

下面展示了这样的分成排列。

技术图片

  要取一个值,oldp需指向一个缓冲区,以让内核存放该值。oldlenp是一个值-结果参数:调用函数时oldlenp指向的值是缓冲区的大小,返回的值是内核在缓冲区中返回的数据量,如果缓冲区不够大,就返回ENOMEM错误。作为一个特例,oldp可以是一个空指针而oldlenp是一个非空指针,内核确定这个调用本应返回的数据量,并通过oldlenp返回这个值。

  要设置一个新值,newp需指向一个大小为newlen的缓冲区,如果没有指定新值,newp应为一个空指针,newlen应为0。

  sysctl手册详细叙述了可以使用该函数获取的各种系统信息,如:文件系统、虚拟内存、内核限制、硬件各方面信息。我们感兴趣的是网络子系统,通过吧name数组的第一个元素设置为CTL_NET(CTL_XXX常值在,<sys/sysctl.h>头文件中定义)来指定,第二个元素可以是以下几种:

  1. AF_INET:       获取或者设置影响网际协议的变量。下一级为使用某个IPPROTO_XXX常值指定的具体协议。
  2. AF_LINK:       获取或设置链路层信息,例如:PPP接口的数目。
  3. AF_ROUTE:    返回路由表或接口清单的信息。
  4. AF_UNSPEC:  获取或设置一些套接口层变量,例如套接口发送或接收缓冲区的最大大小

  name数组第二个元素为AF_ROUTE时,第三个元素(协议号)总是0(因为AF_ROUTE族不像譬如说AF_INET族那样其中有协议),第四个元素是一个地址族,第五、六级指定做什么。

技术图片

  1. NET_RT_DUMP返回由name[3]指定的地址族的路由表。如果所指定的地址族为0,那么返回所有地址族的路由表。
  2. NET_RT_FLAGS返回由name[3]指定的地址族的路由表,但是仅限于那些所带标志(若干个RTF_xxx常值的逻辑或)与由name[5]指定的标志相匹配的路由表项。
  3. NET_RT_IFLIST返回所有已配置接口的信息。如果name[5]不为0,他就是某个接口的索引号,仅仅返回该接口的信息;已赋予每个接口的所有地址也同时返回。不过如果name[3]不为0,name仅局限于指定地址族的地址。

技术图片

  判断UDP校检和是否开启

#include    "unproute.h"
#include    <netinet/udp.h>
#include    <netinet/ip_var.h>
#include    <netinet/udp_var.h>        /* for UDPCTL_xxx constants */

int
main(int argc, char **argv)
{
    int        mib[4], val;
    size_t    len;

    mib[0] = CTL_NET;
    mib[1] = AF_INET;
    mib[2] = IPPROTO_UDP;
    mib[3] = UDPCTL_CHECKSUM;

    len = sizeof(val);
    Sysctl(mib, 4, &val, &len, NULL, 0);
    printf("udp checksum flag: %d
", val);

    exit(0);
}

接口名字和索引函数

  下面四个函数用于需要描述一个解耦的场合,这里存在一个概念,即每个接口都有一个唯一的名字和一个唯一的正值索引(0从不用做索引)

#include <net/if.h>

unsigned int if_nametoindex(const char * ifname); 
// 返回:成功时为正的接口索引,出错时为0
char * if_indextoname(unsigned int ifindex, char * ifname);
 // 返回: 成功时为指向接口名的指针,出错时为NULL
struct if_nameindex * if_nameindex(void);
 //返回: 成功时为非空指针,出错时为NULL
void if_freenameindex(struct if_nameindex * ptr);

  if_nametoindex返回名为ifname的接口的索引,if_indextoname对给定的ifindex返回一个指向其接口名的指针,ifname参数指向一个大小为IFNAMSIZ头文件中定义的缓冲区,调用者必须分配这个缓冲区以保存结果,成功时这个指针也是函数的返回值,if_nameindex返回一个指向if_nameindex结构的数组的指针。

struct if_nameindex
{
    unsigned int if_index;//1,2,3...
    char *if_name;//null terminated    name:"le0"...
}; 

 

以上是关于路由套接字的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段11——vue路由的配置

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

我需要安装啥软件包才能使用路由套接字?

Play Framework 2.5,到 Web 套接字的路由无法编译

Express实战 - 应用案例- realworld-API - 路由设计 - mongoose - 数据验证 - 密码加密 - 登录接口 - 身份认证 - token - 增删改查API(代码片段

什么可能导致 ZMQ 路由器套接字阻塞?