Created
April 27, 2015 09:08
-
-
Save rickyzhang-cn/8f91f5ae99c82b26f5d3 to your computer and use it in GitHub Desktop.
一个简单的使用tun/tap建立tunnel的示例代码
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/************************************************************************** | |
* simpletun.c * | |
* * | |
* A simplistic, simple-minded, naive tunnelling program using tun/tap * | |
* interfaces and TCP. Handles (badly) IPv4 for tun, ARP and IPv4 for * | |
* tap. DO NOT USE THIS PROGRAM FOR SERIOUS PURPOSES. * | |
* * | |
* You have been warned. * | |
* * | |
* (C) 2009 Davide Brini. * | |
* * | |
* DISCLAIMER AND WARNING: this is all work in progress. The code is * | |
* ugly, the algorithms are naive, error checking and input validation * | |
* are very basic, and of course there can be bugs. If that's not enough, * | |
* the program has not been thoroughly tested, so it might even fail at * | |
* the few simple things it should be supposed to do right. * | |
* Needless to say, I take no responsibility whatsoever for what the * | |
* program might do. The program has been written mostly for learning * | |
* purposes, and can be used in the hope that is useful, but everything * | |
* is to be taken "as is" and without any kind of warranty, implicit or * | |
* explicit. See the file LICENSE for further details. * | |
*************************************************************************/ | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <linux/if.h> | |
#include <linux/if_tun.h> | |
#include <sys/types.h> | |
#include <sys/ioctl.h> | |
#include <sys/stat.h> | |
#include <fcntl.h> | |
#include <arpa/inet.h> | |
#include <sys/select.h> | |
#include <sys/time.h> | |
#include <errno.h> | |
#include <stdarg.h> | |
/* buffer for reading from tun/tap interface, must be >= 1500 */ | |
#define BUFSIZE 2000 | |
#define CLIENT 0 | |
#define SERVER 1 | |
#define PORT 55555 | |
/* some common lengths */ | |
#define IP_HDR_LEN 20 | |
#define ETH_HDR_LEN 14 | |
#define ARP_PKT_LEN 28 | |
int debug; | |
char *progname; | |
/************************************************************************** | |
* tun_alloc: allocates or reconnects to a tun/tap device. The caller * | |
* needs to reserve enough space in *dev. * | |
**************************************************************************/ | |
int tun_alloc(char *dev, int flags) { | |
struct ifreq ifr; | |
int fd, err; | |
if( (fd = open("/dev/net/tun", O_RDWR)) < 0 ) { | |
perror("Opening /dev/net/tun"); | |
return fd; | |
} | |
memset(&ifr, 0, sizeof(ifr)); | |
ifr.ifr_flags = flags; | |
if (*dev) { | |
strncpy(ifr.ifr_name, dev, IFNAMSIZ); | |
} | |
if( (err = ioctl(fd, TUNSETIFF, (void *)&ifr)) < 0 ) { | |
perror("ioctl(TUNSETIFF)"); | |
close(fd); | |
return err; | |
} | |
strcpy(dev, ifr.ifr_name); | |
return fd; | |
} | |
/************************************************************************** | |
* cread: read routine that checks for errors and exits if an error is * | |
* returned. * | |
**************************************************************************/ | |
int cread(int fd, char *buf, int n){ | |
int nread; | |
if((nread=read(fd, buf, n))<0){ | |
perror("Reading data"); | |
exit(1); | |
} | |
return nread; | |
} | |
/************************************************************************** | |
* cwrite: write routine that checks for errors and exits if an error is * | |
* returned. * | |
**************************************************************************/ | |
int cwrite(int fd, char *buf, int n){ | |
int nwrite; | |
if((nwrite=write(fd, buf, n))<0){ | |
perror("Writing data"); | |
exit(1); | |
} | |
return nwrite; | |
} | |
/************************************************************************** | |
* read_n: ensures we read exactly n bytes, and puts those into "buf". * | |
* (unless EOF, of course) * | |
**************************************************************************/ | |
int read_n(int fd, char *buf, int n) { | |
int nread, left = n; | |
while(left > 0) { | |
if ((nread = cread(fd, buf, left))==0){ | |
return 0 ; | |
}else { | |
left -= nread; | |
buf += nread; | |
} | |
} | |
return n; | |
} | |
/************************************************************************** | |
* do_debug: prints debugging stuff (doh!) * | |
**************************************************************************/ | |
void do_debug(char *msg, ...){ | |
va_list argp; | |
if(debug){ | |
va_start(argp, msg); | |
vfprintf(stderr, msg, argp); | |
va_end(argp); | |
} | |
} | |
/************************************************************************** | |
* my_err: prints custom error messages on stderr. * | |
**************************************************************************/ | |
void my_err(char *msg, ...) { | |
va_list argp; | |
va_start(argp, msg); | |
vfprintf(stderr, msg, argp); | |
va_end(argp); | |
} | |
/************************************************************************** | |
* usage: prints usage and exits. * | |
**************************************************************************/ | |
void usage(void) { | |
fprintf(stderr, "Usage:\n"); | |
fprintf(stderr, "%s -i <ifacename> [-s|-c <serverIP>] [-p <port>] [-u|-a] [-d]\n", progname); | |
fprintf(stderr, "%s -h\n", progname); | |
fprintf(stderr, "\n"); | |
fprintf(stderr, "-i <ifacename>: Name of interface to use (mandatory)\n"); | |
fprintf(stderr, "-s|-c <serverIP>: run in server mode (-s), or specify server address (-c <serverIP>) (mandatory)\n"); | |
fprintf(stderr, "-p <port>: port to listen on (if run in server mode) or to connect to (in client mode), default 55555\n"); | |
fprintf(stderr, "-u|-a: use TUN (-u, default) or TAP (-a)\n"); | |
fprintf(stderr, "-d: outputs debug information while running\n"); | |
fprintf(stderr, "-h: prints this help text\n"); | |
exit(1); | |
} | |
int main(int argc, char *argv[]) { | |
int tap_fd, option; | |
int flags = IFF_TUN; | |
char if_name[IFNAMSIZ] = ""; | |
int header_len = IP_HDR_LEN; | |
int maxfd; | |
uint16_t nread, nwrite, plength; | |
// uint16_t total_len, ethertype; | |
char buffer[BUFSIZE]; | |
struct sockaddr_in local, remote; | |
char remote_ip[16] = ""; | |
unsigned short int port = PORT; | |
int sock_fd, net_fd, optval = 1; | |
socklen_t remotelen; | |
int cliserv = -1; /* must be specified on cmd line */ | |
unsigned long int tap2net = 0, net2tap = 0; | |
progname = argv[0]; | |
/* Check command line options */ | |
while((option = getopt(argc, argv, "i:sc:p:uahd")) > 0){ | |
switch(option) { | |
case 'd': | |
debug = 1; | |
break; | |
case 'h': | |
usage(); | |
break; | |
case 'i': | |
strncpy(if_name,optarg,IFNAMSIZ-1); | |
break; | |
case 's': | |
cliserv = SERVER; | |
break; | |
case 'c': | |
cliserv = CLIENT; | |
strncpy(remote_ip,optarg,15); | |
break; | |
case 'p': | |
port = atoi(optarg); | |
break; | |
case 'u': | |
flags = IFF_TUN; | |
break; | |
case 'a': | |
flags = IFF_TAP; | |
header_len = ETH_HDR_LEN; | |
break; | |
default: | |
my_err("Unknown option %c\n", option); | |
usage(); | |
} | |
} | |
argv += optind; | |
argc -= optind; | |
if(argc > 0){ | |
my_err("Too many options!\n"); | |
usage(); | |
} | |
if(*if_name == '\0'){ | |
my_err("Must specify interface name!\n"); | |
usage(); | |
}else if(cliserv < 0){ | |
my_err("Must specify client or server mode!\n"); | |
usage(); | |
}else if((cliserv == CLIENT)&&(*remote_ip == '\0')){ | |
my_err("Must specify server address!\n"); | |
usage(); | |
} | |
/* initialize tun/tap interface */ | |
if ( (tap_fd = tun_alloc(if_name, flags | IFF_NO_PI)) < 0 ) { | |
my_err("Error connecting to tun/tap interface %s!\n", if_name); | |
exit(1); | |
} | |
do_debug("Successfully connected to interface %s\n", if_name); | |
if ( (sock_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { | |
perror("socket()"); | |
exit(1); | |
} | |
if(cliserv==CLIENT){ | |
/* Client, try to connect to server */ | |
/* assign the destination address */ | |
memset(&remote, 0, sizeof(remote)); | |
remote.sin_family = AF_INET; | |
remote.sin_addr.s_addr = inet_addr(remote_ip); | |
remote.sin_port = htons(port); | |
/* connection request */ | |
if (connect(sock_fd, (struct sockaddr*) &remote, sizeof(remote)) < 0){ | |
perror("connect()"); | |
exit(1); | |
} | |
net_fd = sock_fd; | |
do_debug("CLIENT: Connected to server %s\n", inet_ntoa(remote.sin_addr)); | |
} else { | |
/* Server, wait for connections */ | |
/* avoid EADDRINUSE error on bind() */ | |
if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval)) < 0){ | |
perror("setsockopt()"); | |
exit(1); | |
} | |
memset(&local, 0, sizeof(local)); | |
local.sin_family = AF_INET; | |
local.sin_addr.s_addr = htonl(INADDR_ANY); | |
local.sin_port = htons(port); | |
if (bind(sock_fd, (struct sockaddr*) &local, sizeof(local)) < 0){ | |
perror("bind()"); | |
exit(1); | |
} | |
if (listen(sock_fd, 5) < 0){ | |
perror("listen()"); | |
exit(1); | |
} | |
/* wait for connection request */ | |
remotelen = sizeof(remote); | |
memset(&remote, 0, remotelen); | |
if ((net_fd = accept(sock_fd, (struct sockaddr*)&remote, &remotelen)) < 0){ | |
perror("accept()"); | |
exit(1); | |
} | |
do_debug("SERVER: Client connected from %s\n", inet_ntoa(remote.sin_addr)); | |
} | |
/* use select() to handle two descriptors at once */ | |
maxfd = (tap_fd > net_fd)?tap_fd:net_fd; | |
while(1) { | |
int ret; | |
fd_set rd_set; | |
FD_ZERO(&rd_set); | |
FD_SET(tap_fd, &rd_set); FD_SET(net_fd, &rd_set); | |
ret = select(maxfd + 1, &rd_set, NULL, NULL, NULL); | |
if (ret < 0 && errno == EINTR){ | |
continue; | |
} | |
if (ret < 0) { | |
perror("select()"); | |
exit(1); | |
} | |
if(FD_ISSET(tap_fd, &rd_set)){ | |
/* data from tun/tap: just read it and write it to the network */ | |
nread = cread(tap_fd, buffer, BUFSIZE); | |
tap2net++; | |
do_debug("TAP2NET %lu: Read %d bytes from the tap interface\n", tap2net, nread); | |
/* write length + packet */ | |
plength = htons(nread); | |
nwrite = cwrite(net_fd, (char *)&plength, sizeof(plength)); | |
nwrite = cwrite(net_fd, buffer, nread); | |
do_debug("TAP2NET %lu: Written %d bytes to the network\n", tap2net, nwrite); | |
} | |
if(FD_ISSET(net_fd, &rd_set)){ | |
/* data from the network: read it, and write it to the tun/tap interface. | |
* We need to read the length first, and then the packet */ | |
/* Read length */ | |
nread = read_n(net_fd, (char *)&plength, sizeof(plength)); | |
if(nread == 0) { | |
/* ctrl-c at the other end */ | |
break; | |
} | |
net2tap++; | |
/* read packet */ | |
nread = read_n(net_fd, buffer, ntohs(plength)); | |
do_debug("NET2TAP %lu: Read %d bytes from the network\n", net2tap, nread); | |
/* now buffer[] contains a full packet or frame, write it into the tun/tap interface */ | |
nwrite = cwrite(tap_fd, buffer, nread); | |
do_debug("NET2TAP %lu: Written %d bytes to the tap interface\n", net2tap, nwrite); | |
} | |
} | |
return(0); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
下面是一个host到host的tunnel实验
环境说明:
Server eth0:192.168.0.6 tun0:10.0.4.1
Client eth0:192.168.0.142 tun0:10.0.5.1
Server和Client可以通过真实网卡ping通就OK
然后通过命令
Server:
./simpletun -i tun0 -s -d
ip addr add 10.0.4.1/24 dev tun0
ifconfig tun0 up
route add -net 10.0.5.0 netmask 255.255.255.0 dev tun0
Client:
./simpletun -i tun0 -c 192.168.10.5 -d
ip addr add 10.0.5.1/24 dev tun0
ifconfig tun0 up
route add -net 10.0.4.0 netmask 255.255.255.0 dev tun0
上面的实验Server和Client之间就可以通过建立在tcp socket上的tunnel互相通信,这里的Server和Client的区分是tcp建立连接的服务和客户端,和tun/tap建立的tunnel无关。
在Server上运行ping 10.0.5.1的过程:
1.ICMP包经过TCP/IP协议栈,协议栈的路由表项将其转发给tun0虚拟网卡上
2.在tun0上的虚拟网卡对发送过来的包没有经过什么处理,直接写到sock_fd这个TCP stream上
3.TCP/IP协议栈对这个包进行处理,添加sock_fd相关的报文头,通过真实网卡发送到Client那边
4.Client侧网卡接收到该数据包,解TCP头发送给sock_fd上
5.sock_fd接收到的包全都转发给tun0,tun0对应的协议栈对这个包进行处理,发送ICMP反馈
6.同Server侧一样发送给Server该ICMP反馈