Split DNS setup with tsdnsproxy for overlapping CIDR ranges querying over 4via6 subnets

TailscaleでAmazon VPCで使用しているCIDRレンジをsubnet routingしようとすると、高確率でレンジが衝突します。 到達性については4via6という仕組みで解決しますが、DNSについてはもうひと工夫必要となり、そこが今回の記事の本題となります。

例となるシナリオ

前提

解決したい課題

解決策: tsdnsproxy

DNSプロキシ実装 tsdnsproxy を導入し、Split DNSと 透過4via6アドレス変換を実装することで、VPC内と同じDNS名でアクセスが可能になります。

tsdnsproxyについて

tsdnsproxy はTailscaleのコミュニティ (作者はTailscaleの社員) OSSプロダクトで、以下の特徴があります。

実装

以下の手順で実装していきます。

  1. 設定をGrantsに記述する
  2. Auth Keyの発行
  3. tsdnsproxyの起動
  4. 動作確認
  5. Tailscaleの管理画面 - DNS - Add Nameserverで “Restrict to domain” (Split DNS) としてtsdnsproxyのTailnet IPを指定する

Step 1: tsdnsproxy設定

ACLのtsdnsproxyに関するGrantsを以下のように設定します。

{
  "grants": [
    {
      "app": {
        "rajsingh.info/cap/tsdnsproxy": [
          {
            "stg.example.internal": {
              "dns": [
                "[fd7a:115c:a1e0:b1a:0:1:a0f:2]:53"
              ],
              "translateid": 1
            },
            "prd.example.internal": {
              "dns": [
                "[fd7a:115c:a1e0:b1a:0:2:a0f:2]:53"
              ],
              "translateid": 2
            }
          }
        ]
      },
      "dst": [
        "tag:tsdnsproxy"
      ],
      "src": [
        "*"
      ]
    }
  ]
}

ポイント:

Step 2: Auth Keyの発行

Tailscale管理画面 - Settings - Keys からAuth keysを発行します。

Device Settingsでは、

を選択します。Auth Keyはreusableである必要はありません。1度tailnetに参加できれば、以後はnode keyで参加できるためです。

Step 3: tsdnsproxyの起動

今回は docker compose + systemd でサービス化しました。ここではdocker composeの設定を記します。systemdのことはLLMに聞けば教えてくれます。

services:
  tsdnsproxy:
    image: ghcr.io/rajsinghtech/tsdnsproxy:main
    container_name: tsdnsproxy
    restart: unless-stopped
    environment:
      - TS_AUTHKEY=${TS_AUTHKEY}
      - TSDNSPROXY_HOSTNAME=${TSDNSPROXY_HOSTNAME:-tsdnsproxy}
      - TSDNSPROXY_STATE_DIR=/var/lib/tsdnsproxy
      - TSDNSPROXY_LISTEN_ADDRS=${TSDNSPROXY_LISTEN_ADDRS:-tailscale}
      - TSDNSPROXY_HEALTH_ADDR=:8080
      - TSDNSPROXY_VERBOSE=${TSDNSPROXY_VERBOSE:-true}
      - TSDNSPROXY_ACCEPT_ROUTES=true
    volumes:
      - tsdnsproxy-state:/var/lib/tsdnsproxy
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:8080/ready"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

volumes:
  tsdnsproxy-state:

ポイント:

設定ができれば docker compose up で起動してください。

Step 4: 動作確認

tsdnsproxyがtailnet IP 100.93.1.1 で動作しているとします。

基本的な動作は問題ないとして、次にCloud Map連携の動作確認をします。VPC-1とVPC-2のCloud Mapのサービスに1つ追加しておきます 例えば、 VPC-1に dns.stg.example.internal として 10.0.0.2 (Amazon Provided DNS) を登録します。

VPC-1内で dns.stg.example.internal を名前解決してみます。なお、EC2インスタンスがtailnetに参加している場合Split DNS設定が反映されているため4via6経由になってしまいます。subnet router配下のtailnet未参加のノードはこの影響を受けません。

# VPC-1内のEC2インスタンス (このインスタンスはtailnetに参加していない場合)
dig +short dns.stg.example.internal
10.0.0.2


# VPC-1内のEC2インスタンス (このインスタンスはtailnetに参加している場合
dig +short @10.0.0.2 dns.stg.example.internal
10.0.0.2

では、通常のtsdnsproxy経由で名前解決してみましょう。AAAAレコードのみに応答します。

dig +short @100.93.1.1 -t aaaa dns.stg.example.internal
fd7a:115c:a1e0:b1a:0:1:a00:2

きちんと4via6アドレスに変換されていることが確認できます。

Step 5: Split DNSを設定する

仕上げにTailscale管理画面からSplit DNSを構成します。

Tailscaleの管理画面 - DNS - Add Nameserver (Custom) で “Restrict to domain” (Split DNS) としてtsdnsproxyのTailnet IP (ここでは100.93.1.1)を指定します。

Split DNS

Exit Node使用時にも有効にした場合は “Use with exit node” を指定しておきます。

最後の動作確認として、tailnetに参加しているlaptop等で名前解決をしてみます。

dig -t aaaa dns.stg.example.internal

; <<>> DiG 9.10.6 <<>> -t aaaa dns.stg.example.internal
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 27426
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;dns.stg.example.internal. IN      AAAA

;; ANSWER SECTION:
dns.stg.example.internal. 300 IN   AAAA    fd7a:115c:a1e0:b1a:0:1:a00:2

;; Query time: 59 msec
;; SERVER: 100.100.100.100#53(100.100.100.100)
;; WHEN: Sun Jan 25 12:26:14 JST 2026
;; MSG SIZE  rcvd: 75

TailscaleのMagicDNS (100.100.100.100)がきちんとSplit DNSを解決できており、tsdnsproxyによって4via6アドレスに変換されていることが確認できます。ACLで到達性があればそのまま接続可能となります。

最後に

tsdnsproxyのGrantsのsrcを制御することで、誰にどの名前解決を許可するかをさらに細かく制御できます。もっというと、srcによって同じドメインを別のbackendに転送することができるので、ドメインが被っていても一部対応可能です。

tsdnsproxyでSplit DNSと4via6を組合せることで構成の幅がさらに広がりました。