2つのDockerコンテナが実行されています(Ubuntu 22.04)。彼らは同じネットワーク上にあるので、互いに会話することができます(私はこれをで確認しましたnc
)。最初のコンテナでは、トンデバイスを作成し、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
その後、私のプログラムは、送信元IPが5.6.7.8で宛先IPが192.168.48.3(別のコンテナ)のトンデバイスにTCP SYNパケットを書き込みます。ただし、パケットは他のコンテナに到達しません。これを実行しましたが、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
私がやっていることを再現する方法は次のとおりです。
ドッカーファイル:
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`
答え1
渡すフワル、パケットが拒否されたことがわかりました。fib_validate_source
カーネルでは、火星パケット。
私が収集したのは、送信元IPがトンデバイスの送信元IPと同じであるため、パケットが疑わしいようです。アドレスが192.168.0.1の物理インターフェイスがあり、そのインターフェイスから送信元IPが192.168.0.1のパケットを受信するとします。なぜ自分にメッセージを送るのか疑問に思うでしょう。
そのため、パケットのソースIPを5.6.7.9(tunデバイスのアドレスはそのまま維持)に調整してから! ICMPレスポンスを頂きました!