1. 程式人生 > >虛擬網絡卡 TUN/TAP 驅動程式設計原理

虛擬網絡卡 TUN/TAP 驅動程式設計原理

TUN/TAP虛擬網路裝置為使用者空間程式提供了網路資料包的傳送和接收能力。他既可以當做點對點裝置(TUN),也可以當做乙太網裝置(TAP)。實際上,不僅Linux支援TUN/TAP虛擬網路裝置,其他UNIX也是支援的,他們之間只有少許差別。

原理簡介

TUN/TAP 虛擬網路裝置的原理比較簡單,他在Linux核心中添加了一個TUN/TAP虛擬網路裝置的驅動程式和一個與之相關連的字元裝置 /dev/net/tun,字元裝置tun作為使用者空間和核心空間交換資料的介面。當核心將資料包傳送到虛擬網路裝置時,資料包被儲存在裝置相關的一個隊 列中,直到使用者空間程式通過開啟的字元裝置tun的描述符讀取時,它才會被拷貝到使用者空間的緩衝區中,其效果就相當於,資料包直接傳送到了使用者空間。通過 系統呼叫write傳送資料包時其原理與此類似。

值得注意的是:一次read系統呼叫,有且只有一個數據包被傳送到使用者空間,並且當用戶空間的緩衝區比較小時,資料包將被截斷,剩餘部分將永久地消失,write系統呼叫與read類似,每次只發送一個數據包。所以在編寫此類程式的時候,請用足夠大的緩衝區

直接呼叫系統呼叫read/write ,避免採用C語言的帶快取的IO函式。

準備工作

首先你需要一個能工作的Linux作業系統,並且核心支援TUN/TAP虛擬網路裝置,如果沒有,請在核心中選中:

Device Drivers => Network device support => Universal TUN/TAP device driver support


你可以選擇編譯進核心或者是編譯成模組,然後重新編譯核心並用新核心啟動。如果你編譯的是模組,那麼在下步開始之前,你需要手工載入它。

[email protected] ~ # modprobe tun


開始程式設計



從程式碼開始,

12 # include < linux/ if_tun. h>
13
14 int tun_create( char * dev, int flags)
15 {
16     struct ifreq ifr;
17     int fd, err;
18
19     assert ( dev ! = NULL ) ;
20
21     if ( ( fd = open ( "/dev/net/tun" , O_RDWR) ) < 0)
22         return fd;
23
24     memset

( & ifr, 0, sizeof ( ifr) ) ;
25     ifr. ifr_flags | = flags;
26     if ( * dev ! = '/0' )
27         strncpy ( ifr. ifr_name, dev, IFNAMSIZ) ;
28     if ( ( err = ioctl( fd, TUNSETIFF, ( void * ) & ifr) ) < 0) {
29         close ( fd) ;
30         return err;
31     }
32     strcpy ( dev, ifr. ifr_name) ;
33
34     return fd;
35 }


為了使用TUN/TAP裝置,我們必須包含特定的頭 檔案linux/if_tun.h,如12行所示。在21行,我們打開了字元裝置/dev/net/tun。接下來我們需要為ioctl的 TUNSETIFF命令初始化一個結構體ifr,一般的時候我們只需要關心其中的兩個成員ifr_name, ifr_flags。ifr_name定義了要建立或者是開啟的虛擬網路裝置的名字,如果它為空或者是此網路裝置不存在,核心將新建一個虛擬網路裝置,並 返回新建的虛擬網路裝置的名字,同時檔案描述符fd也將和此網路裝置建立起關聯。如果並沒有指定網路裝置的名字,核心將根據其型別自動選擇tunXX和 tapXX作為其名字。ifr_flags用來描述網路裝置的一些屬性,比如說是點對點裝置還是乙太網裝置。詳細的選項解釋如下:

  • IFF_TUN: 建立一個點對點裝置
  • IFF_TAP: 建立一個乙太網裝置
  • IFF_NO_PI: 不包含包資訊,預設的每個資料包當傳到使用者空間時,都將包含一個附加的包頭來儲存包資訊
  • IFF_ONE_QUEUE: 採用單一佇列模式,即當資料包佇列滿的時候,由虛擬網路裝置自已丟棄以後的資料包直到資料包佇列再有空閒。

配置的時候,IFF_TUN和IFF_TAP必須擇一,其他選項則可任意組合。其中IFF_NO_PI沒有開啟時所附加的包資訊頭如下:

struct tun_pi {
    unsigned short flags;
    unsigned short proto;
} ;


目前,flags只在收取資料包的時候有效,當它的TUN_PKT_STRIP標誌被置時,表示當前的使用者空間緩衝區太小,以致資料包被截斷。proto成員表示傳送/接收的資料包的協議。

上面程式碼中的檔案描述符fd除了支援TUN_SETIFF和其他的常規ioctl命令外,還支援以下命令:

  • TUNSETNOCSUM: 不做校驗和校驗。引數為int型的bool值。
  • TUNSETPERSIST: 把對應網路裝置設定成持續模式,預設的虛擬網路裝置,當其相關的檔案符被關閉時,也將會伴隨著與之相關的路由等資訊同時消失。如果設定成持續模式,那麼它將會被保留供以後使用。引數為int型的bool值。
  • TUNSETOWNER: 設定網路裝置的屬主。引數型別為uid_t。
  • TUNSETLINK: 設定網路裝置的鏈路型別,此命令只有在虛擬網路裝置關閉的情況下有效。引數為int型。

一個小例項

int main( int argc, char * argv[ ] )
{
        int tun, ret;
        char tun_name[ IFNAMSIZ] ;
        unsigned char buf[ 4096] ;

         tun_name[ 0] = '/0' ;
         tun = tun_create( tun_name, IFF_TUN | IFF_NO_PI) ;
        if ( tun < 0) {
                perror ( "tun_create" ) ;
                return 1;
        }
        printf ( "TUN name is %s/n" , tun_name) ;

        while ( 1) {
                unsigned char ip[ 4] ;

                 ret = read ( tun, buf, sizeof ( buf) ) ;
                if ( ret < 0)
                        break ;
                memcpy ( ip, & buf[ 12] , 4) ;
                memcpy ( & buf[ 12] , & buf[ 16] , 4) ;
                memcpy ( & buf[ 16] , ip, 4) ;
                 buf[ 20] = 0;
                * ( ( unsigned short * ) & buf[ 22] ) + = 8;
                printf ( "read %d bytes/n" , ret) ;
                 ret = write ( tun, buf, ret) ;
                printf ( "write %d bytes/n" , ret) ;
        }

        return 0;
}


以上程式碼簡答地處理了ICMP的ECHO包,並回應以ECHO REPLY。

首先執行這個程式:

[email protected] test # ./a.out
TUN name is tun0


接著在另外一個終端執行如下命令:

[email protected] linux-2.6.15-gentoo # ifconfig tun0 0.0.0.0 up
[email protected] linux-2.6.15-gentoo # route add 10.10.10.1 dev tun0
[email protected] linux-2.6.15-gentoo # ping 10.10.10.1
PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=1.09 ms
64 bytes from 10.10.10.1: icmp_seq=2 ttl=64 time=5.18 ms
64 bytes from 10.10.10.1: icmp_seq=3 ttl=64 time=3.37 ms

--- 10.10.10.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2011ms
rtt min/avg/max/mdev = 1.097/3.218/5.181/1.671 ms


可見,我們順利地接受到了迴應包,這時,切回到前一個終端下:

read 84 bytes
write 84 bytes
read 84 bytes
write 84 bytes
read 84 bytes
write 84 bytes


一切正如我們所預想的那樣。

TUN/TAP能做什麼?

hoho,問這個問題似乎有些傻,你說一個網絡卡能做什麼?我可以告訴你兩個基於此的開源專案:vtunopenvpn ,至於其他的應用,請自由發揮你的想像力吧!

TUN/TAP 驅動的使用並分析虛擬網絡卡 TUN/TAP 驅動程式在 Linux 環境下的設計思路

簡介

虛擬網絡卡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驅動實現的隧道封裝。

在linux 2.4核心版本及以後版本中,tun/tap驅動是作為系統預設預先編譯進核心中的。在使用之前,確保已經裝載了tun/tap模組並建立裝置檔案:



#modprobe tun

#mknod /dev/net/tun c 10 200


引數c表示是字元裝置, 10和200分別是主裝置號和次裝置號。

這樣,我們就可以在程式中使用該驅動了。



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驅動程式的資料接收和傳送並不直接和真實網絡卡打交道,而是通過使用者態來轉交。在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驅動很巧妙的將字元驅動和網絡卡驅動糅合在一起,本文重點分析兩種驅動之間的銜接,具體的驅動處理細節沒有一一列出,請讀者參考相關文件。

參考:

http://linux.chinaunix.net/docs/2006-11-24/3296.shtml

http://hi.baidu.com/zkheartboy/blog/item/e96acf33508e4a40ad4b5f88.html

相關推薦

虛擬 TUN/TAP 驅動程式設計原理

TUN/TAP虛擬網路裝置為使用者空間程式提供了網路資料包的傳送和接收能力。他既可以當做點對點裝置(TUN),也可以當做乙太網裝置(TAP)。實際上,不僅Linux支援TUN/TAP虛擬網路裝置,其他UNIX也是支援的,他們之間只有少許差別。原理簡介 TUN/TAP 虛擬

虛擬TUN/TAP裝置使用例項

文章出處:http://blog.csdn.net/solstice/article/details/6579232 轉載淵源:這篇文章源自陳碩老師的部落格,原文討論的主題是在繞開作業系統協議棧的情況下,對tcp併發連線數的支援情況;因為其中對TUN / TAP裝置的使用

為openvpn建立tap虛擬

openvpn配置: 1、openvpn會使用tap虛擬網絡卡,安裝openvpn後會自動建立一個tap裝置。 2、需要建立兩個vpn連線時,就需要手動增加一個tap虛擬裝置。增加的方法為: "C:\Program Files\TAP-Windows\bin\tapinstall.exe" install

linux驅動驅動-虛擬驅動編寫

我們來實現這麼一個目的 我ping 3.3.3.4的時候,按理說如果是真實網絡卡的話,我們3.3.3.3的機器,和3.3.3.4的機器。ping 3.3.3.4的時候3.3.3.3的機器會把包發給3.3.3.4,3.3.3.4收到包之後又會把包發給3.3.3.3 在驅

Linux驅動虛擬

寫網絡卡驅動之前我總結一下個人的一些觀點:其實寫驅動並不是大家想想的那麼難,這裡我客觀評價一下核心層和應用層的區別: 底層: 工作在核心層的朋友應該有這種感覺,才開始學的時候真的很難,也就是說上手難,我就拿Linux驅動來說吧,寫一個完整的驅動,你得裝一個虛

windows虛擬驅動開發

詳見      http://blog.csdn.net/fanxiushu/article/details/8526708      http://blog.csdn.net/fanxiushu/article/details/8526719      http://blog.csdn.net/fanxi

驅動虛擬驅動編寫

#include <Linux/module.h> #include <linux/errno.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/k

圖解幾個與Linux網路虛擬化相關的虛擬-VETH/MACVLAN/MACVTAP/IPVLAN

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

Linux移除虛擬

今天上大資料實踐課時,使用學校提供的雲主機平臺建立了幾臺vps,但是安全組配置好之後發現無法用ssh無法登入,ping也不通,提示網路無法到達。 但是拿別人的電腦試了下能順利使用ssh連線。 有人說是我電腦防火牆的問題,對於這個說法我是萬萬不信的。 反覆觀察後發現: vps的地址是172.19.241.45

已解決 vmware 虛擬機器安裝後沒有虛擬問題

        我用的方法是重灌vmware ,使用的是win10的系統 。之前安裝網ubuntu以後,發現主機並沒有虛擬網絡卡,也百度了各種方法,然而並沒有什麼用,也問了很多人,他們也提供了各種方法,但是試過之後也都沒有什麼用,下面我就介紹一下我成功的操作步

Linux redhat 6.5 安裝I219-LM 驅動

--------安裝---------- 1. 首先,下載網絡卡驅動e1000e http://downloadcenter.intel.com/download/15817 2. 複製到linux系統,解壓縮。 3. 進入目錄下的src資料夾 4. 編譯 make install 5. 更新模組

新增橋,新增一對虛擬

新增網橋 新增網橋,名稱為br0 brctl addbr br0 將網橋關聯到網絡卡ens33 brctl addif br0 ens33 這裡如果你是ssh連線,執行完了你可能就會斷開連線了,所以最好吧這一步和下面幾步連在一起執行這樣就不會斷開了 brctl addif

安裝過VMware後,沒有虛擬vmnet1、vmnet8

原文地址:https://blog.csdn.net/qq_36769100/article/details/78802756 問題簡述: win8、win10系統,安裝過VMware後,在本地的網路介面卡中找不到相關的虛擬網絡卡VMnet。 解決步驟: 1.開

Linux Namespace Veth虛擬

Linux Namespace Network Namespace 建立networ

混合多系統虛擬核間中斷實現

APIC介紹APIC的全稱是Advanced Programmable Interrupt Controller,是8259控制器的升級版本。APIC包括了Local APIC和I/O APIC兩部分內容,Local APIC是總的控制器,位於CPU內部;I/O APIC主要用於處理外部裝置的中斷。引入

centos7 增加虛擬

確認核心是否有tun模組 # modinfo tun yum install tunctl -y 如果找不到 vim /etc/yum.repos.d/nux-misc.repo [nux-misc]name=Nux Miscbaseurl=http://li.nux.ro/download/nux

安裝VMware後,本機網路介面卡中沒有虛擬VMnet1、VMnet8

1、開啟本機服務,開啟相關的服務。   2、重置虛擬網路編輯器 1>開啟VMware,點選下圖左上角標註的"編輯",然後選中並進入"虛擬網路編輯器"。 2>還原預設設定   如果 "還原預設設定" 如上圖一

VMWare克隆CentOS後虛擬無法啟動

大家在學習linux時,會建立linux虛擬模板,然後我們會建立克隆,但克隆會檢視啟動過程會發現網絡卡無法啟動,此時我們需要如下操作。 vi /etc/sysconfig/network-scripts/ifcfg-eth0 #vi編輯器編輯這個檔案。 #按i進入編輯模式,刪除HWAD

VMware安裝後沒有虛擬VMnet1和VMnet8

筆者是大資料初學者,(同時也是第一次寫部落格,本著互相交流、互相學習的心態。不喜勿噴.....哈哈 )這兩天剛剛接觸到lunix的學習,昨天安裝好了VMware,今天進行了虛擬機器通訊的配置,對於建立好的虛擬機器,可以ping通網路,包括(主機的IP,閘道器,外

在win10環境下安裝vmware軟體後沒有vmnet1、vmnet8虛擬

在win10環境下安裝vmware軟體後沒有虛擬網絡卡!    此問題困擾已經半年之久,差點重灌系統,更甚至更換筆記本,網上的方法幾乎全部用過了,最後一波自己嘗試終於解決了!!!   解決辦法:  環境確認win10,