手順3:HGWの 設置
HGW側インターフェースの 設定
HGW側インターフェースhgw
)の
ネットワーク構成で
送信する
> rdisc6 -1 wan0Soliciting ff02::2 (ff02::2) on wan0...
Hop limit : 64 ( 0x40)Stateful address conf. : YesStateful other conf. : YesMobile home agent : NoRouter preference : mediumNeighbor discovery proxy : NoRouter lifetime : 1800 (0x00000708) secondsReachable time : 300000 (0x000493e0) millisecondsRetransmit time : 10000 (0x00002710) milliseconds Source link-layer address: XX:XX:XX:XX:XX:XX MTU : 1500 bytes (valid) from fe80::xxxx:xxxx:xxxx:xxxx
これを
[Match]Name=hgw
[Network]LinkLocalAddressing=ipv6IPv6Forwarding=yesIPv6SendRA=yes
# フレッツ網に似せたRAを射出する[IPv6SendRA]# M-flag、O-flagを有効にしておくManaged=yesOtherInformation=yesHopLimit=64RouterLifetimeSec=1800ReachableTimeSec=300RetransmitSec=10
IPv4の 転送ルール設定
HGW向けに、
#!/usr/bin/nft -f
destroy table netdev bridge-hgw-v4table netdev bridge-hgw-v4 { chain inbound { type filter hook ingress device "wan0" priority -500; policy accept; ether type { ip, arp } counter fwd to "hgw" }
chain outbound { type filter hook ingress device "hgw" priority -500; policy accept; ether type { ip, arp } counter fwd to "wan0" }}
これだけで、wan0
にhgw
から
ただし、ingress
フックは
[Unit]Description=Apply nftables filter rules for hgwBindsTo=sys-subsystem-net-devices-hgw.deviceAfter=sys-subsystem-net-devices-hgw.device nftables.serviceRequires=nftables.service
[Service]Type=oneshotExecStart=/usr/bin/nft -f /etc/nftables.d/lazy/hgw.nftRemainAfterExit=true
[Install]WantedBy=multi-user.target
systemctl enable nftables-hgw
ファイアウォールの 設定更新
HGWからの
#!/usr/bin/nft -f
destroy table inet filtertable inet filter { set LANv4 { type ipv4_addr; flags interval; elements = { 10.0.0.0/24 } }
chain input { type filter hook input priority filter; policy drop; ct state invalid drop; ct state { established, related } accept; iif lo accept icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, nd-neighbor-solicit, nd-neighbor-advert } limit rate 5/second burst 10 packets accept icmpv6 type { nd-router-advert } iif "wan0" counter accept icmpv6 type { echo-request, nd-router-solicit } iifname "home" counter accept icmpv6 type { echo-request, nd-router-solicit } iifname { "home", "hgw" } counter accept icmp type { destination-unreachable, time-exceeded, parameter-problem } limit rate 5/second burst 10 packets accept icmp type { echo-request } iifname { "home" } accept udp dport dhcpv6-client iif "wan0" ip6 saddr fe80::/64 counter accept udp dport dhcpv6-server iifname "hgw" ip6 saddr fe80::/64 accept udp dport bootps iifname "home" accept ip saddr @LANv4 iifname { "home" } jump lan-services ip6 saddr fe80::/64 iifname { "home" } jump lan-services counter }
chain lan-services { meta l4proto { tcp, udp } th dport { ssh, domain } accept }
chain forward { type filter hook forward priority filter; policy drop; ct state invalid drop; ct state { established, related } accept; icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem } counter accept icmp type { destination-unreachable, time-exceeded, parameter-problem } accept meta l4proto { tcp, udp } th dport { 135, 137-139, 445 } oifname { "wan0", "ip6tnl0" } counter drop comment "NetBIOS" iifname "home" oif "wan0" accept comment "v6" iifname { "home", "hgw" } oif "wan0" accept comment "v6" iifname "home" oifname "ip6tnl0" accept comment "v4" counter }}
include "/etc/nftables.d/*.nft"
DHCPv6サーバの 設定
DHCPv6サーバ
払い出すIPv6プレフィックスや、
{ "Dhcp6": { "interfaces-config": { "interfaces": [ "hgw" ] }, "control-socket": { "socket-type": "unix", "socket-name": "kea6-ctrl-socket" }, "lease-database": { "type": "memfile", "lfc-interval": 3600 }, "expired-leases-processing": { "reclaim-timer-wait-time": 10, "flush-reclaimed-timer-wait-time": 25, "hold-reclaimed-time": 3600, "max-reclaim-leases": 100, "max-reclaim-time": 250, "unwarned-reclaim-cycles": 5 }, // フレッツ網を模倣 "renew-timer": 7200, "rebind-timer": 10800, "preferred-lifetime": 12600, "valid-lifetime": 14400, "hooks-libraries": [ // DHCPリースの変動時にスクリプトを実行するフック { "library": "/usr/lib/kea/hooks/libdhcp_run_script.so", "parameters": { "name": "/usr/share/kea/scripts/pd-route.sh", "sync": false } } ], "subnet6": [], "loggers": [ { "name": "kea-dhcp6", "output-options": [ { "output": "kea-dhcp6.log" } ], "severity": "INFO", "debuglevel": 0 } ] }}
ただし、
#!/bin/shPROTO=static
remove_ipv6_routes() { if [ "$LEASE6_TYPE" = "IA_PD" ]; then ip -6 route delete "${LEASE6_ADDRESS}/${LEASE6_PREFIX_LEN}" proto "${PROTO}" fi}
on_commit() { if [ "$LEASES6_SIZE" -gt 0 ]; then if [ "$LEASES6_AT0_TYPE" = "IA_PD" ]; then ip -6 route replace "${LEASES6_AT0_ADDRESS}/${LEASES6_AT0_PREFIX_LEN}" via "${QUERY6_REMOTE_ADDR}" dev "${QUERY6_IFACE_NAME}" proto "${PROTO}" fi fi}
case "$1" in"lease6_release" | "lease6_expire") remove_ipv6_routes ;;"leases6_committed") on_commit ;;esac
また、
{ "Control-agent": { "http-host": "127.0.0.1", "http-port": 8000, "control-sockets": { "dhcp4": { "socket-type": "unix", "socket-name": "kea4-ctrl-socket" }, "dhcp6": { "socket-type": "unix", "socket-name": "kea6-ctrl-socket" }, "d2": { "socket-type": "unix", "socket-name": "kea-ddns-ctrl-socket" } }, "hooks-libraries": [], "loggers": [ { "name": "kea-ctrl-agent", "output-options": [ { "output": "kea-ctrl-agent.log" } ], "severity": "INFO", "debuglevel": 0 } ] }}
systemctl enable --now kea-dhcp6systemctl enable --now kea-ctrl-agent
ひかり電話関連情報の 受信
さて、第二部のように、
- Option 17, 22, 23, 24, 31を
要求し - Option 16に
適切な 値を 含める
ことで、
リンク先資料の
Option Request に
指定可能な オプション番号は、 ”17”,”22”,”23”,”24”,”31”です
そして
端末への
設定自動化の ための ネットワーク関連情報通知機能を 取得する 場合は、 端末からの 要求メッセージにて、 Option Requestオプション (6)に オプション17を 含め、 Vendor Classオプション(16)に 必要な 情報 (端末情報)を 設定し、 網に 送信します
何やら
ネットワーク関連情報を
設定する 端末の ハードウェアアドレス (MACアドレス)を 設定
と
これを
# ...[DHCPv6]DUIDType=link-layerIAID=0SendHostname=noUseCaptivePortal=noUseDNR=no# これがなければsystemd-networkdはOption 24を無視するため注意UseDomains=yesRequestOptions=17 22 23 24 31# 一見文字列しかダメそうだが、実はhexも送れるVendorClass=\xde\xad\xbe\xef\x00\x05# ...
必要なde:ad:be:ef:00:05
と
> ip -6 neigh show dev hgw | awk '{print $3}'(中略)de:ad:be:ef:00:05
などと
設定を
networkctl reloadbusctl -j call org.freedesktop.network1 /org/freedesktop/network1 org.freedesktop.network1.Manager Describe | jq '.data[0] | fromjson | .Interfaces[] | select(.DHCPv6Client) | .DHCPv6Client.VendorSpecificOptions'
ひかり電話関連情報が
[ { "EnterpriseId": 210, "SubOptionCode": 202, "SubOptionData": "XXXXXXXXXX" }, { "EnterpriseId": 210, "SubOptionCode": 204, "SubOptionData": "XXXXXXXXXX" }, { "EnterpriseId": 210, "SubOptionCode": 201, "SubOptionData": "deadbeef0005" }, { "EnterpriseId": 210, "SubOptionCode": 210, "SubOptionData": "XXXXXXXXXX" }]
な
ひかり電話関連情報の 配布
最後に、
Keaの
スクリプト(長いので注意)
#!/usr/bin/env python
from asyncio import Event, create_task, runfrom ipaddress import IPv6Addressfrom json import loadsfrom time import sleepfrom typing import TypedDict
from requests import exceptions, postfrom sdbus import ( DbusInterfaceCommonAsync, dbus_method_async, dbus_signal_async, sd_bus_open_system, set_default_bus,)
SYSTEMD_IF = "org.freedesktop.systemd1"SYSTEMD_NODE_PATH = "/org/freedesktop/systemd1"SYSTEMD_MGR_IF = "org.freedesktop.systemd1.Manager"
NETWORKD_IF = "org.freedesktop.network1"NETWORKD_NODE_PATH = "/org/freedesktop/network1"NETWORKD_MGR_IF = "org.freedesktop.network1.Manager"NETWORKD_LINK_IF = "org.freedesktop.network1.Link"NETWORKD_DHCP6C_IF = "org.freedesktop.network1.DHCPv6Client"
DHCP6C_ETH = "wan0"HGW_ETH = "hgw"
KEA_API = "http://127.0.0.1:8000"KEA_SERVICE = "kea-dhcp6.service"KEA_MAX_RETRIES = 10KEA_RETRY_DELAY = 5
class Networkd(DbusInterfaceCommonAsync, interface_name=NETWORKD_MGR_IF): def __init__(self) -> None: super().__init__() self._proxify(NETWORKD_IF, NETWORKD_NODE_PATH)
@dbus_method_async("s", "io") async def get_link_by_name(self, name: str) -> tuple[int, str]: raise NotImplementedError
class NetworkdLink(DbusInterfaceCommonAsync, interface_name=NETWORKD_LINK_IF): def __init__(self, object_path: str) -> None: super().__init__() self._proxify(NETWORKD_IF, object_path)
@dbus_method_async(result_signature="s") async def describe(self) -> str: raise NotImplementedError
class Systemd(DbusInterfaceCommonAsync, interface_name=SYSTEMD_MGR_IF): def __init__(self, object_path: str) -> None: super().__init__() self._proxify(SYSTEMD_IF, SYSTEMD_NODE_PATH)
@dbus_signal_async(signal_signature="uoss") def job_removed(self) -> tuple[int, str, str, str]: raise NotImplementedError
class VendorSpecificOption(TypedDict): EnterpriseId: int SubOptionCode: int SubOptionData: str
class Dhcp6(TypedDict): prefix: IPv6Address dns: str ntp: str sip: str domains: str opts: list[VendorSpecificOption]
class Dhcp6State: def __init__(self): self.state: Dhcp6 | None = None
def get(self) -> Dhcp6 | None: return self.state
async def set(self, linkpath: str): dhcp6 = loads(await NetworkdLink(linkpath).describe()) dns, ntp, sip = [ ", ".join([IPv6Address(bytes(e["Address"])).compressed for e in dhcp6[k]]) for k in ["DNS", "NTP", "SIP"] ] self.state = { "prefix": IPv6Address(bytes(dhcp6["DHCPv6Client"]["Prefixes"][0]["Prefix"])), "dns": dns, "ntp": ntp, "sip": sip, "domains": ", ".join([f"{e['Domain']}." for e in dhcp6["SearchDomains"]]), "opts": dhcp6["DHCPv6Client"]["VendorSpecificOptions"], }
return self.state
def update_kea_config(dhcp6: Dhcp6): def cmd(cmd: str, config: dict[str, str] | None): return post( KEA_API, json={"command": cmd, "service": ["dhcp6"], "arguments": {"Dhcp6": config}}, timeout=3, )
def get_config(): for i in range(KEA_MAX_RETRIES): try: resp = cmd("config-get", None) json = resp.json()[0] if resp.status_code == 200 and json["result"] == 0: return resp.json()[0]["arguments"]["Dhcp6"] else: raise exceptions.ConnectionError except exceptions.ConnectionError: print( f"Kea API not reachable, retrying in {KEA_RETRY_DELAY} seconds... ({i + 1}/{KEA_MAX_RETRIES})" ) sleep(KEA_RETRY_DELAY) print("Kea API is not reachable after maximum retries.") return None
config = get_config()
if config is None: return
config["subnet6"] = [ { "id": 1, "subnet": f"{dhcp6['prefix'] + (0xEF << 64)}/64", "interface": HGW_ETH, "pools": [{"pool": f"{dhcp6['prefix'] + (0xEF << 64)}/64"}], "pd-pools": [ { "prefix": f"{dhcp6['prefix'] + (0xF0 << 64)}", "prefix-len": 60, "delegated-len": 60, } ], } ]
config["option-def"] = [ { "name": f"ntt-{opt['SubOptionCode']}", "code": opt["SubOptionCode"], "type": "binary", "space": f"vendor-{opt['EnterpriseId']}", } for opt in dhcp6["opts"] ]
config["option-data"] = [ { "code": opt["SubOptionCode"], "always-send": True, "space": f"vendor-{opt['EnterpriseId']}", "csv-format": False, "data": opt["SubOptionData"], } for opt in dhcp6["opts"] ] + [ { "name": "vendor-opts", "data": f"{dhcp6['opts'][0]['EnterpriseId']}", }, {"name": "sip-server-addr", "data": dhcp6["sip"]}, {"name": "dns-servers", "data": dhcp6["dns"]}, {"name": "domain-search", "data": dhcp6["domains"]}, {"name": "sntp-servers", "data": dhcp6["ntp"]}, ]
test_config = cmd("config-test", config).json()[0]
print(f"Kea: {test_config['text']}")
if test_config["result"] != 0: return
set_config = cmd("config-set", config).json()[0]
if set_config["result"] != 0: print(f"Error: {set_config['text']}") return
print(f"Kea: {set_config['text']} Hash: {set_config['arguments']['hash']}")
async def observe_dhcp6_lease(dhcp6_state: Dhcp6State): async for linkpath, ( iface, props, _, ) in DbusInterfaceCommonAsync.properties_changed.catch_anywhere(NETWORKD_IF): if iface != NETWORKD_DHCP6C_IF or props["State"][1] != "bound": continue
print(f"DHCPv6 lease acquired on {linkpath}, updating Kea configuration...")
dhcp6 = await dhcp6_state.set(linkpath) update_kea_config(dhcp6)
async def observe_kea_restart(dhcp6_state: Dhcp6State): async for _, ( _, _, service, signal, ) in Systemd.job_removed.catch_anywhere(SYSTEMD_IF): if service != KEA_SERVICE or signal != "done": continue
print(f"{KEA_SERVICE} restarted, updating Kea configuration...")
dhcp6 = dhcp6_state.get()
if dhcp6 is None: print("No DHCPv6 state available, fetching from Networkd...") _, linkpath = await Networkd().get_link_by_name(DHCP6C_ETH) print(f"Link path: {linkpath}") dhcp6 = await dhcp6_state.set(linkpath)
update_kea_config(dhcp6)
async def main(): set_default_bus(sd_bus_open_system())
print("Start DHCPv6 proxying...")
dhcp6_state_manager = Dhcp6State()
task1 = create_task(observe_dhcp6_lease(dhcp6_state_manager)) task2 = create_task(observe_kea_restart(dhcp6_state_manager))
await task1 await task2
_ = await Event().wait()
if __name__ == "__main__": try: run(main()) except KeyboardInterrupt: print("\nExiting...") except Exception as e: print(f"Error: {e}")
スクリプト内にも
systemd v258の
あとは
[Unit]Description=Networkd DHCPv6 lease info relay for KeaAfter=network.target sys-subsystem-net-devices-wan0.deviceBindsTo=sys-subsystem-net-devices-wan0.device
[Service]ExecStart=/usr/local/bin/networkd-kea6-relayRestart=alwaysRestartSec=5Environment=PYTHONUNBUFFERED=1
[Install]WantedBy=multi-user.target
systemctl enable --now networkd-kea6-relay
動作検証
IPv6 IPoE
VDSL回線
主要な
IPv4 over IPv6
速度・遅延にtc
を
HGW・ ひかり電話
HGWの
ハードウェアの 状態
ルータの
今後の 課題
- IPv6プレフィックス変更への
対応 - ごく
まれに 工事などで 変更される 場合が あるらしい - ルール計算を
自動化すれば 問題ない?
- ごく
- VDSLを
やめたい - 集合住宅を
脱出、 も しくは 爆破
- 集合住宅を
まとめ
みなさんも