[Home] [Kuri] [Sysad] [Internet?] [Blog] [Java] [Windows] [Download] [Profile] [Flash] [+]

OpenBSD の PF でフィルタリングや NAPT, ポートフォワーディング

ようやく、ファイアウォールマシンを FreeBSD から OpenBSD に変更できました。 OpenBSD では、IP Filter ではなく、PF というものになりました。

今回は、PF を使ったフィルタリング、NAPT およびポートフォワーディングの設定方法について書きました。


目次


1. 環境

OS は OpenBSD 3.3 です。


2. フィルタリング

設定ファイル /etc/pf.conf にルールを書き、pfctl コマンドで反映させます。 設定ファイルには、1行に1つのルールを書きます。 ルールは、IP Filter にそっくりです。 そっくりですが、マクロが使えたり、ルールをひとまとめにできたりします。 こっちの方が便利です。

2.1 書式

「簡単な」書式は以下の通りです。

action in-out [log] [quick] [on interface] [af] [proto protocol] [srcdist [flags flag] [state] ]

それぞれの意味を以下に示します。

キーワード意 味 な ど
action 許可は pass, 拒否は block
in-out 入ってくる時のチェックは in, 出ていく時は out
log ログに残す場合に指定
quick 該当する場合移行のルールを見ずに適用させる場合に指定
on interface 該当するインタフェースの指定
af アドレスファミリの指定。inetinet6.
proto protocol プロトコルの指定(icmp,tcp,udpなど)
srcdist 送信元および送付先の指定 (all もしくは from object to object)
flags flag TCP のフラグ指定(check/mask)。大抵は S/SA(SYN だけならマッチ)
state 状態が保存され、ポートや ACK# 等が一致すれば許可 (keep state,modulate state)
object IPアドレスおよびポートの指定(port = 110 や port > 1023 など)

2.2 設定例

ここでは、単純に、外からから来るパケットのうち 22/tcp だけを通す設定をします。 こちらから出ていく分には、制限を設けません。
/etc/pf.conf の内容は、以下のようになります。

$lo_if = "lo0"
$ex_if = "ne1"
$ex_addr = "10.0.0.245"
block log all
pass quick on $lo_if all
pass in  quick on $ex_if proto tcp from any to $ex_addr port 22 flags S/SA modulate state
pass in  quick on $ex_if inet proto icmp from any to $ex_addr icmp-type echoreq keep state
pass out quick on $ex_if proto { udp, tcp, icmp } from any to any keep state

最初の3行はマクロです。
$lo_if は、ループバック・インタフェースです。
$ex_if は、LAN につながるインタフェースです。
$ex_addr は、$ex_if にアサインされたIPアドレスです。
マクロを使うと、環境が変わっても、ここの定義だけを書き換えればいいので、便利です。

4行目は、すべてをブロックするというルールです。 in-out を省略すると、両方が適用されます。 すべてのルールを見て、他に適用するルールがなければ、このルールが適用されます。 IP Filter と同様、デフォルトは通してしまいますので、これを書いておきます。
log をつけてあるので、これが適用されたパケットはログに残ります。

5行目は、ループバック・インタフェースに流れるパケットをすべて許可するというルールです。 quick がついていますので、該当すれば即適用されます。 チェックする必要のないインタフェースは、このように書いておけばいいと思います。

6行目は、$ex_if から来た 22/tcp 宛のパケットを許可するルールです。 これも quick がついていますので、該当すれば即適用されます。

7行目は、$ex_if から来た icmp echo request のパケットを許可するルールです。

8行目は、$ex_if から出ていく udp, tcp および icmp パケットをすべて許可するルールです。

これで、
ループバック・インタフェースを流れるパケットは無条件に許可。
外に出ていく udp, tcp および icmp のパケットは、無条件に許可。
外から入ってくるパケットのうち、22/tcp および icmp echo request は許可し、他は拒否。
という設定ができたことになります。

あとは、pfctl コマンドで適用させるだけです。

# pfctl -f /etc/pf.conf

2.3 確認

まずは、他のマシンからアクセスしてみます。
22/tcp と icmp echo request が通ることを、SSH と ping で確認します。

10.0.0.1# slogin 10.0.0.245
usu@10.0.0.245's password:
...
10.0.0.1# ping 10.0.0.245
PING 10.0.0.245 (10.0.0.245) 送信元 10.0.0.1 : 56(84) bytes of data.
64 バイト応答 送信元 10.0.0.245: icmp_seq=0 ttl=64 時間=675 マイクロ秒
64 バイト応答 送信元 10.0.0.245: icmp_seq=0 ttl=64 時間=631 マイクロ秒
64 バイト応答 送信元 10.0.0.245: icmp_seq=0 ttl=64 時間=599 マイクロ秒

--- 10.0.0.245 ping 統計 ---
送信パケット数 3, 受信パケット数 3, パケット損失 0%
Round-Trip 最小/平均/最大/mdev = 0.599/0.635/0.675/0.031ミリ秒

通らなかったら、悩んでください。
そして、それ以外がつながらないことを、以下のように確認します。

10.0.0.1# telnet 10.0.0.245
Trying 10.0.0.245... (しばらく待つ)
10.0.0.1# telnet 10.0.0.245 80
Trying 10.0.0.245... (しばらく待つ)

つながらなければ OK です。つながってしまったら、悩んでください。

ログは、/var/log/pflog に記録されます。ただしバイナリです。
tcpdump の形式なので、tcpdump で読めます。

# tcpdump -e -ttt -n -r /var/log/pflog
...
Sep 11 05:49:40.158819 rule 0/0(match): block in on ne1: 10.0.0.1.4052 > \
10.0.0.245.80: S 1414108234:1414108234(0) win 64240  (DF)
Sep 11 05:49:42.723660 rule 0/0(match): block in on ne1: 10.0.0.1.4052 > \
10.0.0.245.80: S 1414108234:1414108234(0) win 64240  (DF)

-e オプションは、マッチしたルールなどの情報を出力するためのものです。
-ttt オプションは、月日を出力するためのものです。
-n オプションは、名前の解決をしないためのものです。
-r オプションで、ファイルを指定します。
上記は、先ほど telnet コマンドで 80/tcp にアクセスしたときの不正アクセス(!?)です。


3. NAPT(IP masquerade)

NAPT の場合も、/etc/pf.conf に設定を書きます。

その前に、IP フォワーディングを有効にしておく必要があります。
コマンドラインで直接行うには、sysctl コマンドを使用します。

sysctl -w net.inet.ip.forwarding=1

OpenBSD 起動時に自動的に行わせるには、/etc/sysctl.conf に以下を追加します。

net.inet.ip.forwarding=1

3.1 書式

「簡単な」書式は以下の通りです。

nat on ext_if [af] from src_addr [port src_port] to dst_addr [port dst_port] -> ext_addr

それぞれの意味を以下に示します。

キーワード意 味 な ど
ext_if 外部向けのネットワーク・インタフェース
af アドレスファミリ。inet か inet6
src_addr 送信元の内部アドレス
src_port 送信元のポート
dst_addr 送信先のアドレス(といっても any でいいと思う)
dst_port 送信先のポート(といっても指定する必要はなさそう)
ext_addr 外部アドレス(src_addr がこのアドレスに変換される)

設定を反映させるには、フィルタリングのときと同様に pfctl コマンドを使用します。

3.2 設定例

例えば、ne0 から出ていくパケットの送信元IPアドレス 10.0.0.0/8 を、 グローバルIPアドレス 1.2.3.4 に変換するには、以下のように記述します。

nat on ne0 from 10.0.0.0/8 to any -> 1.2.3.4

ne0 にアサインされたIPアドレスが 1.2.3.4 なら、以下のようにも書けます。
# ne0 にアサインされたアドレスに変換したい場合というべきでしょうか。

nat on ne0 from 10.0.0.0/8 to any -> ne0

さらにマクロを使えば、汎用性が増します。たぶん。

$ex_if = "ne0"
nat on $ex_if from 10.0.0.0/8 to any -> $ext_if

ne0 にアサインされるアドレスが DHCP などで動的に割り振られる場合は、 括弧で囲みます。 括弧で囲むと、アドレスが変更されても新しいアドレスを使ってくれます。
# 括弧で囲まないと、pfctl で適用したときのアドレス固定になります。

$ex_if = "ne0"
nat on $ex_if from 10.0.0.0/8 to any -> ($ext_if)

3.3 確認

内部から、直接外部にアクセスしてみます。

% telnet www.yahoo.co.jp 80
Trying 210.81.150.5...
Connected to www.yahoo.co.jp.
Escape character is '^]'.
HEAD / HTTP/1.0

HTTP/1.1 200 OK
Date: Thu, 11 Sep 2003 13:59:33 GMT
P3P: policyref="http://privacy.yahoo.co.jp/w3c/p3p.xml", ...
...後略

pfctl コマンドを -s state オプションつきで実行すると、 変換の具合を見ることができます。

# pfctl -s state
...
tcp 10.0.0.11:1877 -> 1.2.3.4:64464 -> 211.14.15.5:80   FIN_WAIT_2:FIN_WAIT_2


4. ポートフォワーディング

いわずもがなですが、内部のマシンの特定のポートを、外から見えるようにできます。

4.1 書式

「簡単な」書式は以下の通りです。

rdr on ext_if proto protocol from src_addr to dst_addr port dst_port -> in_addr [port in_port]

それぞれの意味を以下に示します。

キーワード意 味 な ど
ext_if 外部向けのネットワーク・インタフェース
proto protocol プロトコルの指定(icmp,tcp,udpなど)
src_addr 送信元のアドレス(any でいいと思う)
dst_addr 送信先の外部アドレス
dst_port 送信先のポート
in_addr 内部アドレス(dst_addr がこのアドレスに変換される)
in_port 送信先のポート(dst_port がこのポートに変換される)

設定を反映させるには、フィルタリングのときと同様に pfctl コマンドを使用します。

4.2 設定例

ネットワーク・インタフェース ne0 から、 IPアドレス 1.2.3.4 の 22/tcp 宛に来たパケットを、 内部のマシン 10.0.0.245 のフォワードする設定は、以下のようになります。

rdr on ne0 proto tcp from any to 1.2.3.4 port 22 -> 10.0.0.245

プロトコルが同じであれば、複数をまとめられます。
22/tcp の他に、80/tcp, 443/tcp もフォワードする設定は以下の通りです。

rdr on ne0 proto tcp from any to 1.2.3.4 port { 22, 80, 443 } -> 10.0.0.245

さらに、マクロを使ったほうがすっきりします。(1つだけだと意味ないですが)
以下では、さらに 53/udp をフォワードしています。

$ex_if = "ne0"
$ex_addr = "1.2.3.4"
$in_addr = "10.0.0.245"
$ex_tport = "{ 22, 80, 443 }"
$ex_uport = "53"
rdr on $ex_if proto tcp from any to $ex_addr port $ex_tport -> $in_addr
rdr on $ex_if proto udp from any to $ex_addr port $ex_uport -> $in_addr

4.3 確認

外部からアクセスしてみます。
…例は割愛します。:-)


5. 全部ひっくるめた設定例

以下のような環境だとします。

              1.2.3.4(ne0)         10.0.0.245(ne1)    10.0.0.1
INTERNET ---------------- [OpenBSD] ------------------------- [Server]
           <1.2.3.0/24>                    <10.0.0.0/8>

…という条件にあう設定を作ってみました。
最初は、マクロやテーブルの設定です。

# 外向きのインタフェース、アドレス、ネットワーク
$ex_if = "ne0"
$ex_gw = "1.2.3.4"
table <ex_net> { 1.2.3.0/24 }

# 内向きのインタフェース、アドレス、ネットワーク
$in_if = "ne1"
$in_gw = "10.0.0.245"
table <in_net> { 10.0.0.0/8 }

# ループバック・インタフェース
$lo_if = "lo0"

# ホスト
$in_sv = "10.0.0.1"
$ex_sv = "1.2.3.3"

# 公開するポート
$sv_tport = "{ 22, 80, 443 }"
$sv_uport = "53"
$gw_tport = "{ 22, 25 }"

まずは、NAPT の設定を記述してみます。

nat on $ex_if from <in_net> to any -> ($ex_if)

次に、ポートフォワーディングの設定を記述してみます。

# 1.2.3.3 の 22/tcp, 80/tcp, 443/tcp を 10.0.0.1 へ。
rdr on $ex_if proto tcp from any to $ex_sv port $sv_tport -> $in_sv
# 1.2.3.3 の 53/udp を 10.0.0.1 へ。
rdr on $ex_if proto ucp from any to $ex_sv port $sv_uport -> $in_sv

そして、いよいよフィルタリングです。
ポートフォワーディングなどの設定をしている場合は、 変換後のアドレスを使う必要があります。
# IP Filter と同じですね。

# デフォルトはすべて拒否!!!
block log all

# ループバックと内部からは許可
pass quick on $lo_if all
pass quick on $in_if all

# 外部からは以下だけ許可 (他はデフォルト拒否だ!!)
pass in quick on $ex_if proto tcp from any to $in_sv port $sv_tport flags S/SA modulate state
pass in quick on $ex_if proto udp from any to $in_sv port $sv_uport keep state
pass in quick on $ex_if proto tcp from any to $ex_gw port $gw_tport flags S/SA modulate state
# ICMP echo request も許可 (気に入らなければコメントアウトしてください)
pass in quick on $ex_if inet proto icmp all icmp-type echoreq keep state

# 中から外は許可
pass out quick on $ex_if proto { udp, tcp, icmp } from any to any keep state

IP Filter よりもずいぶんすっきりします。:-)
あと、石橋を叩くなら、外部から来るパケットで、 送信元がプライベートアドレスのパケットを拒否する設定でしょうか。

table <rfc1918_net> { 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }
block in log quick on $ex_if from <rfc1918_net> to any

ただし、INTERNET との接続が PPP や PPPoE などの場合はいいのですが、 普通に Ethernet でつながっている場合、 外部インタフェースにアサインされていないアドレスを使用する際には注意が必要です。
例えば上記の場合、ne0 のエイリアスに 1.2.3.3 をアサインしておくか、 arp コマンドで設定しておく必要があると思います。
# でないと、1.2.3.3 宛のパケットが OpenBSD に届きません。

Powered by Apache PostgreSQL Usupi Logo Kuri Logo
[Home] [Kuri] [Sysad] [Internet?] [Blog] [Java] [Windows] [Download] [Profile] [Flash] [-]
usu@usupi.org Last modified : Fri Sep 12 23:16:09 2003