Linux原始套接字實現分析


 

分類: LINUX

2012-10-27 21:31:45

 

    本文從IPV4協議棧原始套接字的分類入手,詳細介紹了鏈路層和網絡層原始套接字的特點及其內核實現細節。並結合原始套接字的實際應用,說明各類型原始套接字的適應范圍,以及在實際使用時需要注意的問題。

 

 

 

 

 

一、原始套接字概述

 

協議棧的原始套接字從實現上可以分為“鏈路層原始套接字”和“網絡層原始套接字”兩大類。本節主要描述各自的特點及其適用范圍。

鏈路層原始套接字可以直接用於接收和發送鏈路層的MAC幀,在發送時需要由調用者自行構造和封裝MAC首部。而網絡層原始套接字可以直接用於接收和發送IP層的報文數據,在發送時需要自行構造IP報文頭(取決是否設置IP_HDRINCL選項)。

 

1.1  鏈路層原始套接字

 

鏈路層原始套接字調用socket()函數創建。第一個參數指定協議族類型為PF_PACKET,第二個參數type可以設置為SOCK_RAW或SOCK_DGRAM,第三個參數是協議類型(該參數只對報文接收有意義)。協議類型protocol不同取值的意義具體見表1所示:

  1. socket(PF_PACKET, type, htons(protocol))

      

a)       參數type設置為SOCK_RAW時,套接字接收和發送的數據都是從MAC首部開始的。在發送時需要由調用者從MAC首部開始構造和封裝報文數據。type設置為SOCK_RAW的情況應用是比較多的,因為某些項目會使用到自定義的二層報文類型。

  1. socket(PF_PACKET, SOCK_RAW, htons(protocol))

 

b)      參數type設置為SOCK_DGRAM時,套接字接收到的數據報文會將MAC首部去掉。同時在發送時也不需要再手動構造MAC首部,只需要從IP首部(或ARP首部,取決於封裝的報文類型)開始構造即可,而MAC首部的填充由內核實現的。若對於MAC首部不關心的場景,可以使用這種類型,這種用法用得比較少。

  1. socket(PF_PACKET, SOCK_DGRAM, htons(protocol))

     

 

 

 

表1  protocol不同取值

 

 

 

 

 

 

protocol

作用

ETH_P_ALL

 0x0003

報收本機收到的所有二層報文

ETH_P_IP

0x0800

報收本機收到的所有IP報文

ETH_P_ARP

0x0806

報收本機收到的所有ARP報文

ETH_P_RARP

0x8035

報收本機收到的所有RARP報文

自定義協議

比如0x0810

報收本機收到的所有類型為0x0810的二層報文

不指定

0

不能用於接收,只用於發送

……

……

……

 

 

 

 

表1中protocol的取值中有兩個值是比較特殊的。當protocol為ETH_P_ALL時,表示能夠接收本機收到的所有二層報文(包括IP, ARP, 自定義二層報文等),同時這種類型套接字還能夠將外發的報文再收回來。當protocol為0時,表示該套接字不能用於接收報文,只能用於發送。具體的實現細節在2.2節中會詳細介紹。

 

 

 

 

 

 

1.2  網絡層原始套接字

 

創建面向連接的TCP和創建面向無連接的UDP套接字,在接收和發送時只能操作數據部分,而不能對IP首部或TCP和UDP首部進行操作。如果想要操作IP首部或傳輸層協議首部,就需要調用如下socket()函數創建網絡層原始套接字。第一個參數指定協議族的類型為PF_INET,第二個參數為SOCK_RAW,第三個參數protocol為協議類型(不同取值的意義見表2)。產品線有使用OSPF和RSVP等協議,需要使用這種類型的套接字。

  1. socktet(PF_INET, SOCK_RAW, protocol)
   

a)       接收報文

網絡層原始套接字接收到的報文數據是從IP首部開始的,即接收到的數據包含了IP首部, TCP/UDP/ICMP等首部, 以及數據部分。

 

     

b)      發送報文

網絡層原始套接字發送的報文數據,在默認情況下是從IP首部之后開始的,即需要由調用者自行構造和封裝TCP/UDP等協議首部。

 

 

這種套接字也提供了發送時從IP首部開始構造數據的功能,通過setsockopt()給套接字設置上IP_HDRINCL選項,就需要在發送時自行構造IP首部。

 

   

  1. int val = 1; 
  2. setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val));

 

 

 

 

 

表2  protocol不同取

protocol

作用

IPPROTO_TCP

6

報收TCP類型的報文

IPPROTO_UDP

17

報收UDP類型的報文

IPPROTO_ICMP

1

報收ICMP類型的報文

IPPROTO_IGMP

2

報收IGMP類型的報文

IPPROTO_RAW

255

不能用於接收,只用於發送(需要構造IP首部)

OSPF

89

接收協議號為89的報文

……

……

……

表2中protocol取值為IPPROTO_RAW是比較特殊的,表示套接字不能用於接收,只能用於發送(且發送時需要從IP首部開始構造報文)。具體的實現細節在2.3節中會詳細介紹。

 

 

 

 

 

 

二、原始套接字實現

 

本節主要首先介紹鏈路層和網絡層原始套接字報文的收發總體流程,再分別對兩類套接字的創建、接收、發送等具體實現細節進行介紹。

 

 

 

 

 

 

2.1  原始套接字報文收發流程

 

 

圖1  原始套接字收發流程

 

 

 

 

 

 

如上圖1所示為鏈路層和網絡層原始套接字的收發總體流程。網卡驅動收到報文后在軟中斷上下文中由netif_receive_skb()處理,匹配是否有注冊的鏈路層原始套接字,若匹配上就通過skb_clone()來克隆報文,並將報文交給相應的原始套接字。對於IP報文,在協議棧的ip_local_deliver_finish()函數中會匹配是否有注冊的網絡層原始套接字,若匹配上就通過skb_clone()克隆報文並交給相應的原始套接字來處理。

注意:這里只是將報文克隆一份交給原始套接字,而該報文還是會繼續走后續的協議棧處理流程。

 

 

 

 

 

      鏈路層原始套接字的發送,直接由套接字層調用packet_sendmsg()函數,最終再調用網卡驅動的發送函數。網絡層原始套接字的發送實現要相對復雜一些,由套接字層調用inet_sendmsg()->raw_sendmsg(),再經過路由和鄰居子系統的處理后,最終調用網卡驅動的發送函數。若注冊了ETH_P_ALL類型套接字,還需要將外發報文再收回去。

 

 

 

 

 

 

2.2  鏈路層原始套接字的實現

 

2.2.1  套接字創建

 

調用socket()函數創建套接字的流程如下,鏈路層原始套接字最終由packet_create()創建。

sys_socket()->sock_create()->__sock_create()->packet_create()

 

    當socket()函數的第三個參數protocol為非零的情況下,會調用dev_add_pack()將鏈路層套接字packet_sock的packet_type結構鏈到ptype_all鏈表或ptype_base鏈表中。    

  1. void dev_add_pack(struct packet_type *pt) 
  2.         …… 
  3.         if (pt->type == htons(ETH_P_ALL)) { 
  4.                 netdev_nit++; 
  5.                 list_add_rcu(&pt->list, &ptype_all); 
  6.         } else { 
  7.                 hash = ntohs(pt->type) & 15; 
  8.                 list_add_rcu(&pt->list, &ptype_base[hash]); 
  9.         } 
  10.         …… 
  11. }

    當protocol為ETH_P_ALL時,會將套接字加入到ptype_all鏈表中。如圖2所示,這里創建了兩個鏈路層原始套接字。

 

 

圖2  ptype_all鏈表

 

 

 

 

 

當protocol為其它非0值時,會將套接字加入到ptype_base鏈表中。如圖3所示,協議棧本身也需要注冊packet_type結構,圖中淺色的兩個packet_type結構分別是IP協議和ARP協議注冊的,其處理函數分別為ip_rcv()和arp_rcv()。圖中另外3個深色的packet_type結構則是鏈路層原始套接字注冊的,分別用於接收類型為ETH_P_IP、ETH_P_ARP和0x0810類型的報文。

 

 

圖3  ptype_base鏈表

 

 

 

 

 

 

 

 

 

 

 

2.2.2  報文接收

 

網卡驅動程序接收到報文后,在軟中斷上下文由netif_receive_skb()處理。首先會逐個遍歷ptype_all鏈表中的packet_type結構,若滿足條件“(!ptype->dev || ptype->dev == skb->dev)”,即套接字未綁定或者套接字綁定網口與skb所在網口匹配,就增加報文引用計數並交給packet_rcv()函數處理(若使用PACKET_MMAP收包方式則由tpacket_rcv()函數處理)。

網卡驅動->netif_receive_skb()->deliver_skb()->packet_rcv()/tpacket_rcv()

 

    以非PACKET_MMAP收包方式為例進行說明,packet_rcv()函數中比較重要的代碼片段如下。當報文skb到達packet_rcv()函數時,其skb->data所指的數據是不包含MAC首部的,所以對於type為非SOCK_DGRAM(即SOCK_RAW)類型,需要將skb->data指針前移,以便數據部分可以包含MAC首部。最后將skb放到套接字的接收隊列sk->sk_receive_queue中,並喚醒用戶態進程來讀取套接字中的數據。

  1. …… 
  2. if (sk->sk_type != SOCK_DGRAM) //即SOCK_RAW類型 
  3.         skb_push(skb, skb->data - skb->mac.raw); 
  4. …… 
  5. __skb_queue_tail(&sk->sk_receive_queue, skb); 
  6. sk->sk_data_ready(sk, skb->len); //喚醒進程讀取數據 
  7. ……

PACKET_MMAP收包方式的實現有所不同,tpacket_rcv()函數將skb->data拷貝到與用戶態mmap映射的共享內存中,最后喚醒用戶態進程來讀取數據。由於報文的內容已存放在內核空間和用戶空間共享的緩沖區中,用戶態可以直接讀取以減少數據的拷貝,所以這種方式效率比較高。

 

    上面介紹了報文接收在軟中斷的處理流程。下面以非PACKET_MMAP收包方式為例,介紹用戶態讀取報文數據的流程。用戶態recvmsg()最終調用skb_recv_datagram(),如果套接字接收隊列sk->sk_receive_queue中有報文就取skb並返回。否則調用wait_for_packet()等待,直到內核軟中斷收到報文並喚醒用戶態進程。

sys_recvmsg()->sock_recvmsg()->…->packet_recvmsg()->skb_recv_datagram()

 

 

 

 

 

 

2.2.3  報文發送

 

用戶態調用sendto()或sendmsg()發送報文的內核態處理流程如下,由套接字層最終會調用到packet_sendmsg()。

sys_sendto()->sock_sendmsg()->__sock_sendmsg()->packet_sendmsg()->dev_queue_xmit()

 

    該函數比較重要的函數片段如下。首先進行參數檢查及skb分配,再調用驅動程序的hard_header函數(對於以太網驅動是eth_header()函數)來構造報文的MAC頭部,此時的skb->data是指向MAC首部的,且skb->len為MAC首部長度(即14)。對於創建時指定type為SOCK_RAW類型套接字,由於在發送時需要自行構造MAC頭部,所以將skb->tail指針恢復到MAC首部開始的位置,並將skb->len設置為0(即不使用內核構造的MAC首部)。接着再調用memcpy_fromiovec()從skb->tail的位置開始拷貝報文數據,最終調用網卡驅動的發送函數將報文發送出去。

注:如果創建套接字時指定type為SOCK_DGRAM,則使用內核構造的MAC首部,用戶態發送的數據中不含MAC頭部數據。

 

         

  1. …… 
  2. res = dev->hard_header(skb, dev, ntohs(proto), addr, NULL, len); //構造MAC首部 
  3. if (sock->type != SOCK_DGRAM) { 
  4.         skb->tail = skb->data; //SOCK_RAW類型 
  5.         skb->len = 0; 
  6. ……
  7. err = memcpy_fromiovec(skb_put(skb,len), msg->msg_iov, len); //拷貝報文數據
  8. …… 
  9. err = dev_queue_xmit(skb); //發送報文 
  10. ……

 

 

2.2.4  其它

 

 

a)         套接字的綁定

鏈路層原始套接字可調用bind()函數進行綁定,讓packet_type結構dev字段指向相應的net_device結構,即將套接字綁定到相應的網口上。如2.2.2節報文接收的描述,在接收時如果套接口有綁定就需要進一步確認當前skb->dev是否與綁定網口相匹配,只有匹配的才會將報文上送到相應的套接字。

sys_bind()->packet_bind()->packet_do_bind()

b)        套接字選項

以下是比較常用的套接字選項

PACKET_RX_RING:用於PACKET_MMAP收包方式設置接收環形隊列

PACKET_STATISTICS:用於讀取收包統計信息

 

c)       信息查看

鏈路層原始套接字的信息可通過/proc/net/packet進行查看。如下為圖2和圖3中創建的原始套接字的信息,可以查看到創建時指定的協議類型、是否綁定網口、已使用的接收緩存大小等信息。這些信息對於分析和定位問題有幫助。 

  1. cat /proc/net/packet
  2. sk RefCnt Type Proto Iface R Rmem User Inode
  3. ffff810007df8400 3 3 0810 0 1 0 0 1310
  4. ffff810007df8800 3 3 0806 0 1 0 0 1309
  5. ffff810007df8c00 3 3 0800 0 1 560 0 1308
  6. ffff810007df8000 3 3 0003 0 1 560 0 1307
  7. ffff810007df3800 3 3 0003 0 1 560 0 1306

 

 

 

 

 

2.3  網絡層原始套接字的實現

 

2.3.1  套接字創建

 

如圖4所示,在IPV4協議棧中一個傳輸層協議(如TCP,UDP,UDP-Lite等)對應一個inet_protosw結構,而inet_protosw結構中又包含了proto_ops結構和proto結構。網絡子系統初始化時將所有的inet_protosw結構hash到全局的inetsw[]數組中。proto_ops結構實現的是從與協議無關的套接口層到協議相關的傳輸層的轉接,而proto結構又將傳輸層映射到網絡層。

 

 

圖4  inetsw[]數組結構

 

 

 

 

 

 

    調用socket()函數創建套接字的流程如下,網絡層原始套接字最終由inet_create()創建。

sys_socket()->sock_create()->__sock_create()->inet_create()

 

    inet_create()函數除用於創建網絡層原始套接字外,還用於創建TCP、UDP套接字。首先根據socket()函數的第二個參數(即SOCK_RAW)在inetsw[]數組中匹配到相應的inet_protosw結構。並將套接字結構的ops設置為inet_sockraw_ops,將套接字結構的sk_prot設置為raw_prot。然后對於SOCK_RAW類型套接字,還要將inet->num設置為協議類型,以便最后能調用proto結構的hash函數(即raw_v4_hash())。

 

         

  1. …… 
  2. sock->ops = answer->ops; //將socket結構的ops設置為inet_sockraw_ops 
  3. answer_prot = answer->prot; 
  4. …… 
  5. if (SOCK_RAW == sock->type) { //SOCK_RAW類型的套接字,設置inet->num 
  6.         inet->num = protocol; 
  7.         if (IPPROTO_RAW == protocol) //protocol為IPPROTO_RAW的特殊處理, 
  8.                 inet->hdrincl = 1; 后續在報文發送時會再講到 
  9. ……
  10. if (inet->num) {
  11.         inet->sport = htons(inet->num); 
  12.         sk->sk_prot->hash(sk); //調用raw_v4_hash()函數將套接字鏈到raw_v4_htable中 
  13. ……

 

經過如上操作后,相應的套接字結構sock會通過raw_v4_hash()函數鏈到raw_v4_htable鏈表中,網絡層原始套接字報文接收時需要使用到raw_v4_htable。如圖5所示,共創建了3個網絡層原始套接字,協議類型分別為 IPPROTO_TCP、IPPROTO_ICMP和89。

 

 

 

圖5  raw_v4_htable鏈表

 

 

 

 

 

 

 

 

 

 

 

2.3.2  報文接收

 

網卡驅動收到報文后在軟中斷上下文由netif_receive_skb()處理,對於IP報文且目的地址為本機的會由ip_rcv()最終調用ip_local_deliver_finish()函數。ip_local_deliver_finish()主要功能的代碼片段如下,先根據報文的L4層協議類型hash值在圖5中的raw_v4_htable表中查找是否有匹配的sock。如果有匹配的sock結構,就進一步調用raw_v4_input()處理網絡層原始套接字。不管是否有原始套接字要處理,該報文都會走后續的協議棧處理流程。即會繼續匹配inet_protos[]數組,根據L4層協議類型走TCP、UDP、ICMP等不同處理流程。

 

         

  1. …… 
  2. hash = protocol & (MAX_INET_PROTOS - 1); //根據報文協議類型取hash值 
  3. raw_sk = sk_head(&raw_v4_htable[hash]); //在raw_v4_htable中查找 
  4. …… 
  5. if (raw_sk && !raw_v4_input(skb, skb->nh.iph, hash)) //處理原始套接字 
  6. …… 
  7. if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) { //匹配inet_protos[]數組 
  8.         …… 
  9.         ret = ipprot->handler(skb); //調用傳輸層處理函數 
  10.         …… 
  11. } else { //如果在inet_protos[]數組中未匹配到,則釋放報文
  12.         …… 
  13.         kfree_skb(skb); 
  14. ……

 

如圖6所示的inet_protos[]數組,每項由net_protocol結構組成。表示一個協議(包括傳輸層協議和網絡層附屬協議)的接收處理函數集,一般包括一個正常接收函數和一個出錯接收函數。圖中TCP、UDP和ICMP協議的接收處理函數分別為tcp_v4_rcv()、udp_rcv()和icmp_rcv()。如果在inet_protos[]數組中未配置到相應的net_protocol結構,報文就會被丟棄掉。比如OSPF報文(協議類型為89)在inet_protos[]數組中沒有相應的項,內核會將其丟棄掉,這種報文只能提供網絡層原始套接字接收到用戶態來處理。

 

 

 

圖6  inet_protos[]數組結構

 

 

 

 

 

 

    網絡層原始套接字的總體接收流程如下,最終會將skb掛到相應套接字上,並喚醒用戶態進程讀取報文數據。

網卡驅動->netif_receive_skb()->ip_rcv()->ip_rcv_finish()->ip_local_deliver()->ip_local

_deliver_finish()->raw_v4_input()->raw_rcv()->raw_rcv_skb()->sock_queue_rcv_skb()

 


  1. …… 
  2. skb_queue_tail(&sk->sk_receive_queue, skb); //掛到接收隊列 
  3. if (!sock_flag(sk, SOCK_DEAD)) 
  4.         sk->sk_data_ready(sk, skb_len); //喚醒用戶態進程 
  5. ……

 

 

 

 

 

       上面介紹了報文接收在軟中斷的處理流程,下面介紹用戶態進程讀取報文是如何實現的。用戶態的recvmsg()最終會調用raw_recvmsg(),后者再調用skb_recv_datagram。如果套接字接收隊列sk->sk_receive_queue中有報文就取skb並返回。否則調用wait_for_packet()等待,直到內核軟中斷收到報文並喚醒用戶態進程。

sys_recvmsg()->sock_recvmsg()->…->sock_common_recvmsg()->raw_recvmsg()

 

 

 

 

 

 

2.3.3  報文發送

 

用戶態調用sendto()或sendmsg()發送報文的內核態處理流程如下,最終由raw_sendmsg()進行發送。

sys_sendto()->sock_sendmsg()->__sock_sendmsg()->inet_sendmsg()->raw_sendmsg()

    此函數先進行一些參數合法性檢測,然后調用ip_route_output_slow()進行選路。選路成功后主要執行如下代碼片段,根據inet->hdrincl是否設置走不同的流程。raw_send_hdrinc()函數表示用戶態發送的數據中需要包含IP首部,即由調用者在發送時自行構造IP首部。如果inet->hdrincl未置位,表示內核會構造IP首部,即調用者發送的數據中不包含IP首部。不管走哪個流程,最終都會經過ip_output()->ip_finish_output()->…->dev_queue_xmit()將報文交給網卡驅動的發送函數發送出去。

 

  1. …… 
  2. if (inet->hdrincl) { //調用者要構造IP首部 
  3.         err = raw_send_hdrinc(sk, msg->msg_iov, len, 
  4.                               rt, msg->msg_flags); 
  5. } else { 
  6.         …… //由內核構造IP首部 
  7.        err = ip_push_pending_frames(sk); 
  8. ……

   注:inet->hdrincl置位表示用戶態發送的數據中要包含IP首部,inet->hdrincl在以下兩種情況下被置位。

    a). 給套接字設置IP_HDRINCL選項

          setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, &val, sizeof(val))

    b). 調用socket()創建套接字時,第三個參數指定為IPPROTO_RAW,見2.3.1節。

          socktet(PF_INET, SOCK_RAW, IPPROTO_RAW)

 

 

 

 

 

 

2.3.4  其它

 

a)       套接字綁定

若原始套接字調用bind()綁定了一個地址,則該套接口只能收到目的IP地址與綁定地址相匹配的報文。內核的具體實現是raw_bind(),將inet->rcv_saddr設置為綁定地址。在原始套接字接收時,__raw_v4_lookup()在設置了inet->rcv_saddr字段的情況下,會判斷該字段是否與報文目的IP地址相同。

sys_bind()->inet_bind()->raw_bind()

 

b)      信息查看

網絡層原始套接字的信息可通過/proc/net/raw進行查看。如下為圖5所創建的3個網絡層原始套接字的信息,可以查看到創建套接字時指定的協議類型、綁定的地址、發送和接收隊列已使用的緩存大小等信息。這些信息對於分析和定位問題有幫助。

  1. cat /proc/net/raw
  2. sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
  3. 1: 00000000:0001 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 1323 2 ffff8100070b2380
  4. 6: 00000000:0006 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 1322 2 ffff8100070b2080
  5. 89: 00000000:0059 00000000:0000 07 00000000:00000000 00:00000000 00000000 0 0 1324 2 ffff8100070b2680

 

 三、應用及注意事項

 

 

3.1  使用鏈路層原始套接字

 

 

注意事項:

 

 

 

 

 

 

a)       盡量避免創建過多原始套接字,且原始套接字要盡量綁定網卡。因為收到每個報文除了會將其分發給綁定在該網卡上的原始套接字外,還會分發給沒有綁定網卡的原始套接字。如果原始套接字較多,一個報文就會在軟中斷上下文中分發多次,造成處理時間過長。

 

b)      發包和收包盡量使用同一個原始套接字。如果發包與收包使用兩個不同的原始套接字,會由於接收報文時分發多次而影響性能。而且用於發送的那個套接字的接收隊列上也會緩存報文,直至達到接收隊列大小限制,會造成內存泄露。

 

c)       若只接收指定類型二層報文,在調用socket()時指定第三個參數的協議類型,而最好不要使用ETH_P_ALL。因為ETH_P_ALL會接收所有類型的報文,而且還會將外發報文收回來,這樣就需要做BPF過濾,比較影響性能。

 

 

3.2  使用網絡層原始套接字

 

 

 

 

 

注意事項:

 

 

 

 

 

 

a)       由於IP報文的重組是在網絡層原始套接字接收流程之前執行的,所以該原始套接字不能接收到UDP和TCP的分組數據。

 

b)      若原始套接字已由bind()綁定了某個本地IP地址,那么只有目的IP地址與綁定地址匹配的報文,才能遞送到這個套接口上。

 

c)       若原始套接字已由connect()指定了某個遠地IP地址,那么只有源IP地址與這個已連接地址匹配的報文,才能遞送到這個套接口上。

 

 

 

 

 

 

3.3  網絡診斷工具使用原始套接字

 

很多網絡診斷工具也是利用原始套接字來實現的,經常會使用到的有tcpdump, ping和traceroute等。

tcpdump

 

 

 

 

 

 

該工具用於截獲網口上的報文流量。其實現原理是創建ETH_P_ALL類型的鏈路層原始套接字,讀取和解析報文數據並將信息顯示出來。

 

ping

 

 

 

 

 

 

該工具用於檢查網絡連接。其實現原理是創建網絡層原始套接字,指定協議類型為IPPROTO_ICMP。檢測方構造ICMP回射請求報文(類型為ICMP_ECHO),根據ICMP協議實現,被檢測方收到該請求報文后會響應一個ICMP回射應答報文(類型為ICMP_ECHOREPLY)。然后檢測方通過原始套接字讀取並解析應答報文,並顯示出序號、TTL等信息。

 

traceroute

 

 

 

 

 

 

該工具用於跟蹤IP報文在網絡中的路由過程。其實現原理也是創建網絡層原始套接字,指定協議類型為IPPROTO_ICMP。假設從A主機路由到D主機,需要依次經過B主機和C主機。使用traceroute來跟蹤A主機到D主機的路由途徑,具體步驟如下,在每次探測過程中會顯示各節點的IP、時間等信息。

a)       A主機使用普通的UDP套接字向目的主機發送TTL為1(使用套接口選項IP_TTL來修改)的UDP報文;

b)      B主機收到該UDP報文后,由於TTL為1會拒絕轉發,並且向A主機發送code為ICMP_EXC_TTL的ICMP報文;

c)       A主機用創建的網絡層原始套接字讀取並解析ICMP報文。如果ICMP報文code是ICMP_EXC_TTL,就將UDP報文的TTL增加1並回到步驟a)繼續進行探測;如果ICMP報文的code是ICMP_PROT_UNREACH,表示UDP報文到達了目的地。

 

              A主機―>B主機―>C主機―>D主機


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM