Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

PFコマンド(Packer Filter)のルールの基本を復習する

近年は野生の自宅サーバを見る機会も減ってきましたね.環境の変化によるものなのでしょうか.

さて,自宅サーバを運用するにあたって避けられないのがファイアウォール,そしてそれを扱うパケットフィルタリングです.有名どころだと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については,力尽きてしまったので後日書くかもしれません.

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?