Ubuntu做软路由(二):实现MVP

上一篇大概算是给ubuntu做软路由做了一些准备,主要是排除一些干扰项,让我们可以专注于搭建路由器真正核心的任务。
所以现在我们就要开始专注于如何实现路由功能。

还是,这一块我也不是特别熟悉,而且概念我也是遗忘了很多。所以只能说勉强指个方向。

另外,这一块稍微有点麻烦,我又比较懒。review能省就省了,有啥语句不通,错别字什么的,还请见谅。


默认网关

想了半天,觉得还是从网关开始。

先抛开路由器不谈。我们找一台连接了路由器之后的机器,并查看其网络配置。可以看到这些设备都会配置一个默认网关。

于是我们就有了两个概念需要说明:1. 默认网关;2. 网关。

为了说明这两个概念,我们先考虑下面这个例子:
现在假设你有两台电脑A和B,并且有两台路由器C和D。电脑A同时连接C和D,B只连接C。路由器C没有连接外网,D连接了外网。
我们假设B的IP是192.168.1.101,路由器C的IP是192.168.1.1,路由器D的IP是192.168.2.1。显然A有两个网卡,每个网卡都有对应路由器给分配的IP地址,我们假设C给A的IP是192.168.1.100,D给A的IP是192.168.2.102。
显然A和B是可以通过内网的IP相互访问的,这个访问过程显然是通过路由器C。同时显然,A也是可以通过D访问公网的。

问题在于,A是怎么知道访问B的IP时要走路由器C,访问公网时要走路由器D呢?其实这就是A上面配置了网关和默认网关。
我们可以在A上面配置,说凡是目标是192.168.1.0/24这个网段IP的包,都走192.168.1.1这个网关。同时配置一个默认网关是192.168.2.1。
这样当A访问B的IP时,因为找到了一个对应的网关,所以A就会试图根据这个网关的配置访问B。
而当A访问公网时因为找不到任何对应的网关配置,所以A就会选择默认网关。

所以,默认网关大概就是家庭局域网内设备通过路由访问网络的原理。


IP转发

现在我们可以把视角移回路由器。有了网关,我们知道对于路由器来说,不仅仅会收到各种访问自己的流量。更多的时候,路由器会收到这样的流量:“我,192.168.1.200,麻烦帮忙把这个包转发到百度。”
路由器要实现这个,就需要IP转发。

IP转发这个名字就非常科学。
首先它是IP转发,不是TCP转发,也不是UDP转发。这意味着这个转发是IP层的。
进一步,既然是IP层的转发,那么对于TCP来说,是不是握手好了,连接是不是建立了,其实都不是这个IP转发的事情。
其次,他是转发,不是代理。也就是说,这个过程是不是很关注数据包里有什么内容,只是把它发给下一个接受者。

那么到此,如果我们是发送一个UDP包,其实就已经没有任何问题了。但是众所周知,TCP是需要对方回复各种ACK确认的。
对于这个问题,首先他不是IP层需要解决的问题。所以这个问题应该是TCP协议自己去解决。TCP协议怎么做的呢?每一跳都会记录上一跳的IP地址。

对于UDP
很不严谨的说,TCP大概是这样,就是记录每一跳的IP

NAT

抛开UDP不谈,我们基于TCP进一步的说明NAT。
NAT是network address translation的缩写,也就是地址转换。

为什么要NAT转换呢?简而言之,就是当路由器D把流量发给CMCC的时候,因为未来CMCC需要把TCP的各种回复发回来。那么路由器就需要“骗”CMCC说:这个请求是路由器D发给你的。也就是路由器会把自己的IP给CMCC(结合前面网关的部分,对于路由器D来说,默认网关就是指向上游CMCC)。这样后面CMCC回复的时候就知道回复给谁。
这个把A的原始包中,发送者IP替换为路由的IP的过程,就是一个NAT的过程。

当然,当路由器D做NAT的时候,会把A的信息放在TCP的包中。当CMCC根据NAT后的发送者信息把回复发到路由器D的时候,路由器D就会把自己的IP拿掉,再把A的IP换回去,然后发给A。
同理,如果A本身也是一个路由的话,也会做类似的操作,继续往前转发。


动手实现

基于上面两个概念,我觉得已经大体上明确了路由器本身的工作原理:就是每台内网的机器把路由器的IP配置成自己的网关,然后路由器再配置一个NAT
而机器配置网关这个,一方面可以手动配置,二方面这更多是DHCP server去解决的问题,如我们上一篇文章所说,这本不是路由的核心。
所以,现在就剩下唯一一项工作:怎么配置NAT。

打开IP转发

首先,需要说明的是,大多数情况下,linux的各种发行版都是默认禁止了ipv4 forwarding的。所以毫无疑问,我们需要enable这个。毕竟如果连IP转发都直接被禁止了,那肯定是不能工作的。
具体操作可以参考:https://linuxconfig.org/how-to-turn-on-off-ip-forwarding-in-linux

Iptables

Iptables是Linux一个操作Linux内核firewall的工具。通过这个工具,我们可以让Linux内核知道应当如何处理IP包(注意,不是TCP,也不是UDP)。
这里我们就是通过这个工具来实现NAT。

Iptables工作流程镇楼

这里就不进一步讲解iptables四表五链了。我们只需要知道图里每个方块的上半部分(蓝色和绿色)是iptables的表,下半部分(白色)是链。

这里假设前面的网关和路由规则设置都已经正确(具体怎么设置,后面会说),那么从电脑A发送的TCP包大概会经过这样一个链路:

也就是横着的中间部分

其中最核心的是最后一步:NAT(POSTROUTING)。因为前面的几个表更多的是考虑要不要丢弃这些包(也就是firewall禁止访问),所以我们暂时不需要关心。而我们要实现NAT,自然是在最后一步的时候,“偷偷”把发送者IP换成自己的IP。

配置其实也很简单,其实就是一句话:
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
-t nat的意思是,改动nat这张表的规则。-A POSTROUTING表示改动POSTROUTING这条链。-o eth0表示如果output的网卡是eth0时才应用此规则(这里eth0只是给一个例子,需要替换成接公网的那张网卡)。-j MASQUERADE表示此规则采取一个叫做MASQUERADE的动作。

MASQUERADE是一个iptables预定义的一个动作。这个动作就是根据-o的网卡被分配的IP,将原包的发送者IP换成-o所指定的网卡被分配的IP。也就是前面说的NAT。

当然,iptables其实也提供了一个叫做SNAT的动作,这个动作其实也是做NAT的。这个动作和MASQUERADE的区别是:MASQUERADE是自动拿网卡的IP,SNAT需要你自己指定这个IP。
但SNAT是有个特性是,这个手动指定IP可以指定多个IP。公网多线的时候会比较好用,但对于家庭路由就不太合适了。

配置netplan与路由规则

这里就比较Ubuntu only了。这一步的主要目的是配置路由和网关,以及指定各个网口的配置。
对于Ubuntu来说,自带的NetworkManager真是一个令人又爱又恨的东西。这里NetworkManager的功能令人比较满意。
Centos之类的其他,虽然没有预装NetworkManager,但其实也是类似。最终目标都是修改路由表。NetworkManager只是一个比较方便的工具罢了。

NetworkManager下配置就非常简单:
在/etc/netplan里创建一个yaml,也就是编辑/etc/netplan/anyname.yaml

network:
  version: 2
  ethernets:
    eth1:
      addresses: ['192.168.2.1/24']
    eth0:
      dhcp4: yes

也就是说,我们配置连接内网的eth1的ip段为192.168.2.0/24,并且自己的IP是192.168.2.1。而连接公网的eth0直接从上游的光猫的dhcp中拿ip。
配置完毕后,运行netplan apply即可。

完成后,我们再通过route -n查看一下路由表,其中我们需要着重关注FLAGUG的规则,确保带这个flag的那一条路由是走向公网的。也就是前面所说的默认网关应该是走向公网的。
举个例子:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         192.168.3.254   0.0.0.0         UG    100    0        0 eth0
192.168.3.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.3.254   0.0.0.0         255.255.255.255 UH    100    0        0 eth0
192.168.2.0    0.0.0.0         255.255.255.0   U     0      0        0 eth1

其中前三条应该是上游的光猫的DHCP server提供的信息。最后一条的规则是192.168.2.0,mask是255.255.255.0,也就是说有且只有目标地址是192.168.2.0/24的时候,发送到eth1这张网卡。
这就实现了前面的网关和转发。

Iptables的额外要求

到此为止,我们都暂时忽略除了路由本身以外的一切东西,包括安全。因此,到此为止,我们需要保证iptables默认是会Accept任何包。


小结

我这里考虑过要不要把netplan放在iptables前面,因为其实这件事情的实际操作顺序应该是:
1. 用户修改netplan的配置。
2. 然后这个配置apply之后导致路由规则改变。
3. 然后因为路由规则改变导致数据包被发送到iptables横向往右的链路。
4. 用户开启IP转发。
5. 最后,才是iptables的nat表在POSTROUTING前拿到这个包,做NAT。
但前面原理的最后一步是NAT,所以实现的部分就采取就近原则搞了个倒叙。(反正也就才两步)

总的来说,真正落地动手,其实也就三件事:
1. 配netplan,也就7行配置
2. 开IP转发,也就改一行系统配置
3. 添加一条iptables的规则,也就一行命令。


到此为止,这个路由器应该已经可以跑通happy path了,算是把MVP搞定了。
前提是你手动指定这个路由器后面机器的IP(因为没有DHCP),以及指定一个公网的DNS(因为也没有DNS server)。
而且目前这个路由器非常不安全(因为防火墙没有做任何设置)。

蜀ICP备19018968号