背景
我が家のネットワークの調子が悪い。
ミーティング中やゲーム中に突如回線落ちする。ひどいときは日に3回ほどネットが切断される。どうやら利用しているルーターの負荷が高まると勝手に再起動し、その間に通信ができないということがわかった。
しょうがないので、ちょっと高めのIPv6対応ルーターを導入することにした。
東京住まいということもあり、PPPoE通信すると夜は画像すら満足にダウンロードできないレベルで遅い。IPoE接続(v6プラス)のルーターは必須だ。
Amazonでルーターを購入し利用しはじめて数日、新ルーターは性能が低いのかIPoEでも速度がでないことに気がついた。ネットの調子悪いのか?と思い昔のルーターに戻して通信したら爆速。まじか、せっかく高い金払ったのに通信遅かったら意味ないじゃん。
こうなったら、ルーターを自分で作るしか無い。
Rasberry Pi4を使って自分でルーターを作ることに決めた。
構築するネットワーク
構築するネットワークは以下の通り。
我が家のISPは@Niftyで、IPv6プラスオプションを申し込んでいる。
IPv6のインターネット接続は簡単、単にイーサネットケーブルを直接ONUに繋げばよい。
ただ、自宅内のすべての端末がグローバルネットワークにダイレクト接続されるのはセキュリティの面で怖い。そのため、自宅内はIPv6プライベートネットワーク(fd00::/64)を組み、ルーターがIPマスカレードしてグローバルなネットワークにでていく構成とした。
IPv6の経路構築とIP割当は(DHCPv6ではなく)RAで行う。DNSの通知も今回RAにまかせることとした。
問題はIPv4ネットワークへの接続だ。IPv4 インターネットを利用するためには、JPNE(IPoE提供事業者)のBR(Border Relay)にトンネルを張り、途中までIPv6で通信しBRでIPv4にでていく必要がある。JPNEの場合、MAP-E方式でIPv4 over IPv6を実現しているとのこと。
自宅内のIPv4はプライベートネットワーク(10.0.0.0/8) を組み、IPアドレスの配布とDNS通知はDHCPで行う。
プライベートネットワークから外部に通信する際は、ルーターが一度通信を受け止め、BRへのトンネルを経由してインターネットとの通信を行う。
Rasberry Piのセットアップ
Rasberry Piは 4のModelB ( https://www.raspberrypi.org/products/raspberry-pi-4-model-b/ )を使うことにした。こいつをルーターにする。OSはUbuntu Server 20.04LTS。
Rasberry Pi はLANコネクタは1つしかないので、別途USB接続のLANアダプタを購入し接続した。これで eth0 と eth1 の2つができた。
今回はeth0をWANネットワークとし、eth1をLANネットワークとする。
Rasberry PiへのOSインストールなどは省略する。
ルーターにするために必要なパッケージはあらかじめ以下コマンドでインストールしておく。
# IPv6 Router Advertisement(RA) のために利用
$ sudo apt install radvd
# IPv4 DHCPのためにインストール
$ sudo apt install isc-dhcp-server
IPv6の世界へRasberry Piを接続する
まずは現状のnetplanを確認。
eth0はONUに直接つなぎ、IPv6の世界へ接続する。
eth1は何も接続していない。
# cat /etc/netplan/50-cloud-init.yaml
network:
ethernets:
eth0:
dhcp4: true
optional: true
version: 2
この状態でping6をかけてみる。
2404:6800:4004:80b::200e は youtube.com だ。
ubuntu@rasp-server:~$ ping6 2404:6800:4004:80b::200e
PING 2404:6800:4004:80b::200e(2404:6800:4004:80b::200e) 56 data bytes
64 bytes from 2404:6800:4004:80b::200e: icmp_seq=1 ttl=115 time=7.31ms
無事IPv6での通信が行えた。
DNSが無いため名前解決はできない。そのため、ping6 youtube.com
コマンドを入力してもレスポンスは返ってこない。
また、IPv4の通信も当然ながらできない。
ubuntu@rasp-server:~$ ping 163.44.168.xxx
ping: connect: Network is unreachable
ルーターIP割当とRA/DHCPの設定
次にnetplanを変更し、eth0にWAN用の設定をし、eth1にプライベートネットワークルーターとしてのIPを設定する。
eth0はRAを受け取り、グローバルなIPv6アドレスをもらうようにする。
eth1はプライベートネットワークのゲートウェイになるため、IPアドレスは固定化しておく。
DNSは自前でサービスを立てず、Googleの8.8.8.8を利用することにした。
$ cat /etc/netplan/50-cloud-init.yaml
network:
ethernets:
eth0:
dhcp4: true
dhcp6: false
accept-ra: yes
optional: true
eth1:
dhcp4: false
dhcp6: false
addresses:
- 10.0.0.1/8
- fd00::1/64
gateway4: 10.0.0.1
gateway6: fd00::1
nameservers:
addresses:
- 8.8.8.8
- 8.8.4.4
- 2001:4860:4860::8888
- 2001:4860:4860::8844
version: 2
プライベートネットワーク内にIPv4を配るため、(IPv4の)DHCPの設定をする。
10.0.0.0/8 のネットワークをプライベートとして利用することとした。
$ sudo vim /etc/dhcp/dhcpd.conf
# option domain-name "example.org";
# option domain-name-servers ns1.example.org, ns2.example.org;
## 上記はいらないのでコメントアウト
# 以下有効にする
ddns-update-style none;
# If this DHCP server is the official DHCP server for the local
# network, the authoritative directive should be uncommented.
# 以下も有効にする
authoritative;
# Use this to send dhcp log messages to a different log file (you also
# have to hack syslog.conf to complete the redirection).
# ログも有効にする
log-facility local7;
# 10.0.0.0 のローカルネットワーク構築
subnet 10.0.0.0 netmask 255.0.0.0 {
range 10.0.0.20 10.0.255.254; # DHCPとして割り当てるIPアドレスの範囲
option routers 10.0.0.1; # ルーターのアドレス=自分のアドレス
option broadcast-address 10.255.255.255;
option domain-name-servers 8.8.8.8, 8.8.4.4; # DNSはgoogleを使う
default-lease-time 432000; # IPリース時間は長めに取る
max-lease-time 864000;
}
プライベートネットワーク内のIPv6経路とアドレス割当のため、RAを設定する。以下のようにradvd.confを追加する。
$ cat /etc/radvd.conf
interface eth1 { # インターフェースはeth1(LAN)
AdvSendAdvert on;
AdvManagedFlag off; # Mフラグ Off
AdvOtherConfigFlag off; # Oフラグ Off
MinRtrAdvInterval 30;
MaxRtrAdvInterval 100;
prefix fd00::/64 { # fd00::/64 プライベートネットワークに通知
AdvOnLink on;
AdvRouterAddr on;
AdvAutonomous on;
};
RDNSS 2001:4860:4860::8888 2001:4860:4860::8844 # GoogleのDNSを使う
{
};
};
ここまで構築したら、設定をすべて反映する
sudo netplan try
sudo systemctl enable isc-dhcp-server
sudo systemctl enable radvd
sudo reboot # 念の為サーバーを再起動
反映が終わったら、eth1を内部スイッチにつなぎ、他のPCと接続する。
今回はiMacをeth1側につなぎ、DHCPで割り振ったIPの更新を行った。
結果、以下のようにプライベートIPとDNSが振られていることがわかる。
IPは割り当てられているが、この状態でIPv6のインターネットにも接続はできない。
IPv6をフォワードさせる
プライベートネットワークから外のネットワークにでていくためには、ルーターがIPv4/IPv6をフォワードする必要がある。
そのため、/etc/sysctl.conf を以下のように修正しForward設定を有効化する。
# /etc/sysctl.conf
# Uncomment the next line to enable packet forwarding for IPv4
# IPv4をforwardするためにコメントアウトする
net.ipv4.ip_forward=1
# Uncomment the next line to enable packet forwarding for IPv6
# Enabling this option disables Stateless Address Autoconfiguration
# based on Router Advertisements for this host
# IPv6をforwardするためにコメントアウトする
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.eth0.accept_ra = 2 # WAN側 RAを受け付ける
net.ipv6.conf.eth1.accept_ra = 0
net.ipv6.conf.all.disable_ipv6 = 0 # IPv6有効化
net.ipv6.conf.default.disable_ipv6 = 0
上記の設定を行ったら、以下コマンドで反映する
sudo sysctl -p
次にip6tablesを使い、fd00::/64 (ローカルネットワーク)ソースの通信をIPマスカレードしてWAN側に流す。すべての通信が素通りだと怖いので、ローカル発信の通信のみ許可する。このスクリプトを ipv6_forward.sh
として保存する
#!/bin/bash
WAN_DEVICE=eth0
LAN_DEVICE=eth1
LOCAL_SOURCE="fd00::/64"
ip6tables -F
# すべての通信を素通りだと怖いので MARKをつけてLAN内のものからのみ許可する
ip6tables -t nat -A PREROUTING -i $LAN_DEVICE -j MARK --set-mark 1000
ip6tables -t filter -A FORWARD -p tcp -m mark --mark 1000 -j ACCEPT
ip6tables -t filter -A FORWARD -p udp -m mark --mark 1000 -j ACCEPT
ip6tables -t filter -A FORWARD -m mark --mark 1000 -j ACCEPT
# SSHはローカルネットワークからのみ許可
ip6tables -A INPUT -p tcp -s $LOCAL_SOURCE --dport 22 -j ACCEPT
ip6tables -A INPUT -p tcp --dport 22 -j DROP
# fd00::/64 宛の通信は全部WANに流す
ip6tables -t nat -A POSTROUTING -s $LOCAL_SOURCE -o $WAN_DEVICE -j MASQUERADE
sudo chmod +x ./ipv6_forward.sh
sudo ./ipv6_forward.sh
しばらく時間をおいてから、iMacからcurlでyoutube.comに接続してみる。
[nishio@NPC] $ curl youtube.com -v [~]
* Trying 2404:6800:4004:81d::200e...
* TCP_NODELAY set
* Connected to youtube.com (2404:6800:4004:81d::200e) port 80 (#0)
> GET / HTTP/1.1
> Host: youtube.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Location: https://youtube.com/
< Content-Length: 0
< Date: Sun, 16 Aug 2020 09:38:36 GMT
< Content-Type: text/html
< Server: YouTube Frontend Proxy
< X-XSS-Protection: 0
<
* Connection #0 to host youtube.com left intact
* Closing connection 0
接続成功! しかし、YoutubeはIPv6に対応しているから見られるけど、あいかわらずIPv4のサイトは見られない状態。
Rasberry Pi ルーターを Map-EでIPv4の世界に接続する
問題のIPv4接続。これは一筋縄ではいかず、自分の契約しているISPや通信方式に依存した設定が必要となる。
JPNEのBRへの接続方法は非公開で、手軽に接続とはいかない。ただ、某大型掲示板に接続方法が記載されており(参考: https://gato.intaa.net/archives/13186 )、どこかの誰かが晒してくれたスクリプトを使い接続を行う。
まずはじめに、 http://ipv4.web.fc2.com/map-e.html にアクセスし、自分が取得できたIPv6アドレスを入力する。
するとCE、IPv4、PSIDが取得できるので、これを以下スクリプトに記載する。BRのアドレスは2chで拾った。
#!/bin/sh
BR='2404:9200:225:100::64'
CE='CEのアドレス'
IP4='自分のグローバルIPアドレス'
PSID='自分のPSID'
WANDEV='eth0'
LANDEV='eth1'
TUNDEV='ip6tunnel1'
ip -6 addr add $CE dev $WANDEV # WAN側デバイスにCEアドレス割当
ip -6 tunnel add $TUNDEV mode ip4ip6 remote $BR local $CE dev $WANDEV encaplimit none # TunnelでBRに接続
ip link set dev $TUNDEV mtu 1460 # MTU設定
ip link set dev $TUNDEV up # Tunnel有効化
# IPv4のデフォルトルートをトンネルしたDeviceにする
ip -4 route delete default
ip -4 route add default dev $TUNDEV
# Map-Eの転送許可追加
iptables -t nat -F
rule=1
while [ $rule -le 15 ] ; do
mark=`expr $rule + 16`
pn=`expr $rule - 1`
portl=`expr $rule \* 4096 + $PSID \* 16`
portr=`expr $portl + 15`
# ローカルネットワークからの通信にマークをつけて転送許可
iptables -t nat -A PREROUTING -i $LANDEV -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 filter -A FORWARD -p icmp -m mark --mark $mark -j ACCEPT
iptables -t filter -A FORWARD -p tcp -m mark --mark $mark -j ACCEPT
iptables -t filter -A FORWARD -p udp -m mark --mark $mark -j ACCEPT
# Map-Eのルールにしたがい通信を飛ばす このルールは自分の契約による
iptables -t nat -A POSTROUTING -p icmp -o $TUNDEV -m mark --mark $mark -j SNAT --to $IP4:$portl-$portr
iptables -t nat -A POSTROUTING -p tcp -o $TUNDEV -m mark --mark $mark -j SNAT --to $IP4:$portl-$portr
iptables -t nat -A POSTROUTING -p udp -o $TUNDEV -m mark --mark $mark -j SNAT --to $IP4:$portl-$portr
rule=`expr $rule + 1`
done
iptables -t mangle -o $TUNDEV --insert FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu
sudo chmod +x ./ipv4_forward.sh
sudo ./ipv4_forward.sh
ここまで設定したら、しばらく時間をおいて再度iMacから通信をしてみる。
[nishio@NPC] $ curl densan-labs.net -v [~]
* Trying 163.44.168.152...
* TCP_NODELAY set
* Connected to densan-labs.net (163.44.168.152) port 80 (#0)
> GET / HTTP/1.1
> Host: densan-labs.net
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Server: nginx
< Date: Sun, 16 Aug 2020 09:57:44 GMT
< Content-Type: text/html
< Content-Length: 178
< Connection: keep-alive
< Location: https://densan-labs.net/
<
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx</center>
</body>
</html>
* Connection #0 to host densan-labs.net left intact
* Closing connection 0
IPv4で接続できた。
IPv6 で本当に通信できているか確認する
https://test-ipv6.com/index.html.ja_JP や https://ipv6-test.com/ のサイトにアクセスすると、
通信がIPv6でできているか確認できる。
我が家のiMacから通信した結果は以下のようになった。
念の為、http://www.ipv6scanner.com/cgi-bin/main.py などでポートスキャンをかけ、余計なポートが空いていないかどうかのチェックもしておく。通信がすべてFilterされていれば安心だ。
これでルーターの完成。お疲れさまでした。