本实验本身并不是最终大作业(Virtual Private Network (V*N) Lab)的必做前置内容,在完成了PKI实验与TLS实验后即可完成大作业。不过依旧按照教学大纲继续完成本项实验T_T
本次实验仅涉及V*N实现过程中管道传送的实现,而并不设计加密保护的内容。
The lab covers the following topics:
• Virtual Private Network
• The TUN/TAP virtual interface
• IP tunneling
• Routing
本来client与V*N服务器应该通过网络连接,不过为了简化实验,这里将其安置在同一局域网下(即虚机可与容器直接相连)。但即使这样,为了实验准确性,不应使主机U与主机V直接相连。
本次实验依旧是VM20.04上推荐进行的实验,在官网上下载[LabSetup]并放入虚机,按照PKI与TLS中的方法进行运作。默认使用的是 docker-compose.yml
而不是其2
,2是用于task 8的双私网环境的服务器搭建。
注意运行时某个过去用过的容器可能占用了本次实验的ip网段,可以使用docker rm containerName
将其删除。
本实验中包嗅探将是一个常用的调试方式,有以下几种方式可供使用:
tcpdump -i eth0 -n
端口名应做适当调整,选择自己希望探查的端口。tcpdump在容器中运行时由于实验设置,仅能嗅探自身的网络报,在主虚机中理论上可以嗅探每一个容器及自身。wireshark同理。
完成以下来证明完成了实验设置:
• Host U can communicate with VN Server.
• VN Server can communicate with Host V.
• Host U should not be able to communicate with Host V.
• Run tcpdump on the router, and sniff the traffic on each of the network. Show that you can capture packets.
192.168.60.1
,结果只看到了了ARP报文,之后再ping192.168.60.11
才找到了ICMP报文:至此完成了实验环境的配置与确认。
V*N管道基于TUN/TAP技术,TAP负责仿真一个接口设备并操作2层数据帧,TUN负责仿真一个网络层设备并操作3层IP报文。
本节实验基于以下代码进行修改:
#!/usr/bin/env python3
import fcntl
import struct
import os
import time
from scapy.all import *
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
# Create the tun interface
tun = os.open("/dev/net/tun", os.O_RDWR)
ifr = struct.pack('16sH', b'tun%d', IFF_TUN | IFF_NO_PI)
ifname_bytes = fcntl.ioctl(tun, TUNSETIFF, ifr)
# Get the interface name
ifname = ifname_bytes.decode('UTF-8')[:16].strip("\x00")
print("Interface Name: {}".format(ifname))
while True:
time.sleep(10)
在主机U上直接运行该程序,由于root与seed不是同一个用户也不是同一个用户组,所以添加权限后执行:
该程序会创建一个接口tun0
,但当程序结束时该接口也会被撤销,因此最后使用循环锁住该程序。我们当然可以使其隐式执行,但这里选择另开一个窗口,使用ip address
查看当前各个接口状态:
文档还要求我们将该接口前缀改为姓,所幸py中比较好找,在一片生成接口的函数中找到并修改即可:
使用以下指令进行接口的地址分配与状态修改
// Assign IP address to the interface
# ip addr add 192.168.53.99/24 dev tun0
// Bring up the interface
# ip link set dev tun0 up
同时我们也可以在py代码中添加以下,来使每次打开接口自动设置:
os.system("ip addr add 192.168.53.99/24 dev {}".format(ifname))
os.system("ip link set dev {} up".format(ifname))
在加入配置命令后再次打开,使用指令查看接口,可以发现为接口分配的新接口编号递增,在之后的实验中我们将能发现其上限是很高的。
使用以下代码替换源码中的while循环以实现读取:
while True:
# Get a packet from the tun interface
packet = os.read(tun, 2048)
if packet:
ip = IP(packet)
print(ip.summary())
替换后执行,发现并没有报文被读取,这是因为接口并没有收到任意一份数据。完成以下任务:
• On Host U, ping a host in the 192.168.53.0/24 network. What are printed out by the tun.py program? What has happened? Why?
• On Host U, ping a host in the internal network 192.168.60.0/24, Does tun.py print out anything? Why?
192.168.53.1
与192.168.53.2
时收到以下报文,说明系统会将该网段的报文自动发向该接口:192.168.53.99
即我们的管道接口时并不会在该接口收到报文,而发出ping请求的终端却能收到报文。猜测是因为系统发现是发送给自己地址,所以替换为环回地址了。使用tcpdump
监听一下环回地址,果然如此:实验手册给出了以下范例代码,【但实际上屁用没有,要去网上查:
# Send out a spoof packet using the tun interface
newip = IP(src='1.2.3.4', dst=ip.src)
newpkt = newip/ip.payload
os.write(tun, bytes(newpkt))
本节要求完成:
• After getting a packet from the TUN interface, if this packet is an ICMP echo request packet, construct a corresponding echo reply packet and write it to the TUN interface. Please provide evidence to show that the code works as expected.
• Instead of writing an IP packet to the interface, write some arbitrary data to the interface, and report your observation.
使用以下代码写入一个报文以模拟收到报文:
# Receive a packet
newip = IP(src='1.2.3.4', dst='192.168.53.99')
newpkt = newip/ICMP()/b'123'
os.write(tun, bytes(newpkt))
由于任何ip起点与终点是本机的报文都会被内核接收(由task 4得知),不符合网段要求的报文也会被拒绝,所以这里发送与接收的报文仅用于展示代码可以生成对应报文。写入一条报文并生成对应的报文:
部分代码:
......
# Simulate receiving a packet
newip = IP(src='1.2.3.4', dst='192.168.53.12')
newpkt = newip/ICMP()/b'123'
os.write(tun, bytes(newpkt))
while True:
# Get a packet from the tun interface
packet = os.read(tun, 2048)
if packet:
ip = IP(packet)
print(ip.summary())
print(ip.payload)
if ICMP in ip:
# Send out a packet using the tun interface
newip = IP(src=ip.dst, dst=ip.src)
newpkt = newip/ip.payload
os.write(tun, bytes(newpkt))
print('Send out:')
print(IP(bytes(newpkt)).summary())
随意写点东西进去:
os.write(tun,bytes('123123'))
返回错误信息表示拒绝未经编码的数据:
Traceback (most recent call last):
File "./tun.py", line 31, in
os.write(tun,bytes('123123'))
TypeError: string argument without an encoding
将一个完整的IP报文放入TCP或UDP报文,并将其发送给目标,这就是通过管道发送报文。我们这一次task使用的是UDP。
tun_server.py
始终监听9090端口并输出展示一切接收到的报文,并且拆解报文展示内部的报文。
#!/usr/bin/env python3
from scapy.all import *
IP_A = "0.0.0.0"
PORT = 9090
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((IP_A, PORT))
while True:
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
pkt = IP(data)
print(" Inside: {} --> {}".format(pkt.src, pkt.dst))
客户端部分代码替换为:
# Create UDP socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
# Get a packet from the tun interface
packet = os.read(tun, 2048)
if packet:
# Send the packet via the tunnel
sock.sendto(packet, (10.9.0.11, 9090))
最后服务器ip与端口都要修改为服务器对应的内容。
分别运行上述程序,并在客户端ping192.168.53.2
,则能在服务器上接收到以下信息:
理由如下:客户机会自动将
192.168.53.0/24
转到我们之前设定的sun0接口,然而其并不是真实(虚拟)的接口,并不能真正发送报文。但是我们的程序却能读取并将其打包发送给我们设定的服务器。服务器将其解包,并读取到我们发送的报文实际上是从53.99发向53.2
但是这样并不能使我们ping到我们的Host V
,所以我们应该在客户机上添加ip导向,将报文导向我们的sun0。使用以下指令添加:
# ip route add dev via
同时我们也可以直接在代码中添加以下以永久执行:
# Send Host-V package to sun0
os.system("ip route add {} dev {} via 192.168.53.99".format(Host-V, ifname))
这样我们的服务器就可以接收到了。
一个linux系统默认自己会是一个主机而并非一个网关,但是docker已经帮我们设置好了,因此其收到的报文如果满足转发条件则一定会转发:
sysctls:
- net.ipv4.ip_forward=1
设置服务器以使其能够同样实现隧道,满足以下功能:
• Create a TUN interface and configure it.
• Get the data from the socket interface; treat the received data as an IP packet.
• Write the packet to the TUN interface.
修改保留的tun.py与之前的while代码为新的tun_server.py:
#!/usr/bin/env python3
import fcntl
import struct
import os
import time
from scapy.all import *
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
Host_U = '192.168.53.0/24'
# Create the tun interface
tun = os.open("/dev/net/tun", os.O_RDWR)
ifr = struct.pack('16sH', b'sun_s%d', IFF_TUN | IFF_NO_PI)
ifname_bytes = fcntl.ioctl(tun, TUNSETIFF, ifr)
# Get the interface name
ifname = ifname_bytes.decode('UTF-8')[:16].strip("\x00")
print("Interface Name: {}".format(ifname))
# Assign an IP address & bring up the interface
os.system("ip addr add 192.168.78.100/24 dev {}".format(ifname))
os.system("ip link set dev {} up".format(ifname))
# Send Host-V package to sun0
os.system("ip route add {} dev {} via 192.168.78.100".format(Host_U, ifname))
# Simulate receiving a packet
#newip = IP(src='192.168.53.99', dst='192.168.53.12')
#newpkt = newip/ICMP()/b'123'
#os.write(tun, bytes(newpkt))
IP_A = "0.0.0.0"
PORT = 9090
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((IP_A, PORT))
while True:
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
packet = IP(data)
print(" Inside: {} --> {}".format(packet.src, packet.dst))
if packet:
ip = packet
if ICMP in ip:
# Send out a packet using the tun interface
os.write(tun, bytes(ip))
print('Send out:')
print(ip.summary())
print()
通过输出我们可以看到代码正确运行发送了隧道中传送的ICMP报文:
通过wireshark我们可以更清楚地看到,我们的Host-V已经做出了它的回应,但很显然客户机并没有收到,这将在接下来的task中得到完善:
使用以下代码可以实现监视多个接口,使用select.select
实现通信监控,相关内容:
# We assume that sock and tun file descriptors have already been created.
while True:
# this will block until at least one interface is ready
ready, _, _ = select.select([sock, tun], [], [])
for fd in ready:
if fd is sock:
data, (ip, port) = sock.recvfrom(2048)
pkt = IP(data)
print("From socket <==: {} --> {}".format(pkt.src, pkt.dst))
... (code needs to be added by students) ...
if fd is tun:
packet = os.read(tun, 2048)
pkt = IP(packet)
print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst))
... (code needs to be added by students) ...
省流版,代码完成,客户机与服务器代码相同,仅发送的IP地址不同,连监听端口都一致(因为实际上这部分的客户机负责了本应是服务器该负责的工作,类似于当前流行的(魔fan法qiang)工具),这部分展示的客户机的部分代码是负责接收与转发的关键部分:
# Create UDP socket
IP_A = "0.0.0.0"
PORT = 9090
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((IP_A, PORT))
while True:
# this will block until at least one interface is ready
ready, _, _ = select.select([sock, tun], [], [])
for fd in ready:
if fd is sock:
# IP 0.0.0.0 Port 9090: Has receive a VPN package!!!
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
packet = IP(data)
print(" Inside: {} --> {}".format(packet.src, packet.dst))
if packet:
ip = packet
# Send out a packet using the tun interface
os.write(tun, bytes(ip))
print('Send out:')
print(ip.summary())
print()
if fd is tun:
# Get a packet from the tun interface
packet = os.read(tun, 2048)
pkt = IP(packet)
print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst))
if packet:
# Send the packet via the tunnel
sock.sendto(packet, ('10.9.0.11', 9090))
在保持远程登录在线状态的同时,打破客户端或服务器的隧道服务,都出现了同样的情况,即无法在远程登录界面输入任何文字,并且不再会有新的输出。当短时间重新连回服务时,积压在TUN文件的报文缓存就会被逐个释放。如图:
本次实验简化了网络环境,在私网机上设置的除了192.168.60.0/24
的流量外均发往VpN服务器。但实际情况远非如此。因此可以在私网机上进行设置,将发往所有处在隧道另一端的网段的报文全都默认导向VpN服务器,即相当于Task 3最后一部分的工作。
// Delete the default entry
# ip route del default
// Add an entry
# ip route add 192.168.53.0/24 via 192.168.60.11 # 服务器网址
本节的网络拓扑结构如图所示。要求通过架设隧道以实现两端互通并证明:
可以通过观察docker配置文件发现,2仅在1的基础上为所谓的客户机增加了两台子机。
通过以下指令运行容器:
$ docker-compose -f docker-compose2.yml build
$ docker-compose -f docker-compose2.yml up
$ docker-compose -f docker-compose2.yml down
之前的所有实验都是基于TUN技术,TAP是与TUN十分类似的技术,以至于其代码也极其相似。TUN的接口时建立在第三层网络层的,而TAP则是建立在第二层MAC层的。因此,TAP上的报文应包括MAC报文头。
我们依然使用之前的容器就可以进行实验。唯一需要修改的就是我们设置接口时设置为TAP,如图:
我们之前将隧道收到的报文都视为IP报文,但是改为TAP后实际收到的是MAC报文,应在原来的代码中将收到的二进制数据解析为Ether
,这样就能正确解析,但仍然无法完成通信:
要完成以上通信,必须要先完善ARP的通信过程。这里附上ARP协议格式:
# scapy官方文档的ARP格式
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HWTYPE | PTYPE |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HWLEN | PLEN | OP |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HWSRC | PSRC |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| HWDST | PDST |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
具体含义参考本篇。其中,每个字段的含义如下。
通过修改代码,可以实现arping
#!/usr/bin/env python3
# tap_client.py
import fcntl
import struct
import os
import time
from scapy.all import *
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
Host_V = '192.168.60.0/24'
LOCAL_IP = '192.168.53.99'
# Create the tap interface
tap = os.open("/dev/net/tun", os.O_RDWR)
ifr = struct.pack('16sH', b'sun_c%d', IFF_TAP | IFF_NO_PI)
ifname_bytes = fcntl.ioctl(tap, TUNSETIFF, ifr)
# Get the interface name
ifname = ifname_bytes.decode('UTF-8')[:16].strip("\x00")
print("Interface Name: {}".format(ifname))
# Assign an IP address & bring up the interface
os.system("ip addr add {}/24 dev {}".format(LOCAL_IP, ifname))
os.system("ip link set dev {} up".format(ifname))
# Send Host-V package to sun0
os.system("ip route add {} dev {} via {}".format(Host_V, ifname, LOCAL_IP))
# Simulate receiving a packet
#newip = IP(src='192.168.53.99', dst='192.168.53.12')
#newpkt = newip/ICMP()/b'123'
#os.write(tap, bytes(newpkt))
# Create UDP socket
IP_A = "0.0.0.0"
PORT = 9090
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((IP_A, PORT))
while True:
# this will block until at least one interface is ready
ready, _, _ = select.select([sock, tap], [], [])
for fd in ready:
if fd is sock:
# IP 0.0.0.0 Port 9090: Has receive a VPN package!!!
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
ether = Ether(data)
print(" Inside: {} --> {}".format(ether.src, ether.dst))
if ether:
if ARP in ether and ether[ARP].op == 1 :
GOAL_MAC = getmacbyip(ether[ARP].pdst)
arp = ether[ARP]
newether = Ether(dst=ether.src, src=GOAL_MAC)
newarp = ARP(psrc=arp.pdst, hwsrc=GOAL_MAC,pdst=arp.psrc, hwdst=ether.src, op=2)
newpkt = newether/newarp
print("***** Fake response: {}".format(newpkt.summary()))
sock.sendto(bytes(newpkt), ('10.9.0.11', 9090))
else:
# Send out a packet using the tap interface
ether
os.write(tap, bytes(ether))
print('Send out:', ether.summary())
print()
if fd is tap:
# Get a packet from the tap interface
packet = os.read(tap, 2048)
ether = Ether(packet)
print(ether.summary())
print("From tap ==>: {} --> {}".format(ether.src, ether.dst))
if ether:
# Send the packet via the tunnel
sock.sendto(bytes(ether), ('10.9.0.11', 9090))
注意这里的代码并不能实现ping,不知道理由。
二编!!ping通了!!!(时隔7天,在yw的指导下,yw yyds!!!)
这里VPN服务器在写入tap接口时应当把接口想象成目标接口dst
而不是发送接口src
,因为实际上这个接口毕竟是我们虚拟出来的,并不能将报文进行直接发送,而是要将其转入内核,让内核选择可以执行发送的接口进行发送。而为了让我们的虚拟接口tap执行转发,即使其属于我们的服务器主机,但也应将其视为目标接口,这样tap就会认为自己收到一个报文并将其转发,否则就会将其放入缓存直至被丢弃。
新的代码!!
#!/usr/bin/env python3
# tap_client.py
import fcntl
import struct
import os
import time
from scapy.all import *
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
Host_V = '192.168.60.0/24'
LOCAL_IP = '192.168.53.99'
TAP_MAC = 'aa:aa:aa:aa:aa:aa'
# Create the tap interface
tap = os.open("/dev/net/tun", os.O_RDWR)
ifr = struct.pack('16sH', b'sun_c%d', IFF_TAP | IFF_NO_PI)
ifname_bytes = fcntl.ioctl(tap, TUNSETIFF, ifr)
# Get the interface name
ifname = ifname_bytes.decode('UTF-8')[:16].strip("\x00")
print("Interface Name: {}".format(ifname))
# Assign an IP address & bring up the interface
os.system("ip addr add {}/24 dev {}".format(LOCAL_IP, ifname))
os.system("ip link set dev {} addr {} up".format(ifname, TAP_MAC))
# Send Host-V package to sun0
os.system("ip route add {} dev {} via {}".format(Host_V, ifname, LOCAL_IP))
# Create UDP socket
IP_A = "0.0.0.0"
PORT = 9090
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((IP_A, PORT))
while True:
# this will block until at least one interface is ready
ready, _, _ = select.select([sock, tap], [], [])
for fd in ready:
if fd is sock:
print('# IP 0.0.0.0 Port 9090: Has receive a VPN package!!!')
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
ether = Ether(data)
print(" Inside: {} --> {}".format(ether.src, ether.dst))
if ether:
if ARP in ether and ether[ARP].op == 1 :
GOAL_MAC = TAP_MAC
arp = ether[ARP]
newether = Ether(dst=ether.src, src=GOAL_MAC)
newarp = ARP(psrc=arp.pdst, hwsrc=GOAL_MAC,pdst=arp.psrc, hwdst=ether.src, op=2)
newpkt = newether/newarp
print("***** ARP fake response: {}".format(newpkt.summary()))
sock.sendto(bytes(newpkt), ('10.9.0.11', 9090))
print()
else:
# Send out a packet using the tap interface
ether[Ether].dst = TAP_MAC
os.write(tap, bytes(ether))
print('Send out:', ether.summary())
print()
if fd is tap:
# Get a packet from the tap interface
packet = os.read(tap, 2048)
ether = Ether(packet)
print("From tap ==>: {} --> {}".format(ether.src, ether.dst))
print()
if ether:
# Send the packet via the tunnel
sock.sendto(bytes(ether), ('10.9.0.11', 9090))
#!/usr/bin/env python3
# tap_server.py
import fcntl
import struct
import os
import time
from scapy.all import *
TUNSETIFF = 0x400454ca
IFF_TUN = 0x0001
IFF_TAP = 0x0002
IFF_NO_PI = 0x1000
Host_U = '192.168.50.0/24'
LOCAL_IP = '192.168.78.100'
TAP_MAC = 'aa:aa:aa:aa:aa:bb'
# Create the tap interface
tap = os.open("/dev/net/tun", os.O_RDWR)
ifr = struct.pack('16sH', b'sun_s%d', IFF_TAP | IFF_NO_PI)
ifname_bytes = fcntl.ioctl(tap, TUNSETIFF, ifr)
# Get the interface name
ifname = ifname_bytes.decode('UTF-8')[:16].strip("\x00")
print("Interface Name: {}".format(ifname))
# Assign an IP address & bring up the interface
os.system("ip addr add {}/24 dev {}".format(LOCAL_IP, ifname))
os.system("ip link set dev {} addr {} up".format(ifname, TAP_MAC))
# Send Host-V package to sun0
os.system("ip route add {} dev {} via {}".format(Host_U, ifname, LOCAL_IP))
# Create UDP socket
IP_A = "0.0.0.0"
PORT = 9090
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind((IP_A, PORT))
while True:
# this will block until at least one interface is ready
ready, _, _ = select.select([sock, tap], [], [])
for fd in ready:
if fd is sock:
# IP 0.0.0.0 Port 9090: Has receive a VPN package!!!
data, (ip, port) = sock.recvfrom(2048)
print("{}:{} --> {}:{}".format(ip, port, IP_A, PORT))
ether = Ether(data)
print(" Inside: {} --> {}".format(ether.src, ether.dst))
if ether:
if ARP in ether and ether[ARP].op == 1:
GOAL_MAC = TAP_MAC
arp = ether[ARP]
newether = Ether(dst=ether.src, src = GOAL_MAC)
newarp = ARP(psrc=arp.pdst, hwsrc=GOAL_MAC,pdst=arp.psrc, hwdst=ether.src, op=2)
newpkt = newether/newarp
print("***** GOAL response: {}".format(newpkt.summary()))
sock.sendto(bytes(newpkt), ('10.9.0.12', 9090))
print()
else:
# Send out a packet using the tap interface
ether[Ether].dst = TAP_MAC
os.write(tap, bytes(ether))
print('Send out:', ether.summary())
print()
if fd is tap:
# Get a packet from the tap interface
packet = os.read(tap, 2048)
ether = Ether(packet)
print("From tap ==>: {} --> {}".format(ether.src, ether.dst))
print()
if ether:
# Send the packet via the tunnel
sock.sendto(bytes(ether), ('10.9.0.12', 9090))
还有ping通之后的截图!!!!!!!!!!!!!
但是由于VPN的特殊性,我们的两个tap接口并不能进行ping,只有两边的HOST_U和HOST_V可以互相ping
没啥好总结的,task9让我心力交瘁。
二编:
yw yyds!!!