原文Universal TUN/TAP device driver
1. 描述
TUN/TAP為用戶空間提供分組接收和傳輸。既可以看作一個Point-to-Point設(shè)備,也可以看做一個Ethernet設(shè)備。它從用戶空間程序接收分組,而不是從物理媒介;將分組寫入用戶空間程序而不是通過物理媒介。
一個程序打開/dev/net/tun字符型文件,并向內(nèi)核發(fā)出ioctl()來注冊一個網(wǎng)絡(luò)設(shè)備。根據(jù)選項,這個網(wǎng)絡(luò)設(shè)備將顯示為tunXX或者tapXX。當程序關(guān)閉文件描述符時,這個網(wǎng)絡(luò)設(shè)備和所有對應(yīng)的路由都將關(guān)閉。
用戶空間程序read/write IP數(shù)據(jù)包(通過tun)還是以太網(wǎng)幀(通過tap),取決于選擇的設(shè)備類型。正在使用哪一個取決于ioctl()設(shè)置的flag。
http://vtun.sourceforge.net/tun下的軟件包包含兩個簡單的示例,介紹如何使用tun和tap設(shè)備。兩個示例的工作原理都像在兩個網(wǎng)絡(luò)接口之間的橋一樣。
- br_select.c - 基于選擇系統(tǒng)調(diào)用的橋
- br_sigio.c - 基于async io和SIGIO信號的橋
另外,最好的例子來源于VTun(http://vtun.sourceforge.net) :))
2. 配置
創(chuàng)建設(shè)備節(jié)點:
mkdir /dev/net
mknod /dev/net/tun c 10 200
設(shè)置權(quán)限:
chmod 0666 /dev/net/tun
驅(qū)動程序模塊自動加載:
確保內(nèi)核啟用了“內(nèi)核模塊加載器” - 模塊自動加載支持。 內(nèi)核應(yīng)該在第一次訪問時加載它。
手動加載:
insert the module by hand:
modprobe tun
后一種方法,每次當你需要使用模塊的時候,你都需要加載它。另一種方法將在你打開/dev/net/tun的時候自動加載。
3. 編程接口
3.1 網(wǎng)絡(luò)設(shè)備分配
設(shè)備的名稱char *dev是具有格式的字符串(e.g. "tun%d"),也可以是任何有效的網(wǎng)絡(luò)設(shè)備名稱。注意,字符指針將會被真實網(wǎng)絡(luò)設(shè)備名覆蓋。
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_alloc(char *dev)
{
struct ifreq ifr;
int fd, err;
//獲取/dev/net/tun的文件描述符
if( (fd = open("/dev/net/tun", O_RDWR)) < 0 )
return tun_alloc_old(dev);
memset(&ifr, 0, sizeof(ifr));
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
*/
ifr.ifr_flags = IFF_TUN;
if( *dev )
strncpy(ifr,ifr_name, dev, IFNAMSIZ);
if( (err = ioctl(fd, TUNSETIFF, (void *) &ifr)) < 0 ) {
close(fd);
return err;
}
strcpy(dev, ifr.ifr_name);
return fd;
}
3.2 幀格式
如果IFF_NO_PI標志沒有被設(shè)置,每一幀格式如下:
Flags [2 bytes]
Proto [2 bytes]
Raw protocol(IP, IPv6, etc) frame.
3.3 多隊列tuntap接口
從3.8版開始,Linux支持多隊列tuntap,它可以使用多個文件描述符(隊列)來并行發(fā)送或接收數(shù)據(jù)包。 設(shè)備分配與以前相同,如果用戶想要創(chuàng)建多個隊列,則必須使用IFF_MULTI_QUEUE標志多次調(diào)用具有相同設(shè)備名稱的TUNSETIFF。
char *dev是設(shè)備的名稱,queues是要創(chuàng)建的隊列數(shù),fds用于存儲和返回創(chuàng)建給調(diào)用者的文件描述符(隊列)。 每個文件描述符都作為一個隊列的接口,可以被用戶空間訪問。
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_alloc_mq(char *dev, int queues, int *fds)
{
struct ifreq ifr;
int fd, err, i;
if (!dev)
return -1;
memset(&ifr, 0, sizeof(ifr));
/* Flags: IFF_TUN - TUN device (no Ethernet headers)
* IFF_TAP - TAP device
*
* IFF_NO_PI - Do not provide packet information
* IFF_MULTI_QUEUE - Create a queue of multiqueue device
*/
ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_MULTI_QUEUE;
strcpy(ifr.ifr_name, dev);
for (i = 0; i < queues; i++) {
if ((fd = open("/dev/net/tun", O_RDWR)) < 0)
goto er;
err = ioctl(fd, TUNSETIFF, (void *)&ifr);
if (err) {
close(fd);
goto err;
}
fds[i] = fd;
}
return 0;
err:
for (--i; i >= 0; i--)
close(fds[i]);
return err;
}
引入了一個新的ioctl(TUNSETQUEUE)來啟用或禁用隊列。 當用IFF_DETACH_QUEUE標志調(diào)用它時,隊列被禁用。 當用IFF_ATTACH_QUEUE標志調(diào)用它時,隊列被啟用。 通過TUNSETIFF創(chuàng)建隊列后,隊列默認啟用。
fd是我們想要啟用或禁用的文件描述符(隊列),當enable為真時,我們啟用它,否則我們禁用它
#include <linux/if.h>
#include <linux/if_tun.h>
int tun_set_queue(int fd, int enable)
{
struct ifreq ifr;
memset(&ifr, 0, sizeof(ifr));
if (enable)
ifr.ifr_flags = IFF_ATTACH_QUEUE;
else
ifr.ifr_flags = IFF_DETACH_QUEUE;
return ioctl(fd, TUNSETQUEUE, (void *)&ifr);
}