概述
在服務(wù)器上遏暴,我們一般會(huì)根據(jù)官方文檔或者遇到網(wǎng)絡(luò)不通的時(shí)候,去設(shè)置ip_forward設(shè)置為1指黎,問題雖然解決了朋凉,可能并不清楚ip_forward背后的原理,本篇文章將一探究竟醋安。
- 為什么要設(shè)置ip_forward為1杂彭?
- ip_forward在協(xié)議棧處理的哪個(gè)過程中生效?
- 如何在繁雜的內(nèi)核代碼中找到處理ip_forward相關(guān)的流程吓揪。
ip_forward作用
一般來說亲怠,一臺(tái)服務(wù)器只處理mac地址和ip地址為本機(jī)的數(shù)據(jù)包,即ip_forward默認(rèn)為0柠辞。但是隨著云和虛擬網(wǎng)絡(luò)的發(fā)展赁炎,linux可以作為一個(gè)軟路由轉(zhuǎn)發(fā)路過的數(shù)據(jù)包。因此當(dāng)需要宿主機(jī)處理目的地址非本機(jī)的數(shù)據(jù)包時(shí),需要使能ip_forward徙垫。
// 臨時(shí)生效,永久生效放棒,需要修改/etc/sysctl.conf文件
echo 1 > /proc/sys/net/ipv4/ip_forward
ip_forward在哪個(gè)處理過程中生效
還是用經(jīng)典的netfilter的框架圖姻报。
這里只分析最簡(jiǎn)單的一種情況,單播间螟。下面這段代碼是路由查找過程中的一個(gè)函數(shù)吴旋,從上面的圖看出,在prerouting后厢破,要經(jīng)過router模塊荣瑟,去判斷該包的類型,是送往本機(jī)的包摩泪,還是需要轉(zhuǎn)發(fā)的包笆焰。注意這里的轉(zhuǎn)發(fā)可以是直接出物理網(wǎng)卡,也可以是轉(zhuǎn)發(fā)到主機(jī)的其他虛擬網(wǎng)卡這張圖是三層的處理见坑,能到prerouting進(jìn)行處理嚷掠,說明mac地址是已經(jīng)匹配了的。
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);
// 本機(jī)的包荞驴,繼續(xù)處理
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;
}
// 非本機(jī)的包不皆,如果ip_forward未使能,返回錯(cuò)誤
if (!IN_DEV_FORWARD(in_dev)) {
err = -EHOSTUNREACH;
goto no_route;
}
}
那這里如何從查找路由的結(jié)果中判斷是本機(jī)的包還是非本機(jī)的包熊楼?這就涉及到路由表的知識(shí)霹娄,常用的默認(rèn)存在的三張表 default,main鲫骗,local犬耻。fib_lookup會(huì)根據(jù)目的地址去查找,并將結(jié)果保存到res中挎峦。
// 查看local表的路由香追,目的地址屬于這里就是本機(jī)的包。
[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
// 保存路由表查找結(jié)果的數(shù)據(jù)結(jié)構(gòu)
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的處理流程
如果直接去翻看內(nèi)核代碼坦胶,根本是沒有頭緒的透典,下面的介紹也是閱讀的一種技巧。
1顿苇、 搜索ip_forward關(guān)鍵字峭咒,查看proc的處理函數(shù),及echo 1 > ip_forward后內(nèi)核的處理函數(shù)纪岁。
2凑队、 找到ip_forward設(shè)置的方式,一般會(huì)有宏處理。
3漩氨、 轉(zhuǎn)變思路西壮,查找ip_forward使用的地方。
// 通過ip_forward關(guān)鍵字叫惊,找到proc相關(guān)處理函數(shù)款青,即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)
{
// 這兩個(gè)函數(shù)就是關(guān)鍵點(diǎn),看函數(shù)名霍狰,就可以大概知道作用抡草。
inet_forward_change(net);
inet_netconf_notify_devconf(net, RTM_NEWNETCONF,
NETCONFA_FORWARDING,
NETCONFA_IFINDEX_DEFAULT,
net->ipv4.devconf_dflt);
}
// 查看inet_forward_change函數(shù)
static void inet_forward_change(struct net *net)
{
// 遍歷該命名空間的所有網(wǎng)卡,設(shè)置網(wǎng)卡屬性
for_each_netdev(net, dev) {
struct in_device *in_dev;
in_dev = __in_dev_get_rtnl(dev);
if (in_dev) {
// 這里是重點(diǎn)蔗坯,需要探究這個(gè)宏將這個(gè)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宏康震,清晰明了的看到這個(gè)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的處理流程以及保存在了哪個(gè)數(shù)據(jù)結(jié)構(gòu)中腿短。另外看到set(IN_DEV_CONF_SET)函數(shù),對(duì)應(yīng)就應(yīng)該有g(shù)et函數(shù)鼎兽,那么查找get函數(shù)就可以找到該參數(shù)在哪些流程中生效答姥。
#define IN_DEV_CONF_GET(in_dev, attr) \
ipv4_devconf_get((in_dev), IPV4_DEVCONF_ ## attr)
// 在以下函數(shù)中都有調(diào)用
ip_route_input_mc
__mkroute_input
ip_route_input_slow // 這個(gè)就是上面分析的函數(shù),只是一種情況谚咬。
__mkroute_output