实际上,VPN 客户端和 VPN 服务器是通过 Internet 连接的。为了简单起见,我们将这两台机器直接连接到实验室的同一个局域网,也就是说,这个局域网模拟了互联网。
进行如下测试,确保实验环境设置正确:
Host U 可以和 VPN Server 通信
VPN Server 可以和 Host V 通信
Host U 不能和 Host v 通信
在路由器上运行 tcpdump,并嗅探每个网络上的流量
Task 2: Create and Configure TUN Interface
我们要建立基于 TUN/TAP 技术的 VPN 隧道。TUN 和 TAP 是虚拟网络内核驱动程序;它们实现了完全由软件支持的网络设备。TAP 模拟一个以太网设备,它与第二层包(如以太网帧)一起工作; TUN 模拟一个网络层设备,它与第三层包(如 IP 包)一起工作。有了 TUN/TAP,我们可以创建虚拟网络接口。
tun_server.py 程序从隧道获取数据包之后,需要将数据包提供给内核,这样内核就可以将数据包路由到最终目的地,这同样需要 TUN 接口来完成。我们需要:
创建 TUN 接口并进行配置
从套接字接口获取数据,将接收到的数据作为 IP 包处理
将数据包写入 TUN 接口
# Create the tun interface tun = os.open("/dev/net/tun", os.O_RDWR) ifr = struct.pack('16sH', b'ceyewan%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)) # 这里需要分配 192.168.60.0/24 网段的 IP 地址 os.system("ip addr add 192.168.60.99/24 dev {}".format(ifname)) os.system("ip link set dev {} up".format(ifname))
# Create the tun interface tun = os.open("/dev/net/tun", os.O_RDWR) ifr = struct.pack('16sH', b'ceyewan%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)) os.system("ip addr add 192.168.60.99/24 dev {}".format(ifname)) os.system("ip link set dev {} up".format(ifname)) os.system("ip route add 192.168.53.0/24 dev ceyewan0 via 192.168.60.99")
# listen UDP packet IP_A = "0.0.0.0" PORT = 9090 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((IP_A, PORT)) whileTrue: 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)) os.write(tun, data) if fd is tun: packet = os.read(tun, 2048) pkt = IP(packet) print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst)) sock.sendto(packet, (ip, port))
tun_client.py
#!/usr/bin/env python3
import fcntl import struct import os import time from scapy.allimport *
# Create the tun interface tun = os.open("/dev/net/tun", os.O_RDWR) ifr = struct.pack('16sH', b'ceyewan%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)) os.system("ip addr add 192.168.53.99/24 dev {}".format(ifname)) os.system("ip link set dev {} up".format(ifname)) os.system("ip route add 192.168.60.0/24 dev ceyewan0 via 192.168.53.99")
# Create UDP socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) whileTrue: 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)) os.write(tun, data) if fd is tun: packet = os.read(tun, 2048) pkt = IP(packet) print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst)) sock.sendto(packet, ("10.9.0.11", 9090))
每次重启程序都需要配一遍默认路由,因为程序执行结束后,虚拟网卡就没了,默认路由也就失效了。然后 Host U to Host V 没问题,Host V to Host U 利用之前建立的连接也能通信,但不稳定。因为 Client 端没有监听端口。
Task 6: Tunnel-Breaking Experiment
我们把默认路由配置写到代码里,上面我已经写了。在连接时,我们发现输入的字符可以正确的回显,说明是通路,断开 VPN Server 后,输入字符不能回显。重新启动 VPN Server,发现刚刚的输入都显示出来了,判断是网络一直在重试,然后通了就一下全显示了。后续能正常工作,没有问题。
Task 7: Routing Experiment on Host V
在一个真正的 VPN 系统中,流量将被加密。这意味着返回的流量必须从同一个隧道返回(使用同一个 TLS/SSL 连接)。因此,Host V 的流量要经过 VPN Server 是非常重要的。在实验的设置中,Host V 的路由表有一个默认设置:到任何目的地的数据包,除 了 192.168.60.0/24 网络,将自动路由到 VPN 服务器。在现实世界中,Host V 可能离 VPN 服务器有几个跳,并且默认的路由条目可能不能保证将返回数据包路由回 VPN 服务器。必须正确设置私有网络中的路由表,以确保到隧道另一端的数据包将被路由到 VPN 服务器。
ip route del default ip route add 192.168.53.0/24 via 192.168.60.11
当删除默认路由后,只要我们配置了 Host V 的数据都经过 VPN Server,还是可以正常的进行通信。