Сети для самых маленьких. Часть 11: DHCPv4

Во всех предыдущих частях у хоста уже был адрес 192.168.1.10, маска /24, шлюз 192.168.1.1 и адрес DNS-сервера. Откуда? В пятой части мы видели, что всё это можно задать руками, и в серверных сетях так и делают. Но ноутбук, который утром в офисе, днём в кафе, а вечером дома, руками не настроишь: в каждой сети у него должен быть свой адрес из её диапазона и её шлюз. Эту работу делает DHCP — Dynamic Host Configuration Protocol, RFC 2131. Он же причина, по которой домашняя сеть «просто работает»: роутер раздаёт адреса, а вы об этом не думаете.

Задача выглядит парадоксально: чтобы получить адрес, нужно отправить запрос, а отправить IP-пакет без адреса нельзя. Посмотрим, как это обходят.


Формат: наследство BOOTP

DHCP вырос из BOOTP — протокола 1985 года для загрузки бездисковых станций, которым нужно было узнать свой адрес и откуда взять образ системы. DHCP сохранил его формат сообщения целиком и добавил к нему опции и аренду адресов. Поэтому в tcpdump он до сих пор подписан BOOTP/DHCP.

Сообщение едет в UDP (часть 9): клиент шлёт с порта 68 на порт 67, сервер отвечает с 67 на 68. Фиксированная часть — 236 байт:

 байты
 ┌───────┬───────┬───────┬───────┐
 │ op    │ htype │ hlen  │ hops  │   op: 1 запрос, 2 ответ; htype 1, hlen 6 — Ethernet; hops — счётчик relay
 ├───────┴───────┴───────┴───────┤
 │            xid (4)            │   идентификатор обмена, случайный, выбирает клиент
 ├───────────────┬───────────────┤
 │   secs (2)    │   flags (2)   │   secs — сколько клиент уже пытается; flags: бит Broadcast
 ├───────────────┴───────────────┤
 │          ciaddr (4)           │   адрес клиента, если он у него уже есть (продление)
 ├───────────────────────────────┤
 │          yiaddr (4)           │   «your» — адрес, который сервер выдаёт
 ├───────────────────────────────┤
 │          siaddr (4)           │   адрес сервера для следующего шага загрузки (TFTP)
 ├───────────────────────────────┤
 │          giaddr (4)           │   адрес relay-агента, если запрос прошёл через роутер
 ├───────────────────────────────┤
 │          chaddr (16)          │   MAC клиента (6 байт, остальное нули)
 ├───────────────────────────────┤
 │          sname (64)           │   имя сервера, почти всегда пусто
 ├───────────────────────────────┤
 │          file (128)           │   имя файла загрузки, почти всегда пусто
 ├───────────────────────────────┤
 │ magic cookie 63 82 53 63      │   «дальше опции DHCP»
 ├───────────────────────────────┤
 │ опции: код, длина, значение … │   до 255 — конец
 └───────────────────────────────┘

Всё интересное — в опциях, каждая в формате «код (1 байт), длина (1 байт), значение». Главная — опция 53, DHCP Message Type, по которой сообщение и называется:

53 = Тип Кто шлёт
1 DISCOVER клиент: «есть тут DHCP-сервер?»
2 OFFER сервер: «есть, предлагаю адрес»
3 REQUEST клиент: «беру этот», а также продление
4 DECLINE клиент: «адрес уже кем-то занят»
5 ACK сервер: «подтверждаю, вот параметры»
6 NAK сервер: «не могу, начни сначала»
7 RELEASE клиент: «адрес больше не нужен»
8 INFORM клиент: «адрес есть, нужны только параметры»

DORA: четыре сообщения

Первое получение адреса — четыре сообщения, по первым буквам которых обмен называют DORA:

   клиент (без адреса)                                        сервер 192.168.1.1
     │                                                                 │
     ├── DISCOVER ──────────────── broadcast ─────────────────────────►│
     │   от 0.0.0.0:68 на 255.255.255.255:67                           │
     │   chaddr = мой MAC, xid = 0x1a2b3c4d                            │
     │   опции: 53=1, 55 (что мне нужно), 61 (client id), 12 (hostname)│
     │                                                                 │
     │◄── OFFER ────────────────── broadcast или на MAC ───────────────┤
     │   yiaddr = 192.168.1.10, xid тот же                             │
     │   опции: 53=2, 54 (server id 192.168.1.1), 51 (аренда),         │
     │          1 (маска), 3 (шлюз), 6 (DNS)                           │
     │                                                                 │
     ├── REQUEST ──────────────── broadcast ──────────────────────────►│  (слышат все серверы)
     │   опции: 53=3, 50 (хочу 192.168.1.10), 54 (у 192.168.1.1)       │
     │                                                                 │
     │◄── ACK ─────────────────────────────────────────────────────────┤
     │   yiaddr = 192.168.1.10, те же опции                            │
     │                                                                 │
     │ ARP probe на 192.168.1.10 (часть 7):                            │
     │   свободен → адрес на интерфейс                                 │

DISCOVER. У клиента нет адреса, поэтому в заголовке IP отправитель 0.0.0.0 — «адреса нет» из пятой части, — а получатель 255.255.255.255 — ограниченное широковещание, всем в этом сегменте. В кадре Ethernet получатель ff:ff:ff:ff:ff:ff. Пакет получат все, но ответит только DHCP-сервер. Клиент кладёт свой MAC в chaddr, случайный xid, по которому отличит ответы на свой запрос от чужих, и опцию 55 — список параметров, которые ему нужны: маска, шлюз, DNS, доменное имя. Если у клиента был адрес в прошлый раз, он попросит его опцией 50 — серверы стараются выдавать знакомым клиентам прежние адреса.

OFFER. Сервер выбирает свободный адрес из своего пула, временно резервирует его и отвечает. Как доставить ответ хосту, у которого нет адреса? Сервер пишет предлагаемый адрес в yiaddr, а сам пакет шлёт либо на 255.255.255.255 (если клиент выставил флаг Broadcast — так делают стеки, которые не примут unicast на ещё не назначенный адрес), либо unicast-кадром прямо на MAC из chaddr с IP-адресом назначения yiaddr, минуя ARP: спрашивать «у кого 192.168.1.10» бессмысленно, у него ещё нет этого адреса. Linux-клиенты принимают второй вариант и экономят широковещание.

REQUEST. Клиент выбирает предложение (если серверов несколько, обычно первое пришедшее) и просит именно его: опция 50 с адресом и опция 54 с адресом сервера. И снова широковещательно — не потому что не может иначе, а чтобы остальные серверы увидели, что их предложения отвергнуты, и освободили зарезервированные адреса.

ACK. Сервер записывает аренду в свою базу и подтверждает с полным набором опций. Только теперь клиент назначает адрес интерфейсу, маршрут по умолчанию через шлюз из опции 3 и DNS из опции 6. Перед этим аккуратный клиент проверяет адрес ARP-probe и, если кто-то откликнулся, шлёт серверу DECLINE и начинает заново; сервер помечает адрес как плохой.

В tcpdump это выглядит так (-v раскрывает опции):

$ sudo tcpdump -i eth0 -n -v 'udp port 67 or udp port 68'
IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 3c:52:82:1a:2b:3c, length 300, xid 0x1a2b3c4d, Flags [none]
	  Client-Ethernet-Address 3c:52:82:1a:2b:3c
	  Vendor-rfc1048 Extensions
	    Magic Cookie 0x63825363
	    DHCP-Message (53), length 1: Discover
	    Client-ID (61), length 7: ether 3c:52:82:1a:2b:3c
	    Hostname (12), length 6: "laptop"
	    Parameter-Request (55), length 13: Subnet-Mask, BR, Default-Gateway, Domain-Name, Domain-Name-Server, ...
IP 192.168.1.1.67 > 192.168.1.10.68: BOOTP/DHCP, Reply, length 300, xid 0x1a2b3c4d, Flags [none]
	  Your-IP 192.168.1.10
	  Client-Ethernet-Address 3c:52:82:1a:2b:3c
	  Vendor-rfc1048 Extensions
	    Magic Cookie 0x63825363
	    DHCP-Message (53), length 1: Offer
	    Server-ID (54), length 4: 192.168.1.1
	    Lease-Time (51), length 4: 86400
	    Subnet-Mask (1), length 4: 255.255.255.0
	    Default-Gateway (3), length 4: 192.168.1.1
	    Domain-Name-Server (6), length 4: 192.168.1.1
IP 0.0.0.0.68 > 255.255.255.255.67: BOOTP/DHCP, Request from 3c:52:82:1a:2b:3c, length 300, xid 0x1a2b3c4d
	    DHCP-Message (53), length 1: Request
	    Requested-IP (50), length 4: 192.168.1.10
	    Server-ID (54), length 4: 192.168.1.1
IP 192.168.1.1.67 > 192.168.1.10.68: BOOTP/DHCP, Reply, length 300, xid 0x1a2b3c4d
	    DHCP-Message (53), length 1: ACK
	    ...

Один xid на все четыре сообщения, 0.0.0.0 в отправителе у клиента, и OFFER, ушедший на 192.168.1.10, которого ещё ни у кого нет.


Опции

Несколько десятков кодов, из которых в жизни встречается дюжина:

Код Имя Что
1 Subnet Mask маска
3 Router шлюз по умолчанию; может быть список
6 Domain Name Server адреса DNS, по порядку предпочтения
12 Host Name клиент сообщает своё имя; сервер может регистрировать его в DNS
15 Domain Name домен, который клиент дописывает к коротким именам
28 Broadcast Address широковещательный адрес сети
42 NTP Servers серверы времени
43 Vendor Specific что угодно, договорённость производителя: так точки доступа узнают адрес контроллера, а IP-телефоны — адрес АТС
50 Requested IP Address клиент просит конкретный адрес
51 Lease Time срок аренды в секундах
54 Server Identifier адрес сервера; по нему клиент выбирает, кому отвечать
55 Parameter Request List что клиенту нужно
58, 59 Renewal (T1), Rebinding (T2) когда продлевать, по умолчанию 50 % и 87,5 % аренды
61 Client Identifier чем клиент себя считает: обычно MAC, но может быть произвольная строка; по нему сервер ведёт резервирования
66, 67 TFTP Server, Bootfile для сетевой загрузки: откуда и какой файл взять по TFTP — простейшему протоколу передачи файлов поверх UDP; так работает PXE, загрузка машины по сети прошивкой сетевой карты
82 Relay Agent Information вставляет relay или коммутатор: с какого порта пришёл запрос; по ней сервер может выдавать адреса по розетке
119 Domain Search список доменов для поиска коротких имён
121 Classless Static Routes маршруты, которые клиенту надо добавить; если клиент её запросил и получил, опцию 3 он обязан проигнорировать, и об этом регулярно забывают
255 End конец опций

Клиент получает только то, что запросил в опции 55, и то, что сервер для него настроил. Если в сети «не работает DNS после получения адреса» — первым делом смотреть, есть ли опция 6 в ACK.


Аренда и её продление

Адрес выдаётся на срок — аренду (lease), опция 51. Это и отличает DHCP от BOOTP: адрес, которым перестали пользоваться, возвращается в пул. Срок задаёт администратор: сутки для офиса, час для гостевой сети с текучкой, неделя там, где хочется стабильности.

Клиент не ждёт истечения:

 получен ACK          T1 = 50 %              T2 = 87,5 %           100 %
 ├────────────────────┼───────────────────────┼─────────────────────┤
 │  BOUND             │  RENEWING             │  REBINDING          │ адрес снят,
 │  просто работает   │  REQUEST unicast      │  REQUEST broadcast  │ DISCOVER заново
 │                    │  своему серверу       │  любому серверу     │
  • В T1, на половине срока, клиент шлёт REQUEST unicast прямо серверу, выдавшему адрес (теперь у клиента есть адрес, и широковещание не нужно), с ciaddr = свой адрес. Сервер отвечает ACK с новым сроком, и отсчёт начинается заново. Если сервер молчит, клиент повторяет через растущие интервалы.
  • В T2, на 87,5 %, клиент решает, что его сервер умер, и шлёт REQUEST широковещательно: продлить может любой сервер, знающий этот адрес.
  • В 100 % адрес снимается с интерфейса, и всё начинается с DISCOVER. Соединения рвутся. При живом сервере до этого не доходит никогда.

При включении машина с сохранённой арендой не делает DISCOVER, а сразу шлёт REQUEST с опцией 50 — «у меня был 192.168.1.10, он ещё мой?». Если машина в той же сети — ACK, и адрес работает через полсекунды. Если её перенесли в другую сеть, сервер там ответит NAK: адрес не из моего пула, начинай сначала. Так ноутбук, приехавший из офиса домой, не держится за офисный адрес.

RELEASE клиент шлёт при аккуратном отключении (dhclient -r, ipconfig /release), чтобы сервер сразу освободил адрес. Обязательным это не является, и при выдернутом кабеле аренда живёт до конца срока. Поэтому в маленьком пуле с короткой текучкой адреса «кончаются», хотя машин в сети мало: пул занят арендами ушедших.

INFORM — для машин со статическим адресом, которым нужны только опции: DNS, домен, прокси. Сервер отвечает ACK без yiaddr и без аренды.


Через роутер: relay

Широковещание не проходит через роутеры (адрес 255.255.255.255 из таблицы в пятой части). Значит, DHCP-сервер должен стоять в каждом сегменте? В 1985 году так и было; в офисе с пятьюдесятью VLAN это невозможно. Поэтому роутер (или L3-коммутатор) умеет быть relay-агентом:

   клиент 10.0.5.x              роутер                              сервер 192.168.1.1
     │                     10.0.5.1 │ 192.168.1.254                        │
     ├── DISCOVER broadcast ───────►│                                      │
     │                              ├── DISCOVER unicast ─────────────────►│  giaddr = 10.0.5.1, hops = 1
     │                              │                                      │  сервер: giaddr в сети 10.0.5.0/24 →
     │                              │                                      │  берём пул для 10.0.5.0/24
     │                              │◄── OFFER unicast на 10.0.5.1 ────────┤  yiaddr = 10.0.5.20
     │◄── OFFER ────────────────────┤                                      │
  1. Relay на интерфейсе 10.0.5.1 ловит широковещательный DISCOVER, вписывает свой адрес в этом сегменте в giaddr, увеличивает hops и отправляет пакет unicast серверу, адрес которого задан в его настройках (ip helper-address в терминах Cisco).
  2. Сервер видит giaddr ≠ 0, понимает, что клиент не в его сегменте, и по giaddr выбирает пул: 10.0.5.1 принадлежит сети 10.0.5.0/24, значит, адрес даём оттуда. Это главное: у сервера может быть сколько угодно пулов для разных сетей, и выбирает он их по адресу relay.
  3. Ответ сервер шлёт unicast на giaddr, relay принимает его на порту 67 и передаёт клиенту в своём сегменте: широковещательно или на MAC из chaddr.

Relay может добавить опцию 82 с номером порта коммутатора, с которого пришёл запрос, и тогда сервер способен выдавать конкретный адрес конкретной розетке, кто бы в неё ни воткнулся. Так делают провайдеры и крупные сети.


Резервирования, пулы, конфликты

На сервере адресное пространство сегмента делят на:

  • пул (range, scope) динамических адресов, например 192.168.1.100–192.168.1.200;
  • резервирования: «MAC такой-то всегда получает 192.168.1.50» — для принтеров и серверов, которым нужен постоянный адрес, но хочется управлять им централизованно;
  • исключения: адреса, которые сервер не трогает, потому что они настроены руками.

Конфигурация в самом простом сервере, dnsmasq, умещается в три строки:

dhcp-range=192.168.1.100,192.168.1.200,24h
dhcp-option=option:router,192.168.1.1
dhcp-host=00:11:22:33:44:55,192.168.1.50,printer

Перед тем как предложить адрес, сервер обычно пингует его: если кто-то отвечает, адрес помечается занятым, и предлагается следующий. Это защищает от машин со статическими адресами внутри пула, но не вполне: выключенная сейчас машина не ответит.

Два сервера

Если в сегменте два DHCP-сервера, на DISCOVER ответят оба, а клиент возьмёт первый OFFER. С двумя настроенными серверами, которые делят пул или синхронизируют аренды (failover), это и есть отказоустойчивость. Со случайным вторым — принесённым из дома роутером, воткнутым LAN-портом в офисную розетку, — это классическая авария: часть машин получает адрес 192.168.0.x и шлюз, ведущий в никуда, причём какая именно часть — зависит от того, чей OFFER пришёл раньше. Половина офиса работает, половина нет, и перезагрузка меняет состав половин.

Защита — на коммутаторе: DHCP snooping. Коммутатор пропускает OFFER и ACK только с портов, помеченных доверенными (туда, где настоящий сервер или аплинк к нему), а с остальных отбрасывает. Заодно он запоминает, какой адрес на каком порту выдан, и эта таблица кормит Dynamic ARP Inspection из седьмой части. В протоколе самом никакой защиты нет: клиент верит любому, кто ответил.

Когда сервера нет

Клиент повторяет DISCOVER с растущим интервалом, и дальше поведение зависит от системы. Windows и macOS через минуту назначают себе адрес из 169.254.0.0/16 (APIPA из пятой части): связь с соседями, сделавшими то же самое, есть, с интернетом — нет. Увидели 169.254 — DHCP не ответил. Linux с NetworkManager делает то же, а dhclient в одиночку оставляет интерфейс без адреса и продолжает спрашивать. Клиент при этом продолжает слушать и подхватывает адрес, как только сервер появится.


Смотрим руками

Запросить адрес заново и посмотреть обмен (в отдельном окне — tcpdump из раздела про DORA):

$ sudo dhclient -r eth0 && sudo dhclient -v eth0
DHCPDISCOVER on eth0 to 255.255.255.255 port 67 interval 3
DHCPOFFER of 192.168.1.10 from 192.168.1.1
DHCPREQUEST for 192.168.1.10 on eth0 to 255.255.255.255 port 67
DHCPACK of 192.168.1.10 from 192.168.1.1
bound to 192.168.1.10 -- renewal in 39212 seconds.

«renewal in 39212» — T1, примерно половина суточной аренды. Полученные параметры:

$ cat /var/lib/dhcp/dhclient.leases        # dhclient
$ nmcli device show eth0 | grep DHCP4      # NetworkManager
$ networkctl status eth0                   # systemd-networkd

На Windows: ipconfig /all показывает сервер, время получения и истечения аренды; ipconfig /release и ipconfig /renew — RELEASE и новый DISCOVER.

Найти чужой DHCP-сервер: sudo nmap --script broadcast-dhcp-discover шлёт DISCOVER и перечисляет всех, кто ответил, с их адресами и предложениями. Или просто tcpdump на udp port 67 и смотреть, с каких адресов приходят OFFER.


Что запомнить

  • DHCP — UDP 67/68, формат BOOTP плюс опции. Клиент без адреса шлёт с 0.0.0.0 на 255.255.255.255, сервер отвечает на MAC или широковещательно; xid связывает обмен.
  • DORA: DISCOVER, OFFER, REQUEST, ACK. REQUEST широковещательный, чтобы остальные серверы отозвали предложения. После ACK — ARP-probe, при конфликте DECLINE.
  • Всё полезное в опциях: 1 маска, 3 шлюз, 6 DNS, 51 аренда, 54 сервер, 121 маршруты (перебивает 3).
  • Аренда продлевается в T1 unicast своему серверу, в T2 широковещательно любому; при включении — REQUEST старого адреса, NAK в чужой сети.
  • Через роутер — relay: giaddr говорит серверу, из какого пула выдавать.
  • Два сервера в сегменте — половина сети без связи; защита — DHCP snooping. 169.254.x.x — сервер не ответил.

Дальше — Часть 12: DNS: как имя превращается в адрес, где лежит дерево имён и кто за какую его часть отвечает, полный путь запроса от вашей машины через корневые серверы до ответа, формат сообщения, типы записей, кеширование и TTL, и что такое dig +trace.

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.