Ubuntu

Ubuntu20.04 UFWを使ってルーターを作る

現在、家庭用に立てたルーターは、iptablesでルールを決めて動作している
でも、UbuntuにはUFWがあるわけだから、それを使いつつルーティングだけを適切に設定するのが素直な気がする。



広告


その観点で検索してみたら上位に出てきたページがこちら。とても簡単そうに書かれている。
Qiita / Ubuntu の ufw で NAT サーバを作る

これは、是非習得したいスキルだ。ルーター自体のポートを開けるのも塞ぐのもUFWなら簡単だし。

環境

Ubuntu 20.04でルーターを構築する。

RouterSVはローカルネットワーク接続の他にPPPoE接続を確立し、インターネットに直接つながるようにする。
ローカルのWebSVは、RouterSVを使ってインターネットにサービスを提供する。

回線はFlet’sで、v6プラスオプションが付いている。v6プラスによるネットワーク接続の他に、PPPoE接続を利用することができる

サーバー名機能NICIPアドレス
RouterCaキャリアのルーター兼DNS192.168.0.1/24
RouterSVルーターens160192.168.0.11/24
ppp0接続のたびにアドレスが変わる。
インターネット接続ができる。
WebSVインターネットに公開するWebサーバー
ゲートウェイにRouterSVを設定
ens33192.168.0.77/24

WebSVは運用中のローカルネットワークと同じセグメントにいるので、他の端末達にもサービスが提供できるという手抜きっぽい設定になっている。

追記2021/09/27:
この記事ではIPv4のルーターを作りつつ、インターネットに向けてサービスを公開している。
この後、IPv6でもサービス公開している。ご参考まで。

設定

以前、iptablesで作った設定を参考にして、ルーターとしての設定をしていく。

最初にUbuntuをアップデート。

$ sudo apt update; sudo apt -y dist-upgrade; sudo apt -y autoremove
$ sudo reboot

2023/11/10 修正
インターネット側にSSHの口を開けておくので、パスワード認証は無効にしておきたいと思った。
以前、sshd_configは「後勝ち」と思い込んでその対策を書いていたのだけれど、実際には「先勝ち」だった。

これを書いた当時と違って、Ubuntu 20.04も22.04もcloud-initに関連する設定ファイルができていて、これが先に読み込まれる仕組みになっていた。
cloud-initはパスワード認証を有効にするから、sshd_configにいくらPasswordAuthentication noと書いても、パスワード認証は有効になったまま。

ということで、新しくインストールしたサーバーでパスワード認証を無効にする方法を整理してみた。
Ubuntu22.04 sshdでPasswordAuthentication noが効かない

最初にこれをやっておけば安心。

さぁ、作業開始。

タイムゾーンを設定

ログを見るときに頭の中で時間変換しなくていいのが楽なのでJSTに変更。必須ではない。

$ sudo timedatectl set-timezone Asia/Tokyo

IPアドレスの固定

ルーターなのでIPアドレスは固定。

/etc/netplan/00-installer-config.yaml

# This is the network config written by 'subiquity'
network:
  ethernets:
    ens160:
      addresses:
      - 192.168.0.11/24
      gateway4: 192.168.0.1
      nameservers:
        addresses:
        - 192.168.0.1
        search:
        - myhome.local ←ここは所属するドメインにあわせる
  version: 2

※gateway4は[RouterCA]をちゃんと指定しておかないと、PPPoEのセッション確立にとても時間が掛かってしまう。PPPoEが確立した後の宛先は、インストールすpppoeconfをインストールする時に決める。

設定変更したら、以下のコマンドで反映させる。

$ sudo netplan apply

なお、IPv6について何も設定していないが、RA(Router Advertisement)により自動構成されていた。

OSアップデートの設定

セキュリティアップデートは必要だけれども、外部からの接続は厳しく遮断するので、めったなことではやられないだろう。
ということで、自動アップデートはせずに、手動でアップデートしていく設定に。

$ sudo dpkg-reconfigure unattended-upgrades

■Configuring unattended-upgrades
システムを安全に保つためには、頻繁にアップデートを適用することが重要です。
デフォルトでは、パッケージ管理ツールを使って手動でアップデートを適用する必要があります。
また、重要なアップデートを自動的にダウンロードしてインストールするように設定することもできます。

安定したアップデートを自動的にダウンロードしてインストールしますか?

→ No

Replacing config file /etc/apt/apt.conf.d/20auto-upgrades with new version

実際には、ファイルは更新されなかった(タイムスタンプが変わっていない)。
中身はどうなっているかというと…

/etc/apt/apt.conf.d

APT::Periodic::Update-Package-Lists "0"; ←アップデートをチェックしない
APT::Periodic::Unattended-Upgrade "0";   ←アップデートをインストールしない

ということで、デフォルト値がこれだった模様。

UFWの基本設定

SSHで接続できるようにした上で有効化する。

$ sudo ufw allow from 0.0.0.0/0 to 0.0.0.0/0 port 22 proto tcp comment "SSH"
$ sudo ufw enable

※接続元をIPv4に限定している。接続元をローカルに限定するのも良いかもしれない。
※anyにせずに0.0.0.0/0にしたのはIPv4だけのルールにしたかったから。

UFWは、初期設定のままだとsyslogにブロックログを出力する。
インターネットにさらすルーターは、ブロックログが半端ないことになるので、syslogに出力しないように設定を変更。

/etc/rsyslog.d/20-ufw.conf

# Uncomment the following to stop logging anything that matches the last rule.
# Doing this will stop logging kernel generated UFW log messages to the file
# normally containing kern.* messages (eg, /var/log/kern.log)
& stop

※& stopの行頭に付いている#を外して有効行にする。

設定を反映させる。

$ sudo systemctl restart rsyslog

ログの保管期間は…1年くらいあればいいか。

/etc/logrotate.d/ufw

/var/log/ufw.log
{
    rotate 54
    weekly
…

この設定はcronから呼ばれたときに参照するので、特に反映操作はない。

PPPoE接続の確立

インターネット接続のために、PPPoE接続を確立する。

$ sudo apt install pppoeconf
$ sudo pppoeconf

セットアップ中の問い合わせに答えていく。メッセージはDeepL先生に翻訳してもらったもの。

■OKAY TO MODIFY
このプログラムを続行すると、これらの設定ファイルが変更されます。
/etc/ppp/peers/dsl-provider /etc/network/interfaces および /etc/ppp/*-secrets
はいという前に、必ずバックアップを取っておいてください。
設定を続行しますか?
→ Yes

■POPULAR OPTIONS
一般的なダイアルアッププロバイダを利用している人の多くは、
設定で「noauth」と「defaultroute」のオプションを選択し、
「nodetach」のオプションを削除しています。
設定ファイルを確認して、必要に応じてこれらの設定を変更する必要がありますか?
→ Yes

■ENTER USERNAME
通常、プロバイダーへのPPPログインに必要なユーザー名を下の
入力ボックスに入力してください。ヘルプ画面を表示したい場合は、
ユーザー名を削除してOKを押してください。
→ 契約プロバイダーのユーザーアカウント

■ENTER PASSWORD
プロバイダへのPPPログイン時に必要なパスワードを下の入力ボックスに入力してください。     
注:入力中にパスワードがプレーンテキストで表示されます。
→ パスワード

■USE PEER DNS
通常のホスト名を解決するためには、少なくとも1つのDNS IPアドレスが必要です。
通常、接続が確立されると、プロバイダーから使用可能なサーバーのアドレスが
送られてきます。これらのアドレスを、ローカルの/etc/resolv.confファイルの
ネームサーバーのリストに自動的に追加しますか?(推奨)
→ Yes

■LIMITED MSS PROBLEM
多くのプロバイダでは、MSSが1460以上のTCPパケットをサポートしていない
ルータがあります。通常、送信パケットは、デフォルトのMTUサイズ(1500)を
持つ1つの実際のイーサネットリンクを経由する場合、このMSSを持ちます。
残念ながら、他のホストからのパケットを転送している場合(マスカレードを
行っている場合)、パケットのサイズやクライアントホストへの経路によっては
MSSが大きくなり、クライアントマシンが一部のサイトに接続できなくなります。
解決策としては、pppoeで最大MSSを制限することができます。
この問題の詳細は pppoe のドキュメントを参照してください。

pppoeはMSSを1452バイトに制限すべきでしょうか?
わからなければ、「はい」と答えてください。
(もし、上記のような問題が発生する場合は、dsl-providerファイルで1412に設定してみてください)。
→ Yes

■DONE
あなたのPPPDが設定されました。起動時に接続を開始しますか?
→ Yes

■ESTABLISH A CONNECTION
これで、"pon dsl-provider "でDSL接続を行い、"poff "でDSL接続を
終了することができます。今すぐ接続を開始しますか?
→ Yes

■CONNECTION INITIATED
DSL接続がトリガーされました。plog "コマンドでステータスを確認したり、
"ip addr show ppp0 "で一般的なインターフェース情報を確認することができます。
→ OK

これで接続が完了したので、確認してみる。

$ ip addr show ppp0
3: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1454 qdisc fq_codel state UNKNOWN group default qlen 3
    link/ppp
    inet nnn.nnn.nnn.nnn peer nnn.nnn.nnn.nn2/32 scope global ppp0
       valid_lft forever preferred_lft forever

USE PEER DNSの問い合わせでYesを選択しているが、実際にはスタブリゾルバの設定はされておらず、ローカルのDNSを参照している。

恐らくはこの設定のはずで、動作しないのはちょっと不可解。
/etc/ppp/peers/dsl-provider

noipdefault
defaultroute
replacedefaultroute
hide-password
#lcp-echo-interval 30
#lcp-echo-failure 4
noauth
persist
#mtu 1492
#persist
#maxfail 0
#holdoff 20
plugin rp-pppoe.so
nic-ens160
user "契約プロバイダーのユーザーアカウント"
usepeerdns

後から考えてみると、外からの要求をルーティングするときにはIPアドレスが分かっていてDNATするし、サーバーも要求のあったIPアドレスに返事をしようとするわけだから、名前解決はローカルで行った方が都合が良いかもしれない。

さて、ppp0はリンクアップしたらDynamicDNSサービスにIPアドレスを伝えたい。接続後に実行されるスクリプトを作る。

/etc/ppp/ip-up.d/6ddns

#!/bin/sh
#実行したいコマンド
exit 0

保存後、実行権限を付けておく。

PPPoE切断時のケア

2023/09/24 追記
再起動時になかなかPPPoEの接続が確立しない。

/var/log/syslog

Sep 24 14:46:03 hostname pppd[894]: Timeout waiting for PADO packets
Sep 24 14:46:03 hostname pppd[894]: Unable to complete PPPoE Discovery
Sep 24 14:47:09 hostname pppd[894]: Timeout waiting for PADO packets
Sep 24 14:47:09 hostname pppd[894]: Unable to complete PPPoE Discovery
Sep 24 14:48:14 hostname pppd[894]: Timeout waiting for PADO packets
Sep 24 14:48:14 hostname pppd[894]: Unable to complete PPPoE Discovery
Sep 24 14:48:49 hostname pppd[894]: PPP session is 17975
Sep 24 14:48:49 hostname pppd[894]: Connected to nn:nn:nn:nn:nn:nn via interface ens160
Sep 24 14:48:49 hostname pppd[894]: Using interface ppp0
Sep 24 14:48:49 hostname pppd[894]: Connect: ppp0 <--> ens160

遅すぎるなと思ってどうにかできないか探っていたところ、海外のルーターのQ&Aに切断処理をしてみれば?と書かれているのを発見。
そういわれてみれば、これはすぐに接続が確立できる。

$ sudo poff
$ sudo pon dsl-provider

このことから、シャットダウン時にpoffコマンドを実行するように仕掛けてみた。
Go Linux Cloud / How to run script with systemd right before shutdown in Linux

/etc/systemd/system/my-shutdown-script.service ※新規作成

[Unit]
Description=Run scripts at shutdown
DefaultDependencies=no
Before=shutdown.target

[Service]
Type=oneshot
ExecStart=/root/my-shutdown-script.sh
TimeoutStartSec=0

[Install]
WantedBy=shutdown.target

/root/my-shutdown-script.sh ※新規作成

#!/bin/bash
poff

スクリプトに実行権限を付けて、サービスを有効にする。

$ sudo chmod +x /root/my-shutdown-script.sh
$ sudo systemctl enable my-shutdown-script.service

このやり方が正しいのかどうか分からないが、少なくとも再起動したときに、すぐに接続が確立できるようになった。

カーネルパラメーターでパケット転送を有効化

カーネルにパケット転送することを伝える。
一般には、/etc/sysctl.confで設定するのだけれど、今回はUFWでルーターを設定しようとしている。

/etc/ufw/sysctl.conf

# Uncomment this to allow this host to route packets between interfaces
net/ipv4/ip_forward=1
#net/ipv6/conf/default/forwarding=1
#net/ipv6/conf/all/forwarding=1

※ipv4の行頭についている#を外して有効行にする。

設定を反映させて、状態を確認する。
@IT / カーネルパラメータの値を調べるには

$ sudo ufw reload
$ sudo cat /proc/sys/net/ipv4/ip_forward
1

これは恒久設定になっていて、再起動してもちゃんと反映されている。

これで転送を有効化できるのだが、やっぱり転送や~めたとなった場合、再度コメントにしてUFWをreloadしても戻らなかった。
サーバーを再起動するか、以下のコマンドで転送を止める。

$ sudo sysctl net.ipv4.ip_forward=0
net.ipv4.ip_forward = 0
$ sudo cat /proc/sys/net/ipv4/ip_forward
0

UFWによる転送設定(outbound)

ローカルからインターネットに出るための設定をしていく。

IP Masquerade

内側にいるホストからインターネットへの転送をするにあたり、IP Masqueradeする。

/etc/ufw/before.rules

…
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT

# NAT rules
*nat
:POSTROUTING ACCEPT [0:0]
-F
-A POSTROUTING -s 192.168.0.0/24 -o ppp0 -j MASQUERADE
COMMIT

※赤文字部分を追加。

POSTROUTINGの[0:0]指定は省略することができ、その場合は、カウンターが0クリアされない。

-Fオプションは、iptablesコマンドではチェーンを初期化するために指定する。
最初に示した参照先で教えてくれている手法で、実際にやってみたところ、

  • 初期状態ではこのチェーンには何も設定されていないこと。
  • ufw disable→ufw enableで同じルールが追加されてしまうこと。
  • ufw reloadで同じルールが追加されてしまうこと。

が確認できたので、教えてもらったとおりに設定している。

設定を反映させて確認する。

$ sudo ufw reload
$ sudo iptables -t nat --list-rules
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-A POSTROUTING -s 192.168.0.0/24 -o ens160 -j MASQUERADE

転送許可

カーネルパラメーターを転送可能にしても、それだけではパケットは転送されない。
一般にはデフォルトの転送ポリシーをACCEPTにして転送させるようだけれども、以前の設定を踏襲してポリシーはDROPのままとして、転送ルールを追加してみた。

具体的には、ローカルから外に出るものを許可する(135,137:139,445を除く)。

$ sudo ufw route deny in on ens160 out on ppp0 to 0.0.0.0/0 port 135,137:139,445 proto tcp
$ sudo ufw route deny in on ens160 out on ppp0 to 0.0.0.0/0 port 135,137:139,445 proto udp
$ sudo ufw route allow in on ens160 out on ppp0 to 0.0.0.0/0

$ sudo iptables -t filter --list-rules ufw-user-forward
-N ufw-user-forward
-A ufw-user-forward -i ens160 -o ppp0 -p tcp -m multiport --dports 135,137:139,445 -j DROP
-A ufw-user-forward -i ens160 -o ppp0 -p udp -m multiport --dports 135,137:139,445 -j DROP
-A ufw-user-forward -i ens160 -o ppp0 -j ACCEPT

※順序が重要。delete→insertするか、/etc/ufw/user.rulesの中を並べ替えてreloadするか、どうにかしてそろえる。

外部から送られてくるパケットのうち、接続済み、または、接続済みセッションに関連するものについては通過を許可したい。
確認してみたところ、その設定は標準で入っていた。

$ sudo iptables --list-rules ufw-before-forward
-N ufw-before-forward
-A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A ufw-before-forward -p icmp -m icmp --icmp-type 3 -j ACCEPT
-A ufw-before-forward -p icmp -m icmp --icmp-type 11 -j ACCEPT
-A ufw-before-forward -p icmp -m icmp --icmp-type 12 -j ACCEPT
-A ufw-before-forward -p icmp -m icmp --icmp-type 8 -j ACCEPT
-A ufw-before-forward -j ufw-user-forward

※以前は-m state –state NEW,ESTABLISHEDという条件を書いていたが、ローカルでルーターが1つしかないような環境では判断できない場合があり、conntrackの方がより正確に判断できるそうだ。

ここまでの設定で、インターネットへのアクセスができるルーターとして動作する。

UFWによる転送設定(inbound)

インターネットにサービス公開するための設定をしていく。

NAT

ppp0に到着した80/tcp、および、443/tcpへのパケットの宛先を、[WebSV]に書き換える。
先程書いたルールへの追記になる。

/etc/ufw/before.rules

…
# don't delete the 'COMMIT' line or these rules won't be processed
COMMIT

# NAT rules
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-F
-A PREROUTING -i ppp0 -p tcp --dport  80 -j DNAT --to-destination 192.168.0.77
-A PREROUTING -i ppp0 -p tcp --dport 443 -j DNAT --to-destination 192.168.0.77
-A POSTROUTING -s 192.168.0.0/24 -o ppp0 -j MASQUERADE
COMMIT

※赤文字部分を追記。

PREROUTINGの[0:0]指定は省略することができ、その場合は、カウンターが0クリアされない。

複数ポートを一括で指定する場合の書き方はこれ。

-A PREROUTING -i ens160 -p tcp -m multiport --dports 80,443 -j DNAT --to-destination 192.168.0.77

設定を反映させる。

$ sudo ufw reload
$ sudo iptables -t nat --list-rules
-P PREROUTING ACCEPT
-P INPUT ACCEPT
-P OUTPUT ACCEPT
-P POSTROUTING ACCEPT
-A PREROUTING -i ppp0 -p tcp -m tcp --dport 80 -j DNAT --to-destination 192.168.0.77
-A PREROUTING -i ppp0 -p tcp -m tcp --dport 443 -j DNAT --to-destination 192.168.0.77
-A POSTROUTING -s 192.168.34.0/24 -o ens160 -j MASQUERADE

設定を見直すと、-m tcpが付与されている。
試しに、-m tcpだけを設定してみたところ、-p tcpが付与され、その後にufw reloadするとエラーになった。
何故こうなるのかというところは分からなかったが、セットで設定するものなのだろう。気にしない。

転送許可

宛先を[WebSV]に書き換えても、転送のデフォルトポリシーはDROPのため、ブロックされる。
filterテーブルの設定がちょっとめんどくさくなっているが、きちんとガードが掛かった設定になっているのだ。

ということで、ppp0からens160を経由した[WebSV]への転送を許可する。

$ sudo ufw route allow in on ppp0 out on ens160 to 192.168.0.77 port 80 proto tcp
$ sudo ufw route allow in on ppp0 out on ens160 to 192.168.0.77 port 443 proto tcp

$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       192.168.0.0/24             # SSH

135,137:139,445/udp on ens160 DENY FWD    Anywhere on ppp0
135,137:139,445/tcp on ens160 DENY FWD    Anywhere on ppp0
Anywhere on ens160         ALLOW FWD   Anywhere on ppp0
192.168.0.77 443/tcp on ens160 ALLOW FWD   Anywhere on ppp0
192.168.0.77 80/tcp on ens160 ALLOW FWD   Anywhere on ppp0

複数ポートを一括で指定する場合の書き方はこれ。

$ sudo ufw route allow in on ppp0 out on ens160 to 192.168.0.77 port 80,443 proto tcp

IPアドレスの固定(WebSV)

サービス公開する側のIPアドレスも計画に沿って固定する。

/etc/netplan/00-installer-config.yaml

# This is the network config written by 'subiquity'
network:
  ethernets:
    ens33:
      addresses:
      - 192.168.0.77/24
      gateway4: 192.168.0.11
      nameservers:
        addresses:
        - 192.168.0.1
        search:
        - myhome.local ←ここは所属するドメインにあわせる
  version: 2

※gateway4として、立てた[RouterSV]のIPアドレスを設定。[RouterSV]はDNS機能を提供していないので、DNSは[RouterCA]をそのまま使う。

設定変更したら、以下のコマンドで反映させる。

$ sudo netplan apply

これで[WebSV]のサービスをインターネットに公開できるようになった。

UFWについて

UFWの構成を知ることは、安全な設定をしていく上で重要なのではないかと思う。
iptablesをラッピングしている訳なので、後でiptablesについても整理する。

設定ファイル

どんな構成で動いているのかを知っておけば、どこに手を入れるべきなのかが分かるのかなと。

ファイル概要備考
/etc/default/ufw管理者やエンドユーザーが変更するパラメーターが定義されている。
ここで、基本動作を変更することができる。
ask ubuntu / What is the purpose of /etc/default?
/etc/ufw/ufw.confスタートアップ時に有効にするかどうか等の基本設定。ログレベルの指定項目もあった。
/etc/ufw/sysctl.confネットワーク変数を設定する。
/etc/sysctl.confや/etc/sysctl.d配下の設定を上書きすることに注意。
UFWに関連するカーネルパラメーターが設定できる。
net/ipv4/ip_forward=1を有効にて、ufw reload すると反映された。
再起動後も反映される恒久設定になる。
/etc/ufw/before.initufw初期化前に呼び出されるスクリプト。ufwではなく、ufw-initにより呼び出される。
UFWより早く何かをしたいときに中身を書けば良さそう。
/etc/ufw/before.rules
/etc/ufw/before6.rules
ufwコマンドで追加したルールの前に実行されるルール。iptables.restoreの文法が使われる。
/etc/ufw/user.rules
/etc/ufw/user6.rules
ufwコマンドで追加したAccept, Deny, Drop等のルール。ココに直接ルールを書いて ufw reload すると反映された。
/etc/ufw/after.rules
/etc/ufw/after6.rules
ufwコマンドで追加したルールの後で実行されるルール。
/etc/ufw/after.init ufw初期化後に呼び出されるスクリプト。 ufwではなく、ufw-initにより呼び出される。
UFW起動後に何かをしたいときに中身を書けば良さそう。

チェーン構成

どのようなチェーン構成になっているのか、何もルールを追加していない状態(sshの許可すらしていない)でfilterテーブルを見てみた。

$ sudo ufw enable
$ sudo iptables -t filter --list
…
Chain ufw-before-input (1 references)
target     prot opt source               destination
ACCEPT     all  --  anywhere             anywhere ← これはローカルループバックを許可している。
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
…

※SSHが切断されていないのは、確立している通信は許容される設定が入っているからかな。

見てみるとかなりのチェーンが定義されている。一部を書き出してみる。

チェーンデフォルト
ポリシー
ジャンプ先ジャンプ先2
INPUTDROPufw-before-logging-input ※未定義
ufw-before-inputufw-user-input
※コマンドで追加したルールが入る
ufw-after-inputufw-skip-to-policy-input
※/etc/default/ufwで定義されたデフォルトポリシーに書き換えられる
ufw-after-logging-input ※未定義
ufw-reject-input ※未定義
ufw-track-input
FORWARDDROPufw-before-logging-forward
ufw-before-forward
ufw-after-forward
ufw-after-logging-forward
ufw-reject-forward
ufw-track-forward
省略
OUTPUTACCEPTufw-before-logging-output
ufw-before-output
ufw-after-output
ufw-after-logging-output
ufw-reject-output
ufw-track-output
省略

たくさんのチェーンが作られてそこにジャンプするようになっているんだけれど、ufw-after-inputでデフォルトポリシーが適用されて判定が終わってしまうのではないかと…まだ理解し切れていないということなのだろう。

変更の反映

設定を反映させるには、以下の方法が使えた。

$ sudo ufw reload

設定を失敗するとreloadに失敗し、UFWは動作していない状態になる。
その場合には設定を修正して、UFWを有効化する。

$ sudo ufw enable

設定が正しければ、起動して保護された状態になる。

状態の確認

状態を確認するのには、いつも通りこのコマンドが使える。

$ sudo ufw status

ただ、今回natテーブルも編集しているので、iptablesの状態を見るのに、以下が使えた。
DigitalOcean / Iptablesファイアウォールルールを一覧表示および削除する方法

$ sudo iptables -t nat --list       ←構造がわかりやすい
$ sudo iptables -t nat --list-rules ←ルールが詳細に表示される

iptablesについて

Linuxカーネル2.4以降では、netfilterというパケット処理のためのフレームワークをもっており、これの設定を操作するツールがiptablesである。

ウィキペディア iptables

恥ずかしながら、このことを知らなかった。iptablesという仕組みで保護しているのだとばかり…

netfilter

netfilterはLinuxカーネルによって提供されるフレームワークであり、パケットフィルタリング、ネットワークアドレス変換、および、ポート変換のための様々な機能と操作を提供する。ネットワークを介してパケット送信したり、機密性の高い場所へのパケット到達を禁止したりする機能がある。

Wikipedia Netfilter

パケットが入ってくると、カーネル内で一連のステップを経てアプリケーションに渡されるか、別のホストに転送される。
その「一連のステップ」でやることを追加・変更・削除するために、iptablesコマンドを利用する。

netfilterのドキュメントはこちらにまとめられている。
netfilter / Documentation about the netfilter/iptables project

一番最初にあるチュートリアルが、なんというか色々と教えてくれるというか、全部?なのというくらいに色々書いてくれているので、理解しておきたい。
日本語訳してくれているサイトがあるので併せてリンクを記載。
netfilter / The iptables tutorial by Oskar Andreasson
Stray Penguin / Iptablesチュートリアル 日本語訳

テーブルとチェーン

ウィキペディアのページに張ってある絵をよく見ると、テーブルとチェーンのことが書かれている。
テーブルにはそれぞれの特徴があり、役割が決まっている。
また、各テーブルのPREROUTINGチェーンが raw → mangle → nat の順で呼び出されていることが分かる。

ubuntu manpageでは、5つのテーブルが書かれている。
チュートリアルとマニュアルをみながら、テーブルにある組み込みチェーンを整理してみた。

テーブルチェーン機能
filterパケットフィルタリングを行う。
FORWARD外からやってきて外へ出て行くパケットを処理する。
INPUTローカルに向かってきたパケットを処理する。
OUTPUTローカルで作られたパケットを処理する。
natネットワークアドレス変換に利用する。
このテーブルでフィルタリングをしてはいけない。
PREROUTINGパケットが入ってきたらすぐに変換する。
OUTPUTローカルで作られたパケットをルーティング前に変換する。
POSTROUTINGパケットがファイアウォールを出て行く直前に変換する。
mangleパケットの内容やヘッダを書き換え、他のルールやルーティングで活用する。
NAT/Masqueradeはできない。
PREROUTINGパケットがやってきてルーティングされる前に改変。
POSTROUTINGすべてのルーティング判定後に改変。
OUTPUTローカルで作られたパケットをルーティング後に改変。
INPUTパケットがローカルに入ってきて、プログラムに渡される前に改変。
FORWARDルーティング判定の途中で改変。
rawNOTRACK(追跡しない)にする。
PREROUTING外から入ってきたパケットに一番最初に変更を掛けられる。
OUTPUTローカルで作られたパケットに一番最初に変更をかけられる。
securityMandatory Access Controlのネットワークルールで使用する。
filterの後で呼び出される。
MACはSE-Linux等で実装されているようだが…Ubuntuで使えるのか不明。
INPUTローカルに向かってきたパケットを変更する。
OUTPUTローカルで作られたパケットを変更する。
FORWARD外からやってきて外へ出て行くパケットを変更する。

テーブルには、ユーザー定義チェーンを追加することができ、以下の特徴がある。

  • Jumpできる先はユーザー定義チェーンに限られる。組み込みチェーンにJumpすることはできない。
  • デフォルトポリシーは指定できない。
  • ユーザー定義チェーンの終端まで達すると、Jump元に戻される。

iptables-restore文法

この文法を正確に記しているドキュメントが見つからない。チュートリアルから読み解くと…

# コメント
*テーブル名
:チェーン名 チェーンポリシー[パケットカウンタ:バイトカウンタ]
ルールを書く
COMMIT

コメント

行頭に # を書くことでコメントとして扱われる。

e.g.

# a few comments.

テーブル

先頭に * を書き、空白を開けずにテーブル名を書く。

e.g. filterテーブルについて書くことを示す。

*filter

複数のテーブルをいっぺんに書き上げてCOMMITすることはできない。
複数のテーブル名をつなげて書き、チェーンを書くことまではできたが、ルールを書いたところでiptables-restoreがエラーを返すようになったため。

チェーン

先頭に : を書き、空白を開けずにチェーン名、チェーンのデフォルトポリシー、パケットカウンタ、バイトカウンタの順で書く。
ルールを書きたいチェーンを連続して指定できる。

e.g. 組み込みチェーンFORWARDとufw-before-forwardについて書くことを示す。

:FORWARD DROP [0:0]
:ufw-before-forward - [0:0]

デフォルトポリシーを持つことができるのは組み込みチェーンだけなので、ユーザー定義チェーンのポリシーは – (ハイフン)で無指定になる。

パケットカウンタとバイトカウンタについて、統計情報を知りたいときに値を取り出して使うことができるとされている。
[0:0]を指定することによってゼロクリアされ、指定しない(書かない)ことでカウンタ値は消されずに保持される。

ルール

ルールはiptablesコマンドのパラメーターを書くイメージ。
テーブル指定は最初にしているので、-t tablenameは書かない。

e.g. 自宅のプリンターがネットワークに飛ばすIGMPメッセージがログに出力されないようにDROPする。

-A ufw-before-input -p igmp -m mac --mac-source nn:nn:nn:nn:nn:nn -j DROP

※MACアドレスはマスクしている。

iptablesコマンドであれば、シェルで変数を使ってわかりやすくしたり、動的に値を設定したりできるが、この文法ではそれが許されない。
これはデメリットになるかもしれないが、どうしても動的に値を設定したいのなら、ufwの初期化時に呼び出されるスクリプトの中で書き換えれば良さそうだ。

確定

定義を確定させる。

COMMIT

COMMITしなくても ufw enable であれば動作するし、ルールも入っていた。
しかし、ufw reload ではエラーが発生する。

ルールの変更は ufw reload で正しく書かれていることを確認するのが良さそうだ。

起きたこと

ルーターの問題じゃない その1

実は、WebSVを直接インターネットにさらしていて、今回そこにルーターを挟み込んだのだが、WebSVの応答が不安定だった。

テスト用の回線1では、遅いけど応答する。
テスト用の回線2では、タイムアウトする。

pppoeconfをアンインストールし、デフォルトゲートウェイも変更したはずだったが…変更前のデフォルトルートが最優先で残されており、それは存在しないIPアドレスであった。

ip route delでルートを消そうとしたが消えない…ということで、再起動して問題解消。

どんなルートだったか記録を取っていなかった。
もう再現できないと思うので、中途半端なTipsとなってしまった。

ルーターの問題じゃない その2

ローカルエリアにサブネット作って、NAT動作を確認してみたら、通信が確立しない。
pingは通るのにパケットが戻ってこない…

$ ip route
default via 192.168.0.77 dev ens33 proto static
192.168.0.0/24 dev ens33 proto kernel scope link src 192.168.0.1

これではサーバーがルーターにパケットを戻せないので、戻るルートを伝える。

$ sudo ip route add 192.168.1.0/24 via 192.168.0.11
$ ip route
default via 192.168.0.77 dev ens33 proto static
192.168.0.0/24 dev ens33 proto kernel scope link src 192.168.0.1
192.168.1.0/24 via 192.168.0.11 dev ens33

サーバー側もパケットを戻す先を意識しておかなきゃだわ。

複数プロトコルの一括指定

プロトコルを複数指定するためには、新しいチェーンを作り、そこで例えばtcpとudpを許可する。
その上で、判断したいパケットをそのチェーンにジャンプして判断させればよいらしい。
StackExchange / Reduce firewall rules by half – one iptables rule for tcp and udp

これはiptablesでルーターを書いている場合にかっこいい解決方法だが、ufwでルーターを作る場合にはチェーンを操作することは難しそうで、個別に指定するのが素直に思える。

やったこと

sedはmanpageを見てもよく分からなかったのだが、完全マニュアルは他にあって、それを日本語訳してくださっていた
ようやくこんな感じ?で使えそうだと分かった。

$ ip addr show ppp0 | grep "inet " | sed -e 's/ \+inet \([0-9\.]\+\)[\/ ].\+$/\1/'

これが取れると何がいいかというと…ppp0のIPアドレスが、DynamicDNSに登録されているIPアドレスと一致するかどうかを確認できること。
違っていたらDynamicDNSに新しいIPアドレスはこれです!とお知らせすることができるようになる。

さいごに

UFWを使えばルーター構築が超簡単になるんじゃね?と思ったけれども、実際そんなに難しくはないけれども、ちゃんと確かめようとするとそれなりに時間は掛かる。だから、もう一度調べ直さずに済むようにこのメモを作っているわけだけれども…

記事が長い。

LTSのサポート期限が切れたとき、またこの記事が使えるのかどうか。
4年後くらいに分かる。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他

  1. オッサン より:

    素晴らしい記事 消さないで欲しい

    • rohhie より:

      ど、どういう意味の素晴らしいでしょうか?(笑)

      額面通りに受け取って、感謝の意を表したいと思います。
      コメントしていただき、ありがとうございました。

      お気付きの点がありましたら、ぜひコメントをお願い致します。