iphdr與tcphdr詳解(skb_header_pointer函數分析)

來源:互聯網
上載者:User

linux 2.6.26 下擷取tcp資訊: 
tcph=skb_header_pointer(skb, protoff, sizeof(tcph), &tcph);    //skb_header_pointer是個庫函數
skb_header_pointer函數
該函數本身很簡單,定義如下:
/* #include <linux/skbuff.h>*/
static inline void *skb_header_pointer(const struct sk_buff *skb, int offset, int len, void *buffer)
{
 int hlen = skb_headlen(skb);
 if (hlen - offset >= len)
  return skb->data + offset;
 if (skb_copy_bits(skb, offset, buffer, len) < 0)
  return NULL;
 return buffer;
}
其中參數為:
skb:資料包struct sk_buff的指標
offset:相對資料起始頭(如IP頭)的位移量
len:資料長度
buffer:緩衝區,大小不小於len

其中skb_headlen()定義為:
/* #include <linux/skbuff.h> */
static inline unsigned int skb_headlen(const struct sk_buff *skb)
{
 return skb->len - skb->data_len;
}
其中skb->len是資料包長度,在IPv4中就是單個完整IP包的總長,但這些資料並不一定都在當前記憶體頁;skb->data_len表示在其他頁的資料長度,因此skb->len - skb->data_len表示在當前頁的資料大小。

/* net/core/skbuff.c */
/* Copy some data bits from skb to kernel buffer. */
int skb_copy_bits(const struct sk_buff *skb, int offset, void *to, int len)
{
 int i, copy;
 int start = skb_headlen(skb);
 if (offset > (int)skb->len - len)
  goto fault;
 /* Copy header. */
 if ((copy = start - offset) > 0) {
// 拷貝在當前頁面中的部分
  if (copy > len)
   copy = len;
  memcpy(to, skb->data + offset, copy);
  if ((len -= copy) == 0)
   return 0;
  offset += copy;
  to     += copy;
 }
// 拷貝本skb中其他片段中的部分
 for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
  int end;
  BUG_TRAP(start <= offset + len);
  end = start + skb_shinfo(skb)->frags[i].size;
  if ((copy = end - offset) > 0) {
   u8 *vaddr;
   if (copy > len)
    copy = len;
   vaddr = kmap_skb_frag(&skb_shinfo(skb)->frags[i]);
   memcpy(to,
          vaddr + skb_shinfo(skb)->frags[i].page_offset+
          offset - start, copy);
   kunmap_skb_frag(vaddr);
   if ((len -= copy) == 0)
    return 0;
   offset += copy;
   to     += copy;
  }
  start = end;
 }
// 拷貝其他片段skb中的資料部分
 if (skb_shinfo(skb)->frag_list) {
  struct sk_buff *list = skb_shinfo(skb)->frag_list;
  for (; list; list = list->next) {
   int end;
   BUG_TRAP(start <= offset + len);
   end = start + list->len;
   if ((copy = end - offset) > 0) {
    if (copy > len)
     copy = len;
    if (skb_copy_bits(list, offset - start,
        to, copy))
     goto fault;
    if ((len -= copy) == 0)
     return 0;
    offset += copy;
    to     += copy;
   }
   start = end;
  }
 }
 if (!len)
  return 0;
fault:
 return -EFAULT;
}

iphdr結構詳解:
include <linux/ip.h>
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8    ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8    version:4,
ihl:4;
#else
#error “Please fix ”
#endif
__u8    tos;
__be16 -tot_len;
__be16 -id;
__be16 -frag_off;
__u8    ttl;
__u8    protocol;
__be16 -check;
__be32 -saddr;
__be32 -daddr;
};

iphdr->version
     版本(4位),目前的協議版本號碼是4,因此IP有時也稱作IPv4。
iphdr->ihl
     首部長度(4位):首部長度指的是IP層頭部佔32 bit字的數目(也就是IP層頭部包含多少個4位元組 — 32位),包括任何選項。由於它是一個4位元欄位,單位為4個位元組,因此,一個IP包頭的長度最長為“1111”,即15*4=60個位元組,因此首部最長為60個位元組。普通IP資料報(沒有任何選擇項)欄位的值是5  5 * 32 / 8 = 5 * 4 = 20 Bytes
iphdr->tos
     服務類型欄位(8位): 服務類型(TOS)欄位包括一個3 bit的優先權子欄位(現在已被忽略),4 bit的TOS子欄位和1 bit未用位但必須置0。4 bit的TOS子欄位分別代表:最小時延、最大輸送量、最高可靠性和最小費用。4 bit中只能設定其中1 bit。如果所有4 bit均為0,那麼就意味著是一般服務。
例:
8個BIT的含義是:
000 前三位不用
0 表示最小時延,如Telnet服務使用該位
0 表示輸送量,如FTP服務使用該位
0 表示可靠性,如SNMP服務使用該位
0 表示最小代價
0 不用
iphdr->tot_len
     總長度欄位(16位)是指整個IP資料報的長度,以位元組為單位。利用首部長度欄位和總長度欄位,就可以知道 IP資料報中資料內容的起始位置和長度。由於該欄位長16位元(一個IP資料包的最大長度為2的16次方減1,即:65535個位元組),所以在乙太網路中能夠傳輸的最大IP資料包為65535個位元組。總長度欄位是IP首部中必要的內容,因為一些資料鏈路(如乙太網路)需要填充一些資料以達到最小長度。儘管乙太網路的最小幀長為46位元組,但是IP資料可能會更短。如果沒有總長度欄位,那麼IP層就不知道46位元組中有多少是IP資料報的內容。
iphdr->id
     識別欄位(16位)唯一地標識主機發送的每一份資料報。通常每發送一份報文它的值就會加1。(例如:Identification=363,表示IP包識別號為363。該部分佔16個BIT,以十進位數表示。)iphdr->frag_off (16位)frag_off域的低13位 — 分段位移(Fragment offset)域指明了該分段在當前資料報中的什麼位置上。除了一個資料報的最後一個分段以外,其他所有的分段(分區)必須是8位元組的倍數。這是8位元組是基本分段單位。由於該域有13個位,所以,每個資料報最多有8192個分段。因此,最大的資料報長度為65,536位元組,比 iphdr->tot_len域還要大1。iphdr->frag_off的高3位:
(1) 位元0是保留的,必須為0;
(2) 位元1是“更多分區”(MF — More Fragment)標誌。除了最後一片外,其他每個組成資料報的片都要把該位元置1。
(3) 位元2是“不分區”(DF — Don’t Fragment)標誌,如果將這一位元置1,IP將不對資料報進行分區,這時如果有需要進行分區的資料報到來,會丟棄此資料報並發送一個ICMP差錯報文給開始端點。
iphdr->ttl
     TTL(time-to-live) — 8位,存留時間欄位設定了資料報可以經過的最多路由器數。它指定了資料報的存留時間。TTL的初始值由源主機設定(通常為32或64),一旦經過一個處理它的路由器,它的值就減去1。當該欄位的值為0時,資料報就被丟棄,並發送ICMP報文通知源主機。TTL(Time to live)域是一個用於限制分組生存期的計數器。這裡的計數時間單位為秒,因此最大的生存期為255秒。在每一跳上該計數器必須被遞減,而且,當資料報在一台路由器上排隊時間較長時,該計數器必須被多倍遞減。在實踐中,它只是跳計數器,當它遞減到0的時候,分組被丟棄,路由器給源主機發送一個警告分組。此項特性可以避免資料報長時間地逗留在網路中,有時候當路由表被破壞之後,這種事情是有可能發生的。
iphdr->protocol
     協議欄位(8位): 根據它可以識別是哪個協議向IP傳送資料。當網路層組裝完成一個完整的資料報之後,它需要知道該如何對它進行處理。協議(Protocol)域指明了該將它交給哪個傳輸進程。TCP是一種可能,但是UDP或者其他的協議也是可能的。Proctol=6(TCP),表示協議類型為TCP,協議代碼是6。如果是UDP協議,則此處的協議代碼應為17。如果是ICMP協議,則此處的協議代碼應為1。該部分佔8個BIT。(IPPROTO_TCP ,IPPROTO_UDP,IPPROTO_ICMP etc.)
iphdr->check
     首部檢驗和欄位(16位)是根據IP首部計算的檢驗和碼。它不對首部後面的資料進行計算。 ICMP、IGMP、UDP和TCP在它們各自的首部中均含有同時覆蓋首部和資料檢驗和碼。為了計算一份資料報的IP檢驗和,首先把檢驗和欄位置為0。然後,對首部中每個16 bit進行二進位反碼求和(整個首部看成是由一串16 bit的字組成),結果存在檢驗和欄位中。當收到一份IP資料報後,同樣對首部中每個16 bit進行二進位反碼的求和。由於接收方在計算過程中包含了發送方存在首部中的檢驗和,因此,如果首部在傳輸過程中沒有發生任何差錯,那麼接收方計算的結果應該為全1。如果結果不是全1(即檢驗和錯誤),那麼IP就丟棄收到的資料報。但是不產生差錯報文,由上層去發現丟失的資料報並進行重傳。
iphdr->saddr
     32位源IP地址
iphdr->daddr
     32位目的IP地址
網路位元組序
     4個位元組的32 bit值以下面的次序傳輸:首先是0~7bit,其次8~15bit,然後16~23bit,最後是24~31 bit。這種傳輸次序稱作big endian位元組序。由於TCP/IP首部中所有的二進位整數在網路中傳輸時都要求以這種次序,因此它又稱作網路位元組序。

tcphdr結構詳解:
sk_buff->tcphdr
/usr/src/linux-2.6.19/include/linux/tcp.h
struct tcphdr {
    __be16 source;
    __be16 dest;
    __be32 seq;
    __be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u16   res1:4,
            doff:4,
            fin:1,
            syn:1,
            rst:1,
            psh:1,
            ack:1,
            urg:1,
            ece:1,
            cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
    __u16   doff:4,
            res1:4,
            cwr:1,
            ece:1,
            urg:1,
            ack:1,
            psh:1,
            rst:1,
            syn:1,
            fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
    __be16 window;
    __be16 check;
    __be16 urg_ptr;
};
     |----------------|----------------|-------------
     |     source     |     dest       |
     |----------------|----------------|
     |               seq               |
     |---------------------------------|
     |               ack_seq           | 20 Bytes
     |----|----|------|----------------|
     |doff|res1|      |     window     |
     |----|----|------|----------------|
     |     check      |     urg_ptr    |
     |----------------|----------------|-------------
     |             options             | 4 Bytes
     |---------------------------------| 

                    TCP頭
tcphdr->source
     16位源連接埠號碼
tcphdr->dest
     16位目的連接埠號碼
tcphdr->seq
     表示此次發送的資料在整個報文段中的起始位元組數。序號是32 bit的無符號數。為了安全起見,它的初始值是一個隨機產生的數,它到達32位最大值後,又從零開始。
tcphdr->ack_seq
     指定的是下一個期望接收的位元組,而不是已經正確接收到的最後一個位元組。
tcphdr->doff
     TCP頭長度,指明了在TCP頭部包含多少個32位的字。此資訊是必須的,因為options域的長度是可變的,所以整個TCP頭部的長度也是變化的。從技術上講,這個域實際上指明了資料部分在段內部的其起始地址(以32位字作為單位進行計量),因為這個數值正好是按字為單位的TCP頭部的長度,所以,二者的效果是等同的
tcphdr->res1為保留位
tcphdr->window
     是16位滑動視窗的大小,單位為位元組,起始於確認序號欄位指明的值,這個值是接收端正期望接收的位元組數,其最大值是63353位元組。
     TCP中的流量控制是通過一個可變大小的滑動視窗來完成的。window域指定了從被確認的位元組算起可以接收的多少個位元組。window = 0也是合法的,這相當於說,到現在為止多達ack_seq-1個位元組已經接收到了,但是接收方現在狀態不佳,需要休息一下,等一會兒再繼續接收更多的資料。以後,接收方可以通過發送一個同樣ack_seq但是window不為0的資料區段,告訴發送方繼續發送資料區段。
tcphdr->check
     是檢驗和,覆蓋了整個的TCP報文段,這是一個強制性的欄位,一定是由發送端計算和儲存,並由接收端進行驗證。
tcphdr->urg_ptr
     這個域被用來指示緊急資料在當前資料區段中的位置,它是一個相對於當前序號的位元組位移值。這個設施可以代替中斷資訊。
 
fin, syn, rst, psh, ack, urg為6個標誌位
     這6個位域已經保留了超過四分之一個世紀的時間而仍然原封未動,這樣的事實正好也說明了TCP的設計者們考慮的是多麼的周到。它們的含義如下:
    tcphdr->fin fin位被用於釋放一個串連。它表示發送方已經沒有資料要傳輸了。
    tcphdr->syn 同步序號,用來發起一個串連。syn位被用於建立串連的過程。在串連請求中,syn=1; ack=0表示該資料區段沒有使用捎帶的確認域。串連應答捎帶了一個確認,所以有syn=1; ack=1。本質上,syn位被用來表示connection request和connection accepted,然而進一步用ack位來區分這兩種情況。
    tcphdr->rst 該為用於重設一個已經混亂的串連,之所以會混亂,可能是由於主機崩潰,或者其他的原因。該位也可以被用來拒絕一個無效的資料區段,或者拒絕一個串連請求。一般而言,如果你得到的資料區段設定了rst位,那說明你這一端有了問題。
    tcphdr->psh 接收方在收到資料後應立即請求將資料遞交給應用程式,而不是將它緩衝起來直到整個緩衝區接收滿為止(這樣做的目的可能是為了效率的原因)
    tcphdr->ack ack位被設定為1表示tcphdr->ack_seq是有效。如果ack為0,則該資料區段不包含確認息,所以,tcphdr->ack_seq域應該被忽略。
    tcphdr->urg 緊急指標有效
    tcphdr->ece 用途暫時不明
    tcphdr->cwr 用途暫時不明
    核心原始碼在函數tcp_transmit_skb()中建立tcp首部。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.