我养了一条鱼,它死了。我悲伤不已,不想土葬,我想给它火葬,把鱼灰撒回海洋,让它回到母亲的怀抱——谁知道那玩意儿越烤越香,后来我就买了瓶啤酒......很多事情,初衷并不是那样。
其实,我最初看网络的内容,是看到k8s kube-proxy的iptables和ipvs,然后想起来一直想把网络好好看一下,然后就从计算机网络开始了,再然后多播和广播,Linux虚拟网络设备,也都是突然碰到了,然后搞明白了,解答了一些心中的疑惑,然后终于可以开始iptables了。但是本文其实和iptabes没啥关系,本来是想写iptabes的,但是写着写着又发现了一些别的东西,然后也想不到名字了,就假装是iptables(1)吧,具体原因在最后说明一下。
测试环境
之前的环境太弱了,升级一下,四台kvm虚拟机,新绛两个独立的网桥,br1 包含 vm1 vm2,br2包含 vm1vm3 vm4,把vm1的net.ipv4.ip_forward打开,再分别设置为vm1和vm2 vm3的默认路由,这样vm1看起来就像一个路由器一样了。之后会在vm3 vm4运行。测试程序,http server,8080端口,ping pong。
vm1 | 192.168.11.1 | 12:00:00:00:00:00 |
192.168.22.1 | 12:00:00:00:00:01 | |
vm2 | 192.168.11.2 | 12:00:00:00:00:02 |
vm3 | 192.168.22.3 | 12:00:00:00:00:03 |
vm4 | 192.168.22.4 | 12:00:00:00:00:04 |
vm1
vm1
vm2
vm3
vm4
tcpdump -i ens3 tcp port 8080 -vvv -n -S -e
直接交付
直接访问,clientserver处于同一网段。,三次握手、http请求响应、四次挥手,都是类似的,所以这里就只说三次握手,三次握手的三次交互,c->s s->c c->s,从地址上来说是相反的,你和谁握手,自然也和谁交互,最后和谁分手,所以也只说第一次交互吧,后面都这样。
vm4 client
vm3 server
vm1 网关 & 路由器router & gateway
由于处于同一个网络,所以属于直接交付,不需要经过router,只有client和server能抓到包,包的内容一模一样的(除了时间戳)。在数据链路层,client -> server,在网络层,client -> server,这个情况比较简单。
通过路由转发
通过路由转发,clientserver处于不同的子网,通过router连接起来。
vm2 client的视角,在数据链路层,client -> router,在网络层,client -> server(router有两个ip和mac,只能用同一网段的mac,下文雷同)。网络层就是就是最最原始的client -> server,但是数据链路层的目的mac不是server的,而是router的,client发现目的地不在同一个网段,于是查找路由表,木有找到专用路由,走默认路由,通过router发送,目的mac就是router在当前网络下的mac。
vm3 server的视角,在数据链路层,router -> server,在网络层,client -> server。网络层依然是最最原始的client ->server,但是数据链路层的源mac不是client的,而是router的,因为router做了一个中转。
vm1 网关 & 路由器的视角,可以理解为router就是一个中继。router抓到的包比较复杂一些,数量也多了一倍。依然只说三次握手的第一次交互,网络层依然是最最原始的client -> server,这个永远不变(这里也是可以变的,比如后面要说的nat模式)。在数据链路层,第一次握手,首先是client -> router,然后是router -> server,router出现了两次,但是两次的mac是不一样的,因为router又两个mac。嗯,这就是路由器,从数据链路层来看,相当于一个中继。
ip_forward
出于安全考虑,Linux系统默认是禁止数据包转发的。所谓转发即当主机拥有多于一块的网卡时,其中一块收到数据包,根据数据包的目的ip地址将数据包发往本机另一块网卡,该网卡根据路由表继续发送数据包。这通常是路由器所要实现的功能。要让Linux系统具有路由转发功能,需要配置一个Linux的内核参数net.ipv4.ip_forward。这个参数指定了Linux系统当前对路由转发功能的支持情况;其值为0时表示禁止进行IP转发;如果是1,则说明IP转发功能已经打开。
上一节测试时打开ip_forward的,如果关闭会怎么样呢,试一下,所有条件和上一节一致,只是关闭ip_forward。sysctl -w net.ipv4.ip_forward=0
请求超时,直接ctrl+c结束,看一下抓到的包,这次server没有收到任何东西。client和router的包是一样的,这里只截了3个,后面还有,都是第一次握手。在数据链路层,client -> router,在网络层,client -> router,这个情况和直接交付有点儿类似,这个时候router其实不再是一个router了,有点儿像一个普通主机,因为本地并没有一个这样的服务端运行,所以最终请求还是失败了。其实这里用tcp测试更好一些,router收到第一次握手的包,然后本地没有开启ip_forward,协议栈发现目的ip不是本机的,也不是广播包,所以就扔了,client并不知道router其实已经不能路由了,以为是网络状况不好,发送失败或者没收到响应,就去重试,直到超时(协议栈应该有个超时限制,应用层也有超时时间,也就是经常看到的connection timeout)
作为对比,再试一种情况,如果处于同一网段,但是server并没有运行响应服务,会怎么样呢。client server抓到的包是一样的,看一下吧,第一次握手,然后收到一个rst。
直接交付但是服务端挂了和通过一个坏的路由交付,结果是不一样的,应该是直接交付,运输层收到包以后,找不到对应端口的服务,所以就回一个rst,但是对于路由,网络层收到包之后,发现不是自己的包,也不是广播的包,所以就扔了,所以客户端不断重试直到超时。
NAT
先看一下nat上网的包长啥样吧,找一台有公网ip的机器,运行服务端,通过我自己的机器去请求服务。111.199.190.226是我自己的ip,就不隐藏了,公网那台机器为了隐私,截图中替换了。可以看到,在我自己的公网出口之前的部分已经被完全屏蔽了,最上层的一段就是我的公网IP和服务器公网IP之间的交互。
让内网中的主机访问互联网其实我们就有了两种方式。nat是在网络层以及传输层实现的;更多的它操纵的是网络层的内容。虽然说nat也可以实现所谓让那些私网地址的客户端经过地址转换以后访问互联网上的主机,不过,nat早期所实现的主要目的不是为了让你访问互联网,而是为了隐藏本地网络中的主机的,我们上网用的nat其实属于snat。所以nat最初出现的目的是为了安全性的。另一种方式是proxy,代理通常是在应用层实现的;通常它代理对于某一特定应用的请求。
开头说了,本来是想开始iptables的,但是发现了一个问题,环境并不适合,问题在于router,现在的router是连接连个子网,它其实是一个中心,在两个子网里面它都是网关,所以严格来说这不是一个二层拓扑,所以这个环境是严格来说是局域网的模型。真正的二层拓扑是,router在另一个网络里面只是一个普通节点,不是网关,我们不可能让我们的router在公网也是个网关,不合理,所以另个一网络应该有它自己的网关,所以下一次要把环境再调整一下,敬请期待吧。