Не могу получить Ethernet-пакет через TAP device (использую функционал TunTap)
Коллеги, добрый день. Прошу помощи, чтобы разобраться в следующей ситуации. Хочу обрабатывать Ethernet-frame в пользовательском пространстве. Для "получения" фрейма хочу использовать Tap-device (такая задача, Raw-socket в данном контексте не подходит). Собственно вопрос. Создаю Tap-device следующими командами:
sudo ip tuntap add dev tap0 mode tap
sudo ip address add 10.10.10.3/30 dev tap0
запускаю в промежутке самописную программную часть (о ней ниже). Далее делаю
sudo ip link set tap0 up
ip address show tap0
В итоге имею в консоли следующий вывод:
7: tap0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN group default qlen 1000
link/ether ba:ba:18:ce:bb:66 brd ff:ff:ff:ff:ff:ff
inet 10.10.10.3/30 scope global tap0
valid_lft forever preferred_lft forever
В промежутке, при помощи программы связываю созданный tap-device с файлом "/dev/net/tun". Текст программы:
#include <arpa/inet.h>
#include <cstring>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_tun.h>
#include <net/if.h>
#include <netinet/ether.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stropts.h>
#include <syslog.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int tun_alloc(char *dev, short flags);
int main() {
char tun_name[IFNAMSIZ];
char buffer[1500];
printf("Programm is starting\n");
/* Подключаюсь к tap-device */
strcpy(tun_name, "tap0");
int tun_fd = tun_alloc(tun_name, IFF_TAP); /* tun interface */
if (tun_fd < 0) {
perror("Allocating interface");
exit(1);
}
printf("Interface number = %d \n", tun_fd);
printf("Start reading loop \n");
while (1) {
printf("Whating for data... \n");
int nread = read(tun_fd, buffer, sizeof(buffer));
if (nread < 0) {
perror("Reading from interface");
close(tun_fd);
exit(1);
}
/* Здесь обрабатывать полученный фрейм */
printf("Read %d bytes from device %s\n", nread, tun_name);
}
return 0;
}
int tun_alloc(char *dev, short flags) {
printf("Entering tun_allocate...\n");
struct ifreq ifr;
int fd, err;
char *clonedev = "/dev/net/tun";
/* Открываем файловый дескриптор вирт.устройства,
с которого ожидаю получить Ethernet-frame*/
if ((fd = open(clonedev, O_RDWR)) < 0) {
perror("open(clonedev, O_RDWR)");
return fd;
}
memset(&ifr, 0, sizeof(ifr));
ifr.ifr_flags = flags;
if (*dev) {
strncpy(ifr.ifr_name, dev, IFNAMSIZ);
}
/* пытаюсь увязать файл и Tap-device */
if ((err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0) {
close(fd);
perror("ioctl(fd, TUNSETIFF, (void *)&ifr)");
return err;
}
strcpy(dev, ifr.ifr_name);
printf("Everything seems OK! \n");
return fd;
}
При запуске начинаю получать кучу служебных Ethernet-фреймов, то есть вроде все работает. Но хочется анализировать "свои" пакеты.
Для этого запускаю простой UDP-sender, который сажаю на устройство с адресом 192.168.44.128 : 9999 и начинаю слать данные на 10.10.10.3:9999.
Иииии... на открытом файловом дескрипторе fd я ничего не получаю. При этом ради прикола, пробовал послушать 10.10.10.3:9999 при помощи программы UDP-listener (обычный набор open ... bind ... read ... cout). Там все данные доходят.
Я не понимаю, почему не получаю на fd Ethernet-фреймы, хотя, по мануалу TunTap-a должен их получать, как я понимаю.
Прошу совета почему так происходит. У меня неверный код? Я не правильно понимаю концепцию TunTap? Не верное создание Tap-device? Не верное понимание сетевого стека?
Пробовал порешать похожую задачу с созданием Tun-device, результат аналогичный. Все извращения происходят под VM Ware Workstation 15 +5.4.0-42-generic #46~18.04.1-Ubuntu SMP Fri Jul 10 07:21:24 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux.
Ответы (1 шт):
Я не правильно понимаю концепцию TunTap?
Да. Tun/Tap используется для создания VPN соединения. Пакеты отправляемые на L3 адрес, присвоенный tap-интерфейсу, в него по факту не попадают: они обрабатываются ядром и сразу передаются программе, которой они адресованы (если такая есть). Этот адрес нужен, чтобы на него приходили данные с другой стороны VPN соединения.
Т.е. чтобы что-то отправилось через tap-интерфейс, нужно посылать данные в подсеть, которая находится за ним (10.10.10.0/30), например на 10.10.10.1.
Но т.к. tap эмулирует L2-интерфейс, то UDP/IP-пакет тоже «просто так» не уйдёт: чтобы его отправить ядру нужно знать не только L3 (IP) адрес, но и L2 (MAC) адрес интерфейса, на который отправлять пакет. Для того, чтобы его выяснить, используется протокол ARP. Т.о. в текущем варианте это вызовет только отправку нескольких arp-запросов, которые, само собой, останутся без ответа. Чтобы увидеть сам UDP-пакет на интерфейсе можно, например:
- Установить реальное VPN-соединение с другим узлом.¹
- Эмулировать ответ на ARP-запрос.
¹ Наколеночный пример из гугла см. здесь.