ping6.net
ベストプラクティス

Web開発者のためのIPv6

Web開発者がIPv6について知る必要があること:括弧付きURL、データベースストレージ、ソケットプログラミング、避けるべき一般的なバグ。

ping6.net2024年12月14日2 min read
IPv6Web開発プログラミングデータベースソケット

Web開発者のためのIPv6#

「後で問題になる」と思ってIPv6を無視してきた場合、後は今です。モバイルネットワークはIPv6ファーストで実行されています。NAT64の背後にいる一部のユーザーは、IPv6経由でのみ到達できます。開発マシンのlocalhostでさえ、おそらく127.0.0.1の前に::1に解決されます。

このガイドは、実際に知る必要があることをカバーしています:URLでのIPv6の処理、データベースへのアドレスの保存、ソケットプログラミング、注意しないとかまれるバグ。

TL;DR - 要点まとめ

重要ポイント:

  • URLではIPv6アドレスを角括弧で囲む:http://[2001:db8::1]:8080
  • PostgreSQLはINET型を使用、MySQLはVARBINARY(16)またはVARCHAR(45)を使用
  • 比較前にアドレスを正規化 - IPv6には複数の有効な表現があります
  • デュアルスタックソケットには::にバインド(IPv4とIPv6の両方を受け入れる)
  • 名前解決にはgetaddrinfo()を使用 - IPv4アドレスをハードコードしないでください
  • レート制限はIPv6サブネット(/64)を使用 - プライバシー拡張によりアドレスが変更されます
  • 正規表現でIPv6を検証しないでください - 適切な解析ライブラリを使用してください

ジャンプ: URLでの使用 | データベース | ソケット | 一般的なバグ


URLでのIPv6#

IPv6アドレスにはコロンが含まれており、URLのポート区切り文字と競合します。解決策は角括弧表記です:

http://[2001:db8::1]:8080/api/users
https://[2606:2800:220:1:248:1893:25c8:1946]/

括弧がないと、パーサーはアドレスが終わりポートが始まる場所を判断できません。これは以下に適用されます:

  • HTTP/HTTPS URL
  • WebSocket接続(ws://[::1]:3000
  • データベース接続文字列
  • 任意のURIスキーム

プログラムでURLを生成する場合、IPv6アドレスを括弧で囲みます。解析する場合、検証または保存の前にそれらを削除します。


データベースストレージ#

クラシックな間違いは、IPアドレスにVARCHAR(15)を使用することです。これはIPv4(最大15文字:255.255.255.255)に適合しますが、IPv6には適合しません。

PostgreSQLにはIPv4とIPv6の両方を処理するネイティブINET型があります:

CREATE TABLE users (
  id SERIAL PRIMARY KEY,
  ip_address INET NOT NULL
);
 
-- 自動検証と正規化
INSERT INTO users (ip_address) VALUES ('2001:db8::1');
INSERT INTO users (ip_address) VALUES ('192.0.2.1');

INET型は、アドレスを効率的に保存し、挿入時に検証し、<<および>>演算子を使用したサブネットマッチングなどのネットワーク操作をサポートします。

MySQLにはネイティブ型がないため、次のいずれかを使用します:

  • VARBINARY(16) - バイナリ表現を保存(IPv6の場合は16バイト、IPv4の場合は4)
  • VARCHAR(45) - 文字列表現を保存(展開されたIPv6の最大長)

バイナリストレージはより効率的ですが、変換関数が必要です:

-- 保存
INSERT INTO users (ip_address) VALUES (INET6_ATON('2001:db8::1'));
 
-- 取得
SELECT INET6_NTOA(ip_address) FROM users;

**正規化が重要です。**IPv6アドレスには複数の有効な表現があります:

  • 2001:0db8:0000:0000:0000:0000:0000:0001
  • 2001:db8::1(圧縮)
  • 2001:db8:0:0:0:0:0:1(部分的に圧縮)

比較またはインデックス作成の前に常に正規化します。ほとんどのライブラリには正規形式関数があります。


ソケットプログラミング#

ソケットAPIはIPv4とIPv6を別々のアドレスファミリとして扱います。最新のアプリケーションは両方をサポートする必要があります。

Node.jsはデフォルトでデュアルスタックソケットを使用します:

const http = require('http');
 
// ::(すべてのIPv6アドレス)にバインドし、マッピング経由でIPv4を受け入れる
const server = http.createServer((req, res) => {
  res.end(`Your IP: ${req.socket.remoteAddress}`);
});
 
server.listen(3000, '::');

::アドレスは0.0.0.0のIPv6版であり、すべてのインターフェイスで接続を受け入れます。ほとんどのシステムは、IPv4マップIPv6アドレス(::ffff:192.0.2.1)をサポートしており、単一のIPv6ソケットで両方のプロトコルを処理できます。

Pythonは明示的なデュアルスタック処理が必要です:

import socket
 
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
sock.bind(('::', 8080))
sock.listen(5)

IPV6_V6ONLYフラグは、ソケットがIPv6のみを受け入れるか、IPv4とIPv6の両方を受け入れるかを制御します。

Goはデフォルトでデュアルスタックを作成します:

listener, err := net.Listen("tcp", ":8080")
// 自動的にIPv4とIPv6の両方でリッスン

クライアント接続の場合、手動でアドレスを解決する代わりに、常にgetaddrinfo()(または言語の同等物)を使用します。IPv4、IPv6、デュアルスタックシナリオを正しく処理します:

import socket
 
# これをしないでください
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('example.com', 80))
 
# これを行ってください - IPv4とIPv6の両方で機能します
addr_info = socket.getaddrinfo('example.com', 80, socket.AF_UNSPEC, socket.SOCK_STREAM)
sock = socket.socket(addr_info[0][0], addr_info[0][1])
sock.connect(addr_info[0][4])

ローカルテスト#

ほとんどのシステムはlocalhost::1(IPv6)と127.0.0.1(IPv4)の両方に解決します。/etc/hostsを確認:

127.0.0.1       localhost
::1             localhost

アプリケーションが127.0.0.1のみにバインドしている場合、IPv6接続を受け入れません。デュアルスタックの場合は::にバインドするか、両方のアドレスに明示的にバインドします。

IPv6のみの動作をテストするには、ローカルマシンでIPv4を無効にするか、HTTPクライアントで[::1]を明示的に使用します:

curl http://[::1]:3000/

現実的なテストには、IPv6のみのテストネットワークを使用します。多くのクラウドプロバイダーは、NAT64/DNS64などのエッジケースを処理することを強制するIPv6のみのインスタンスを提供します。


CDNとオリジンサーバー#

ほとんどのCDN(Cloudflare、Fastly、AWS CloudFront)はデフォルトでIPv6をサポートします。クライアントにIPv6経由でコンテンツを提供し、必要に応じてオリジンのIPv4に変換します。

キャッチ:API を直接公開する場合(CDNをバイパス)、オリジンサーバーはIPv6をサポートする必要があります。DNSにAAAAレコードがあり、ファイアウォールがIPv6トラフィックを許可することを確認してください。


一般的なバグ#

**正規表現検証。**IPv6正規表現パターンは非常に複雑で、通常は間違っています:

// これをしないでください
const ipv6Regex = /^([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}$/i;
// ::圧縮、IPv4マップアドレス、ゾーンIDで失敗...

代わりに適切な解析ライブラリを使用してください。すべての主要言語には、IPv6を正しく処理するものがあります。検証には、IPv6バリデーターツールを使用してください。

**ハードコーディングされたIPv4アドレス。**コードベースで次のようなパターンを検索:

const API_SERVER = 'http://192.168.1.100:3000';  // IPv6では機能しません

代わりにホスト名を使用するか、両方のアドレスファミリをサポートします。

**IPベースのレート制限。**プライバシー拡張により、IPv6アドレスは頻繁に変更されます。正確なアドレスでレート制限すると失敗します。代わりに/64サブネットで制限します:

// 悪い
const key = `rate:${ipAddress}`;
 
// IPv6により良い
const key = `rate:${ipv6ToSubnet64(ipAddress)}`;

**IPv6サポートのないライブラリ。**古いHTTPクライアント、データベースドライバー、ネットワークライブラリはIPv6を処理しない場合があります。実際のIPv6アドレスでテストしてください。localhostだけではありません。ライブラリが失敗した場合は、更新または代替を確認してください。

**URL解析のエッジケース。**ルーター/フレームワークが括弧付きIPv6アドレスを処理することを確認:

GET http://[2001:db8::1]:8080/api/users
Host: [2001:db8::1]:8080

一部のパーサーは、Hostヘッダーに括弧を誤って含めたり、アドレスを正しく抽出できなかったりします。


チェックリスト#

アプリケーションがIPv6対応の場合:

  • データベース列が45文字の文字列を保存できるか、ネイティブIPタイプを使用
  • サーバーが::にバインドするか、IPv4とIPv6の両方で明示的にリッスン
  • クライアントコードが名前解決にgetaddrinfo()または同等物を使用
  • IP検証が正規表現に依存しない
  • 設定にハードコーディングされたIPv4アドレスがない
  • レート制限とジオロケーションがIPv6サブネットを処理
  • URL解析が括弧表記を処理

IPv6はもはやオプションではありません。最初から組み込むと、後で痛みを伴う移行を避けることができます。

関連記事#

実際に試してみましょう

IPv6バリデーターおよびPingツールを使用して、アプリケーションのIPv6サポートをテストします。