一、策略路由
策略路由就是根据配置策略 查找路由表,早期的linux 版本是不支持策略路由的。
默认的查找策略就是 先查local路由表,再查main路由表,最后在查 default 路由表。查找策略是有优先级的。当支持策略路由时,内核最多可以配置255个路由表,这个时候根据匹配策略,匹配后再去查找该策略指定的路由表,内核最多支持32768张策略表,策略表按照优先级从高到低的顺序 挂在一张链表上,优先级范围0-32767,数值越低优先级越高。
内核策略路由部分 包括初始化、添加、删除和 查找。
添加部分即使收到用户层配置命令的时候进行添加 删除 修改 查询等操作,即 ip rule add/del 等。
应用层和内核层 通信 是使用 netlink 通信,在内核源码中注册了netlink 的操作函数。
策略查找操作是在查找路由的时候进行,当路由缓存查找失败的时候 就会去查找路由策略,然后根据路由策略去查找路由表。
策略路由 启动初始化流程:
inet_init() ->
ip_init()->
ip_rt_init()->
ip_fib_init()->
register_pernet_subsys(&fib_net_ops)->
fib_net_init->
ip_fib_net_init->
fib4_rules_init->
fib_default_rules_init
策略路由启动:
//kernel/net/ipv4/fib_rules.c
int __net_init fib4_rules_init(struct net *net)
{
int err;
struct fib_rules_ops *ops;
//注册策略路由操作集函数,注册进net
ops = fib_rules_register(&fib4_rules_ops_template, net);
if (IS_ERR(ops))
return PTR_ERR(ops);
//初始化默认策略, 优先级分别为 0, 32766,32767
err = fib_default_rules_init(ops);
if (err < 0)
goto fail;
net->ipv4.rules_ops = ops;
net->ipv4.fib_has_custom_rules = false;
net->ipv4.fib_rules_require_fldissect = 0;
return 0;
fail:
/* also cleans all rules already added */
fib_rules_unregister(ops);
return err;
}
fib4_rules_ops_template 策略路由操作集,策略路由查找的时候用到:
//kernel/net/ipv4/fib_rules.c
static const struct fib_rules_ops __net_initconst fib4_rules_ops_template = {
.family = AF_INET,
.rule_size = sizeof(struct fib4_rule),
.addr_size = sizeof(u32),
.action = fib4_rule_action, //匹配后的函数
.suppress = fib4_rule_suppress,
.match = fib4_rule_match, //判断是否匹配
.configure = fib4_rule_configure,
.delete = fib4_rule_delete,
.compare = fib4_rule_compare,
.fill = fib4_rule_fill,
.nlmsg_payload = fib4_rule_nlmsg_payload,
.flush_cache = fib4_rule_flush_cache,
.nlgroup = RTNLGRP_IPV4_RULE,
.policy = fib4_rule_policy,
.owner = THIS_MODULE,
};
初始化三个默认策略表:
static int fib_default_rules_init(struct fib_rules_ops *ops)
{
int err;
//优先级最高 0, 匹配查找local 路由表
err = fib_default_rule_add(ops, 0, RT_TABLE_LOCAL, 0);
if (err < 0)
return err;
//优先级为 32766, 匹配查找main表
err = fib_default_rule_add(ops, 0x7FFE, RT_TABLE_MAIN, 0);
if (err < 0)
return err;
//优先级为 32767,匹配查找default 表
err = fib_default_rule_add(ops, 0x7FFF, RT_TABLE_DEFAULT, 0);
if (err < 0)
return err;
return 0;
}
上这部分初始化是被IP初始化调用部分,下面这部分是策略路由模块的注册函数:
//kernel/net/core/fib_rules.c
static int __init fib_rules_init(void)
{
int err;
//注册应用层配置命令,添加\删除、dump
rtnl_register(PF_UNSPEC, RTM_NEWRULE, fib_nl_newrule, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_DELRULE, fib_nl_delrule, NULL, 0);
rtnl_register(PF_UNSPEC, RTM_GETRULE, NULL, fib_nl_dumprule, 0);
err = register_pernet_subsys(&fib_rules_net_ops);
if (err < 0)
goto fail;
//注册事件通知链,监听其他子系统异常信息
err = register_netdevice_notifier(&fib_rules_notifier);
if (err < 0)
goto fail_unregister;
return 0;
fail_unregister:
unregister_pernet_subsys(&fib_rules_net_ops);
fail:
rtnl_unregister(PF_UNSPEC, RTM_NEWRULE);
rtnl_unregister(PF_UNSPEC, RTM_DELRULE);
rtnl_unregister(PF_UNSPEC, RTM_GETRULE);
return err;
}
subsys_initcall(fib_rules_init);
这个函数是 网络core 部分的驱动代码。
当使用 ip rule add 或者 ip rule del 等应用层的命令时,最终会调用策略路由子系统注册的netlink 函数。处理函数的实现比较简单,先检查参数的合法性,然后对策略表 net->ipv4->rules_ops->rules_list 执行添加或者删除等操作。
下面接着看 策略路由查找。查找操作发生在路由缓存没有命中的情况下,内核在网络层收到报文的时候需要进行路由判断是发送到本地还是转发,同样,发送报文的时候也需要查找路由,判断走哪个出口网卡或者下一跳 等。
接收报文时:
ip_rcv()->
ip_rcv_finish()->
ip_rcv_finish_core()->
ip_route_input_noref->
ip_route_input_rcu->
ip_route_input_slow->
fib_lookup->
__fib_lookup->
fib_rules_lookup
发送报文时:
tcp_sendmsg/udp_sendmsg->
ip_route_output_flow->
__ip_route_output_key->
ip_route_output_key_hash_rcu->
fib_lookup->
fib_rules_lookup
通过上图可以看出,无论是接收报文还是发送报文,当路由缓存没有命中,查找策略的接口就是fib_rules_lookup,这个函数就是查找策略表,然后根据匹配的策略执行相应的动作:
fib_rules_lookup:
//kernel/net/core/fib_rules.c
int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl,
int flags, struct fib_lookup_arg *arg)
{
struct fib_rule *rule;
int err;
rcu_read_lock();
//遍历 fib_rules_ops 的rules_list
list_for_each_entry_rcu(rule, &ops->rules_list, list) {
jumped:
//根据报文信息去查找策略,并将查询到的策略保存到rule 里
if (!fib_rule_match(rule, ops, fl, flags, arg))
continue;
//跳过当前策略表,继续查找
if (rule->action == FR_ACT_GOTO) {
struct fib_rule *target;
target = rcu_dereference(rule->ctarget);
if (target == NULL) {
continue;
} else {
rule = target;
goto jumped;
}
} else if (rule->action == FR_ACT_NOP)
continue;//效果同上,继续查找
else
//查询到了,根据策略执行相应的动作
//ops->action 在路由策略子系统初始化时 已经指定,
//ipv4 指定的函数是fib4_rule_action
err = INDIRECT_CALL_MT(ops->action,
fib6_rule_action,
fib4_rule_action,
rule, fl, flags, arg);
if (!err && ops->suppress && INDIRECT_CALL_MT(ops->suppress,
fib6_rule_suppress,
fib4_rule_suppress,
rule, flags, arg))
continue;
if (err != -EAGAIN) {
if ((arg->flags & FIB_LOOKUP_NOREF) ||
likely(refcount_inc_not_zero(&rule->refcnt))) {
arg->rule = rule;
goto out;
}
break;
}
}
err = -ESRCH;
out:
rcu_read_unlock();
return err;
}
EXPORT_SYMBOL_GPL(fib_rules_lookup);
fib_rule_match 函数就是执行对策略规则的匹配。
static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops,
struct flowi *fl, int flags,
struct fib_lookup_arg *arg)
{
int ret = 0;
//如指定入接口,数据包的入接口必须和策略rule中指定的入接口相同;
if (rule->iifindex && (rule->iifindex != fl->flowi_iif))
goto out;
//如指定出接口,二者的出接口必须相同;
if (rule->oifindex && (rule->oifindex != fl->flowi_oif))
goto out;
//如指定流标记,二者的流标记必须相同;
if ((rule->mark ^ fl->flowi_mark) & rule->mark_mask)
goto out;
//如指定隧道ID,二者的隧道ID必须相同;
if (rule->tun_id && (rule->tun_id != fl->flowi_tun_key.tun_id))
goto out;
if (rule->l3mdev && !l3mdev_fib_rule_match(rule->fr_net, fl, arg))
goto out;
if (uid_lt(fl->flowi_uid, rule->uid_range.start) ||
uid_gt(fl->flowi_uid, rule->uid_range.end))
goto out;
//上面的判断都符合之后,会调用IPv4 的fib4_rule_match 函数
ret = INDIRECT_CALL_MT(ops->match,
fib6_rule_match,
fib4_rule_match,
rule, fl, flags);
out:
return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
}
fib4_rule_match:针对IPv4 的策略查找
//kernel/net/ipv4/fib_rules.c
INDIRECT_CALLABLE_SCOPE int fib4_rule_match(struct fib_rule *rule,
struct flowi *fl, int flags)
{
struct fib4_rule *r = (struct fib4_rule *) rule;
struct flowi4 *fl4 = &fl->u.ip4;
__be32 daddr = fl4->daddr;
__be32 saddr = fl4->saddr;
//策略rule中指定的源IP地址与数据包的源IP地址,与掩码进行位与操作,结果必须相同
//策略rule中指定的目的IP地址与数据包的目的IP地址,与掩码进行位与操作,结果必须相同;
if (((saddr ^ r->src) & r->srcmask) ||
((daddr ^ r->dst) & r->dstmask))
return 0;
//如指定TOS值,二者的TOS值必须相同
if (r->tos && (r->tos != fl4->flowi4_tos))
return 0;
if (rule->ip_proto && (rule->ip_proto != fl4->flowi4_proto))
return 0;
if (fib_rule_port_range_set(&rule->sport_range) &&
!fib_rule_port_inrange(&rule->sport_range, fl4->fl4_sport))
return 0;
if (fib_rule_port_range_set(&rule->dport_range) &&
!fib_rule_port_inrange(&rule->dport_range, fl4->fl4_dport))
return 0;
return 1;
}
策略匹配之后,执行相应的action 函数:fib4_rule_action
INDIRECT_CALLABLE_SCOPE int fib4_rule_action(struct fib_rule *rule,
struct flowi *flp, int flags,
struct fib_lookup_arg *arg)
{
int err = -EAGAIN;
struct fib_table *tbl;
u32 tb_id;
switch (rule->action) {
case FR_ACT_TO_TBL: //查找路由表
break;
case FR_ACT_UNREACHABLE: //不可达
return -ENETUNREACH;
case FR_ACT_PROHIBIT: //禁止此类报文
return -EACCES;
case FR_ACT_BLACKHOLE: //丢弃此类报文
default:
return -EINVAL;
}
rcu_read_lock();
//根据策略表ID获取指定路由表ID
tb_id = fib_rule_get_table(rule, arg);
tbl = fib_get_table(rule->fr_net, tb_id);//在获取指定的路由表
if (tbl)查询路由表
err = fib_table_lookup(tbl, &flp->u.ip4,
(struct fib_result *)arg->result,
arg->flags);
rcu_read_unlock();
return err;
}
上面分析了根据策略路由去查找相应的路由表,下面展示一下策略路由和 路由表之间的关系图:

在上面的fib4_rule_action 函数中,是根据rule 的ID 获取路右表的ID,其实就是 RT_TABLE_LOCAL、RT_TABLE_MAIN、RT_TABLE_DEFAULT。找到路由表后,就真正调用查找路由表的函数:fib_table_lookup 。因为查找路由表比较耗时,所以内核设计在查找真正路由表之前先查找策略路由,以丢弃一些非当前主机的报文。
二、路由策略的相关命令
查看linux 系统下的策略表: ip rule show

2.1 下面的命令分析:
IP Rule 命令
-
ip rule add from 192.168.1.123 table 5- 从 IP 地址
192.168.1.123的流量将使用路由表5。
- 从 IP 地址
-
ip rule add to 192.168.1.1 table 5- 目的 IP 地址为
192.168.1.1的流量将使用路由表5。
- 目的 IP 地址为
-
ip rule add dev eth1 table 5- 从网络接口
eth1发送的流量将使用路由表5。
- 从网络接口
-
ip rule add tos 8 table 5- TOS(Type of Service)字段值为
8的流量将使用路由表5。
- TOS(Type of Service)字段值为
-
ip rule add not from 192.168.100.100 table 5- 除了源 IP 地址为
192.168.100.100的流量外,其他流量将使用路由表5。
- 除了源 IP 地址为
Iptables 命令
iptables -t mangle -A FORWARD -i eth0 -p tcp --dport 23 -j MARK --set-mark 23- 在
mangle表的FORWARD链中,针对进入接口eth0的 TCP 流量,目的端口为23(Telnet),将该流量标记为23。这个标记可以在后续的路由规则中使用。
- 在
结合使用
ip rule add fwmark 23 table 5- 任何标记为
23的流量将使用路由表5。这个命令结合前面的iptables规则使用,将通过 Telnet(端口 23)的流量标记为23,并通过路由表5进行转发。
- 任何标记为
2.2 补充:上面有iptable 命令的使用,就是传说中的 五链四表
五链四表是:
具体的四表
filter 表——过滤数据包
Nat 表——用于网络地址转换(IP、端口)
Mangle 表——修改数据包的服务类型、TTL、并且可以配置路由实现 QOS
Raw 表——决定数据包是否被状态跟踪机制处理
具体的五链
INPUT 链——进来的数据包应用此规则链中的策略
OUTPUT 链——外出的数据包应用此规则链中的策略
FORWARD 链——转发数据包时应用此规则链中的策略
PREROUTING 链——对数据包作路由选择前应用此链中的规则(所有的数据包进来的时侯都先由这个链处理)
POSTROUTING 链——对数据包作路由选择后应用此链中的规则(所有的数据包出来的时侯都先由这个链处理)
表就是存储的规则;数据报到了该链处,会去对应表中查询设置的规则,然后决定是否放行、丢弃、转发还是修改等等操作。
那么五链四表 和路由的关系,如图:

我这里就举 ip_rcv 收到报文后,源码是如何?
/*
* IP receive entry point
*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt,
struct net_device *orig_dev)
{
struct net *net = dev_net(dev);
skb = ip_rcv_core(skb, net);
if (skb == NULL)
return NET_RX_DROP;
return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING,
net, NULL, skb, dev, NULL,
ip_rcv_finish);
}
可以看到NF_HOOK 钩子宏函数,这个函数就是先执行 匹配NF_INET_PRE_ROUTING 链的表,结束后,在执行 ip_rcv_finish函数,进入路由,也就是上面说到的策略路由 ip rule 。所以说,iptable 是在四表中增加规则,而五链是源码固定的。这里不详细说明 五链四表的关系。
上面的iptable 和 ip rule 匹配使用 是可以的。
798

被折叠的 条评论
为什么被折叠?



