Сети для самых маленьких. Часть 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) пароль по линии не передаёт:

  1. Сервер шлёт Challenge: случайную строку и своё имя.
  2. Клиент считает MD5(идентификатор, пароль, challenge) и шлёт Response: хеш и свой логин.
  3. Сервер, зная пароль, считает то же самое, сравнивает и отвечает 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 ──────────────────────── любая из сторон ─────►│  «сессия закрыта»
  1. PADI (PPPoE Active Discovery Initiation), код 0x09. Клиент ещё ничего не знает и шлёт кадр на широковещательный адрес. Внутри тег Service-Name (обычно пустой — «любая услуга») и Host-Uniq — случайное число, по которому клиент потом узнает ответы на свой запрос среди чужих.
  2. PADO (Offer), код 0x07. Каждый концентратор, услышавший PADI и готовый обслужить, отвечает unicast-кадром с тегами AC-Name (своё имя), Service-Name и AC-Cookie — значением, которое клиент должен вернуть без изменений. Cookie защищает концентратор от затопления: он не заводит состояние, пока клиент не докажет, что действительно получил ответ.
  3. PADR (Request), код 0x19. Клиент выбирает один из PADO (обычно первый) и шлёт ему запрос сессии с тем же AC-Cookie.
  4. 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-адрес и маска, как по ним считаются подсети, как машина решает, сосед перед ней или нужен шлюз, и какие адреса нельзя встретить в интернете.

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

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