tun/tap 驅動程序實現了虛擬網卡的功能,tun表示虛擬的是點對點設備,tap表示虛擬的是以太網設備,這兩種設備針對網絡包實施不同的封裝。利用tun/tap 驅動,可以將tcp/ip協議棧處理好的網絡分包傳給任何一個使用tun/tap驅動的進程,由進程重新處理后再發到物理鏈路中。
開源項目openvpn (http://openvpn.sourceforge.net)和Vtun(http://vtun.sourceforge.net)都是利用tun/tap驅動實現的隧道封裝。
Tun Tap區別
現在統一的說法是:
TUN是點對點的三層設備,工作在IP層,處理IP分組;
TAP是虛擬以太網設備,工作在第二層,處理以太網幀;
而以下查得的資料也印證了這一點:
摘自Universal TUN/TAP device driver的FAQ:
The TUN is Virtual Point-to-Point network device.TUN driver was designed as low level kernel support for IP tunneling.
The TAP is a Virtual Ethernet network device.TAP driver was designed as low level kernel support for Ethernet tunneling.
TUN works with IP frames. TAP works with Ethernet frames
摘自OpenVPN的FAQ:The difference between a tun and tap device is this: a tun device is a virtual IP point-to-point device and a tap device is a virtual ethernet device.
其實這只是一面,說的很籠統,下面看一些更多的不同:
1、設備號大不同。
TUN 設備號是10 200,是字符設備下的misc設備,在2.6內核的miscdevice.h中定義的TUN_MINOR(從設備號)也是200;而TAP的是36 16,字符設備的netlink支持。
參見device-list(2008-3):
http://www.lanana.org/docs/device-list/devices-2.6+.txt
2、顯然,設備號的不同導致了文件節點的不同:
TUN:/dev/net/tun
TAP:/dev/tap0
但是Universal TUN/TAP device driver的似乎是/dev/tun的類型,仔細發現這是在2.4內核之前的,在2.6以后,TUN設備就會對應於文件/dev/net/tun。
3、補充一下,從其他資料偶爾看到了:
TAP:子網掩碼是/24,255.255.255.0,對應以太網設備
TUN:子網掩碼是/30,255.255.255.252,點對點設備。
最后,在VTUN源代碼咋實現open設備的打開時,TAP和TUN也是分開的,好像有兩個TUN,一個是/dev/tun的節點(不包含<linux/if_tun.h>),一個是/dev/net/tun(包含<linux/if_tun.h>)。前者看來是為了2.4的核而設置的。
總結一下吧(主要是TUN設備的困惑,TAP就沒有):
2.4的核:TUN 設備號是36 16+,文件節點:/dev/tun0
2.6的核:TUN 設備號是10 200,文件節點:/dev/net/tun
補充一下,其實具體是TUN或者是TAP,都不是在open打開設備的文件節點時就確定的。文件節點只是內核提供給用戶的接口而已,應用程序需要ioctl
設置自己的虛擬網絡設備的工作模式,是TUN還是TAP。
所以上面第二點的理解,需要變通一下!
簡介
虛擬網卡Tun/tap驅動是一個開源項目,支持很多的類UNIX平台,OpenVPN和Vtun都是基於它實現隧道包封裝。本文將介紹tun/tap驅動的使用並分析虛擬網卡tun/tap驅動程序在linux環境下的設計思路。
tun/tap 驅動程序實現了虛擬網卡的功能,tun表示虛擬的是點對點設備,tap表示虛擬的是以太網設備,這兩種設備針對網絡包實施不同的封裝。利用tun/tap 驅動,可以將tcp/ip協議棧處理好的網絡分包傳給任何一個使用tun/tap驅動的進程,由進程重新處理后再發到物理鏈路中。開源項目 openvpn( http://openvpn.sourceforge.net)和Vtun( http://vtun.sourceforge.net)都是利用tun/tap驅動實現的隧道封裝。
使用tun/tap驅動
在linux 2.4內核版本及以后版本中,tun/tap驅動是作為系統默認預先編譯進內核中的。在使用之前,確保已經裝載了tun/tap模塊並建立設備文件:
#modprobe tun #mknod /dev/net/tun c 10 200
參數c表示是字符設備, 10和200分別是主設備號和次設備號。
這樣,我們就可以在程序中使用該驅動了。
使用tun/tap設備的示例程序(摘自openvpn開源項目 http://openvpn.sourceforge.net,tun.c文件)
int open_tun (const char *dev, char *actual, int size) { struct ifreq ifr; int fd; char *device = "/dev/net/tun"; if ((fd = open (device, O_RDWR)) < 0) //創建描述符 msg (M_ERR, "Cannot open TUN/TAP dev %s", device); memset (&ifr, 0, sizeof (ifr)); ifr.ifr_flags = IFF_NO_PI; if (!strncmp (dev, "tun", 3)) { ifr.ifr_flags |= IFF_TUN; } else if (!strncmp (dev, "tap", 3)) { ifr.ifr_flags |= IFF_TAP; } else { msg (M_FATAL, "I don't recognize device %s as a TUN or TAP device",dev); } if (strlen (dev) > 3) /* unit number specified? */ strncpy (ifr.ifr_name, dev, IFNAMSIZ); if (ioctl (fd, TUNSETIFF, (void *) &ifr) < 0) //打開虛擬網卡 msg (M_ERR, "Cannot ioctl TUNSETIFF %s", dev); set_nonblock (fd); msg (M_INFO, "TUN/TAP device %s opened", ifr.ifr_name); strncpynt (actual, ifr.ifr_name, size); return fd; }
調用上述函數后,就可以在shell命令行下使用ifconfig 命令配置虛擬網卡了,通過生成的字符設備描述符,在程序中使用read和write函數就可以讀取或者發送給虛擬的網卡數據了。
Tun/tap驅動程序工作原理
做 為虛擬網卡驅動,Tun/tap驅動程序的數據接收和發送並不直接和真實網卡打交道,而是通過用戶態來轉交。在linux下,要實現核心態和用戶態數據的 交互,有多種方式:可以通用socket創建特殊套接字,利用套接字實現數據交互;通過proc文件系統創建文件來進行數據交互;還可以使用設備文件的方 式,訪問設備文件會調用設備驅動相應的例程,設備驅動本身就是核心態和用戶態的一個接口,Tun/tap驅動就是利用設備文件實現用戶態和核心態的數據交 互。
從結構上來說,Tun/tap驅動並不單純是實現網卡驅動,同時它還實現了字符設備驅動部分。以字符設備的方式連接用戶態和核心態。下面是示意圖:
Tun/tap 驅動程序中包含兩個部分,一部分是字符設備驅動,還有一部分是網卡驅動部分。利用網卡驅動部分接收來自TCP/IP協議棧的網絡分包並發送或者反過來將接 收到的網絡分包傳給協議棧處理,而字符驅動部分則將網絡分包在內核與用戶態之間傳送,模擬物理鏈路的數據接收和發送。Tun/tap驅動很好的實現了兩種 驅動的結合。
下面是定義的tun/tap設備結構:
struct tun_struct { char name[8]; //設備名 unsigned long flags; //區分tun和tap設備 struct fasync_struct *fasync; //文件異步通知結構 wait_queue_head_t read_wait; //等待隊列 struct net_device dev; //linux 抽象網絡設備結構 struct sk_buff_head txq; //網絡緩沖區隊列 struct net_device_stats stats; //網卡狀態信息結構 };
struct net_device結構是linux內核提供的統一網絡設備結構,定義了系統統一的訪問接口。
Tun/tap驅動中實現的網卡驅動的處理例程:
static int tun_net_open(struct net_device *dev);
static int tun_net_close(struct net_device *dev);
static int tun_net_xmit(struct sk_buff *skb, struct net_device *dev);//數據包發送例程
static void tun_net_mclist(struct net_device *dev);//設置多點傳輸的地址鏈表
static struct net_device_stats *tun_net_stats(struct net_device *dev);//當一個應用程序需要知道網絡接口的一些統計數據時,可調用該函數,如ifconfig、netstat等。
int tun_net_init(struct net_device *dev);//網絡設備初始例程
字符設備部分:
在Linux中,字符設備和塊設備統一以文件的方式訪問,訪問它們的接口是統一的,都是使用open()函數打開設備文件或普通文件,用read()和write()函數實現讀寫文件等等。Tun/tap驅動定義的字符設備的訪問接口如下:
static struct file_operations tun_fops = {
owner: THIS_MODULE,
llseek: tun_chr_lseek,
read tun_chr_read,
write: tun_chr_write,
poll: tun_chr_poll,
ioctl: tun_chr_ioctl,
open: tun_chr_open,
release: tun_chr_close,
fasync: tun_chr_fasync
};
在內核中利用misc_register() 函數將該驅動注冊為非標准字符設備驅動,提供字符設備具有的各種程序接口。代碼摘自linux-2.4.20\linux-2.4.20\drivers\net\tun.c
static struct miscdevice tun_miscdev= { TUN_MINOR, "net/tun", &tun_fops }; int __init tun_init(void) { … if (misc_register(&tun_miscdev)) { printk(KERN_ERR "tun: Can't register misc device %d\n", TUN_MINOR); return -EIO; } return 0; }
當打開一個tun/tap設備時,open 函數將調用tun_chr_open()函數,其中將完成一些重要的初始化過程,包括設置網卡驅動部分的初始化函數以及網絡緩沖區鏈表的初始化和等待隊列 的初始化。Tun/tap驅動中網卡的注冊被嵌入了字符驅動的ioctl例程中,它是通過對字符設備文件描述符利用自定義的ioctl設置標志 TUNSETIFF完成網卡的注冊的。下面是函數調用關系示意圖:
使 用ioctl()函數操作字符設備文件描述符,將調用字符設備中tun_chr_ioctl 來設置已經open好的tun/tap設備,如果設置標志為TUNSETIFF,則調用tun_set_iff() 函數,此函數將完成很重要的一步操作,就是對網卡驅動進行注冊register_netdev(&tun->dev),網卡驅動的各個處理 例程的掛接在open操作時由tun_chr_open()函數初始化好了。
Tun/tap設備的工作過程:
Tun/tap 設備提供的虛擬網卡驅動,從tcp/ip協議棧的角度而言,它與真實網卡驅動並沒有區別。從驅動程序的角度來說,它與真實網卡的不同表現在tun/tap 設備獲取的數據不是來自物理鏈路,而是來自用戶區,Tun/tap設備驅動通過字符設備文件來實現數據從用戶區的獲取。發送數據時tun/tap設備也不 是發送到物理鏈路,而是通過字符設備發送至用戶區,再由用戶區程序通過其他渠道發送。
發送過程:
使 用tun/tap網卡的程序經過協議棧把數據傳送給驅動程序,驅動程序調用注冊好的hard_start_xmit函數發 送,hard_start_xmit函數又會調用tun_net_xmit函數,其中skb將會被加入skb鏈表,然后喚醒被阻塞的使用tun/tap設 備字符驅動讀數據的進程,接着tun/tap設備的字符驅動部分調用其tun_chr_read()過程讀取skb鏈表,並將每一個讀到的skb發往用戶 區,完成虛擬網卡的數據發送。
接收數據的過程:
當 我們使用write()系統調用向tun/tap設備的字符設備文件寫入數據時,tun_chr_write函數將被調用,它使用 tun_get_user從用戶區接受數據,其中將數據存入skb中,然后調用關鍵的函數netif_rx(skb) 將skb送給tcp/ip協議棧處理,完成虛擬網卡的數據接收。
小結
tun/tap驅動很巧妙的將字符驅動和網卡驅動糅合在一起,本文重點分析兩種驅動之間的銜接,具體的驅動處理細節沒有一一列出,請讀者參考相關文檔。
參考資料
- 《Linux 設備驅動程序 第二版》,(美)Alessando Rubini
- http://openvpn.sourceforge.net
- Linux串口上網的簡單實現
- linux 源代碼