在 Ubuntu 22.04 上,我设置了以下iptables
规则:
iptables -I OUTPUT -d 192.168.0.0/16 -j LOG --log-prefix "CHECK1 "
iptables -I FORWARD -d 192.168.0.0/16 -j LOG --log-prefix "CHECK2 "
为了验证我的设置是否正确,我在浏览器中导航到 website.com(位于子网中192.168.0.0/16
)并看到一条CHECK1
消息显示在/var/log/kern.log
.
然后我运行了一个 C 程序,该程序创建了tun
一个 IP 地址为 的接口172.30.0.1
,并从中发出了一个手工制作的 TCP-SYN 数据包。数据包的源地址172.30.0.1
和目标地址为192.168.255.8
(website.com 的地址)。它出现在 Wireshark 中。但是, 中没有相应的日志消息/var/log/kern.log
。
这个数据包发生了什么?
这是iptables-save
:
*filter
:INPUT ACCEPT [0:0]
:FORWARD DROP [0:0]
:OUTPUT ACCEPT [0:0]
:DOCKER - [0:0]
:DOCKER-ISOLATION-STAGE-1 - [0:0]
:DOCKER-ISOLATION-STAGE-2 - [0:0]
:DOCKER-USER - [0:0]
-A FORWARD -d 192.168.0.0/16 -j LOG --log-prefix "CHECK2 "
-A FORWARD -j DOCKER-USER
-A FORWARD -j DOCKER-ISOLATION-STAGE-1
-A FORWARD -o docker0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o docker0 -j DOCKER
-A FORWARD -i docker0 ! -o docker0 -j ACCEPT
-A FORWARD -i docker0 -o docker0 -j ACCEPT
-A OUTPUT -d 192.168.0.0/16 -j LOG --log-prefix "CHECK1 "
-A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2
-A DOCKER-ISOLATION-STAGE-1 -j RETURN
-A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP
-A DOCKER-ISOLATION-STAGE-2 -j RETURN
-A DOCKER-USER -j RETURN
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:DOCKER - [0:0]
-A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
-A OUTPUT ! -D 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
-A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
-A DOCKER -i docker0 -j RETURN
COMMIT
这是 C 代码(为简洁起见,我省略了错误检查):
#include <arpa/inet.h>
#include <fcntl.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
static int
tunAlloc(void)
{
int fd;
struct ifreq ifr = {.ifr_name = "tun0", .ifr_flags = IFF_TUN | IFF_NO_PI};
fd = open("/dev/net/tun", O_RDWR);
ioctl(fd, TUNSETIFF, (void*)&ifr);
ioctl(fd, TUNSETOWNER, geteuid());
return fd;
}
static void
bringInterfaceUp(void)
{
int sock;
struct sockaddr_in addr = {.sin_family = AF_INET};
struct ifreq ifr = {.ifr_name = "tun0"};
inet_aton("172.30.0.1", &addr.sin_addr);
memcpy(&ifr.ifr_addr, &addr, sizeof(struct sockaddr));
sock = socket(AF_INET, SOCK_DGRAM, 0);
ioctl(sock, SIOCSIFADDR, &ifr);
ioctl(sock, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
ioctl(sock, SIOCSIFFLAGS, &ifr);
close(sock);
}
static void
emitPacket(int tap_fd)
{
unsigned char packet[] = {0x45, 0x00, 0x00, 0x3c, 0xd8, 0x6f, 0x40, 0x00, 0x3f, 0x06, 0x08, 0x91,
172, 30, 0, 1, 192, 168, 255, 8, 0xa2, 0x9a, 0x27, 0x11,
0x80, 0x0b, 0x63, 0x79, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0xfa, 0xf0,
0x89, 0xd8, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a,
0x5b, 0x76, 0x5f, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x07};
write(tap_fd, packet, sizeof(packet));
}
int
main()
{
int tap_fd;
tap_fd = tunAlloc();
bringInterfaceUp();
emitPacket(tap_fd);
close(tap_fd);
return 0;
}
使用您的示例代码,我看到了您描述的相同问题。
“小包,你在哪儿?” ( pwru ) 是一个很好的诊断工具,当数据包似乎神秘地消失时,当我运行时:
当您的代码发出数据包时,我看到以下内容:
这表明数据包已被丢弃,因为它包含不正确的校验和。
如果我们使用 wireshark 检查数据包捕获,它会告诉我们正确的校验和。解决这些让我们:
但这在路由逻辑中失败了:
事实上,如果你
log_martians
在你的系统上启用了,你会看到:...我们收到该错误是因为有一个数据包进入一个与该接口具有相同地址的接口,这是一个问题(我们永远无法正确响应它)。
tun0
如果我们修改您的代码以 (a) 设置与数据包中不同的地址,并且 (b) 使用/24
网络掩码以便我们获得到适当网络的自动路由:我们现在看到数据包发出于
eth0
: