IP協議的功能
回顧一下前面寫的關于IP協議的文章:
IP協議基礎掃盲班
IP地址相關知識深入了解~
IP數據報分析
IP數據報結構、IP分片的原理與處理
IP數據報的收發
回顧一下前面的文章所提及的知識點,總結一下IP協議的功能,得到以下結論:
- 編址(目標端的IP地址),數據傳輸的過程當中就必須表明要發送目標端的IP地址
- 尋址和路由(根據對方的IP地址,尋找最佳路徑傳輸信息);
- 數據報的分片和重組。
- 傳遞服務是不可靠的(IP協議只是盡自己最大努力去傳輸數據包),它也是無連接的協議
IP數據報發送
IP協議是網絡層的主要協議,在上層傳輸協議(如TCP/UDP)需要發送數據時,會將數據封裝起來,然后傳遞到IP層,IP協議首先會根據上層協議的目標IP地址選擇一個合適的網卡進行發送數據( 路由),然后IP協議將再次封裝數據形成IP數據報,主要的操作就是填寫IP數據報首部對應的各個字段:目標IP地址、源IP地址、協議類型、生存時間等,最后在IP層通過回調函數netif->output(即etharp_output()函數)將IP數據報投遞給ARP,再調用網卡底層發送函數進行發送,這樣子自上而下的數據就發送出去,IP協議以目標IP地址作為目標主機的身份地址。
/*** ip_output_if的簡化版接口。它找到發送數據包的netif網絡接口并調用ip_output_if來完成實際工作。** @param p 要發送的數據包(p->payload(有效負載)指向數據,如果dest == LWIP_IP_HDRINCL,則p已包含IP頭和p->有效負載指向該IP頭)* @param src 要發送的源IP地址(如果src == IP4_ADDR_ANY,則用發送的netif綁定的IP地址用作源地址)* @param dest 目的IP地址* @param ttl 要在IP標頭中設置的TTL值(生存時間)* @param tos 用于在IP標頭中設置的TOS值* @param proto 將在IP頭中設置對應的上層協議* @return ERR_OK 如果數據包發送正常就返回ok,* 如果p沒有足夠的空間用于IP /LINK標頭,則為ERR_BUF* 其他則返回netif->output返回的錯誤* @return ERR_RTE如果沒有找到路線* 請參閱ip_output_if()以獲取更多返回值*/err_tip4_output(struct pbuf *p,constip4_addr_t*src,constip4_addr_t*dest,u8_t ttl,u8_t tos,u8_t proto){struct netif *netif;LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);//根據目標IP地址找到對應的網卡發送數據if((netif = ip4_route_src(src, dest))== NULL){LWIP_DEBUGF(IP_DEBUG,("ip4_output: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\\n",ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));IP_STATS_INC(ip.rterr);return ERR_RTE;}return ip4_output_if(p, src, dest, ttl, tos, proto, netif);}
路由過程的實現
路由(routing)就是通過互聯的網絡把信息從源地址傳輸到目的地址的活動,發送端必然需要找到一個網卡將數據報發送出去,而實現這個過程的函數就是 ip4_route_src()。
其實lwip對 ip4_route_src()函數進行了重新定義,實際上是調用了 ip4_route()函數。這個函數的原理就是根據指定的IP地址找到合適的網卡 netif,然后返回,前面的文章也提到過,lwip的網卡是通過 netif_list列表管理的,那么找網卡的操作也必然是遍歷網卡列表 netif_list,判斷網卡是否已經掛載并且IP地址是否有效,如果連網卡都找不到,那就不用發送數據了,返回null。
#define ip4_route_src(src, dest) ip4_route(dest)
/***為給定的IP地址查找適當的網絡接口。*它搜索網絡接口列表。找到匹配項**@param dest 要查找路由的目標IP地址*@return 發送到達目的地的網卡 netif*/struct netif *ip4_route(constip4_addr_t*dest){#if!LWIP_SINGLE_NETIFstruct netif *netif;LWIP_ASSERT_CORE_LOCKED();#if LWIP_MULTICAST_TX_OPTIONS/*默認使用管理選擇的接口進行多播*/if(ip4_addr_ismulticast(dest)&& ip4_default_multicast_netif){return ip4_default_multicast_netif;}#endif /* LWIP_MULTICAST_TX_OPTIONS *//* bug #54569: in case LWIP_SINGLE_NETIF=1 and LWIP_DEBUGF() disabled, the following loop is optimized away */LWIP_UNUSED_ARG(dest);/*遍歷網卡列表netif_list */NETIF_FOREACH(netif){/* 如果網卡已經掛載并且IP地址是有效的 */if(netif_is_up(netif)&& netif_is_link_up(netif)&&!ip4_addr_isany_val(*netif_ip4_addr(netif))){/* 網絡掩碼匹配? */if(ip4_addr_netcmp(dest, netif_ip4_addr(netif), netif_ip4_netmask(netif))){/* 返回找到的網卡netif */return netif;}/* 網關在非廣播接口上匹配?(即在點對點接口中對等) */if(((netif->flags & NETIF_FLAG_BROADCAST)==0)&& ip4_addr_cmp(dest, netif_ip4_gw(netif))){/* 返回找到的網卡netif */return netif;}}}#if LWIP_NETIF_LOOPBACK &&!LWIP_HAVE_LOOPIF /**如果打開環回地址的宏定義 *//* loopif is disabled, looopback traffic is passed through any netif */if(ip4_addr_isloopback(dest)){/*不檢查環回流量的鏈接*/if(netif_default != NULL && netif_is_up(netif_default)){return netif_default;}/*默認netif沒有啟動,只需使用任何netif進行環回流量*/NETIF_FOREACH(netif){if(netif_is_up(netif)){return netif;}}return NULL;}#endif /* LWIP_NETIF_LOOPBACK && !LWIP_HAVE_LOOPIF */#ifdef LWIP_HOOK_IP4_ROUTE_SRCnetif = LWIP_HOOK_IP4_ROUTE_SRC(NULL, dest);if(netif != NULL){return netif;}#elif defined(LWIP_HOOK_IP4_ROUTE)netif = LWIP_HOOK_IP4_ROUTE(dest);if(netif != NULL){return netif;}#endif#endif /* !LWIP_SINGLE_NETIF */if((netif_default == NULL)||!netif_is_up(netif_default)||!netif_is_link_up(netif_default)||ip4_addr_isany_val(*netif_ip4_addr(netif_default))|| ip4_addr_isloopback(dest)){/*找不到匹配的netif,默認的netif不可用。建議使用LWIP_HOOK_IP4_ROUTE()*/LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,("ip4_route: No route to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\\n",ip4_addr1_16(dest), ip4_addr2_16(dest), ip4_addr3_16(dest), ip4_addr4_16(dest)));IP_STATS_INC(ip.rterr);MIB2_STATS_INC(mib2.ipoutnoroutes);return NULL;}return netif_default;}
ip4_output_if函數
找到網卡之后就調用 ip4_output_if()函數將數據發送出去,這個函數會指定發送數據的網卡,同時會將來自上層協議(tcp、udp)的數據進行封裝,組成IP數據報再發送,不過這個函數層層調用,比較麻煩,具體如下:。
/*** 在網絡接口上發送IP數據包。這個函數構造IP數據包首部并計算IP頭校驗和,* 如果源IP地址為NULL,在發送的時候就填寫發送網卡的IP地址為源IP地址* 如果目標IP地址是LWIP_IP_HDRINCL,則假定pbuf已經存在包括IP頭和有效負載指向它而不是數據。** @param p 要發送的數據包(p->payload(有效負載)指向數據,如果dest == LWIP_IP_HDRINCL,則p已包含IP頭和p->有效負載指向該IP頭)* @param src 要發送的源IP地址(如果src == IP4_ADDR_ANY,則用發送的netif綁定的IP地址用作源地址)* @param dest 目的IP地址* @param ttl 要在IP標頭中設置的TTL值(生存時間)* @param tos 用于在IP標頭中設置的TOS值* @param proto 將在IP頭中設置對應的上層協議* @param netif 發送此數據包的netif* @return ERR_OK 如果數據包發送正常就返回ok,* 如果p沒有足夠的空間用于IP /LINK標頭,則為ERR_BUF* 其他則返回netif->output返回的錯誤** @note ip_id:RFC791“某些主機可能只需使用* 獨立于目的地的唯一標識符“*/err_tip4_output_if(struct pbuf *p,constip4_addr_t*src,constip4_addr_t*dest,u8_t ttl,u8_t tos,u8_t proto,struct netif *netif){#if IP_OPTIONS_SENDreturn ip4_output_if_opt(p, src, dest, ttl, tos, proto, netif, NULL,0);}
/*** 與ip_output_if()相同,但可以包含IP選項:** @param ip_options指向IP選項的指針,復制到IP頭中* @param optlen ip_options的長度*/err_tip4_output_if_opt(struct pbuf *p,constip4_addr_t*src,constip4_addr_t*dest,u8_t ttl,u8_t tos,u8_t proto,struct netif *netif,void*ip_options,u16_t optlen){#endif /* IP_OPTIONS_SEND */constip4_addr_t*src_used = src;if(dest != LWIP_IP_HDRINCL){if(ip4_addr_isany(src)){src_used = netif_ip4_addr(netif);}}#if IP_OPTIONS_SENDreturn ip4_output_if_opt_src(p, src_used, dest, ttl, tos, proto, netif,ip_options, optlen);#else/* IP_OPTIONS_SEND */return ip4_output_if_src(p, src_used, dest, ttl, tos, proto, netif);#endif /* IP_OPTIONS_SEND */}
ip4_output_if_opt_src
首先看看這個函數到底做了什么吧:在上層協議遞交數據包后,通過層層調用,最終到 ip4_output_if_opt_src()函數中處理,它的處理如下:
代碼的實現如下:注釋非常豐富。主要過程就是:
- 判斷是否填寫好IP數據報首部?若目標IP地址為LWIPIPHDRINCL表示已經填寫好IP數據報首部,且payload指針也指向了IP數據報首部。
- 如果沒有填寫IP數據報首部,調用
pbuf_add_header()函數調整數據區域指針以指向IP數據報首部。 - 填寫IP數據報中的生存時間、服務類型、上層協議、目標IP地址、版本號與首部長度、數據報總長度、標志位和分片偏移量、標識、源IP地址等內容,總之就是將IP數據報首部的內容該填的都填上。
- 如果目標IP地址是自己的網卡IP地址,調用環回輸入函數
netif_loop_output()發送IP數據報給自己,這種處理一般是用于測試代碼。 - 如果IP數據報太大,數據報總長度大于網卡的MTU,則需要進行分片處理,調用
ip4_frag()函數進行發送。 - 直接調用注冊的
netif->output接口傳遞給ARP,實際上就是調用etharp_output()函數,在這里它會將IP地址解析成對應的MAC地址,并且調用網卡發送函數進行發送。
/*** 與ip4_output_if_opt()相同,當源地址是'IP4_ADDR_ANY'時,'src'地址不會被netif地址替換*/err_tip4_output_if_opt_src(struct pbuf *p,constip4_addr_t*src,constip4_addr_t*dest,u8_t ttl,u8_t tos,u8_t proto,struct netif *netif,void*ip_options,u16_t optlen){#endif /* IP_OPTIONS_SEND */struct ip_hdr *iphdr;ip4_addr_t dest_addr;#if CHECKSUM_GEN_IP_INLINEu32_t chk_sum =0;#endif /* CHECKSUM_GEN_IP_INLINE */LWIP_ASSERT_CORE_LOCKED();LWIP_IP_CHECK_PBUF_REF_COUNT_FOR_TX(p);MIB2_STATS_INC(mib2.ipoutrequests);/* 應該要構建IP首部還是已經包含在pbuf中了?如果是要構建IP數據報首部 */if(dest != LWIP_IP_HDRINCL){u16_t ip_hlen = IP_HLEN;#if IP_OPTIONS_SENDu16_t optlen_aligned =0;if(optlen !=0){#if CHECKSUM_GEN_IP_INLINEint i;#endif /* CHECKSUM_GEN_IP_INLINE */if(optlen >(IP_HLEN_MAX - IP_HLEN)){/* 選項字段太長 */LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,("ip4_output_if_opt: optlen too long\\n"));IP_STATS_INC(ip.err);MIB2_STATS_INC(mib2.ipoutdiscards);return ERR_VAL;}/* 選項字段按照4字節對齊 */optlen_aligned =(u16_t)((optlen +3)&~3);ip_hlen =(u16_t)(ip_hlen + optlen_aligned);/* 首先寫入IP選項字段 */if(pbuf_add_header(p, optlen_aligned)){LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,("ip4_output_if_opt: not enough room for IP options in pbuf\\n"));IP_STATS_INC(ip.err);MIB2_STATS_INC(mib2.ipoutdiscards);return ERR_BUF;}MEMCPY(p->payload, ip_options, optlen);if(optlen < optlen_aligned){/* 剩余字節清零 */memset(((char*)p->payload)+ optlen,0,(size_t)(optlen_aligned - optlen));}#if CHECKSUM_GEN_IP_INLINEfor(i =0; i < optlen_aligned /2; i++){chk_sum +=((u16_t*)p->payload)[i];}#endif /* CHECKSUM_GEN_IP_INLINE */}#endif /* IP_OPTIONS_SEND *//* 生成IP頭 */if(pbuf_add_header(p, IP_HLEN)){LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,("ip4_output: not enough room for IP header in pbuf\\n"));IP_STATS_INC(ip.err);MIB2_STATS_INC(mib2.ipoutdiscards);return ERR_BUF;}iphdr =(struct ip_hdr *)p->payload;(p->len >=sizeof(struct ip_hdr)));IPH_TTL_SET(iphdr, ttl);IPH_PROTO_SET(iphdr, proto);#if CHECKSUM_GEN_IP_INLINEchk_sum += PP_NTOHS(proto |(ttl <<8));#endif /* CHECKSUM_GEN_IP_INLINE *//* 構建目的IP地址,此處的目的IP地址不能為NULL */ip4_addr_copy(iphdr->dest,*dest);#if CHECKSUM_GEN_IP_INLINEchk_sum += ip4_addr_get_u32(&iphdr->dest)&0xFFFF;chk_sum += ip4_addr_get_u32(&iphdr->dest)>>16;#endif /* CHECKSUM_GEN_IP_INLINE */IPH_VHL_SET(iphdr,4, ip_hlen /4);IPH_TOS_SET(iphdr, tos);#if CHECKSUM_GEN_IP_INLINEchk_sum += PP_NTOHS(tos |(iphdr->_v_hl <<8));#endif /* CHECKSUM_GEN_IP_INLINE */IPH_LEN_SET(iphdr, lwip_htons(p->tot_len));#if CHECKSUM_GEN_IP_INLINEchk_sum += iphdr->_len;#endif /* CHECKSUM_GEN_IP_INLINE */IPH_OFFSET_SET(iphdr,0);IPH_ID_SET(iphdr, lwip_htons(ip_id));#if CHECKSUM_GEN_IP_INLINEchk_sum += iphdr->_id;#endif /* CHECKSUM_GEN_IP_INLINE */++ip_id;if(src == NULL){ip4_addr_copy(iphdr->src,*IP4_ADDR_ANY4);/** 構建源IP地址 */}else{/* 此處的源IP地址不能為NULL */ip4_addr_copy(iphdr->src,*src);}#if CHECKSUM_GEN_IP_INLINEchk_sum += ip4_addr_get_u32(&iphdr->src)&0xFFFF;chk_sum += ip4_addr_get_u32(&iphdr->src)>>16;chk_sum =(chk_sum >>16)+(chk_sum &0xFFFF);chk_sum =(chk_sum >>16)+ chk_sum;chk_sum =~chk_sum;IF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP){iphdr->_chksum =(u16_t)chk_sum;/* network order */}#if LWIP_CHECKSUM_CTRL_PER_NETIFelse{IPH_CHKSUM_SET(iphdr,0);}#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/#else/* CHECKSUM_GEN_IP_INLINE */IPH_CHKSUM_SET(iphdr,0);#if CHECKSUM_GEN_IPIF__NETIF_CHECKSUM_ENABLED(netif, NETIF_CHECKSUM_GEN_IP){IPH_CHKSUM_SET(iphdr, inet_chksum(iphdr, ip_hlen));}#endif /* CHECKSUM_GEN_IP */#endif /* CHECKSUM_GEN_IP_INLINE */}else{/* IP頭已包含在pbuf中 */if(p->len < IP_HLEN){/** pbuf的長度小于IP數據報首部長度(20字節) */LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_LEVEL_SERIOUS,("ip4_output: LWIP_IP_HDRINCL but pbuf is too short\\n"));IP_STATS_INC(ip.err);MIB2_STATS_INC(mib2.ipoutdiscards);return ERR_BUF;}iphdr =(struct ip_hdr *)p->payload;/** 直接從數據區域獲取IP數據報首部 */ip4_addr_copy(dest_addr, iphdr->dest);/** 獲取目的IP地址 */dest =&dest_addr;}IP_STATS_INC(ip.xmit);LWIP_DEBUGF(IP_DEBUG,("ip4_output_if: %c%c%"U16_F"\\n", netif->name[0], netif->name[1],(u16_t)netif->num));ip4_debug_print(p);#if ENABLE_LOOPBACK /** 換回接口 */if(ip4_addr_cmp(dest, netif_ip4_addr(netif))#if!LWIP_HAVE_LOOPIF|| ip4_addr_isloopback(dest)#endif /* !LWIP_HAVE_LOOPIF */){/* 數據包是給自己的,將其放入環回接口 */LWIP_DEBUGF(IP_DEBUG,("netif_loop_output()"));return netif_loop_output(netif, p);}#if LWIP_MULTICAST_TX_OPTIONSif((p->flags & PBUF_FLAG_MCASTLOOP)!=0){netif_loop_output(netif, p);}#endif /* LWIP_MULTICAST_TX_OPTIONS */#endif /* ENABLE_LOOPBACK */#if IP_FRAG/** 要發送的數據報大于mtu,需要分片,此處的前提是使能了IP_FRAG (IP分片) */if(netif->mtu &&(p->tot_len > netif->mtu)){return ip4_frag(p, netif, dest);/** 調用IP數據報分片函數將數據報分片發送出去 */}#endif /* IP_FRAG */LWIP_DEBUGF(IP_DEBUG,("ip4_output_if: call netif->output()\\n"));return netif->output(netif, p, dest);/** 如果不需要分片就直接通過網卡發送出去,netif->output() */}
最后提個醒
此外:上層協議是不會直接調用 ip4_output()函數的,lwip是通過宏定義將 ip4_output()函數進行重新定義:
#define ip_output(p, src, dest, ttl, tos, proto) ip4_output(p, src, dest, ttl, tos, proto
-
IP協議
+關注
關注
3文章
85瀏覽量
22532 -
傳輸協議
+關注
關注
0文章
80瀏覽量
11950 -
網絡層
+關注
關注
0文章
40瀏覽量
11358
發布評論請先 登錄
深入了解IP數據報發送的過程
評論