路由

众所周知,华工南校的校园网(IPv4 有线)有两个出口——联通出口和中国教育网出口。通过简单的 traceroute 尝试我们就会发现,访问教育网的服务器会从教育网出口出,而其他所有访问都从联通出口出。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
去往 www.163.com 走的是联通出口:
traceroute to www.163.com (36.250.248.216), 30 hops max, 38 byte packets
1 * ---
2 222.201.128.141 0.772 ms |
3 172.18.5.41 1.526 ms | <----- 校内
4 222.201.129.17 5.874 ms |
5 192.168.66.250 0.944 ms ---
6 210.21.40.13 1.569 ms <----- 广州联通
7 120.80.170.237 2.378 ms
8 120.80.171.101 2.449 ms
9 120.83.0.117 11.380 ms
10 219.158.114.86 11.196 ms
11 103.240.84.54 15.937 ms
12 103.240.84.150 14.534 ms
13 36.250.248.216 16.535 ms

去往 www.sjtu.edu.cn 走的是教育网出口:
traceroute to www.sjtu.edu.cn (202.120.2.119), 30 hops max, 38 byte packets
1 * ---
2 222.201.128.141 8.246 ms |
3 172.18.5.41 2.118 ms | <----- 校内
4 222.201.129.17 1.287 ms |
5 192.168.66.250 0.899 ms ---
6 202.112.19.41 1.270 ms <------- 广州教育网
7 *
8 *
9 101.4.116.34 1.293 ms
10 101.4.112.46 35.867 ms
11 101.4.117.42 35.811 ms
12 101.4.117.45 32.165 ms
13 101.4.115.173 41.123 ms
14 *
15 10.255.38.245 34.936 ms
16 10.255.38.2 32.863 ms
17 10.255.38.33 32.241 ms
18 10.255.38.33 34.276 ms
19 * * *
20 * * *
21 202.120.2.119 32.372 ms

这样设计的想法当然是好的,因为大部分站点的都没有接教育网线路,从教育网访问得通过互通,网络质量可能就会下降。很多其他学校只有教育网出口,问问他们的体验就懂了。而大部分大站都对联通线路友好,出国线路就更别提了,联通秒教育网不知多少条街。但结果就是,大部分的流量都是从联通出口出的,在高峰时段,联通出口就会十分拥挤,具体体现就是延迟增加、丢包率高。

路由部分没多少可研究的部分,基本的现象只要试试就会发现。我也没有检验过是不是前往所有的教育网 IP 都是走教育网出口,或者是不是所有要通过互通的 IP 都是走联通出口,但大致不会差吧。

NAT

上面讲到南校的校园网有教育网和联通两个出口,下面就分别讲讲两个出口的 NAT 大致是什么情况。

教育网出口

我们都知道通过有线上网我们需要手动设置一个像 110.64.*.* 的提前分配好的 IP 地址。我们会惊奇地发现,这竟然是一个公网地址。如果你试过在百度里搜索 ip 的话,你有时候会发现获取到的就是这个 IP。(这个时候,其实你就是通过教育网出口访问的百度)

这时候我们就想,既然我们有了一个公网 IP,那是不是公网上的人可以直接访问到我们的计算机了?

简单尝试一下就发现并不是如此。不管是通过 ICMP,还是 TCP 或 UDP,都不能直接访问我们宿舍的电脑。

从上面的 traceroute 中我们也可以看到,虽然我们拥有一个公网 IP,但我们的电脑并不是直接连到教育网的核心路由器上的,而是最终由边界路由器为我们发送到骨干网。

1
2
3
4
5
6
7
traceroute to www.sjtu.edu.cn (202.120.2.119), 30 hops max, 38 byte packets
1 *
2 222.201.128.141 8.246 ms
3 172.18.5.41 2.118 ms
4 222.201.129.17 1.287 ms
5 192.168.66.250 0.899 ms <------- 边界路由器
6 202.112.19.41 1.270 ms <------- CERNET 骨干网

只能校园网电脑先访问外面的服务器,在边界路由器上打好洞,这样外界发来的包才不会在边界路由器上被丢弃掉。

我们用阿里云深圳的服务器(走教育网出口)来做实验。服务器上开一个 TCP Server,客户端用 nc 去请求。

下面 S: 开头的表示服务端,C: 开头的表示客户端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
S:  s = TCPServer.new '0.0.0.0', 2000
c = s.accept

C: nc 120.79.x.x 2000
> hello

S: c.peeraddr => ["AF_INET", 57032, "110.64.y.y", "110.64.y.y"]
c.recvfrom(1000) => ["hello\n", nil]
c.puts "hi"

C: => hi
TCPDump (Client):
IP 110.64.y.y.57032 > 120.79.x.x.2000: Flags [.], ...
IP 110.64.y.y.57032 > 120.79.x.x.2000: Flags [.], ...

我们发现服务端收到的远端端口就是客户端实际的端口。这也很容易理解,因为校园网用户本来就具有公网 IP。

我们用 UDP 协议来测试具体的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
S:  s = UDPSocket.new
s.bind 120.79.x.x, 2222
s2 = UDPSocket.new
s2.bind 120.79.x.x, 3333

C: echo "Hello" | nc -u -p 50000 120.79.x.x 2222 > nc1

S: s.recvfrom(1000)
=> ["Hello\n", ["AF_INET", 50000, "110.64.y.y", "110.64.y.y"]]
s.send 'Hello', 0, '110.64.y.y', 50000

C: nc1: Hello

S: s2.send 'Hello', 0, '110.64.y.y', 50000
C: nc1: 没有接收到

C: echo "Hello" | nc -u -p 40000 120.79.x.x 3333 > nc2
S: s2.send 'Hello', 0, '110.64.y.y', 50000
C: nc1: 没有接收到

可以看到,只有被发送到的 IP:port 发的包才会被接受通过。

我们可以不把这认为是 NAT,也可以认为这是一个内部 IP:port 和外部 IP:port 永远一致映射,但端口受限的 NAT。

联通出口

学校肯定只有有限的联通出口 IP,每个校园网用户也没有分配到联通的 IP,所以在出口处做 NAT 是不可避免的。

这一次我们直接用 UDP 来做实验,服务端监听 UDP 包,在客户端用 nc 来发 UDP 包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
S1: s = UDPSocket.new
s.bind $server1_ip, 2222
s2 = UDPSocket.new
s2.bind $server1_ip, 3333

S2: s = UDPSocket.new
s.bind $server2_ip, 2222

C: echo "Hello" | nc -u -p 50000 server1_ip 2222 > nc1

S1: s.recvfrom(1000)
=> ["Hello\n", ["AF_INET", 62591, "221.4.34.195", "221.4.34.195"]]
s.send 'Hello', 0, '221.4.34.195', 62591

C: nc1: Hello

S1: s2.send 'Hello', 0, '221.4.34.195', 62591
C: nc1: 没有接收到

C: echo "Hello" | nc -u -p 50000 server1_ip 3333

S1: s.recvfrom(1000)
=> ["Hello\n", ["AF_INET", 19894, "221.4.34.195", "221.4.34.195"]]

C: echo "Hi" | nc -u -p 40000 server1_ip 2222

S1: s.recvfrom(1000)
=> ["Hi\n", ["AF_INET", 64452, "221.4.34.195", "221.4.34.195"]]

C: echo "Hi" | nc -u -p 50000 server1_ip 2222

S1: s.recvfrom(1000)
=> ["Hi\n", ["AF_INET", 62591, "221.4.34.195", "221.4.34.195"]]

C: echo "Hi" | nc -u -p 50000 server2_ip 2222

S2: s.recvfrom(1000)
=> ["Hi\n", ["AF_INET", 3189, "221.4.34.195", "221.4.34.195"]]

# 约一分钟后

C: echo "Hi" | nc -u -p 50000 server_ip 2222

S: s.recvfrom(1000)
=> ["Hi\n", ["AF_INET", 64488, "221.4.34.195", "221.4.34.195"]]

从上面的实验我们可以看到,从同一个 IP:port 在一段时间内发向同一个 IP:port,NAT 后的 port 是不会变的。而任何其他情况,不管是目标地址改变,还是目标端口、发送端口改变,NAT 后的 port 都会改变。

可以认为这是一个对称 NAT。

不切实际的梦想

联通的出口很拥挤,那我就想用教育网,只要我能找到对教育网友好的服务器做代理就行。但我能找到的走教育网出口服务器就只有阿里云,而阿里云的带宽价格十分昂贵,不能承受。

于是我就有这样的一个想法,既然我自己不能走教育网出去,那我让服务器主动把包发到我的教育网地址上不行吗?

对校园网的 NAT 研究了一番后发现这应该是不行的。

首先,要服务器能发包到教育网 IP 上,必须要先打洞。我们可以通过发包给阿里云的服务器来达到打洞的目的。

然后,拿到了洞的地址,我们想要让另一台大带宽服务器(称之为 S)把包发送到这个洞上,这样校园网内部的服务器就可以拿到数据了。

但这里有一个问题,由于教育网出口是一个端口受限的 NAT(姑且这么说),那么对于源地址有限制就更不用说了,所以直接让服务器 S 发包过来是不行的。边界路由器发现包的来源并不是曾经发包出去的目的地址,就拒绝让包通过了。

用通常的穿透端口受限 NAT 的方式又是不行的,因为你不能直接发包给 S,发包给 S 会从联通出口出去,就起不到在教育网出口钻洞的作用了。

唯一可能的方法就是,在服务器 S 那里,篡改包中的源地址。然而我并没有发现能够允许这么干的服务器。一般第一个路由器发现包的源地址有问题,就认为这是个坏包,就扔了。

要是知道有什么强制走教育网出口的好方法,欢迎交流。