Debianで作ったルーターの調整について

(先程上げた記事にMACアドレス記載されていたので念の為上げ直します)

# 前提

https://qiita.com/m-m-n/items/0fa1bc14ed6584295adc

以前こんな感じで、ミニPCにDebianをインストールしてルーターを作った
現在もPCでルーターが運用されている

MinisforumのミニPCがちょいちょいストレージのエラーかなんかで再マウントされ、リードオンリーになりルーターが機能しなくなるケースがあったので、ミニPCの選定で最終候補に残っていたもう片方の[Skynew IN-1](https://www.skynew.jp/SHOP/IN-1.html)を結局買う
選定中より値上りしてしまっていた🥺

必要な手順やconfigについてはセットアップしやすいようにスクリプトを作っていたので、移行はそんなに手間取らずに完了できて良かった

# 問題が発生

以前からあったかもだけど
- LAN側からIPv4が通らないことがある
- LAN側からpingするとIPv4が優先される

という事象が見られた

IPv4が通らなくなることがあるのが解消できればLAN側でIPv4が優先されてても困ることはないのだけど、折角IPv6に対応してるのであればIPv6で通信したほうが良い

# 結論としては

## 【問題】LAN側からIPv4が通らないことがある

### 【解決？】MTUの値を調整したら解決した気がする

もうちょっと様子見てみないとなんともだけど、少なくとも調整してから何回かping打ってみて問題が解消されている
以前は何回か打てばpingが返ってこなくなる事象に遭遇することが頻繁にあった

## 【問題】LAN側からpingするとIPv4が優先される

### 【解決】gai.confの修正で解決した

`/etc/gai.conf` を修正してやることでデフォルトでIPv6が優先されるようになった

詳しくは[後述](https://qiita.com/m-m-n/items/73a02d8673c3220da629#%E7%B5%90%E6%9E%9C)

# 調べたこと

そもそもMAP-Eの設定をした後にIPv4とIPv6でpingして疎通確認までスクリプトで行っているのだけど、そこでIPv4が失敗していることがあった
ここはもう大分安定していたんだけど、その経験ゆえに何か違和感あればping飛ばしてみる癖がついていた

そこでLAN側からping打つと返ってこないケースが見られ、その際にIPv4で名前解決されていた

ということは？

## DNSについて確認する

ルーター側でsystemd-resolvedを利用しDNSサーバーにしている
LAN側では基本的にDNSサーバーをルーターのIPアドレスに指定する運用にしている
メインPCのUbuntuではsystemd-resolvedがスタブリゾルバとして動いており、そこでルーターのIPアドレスが指定されている

なので、 `/etc/resolv.conf` 自体は `127.0.0.53` となっているが実態としてはルーターに対して名前解決を行う

以下のコマンドを打つなどして挙動を確かめてみた

```bash
nslookup google.com
nslookup google.com 192.168.0.1
nslookup google.com fd00:0:dead:beef::1
dig A google.com
dig A google.com @192.168.0.1
dig A google.com @fd00:0:dead:beef::1
dig AAAA google.com
dig AAAA google.com @192.168.0.1
dig AAAA google.com @fd00:0:dead:beef::1
sudo resolvectl -p dns query google.com
```

DNSへの接続がIPv4だろうがIPv6だろうが、AレコードかAAAAレコードかの話だし、ここは何も問題なく想定通りにIPv4とIPv6が返ることを確認できた

## pingを打つ

```bash
ping google.com
ping -4 google.com
ping -6 google.com
```

デフォルトでIPv4になってしまっていたが、IPv4では問題の事象に遭遇しなければping通るし、IPv6指定なら問題なく名前解決もpingも通った

これをルーターのPCでやるとデフォルトでもIPv6で名前解決されていた……

## wgetしてみる

```bash
wget --no-proxy -O /dev/null https://www.google.com/
```

名前解決でIPv4とIPv6が表示されるがIPv4が利用される……
そしてIPv4が通らないケースに当たるとタイムアウトしてIPv6の通信で成功する状態となった

# ラチがあかない

IPv6優先で調べるとすでにIPv6優先になっているとか、(そのため)IPv6を無効にするためにはとかの記事が多くでてくる
違う、そうじゃない

そもそもlocalhostに対してping打つと::1で名前解決されるのでIPv6が優先されないわけじゃないようだ
もっとも/etc/hostsに記載されているのでDNSサーバーに問い合わせしてるわけではないが

一方、pingが返ってこなくなる問題についてはどこかでパケットが闇に葬り去られている雰囲気
そして果たしてこれが原因かは不明だけど、どうやらPath MTU Discovery Black Holeなる問題があるらしい

こういうのはwiresharkとかでパケット解析するとわかるものなのだろうか
HTTPとかの平文パケットを読んでみたことはあるけれども……

色々ググっては試してみるがまったく解決には至らなかった

# 結果

まずIPv6優先の方から

ULAしか持っていないからIPv4が優先されるのかと思い、ipコマンドで適当にグローバルアドレスのプレフィックスに合わせたIPv6を振ってみたらpingでIPv6が利用されるようになった
こうなると話は早く、ULAがIPv4より優先される方法を調べればいい

https://yoshi0808.github.io/new-technology/2021/06/26/ipv6-first/

この辺を参考にして `/etc/gai.conf` で以下を記載した

```conf
label fc00::/7 1
precedence fc00::/7 40
precedence ::ffff:0:0/96 1
```

これで無事にIPv6が優先されるようになり、IPv6環境確認サイトでもいい感じになった

https://test-ipv6.com/index.html.ja_JP

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/426191/282dafd3-97b3-78e5-e906-126bdaba8301.png)

続いてIPv4の通信が失敗する方について

IPv6のMTUの最小値が1280ということでWAN側のデバイスのMTUとIPv4を通すトンネルデバイスのMTUを1280に設定してみた
これでとりあえず設定してからpingが返ってこなくなる事象を見ていないが対処法として良いのかが不明
MTU上げられるなら数値大きい方が良いですしね……

MAP-EでIPv4通信するので、実際の物理的なNICにはIPv6で通信してると思うので、そこのMTUを1280にすることでIPv4で起きる事象が解消されるのは特に違和感ないです

が、この辺は良くわかってないので巧い設定があれば知りたい……

ちなみに元々100MbpsのマンションのVDSLなのでMTUを1280にしてもスピードテストで特に変わらず80〜90Mbpsというところ

# 付録

前の記事に載せてたadd-rulesスクリプトは以下のように変更した

```bash:~/map-e/add-rules
#!/bin/bash

function finish() {
  cd - > /dev/null 2>&1
  exit $1
}

cd $(dirname "$0")

NODE_PATH=$(which node)
if [ $? -ne 0 ]; then
  echo $NODE_PATH
  finish 1
fi

# LAN側のNIC
LAN_DEV=enp3s0
# WAN側のNIC
WAN_DEV=enp4s0
# TUNNELデバイス
TUN_DEV=tun0

# トンネルデバイスは落とす
ip link set $TUN_DEV down

# IPv6
WAN_IPv6=$(ip -6 addr show dev "$WAN_DEV" | grep inet6 | grep -v "inet6 fe" | sed -e 's/^ *inet6 \([^ ]*\).*/\1/')

# pppoeを落としておく
poff -a > /dev/null 2>&1

NODE_RET=$(node ./map-e.js "$WAN_IPv6")
if [ $? -ne 0 ]; then
    echo "$NODE_RET"
    finish 2
fi

echo $NODE_RET
eval "$NODE_RET"
# echo $BR
# echo $CE
# echo $IPv4
# echo $PSID

cat << YAML > /etc/netplan/00-init.yaml
network:
  version: 2
  renderer: networkd
  ethernets:
    # LAN
    ${LAN_DEV}:
      dhcp4: false
      dhcp6: true
      accept-ra: true
      mtu: 1500
      addresses:
        - 192.168.0.1/24
        - fd00:0:dead:beef::1/64
      match:
        macaddress: 00:00:00:00:00:00
      set-name: enp3s0
    # WAN
    ${WAN_DEV}:
      dhcp4: false
      dhcp6: true
      accept-ra: true
      optional: true
      mtu: 1280
      addresses:
        - ${CE}/64
      match:
        macaddress: 00:00:00:00:00:00
      set-name: enp4s0
      nameservers:
        addresses:
          - 2001:4860:4860::8888
          - 2001:4860:4860::8844
YAML

# cat /etc/netplan/00-init.yaml

cat << YAML > /etc/netplan/10-ipoe-gw.yaml
network:
  version: 2
  tunnels:
    ${TUN_DEV}:
      mode: ipip6
      accept-ra: true
      local: ${CE}
      remote: ${BR}
      mtu: 1280
      addresses:
        - ${IPv4}/32
      routes:
        - to: 0.0.0.0/0
          scope: link
YAML

# cat /etc/netplan/10-ipoe-gw.yaml

netplan generate

sleep 1

ip addr flush ${TUN_DEV}
ip link set ${TUN_DEV} down && ip link set ${TUN_DEV} up
ip -4 route delete default dev ${TUN_DEV}
ip -4 route add default dev ${TUN_DEV}
ip -6 tunnel change ${TUN_DEV} encaplimit none

iptables -t nat -F

rule=1
while [ $rule -lt 16 ]; do
  mark=$(($rule + 16))
  pn=$(($rule - 1))
  port_l=$(($rule * 4096 + $PSID * 16))
  port_r=$(($port_l + 15))

  iptables -t nat -A PREROUTING -m statistic --mode nth --every 15 --packet $pn -j MARK --set-mark $mark
  iptables -t nat -A OUTPUT -m statistic --mode nth --every 15 --packet $pn -j MARK --set-mark $mark

  iptables -t nat -A POSTROUTING -p icmp -o $TUN_DEV -m mark --mark $mark -j SNAT --to $IPv4:$port_l-$port_r
  iptables -t nat -A POSTROUTING -p tcp -o $TUN_DEV -m mark --mark $mark -j SNAT --to $IPv4:$port_l-$port_r
  iptables -t nat -A POSTROUTING -p udp -o $TUN_DEV -m mark --mark $mark -j SNAT --to $IPv4:$port_l-$port_r

  rule=$(($rule + 1))
done

iptables -t mangle -F
iptables -t mangle -o $TUN_DEV --insert FORWARD 1 -p tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1400:65495 -j TCPMSS --clamp-mss-to-pmtu

finish 0
```

これを呼び出すスクリプトは以下のようになっています

```bash:~/start-up/exec
#!/bin/bash

# OSブート時に実行されるスクリプト
# rootユーザーによって実行される想定

function writelog() {
  LOG_PATH=/dev/shm/startup.log
  echo "[$(date "+%Y-%m-%d %H:%M:%S")] $1" >> $LOG_PATH
}

writelog "start-up スクリプトを始します"

cd $(dirname "$0")

TRY=0
RETRY_MAX=5
while [ true ]; do
  writelog "map-e の適用処理を開始します"
  # MAP-E適用
  writelog "$(../map-e/add-rules 2>&1)"
  netplan apply

  writelog "www.iijmio.jp へ IPv4 PING を送信します"
  ping -4 -n -c3 -W1 www.iijmio.jp > /dev/null 2>&1
  IIJMIO_IPv4_PING=$?
  writelog "www.iijmio.jp へ IPv6 PING を送信します"
  ping -6 -n -c3 -W1 www.iijmio.jp > /dev/null 2>&1
  IIJMIO_IPv6_PING=$?

  writelog "google.com へ IPv4 PING を送信します"
  ping -4 -n -c3 -W1 google.com > /dev/null 2>&1
  GOOGLE_IPv4_PING=$?
  writelog "google.com へ IPv6 PING を送信します"
  ping -6 -n -c3 -W1 google.com > /dev/null 2>&1
  GOOGLE_IPv6_PING=$?

  IPv4_OK=0
  IPv6_OK=0

  if [ $IIJMIO_IPv4_PING -eq 0 -o $GOOGLE_IPv4_PING -eq 0 ]; then
    IPv4_OK=1
  fi
  if [ $IIJMIO_IPv6_PING -eq 0 -o $GOOGLE_IPv6_PING -eq 0 ]; then
    IPv6_OK=1
  fi

  if [ $IPv4_OK -eq 1 -a $IPv6_OK -eq 1 ]; then
    writelog "PING 確認が成功しました！ ($(($TRY + 1))回試行)"
    writelog "ping result (www.iijmio.jp):        $IIJMIO_IPv4_PING"
    writelog "ping result (www.iijmio.jp [IPv6]): $IIJMIO_IPv6_PING"
    writelog "ping result (google.com):           $GOOGLE_IPv4_PING"
    writelog "ping result (google.com [IPv6]):    $GOOGLE_IPv6_PING"
    break
  else
    if [ $TRY -ge $RETRY_MAX ]; then
      writelog "PING 確認に失敗しました"
      writelog "start-up スクリプトを停止します"
      echo "$(
        echo "ルーター起動スクリプトエラー"
        echo
        echo "map-eの設定に失敗しました"
        echo "ping result (www.iijmio.jp):        $IIJMIO_IPv4_PING"
        echo "ping result (www.iijmio.jp [IPv6]): $IIJMIO_IPv6_PING"
        echo "ping result (google.com):           $GOOGLE_IPv4_PING"
        echo "ping result (google.com [IPv6]):    $GOOGLE_IPv6_PING"
      )" | mail -s "ルーター起動スクリプトエラー" -a "From:alert@example.com" "$STARTUP_MAILTO"
      exit 1
    fi
    writelog "$(($TRY + 1))回目失敗"
    writelog "IPv4_OK: $IPv4_OK"
    writelog "IPv6_OK: $IPv6_OK"
    if [ $TRY -lt $RETRY_MAX ]; then
      sleep $((2 ** $TRY))
    fi
  fi

  TRY=$(($TRY + 1))
done

writelog "start-up スクリプトの全行程が完了いたしました"
```

色々触っててexecの方でnetplay applyするようにしてたのがそのままになっていた
add-rules側でnetplan applyまでする方が良いと思う

IPv4のping失敗は今回解決したIPv4に繋がらないケースがあるために発生していたと思われるので、リトライ処理も不要になるはずだが1発で終わるなら特に問題はないのでこれはそのままにしておく

---

それでは良いDebianライフを