connect 路由 ip_route_connect
ip_route_connect_init -> flowi4_init_output
这个函数会去初始化fl4
的数据,例如fl4->daddr
,fl4->saddr
,fl4->fl4_dport
,fl4->fl4_sport
,但是并非都是已经设置好值的,因为想要发送第一个SYN
报文,就需要完整的来源/目的地址
,来源/目的端口
,然而在客户端的socket connect
中,只有目的地址/端口
可以被用户提供出来,来源端口
可以从未用端口中动态分配一个,然而一个主机上可能有多个IP,那么来源地址
如果错误的话,就很有可能造成数据不可达,因此这时候需要有路由参与在内。
而关于这个来源地址
在接口层次上还分为bind
和no-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
进行路由项查找。
err = 0
,又因为res->type = RTN_LOCAL
,且fl4->saddr = 0
,所以fl4->saddr = 127.0.0.1
且fl4->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; }