3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

dockerのポートフォワードをIPv6にも対応させる

Last updated at Posted at 2020-02-26

更新履歴

2020/08/02
いつのまにかスクリプト内で発行するnftコマンドのオプション-aが先頭にないとエラーになっていたので、修正しました。

概要

squidでActiveDirectory連携とSSLインターセプトするProxyをdockerで手軽につくる
Docker Composeでネットワークサービス群を5分で作れるようにした(dhcp/radius/proxy/tftp/syslog/dns)
上記のエントリでつくったProxyにULAのIPv6 Onlyな環境からアクセスしようとしたら、できなかった。

以下、Windowsの状況ですが、iOSとAndroidも混在しているので、DHCPv6が使えず(AndroidがDHCPv6つかえない)、ゲートウェイでRAとRDNSS(ドメインサフィックスも付与)を動作させている状態です。

Wireless LAN adapter Wi-Fi:

   接続固有の DNS サフィックス . . . . .: prosper2.net
   説明. . . . . . . . . . . . . . . . .: Intel(R) WiFi Link 5300 AGN
   物理アドレス. . . . . . . . . . . . .: 00-21-6A-CA-4A-A2
   DHCP 有効 . . . . . . . . . . . . . .: はい
   自動構成有効. . . . . . . . . . . . .: はい
   IPv6 アドレス . . . . . . . . . . . .: fd5a:ceb9:ed8d:fe10:70ff:9d38:95b6:9682(優先)
   一時 IPv6 アドレス. . . . . . . . . .: fd5a:ceb9:ed8d:fe10:4ca8:def3:2da5:a3b6(優先)
   リンクローカル IPv6 アドレス. . . . .: fe80::70ff:9d38:95b6:9682%13(優先)
   デフォルト ゲートウェイ . . . . . . .: fe80::b60c:25ff:fe0f:df11%13
   DHCPv6 IAID . . . . . . . . . . . . .: 117449066
   DHCPv6 クライアント DUID. . . . . . .: 00-01-00-01-24-DA-AE-0D-5C-26-0A-2D-95-59
   DNS サーバー. . . . . . . . . . . . .: fd5a:ceb9:ed8d:fe0a:10:254:10:251
   NetBIOS over TCP/IP . . . . . . . . .: 無効
   接続固有の DNS サフィックス検索の一覧:
                                          prosper2.net

なぜ通信できなかったのか

Dockerネットワークは以下のコマンドで作成したものを利用していました。

# docker network create --ipv6  --driver=bridge  --subnet=fd5a:ceb9:ed8d:dead::/64 br_infra_net

IPv4のDNATは設定されていた。(ほかのコンテナのものも入ってます)

# nft list chain ip nat PREROUTING ; nft list chain ip nat DOCKER
table ip nat {
        chain PREROUTING {
                type nat hook prerouting priority -100; policy accept;
                fib daddr type local counter packets 42 bytes 6049 jump DOCKER
        }
}
table ip nat {
        chain DOCKER {
                meta l4proto tcp tcp dport 8081 counter packets 2 bytes 104 dnat to 172.18.0.2:8081
                meta l4proto tcp tcp dport 53 counter packets 0 bytes 0 dnat to 172.18.0.3:53
                meta l4proto udp udp dport 53 counter packets 0 bytes 0 dnat to 172.18.0.3:53
                meta l4proto udp udp dport 514 counter packets 10 bytes 3332 dnat to 172.18.0.4:514
                meta l4proto tcp tcp dport 8080 counter packets 0 bytes 0 dnat to 172.18.0.5:8080
                meta l4proto udp udp dport 1813 counter packets 0 bytes 0 dnat to 172.18.0.6:1813
                meta l4proto udp udp dport 1812 counter packets 0 bytes 0 dnat to 172.18.0.6:1812
        }
}

IPv6はされてなかった。悲しい。

# nft list chain ip6 nat PREROUTING
table ip6 nat {
        chain PREROUTING {
                type nat hook prerouting priority -100; policy accept;
        }
}

どうやらコンテナにIPv6ふってるんだからNDPで直接通信してね。
みたいなニュアンスらしい。(違ってたらごめんなさい)

やだ、めんどくさい。
IPv4とおんなじ考え方で通信させたいんだ。

スクリプトで対応した

ちゃんとdockerのマニュアルとか読めば解決策があるような気もするのですが、面倒なのでnftでDNATすることにしました。
IPv4のnftを参考にして、IPv6もおなじようなDNATするスクリプトを作成。

スクリプトは jq を利用しています。
dnf -y install jq などでインストールしておく必要があります。

ipv6_port_bind.pl
#!/usr/bin/perl
use strict;

my ($buf,$addr,$cnt,$port,$prot,$handle,$cmd);

my $CHAIN = "DOCKER-USER-ipv6-dnat";

if($ARGV[0] !~ /flush|^$/){
  print "usage : $0 [ flush ]\n";
  exit;
}


####################
#  FLUSH RULES
####################


print "---------- FLUSH exist rules and chains ----------\n";
foreach(split(/\n/,`nft -a list chain ip6 nat PREROUTING | grep "jump $CHAIN" 2> /dev/null`)){
  chomp($_);
  $handle = (split(/\ /,$_))[-1];
  $cmd = "nft delete rule ip6 nat PREROUTING handle $handle";
  print $cmd."\n";
  system("$cmd 2> /dev/null");
}

$cmd = "nft delete chain ip6 nat $CHAIN";
print $cmd."\n";
system("$cmd 2> /dev/null");

if($ARGV[0] eq "flush"){
  exit;
}

print "\n";

####################
#  NEW RULES
####################

$buf  = "";
$buf .= "nft create chain ip6 nat $CHAIN\n";

foreach $cnt (split(/\n/,`docker-compose ps --service`)){
  chomp($cnt);

  $addr = `docker inspect $cnt | jq  '.[].NetworkSettings.Networks[].GlobalIPv6Address'`;
  chomp($addr);
  $addr =~ s/\"//g;

  foreach(split(/\n/,`docker inspect $cnt | jq  '.[].HostConfig.PortBindings|keys' | egrep "tcp|udp" `)){
    chomp($_);
    $_ =~ s/\"|\,|\ //g;
    $port = (split(/\//,$_))[0];
    $prot = (split(/\//,$_))[1];

    #print "$addr : $port $prot \n";
    $buf .= "nft add rule ip6 nat $CHAIN meta l4proto $prot $prot dport $port counter dnat to [$addr]:$port\n";
  }

}

$buf .= "nft add rule ip6 nat PREROUTING  fib daddr type local counter jump $CHAIN\n";

print "---------- CREATE chain and ADD rules ----------\n";
system($buf);
print $buf."\n";

print "---------- CHECK rules and chain ----------\n";
print `nft list chain ip6 nat PREROUTING ; nft list chain ip6 nat $CHAIN`;

exit;

内容は、IPv4のDNATの処理を同じものを、 docker-compose.yml で生成したコンテナに対して実施しています。

docker-compose.yml と同じディレクトリにおいて、スクリプトを実行すると、起動しているコンテナに対して docker inspect コンテナ を実行します。
得られた出力からポートバインドの設定を抜き出して、 nft の処理を生成しています。

実際に動作させると、以下のようになります。

# ./ipv6_port_bind.pl

---------- FLUSH exist rules and chains ----------
nft delete rule ip6 nat PREROUTING handle 250
nft delete chain ip6 nat DOCKER-USER-ipv6-dnat

---------- CREATE chain and ADD rules ----------
nft create chain ip6 nat DOCKER-USER-ipv6-dnat
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto udp udp dport 514 counter dnat to [fd5a:ceb9:ed8d:dead::4]:514
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto tcp tcp dport 8081 counter dnat to [fd5a:ceb9:ed8d:dead::2]:8081
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto tcp tcp dport 8080 counter dnat to [fd5a:ceb9:ed8d:dead::5]:8080
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto udp udp dport 1812 counter dnat to [fd5a:ceb9:ed8d:dead::6]:1812
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto udp udp dport 1813 counter dnat to [fd5a:ceb9:ed8d:dead::6]:1813
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto tcp tcp dport 53 counter dnat to [fd5a:ceb9:ed8d:dead::3]:53
nft add rule ip6 nat DOCKER-USER-ipv6-dnat meta l4proto udp udp dport 53 counter dnat to [fd5a:ceb9:ed8d:dead::3]:53
nft add rule ip6 nat PREROUTING  fib daddr type local counter jump DOCKER-USER-ipv6-dnat

---------- CHECK rules and chain ----------
table ip6 nat {
        chain PREROUTING {
                type nat hook prerouting priority -100; policy accept;
                fib daddr type local counter packets 0 bytes 0 jump DOCKER-USER-ipv6-dnat
        }
}
table ip6 nat {
        chain DOCKER-USER-ipv6-dnat {
                udp dport shell counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::4]:shell
                tcp dport tproxy counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::2]:tproxy
                tcp dport http-alt counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::5]:http-alt
                udp dport radius counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::6]:radius
                udp dport radius-acct counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::6]:radius-acct
                tcp dport domain counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::3]:domain
                udp dport domain counter packets 0 bytes 0 dnat to [fd5a:ceb9:ed8d:dead::3]:domain
        }
}

複数回起動しても問題ないように、処理の際には、既存ルールをフラッシュしてから、再作成しています。
nft ~ で始まる行が、実際に発行されるnftコマンドです。
最後に新たに作成したルールとCHAINを表示しています。

これで、ULAなIPv6 Only環境からもDockerコンテナにアクセスできるようになりました。
よかった。

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?