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
Проблема исчерпания 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);
}
}
Планирование сети
- VPC CIDR: выбирайте достаточно большой диапазон (/16 для VPC), но не перекрывающийся с другими VPC
- Подсети: разделяйте по уровням (public, app, data) и зонам доступности
- NAT Gateway: для исходящего трафика из приватных подсетей
- 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)