Overview
核心内容:了解VPN的两个基本部分(隧道以及加密),涉及到的板块/主题:VPN、TUN/TAP虚拟接口、IP隧道、路由
Tasks
Task 1:网络设置
VPN Client/Host U | 10.0.2.4 |
---|---|
Gateway | 10.0.2.5、192.168.60.1 |
Host V | 192.168.60.101 |
- U和VPN Server之间通信
- VPN Server和V之间进行通信
- Host U和Host V之间不能通信
Task 2:创建和配置TUN接口
Task 2.a:接口命名
直接在py文件中把tun%d
改为jia%d
就行,结果如下
Task 2.b:设置TUN接口
可以看到此时的jia0
端口已经有了对应的IP地址192.168.53.99,并且端口已经打开
Task 2.c:从TUN接口中读取
可以看到打印出来发往192.168.53.0/24的IP包的信息,因为调用了标准的read来从tun文件来读取数据
而当我们ping 192.168.60.0/24内的主机的时候,此时并没有打印出任何信息,因为tun接口的IP地址为192.168.53.99,和192.168.60.1不属于同一个子网,也没有设置相应的路由表项
Task 2.d:写入到TUN接口
如果是echo request包的时候,构造echo response包并写入
while True:
# Get a packet from the tun interface
packet = os.read(tun, 2048)
if True:
ip = IP(packet)
# print(ip.summary())
if ip.proto == 1: # ICMP package
newip = IP(src = ip.dst, dst = ip.src)
newpkt = newip/ICMP(type = 0, code = 0)
os.write(tun, bytes(newpkt))
print(ip.summary())
之后再次选择ping 192.168.53.1
,同时用wireshark抓包,抓包情况如下,tun收到了相应的回显包的请求
向接口中写入任意的数据,而不是IP包的时候
同样运行ping 192.168.53.1,之后wireshark抓包情况如下,可以看出来tun会把写入的东西当作IP数据包来处理
Task 3:通过隧道将IP包发往VPN server
当我们ping 192.168.53.1的时候,内部会有一个IP数据包发往对应的IP地址,之后外层的UDP包是由10.0.2.4发往10.0.2.5的,wireshark抓包如下
当我们选择ping 192.168.60.10的时候,VPN Server端什么都不会打印出来,通过wireshark抓包可以发现,我认为这是由于目的IP和TUN接口不在一个子网当中,并且没有对应的路由表项,所以会将发给192.168.60.0/24的数据包交给10.0.2.4来发送,而此时tun_client.py相当于是不发挥作用
为了解决这个问题,我们需要设置路由,将发给192.168.60.0/24的数据包交给TUN接口来发送
之后再次ping 192.168.60.0/24,VPN_Server端会收到相应的数据包,并且wireshark会显示发包过程
Task 3回顾
对于整个过程要有所理解,首先我们前面之所以ping 192.168.53.0/24能够成功,是因为Host U知道发往这个子网的IP数据包应该交给192.168.53.99(TUN接口)来处理,而之后当我们ping 192.168.60.0/24的时候,并不知道是路由表项,会交给10.0.2.4来处理,这就有了上面我们用wireshark抓包会显示从10.0.2.4这个接口发出的原因,因为这时候TUN接口压根不起作用,而之后,当我们知道要将发给192.168.60.0/24的IP数据包交给TUN接口的时候,我们就可以开始执行后面的code
Task 4:设置VPN服务器
在tun_server.py收到一个来自于隧道当中的包的时候,需要提交给内核,之后内核将其路由到最终目的地。这需要TUN接口来完成
我认为之所以需要TUN接口来运行,其实是相当于是解包的过程,因为在Host U的那一端,我们首先是将数据写入到tun文件当中,这时候相当于是有了一个IP包(里),但是之后这个包会在10.0.2.4那里被放到UDP的有效载荷那里,之后外面再包上UDP头部以及IP头部。现在在VPN server端,其实只能是读取到pkt的信息,也就是里面的IP包那一层,还需要TUN接口来进一步解包
首先,在VPN_Server上面可以根据terminal打印的东西看到,ping 192.168.60.101的数据包是到达了VPN_Server的
之后,我们进行抓包,可以看到在wireshak中,192.168.53.99是向192.168.60.101发送了ICMP request包并且目的主机返回了ICMP的response包的
但是此时根据在VPN_Client运行的TUN接口的termina可以得知,是没有收到相应的数据包的。分析可以得知ICMP request包的确到达,此时单方面ping通
Task 5:处理两个方向上的流量
在VPN server上面,当监听的是socket的时候,那说明是收到了来自于其他地方的包,所以需要将包递交给TUN接口来进一步解包,而当时tun接口的时候,需要把包读出来,通过socket发给Host V
#!/usr/bin/python3 import fcntl import struct import os import time from scapy.all import * IP_A = "0.0.0.0" PORT = 9090 TUNSETIFF = 0x400454ca IFF_TUN = 0x0001 IFF_TAP = 0x0002 IFF_NO_PI = 0x1000 global port sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((IP_A, PORT)) # Create the tun interface tun = os.open("/dev/net/tun", os.O_RDWR) ifr = struct.pack('16sH', b'long%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.100/24 dev {}".format(ifname)) os.system("ip link set dev {} up".format(ifname)) 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)) #we need to send the IP packet to the TUN interface os.write(tun, bytes(pkt)) if fd is tun: # read from the file packet = os.read(tun, 2048) pkt = IP(packet) print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst)) sock.sendto(packet, ("10.0.2.4", port))
在VPN_Client上面,当监听到socket的时候,说明是来自外面的包,需要发给TUN接口,而当是TUN接口的时候,需要利用socket发给VPN_Server
#!/usr/bin/python3 # -*- coding: UTF-8 -*- import fcntl import struct import os import time import select import socket from scapy.all import * TUNSETIFF = 0x400454ca IFF_TUN = 0x0001 IFF_TAP = 0x0002 IFF_NO_PI = 0x1000 PORT = 9090 # Create the tun interface tun = os.open("/dev/net/tun", os.O_RDWR) ifr = struct.pack('16sH', b'jia%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)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 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)) #we need to send the IP packet to the TUN interface os.write(tun, bytes(pkt)) if fd is tun: # read from the file packet = os.read(tun, 2048) pkt = IP(packet) print("From tun ==>: {} --> {}".format(pkt.src, pkt.dst)) sock.sendto(packet, ("10.0.2.5", 9090))
当我们选择ping的时候,可以看到ping成功
对于包流动的分析
数据包流动情况:执行命令时,通过 TUN 发包,client 程序检测到 TUN 接收到包后,读取并通过 socket 发给 VPN server;这时 server 程序又检测到 socket 收到包,取 出内层 IP 包并写到 TUN,VPN server 通过内部网发给主机 V;主机 V 收到 echo-request 包后回 复 echo-reply 包;该包到达 VPN server 后,server 程序又检测到 TUN 收到包,读取并通过 socket 发给主机 U;client 程序又检测到 socket 收到包,读取并写到 TUN 接口,完成通信。
之后再host U上面telnet host V,数据包流动情况和上面基本一样
Task 6:隧道破除实验
关闭tun_client.py,并不能看到输入的内容,此时TCP链接发送RST、ACK包,连接被断开
当我们重新运行时,之前在断开连接后输入的命令会显示出来,并且在运行py文件的terminal上面会有包发送和收到的打印
原因我认为是我们在破坏了VPN隧道之后,数据包不能到达,但是由于是TCP连接,会持续发包一段时间,这段时间内重新建立VPN隧道连接,那么TCP连接就可以重新得以建立,所以之后telnet的命令会显示出来
Task 7:主机V上面的路由实验
- 删除默认条目,添加发往192.168.53.0/24的数据包要通过192.168.60.1这个路由器的条目
- 测试,用主机U ping V,成功
Task 8:试验TUN的IP地址
更改IP后再次ping 192.168.60.101 ,并且我认为这时候一个是需要设置Host U上面的路由的,即需要选择哪个接口来发包,可以发现丢包位置是在VPN_Server
丢包原因:违反了反向路由机制,VPN_Server的应用层是从TUN接口收到数据包的,这时候进行模拟,模拟reply包从哪里发回去,由于未设置路由并且不是在同一个子网当中,所以会选择默认接口(在这里是10.0.2.5)而不是TUN接口,这就造成了输入和输出接口不一致(输入端口是TUN接口,输出是10.0.2.5的接口),所以会在收到这个包的时候将包丢弃
解决方法:在VPN_Server上面设置路由,设置发往192.168.30.0/24子网的包是通过TUN接口的
再次运行ping 192.168.60.101,ping 成功(记得把task 7修改的路由表改回来或者重新添加)
Task 9:试验TAP接口
当我们修改配置对应的TAP接口,命名为jia0,并且设置他的IP地址为192.168.53.99,开始运行ping 192.168.53.0/24
(注意设置对应的路由)
根据相应的代码
可以看到此时会产生一个ARP包,结合上面代码,会发现这是先构造了一个数据包,之后把这个包发送给TAP接口,而之后会由于打印出summary而显示在运行TUN.py的terminal上面
判断是否是ARP请求并返回ARP应答实现思路:在while上面加一个条件判断,当读出的数据包是ARP请求的时候,进行一个write来返回
实现:
arping -I tap0 192.168.53.33
的时候,而在arping -I tap0 1.2.3.4
的时候和下面结果一样
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!