aodv之二

Unix 时间戳前一文《aodv之一》是记录了aodv内核模块kaodv的实现机制和工作过程,因为内核模块使用netlink与用户空间进行通信,所以下面这个文章记录自己阅读aodv用户层代码的一些理解

首先定义一些变量,必须的变量有网络设备名ifname,文件描述符集rfds,readers,一个守护进程变量,两个时间结构体,struct timeval *timeout; struct timespec timeout_spec;前者是秒与微妙级别的,后者是秒与纳秒级别的,struct sigacton sigact 用于处理关联信号与回调函数。sigset_t mask, origmask是信号集。

  • 运行程序的一些配置

第一步是获取本程序名称。
第二步就是关联一些会影响到程序的信号,如SIGTERM,SIGHUP,SIGINT还有SIGSEGV,不过关联的处理程序signal_handler并没有做任何的处理,只是输出一些信息。虽然这样设置了,但是后面程序又使用sigprocmask(SIG_BLOCK, &mask, &origmask)阻塞对应的信号,感觉和前面设计关联处理函数实现一样的功能,好像重复了(有待研究)
第三步就是解析命令行参数,主要包括:
-d 为关闭调试模式同时使进程处于守护进程模式
-f 设置链路层返回,应该是用于确认链路有效的,链路有效路由默认设为10000(10s)
-g rreq_gratuitous使能RREQ的功能,就是要求RREQ达到目的节点的时候才返回RREP,而不是遇到可到目的节点的节点就返回RREP(还是返过来?)
-i 设置网络设备接口的名字,默认是第一个无线接口
-j 触发hello_jittering(默认启动)
-l log调试输出到文件
-n 设置接收到多少个hellos才把节点当作邻居节点,默认不少于2
-o 是否优化hello,如果优化的话,仅在转发数据的情况下发送hello(处于试验性的)
-q 对于aodv的控制数据packet(RREQ,RREP,RERR)设置最小的信号质量阈值
-r 每N秒log输出路由表(默认0,关闭路由表输出)
-u 检测和避免不对称链路(试验性的)
-w 试验性的,使能网关模式支持
-x 失能对于RREQ的扩展令牌搜索
-L 使能local repair
-D 失能重启后15秒的延迟,也就是说默认重启后有15秒的延迟处理
-R 触发速率限制(RREQs,RERRs),默认使能速率限制
-V 打印AODV-UU版本

  • 程序是否为守护进程
    geteuid()判断该进程是否以root权限运行,因为要处理内核通信,同时对相关设备的操作,必须运行于root权限
    如果要以守护进程的方式运行,必须fork()子进程,父进程退出,关闭stdout,stderr,同时setsid()把该子进程设置为新的会话的领头进程
    ,使其与父进程的会话组和进程组脱离,同时与终端脱离。
  • 进一步初始化
struct routing_table{
    unsigned int num_entries;
    unsigned int num_active;
    list_t tbl[RT_TABLESIZE];
}

对路由表的处理是路由条目和有效路由都为0,路由表为哈希循环双向路由链表

log 初始化,这里不讨论ns的仿真,只看实现
默认log终端输出,否则log_to_file为1,则在文件/var/log/aodvd.log输出调试信息,如果使能路由表信息输出,则在/var/log/aodvd.rtlog文件输出信息

主机初始化host_init

struct host_info{
    u_int32_t seqno;//序列号
    struct timeval bcast_time;//最近一次广播时间
    struct timeval fwd_time;//最近一次转发数据packet时间
    u_int32_t rreq_id;//RREQ id
    int nif;//接口号
    struct dev_info devs[MAX_NR_INTERFACES+1];//设备信息
}
struct dev_info{
    int enabled;/* 1 if struct is used, else 0 */
    int sock;
#ifdef CONFIG_GATEWAY
    int psock;/* Socket to send buffered data packets. */
#endif
    unsigned int ifindex;
    char ifname[IFNAMSIZ];
    struct in_addr ipaddr;/* The local IP address */
    struct in_addr netmask;/* The netmask we use */
    struct in_addr broadcast;
}

如果设备名为空,我们搜索第一个无线设备

struct ifconf{
    int ifc_len;/*size of buffer*/
    union{
        char *ifcu_buf;
        struct ifreq *ifcu_req;
    }ifc_ifcu;
};
struct ifreq{
#define IFHWADDRLEN 6
    union{
        char ifrn_name[IFNAMSIZ];/*if name, e.g. "en0"*/
    }ifr_ifrn;
    union{
        struct sockaddr ifru_addr;
        struct sockaddr ifru_dstaddr;
        struct sockaddr ifru_broadaddr;
        struct sockaddr ifru_netmask;
        struct sockaddr ifru_hwaddr;
        short ifru_flags;
        int ifru_ivalue;
        int ifru_mtu;
        struct  ifmap ifru_map;
        char    ifru_slave[IFNAMSIZ];   /* Just fits the size */
        char    ifru_newname[IFNAMSIZ];
        void *  ifru_data;
        struct  if_settings ifru_settings;
    }ifr_ifru;
}

通过ioctl(iw_sock, SIOCGIFCONF, &ifc)获取接口信息

struct  iwreq     
{
    union
    {
        char    ifrn_name[IFNAMSIZ];    /* if name, e.g. "eth0" */      
    }ifr_ifrn;                                                   /* Data part (defined just above)*/
};          

iwreq和ifreq是一样的

union iwreq_data
{
    /*Config - generic*/
    char name[IFNAMSIZ];
    /*Name: used to verify the presence of wireless extensions .
    *Name of the protocol/provider...*/
    struct iw_point essid;/* Extended network name */
    struct iw_param nwid; /* network id (or domain - the cell) */
    struct iw_freq freq;/* frequency or channel :* 0-1000 = channel 
    * > 1000 = frequency in Hz */

    struct iw_param sens;/* signal level threshold */
    struct iw_param bitrate;/* default bit rate */
    struct iw_param txpower;/* default transmit power */
    struct iw_param rts;/* RTS threshold threshold */ 
    struct iw_param frag;/* Fragmentation threshold */
    __u32 mode;/* Operation mode */
    struct iw_param retry;/* Retry limits & lifetime */

    struct iw_point encoding;/* Encoding stuff : tokens */
    struct iw_param power;/* PM duration/timeout */
    struct iw_quality qual;/* Quality part of statistics */

    struct sockaddr ap_addr;/* Access point address */ 
    struct sockaddr addr;/* Destination address (hw/mac) */

    struct iw_param param;/* Other small parameters */
    struct iw_point data; /* Other large parameters */

}

序列号初始化为1,rreq_id号为0,上一次的广播时间(第一次)为当前时间。接着就是根据接口名字获取接口ip地址,子网掩码,广播地址,标记相关索引结构体dev_info已被使用。然后装载kaodv.ko模块。装载内核模块主要采用了system命令,同时休眠1s

  • 内核方面的设置
    启用数据包转发功能/proc/sys/net/ipv4/ip_forward
    关闭icmp重定向功能/proc/sys/net/ipv4/conf/eth0/send_redirects
    /proc/sys/net/ipv4/conf/eth0/accept_redirects
    还有
    /proc/sys/net/ipv4/conf/all/send_redirects
    /proc/sys/net/ipv4/conf/all/accept_redirects
    关闭以上重定向功能,则不用经过路由器转发(如当你在同一个实体网络内假设一部路由器,但这个实体网络有两个IP网络,,例如192.168.0.0/24 192.168.1.0/24 这时192.168.0.100想要向192.168.1.100传送信息时,路由器可能会传送一个ICMP redirect封包告知192.168.0.100直接传送数据给192.168.1.100即可,而不需要透过路由器。)
  • netlink初始化
struct sockaddr_nl{
    __kernel_sa_family_t nl_family; /*AF_NETLINK*/
    unsigned short nl_pad;/*zero*/
    __u32 nl_pid; /*port ID*/
    __u32 nl_groups;  /*multicast groups mask*/
}

自定义的结构体

struct nlsock{
    int sock;
    int seq;
    struct sockaddr_nl local;
}

通过socket(PF_NETLINK, SOCK_RAW, NETLINK_AODV);创建netlink套接字,用于与内核通信
一个重要的函数就是attach_callback_func(aodvnl.sock, nl_kaodv_callback),
首先aodv应用程序限制了5个回调函数的上限数量,而函数attach_callback_func就是判断回调函数数量是否已达上限,同时未达上限时对全局变量callbacks[]赋值,就是设置对应的文件描述符和回调函数。所以这里就是把aodvnl.sock文件描述符与其回调函数nl_kaodv_callbac结合起来。这里有一个结构体kaodv_rt_msg(kaodv_rt_msg_t)

typedef struct kadov_rt_msg{
    u_int8_t type;
    u_int32_t src;
    u_int32_t dst;
    u_int32_t nhop;
    u_int32_t flags;
    int ifindex;
    long time;
}kaodv_rt_msg_t;
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 */
}
struct nlmsgerr{
    int error;
    struct nlmsghdr msg;
}

在aodv用户空间中处理的nl信息类型有NLMSG_ERROR,KAODVM_DEBUG,KAODVM_TIMEOUT,KAODVM_ROUTE_REQ,KAODVM_REPAIR,KAODVM_ROUTE_UPDATE,KAODVM_SEND_RERR。
对于NLMSG_ERROR,处理的方式就是输出调试信息“NLMSG——ERROR, error=%d type=%s“,其中type有Add route,Delete route,Timeout,Route Request, Route repair, No route found,Route update, Send route error,Configuration,Debug
对于KAODVM_DEBUG,输出类似与”kaodv:%s”的调试信息,%s为具体的数据信息.
对于KAODVM_TIMEOUT,输出调试信息”Got TIMEOUT msg from kernel for %s”,%s为对应目的节点的ip地址,同时对路由表进行timeout操作,在aodv中路由表条目如下
这个是自定义的结构体

struct rt_table{
    list_t l;
    struct in_addr dest_addr;/* IP address of the destination */
    u_int32_t dest_seqno;
    unsigned int ifindex;/* Network interface index... */
    struct in_addr next_hop;/* IP address of the next hop to the dest */
    u_int8_t hcnt;/* Distance (in hops) to the destination */
    u_int16_t flags; /* Routing flags */  
    u_int8_t state;/* The state of this entry */
    struct timer rt_timer;/* The timer associated with this entry */
    struct timer ack_timer;/* RREP_ack timer for this destination */
    struct timer hello_timer;
    struct timer last_hello_time;
    u_int8_t hello_cnt;
    hash_value hash;
    int nprec;/* Number of precursors */
    list_t precursors; /* List of neighbors using the route */
};
struct timer{
    list_t l;
    int used;
    struct timeval timeout;
    timeout_func_t handler;
    void *data;
};

其中如果找到匹配的路由条目的state为有效的(VALID),则意味者之前并没有出现timeout情况,所以要对该路由进行处理,如该路由为到目的节点的距离为1,即rt->hcnt == 1,则表示目的节点是本节点的邻居,利用函数neighbor_link_break处理(移除路由条目上定时器结构体(rt_timer,hello_timer,ack_timer),路由条目设为无效,路由表的有效路由数目减1,该路由条目的hello计数清0,对应的路由条目的序列号+1,路由条目的上一次hello time清零,向内核发送删除对应路由的信息KAODVM_DELROUTE,处理向内核发送KAODVM_DELROUTE,还有更进一步的操作内核路由表(这个是系统内核路由表,就是说除了kaodv内核模块的路由表,还要处理系统的静态路由表))

struct rtmsg {
    unsigned char rtm_family;
    unsigned char rtm_dst_len;
    unsigned char rtm_src_len;
    unsigned char rtm_tos;

    unsigned char rtm_table;
    unsigned char rtm_protocol;
    unsigned char rtm_scope;
    unsigned char rtm_type;
    unsigned rtm_flags;
}

具体上面的数据结构表示什么,看参考文件/usr/include/linux/rtnetlink.h
如果定义了网关,则也要把路由表中下一跳是该网关地址的路由删除掉,由于网关路由应该有很多邻居,所以也要删除掉该路由条目的前驱(邻居),无论如何,对应路由条目的前驱(邻居)要删除掉。
同时要查找整个路由表,路由条目有效的前提下,如果路由条目的下一跳是对应要删除的目的节点地址,或者该路由条目的目的节点地址对应要删除的目的节点地址

对于KAODVM_ROUTE_REQ路由请求,rreq_route_discovery(dest_addr, 0, NULL)发起路由请求,这里涉及了一个数据结构

/* This is a list of nodes that route discovery are performed for */
typedef struct seek_list{
    list_t l;
    struct in_addr dest_dest;
    u_int32_t dest_seqno;
    struct ip_data *ipd;
    u_int8_t flags;/* The flags we are using for resending the RREQ */
    int reqs;
    int ttl;
    struct timer seek_timer;
}seek_list_t;

在这里,要注意的一种情况就是gratuitous RREP,当指示解决的问题是,如果RREQ发送到达一个中继节点node,该node有到目的节点的路由的情况下,如果没有设置Destination Only标记,则该节点会返回RREP给源节点,但是这种情况下目的节点并不知道达到源节点的路由路径,如果是TCP建立握手连接的话,目的节点就要发起(originating node)RREQ,因此,设置gratuitous RREP,则该中继节点会向目的节点单播RREP,告诉目的节点有到源节点的路由.
每发送一个rreq,在seek_list_t中插入记录,防止多操作。在插入seek_list_t中,还对其路由发现的超时做了相对应处理timer_init(&entry->seek_timer, &NS_CLASS route_discovery_timeout, entry);
在函数route_discovery_timeout中,如果rreq发送没有超出RREQ_RETRIES(2),则继续重发,如果超出了这个次数,则利用netlink向内核路由发送KAODVM_NOROUTE_FOUND信息.同时用户空间查找路由表条目,如果有该路由而且该路由处于RT_REPAIR则报告路由修复失败同时发送相关的RRER广播信息。

KAODVM_REPAIR
执行rreq_local_repair(fwd_rt, src_addr, NULL)进行路由修复,主要也是发送相应的rreq

KAODVM_ROUTE_UPDATE
根据输入的数据packet和输出的数据packet,更新到源节点和到目的节点路由条目的时间有效性

KAODVM_SEND_RERR
发送rerr的广播数据包

rtnl是用于修改内核路由表的socket,不是kaodv.ko的,而是系统的
RTMGRP_NOTIFY|RTMGRP_IPV4_IFADDR|RTMGRP_IPV4_ROUTE,为了实时监控本机路由表的,需要创建以上参数的netlink套接字
aodv中,该netlink sock是和内核通信的,主要处理的路由事件有
NLMSG_ERROR/RTM_NEWLINK/RTM_NEWADDR/RTM_NEWROUTE,
nl_rt_callback该函数只是处理内核发送的一些提示信息,没有做什么实质性的东西,但是该sock可能在后续修改路由表中会有用到

函数nl_send_conf_msg主要是向kaodv内核模块发送KAODVM_CONFIG配置信息,主要配置的信息有active_route_timeout/qual_th/is_gateway
默认的阈值qual_threshold是0,这个值会影响到AODV控制信息的接收,active_route_timeout默认为3000,即3s,is_gateway默认不开启。

aodv_socket_init:创建sock UDP套接字(用于aodv socket),如果define CONFIG_GATEWAY,则创建sock IPPROTO_RAW(Data packet send socket)
对aodv socket还要设置允许广播,因为这些socket专门用于传送RREQ,RREP(单播),RRER的广播包的。还有设置很多其他参数如SO_BINDTODEVICE/SO_PRIORITY/IP_RECVTTL/IP_PKTINFO/SO_BINDTODEVICE/SO_SNDBUF/SO_RCVBUF等
最后也设置了回调函数aodv_socket_read

llf_init,如果设置了llfeedback,就是通过link来感知邻居节点的话,创建netlink socket用于通信

以上初始完之后,进入死循环,不断地检查定时器过期与否与检测那些文件描述符是可读的。
在这里使用select来监控所有创建的socket文件描述符,readers记录了所有的文件描述符,是一个文件描述符集合
函数timer_age_queue()就是询问定时器队列过期的情况,检查到过期的定时器就去执行对应的函数,没有过期的就返回离第一个就要过期的定时器的剩余时间值。
pselect函数使用的超时值就是上面第一个定时器要过期的剩余时间值,因此,对定时器或可读描述符都能及时响应。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值