connect 路由 ip_route_connect

  ip_route_connect_init -> flowi4_init_output

  这个函数会去初始化fl4的数据,例如fl4->daddrfl4->saddrfl4->fl4_dportfl4->fl4_sport,但是并非都是已经设置好值的,因为想要发送第一个SYN报文,就需要完整的来源/目的地址来源/目的端口,然而在客户端的socket connect中,只有目的地址/端口可以被用户提供出来,来源端口可以从未用端口中动态分配一个,然而一个主机上可能有多个IP,那么来源地址如果错误的话,就很有可能造成数据不可达,因此这时候需要有路由参与在内。

  而关于这个来源地址在接口层次上还分为bindno-bind类型,如果是bind类型的话来源地址就是bind地址,但是no-bind类型的话则需要根据路由的结果确定来源地址

  一个数据包传输到本地后,需要先在IP层查找路由,然后在传输层查找socket,因此为了高效利用,linux内核提供了一个特性ip_early_demux,其能力就是在socket中增加一个dst字段作为缓存路由,skb在查找路由前优先查找socket,找到的话就把缓存的dst设置到skb,那么再去查找路由的时候发现已经有dst了,就省去查找路由的过程

static inline struct rtable *ip_route_connect(struct flowi4 *fl4,
                          __be32 dst, __be32 src, u32 tos,
                          int oif, u8 protocol,
                          __be16 sport, __be16 dport,
                          struct sock *sk)
{
    struct net *net = sock_net(sk);
    struct rtable *rt;

    ip_route_connect_init(fl4, dst, src, tos, oif, protocol,
                  sport, dport, sk);

    if (!dst || !src) {
        rt = __ip_route_output_key(net, fl4);
        if (IS_ERR(rt))
            return rt;
        ip_rt_put(rt);
        flowi4_update_output(fl4, oif, tos, fl4->daddr, fl4->saddr);
    }
    security_sk_classify_flow(sk, flowi4_to_flowi(fl4));
    return ip_route_output_flow(net, fl4, sk);
}

   初始化完fl4后会有一个判断,因为src是没有设置的,所以会自动进入到__ip_route_output_key的流程,接着在ip_route_output_key_hash中填充一些fl4的数据然后进入到ip_route_output_key_hash_rcu

函数开始便是三个路由条件判断:

if (fl4->saddr) {  //来源地址,为空
if (fl4->flowi4_oif) {   //出口设备,为0
if (!fl4->daddr) {  //目标地址,为用户定义地址

三个条件都没有符合,直接进入到err = fib_lookup(net, fl4, res, 0);函数,这个是路由转发表检索函数,fl4是查找条件,而res是路由查找结果。

因为此时的来源地址为空,那么策略路由的from关键字将无法匹配到所有没有bind地址的程序发出的数据包,实际上来说这就导致不会进入到任何一张自定义策略路由表中。

  简单点说,策略路由就是根据来源地址目的地址入接口出接口等元素决定数据包在路由前是否进入到该张策略路由表

err = fib_rules_lookup(net->ipv4.rules_ops, flowi4_to_flowi(flp), 0, &arg);

net->ipv4.rules_ops中的rules_list链表保存了当前net namespace所有的路由策略。通过list_for_each_entry_rcu遍历链表,然后通过fib_rule_match匹配策略

而关于函数match的方法

通用规则匹配
1)如指定入接口,数据包的入接口必须和策略rule中指定的入接口相同; //iif eth1
2)如指定出接口,二者的出接口必须相同;  //oif eth0
3)如指定流标记,二者的流标记必须相同; //fwmark 0x3
4)如指定隧道ID,二者的隧道ID必须相同; //tunid

另外,针对IPv4协议:
5)策略rule中指定的源IP地址与数据包的源IP地址,与掩码进行位与操作,结果必须相同;//from
5)策略rule中指定的目的IP地址与数据包的目的IP地址,与掩码进行位与操作,结果必须相同; //to
7)如指定TOS值,二者的TOS值必须相同; //服务类型,即指定服务的流量 //tos

首先遍历到的0: from all lookup local在进入到fib4_rule_match后会因为什么都没有设置而直接返回1,也就是直接匹配到。此刻本条规则的大致数据如下:

r->action = FR_ACT_TO_TBL;
r->pref = 0;
r->table = RT_TABLE_LOCAL;
r->flags = 0;

 

 匹配到规则后返回至fib_rules_lookup,判断下action接后执行ops->action的操作,此刻应该是fib4_rule_action,在没有使用l3mdev的情况下,使用rule->table作为table id,然后调用fib_get_table获取该表,最后通过fib_table_lookup进行路由项查找。
 fib_lookup最后一直返回的err = 0,又因为res->type = RTN_LOCAL,且fl4->saddr = 0,所以fl4->saddr = 127.0.0.1fl4->flowi4_oif = loopback_dev
接着创建路由缓存条目__mkroute_output。 直接看到rt_set_nexthop(rth, fl4->daddr, res, fnhe, fi, type, 0, do_cache);,然后重新填充fl4的数据
flowi4_update_output
/* Reset some input parameters after previous lookup */
static inline void flowi4_update_output(struct flowi4 *fl4, int oif, __u8 tos,
                    __be32 daddr, __be32 saddr)
{
    fl4->flowi4_oif = oif;
    fl4->flowi4_tos = tos;
    fl4->daddr = daddr;
    fl4->saddr = saddr;
}

 



posted @ 2024-10-09 22:02  codestacklinuxer  阅读(35)  评论(0)    收藏  举报