3 IP Addresses, structs, and Data Munging

3 IP Addresses, structs, and Data Munging #

ここからは気分転換にコードの話をするところです。

その前に、もっとノンコードの話をしましょう! イエーイ! まず最初に IP アドレスとポートについて少しお話したいと思いますので、それを整理します。それからソケット API がどのように IP アドレスや他のデータを保存し、操作するかについて話します。

3.1 IP Addresses, versions 4 and 6 #

ベン・ケノービがまだオビワン・ケノービと呼ばれていた頃、インターネット・プロトコル・バージョン4(IPv4)と呼ばれる素晴らしいネットワーク・ルーティング・システムが存在しました。IPv4 は4バイト(4オクテット)で構成されるアドレスで、一般に「ドットと数字」で表記されるのが一般的でした。192.0.2.111 のように。

皆さんも一度は目にしたことがあるのではないでしょうか。

実際、この記事を書いている時点では、インターネット上のほぼすべてのサイトが IPv4 を使っています。

オビ・ワンをはじめ、誰もが幸せでした。しかし、ヴィント・サーフという名の否定的な人物が、IPv4 アドレスが足りなくなると警告を発したのです!

( ヴィント・サーフ氏は、IPv4 による「破滅と暗黒の黙示録」の到来を警告するとともに、「インターネットの父」としても有名です。だから、私は彼の判断に二の足を踏む立場にはないのだ)。

アドレスが足りなくなる?そんなことがあるのでしょうか?つまり、32ビットの IPv4 アドレスには何十億もの IP アドレスが存在するのです。本当に何十億台ものコンピュータがあるのでしょうか?

Yes.

また、コンピュータが数台しかなく、10億という数字があり得ないほど大きいと誰もが思っていた当初、いくつかの大きな組織は、自分たちが使うために何百万というIPアドレスを惜しげもなく割り当てていたのです。(ゼロックス、MIT、フォード、HP、IBM、GE、AT&T、そしてアップルという小さな会社などです)。

実際、いくつかの応急処置がなかったら、とっくに使い果たしていたでしょう。

しかし今は、すべての人間がIPアドレスを持ち、すべてのコンピュータ、電卓、電話、パーキングメーター、そして(なぜか)子犬も、という時代です。

そして、IPv6 が誕生したのです。ヴィント・サーフはおそらく不死身なので(たとえ肉体がこの世を去ったとしても、おそらく彼はすでにインターネット2の奥深くにある超知的な ELIZA プログラムとして存在しているはずです)、もし次のバージョンのインターネットプロトコルで十分なアドレスが確保できなければ、誰も彼の「だから言っただろう」という言葉を再び聞きたくはないでしょう。

これは何を示唆しているのでしょうか?

もっとたくさんのアドレスが必要だということです。2倍どころか10億倍でもなく1000兆倍でもなく 7900万ビリオン・トリリオンの数のアドレスが必要なのです!そうこなくちゃ!

ビージェイ、それは本当なの?大きな数字を信じない理由があるんだ。“32ビットと128ビットの差は大したことないように聞こえるかもしれない、96ビット多いだけだろ?しかし、私たちはここで累乗の話をしていることを忘れてはなりません。32ビットは約40億の数字(2^32)を表し、128ビットは約340兆の数字(2^128)を表します。これは、宇宙の星1つに対して、100万個の IPv4 インターネットがあるようなものです。

IPv4のドットや数字も忘れて、16進数では、2バイトの塊をコロンで区切って、このように表現しています。

2001:0db8:c9d2:aee5:73e3:934a:a5ae:9551

それだけではありません! 多くの場合、IPアドレスにはたくさんのゼロが含まれていますが、それらを2つのコロンで区切って圧縮することができます。そして、各バイトペアの先頭のゼロを省くことができます。例えば、次のようなアドレスのペアは、それぞれ等価です。

2001:0db8:c9d2:0012:0000:0000:0000:0051
2001:db8:c9d2:12::51

2001:0db8:ab00:0000:0000:0000:0000:0000
2001:db8:ab00::

0000:0000:0000:0000:0000:0000:0000:0001
::1

アドレス ::1 はループバックアドレスです。常に「今走っているこのマシン」という意味です。IPv4 では、ループバックアドレスは 127.0.0.1 です。

最後に、IPv6 アドレスの IPv4 互換モードですが、これは皆さんが遭遇する可能性のあるものです。例えば、192.0.2.33という IPv4 アドレスを IPv6 アドレスとして表現したい場合、次のような表記をします。"::ffff:192.0.2.33"。

本気で楽しみたいんです。

実際、IPv6 の開発者たちは、何兆個ものアドレスを軽率にも予約用に切り捨てたほど、IPv6 は楽しいものなのですが、数が多すぎて、正直言って、もう誰が数えているのでしょうか?銀河系のすべての惑星のすべての男性、女性、子供、子犬、そしてパーキングメーターのために十分な数が残されています。 信じてくれ、銀河系のどの星にもパーキングメーターはあるんだ。本当なんだ。

3.1.1 Subnets #

組織上、「このIPアドレスの先頭からこのビットまでがネットワーク部分、それ以外がホスト部分」と宣言するのが便利な場合があります。

例えば、IPv4 では 192.0.2.12 というように、最初の3バイトがネットワークで、最後の1バイトがホストと言えるでしょう。あるいは、別の言い方をすれば、ネットワーク 192.0.2.0 上のホスト 12 について話していることになります (ホストであるバイトをゼロにしているところをご覧ください)。

そして、さらに時代遅れの情報を! 準備はいいですか?古代では、サブネットには「クラス」があり、アドレスの最初の1バイト、2バイト、3バイトがネットワーク部分でした。運良く1バイトがネットワーク、3バイトがホストの場合、ネットワーク上に24ビット分のホスト(1600万程度)を持つことができます。これが「クラスA」のネットワークである。一方、「クラスC」は、ネットワークが3バイト、ホストが1バイトで、256台のホスト(ただし、予約された数台は除く)を持ちます。

ご覧のように、Aクラスがほんの少し、Cクラスが大量に、そして真ん中にBクラスが何個かある状態でした。

IPアドレスのネットワーク部分は、ネットマスクと呼ばれるもので記述されており、IPアドレスとビット単位でANDすることでネットワーク番号を取得します。ネットマスクは通常 255.255.255.0 のような形をしています。(例えば、このネットマスクでは、あなたのIPが 192.0.2.12 なら、あなたのネットワークは 192.0.2.12 AND 255.255.255.0 で、 192.0.2.0 となります)。

しかし、残念ながら、これはインターネットの最終的なニーズに対して十分なきめ細かさではないことが判明しました。クラスCのネットワークはすぐに足りなくなったし、クラスAのネットワークも足りなくなったので、わざわざ尋ねる必要はありません。この問題を解決するために、権力者たちはネットマスクを 8、16、24 のどれでもなく、任意のビット数にすることを許可しました。例えば 255.255.255.252 というネットマスクは、30 ビットのネットワークと 2 ビットのホストで、 ネットワーク上に 4 つのホストを置くことができます。 (ネットマスクは常に1ビットの束と0ビットの束からなることに注意してください)。

しかし、255.192.0.0 のような大きな数字の羅列をネットマスクとして使うのは、少し扱いにくいです。まず、それが何ビットなのかが直感的にわからないし、コンパクトでもありません。そこで新スタイルが登場したのですが、これはもっとすっきりしています。IP アドレスの後にスラッシュを付けて、その後に10進数でネットワークのビット数を指定するだけです。こんな感じです。192.0.2.12/30

あるいは、IPv6 の場合、このようなものです。2001:db8::/32 または 2001:db8:5413:4028::9db9/64 です。

3.1.2 Port Numbers #

以前、インターネット層(IP)とホスト間トランスポート層(TCPとUDP)を分離した レイヤードネットワークモデルをご紹介しましたが、覚えていらっしゃいますか?次の段落の前に、そのことをしっかり覚えておいてください。

IP アドレス(IP 層で使われる)の他に、TCP(ストリームソケット)や、偶然にも UDP(データグラムソケット)で使われるアドレスがあることが判明したのです。それは、ポート番号です。これは16ビットの数字で、接続のためのローカルアドレスのようなものです。

IP アドレスはホテルの番地、ポート番号は部屋番号だと思ってください。後で自動車業界の例も考えてみましょう。

例えば、受信メールとウェブサービスの両方を扱うコンピュータを用意したい場合、1つのIPアドレスを持つコンピュータでその2つを区別する方法はあるでしょうか?

さて、インターネット上のサービスには、それぞれ異なるウェルノウン・ポート番号が設定されています。 IANA のポート一覧か、Unix であれば /etc/services ファイルで確認することができます。HTTP(ウェブ)はポート80、telnet はポート23、SMTP はポート25、ゲーム DOOM はポート666、などなど。1024以下のポートはしばしば特殊とみなされ、通常、使用するには OS の特別な権限が必要です。

といったところでしょうか。

3.2 Byte Order #

レルムの命令で!バイトの並び順は2種類とします。今後、Lame and Magnificent と呼ばれるようになります。

というのは冗談ですが、本当にどちらか一方が優れているのです。:-)

あなたのコンピュータは、あなたの背後でバイトを逆順に保存しているかもしれないのです。そうなんです。誰もあなたに言いたくはなかったのです。

つまり、2バイトの16進数、たとえば b34f を表現する場合、b34f の2バイトに続けて格納する、というのがインターネットの世界の共通認識になっているのです。これは理にかなっているし、 ウィルフォード・ブリムリーも言うように、正しい行為です。このように、大きい方の端が先になるように格納された数字をビッグエンディアン(Big-Endian)と呼びます。

残念ながら、世界中に散在する一部のコンピュータ、すなわちインテルまたはインテル互換のプロセッサを搭載したものは、バイトを逆に格納しているため、b34f4fb3 の連続したバイトとしてメモリに格納されることになります。この記憶方式をリトルエンディアンと呼びます。

でも、ちょっと待ってください!用語の説明はまだ終わっていないのです。もっとまともなビッグエンディアンはネットワークバイトオーダーとも呼ばれ、私たちネットワーク系が好む順序だからです。

コンピュータはホストバイトオーダーで数字を記憶しています。インテル 80x86 であれば、ホストバイト順はリトルエンディアンです。モトローラ 68K の場合は、ビッグエンディアンです。PowerPC なら、ホストバイトの並びは……まあ、人それぞれですね。

パケットを作成するときやデータ構造を埋めるときに、2バイトや4バイトの数値がネットワークバイトオーダーになっていることを確認する必要があることがよくあります。しかし、ネイティブなホストバイトオーダーがわからない場合、どのようにすればよいのでしょうか。

朗報です。ホストのバイトオーダーが正しくないと仮定して、値をネットワークバ イトオーダーに設定するための関数を常に実行するようにすればよいのです。この関数は、必要であれば魔法のような変換を行い、エンディアンが異なるマシンにもコードを移植することができます。

よしよし。変換できる数値は、short(2バイト)と long(4バイト)の2種類です。これらの関数は、符号なしのバリエーションでも動作します。例えば、short をホストバイトオーダーからネットワークバイトオーダーに変換したいとします。まず “h” でホスト、その後に “to” をつけます。そして、“n” は “network”、“s” は “short” を表します。h-to-n-s または htons() (読み方: “ホストからネットワークへのショート”) です。

簡単すぎるくらいに…。

“n”、“h”、“s”、“l” の組み合わせは、本当にくだらないものを除いて、すべて使うことができるのです。たとえば、stolh() (“Short to Long Host”) という関数はありません—とにかく、このパーティーでは。しかし、あるのです。

関数 説明
htons() host to network short
htonl() host to network long
ntohs() network to host short
ntohl() network to host long

基本的には、送出する前にネットワークバイトオーダーに変換し、送出後にホストバイトオーダーに変換します。

64bitのバリエーションは知らないです、すみません。また、浮動小数点をやりたい場合は、ずっと下の Serialization の章をチェックしてください。

この文書では、特に断らない限り、数値はホストバイトオーダーであると仮定しています。

3.3 structs #

さて、ついにここまで来ました。そろそろプログラミングの話をしましょう。この章では、ソケットインターフェイスで使用される様々なデータ型について説明します。

まず、簡単なものからです。ソケットディスクリプタです。ソケットディスクリプタは以下のような型です。

int

普通の int です。

ここからは変な話なので、我慢して読んでください。

My First Struct™—struct addrinfo。この構造体は最近開発されたもので、ソケットアドレス構造体を後で使用するために準備するために使用されます。また、ホスト名のルックアップやサービス名のルックアップにも使用されます。これは、後で実際の使い方を説明するときに、より意味をなすと思いますが、今は、接続を行うときに最初に呼び出されるものの1つであることを知っておいてください。

struct addrinfo {
    int              ai_flags;     // AI_PASSIVE, AI_CANONNAME, etc.
    int              ai_family;    // AF_INET, AF_INET6, AF_UNSPEC
    int              ai_socktype;  // SOCK_STREAM, SOCK_DGRAM
    int              ai_protocol;  // use 0 for "any"
    size_t           ai_addrlen;   // size of ai_addr in bytes
    struct sockaddr *ai_addr;      // struct sockaddr_in or _in6
    char            *ai_canonname; // full canonical hostname

    struct addrinfo *ai_next;      // linked list, next node
};

この構造体を少し読み込んでから、getaddrinfo() を呼び出します。この構造体のリンクリストへのポインタが返され、必要なものがすべて満たされます。

ai_family フィールドで IPv4 か IPv6 を使うように強制することもできますし、AF_UNSPEC のままにして何でも使えるようにすることも可能です。これは、あなたのコードが IP バージョンに依存しないので、クールです。

これはリンクされたリストであることに注意してください:ai_next は次の要素を指しています—そこから選択するためにいくつかの結果があるかもしれません。私は最初にうまくいった結果を使いますが、あなたは異なるビジネスニーズを持っているかもしれません。何でもかんでも知ってるわけじゃないんです!

struct addrinfoai_addr フィールドは struct sockaddr へのポインタであることがわかります。ここからが、IP アドレス構造体の中身についての細かい話になります。

通常、これらの構造体に書き込む必要はありません。多くの場合、addrinfo 構造体を埋めるために getaddrinfo() を呼び出すだけでよいでしょう。しかし、これらの構造体の内部を覗いて値を取得する必要があるため、ここでそれらを紹介します。

(また、構造体 addrinfo が発明される前に書かれたコードはすべて、これらのものをすべて手作業で梱包していたので、まさにそのような IPv4 コードを多く見かけることができます。このガイドの古いバージョンなどでもそうです)。

ある構造体は IPv4 で、ある構造体は IPv6 で、ある構造体はその両方です。どれが何なのか、メモしておきます。

とにかく、構造体 sockaddr は、多くの種類のソケットのためのソケットアドレス情報を保持します。

struct sockaddr {
    unsigned short    sa_family;    // address family, AF_xxx
    char              sa_data[14];  // 14 bytes of protocol address
};

sa_family には様々なものを指定できますが、この文書ではすべて AF_INET (IPv4) または AF_INET6 (IPv6) とします。sa_data にはソケットの宛先アドレスとポート番号を指定します。sa_data にアドレスを手で詰め込むのは面倒なので、これはかなり扱いにくいです。

構造体 sockaddr を扱うために、プログラマは IPv4 で使用する構造体 sockaddr_in(“in” は “Internet” の意)を並列に作成しました。

sockaddr_in 構造体へのポインタは sockaddr 構造体へのポインタにキャストすることができ、その逆も可能です。つまり、connect()struct sockaddr* を要求しても、struct sockaddr_in を使用して、最後の最後でキャストすることができるのです!

// (IPv4 only--see struct sockaddr_in6 for IPv6)

struct sockaddr_in {
    short int          sin_family;  // Address family, AF_INET
    unsigned short int sin_port;    // Port number
    struct in_addr     sin_addr;    // Internet address
    unsigned char      sin_zero[8]; // Same size as struct sockaddr
};

この構造体により、ソケットアドレスの要素を簡単に参照することができます。sin_zero (構造体を struct sockaddr の長さに合わせるために含まれます) は、関数 memset() ですべて 0 に設定する必要があることに注意すること。また、sin_familystruct sockaddrsa_family に相当し、"AF_INET” に設定されることに注意します。最後に、sin_port はネットワークバイトオーダーでなければなりません(htons() を使用することで!)。

もっと掘り下げましょう!sin_addr フィールドは in_addr 構造体であることがわかりますね。あれは何なんだ?まあ、大げさではなく、史上最も恐ろしい組合せの1つです。

// (IPv4 only--see struct in6_addr for IPv6)

// Internet address (a structure for historical reasons)
struct in_addr {
    uint32_t s_addr; // that's a 32-bit int (4 bytes)
};

うおぉ まあ、昔はユニオンだったんだけど、今はもうそういう時代じゃないみたいだね。おつかれさまでした。つまり、inastruct sockaddr_in 型と宣言した場合、ina.sin_addr.s_addr は4バイトの IP アドレス(ネットワークバイトオーダー)を参照することになります。あなたのシステムがまだ struct in_addr のための神々しいユニオンを使用している場合でも、あなたはまだ私が上記のように全く同じ方法で4バイトの IP アドレスを参照することができます(これは #defines によるものです)ことに注意してください。

IPv6 ではどうでしょうか。これについても同様の構造体が存在します。

// (IPv6 only--see struct sockaddr_in and struct in_addr for IPv4)

struct sockaddr_in6 {
    u_int16_t       sin6_family;   // address family, AF_INET6
    u_int16_t       sin6_port;     // port number, Network Byte Order
    u_int32_t       sin6_flowinfo; // IPv6 flow information
    struct in6_addr sin6_addr;     // IPv6 address
    u_int32_t       sin6_scope_id; // Scope ID
};

struct in6_addr {
    unsigned char   s6_addr[16];   // IPv6 address
};

IPv4 が IPv4 アドレスとポート番号を持つように、IPv6 も IPv6 アドレスとポート番号を持つことに注意してください。

また、IPv6 フロー情報やスコープ ID のフィールドについては、今のところ触れないことに注意してください。:-)

最後になりますが、こちらもシンプルな構造体である struct sockaddr_storage は、IPv4 と IPv6 の両方の構造体を保持できるように十分な大きさに設計されています。コールによっては、struct sockaddr に IPv4 と IPv6 のどちらのアドレスが記入されるのか事前にわからないことがありますよね。そこで、この並列構造体を渡しますが、サイズが大きい以外は struct sockaddr とよく似ており、必要な型にキャストします。

struct sockaddr_storage {
    sa_family_t  ss_family;     // address family

    // all this is padding, implementation specific, ignore it:
    char      __ss_pad1[_SS_PAD1SIZE];
    int64_t   __ss_align;
    char      __ss_pad2[_SS_PAD2SIZE];
};

重要なのは、ss_family フィールドでアドレスファミリーを確認できることで、これが AF_INETAF_INET6(IPv4 か IPv6 か)かを確認することです。それから、必要なら struct sockaddr_instruct sockaddr_in6 にキャストすることができます。

3.4 IP Addresses, Part Deux #

幸いなことに、IP アドレスを操作するための関数がたくさんあります。手書きで把握して << 演算子で long に詰め込む必要はありません。

まず、struct sockaddr_in ina があり、そこに格納したい IP アドレスが 10.12.110.57 または 2001:db8:63b3:1::3490 だとしましょう。inet_pton() という関数は、数字とドットで表記された IP アドレスを、AF_INETAF_INET6 の指定によって、in_addr 構造体か in6_addr 構造体に変換する関数です。("pton" は “presentation to network” の略で、覚えやすければ “printable to network” と呼んでも構いません)。変換は次のように行うことができます。

struct sockaddr_in sa; // IPv4
struct sockaddr_in6 sa6; // IPv6

inet_pton(AF_INET, "10.12.110.57", &(sa.sin_addr)); // IPv4
inet_pton(AF_INET6, "2001:db8:63b3:1::3490", &(sa6.sin6_addr)); // IPv6

(クイックメモ: 古い方法では、inet_addr() という関数や inet_aton() という別の関数を使っていましたが、これらはもう時代遅れで IPv6 では動きません。)

さて、上記のコードスニペットは、エラーチェックがないため、あまり堅牢ではありません。inet_pton() はエラー時に -1 を返し、アドレスがめちゃくちゃになった場合は 0 を返します。ですから、使用する前に結果が 0 よりも大きいことを確認してください!

さて、これで文字列の IP アドレスをバイナリ表現に変換することができるようになりました。では、その逆はどうでしょうか?in_addr 構造体を持っていて、それを数字とドットの表記で印刷したい場合はどうでしょうか。(この場合、関数 inet_ntop() ("ntop" は “network to presentation” という意味です。覚えやすければ “network to printable” と呼んでも構いません) を次のように使用します。

// IPv4:

char ip4[INET_ADDRSTRLEN];  // space to hold the IPv4 string
struct sockaddr_in sa;      // pretend this is loaded with something

inet_ntop(AF_INET, &(sa.sin_addr), ip4, INET_ADDRSTRLEN);

printf("The IPv4 address is: %s\n", ip4);


// IPv6:

char ip6[INET6_ADDRSTRLEN]; // space to hold the IPv6 string
struct sockaddr_in6 sa6;    // pretend this is loaded with something

inet_ntop(AF_INET6, &(sa6.sin6_addr), ip6, INET6_ADDRSTRLEN);

printf("The address is: %s\n", ip6);

呼び出す際には、アドレスの種類(IPv4 または IPv6)、アドレス、結果を格納する文字列へのポインタ、その文字列の最大長を渡すことになります。(2つのマクロは、最大の IPv4 または IPv6 アドレスを保持するために必要な文字列のサイズを都合よく保持します。INET_ADDRSTRLENINET6_ADDRSTRLEN です)。

(古いやり方についてもう一度簡単に触れておくと、この変換を行う歴史的な関数は inet_ntoa() と呼ばれるものでした。これも時代遅れで、IPv6 では動きません。)

最後に、これらの関数は数値の IP アドレスに対してのみ動作します。"www.example.com" のようなホスト名に対してネームサーバの DNS ルックアップは行いません。後ほど説明するように、そのためには getaddrinfo() を使用します。

3.4.1 Private (Or Disconnected) Networks #

多くの場所では、自分たちを守るために、ネットワークを他の地域から隠すファイアウォールがあります。そして多くの場合、ファイアウォールは、ネットワークアドレス変換(NAT)と呼ばれるプロセスを使って、“内部” IP アドレスを"外部"(世界中の誰もが知っている)IP アドレスに変換しています。

もう緊張してきましたか?“こんな変なことして どこへ行くんだろう?”

まあ、ノンアルコール飲料でも買ってリラックスしてください。初心者の場合、NAT は透過的に行われるので、心配する必要もありませんから。しかし、あなたが見ているネットワーク番号に混乱し始めた場合に備えて、ファイアウォールの背後にあるネットワークについて話したいと思います。

例えば、私の自宅にはファイアウォールがあります。DSL 会社から割り当てられた2つの固定 IPv4 アドレスを持っていますが、ネットワーク上に7台のコンピューターがあります。どうしてこんなことが可能なのでしょうか?2台のコンピュータが同じ IP アドレスを共有することはできませんし、そうでなければデータはどちらに行けばいいのかわからなくなってしまいます。

答えは、“同じIPアドレスを共有していない"です。2400万個の IP アドレスが割り当てられたプライベートネットワーク上にあるのです。それらはすべて私のためだけのものです。まあ、他の人たちから見れば、すべて私のためのものなのですが。ここで、何が起こっているのかを説明します。

リモートコンピューターにログインすると、ISP から提供されたパブリック IP アドレスである 192.0.2.33 からログインしていると表示されるのです。しかし、ローカルコンピューターにその IP アドレスを尋ねると、10.0.0.5 と答えるのです。誰が IP アドレスを変換しているのでしょうか?そうです、ファイアウォールです。ファイアウォールが NAT しているのです。

10.x.x.x は、完全に切断されたネットワークか、ファイアウォールの内側にあるネットワークでのみ使用される、数少ない予約ネットワークの1つです。どのプライベート・ネットワーク番号が使用できるかの詳細は、 RFC 1918 に概説されていますが、一般的によく目にするのは、10.x.x.x192.168.x.x で、x は通常 0 ~ 255 です。一般的ではないのは、172.y.x.x で、y は16から31の間です。

NAT するファイアウォールの内側のネットワークは、これらの予約されたネットワークのいずれかにある必要はありませんが、一般的にはそうなっています。

(楽しい事実!私の外部 IP アドレスは、本当は 192.0.2.33 ではないのです。192.0.2.x ネットワークは、このガイドのように、ドキュメントで使用するための架空の"本当の” IP アドレスのために予約されているのです!わーい、すごい!)

IPv6 にも、ある意味プライベートネットワークがあります。 RFC 4193 にあるように、fdXX:(将来的には fcXX:)で始まります。しかし、NAT と IPv6 は一般的に混ざりません(このドキュメントの範囲外である IPv6 から IPv4 へのゲートウェイを行う場合を除きます)。理論的には、自由に使えるアドレスが非常に多くなるため、NAT を使用する必要はなくなるはずです。しかし、外部にルーティングしないネットワーク上で自分のためにアドレスを割り当てたい場合は、このようにします。