自宅のフレッツ光VDSL環境で、APU2でIPoEルーターを自作した時のメモ

:::note
この記事はUbuntu 20.04を前提にしています。

Ubuntu 22.04以降はDHCPv6-PDによる追加のIPv6のアドレスが取得できなくなっていて、IPv4のみを使ってインターネットにアクセスしていました。

現在はFD.io VPPを利用して問題を解決しています。

* [FD.io VPP 24.02でフレッツ(IPoE)ホームルーターを自作した時のメモ](https://qiita.com/YasuhiroABE/items/9c2b295f72a38ed22ed1)
:::

# はじめに

IPoEルーター構築のきっかけはVDSLポートの故障と思われる頻繁なWANインタフェースのdown/upが記録されるようになったことにあります。修理までの間、PPPoEのセッションは一度切れると再開までには時間がかかるので、素直なIPv6の接続ができるIPoEであればWANインタフェースがupして少しは早く通信できるだろうと考えたからでした。

実際の問題はマンションのMDF内のVDSL装置自体にあったので、NTTによる交換が実施され、接続は安定しています。また113のオペレーターの対応がとても良かったのが印象的でした。

実験や作業に必要な機能はVPSなどを利用してインターネット上に置き、VPN経由でアクセスできるようにしたため、直接自宅にアクセスするための固定IPv4の必要性がほぼなくなり、料金の安いプランに切り替えるタイミングだったこともIPoEに変更した理由の一つです。

使ってみた感想は、PPPoEと比べると、かなりシンプルな標準技術だけでインターネットに接続できるという点では優れていると感じています。ただIPv6のprefixはほぼ固定ですが、変動する可能性はあるので、そこを考慮に入れて環境を構築するのは少し面倒かなとは思っています。

# 参考資料

* https://mxnl.com/ds-lite/ (netplanの設定はほぼそのまま利用させて頂きました)
* https://tech.nosuz.jp/2017/04/zoot-native-ds-lite/
* https://lists.isc.org/pipermail/dhcp-users/2013-December/017455.html

# 環境

* NTT東 フレッツ光ネクスト マンションタイプ (VDSL接続、光でんわ契約有）
* HGW・VDSLモデム - RV-440MI (レンタル)
* Hardware: APU2 (PC Engines社製, Type: apu2c4, Memory: 4GB, CPU: GX-412TC)
* OS: Ubuntu 18.04.5 Server amd64版 (20.04.4 に do-release-upgrade を実施済み)
* ISP: Interlink (インターリンク フレッツ接続 ZOOT NATIVE IPv4固定IP無しプラン)

> IPoE接続では、光でんわ契約の有無によって設定方法が異なりますので注意してください。
(光電話契約がない場合は、DPCHv6-PDは利用できないとあるので、Router Advertisement (RA)によって機器を構成する必要があるはずです。enp1s0とenp2s0をブリッジ接続し、sysctlでNDPを通過させるのかなと考えていますが検証していません)

今回の構成例を図にしました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78296/c2052ba4-9ae9-431d-6020-aacbf596530b.png)

既に構成しているPPPoE環境は、APUをサーバーとしてRV-440MIに接続し、並列に動作していますが、相互に関連はないので図に記載していません。

ハードウェアにAPU2以外のPCを利用する場合には、今回の説明では少なくとも2つ以上のNICを搭載していることが必須です。また enp1s0 などのデバイス名は、実態に合わせて読み替えてください。

# 参考資料にある設定を試した結果と原因

記載していないものも含めて、いろいろ試した結果、期待どおりに動くことはありませんでした。

問題は設定が正しいかどうかということより、systemctlからサービスの起動に失敗することに起因した再現性のなさにより、最終的に設定が正しいのかどうか分からなかったものがほとんどでした。

遭遇した問題の中には、例えば、DHCPv6-PDを設定するため、wide-dhcpv6-client.serviceを起動できない現象がありましたが、これはsystemd-networkd.serviceが起動していてポートを専有していることによるものでした。

この種のエラーに遭遇した場合には、```$ sudo lsof -i |grep dhcp```のように、lsofコマンドを利用して関連するポートを掴んでいるプロセスを発見する方法がお勧めです。

ただisc-dhcp-serverパッケージを導入していた気がしていて、これが根本的な原因かもしれませんが、まだ検証できていません。

```text:syslogの抜粋
Dec 23 01:03:59 ubuntu wide-dhcpv6-client[479]:    ...fail!
Dec 23 01:03:59 ubuntu systemd[1]: wide-dhcpv6-client.service: Control process exited, code=exited status=1
Dec 23 01:03:59 ubuntu systemd[1]: wide-dhcpv6-client.service: Failed with result 'exit-code'.
```

またdnsmasq.serviceが起動しない問題もありましたが、これはsystemd-resolved.serviceが起動していることにより53番ポートにbindできないことが問題でした。

```text:syslogの抜粋
Dec 23 00:39:53 ubuntu dnsmasq[1540]: dnsmasq: failed to create listening socket for port 53: Address already in use
```

また、参考にした資料の中には、LAN側ではIPv4(DHCPv4)のみを設定していると思われるものもあり、IPv6アドレスをLAN内でDHCPv6で配布したいという希望には合わないものもありました。

# パッケージの追加とサービスの停止

必要なパッケージを追加し、dnsmasqの起動と衝突するサービスは停止するようにします。
デフォルトで導入されているため、ここに記載していませんが、導入されているisc-dhcp-clientパッケージは削除しないでください。

```bash:aptコマンドとsystemctlコマンドの実行
$ sudo apt install dnsmasq wide-dhcpv6-client
$ sudo systemctl disable systemd-resolved.service
$ sudo systemctl stop systemd-resolved.service
```

systemd-resolved.serviceが掴んでいた/etc/resolv.confがシンボリックリンクになっているはずなので、これを削除し、最低限の内容に置き換えます。

```bash:/etc/resolv.confシンボリックリンクファイルの削除
$ sudo rm /etc/resolv.conf
$ echo nameserver 192.168.1.1 | sudo tee /etc/resolv.conf
```

この他にもsystemd-networkd.serviceもあるのですが、これは完全に停止させることができなかったので別にスクリプトを作成しています。

# とりあえず動いたシステムの設定

各機器と割り当てられているIPアドレスについては、このページの先頭に追加した図も参考にしてください。

ホームルーターとするAPU2にはNTTからレンタルしているRV-440MI(HGW/VDSL機器)とenp1s0で繋がっています。このenp1s0には、RV-440MIのDHCPv4サーバーから192.168.1.2/24を払い出されていて、IPv6にはRV-440MIからRA経由でIPv6アドレスを受け取り、DNS,GWの情報を受け取り設定しています。この状態でIPv4 over IPv6 (IPIP6)を設定することで、このホームルーター(APU2)単体からはIPv4・IPv6でインターネットにアクセスできるようになります。

この状態でもenp2s0に適当なIPv4アドレスを割り当てて、LAN内のマシンにIPv4アドレスを適切に手動で設定すれば、IPIP6からインターネットにアクセスすることはできます。

RV-440MIはDHCPv6には対応していません(DHCPv6-PDには反応します)ので、netplanの設定ではenp1s0のdhcp6はnoに設定しています。

APU2のenp2s0をLAN側のインタフェースとしていて、192.168.1.0/24の範囲からアドレスを静的に設定しています。このため厳密にはRV-440MIのDHCPv4が割り当てるアドレスと衝突する可能性はあります。HGWのLANポートに様々な機器を接続すると、192.168.1.10/24が割り当てられると思いますので気をつけてください。IPv6用にはRV-440MIのDHCPv6-PDサーバー機能を利用して、enp2s0に/60なアドレスが割り当てられています。dnsmasqを利用して、enp2s0からDHCPv4, DHCPv6+RA(ra-stateless)のサービスをLANに向けて提供しています。

## netplanの設定

```yaml:最終的な/etc/netplan/01-netcfg.yamlファイルの内容
network:
  version: 2
  ethernets:
    enp1s0:
      accept-ra: yes
      dhcp4: yes
      dhcp6: no
    enp2s0:
      accept-ra: no
      dhcp4: no
      dhcp6: no
      addresses:
        - 192.168.1.10/24
  tunnels:
    ip6tnl1:
      mode: ipip6
      remote: 2404:8e00::feed:100
      local: 2409:xxxx:xxxx:xx00:20d:xxxx:xxxx:7b1c
      routes:
      - to: 0.0.0.0/0
        scope: link
```

/etc/netplan/01-netcfg.yamlのlocal:に設定するIPv6アドレスはenp1s0かenp2s0のいずれかのアドレスを転記します。これは一度APU2をHGWに接続し、RAやDHCPv6-PDで割り当てあれるアドレスを確認しないと記述できません。そのため最初はtunnels以下は削除したnetplanの設定で動作させる必要があるでしょう。remote:のIPv6アドレスはフレッツ東か西かによって異なり、ISPが提供するドキュメントや参考資料の記述を参照してください。

## wide-dhcpv6-client(DHCPv6-PD)の設定

導入時にNICについて質問されているはずですが、/etc/default/wide-dhcpv6-client の内容を確認します。
INTERFACESにはenp1s0を指定しています。

```text:/etc/default/wide-dhcpv6-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"

# Verbose level for syslog. Default is 0 (0: minimal; 1: info; 2: debug)
#VERBOSE=0
```

次に /etc/wide-dhcpv6/dhcp6c.conf を編集します。

```text:/etc/wide-dhcpv6/dhcp6c.confの全体
interface enp1s0 {
  send ia-pd 0;
  request domain-name-servers;
  script "/etc/wide-dhcpv6/dhcp6c-script"; ## 複数のscript行を記述するとエラーとなる
};

id-assoc pd 0 {
  prefix-interface enp2s0 {
    sla-id 1;
    sla-len 4;
  };
};
```

/etc/wide-dhcpv6/dhcp6c.confに設定するsla-len 4;の設定は、DHCPv6-PDで割り当てられたPrefixが/60の場合です。ここら辺はHGWのDHCPv6-PDサーバー側の動き次第なので、よく見る/56の場合は、sla-len 8;となり、合計値を64に合わせます。この数字をいくつにするかは、とりあえずsla-len 0;を設定しておき、enp2s0に割り当てられたIPv6のネットマスクを確認することで判断できます。

/etc/wide-dhcpv6/dhcp6c-scriptは/etc/resolv.confを更新するスクリプトですが、ここに別のスクリプトを加えることはできないため ip6tables-restore を起動する処理はdhcp6c-scriptに加えています。

```text:/etc/wide-dhcpv6/dhcp6c-script追記部分
PATH=/sbin:/usr/sbin:$PATH
export PATH
ip6tables-restore < /etc/network/ip6tables.conf >/dev/null 2>&1
```
Ubuntu 18.04と20.04でip6tables-restoreなどのコマンドが移動しているため、PATH環境変数を利用しています。
ここで指定している/etc/network/ip6tables.confにはfirewallのセクションで説明する内容の処理が含まれています。

## dnsmasqの設定

DHCPサーバーとDNSサーバーとして、dnsmasqを利用しています。
他の参考資料に挙げている説明では、unboundやisc-dhcp-serverを利用している部分です。

dnsmasqを利用する利点は、DHCPサーバーとDNSサーバーの挙動を連携させることができる点です。

```text:/etc/dnsmasq.d/00.ipv4.conf
no-resolv
domain=yasundial.org
interface=enp2s0

# DHCPd specific settings
dhcp-range=tag:enp2s0,192.168.1.200,192.168.1.250
# gateway router
dhcp-option=tag:enp2s0,option:router,192.168.1.10
# dns server
dhcp-option=tag:enp2s0,option:dns-server,192.168.1.10
dhcp-option=tag:enp2s0,option:ntp-server,192.168.1.10
# dnc domain search
dhcp-option=tag:enp2s0,option:domain-search,yasundial.org

server=192.168.1.1
```

```text:/etc/dnsmasq.d/01.ipv6.conf
dhcp-range=::,constructor:enp2s0,ra-stateless
enable-ra
```

この設定によってDHCPv6-PDで割り当てられたenp20のIPv6アドレスの範囲から自動的にDHCPv6用のアドレスを割り当てるようになります。

## sysctlの設定

設定は/etc/sysctl.confのコメントを外したものが、ほとんどですが、ファイルの先頭に```net.ipv6.conf.enp1s0.accept_ra=1```だけ追記しています。設定をしなくてもnetplanにより構成されますが、念のため記載しています。

```bash:/etc/sysctl.confの設定状況
$ sudo sysctl -p
net.ipv6.conf.enp1s0.accept_ra = 2
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
```

## systemdサービスの設定

冒頭で説明したように、systemdから起動されるサービスのいくつかを手動で無効にする必要がありました。
それでもsystemd-networkd.serviceが強制的に起動してしまうため、wide-dhcpv6-client.serviceが実行できない問題に対応するために、とりあえずcrontabに次のようなスクリプトを配置しました。

```bash:/usr/local/sbin/check-ipoe.sh
#!/bin/bash

PATH=/sbin:/usr/sbin:/bin:/usr/bin:$PATH
export PATH

WAN_IF="enp1s0"
WAN_IPV6ADDR=$(ip -6 addr show dev ${WAN_IF} mngtmpaddr| grep inet6 | awk '{sub("/.*","",$2); print $2}')
WAN_IPV6ADDR_PREFIX=$(echo ${WAN_IPV6ADDR} | awk -F: '{print $1}')
LAN_IF="enp2s0"

function check_ipv6_addr() {
  echo $(ip -6 addr show dev ${LAN_IF} | grep inet6 | grep ${WAN_IPV6ADDR_PREFIX} | awk '{sub("/.*","",$2); print $2}')
}

## check dhcp-ipoe
if test -n "${WAN_IPV6ADDR}" -a -z "$(check_ipv6_addr)" ; then
  systemctl stop systemd-networkd.service
  systemctl disable systemd-networkd.service
  systemctl start wide-dhcpv6-client.service
fi
```

常駐するわけではないのに無駄にfunctionとか使っていますが、/usr/local/sbin/check-ipoe.shを作成して次のように定期的に実行されるよう```$ sudo crontab -e```でrootのエントリに追加しています。

```text:crontabエントリ
* * * * * /usr/local/sbin/check-ipoe.sh >/dev/null 2>&1
```

このスクリプトには将来的に各設定ファイルに埋め込まれているIPv6アドレスが変化した場合に、それらを修正するような機能も追加する予定です。

設定は以上です。

# 実際に割り当てられたIPv6アドレスの様子

enp1s0に割り当てられたIPv6アドレスの/64がxx00で終わり、enp2s0に割り当てられたIPv6アドレスのprefixがxx11で終っていることが分かると思います。

* enp1s0側IPv6のPrefix - 2409:xxxx:xxxx:xx00::/64
* enp1s0側IPv6のPrefix - 2409:xxxx:xxxx:xx11::/64

```bash:
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host
       valid_lft forever preferred_lft forever
2: enp1s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0d:b9:xx:xx:1c brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.2/24 brd 192.168.1.255 scope global dynamic enp1s0
       valid_lft 9650sec preferred_lft 9650sec
    inet6 2409:xxxx:xxxx:xx00:20d:xxxx:xxxx:7b1c/64 scope global dynamic mngtmpaddr noprefixroute
       valid_lft 9650sec preferred_lft 7850sec
    inet6 fe80::xxxx:xxxx:fe4a:7b1c/64 scope link
       valid_lft forever preferred_lft forever
3: enp2s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:0d:b9:xx:xx:1d brd ff:ff:ff:ff:ff:ff
    inet 192.168.1.10/24 brd 192.168.1.255 scope global enp2s0
       valid_lft forever preferred_lft forever
    inet6 2409:xxxx:xxxx:xx11:20d:xxxx:xxxx:7b1d/64 scope global
       valid_lft forever preferred_lft forever
    inet6 fe80::xxxx:xxxx:fe4a:7b1d/64 scope link
       valid_lft forever preferred_lft forever
4: enp3s0: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN group default qlen 1000
    link/ether 00:0d:b9:xx:xx:1e brd ff:ff:ff:ff:ff:ff
5: ip6tnl0@NONE: <NOARP> mtu 1452 qdisc noop state DOWN group default qlen 1000
    link/tunnel6 :: brd ::
6: ip6tnl1@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1452 qdisc noqueue state UNKNOWN group default qlen 1000
    link/tunnel6 2409:xxxx:xxxx:xx00:20d:xxxx:xxxx:7b1c peer 2404:8e00::feed:100
    inet6 fe80::xxxx:xxxx:fe9d:5bd7/64 scope link
       valid_lft forever preferred_lft forever
```

ip routeコマンドの出力は以下のとおりです。

```bash:
$ ip route
default dev ip6tnl1 proto static scope link
default via 192.168.1.1 dev enp1s0 proto dhcp src 192.168.1.2 metric 100
192.168.1.0/24 dev enp2s0 proto kernel scope link src 192.168.1.10
192.168.1.0/24 dev enp1s0 proto kernel scope link src 192.168.1.2
192.168.1.1 dev enp1s0 proto dhcp scope link src 192.168.1.2 metric 100
```

```bash:
$ ip -6 route
2409:xxxx:xxxx:xx00::/64 dev enp1s0 proto ra metric 100 pref medium
2409:xxxx:xxxx:xx11::/64 dev enp2s0 proto kernel metric 256 pref medium
fe80::/64 dev ip6tnl1 proto kernel metric 256 pref medium
fe80::/64 dev enp2s0 proto kernel metric 256 pref medium
fe80::/64 dev enp1s0 proto kernel metric 256 pref medium
default via fe80::xxxx:xxxx:fef9:b078 dev enp1s0 proto ra metric 100 pref medium
```

# ip6tables(ipv6 firewall)の設定

nftablesに移行しているご時世ですが、ip6tablesの設定です。

内容は、wide-dhcpv6-clientのセクションで説明していますが、/etc/wide-dhcpv6/dhcp6c-scriptの中で、```ip6tables-restore < /etc/network/ip6tables.conf``` を加えて反映させています。

```text:/etc/network/ip6tables.conf
# Generated by ip6tables-save v1.4.21 on Sun Aug 21 18:02:47 2016
*filter
:INPUT DROP [0:0]
:FORWARD DROP [0:0]
:OUTPUT DROP [0:0]
:logdrop - [0:0]
:logdropfwd - [0:0]
:logdropout - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -i enp2s0 -j ACCEPT
-A INPUT -i enp1s0 -p udp -m udp --dport 546 -j ACCEPT
-A INPUT -i enp1s0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 1   -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 2   -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 3   -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 4   -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 133 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 134 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 135 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 136 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 137 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 141 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 142 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 130 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 131 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 132 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 143 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 148 -j ACCEPT
-A INPUT              -p ipv6-icmp --icmpv6-type 149 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 151 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 152 -j ACCEPT
-A INPUT -s fe80::/10 -p ipv6-icmp --icmpv6-type 153 -j ACCEPT
-A INPUT -p tcp -m tcp --tcp-flags RST RST -j DROP
-A INPUT -j logdrop
-N ICMPFLOOD
-A ICMPFLOOD -m recent --set --name ICMP --rsource
-A ICMPFLOOD -m recent --update --seconds 1 --hitcount 6 --name ICMP --rsource --rttl -m limit --limit 1/sec --limit-burst 1 -j LOG --log-prefix "ip6tables[ICMP-flood]: "
-A ICMPFLOOD -m recent --update --seconds 1 --hitcount 6 --name ICMP --rsource --rttl -j DROP
-A ICMPFLOOD -j ACCEPT
-A FORWARD -i lo -j ACCEPT
-A FORWARD -i enp2s0 -j ACCEPT
-A FORWARD -i enp1s0 -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -j logdropfwd
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -o enp2s0 -j ACCEPT
-A OUTPUT -o enp1s0 -j ACCEPT
-A OUTPUT -j logdropout
-A logdrop -j LOG --log-prefix "dropped: "
-A logdrop -j DROP
-A logdropfwd -j LOG --log-prefix "[forward] dropped: "
-A logdropfwd -j DROP
-A logdropout -j LOG --log-prefix "[output] dropped: "
-A logdropout -j DROP
COMMIT
# Completed on Sun Aug 21 18:02:47 2016
```

IPv4用の設定はNAPTを経由しているため、念のためにstateでRELATED,ESTABLISHEDなパケットだけを通すような設定で十分だと思います。IPv4で固定アドレスを設定している場合には、より詳細なiptablesのルールを準備する必要があると思われるので注意してください。

# 課題や他に遭遇した問題など

## WAN,LAN両側が192.168.1.0/24ネットワークで構成されている点について

enp1s0とenp2s0の両方で、192.168.1.0/24を利用しているのはあまり良い案とはいえないと思います。
今回は既存のネットワークが192.168.1.0/24で構成されているので引き継ぎましたが、enp2s0側のLANは192.168.2.0/24などに分けるのが良いでしょう。

## WAN側でDHCPv4, RAとDHCPv6-PDを利用している点について

市販されている機器向けの設定を確認すると、DHCPv6-PDの設定だけが指定されているのが気になります。
とはいえ、RV-440MIを利用している限りは、APU2もクライアントとしてインターネットに接続したいので、このような設定が必要だろうと思っているところです。

## 構築の過程によって再現性がばらつくかもしれない問題について

APU2は別記事にまとめていますが、iPXE + minimal構成でインストールしてから作業を開始しています。

ただ、このサーバーを構築する課程で、当初はdnsmasqとwide-dhcpv6-clientを利用せずに、unboundとisc-dhcp-serverで構築する予定だったので、一度は関連するパッケージを導入してテストをしています。

後からこれらのサービスを停止してdnsmasq,wide-dhcpv6-clientのパッケージ導入 + 設定変更しているため、作業の手順によっては一部で再現性がない、必要のない手順が記述されている可能性はあります。

# Uubntu 20.04.1 へのアップグレードで遭遇した問題

do-release-upgradeによって18.04.5から20.04.1にUbuntuのバージョンを更新しました。
問題に遭遇した点を書いておきます。

## enp1s0側のIPv4アドレス(192.168.1.2/24)が割り当てられない

Netplanの設定でHGWのDHCPサーバーから割り当ててもらっていたIPv4アドレスが設定されない問題が発生したため、静的に設定するよう設定ファイルを変更しました。

```yaml:/etc/netplan/01-netcfg.yaml
network:
  version: 2
  ethernets:
    enp1s0:
      accept-ra: yes
      dhcp4: no
      dhcp6: no
      addresses:
        - 192.168.1.2/24
      routes:
        - to: 192.168.1.1/32
          via: 192.168.1.2
    enp2s0:
      accept-ra: no
      dhcp4: no
      dhcp6: no
      addresses:
        - 192.168.1.10/24
  tunnels:
    ip6tnl1:
      mode: ipip6
      remote: 2404:8e00::feed:100
      local: 2409:xxxx:xxxx:xx00:20d:xxxx:xxxx:7b1c
      routes:
      - to: 0.0.0.0/0
        scope: link
```

DHCPサーバーから渡されてきたIPアドレスとルーティング情報をnetplan上に設定しています。
HGWに他のマシンを接続するとIPが重複する可能性がありますが、このAPU2のみ接続しているので、このような設定にしました。

192.168.1.1への接続はDNSサーバーの問い合わせに利用しているだけなので、利用しないようにする方法もあると思います。

## ip6tablesコマンドのPATHの変更

/etc/wide-dhcpv6/dhcp6c-scriptファイルに追記していた、ip6tables-restoreコマンドのPATHが変更になっています。

```bash:変更部分の抜粋
/usr/sbin/ip6tables-restore < /etc/network/ip6tables.conf >/dev/null 2>&1
```

あらかじめPATH環境変数を適切に設定しておけば、full-pathで記述する必要はないので、問題には遭遇しなかったと反省しています。

```bash:18.04と互換性のある変更方法

export PATH=/sbin:/usr/sbin:$PATH
ip6tables-restore < /etc/network/ip6tables.conf >/dev/null 2>&1

```

基本的にコマンドは絶対PATHで書くべきだと思っていますが、本文ではPATH環境変数を利用する方法に変更しました。

また、ちゃんとnftablesに移行しないとだと思っていますが、ちょっと規模が大きいのでなかなか勉強できていません。

## ip6_tunnelが定期的にdownする現象

次のようなログが出力され、IPIP6のtunnelがダウンしていることを確認しました。
これはインターネット接続がIpv6を含めてダウンしている事で気がつきました。

```text
Jan  9 07:23:08 ubuntu kernel: [549439.141959] ip6_tunnel: ip6tnl1 xmit: Local address not yet configured!
Jan  9 07:23:08 ubuntu kernel: [549439.280100] ip6_tunnel: ip6tnl1 xmit: Local address not yet configured!
```

調べてみるとUbuntu 18.04でも定期的に出力されていたようですが、ネットワーク接続がダウンしていることはありませんでした。

とりあえずネットワーク接続を確認するスクリプトをcronに設定しています。

```bash:check-nwconn.sh
#!/bin/bash

PATH=/sbin:/bin:/usr/bin:/usr/sbin:$PATH
export PATH

ping -c 2 www.ibm.com > /dev/null
if test "$?" != "0"; then
  netplan apply
fi
```

cronには次のように設定しました。

```text:crontab
* * * * * /usr/local/sbin/check-ip6tnl1.sh >/dev/null 2>&1
```

### 原因と恒久的な対策

この現象はおそらくenp1s0側のRAを正常に処理できていない可能性があります。

まず、この問題は約90分間隔で、定期的に通信が遮断している事が分かっています。

```text:/var/log/syslogからの抜粋(最後と最初のエラーメッセージ)
Jan  9 20:15:05 ubuntu kernel: [20395.717684] ip6_tunnel: ip6tnl1 xmit: Local address not yet configured!
Jan  9 21:45:05 ubuntu kernel: [25795.487041] ip6_tunnel: ip6tnl1 xmit: Local address not yet configured!
```

```ip -6 route```コマンドの出力を確認すると、Ubuntu 18.04では表示されていなかった"expires xxxsec"という出力が確認できます。この数値はカウントダウンしていきますが、再起動直後の値は5400秒(90分)に近い事が分かっています。

```text:ipコマンドの出力
## ubuntu 18.04でのip -6 routeコマンドの出力
default via fe80::xxxx:xxxx:fef9:b078 dev enp1s0 proto ra metric 100 pref medium

## ubuntu 20.04でのip -6 routeコマンドの出力
default via fe80::xxxx:xxxx:fef9:b078 dev enp1s0 proto ra metric 1024 expires 4476sec pref medium
```

そして、ip -6 routeの出力を定期的に取得することで、問題が発生している時には、このdefault routeの設定が出力されていない事も分かっています。

類似の質問はaskubuntu.comでも確認できました。

* [askubuntu.com - Netplan-based network config ignores IPv6 router advertisements on Ubuntu 20.04](https://askubuntu.com/questions/1264176/netplan-based-network-config-ignores-ipv6-router-advertisements-on-ubuntu-20-04)

関連するQAを確認すると、気になったのは次の質問と回答でした。

* [askubuntu.com - ipv6 forwarding kills ipv6 connection?](https://askubuntu.com/questions/463625/ipv6-forwarding-kills-ipv6-connection)

どうやらipv6のforwarding設定を有効(ルーター設定)にすると、accept_raに1を設定しても無視され、2を指定する必要があるようです。これでも起動時にはenp1s0側にipv6アドレスは設定されているのですが、ともかく/etc/sysctl.confを変更し、様子をみることとしました。

```text:/etc/sysctl.confの該当箇所を変更
net.ipv6.conf.enp1s0.accept_ra=2
```

しばらくip -6 routeコマンドの出力を確認すると定期的にRAを受信してexpiresの数値が更新されることを確認しました。

```text:
...
default via fe80::xxxx:xxxx:fef9:b078 dev enp1s0 proto ra metric 1024 expires 4710sec hoplimit 64 pref medium
default via fe80::xxxx:xxxx:fef9:b078 dev enp1s0 proto ra metric 1024 expires 5386sec hoplimit 64 pref medium
...
```

tcpdumpでRAの内容を確認してみると、router lifetimeが5400secで、RA自身は約20〜30分前後の間隔で送信されており、expiresは定期的にRAを受信したタイミングで5400secに更新されているようでした。
元々のaccept_raの設定値が間違っていたことが明かになったので、これについては本文を修正しています。

```bash:tcpdumpの実行例(enp1s0側のみ)
$ sudo tcpdump -vv -i enp1s0 "icmp6 && ip6[40] == 134"
tcpdump: listening on enp1s0, link-type EN10MB (Ethernet), capture size 262144 bytes
18:12:31.586470 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 56) fe80::xxxx:xxxx:fef9:b078 > ip6-allnodes: [icmp6 sum ok] ICMP6, router advertisement, length 56
        hop limit 64, Flags [other stateful], pref medium, router lifetime 5400s, reachable time 30000ms, retrans timer 1000ms
          prefix info option (3), length 32 (4): 2409:xxxx:xxxx:xx00::/64, Flags [onlink, auto], valid time 14400s, pref. time 12600s
...
18:39:38.690403 IP6 (hlim 255, next-header ICMPv6 (58) payload length: 56) fe80::xxxx:xxxx:fef9:b078 > ip6-allnodes: [icmp6 sum ok] ICMP6, router advertisement, length 56
        hop limit 64, Flags [other stateful], pref medium, router lifetime 5400s, reachable time 30000ms, retrans timer 1000ms
          prefix info option (3), length 32 (4): 2409:xxxx:xxxx:xx00::/64, Flags [onlink, auto], valid time 14400s, pref. time 12600s
...
```

# Uubntu 22.04.3 へのアップグレードで遭遇した問題

18.04から使い続けてきたIPoEルーターのOSを20.04から22.04にアップグレードしたところ、インターネットに接続できなくなりました。

遭遇した現象は次のとおりです。

* systemd-networkd.service と wide-dhcpv6-client が競合してDHCPv6-PDによるアドレス割り当てが動作しなくなった
* WAN側インタフェース(enp1s0)にIPv6アドレスが割り当てられなくなった
* ip6tnl1が割り当てられなくなった

反対に次のような状態も確認できていて、完全に機能が停止しているわけではありません。

* LAN側インタフェース(enp2s0)にはDHCPv6-PDからipv6アドレスが割り当てられている
* DNSサーバーの情報は割り当てられていてIPoEルーター上のnslookupには成功している
* dnsmasqはLANに対してIPv6の割り当てやDNSサービスを提供できている

どうやらwide-dhcpv6周りの挙動に問題がありそうですが、enp2s0に対しては正常に動作しているようにみえます。

## この機会に、まずは関係ないところを整理する

wide-dhcpv6-client を使わないようにした方が良いかもしれないと考えて、ip6tables-restoreの呼び出しをdhcp6c-scriptから削除しました。

代わりに **iptables-persistent** パッケージを導入しました。
これまでのip6tables-saveの出力を保存していた/etc/network/ip6tables.confを/etc/iptables/rules.v6に移動して**iptables.service**を有効にしています。

```bash:
$ sudo mv /etc/network/ip6tables.conf /etc/iptables/rules.v6
$ sudo systemctl enable iptables.service
```

## いろいろ調査した結果

Ubuntu 22.04のsystemd-networkdのDHCPv6-PDについては不具合の報告がいくつかあるようです。

うまく設定できている事例も報告されていますが、実際に試すとうまく動かなかったり、``man systemd.network`` に記載されていない設定が入っていたり少し怪しい雰囲気もあったりします。

少なくとも手元の環境では次の問題は解決できませんでした。

1. wide-dhcpv6-clientがsystemd-networkdとの競合によってどうしても安定して動作しない
2. ip6tnl1がどうやってもnetplan経由では設定できない
3. systemd-networkd単体ではDHCPv6-PDによるenp2s0へのIPv6アドレスの割り当てができない

wide-dhcpv6-clientについては他のdhcpcdやisc-dhcp-clientを使うなど他の方法もあるとは思います。

isc-dhcp-clientは/etc/dhcp/pd-scriptを起動しようとするとAppArmorに怒られてしまうので、/etc/apparmor.d/sbin.dhclientを変更して試しています。

```diff:/etc/apparmor.d/sbin.dhclientに加えた変更
--- sbin.dhclient.orig  2023-11-12 23:46:06.961079310 +0900
+++ sbin.dhclient       2023-11-12 23:45:48.393079147 +0900
@@ -55,6 +55,7 @@

   # if there is a custom script, let it run unconfined
   /etc/dhcp/dhclient-script Uxr,
+  /etc/dhcp/pd-script Uxr,

   # The dhclient-script shell script sources other shell scripts rather than
   # executing them, so we can't just use a separate profile for dhclient-script
```

ip6tnl1については、たまにenp1s0とenp2s0にIPv6が割り当てられて安定して動いているようにみえた時でも構成に失敗していました。

systemd-networkdだけでDHCPv6-PDが完結すればそれが一番良いのですが、Ubuntu 22.04ではそうはいかないようです。

少なくともIPv4によるipip6を含めた通信自体には問題ないので、Ubuntu 24.04で解決すると良いなぁと思いつつ、実用上の問題はあまりないので様子をみることにしました。

## wide-dhcpv6-clientの削除

うまく動かないのでパッケージを削除しました。

```bash:
$ sudo apt purge wide-dhcpv6-client
```

## Ubuntu 22.04でのip6tnl1の有効化について

/etc/netplan/ 以下の設定はUbuntu 20.04と同様に設定します。これがないと以下のip6tnl1デバイスはUPしません。

```bash:ip6tnl1を手動で設定する
$ ip link add name ip6tnl1 type ip6tnl local 2409:xxxx:xxxx:xx00:20d:xxxx:xxxx:7b1c remote 2404:8e00::feed:101 mode ipip6
```

このコマンドを必要に応じて実行するスクリプトを配置してシステム起動後1分以内にはリンクがUPするようになっています。

## systemd-networkd関連の設定

以下の2つのファイルを配置しています。
うまく動いていないDHCPv6-PD関連の設定も入っていますが、これはbr0で構成すれば動くんだろうなぁと思いつつ、そこまでまだ進めていません。

```yaml:/etc/systemd/network/10-netplan-enp1s0.network.d/override.conf
[Match]
Name=enp1s0

[DHCPv6]
PrefixDelegationHint=::/60
DUIDType=link-layer

[Network]
DHCP=yes
```

```yaml:/etc/systemd/network/10-netplan-enp2s0.network.d/override.conf

[Match]
Name=enp2s0

[Network]
Address=192.168.1.10/24
DHCPv6PrefixDelegation=yes
IPv6DuplicateAddressDetection=1
LinkLocalAddressing=ipv6

[DHCPv6PrefixDelegation]
SubnetId=1

[DHCPv6]
DUIDType=link-layer
```

ここまで進めてもdnsmasqが割り当てるIPv6アドレスがないといったエラーは出していますが、とりあえずはIPv4をdnsmasqから割り当ててip6tnl1経由でインターネットにアクセスできるようになっています。

APU2からはIPv6でも問題なく動作しているので、どうしてもIPv6経由でないと閲覧できない情報などがあれば考えますが、IPMasqueradeはやりたいこととは違うので試していません。


## 参考資料

* https://erik.nygren.org/dhcpv6-pd-on-ubuntu-2204.html
* https://developers.redhat.com/blog/2019/05/17/an-introduction-to-linux-virtual-interfaces-tunnels#ip6tnl_tunnel
* https://bugs.launchpad.net/netplan/+bug/1771886

# Uubntu 24.04 への導入で遭遇した問題

Ubuntu 24.04には新規にAPU2を準備してUbuntu 24.04を導入してから、Ansibleを利用して22.04と同様の構成にしています。

Ubuntu 22.04と比較しながら作業を進めて、22.04と同程度には動作していますが、DHCPv6-PDによるenp2s0へのIPv6アドレスの付与には成功していません。

## systemdのバージョンアップによる変更

Ubuntu 22.04ではsystemd v249が、Ubuntu 24.04ではv255が利用されています。

IPv6周りの設定は変数名の変更(DHCPv6PrefixDelegation → DHCPPrefixDelegation)や追加があるため、そのままでは動作しません。

マニュアルに従って変更する必要があります。

## Netplanのバージョンアップによる影響

Ubuntu 22.04ではcronjobによってip6tnl1をリンクアップしていましたが、22.04の設定のままで6to4のトンネルは動作していて問題なく通信ができています。

## その他に試していること

wide-dpcpv6がうまく動作しないのは引き続き同様なので、dhcpcdなどを試してみましたが、enp2s0側にPrefix Delegationで取得したIPv6アドレスを付与することはできていません。

この点については、fd.io vppの導入も含めて引き続き実験を進めていきます。

以上