家で使っているルーターはIPv6パススルー機能はついているものの、IPv6 プラスは非対応なので、ソフトウェア的に対応できないかなーと探していたところこんな記事を見つけた。

簡単に言うと、家中のIPv4の通信をUbuntuの上のルーターに集めてIPv6でカプセリングし、予め調べておいたカスタマーエッジに転送するというもの。

手元にあったAndroid端末のNexus 5にUbuntu Touchを焼いてトンネリングを実現してみた。

効果

有線化してNexus 5を介した「IPoE + IPv4 over IPv6(v6プラス)」と「PPPoE」を比較した結果がこちら

Nexus 5を介した通信

もともとの通信

IPv4の上り速度は低下しているが(というかPPPoeの上りが以上に早い)
IPv4の下り速度が、5.87Mbpsから60.7Mbpsにアップしている。

追記:
Apex Legendsなどのオンラインゲームでは何故か半分くらいの確率でパケットロスが起きる。
うまくつながったときは1試合通して大丈夫。うまくつながらなかったときは1試合通してパケロス。困る…

カーネルとシステムの両方をビルドする

UBports公式の記事を参考にビルドを行う。

環境構築 (Docker)

後々詰まることの無いように、Dockerfileも用意しておいた。
Dockerをインストール済みであれば、以下コマンドを打つだけで環境構築ができる。

docker run --name hoge -it kajindowsxp/ubp-build-hammerhead

環境構築 (Ubuntu)

Dockerを使わない場合はこっち。
公式はUbuntu 16.04を推奨しています。

必要パッケージのインストール

sudo dpkg --add-architecture i386 && sudo apt update
sudo apt install schedtool gcc g++ g++-multilib zlib1g-dev:i386 \
     zip libxml2-utils bc python-launchpadlib phablet-tools

ディレクトリ作成

mkdir ~/ubp-5.1
cd ~/ubp-5.1

リポジトリをinitする
allthefixingsブランチには、現在サポートされているUbuntu Touchのデバイス固有の情報が追加されている。

repo init -u https://github.com/ubports/android -b ubp-5.1-allthefixings --depth=1

ソースコードを取ってくる

repo sync -j10 -c

config設定

デフォルトのビルド設定では、IPv6トンネル機能が無効化されているので、
kernel/lge/hammerhead/arch/arm/configs/cyanogenmod_hammerhead_defconfigを編集する。

# CONFIG_IPV6_TUNNEL is not setの下に、
CONFIG_IPV6_TUNNEL=yを追記すればOK

# CONFIG_IPV6_TUNNEL is not set
CONFIG_IPV6_TUNNEL=y

ビルド

ビルド時の環境情報(環境変数など)を読み込む

source build/envsetup.sh

ビルド対象端末の固有情報を読み込む
書式は、cm_[デバイス名]-userdebug

lunch cm_hammerhead-userdebug

ビルド開始

mka

イメージを端末に焼く

生成先のディレクトリに移動するcoutというコマンドを実行する

cout

Dockerを使った場合は、生成物をホストにコピーしておく

root@b3771880db02:/work/ubp-5.1# cout
root@b3771880db02:/work/ubp-5.1/out/target/product/hammerhead# pwd
/work/ubp-5.1/out/target/product/hammerhead
root@b3771880db02:/work/ubp-5.1/out/target/product/hammerhead# exit
exit
aaa@DESKTOP-4BC47UM:~$ docker cp hoge:/work/ubp-5.1/out/target/product/hammerhead ./
aaa@DESKTOP-4BC47UM:~$ cd hammerhead/
aaa@DESKTOP-4BC47UM:~/hammerhead$ ls
android-info.txt  data                 obj                       recovery      system.img
boot.img          fake_packages        previous_build_config.mk  recovery.img  ubuntu
cache             gen                  ramdisk-android.img       root          userdata.img
cache.img         installed-files.txt  ramdisk-recovery.img      symbols
clean_steps.mk    kernel               ramdisk-ubuntu.img        system

Nexus 5をfastbootモードで起動しておき、PCにつなげる。

以下コマンドを実行し、bootとrecoveryを焼く

fastboot flash boot boot.img fastboot flash recovery recovery.img

次に、以下のテキストを[replace-file-system.sh](https://github.com/janimo/phablet-porting-scripts/blob/68734ca07998b8e784397df77d9aca4b968b3815/build/replace-android-system)として保存する。

#!/bin/bash

# Wait until the adb shell is unavailable, meaning the device is rebooting
wait_for_reboot() {
    while test -n "$(adb shell echo '1' 2>/dev/null)"
    do
        echo -n ".";
        sleep 3;
    done
    echo
}

#Wait until we get a working adb shell, meaning the device is in normal or recovery mode
wait_for_device() {
    while test -z "$(adb shell echo '1' 2>/dev/null)"
    do
        echo -n ".";
        sleep 3;
    done
    echo
}

SYSTEM_IMAGE=$1

if [ ! -f "$SYSTEM_IMAGE" ]; then
    echo "Usage: $0 system.img"
    exit
fi

echo "Rebooting to recovery"
adb reboot recovery
wait_for_device

echo "Mounting system partition"
adb shell "mkdir /a; if [ -e emmc@android ]; then mount emmc@android /a; else mount /data; mount /data/system.img /a; fi"

if file $SYSTEM_IMAGE | grep -v ": Linux rev 1.0 ext4" >/dev/null; then
    echo "Converting from sparse ext4 image to mountable ext4 image"
    simg2img $SYSTEM_IMAGE tmp.img >/dev/null
    resize2fs -M tmp.img >/dev/null 2>&1
    mv tmp.img $SYSTEM_IMAGE
fi

echo Pushing android system image...
adb push $SYSTEM_IMAGE /a/var/lib/lxc/android/system.img >/dev/null 2>&1 &

SIZE=$(stat -t $SYSTEM_IMAGE |awk '{print $2}')
S=0
while test $S -lt $SIZE
do
    sleep 1
    S=$(adb shell stat -t /a/var/lib/lxc/android/system.img | awk '{print $2}')
    printf "%0.2d%%\r" $[100*$S/$SIZE]
done

echo "Done, rebooting to Ubuntu"

adb reboot

# vim: expandtab: ts=4: sw=4

以下コマンドで実行権限を付与し、systemをnexus 5に書き込む

chmod +x ./replace-file-system.sh
./replace-file-system.sh system.img

端末がfastbootのままrecoveryモードにならない場合は、音量ボタンでrecoveryを選択し、電源ボタンを押す。

IPv4 over IPv6を設定する

こちらの記事を参考にnetwork.shを作成する

私の環境ではこんな感じになった。

#!/bin/sh
#set -x
BR='xxxx:xxxx:xxxx:xxxx::xxxx'
CE='xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx'
IP4='xxx.xxx.xxx.xxx'
PSID='xx'
WANDEV='wlan0'
TUNDEV='testtun' 

ip -6 addr add $CE dev $WANDEV
ip -6 tunnel add $TUNDEV mode ip4ip6 remote $BR local $CE dev $WANDEV encaplimit none
ip link set dev $TUNDEV mtu 1460
ip link set dev $TUNDEV up

ip -4 route delete default
ip -4 route add default dev $TUNDEV

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 -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 $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 1 -p tcp --tcp-flags SYN,RST SYN -m tcpmss --mss 1400:65495 -j TCPMSS --clamp-mss-to-pmtu

network.shを実行する

sudo bash network.sh

ここで端末のブラウザ(Morph Browser)からhttp://kiriwake.jpne.co.jp/v/にアクセスすると
「v6プラスつかっています」と表示される。

他のPCからのアクセスに対応する

nexus 5でやること

まず、書き込みができるようにrwでマウントする

sudo mount -o remount,rw /

次に/etc/sysctl.confを開き、net.ipv4.ip_forward=1のコメントアウト(#)を外す

sudo nano /etc/sysctl.conf
編集前
#net.ipv4.ip_forward=1

編集後
net.ipv4.ip_forward=1

変更が終わったら下記コマンドで反映させる

sudo sysctl -p

また、端末のローカルのIPv4アドレスを調べておく。
私の環境では192.168.11.6であることがわかる

$ ip -4 a show wlan0
22: wlan0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    inet 192.168.11.6/24 brd 192.168.11.255 scope global wlan0

他のPCでやること

IPv4のデフォルトルートを削除し、新たにNexus 5を経由して通信するように変更する
アドレスは先程調べたものを指定する

Linuxの場合

ターミナルで以下コマンドを入力する

sudo ip -4 route delete default
sudo ip -4 route add default via 192.168.11.6

Windowsの場合

コマンドプロンプトを管理者権限で開き、以下コマンドを入力する

route DELETE 0.0.0.0
route add 0.0.0.0/0 192.168.11.9

同様にブラウザからhttp://kiriwake.jpne.co.jp/v/にアクセスし、
「v6プラスつかっています」と表示されれば成功

知らなかった用語集

ネットワークを弄くり回すのは初めてだったので知らない単語や仕組みが沢山あった。
自分向けのメモ。

  • UT
    Ubuntu Touch
  • ubp
    ubports
  • phablet
    phone + tabletでスマートフォンのこと
  • JPNE
  • 一般人はVPSとインターネット回線の契約をするが、IPv6通信ではVPSが更にJPNEという組織に委託している。
  • CE(Customer Edge)
    自宅にある、IPv6トンネルの始端となる装置
  • BR(Border Relay)
    JPNEに存在している、IPv6トンネルの終端となる装置。
  • PSID(Port-Set Identifiers)
    CEごとに割り当てられる識別子。これを使ってどのポート番号を利用できるかが計算される。
  • dev (device) (developmentではない)
    「外部への通信」や「トンネリング」など機能をもたせることでインターフェースとして利用できる。
  • repo
    gitを管理するツール。複数のリポジトリを一気にプルしたりコミットしたりできる。