FreeBSDをルータに

いまどき、ルータといえば、自宅なら無線WiFiルータ、業務でも専用のハードウェアを置くのが当たり前である。
FreeBSDをルータにするのは流行らない。
しかしだからこそ、もしものときのために記録に残す。

方針

FreeBSDをルータにするにはいろいろと種類がある。
特にそういったことは概観せず、もういきなり決め打ちするが、pfを使う。

本稿での構成

構成は以下のとおりである。
セキュリティ要件は一切、設定しない。
ただ、単にFreeBSD Routerを通してLAN内のクライアントが外に出ていければよい。
セキュリティはあとでゆっくり、ご自由にどうぞ。

Gateway to The Net   <-- WLAN --> (wlan0) FreeBSD Router (em0) <-- Ethernet --> LAN
192.168.10.1                 192.168.10.x              10.0.0.1              10.0.0.x
DHCP Server                   DHCP Client       pf     DHCP Server          DHCP Client

本稿で扱う部分

上記構成図で言う、FreeBSD Routerのpf設定をメインにする。
ただし、FreeBSDのインタフェースが、片方がDHCP Client、他方がDHCP Serverになっているので、影響しそうなところは触れる。
また、以下、特に断りのない限り、FreeBSD Routerのことを「ルータ」と記す。

デフォルトルータの設定

兎にも角にもまずデフォルトルートを設定する。
これは言わずもがな、FreeBSDルータから見てインターネット側へのデフォルトルート。
/etc/rc.confに以下を書き込む。

defaultrouter="192.168.0.1"

再起動したあと、/etc/resolv.confに以下の記載があることを確認する。
これはLAN向けのネームサーバですね。

# Generated by resolvconf
nameserver 192.168.10.1

pfの有効化

pfを有効にします。有効にするには/etc/pf.confに以下を書き込む。

pf_enable="YES"

もちろん、以下を実行してもOK。

sudo sysrc pf_enable=YES

pf基本設定

pfのログはデフォルトで/var/log/pflogに記録される。
実際の記録にはrc.confpflog_enable=yesを記載する必要がある。

前後するが、私の/etc/rc.confは以下の通り。

# For router
pf_enable=yes
pflog_enable=yes
gateway_enable="YES"    # set to YES if this host will be a gateway
defaultrouter="192.168.10.1"

FreeBSDのルータ化

上記で少し書いたが、ルータにするには、/etc/rc.confgateway_enable=yesを記載しておく必要がある。

pfルールの設定

exampleがあるので、/etcの下にコピーする。

sudo cp /usr/share/examples/pf/pf.conf /etc/pf.conf

といいつつ、いったん全部コメントアウトして、必要最低限のものだけ有効化していく。

pf.confにおける記載順序

pf.confには記載順序にルールがある。
具体的には、以下の順番であることが求められる。
もっとハッキリ言うと、この順番でないとpfは起動しません。

  1. Options
  2. normalization
  3. queueing
  4. translation
  5. filtering

本稿では2, 4, 5が該当。
2はルータを通過するトラフィックの正常化のこと。
4は例えばNATのこと。
5はフィルタのこと。
このほか、記述を簡単にするためのマクロ定義がある。これはその性質上、冒頭で扱う。(だって文末でマクロ定義したって仕方ないもんね)
ではさっそくマクロ定義から。

定義(マクロ)の宣言

インターネット側IFをext_ifとして、LAN側IFをint_ifに設定する。
外側向けIFはwlan0、内側はem0なのでそのとおりに。

ext_if="wlan0"
int_if="em0"

LAN側ネットワークとしてlocalnetを定義。
こうすると、以降のルールでLAN側ネットワークを$localnetとして扱うことができる。
:networkとは、ネットワークインタフェースに続けて記載し、そのインタフェースに接続されたネットワークのことを表す。
この場合、LAN側インタフェースにつながっているネットワーク全部を指し示す。

localnet = $int_if:network

トラフィック正常化

いたずらパケットを正常化するためのルール。
要件に示したように、特にセキュリティのためのルール設定はしないが、これは難しいこともなく一行追加するだけでよいので記述。
こうするとpfは断片化されたパケットを再構築したり、ありえない組み合わせのTCPフラグが立ったパケットを蹴飛ばすとかしてくれる。
やっておいて損はない。

scrub in all

NAT設定

目玉であるNATの設定。
nat onに続けて、NATを有効にするインタフェースを指定する。
ここでは$ext_if
起点はLAN側IFのすべての通信なので、from $localhostを指定。
宛先は特に制限しないのでto anyとする。
それら通信のソースアドレスを$ext_ifに変換して外側へ送り出す。
なお、$ext_ifはダイナミックなので($ext_if)と記述する。
こうすることで、IPアドレスが変わったとしても通信が継続されるようになる、と書いてある。

# ext_if IP address could be dynamic, hence ($ext_if)
nat on $ext_if from $localnet to any -> ($ext_if)

フィルタリング設定

まず全部の通信をブロック。

block in all

Keep state設定。
これ複雑なので図示する。
ルータの入り口である$int_ifに入るときと、
ルータからの出口である$ext_ifから出るときの双方に設定が必要。

LAN ---> em0 ---> wlan0 ---> the Net
       --> rule 1
                      --> rule 2

上記をpfルールとして記述すると以下の通り。
rule1が1行目に、rule2が2行目に対応。

pass in on $int_if from $localnet to $ext_if:network keep state
pass out on $ext_if from $localnet to $ext_if:network keep state

設定確認

ここまでできたら設定確認をする。
確認はpfctl -vnf /etc/pf.confとすればよい。
実行してみると、各種ルールが具体的に展開されて表示されるので、自分で作ったルールを検証するとよい。

$ sudo pfctl -vnf /etc/pf.conf
ext_if = "wlan0"
int_if = "em0"
localnet = "em0:network"
scrub in all fragment reassemble
nat on wlan0 inet from 10.0.0.0/24 to any -> (wlan0) round-robin
block drop in all
pass inet from 127.0.0.1 to any flags S/SA keep state
pass inet from 10.0.0.0/24 to any flags S/SA keep state

pfの実行

pfの実行にはpfctl -eと、停止にはpfctl -dとすればよい。
また、実行中の通信についてはpfctl -s statesとすればよい。

設定読み込み

設定変更をした場合、それを有効化するには以下のようにすればよい。
pfの有効化はルータマシンでやりましょうね(戒め)。

$ sudo pfctl -F all -f /etc/pf.conf
rules cleared
nat cleared
0 tables deleted.
7 states cleared
source tracking entries cleared
pf: statistics cleared
pf: interface flags reset

以上