ping6.net
Лучшие Практики

IPv6 для веб-разработчиков

Что нужно знать веб-разработчикам об IPv6: URL со скобками, хранение в базах данных, программирование сокетов и распространённые баги, которых следует избегать.

ping6.net14 декабря 2024 г.6 min read
IPv6веб-разработкапрограммированиебазы данныхсокеты

Если вы игнорировали IPv6, потому что «это будет проблемой на потом», потом — это сейчас. Мобильные сети работают с приоритетом IPv6. Некоторые пользователи за NAT64 могут достичь вас только через IPv6. Даже localhost на вашей машине разработки, вероятно, разрешается в ::1 перед 127.0.0.1.

Это руководство покрывает то, что вам действительно нужно знать: обработка IPv6 в URL, хранение адресов в базах данных, программирование сокетов и баги, которые укусят вас, если вы не будете осторожны.

TL;DR - Краткое резюме

Ключевые моменты:

  • IPv6-адреса в URL требуют квадратных скобок: http://[2001:db8::1]:8080/
  • Храните адреса как VARBINARY(16) или INET в базах данных, не VARCHAR
  • Используйте :: (dual-stack) для прослушивания сокетов, чтобы принимать IPv4 и IPv6
  • Регулярные выражения IPv4 не работают для IPv6 — используйте встроенные функции парсинга
  • Тестируйте на реальных IPv6-сетях, не только на localhost

Перейти к: IPv6 в URL | Хранение в базах данных | Программирование сокетов

IPv6 в URL#

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-адреса в скобки. При парсинге отбрасывайте их перед валидацией или хранением.

Хранение в базах данных#

Классическая ошибка — использование VARCHAR(15) для IP-адресов. Это подходит для IPv4 (максимум 15 символов: 255.255.255.255), но не для IPv6.

PostgreSQL имеет нативный тип INET, который обрабатывает как IPv4, так и IPv6:

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) — хранить двоичное представление (16 байт для IPv6, 4 для IPv4)
  • 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 (частично сжатый)

Всегда нормализуйте перед сравнением или индексированием. Большинство библиотек имеют функцию канонической формы.

Программирование сокетов#

Socket API рассматривает IPv4 и IPv6 как отдельные семейства адресов. Современные приложения должны поддерживать оба.

Node.js по умолчанию использует dual-stack сокеты:

const http = require('http');
 
// Привязывается к :: (все IPv6-адреса) и принимает IPv4 через mapping
const server = http.createServer((req, res) => {
  res.end(`Your IP: ${req.socket.remoteAddress}`);
});
 
server.listen(3000, '::');

Адрес :: — IPv6-эквивалент 0.0.0.0, принимающий соединения на всех интерфейсах. Большинство систем поддерживают IPv4-mapped IPv6-адреса (::ffff:192.0.2.1), позволяя одному IPv6-сокету обрабатывать оба протокола.

Python требует явной обработки dual-stack:

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 делает dual-stack по умолчанию:

listener, err := net.Listen("tcp", ":8080")
// Автоматически слушает как IPv4, так и IPv6

Для клиентских соединений всегда используйте getaddrinfo() (или эквивалент вашего языка) вместо ручного разрешения адресов. Он корректно обрабатывает IPv4, IPv6 и dual-stack сценарии:

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-соединения. Привяжитесь к :: для dual-stack или явно привяжитесь к обоим адресам.

Для тестирования поведения только IPv6 отключите IPv4 на вашем loopback-интерфейсе или явно используйте [::1] в вашем HTTP-клиенте:

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

Для реалистичного тестирования используйте IPv6-only тестовую сеть. Многие облачные провайдеры предлагают IPv6-only экземпляры, которые вынуждают вас обрабатывать крайние случаи вроде NAT64/DNS64.

CDN и origin-серверы#

Большинство CDN (Cloudflare, Fastly, AWS CloudFront) поддерживают IPv6 по умолчанию. Они будут обслуживать контент через IPv6 клиентам и транслировать в IPv4 для вашего origin, если нужно.

Загвоздка: если вы выставляете API напрямую (обходя CDN), ваш origin-сервер должен поддерживать IPv6. Проверьте, что ваш DNS имеет AAAA-записи, и ваш файрвол разрешает IPv6-трафик.

Распространённые баги#

Regex-валидация. IPv6 regex-паттерны печально сложны и обычно неправильны:

// Не делайте так
const ipv6Regex = /^([0-9a-f]{1,4}:){7}[0-9a-f]{1,4}$/i;
// Отказывает на :: сжатии, IPv4-mapped адресах, zone ID...

Вместо этого используйте правильную библиотеку парсинга. Каждый основной язык имеет такую, которая корректно обрабатывает IPv6. Для валидации используйте наш инструмент валидатора IPv6.

Жёстко закодированные IPv4-адреса. Ищите в вашей кодовой базе паттерны вроде:

const API_SERVER = 'http://192.168.1.100:3000';  // Не работает с IPv6

Используйте вместо этого hostnames или поддерживайте оба семейства адресов.

Rate limiting по IP. IPv6-адреса часто меняются из-за расширений приватности. Rate limiting по точному адресу отказывает. Ограничивайте по подсети /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-валидация не полагается на regex
  • Нет жёстко закодированных IPv4-адресов в конфигурации
  • Rate limiting и геолокация обрабатывают IPv6-подсети
  • Парсинг URL обрабатывает нотацию скобок

IPv6 больше не опционален. Встройте его с самого начала, и вы избежите болезненной миграции позже.

Связанные статьи#

  • Основы IPv6 — Поймите форматы адресов и базовые концепции для разработки
  • Типы адресов IPv6 — Изучите различные типы адресов, которые вам нужно обрабатывать в коде

Валидируйте адреса

Используйте валидатор IPv6 и инструмент Ping для тестирования вашего кода с реальными IPv6-адресами.