Linux ip_forward
linux ip_forward 作用:可以实现路由器的功能,利用 linux 主机的两块网卡(网卡处于不同网段),将数据包在两块网卡之间进行传送,实现两个网络之间的访问
这里使用 3 台虚拟机进行测试,使用 virtual box 创建3个虚拟机,网络管理都添加 host-only (仅主机)网络,与 桥接网络,这样就可以使虚拟机处在两个不同的网段。
也可以在虚拟机设置中,添加两个 host-only 网络,只要使虚拟机都含有两个网段的 ip 地址即可
其实对于两台测试用的主机,每台主机只需要一个网卡就可以了,保证两个 ip 所在网段不同即可,但也要保证和中间转发主机在同一网段,这样就不必执行下面的关闭网卡操作。
两个网段地址
# 私有网络 host-only
172.28.128.0/24
# 桥接网卡
192.168.1.0/24
将 3 台主机标记为 A,B, S。让 主机 S 作为中间转发的设备。
在 win 主机上通过 ssh 连接到 虚拟机,对于 3台 虚拟机,使用的 ssh 连接地址并不相同,因为后续我们需要关闭一些网卡
这里只显示与两个网段先关的 网卡与 IP 信息
172.28.128.0 网段的地址
[root@host-A ~]# ip a
4: enp0s9: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:bd:ca:83 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.104/24 brd 192.168.1.255 scope global noprefixroute dynamic enp0s9
valid_lft 6278sec preferred_lft 6278sec
inet6 fe80::2416:a0e8:dd20:facf/64 scope link noprefixroute
valid_lft forever preferred_lft forever
5: enp0s10: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:7e:85:e7 brd ff:ff:ff:ff:ff:ff
inet 172.28.128.10/24 brd 172.28.128.255 scope global noprefixroute dynamic enp0s10
valid_lft 485sec preferred_lft 485sec
inet6 fe80::87b9:63b4:bd11:4964/64 scope link noprefixroute
valid_lft forever preferred_lft forever
可以得到 A 主机的 ip 地址 与对应的网卡
enp0s9 --- 192.168.1.104
enp0s10 --- 172.28.128.10
[root@host-B ~]# ip a
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:14:11:43 brd ff:ff:ff:ff:ff:ff
inet 172.28.128.6/24 brd 172.28.128.255 scope global noprefixroute dynamic eth1
valid_lft 535sec preferred_lft 535sec
inet6 fe80::a00:27ff:fe14:1143/64 scope link
valid_lft forever preferred_lft forever
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:15:26:84 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.119/24 brd 192.168.1.255 scope global noprefixroute dynamic eth2
valid_lft 6309sec preferred_lft 6309sec
inet6 fe80::a00:27ff:fe15:2684/64 scope link
valid_lft forever preferred_lft forever
可以得到 B 主机的两个 ip 地址
eth2 ---- 192.168.1.119
eth1 ---- 172.28.128.6
root@host-S:~# ip a
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:db:37:e6 brd ff:ff:ff:ff:ff:ff
inet 172.28.128.8/24 brd 172.28.128.255 scope global dynamic eth1
valid_lft 500sec preferred_lft 500sec
inet6 fe80::a00:27ff:fedb:37e6/64 scope link
valid_lft forever preferred_lft forever
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 08:00:27:d3:dc:52 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.105/24 brd 192.168.1.255 scope global dynamic noprefixroute eth2
valid_lft 6030sec preferred_lft 6030sec
inet6 fe80::a00:27ff:fed3:dc52/64 scope link noprefixroute
valid_lft forever preferred_lft forever
可以得到 S 的两个 ip 地址
eth2 ---- 192.168.1.105
eth1 ---- 172.28.128.8
这里我们先测试一个 A 与 B 是否可以 ping 通 | 分别使用 2个 ip 地址测试
[root@host-A ~]# ping -c 4 172.28.128.6
PING 172.28.128.6 (172.28.128.6) 56(84) bytes of data.
64 bytes from 172.28.128.6: icmp_seq=1 ttl=64 time=0.731 ms
64 bytes from 172.28.128.6: icmp_seq=2 ttl=64 time=1.59 ms
64 bytes from 172.28.128.6: icmp_seq=3 ttl=64 time=1.73 ms
64 bytes from 172.28.128.6: icmp_seq=4 ttl=64 time=1.76 ms
--- 172.28.128.6 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3077ms
rtt min/avg/max/mdev = 0.731/1.457/1.767/0.426 ms
[root@host-A ~]# ping -c 4 192.168.1.119
PING 192.168.1.119 (192.168.1.119) 56(84) bytes of data.
64 bytes from 192.168.1.119: icmp_seq=1 ttl=64 time=0.728 ms
64 bytes from 192.168.1.119: icmp_seq=2 ttl=64 time=2.38 ms
64 bytes from 192.168.1.119: icmp_seq=3 ttl=64 time=1.74 ms
64 bytes from 192.168.1.119: icmp_seq=4 ttl=64 time=1.00 ms
--- 192.168.1.119 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3006ms
rtt min/avg/max/mdev = 0.728/1.467/2.386/0.648 ms
可以 ping 通
如果在 宿主机 主机上使用 ssh 工具操作 虚拟机,请注意 使用对应的 ip 地址,因为需要关闭对应的网卡
对于 A 主机,保留 172.28.128.0
网段的 ip 地址,关闭 192.168.1.0
网段对应的 网卡,所以使用 ssh 控制主机 A 时,使用 172.28.128.*
的地址
对应 B 主机,同上,只是保留 192.168.1.0
网段的 ip 地址,关闭 172.28.128.0
对于 S 主机,不需要操作
如果在虚拟机窗口中执行命令,则不必关心上面的操作
# 关闭 192.168.1.0 网段的网卡
[root@host-A ~]# ip link set enp0s9 down
# 将 S 的 172.28.128.0 网段的 ip 设置为 默认路由,用于转发
[root@host-A ~]# ip route add default via 172.28.128.8
[root@host-A ~]# ip route
default via 172.28.128.8 dev enp0s10
# 关闭 172.28.128.0 网段的网卡
[root@host-B ~]# ip link set eth1 down
# 将 主机 S 的 192.168.1.0 网段的 ip 设置为 默认路由
[root@host-B ~]# ip route add default via 192.168.1.105
[root@host-B ~]# ip route
default via 192.168.1.105 dev eth2
主机 S ip_forward 关闭时测试,A, B 主机 ping 测试
# A 主机
[root@host-A ~]# ping -c 4 192.168.1.119
PING 192.168.1.119 (192.168.1.119) 56(84) bytes of data.
--- 192.168.1.119 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3062ms
# B 主机
[root@host-B ~]# ping -c 4 172.28.128.10
PING 172.28.128.10 (172.28.128.10) 56(84) bytes of data.
--- 172.28.128.10 ping statistics ---
4 packets transmitted, 0 received, 100% packet loss, time 3265ms
开启 主机 S ip_forward 功能
# 主机 S
root@host-S:~# echo 1 > /proc/sys/net/ipv4/ip_forward
root@host-S:~# cat /proc/sys/net/ipv4/ip_forward
1
# 主机 A
[root@host-A ~]# ping -c 4 192.168.1.119
PING 192.168.1.119 (192.168.1.119) 56(84) bytes of data.
64 bytes from 192.168.1.119: icmp_seq=1 ttl=63 time=0.701 ms
64 bytes from 192.168.1.119: icmp_seq=2 ttl=63 time=1.53 ms
64 bytes from 192.168.1.119: icmp_seq=3 ttl=63 time=1.99 ms
64 bytes from 192.168.1.119: icmp_seq=4 ttl=63 time=2.27 ms
--- 192.168.1.119 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3035ms
rtt min/avg/max/mdev = 0.701/1.625/2.273/0.597 ms
# 主机 B
[root@host-B ~]# ping -c 4 172.28.128.10
PING 172.28.128.10 (172.28.128.10) 56(84) bytes of data.
64 bytes from 172.28.128.10: icmp_seq=1 ttl=63 time=0.725 ms
64 bytes from 172.28.128.10: icmp_seq=2 ttl=63 time=1.06 ms
64 bytes from 172.28.128.10: icmp_seq=3 ttl=63 time=1.98 ms
64 bytes from 172.28.128.10: icmp_seq=4 ttl=63 time=2.22 ms
--- 172.28.128.10 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3022ms
rtt min/avg/max/mdev = 0.725/1.500/2.228/0.623 ms
测试成功,主机 S 确实实现了 路由器连同两个网段的 功能
在上面的设置中,修改了 A, B 主机默认的路由设置,尝试不修改路由看一下能否 ping 通。
删除路由的命令
# 删除指定 路由
ip route del default via $ip
# 删除最上层的 路由
ip route del default
在 主机 A,B 中,修改默认路由的作用是保证 数据包根据 ip 地址能够通过特定的网卡到达主机 S
如果主机 A 修改了默认路由为 S 的 ip 地址,主机 B 没有修改,则数据包可以通过 A --> S,并且可以通过 S 在网卡之间转发,然后到达B,S --> B。但是 B 的回复数据包,因为没有修改默认路由到 S,所以无法发送到 S,故回复失败。即 A --> S.eth1 --> S.eth2 --> B 成功,B --> ?
如果主机 B 修改,A 没有修改,效果类同上述
如果两个主机 A,B 都没有 修改默认路由,则发送数据包都无法到达 S
以下为个人理解,可能存在错误
如果虚拟机开启了 NAT 网络地址转换,测试时可能会出现问题
ip route add 可以添加默认路由,但是只有最上层的能够使用 linux 只能有一个默认路由
# 默认路由只能添加一个
# 虽然原来有一个 default, 可能因为是自动生成的,所以可以手动添加一个
# 手动添加成功后,便不能添加默认路由
[root@nodejs ~]# ip route
default via 192.168.1.1 dev eth2 proto dhcp metric 104
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
172.28.128.0/24 dev eth1 proto kernel scope link src 172.28.128.6 metric 103
192.168.1.0/24 dev eth2 proto kernel scope link src 192.168.1.119 metric 104
[root@nodejs ~]# ip route add default via 172.28.128.2
[root@nodejs ~]# ip route
default via 172.28.128.2 dev eth1
default via 192.168.1.1 dev eth2 proto dhcp metric 104
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100
172.28.128.0/24 dev eth1 proto kernel scope link src 172.28.128.6 metric 103
192.168.1.0/24 dev eth2 proto kernel scope link src 192.168.1.119 metric 104
~~linux 主机开启 ip_forward 可以实现不同网段之间的联通,但是还是不能实现 路由器连通外网的能力。~~如果 linux 主机有公网 ip,应该可以实现访问外网。
再次记录以下 主机的 ip 地址
# 使用 # 注释的为 关闭的网卡
# 主机 A
# enp0s9 --- 192.168.1.104
enp0s10 --- 172.28.128.10
# 主机 B
eth2 ---- 192.168.1.119
# eth1 ---- 172.28.128.6
# 主机 S
eth2 ---- 192.168.1.105
eth1 ---- 172.28.128.8
在之前的测试中,主机 S 开启了 ip_forward
,使用主机 A 将默认路由设置为 172.28.128.8
主机 S 和 A 的路由表如下:
# 主机 A
[root@host-A ~]# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.28.128.8 0.0.0.0 UG 0 0 0 enp0s10
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
172.28.128.0 0.0.0.0 255.255.255.0 U 102 0 0 enp0s10
# 主机 S
root@host-S:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.1.1 0.0.0.0 UG 100 0 0 eth2
172.28.128.0 0.0.0.0 255.255.255.0 U 0 0 0 eth1
192.168.1.0 0.0.0.0 255.255.255.0 U 100 0 0 eth2
在之前测试 将主机 S 作为中间主机,实现主机 A 访问外网时,使用 ping www.baidu.com
没有回应,便得到了 linux 主机 开启 ip_forward 之后,只能实现不同网段的互通,不能实现访问外网的 错误结论。
最初的考虑的错误原因是 数据包从 A 到达了 S,S 匹配目标 ip 时,ip 不符合 。192.168.1.0
这个网段,所以没有进行转发
后来仔细思考一下,按照 主机 S 的路由表,不符合 192.168.1.0
网段的 ip 应该会被发送到 192.168.1.1
(也就是虚拟机 S 桥接网卡外围局域网的网关), 这个逻辑应该不会有问题。那么为什么 ping
命令会失败呢,这不就是说明无法访问外网吗?貌似之前的想法是对的,再深入思考之后,感觉还是不太对,按照之前的设置来说,A 发送的数据包肯定能经过 S 然后到达外围路由器,那么 ping
命令失败的原因可能是没有收到回复的数据包,也就是说 本来的双向通信,有一个方向不通。
根据上面的假设,我们可以验证一下:
为了简单起见,这里不使用 ping www.baidu.com
,而是使用 ping $ip
。使用 ip
可以避免 DNS 的参与,如果使用 ping www.baidu.com
,也是可以分析的,不过在主机 S 上会看到 主机 A 发送的 DNS 请求包,使用 ip 更加直接。
使用 iftop
工具监控一下网卡的流量,这里 针对两个网卡 监控 2次
# 主机 S eth1 对应 172.28.128.8
root@host-S:~# iftop -i eth1
# 然后在主机 A 上执行 ping ping 220.181.38.150
# 再回到 主机 A 查看结果
从上图可以看到 220.181.30.150
<=> 172.28.128.10
之间的流量,注意最右边的值, 即目标 ip 与 主机 A 之间,只有主机 A 向 目标 ip 发送的数据包,没有回应的包,所以可以说明,主机 A 发送的数据 经过默认路由到达了 主机 S,下面观察主机 S 有没有在网卡之间进行转发
请确保 ip_forward
选项已开启
# 主机 S eth2 对应 192.168.1.105
root@host-S:~# iftop -i eth2
# 然后在主机 A 上执行 ping ping 220.181.38.150
# 再回到 主机 A 查看结果
同样,可以看到 数据包只有 主机 A 向 目标 ip 的流量,可以判断 主机 S 进行了数据转发,数据包从 网卡 eth1
流向了 eth2
因此,主机 A ping
失败的原因在于不能接收到目标 ip 回复数据包
那么问题出在哪里呢?
这里把外围的路由器,即宿主机(win 主机)连接的路由器用 R 表示
我们分析一下数据包的流向,主机
A
—> 主机S.eth1
----> 主机S.eth2
---->R
R 是可以连通外网的,那么数据包回复一定能到达路由器 R,然而 路由器 R 的接口(除了对外的接口)都只存在于一个网段 192.168.1.0
。当路由器收到目标 ip 为 (主机 A 的 ip) 172.28.128.10
时,R没有处于 对应网段的接口,(按照路由器默认路由,数据包再次被路由器送出去,由于目的地址是个局域网 ip,在路由器之间转发过程中会丢失,这里不太确定具体的过程,可能有误)总之数据包不能回到 主机 A 那里。
所以为了让数据包能够找到主机 A,我们需要给路由器添加一个静态路由,目的网段为 172.28.128.0
,子网掩码 255.255.255.0
。重要的是对网关的设置,按照逻辑,我们需要设置 172.28.128.0
网段的 网关 ip,在virtual box
网络管理器中可以查看,我这里是 172.28.128.2
,那是不是将这个地址设置为网关呢?
思考一下,假设我们设置了 172.28.128.2
,那么路由器能够找到这个 ip 对应的主机吗?
是不可以的,因为路由器所有的接口连接到的主机 的 ip 都在 192.168.1.0
这个网段,而 172.28.128.0
这个网段是由 virtual box
在宿主机上虚拟出来的,所以路由器是感知不到 172.28.128.0
这个网段的。设置 172.28.128.2
也就没有什么用。
那么应该设置哪个 ip 呢,答案是 主机 S 在 192.168.1.0
所在网段的 ip,这个 ip 虽然是 virtual box
桥接网卡得到的,但是也相当于连接到了路由器,所以路由器能够感知到这台主机,虽然 arp
表中对应的 mac
地址都是宿主机的 mac
地址,不过不必在意这些,这些都会由 virtual box
桥接网卡的功能来完成。我们的目的是让数据包能找找到接口出来,到达 S,再由 S 转发,所以静态路由的信息应该如下:
目的网络地址 | 子网掩码 | 网关 |
---|---|---|
172.28.128.0 |
255.255.255.0 |
192.168.1.5 (主机 S 的 ip 地址) |
根据上面的信息在路由器中新增一条 静态路由即可
这样设置好之后,就可以梳理一下目标主机回复的数据包的流向:
目标主机 --> --> --> 路由器
R
—> 主机S.eth2
—> 主机S.eth1
—> 主机A
路由表的作用就在于匹配目标 ip 所在的网段,将数据转发到 对应的网关
再次测试 ping
命令,可以看到成功
[root@host-A ~]# ping -c 2 www.baidu.com
PING www.a.shifen.com (220.181.38.150) 56(84) bytes of data.
64 bytes from 220.181.38.150 (220.181.38.150): icmp_seq=1 ttl=51 time=37.8 ms
64 bytes from 220.181.38.150 (220.181.38.150): icmp_seq=2 ttl=51 time=39.3 ms
--- www.a.shifen.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1022ms
rtt min/avg/max/mdev = 37.822/38.581/39.341/0.784 ms
如果失败,有可能是 DNS 没有设置,如果还是失败,请排查原因
# DNS 设置
# vi /etc/resolv.conf
# 添加
nameserver 114.114.114.114
也可以在主机 S 上继续使用 iftop
可以观察到 数据包的双向流动
除了 之前 测试的 主机 S 实现了 主机 A 和 主机 B 不同网段的互相访问;这个借助主机 S,修改路由器的路由表,实现主机 A 访问外网,也说明了 linux 开启 ip_forward 可以实现路由器的功能。
ip route # 查看路由表
route -n
ip neigh # arp 表
arp -na
# 当一些 主机 不在 arp 记录中时,是因为之前两天主机没有通信过,ping 一下,arp 表中就会有记录了
# 添加静态路由
# 在前面的设置中,我们将 主机 S 的 ip 添加为 主机 A,B 的默认路由
# 如果不添加,可以利用下面的方式 更改 ip 对应 的 arp ,也能达到相同的效果
# arp 欺骗
arp -s $ip $mac
# 网卡启停
ip link set $eth up/down
# 重启网络
service network[ing] restart
# 网卡流量监控
# https://www.jb51.net/LINUXjishu/593625.html
iftop -i $eth
# DNS 配置文件
/etc/resolv.conf
# 网卡配置文件
/etc/sysconfig/network-scripts/ifcfg-$eth
# 主机互通 可达性测试
ping -c $num $ip/$domain
# 修改默认路由
ip route add default via $ip
ip route del default
# ip_forward 控制文件 临时有效
/proc/sys/net/ipv4/ip_forward