我有两个正在运行的 Docker 容器(Ubuntu 22.04)。他们位于同一网络上,因此可以互相交谈(我已经用 验证了这一点nc
)。在第一个容器中,我运行了一个程序,该程序创建了一个 tun 设备,为其指定了 IPv4 地址,然后将其启动。 ip addr
节目
4: tun0: <POINTOPOINT,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
link/none
inet 5.6.7.8/32 scope global tun0
valid_lft forever preferred_lft forever
89: eth0@if90: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:c0:a8:30:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.48.2/20 brd 192.168.63.255 scope global eth0
valid_lft forever preferred_lft forever
然后,我的程序将 TCP SYN 数据包写入 tun 设备,源 IP 为 5.6.7.8,目标 IP 为 192.168.48.3(另一个容器)。然而,数据包永远不会到达另一个容器。我已经运行tcpdump
,数据包出现在tun0
界面上,但没有出现在eth0
. tcpdump
另一个容器上显示没有数据包到达。
为什么数据包不会被路由通过eth0
?
当我通过启动容器时docker-compose
,我设置了
sysctls:
- net.ipv4.ip_forward=1
对于第一个容器。
我还设置了 NAT:
iptables -I FORWARD -i tun0 -o eth0 -j ACCEPT
iptables -I FORWARD -i eth0 -o tun0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
以下是重新创建我正在做的事情的方法:
Dockerfile:
FROM ubuntu:22.04
RUN apt -y update && \
apt -y install build-essential iptables tcpdump
WORKDIR /app
发射.c:
#include <arpa/inet.h>
#include <fcntl.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define TUN_DEVICE "tun0"
#define SOURCE_IP "5.6.7.8"
#define DESTINATION_IP "192.168.48.3" // change to the actual address
unsigned char packet[] = {
0x45, 0x00, 0x00, 0x54, 0x3a, 0x58, 0x40, 0x00, 0x40, 0x01, 0x03, 0x9d,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x48, 0xc1,
0x00, 0x03, 0x00, 0x01, 0xa1, 0x81, 0x3a, 0x65, 0x00, 0x00, 0x00, 0x00,
0x0d, 0x81, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b,
0x2c, 0x2d, 0x2e, 0x2f, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37
};
/*
Note: The IPv4 checksum will be wrong. Run this program once and cause it to emit
the packet by sending the process SIGUSR1. Capture the packet with tcpdump which
will tell you that the checksum is wrong and what it should be. The result goes
at offset 10 in the packet.
*/
int tun_fd = -1;
void signal_handler(int signum)
{
(void)signum;
dprintf(STDOUT_FILENO, "Emitting packet\n");
if ( write(tun_fd, packet, sizeof(packet)) < 0 ) {
_exit(1);
}
}
int set_address_and_bring_up(void)
{
int ret = -1, sock;
struct ifreq ifr = {.ifr_name = TUN_DEVICE};
struct sockaddr_in addr = {.sin_family = AF_INET};
inet_pton(AF_INET, SOURCE_IP, &addr.sin_addr);
memcpy(&ifr.ifr_addr, &addr, sizeof(addr));
sock = socket(AF_INET, SOCK_DGRAM, 0);
if ( sock < 0 ) {
perror("socket");
return -1;
}
if ( ioctl(sock, SIOCSIFADDR, &ifr) == -1 ) {
perror("ioctl (SIOCSIFADDR)");
goto done;
}
ifr.ifr_flags = IFF_UP;
if ( ioctl(sock, SIOCSIFFLAGS, &ifr) == -1 ) {
perror("ioctl (SIOCSIFFLAGS)");
goto done;
}
ret = 0;
done:
close(sock);
return ret;
}
int tun_device_create(void) {
int fd;
struct ifreq ifr = {.ifr_name = TUN_DEVICE, .ifr_flags = IFF_TUN | IFF_NO_PI};
fd = open("/dev/net/tun", O_RDWR);
if ( fd < 0 ) {
perror("open");
return -1;
}
if ( ioctl(fd, TUNSETIFF, &ifr) == -1 ) {
perror("ioctl (TUNSETIFF)");
goto error;
}
if ( set_address_and_bring_up() != 0 ) {
goto error;
}
return fd;
error:
close(fd);
return -1;
}
int main() {
struct sigaction action = {.sa_handler = signal_handler};
inet_pton(AF_INET, SOURCE_IP, packet + 12);
inet_pton(AF_INET, DESTINATION_IP, packet + 16);
tun_fd = tun_device_create();
if ( tun_fd < 0 ) {
return 1;
}
sigaction(SIGUSR1, &action, NULL);
while (1) {
pause();
}
return 0;
}
iptables_script.sh:
#!/bin/sh -ex
iptables -I FORWARD -i tun0 -o eth0 -j ACCEPT
iptables -I FORWARD -i eth0 -o tun0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t nat -I POSTROUTING -o eth0 -j MASQUERADE
启动容器:
# First container
docker run --rm -it --cap-add NET_ADMIN -v $PWD:/app --device=/dev/net/tun my_image bash
# Second container
docker run --rm -it ubuntu:22.04 bash
在第一个容器中运行:
test "`cat /proc/sys/net/ipv4/ip_forward`" -eq 1
gcc emit.c
./a.out &
./iptables_script.sh
kill -s USR1 `pgrep a.out`
通过pwru,我能够看出该数据包
fib_validate_source
在内核中被视为火星数据包而被拒绝。我收集到的情况是,由于数据包的源 IP 与 tun 设备的源 IP 相同,因此该数据包看起来很可疑。想象一下,如果您有一个地址为 192.168.0.1 的物理接口,并且您在该接口上收到了一个源 IP 为 192.168.0.1 的数据包。您可能想知道为什么要给自己发送消息。
因此,我将数据包的源 IP 调整为 5.6.7.9(保留 tun 设备的地址),然后!已收到 ICMP 回复!