简体   繁体   English

Linux TUN / TAP:无法从TAP设备读取数据

[英]Linux TUN/TAP: Unable to read data back from TAP devices

The question is about the proper configuration of a Linux host that would like to make use of the Tun/Tap module. 问题是关于想要使用Tun / Tap模块的Linux主机的正确配置。

My Goal: 我的目标:

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). 利用现有的路由软件(下面的APP1和APP2),但拦截和修改由它发送和接收的所有消息(由Mediator完成)。

My Scenario: 我的场景:

              Ubuntu 10.04 Machine
+---------------------------------------------+
|                                             |
|APP1 --- tap1 --- Mediator --- tap2 --- APP2 |
|                                             |
+---------------------------------------------+
  • tap1 and tap2: tap devices setup with IFF_TAP flag and IPs 10.0.0.1/24 and 10.0.0.2/24 respectively. tap1和tap2:分别使用IFF_TAP标志和IP 10.0.0.1/24和10.0.0.2/24设置设备。 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; } 
  • Mediator: Small self-written code to simply relay data between tap1 and tap2. 介体:小型自编代码,用于在tap1和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 and APP2: OSPF routing daemons communicating via tap1 and tap2 respectively. APP1和APP2:OSPF路由守护进程分别通过tap1和tap2进行通信。 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) ... 

My Usage: 我的用法:

  • Attach wireshark to tap1. 将wireshark连接到tap1。 (no traffic seen yet). (还没有看到交通)。
  • Start APP1. 启动APP1。 (wireshark sees IGMP and OSPF messages with source 10.0.0.1 (tap1)) (wireshark看到带有源10.0.0.1(tap1)的IGMP和OSPF消息)
  • Start APP2. 启动APP2。 (wireshark still sees only IGMP and OSPF messages with source 10.0.0.1 (tap1) since Mediator not running yet) (wireshark仍然只看到带有源10.0.0.1(tap1)的IGMP和OSPF消息,因为Mediator尚未运行)
  • Start Mediator. 启动调解员。 (wireshark now sees IGMP and OSPF messages with sources of both tap1 and tap2). (wireshark现在看到带有tap1和tap2源的IGMP和OSPF消息)。

My Issue: 我的问题:

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. 即使连接到tap1的wireshark看到来自tap1和tap2的消息,APP2也不接收APP1发送的消息,APP2也不接收来自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. 在上面显示的strace提取中,select()调用永远不会返回文件描述符8,它实际上是连接到tap1的套接字。

My Questions: 我的问题:

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? 为什么APP1没有收到APP2发送的消息,即使这些消息是由APP2发送的,由Mediator中继并且被连接到tap1的wireshark看到了?

Do I have to add any type/kind of additional routes on my Linux host? 我是否必须在Linux主机上添加任何类型/种类的其他路由?

Did I make a mistake in setting up the tun/tap devices? 我在设置tun / tap设备时犯了错误吗?

Does my Mediator code not work properly? 我的Mediator代码不能正常工作吗?

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. 我没有尝试过你的代码(有些奇怪的是,你能够在不使用多队列标志的情况下从用户空间打开TAP设备两次,但我们假设这是正确的),但是你处理TAP设备的方式存在概念上的错误。

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. 什么TUN / TAP本质上只是一个管道,这个管道的一端在内核(tapX接口),另一端在一些用户空间应用程序中。 Whatever this application writes to the pipe gets to the kernel interface as incoming traffic (and you see it with wireshark). 无论此应用程序写入管道的任何内容都作为传入流量进入内核接口(您可以通过wireshark查看它)。 Whatever kernel sends to that pipe (outgoing to tapX) ends up coming into application (the data you can read in application). 无论内核发送到该管道(传递到tapX)都会进入应用程序(您可以在应用程序中读取的数据)。

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. 当然,如果你不想只是桥接,而是以某种方式修改流量,事情会变得复杂一些。

One way to solve this problem is to add another pair of TAP interfaces. 解决此问题的一种方法是添加另一对TAP接口。 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. 你用tap3桥接(如在内核桥中)你的tap1,用tap4桥接tap2,现在你打开tap3并在你的'mediator'中点击4并在它们之间代理帧。 This is horribly inefficient, but may be a solution for your problem. 这非常低效,但可能是您的问题的解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM