Skip to content

搞英语 → 看世界

翻译英文优质信息和名人推特

Menu
  • 首页
  • 作者列表
  • 独立博客
  • 专业媒体
  • 名人推特
  • 邮件列表
  • 关于本站
Menu

如何使用 tun/tap 在 Python 中发送原始网络数据包

Posted on 2022-09-07

你好!

最近我一直在做一个项目,我在不使用任何库的情况下在 Python 中实现了一堆计算机网络协议的小型玩具工作版本,作为解释计算机网络如何工作的一种方式。

我仍在努力编写那个项目,但今天我想谈谈如何做第一步:用 Python 发送网络数据包。

在这篇文章中,我们将从一个小型 Python 程序发送一个 SYN 数据包(TCP 连接中的第一个数据包),并从example.com获得回复。这篇文章中的所有代码都在这个 gist中。

什么是网络数据包?

网络数据包是一个字节串。例如,这是 TCP 连接中的第一个数据包:

 b'E\x00\x00,\x00\x01\x00\x00@\x06\x00\xc4\xc0\x00\x02\x02"\xc2\x95Cx\x0c\x00P\xf4p\x98\x8b\x00\x00\x00\x00`\x02\xff\xff\x18\xc6\x00\x00\x02\x04\x05\xb4'

我不会在这篇文章中讨论这个字节串的结构(虽然我会说这个特定的字节串有两部分:前 20 个字节是 IP 地址部分,其余的是 TCP 部分)

关键是要发送网络数据包,我们需要能够发送和接收字节串。

为什么 tun/tap?

在 Linux(或任何操作系统)上编写自己的 TCP 实现的问题是——Linux 内核已经有了 TCP 实现!

因此,如果您在普通网络接口上向 example.com 之类的主机发送 SYN 数据包,将会发生以下情况:

  1. 你向 example.com 发送一个 SYN 数据包
  2. example.com 回复 SYN ACK(到目前为止一切顺利!)
  3. 你机器上的 Linux 内核收到 SYN ACK,认为“wtf??我没有建立这个连接??”,然后关闭连接
  4. 你很难过。没有适合您的 TCP 连接。

几年前我和一位朋友谈论过这个问题,他说“你应该使用 tun/tap!”。不过,我花了好几个小时才弄清楚如何做到这一点,这就是我写这篇博文的原因:)

tun/tap 给你一个“虚拟网络设备”

我喜欢思考tun/tap的方式是——想象我的网络中有一台小型计算机,它正在发送和接收网络数据包。但它不是一台真正的计算机,它只是我编写的一个 Python 程序。

老实说,这种解释比我想要的还要糟糕。我希望我能准确理解 tun/tap 设备是如何与真正的 Linux 网络堆栈交互的,但不幸的是我不知道,所以“虚拟网络设备”就是你所得到的。希望下面的代码示例将使这一切变得更加清晰。

tun vs tap

名为“tun/tap”的系统允许您创建两种网络接口:

  • “tun”,可让您设置 IP 层数据包
  • “tap”,可让您设置以太网层数据包

我们将使用tun ,因为这就是我可以弄清楚如何开始工作的方法。水龙头也可能起作用。

如何创建一个tun接口

以下是我创建 IP 地址为 192.0.2.2 的 tun 接口的方法。

 sudo ip tuntap add name tun0 mode tun user $USER sudo ip link set tun0 up sudo ip addr add 192.0.2.1 peer 192.0.2.2 dev tun0 sudo iptables -t nat -A POSTROUTING -s 192.0.2.2 -j MASQUERADE sudo iptables -A FORWARD -i tun0 -s 192.0.2.2 -j ACCEPT sudo iptables -A FORWARD -o tun0 -d 192.0.2.2 -j ACCEPT

这些命令做两件事:

  1. 使用 IP 192.0.2.2创建tun设备(并授予您的用户写入权限)
  2. 设置iptables以使用 NAT 将来自该 tun 设备的数据包代理到互联网

iptables 部分非常重要,否则数据包将只存在于我的计算机内部,不会发送到互联网,那会有什么乐趣?

我不打算解释这个ip addr add命令,因为我不明白它,我发现ip非常难以理解,现在我只能在没有完全理解它们的情况下复制和粘贴ip命令。不过它确实有效。

Python中如何连接tun接口

这是一个打开 tun 接口的函数,你可以像openTun('tun0')一样调用它。我通过在scapy源代码中搜索“tun”找到了如何编写它。

 import struct from fcntl import ioctl def openTun(tunName): tun = open("/dev/net/tun", "r+b", buffering=0) LINUX_IFF_TUN = 0x0001 LINUX_IFF_NO_PI = 0x1000 LINUX_TUNSETIFF = 0x400454CA flags = LINUX_IFF_TUN | LINUX_IFF_NO_PI ifs = struct.pack("16sH22s", tunName, flags, b"") ioctl(tun, LINUX_TUNSETIFF, ifs) return tun

这一切都是

  1. 以二进制模式打开/dev/net/tun
  2. 调用ioctl来告诉 Linux 我们想要一个tun设备,而我们想要的设备称为tun0 (或者我们传递给函数的任何tunName )。

一旦它打开,我们就可以像 Python 中的任何其他文件一样read和write它。

让我们发送一个SYN数据包!

现在我们有了openTun函数,我们可以发送一个 SYN 数据包了!

这是使用openTun函数的 Python 代码的样子。

 syn = b'E\x00\x00,\x00\x01\x00\x00@\x06\x00\xc4\xc0\x00\x02\x02"\xc2\x95Cx\x0c\x00P\xf4p\x98\x8b\x00\x00\x00\x00`\x02\xff\xff\x18\xc6\x00\x00\x02\x04\x05\xb4' tun = openTun(b"tun0") tun.write(syn) reply = tun.read(1024) print(repr(reply))

如果我将其作为sudo python3 syn.py运行,它会打印出来自example.com的回复:

 b'E\x00\x00,\x00\x00@\x00&\x06\xda\xc4"\xc2\x95C\xc0\x00\x02\x02\x00Px\x0cyvL\x84\xf4p\x98\x8c`\x12\xfb\xe0W\xb5\x00\x00\x02\x04\x04\xd8'

显然,这是发送 SYN 数据包的一种非常愚蠢的方式——真正的实现将使用实际代码来生成该字节字符串,而不是对其进行硬编码,并且我们将解析回复而不是仅仅打印出原始字节字符串。但我不想在这篇文章中深入探讨 TCP 的结构,所以这就是我们正在做的事情。

用 tcpdump 查看这些数据包

如果我们在tun0接口上运行 tcpdump,我们可以看到我们发送的数据包和来自example.com的答案:

 $ sudo tcpdump -ni tun0 12:51:01.905933 IP 192.0.2.2.30732 > 34.194.149.67.80: Flags [S], seq 4101019787, win 65535, options [mss 1460], length 0 12:51:01.932178 IP 34.194.149.67.80 > 192.0.2.2.30732: Flags [S.], seq 3300937416, ack 4101019788, win 64480, options [mss 1240], length 0

Flags [S]是我们发送的 SYN, Flags [S.]是响应的 SYN ACK 数据包!我们沟通成功! Linux 网络堆栈根本没有干扰!

tcpdump 还向我们展示了 NAT 是如何工作的

我们还可以在我的真实网络接口( wlp3so ,我的无线网卡)上运行tcpdump ,以查看正在发送和接收的数据包。我们将通过-i wlp3s0而不是-i tun0 。

 $ sudo tcpdump -ni wlp3s0 host 34.194.149.67 tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on wlp3s0, link-type EN10MB (Ethernet), snapshot length 262144 bytes 12:56:01.204382 IP 192.168.1.181.30732 > 34.194.149.67.80: Flags [S], seq 4101019787, win 65535, options [mss 1460], length 0 12:56:01.228239 IP 34.194.149.67.80 > 192.168.1.181.30732: Flags [S.], seq 144769955, ack 4101019788, win 64480, options [mss 1240], length 0 12:56:05.334427 IP 34.194.149.67.80 > 192.168.1.181.30732: Flags [S.], seq 144769955, ack 4101019788, win 64480, options [mss 1240], length 0 12:56:13.524973 IP 34.194.149.67.80 > 192.168.1.181.30732: Flags [S.], seq 144769955, ack 4101019788, win 64480, options [mss 1240], length 0 12:56:29.705007 IP 34.194.149.67.80 > 192.168.1.181.30732: Flags [S.], seq 144769955, ack 4101019788, win 64480, options [mss 1240], length 0

这里有几点需要注意:

  • IP 地址是不同的——上面的 IPtables 规则已将它们从192.0.2.2重写为192.168.1.181 。这种重写称为“网络地址转换”或“NAT”。
  • 我们从example.com收到了一堆回复——它正在做一个指数退避,它在 4 秒后重试,然后是 8 秒,然后是 16 秒。这是因为我们没有完成 TCP 握手——我们只是发送了一个 SYN 并让它挂起!实际上有一种像这样的 DDOS 攻击,称为 SYN 泛洪,但仅仅发送一个或两个 SYN 数据包并不是什么大问题。
  • 我必须添加host 34.194.149.67因为在我的真实 wifi 连接上发送了很多 TCP 数据包,所以我需要忽略这些

我不完全确定为什么我们在wlp3s0上看到比在tun0上更多的 SYN 回复,我猜这是因为我们在 Python 程序中只阅读了 1 个回复。

这很容易而且非常可靠

上次我尝试在 Python 中实现 TCP 时,我使用了一种叫做“ARP 欺骗”的东西。我不会在这里谈论这个(早在 2013 年这个博客上有一些关于它的帖子),但这种方式更可靠。

并且 ARP 欺骗是在您不拥有的网络上进行的一种粗略的事情。

这是代码

我把这篇博文中的所有代码都放在了这个 gist中,如果你想自己尝试一下,你可以运行

bash setup.sh # needs to run as root, has lots of `sudo` commands python3 syn.py # runs as a regular user

它只适用于 Linux,但我认为也有一种方法可以在 Mac 上设置 tun/tap。

scapy 的插头

我将在这里用一个scapy插件作为结尾:它是一个非常棒的 Python 网络库,无需自己编写所有代码即可进行此类实验。

这篇文章是关于自己编写所有代码的,所以我不会多说。

原文: https://jvns.ca/blog/2022/09/06/send-network-packets-python-tun-tap/

本站文章系自动翻译,站长会周期检查,如果有不当内容,请点此留言,非常感谢。
  • Abhinav
  • Abigail Pain
  • Adam Fortuna
  • Alberto Gallego
  • Alex Wlchan
  • Answer.AI
  • Arne Bahlo
  • Ben Carlson
  • Ben Kuhn
  • Bert Hubert
  • Bits about Money
  • Brian Krebs
  • ByteByteGo
  • Chip Huyen
  • Chips and Cheese
  • Christopher Butler
  • Colin Percival
  • Cool Infographics
  • Dan Sinker
  • David Walsh
  • Dmitry Dolzhenko
  • Dustin Curtis
  • Elad Gil
  • Ellie Huxtable
  • Ethan Marcotte
  • Exponential View
  • FAIL Blog
  • Founder Weekly
  • Geoffrey Huntley
  • Geoffrey Litt
  • Greg Mankiw
  • Henrique Dias
  • Hypercritical
  • IEEE Spectrum
  • Investment Talk
  • Jaz
  • Jeff Geerling
  • Jonas Hietala
  • Josh Comeau
  • Lenny Rachitsky
  • Liz Danzico
  • Lou Plummer
  • Luke Wroblewski
  • Matt Baer
  • Matt Stoller
  • Matthias Endler
  • Mert Bulan
  • Mostly metrics
  • News Letter
  • NextDraft
  • Non_Interactive
  • Not Boring
  • One Useful Thing
  • Phil Eaton
  • Product Market Fit
  • Readwise
  • ReedyBear
  • Robert Heaton
  • Rohit Patel
  • Ruben Schade
  • Sage Economics
  • Sam Altman
  • Sam Rose
  • selfh.st
  • Shtetl-Optimized
  • Simon schreibt
  • Slashdot
  • Small Good Things
  • Taylor Troesh
  • Telegram Blog
  • The Macro Compass
  • The Pomp Letter
  • thesephist
  • Thinking Deep & Wide
  • Tim Kellogg
  • Understanding AI
  • 英文媒体
  • 英文推特
  • 英文独立博客
©2025 搞英语 → 看世界 | Design: Newspaperly WordPress Theme