如何伪装tap接口流量

4

我目前正在使用Java开发VPN服务器,尽可能多地使用Java,并计划通过tap设备执行客户端数据包的路由。

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

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)
< p > 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

以下是我如何连接到tap设备的方法:

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设备的最后四个字节与我分配的虚拟IP异或,生成客户端的MAC地址。(在我的测试中,客户端的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

任何形式的帮助都非常感激。
附言:我正在Ubuntu 16.04上开发。
编辑: 为确保数据包不从eth0离开,我在不同的终端上启动了"tcpdump -i eth0 host 5.189.147.197 -n"和"tcpdump -i tap0 host 5.189.147.197 -n",同时客户端尝试连接到"5.189.147.197"。我观察到了tap0接口上的流量,但没有观察到eth0接口上的流量。因此它们肯定没有被转发。

在eth0上使用tcpdump,你看到了什么?它们根本没有到达那里吗?过滤表(sudo iptables -L)的规则是什么? - user1794469
@user1794469 所有链的默认策略均为“接受”,且没有其他规则。 - ram
@user1794469,由于响应没有返回,我认为它们根本没有到达“eth0”,但让我确保一下。稍后更新。 - ram
@user1794469请查看编辑。简单来说,数据包不会出现在“eth0”上。你对发生了什么有任何想法吗?实际上这是一个非常基本的路由问题。 - ram
也许比我更有经验的人可以参与讨论,但我认为您需要使用 tap0eth0 进行桥接。以下是如何操作的信息:https://help.ubuntu.com/community/NetworkConnectionBridge - user1794469
1个回答

5
一如既往,问题的原因很简单:在注入数据包时,我在IP头中计算校验和时出现错误。我的意思是,在加法运算后忘记翻转比特了。真惭愧:(。
我没有删除整个问题,因为它可能会帮助那些开发类似应用程序的人们。
要检查校验和是否正确,您可以以更详细的模式运行tcpdump,即包括一些-v参数。我使用了tcpdump -i tap0 -n -X -s 0 -vvv来观察数据包的内容。这对我帮助很大。
关于用户1794469建议的桥接选项,您需要具备额外的IP地址,以便将其分配给客户端,因为桥接会使客户端像新的参与者一样(没有任何NAT)加入子网。然而,我的VPS只有一个分配给它的IP,并且不在NAT之后,因此没有DHCP等。这就是为什么我需要有自己的NAT。现在它完美地工作了。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接