LwIP之ICMP協議

ICMP(網際控制報文協議),ICMP數據包是封裝在IP數據包中的,由於IP不是爲可靠傳輸服務設計的,ICMP的目的主要是用於在TCP/IP網絡中發送和控制消息。主要應用有Ping、Traceroute和MTU測試。

                   

ICMP報文的種類有三大種類,即ICMP差錯報文、控制報文、請求/應答報文,各大類型報文又分爲多種類型報文。

差錯報文:

   (1) 特點:

            1.ICMP差錯報文都是有路由器發送到源主機的。

            2.ICMP報文只提供IP數據報的差錯報告,並不採取處理措施,差錯處理由應用程序處理。

            3.傳輸過程中可能丟失、損壞,甚至被拋棄。

            4.ICMP差錯報文是伴隨着拋棄出錯的IP數據報而產生的。

            5.爲了防止廣播風暴,以下情況不會產生ICMP差錯報文。

                    1)ICMP差錯報文    2)目的地址是廣播或多播    3)鏈路層廣播的數據報    4)不是IP分片的第一片    5)源地址是零地址、回送地址、廣播地址或多播地址

    (2)信息不可達報文:

            1.目的機硬件故障或關機

            2.目標地址不存在

            3.網關不知道去往目的機的路徑

    (3)超時報文:

            1.爲了避免無限制的在網中循環,IP協議採用   

                    1)在數據報頭設置TTL域    2)對分片數據報採用定時器技術

            2.當報文超時出現時,路由器或目的機立即丟棄該數據報,並向信源機發送超時報文

 

控制報文:

     (1)擁塞控制與源站控制報文:

             1.當路由器接收比處理快或者傳入比傳出快時就會產生擁塞

             2.路由器通過發送源站抑制報文來抑制主機發送速率

             3.源主機一段時間內沒有收到抑制報文,便認爲抑制解除,逐漸恢復原來的數據流量

     (2)路由控制與重定向報文:

             1.當源主機要向目標主機發送IP數據報時,則把IP數據報發送給默認路器1,再由路由器1經過選路發送給路由器2,路由器2發送給目標主機

             2.如果路由器1和路由器2在同一網絡,路由器1發現源主機可直接發送IP數據報給路由器2,就會向源主機發送重定向報文,以後源主機將直接發送IP報文給路由器2

 

請求/應答報文:

    (1)回送請求與應答報文:

            1.測試目標主機和路由器是否可以到達

    (2)時戳請求與應答報文:

            1.同步互聯網中各個主機的時鐘

    (3)地址掩碼請求和應答報文

            1.用於無盤系統在引導過程中獲取子網掩碼。啓動時廣播地址掩碼請求,路由器收到請求後回送一個包含32位地址掩碼應答報文

 

報文格式,ICMP報文由8字節首部和可變長數據部分組成。不同類型的ICMP報文,首部格式有一定差異,但是首部前4字節的字段對所有類型通用。

類型:標識了ICMP具體類型

代碼:進一步指出產生該類型ICMP的原因

校驗和:整個ICMP的校驗和

 

LWIP實現了ICMP的部分功能:目的不可達報文、超時報文、回送查詢報文。

報文格式分別如下所示:

ICMP頭部基本類似,信息不可達和超時報文的頭部,相當於回送查詢報文頭部中標識符和序號設置爲0的情況。因此,LwIP只以回送查詢報文爲基礎提供了一種ICMP頭部結構體

/* ICMP類型 */
#define ICMP_ER 0      	/* 回送查詢應答 */
#define ICMP_DUR 3    	/* 目的不可達 */
#define ICMP_SQ 4      	/* 源端被關閉 */
#define ICMP_RD 5      	/* 重定向 */
#define ICMP_ECHO 8  	/* 回送查詢請求 */
#define ICMP_TE 11     	/* 超時 */
#define ICMP_PP 12     	/* 參數問題 */
#define ICMP_TS 13     	/* 時間戳請求 */
#define ICMP_TSR 14    	/* 時間戳應答 */
#define ICMP_IRQ 15    	/* 信息請求 */
#define ICMP_IR 16     	/* 信息應答 */

/* ICMP目的不可達代碼 */
enum icmp_dur_type {
  ICMP_DUR_NET = 0,    	/* 網絡不可達 */
  ICMP_DUR_HOST = 1,   	/* 主機不可達 */
  ICMP_DUR_PROTO = 2,  	/* 協議不可達 */
  ICMP_DUR_PORT = 3,   	/* 端口不可達 */
  ICMP_DUR_FRAG = 4,   	/* 需要分片但不分片位被置位 */
  ICMP_DUR_SR = 5      	/* 源路由失敗 */
};

/* ICMP超時代碼 */
enum icmp_te_type {
  ICMP_TE_TTL = 0,      /* 生存時間計時器超時 */
  ICMP_TE_FRAG = 1      /* 分片重裝超時 */
};

/* ICMP回送查詢報文頭部 */
struct icmp_echo_hdr {
  PACK_STRUCT_FIELD(u8_t type);		//類型
  PACK_STRUCT_FIELD(u8_t code);		//代碼
  PACK_STRUCT_FIELD(u16_t chksum);	//檢驗和
  PACK_STRUCT_FIELD(u16_t id);		//標識符
  PACK_STRUCT_FIELD(u16_t seqno);	//序號
} PACK_STRUCT_STRUCT;
PACK_STRUCT_END

 

在前面分析ip_input函數時,當報文類型爲ICMP報文時,調用icmp_input函數處理ICMP報文。當遇到不支持的協議時調用icmp_dest_unreach函數,返回目的不可達報文。

/* IP數據包輸入處理 */
err_t ip_input(struct pbuf *p, struct netif *inp)
{
  struct ip_hdr *iphdr;

  ......

  /* IP頭部指針 */
  iphdr = p->payload;

  ......

	/* 判斷協議類型 */
    switch (IPH_PROTO(iphdr)) {
    /* UDP協議 */
    case IP_PROTO_UDP:
      udp_input(p, inp);
      break;

	/* TCP協議 */
    case IP_PROTO_TCP:
      tcp_input(p, inp);
      break;

	/* ICMP協議 */
    case IP_PROTO_ICMP:
      icmp_input(p, inp);
      break;

	/* 不支持的協議 */
    default:
	  /* 目的IP不是廣播地址且不是組播地址 */
      if (!ip_addr_isbroadcast(&(iphdr->dest), inp) && !ip_addr_ismulticast(&(iphdr->dest))) {
        p->payload = iphdr;
        /* 發送ICMP目的不可達報文(協議不可達) */
        icmp_dest_unreach(p, ICMP_DUR_PROTO);
      }
	  /* 釋放數據包 */
      pbuf_free(p);
    }
  }

  ......

  return ERR_OK;
}

下面先來看目的不可達報文的實現方式

/* 發送ICMP目的不可達報文 */
void icmp_dest_unreach(struct pbuf *p, enum icmp_dur_type t)
{
  icmp_send_response(p, ICMP_DUR, t);
}

/* 發送ICMP響應報文 */
static void icmp_send_response(struct pbuf *p, u8_t type, u8_t code)
{
  struct pbuf *q;
  struct ip_hdr *iphdr;
  struct icmp_echo_hdr *icmphdr;

  /* 爲ICMP報文申請內存空間 */
  q = pbuf_alloc(PBUF_IP, sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE, PBUF_RAM);
  if (q == NULL) {
    LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded: failed to allocate pbuf for ICMP packet.\n"));
    return;
  }
  LWIP_ASSERT("check that first pbuf can hold icmp message", (q->len >= (sizeof(struct icmp_echo_hdr) + IP_HLEN + ICMP_DEST_UNREACH_DATASIZE)));

  /* IP頭部指針 */
  iphdr = p->payload;
  LWIP_DEBUGF(ICMP_DEBUG, ("icmp_time_exceeded from "));
  ip_addr_debug_print(ICMP_DEBUG, &(iphdr->src));
  LWIP_DEBUGF(ICMP_DEBUG, (" to "));
  ip_addr_debug_print(ICMP_DEBUG, &(iphdr->dest));
  LWIP_DEBUGF(ICMP_DEBUG, ("\n"));

  /* ICMP頭部指針 */
  icmphdr = q->payload;
  /* ICMP頭部類型 */
  icmphdr->type = type;
  /* ICMP頭部代碼 */
  icmphdr->code = code;
  /* ICMP頭部剩餘字節(標識符) */
  icmphdr->id = 0;
  /* ICMP頭部剩餘字節(序號) */
  icmphdr->seqno = 0;

  /* 將源數據報中的8字節 */
  SMEMCPY((u8_t *)q->payload + sizeof(struct icmp_echo_hdr), (u8_t *)p->payload, IP_HLEN + ICMP_DEST_UNREACH_DATASIZE);

  /* ICMP頭部檢驗和 */
  icmphdr->chksum = 0;
  icmphdr->chksum = inet_chksum(icmphdr, q->len);
  ICMP_STATS_INC(icmp.xmit);
  snmp_inc_icmpoutmsgs();
  snmp_inc_icmpouttimeexcds();

  /* 調用IP輸出函數,發送ICMP數據包 */
  ip_output(q, NULL, &(iphdr->src), ICMP_TTL, 0, IP_PROTO_ICMP);

  /* 釋放數據包 */
  pbuf_free(q);
}

接下來再看 icmp_input函數

/* ICMP報文接收處理 */
void icmp_input(struct pbuf *p, struct netif *inp)
{
  u8_t type;
  struct icmp_echo_hdr *iecho;
  struct ip_hdr *iphdr;
  struct ip_addr tmpaddr;
  s16_t hlen;

  /* IP頭部指針 */
  iphdr = p->payload;
  /* IP頭部長度 */
  hlen = IPH_HL(iphdr) * 4;
  
  /* 向後調整剝離IP頭部,保證ICMP頭部不小於4字節 */
  if (pbuf_header(p, -hlen) || (p->tot_len < sizeof(u16_t) * 2)) {
    goto lenerr;
  }

  /* ICMP類型 */
  type = *((u8_t *)p->payload);

  /* 判斷ICMP類型 */
  switch (type) {
  /* 回送查詢報文 */
  case ICMP_ECHO:
    {
      int accepted = 1;
      
      /* ICMP目的IP爲組播地址 */
      if (ip_addr_ismulticast(&iphdr->dest)) {
        accepted = 0;	//不做處理
      }

      /* ICMP目的IP爲廣播地址 */
      if (ip_addr_isbroadcast(&iphdr->dest, inp)) {
        accepted = 0;	//不做處理
      }

	  /* 不做處理,直接釋放ICMP數據包 */
      if (!accepted) {
        pbuf_free(p);
        return;
      }
    }

    /* 檢查ICMP數據包長度合不合理 */
    if (p->tot_len < sizeof(struct icmp_echo_hdr)) {
      goto lenerr;
    }

    /* 檢查檢驗和是否正確 */
    if (inet_chksum_pbuf(p) != 0) {
      pbuf_free(p);
      return;
    }
    
	/* ICMP頭部指針 */
    iecho = p->payload;
    
    /* 將IP頭部中源地址和目的地址交換 */
    tmpaddr.addr = iphdr->src.addr;
    iphdr->src.addr = iphdr->dest.addr;
    iphdr->dest.addr = tmpaddr.addr;

	/* 設置ICMP報文類型爲回送查詢應答 */
    ICMPH_TYPE_SET(iecho, ICMP_ER);
    
    /* 重新設置ICMP頭部校驗和 */
    if (iecho->chksum >= htons(0xffff - (ICMP_ECHO << 8))) {
      iecho->chksum += htons(ICMP_ECHO << 8) + 1;
    } else {
      iecho->chksum += htons(ICMP_ECHO << 8);
    }

    /* 設置IP頭部TTL字段 */
    IPH_TTL_SET(iphdr, ICMP_TTL);
    /* 計算IP頭部校驗和字段 */
    IPH_CHKSUM_SET(iphdr, 0);
    IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, IP_HLEN));

	/* 向前調整出IP頭部空間 */
    if(pbuf_header(p, hlen)) {
    } 
    else {
      /* 通過IP發送函數,發送回送查詢應答報文 */
      ip_output_if(p, &(iphdr->src), IP_HDRINCL, ICMP_TTL, 0, IP_PROTO_ICMP, inp);
    }
    break;

  /* 不處理其它類型的ICMP報文 */
  default:
    break;
  }
  pbuf_free(p);
  return;
  
lenerr:
  pbuf_free(p);
  return;
}

 

在前面分析IP數據報重組的時候, 一旦重組超時協議棧就會刪除重組並向源主機返回ICMP分片重組超時報文

/* 重組IP數據報定時器回調函數(週期1秒) */
void ip_reass_tmr(void)
{
  struct ip_reassdata *r, *prev = NULL;

  /* 遍歷所有重組IP數據報 */
  r = reassdatagrams;
  while (r != NULL) {
  	/* 沒有超時,將生存週期減一 */
    if (r->timer > 0) {
      r->timer--;
      prev = r;
      r = r->next;
    } 
	/* 超時 */
    else {
      struct ip_reassdata *tmp;

      tmp = r;
      r = r->next;

      /* 釋放重組IP數據報的所有分片,並將重組IP數據報從鏈表中移除 */
      ip_reass_free_complete_datagram(tmp, prev);
     }
   }
}

/* 釋放重組IP數據報的所有分片,並將重組IP數據報從鏈表中移除 */
static int ip_reass_free_complete_datagram(struct ip_reassdata *ipr, struct ip_reassdata *prev)
{
  ......
  struct ip_reass_helper *iprh;

  /* 向源主機發送ICMP分片重組超時報文 */
  iprh = (struct ip_reass_helper *)ipr->p->payload;
  if (iprh->start == 0) {
    p = ipr->p;
    ipr->p = iprh->next_pbuf;
    SMEMCPY(p->payload, &ipr->iphdr, IP_HLEN);
    icmp_time_exceeded(p, ICMP_TE_FRAG);
    pbufs_freed += pbuf_clen(p);
    pbuf_free(p);
  }

  ......
}

下面就來看一下ICMP超時報文發送函數

/* 發送ICMP超時報文 */
void icmp_time_exceeded(struct pbuf *p, enum icmp_te_type t)
{
  icmp_send_response(p, ICMP_TE, t);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章