systemd-networkd でラズパイルーターを構築する
n番煎じなネタですが、ラズパイルーターを構築しました
前提条件
- ハードウェア: Raspberry Pi 4 Model B Rev 1.4
- メモリ2GBモデル
- OS: Raspberry Pi OS Lite 64bit
- Debian 12 (bookworm) がベースのやつ
- 作るものは「WireGuard付き無線LANルーター」
- 無線部分は5GHzのみ
- networkdで出来ることは極力networkdでやる
- 有線/VPN部分はnetworkdで完結した
- 無線部分はさすがにhostapdが必要だった
- 有線/無線共にUSB NICは足さない
- 有線オンボードNICはL2SWに繋ぐ
- L2SW側はトランクポートに設定し、各種クライアントはNativeVLANで繋ぐ。WAN側はVLAN10を使う
OS準備
SDへの書き込みはRaspberry Pi Imagerを使い、カスタマイズを入れる
- 一般
- ユーザー名とパスワードを指定する
- ロケールを
Asia/Tokyo
にしてキーボードレイアウトもjp
にする
- サービス
- sshdの有効化とauthorized_keysの指定
- オプション
- テレメトリーを切る
ネットワーク系 SSHするまで
- Predictable Nameのところはraspi-configを参考にした
# NetworkManagerからnetworkdに切り替える。適用は豪快にreboot
sudo systemctl disable NetworkManager.service
sudo systemctl enable systemd-networkd.service
sudo reboot
# 推測できるインターフェース名(Predictable Network Interface Names)を有効化する
# この2つのファイルは/dev/nullへのシンボリックリンクになっており、消すことで
# /usr/lib/systemd/network/99-default.linkなどのマスクが解除される
sudo rm /etc/systemd/network/99-default.link
sudo rm /etc/systemd/network/73-usb-net-by-mac.link
# これも適用方法が分からないのでrebootする。再起動後はeth0がend0になる
# eno0になると思っていたが違った
sudo reboot
# 有線LANの最低限の設定を入れる。vimは入っていないのでnanoを使う
sudo nano /etc/systemd/network/80-end0.network
[Match]
Name=end0
[Network]
Address=192.168.11.1/24
DHCPServer=true
# 適用はnetworkctlで出来るらしい。ダメそうならreboot
sudo networkctl reload
- ここでSSHできるはず。PCを直結してSSHしてみる
有線LAN IPv4関連の設定
- ドキュメントはここ。デフォルトで入っているsystemdのバージョンは252だった
# 上で作ったファイルをもう一度編集する
sudo nano /etc/systemd/network/80-end0.network
[Match]
Name=end0
[Network]
# メモをつける。networkctl status end0 で表示されなかったので、どこに出てくるのか分からない
Description=Onboard NIC
# 割り当てるIPアドレス
Address=192.168.11.1/24
# IPv4のDHCPサーバを起動する
# 0.0.0.0:67 でlistenするので、WAN側のnftablesはしっかり書かないと怖い
DHCPServer=yes
# IPv4/IPv6でパケットのフォワードを有効化する
# sysctlで net.ipv4.ip_forward と net.ipv6.conf.all.forwarding を書き込んでくれる
# IPForward=ipv4 と書けばIPv4だけ書き込むはず
IPForward=yes
# いわゆるNAPTを有効化する。sudo nft list rulesetすると色々ルールが増えている
# nftなので、sudo systemctl restart nftables.serviceすると消えてしまう
# 不便なので使うのをやめた
# IPMasquerade=ipv4
# WAN接続用のVLANを紐づける。対応する.netdevをまだ作ってないので今は何も起きない
VLAN=vlan10
# DHCPv6-PDで受け取ったアドレスをRAで配布する。まだ上流の設定が無いので何も起きない
DHCPPrefixDelegation=yes
IPv6SendRA=yes
[DHCPServer]
# DHCPで配るIPアドレスは最初の100個を飛ばしてそこから50個を使う(多分。境界値がちょっと怪しい)
PoolOffset=100
PoolSize=50
# DHCPで配るDNSサーバのアドレス。適当にGoogleDNS
DNS=8.8.8.8
DNS=8.8.4.4
# DHCPv6-PDの設定。まだ上流の設定が無いので何も起きない
[DHCPPrefixDelegation]
UplinkInterface=:auto
SubnetId=11
# ここでもう1回適用
sudo networkctl reload
WAN側VLANインターフェース作成
- ドキュメントはここ
- 契約してる回線の仕様は以下
- IPv4は素直にDHCPで降ってくる
- IPv6はDHCPv6-PDで/56のプレフィックスがもらえる
- 今回は試していないが、networkdにNDProxyの機能も内蔵されているらしい
sudo nano /etc/systemd/network/25-vlan10.netdev
[NetDev]
Name=vlan10
Kind=vlan
[VLAN]
Id=10
# 適用。networkctlのドキュメントにある通り、reloadが効くのはnetdev新規作成時のみ。修正する時はよく分からないので再起動する
# Note that even if an existing .netdev is modified or removed, systemd-networkd does not update or remove the netdev.
sudo networkctl reload
# vlan10@end0 が増えているはず
ip link
# VLANインターフェース向けにIPアドレス設定などを入れる
sudo nano /etc/systemd/network/81-vlan10.network
[Match]
Name=vlan10
[Network]
# DHCPクライアントを有効化する。yesだとIPv4/v6両方で有効化されるので、必要に応じてipv4に変える
DHCP=yes
# DHCPv6-PDの移譲を受ける
DHCPPrefixDelegation=yes
# IPv6のアドレスはDHCPで受け取るがRAでも受け取る。ルーティング情報などが降ってくる
IPv6AcceptRA=yes
[DHCPv6]
# サンプルにこう書いてあったので書く。必要性はよくわかっていない
# Allows DHCPv6 client to start without router advertisements's "managed" or "other configuration" flag.
WithoutRA=solicit
# フレッツ環境だとこれも必要らしい?DUIDはなんでも良い気がするので適当に入れる
DUIDType=link-layer
[DHCPPrefixDelegation]
# DHCPv6-PDの上流インターフェースは自分だと明示する
UplinkInterface=:self
# 受け取ったプレフィックスにくっつけるサブネットID
# https://www.nic.ad.jp/ja/newsletter/No32/090.html
SubnetId=0
# 受け取ったプレフィックスを広報するか。これを許可するのはLAN側のみ
Announce=no
nftables設定
- 中から外への通信を全許可し、その戻りも全許可
- 外から中への通信は基本的に拒否
sudo nano /etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
# LAN側とWAN側のインターフェース名を定義しておく
define LAN_IF_NAME = { "end0", "wld0", "wg0" }
define WAN_IF_NAME = { "vlan10" }
# IPv4/v6両対応のテーブル
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# established/relatedは許可
ct state established,related accept
# invalidは拒否
ct state invalid drop
# loopback許可
iif lo accept
# 外からのICMPv6許可。厳密にやるならRFC4890に沿う
iifname $WAN_IF_NAME ip6 nexthdr icmpv6 icmpv6 type { nd-neighbor-solicit, nd-neighbor-advert, nd-router-advert, mld-listener-query } accept
# 中からのICMPは全許可
iifname $LAN_IF_NAME ip6 nexthdr icmpv6 accept
iifname $LAN_IF_NAME ip protocol icmp accept
# 外からのDHCPv6許可
iifname $WAN_IF_NAME ip6 saddr fe80::/10 udp dport 546 accept
# 中からのSSH/DHCP許可
iifname $LAN_IF_NAME tcp dport 22 accept
iifname $LAN_IF_NAME udp dport 67 accept
# wireguardは外内どこからでも許可
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
# 中から外へ全許可し、戻りを許可
iifname $WAN_IF_NAME ct state established,related accept
iifname $LAN_IF_NAME accept
}
chain output {
type filter hook output priority filter; policy accept;
}
}
# IPv4のみのテーブル
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat;
# いわゆるNAPT
oifname $WAN_IF_NAME masquerade
}
}
# nftables起動。忘れず自動起動も設定する
sudo systemctl start nftables.service
sudo systemctl enable nftables.service
- ここでついに結線する
- L2SW側でトランクポートを作り、allowed vlanにインターネットセグメント、native vlanにクライアント用セグメントをアサインしておく
ip addr
とnetworkctl status vlan10
でIPアドレスが降ってきたことを見る- DHCPv6のリース状況は
networkctl status vlan10
の下の方のログにしか出ない
- DHCPv6のリース状況は
DNS設定とapt更新
- Debian12ではデフォルトでsystemd-resolvedがインストールされなくなった
- なくても困らないので、直でresolv.confをいじる
sudo nano /etc/resolv.conf
# 適当にGoogleDNSを指定
nameserver 8.8.8.8
nameserver 8.8.4.4
# いつもの更新をかける
sudo apt update
sudo apt full-upgrade
sudo reboot
WireGuard設定
- クライアント側の公開鍵を登録しないといけないので、サーバ単体では設定が完了しない
- SSHのauthorized_keysみたいなもの
# wgコマンドのインストール
sudo apt install wireguard-tools
# 秘密鍵を作り保存する。権限的にnetworkdで読めるようにする
wg genkey | sudo tee /etc/wireguard/private.key
sudo chmod 640 /etc/wireguard/private.key
sudo chown root:systemd-network /etc/wireguard/private.key
# 公開鍵を作る。秘密鍵が変わらなければ公開鍵も変わらないので、特に保存する必要はない
# 今後iPhoneなどクライアント側の接続設定を作る時に使う
sudo cat /etc/wireguard/private.key | wg pubkey
# netdev作成
sudo nano /etc/systemd/network/25-wg0.netdev
[NetDev]
Name=wg0
Kind=wireguard
[WireGuard]
PrivateKeyFile = /etc/wireguard/private.key
ListenPort = 51820
[WireGuardPeer]
PublicKey = <ここにクライアント側の公開鍵>
# クライアント側のIPアドレス。このIPアドレスがサーバ側のルーティングに足されるイメージ
AllowedIPs = 192.168.42.5/32
PersistentKeepalive = 25
# network作成
sudo nano /etc/systemd/network/81-wg0.network
[Match]
Name=wg0
[Network]
Address=192.168.42.1/24
# 適用
sudo networkctl reload
# wg0の状態を見る
networkctl list
sudo wg show
内蔵無線LANの名前固定
- Predictable Nameがなぜか内蔵無線LANでは効かず、wlan0になるので設定を入れる
- 必須ではないが、無線用にUSB NICを足したりする時に効いてくる
# Pathを確認
networkctl status wlan0 | grep Path
# 名前固定用の設定を入れる
sudo nano /etc/systemd/network/10-onboard-wlan.link
[Match]
# 確認したPath
Path=platform-fe300000.mmcnr
[Link]
# end0に寄せてwld0に固定する
Name=wld0
# 適用方法が分からなかったのでreboot
sudo reboot
# wlan0 が wld0 に変わったことを見る
networkctl list
無線LANの設定
- regdomのところはraspi-configを参考にした
- コマンドラインパラメータで指定していたが、modprobe.dでもいいらしい
- raspi-config/raspi-config at afcfaca34099cdd275b9c8936598e552a7ddd0ce · RPi-Distro/raspi-config
- hostapd.confのドキュメントはWeb上に無かった
/usr/share/doc/hostapd/examples/hostapd.conf
にコメントがあるのでそれを読む- これくらいの量がある ので頑張って読む
- WPA3を使おうとしたがどうにも動かなかった
# 無線の規制範囲を日本にする。hostapdでも指定しているので、要らないかもしれない
echo 'options cfg80211 ieee80211_regdom=JP' | sudo tee /etc/modprobe.d/ieee80211_regdom.conf
# 適用方法が分からなかったのでreboot
sudo reboot
# 規制範囲などを眺めてみる
iw reg get
iw list
# hostapdインストール。AP作成だけはnetworkdでは出来なかった
sudo apt install hostapd
# hostapdのconfig作成
sudo nano /etc/hostapd/wld0.conf
# APにするインターフェース名
interface=wld0
# ログを標準出力に出す。systemd経由で起動するのでjournaldで読める
# -1は全てのモジュールという意味で、モジュールごとに有効・無効を切り替えることもできる
logger_stdout=-1
# 出すログはinfo以上
logger_stdout_level=2
# 起動したhostapdを操作するためのファイル(socket?)を置くディレクトリ
# hostapd_cliで使う
ctrl_interface=/var/run/hostapd
# SSID名
ssid=test-ap-A
# 準拠する規制範囲
country_code=JP
# ビーコンに国情報を含めるか。DFSで使う
ieee80211d=1
# DFSを有効化するか。5GHz帯の上の方のチャンネルで使うやつ
ieee80211h=1
# 5GHz帯を使う
hw_mode=a
# チャンネルは自動調整が良い感じに動くことを祈る
channel=acs_survey
# まれにhostapdが起動しなくなることがあったので、固定した方が安心。固定する場合は普通に数値を指定する
#channel=52
# WEPを明示的に切る。ビット指定なので分かりにくいが、3にするとWEPも有効になる
auth_algs=1
# 802.11n(Wi-Fi4)を有効化する
ieee80211n=1
# 有効化するHTの機能。深く考えずiw list で表示されたHT機能を指定する
ht_capab=[HT40+][SHORT-GI-20][SHORT-GI-40][DSSS_CCK-40]
# 802.11ac(Wi-Fi5)を有効化する
ieee80211ac=1
# 有効化するVHTの機能。これも深く考えずiw list で表示されたVHT機能を指定する
vht_capab=[SHORT-GI-80][SU-BEAMFORMEE]
# 80 MHz幅を使う
vht_oper_chwidth=1
# WPA2を有効化する
# これもビット表現なので、3にするとWPAとWPA2が有効になる
wpa=2
# パスワード
wpa_passphrase=password01
# 事前共有鍵方式
wpa_key_mgmt=WPA-PSK
# 暗号アルゴリズム
wpa_pairwise=CCMP
# hostapd起動。@の後ろはconfig名に合わせる(interface名ではない)
# 起動後は適当なスマホ等でSSIDが飛んでいることを見る
sudo systemctl start hostapd@wld0.service
# 忘れず有効化
sudo systemctl enable hostapd@wld0.service
# APにIPアドレスを付与する
sudo nano /etc/systemd/network/81-wld0.network
# 有線部分とほぼ同じ設定を入れる
[Match]
Name=wld0
WLANInterfaceType=ap
[Network]
Address=192.168.31.1/24
DHCPServer=yes
DHCPPrefixDelegation=yes
IPv6SendRA=yes
[DHCPServer]
PoolOffset=100
PoolSize=50
DNS=8.8.8.8
DNS=8.8.4.4
[DHCPPrefixDelegation]
UplinkInterface=:auto
SubnetId=31
# 適用
sudo networkctl reload
# wld0が管理状態になっているのを見る
networkctl list
ログ書き込み抑制と不要なもの削除
- journaldのログをtmpfsに移す
- microSDの消耗を避けるためなので、Pi5でSSD積んでいる場合は不要なはず
- Debian12からrsyslogはデフォルトで入らなくなったので、その辺りの抑制設定は不要
- そもそもOverlayFSを使ってSDカードへの書き込みを止める方法もある
/
はこれに沿ってやる: 第568回 overlayrootでUbuntuを一時的に読み込み専用にする | gihyo.jp/boot/firmware
は/etc/fstab
でroにするだけ- raspi-configでも有効化でき、このあたりがソース raspi-config/raspi-config at afcfaca34099cdd275b9c8936598e552a7ddd0ce · RPi-Distro/raspi-config
- bluetoothの止め方は色々あるが、パッケージを消す方向でやる
# ログの保存先を消す。消すとtmpfsの/run/log/journal/に書かれるようになる
# この挙動は/etc/systemd/journald.confのStorage=autoのあたりを参照のこと
sudo rm -rf /var/log/journal
# mDNSとbluetoothは使わないので消す
sudo apt purge avahi-daemon pi-bluetooth
sudo apt autopurge
# ついでにソフトウェア的にも切っておく
rfkill block bluetooth
おわりに
思った以上に時間が溶けた