概述
在服务器上,我们一般会根据官方文档或者遇到网络不通的时候,去设置ip_forward设置为1,问题虽然解决了,可能并不清楚ip_forward背后的原理,本篇文章将一探究竟。
- 为什么要设置ip_forward为1?
- ip_forward在协议栈处理的哪个过程中生效?
- 如何在繁杂的内核代码中找到处理ip_forward相关的流程。
ip_forward作用
一般来说,一台服务器只处理mac地址和ip地址为本机的数据包,即ip_forward默认为0。但是随着云和虚拟网络的发展,linux可以作为一个软路由转发路过的数据包。因此当需要宿主机处理目的地址非本机的数据包时,需要使能ip_forward。
// 临时生效,永久生效,需要修改/etc/sysctl.conf文件
echo 1 > /proc/sys/net/ipv4/ip_forward
ip_forward在哪个处理过程中生效
还是用经典的netfilter的框架图。
这里只分析最简单的一种情况,单播。下面这段代码是路由查找过程中的一个函数,从上面的图看出,在prerouting后,要经过router模块,去判断该包的类型,是送往本机的包,还是需要转发的包。注意这里的转发可以是直接出物理网卡,也可以是转发到主机的其他虚拟网卡这张图是三层的处理,能到prerouting进行处理,说明mac地址是已经匹配了的。
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
u8 tos, struct net_device *dev,
struct fib_result *res)
{
struct in_device *in_dev = __in_dev_get_rcu(dev);
// 查找路由表
err = fib_lookup(net, &fl4, res, 0);
// 本机的包,继续处理
if (res->type == RTN_LOCAL) {
err = fib_validate_source(skb, saddr, daddr, tos,
0, dev, in_dev, &itag);
if (err < 0)
goto martian_source;
goto local_input;
}
// 非本机的包,如果ip_forward未使能,返回错误
if (!IN_DEV_FORWARD(in_dev)) {
err = -EHOSTUNREACH;
goto no_route;
}
}
那这里如何从查找路由的结果中判断是本机的包还是非本机的包?这就涉及到路由表的知识,常用的默认存在的三张表 default,main,local。fib_lookup会根据目的地址去查找,并将结果保存到res中。
// 查看local表的路由,目的地址属于这里就是本机的包。
[root@10 yaml]# ip r list table local
broadcast 10.10.101.0 dev ens192 proto kernel scope link src 10.10.101.91
local 10.10.101.91 dev ens192 proto kernel scope host src 10.10.101.91
broadcast 10.10.101.255 dev ens192 proto kernel scope link src 10.10.101.91
local 10.96.0.1 dev kube-ipvs0 proto kernel scope host src 10.96.0.1
local 10.96.0.10 dev kube-ipvs0 proto kernel scope host src 10.96.0.10
local 10.96.0.161 dev kube-ipvs0 proto kernel scope host src 10.96.0.161
local 10.96.9.71 dev kube-ipvs0 proto kernel scope host src 10.96.9.71
local 10.96.37.82 dev kube-ipvs0 proto kernel scope host src 10.96.37.82
local 10.96.106.146 dev kube-ipvs0 proto kernel scope host src 10.96.106.146
local 10.96.127.95 dev kube-ipvs0 proto kernel scope host src 10.96.127.95
local 10.96.179.37 dev kube-ipvs0 proto kernel scope host src 10.96.179.37
local 10.96.241.158 dev kube-ipvs0 proto kernel scope host src 10.96.241.158
local 10.244.12.177 dev vxlan.calicoV4 proto kernel scope host src 10.244.12.177
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 172.17.0.0 dev docker0 proto kernel scope link src 172.17.0.1
local 172.17.0.1 dev docker0 proto kernel scope host src 172.17.0.1
broadcast 172.17.255.255 dev docker0 proto kernel scope link src 172.17.0.1
// 保存路由表查找结果的数据结构
struct fib_result {
__be32 prefix;
unsigned char prefixlen;
unsigned char nh_sel;
unsigned char type;
unsigned char scope;
u32 tclassid;
struct fib_info *fi;
struct fib_table *table;
struct hlist_head *fa_head;
};
如何在代码中查找ip_forward的处理流程
如果直接去翻看内核代码,根本是没有头绪的,下面的介绍也是阅读的一种技巧。
1、 搜索ip_forward关键字,查看proc的处理函数,及echo 1 > ip_forward后内核的处理函数。
2、 找到ip_forward设置的方式,一般会有宏处理。
3、 转变思路,查找ip_forward使用的地方。
// 通过ip_forward关键字,找到proc相关处理函数,即devinet_sysctl_forward
static struct ctl_table ctl_forward_entry[] = {
{
.procname = "ip_forward",
.data = &ipv4_devconf.data[
IPV4_DEVCONF_FORWARDING - 1],
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = devinet_sysctl_forward,
.extra1 = &ipv4_devconf,
.extra2 = &init_net,
},
};
static int devinet_sysctl_forward(struct ctl_table *ctl, int write,
void __user *buffer,
size_t *lenp, loff_t *ppos)
{
// 这两个函数就是关键点,看函数名,就可以大概知道作用。
inet_forward_change(net);
inet_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORWARDING,
NETCONFA_IFINDEX_DEFAULT,
net->ipv4.devconf_dflt);
}
// 查看inet_forward_change函数
static void inet_forward_change(struct net *net)
{
// 遍历该命名空间的所有网卡,设置网卡属性
for_each_netdev(net, dev) {
struct in_device *in_dev;
in_dev = __in_dev_get_rtnl(dev);
if (in_dev) {
// 这里是重点,需要探究这个宏将这个on保存到了哪里
IN_DEV_CONF_SET(in_dev, FORWARDING, on);
inet_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORWARDING,
dev->ifindex, &in_dev->cnf);
}
}
}
// 查看IN_DEV_CONF_SET宏,清晰明了的看到这个on保存到了哪里。
#define IN_DEV_CONF_SET(in_dev, attr, val) \
ipv4_devconf_set((in_dev), IPV4_DEVCONF_ ## attr, (val))
static inline void ipv4_devconf_set(struct in_device *in_dev, int index,
int val)
{
set_bit(index, in_dev->cnf.state);
in_dev->cnf.data[index] = val;
}
通过上面的分析,我们清楚的看到了ip_forward的处理流程以及保存在了哪个数据结构中。另外看到set(IN_DEV_CONF_SET)函数,对应就应该有get函数,那么查找get函数就可以找到该参数在哪些流程中生效。
#define IN_DEV_CONF_GET(in_dev, attr) \
ipv4_devconf_get((in_dev), IPV4_DEVCONF_ ## attr)
// 在以下函数中都有调用
ip_route_input_mc
__mkroute_input
ip_route_input_slow // 这个就是上面分析的函数,只是一种情况。
__mkroute_output