近年は野生の自宅サーバを見る機会も減ってきましたね.環境の変化によるものなのでしょうか.
さて,自宅サーバを運用するにあたって避けられないのがファイアウォール,そしてそれを扱うパケットフィルタリングです.有名どころだとiptables
がLinuxまわりではよく使われていますが,わたくしのようなFreeBSDを使っている人間はpf
を使っている人が多いのではないでしょうか.
この記事では,パケットフィルタであるpf
の,特に自宅サーバにおける基本的な使い方に特化した解説を行い,パケットが届かないイライラをなくすことを目指します.
自分のために書いたドキュメントをもとに書いているので,間違ったことを言っていたらしかってください.
「○○が書かれてないんですけど!!」と思ったそこのあなたは,manを読んでください.
PF なにができるの
pfはPacket filterの略です.もともとはOpenBSDで使われていましたが,FreeBSDでも使うことができます.
pfはパケットをフィルタするほかにも,フラグメントになったパケットの修復やルーティング,QoS制御も行うことができます.
PFにおける「発信」と「着信」
PFはカーネルモジュールとして組込まれているのですが,どのような位置付けにあるのでしょう.
- pfは,ネットワークインターフェイス(以下,I/F)とカーネルとの間で仕事をします.
- I/Fからカーネルへの方向を「着信」と呼びます.
- カーネルからI/Fへの方向を「発信」と呼びます.
インターネットからやってくるから着信というわけではありません.ルータでpfを動かしている場合,インターネットからやってくるパケットをLANに転送することがあるでしょう.このときのパケットの流れは
- インターネット側I/Fから「着信」
- LAN側I/Fへ「発信」
となっています.
PFのルールセットの概要
FreeBSD 12の場合,pfのルールセットは/etc/pf.conf
に記述することができます.
1行にアクションとパラメータを並べて書くことで1つのルールを定義することができます.
# example pass in proto tcp from 192.168.0.0/24 to port 80 label "HTTP"
ここでは,pass
アクションが定義され,そのパラメータはin
proto tcp
from 192.168.0.0/24
to port 80
label "HTTP"
です.
ルールセットの評価順序の基本は
- 上から下へとマッチングされる
- 最後にマッチした(=マッチするルールのうち,最も下に書かれた)ルールのアクションが実行される
です.「どうしてこのルールが効かないんだ?」と思ったら,順序が悪いのかもしれません.
全体設定: ブロックポリシー
pfはパケットフィルタなので,パケットをブロックできます.
さて,ブロックをどのように行うのか?何も言わずに捨てるのか,RSTを送ってあげようか?という方針を決めるのがブロックポリシーです.
set block-policy drop
などと書くことでブロックポリシーを設定できます.自宅鯖ではdrop
にしておけばよいのではないでしょうか.
全体設定: set skip on lo0
このインターフェイスを通過するパケットはpfの処理対象外にしたい,ということがあると思います.例えばlo0
はループバックデバイスなので,勝手にフィルタしてほしくないですね.
set skip on lo0
と書くことで,処理をスキップすることができます.
良く使うルールたち
ここでは良く使う頻出ルールについて説明していきます.
ルールを「アクション」と「パラメータ」に分け,そしてさらに「パラメータ」を「条件にかかわるパラメータ」「動作にかかわるパラメータ」に分類して解説します.
アクション
「ルールがあるのはわかったよ.じゃあルールにマッチしたパケットをどうするのさ?」というのがアクションです.
自宅サーバでよく使うアクションは以下の通りです.
pass
block
scrub
それぞれについて見ていきましょう.
pass
I/Fからカーネルへ,もしくはカーネルからI/Fへと,パケットを通過させます.
pf.conf
に何も書かれていなければ,全てのパケットはpass
されます.
block
パケットを通過させません.どのように通過させないかは,先述したブロックポリシーに依存します.
単にblock
と書いた場合は,drop
ポリシーが選ばれるので,静かにパケットは捨てられ,pfはそれ以上の事はしません.
scrub
scrub
の仕事はおおまかに言えば,パケットの修復です.自宅サーバレベルでは,フラグメントしたTCPパケットを修復するといった用途が多いのではないでしょうか.
# フラグメントを修復する scrub in on <interface> all fragment reassemble
パラメータ
ここでは,アクションに付随して指定できるパラメータを説明します.
パラメータにはおおまかに,「アクションが適用される条件を指定するもの」と「アクションの動作を詳細に指定するもの」との二者があります.
また,後述するfrom ... port ... to ... port ...
を除いては,(たぶん)順序の指定はありません.from ... port ... to ... port ...
に限って,この順序で書く必要があります.
それぞれについて見ていきましょう.
条件にかかわるもの
条件にかかわるパラメータは,そのアクションが実行されるための条件を絞り込むためのものです.言い換えると,そのルールで定義された「条件にかかわるパラメータ」全てが満足されなければ,そのアクションは実行されません.
in
/ out
パケットが着信か発信かを要求できます.in
が着信で,out
が発信です.いずれも指定しない場合,どちらのパケットにもマッチします.
# 例: 全てのTCP 80番への着信パケットを許可する pass in proto tcp to port 80
on <interface>
発着信するインターフェイスが特定のものであることを要求できます.
# 例: 全てのTCP 80番への着信パケットを許可する -- ただし,em0インターフェイスを経由するものに限る pass in on em0 proto tcp to port 80
ちなみにパラメータに指定する大抵の要素は !
を使って意味を反転させることができます.
# 例: 全てのTCP 80番への着信パケットを許可する -- ただし,em0以外のインターフェイスに限る pass in on ! em0 proto tcp to port 80
inet
/ inet6
特定のアドレスファミリを要求できます.inet
はIPv4で,inet6
はIPv6です.
# 例: 全てのTCP 80番への着信パケットを許可する -- ただしIPv4のみ pass in inet proto tcp to port 80
proto <protocol>
プロトコル(icmp
/ icmp6
/ tcp
/ udp
)を1つもしくは複数指定できます.
# 例: 全てのTCP/UDP 80番への着信パケットを許可する pass in proto { tcp, udp } to port 80
from [<source>] [port <port>]
最も良く使うであろうパラメータです.パケットの出処が特定のものであることを要求します.
出処とport
指定はオプショナルで,省略すると「全ての出処」「全てのポート」を指定したのと同義です.
# 例: 192.168.0.0/24からやってくる全てのTCP着信パケットを許可する -- ポートは問わない pass in proto tcp from 192.168.0.0/24
to [<destination>] [port <port>]
これも最も良く使うであろうパラメータです.パケットの宛先が特定のものであることを要求します.
こちらも宛先とport
はオプショナルです.
# 例: 全てのTCP 80番への着信パケットを許可する -- 宛先アドレスは問わない pass in proto tcp to port 80
マシンをgatewayにするのでない限り,destは自分になるはずなのであまり考えることはありませんが,gatewayにする場合は,自分宛てでないパケットもpfを通過していくことになるので考える必要があります.
また,from
の記述の後にしかto
は書けないはずです.
all
from any to any
のシノニムです.fromもtoも書くことがないときはall
と書きます.
tagged <tag>
パケットに特定のタグ(後述)がついていることを要求できます.
# 例: "safe"タグがついているパケットは即時通過させる pass quick tagged "safe"
動作にかかわるもの
パラメータを使って,アクションの細かい挙動を修正することができる場合があります.ここではそういったパラメータを紹介します.
label <label>
ルールを分かりやすくするために,人間が読むためのラベルを付けることができます.実際のパケットフィルタリング時に顧みられることはなさそうです.
pass in proto tcp to port 80 label "全てのTCP 80番への着信パケットを許可する -- 宛先アドレスは問わない"
tag <tag>
細やかな制御を行うために,パケットにPF内部でのみ通用する印を付けて,それに合致するパケットのみを制御の対象としたい場合があります.この印をPF内部ではタグと呼びます.VLANとは関係ありません.
tag
を使うと,ルールにマッチしたパケットにはタグが付きます.
pass in proto tcp to port 80 tag "http" label "全てのTCP 80番への着信パケットを許可する -- 宛先アドレスは問わない"
タグは複数回付けることができますが,付けるたびに上書きされます.つまり,パケットは1つまでのタグしか保持しません.
log
パケットがやってきたというログを生成します.ログはpflogインターフェイスに送られます.pflogについては,ここでは省略します.
quick
このルールにマッチしたとき,残りのルールのマッチングを行わず,このルールを即時実行して終了します.
プログラミングでいうところのfast-returnだと考えると分かりやすいかもしれません.
# 例: "safe"タグがついているパケットは即時通過させる pass quick tagged "safe"
keep state
これも頻出です.pass
と組み合わせて使います.
keep state
が含まれるルールにマッチしたとき,pfは送受信者の間に「状態」を生成します.
状態テーブルに状態が記録されると,次のようなメリットを受けることができます:
- pfはパケットをルールセットにマッチングする前に状態テーブルを読むので,以降の同じコネクションに属するパケットはマッチングがスキップされ,高速にパケットが通過できる
- 帰りのパケットも同じアクションが実行されるようになるので,帰りのパケット分のルール記述が不要になる
ちなみに,pass
されるときにstateは自動的に生成されるのでkeep state
を書く必要は大抵ありません.
keep state
を書く必要があるのは,keep state
にさらにパラメータを追加する場合,またはNATを使うことでアドレスが変化している場合です.
fastroute
パケットに対して通常のルーティングを行い,転送します.
route-to <interface>
パケットを特定のI/Fに転送します.行きのパケットしか転送しないので,帰りのパケットを転送してほしいときはreply-to
を使う必要があります.
むすび
「同時に複数のI/Fやアドレスレンジを指定する」といった便利な記法がいろいろあるので,もっと知りたくなったらマニュアルを見ましょう.
NATについては,力尽きてしまったので後日書くかもしれません.