Linux TUN/TAP:无法从TAP设备读回数据 [英] Linux TUN/TAP: Unable to read data back from TAP devices
问题描述
问题在于要使用Tun/Tap模块的Linux主机的正确配置.
The question is about the proper configuration of a Linux host that would like to make use of the Tun/Tap module.
我的目标:
使用现有的路由软件(以下简称APP1和APP2),但截取和修改由其发送和接收的所有消息(由Mediator完成).
Making use of an existing routing software (APP1 and APP2 in the following) but intercepting and modifiying all messages sent and received by it (done by the Mediator).
我的场景:
Ubuntu 10.04 Machine
+---------------------------------------------+
| |
|APP1 --- tap1 --- Mediator --- tap2 --- APP2 |
| |
+---------------------------------------------+
-
tap1和tap2:分别使用IFF_TAP标志和IP 10.0.0.1/24和10.0.0.2/24设置Tap设备.创建设备的代码如下:
tap1 and tap2: tap devices setup with IFF_TAP flag and IPs 10.0.0.1/24 and 10.0.0.2/24 respectively. The code to create the devices is the following:
#include <stdlib.h> #include <stdio.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <fcntl.h> #include <linux/if.h> #include <linux/if_tun.h> #include <string.h> #include <errno.h> #include <sys/resource.h> void createTun(char *, char *, short); int main(void) { const short FLAGS = IFF_TAP; char *tunName; char *tunIP; // Create tap1 tunName = "tap1\0"; tunIP = "10.0.0.1/24\0"; createTun(tunName, tunIP, FLAGS); printf("Created %s with IP %s\n", tunName, tunIP); // Create tap2 tunName = "tap2\0"; tunIP = "10.0.0.2/24\0"; createTun(tunName, tunIP, FLAGS); printf("Created %s with IP %s\n", tunName, tunIP); return 0; } void createTun(char *tunName, char *tunIP, short FLAGS) { char *cmd; char *cloneDev = "/dev/net/tun"; char *cmdIPLinkUpTemplate = "ip link set %s up"; char *cmdIPAddrAddTemplate = "ip addr add %s dev %s"; int cmdIPLinkUpRawLength = strlen(cmdIPLinkUpTemplate) - 2; int cmdIPAddrAddRawLength = strlen(cmdIPAddrAddTemplate) - 4; FILE *fp; int fd, err, owner, group; struct ifreq ifr; owner = geteuid(); group = getegid(); // open the clone device if((fd = open(cloneDev, O_RDWR)) < 0) { perror("OPEN CLONEDEV failed."); exit(EXIT_FAILURE); } memset(&ifr, 0, sizeof(struct ifreq)); ifr.ifr_flags = FLAGS; strncpy(ifr.ifr_name, tunName, strlen(tunName)); // create the device if(ioctl(fd, TUNSETIFF, (void *) &ifr) < 0) { perror("IOCTL SETIFF denied."); close(fd); exit(EXIT_FAILURE); } // set dev owner if(owner != -1) { if(ioctl(fd, TUNSETOWNER, owner) < 0) { perror("IOCTL SETOWNER denied."); close(fd); exit(EXIT_FAILURE); } } // set dev group if(group != -1) { if(ioctl(fd, TUNSETGROUP, group) < 0) { perror("IOCTL SETGROUP denied."); close(fd); exit(EXIT_FAILURE); } } // set dev persistent if(ioctl(fd, TUNSETPERSIST, 1) < 0) { perror("IOCTL SETPERSIST denied."); close(fd); exit(EXIT_FAILURE); } // Set dev up cmd = malloc(cmdIPLinkUpRawLength + strlen(tunName) + 1); sprintf(cmd, cmdIPLinkUpTemplate, ifr.ifr_name); fp = popen(cmd, "r"); if(fp == NULL) { perror("POPEN failed."); close(fd); free(cmd); exit(EXIT_FAILURE); } pclose(fp); free(cmd); // Assign IP cmd = malloc(cmdIPAddrAddRawLength + strlen(tunIP) + strlen(tunName) + 1); sprintf(cmd, cmdIPAddrAddTemplate, tunIP, tunName); fp = popen(cmd, "r"); if(fp == NULL) { perror("POPEN failed."); close(fd); free(cmd); exit(EXIT_FAILURE); } pclose(fp); free(cmd); return; }
-
介体:小的自编写代码,可以简单地在tap1和tap2之间中继数据.基本结构如下:
Mediator: Small self-written code to simply relay data between tap1 and tap2. the basics structure is the following:
#include <unistd.h> #include <stdio.h> #include <sys/socket.h> #include <netinet/ip.h> #include <sys/ioctl.h> #include <sys/resource.h> #include <sys/epoll.h> #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <linux/if.h> #include <linux/if_tun.h> int main(int argc, char *argv[]) { const int NOF_FD = 2; const char *TUN1 = "tap1"; const char *TUN2 = "tap2"; const char *CLONEDEV = "/dev/net/tun"; int fd_tun1, fd_tun2, fd_epoll; struct ifreq ifr_tun1, ifr_tun2; struct epoll_event ev; const int MAX_EVENTS = 1; int ready, s, t; const int MAX_BUF = 2000; char buf[MAX_BUF]; struct sockaddr_in to; const short FLAGS = IFF_TAP; // Open tap1 if((fd_tun1 = open(CLONEDEV, O_RDWR)) < 0) { perror("OPEN CLONEDEV for tun1 failed"); exit(EXIT_FAILURE); } memset(&ifr_tun1, 0, sizeof(struct ifreq)); ifr_tun1.ifr_flags = FLAGS; strcpy(ifr_tun1.ifr_name, TUN1); if(ioctl(fd_tun1, TUNSETIFF, (void *) &ifr_tun1) < 0) { perror("IOCTL SETIFF for tap1 failed"); close(fd_tun1); exit(EXIT_FAILURE); } // Open tap2 if((fd_tun2 = open(CLONEDEV, O_RDWR)) < 0) { perror("OPEN CLONEDEV for tap2 failed"); exit(EXIT_FAILURE); } memset(&ifr_tun2, 0, sizeof(struct ifreq)); ifr_tun2.ifr_flags = FLAGS; strcpy(ifr_tun2.ifr_name, TUN2); if(ioctl(fd_tun2, TUNSETIFF, (void *) &ifr_tun2) < 0) { perror("IOCTL SETIFF for tun2 failed"); close(fd_tun1); close(fd_tun2); exit(EXIT_FAILURE); } // Prepare EPOLL if((fd_epoll = epoll_create(NOF_FD)) < 0) { perror("EPOLL CREATE failed"); close(fd_tun1); close(fd_tun2); exit(EXIT_FAILURE); } memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = fd_tun1; if(epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_tun1, &ev) < 0) { perror("EPOLL CTL ADD fd_tun1 failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } memset(&ev, 0, sizeof(ev)); ev.events = EPOLLIN; ev.data.fd = fd_tun2; if(epoll_ctl(fd_epoll, EPOLL_CTL_ADD, fd_tun2, &ev) < 0) { perror("EPOLL CTL ADD fd_tun2 failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } // Do relay while(1) { if((ready = epoll_wait(fd_epoll, &ev, MAX_EVENTS, -1)) < 0) { if(errno == EINTR) continue; else { perror("EPOLL WAIT failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } } //printf("EPOLL WAIT SIGNALED\n"); if(ev.events & EPOLLIN) { if((s = read(ev.data.fd, buf, MAX_BUF)) < 0) { perror("READ failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } printf("Read from %s. Bytes: %d\nData:\n", (ev.data.fd == fd_tun1 ? "tun1" : "tun2"), s); int k; for(k = 0; k < s; k++) { printf("%c", buf[k]); } printf("\n"); t = (ev.data.fd == fd_tun1) ? fd_tun2 : fd_tun1; if((s = write(t, buf, s)) < 0) { perror("WRITE failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } printf("Written to %s. Bytes: %d\n", (t == fd_tun1 ? "tun1" : "tun2"), s); if(epoll_ctl(fd_epoll, EPOLL_CTL_DEL, ev.data.fd, NULL) < 0) { perror("EPOLL CTL DEL failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } if(epoll_ctl(fd_epoll, EPOLL_CTL_ADD, ev.data.fd, &ev) < 0) { perror("EPOLL CTL ADD failed"); close(fd_tun1); close(fd_tun2); close(fd_epoll); exit(EXIT_FAILURE); } } printf("\n\n"); } }
-
APP1和APP2:OSPF路由守护程序分别通过tap1和tap2进行通信.守护程序的痕迹表明,基本上涉及以下系统调用:
APP1 and APP2: OSPF routing daemons communicating via tap1 and tap2 respectively. An strace of the daemons shows that basically the following system calls are involved:
socket(PF_INET, SOCK_RAW, 0X59 /*IPPROTO_??? */) = 8 // Opening a socket for OSPF and tap1 fcntl64(8, F_SETFL, 0_RDONLY | 0_NONBLOCK) = 0 setsockopt(8, SOL_IP, IP_TOS, [192], 4) = 0 setsockopt(8, SOL_SOCKET, SO_PRIORITY, [7], 4) = 0 setsockopt(8, SOL_IP, IP_PKTINFO, [1], 4) = 0 setsockopt(8, SOL_IP, IP_MTU_DISCOVER, [0], 4) = 0 setsockopt(8, SOL_IP, IP_MULTICAST_LOOP, [0], 4) = 0 setsockopt(8, SOL_IP, IP_MULTICAST_TTL, [1], 4) = 0 setsockopt(8, SOL_IP, IP_MUTLICAST_IF, "\0\0\0\0\n\0\0\1\223\0\0\0", 12) = 0 setsockopt(8, SOL_SOCKET, SO_BINDTODEVICE, "tap1\0\0\0\0\0\0\0\0\0\0\0\0\0\315\375\307\250\352\t\t8\207\t\10\0\0\0\0", 32) = 0 setsockopt(8, SOL_IP, IP_ADD_MEMBERSHIP, "340\0\0\5\n\0\0\1\223\0\0\0", 12) = 0 // Then it gets in a cycle like: select(9, [3, 7, 8], [], NULL, {1, 0}) = 0 (Timeout) clock_gettime(CLOCK_MONOTONIC, {120893, 360452769}) = 0 time(NULL) clock_gettime(CLOCK_MONOTONIC, {120893, 360504525}) = 0 select(9, [3, 7, 8], [], NULL, {1, 0}) = 0 (Timeout) clock_gettime(CLOCK_MONOTONIC, {120894, 363022746}) = 0 time(NULL) ...
- 将Wireshk连接到tap1. (尚未看到流量).
- 启动APP1. (wireshark看到源为10.0.0.1(tap1)的IGMP和OSPF消息)
- 启动APP2. (由于Mediator尚未运行,wireshark仍仅看到源10.0.0.1(tap1)的IGMP和OSPF消息)
- 启动调解器. (wireshark现在可以看到源为tap1和tap2的IGMP和OSPF消息).
我的使用情况:
我的问题:
即使wireshark(连接到tap1)可以看到来自tap1和tap2的消息,但是APP2不会接收到APP1发送的消息,APP2也不会接收到来自APP1的消息.在上面显示的strace提取中,select()调用从不返回文件描述符8,该文件描述符实际上是连接到tap1的套接字.
Even though wireshark - attached to tap1 - sees messages from both tap1 and tap2, APP2 does not receive the messages sent by APP1 and neither does APP2 receive the messages from APP1. In the strace extract shown above the select() call never returns the file descriptor 8 which actually would be the socket connected to tap1.
我的问题:
即使APP1发送了这些消息,并由介体中继并且由tap1附带的Wireshark看到了,为什么APP1仍未收到APP2发送的消息?
Why does APP1 not receive the messages send by APP2 even though those messages are sent by APP2, relayed by the Mediator and seen by wireshark that is attached to tap1?
我是否必须在Linux主机上添加任何类型/种类的其他路由?
Do I have to add any type/kind of additional routes on my Linux host?
在设置tun/tap设备时我是否犯了错误?
Did I make a mistake in setting up the tun/tap devices?
我的调解员代码不能正常工作吗?
Does my Mediator code not work properly?
推荐答案
I've not tried your code (it's a bit strange that you were able to open TAP device twice from userspace not using a multiqueue flag, but let's assume that is correct), but you have a conceptual error in the way you handle TAP devices.
TUN/TAP本质上只是一个管道,该管道的一侧在内核中(tapX接口),而另一侧在某些用户空间应用程序中.该应用程序写入管道的所有内容都会作为传入流量到达内核接口(您可以通过wireshark看到它).内核发送到该管道的任何数据(发送到tapX)最终都会进入应用程序(您可以在应用程序中读取的数据).
What TUN/TAP is essentially just a pipe, one side of this pipe is in the kernel (the tapX interface) and the other in some userspace application. Whatever this application writes to the pipe gets to the kernel interface as incoming traffic (and you see it with wireshark). Whatever kernel sends to that pipe (outgoing to tapX) ends up coming into application (the data you can read in application).
您的代码当前正在做的是打开同一管道的另一个用户空间部分,这不是您想要的.您想在管道的另一侧获得流量.从技术上讲,您当前正在做的事情可以通过一个简单的桥接接口来完成,将两个分接头都添加为端口.当然,如果您不仅要桥接,而且要以某种方式修改流量,事情会变得有些复杂.
What your code currently doing is opening another userspace part of the same pipe, and that's not what you want. You want to get traffic on the other side of the pipe. Technically, what you're currently doing could be done by a simple bridge interface with both taps added as ports into it. Of course, if you want to not just bridge, but to modify traffic in some way things get a bit more complicated.
解决此问题的一种方法是添加另一对TAP接口.您可以将tap1和tap2与tap4桥接起来(就像在内核桥接中一样),现在您可以在介体"中打开tap3和tap4,并在它们之间使用代理帧.这效率极低,但是可能可以解决您的问题.
One way to solve this problem is to add another pair of TAP interfaces. You bridge (as in kernel bridge) your tap1 with tap3 and tap2 with tap4, now you open tap3 and tap4 in your 'mediator' and proxy frames between them. This is horribly inefficient, but may be a solution for your problem.
这篇关于Linux TUN/TAP:无法从TAP设备读回数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!