MidТеория13 min

IPv4 и IPv6

IP-адресация, NAT, subnetting, CIDR, переход на IPv6 и влияние на проектирование сетевой инфраструктуры

IPv4

Формат адреса

IPv4-адрес -- это 32-битное число, записываемое в виде четырёх октетов через точку.

Пример: 192.168.1.100

Бинарно: 11000000.10101000.00000001.01100100

Всего адресов: 2^32 = 4,294,967,296 (~4.3 миллиарда)

Классы адресов (устаревшая классификация)

Класс Диапазон Маска Хостов Назначение
A 1.0.0.0 - 126.0.0.0 /8 16.7M Крупные сети
B 128.0.0.0 - 191.255.0.0 /16 65,534 Средние сети
C 192.0.0.0 - 223.255.255.0 /24 254 Малые сети
D 224.0.0.0 - 239.255.255.255 - - Multicast
E 240.0.0.0 - 255.255.255.255 - - Зарезервировано

Приватные адреса (RFC 1918)

Эти адреса не маршрутизируются в Интернете и используются во внутренних сетях.

Диапазон CIDR Количество адресов
10.0.0.0 - 10.255.255.255 10.0.0.0/8 16,777,216
172.16.0.0 - 172.31.255.255 172.16.0.0/12 1,048,576
192.168.0.0 - 192.168.255.255 192.168.0.0/16 65,536

Для System Design: В облачных VPC (Virtual Private Cloud) используются приватные адреса из этих диапазонов. При проектировании сети важно планировать адресное пространство, чтобы избежать конфликтов при VPC peering.

Subnetting и CIDR

CIDR (Classless Inter-Domain Routing) заменил классовую адресацию. Маска сети задаётся числом бит.

192.168.1.0/24

/24 означает: первые 24 бита = адрес сети, последние 8 бит = хосты
Сетевая маска: 255.255.255.0
Количество хостов: 2^8 - 2 = 254 (минус сеть и broadcast)

Примеры разбиения:

CIDR Маска Хостов Применение
/32 255.255.255.255 1 Один хост (loopback, VIP)
/28 255.255.255.240 14 Маленькая подсеть (DMZ)
/24 255.255.255.0 254 Типичная LAN
/16 255.255.0.0 65,534 Большая сеть (VPC)
/8 255.0.0.0 16.7M Очень большая сеть
<?php

declare(strict_types=1);

/**
 * IP address and subnet utilities.
 * Useful for network planning in System Design.
 */
final class SubnetCalculator
{
    /**
     * Calculate subnet details from CIDR notation.
     *
     * @return array{
     *     network: string,
     *     broadcast: string,
     *     netmask: string,
     *     hosts: int,
     *     first_host: string,
     *     last_host: string
     * }
     */
    public static function calculate(string $cidr): array
    {
        [$ip, $prefix] = explode('/', $cidr);
        $prefix = (int) $prefix;

        $ipLong = ip2long($ip);
        $netmask = -1 << (32 - $prefix);
        $network = $ipLong & $netmask;
        $broadcast = $network | ~$netmask;

        $hostCount = (1 << (32 - $prefix)) - 2;
        if ($hostCount < 0) {
            $hostCount = 0;
        }

        return [
            'network' => long2ip($network),
            'broadcast' => long2ip($broadcast),
            'netmask' => long2ip($netmask),
            'hosts' => $hostCount,
            'first_host' => long2ip($network + 1),
            'last_host' => long2ip($broadcast - 1),
        ];
    }

    /**
     * Check if an IP is in a given subnet.
     * Common task for ACL and security rules.
     */
    public static function isInSubnet(string $ip, string $cidr): bool
    {
        [$subnet, $prefix] = explode('/', $cidr);
        $prefix = (int) $prefix;

        $ipLong = ip2long($ip);
        $subnetLong = ip2long($subnet);
        $mask = -1 << (32 - $prefix);

        return ($ipLong & $mask) === ($subnetLong & $mask);
    }

    /**
     * Plan VPC subnets for a multi-tier architecture.
     *
     * @return array<string, array{cidr: string, hosts: int, purpose: string}>
     */
    public static function planVpcSubnets(string $vpcCidr = '10.0.0.0/16'): array
    {
        return [
            'public_a' => [
                'cidr' => '10.0.1.0/24',
                'hosts' => 254,
                'purpose' => 'Load balancers, NAT gateways (AZ-a)',
            ],
            'public_b' => [
                'cidr' => '10.0.2.0/24',
                'hosts' => 254,
                'purpose' => 'Load balancers, NAT gateways (AZ-b)',
            ],
            'app_a' => [
                'cidr' => '10.0.10.0/24',
                'hosts' => 254,
                'purpose' => 'Application servers (AZ-a)',
            ],
            'app_b' => [
                'cidr' => '10.0.11.0/24',
                'hosts' => 254,
                'purpose' => 'Application servers (AZ-b)',
            ],
            'db_a' => [
                'cidr' => '10.0.20.0/24',
                'hosts' => 254,
                'purpose' => 'Database servers (AZ-a)',
            ],
            'db_b' => [
                'cidr' => '10.0.21.0/24',
                'hosts' => 254,
                'purpose' => 'Database servers (AZ-b)',
            ],
        ];
    }
}

// Example: calculate subnet
$result = SubnetCalculator::calculate('192.168.1.0/24');
// network: 192.168.1.0, broadcast: 192.168.1.255, hosts: 254

// Check IP in subnet
$inSubnet = SubnetCalculator::isInSubnet('10.0.10.50', '10.0.10.0/24');
// true
## NAT (Network Address Translation)

Проблема исчерпания IPv4

4.3 миллиарда адресов недостаточно для всех устройств в мире. NAT решает эту проблему, позволяя множеству устройств использовать один публичный IP.

Внутренняя сеть                NAT Router               Интернет
192.168.1.10:12345  ──►  203.0.113.1:40001  ──►  93.184.216.34:443
192.168.1.11:12346  ──►  203.0.113.1:40002  ──►  93.184.216.34:443
192.168.1.12:12347  ──►  203.0.113.1:40003  ──►  93.184.216.34:443

Типы NAT

Тип Описание Применение
SNAT (Source NAT) Изменяет source IP Домашний роутер, корпоративная сеть
DNAT (Destination NAT) Изменяет destination IP Port forwarding, load balancing
PAT (Port Address Translation) NAT с трансляцией портов Множество хостов за одним IP
1:1 NAT Один-к-одному маппинг Серверы с фиксированным внешним IP

Для System Design: NAT добавляет сложность: нет прямого входящего соединения к хостам за NAT. Для входящих соединений нужен port forwarding или load balancer. В облачных средах NAT Gateway используется для исходящего трафика из приватных подсетей.

IPv6

Формат адреса

IPv6 использует 128-битные адреса, записываемые в hex через двоеточия.

Полный формат:    2001:0db8:0000:0000:0000:0000:0000:0001
Сокращённый:      2001:db8::1

Всего адресов: 2^128 ≈ 3.4 × 10^38 (340 ундециллионов)

Сравнение IPv4 и IPv6

Параметр IPv4 IPv6
Размер адреса 32 бита 128 бит
Формат Десятичный (192.168.1.1) Hex (2001:db8::1)
Количество адресов 4.3 × 10^9 3.4 × 10^38
NAT Обязателен Не нужен
Заголовок 20-60 байт (variable) 40 байт (fixed)
Фрагментация Роутерами Только отправителем
IPsec Опционально Встроенный
Broadcast Есть Заменён Multicast
Автоконфигурация DHCP SLAAC + DHCPv6

Переход на IPv6

Переход с IPv4 на IPv6 -- длительный процесс, длящийся уже 25+ лет. Основные механизмы перехода:

Dual Stack:

  • Устройство имеет и IPv4, и IPv6 адрес
  • Самый распространённый подход
  • Работает с обоими протоколами одновременно

Tunneling:

  • IPv6-пакеты инкапсулируются в IPv4
  • 6to4, Teredo, ISATAP

Translation:

  • NAT64 -- трансляция между IPv6 и IPv4
  • Позволяет IPv6-only клиентам обращаться к IPv4-серверам
<?php

declare(strict_types=1);

/**
 * Working with IP addresses in PHP.
 * Support for both IPv4 and IPv6.
 */
final class IpAddressUtils
{
    /**
     * Validate and classify an IP address.
     *
     * @return array{valid: bool, version: int, type: string}
     */
    public static function analyze(string $ip): array
    {
        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
            return [
                'valid' => true,
                'version' => 4,
                'type' => self::classifyIpv4($ip),
            ];
        }

        if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
            return [
                'valid' => true,
                'version' => 6,
                'type' => self::classifyIpv6($ip),
            ];
        }

        return ['valid' => false, 'version' => 0, 'type' => 'invalid'];
    }

    /**
     * Check if request comes from a private/internal network.
     * Useful for internal API endpoints that should not be public.
     */
    public static function isPrivate(string $ip): bool
    {
        return !filter_var(
            $ip,
            FILTER_VALIDATE_IP,
            FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE,
        );
    }

    /**
     * Get client IP from request headers.
     * Handles proxy chains (X-Forwarded-For).
     *
     * @param array<string, string> $headers
     * @param string[] $trustedProxies
     */
    public static function getClientIp(
        array $headers,
        string $remoteAddr,
        array $trustedProxies = [],
    ): string {
        // Only trust X-Forwarded-For if request came from trusted proxy
        if (!in_array($remoteAddr, $trustedProxies, true)) {
            return $remoteAddr;
        }

        $forwardedFor = $headers['X-Forwarded-For'] ?? '';
        if ($forwardedFor === '') {
            return $remoteAddr;
        }

        // X-Forwarded-For: client, proxy1, proxy2
        // The leftmost IP is the original client
        $ips = array_map('trim', explode(',', $forwardedFor));

        // Find the first non-trusted IP from the right
        $ips = array_reverse($ips);
        foreach ($ips as $ip) {
            if (!in_array($ip, $trustedProxies, true)) {
                return $ip;
            }
        }

        return $remoteAddr;
    }

    private static function classifyIpv4(string $ip): string
    {
        $long = ip2long($ip);

        // Private ranges
        if (self::isInRange($long, '10.0.0.0', '10.255.255.255')) {
            return 'private (Class A)';
        }
        if (self::isInRange($long, '172.16.0.0', '172.31.255.255')) {
            return 'private (Class B)';
        }
        if (self::isInRange($long, '192.168.0.0', '192.168.255.255')) {
            return 'private (Class C)';
        }
        if (self::isInRange($long, '127.0.0.0', '127.255.255.255')) {
            return 'loopback';
        }
        if (self::isInRange($long, '169.254.0.0', '169.254.255.255')) {
            return 'link-local';
        }

        return 'public';
    }

    private static function classifyIpv6(string $ip): string
    {
        if (str_starts_with($ip, '::1')) {
            return 'loopback';
        }
        if (str_starts_with(strtolower($ip), 'fe80')) {
            return 'link-local';
        }
        if (str_starts_with(strtolower($ip), 'fc') || str_starts_with(strtolower($ip), 'fd')) {
            return 'unique-local (private)';
        }

        return 'global unicast';
    }

    private static function isInRange(int $ip, string $start, string $end): bool
    {
        return $ip >= ip2long($start) && $ip <= ip2long($end);
    }
}
## Влияние на System Design

Планирование сети

  1. VPC CIDR: выбирайте достаточно большой диапазон (/16 для VPC), но не перекрывающийся с другими VPC
  2. Подсети: разделяйте по уровням (public, app, data) и зонам доступности
  3. NAT Gateway: для исходящего трафика из приватных подсетей
  4. IPv6: планируйте dual-stack, если нужен прямой IPv6-доступ

Типичные проблемы

  • Исчерпание IP-адресов в подсети при масштабировании
  • Конфликты CIDR при VPC peering
  • NAT как single point of failure и bottleneck
  • Неправильная обработка X-Forwarded-For за прокси

Выводы

  • IPv4 адресов недостаточно, NAT решает проблему ценой сложности
  • CIDR/subnetting позволяет эффективно разбивать адресное пространство
  • IPv6 решает проблему исчерпания адресов, но переход идёт медленно
  • Планирование сети (CIDR, подсети, AZ) -- важная часть System Design
  • PHP предоставляет инструменты для работы с IP (filter_var, ip2long, inet_pton)