FreeBSDでルーターを作る

# FreeBSDでルーターを作る
ここで言うのは、家庭用の所謂ブロードバンドルーターに相当するもののことです。
書いておいてなんですが、私自身そろそろ自作ルーターを使うのはやめようかなと思っているところです。とは言え、やめてしまうと作ったときのノウハウが失われてしまうので、現時点でのノウハウを書き出しておこうというのが本記事の目的です。

## ルーターの仕様
作成するルーターの仕様は以下の通り。

- 回線はNTTフレッツ光、プロバイダはOCNです。光電話は導入していません。(電話を導入するともらえるIPv6アドレスが/56のプレフィクスで来てしまうというような話があった気がします。その場合は、私の設定例ではうまく動かないかもしれません。)
- IPv4はPPPoEで接続
- IPv4アドレスはnatして、LAN側にはDHCPでプライベートIPv4アドレスを配布する
- IPv4 over IPv6は使いません(使えません)
- IPv6はIPoEを使い、ブリッジ形式にして、回線側から流れてくるRAをそのままLAN側にも流します。つまり、LAN側もそのままIPv6グローバルアドレスがつきます。

ちなみにIPv4 over IPv6を使えないのが、FreeBSDの自作ルーターをやめようかなと思っている最大の理由です。現時点(2021年4月)ではFreeBSDではMAP-Eに対応していないので...
DS-Liteなどの方式であればできる場合もあるようですが、私が使っているプロバイダでは使えないので、そちらの方式には触れません。

参考リンク：https://bokut.in/note/2020/03/g-map-e-freebsd

## ハードウェアの準備
WAN側とLAN側、ネットワークインターフェースが2つ必要です。
また、24時間365日稼働することになるので、消費電力の小さいマシンがよいでしょう。
ここはRaspberry Piでと言いたいところですが、スペック低くて使いづらいので、省電力のPC(Intel CPU)を自作してます。

## FreeBSDの設定1：アドレスの取得

何はともあれ、OSをインストールしましょう。
もうすぐ13.0が出そうな所ですが、今のところは12.2-RELEASEで良いと思います。
(とかって記事を温めている間に13.0-RELEASE出ましたね...)

OSをインストールしたら、まずはルーターにする前に単体でネットワークに接続できないとその先の作業が困ります。
そこで、さっそくPPPoEとIPoEでアドレスを取得する設定をします。

私が使っているマシンは、オンボードのre0とPCIeに増設したem0の二つのNICがあります。
re0をWAN側、em0をLAN側として利用します。
まずはONUとWAN側のNICをケーブルで接続しておきましょう。

### /etc/ppp/ppp.conf
<pre>
default:
 set log All -tcp/ip -timer -debug -physical -sync -dns
 allow users yourid      #ここは自分のログインIDで

 # PPPoE:の後ろはWAN側のNICのデバイス名を指定
 set device PPPoE:re0
 # MTU,MRUはNTT東と西で確か違うので注意。この例はNTT東日本の例
 set mru 1454
 set mtu 1454

 set cd 5
 set reconnect 3 5
 set dial
 set login

ocn:
 set server /var/run/pppctl "" 0117
 set authname yourid       #ここはプロバイダから渡されたIDとパスワード
 set authkey yourpassword
 disable ipv6cp
 add default HISADDR       # Add a (sticky) default route
 nat enable no             # NATはpfでするのでppp.confの設定としてはdisableに
</pre>

### /etc/rc.conf
以下を追加します。
<pre>
ifconfig_re0_ipv6="inet6 accept_rtadv"
ppp_enable="YES"
ppp_mode="ddial"
ppp_profile="ocn"
ppp_nat="NO"
</pre>

ここまで来たら、OS再起動でIPv4/IPv6アドレスを取得するところまで
できていると思います。
名前が引けないようであれば、/etc/resolv.confにDNSの設定が入ってないかも。
プロバイダのDNSを調べて書いておきましょう。
よく分からなかったらとりあえずはGoogleのDNSを使う手もあります。(8.8.8.8)

### /etc/resolv.conf
<pre>
nameserver プロバイダのDNS1
nameserver プロバイダのDNS2
</pre>

<pre>
$ ping www.ocn.ne.jp
$ ping6 www.ocn.ne.jp
</pre>
などやって、プロバイダのサイトに到達できることを確認します。
ちなみにこの例は12.2-RELEASEを使うことを前提に書いてますが、13.0-RELEASEからはpingとping6が統合されちゃうんだそうで。その場合、IPv4アドレスに向かってping打ちたい場合は、
<pre>
$ ping -4 www.ocn.ne.jp
</pre>
という風にやってください。

ここまできたら、このマシンはインターネットに接続できるので、
必要なパッケージをインストールしたり、freebsd-updateでセキュリティパッチを当てておきましょう。
この後必要になるパッケージは最低限、以下です。

- isc-dhcp44-server
- miniupnpd
- sshguard

## FreeBSDの設定2：ルーターにする
このままだと、このFreeBSDマシンしかインターネットに接続できないので、
次はルーターになるように設定します。
あらかじめLAN側にHUBや、無線LANルーター(ブリッジモード)を接続しておき、スマホや他のPC等が接続できるように準備しましょう。

とりあえず、LAN側のインターフェースにIPv4アドレスをつけます。
どう付けるかはお好みですが、ありがちなところで、クラスCのプライベートアドレスにします。

### /etc/rc.conf
<pre>
ifconfig_em0="inet 192.168.0.1 netmask 255.255.255.0"
</pre>

次に、WANとLANのインターフェースre0とem0でブリッジを作ります。
そしてre0ではIPv6アドレスを取得しないようにし、ブリッジbridge0で取得するよう設定を変えます。

### /etc/rc.conf
<pre>
ifconfig_re0="up"
cloned_interfaces="bridge0"
ifconfig_bridge0="addm re0 addm em0 up"
ifconfig_bridge0_ipv6="inet6 auto_linklocal accept_rtadv"
</pre>

はい、これで再起動したらIPv6ブリッジになってます。
LAN側に接続した機器にもRAが流れるので、IPv6アドレスが取得できるようになっているはずです。
しかし、このままではLAN側の機器にはIPv4アドレスがつかないので、色々不都合もあるかと思いますので、引き続き、IPv4ルーターになるように設定します。

NATとFirewallとしてpfを使用します。

### /etc/pf.conf
デフォルトでpf.confというファイルは存在しないので、作ってください。
rc.confで指定するので、pf.rulesなど別の名前でもいいです。
私が使っているものをほぼそのまま載せます。
しかし、動いているからそのまま使っているけど、よく見ると何か意味のない記述がのこっているぞ...

<pre>
internal_net="192.168.0.0/24"  # LANのアドレス
ext_if="tun0"    # 外向きのインターフェース。ここではtun0になる
ext_if6="re0"    # 外向きのインターフェース。IPv6
ext_if6br="bridge0"
int_if="em0"     # 内向きのインターフェース
host_eagle="192.168.0.3" # 内部のNASサーバ

# Tables: similar to macros, but more flexible for many addresses.
table &lt;privates&gt; { 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, 10.0.0.0/8 }
table &lt;local&gt; { 127.0.0.1, 192.168.0.1, 192.168.0.3 }
table &lt;special&gt; const { 0/8, 14/8, 24/8, 39/8, 127/8, 128.0/16, 169.254/16, \
                        192.0.0/24, 192.0.2/24, 192.88.99/24, 198.18/15, \
                        223.255.255/24, 224/4, 240/4 }
table &lt;sshguard&gt; persist
table &lt;blacklist&gt; persist file "/etc/pf-blacklist.conf"

# 各種オプション(主にタイムアウト関係)
#set timeout { interval 10, frag 30 }
set timeout { tcp.first 120, tcp.opening 30, tcp.established 86400 }
set timeout { tcp.closing 900, tcp.finwait 45, tcp.closed 90 }
set timeout { udp.first 60, udp.single 30, udp.multiple 60 }
set timeout { icmp.first 20, icmp.error 10 }
set timeout { other.first 60, other.single 30, other.multiple 60 }
set timeout { adaptive.start 0, adaptive.end 0 }
set limit { states 10000, frags 5000 }
set loginterface tun0   # ログを取る対象のIF
set optimization normal
set block-policy drop         # パケットを通さない（block）とき、
                              # RST（切断）を返さずそのまま捨てる
# Normalization: reassemble fragments and resolve or reduce traffic ambiguities.
scrub in all

# Translation: specify how addresses are to be mapped or redirected.
nat on $ext_if from $int_if:network to any -> ($ext_if) static-port
rdr on $ext_if proto tcp from any to any port 8180 -> $host_eagle port 80
rdr on $ext_if proto tcp from any to any port 8022 -> $host_eagle port 22

rdr-anchor "miniupnpd"

# Filtering: the implicit first two rules are
anchor "miniupnpd"

# 基本ルールはすべてブロック
block all

# block using blacklist
block in log quick on $ext_if proto tcp from &lt;blacklist&gt; to any
block in log quick on $ext_if proto tcp from &lt;sshguard&gt; to any port 22 label "ssh bruteforce"
block in log quick on $ext_if6br inet6 proto tcp from &lt;sshguard&gt; to any port 22 label "ssh bruteforce"
block in log quick on $int_if inet6 proto tcp from &lt;sshguard&gt; to any port 22 label "ssh bruteforce"

# ルーターから外に出るパケットはすべて許可
pass out quick on $ext_if proto tcp all modulate state
pass out quick on $ext_if proto udp all keep state
pass out quick on $ext_if inet proto icmp all keep state

pass out quick on $ext_if6 inet6 proto tcp all modulate state
pass in quick on $ext_if6 inet6 proto tcp all modulate state
pass out quick on $ext_if6 inet6 proto udp all keep state
pass in quick on $ext_if6 inet6 proto udp all keep state
pass out quick on $ext_if6 inet6 proto icmp6 all keep state
pass in quick on $ext_if6 inet6 proto icmp6 all keep state

set skip on bridge0

# 外部からのssh，http，ntpを許可する
#（ルーターでこれらのサービスを行なっている場合）
pass in on $ext_if proto tcp from any to $ext_if port 22 flags S/SA modulate state
pass in on $ext_if6 inet6 proto tcp from any to any port 22 flags S/SA modulate state
pass in on $ext_if proto tcp from any to $ext_if port 80 flags S/SA modulate state
pass in on $ext_if6 inet6 proto tcp from any to any port 80 flags S/SA modulate state
pass in on $ext_if proto udp from any to $ext_if port 123 keep state
pass in on $ext_if inet proto tcp from any to $host_eagle port {22, 80} synproxy state
pass in on $ext_if inet proto tcp from any to $host_eagle port {22, 80} synproxy state

# Allow ICMP
pass in quick on $ext_if inet proto icmp from any to any icmp-type echoreq keep state

# ルーターマシンのループバックを許可
set skip on lo0

# ルーターとLAN内部との通信はすべて許可
pass on $int_if all
</pre>

肝になる行はこれですね。
<pre>
nat on $ext_if from $int_if:network to any -> ($ext_if) static-port
</pre>
ここでnatしています。またゲーマーの方は最後のstatic-portが必須です。
これがないと、LAN内の端末で使用しているポート番号がルーターで別の番号に変換されてしまうため、ゲーム機で回線チェックをするとclosedなnatと判定されます。(技術的な用語としては正しくないと思いますが、許して。つまりはPS4等でNAT3と判定されます。)
ゲームしない人はなくてもおそらく大丈夫です。
これに加えて、miniupnpd向けの設定も入れてあります。
こちらもゲームなどで、ポート開放を自動でやる場合に必要です。
UPNPに頼らず自分で設定する方法もありますが、簡単で今のところ不都合ないので、これでやっています。こちらもminiupnpdをインストールするのを忘れずに。

以下の部分は\$host_eagleで定義したIPv4アドレスにsshとhttpサーバが立ち上がっているので、そこへのポートフォワードの設定です。このポート番号に接続すると、\$host_eagle の80番、22番にそれぞれ転送されますという設定です。
そういったサーバーを立ち上げていなければ不要ですので、無視してください。

<pre>
rdr on $ext_if proto tcp from any to any port 8180 -> $host_eagle port 80
rdr on $ext_if proto tcp from any to any port 8022 -> $host_eagle port 22
</pre>

また、sshでブルートフォースアタックされるのに備えて、sshguardの設定も入れてあります。パッケージでsshguardを入れるのを忘れないでください。
ssh使わないならsshguard以前に、閉じておくか、ポート番号を22から変えておく方が安全です。毎日ものすごい量のアタック来ますw 
IPv6アドレスから攻撃されたことはないのですが、一応こちらにもsshguardの設定を入れてあります。
また、pf-blacklist.confというファイルを作ってblockリストにしてあります。
頻繁に攻撃してくるアドレスは最初からふさいでおくに限ります。

### /etc/rc.conf
pfを有効にする設定を入れます。
また、natするようになったので、これでやっとルーターとして機能するようになります。
ちゃんとgateway_enable="YES"を入れて、パケットがインターフェースをまたげるようにしておきましょう。
<pre>
gateway_enable="YES"
pf_enable="YES"
pf_rules="/etc/pf.conf"
pflog_enable="YES"
pfstatd_enable="YES"
miniupnpd_enable="YES"
sshguard_enable="YES"
</pre>
pflogやpfstatdはなくてもいいですが、お好みで。

また、これだけだと、起動時にtun0がまだできてないためにpfでエラーになるので、pppの接続が確立した時点で、pf.confを読み直すようにppp.linkupを設定します。

### /etc/ppp/ppp.linkup
<pre>
ocn:
  !bg /usr/sbin/service pf reload
</pre>

### /usr/local/etc/miniupnpd.conf
miniupnpdはこんな設定です。
uuidはインストールしたときに自動生成されていると思います。
(多分。自分で作成した覚えがないので...)

<pre>
ext_ifname=tun0
listening_ip=em0
port=5555
enable_natpmp=yes
secure_mode=yes
system_uptime=yes
clean_ruleset_interval=600
uuid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
allow 1024-65535 192.168.0.0/24 1024-65535
deny 0-65535 0.0.0.0/0 0-65535
</pre>

さて、LAN配下のマシンをIPv4固定アドレスで使うなら、もうこれで完成したようなものですが、昨今色んなものにIPアドレス付くので、流石にそれは面倒ですよね。というわけで、最後にDHCPサーバーを構築します。

## DHCPサーバーを構築する

もう一息、DHCPサーバーを立ち上げます。
設定は、以下の通り。
このサーバーでDNSサーバを上げているので、
domain-name-serversには192.168.0.1を指定しています。
DNS上げてなければ、プロバイダのDNSを指定すれば良いと思います。
rangeは192.168.0.10 192.168.0.220としていますが、これは
固定アドレスの余地を残すためです。個人宅なら200もアドレス割り当てられれば十分でしょう。

DDNSで自動的にホスト名を登録するため、ddns-update-styleも設定していますが、プロバイダのDNSしか使わない場合は無効にしておいた方が良いと思います。
また、NTPサーバーも立ち上げているので、そのアドレスも設定しています。
WindowsとかのDHCPクライアントってNTPのフィールド認識してくれるのかしら？

### /usr/local/etc/dhcpd.conf
<pre>
option domain-name "sub.example.org";
option domain-name-servers 192.168.0.1;
option ntp-servers 192.168.0.1;
ddns-update-style interim;
update-conflict-detection false;

zone sub.example.org. {
    primary 192.168.0.1;
}

zone 0.168.192.in-addr.arpa. {
    primary 192.168.0.1;
}

default-lease-time 600;
max-lease-time 7200;
authoritative;
log-facility local7;

subnet 192.168.0.0 netmask 255.255.255.0 {
    range 192.168.0.10 192.168.0.220;
    option routers 192.168.0.1;
    option subnet-mask 255.255.255.0;
}
</pre>

後はrc.confにdhcpdを立ち上げる設定を入れておけばOKです。

### /etc/rc.conf
<pre>
dhcpd_enable="YES"
dhcpd_conf="/usr/local/etc/dhcpd.conf"
dhcpd_ifaces="em0"
</pre>

## 完成！
ここまでくればルーターとしては完成です。
内部DNSを立ち上げてLAN内のホスト名を管理するとかやるのもありですが、mdnsとか使う方が簡単なんで、今は無理に立ち上げる必要ないかと思います。一応私はこのルーター上でDNSを立ち上げているのですが、この記事では長くなるので触れません。

接続完了後、ifconfigするとこうなっています。(loなど関係ないデバイスは省略しています。)

<pre>
em0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=81009b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,VLAN_HWFILTER>
        ether xx:xx:xx:xx:xx:xx
        inet 192.168.0.1 netmask 0xffffff00 broadcast 192.168.0.255
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
re0: flags=8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> metric 0 mtu 1500
        options=8209b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,VLAN_HWCSUM,WOL_MAGIC,LINKSTATE>
        ether yy:yy:yy:yy:yy:yy
        media: Ethernet autoselect (1000baseT <full-duplex>)
        status: active
        nd6 options=29<PERFORMNUD,IFDISABLED,AUTO_LINKLOCAL>
bridge0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
        ether xx:xx:xx:xx:xx:xx
        inet6 fe80::77:xxxx::xxxx%bridge0 prefixlen 64 scopeid 0x4
        inet6 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx prefixlen 64 autoconf
        id 00:00:00:00:00:00 priority 32768 hellotime 2 fwddelay 15
        maxage 20 holdcnt 6 proto stp-rstp maxaddr 2000 timeout 1200
        root id 00:00:00:00:00:00 priority 32768 ifcost 0 port 0
        member: em0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 1 priority 128 path cost 2000000
        member: re0 flags=143<LEARNING,DISCOVER,AUTOEDGE,AUTOPTP>
                ifmaxaddr 0 port 2 priority 128 path cost 55
        groups: bridge
        nd6 options=23<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL>
tun0: flags=8051<UP,POINTOPOINT,RUNNING,MULTICAST> metric 0 mtu 1454
        options=80000<LINKSTATE>
        inet xxx.xxx.xxx.xxx --> yyy.yyy.yyy.yyy netmask 0xffffffff
        groups: tun
        nd6 options=21<PERFORMNUD,AUTO_LINKLOCAL>
        Opened by PID 863

</pre>

### 最後に
長くなりましたが、これでおしまいです。
IPv6側の方のセキュリティには若干疑問が残りますが、そこは使う人で適宜見直していただければと思います。

こんな説明じゃ分からんぞとか、書いてあるとおりに設定してみたけど、動かないとか、ここ間違ってるぞなどあればご連絡ください。
可能な範囲で対応致します。

あと、FreeBSDでもMAP-E(OCNバーチャルコネクト)対応できるよなどという話があれば、それも是非教えてください。

と、記事を温めていたら、ついに先日pfにMAP-E対応が入りました！w
https://cgit.freebsd.org/src/commit/?id=e49799dcf14e7026f377d26a70fe0a3a3d15390a

マジかよ。というわけで次はこの新機能を使ってIPv4 over IPv6に挑戦したいと思います。

つづく...