我知道在 Linux 上从现有网络接口写入和接收网络数据包的两种主要方法。第一种是使用经典套接字 API,其中 Linux 内核负责管理我发送和接收的数据的所有 TCP/IP 封装。第二种是使用 AF_PACKET 接口,我在其中读取和写入原始 IP(甚至以太网)数据包到接口。我搁置了 TAP/TUN 设备、veth 对、网桥等,因为在我的用例中我无法创建任何新的网络接口,只能使用现有的接口。
使用 AF_PACKET 方法,如果我想发送和接收数据并参与“真实”的 TCP 连接,似乎我必须或多或少地接管整个接口的控制权,因为如果我写入一个 SYN 数据包,并在响应中收到 SYNACK 数据包,那么即使我通过解码 AF_PACKET 套接字上的数据包来处理 SYNACK 数据包,Linux 内核也会处理 SYNACK 数据包,可能以某种方式响应,甚至将其路由到现有的套接字 API 级连接。
我想知道的是:如果我实现自己的 TCP 堆栈,是否有任何方法可以通过 Linux 内核主机接口上的端口进行通信,同时允许其他进程仍在同一主机接口上使用普通套接字 API?我想对 Linux 内核说的是:“嘿,请给我一个您不使用的 TCP 端口,从现在开始,如果您收到往返于该端口的数据包,请忽略它们”,然后我使用我的 AF_PACKET 套接字通过这个分配的端口写入 TCP 流量。
现在,我当然可以编写一个 nftables 规则来丢弃到某个端口的 TCP 流量。这样做的问题是:如何找到未被其他进程使用的端口,以及如何在我的进程退出时可靠地清理规则?我发现,nftables 规则很难以一种可靠的方式设置在已经具有复杂 nftables 设置的主机上,并与安装了自己的 nftables 规则的 docker 等服务进行互操作。
1
最佳答案
1
我认为这正是tun
/tap
设备的用途;这些是由应用程序控制的虚拟网络设备。tap
设备是第 2 层(因此您可以实现非 IP 协议),tun
设备是第 3 层。
所以:
-
ip link
使用/ etc配置接口,ip addr
以便您可以将流量传递给它。例如:ip addr add 192.168.43.1/24 dev tun0 ip link set tun0 up
-
将流量引导至
192.168.43.0/24
网络上的地址:nc 192.168.43.10 100
-
您的应用程序将从您在第一步收到的文件描述符中读取数据,并且也可以写回数据。
下面是创建tun
接口、从中读取数据并在标准输出上进行十六进制转储的示例代码:
#include <errno.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
int tun_alloc(char *dev) {
struct ifreq ifr;
int fd, err;
if ((fd = open("/dev/net/tun", O_RDWR)) < 0) {
fprintf(stderr, "failed to open /dev/net/tun: %d: %s\n", fd,
strerror(errno));
return -1;
}
memset(&ifr, 0, sizeof(ifr));
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TUN;
if (*dev)
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) {
close(fd);
fprintf(stderr, "failed to acquire interface: %d: %s\n", fd,
strerror(errno));
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
// via https://gist.github.com/ccbrown/9722406
void hexdump(const void *data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
printf("X ", ((unsigned char *)data)[i]);
if (((unsigned char *)data)[i] >= ' ' &&
((unsigned char *)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char *)data)[i];
} else {
ascii[i % 16] = '.';
}
if ((i + 1) % 8 == 0 || i + 1 == size) {
printf(" ");
if ((i + 1) % 16 == 0) {
printf("| %s \n", ascii);
} else if (i + 1 == size) {
ascii[(i + 1) % 16] = '\0';
if ((i + 1) % 16 <= 8) {
printf(" ");
}
for (j = (i + 1) % 16; j < 16; ++j) {
printf(" ");
}
printf("| %s \n", ascii);
}
}
}
}
int main() {
char name[IFNAMSIZ + 1], cmd[1024];
int fd;
fd = tun_alloc(name);
if (fd < 0) {
fprintf(stderr, "tunnel allocation failed\n");
exit(1);
}
fprintf(stderr, "got interface: %s\n", name);
snprintf(cmd, sizeof(cmd), "ip addr add 192.168.43.1/24 dev %s", name);
if (system(cmd) != 0) {
fprintf(stderr, "failed to configure interface\n");
exit(1);
}
snprintf(cmd, sizeof(cmd), "ip link set %s up", name);
if (system(cmd) != 0) {
fprintf(stderr, "failed to configure interface\n");
exit(1);
}
while (1) {
char buf[8192];
int nb;
nb = read(fd, buf, sizeof(buf));
if (nb == 0) {
break;
} else if (nb < 0) {
fprintf(stderr, "read failed: %s\n", strerror(errno));
exit(1);
}
printf("read %d bytes\n", nb);
hexdump(buf, nb);
}
return 0;
}
如果我们运行该程序(请注意,您必须以 身份运行该程序root
)并尝试连接到地址192.168.43.10
:
nc 192.168.43.10 100
我们看到的输出:
read 64 bytes
00 00 08 00 45 00 00 3C 7B B8 40 00 40 06 E7 A7 | ....E..<{.@.@...
C0 A8 2B 01 C0 A8 2B 0A E5 28 00 64 7C 20 6C 3D | ..+...+..(.d| l=
00 00 00 00 A0 02 FA F0 82 47 00 00 02 04 05 B4 | .........G......
04 02 08 0A C6 5E 5F 22 00 00 00 00 01 03 03 07 | .....^_"........
根据,这是两个字节的标志、两个字节的协议信息,然后是原始协议数据。因此,剥离 tun 设备标头后,我们得到:
4500 003c e859 4000 4006 7b06 c0a8 2b01 E..<.Y@.@.{...+.
c0a8 2b0a b63c 0016 4710 5595 0000 0000 ..+..<..G.U.....
a002 faf0 6928 0000 0204 05b4 0402 080a ....i(..........
c654 f33d 0000 0000 0103 0307 .T.=........
这是标准 IPv4 数据包。我们可以让应用程序对其进行解码并生成适当的响应。
笔记:
- 您可能还会看到其他输出,因为大多数系统通过所有可用接口执行各种网络服务公告/发现。
- 如果您不想要初始数据包元数据(标志和协议信息),那么您可以
IFF_NO_PI
在制作时进行设置TUNSETIFF
ioctl
;在这种情况下,您将只收到原始 IP 数据。
|
–
|