Debian bookworm で v6プラス (MAP-E) + 固定IP + ひかり電話(IPv6 /56) のゲートウェイを組む
enひかりという光コラボレーションというサービスにて、 v6プラス (MAP-E) および固定IP を契約しております。 今回は YAMAHA の RTX830 や NVR510 のリプレースとして Debian bookworm を用いて PC ルータを組むことにしたので、その内容をまとめます。 構築が一段落してから思い出しながら記載しているので順番がおかしい点や、ファイアウォール設定などに不備が有ると思います。 まだ改良途中であることをご容赦ください。
v6プラス(MAP-E) + 固定IP + ひかり電話 の構成です。
記事の末尾に参考にさせていただいた記事をリンクしていますので、そちらもご確認いただければと思います。
なぜ Linux ルータを構築するのか
YAMAHA のルータからリプレースする一番の目的は、ゲートウェイ自身で WireGuard などのグローバル向けサービスを終端することです。 また、 vnStat などによるトラフィックモニタリングを実施したり、機器ごとのパケットフィルタリング実装を学ばなくて済むように nftables を用いたりすることも目的です。 LAN 側は5個ほどの VLAN に分割し、 IPv6 もサブネットを分割するために「ひかり電話」オプション(後述します)を契約します。
今回は余っているデスクトップコンピュータを用いて、我が家のメインスイッチの 40GbE ポートと QSFP+ DAC 1本で接続し、 Trunk VLAN で全ての VLAN を通します。
接続構成
1GbE スイッチ (NGN網を小型ONUで収容)
^
| (Trunk VLAN 1-4094)
v
40GbE スイッチ (Mellanox SX6036) <-> PC, サーバ...
^
| (Trunk VLAN 1-4094)
v
PC ルータ (Debian bookworm)
スイッチ設定(抜粋)
interface ethernet 1/3 switchport mode hybrid # Hybrid は構築中の作業によるもの
vlan 2-502
vlan 503-1003
vlan 1004-1504
vlan 1505-2005
vlan 2006-2506
vlan 2507-3007
vlan 3008-3508
vlan 3509-4009
vlan 4010-4094
interface ethernet 1/3 switchport hybrid allowed-vlan all
VLAN 構成
VLAN 番号は、ローカル側を 1 から、グロバール側を末尾の 4094 から採番しています。
ローカル側は 10.0.X.0/24
の X のように、第 3 オクテットに VLAN 番号を埋め込みます。
255 まで採番できますが、溢れた場合は第 2 オクテットをインクリメントすることとします。
当初は 192.168.0.0/16 を利用する予定でしたが、賃貸付随の無料インターネットが 192.168.0.0/16 という大きいサブネットで余剰が無かったため、 10.0.0.0/8 を用います。
- 1(未構築): 10.0.1.0/24, ネットワーク機器マネジメント
- 2: 10.0.2.0/24, メインネットワーク (信頼できる機器のみ)
- 3: 10.0.3.0/24, サブネットワーク (Microsoft Windows や macOS, IoT 機器, リモートワーク端末などの信頼できない機器)
- 4: 10.0.4.0/24, サブネットワーク (用途未定)
- 5: 10.0.5.0/24, サブネットワーク (用途未定)
- 4093(未構築): 賃貸付随の無料インターネット, 192.168.0.0/16 の範囲から DHCP で割り当てられる
- 4094: NGN
NGN 網から配布される IPv6 アドレスやひかり電話オプションについて
v6オプションを契約し NGN 網に接続すると、グローバルにルーティングされない IPv6 アドレスが付与され、 NGN 網内での折り返しを用いて VPN などを構築することが可能なことは多くの方がご存知かと思います。 また、 DS-Lite や MAP-E などの IPoE 方式を契約すると、 IPv6 アドレスが VNE 事業者所有のものに変更され、グローバルに到達可能なものとなります。この IPv6 アドレスは VNE 事業者が保有するものですが、割り当ては引き続き NTT 東西により NGN 網から行われます。
IPoE 方式により NTT 東西から割り当てられる(VNE事業者保有の) IPv6 アドレスですが、 NTT東日本のフレッツ光(クロスを除く)では、ひかり電話を契約しない場合は /64(RA)
, ひかり電話を契約している場合は /56(DHCPv6-PD)
で配布されます。
IPv6 の基本的な最小サブネットは /64
ですから、複数のサブネットが欲しい場合は ひかり電話
の契約が必要ということになります。
当方はひかり電話オプションを契約しておらず /64(RA)
の構成でしたが、 /64
をより小さいサブネットに分割してしまうと、 RA によるアドレス配布は不可能となり DHCPv6 (Android は未サポート) が必要となることから、ひかり電話
を契約することにしました。
また、 IPv6 アドレスにも IPv4 ローカルアドレスに相当する ULA の定義が有るので複数のサブネットを定義することができますが、 OS によっては IPv6 ULA + プライベート IPv4 のデュアルスタック環境において、 IPv6 通信を優先しない挙動が見られたので採用しませんでした。
当然ながら IPv6 で NAT が必要になってしまうという問題も発生するので、望ましくありません。
なお、 ND Proxy を用いることで複数の L2 サブネットに跨る IPv6 L3 サブネットを構築することも1つの解決策では有りますが、十分に検証しなかったため割愛します。
参考:
- IPoE協議会: IPoE方式とVNEの役割
- 株式会社JPIX: VNEサービスの特徴
- サイレックス・テクノロジー株式会社: NATのはなし IPv6 における NAT について参考になります。
DS-Lite や MAP-E について
IPoE 契約している場合、 IPv6 は前述の通り RA または DHCPv6-PD で割り当てられ、そのままインターネットへのリーチが有るので気にする必要が有りません。 一方で IPv4 については、 利用者のルータと VNE 事業者の装置間で IPv4 over IPv6 トンネリングし、 IPv4 インターネットへのリーチを確保します。 v6プラス/transix/Xpass などのサービスが有り VNE 事業者が提供しますが、それぞれカブセル化の技術として v6プラスは MAP-E を、 transix/Xpass は DS-Lite を用います。
DS-Lite は非常に単純で、 VNE 事業者側の装置(AFTR)で CGNAT されグローバルIPアドレスに変換されます。 基本的にグローバルアドレスは複数のユーザで共有されるため、利用できるポート(NATセッション数)は決められており、また NAPT の挙動をコントロールすることもできません。 当然ながら、グローバルに向けて自宅サーバを公開することも困難となります。
MAP-E は、一般的な PPPoE と同様に利用者ルータでグローバルアドレスに NAPT します。 DS-Lite と同様に利用できるポート(NATセッション数)は決められておりますが、利用者で NAPT の挙動をコントロールできるため、 NAPT のセッション数不足に陥いりにくい特徴が有りますし、割り当てられたポートを用いて自宅サーバを公開することも可能です。 NAPT する為に必要な情報であるグローバル IPv4 アドレスと利用可能なポートレンジは、割り当てられた IPv6 アドレスから計算して算出しますが、この計算については割愛します。 また、固定IP オプションを用いると、グローバルIPv4アドレスが付与されるため、ウェルノンポートを含めた任意のポートを使うことができますし、より NAPT のセッション数不足に陥いる可能性を削減できます。 固定IP オプションを用いた場合、契約時に IPv4 アドレスが書面で通知され全てのポートが利用できますので、 IPv6 アドレスからの計算の必要は有りません。
IPv6 での接続と DHCPv6-PD
詳細は割愛しますが、ひかり電話を契約している場合、 RA でデフォルトゲートウェイアドレスが、 DHCPv6-PD で IPv6 サブネットが振ってきます。
radvdump で RA の情報を確認すると AdvManagedFlag on, AdvOtherConfigFlag on となっており、 DHCP による取得が必要なことを示しています。
また fe80::xxxx:xxxx:xxxx
の部分がデフォルトゲートウェイです。
#
# radvd configuration generated by radvdump 2.19
# based on Router Advertisement from fe80::xxxx:xxxx:xxxx
# received by interface enp1s0.4094
#
interface enp1s0.4094
{
AdvSendAdvert on;
# Note: {Min,Max}RtrAdvInterval cannot be obtained with radvdump
AdvManagedFlag on;
AdvOtherConfigFlag on;
AdvReachableTime 300000;
AdvRetransTimer 10000;
AdvCurHopLimit 64;
AdvDefaultLifetime 1800;
AdvHomeAgentFlag off;
AdvDefaultPreference medium;
AdvSourceLLAddress on;
}; # End of interface definition
続いて /etc/network/interfaces
を設定し、IPv6 アドレスおよび RA を受信するようにします。
前半の 2001:db8:a:0
の部分は NGN 開通時に決定しますが、既に知っていたため直接記載しております。
本来であれば半固定ですので、きちんと取得して設定するようにスクリプトを作成すべきであり、今後の改善タスクとして積んでいます。
1:2:3:0
の部分は、契約書類に インターフェイスID
として記載されている値を用います。
VNE 事業者は、この IPv6 アドレスに対して IPv4 over IPv6 を行いますので、謝りなく設定する必要が有ります。
auto enp1s0.4094
iface enp1s0.4094 inet manual
iface enp1s0.4094 inet6 static
address 2001:db8:a:0:1:2:3:0/56
accept_ra 2
今回は WIDE-DHCPv6 を用いて、 LAN 側の各インタフェースに対して IPv6 を /64
で割り当てます。
/etc/network/interfaces
では IPv6 アドレスを記載せず、 IPv4 アドレスのみ設定しておきます。
auto enp1s0.2
iface enp1s0.2 inet static
address 10.0.2.1/24
iface enp1s0.2 inet6 manual
auto enp1s0.3
iface enp1s0.3 inet static
address 10.0.3.1/24
iface enp1s0.3 inet6 manual
auto enp1s0.4
iface enp1s0.4 inet static
address 10.0.4.1/24
iface enp1s0.4 inet6 manual
auto enp1s0.5
iface enp1s0.5 inet static
address 10.0.5.1/24
iface enp1s0.5 inet6 manual
IPv6 フォワーディングを設定しておきます。
/etc/sysctl.conf
net.ipv6.conf.all.forwarding=1
まずは /etc/default/wide-dhcpv6-client
で DHCP Client として動作するインタフェースを定義します。
# Defaults for dhcpv6 client initscript
# Used by /etc/init.d/wide-dhcpv6-client
# Interfaces on which the client should send DHCPv6 requests and listen to
# answers. If empty, the client is deactivated.
INTERFACES="enp1s0.4094"
/etc/wide-dhcpv6/dhcp6c.conf
で次のように設定すると、 DNS サーバの情報をリクエストし /etc/wide-dhcpv6/dhcp6c-script
を用いて /etc/resolv.conf
が書き換えられます。
また、 DHCPv6-PD で受け取った /52
のサブネットを /64
に分割して各 VLAN インタフェースに設定します。
sla-len の値は 払い出された Prefix(/56) + sla-len = /64
となるように 8
を指定しています。
これは、 8 bit で表現可能な 256 個だけ /64
のサブネットに分割できることを意味します。
# Default dhpc6c configuration: it assumes the address is autoconfigured using
# router advertisements.
interface enp1s0.4094 {
send ia-pd 0;
request domain-name-servers;
script "/etc/wide-dhcpv6/dhcp6c-script";
};
id-assoc pd 0 {
prefix-interface enp1s0.2 {
sla-id 2;
sla-len 8;
};
prefix-interface enp1s0.3 {
sla-id 3;
sla-len 8;
};
prefix-interface enp1s0.4 {
sla-id 4;
sla-len 8;
};
prefix-interface enp1s0.5 {
sla-id 5;
sla-len 8;
};
};
私の場合は、 /etc/wide-dhcpv6/dhcp6c-script
スクリプトを下記の通り書き換えています。
これは、任意の search
のみを指定し、後の dnsmasq のために nameserver の先頭に 127.0.0.1
を挿入するためです。
#!/bin/sh
[ -f /etc/default/wide-dhcpv6-client ] && . /etc/default/wide-dhcpv6-client
if [ -n "$new_domain_name" -o -n "$new_domain_name_servers" ]; then
old_resolv_conf=/etc/resolv.conf
new_resolv_conf=/etc/resolv.conf.dhcp6c-new
echo "search yamano.dev" > $new_resolv_conf
echo nameserver 127.0.0.1 >> $new_resolv_conf
if [ -n "$new_domain_name_servers" ]; then
for nameserver in $new_domain_name_servers; do
echo nameserver $nameserver >> $new_resolv_conf
done
fi
chown --reference=$old_resolv_conf $new_resolv_conf
chmod --reference=$old_resolv_conf $new_resolv_conf
mv -f $new_resolv_conf $old_resolv_conf
fi
exit 0
加えて、 NGN 網では DUID-LL で DHCPv6 をリクエストする必要が有るので wide_mkduid.pl を用いてファイルを Generate し /var/lib/dhcpv6/dhcp6c_duid
に配置する必要が有ります。
また NGN 網では DUID-LL に含まれる hardware type は 1 である必要が有るので、 wide_mkduid.pl にはパッチを当てておく必要が有ります。
wide_mkduid.pl およびパッチについては、正確な配布元が分からなかったので、申しわけないですがインターネット上から探して入手してください。
これらを設定したら、インタフェースを UP し wide-dhcpv6-client を実行することで、次のように IPv6 アドレスと /etc/resolv.conf
が設定されます。
$ ip -6 addr show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
4: enp1s0.2@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:a:2:a:b:c:d/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a:b:c:d/64 scope link
valid_lft forever preferred_lft forever
5: enp1s0.3@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:a:3:a:b:c:d/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a:b:c:d/64 scope link
valid_lft forever preferred_lft forever
6: enp1s0.4@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:a:4:a:b:c:d/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a:b:c:d/64 scope link
valid_lft forever preferred_lft forever
19: enp1s0.5@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:a:5:a:b:c:d/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a:b:c:d/64 scope link
valid_lft forever preferred_lft forever
9: enp1s0.4094@enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 2001:db8:a:0:1:2:3:0/56 scope global
valid_lft forever preferred_lft forever
inet6 fe80::a:b:c:d/64 scope link
valid_lft forever preferred_lft forever
$ ip -6 route show
2001:db8:a:2::/64 dev enp1s0.2 proto kernel metric 256 pref medium
2001:db8:a:3::/64 dev enp1s0.3 proto kernel metric 256 pref medium
2001:db8:a:4::/64 dev enp1s0.4 proto kernel metric 256 pref medium
2001:db8:a:5::/64 dev enp1s0.5 proto kernel metric 256 pref medium
2001:db8:a:0::/56 dev enp1s0.4094 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0.2 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0.3 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0.4 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0.5 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0.4094 proto kernel metric 256 pref medium
default via fe80::xxxx:xxxx:xxxx dev enp1s0.4094 proto ra metric 1024 expires 1444sec hoplimit 64 pref medium
/etc/resolv.conf
search yamano.dev
nameserver 127.0.0.1
nameserver 2001:db8::1
nameserver 2001:db8::2
MAP-E
MAP-E は前述の通り、 IPv4 アドレスや利用可能ポートの計算が面倒であり、加えて nftables/iptables による IPマスカレード 設定も送信元ポートを適切に行う必要が有ります。 しかし今回は、固定IPオプションを契約しており、前述の通り全てのポートが利用できますので、単純な IPv4 over IPv6 と IPマスカレード で済みます。
まずは /etc/network/interfaces
で IPv4 over IPv6 の tun0 インタフェースを作成します。
endpoint には、 VNE 事業者の BR (Border Router) の IPv6 アドレスを指定する必要が有り、契約書類に記載が有ります。
tun0 の IP となる address には、固定IP オプションでアサインされた IPv4 アドレスを記載します。
auto tun0
iface tun0 inet tunnel
mode ipip6
local 2001:db8:a:0:1:2:3:0
endpoint 2001:db8::1234
address 203.0.113.201
netmask 255.255.255.255
post-up ip route add default dev $IFACE
この状態で tun0 インタフェースを UP すると、 IPv4 アドレスを用いた通信が可能となります。 当方の環境だと、前述した DHCPv6-PD クライアントが動作していないと IPv4 通信が受信できなかったため、正常に通信できない場合は DHCPv6-PD クライアントの状態も確認するのが良いと思われます。 DHCPv6-PD クライアントが動作しなければ、 VNE 事業者内でのルーティングが追加されていないと思われます。
後は、必要に応じてカーネルパラメータを設定したり、IPマスカレードを設定することとなります。 あわせて、 Path MTU Discovery ブラックホールの解決策として、 MSS Clamping しておきます。
/etc/sysctl.conf
net.ipv4.conf.default.rp_filter=1
net.ipv4.conf.all.rp_filter=1
net.ipv4.ip_forward=1
/etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy accept;
}
chain forward {
type filter hook forward priority filter; policy accept;
# IPv4 TCP MSS clamping (tun0)
iifname tun0 tcp flags syn tcp option maxseg size set rt mtu
oifname tun0 tcp flags syn tcp option maxseg size set rt mtu
}
chain output {
type filter hook output priority filter; policy accept;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority 0;
}
chain postrouting {
type nat hook postrouting priority 0;
oifname tun0 masquerade
}
}
RA による IPv6 配布
次に、 LAN 側に IPv6 を RA で配布するために radvd を用います。 prefix には、各インタフェースに設定された IPv6 アドレスの prefix を指定します。 また、 RDNSS 拡張で、自身のリンクローカルアドレスをDNSサーバとして配布しています。(後述しますが dnsmasq で構築します)
/etc/radvd.conf
interface enp1s0.2 {
AdvSendAdvert on;
prefix 2001:db8:a:2::/64 { };
RDNSS fe80::a:b:c:d { };
};
interface enp1s0.3 {
AdvSendAdvert on;
prefix 2001:db8:a:3::/64 { };
RDNSS fe80::a:b:c:d { };
};
interface enp1s0.4 {
AdvSendAdvert on;
prefix 2001:db8:a:4::/64 { };
prefix ::/64 { };
RDNSS fe80::a:b:c:d { };
};
interface enp1s0.5 {
AdvSendAdvert on;
prefix 2001:db8:a:5::/64 { };
RDNSS fe80::a:b:c:d { };
};
Dnsmasq による IPv4 DHCP および DNS キャッシュサーバ
Dnsmasq で IPv4 DHCP サーバと、 DNS キャッシュサーバを構築します。
上流の DNS サーバとして、前述した /etc/resolv.conf
のうち、 127.0.0.1
を除いて用いますので、 2001:db8::1
と 2001:db8::2
に問い合わせることになります。
/etc/default/dnsmasq
cache-size=10000
strict-order
interface=enp1s0.2
interface=enp1s0.3
interface=enp1s0.4
interface=enp1s0.5
dhcp-range=10.0.2.151,10.0.2.200,12h
dhcp-range=10.0.3.151,10.0.3.200,12h
dhcp-range=10.0.4.151,10.0.4.200,12h
dhcp-range=10.0.5.151,10.0.5.200,12h
nftables によるファイアウォール
ローカルへのパケットおよびフォワーディングするパケットに対し、パケットフィルタリングを nftables で実装します。 このルールは、まだ実験途中なので、不足している部分や誤っている部分もあるかと思います。
/etc/nftables.conf
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# LoopBack
iif lo accept
# Connection Track
ct state established,related accept
# NTT NGN DHCPv6
iifname enp1s0.4094 ip6 saddr fe80::/10 ip6 daddr fe80::/10 udp sport dhcpv6-server udp dport dhcpv6-client accept
# SSH
tcp dport ssh ct state new accept
# WireGuard
udp dport 51820 ct state new accept
# ICMP
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-neighbor-solicit, nd-neighbor-advert, mld-listener-query, echo-request, echo-reply} counter accept
ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem, echo-request, echo-reply } accept
## from NGN
iifname enp1s0.4094 ip6 nexthdr icmpv6 icmpv6 type nd-router-advert accept
## from Local
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } ip6 nexthdr icmpv6 icmpv6 type { nd-router-solicit, nd-neighbor-solicit, nd-neighbor-advert } accept
# Local Network
## DNS
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } udp dport 53 accept
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } tcp dport 53 accept
## DHCPv4
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } ip protocol udp udp dport 67 accept
## NTP
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } udp dport 123 accept
## Netdata
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } tcp dport 19999 accept
drop
}
chain forward {
type filter hook forward priority filter; policy drop;
# IPv4 TCP MSS clamping (tun0)
iifname tun0 tcp flags syn tcp option maxseg size set rt mtu
oifname tun0 tcp flags syn tcp option maxseg size set rt mtu
# Connection Track
ct state established,related accept
# ICMP
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } accept
ip protocol icmp icmp type { destination-unreachable, time-exceeded, parameter-problem } accept
# TODO: Windows CIFS Drop
# Local Network
iifname { wg0, enp1s0.1, enp1s0.2, enp1s0.3, enp1s0.4, enp1s0.5 } accept
drop
}
chain output {
type filter hook output priority filter;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority 0;
}
chain postrouting {
type nat hook postrouting priority 0;
oifname tun0 masquerade
}
今後
いくつか改善したい所が有るので、今後はこれらを改善することにします。
- 半固定 IPv6 アドレスへの対応
- LAN L3 セグメント間のパケットフィルタリング
- WireGuard 以外のグローバル向けサービスの構築
- クライアント端末ごとのトラフィック量や、DPIを用いたモニタリング
- ひかり電話の併用として、 NVR510 利用もしくは Asterisk への収容
- 賃貸の無料インターネットを用いたバックアップ経路の追加
参考
構築にあたり参考にさせていただきました。