11.1 引言
UDP是一個簡單的面向數(shù)據(jù)報的運輸層協(xié)議:進程的每個輸出操作都正好產(chǎn)生一個UDP數(shù)據(jù)報,并組裝成一份待發(fā)送的IP數(shù)據(jù)報。這與面向流字符的協(xié)議不同,如TCP,應用程序產(chǎn)生的全體數(shù)據(jù)與真正發(fā)送的單個IP數(shù)據(jù)報可能沒有什么聯(lián)系。
UDP數(shù)據(jù)報封裝成一份IP數(shù)據(jù)報的格式如圖11-1所示。
RFC 768[Postel 1980]是UDP的正式規(guī)規(guī)范。
UDP不提供可靠性:它把應用程序傳給IP層的數(shù)據(jù)發(fā)送出去,但是并不保證它們能到達目的地。由于缺乏可靠性,我們似乎覺得要避免使用UDP而使用一種可靠協(xié)議如TCP。我們在第17章討論完TCP后將再回到這個話題,看看什么樣的應用程序可以使用UDP。
應用程序必須關心IP數(shù)據(jù)報的長度。如果它超過網(wǎng)絡的MTU(2.8節(jié)),那么就要對IP數(shù)據(jù)報進行分片。如果需要,源端到目的端之間的每個網(wǎng)絡都要進行分片,并不只是發(fā)送端主機連接第一個網(wǎng)絡才這樣做(我們在2.9節(jié)中已定義了路徑MTU的概念)。在11.5節(jié)中,我們將討論IP分片機制。
11.2 UDP首部
UDP首部的各字段如圖11-2所示。
端口號表示發(fā)送進程和接收進程。在圖1-8中,我們畫出了TCP和UDP用目的端口號來分用來自IP層的數(shù)據(jù)的過程。由于IP層已經(jīng)把IP數(shù)據(jù)報分配給TCP或UDP(根據(jù)IP首部中協(xié)議字段值),因此TCP端口號由TCP來查看,而UDP端口號由UDP來查看。TCP端口號與UDP端口號是相互獨立的。
盡管相互獨立,如果TCP和UDP同時提供某種知名服務,兩個協(xié)議通常選擇相同的端口號。這純粹是為了使用方便,而不是協(xié)議本身的要求。
UDP長度字段指的是UDP首部和UDP數(shù)據(jù)的字節(jié)長度。該字段的最小值為8字節(jié)(發(fā)送一份0字節(jié)的UDP數(shù)據(jù)報是OK)。這個UDP長度是有冗余的。IP數(shù)據(jù)報長度指的是數(shù)據(jù)報全長(圖3-1),因此UDP數(shù)據(jù)報長度是全長減去IP首部的長度(該值在首部長度字段中指定,如圖3-1所示)。
11.3 UDP檢驗和
UDP檢驗和覆蓋UDP首部和UDP數(shù)據(jù)?;叵隝P首部的檢驗和,它只覆蓋IP的首部—并不覆蓋IP數(shù)據(jù)報中的任何數(shù)據(jù)。
UDP和TCP在首部中都有覆蓋它們首部和數(shù)據(jù)的檢驗和。UDP的檢驗和是可選的,而TCP的檢驗和是必需的。
盡管UDP檢驗和的基本計算方法與我們在3.2節(jié)中描述的IP首部檢驗和計算方法相類似(16 bit字的二進制反碼和),但是它們之間存在不同的地方。首先,UDP數(shù)據(jù)報的長度可以為奇數(shù)字節(jié),但是檢驗和算法是把若干個16 bit字相加。解決方法是必要時在最后增加填充字節(jié)0,這只是為了檢驗和的計算(也就是說,可能增加的填充字節(jié)不被傳送)。
其次,UDP數(shù)據(jù)報和TCP段都包含一個12字節(jié)長的偽首部,它是為了計算檢驗和而設置的。偽首部包含IP首部一些字段。其目的是讓UDP兩次檢查數(shù)據(jù)是否已經(jīng)正確到達目的地(例如,IP沒有接受地址不是本主機的數(shù)據(jù)報,以及IP沒有把應傳給另一高層的數(shù)據(jù)報傳給UDP)。UDP數(shù)據(jù)報中的偽首部格式如圖11-3所示。
在該圖中,我們特地舉了一個奇數(shù)長度的數(shù)據(jù)報例子,因而在計算檢驗和時需要加上填充字節(jié)。注意,UDP數(shù)據(jù)報的長度在檢驗和計算過程中出現(xiàn)兩次。
如果檢驗和的計算結(jié)果為0,則存入的值為全1(65535),這在二進制反碼計算中是等效的。如果傳送的檢驗和為0,說明發(fā)送端沒有計算檢驗和。
如果發(fā)送端沒有計算檢驗和而接收端檢測到檢驗和有差錯,那么UDP數(shù)據(jù)報就要被悄悄地丟棄。不產(chǎn)生任何差錯報文(當IP層檢測到IP首部檢驗和有差錯時也這樣做)。
UDP檢驗和是一個端到端的檢驗和。它由發(fā)送端計算,然后由接收端驗證。其目的是為了發(fā)現(xiàn)UDP首部和數(shù)據(jù)在發(fā)送端到接收端之間發(fā)生的任何改動。
盡管UDP檢驗和是可選的,但是它們應該總是在用。在80年代,一些計算機產(chǎn)商在默認條件下關閉UDP檢驗和的功能,以提高使用UDP協(xié)議的NFS(Network File System)的速度。在單個局域網(wǎng)中這可能是可以接受的,但是在數(shù)據(jù)報通過路由器時,通過對鏈路層數(shù)據(jù)幀進行循環(huán)冗余檢驗(如以太網(wǎng)或令牌環(huán)數(shù)據(jù)幀)可以檢測到大多數(shù)的差錯,導致傳輸失敗。不管相信與否,路由器中也存在軟件和硬件差錯,以致于修改數(shù)據(jù)報中的數(shù)據(jù)。如果關閉端到端的UDP檢驗和功能,那么這些差錯在UDP數(shù)據(jù)報中就不能被檢測出來。另外,一些數(shù)據(jù)鏈路層協(xié)議(如SLIP)沒有任何形式的數(shù)據(jù)鏈路檢驗和。
Host Requirements RFC 聲明,UDP檢驗和選項在默認條件下是打開的。它還聲明,如果發(fā)送端已經(jīng)計算了檢驗和,那么接收端必須檢驗接收到的檢驗和(如接收到檢驗和不為0)。但是,許多系統(tǒng)沒有遵守這一點,只是在出口檢驗和選項被打開時才驗證接收到的檢驗和。
11.3.1 tcpdump輸出
很難知道某個特定系統(tǒng)是否打開了UDP檢驗和選項。應用程序通常不可能得到接收到的UDP首部中的檢驗和。為了得到這一點,作者在tcpdump程序中增加了一個選項,以打印出接收到的UDP檢驗和。如果打印出的值為0,說明發(fā)送端沒有計算檢驗和。
測試網(wǎng)絡上三個不同系統(tǒng)的輸出如圖11-4所示(參見封面二)。運行我們自編的sock程序(附錄C),發(fā)送一份包含9個字節(jié)數(shù)據(jù)的UDP數(shù)據(jù)報給標準回顯服務器。
從這里可以看出,三個系統(tǒng)中有兩個打開了UDP檢驗和選項。
還要注意的是,在這個簡單例子中,送出的數(shù)據(jù)報與收到的數(shù)據(jù)報具有相同的檢驗和值(第3和第4行,第5和第6行)。從圖11-3可以看出,兩個IP地址進行了交換,正如兩個端口號一樣。偽首部和UDP首部中的其他字段都是相同的,就像數(shù)據(jù)回顯一樣。這再次表明UDP檢驗和(事實上,TCP/IP協(xié)議簇中所有的檢驗和)是簡單的16 bit和。它們檢測不出交換兩個16 bit的差錯。
作者在14.2節(jié)中在8個域名服務器中各進行了一次DNS查詢。DNS主要使用UDP,結(jié)果只有兩臺服務器打開了UDP檢驗和選項。
11.3.2 一些統(tǒng)計結(jié)果
文獻[Mogul 1992]提供了在一個繁忙的NFS服務器上所發(fā)生的不同檢驗和差錯的統(tǒng)計結(jié)果,時間持續(xù)了40天。統(tǒng)計數(shù)字結(jié)果如圖11-5所示。
最后一列是每一行的大概總數(shù),因為太網(wǎng)和IP層還使用其他的協(xié)議。例如,不是所有的以太網(wǎng)數(shù)據(jù)幀都是IP數(shù)據(jù)報,至少以太網(wǎng)還要使用ARP協(xié)議。不是所有的IP數(shù)據(jù)報都是UDP或TCP數(shù)據(jù),因為ICMP也用IP傳送數(shù)據(jù)。
注意,TCP發(fā)生檢驗和差錯的比例與UDP相比要高得多。這很可能是因為在該系統(tǒng)中的TCP連接經(jīng)常是“遠程”連接(經(jīng)過許多路由器和網(wǎng)橋等中間設備),而UDP一般為本地通信。
從最后一行可以看出,不要完全相信數(shù)據(jù)鏈路(如以太網(wǎng),令牌環(huán)等)的CRC檢驗。應該始終打開端到端的檢驗和功能。而且,如果你的數(shù)據(jù)很有價值,也不要完全相信UDP或TCP的檢驗和,因為這些都只是簡單的檢驗和,不能檢測出所有可能發(fā)生的差錯。
11.4 一個簡單的例子
用我們自己編寫的sock程序生成一些可以通過tcpdump觀察的UDP數(shù)據(jù)報:
bsdi % sock -v -u -i -n4 svr4 discard
connected on 140.252.13.35.1108 to 140.252.13.34.9
bsdi % sock -v -u -i -n4 -w0 svr4 discard
connected on 140.252.13.35.1110 to 140.252.13.34.9
第1次執(zhí)行這個程序時,我們指定verbose模式(-v)來觀察ephemeral端口號,指定UDP(-u)而不是默認的TCP,并且指定源模式(-i)來發(fā)送數(shù)據(jù),而不是讀寫標準的輸入和輸出。-n4選項指明輸出4份數(shù)據(jù)報(默認條件下為1024),目的主機為svr4。在1.12節(jié)描述了丟棄服務。每次寫操作的輸出長度取默認值1024。
第2次運行該程序時我們指定-w0,意思是寫長度為0的數(shù)據(jù)報。兩個命令的tcpdump輸出結(jié)果如圖11-6所示。
輸出顯示有四份1024字節(jié)的數(shù)據(jù)報,接著有四份長度為0的數(shù)據(jù)報。每份數(shù)據(jù)報間隔幾毫秒(輸入第2個命令花了41秒的時間)。
在發(fā)送第1份數(shù)據(jù)報之前,發(fā)送端和接收端之間沒有任何通信(在第17章,我們將看到TCP在發(fā)送數(shù)據(jù)的第1個字節(jié)之前必須與另一端建立連接)。另外,當收到數(shù)據(jù)時,接收端沒有任何確認。在這個例子中,發(fā)送端并不知道另一端是否已經(jīng)收到這些數(shù)據(jù)報。
最后要指出的是,每次運行程序時,源端的UDP端口號都發(fā)生變化。第一次是11 08,然后是11 0。在1.9節(jié)我們已經(jīng)提過,客戶程序使用ephemeral端口號一般在1024~5000之間,正如我們現(xiàn)在看到的這樣。
11.5 IP分片
正如我們在2.8節(jié)描述的那樣,物理網(wǎng)絡層一般要限制每次發(fā)送數(shù)據(jù)幀的最大長度。任何時候IP層接收到一份要發(fā)送的IP數(shù)據(jù)報時,它要判斷向本地哪個接口發(fā)送數(shù)據(jù)(選路),并查詢該接口獲得其MTU。IP把MTU與數(shù)據(jù)報長度進行比較,如果需要則進行分片。分片可以發(fā)生在原始發(fā)送端主機上,也可以發(fā)生在中間路由器上。
把一份IP數(shù)據(jù)報分片以后,只有到達目的地才進行重新組裝(這里的重新組裝與其他網(wǎng)絡協(xié)議不同,它們要求在下一站就進行進行重新組裝,而不是在最終的目的地)。重新組裝由目的端的IP層來完成,其目的是使分片和重新組裝過程對運輸層(TCP和UDP)是透明的,除了某些可能的越級操作外。已經(jīng)分片過的數(shù)據(jù)報有可能會再次進行分片(可能不止一次)。IP首部中包含的數(shù)據(jù)為分片和重新組裝提供了足夠的信息。
回憶IP首部(圖3-1),下面這些字段用于分片過程。對于發(fā)送端發(fā)送的每份IP數(shù)據(jù)報來說,其標識字段都包含一個唯一值。該值在數(shù)據(jù)報分片時被復制到每個片中(我們現(xiàn)在已經(jīng)看到這個字段的用途)。標志字段用其中一個比特來表示“更多的片”。除了最后一片外,其他每個組成數(shù)據(jù)報的片都要把該比特置1。片偏移字段指的是該片偏移原始數(shù)據(jù)報開始處的位置。另外,當數(shù)據(jù)報被分片后,每個片的總長度值要改為該片的長度值。
最后,標志字段中有一個比特稱作“不分片”位。如果將這一比特置1,IP將不對數(shù)據(jù)報進行分片。相反把數(shù)據(jù)報丟棄并發(fā)送一個ICMP差錯報文(“需要進行分片但設置了不分片比特”,見圖6-3)給起始端。在下一節(jié)我們將看到出現(xiàn)這個差錯的例子。
當IP數(shù)據(jù)報被分片后,每一片都成為一個分組,具有自己的IP首部,并在選擇路由時與其他分組獨立。這樣,當數(shù)據(jù)報的這些片到達目的端時有可能會失序,但是在IP首部中有足夠的信息讓接收端能正確組裝這些數(shù)據(jù)報片。
盡管IP分片過程看起來是透明的,但有一點讓人不想使用它:即使只丟失一片數(shù)據(jù)也要重傳整個數(shù)據(jù)報。為什么會發(fā)生這種情況呢?因為IP層本身沒有超時重傳的機制——由更高層來負責超時和重傳(TCP有超時和重傳機制,但UDP沒有。一些UDP應用程序本身也執(zhí)行超時和重傳)。當來自TCP報文段的某一片丟失后,TCP在超時后會重發(fā)整個TCP報文段,該報文段對應于一份IP數(shù)據(jù)報。沒有辦法只重傳數(shù)據(jù)報中的一個數(shù)據(jù)報片。事實上,如果對數(shù)據(jù)報分片的是中間路由器,而不是起始端系統(tǒng),那么起始端系統(tǒng)就無法知道數(shù)據(jù)報是如何被分片的。就這個原因,經(jīng)常要避免分片。文獻[Kent and Mogul 1987]對避免分片進行了論述。
使用UDP很容易導致IP分片(在后面我們將看到,TCP試圖避免分片,但對于應用程序來說幾乎不可能強迫TCP發(fā)送一個需要進行分片的長報文段)。我們可以用sock程序來增加數(shù)據(jù)報的長度,直到分片發(fā)生。在一個以太網(wǎng)上,數(shù)據(jù)幀的最大長度是1500字節(jié)(見圖2-1),其中1472字節(jié)留給數(shù)據(jù),假定IP首部為20字節(jié),UDP首部為8字節(jié)。我們分別以數(shù)據(jù)長度為1471,1472,1473和1474字節(jié)運行sock程序。最后兩次應該發(fā)生分片:
bsdi % sock -u -i -nl -w1471 svr4 discard
bsdi % sock -u -i -nl -w1472 svr4 discard
bsdi % sock -u -i -nl -w1473 svr4 discard
bsdi % sock -u -i -nl -w1474 svr4 discard
相應的tcpdump輸出如圖11-7所示。
前兩份UDP數(shù)據(jù)報(第1行和第2行)能裝入以太網(wǎng)數(shù)據(jù)幀,沒有被分片。但是對應于寫1473字節(jié)的IP數(shù)據(jù)報長度為1501,就必須進行分片(第3行和第4行)。同理,寫1474字節(jié)產(chǎn)生的數(shù)據(jù)報長度為1502,它也需要進行分片(第5行和第6行)。
當IP數(shù)據(jù)報被分片后,tcpdump打印出其他的信息。首先,frag26304(第3行和第4行)和frag26313(第5行和第6行)指的是IP首部中標識字段的值。
分片信息中的下一個數(shù)字,即第3行中位于冒號和@號之間的1480,是除IP首部外的片長。兩份數(shù)據(jù)報第一片的長度均為1480:UDP首部占8字節(jié),用戶數(shù)據(jù)占1472字節(jié)(加上IP首部的20字節(jié)分組長度正好為1500字節(jié))。第1份數(shù)據(jù)報的第2片(第4行)只包含1字節(jié)數(shù)據(jù)—剩下的用戶數(shù)據(jù)。第2份數(shù)據(jù)報的第2片(第6行)包含剩下的2字節(jié)用戶數(shù)據(jù)。
在分片時,除最后一片外,其他每一片中的數(shù)據(jù)部分(除IP首部外的其余部分)必須是8字節(jié)的整數(shù)倍。在本例中,1480是8的整數(shù)倍。
位于@符號后的數(shù)字是從數(shù)據(jù)報開始處計算的片偏移值。兩份數(shù)據(jù)報第1片的偏移值均為0(第3行和第5行),第2片的偏移值為1480(第4行和第6行)。跟在偏移值后面的加號對應于IP首部中3bit標志字段中的“更多片”比特。設置這一比特的目的是讓接收端知道在什么時候完成所有的分片組裝。
最后,注意第4行和第6行(不是第1片)省略了協(xié)議名(UDP)、源端口號和目的端口號。協(xié)議名是可以打印出來的,因為它在IP首部并被復制到各個片中。但是,端口號在UDP首部,只能在第1片中被發(fā)現(xiàn)。
發(fā)送的第3份數(shù)據(jù)報(用戶數(shù)據(jù)為1473字節(jié))分片情況如圖11-8所示。需要重申的是,任何運輸層首部只出現(xiàn)在第1片數(shù)據(jù)中。
另外需要解釋幾個術語:IP數(shù)據(jù)報是指IP層端到端的傳輸單元(在分片之前和重新組裝之后),分組是指在IP層和鏈路層之間傳送的數(shù)據(jù)單元。一個分組可以是一個完整的IP數(shù)據(jù)報,也可以是IP數(shù)據(jù)報的一個分片。
11.6 ICMP不可達差錯(需要分片)
發(fā)生ICMP不可達差錯的另一種情況是,當路由器收到一份需要分片的數(shù)據(jù)報,而在IP首部又設置了不分片(DF)的標志比特。如果某個程序需要判斷到達目的端的路途中最小MTU是多少—稱作路徑MTU發(fā)現(xiàn)機制(2.9節(jié)),那么這個差錯就可以被該程序使用。
這種情況下的ICMP不可達差錯報文格式如圖11-9所示。這里的格式與圖6-10不同,因為在第2個32 bit字中,16~31 bit可以提供下一站的MTU,而不再是0。
如果路由器沒有提供這種新的ICMP差錯報文格式,那么下一站的MTU就設為0。
新版的路由器需求RFC[Almquist 1993]聲明,在發(fā)生這種ICMP不可達差錯時,路由器必須生成這種新格式的報文。
例子
關于分片作者曾經(jīng)遇到過一個問題,ICMP差錯試圖判斷從路由器netb到主機sun之間的撥號SLIP鏈路的MTU。我們知道從sun到netb的鏈路的MTU:當SLIP被安裝到主機sun時,這是SLIP配置過程中的一部分,加上在3.9節(jié)中已經(jīng)通過netstat命令觀察過。現(xiàn)在,我們想從另一個方向來判斷它的MTU(在第25章,將討論如何用SNMP來判斷)。在點到點的鏈路中,不要求兩個方向的MTU為相同值。
所采用的技術是在主機solaris上運行ping程序到主機bsdi,增加數(shù)據(jù)分組長度,直到看見進入的分組被分片為止。如圖11-10所示。
在主機sun上運行tcpdump,觀察SLIP鏈路,看什么時候發(fā)生分片。開始沒有觀察到分片,一切都很正常直到ping分組的數(shù)據(jù)長度從500增加到600字節(jié)??梢钥吹浇邮盏降幕仫@請求(仍然沒有分片),但不見回顯應答。
為了跟蹤下去,也在主機bsdi上運行tcpdump,觀察它接收和發(fā)送的報文。輸出如圖11-11所示。
首先,每行中的標記(DF)說明在IP首部中設置了不分片比特。這意味著Solaris 2.2一般把不分片比特置1,作為實現(xiàn)路徑MTU發(fā)現(xiàn)機制的一部分。
第1行顯示的是回顯請求通過路由器netb到達sun主機,沒有進行分片,并設置了DF比特,因此我們知道還沒有達到netb的SLIP MTU。
接下來,在第2行注意到DF標志被復制到回顯應答報文中。這就帶來了問題?;仫@應答與回顯請求報文長度相同(超過600字節(jié)),但是sun外出的SLIP接口MTU為552。因此回顯應答需要進行分片,但是DF標志比特又被設置了。這樣,sun就產(chǎn)生一個ICMP不可達差錯報文返回給bsdi(報文在bsdi處被丟棄)。
這就是我們在主機solaris上沒有看到任何回顯應答的原因。這些應答永遠不能通過sun。分組的路徑如圖11-12所示。
11.7 用Traceroute確定路徑MTU
盡管大多數(shù)的系統(tǒng)不支持路徑MTU發(fā)現(xiàn)功能,但可以很容易地修改traceroute程序(第8章),用它來確定路徑MTU。要做的是發(fā)送分組,并設置“不分片”標志比特。發(fā)送的第一個分組的長度正好與出口MTU相等,每次收到ICMP“不能分片”差錯時(在上一節(jié)討論的)就減小分組的長度。如果路由器發(fā)送的ICMP差錯報文是新格式,包含出口的MTU,那么就用該MTU值來發(fā)送,否則就用下一個最小的MTU值來發(fā)送。正如RFC 1191[Mogul and Deering 1990]聲明的那樣,MTU值的個數(shù)是有限的,因此在我們的程序中有一些由近似值構(gòu)成的表,取下一個最小MTU值來發(fā)送。
首先,我們嘗試判斷從主機sun到主機slip的路徑MTU,知道SLIP鏈路的MTU為296。
在這個例子中,路由器bsdi沒有在ICMP差錯報文中返回出口MTU,因此我們選擇另一個MTU近似值。TTL為2的第1行輸出打印的主機名為bsdi,但這是因為它是返回ICMP差錯報文的路由器。TTL為2的最后一行正是我們所要找的。
在bsdi上修改ICMP代碼使它返回出口MTU值并不困難,如果那樣做并再次運行該程序,得到如下輸出結(jié)果:
這時,在找到正確的MTU值之前,我們不用逐個嘗試8個不同的MTU值——路由器返回了正確的MTU值。
全球互聯(lián)網(wǎng)
作為一個實驗,我們多次運行修改以后的traceroute程序,目的端為世界各地的主機??梢缘竭_15個國家(包括南極洲),使用了多個跨大西洋和跨太平洋的鏈路。但是,在這樣做之前,作者所在子網(wǎng)與路由器netb之間的撥號SLIP鏈路MTU(見圖11-12)增加到1500,與以太網(wǎng)相同。
在18次運行當中,只有其中2次發(fā)現(xiàn)的路徑MTU小于1500。其中一個跨大西洋的鏈路MTU值為572(其近似值甚至在RFC 11 91中也沒有被列出),而路由器返回的是新格式的ICMP差錯報文。另外一條鏈路,在日本的兩個路由器之間,不能處理1500字節(jié)的數(shù)據(jù)幀,并且路由器沒有返回新格式的ICMP差錯報文。把MTU值設成1006則可以正常工作。
從這個實驗可以得出結(jié)論,現(xiàn)在許多但不是所有的廣域網(wǎng)都可以處理大于512字節(jié)的分組。利用路徑MTU發(fā)現(xiàn)機制,應用程序就可以充分利用更大的MTU來發(fā)送報文。
11.8 采用UDP的路徑MTU發(fā)現(xiàn)
下面對使用UDP的應用程序與路徑MTU發(fā)現(xiàn)機制之間的交互作用進行研究??匆豢慈绻麘贸绦?qū)懥艘粋€對于一些中間鏈路來說太長的數(shù)據(jù)報時會發(fā)生什么情況。
例子
由于我們所使用的支持路徑MTU發(fā)現(xiàn)機制的唯一系統(tǒng)就是Solaris 2.x,因此,將采用它作為源站發(fā)送一份650字節(jié)數(shù)據(jù)報經(jīng)slip。由于slip主機位于MTU為296的SLIP鏈路后,因此,任何長于268字節(jié)(296-20-8)且“不分片”比特置為1的UDP數(shù)據(jù)都會使bsdi路由器產(chǎn)生ICMP“不能分片”差錯報文。圖11-13給出了拓撲結(jié)構(gòu)和MTU。
可以用下面的命令行來產(chǎn)生650字節(jié)UDP數(shù)據(jù)報,每兩個UDP數(shù)據(jù)報之間的間隔是5秒:
solaris %sock -u -i -n10 -w650 -p5 slip discard
圖11-14是tcpdump的輸出結(jié)果。在運行這個例子時,將bsdi設置成在ICMP“不能分片”差錯中,不返回下一跳MTU信息。
在發(fā)送的第一個數(shù)據(jù)報中將DF比特置1(第1行),其結(jié)果是從bsdi路由器發(fā)回我們可以猜測的結(jié)果(第2行)。令人不解的是,發(fā)送一個DF比特置1的數(shù)據(jù)報(第3行),其結(jié)果是同樣的ICMP差錯(第4行)。我們預計這個數(shù)據(jù)報在發(fā)送時應該將DF比特置0。
第5行結(jié)果顯示,IP已經(jīng)知道了發(fā)往該目的地址的數(shù)據(jù)報不能將DF比特置1,因此,IP進而將數(shù)據(jù)報在源站主機上進行分片。這與前面的例子中,IP發(fā)送經(jīng)過UDP的數(shù)據(jù)報,允許具有較小MTU的路由器(在本例中是bsdi)對它進行分片的情況不一樣。由于ICMP“不能分片”報文并沒有指出下一跳的MTU,因此,看來IP猜測MTU為576就行了。第一次分片(第5行)包含544字節(jié)的UDP數(shù)據(jù)、8字節(jié)UDP首部以及20字節(jié)IP首部,因此,總IP數(shù)據(jù)報長度是572字節(jié)。第2次分片(第6行)包含剩余的106字節(jié)UDP數(shù)據(jù)和20字節(jié)IP首部。
不幸的是,第7行的下一個數(shù)據(jù)報將其DF比特置1,因此bsdi將它丟棄并返回ICMP差錯。這時發(fā)生了IP定時器超時,通知IP查看是不是因為路徑MTU增大了而將DF比特再一次置1。我們可以從第19行和20行看出這個結(jié)果。將第7行與19行進行比較,可以看出IP每過30秒就將DF比特置1,以查看路徑MTU是否增大了。
這個30秒的定時器值看來太短。RFC11 91建議其值取10分鐘。可以通過修改ip_ire_pathmtu_interval(E.4節(jié))參數(shù)來改變該值。同時,Solaris 2.2無法對單個UDP應用或所有UDP應用關閉該路徑MTU發(fā)現(xiàn)。只能通過修改ip_path_mtu_discovery參數(shù),在系統(tǒng)一級開放或關閉它。正如在這個例子里所能看到的那樣,如果允許路徑MTU發(fā)現(xiàn),那么當UDP應用程序?qū)懭肟赡鼙环制瑪?shù)據(jù)報時,該數(shù)據(jù)報將被丟棄。
solaris的IP層所假設的最大數(shù)據(jù)報長度(576字節(jié))是不正確的。在圖11-13中,我們看到,實際的MTU值是296字節(jié)。這意味著經(jīng)solaris分片的數(shù)據(jù)報還將被bsdi分片。圖11-15給出了在目的主機(slip)上所收集到的tcpdump對于第一個到達數(shù)據(jù)報的輸出結(jié)果(圖11-14的第5行和第6行)。
在本例中,solaris不應該對外出數(shù)據(jù)報分片,它應該將DF比特置0,讓具有最小MTU的路由器來完成分片工作。
現(xiàn)在我們運行同一個例子,只是對路由器bsdi進行修改使其在ICMP“不能分片”差錯中返回下一跳MTU。圖11-16給出了tcpdump輸出結(jié)果的前6行。
與圖11-14一樣,前兩個數(shù)據(jù)報同樣是將DF比特置1后發(fā)送出去的。但是在知道了下一跳MTU后,只產(chǎn)生了3個數(shù)據(jù)報片,而圖11-15中的bsdi路由器則產(chǎn)生了4個數(shù)據(jù)報片。
11.9 UDP和ARP之間的交互作用
使用UDP,可以看到UDP與ARP典型實現(xiàn)之間的有趣的(而常常未被人提及)交互作用。
我們用sock程序來產(chǎn)生一個包含8192字節(jié)數(shù)據(jù)的UDP數(shù)據(jù)報。預測這將會在以太網(wǎng)上產(chǎn)生6個數(shù)據(jù)報片(見習題11.3)。同時也確保在運行該程序前,ARP緩存是清空的,這樣,在發(fā)送第一個數(shù)據(jù)報片前必須交換ARP請求和應答。
bsdi % arp -a 驗證ARP高速緩存是空的
bsdi % sock -u -i -nl -w8192 svr4 discard
預計在發(fā)送第一個數(shù)據(jù)報片前會先發(fā)送一個ARP請求。IP還會產(chǎn)生5個數(shù)據(jù)報片,這樣就提出了我們必須用tcpdump來回答的兩個問題:在接收到ARP回答前,其余數(shù)據(jù)報片是否已經(jīng)做好了發(fā)送準備?如果是這樣,那么在ARP等待應答時,它會如何處理發(fā)往給定目的的多個報文?圖11-17給出了tcpdump的輸出結(jié)果。
在這個輸出結(jié)果中有一些令人吃驚的結(jié)果。首先,在第一個ARP應答返回以前,總共產(chǎn)生了6個ARP請求。我們認為其原因是IP很快地產(chǎn)生了6個數(shù)據(jù)報片,而每個數(shù)據(jù)報片都引發(fā)了一個ARP請求。
第二,在接收到第一個ARP應答時(第7行),只發(fā)送最后一個數(shù)據(jù)報片(第9行)!看來似乎將前5個數(shù)據(jù)報片全都丟棄了。實際上,這是ARP的正常操作。在大多數(shù)的實現(xiàn)中,在等待一個ARP應答時,只將最后一個報文發(fā)送給特定目的主機。
Host Requirements RFC要求實現(xiàn)中必須防止這種類型的ARP洪泛(ARP flooding,即以高速率重復發(fā)送到同一個IP地址的ARP請求)。建議最高速率是每秒一次。而這里卻在4.3ms內(nèi)發(fā)出了6個ARP請求。
Host Requirements RFC規(guī)定,ARP應該保留至少一個報文,而這個報文必須是最后一個報文。這正是我們在這里所看到的結(jié)果。
另一個無法解釋的不正常的現(xiàn)象是,svr4發(fā)回7個,而不是6個ARP應答。
最后要指出的是,在最后一個ARP應答返回后,繼續(xù)運行tcpdump程序5分鐘,以看看svr4是否會返回ICMP“組裝超時”差錯。并沒有發(fā)送ICMP差錯(我們在圖8-2中給出了該消息的格式。code字段為1表示在重新組裝數(shù)據(jù)報時發(fā)生了超時)。
在第一個數(shù)據(jù)報片出現(xiàn)時,IP層必須啟動一個定時器。這里“第一個”表示給定數(shù)據(jù)報的第一個到達數(shù)據(jù)報片,而不是第一個數(shù)據(jù)報片(數(shù)據(jù)報片偏移為0)。正常的定時器值為30或60秒。如果定時器超時而該數(shù)據(jù)報的所有數(shù)據(jù)報片未能全部到達,那么將這些數(shù)據(jù)報片丟棄。如果不這么做,那些永遠不會到達的數(shù)據(jù)報片(正如我們在本例中所看到的那樣)遲早會引起接收端緩存滿。
這里我們沒看到ICMP消息的原因有兩個。首先,大多數(shù)從Berkeley派生的實現(xiàn)從不產(chǎn)生該差錯!這些實現(xiàn)會設置定時器,也會在定時器溢出時將數(shù)據(jù)報片丟棄,但是不生成ICMP差錯。第二,并未接收到包含UDP首部的偏移量為0的第一個數(shù)據(jù)報片(這是被ARP所丟棄的5個報文的第1個)。除非接收到第一個數(shù)據(jù)報片,否則并不要求任何實現(xiàn)產(chǎn)生ICMP差錯。其原因是因為沒有運輸層首部,ICMP差錯的接收者無法區(qū)分出是哪個進程所發(fā)送的數(shù)據(jù)報被丟棄。這里假設上層(TCP或使用UDP的應用程序)最終會超時并重傳。
在本節(jié)中,我們使用IP數(shù)據(jù)報片來查看UDP與ARP之間的交互作用。如果發(fā)送端迅速發(fā)送多個UDP數(shù)據(jù)報,也可以看到這個交互過程。我們選擇采用分片的方法,是因為IP可以生成報文的速度,比一個用戶進程生成多個數(shù)據(jù)報的速度更快。
盡管本例看來不太可能,但它確實經(jīng)常發(fā)生。NFS發(fā)送的UDP數(shù)據(jù)報長度超過8192字節(jié)。在以太網(wǎng)上,這些數(shù)據(jù)報以我們所指出的方式進行分片,如果適當?shù)腁RP緩存入口發(fā)生超時,那么就可以看到這里所顯示的現(xiàn)象。NFS將超時并重傳,但是由于ARP的有限隊列,第一個IP數(shù)據(jù)報仍可能被丟棄。
11.10 最大UDP數(shù)據(jù)報長度
理論上,IP數(shù)據(jù)報的最大長度是65535字節(jié),這是由IP首部(圖3-1)16比特總長度字段所限制的。去除20字節(jié)的IP首部和8個字節(jié)的UDP首部,UDP數(shù)據(jù)報中用戶數(shù)據(jù)的最長長度為65507字節(jié)。但是,大多數(shù)實現(xiàn)所提供的長度比這個最大值小。
我們將遇到兩個限制因素。第一,應用程序可能會受到其程序接口的限制。socket API提供了一個可供應用程序調(diào)用的函數(shù),以設置接收和發(fā)送緩存的長度。對于UDP socket,這個長度與應用程序可以讀寫的最大UDP數(shù)據(jù)報的長度直接相關?,F(xiàn)在的大部分系統(tǒng)都默認提供了可讀寫大于8192字節(jié)的UDP數(shù)據(jù)報(使用這個默認值是因為8192是NFS讀寫用戶數(shù)據(jù)數(shù)的默認值)。
第二個限制來自于TCP/IP的內(nèi)核實現(xiàn)。可能存在一些實現(xiàn)特性(或差錯),使IP數(shù)據(jù)報長度小于65535字節(jié)。
作者使用sock程序?qū)Σ煌琔DP數(shù)據(jù)報長度進行了試驗。在SunOS 4.1.3下使用環(huán)回接口的最大IP數(shù)據(jù)報長度是32767字節(jié)。比它大的值都會發(fā)生差錯。但是從BSD/386到SunOS 4.1.3的情況下,Sun所能接收到最大IP數(shù)據(jù)報長度為32786字節(jié)(即32758字節(jié)用戶數(shù)據(jù))。在Solaris 2.2下使用環(huán)回接口,最大可收發(fā)IP數(shù)據(jù)報長度為65535字節(jié)。從Solaris 2.2到AIX 3.2.2,發(fā)送的最大IP數(shù)據(jù)報長度可以是65535字節(jié)。很顯然,這個限制與源端和目的端的實現(xiàn)有關。
我們在3.2節(jié)中提過,要求主機必須能夠接收最短為576字節(jié)的IP數(shù)據(jù)報。在許多UDP應用程序的設計中,其應用程序數(shù)據(jù)被限制成512字節(jié)或更小,因此比這個限制值小。例如,我們在10.4節(jié)中看到,路徑信息協(xié)議總是發(fā)送每份數(shù)據(jù)報小于512字節(jié)的數(shù)據(jù)。我們還會在其他UDP應用程序如DNS(第14章)、TFTP(第15章)、BOOTP(第16章)以及SNMP(第25章)中遇到這個限制。
數(shù)據(jù)報截斷
由于IP能夠發(fā)送或接收特定長度的數(shù)據(jù)報并不意味著接收應用程序可以讀取該長度的數(shù)據(jù)。因此,UDP編程接口允許應用程序指定每次返回的最大字節(jié)數(shù)。如果接收到的數(shù)據(jù)報長度大于應用程序所能處理的長度,那么會發(fā)生什么情況呢?
不幸的是,該問題的答案取決于編程接口和實現(xiàn)。
典型的Berkeley版socket API對數(shù)據(jù)報進行截斷,并丟棄任何多余的數(shù)據(jù)。應用程序何時能夠知道,則與版本有關(4.3BSD Reno及其后的版本可以通知應用程序數(shù)據(jù)報被截斷)。
SVR4下的socket API(包括Solaris 2.x)并不截斷數(shù)據(jù)報。超出部分數(shù)據(jù)在后面的讀取中返回。它也不通知應用程序從單個UDP數(shù)據(jù)報中多次進行讀取操作。
TLI API不丟棄數(shù)據(jù)。相反,它返回一個標志表明可以獲得更多的數(shù)據(jù),而應用程序后面的讀操作將返回數(shù)據(jù)報的其余部分。
在討論TCP時,我們發(fā)現(xiàn)它為應用程序提供連續(xù)的字節(jié)流,而沒有任何信息邊界。TCP以應用程序讀操作時所要求的長度來傳送數(shù)據(jù),因此,在這個接口下,不會發(fā)生數(shù)據(jù)丟失。
11.11 ICMP源站抑制差錯
我們同樣也可以使用UDP產(chǎn)生ICMP“源站抑制(source quench)”差錯。當一個系統(tǒng)(路由器或主機)接收數(shù)據(jù)報的速度比其處理速度快時,可能產(chǎn)生這個差錯。注意限定詞“可能”。即使一個系統(tǒng)已經(jīng)沒有緩存并丟棄數(shù)據(jù)報,也不要求它一定要發(fā)送源站抑制報文。
圖11-18給出了ICMP源站抑制差錯報文的格式。有一個很好的方案可以在我們的測試網(wǎng)絡里產(chǎn)生該差錯報文??梢詮腷sdi通過必須經(jīng)過撥號SLIP鏈路的以太網(wǎng),將數(shù)據(jù)報發(fā)送給路由器sun。由于SLIP鏈路的速度大約只有以太網(wǎng)的千分之一,因此,我們很容易就可以使其緩存用完。下面的命令行從主機bsdi通過路由器sun發(fā)送100個1024字節(jié)長數(shù)據(jù)報給solaris。我們將數(shù)據(jù)報發(fā)送給標準的丟棄服務,這樣,這些數(shù)據(jù)報將被忽略:
bsdi % sock -u -i -w1024 -n100 solaris discard
圖11-19給出了與此命令行相對應的tcpdump輸出結(jié)果。
在這個輸出結(jié)果中,刪除了很多行,這只是一個模型。接收前26個數(shù)據(jù)報時未發(fā)生差錯;我們只給出了第一個數(shù)據(jù)報的結(jié)果。然而,從第27個數(shù)據(jù)報開始,每發(fā)送一份數(shù)據(jù)報,就會接收到一份源站抑制差錯報文??偣灿?6+(74×2)=174行輸出結(jié)果。
從2.10節(jié)的并行線吞吐率計算結(jié)果可以知道,以9600 b/s速率傳送1024字節(jié)數(shù)據(jù)報只需要1秒時間(由于從sun到netb的SLIP鏈路的MTU為552字節(jié),因此在我們的例子中,20+8+1024字節(jié)數(shù)據(jù)報將進行分片,因此,其時間會稍長一些)。但是我們可以從圖11-19的時間中看出,sun路由器在不到1秒時間內(nèi)就處理完所有的100個數(shù)據(jù)報,而這時,第一份數(shù)據(jù)報還未通過SLIP鏈路。因此我們用完其緩存就不足不奇了。
盡管RFC 1009 [Braden and Postel 1987]要求路由器在沒有緩存時產(chǎn)生源站抑制差錯報文,但是新的Router Requirements RFC [Almquist 1993]對此作了修改,提出路由器不應該產(chǎn)生源站抑制差錯報文。由于源站抑制要消耗網(wǎng)絡帶寬,且對于擁塞來說是一種無效而不公平的調(diào)整,因此現(xiàn)在人們對于源站抑制差錯的態(tài)度是不支持的。
在本例中,還需要指出的是,sock程序要么沒有接收到源站抑制差錯報文,要么接收到卻將它們忽略了。結(jié)果是如果采用UDP協(xié)議,那么BSD實現(xiàn)通常忽略其接收到的源站抑制報文(正如我們在21.10節(jié)所討論的那樣,TCP接受源站抑制差錯報文,并將放慢在該連接上的數(shù)據(jù)傳輸速度)。其部分原因在于,在接收到源站抑制差錯報文時,導致源站抑制的進程可能已經(jīng)中止了。實際上,如果使用Unix的time程序來測定sock程序所運行的時間,其結(jié)果是它只運行了大約0.5秒時間。但是從圖11-19中可以看到,在發(fā)送第一份數(shù)據(jù)報過后0.71秒才接收到一些源站抑制,而此時該進程已經(jīng)中止。其原因是我們的程序?qū)懭肓?00個數(shù)據(jù)報然后中止了。但是所有的100個數(shù)據(jù)報都已發(fā)送出去—有一些數(shù)據(jù)報在輸出隊列中。
這個例子重申了UDP是一個非可靠的協(xié)議,它說明了端到端的流量控制。盡管sock程序成功地將100個數(shù)據(jù)報寫入其網(wǎng)絡,但只有26個數(shù)據(jù)報真正發(fā)送到了目的端。其他74個數(shù)據(jù)報可能被中間路由器丟棄。除非在應用程序中建立一些應答機制,否則發(fā)送端并不知道接收端是否收到了這些數(shù)據(jù)。
11.12 UDP服務器的設計
使用UDP的一些蘊含對于設計和實現(xiàn)服務器會產(chǎn)生影響。通常,客戶端的設計和實現(xiàn)比服務器端的要容易一些,這就是我們?yōu)槭裁匆懻摲掌鞯脑O計,而不是討論客戶端的設計的原因。典型的服務器與操作系統(tǒng)進行交互作用,而且大多數(shù)需要同時處理多個客戶。
通常一個客戶啟動后直接與單個服務器通信,然后就結(jié)束了。而對于服務器來說,它啟動后處于休眠狀態(tài),等待客戶請求的到來。對于UDP來說,當客戶數(shù)據(jù)報到達時,服務器蘇醒過來,數(shù)據(jù)報中可能包含來自客戶的某種形式的請求消息。
在這里我們所感興趣的并不是客戶和服務器的編程方面([Stevens 1990]對這些方面的細節(jié)進行了討論),而是UDP那些影響使用該協(xié)議的服務器的設計和實現(xiàn)方面的協(xié)議特性(我們在18.11節(jié)中對TCP服務器的設計進行了描述)。盡管我們所描述的一些特性取決于所使用UDP的實現(xiàn),但對于大多數(shù)實現(xiàn)來說,這些特性是公共的。
11.12.1 客戶IP地址及端口號
來自客戶的是UDP數(shù)據(jù)報。IP首部包含源端和目的端IP地址,UDP首部包含了源端和目的端的UDP端口號。當一個應用程序接收到UDP數(shù)據(jù)報時,操作系統(tǒng)必須告訴它是誰發(fā)送了這份消息,即源IP地址和端口號。
這個特性允許一個交互UDP服務器對多個客戶進行處理。給每個發(fā)送請求的客戶發(fā)回應答。
11.12.2 目的IP地址
一些應用程序需要知道數(shù)據(jù)報是發(fā)送給誰的,即目的IP地址。例如,Host Requirements RFC規(guī)定,TFTP服務器必須忽略接收到的發(fā)往廣播地址的數(shù)據(jù)報(我們分別在第12章和第15章對廣播和TFTP進行描述)。
這要求操作系統(tǒng)從接收到的UDP數(shù)據(jù)報中將目的IP地址交給應用程序。不幸的是,并非所有的實現(xiàn)都提供這個功能。
socket API以IP_RECVDSTADDR socket選項提供了這個功能。對于本文中使用的系統(tǒng),只有BSD/386、4.4BSD和AIX 3.2.2支持該選項。SVR4、SunOS 4.x和Solaris 2.x都不支持該選項。
11.12.3 UDP輸入隊列
我們在1.8節(jié)中說過,大多數(shù)UDP服務器是交互服務器。這意味著,單個服務器進程對單個UDP端口上(服務器上的名知端口)的所有客戶請求進行處理。
通常程序所使用的每個UDP端口都與一個有限大小的輸入隊列相聯(lián)系。這意味著,來自不同客戶的差不多同時到達的請求將由UDP自動排隊。接收到的UDP數(shù)據(jù)報以其接收順序交給應用程序(在應用程序要求交送下一個數(shù)據(jù)報時)。
然而,排隊溢出造成內(nèi)核中的UDP模塊丟棄數(shù)據(jù)報的可能性是存在的??梢赃M行以下試驗。我們在作為UDP服務器的bsdi主機上運行sock程序:
bsdi % sock -s -u -v -E -R256 -P30 6666
from 140.252.13.33, to 140.252.13.63: 1111111111 從發(fā)送到廣播地址
from 140.252.13.34, to 140.252.13.35: 4444444444444 從svr4發(fā)送到單播地址
我們指明以下標志:-s表示作為服務器運行,-u表示UDP,-v表示打印客戶的IP地址,-E表示打印目的IP地址(該系統(tǒng)支持這個功能)。另外,我們將這個端口的UDP接收緩存設置為256字節(jié)(-R),其每次應用程序讀取的大小也是這個數(shù)(-r)。標志-P30表示創(chuàng)建UDP端口后,先暫停30秒后再讀取第一個數(shù)據(jù)報。這樣,我們就有時間在另兩臺主機上啟動客戶程序,發(fā)送一些數(shù)據(jù)報,以查看接收隊列是如何工作的。
服務器一開始工作,處于其30秒的暫停時間內(nèi),我們就在sun主機上啟動一個客戶,并發(fā)送三個數(shù)據(jù)報:
目的地址是廣播地址(140.252.13.63)。我們同時也在主機svr4上啟動第2個客戶,并發(fā)送另外三個數(shù)據(jù)報:
首先,我們早些時候在bsdi上所看到的結(jié)果表明,應用程序只接收到2個數(shù)據(jù)報:來自sun的第一個全1報文,和來自svr4的第一個全4報文。其他4個數(shù)據(jù)報看來全被丟棄。
圖11-20給出的tcpdump輸出結(jié)果表明,所有6個數(shù)據(jù)報都發(fā)送給了目的主機。兩個客戶的數(shù)據(jù)報以交替順序鍵入:第一個來自sun,然后是來自svr4的,以此類推。同時也可以看出,全部6個數(shù)據(jù)報大約在12秒內(nèi)發(fā)送完畢,也就是在服務器休眠的30秒內(nèi)完成的。
我們還可以看到,服務器的-E選項使其可以知道每個數(shù)據(jù)報的目的IP地址。如果需要,它可以選擇如何處理其接收到的第一個數(shù)據(jù)報,這個數(shù)據(jù)報的地址是廣播地址。
我們可以從本例中看到以下幾個要點。首先,應用程序并不知道其輸入隊列何時溢出。只是由UDP對超出數(shù)據(jù)報進行丟棄處理。同時,從tcpdump輸出結(jié)果,我們看到,沒有發(fā)回任何信息告訴客戶其數(shù)據(jù)報被丟棄。這里不存在像ICMP源站抑制這樣發(fā)回發(fā)送端的消息。最后,看來UDP輸出隊列是FIFO(先進先出)的,而我們在11.9節(jié)中所看到的ARP輸入?yún)s是LIFO(后進先出)的。
11.12.4 限制本地IP地址
大多數(shù)UDP服務器在創(chuàng)建UDP端點時都使其本地IP地址具有通配符(wildcard)的特點。這就表明進入的UDP數(shù)據(jù)報如果其目的地為服務器端口,那么在任何本地接口均可接收到它。例如,我們以端口號777啟動一個UDP服務器:
sun % sock -u -s 7777
然后,用netstat命令觀察端點的狀態(tài):
這里,我們刪除了許多行,只保留了其中感興趣的東西。-a選項表示報告所有網(wǎng)絡端點的狀態(tài)。-n選項表示以點數(shù)格式打印IP地址而不用DNS把地址轉(zhuǎn)換成名字,打印數(shù)字端口號而不是服務名稱。-finet選項表示只報告TCP和UDP端點。
本地地址以*.7777格式打印,星號表示任何本地IP地址。
當服務器創(chuàng)建端點時,它可以把其中一個主機本地IP地址包括廣播地址指定為端點的本地IP地址。只有當目的IP地址與指定的地址相匹配時,進入的UDP數(shù)據(jù)報才能被送到這個端點。用我們的sock程序,如果在端口號之前指定一個IP地址,那么該IP地址就成為該端點的本地IP地址。例如:
sun % sock -u -s 140.252.1.29 7777
就限制服務器在SLIP接口(140.252.1.29)處接收數(shù)據(jù)報。netstat輸出結(jié)果顯示如下:
如果我們試圖在以太網(wǎng)上的主機bsdi以地址140.252.13.35向該服務器發(fā)送一份數(shù)據(jù)報,那么將返回一個ICMP端口不可達差錯。服務器永遠看不到這份數(shù)據(jù)報。這種情形如圖11-21所示。
有可能在相同的端口上啟動不同的服務器,每個服務器具有不同的本地IP地址。但是,一般必須告訴系統(tǒng)應用程序重用相同的端口號沒有問題。
使用sockets API時,必須指定SO_REUSEADDRsocket選項。在sock程序中是通過-A選項來完成的。
在主機sun上,可以在同一個端口號(8888)上啟動5個不同的服務器:
除了第一個以外,其他的服務器都必須以-A選項啟動,告訴系統(tǒng)可以重用同一個端口號。5個服務器的netstat輸出結(jié)果如下所示:
在這種情況下,到達服務器的數(shù)據(jù)報中,只有帶星號的本地IP地址,其目的地址為140.252.1.255,因為其他4個服務器占用了其他所有可能的IP地址。
如果存在一個含星號的IP地址,那么就隱含了一種優(yōu)先級關系。如果為端點指定了特定IP地址,那么在匹配目的地址時始終優(yōu)先匹配該IP地址。只有在匹配不成功時才使用含星號的端點。
11.12.5 限制遠端IP地址
在前面所有的netstat輸出結(jié)果中,遠端IP地址和遠端端口號都顯示為.,其意思是該端點將接受來自任何IP地址和任何端口號的UDP數(shù)據(jù)報。大多數(shù)系統(tǒng)允許UDP端點對遠端地址進行限制。
這說明端點將只能接收特定IP地址和端口號的UDP數(shù)據(jù)報。sock程序用-f選項來指定遠端IP地址和端口號:
sun % sock -u -s -f 140.252.13.35.4444 5555
這樣就設置了遠端IP地址140.252.13.35(即主機bsdi)和遠端端口號4444。服務器的有名端口號為5555。如果運行netstat命令,我們發(fā)現(xiàn)本地IP地址也被設置了,盡管我們沒有指定。
第11章 UDP:用戶數(shù)據(jù)報協(xié)議_即時通訊網(wǎng)(52im.net)
這是在伯克利派生系統(tǒng)中指定遠端IP地址和端口號帶來的副作用:如果在指定遠端地址時沒有選擇本地地址,那么將自動選擇本地地址。它的值就成為選擇到達遠端IP地址路由時將選擇的接口IP地址。事實上,在這個例子中,sun在以太網(wǎng)上的IP地址與遠端地址140.252.13.33相連。
圖11-22總結(jié)了UDP服務器本身可以創(chuàng)建的三類地址綁定。
在所有情況下,lport指的是服務器有名端口號,localIP必須是本地接口的IP地址。表中這三行的排序是UDP模塊在判斷用哪個端點接收數(shù)據(jù)報時所采用的順序。最為確定的地址(第一行)首先被匹配,最不確定的地址(最后一行IP地址帶有兩個星號)最后進行匹配。
11.12.6 每個端口有多個接收者
盡管在RFC中沒有指明,但大多數(shù)的系統(tǒng)在某一時刻只允許一個程序端點與某個本地I P地址及UDP端口號相關聯(lián)。當目的地為該IP地址及端口號的UDP數(shù)據(jù)報到達主機時,就復制一份傳給該端點。端點的IP地址可以含星號,正如我們前面討論的那樣。
例如,在SunOS 4.1.3中,我們啟動一個端口號為9999的服務器,本地IP地址含有星號:
sun % sock -u -s 9999
接著,如果啟動另一個具有相同本地地址和端口號的服務器,那么它將不運行,盡管我們指定了-A選項:
在一個支持多播的系統(tǒng)上(第12章),這種情況將發(fā)生變化。多個端點可以使用同一個IP地址和UDP端口號,盡管應用程序通常必須告訴API是可行的(如,用-A標志來指明SO_REUSEADDRsocket選項)。
當UDP數(shù)據(jù)報到達的目的IP地址為廣播地址或多播地址,而且在目的IP地址和端口號處有多個端點時,就向每個端點傳送一份數(shù)據(jù)報的復制(端點的本地IP地址可以含有星號,它可匹配任何目的IP地址)。但是,如果UDP數(shù)據(jù)報到達的是一個單播地址,那么只向其中一個端點傳送一份數(shù)據(jù)報的復制。選擇哪個端點傳送數(shù)據(jù)取決于各個不同的系統(tǒng)實現(xiàn)。
11.13 小結(jié)
UDP是一個簡單協(xié)議。它的正式規(guī)范是RFC 768 [Postel 1980],只包含三頁內(nèi)容。它向用戶進程提供的服務位于IP層之上,包括端口號和可選的檢驗和。我們用UDP來檢查檢驗和,并觀察分片是如何進行的。
接著,我們討論了ICMP不可達差錯,它是新的路徑MTU發(fā)現(xiàn)功能中的一部分(2.9節(jié))。用Tr aceroute和UDP來觀察路徑MTU發(fā)現(xiàn)過程。還查看了UDP和ARP之間的接口,大多數(shù)的ARP實現(xiàn)在等待ARP應答時只保留最近傳送給目的端的數(shù)據(jù)報。
當系統(tǒng)接收IP數(shù)據(jù)報的速率超過這些數(shù)據(jù)報被處理的速率時,系統(tǒng)可能發(fā)送ICMP源站抑制差錯報文。使用UDP時很容易產(chǎn)生這樣的ICMP差錯。