c言語でNAT自作 [実機で動作確認]
やったこと
簡易ルーティングテーブルの読み取り
元のルーターにはルーティングテーブルという概念がなく、デフォルトゲートウェイまたは直に繋がっているサブネットにしか送信できないものだったので、ルーティングテーブルを基にパケットフォワーディングをできるようにした。
本来はCLIでルーティングテーブルの要素の追加や削除を行えるようにするのがよいが、時間がないので今回はルーティングテーブルの情報はあらかじめすべてjsonファイルに書いておいて、それをプログラムから読むという仕様にした。
次のような記法で、ネットワークインターフェースとルーティングテーブルの中身のリストを記述する。
{ "interfaces":[ "net0", "net1", "net2" ], "routing_table":[ { "dest_addr":"10.1.0.0", "subnet_mask":"16", "next_hop":"10.2.23.2" }, { "dest_addr":"10.1.14.0", "subnet_mask":"24", "next_hop":"10.2.24.2" } ] }
データ構造
ルーティングテーブルのデータ構造として、Radix Tree を採用する。
ja.tech.jar.jp
テーブルをRadix Tree にすると、宛先の情報・サブネットの情報・ネクストホップの情報をその構造自体に内包することができるため、木の探索を行うだけで最長一致によるフォワード先の決定ができる。
struct node { struct node *parent; struct node *child[2]; u_int32_t daddr_subnet; u_int8_t subnet_mask; u_int32_t next_hop; u_int32_t daddr_full; int is_empty; int is_root; };
処理内容と難しかったポイント
アドレス、サブネットアドレス、サブネットマスクに関する計算する部分を実装するのが一番手間がかかった。
ノードの最長一致での探索は、マスクを一つずつシフトしながら一致するものがないか見てゆく。
今回はシフト処理がうまく書けなかったので、サブネットマスクにあたる数字(0~32)をインクリメントしていき、その数字をネットバイトオーダのマスクに変換する関数に渡している。
この際バイトオーダに注意が必要である。
ノードのroot(サブネットマスク0)からチェックしていき、サブネットが一致するものが見つかったらそれを保存したのち、さらに長いサブネットを持つものと一致しないか探しに木を下ってゆく。
ノードの挿入においては、挿入する本体の親がいないと探索の時に困るため、ダミーノードを作ることで対応した。
NAT機能実装
データ構造
LAN側とWAN側の5tuple(src addr / port , dest addr / port , protocol)を対応づけたものをNATテーブルの要素とする。
またその他にも、現在払い出しているポート番号の情報などもNATテーブルにて保持する。
struct nat_table{ struct nat_table_element *start; struct nat_table_element *end; int num; int used_port[MAX_TABLE_SIZE]; int last_gave_port; }; struct nat_table_element{ struct nat_table_element *prev; struct nat_table_element *next; struct five_tuple *loc_tpl; struct five_tuple *glo_tpl; u_int8_t protocol; time_t last_time; int is_tcp_estab; int tcp_state; }; struct five_tuple{ u_int32_t src_addr; u_int16_t src_port; u_int32_t dst_addr; u_int16_t dst_port; u_int8_t protocol; };
処理
パケットの宛先port,addrと送信元port,addrとプロトコルの5つを識別要素として通信の単位を構成し、LAN側からのパケットにその単位ごとにWANでの送信元portを割り振り、パケットを書き換える。
WAN側からのパケットを受け取ったらNATテーブルを参照し、宛先を然るべきLAN側のport,addrに書き換える。
実験
ネットワークシミュレーションツールのtinet を用いてNATの動作確認を行ったので、その様子を載せる。
上記のようなネットワークを組んでc2からr1にpingを飛ばし、r1でパケットキャプチャを行った。
結果は以下の通り。
r2でNATが行われた結果、r1では10.255.1.2(r2)からパケットが飛んできているように見えている。