春雨日記 about me tags

我が家ではeo光のインターネット回線を利用しており,ONUはeo-RT100を使用しているのですが,LAN内からグローバルIPv4アドレスにアクセスするとポート転送が動作しないという問題がありました.

つまりブラウザがv4を選ぶかv6を選ぶかによってランダムにルーターの管理画面が表示される状況に.

そこでIPv4はルーティングを行い,IPv6はブリッジを行うルーター(ブルーターと言うらしい)を作成し,v4パケットをサーバーのローカルIPに転送することで解決を図ります.

前提

必要な物

  • NIC2個
  • 適当なサーバー(省電力の物が望ましい)
  • (要るなら)L2スイッチ
  • ebtablesが利用できるディストリ

ルーターとして新たに常時稼働の製品を増やす事になるため,市販のルーターに負けないぐらい省電力なサーバーが望ましいです.

というわけで…

HP t630を購入して役割を失っていたWyse 3040に再度役立ってもらう事に.

ルーターとして動作させて計測した所,電源ラインでtyp. 1A,max 1.5Aといった感じですので安心してつけっぱなしにできます.

NICは内蔵1つしかないのでメルカリで買った激安USB3.0ドングルを追加してます.実測800Mbpsぐらい出てるのでまあ困らないです.

なお,Linuxを利用しますが古い目のディストリの方がいいかもしれません.

というのも,最近のディストリではnftablesへの移行が進められており,ArchではebtablesがすでにAUR行きになっています.

なので今回はUbuntu 20.04を利用します.やUbuntu神

適当にUbuntuセットアップ

インストーラーに従ってすすめるだけですが,MMCに対応していないのか?パーティショニングがちょっと変でした.

なのでCtrl+Alt+F2で仮想コンソールに入ってcgdiskで設定しました.WyseのEFIはcgdiskでMMCに作ったESPを普通に認識してくれます.

インストール後,正常に起動する事が確認できたら,いつもの儀式を行います.

1
2
3
4
5
6
apt purge netplan.io
# netplanと同時にcloud-initが消えてくれる
apt purge snapd
# 不要なコンテナが消えてすっきり
apt purge ufw
# ファイアウォールはfirewalldでやるので

これでps axがだいぶエレガントになります.

Ubuntu Server Coreみたいなバリアントが欲しいです…

必要なパッケージを入れる

apt install isc-dhcp-server firewalld ebtables 

もしかしたらまだあるかも

ネットワーク設定

netplanが消えているのでいつもどおりsystemd-networkdを利用します.

私の設定はこんな感じ

/etc/systemd/network/enp1s0.network

[Match]
Name=enp1s0

[Network]
Bridge=br-home
Address=192.168.1.15/24
Gateway=192.168.1.1
DNS=1.1.1.1

/etc/systemd/network/enx.network

[Match]
Name=enx*

[Network]
Bridge=br-home

/etc/systemd/network/br-home.netdev

[NetDev]
Name=br-home
Kind=bridge

[Bridge]
# 重要:無いと一部のv6パケットが破棄される
MulticastSnooping=false

/etc/systemd/network/br-home.network

[Match]
Name=br-home

[Network]
IPv6AcceptRA=false
Address=10.1.10.1/24

[Route]
Gateway=10.1.10.1
Destination=10.1.10.0/24

NIC2つともブリッジしてええのかと思うかもしれませんが,ebtablesによって分離するため問題ありません.

ただし,設定するまでネットに繋がらなくなるため先にDHCPでパッケージを入れてからこの設定を流し込むのがおすすめ.

1
2
systemctl restart systemd-networkd
systemctl enable systemd-networkd

ブルーターへ

1
update-alternatives --config ebtables

ebtables-legacyに変更

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
systemctl enable --now firewalld

# ゾーン設定
firewall-cmd --add-interface=enp1s0 --zone=public --permanent
firewall-cmd --add-interface=br-home --zone=home –permanent
# LANでDHCP許可
firewall-cmd --add-service=dhcp --zone=home –permanent
# マスカレード
firewall-cmd --add-masquerade --zone=home --permanent
# NAPT
firewall-cmd --permanent --direct --add-rule ipv4 nat POSTROUTING 0 -o enp1s0 -j MASQUERADE
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i br-home -o enp1s0 -j ACCEPT
firewall-cmd --permanent --direct --add-rule ipv4 filter FORWARD 0 -i enp1s0 -o br-home -m state --state RELATED,ESTABLISHED -j ACCEPT

# これが肝,ebtablesのbrouteテーブルにてBROUTINGを設定.
# ebtablesでのDROPはルーティングに回すという意味があり,ipv6以外はルーティングを行うようになる.
# よって同じブリッジに属しながら別のv4アドレスを持てる.
firewall-cmd --permanent --direct --add-rule eb broute BROUTING 0 -i enp1s0 -p ! ipv6 -j DROP
firewall-cmd --reload

firewalldはiptablesの保存ツールになってもらいます笑

パケット転送の設定

IPv4でルーティングが行えるようになったので転送の設定を行います.

とはいえ,eo光のような環境ではグローバルv4アドレスが変わるのでpermanentな設定にするのは適切ではありません.

そこでランタイムルールに設定し,1時間毎に再設定するようにします.

シェルスクリプトを作成

/usr/local/bin/natloopback.sh

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash
#DNSからAレコードを取得しdiffにて前回のアドレスと比較
dig -t A dynamic.haru3.me +short > /tmp/iptest.b.txt
diff /tmp/iptest.a.txt /tmp/iptest.b.txt
if [ $? -eq 0 ]; then
    echo "IP is same"
    rm /tmp/iptest.b.txt
    exit 0
fi
rm /tmp/iptest.a.txt
mv /tmp/iptest.b.txt /tmp/iptest.a.txt
GLOBAL_IP=$(cat /tmp/iptest.a.txt)

#サーバーのLAN内IP
export LAN_IP=192.168.1.13
#Bridgeに割り当てられたIP(NATのSourceになる物)
export BR_IP=192.168.1.15

echo "Global IP:" $GLOBAL_IP
echo "Server IP:" $LAN_IP
echo "Internal IP:" $BR_IP

#既存のランタイムルールを破棄
firewall-cmd --reload
#WANのIPに向けたパケット(宛先ポート:80,443)をLAN_IPに転送(DNAT)
firewall-cmd --direct --add-rule ipv4 nat PREROUTING 0 -m tcp -p tcp --dst $GLOBAL_IP --dport 80 -j DNAT --to-destination $LAN_IP:80
firewall-cmd --direct --add-rule ipv4 nat PREROUTING 0 -m tcp -p tcp --dst $GLOBAL_IP --dport 443 -j DNAT --to-destination $LAN_IP:443
#転送されたパケットは送信元ホストがそのままになっているためNAPTの出口アドレスに書き換え(SNAT)
firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -m tcp -p tcp --dst $LAN_IP --dport 80 -j SNAT --to-source $BR_IP
firewall-cmd --direct --add-rule ipv4 nat POSTROUTING 0 -m tcp -p tcp --dst $LAN_IP --dport 443 -j SNAT --to-source $BR_IP
#転送されたパケットの通信を許可
firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -m tcp -p tcp --dst $LAN_IP --dport 80 -j ACCEPT
firewall-cmd --direct --add-rule ipv4 filter FORWARD 0 -m tcp -p tcp --dst $LAN_IP --dport 443 -j ACCEPT
#正常終了
exit 0
1
chmod 755 /usr/local/bin/natloopback.sh

systemdユニットを作成 /etc/systemd/system/natloopback.service

[Unit]
Description=nat loopback
RefuseManualStart=no
RefuseManualStop=yes
After=network.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/natloopback.sh

/etc/systemd/system/natloopback.timer

[Unit]
Description=Run natloopback hourly

[Timer]
OnCalendar=hourly
Persistent=true    
 
[Install]
WantedBy=timers.target
1
systemctl enable --now natloopback.timer
1
journalctl -u natloopback.service

で正常そうなログが出ていたらOK.

DHCPサーバー

正直書くまでも無くねと思いますが…

/etc/dhcp/dhcpd.conf

# 最初の方
option domain-name "internal.haru3.me";
option domain-name-servers 192.168.1.1, 1.1.1.1;

# 省略

# This is a very basic subnet declaration.

#コメントアウト&変更
subnet 10.1.10.0 netmask 255.255.255.0 {
  range 10.1.10.50 10.1.10.200;
  option routers 10.1.10.1;
}

/etc/default/isc-dhcp-server

INTERFACESv4="br-home"
1
2
3
systemctl restart isc-dhcp-server
systemctl enable isc-dhcp-server
systemctl disable --now isc-dhcp-server6

ここまでできてたらbr-homeに接続した機器に適当なIPが振られて通信できるはず.

Overlayfs (Optional)

ルーターは電源ブチ切りしても壊れてはいけないのでシステムをroでマウントし,ログ等はoverlayfsに乗るようにします.

Ubuntuだとoverlayrootという便利ツールが標準装備ですのでちょっと設定変えるだけでOK.

/etc/overlayroot.local.conf

overlayroot="tmpfs"

再起動すると反映されます.

おわりに

このブルーターはPocket WiFiをWANとした環境でも使えるはずなので簡易NASがついにIPv6対応できそうです.

しかしネットにこの手の情報少なすぎませんか?みんなLinuxでIPv6扱わないのかな.

nftablesオンリーになったらいよいよ打つ手が無くなりそうなのですが…

まあ適当なエンプラルーター用意するほうが全てにおいて良さそうな気もしなくないですが.

2022/7/7追記

環境によってはIPv6パケットがフィルタに邪魔される場合があります.

私の場合,/etc/sysctl.confを下記のようにしました.

net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
net.bridge.bridge-nf-call-ip6tables = 0