В настоящее время я разрабатываю VPN-сервер в Java, по крайней мере, как можно больше на Java, и планирую выполнять маршрутизацию клиентских пакетов через устройства- tap
.
В настоящее время я могу писать кадры ethernet
на устройство tap
и я могу наблюдать эти пакеты через tcpdump
. Однако они не перенаправляются на eth0
, хотя я включил переадресацию ip и добавил правило MASQUERADE
для iptables
. (Эта проблема кажется идентичной этому, за исключением того, что интерфейс шлюза является реальным интерфейсом и виртуальным интерфейсом в моей ситуации.)
Выходной сигнал 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()
.
Как я подготовляю рамки ethernet следующим образом:
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;
}
}
mac
- это MAC-адрес устройства- tap
, и я генерирую MAC-адрес клиента путем XORing последних четырех байтов MAC-адреса коммутатора с помощью виртуального IP-адреса, назначенного мной. (В моих тестах IP-адрес клиента 10.1.0.2.) Таким образом, он будет уникальным для всех участников, а также будет легко обрабатывать протоколы ARP/RARP.
Как видно из поля RX packets
вывода ifconfig
, пакеты принимаются в 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, поэтому кажется, что я успешно готовю пакеты Ethernet. 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
Любая помощь высоко ценится.
PS: Я развиваюсь на Ubuntu 16.04.
EDIT: чтобы убедиться, что пакеты не отходят от 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
. Конечно, они не перенаправлены.
Как всегда, причина была довольно простой: во время ввода пакетов я просчитал контрольную сумму в IP-заголовке. А именно, я забыл перевернуть бит после добавления. Мне стыдно :(.
Я не исключаю весь вопрос, так как он может помочь людям при разработке подобных приложений.
Чтобы проверить, соответствуют ли контрольные суммы, вы можете запустить tcpdump
в более подробном режиме, то есть включить несколько параметров -v
. Я использовал tcpdump -i tap0 -n -X -s 0 -vvv
чтобы также наблюдать за содержимым пакетов. Это мне очень помогло.
Что касается опции моста, предложенной пользователем1794469, вам необходимо иметь дополнительные IP-адреса в подсети eth0
, которые доступны для назначения клиентам, потому что мосты делают клиента так, как будто новый участник (без какого-либо NAT) присоединился к подсети. Однако мой VPS имеет один IP-адрес, назначенный ему, и он не находится за NAT, поэтому нет DHCP и т.д. Поэтому мне нужно иметь собственный NAT. Теперь он работает безупречно.
tcpdump
на eth0? Разве они не делают это там вообще? Каковы правила для таблицы фильтров (sudo iptables -L
)?ACCEPT
качестве политики по умолчанию, и других правил нет.