TCP/IP詳解之NAT

南京孫大興 2022-01-08 04:07:45 阅读数:483

tcp ip nat

一、原理
NAT的工作原理就是重寫重寫通過路由器的數據包的識別信息。這種情况常發生在數據傳輸的兩個方向上。在這種最基本的形式中,NAT需要重寫往一個方向傳輸的數據包的源IP地址,重寫往另一個方向傳輸的數據包的目的IP地址。這允許傳出的數據包的源IP地址變為NAT路由器面向Internet的網絡接口地址,而不是原始主機的接口地址。因此,在互聯網上的主機看來,數據包是來自於具備全局路由IP的NAT路由器,而不是比特於NAT內部的私有地址的主機。
在這裏插入圖片描述

二、移植
我的實際項目中,是ppp和eth之間數據包的轉發,具體參考了rtthread組件中的NAT源碼,但是並沒有具體的詳細使用說明,下面記錄我的詳細移植過程,其實還可以參考一下esp官方的SDK,裏面實現了NAPT,完整的嵌入到LWIP協議棧中。
1、rtconfig.h中添加宏定義

#define LWIP_USING_NAT

2、nat初始化
ip_nat_init()放在lwip_init()最後執行;

ip_nat_init();//void lwip_init(void)

3、添加參數配置
ip_nat_start()我是放在ppp_netdev_refresh()最後,ppp撥號成功後配置;

void ip_nat_start(void)
{

ip_nat_entry_t nat_entry;
err_t ret = 0;
struct netif *out_if_ppp;
struct netif *in_if_eth0;
out_if_ppp = netif_find("pp1");//net dev是pp,1錶示add net dev時的序號,如果不加1,find會失敗
in_if_eth0 = netif_find("e00");//net dev是e0,第二個0錶示add net dev時的序號,如果不加0,find會失敗
nat_entry.out_if = out_if_ppp;
nat_entry.in_if = in_if_eth0;
IP4_ADDR(&nat_entry.source_net, 198, 120, 0, 210);//local ip
IP4_ADDR(&nat_entry.source_netmask, 255, 255, 255, 0);
IP4_ADDR(&nat_entry.dest_net, 123, 123, 123, 0);//dest ip
IP4_ADDR(&nat_entry.dest_netmask, 255, 255, 255, 0);
ret = ip_nat_add(&nat_entry);
if (ret != ERR_OK)
{

rt_kprintf("%s ip_nat_add failed ret:%d\r\n", __FUNCTION__, ret);
}
}

4、ip_nat_input()
ip_nat_input()放在ip4_input()函數中,網絡層中處理報文;

 /* send to upper layers */
LWIP_DEBUGF(IP_DEBUG, ("ip4_input: \n"));
ip4_debug_print(p);
LWIP_DEBUGF(IP_DEBUG, ("ip4_input: p->len %"U16_F" p->tot_len %"U16_F"\n", p->len, p->tot_len));
ip_data.current_netif = netif;
ip_data.current_input_netif = inp;
ip_data.current_ip4_header = iphdr;
ip_data.current_ip_header_tot_len = IPH_HL_BYTES(iphdr);
#if IP_NAT
if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp))
ip_nat_input(p);
#endif /* IP_NAT */
#if LWIP_RAW
/* raw input did not eat the packet? */
raw_status = raw_input(p, inp);

5、ip_nat_out()
ip_nat_out()放在ip4_input()函數中,網絡層中處理報文;

/* packet not for us? */
if (netif == NULL)
{

/* packet not for us, route or discard */
LWIP_DEBUGF(IP_DEBUG | LWIP_DBG_TRACE, ("ip4_input: packet not for us.\n"));
#if IP_NAT
if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp))
ip_nat_out(p);
#endif /* IP_NAT */
#if IP_FORWARD
/* non-broadcast packet? */
if (!ip4_addr_isbroadcast(ip4_current_dest_addr(), inp))
{

/* try to forward IP packet on (other) interfaces */
ip4_forward(p, (struct ip_hdr *)p->payload, inp);
}
else
#endif /* IP_FORWARD */
{

IP_STATS_INC(ip.drop);
MIB2_STATS_INC(mib2.ipinaddrerrors);
MIB2_STATS_INC(mib2.ipindiscards);
}
pbuf_free(p);
return ERR_OK;
}

6、pbuf_free: p->ref > 0
移植後運行,pbuf_free一直報LWIP_ASSERT(“pbuf_free: p->ref > 0”, p->ref > 0)的錯誤,就是在pbuf_free的時候p非NULL,但是ref為0,下面是ref的注釋原文:

/** * the reference count always equals the number of pointers * that refer to this pbuf. This can be pointers from an application, * the stack itself, or pbuf->next pointers from a chain. */
LWIP_PBUF_REF_T ref;

通過追踪,我發現在pbuf_chain(q, p)後再pbuf_free(q),就會出現此問題,我是通過pbuf_dechain(q)解開q和p,再pbuf_free(q),而且我把pbuf_free( p )都注釋掉了,因為ip4_input()函數中會執行pbuf_free( p ),不想改變LWIP中的ip4_input(),只負責NAT部分。我不能保證一定就是正確的,如果有誤,還請指出。目前的測試結果看,沒有內存泄漏的問題。

if (consumed)
{

/* packet consumed, send it out on in_if */
struct netif *in_if;
/* check if the pbuf has room for link headers */
if (pbuf_header(p, PBUF_LINK_HLEN))
{

/* pbuf has no room for link headers, allocate an extra pbuf */
q = pbuf_alloc(PBUF_LINK, 0, PBUF_RAM);
if (q == NULL)
{

LWIP_DEBUGF(LWIP_NAT_DEBUG, ("ip_nat_input: no pbuf for outgoing header\n"));
/* @todo: stats? */
// pbuf_free(p);
// p = NULL;
return 1;
}
else
{

pbuf_chain(q, p);
is_chain = 1;
}
}
else
{

/* restore p->payload to IP header */
if (pbuf_header(p, -PBUF_LINK_HLEN))
{

LWIP_DEBUGF(LWIP_NAT_DEBUG, ("ip_nat_input: restoring header failed\n"));
/* @todo: stats? */
// pbuf_free(p);
// p = NULL;
return 1;
}
else q = p;
}
/* if we come here, q is the pbuf to send (either points to p or to a chain) */
in_if = nat_entry.cmn->cfg->entry.in_if;
iphdr->dest.addr = nat_entry.cmn->source.addr;
ip_nat_chksum_adjust((u8_t *) & IPH_CHKSUM(iphdr), (u8_t *) & (nat_entry.cmn->cfg->entry.out_if->ip_addr.addr), 4, (u8_t *) & (iphdr->dest.addr), 4);
ip_nat_dbg_dump("ip_nat_input: packet back to source after nat: ", iphdr);
LWIP_DEBUGF(LWIP_NAT_DEBUG, ("ip_nat_input: sending packet on interface ("));
ip_nat_dbg_dump_ip(&(in_if->ip_addr));
LWIP_DEBUGF(LWIP_NAT_DEBUG, (")\n"));
err = in_if->output(in_if, q, (ip_addr_t *) & (iphdr->dest));
if (err != ERR_OK)
{

LWIP_DEBUGF(LWIP_NAT_DEBUG, ("ip_nat_input: failed to send rewritten packet. link layer returned %d\n", err));
}
/* now that q (and/or p) is sent (or not), give up the reference to it this frees the input pbuf (p) as we have consumed it. */
if (1 == is_chain)
{

pbuf_dechain(q);
pbuf_free(q);
}
}

三、延伸
1、網上看到這樣一個問題:NAT和路由,誰先誰後?
如果流量從inside端口進來,那麼先執行路由,後執行NAT(本地到全局);
如果流量從outside端口進來,那麼先執行NAT(全局到本地),後執行路由。

版权声明:本文为[南京孫大興]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201080407447682.html