PPP通用驅動和通道介面¶
Paul Mackerras paulus@samba.org
2002年2月7日
linux-2.4中的通用PPP驅動程式提供了一種功能的實現,該功能可用於任何PPP實現,包括
網路介面單元(ppp0等)
到網路程式碼的介面
PPP多鏈路:在多個連結之間拆分資料報,以及對接收到的片段進行排序和組合
透過/dev/ppp字元裝置與pppd的介面
資料包壓縮和解壓縮
TCP/IP報頭壓縮和解壓縮
檢測需求撥號和空閒超時的網路流量
簡單的資料包過濾
對於傳送和接收PPP幀,通用PPP驅動程式呼叫PPP channels的服務。 PPP通道封裝了一種將PPP幀從一臺機器傳輸到另一臺機器的機制。 PPP通道的內部實現可能非常複雜,但與通用PPP程式碼的介面非常簡單:它只需要能夠傳送PPP幀,接收PPP幀,以及選擇性地處理ioctl請求。目前,有用於非同步序列埠、同步序列埠和乙太網上的PPP的PPP通道實現。
這種體系結構使得以自然而直接的方式實現PPP多鏈路成為可能,透過允許將多個通道連結到每個ppp網路介面單元。通用層負責在傳輸時分割資料報,並在接收時重新組合它們。
PPP通道API¶
有關用於在通用PPP層和PPP通道之間進行通訊的型別和函式的宣告,請參見 include/linux/ppp_channel.h。
每個通道都必須透過 ppp_channel.ops 指標向通用 PPP 層提供兩個函式
當通用層有一個要傳送的幀時,會呼叫 start_xmit()。通道可以選擇由於流控制原因拒絕該幀。在這種情況下,start_xmit() 應返回 0,並且通道應在稍後再次可以接受幀時呼叫 ppp_output_wakeup() 函式,然後通用層將嘗試重新傳輸被拒絕的幀。如果該幀被接受,則 start_xmit() 函式應返回 1。
ioctl() 提供了一個介面,使用者空間程式可以使用該介面來控制通道行為的各個方面。當用戶空間程式對繫結到該通道的 /dev/ppp 例項執行 ioctl 系統呼叫時,將呼叫此過程。(通常只有 pppd 才會這樣做。)
通用 PPP 層向通道提供七個函式
當建立通道時,會呼叫 ppp_register_channel() 來通知 PPP 通用層它的存在。例如,將序列埠設定為 PPPDISC 行規會導致 ppp_async 通道程式碼呼叫此函式。
當要銷燬通道時,會呼叫 ppp_unregister_channel()。例如,當在序列埠上檢測到結束通話時,ppp_async 通道程式碼會呼叫此函式。
當通道先前拒絕呼叫其 start_xmit 函式,並且現在可以接受更多資料包時,通道會呼叫 ppp_output_wakeup()。
當通道收到完整的 PPP 幀時,通道會呼叫 ppp_input()。
當通道檢測到幀已丟失或刪除時(例如,由於 FCS(幀校驗序列)錯誤),通道會呼叫 ppp_input_error()。
ppp_channel_index() 返回 PPP 通用層分配給此通道的通道索引。通道應提供某種方式(例如,ioctl)將其傳輸回用戶空間,因為使用者空間將需要它來將 /dev/ppp 的例項附加到此通道。
ppp_unit_number() 返回此通道連線到的 ppp 網路介面的單元號,如果該通道未連線,則返回 -1。
將通道連線到 ppp 通用層是從通道程式碼而不是從通用層發起的。期望該通道具有某種方式供使用者級別程序獨立於 ppp 通用層來控制它。例如,對於 ppp_async 通道,這是透過序列埠的檔案描述符提供的。
通常,使用者級別程序將初始化底層通訊介質並使其準備好執行 PPP。例如,對於非同步 tty,這可能涉及設定 tty 速度和模式、發出調變解調器命令,然後與遠端系統進行某種對話以在那裡呼叫 PPP 服務。我們將此過程稱為 discovery。然後,使用者級別程序告訴介質成為 PPP 通道並將其自身註冊到通用 PPP 層。然後,該通道必須將分配給它的通道號報告回用戶級別程序。從那時起,PPP 守護程式 (pppd) 中的 PPP 協商程式碼可以接管並執行 PPP 協商,透過 /dev/ppp 介面訪問通道。
在與 PPP 通用層的介面處,PPP 幀儲存在 skbuff 結構中,並以兩位元組的 PPP 協議號開頭。該幀不包括 0xff address 位元組或 0x03 control 位元組,它們是可選地在非同步 PPP 中使用的。也沒有任何控制字元的轉義,也沒有包括任何 FCS 或幀字元。如果特定介質需要,所有這些都是通道程式碼的責任。也就是說,呈現給 start_xmit() 函式的 skbuff 僅包含 2 位元組協議號和資料,並且呈現給 ppp_input() 的 skbuff 必須採用相同的格式。
通道必須提供一個 ppp_channel 結構的例項來表示該通道。通道可以隨意使用 private 欄位。通道應在呼叫 ppp_register_channel() 之前初始化 mtu 和 hdrlen 欄位,並且在 ppp_unregister_channel() 返回之後才更改它們。mtu 欄位表示 PPP 幀的資料部分的最大大小,即不包括 2 位元組的協議號。
如果通道需要在呈現給它的 skbuff 中進行傳輸時有一些空間(即,在 PPP 幀開始之前,skbuff 資料區域中有一些空閒空間),它應該將 ppp_channel 結構的 hdrlen 欄位設定為所需的空間量。通用 PPP 層將嘗試提供那麼多空間,但通道仍然應該檢查是否有足夠的空間,如果沒有,則複製 skbuff。
在輸入方面,通道應該理想地在呈現給 ppp_input() 的 skbuff 中提供至少 2 個位元組的空間。通用 PPP 程式碼不需要這樣做,但如果這樣做會更有效。
緩衝和流量控制¶
通用 PPP 層旨在最大限度地減少它在傳輸方向上緩衝的資料量。它維護 PPP 單元(網路介面裝置)的傳輸資料包佇列以及每個連線通道的傳輸資料包佇列。通常,該單元的傳輸佇列最多包含一個數據包;例外情況是當 pppd 透過寫入 /dev/ppp 傳送資料包時,以及當核心網路程式碼呼叫通用層的 start_xmit() 函式,並且佇列已停止時,即當通用層呼叫 netif_stop_queue() 時,這隻發生在傳輸超時時。start_xmit 函式始終接受並排隊它被要求傳輸的資料包。
傳輸資料包從 PPP 單元傳輸佇列中出隊,然後進行 TCP/IP 報頭壓縮和資料包壓縮(Deflate 或 BSD-Compress 壓縮),視情況而定。在此之後,資料包不能再重新排序,因為解壓縮演算法依賴於以它們生成的相同順序接收壓縮資料包。
如果未使用多鏈路,則將此資料包傳遞給連線通道的 start_xmit() 函式。如果通道拒絕接收該資料包,則通用層會儲存它以供稍後傳輸。當通道呼叫 ppp_output_wakeup() 或當核心網路程式碼再次呼叫通用層的 start_xmit() 函式時,通用層將再次呼叫通道的 start_xmit() 函式。通用層不包含超時和重傳邏輯;它依賴於核心網路程式碼來實現這一點。
如果正在使用多鏈路,則通用層會將資料包分成一個或多個片段,並在每個片段上放置一個多鏈路報頭。它根據資料包的長度以及目前可能能夠接受片段的通道的數量來決定要使用多少片段。如果通道當前沒有任何排隊要傳輸的片段,則該通道可能能夠接受片段。通道仍然可能拒絕片段;在這種情況下,該片段會排隊,以供通道稍後傳輸。此方案的效果是,更多片段被提供給更高頻寬的通道。這也意味著,在輕負載下,通用層傾向於跨所有通道對大資料包進行分片,從而減少延遲,而在重負載下,資料包將傾向於作為單個片段傳輸,從而減少分片的開銷。
SMP 安全性¶
通用 PPP 層旨在實現 SMP 安全。必要時,鎖用於訪問內部資料結構,以確保它們的完整性。作為其中的一部分,通用層要求通道遵守某些要求,並反過來向通道提供某些保證。本質上,通道需要對構成通道和通用層之間通訊基礎的 ppp_channel 結構提供適當的鎖定。這是因為通道為 ppp_channel 結構提供儲存,因此需要通道提供保證,即此儲存在適當的時間存在並且有效。
通用層需要通道提供這些保證
ppp_channel 物件必須從呼叫 ppp_register_channel() 時存在,直到對 ppp_unregister_channel() 的呼叫返回之後。
在為通道呼叫 ppp_unregister_channel() 時,任何執行緒都不能呼叫該通道的任何 ppp_input()、ppp_input_error()、ppp_output_wakeup()、ppp_channel_index() 或 ppp_unit_number() 函式。
必須從程序上下文中呼叫 ppp_register_channel() 和 ppp_unregister_channel(),而不是從中斷或 softirq/BH 上下文中呼叫。
剩餘的通用層函式可以在 softirq/BH 級別呼叫,但不得從硬體中斷處理程式中呼叫。
通用層可以在 softirq/BH 級別呼叫通道 start_xmit() 函式,但不會在中斷級別呼叫它。因此,start_xmit() 函式可能不會阻塞。
通用層僅會在程序上下文中呼叫通道 ioctl() 函式。
通用層向通道提供以下保證
當任何執行緒已經在該通道的該函式中執行時,通用層不會為該通道呼叫 start_xmit() 函式。
當任何執行緒已經在該通道的該函式中執行時,通用層不會為該通道呼叫 ioctl() 函式。
在對 ppp_unregister_channel() 的呼叫返回時,將沒有任何執行緒從通用層對該通道的 start_xmit() 或 ioctl() 函式的呼叫中執行,並且通用層後續不會呼叫這些函式中的任何一個。
與pppd的介面¶
PPP通用層匯出一個名為/dev/ppp的字元裝置介面。pppd使用它來控制PPP介面單元和通道。雖然只有一個/dev/ppp,但/dev/ppp的每個開啟例項都獨立執行,並且可以附加到PPP單元或PPP通道。這是透過使用file->private_data欄位指向/dev/ppp的每個開啟例項的單獨物件來實現的。這樣,就可以獲得類似於Solaris的克隆開啟的效果,從而使我們能夠控制任意數量的PPP介面和通道,而不必用數百個裝置名稱填充/dev。
開啟/dev/ppp時,將建立一個新的例項,該例項最初未附加。使用ioctl呼叫,然後可以將其附加到現有單元,附加到新建立的單元,或附加到現有通道。附加到單元的例項可以使用read()和write()系統呼叫以及必要的poll()來發送和接收PPP控制幀。類似地,附加到通道的例項可用於在該通道上傳送和接收PPP幀。
就多鏈路而言,單元代表捆綁包,而通道代表各個物理鏈路。因此,透過寫入單元(即,寫入附加到該單元的/dev/ppp例項)傳送的PPP幀將受到捆綁包級別的壓縮以及跨各個鏈路的分段(如果正在使用多鏈路)。相反,透過寫入通道傳送的PPP幀將按原樣在該通道上傳送,而沒有任何多鏈路報頭。
通道最初未附加到任何單元。在此狀態下,它可以用於PPP協商,但不能用於傳輸資料包。然後,可以使用ioctl呼叫將其連線到PPP單元,這使其可以傳送和接收該單元的資料包。
/dev/ppp例項上可用的ioctl呼叫取決於它是未附加的、附加到PPP介面還是附加到PPP通道。未附加例項上可用的ioctl呼叫是
PPPIOCNEWUNIT建立一個新的PPP介面,並使此/dev/ppp例項成為該介面的“所有者”。如果引數指向一個>= 0的int,則該引數應指向一個int,該int是所需的單元號;如果為-1,則分配最小的未使用單元號。成為介面的所有者意味著如果關閉此/dev/ppp例項,該介面將被關閉。
PPPIOCATTACH將此例項附加到現有PPP介面。引數應指向一個包含單元號的int。這不會使此例項成為PPP介面的所有者。
PPPIOCATTCHAN將此例項附加到現有PPP通道。引數應指向一個包含通道號的int。
附加到通道的/dev/ppp例項上可用的ioctl呼叫是
PPPIOCCONNECT將此通道連線到PPP介面。引數應指向一個包含介面單元號的int。如果該通道已連線到介面,則它將返回EINVAL錯誤;如果請求的介面不存在,則返回ENXIO。
PPPIOCDISCONN將此通道與連線到的PPP介面斷開連線。如果該通道未連線到介面,它將返回EINVAL錯誤。
PPPIOCBRIDGECHAN將通道與另一個通道橋接。引數應指向一個包含要橋接到的通道的通道號的int。一旦兩個通道被橋接,透過ppp_input()呈現給一個通道的幀將傳遞到橋接例項以進行轉發傳輸。這允許將幀從一個通道切換到另一個通道:例如,將PPPoE幀傳遞到PPPoL2TP會話中。由於通道橋接中斷了正常的ppp_input()路徑,因此給定的通道不能同時作為橋接的一部分和單元的一部分。如果該通道已經是橋接或單元的一部分,則此ioctl將返回EALREADY錯誤;如果請求的通道不存在,則返回ENXIO。
PPPIOCUNBRIDGECHAN執行與PPPIOCBRIDGECHAN相反的操作,取消橋接通道對。如果該通道不構成橋接的一部分,則此ioctl將返回EINVAL錯誤。
所有其他ioctl命令都傳遞給通道ioctl()函式。
附加到介面單元的例項上可用的ioctl呼叫是
PPPIOCSMRU設定介面的MRU(最大接收單元)。引數應指向一個包含新MRU值的int。
PPPIOCSFLAGS設定控制介面操作的標誌。引數應是指向一個包含新標誌值的int的指標。可以設定的標誌值中的位是
SC_COMP_TCP
啟用傳輸TCP報頭壓縮
SC_NO_TCP_CCID
停用TCP報頭壓縮的連線ID壓縮
SC_REJ_COMP_TCP
停用接收TCP報頭解壓縮
SC_CCP_OPEN
壓縮控制協議(CCP)已開啟,因此請檢查CCP資料包
SC_CCP_UP
CCP已啟動,可能會(解)壓縮資料包
SC_LOOP_TRAFFIC
將IP流量傳送到pppd
SC_MULTILINK
在傳輸的資料包上啟用PPP多鏈路分段
SC_MP_SHORTSEQ
期望接收到的多鏈路片段具有短多鏈路序列號
SC_MP_XSHORTSEQ
傳輸短多鏈路序列號
這些標誌的值在<linux/ppp-ioctl.h>中定義。請注意,如果未選擇CONFIG_PPP_MULTILINK選項,則SC_MULTILINK、SC_MP_SHORTSEQ和SC_MP_XSHORTSEQ位的值將被忽略。
PPPIOCGFLAGS返回介面單元的狀態/控制標誌的值。引數應指向一個int,ioctl將在其中儲存標誌值。除了上面列出的PPPIOCSFLAGS的值之外,以下位也可能在返回的值中設定
SC_COMP_RUN
CCP壓縮器正在執行
SC_DECOMP_RUN
CCP解壓縮器正在執行
SC_DC_ERROR
CCP解壓縮器檢測到非致命錯誤
SC_DC_FERROR
CCP解壓縮器檢測到致命錯誤
PPPIOCSCOMPRESS設定資料包壓縮或解壓縮的引數。引數應指向一個ppp_option_data結構(在<linux/ppp-ioctl.h>中定義),該結構包含一個指標/長度對,該指標/長度對應描述一個記憶體塊,該記憶體塊包含指定壓縮方法及其引數的CCP選項。ppp_option_data結構還包含一個
transmit欄位。如果此值為0,則ioctl將影響接收路徑,否則將影響傳輸路徑。PPPIOCGUNIT在引數指向的int中返回此介面單元的單元號。
PPPIOCSDEBUG將介面的除錯標誌設定為引數指向的int中的值。僅使用最低有效位;如果此值為1,則通用層將在其操作期間列印一些除錯訊息。這僅用於除錯通用PPP層程式碼;通常,它對於解決PPP連線失敗的原因沒有幫助。
PPPIOCGDEBUG在引數指向的int中返回介面的除錯標誌。
PPPIOCGIDLE返回自上次傳送和接收資料包以來經過的時間(以秒為單位)。引數應指向一個ppp_idle結構(在<linux/ppp_defs.h>中定義)。如果啟用了CONFIG_PPP_FILTER選項,則將重置傳輸和接收空閒計時器的資料包集限制為那些透過
active資料包過濾器的資料包。存在此命令的兩個版本,用於處理使用者空間期望時間為32位或64位time_t秒。PPPIOCSMAXCID設定TCP報頭壓縮器和解壓縮器的最大連線ID引數(以及連線槽數)。引數指向的int的低16位指定壓縮器的最大連線ID。如果該int的高16位為非零,則它們指定解壓縮器的最大連線ID,否則解壓縮器的最大連線ID設定為15。
PPPIOCSNPMODE設定給定網路協議的網路協議模式。引數應指向一個npioctl結構(在<linux/ppp-ioctl.h>中定義)。
protocol欄位給出要影響的協議的PPP協議號,並且mode欄位指定如何處理該協議的資料包NPMODE_PASS
正常操作,傳輸和接收資料包
NPMODE_DROP
靜默丟棄此協議的資料包
NPMODE_ERROR
丟棄資料包並在傳輸時返回錯誤
NPMODE_QUEUE
排隊要傳輸的資料包,丟棄接收到的資料包
目前,NPMODE_ERROR和NPMODE_QUEUE具有與NPMODE_DROP相同的效果。
PPPIOCGNPMODE返回給定協議的網路協議模式。引數應指向一個npioctl結構,其中
protocol欄位設定為要查詢的協議的PPP協議號。在返回時,mode欄位將設定為該協議的網路協議模式。PPPIOCSPASS和PPPIOCSACTIVE設定
pass和active資料包過濾器。只有選擇了CONFIG_PPP_FILTER選項,這些ioctl才可用。引數應指向一個sock_fprog結構(在<linux/filter.h>中定義),其中包含用於過濾器的已編譯BPF指令。如果資料包未透過pass過濾器,則會丟棄這些資料包;否則,如果它們未透過active過濾器,則會傳遞這些資料包,但它們不會重置傳輸或接收空閒計時器。PPPIOCSMRRU啟用或停用接收到的資料包的多鏈路處理,並設定多鏈路MRRU(最大重構接收單元)。引數應指向一個包含新MRRU值的int。如果MRRU值為0,則停用接收到的多鏈路片段的處理。只有選擇了CONFIG_PPP_MULTILINK選項,此ioctl才可用。
上次修改時間:2002年2月7日