如何伪装窃听接口流量

How to MASQUERADE tap interface traffic

我目前正在 Java 中开发一个 VPN 服务器,至少在 Java 中尽可能多,并且我计划通过 tap 设备执行客户端数据包路由。

目前,我可以将 ethernet 帧写入 tap 设备,并且可以通过 tcpdump 观察这些数据包。但是它们没有通过 eth0 路由,尽管我启用了 ip 转发并向 iptables 添加了 MASQUERADE 规则。 (这个问题似乎与 that 相同,除了网关接口在那里是一个真实的接口,而在我的情况下是一个虚拟接口。)

ifconfig tap0的输出结果如下:

tap0      Link encap:Ethernet  HWaddr 82:7d:95:39:71:a1  
          inet addr:10.1.0.1  Bcast:10.1.255.255  Mask:255.255.0.0
          inet6 addr: fe80::807d:95ff:fe39:71a1/64 Scope:Link
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:767 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:56838 (56.8 KB)  TX bytes:0 (0.0 B)

ip link show tap0的输出如下:

12: tap0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 82:7d:95:39:71:a1 brd ff:ff:ff:ff:ff:ff

以下是我连接到窃听器设备的方式:

int helper_open(const char* dev_name, int tun_or_tap) {
    struct ifreq ifr;
    int fd;

    if ((fd = open("/dev/net/tun", O_RDWR)) == -1) {
        return -1;
    }

    memset(&ifr, 0, sizeof (ifr));
    strncpy(ifr.ifr_name, dev_name, IFNAMSIZ);
    ifr.ifr_flags = IFF_NO_PI;
    if (tun_or_tap == DEVICE_TUN) {
        ifr.ifr_flags |= IFF_TUN;
    } else if (tun_or_tap == DEVICE_TAP) {
        ifr.ifr_flags |= IFF_TAP;
    } else {
        return -2;
    }

    if (ioctl(fd, TUNSETIFF, (void *) &ifr) == -1) {
        close(fd);
        return -3;
    }

    return fd;
}

成功获取文件描述符后,通过 write() 调用写入设备是微不足道的。

我是如何准备以太网帧的:

public boolean sendIp(byte[] buffer, int start, int length) {
    byte[] frame = new byte[length+14];
    System.arraycopy(mac, 0, frame, 0, 6);
    System.arraycopy(mac, 0, frame, 6, 2);
    byte[] ip = IpUtils.getSourceIp(buffer, start).getAddress();
    for (int i = 0; i < 4; i++) {
        frame[8+i] = (byte) (0xFF & (ip[i] ^ mac[i+2]));
    }
    frame[12] = 0x08;
    frame[13] = 0x00;
    System.arraycopy(buffer, start, frame, 14, length);
    try {
        write(frame, 0, frame.length);
        return true;
    } catch (IOException e) {
        logger.error("cannot send ip packet.", e);
        return false;
    }
}

mactap设备的MAC地址,我通过异或tap设备的最后四个字节生成客户端的MAC地址MAC 使用我分配的虚拟IP。 (在我的测试中,客户端的 IP 是 10.1.0.2。)这样,它对所有参与者来说都是唯一的,也很容易处理 ARP/RARP 协议。

ifconfig输出的RX packets字段可以看出,数据包是在tap设备中接收到的。此外,示例 tcpdump -i tap0 -n 输出如下:

15:53:48.395082 IP 10.1.0.2.47132 > 216.58.208.34.443: Flags [S], seq 3162009985, win 65535, options [mss 1460,sackOK,TS val 4294939804 ecr 0,nop,wscale 6], length 0
15:53:49.396355 IP 10.1.0.2.39713 > 216.58.208.42.443: Flags [S], seq 2459164785, win 65535, options [mss 1460,sackOK,TS val 4294939905 ecr 0,nop,wscale 6], length 0
15:53:49.678691 IP 10.1.0.2.58306 > 194.177.210.54.123: NTPv3, Client, length 48
15:53:50.508132 IP 10.1.0.2.38112 > 172.217.22.110.443: Flags [S], seq 3132386571, win 65535, options [mss 1460,sackOK,TS val 4294940016 ecr 0,nop,wscale 6], length 0
15:53:51.519119 IP 10.1.0.2.37492 > 216.58.207.42.443: Flags [S], seq 3750738666, win 65535, options [mss 1460,sackOK,TS val 4294940117 ecr 0,nop,wscale 6], length 0

数据包已被 tcpdump 正确解码,看来我正在成功准备以太网帧。 sysctl net.ipv4.ip_forward 表示开启了ip转发。那为什么他们不通过 eth0 路由?

iptables -L -n -v -t nat 的输出:

Chain PREROUTING (policy ACCEPT 2436 packets, 132K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain INPUT (policy ACCEPT 2436 packets, 132K bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain OUTPUT (policy ACCEPT 20 packets, 1462 bytes)
 pkts bytes target     prot opt in     out     source               destination         

Chain POSTROUTING (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   20  1462 MASQUERADE  all  --  *      eth0    0.0.0.0/0            0.0.0.0/0

route -n的输出:

Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         173.212.233.1   0.0.0.0         UG    0      0        0 eth0
10.1.0.0        0.0.0.0         255.255.0.0     U     0      0        0 tap0
173.212.233.0   173.212.233.1   255.255.255.0   UG    0      0        0 eth0
173.212.233.0   0.0.0.0         255.255.255.0   U     0      0        0 eth0

非常感谢任何形式的帮助。

P.S:我正在开发 Ubuntu 16.04.

编辑: 为了确保数据包不会离开 eth0,我在不同的终端上启动了 tcpdump -i eth0 host 5.189.147.197 -ntcpdump -i tap0 host 5.189.147.197 -n,同时客户端尝试连接到 5.189.147.197。我观察了 tap0 接口上的流量,但没有观察到 eth0 接口上的流量。所以肯定不会转发。

一如既往,原因很简单:在注入数据包时,我错误地计算了 IP header 中的校验和。即,我忘记在添加后翻转这些位。我真丢人:(。

我不会删除整个问题,因为它可能会帮助人们开发类似的应用程序。

要检查校验和是否正确,您可以 运行 tcpdump 更详细的模式,即包括一些 -v 参数。我还使用 tcpdump -i tap0 -n -X -s 0 -vvv 来观察数据包的内容。对我帮助很大。

关于 user1794469 建议的桥接选项,您需要在 eth0 的子网中有额外的 IP 可分配给客户端,因为桥接使客户端就像一个新参与者(没有任何NAT) 加入子网。但是,我的 VPS 有一个分配给它的 IP,它不在 NAT 后面,所以没有 DHCP 等。这就是为什么我需要有自己的 NAT。现在它可以完美运行了。