IPv6アドレスを十分な数もらえないときでも、コンテナ内にIPv6を通す

# はじめに

*IPv6がきた！ IPv6をコンテナでも使うぞ！ え、/48とか貰えないの！？ ケチ...orz*

基本的には以下のサイトに書いてあるのをつぎはぎしたようなものです。感謝。
- [robbertkl/docker-ipv6nat: Extend Docker with IPv6 NAT, similar to IPv4](https://github.com/robbertkl/docker-ipv6nat)
- [Ubuntu18.04でDockerコンテナのIPv6を有効にする方法](https://ichi.pro/ubuntu-18-04-de-docker-kontena-no-ipv-6-o-yuko-ni-suru-hoho-218268436666786)

# IPv6なのにNAPTって正気?

NAPTを廃止することはIPv6の目的の一つだったわけですが、今の段階でdockerで制限された数のIPv6アドレスをうまく取り扱うにはNAPTが一番手っ取り早い解決策となります。

IPv6でNAPTなんて私も気持ち悪いと思いますし、正気の沙汰ではないと私も思いますが、世の中綺麗事だけではうまくいきません。
時には毒を以て毒を制することも必要です...
いやほんと、普通に/64くださいよ...

# 1個だけIPv6アドレスが使える場合

- ホストで使えるIPv6は `2001:db8:1234:abcd:111:222:333:444/128` のみ

## 準備

- まずホストでちゃんとipv6が通ることを確認
    - `ping6 -n -c 4 www.google.com`
- dockerdをipv6有効で起動
    - 必要箇所のみ抜粋

```/etc/docker/daemon.json
{
  "ipv6": true,
  "fixed-cidr-v6": "fd00::/80"
}
```

- ip6tablesで-t natする

```sh:
ip6tables -t nat -A POSTROUTING -s fd00::/80 ! -o docker0 -j MASQUERADE
ip6tables -t nat -A POSTROUTING -s fd00:1::/80 ! -o docker0 -j MASQUERADE
```

## コンテナ内側から外側へのIPv6アクセス

- ping6をしてみる
    - daemon.jsonに書いてある`fd00::/80`から割り当てられる
    - `docker run --rm -t busybox sh -c "ip addr ; ping6 -n -c 4 www.google.com"`
- docker-composeでnetworks:を指定する場合の例
    - たとえば`fd00:1::/80`を明示的に指定する場合

```docker-compose.yaml
networks:
  default:
    enable_ipv6: true
    ipam:
      config:
        - subnet: fd00:1::/80
```
- いずれもip6tablesのMASQUERADEで出ていく
- うまくいかないときは、docker container ls, docker network ls等でゴミが残っていないことを確認

## 外側からコンテナ内側へのIPv6アクセス

- docker-ipv6natの起動
 
```sh
docker run -d --name ipv6nat \
  --privileged \
  --network host \
  --restart unless-stopped \
  -v /var/run/docker.sock:/var/run/docker.sock:ro \
  -v /lib/modules:/lib/modules:ro \
  robbertkl/ipv6nat
```

- コマンドラインの場合: 省略
- docker-composeの場合:

```docker-compose.yaml
services:
  httpd6:
    image: busybox
    container_name: httpd6
    ports:
      - 8080:80
    command:
      - sh
      - -c
      - 'echo Hello world > index.html && httpd -f -v'
```

- テスト
    - `curl -v [2001:db8:1234:abcd:111:222:333:444]:8080`
    - `docker logs test` でアクセス元が見える

# 複数だけど/xxと指定できるほど多くない数のIPv6アドレスが使える場合

## 凡例
- `2001:db8:1234:abcd:111:222:333:444/128` がホストのIPv6アドレス
- `2001:db8:1234:abcd:a111:222:333:444{0,1,2,3...f}/128` が使用可能で、これをコンテナで使いたい
- `2001:db8:1234:abcd:a111:222:333::/112` をダミーのsubnetとする
- IPv4とMACアドレスは一つだけのまま
    - macvlanは使えない
    - ipvlanは頑張ればいけるかも
- 要するに美雲このはちゃん
    - [IPv6をVPSへ設定する｜ConoHa VPSサポート](https://support.conoha.jp/v/setipv6/)

## docker network connect版

- ip -6つなげる
    - `ip -6 addr add 2001:db8:1234:abcd:a111:222:333:4440/128 dev eth0`
- daemon.json, MASQUERADE, docker-ipv6natは前節と同じ
- network作成
    - `docker network create --ipv6 --subnet=2001:db8:1234:abcd:a111:222:333::/112 --gateway=2001:db8:1234:abcd:a111:222:333:4440 ipv6_exposed_hosts`
    - gatewayで指定したのが外に露出する
- `2001:db8:1234:abcd:a111:222:333:dead` をダミーとして使う
    - これはダミーなので、外からの経路はなくてもいい
- コマンドラインの場合:
    - `docker network connect --ip6 2001:db8:1234:abcd:a111:222:333:dead ipv6_exposed_hosts httpd6`
- docker-composeの場合:
```docker-compose.yaml
networks:
  ipv6_exposed_hosts:
    driver: bridge
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: 2001:db8:1234:abcd:a111:222:333::/112
          gateway: 2001:db8:1234:abcd:a111:222:333:4440
services:
  httpd6:
    image: busybox
    container_name: httpd6
    ports:
      - 8080:80
    command:
      - sh
      - -c
      - 'echo "Hello world!" > index.html && httpd -f -v'
    networks:
      ipv6_exposed_hosts:
        ipv6_address: 2001:db8:1234:abcd:a111:222:333:dead
```

- テスト
    - `curl -6 [2001:db8:1234:abcd:a111:222:333:4440]:8080`
## ip6tables -t nat -j DNAT版

- ip -6つなげる
    - `ip -6 addr add 2001:db8:1234:abcd:a111:222:333:4440/128 dev eth0`
- daemon.json, MASQUERADEは前節と同じ
- コンテナを普通に立ち上げる
- そのコンテナのIPv6アドレスを取得(上記でいう末尾dead、無指定ならfd00::から選ばれるので調べる)
    - `docker container inspect httpd6 | jq .[0].NetworkSettings.Networks[].GlobalIPv6Address`
- ip6tables -j DNATで繋げる
    - `ip6tables -t nat -I DOCKER -i eth0 -d 2001:db8:1234:abcd:a111:222:333:4440 -p tcp -m tcp --dport 8080 -j DNAT --to-destination [${上記アドレス}]:80`
- テスト
    - `curl -6 [2001:db8:1234:abcd:a111:222:333:4440]:8080`

## nd proxy版

- ホストのeth0にip aliasを追加するのではなく、nd proxyを繋げる
```sh:
sysctl -w net.ipv6.conf.all.forwarding=1
sysctl -w net.ipv6.conf.eth0.proxy_ndp=1
ip -6 neigh add proxy 2001:db8:1234:abcd:a111:222:333:4441 dev eth0
```
- docker-composeであげる
```docker-compose.yaml
networks:
  ipv6_exposed_hosts:
    driver: bridge
    enable_ipv6: true
    ipam:
      config:
        - subnet: 2001:db8:1234:abcd:a111:222:333::/112
services:
  httpd6:
    image: busybox
    container_name: httpd6
    command:
      - sh
      - -c
      - 'echo "Hello world!" > index.html && httpd -f -v'
    networks:
      ipv6_exposed_hosts:
        ipv6_address: 2001:db8:1234:abcd:a111:222:333:4441
```
- ipv6_exposed_hostsのidを調べる
    - `docker network ls`
- ufwで穴をあけて経路繋げる
    - `ufw route allow in on eth0 out on br-${上記netowrkidの先頭}`
- テスト
    - `curl -6 [2001:db8:1234:abcd:a111:222:333:4441]:80`

# コンテナ宛通信のip6tablesによるフィルタリング

- 通常、IPv4のコンテナ宛はFORWARD(の子のDOCKER-USER)でフィルタリングしますが、この記事のnd proxy版以外の方法の場合INPUT chainになります

# おわりに

- IPv6アドレスもっとください
- awsさん、IPv4は捨てるんで別に有償でも構わないけどEIPはIPv6対応しないんですか？

# 参考資料

- [IPv6 Subnet Calculator - subnettingpractice.com](https://subnettingpractice.com/ipv6-subnet-calculator.html)
- [DockerのコンテナにIPv6で接続すると接続元IPアドレスがコンテナネットワーク内のIPv4アドレスになる - Qiita](https://qiita.com/int512/items/764175a9b16458a0f38c)
- [robbertkl/docker-ipv6nat: Extend Docker with IPv6 NAT, similar to IPv4](https://github.com/robbertkl/docker-ipv6nat)
- [Ubuntu18.04でDockerコンテナのIPv6を有効にする方法](https://ichi.pro/ubuntu-18-04-de-docker-kontena-no-ipv-6-o-yuko-ni-suru-hoho-218268436666786)
- [docker-composeでnginxをIPv6化 | mahori ブログ 本館](https://mahori.jp/2021/04/13/docker-nginx-ipv6/)
- [RPi4 - Docker with IPv6 and Docker Compose](https://gpailler.github.io/2019-10-08-pi4-part3/)