TUN/TAP设备浅析(二) -- TUN/TAP的编程

这篇文章想详细阐述一下有关于 TUN/TAP 设备的编程。

其实关于这两种设备的编程,基本上属于八股文,大家一般都这么干。

启动设备之前

有的linux 并没有将tun 模块编译到内核之中,所以,我们要做的第一件事情就是检查我们的系统是否支持 TUN/TAP 。具体如何检查和解决,请查看这里http://blog.csdn.net/lishuhuakai/article/details/70305543,这篇文章就不再赘述。

光有tun 模块还不够,我们还要创建上篇文章中所提到的文件,运行命令:

% sudo mknod /dev/net/tun c 10 200 # c表示为字符设备,10和200分别是主设备号和次设备号

这样,你到 /dev/net/ 目录下就可以看到一个名称为 tun 的文件了。当然这里的 tun 可以改成任意的你喜欢的名称。

启动设备

对于TUN设备,我们一般这样来初始化:

int 
tun_alloc(char dev[IFNAMSIZ]) // dev数组用于存储设备的名称
{
  struct ifreq ifr;
  int fd, err;

  if ((fd = open("/dev/net/tun", O_RDWR)) < 0) { // 打开文件
    perror("open");
    return -1;
  }

  bzero(&ifr, sizeof(ifr));
  
  /* Flags : IFF_TUN   - TUN设备
   *         IFF_TAP   - TAP设备
   *         IFF_NO_PI - 不需要提供包的信息
   */
  
  ifr.ifr_flags = IFF_TUN | IFF_NO_PI; // tun设备不包含以太网头部,而tap包含,仅此而已

  if (*dev) {
    strncpy(ifr.ifr_name, dev, IFNAMSIZ); 
  }

  if ((err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0) { // 打开设备
    perror("ioctl TUNSETIFF");
    close(fd);
    return err;
  }
  // 一旦设备开启成功,系统会给设备分配一个名称对于tun设备,一般为tunX,X为从0开始的编号,对于tap设备
  // 一般为tapX,X为从0开始的编号
  strcpy(dev, ifr.ifr_name); // 拷贝设备的名称至dev中
  return fd;
}

如果我们想启动一个TAP 设备的话,很简单,将上面的ifr.ifr_flags = IFF_TUN | IFF_NO_PI;改为ifr.ifr_flags = IFF_TAP | IFF_NO_PI;即可,那么我们就启动了一个 TAP 设备。

设定网络地址

上面的代码打开了文件,并且返回了文件的描述符,但是还不够,对于一张网卡来说,我们还要给其配置网络地址,有时候甚至是路由信息,网卡才能够正常地工作。

一旦虚拟的 TUN/TAP 设备启动成功,我们便可以通过命令来给其设定地址。

我来举个例子,以一个 TAP 设备为例:

% sudo ip link set dev tap0 up  # 启动tap0网卡,虽然网卡已经启动,但是此时使用ipconfig命令并不能看到tap0这个设备,因为我们还没有给其配置ip地址

% sudo ip address add dev tap0 10.0.1.5/24 # 给tap0设置ip地址

% ifconfig  # 此时在ifconfig命令下已经可以看到tap0设备了
tap0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.0.1.5  netmask 255.255.255.0  broadcast 0.0.0.0
        inet6 fe80::1872:80ff:fe20:46e2  prefixlen 64  scopeid 0x20<link>
        ether 1a:72:80:20:46:e2  txqueuelen 1000  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 0  bytes 0 (0.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

        
% ip route show # 显示所有的路由信息
default via 192.168.140.2 dev ens33 proto static metric 100
10.0.0.0/24 dev ens39 proto kernel scope link src 10.0.0.130 metric 100
10.0.1.0/24 dev tap0 proto kernel scope link src 10.0.1.5 # 给网卡设定ip后,系统自动添加了路由
192.168.140.0/24 dev ens33 proto kernel scope link src 192.168.140.133 metric 100

通过手动敲命令的方式来配置 tap0 设备,略显麻烦,其实我们可以直接在程序中调用 system 函数:

int 
run_cmd(char *cmd, ...)
{
    va_list ap;
    char buf[CMDBUFLEN];
    va_start(ap, cmd);
    vsnprintf(buf, CMDBUFLEN, cmd, ap);
    va_end(ap);
    if (debug) { // DEBUG模式下输出信息
        printf("EXEC: %s\n", buf);
    }
    return system(buf);
}

将上面的命令直接传递给 run_cmd 函数即可.

当然,如果你不喜欢这种方式,我们自然还可以有其他的方法,比如说使用下面的函数:

int
set_stack_attribute(char *dev)
{
    struct ifreq ifr;
    struct sockaddr_in addr;
    int sockfd, err = -1;

    bzero(&addr, sizeof(addr));
    addr.sin_family = AF_INET;
    inet_pton(AF_INET, tapaddr, &addr.sin_addr);

    bzero(&ifr, sizeof(ifr));
    strcpy(ifr.ifr_name, dev);
    bcopy(&addr, &ifr.ifr_addr, sizeof(addr));
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }

    // ifconfig tap0 10.0.1.5 #设定ip地址
    if ((err = ioctl(sockfd, SIOCSIFADDR, (void *)&ifr)) < 0) {
        perror("ioctl SIOSIFADDR");
        goto done;
    }

    /* 获得接口的标志 */
    if ((err = ioctl(sockfd, SIOCGIFFLAGS, (void *)&ifr)) < 0) {
        perror("ioctl SIOCGIFADDR");
        goto done;
    }

    /* 设置接口的标志 */
    ifr.ifr_flags |= IFF_UP;
    // ifup tap0 #启动设备
    if ((err = ioctl(sockfd, SIOCSIFFLAGS, (void *)&ifr)) < 0) {
        perror("ioctl SIOCSIFFLAGS");
        goto done;
    }

    inet_pton(AF_INET, "255.255.255.0", &addr.sin_addr);
    bcopy(&addr, &ifr.ifr_netmask, sizeof(addr));
    // ifconfig tap0 10.0.1.5/24 #设定子网掩码
    if ((err = ioctl(sockfd, SIOCSIFNETMASK, (void *) &ifr)) < 0) {
        perror("ioctl SIOCSIFNETMASK");
        goto done;
    }


done:
    close(sockfd);
    return err;
}

上面的函数主要干的事情和上面的命令大致相同。

收发数据

收发数据非常简单,每次读取返回的文件描述符即可接收数据,没有数据到来时,会一直阻塞在哪里,当然,你也可以玩一下非阻塞 IO,然后想要发送数据的话,只需要将数据写入到该文件描述符对应的文件中即可。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容