目次

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の実装だと、以下のような状況が発生する。

  1. ServerのFIBに載っているaddr(C)へのBest経路がnic0側(ISP-A側)にあった場合
    • servは、Src: addr(A)、Dst: addr(C) 、Nexthop: nic0側の中継node
  2. 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送出ロジックは

  1. Dest Addrを確認
  2. FIBからDest Addrをlookupしてnexthopを決定
  3. nexthopに対してもっとも近いnicを決定
  4. 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は選択しない)形式に直すことで、本問題は解決できると思われる。ただし、いくつか制限が出てくる(本質的には不要かもしれない。まだそこまで考えたわけではないので)

以上の制限を課した上で、ip_output.cで、

  1. source Addressを元にして output I/Fを決定する
  2. output I/Fが決まれば、FIB Baseが決まる
  3. SubFIBが存在する場合、制限条件を確認し、sub FIBを決定する。制限条件がない場合、sub FIBは0になる (FIB1→FIB1.0やFIB1.1になるという意味)
  4. 決定されたFIBから経路をlookupする。
  5. nexthopを決定する。もしnexthopが決定できなかった場合、“DEST UNREACH”とする
  6. パケットを構成し、送出する

という処理を行う。

問題

上記実装の問題として、今思いつくのは、

が考えられる。