NAT64網(wǎng)絡(luò)下合成IPv6地址

引言

最近公司的App不能提交到App Store了做裙。因為,我們的app無法支持NAT64網(wǎng)絡(luò)環(huán)境沉迹。蘋果還算是比較人性化,給出了解決方案 Supporting IPv6 DNS64/NAT64 Networks枣耀。這篇文章說得很詳細(xì),大致意思就是盡量使用域名而不是ip地址訪問服務(wù)器庭再,主要調(diào)用了 getaddrinfo 這個方法捞奕。他會根據(jù)你給的選項及當(dāng)前所處的網(wǎng)絡(luò)環(huán)境,返回一個ip地址列表拄轻。當(dāng)然如果是NAT64網(wǎng)絡(luò)颅围,也會包含ipv6地址(如果域名綁定了ipv6地址,則返回服務(wù)器的ipv6地址恨搓;如果服務(wù)器只有ipv4地址院促,則它會自動幫你合成一個ipv6地址)。這樣會省去你很多麻煩斧抱。

我們的app直接使用low-level BSD socket api通過tcp協(xié)議直接與后臺服務(wù)器通信常拓。服務(wù)器只有ipv4地址,并且沒有綁定域名辉浦。剛開始我們嘗試使用蘋果給出的示例代碼解決該問題:

#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <err.h>
 
    uint8_t ipv4[4] = {192, 0, 2, 1};
    struct addrinfo hints, *res, *res0;
    int error, s;
    const char *cause = NULL;
 
    char ipv4_str_buf[INET_ADDRSTRLEN] = { 0 };
    const char *ipv4_str = inet_ntop(AF_INET, &ipv4, ipv4_str_buf, sizeof(ipv4_str_buf));
 
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = PF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_DEFAULT;
    error = getaddrinfo(ipv4_str, "http", &hints, &res0);
    if (error) {
        errx(1, "%s", gai_strerror(error));
        /*NOTREACHED*/
    }
    s = -1;
    for (res = res0; res; res = res->ai_next) {
        s = socket(res->ai_family, res->ai_socktype,
                          res->ai_protocol);
        if (s < 0) {
            cause = "socket";
            continue;
        }
 
        if (connect(s, res->ai_addr, res->ai_addrlen) < 0) {
            cause = "connect";
            close(s);
            s = -1;
            continue;
        }
 
        break;  /* okay we got one */
    }
    if (s < 0) {
        err(1, "%s", cause);
        /*NOTREACHED*/
    }
    freeaddrinfo(res0);

看起來很簡單: 將我們想要合成的ipv4地址弄抬、端口及選項傳給getaddrinfo就可以得到一個地址鏈表;然后宪郊,我們測試地址的連通性掂恕,在其中找到一個可用的拖陆。

但是,經(jīng)過測試發(fā)現(xiàn)懊亡,getaddrinfo針對域名可以工作依啰,但是對于ipv4地址卻不能合成ipv6地址。這非常奇怪店枣,我查閱了兩份關(guān)于IPv6的RFC文檔: RFC4038: Application Aspects of IPv6 Transitionrfc7050: Discovery of the IPv6 Prefix Used for IPv6 Address Synthesis速警;也在蘋果官方論壇上咨詢了,他們給的答復(fù)是可以正常工作鸯两,并且建議使用域名闷旧。。甩卓。但是我這邊確實是不可以的啊鸠匀,相同的代碼蕉斜,而且后臺服務(wù)器也沒域名逾柿。在申請到新域名之前,作為過渡方案宅此,沒辦法机错,只能另辟蹊徑了。

前面提到 getaddrinfo 對于域名可以正確地工作父腕。我們?yōu)槭裁床焕眠@一點(diǎn)呢弱匪?

原理

首先先針對一個比較知名的域名調(diào)用getaddrinfo得到ip地址列表,然后針對地址列表進(jìn)行進(jìn)一步處理璧亮,用我們的ip地址替換ip地址列表中的地址萧诫。在rfc7050: Discovery of the IPv6 Prefix Used for IPv6 Address Synthesis這篇文章中,提到了一個非常特殊的域名 ipv4only.arpa., 該域名只綁定了兩個ipv4地址枝嘶。這非常好帘饶,因為我們無需擔(dān)心挑選的域名綁定了ipv6地址,給我們處理地址列時帶來不便,增加了問題的復(fù)雜度群扶。所以及刻,我們的思路是:

  1. 首先調(diào)用getaddrinfo對 ipv4only.arpa. 進(jìn)行解析,得到地址列表竞阐;
  2. 對地址列表進(jìn)一步進(jìn)行處理:
    • ipv4地址缴饭,直接進(jìn)行處理;
    • ipv6地址骆莹,將高4個字節(jié)替換成我們的ip地址颗搂。
  3. 刪除其中的重復(fù)項。

實現(xiàn)

函數(shù)參數(shù)與getaddrinfo一致幕垦。但是峭火,hostname是ipv4地址字符串毁习,servname是端口數(shù)字字符串。函數(shù)聲明如下:

int
getaddrinfo4ipv4literal(const char *hostname, const char *servname,
            const struct addrinfo *hints, struct addrinfo **res);
  1. 獲取地址列表
int
getaddrinfo4ipv4literal(const char *hostname, const char *servname,
            const struct addrinfo *hints, struct addrinfo **res)
{
    int rlt = 0;
    // 1. get address list
    rlt = getaddrinfo("ipv4only.arpa", "http", hints, res);


需要注意的是卖丸,這里servname傳遞的是"http"纺且。事實證明,傳遞一個任意的端口數(shù)字字符串也是可以的稍浆,并不影響地址的解析载碌。

  1. 地址替換

    地址替換需要注意的是,不同的地址會有不同的替換方法:ipv4直接替換衅枫;ipv6只替換地址的高4個字節(jié)嫁艇。代碼如下:
in_addr_t ipv4 = inet_addr(hostname);
in_port_t port = htons(atoi(servname));
// 2. look through the address list and replace the ipv4 address and port by ours
struct addrinfo *tempRes;
for (tempRes = *res; tempRes; tempRes = tempRes->ai_next) {
    if (tempRes->ai_family == AF_INET)// IPv4
    {
        struct sockaddr_in *dest = (struct sockaddr_in *)tempRes->ai_addr;
        // overwrite
        dest->sin_addr.s_addr = ipv4;
        dest->sin_port = port;
    }
    else if(tempRes->ai_family == AF_INET6)// IPv6
    {
        struct sockaddr_in6 *dest = (struct sockaddr_in6 *)tempRes->ai_addr;
        // overwrite the last four bytes
        memcpy(&dest->sin6_addr.__u6_addr.__u6_addr8 + 12,&ipv4, sizeof(in_addr_t));
        dest->sin6_port = port;
    }
  1. 消除重復(fù)項

    因為 ipv4only.arpa. 綁定了兩個ipv4地址,所以經(jīng)過第二步的替換后弦撩,出現(xiàn)了重復(fù)項步咪。所以,我們需要稍微處理下益楼。本質(zhì)上就是對一個鏈表做刪除重復(fù)項操作猾漫。
/**
 * remove duplicated items
 */
void removeduplicateditems(struct addrinfo **res)
{
    struct addrinfo *current, *checking, *previousChecking;
    
    current = *res;
    
    while (current != NULL && current->ai_next != NULL) {
        checking = current->ai_next;
        previousChecking = current;
        while (checking != NULL) {
            if (ipequal(checking,current)) {
                previousChecking->ai_next = checking->ai_next;
                checking->ai_next = NULL;
                freeaddrinfo(checking);
                checking = previousChecking->ai_next;
            }
            else
            {
                checking = checking->ai_next;
            }
        }
        
        current = current->ai_next;
    }
}

原理其實很簡單。全部代碼見 GitHub感凤。

討論

雖然悯周,我們解決了該問題,但是這只是一個過渡方案陪竿。還是應(yīng)該像蘋果建議的那樣禽翼,申請一個域名,這會降低問題的復(fù)雜度族跛。另外闰挡,服務(wù)器最好分配一個IPv6地址,畢竟IPv6是大勢所趨礁哄。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末长酗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姐仅,更是在濱河造成了極大的恐慌花枫,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件掏膏,死亡現(xiàn)場離奇詭異劳翰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)馒疹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進(jìn)店門佳簸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事生均√耄” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵马胧,是天一觀的道長汉买。 經(jīng)常有香客問我,道長佩脊,這世上最難降的妖魔是什么蛙粘? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮威彰,結(jié)果婚禮上出牧,老公的妹妹穿的比我還像新娘。我一直安慰自己歇盼,他們只是感情好舔痕,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著豹缀,像睡著了一般伯复。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上耿眉,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天边翼,我揣著相機(jī)與錄音鱼响,去河邊找鬼鸣剪。 笑死,一個胖子當(dāng)著我的面吹牛丈积,可吹牛的內(nèi)容都是我干的筐骇。 我是一名探鬼主播,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼江滨,長吁一口氣:“原來是場噩夢啊……” “哼铛纬!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起唬滑,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤告唆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后晶密,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體擒悬,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年稻艰,在試婚紗的時候發(fā)現(xiàn)自己被綠了懂牧。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡尊勿,死狀恐怖僧凤,靈堂內(nèi)的尸體忽然破棺而出畜侦,到底是詐尸還是另有隱情,我是刑警寧澤躯保,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布旋膳,位于F島的核電站,受9級特大地震影響途事,放射性物質(zhì)發(fā)生泄漏溺忧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一盯孙、第九天 我趴在偏房一處隱蔽的房頂上張望鲁森。 院中可真熱鬧,春花似錦振惰、人聲如沸歌溉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽痛垛。三九已至,卻和暖如春桶蛔,著一層夾襖步出監(jiān)牢的瞬間匙头,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工仔雷, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蹂析,地道東北人。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓碟婆,卻偏偏與公主長得像电抚,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子竖共,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容