Сети для самых маленьких. Часть 4: PPPoE
В первой части канальный уровень был просто «доставить соседу», во второй и третьей мы увидели, как это делают Ethernet и Wi-Fi. Сегодня — протокол, который сидит между Ethernet и IP и встречается почти в каждом домашнем роутере: PPPoE, Point-to-Point Protocol over Ethernet. В настройках роутера это тот самый пункт «Тип подключения: PPPoE», рядом с которым вводят логин и пароль от провайдера.
Чтобы понять PPPoE, нужно понять две вещи: что такое PPP и зачем его понадобилось заворачивать в Ethernet.
Задача провайдера
У провайдера в подъезде стоит коммутатор, от него кабели в квартиры. С точки зрения Ethernet все абоненты дома — просто порты одного коммутатора. А провайдеру нужно:
- знать, кто подключился: услуга оплачена или нет, какой тариф;
- считать: время сессии, объём трафика;
- выдать адрес и другие параметры и забрать их обратно, когда абонент отключился;
- управлять сессией: отключить за неуплату, ограничить скорость.
У Ethernet ничего этого нет: кадр либо пришёл, либо нет, а кто его прислал, известно только по MAC-адресу, который любой может подменить. Зато всё это было в PPP — протоколе, написанном для модемных соединений 1990-х, где абонент дозванивался на модемный пул провайдера и перед выходом в интернет вводил логин и пароль. Когда модемы сменились Ethernet и DSL, провайдеры не стали придумывать новое, а взяли PPP и научили его ходить поверх Ethernet. Так в 1999 году появился RFC 2516.
Сегодня есть и другой путь — IPoE: адрес выдаётся по DHCP (о нём в части 11), а абонента опознают по порту коммутатора или MAC-адресу. Но PPPoE по-прежнему массов, особенно там, где доступ идёт через DSL-модем: ADSL и VDSL передают данные по телефонной линии, а модем на стороне абонента превращает их обратно в Ethernet, и дальше уже работает PPPoE.
PPP: протокол для линии точка-точка
PPP (RFC 1661) — канальный протокол для соединения ровно двух сторон. MAC-адресов в нём нет: раз на линии двое, адресовать некого. Кадр PPP в чистом виде устроен просто:
байты: 2 переменная
┌──────────┬────────────────────┐
│ Protocol │ данные │
└──────────┴────────────────────┘
Поле Protocol — наше поле «что внутри», как EtherType в Ethernet:
| Protocol | Что внутри |
|---|---|
0xc021 |
LCP — управление самой линией |
0xc023 |
PAP — аутентификация паролем |
0xc223 |
CHAP — аутентификация по вызову-ответу |
0x8021 |
IPCP — настройка IPv4 поверх линии |
0x8057 |
IPv6CP — настройка IPv6 |
0x0021 |
IPv4-пакет |
0x0057 |
IPv6-пакет |
Закономерность: протоколы с кодом 0x8xxx настраивают семейство 0x0xxx, а 0xcxxx — служебные для самой линии.
Главное в PPP — этапы жизни соединения. Линия не просто «поднялась»: стороны последовательно договариваются о параметрах, проверяют друг друга и только потом настраивают IP.
физическая ┌───────┐ ┌──────────────────┐ ┌─────────────┐ ┌────────┐
линия есть ──►│ LCP ├──►│ аутентификация ├──►│ IPCP/IPv6CP ├──►│ данные │
└───┬───┘ │ (PAP или CHAP) │ └──────┬──────┘ └───┬────┘
│ └──────────────────┘ │ │
└──────── Terminate ◄─────────────────┴──────────────┘
LCP: договориться о линии
LCP (Link Control Protocol) открывает соединение. Обе стороны шлют Configure-Request со списком опций, которые они хотят использовать. На каждый запрос возможны три ответа:
- Configure-Ack — согласен со всеми опциями;
- Configure-Nak — опцию понимаю, но значение не нравится, вот моё (например, «MRU не 1500, а 1492»);
- Configure-Reject — опцию не понимаю или не хочу, убери её.
Отправитель правит запрос и шлёт снова, пока обе стороны не получат Ack. Это общий механизм всех протоколов-«CP» в PPP: IPCP работает точно так же, только с другими опциями.
Опции LCP, которые встречаются в каждой сессии PPPoE:
- MRU (Maximum Receive Unit) — какого размера пакеты сторона готова принимать. В PPPoE — 1492, и почему, ниже.
- Authentication-Protocol — сервер требует аутентификацию и говорит, какую: PAP или CHAP. Клиент обычно этой опции не шлёт, потому что проверять провайдера ему не нужно.
- Magic-Number — случайное число каждой стороны. Если в ответе на свой Echo-Request сторона видит своё же число, значит, линия закольцована и она разговаривает сама с собой.
После открытия LCP продолжает работать как сторож линии: периодически шлёт Echo-Request, ждёт Echo-Reply, и если несколько ответов подряд не пришли — считает линию мёртвой и закрывает сессию. Это и есть причина, по которой роутер «видит», что провайдер пропал, и начинает переподключаться. Закрытие — Terminate-Request и Terminate-Ack.
Аутентификация: PAP и CHAP
PAP (Password Authentication Protocol) — проще некуда: клиент шлёт Authenticate-Request с логином и паролем открытым текстом, сервер отвечает Authenticate-Ack или Authenticate-Nak. Любой, кто видит кадры, видит пароль. В PPPoE внутри провайдерской сети это считается терпимым, но лучше CHAP.
CHAP (Challenge Handshake Authentication Protocol) пароль по линии не передаёт:
- Сервер шлёт Challenge: случайную строку и своё имя.
- Клиент считает
MD5(идентификатор, пароль, challenge)и шлёт Response: хеш и свой логин. - Сервер, зная пароль, считает то же самое, сравнивает и отвечает Success или Failure.
Пароль не виден, а перехваченный ответ бесполезен: в следующий раз challenge будет другой. Слабость в другом: сервер должен хранить пароли в открытом виде, чтобы считать тот же хеш. Есть вариант MS-CHAPv2, в котором обе стороны проверяют друг друга и хеш считается иначе, но схема та же: вызов, ответ, вердикт.
Логин и пароль провайдер проверяет не на самом концентраторе, а на сервере RADIUS: протоколе поверх UDP, по которому сетевое оборудование спрашивает у центральной базы «пускать ли этого пользователя», получает Accept или Reject, а вместе с Accept — параметры: адрес абонента, ограничение скорости, срок сессии. Туда же концентратор потом шлёт учётные записи о начале и конце сессии и объёме трафика. Это и есть ответ на «зачем провайдеру PPP»: вся бухгалтерия висит на одном протоколе.
IPCP: получить адрес
Аутентификация пройдена, и теперь по той же схеме Request/Ack/Nak договариваются об IP. Опций в IPCP (RFC 1332) немного, и главная — IP-Address. Хитрость в том, как клиент получает адрес: он шлёт Configure-Request с опцией IP-Address, равной 0.0.0.0, что означает «адреса нет, дайте». Сервер отвечает Configure-Nak с той же опцией, но настоящим значением: «не 0.0.0.0, а 198.51.100.42». Клиент повторяет запрос уже с этим адресом и получает Ack. Таким же образом, через опции 0x81 и 0x83, передаются адреса DNS-серверов.
Сервер со своей стороны тоже шлёт Configure-Request со своим адресом, и клиент его подтверждает. В итоге у линии два конца с двумя адресами, и в ip addr это выглядит так:
5: ppp0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1492 qdisc fq_codel state UNKNOWN
link/ppp
inet 198.51.100.42 peer 198.51.100.1/32 scope global ppp0
Обратите внимание на NOARP и peer: у интерфейса нет MAC-адреса, нет маски подсети в привычном смысле, есть только «я» и «тот, кто на другом конце». Маршрут по умолчанию — просто default dev ppp0: всё, что не моё, уходит в линию, и адрес шлюза для этого не нужен.
Для IPv6 ту же работу делает IPv6CP, только опция у него одна — идентификатор интерфейса (младшие 64 бита адреса), а сам префикс абонент потом получает уже по протоколам IPv6, о которых будут части 14–16.
После IPCP линия в состоянии «открыта», и дальше в кадрах с Protocol 0x0021 идут обычные IP-пакеты.
PPPoE: PPP внутри кадра Ethernet
Теперь завернём это в Ethernet. У PPP нет адресов, а в сегменте провайдера сотни абонентов и один-два концентратора доступа (Access Concentrator, он же BRAS — Broadband Remote Access Server). Нужно сначала найти концентратор и завести с ним отдельную сессию, а потом слать PPP-кадры именно ему. Поэтому у PPPoE две стадии с разными EtherType:
- Discovery, EtherType
0x8863— поиск концентратора и установление сессии; - Session, EtherType
0x8864— передача PPP-кадров внутри сессии.
Заголовок PPPoE — 6 байт, одинаковый на обеих стадиях:
байты: 1 1 2 2
┌────┬────┬────────┬────────────┬──────────┐
│Ver │Type│ Code │ Session ID │ Length │ затем теги (discovery)
│ =1 │ =1 │ │ │ │ или PPP-кадр (session)
└────┴────┴────────┴────────────┴──────────┘
(Ver и Type по 4 бита)
Code — тип сообщения на стадии discovery или 0x00 для данных, Session ID — номер сессии, который выдаст концентратор, Length — длина содержимого.
Discovery: четыре кадра
клиент концентратор(ы)
│ │
├── PADI ───────────────────── широковещательно ───────►│ «кто тут умеет PPPoE?»
│ ff:ff:ff:ff:ff:ff, Service-Name, Host-Uniq │
│ │
│◄─ PADO ────────────────────────────────── от каждого ─┤ «я, bras-01, могу»
│ AC-Name, Service-Name, AC-Cookie │
│ │
├── PADR ────────────────────── выбранному, unicast ───►│ «хочу сессию с тобой»
│ Service-Name, AC-Cookie │
│ │
│◄─ PADS ───────────────────────────────────────────────┤ «сессия 0x1a2b открыта»
│ Session ID │
│ │
│ ═══════ стадия Session: PPP-кадры с Session ID ══════ │
│ │
├── PADT ──────────────────────── любая из сторон ─────►│ «сессия закрыта»
- PADI (PPPoE Active Discovery Initiation), код
0x09. Клиент ещё ничего не знает и шлёт кадр на широковещательный адрес. Внутри тег Service-Name (обычно пустой — «любая услуга») и Host-Uniq — случайное число, по которому клиент потом узнает ответы на свой запрос среди чужих. - PADO (Offer), код
0x07. Каждый концентратор, услышавший PADI и готовый обслужить, отвечает unicast-кадром с тегами AC-Name (своё имя), Service-Name и AC-Cookie — значением, которое клиент должен вернуть без изменений. Cookie защищает концентратор от затопления: он не заводит состояние, пока клиент не докажет, что действительно получил ответ. - PADR (Request), код
0x19. Клиент выбирает один из PADO (обычно первый) и шлёт ему запрос сессии с тем же AC-Cookie. - PADS (Session-confirmation), код
0x65. Концентратор выдаёт Session ID, ненулевое 16-битное число. С этого момента сессия определяется тройкой: MAC клиента, MAC концентратора, Session ID.
PADT (Terminate), код 0xa7, закрывает сессию с любой стороны. Любопытная деталь: PADT приходит после того, как PPP уже закрылся через LCP Terminate, но если концентратор перезагрузился и забыл сессию, он пошлёт PADT в ответ на первый же кадр с незнакомым Session ID, и клиент узнает, что надо переподключаться.
Если на PADI никто не ответил, клиент повторяет его с растущим интервалом. Это та самая ситуация «роутер пишет Connecting и ничего не происходит»: кабель есть, линк есть, а концентратора за ним нет — обычно потому, что порт в подъезде не в том VLAN.
Session: PPP как есть
На стадии Session в каждом кадре после 6-байтового заголовка PPPoE с кодом 0x00 и нужным Session ID идёт PPP-кадр: 2 байта Protocol и данные. Так, IP-пакет абонента в кабеле выглядит как:
┌──────────────┬────────────┬──────────┬────────────┬─────┐
│ Ethernet 14 │ PPPoE 6 │ PPP 2 │ IP-пакет │ FCS │
│ EtherType │ code 0x00 │ 0x0021 │ │ │
│ 0x8864 │ session ID │ │ │ │
└──────────────┴────────────┴──────────┴────────────┴─────┘
Это обычная матрёшка из первой части, в которой между Ethernet и IP вставили два слоя: PPPoE и PPP. Концентратор снимает все три заголовка, смотрит в IP и маршрутизирует дальше.
MTU 1492
Из картинки выше следует арифметика, из-за которой PPPoE все помнят. В кадр Ethernet помещается 1500 байт данных. Из них 6 съедает заголовок PPPoE и 2 — PPP. Для IP-пакета остаётся 1492. Именно это значение стороны и сообщают друг другу в LCP как MRU, и именно такой MTU получает интерфейс ppp0.
Почему это проблема. Весь остальной интернет живёт с MTU 1500, и сервер на другом конце, ничего не зная о PPPoE, отправляет абоненту пакеты по 1500 байт. Концентратор такой пакет в сессию запихнуть не может. Дальше, по правилам IP (часть 6), он должен либо разрезать пакет на части, либо, если отправитель это запретил флагом DF, выбросить и отправить ему ICMP-сообщение «слишком большой, уменьши до 1492» (подробно в части 8 — это механизм Path MTU Discovery). Современные стеки флаг DF ставят всегда, так что остаётся второй путь, и он работает, пока ICMP-сообщение доходит до сервера. Если где-то по дороге файрвол режет ICMP «на всякий случай», сервер будет слать 1500-байтовые пакеты в никуда. Симптом знаком каждому: страницы с маленькими ответами открываются, большие файлы и картинки висят, ping ходит.
У проблемы два решения на стороне абонента:
- MSS clamping на роутере. При установлении TCP-соединения стороны сообщают друг другу максимальный размер сегмента MSS (часть 10). Роутер переписывает это значение в проходящих пакетах на 1452 (1492 минус 40 байт заголовков IP и TCP), и сервер с самого начала не шлёт больше. На Linux это одно правило:
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu. Почти все домашние роутеры делают это сами. - RFC 4638: если концентратор и коммутаторы провайдера пропускают кадры Ethernet на 8 байт больше стандарта (1508), в PADI/PADO добавляется тег PPP-Max-Payload, и MTU сессии становится честные 1500. Поддерживают не все провайдеры.
Смотрим руками
Поднять PPPoE на Linux умеет NetworkManager (nmcli connection add type pppoe ifname eth1 username user password secret), а под капотом везде демон pppd с плагином rp-pppoe. Его лог рассказывает всю историю соединения:
pppd[1234]: Plugin rp-pppoe.so loaded.
pppd[1234]: PPP session is 6699
pppd[1234]: Connected to 00:11:22:33:44:55 via interface eth1
pppd[1234]: Using interface ppp0
pppd[1234]: Connect: ppp0 <--> eth1
pppd[1234]: CHAP authentication succeeded
pppd[1234]: peer from calling number 00:11:22:33:44:55 authorized
pppd[1234]: local IP address 198.51.100.42
pppd[1234]: remote IP address 198.51.100.1
pppd[1234]: primary DNS address 198.51.100.53
Стадию discovery видно в tcpdump фильтром pppoed, сессию — pppoes:
$ sudo tcpdump -i eth1 -n -e pppoed
3c:52:82:1a:2b:3c > ff:ff:ff:ff:ff:ff, ethertype PPPoE D (0x8863), length 32: PPPoE PADI [Service-Name] [Host-Uniq 0x00000001]
00:11:22:33:44:55 > 3c:52:82:1a:2b:3c, ethertype PPPoE D (0x8863), length 60: PPPoE PADO [AC-Name "bras-01"] [Service-Name] [AC-Cookie 0x...] [Host-Uniq 0x00000001]
3c:52:82:1a:2b:3c > 00:11:22:33:44:55, ethertype PPPoE D (0x8863), length 48: PPPoE PADR [Service-Name] [AC-Cookie 0x...] [Host-Uniq 0x00000001]
00:11:22:33:44:55 > 3c:52:82:1a:2b:3c, ethertype PPPoE D (0x8863), length 60: PPPoE PADS [ses 0x1a2b] [Service-Name] [Host-Uniq 0x00000001]
$ sudo tcpdump -i eth1 -n pppoes
PPPoE [ses 0x1a2b] LCP, Conf-Request (0x01), id 1, length 16
PPPoE [ses 0x1a2b] LCP, Conf-Ack (0x02), id 1, length 16
PPPoE [ses 0x1a2b] CHAP, Challenge (0x01), id 1, Value ..., Name bras-01
PPPoE [ses 0x1a2b] CHAP, Response (0x02), id 1, Value ..., Name user
PPPoE [ses 0x1a2b] CHAP, Success (0x03), id 1
PPPoE [ses 0x1a2b] IPCP, Conf-Request (0x01), id 1, length 10
PPPoE [ses 0x1a2b] IPCP, Conf-Nak (0x03), id 1, length 10
PPPoE [ses 0x1a2b] IPCP, Conf-Request (0x01), id 2, length 10
PPPoE [ses 0x1a2b] IPCP, Conf-Ack (0x02), id 2, length 10
PPPoE [ses 0x1a2b] IP 198.51.100.42 > 203.0.113.10: ICMP echo request, ...
Здесь всё, о чём была статья, в хронологическом порядке: четыре кадра discovery с тегами, LCP, CHAP с вызовом и ответом, IPCP с тем самым Nak, в котором концентратор сообщает адрес, и наконец IP. В Wireshark каждый слой раскрывается отдельно: Ethernet, PPPoE с Session ID, PPP с полем Protocol и дальше либо LCP/CHAP/IPCP с опциями, либо IP.
Что запомнить
- PPP — канальный протокол для двух сторон без адресов, с этапами: LCP договаривается о линии, PAP или CHAP проверяет абонента, IPCP выдаёт адрес. Все «CP» работают одной схемой: Request, затем Ack, Nak или Reject.
- PPPoE нужен, чтобы завести PPP-сессию через общий Ethernet: discovery (PADI → PADO → PADR → PADS) находит концентратор и получает Session ID, дальше PPP-кадры идут внутри Ethernet с этим номером.
- Провайдеру это даёт аутентификацию, учёт и управление сессией через RADIUS, абоненту — логин и пароль в настройках роутера.
- 8 байт заголовков PPPoE и PPP оставляют IP-пакету 1492. Без MSS clamping или рабочего Path MTU Discovery большие ответы из интернета не доходят.
- LCP Echo — сторож линии; PADT — её конец.
На этом блок про канальный уровень закончен: мы умеем доставлять кадры соседу по кабелю, радио и через провайдера. Дальше — IPv4: что такое IP-адрес и маска, как по ним считаются подсети, как машина решает, сосед перед ней или нужен шлюз, и какие адреса нельзя встретить в интернете.
Оставить комментарий могут только зарегистрированные пользователи.
Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.