namespaceを活用したネットワーク検証の一例


現在のウエブ開発では異なるネットワークに存在する端末同士が連携したシステムを構築することが多くあります。

例えば、弊社製品のクラウドロガーLTEがクラウドと連携する場合、通信事業者のネットワーク内の端末とAWSのネットワーク内のサーバーの異なるネットワークに存在する端末同士が通信を行います。

このようなネットワーク環境でのシステム構築の際に、実際にサーバーや端末を用意する前に、PC内に事前検証環境を構築できると便利です。

本記事ではlinux上においてnamespaceを使用して複数のネットワーク環境を構築し、動作検証の一例としてNATのいくつかのパターン(Full Cone NAT, Restricted Cone NAT)の動作を見てみたいと思います。

はじめに

本記事ではコンテナとnamespaceを組み合わせて、複数のネットワーク空間を持つ環境を構築します。
一般的にコンテナ技術としてはdockerが有名ですが、dockerはネットワーク設定を自動化してくれる反面、細かいネットワーク設定を行うことが難しい場合があります。
以下を満たす環境を構築するために、本記事ではipコマンドとsystemd-nspawnを使用したいと思います。

  • ネットワーク管理ツールとして一般的なipコマンドを使用して、ネットワークインターフェースやルーティングの設定を行う
  • 作成したネットワークインターフェースをコンテナのネットワークとして使用する
  • systemdのサービスを動作させる(ただし、今回はsystemdサービスは使用しません)

namespaceについて

namespaceはlinux環境でリソースを独立させるための仕組みです。
CPUやメモリ等のリソースを独立させるcgroupと組み合わせて使用されることが多く、docker等のコンテナ技術の基盤にもなっているようです。
namespaceにはいくつかの種類がありますが、本記事ではネットワークnamespaceを使用してネットワーク空間を独立させます。
(systemd-nspawnは独自にnamespaceやcgroupを使用していると思いますが、本記事では取り上げません)

ネットワーク構成

本記事で作成するネットワークの構成は以下図のようになります。

ネットワークnamespaceはsystemd-nspawnで生成したコンテナに接続します。
かっこ内の上段はコンテナ名、下段はnamespace名を示しています。

実際のインターネット上の通信は多数のゲートウエイを経由しますが、本記事ではインターネットをブラックボックスと考え、1つのnamespaceで構成しています。
このネットワークを構築するためのスクリプト(netns.sh)は本記事末尾に掲載していますので、ここでは詳細については説明を割愛します。

ネットワークnamespaceの準備

netns.shは以下のように実行します。

上記実行後にip netnsで確認するとnamespaceが作成されていることが分かります。
また、namespace内でip linkを実行すると作成したインターフェースが表示されますが、ホスト側で実行した場合は表示されないことから、ネットワークインターフェースも独立したnamespaceに存在していることが分かります。

systemd-nspawn環境の構築

systemd-nspawnはchrootを強化したコマンドと言われており、namespaceやcgroupを使用してコンテナを起動します。

今回は学習用に自前でipコマンドでnamespaceを作成したことから、namespaceと接続しやすいsystemd-nspawnを使用しています。
また、本記事では使用していませんが、systemd-nspawnのコンテナ内ではsystemdを起動できるため、コンテナ中でsystemdサービスを使用したい場合にも便利です。

ルートファイルシステムの準備

今回はdebootstrapで作成したルートファイルシステムをコンテナとして使用します。
以下ではnoble配下にubuntu24.04(noble)のルートファイルシステムをダウンロードしています。

ダウンロードしたままの状態ではユーザのパスワードが設定されていないので、一旦chrootでログインしてパスワードを設定しておきます。
コンテナ内ではrootで作業を行うため、rootのパスワードを設定します。

debootstrapが完了すると、以下のようにsystemd-nspawnから起動できるようになります。
systemdのサービスも起動していることが分かります。

btrfsを使用したルートファイルシステムの作成

起動中のコンテナと同じルートファイルシステムから新たにコンテナを起動しようとすると、以下のようにエラーとなります。

systemd-nspawnに–ephemeralオプションを指定することでこのエラーは回避することができます。
しかし、–ephemeralオプションは起動時にルートファイルシステムを一時領域にコピーするため、起動に時間がかかります。
また複数のコンテナを同時に起動すると、RAMの使用量も増加します。

そこで、起動速度とRAM使用量を改善するために、スナップショットをサポートしているbtrfsでルートファイルシステムを作成したいと思います。
こちらの手順は以下を参考にさせていただきました。
https://idle.nprescott.com/2022/systemd-nspawn-and-btrfs.html

btrfsを使用することで–ephemeral使用時のコンテナ起動時間とRAM使用量がどの程度改善するかを確認してみます。

起動時間の比較

起動時間差を明確にするために、lsコマンドを実行して終了するだけのコンテナを起動しています。
3~4秒高速になっていることが分かります。

メモリ使用量の比較

次にメモリ使用量も見てみます。コンテナ起動直後のRAM使用量を出力しています。
ext4でのfreeの減少量(738->262)よりbtrfを使用した方がfreeの減少量(736->657)が小さく、コンテナ実行中のメモリ使用量が抑えられていることが分かります。

使用するパッケージのインストール

スナップショットを使用して起動したコンテナ上の変更は、コンテナ終了後に破棄されます。
そのため、必要なパッケージは–ephemeralを指定せずに起動したコンテナ上に事前にインストールしておきます。
ここでは本記事後半で使用するconntrackやiptablesをインストールします。

コンテナの起動

コンテナとネットワーク環境の準備が整ったので、ネットワークnamespaceをコンテナに接続して起動してみます。
systemd-nspawnのオプションについて簡単に説明します。

>

オプション 説明
–network-namespace-path コンテナに接続するnamespaceのパス(通常/var/run/netns/配下に存在)を指定
–capabitrty コンテナ内でネットワークの操作を行うため、CAP_NET_ADMINを指定
-M 複数のコンテナを起動するため、各コンテナにはネットワーク構成で示した名前を指定

起動例は以下のようになります。

起動中のコンテナはmachinectlコマンドで確認することができます。
以下の例ではc0, c0-nat, c1、c1-natが起動していることが分かります。
またmachinectlは各種コマンドが搭載されており、起動中のコンテナへのログインも可能です。

NAT動作検証

本記事ではシンプルな動作確認を行うために、UDPパケットの通信を使った検証を行います。

検証対象のNATについて

今回の検証ではNATの動作を見てみたいと思います。
NATは大きく以下の4種類に分けられるようですが、下に行くほど制約が厳しくなります。

NAT種別 説明
Full Cone NAT 内部と外部のポートのマッピングが固定。マッピングされたポートについて、外部からのパケットを内部ホストに転送
Restricted Cone NAT 内部と外部のポートのマッピングが固定。マッピングされたポートについて、内部から通信確立した相手(IPアドレスのみ判定)からのパケットを内部ホストに転送
Port Restricted Cone NAT 内部と外部のポートのマッピングが固定。マッピングされたポートについて、内部から通信確立した相手(外部IPアドレス+ポートを判定)からのパケットを内部ホストに転送
Symmetric NAT 内部からの通信ごとに異なる外部ポートをマッピング。マッピングされたポートについて、内部から通信確立した相手(外部IPアドレス+ポートを判定)からのパケットを内部ホストに転送

現在ではマッピングとフィルタリングを分けてEndpoint-Independent MappingやEndpoint-Independent Filteringのような分類が推奨されるようですが、本記事では上記4分類の用語を使用し、Full Cone NATとRestricted Cone NATの動作をconntrackコマンドで確認してみたいと思います。

conntrackについて

conntrackはカーネルが管理している接続追跡テーブルを表示・操作するためのコマンドです。

ssやnetstatはプロセスと紐づいたソケットの情報を表示しますが、conntrackはカーネル内部のNAT変換や接続や切断の状態を表示することができます。
通信がどのタイミングで接続確立と判断されたかや、NAT変換前後のアドレス・ポート等を確認することができ、通信状況の確認に役立ちます。

conntrackの出力内容について簡単に説明します。
以下はc1からc0-natへnetcatでUDPパケットを送信した際のc1-natのconntrackの出力例です。
conntrackは-Eオプションをつけることで、リアルタイムに状態変化を表示することができます。

フィールド 説明
[NEW] 新しいエントリを作成
udp 17 upd(プロトコル番号17)のパケット
30 応答がない場合に削除されるまでのタイムアウト時間
src=10.0.2.1 dst=100.64.1.100 sport=54536 dport=54321 送信元のIPアドレスは10.0.2.1:54536, 送信先は100.64.1.100:54321
[UNREPLIED] 応答パケットが未検出
src=100.64.1.100 dst=100.64.2.100 sport=54321 dport=54321 期待する応答元は100.64.1.100:54321、応答先は100.64.2.100:54321

Full Cone NATの検証

c0をFull Cone NATとして設定しているので、対向のc1側からnetcatでパケットを送信します。
動作結果をコードブロック内に記載します。

コードブロック内のコメントの番号がタイミングを示しています。
c1からc0-nat(100.64.1.100)のポート54321へUDPパケットを送信するとc0-natでNAT変換が行われ、c0(10.0.1.1)でUDPポート12345で受信待ちしているnetcatにパケットが届いていることが分かります。
また、c0-nat外部からのパケットが無条件でc0-nat内部のc0に届いており、c0-natがFull Cone NATの動作となっていることを確認することができました。

c0

c0-nat

c1

c1-nat

Restricted Cone NATの検証: 接続確立前

Restricted Cone NATは接続が確立する前の状態では、外部からのパケットをNAT内部に転送しません。
c1をRestricted Cone NATとして設定しているので、先ほどとは反対に対向のc0側からnetcatでパケットを送信してみます。
以下のようにRestricted Cone NAT内のc1ではパケットを受信できていないことが分かります。
また、接続が削除された後はc0からc1へパケットが届かなくなっていることも分かります。

c0

c0-nat

c1

c1-nat

Restricted Cone NATの検証: 接続確立時

次にc1が最初にc0へ送信をした場合を見てみます。
この場合、c0が返信した時点で接続が確立されたと判断され、それ以降のc0からのパケットはc1に転送されるようになります。

c0

c0-nat

c1

c1-nat

以上の動作により、c1-natが上述のRestricted Cone NATとして動作していることを確認できました。
また、UDPの場合は一定時間通信がない場合に切断として判断していることや、それ以降は外部からの通信を破棄していることも分かりました。

余談: wireguardにおけるPersistentKeepaliveとの関係

VPNプロトコルとして人気のあるwireguardはノード間の通信はUDPで行います。
以下のwireguardの概要を見ると、NAT配下にあるNATの接続状態を継続するためにKeepAliveパケットを送信する機能があり、送信間隔はPersistentKeepaliveというパラメータで指定できるようです。

NAT and Firewall Traversal Persistence
ここで、以下のように多くのNATにおいて25秒が適当な旨の記載があります。

上記検証でUDPの接続確立後、ASSUREDとなる前の無通信状態では30秒で接続が破棄されることを確認しましたが、wireguardのPersistentKeepaliveの25秒はこれを踏まえての値と推測できます。

後片づけ

以下を実行して今回の検証で作成したnamespaceを削除します。

まとめ

本記事ではipコマンドを使用してネットワークnamespaceを作成し、systemd-nspawnで起動したコンテナに接続する方法を紹介しました。
また、その活用例としていくつかのNATの動作検証を行いました。
これら技術を応用することで同一ホスト内に様々なネットワーク環境を構築することができ、ネットワークの学習やシステム開発の事前検証等の効率化にも役立つと思います。
本記事を活用していただければ幸いです。

netns.sh

本記事で使用したnetns.shを以下に掲載します。
ご使用の環境のIPアドレスと衝突する場合は適宜修正してご使用ください。