IPv6的NAT原理

上海地面通發表於2019-10-11

在億萬網際網路使用者享受著Internet 帶來便利的同時, IPv4 地址即將耗盡的問題卻早在 20 年前就被網路專家們意識到了,並採取了措施延緩 IPv4 的消耗,這項措施就是 NAT (網路地址轉換)技術。 NAT 主要作用在於 節約IP 地址,而非所謂的增加 IP 的方向性以及隱藏私有 IP IPv4 NAT 打破了網際網路本身的“互聯”特性,使得一部分 IP 地址不再雙向可達, NAT 為無方向的 IP 協議增加了一個方向,特別是 stateful NAT 型別。 此時,IPv6 時代已逐漸到來。

IPv6 的標準中不建議使用 NAT 這是何緣由呢?我們已知 IPv6 地址數量巨大 ,只要 在地球上 ,都可 螞蟻 配置 IP 裝置 因此 IPv4 的一些修補手段將不再需要,為了保持協議本身以及相關標準的純潔性, IPv6 幾乎不再 建議使用 NAT 。雖然不再 建議使用 但在某些必要情況下 ,還是可能 需要 實現IPv6 NAT RFC6296 的標題是 IPv6-to-IPv6 Network Prefix Translation ,描述了IPv6 下的 NAT 的實現要點,給出了一個合理的建議,既保持了 IP 的無方向性,又可以滿足 NAT 的語義,這就是 IPv6 NAT stateless 的緣由。

IPv6 地址有將近 128 個可隨意調配的位,其龐大的地址空間,一般的單位都會被分配到一個擁有很大量地址的網段,此網段擁有足夠多的地址來和內網主機進行對映,即可用於對映的 IP 地址池容量巨大,且既然不想再使用非 IP 層的資訊來保持資訊,又要保持對映,那就要用純 IP 層的資訊,這樣對上層影響最小。對於 IPv6 而言, NAT 利用 checksum 演算法來保持流標識資訊,絲毫不管這個 checksum 是誰的 checksum ,因為它根本就不改變資料包的 checksum...

接下來給大家講解一下 checksum 無關性和自動轉換 就是 考慮a+b+c+d=X 其中X 就是 checksum ,我們把 a b 當成源 IP 地址的兩部分, c d 當成目的 IP 地址的兩部分,我們作源地址轉換,將 a b 都改變,比如 a 改變成了 A ,那麼 b 改成多少才能保持 checksum 的值 X 不變,求解 即可 IPv6 的建議 NAT 實現 上文 這個原理, 只是將其換為 計算機布林數域 求解 。既然可以不觸動第四層的checksum 值,那麼 NAT 對第四層協議的影響也就減小了,雖然它還是解決不了諸如 ESP/AH 等穿越 NAT 的問題。基於以上演算法, IPv6 在做 NAT 的時候,在給定的子網網段內,可以自動生成一個新的 IP 地址供對映之用,從演算法本身來看,衝突的可能性非常之小致於 0

既然IPv6 NAT 機制 自動 為一個連線選擇了一個 IP 地址,那麼當返回包到來的時候,如何把地址轉換 為原地址 呢?IPv6 NAT 把地址 轉換回去 這件事完全靠演算法本身, 演算法本身 能將轉換後的地址再轉回原來的, 具有 解的唯一性,在IPv6 NAT 實現中,演算法只針對 IP 地址中 16 位的地址資訊進行自動生成,而其它的則需要手工顯式配置,由於內網 IPv6 地址可以使用 MAC 地址對映成唯一的地址, 轉換後的地址是唯一的,將這一切反過來,最後還是能對映回原始的IP 地址的。

如果拋開地址轉換這一說,僅僅考慮演算法本身,那還是可以給出一個實際可以執行的程式碼的,該程式碼使用了計算checksum 的演算法:

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

 

// 以下 2 個函式就是計算校驗碼的,具體的原理請參 RFC1071/RFC1624/RFC1141

static inline u_int16_t add16(

    u_int16_t a,

    u_int16_t b)

{

    a += b;

    return a + (a < b);

}

 

static inline u_int16_t csum16(const u_int16_t *buf, int len)

{

    u_int16_t csum = 0;

    while(len--) csum = add16(csum, *buf++);

    return csum;

}

 

 

int main(int argc, char **argv)

{

 

    u_int16_t buf[18] = {0};

    int i = 0;

    memcpy(buf, "efghhijk", 8);

    memcpy(buf+4, "12345678", 8);

    memcpy(buf+8, "xxyywert", 8);

    memcpy(buf+12, "zxcvkljh", 8);

    // 正確做法是列印 16 進位制資料,此處為了簡單,列印了字串

    printf(" 原始資料: %s   長度: %d\n", (char*)buf, strlen((char*)buf));    

    printf(" 原始資料的校驗碼: %X\n", csum16(buf, 16));    

    

    u_int16_t tip[3] = {0};

    memcpy(tip, "#$!%", 4);

    u_int16_t tip_sum = csum16(tip, 2);

    printf("\nNAT 規則: efghhijk1234/12 - efghhijk#$!%/12\n\n");

    printf(" 固定從第 9 個位元組開始修改 4 個位元組為: %s 其校驗碼為: %X\n", (char*)tip, tip_sum);

    

    // 定位固定修改後的動態修改的初始地址,注意,我們僅僅修改 16 位資訊

    u_int16_t* pcsum = buf + 4+2;  

    

    // 計算動態修改的值

    *pcsum = ~add16(

        add16(

            ~(*pcsum),

            ~csum16(buf+4, 2)

        ),

        tip_sum

    );

    printf(" 動態修改的值為: %X\n", *pcsum);

    memcpy(buf+4, tip, 4); // 完成修改

    

    printf(" 當前資料: %s   長度: %d\n", buf, strlen((char*)buf));    

    printf(" 當前校驗碼: %X\n", csum16(buf, 16));    

    printf("------------- 以下是還原操作 -------------\n");

    printf("\n 反向 NAT 規則: efghhijk#$!%/12 - efghhijk1234/12\n\n");

 

    u_int16_t tip2[3] = {0};

        memcpy(tip2, "1234", 4);

    printf(" 我們只需要記住原始資料被固定修改前的: %s\n", tip2);

 

    u_int16_t tip_sum2 = csum16(tip2, 2);

    

    u_int16_t* pcsum2 = buf+6;

 

    *pcsum2 = ~add16(

        add16(

            ~(*pcsum2),

            ~csum16(buf+4, 2)

        ),

        tip_sum2

    );

    // 還原

    memcpy(buf+4, tip2, 4);

    

    printf(" 原始資料: %s\n", (char *)buf);

    printf(" 原始校驗碼: %X\n", csum16(buf, 16));    

    

}

 

執行結果如下:

把以上的原理套用在IPv6 NAT 上,就是一種實現。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69943410/viewspace-2659495/,如需轉載,請註明出處,否則將追究法律責任。

相關文章