FreeBSDのMulti-Fibとpacket送出について(駄文)
FreeBSDのMulti-FIBとIPパケット送出について、なんとなく色々考えていたら、少し問題がありそうなことに気がついたので考えてみた。 なお、実際のcodeは見ていないので、もしかすると大勘違いをしている可能性があります。
そこそこ長いかもしれないので、興味のない人はスルーでお願いします。
前提
想定している構成は以下の通り
AS-A addr(A) AS-C +-------- ISP-A --- Router ---+ node --- ISP-C --- Internet Server addr(C) +-------- ISP-B --- Router ---+ AS-B addr(B)
上記構成において、仮にserverのnic0がaddr(A)を持ち、nic1がaddr(B)を持っているとする。 この仮定は今時の日本においては、それほど荒唐無稽なものではない。
例えば、自宅にFlet'sを引き込み、Multi-Sessionを用いて上流ISPを2社と契約し、固定アドレスを割り当ててもらうような場合がこれに当たる。 まぁ、それほど多いとは思わないけど、こういう構成のサイトは企業も含めてそれなりにある。
考察
このようなサイトにおいて、あるnode(addr(C))からaddr(A)にパケットが来たとする。
addr(A)を持つサーバー(Serv)のFIB次第で、返答のパケットが送り出されるNICが決まる現在のFreeBSDの実装だと、以下のような状況が発生する。
- ServerのFIBに載っているaddr(C)へのBest経路がnic0側(ISP-A側)にあった場合
- servは、Src: addr(A)、Dst: addr(C) 、Nexthop: nic0側の中継node
- ServerのFIBに載っているaddr(C)へのBest経路がnic1側(ISP-B側)にあった場合
- servは、Src: addr(A)、Dst: addr(C) 、Nexthop: nic1側の中継node
としてパケットを送出する
現在FreeBSDのnetinet/netinet6の実装は、パケットを送り出す際に、送り出すパケットのsource IP Addressは考慮しておらず、FIBを見た上でベストなI/Fから送り出す。 つまり、FreeBSDのip送出ロジックは
- Dest Addrを確認
- FIBからDest Addrをlookupしてnexthopを決定
- nexthopに対してもっとも近いnicを決定
- 3で決定したnicからpacketを送出
となっていると考えられる。
このような実装において、1の状況になってくれれば問題は発生しないが、もし2の状況になった場合、nic1側の中継nodeがaddr(A)がsrcのパケットを中継してくれるとは限らない。 (これを許すと困ったことになるのはISPで運用していれば理解できると思うので、ここでは詳述しない)
この問題を解決するは、本質的には「全てのI/FにFIBを持たなければならない」と考えられる。なぜなら、入ってきたI/Fからパケットを出すためには、(本質的には)それぞれのI/Fにdest毎のnexthopを持たなければならないからである。 (ただし、FIBを共通化することができる可能性はあるし、そのようにNetworkを構成することができる場合も多いとは考えられる。)
現状のFreeBSDの実装でこの問題を無理矢理にでも解決しようとした場合、pfやipfwを用いたpolicy routeで処理するしかない。 例えば、SRC ADDRがaddr(A)ならば、nic0側のnexthopにpacketを投げる。addr(B)ならnic1側のnexthopにpacketを投げるといった設定を投入ればよい。
しかし、ここでPolicy Routeを利用せずに問題を解決しようとすると、解決策がない。突破口になりそうな機能としてはMulti-FIBがあるが、そこには下記のような問題がある。
FreeBSDのMulti-FIBは、Process単位にFIBを選択可能である。
問題は、Processに割り当てられるFIBを、起動時に1つ設定できる。(もちろん、FIBを選択できるようにApplicationを作成すれば問題にならないが、nginxやapacheなどにその修正を投入するのはそれほど簡単ではなく、現実的ではない)。逆に言えば、パケット送出時に必要なFIBを選択することは(あくまでProcessがそのように実装されていなければ)できない。
つまり、Processレベルで見ると、上記パケット送出時の問題がそのまま残っていると言える。
結論
今のFreeBSDの実装においては、本問題を解決するためにはpolicy routeを利用するしか手がない。
解決策の案
まず、ほとんどの場合、現在の実装で問題はないと言える。想定した構成は、あくまで例外的な構成であり、構成変更できる場合はやりようがあるからである。
例えば、Serverのnicを1つにまとめて、routerでNATするという手が考えられる。こうすることによって、serverはNATされたsource addressから通信を受け取るので、返答をrouterに送れば良くなる。ただし、この場合は、Routerの性能などが問題になる可能性がある。特にNATはstateを持つので、耐障害性などを考慮すると、故障箇所が増えたりdebugが面倒になるなどのデメリットはある。
まぁ、文句ばかり言っても仕方がないので、もし実装を変えるならどうするべきかを考察してみる。
問題の根本は、FreeBSDにおけるMulti-FIB実装が「Process単位でFIBを選択する」実装になっていることにある。これをI/F単位にFIB指定する(ProcessからはFIBは選択しない)形式に直すことで、本問題は解決できると思われる。ただし、いくつか制限が出てくる(本質的には不要かもしれない。まだそこまで考えたわけではないので)
- NIC側の制限
- (Physical/pseudo問わず)全てのNIC deviceに割り当てられるのは、Address1つのみに制限する
- 割り当てたAddress毎にFIBを設定するという手はあるのでこの制限は回避可能かもしれないが、管理がややこしいことになりそうな気がする
- 代わりに、全てのNICには「sub nic」を割り当てられるようにする (すでに不可能ではないはず)
- en0.0 / en0.1 / en0.2…など
- 任意のNIC/sub NICにおいて、「同一のIPv(4|6)アドレス」を持ってはならない(prefixlenが違ってもアドレスが同じならだめ)
- FIBの制限
- 原則として、FIBは最低で1つ存在する
- 原則として、FIBは複数作成できる
- FIBには条件を指定したsub FIBを作成することが可能
- 例えば、FIB0(=FIB0.0) / FIB0.1 / FIB0.2 / … / FIB1(=FIB1.0) / FIB2(=FIB2.0) / FIB2.1 / …
- 条件として設定できるのは、TCP/UDPのport番号など (ここは生煮え)
- FIB0.0 : FIB0のdefault FIB / FIB1.0 : FIB1のdefault FIB / FIB1.1 : FIB1のsub FIBでTCP443/UDP443の場合にのみ利用する / ….など
- 1つのNIC/sub NICに割り当てられるのは、FIBのMajor番号が同じである一連のもののみ
- nic0 : FIB0 / nic0.1 : FIB1.0, FIB1.1 / nic1 : FIB0 など
以上の制限を課した上で、ip_output.cで、
- source Addressを元にして output I/Fを決定する
- output I/Fが決まれば、FIB Baseが決まる
- SubFIBが存在する場合、制限条件を確認し、sub FIBを決定する。制限条件がない場合、sub FIBは0になる (FIB1→FIB1.0やFIB1.1になるという意味)
- 決定されたFIBから経路をlookupする。
- nexthopを決定する。もしnexthopが決定できなかった場合、“DEST UNREACH”とする
- パケットを構成し、送出する
という処理を行う。
問題
上記実装の問題として、今思いつくのは、
- pfやipfwなどのPolicy Routeと相性が悪そうな気がする
- 判断が多くなり遅くなる
- 変更が大きいため、Debug/安定化が遠い
- Process単位でFIBを変えたい場合に対応が難しい
- sub FIBを利用すれば解決できそうに見えるが、全ての場合を網羅できるかはわからない
が考えられる。