最低限のNetwork知識
こんにちはシムディ合同会の開発チームです。
今回はネットワークに関して特にL2とL3の話です。
参考文献(とても良書なので興味ある人は是非)
・tanenbaum先生の教科書(Networkの世界的bible)
・CでネットワークプログラミングをするHands-on本(とても読みやすかった)
・Linuxで仮想ネットワークを構築してみる本(解像度が上がった)
・TCPの最新動向を掘り下げてる本
ネットワークモデルのoverview
コンピューターのネットワークはコンピュータ同士プロトコルという決まり事に沿って通信を行うことで意思疎通を図っている。このプロトコルは多数あり、類似したものを同じ階層に分けてモデル化し考えるのが一般的である。例えば、以下の図はOSIモデルと呼ばれる7層に分かれたプロトコル。
上から簡単に概略を示す。(深入りはしない。)
・アプリケーション層
プログラマーが意識する部分。(HTTP, DNS等)
・プレゼンテーション層
データの表現形式。テキストファイルをASCIIコードのファイルへ変換とか。
・セッション層
通信プログラム間の通信の開始から終了までの手順。
・トランスポート層
エンド間の通信制御。(TCP, UDP等)
・ネットワーク層
通信相手への最適な経路を決定。(IP, ICMP等)
・データリンク層
隣接するデバイス同士の通信。(Ethernet等)
・物理層
物理的な接続。1,0のビット情報を取り扱う。
プロトコルは既に定められているが、そのプロトコルがどの層に所属するのかは恣意的に決めることができる。よって上記の7層モデルが絶対ではなくTCPモデルでは4層で定義されていたり、tanenbaum本では5層モデルで説明が進められている。本記事では主にネットワーク層とデータリンク層の話を扱う。
データリンク層とARP
誤解を恐れずにいうと同じLANケーブルでつながっているコンピュータ同士は直接通信することができる。通信は物理層で行われる0,1の激しいやりとりによって、一塊の情報を通信相手に送っている。送りたい情報が膨大だったり、複数ある場合は分割して分割された塊を相手側でガッチャンコすることで情報を復元させる。
この1個の塊のことをフレームと呼ぶ。ざっくりとした図は以下のようになる。
Payloadには基本的にパケットが入っていると考えれば良く、Headerには送信先のMACアドレスと送信元のMACアドレスが記載されている。MACアドレスとはMedia Access Control アドレスの略で製造メーカー他から各コンピュータに一意に振られている。(だが、VMを立ち上げたりすると仮想的に作ることもできたり、厳密には一意ではない。) このアドレスをもとにフレームは通信相手のコンピュータを探すことになる。
ここで湧いてくる疑問。
・どうやって相手のMACアドレスを知る?
普通プログラマーがどこかのコンピュータを通信をしたい時は、IPアドレスでプログラム上で指定したり、自分のパソコンでさえもlocalhostや127.0.01(この場合はloopbackという)と指定したりするはず。すなわち、コンピュータ同士で通信する時はIPアドレスを基にして相手を探しているわけである。しかし、IPアドレスとはいわばソフトウェアが勝手に付与するものであり、例えばDHCP(Dynamic Host Configuration Protocol)サーバー等を使っていればころころアドレスが変わってしまう。このIPアドレスを使ってそのコンピュータが所属するネットワークの場所を特定するまでがネットワーク層の仕事で、そのネットワーク内で実際にフレームを届けるのはデータリンク層の仕事となる。
さて、どうやって相手のMACアドレスを知る?の疑問の答えはARPというプロトコルになる。ARPとは同じネットワーク内で端末のIPアドレス使ってMACアドレスを聞き出すプロトコルである。以下にARPの流れを示す。
まずは以下の図を想定。PC1とPC2は同じネットワーク内にあるものとする。ルーターを超えると違うネットワークとなり、172.16.1.0/24がネットワークのアドレスを表している。(詳しくはsubnetで検索。) 同じネットワークのことをセグメントとかブロードキャストドメインと呼んだりする。それそれMACアドレスはMAC1, MAC2と記載している。また、ルーターもポートごとにIPアドレスとMACアドレスを有している。
PC1がPC2のIPアドレスを知っていてMACアドレスを知らない場合。以下の用にPC1がネットワーク全体にMACアドレスを聞いて回る。
PC2は自分のIPアドレスを知っていて172.16.1.3と一致しているため、自分のMACアドレスがMAC2であることをネットワーク全体に伝える。この時ルーターもPC1の質問を聞いているが自分のIPアドレスではないため無視をする。
こうしてPC1は172.16.1.3のMACアドレスがMAC2と知ることができる。
この一連の流れをARP(Address Resolution Protocol)と呼び、同じネットワークにいるコンピュータ同士お互いのMACアドレスを教え合っている。上記の場合、ルーターは会話に参加していないが会話の内容は全てキャッチしているので、PC1が得た情報と同じ情報を得ることができる。このPC1がやったようにネットワーク全体に向かって発信することをブロードキャストと呼ぶ。また、APRで得られたIPアドレスとMACアドレスの対応表はARP tableと呼ばれ、
arp -a
というコマンドで確認できる。
・MACアドレスが分かったところでどうやって相手にフレームを届ける?
上の例で、ARPを使いPC1がPC2のMACアドレス情報を入手し、その後フレームをPC2に届けたいとする。これをどう届けるかはプロトコル次第になるが最も簡単な例は同一ネットワーク内にブロードキャストを行いフレームを送りつけるやり方である。
フレームのHeaderには送元と行先のMACアドレスがあるので、誰が誰に送っているのかの情報が分かる。そしてフレームを受け取ったPC2は自分宛のフレームがMAC1から送られてきたので後続の処理を行う。ルーターは自分宛ではないフレームなので無視することになる。
以上のようにして、互いのMACアドレスを確認しあってフレーム単位で通信を行うのがデータリンク層の大きな仕事の一つとなる。
ネットワーク層とルーティングテーブル
先ほどの例では同じセグメント内にいる場合の通信の仕方を説明した。しかし、同じセグメントとはごく狭いLANの一部の話であり、違うネットワークのコンピュータと通信したいことは往々にしてある。(セグメントが大きくなると多数のコンピュータにフレームが届いて通信の効率が悪くなる。)では、その場合どうするのか?
ここでルーターが登場する。先ほどの例でもルーターが出ていたがこのルーターの別のポートが違うネットワークのEthernetにつながっている。下の例でいうとPC1とPC2とルーター1のport1が同じセグメント、PC3とルーター1のport2とルーター2のport1が同じセグメント、PC4とルーター2のport2が同じセグメントというくくりになる。
ルーターではIPアドレスの情報を頼りにして宛先が所属するネットワークを探す。前章ではデータリンク層ではMACアドレスを使うという話をした。今回はIPアドレスを使うのでネットワーク層の話となる。つまり、ルーターとはネットワーク層のIPプロトコルを使用して働くデバイスということである。
前章と少し比較する。前章ではフレームという単位で情報のやりとりをするという話をした。(下図)
このHeaderには前述の通りMACアドレスの情報が入っている。ではIPアドレスの情報はどこにあるのかというとこのPayload fieldに入っている。これがいわゆるパケットと呼ばれるもの。
パケットのHeaderは以下のようになっている。
色々書いてあるが、IPアドレスはSource addressとDestination addressのフィールドに入っていることになる。つまり、データリンク層のプロトコル(e.g. Ethernet) でフレームのHeader情報が使われ、その一個上であるネットワーク層のプロトコル(e.g. Internet Protocol) ではフレームのHeaderが剥がされて現れたパケットのHeaderに書かれたIPアドレス情報を使う。このように層ごとにパケットの両端に情報が足されたり、剥がれたりして層ごとにそれぞれの仕事を行なっている。
ルーターも含めコンピュータはそれぞれルーティングテーブルを保有している。そこには宛先のIPアドレスごとにどこにパケットを送るべきかという情報が記載されている。ちなみに以下コマンド、
ip router
でそのコンピュータが持っているテーブルが出てくる。
ではルーターがどのようにネットワークを探すのかを見ていく。まずは以下の図が今回考えるネットワーク構成だとする。
ルーター1とルーター2を挟んで3つのセグメントがある構成である。ここでPC1がPC4と通信したいとする。ここでは前章で見たフレームの話も含めて説明する。ただし、それぞれネットワーク内でARPは既に行われ、それぞれのコンピュータがARP Tableを保有しているとする。
まず、PC1のルーティングテーブルには以下のようになっているとする。
上記テーブルの意味は通信したい相手が172.16.1.3(PC2)であればルーターを返す必要がないので次の宛先が - 、すなわち同ネットワーク内で前章のブロードキャストを行えば良い。それ以外が宛先の場合(0.0.0.0)、ルーター1を経由する必要があるので次の宛先を172.16.1.1(ルーター1のport1)とする。この、0.0.0.0はデフォルトルートと呼ばれ、一般家庭のPCだとWiFiルーターを向いていることがほとんどのはず。(ググる時は通信の宛先がインターネットなのでとりあえずルーター経由で外に出る必要があるから。)
さて、上でPC1が通信したい相手はPC4(12.16.3.2)なので上記のテーブルによると、まず172.16.1.1(ルーター1のport1)に向かうことになる。もちろん、このルーターまで向かう場合でも同じネットワーク内での通信は前述したブロードキャストによって通信する。なのでフレームは以下の様になる。
ポイントはフレームの宛先はルーター1のport1のMACアドレスであるが、パケットの宛先はPC4のIPアドレスである。フレームを扱うEthernet(データリンク層)では同じセグメント内のブロードキャストで使うため、フレームのHeaderにはルーター1のport1のMACアドレスが入る。そして、パケットを使うインターネットプロトコル(ネットワーク層)ではあくまで最終目標地点のPC4のIPアドレスを保持しておく必要がある。
次にルーター1のルーティングテーブルを見てみる。
先ほど見たルーティングテーブルで説明済みなので特に説明はしないが、今回の宛先はPC4(12.16.3.2)なのでデフォルトルートとなりルーター1のport2からルーター2のport1を目掛けて通信する。例によって、この通信は同じセグメント内なのでEthernetの出番となる。フレームは以下の様になる。これも前述の通りとなる。
次にルーター2のルーティングテーブルを見てみる。
デフォルトルートの次の宛先に次のルーターと記載したがネットワーク構成が右に続いていると考えて欲しい。通信したい相手はPC4(12.16.3.2)は172.16.3.0 / 24のネットワークにいるのでこれ以上ルーターリレーをする必要はなく、ここでブロードキャストを使いフレームを届けることができる。フレームは以下の様になる。このフレームを届けることで無事に中のパケットがPC1からPC4に届けられる。
NAT
Network Address Translationすなわちアドレスのマッピングを行なっている。一番わかりやすい例はprivateアドレスからglobalアドレスへの変換。
例えば、private IPアドレスが192.168.100.101だったとして8.8.8.8にアクセスしたいとする。以下のアドレスは予めPrivate IPアドレスの領域として定められているため、ルーターはこれがPrivateアドレスだと分かる。
10.0.0.0-10.255.255.255
172.16.0.0-172.31.255.255
192.168.0.0-192.168.255.255
8.8.8.8はインターネット上に公開されているアドレスなので外部のルーターを伝っていくが、この時送り元のアドレスがPrivate IPアドレスのままだとパケットを破棄されてしまう。(PrivateアドレスはLAN専用なのでいろんなコンピュータと被りまくり。)
なのでパケットが外部に出るときに、送りもとアドレスをGlobal IPアドレスに変換してあげる必要がある。これがNATである。このNATルーターがLANの外部の境におかれることになる。
上記の例であれば、以下のように送信元のアドレスを変えてしまう。そして、変える前と変えた後のアドレスのマッピング表を保持しておく。(これをNATテーブルと呼ぶ。)
8.8.8.8からのレスポンスは203.0.113.2宛にくるがNATルーターがこれをprivate IPアドレスに変換して本来の送り元に届けられることになる。
一方この変換はPrivate IPとGlobal IPが1対1に対応していて、Private IPの数だけGlobal IPを用意する必要があり、現実的ではないためポートも含めてマッピングするIPマスカレードが一般的である。
Dockerのネットワーク
今回はdockerのbridgeとhostのネットワークについて説明する。
・bridge
Dockerはdefaultではbridgeネットワークを自動で形成する。以下の図を参照。
同じ物理サーバーの上に二つの点線枠があるが、これはName spaceと呼ばれるものでlinux仮想的にネットワークの観点で独立した環境を提供している。つまり、異なるNamespaceはいわば違うlinuxサーバーが存在していると考えると分かりやすいかもしれない。そして、dockerを立ち上げると、dcoker0という仮想bridgeが立ち上がる。bridgeとはそこに繋がっているネットワークを1つのセグメントとして繋ぐ機能をもつ。つまりdocker0に繋がっているコンテナ同士(上図は1個のコンテナのみだが)は同じセグメントに所属することになる。
docker0とコンテナのeth0(ネットワークとコンテナのインターフェース)を繋ぐvethは一個のケーブルが繋がっていると考えるとわかりやすい。
このようにしてbridgeネットワークで立ち上げたdockerのコンテナ達はIPアドレスが127.17.0.1のdocker0という仮想bridgeに繋がっているため、お互いにブロードキャストて通信をし合うことができる。そして、Hostのnamespace上にある、eth0(コンピュータとネットワークの物理的なインターフェース)との間にある丸いものがNATルーターの役割を果たしていて、コンテナ内部からのパケット送信の際にdockerネットワークで使っているIPアドレスから、その物理サーバーのIPアドレスに変換してその変換表を持っておくことになる。このルーターはHost namespaceのサーバーがその役割を担っている。下表参照。
もちろん、この後にインターネットに出ていく前に前述したNATルーターを通ってもう一度アドレスが変換されるが、両方のNATルーターとも変換表を保持しているため問題なく帰ってきたパケットを該当コンテナに返すことができる。
一方でHost側(あるいは外部)からコンテナにアクセスしたい時に、コンテナのどのポートにアクセスすれば良いかという情報をNAT tableに予め書いておく必要がある。(ポートを開放するとはこのこと。) 例えば上記のコンテナのポート80をホストのポート80とマッピングさせる場合、dockerのコマンドでは
-p 80:80
のように書きNATテーブルは以下のようになる。これで外部からアクセスできるようになる。
このようなNATをDestination NATと呼ぶ。前述のNATはSource NATと呼ぶ。
・host
次にhostネットワークについて。これはデフォルトではなくdocker起動時に --net hostのオプションを指定する必要がある。
このネットワークはbridgeに比べると非常に単純でコンテナをHostのnamespace内に配置することになる。なので、コンテナのIPアドレスとホストマシンのIPアドレスが一致するため、サーバー内でNATルーターは必要なく、ポートマッピングをする必要もなくなる。単純に、コンピュータ内に別のプロセスが立ち上がっていると考えれば良さそう。
Hostネットワークでは、例えば複数のコンテナで通信するような場合、listenしてるコンテナが複数同時に立ち上がるとポートが被ってしまうという問題があるので注意が必要。同じHost namespaceにいるの同じポートはどちらかのコンテナしか使えない。下図参照。
Bridgeネットワークするとname spaceが異なるため、ポートが被っても問題なくなる。(仮想的に別マシンとなるのでポートが被ろうが関係ない。) 下図参照。
まとめ
主にL2とL3の層に絞って説明を書いた。ネットワークの話は範囲が膨大に大きいが、ソフトウェアエンジニアやっている以上、知れば知るほど日頃の業務の解像度が高くなるので早めにやっておいた方が良さそう。
弊社シムディ合同会社では自社プロダクト開発、システム開発の請負やってます。
開発チームにご参画いただける方やお仕事のご依頼などお気軽にお問い合わせください!
Discussion
良記事ありがとうございます!!
良記事です!!なかなか、MACアドレスから入っているのは見ないので...
同じネットワークや違うネットワークの所をもう少し詳しくなればもっと良いかと思います(subnetで検索の所)
ネットワークって言葉が抽象的すぎて、混乱する人を見てきたのでw
同じ・違うネットワークはIPアドレスのネットワーク部の事で、どうなると同じになるかがあればもっと分かりやすいかと思います