6.0 KiB
ICMP proxy
icmp_proxy.c/h передаёт IPv4 Echo Request/Reply через ETCP_RT_ID_ICMP_PROXY
в TOPO_GROUP_UTUN. Общий формат router описан в
doc/proxy_protocol.md, контракты API — в
icmp_proxy.h. Другие типы ICMP модуль не проксирует.
Контекст и raw socket
Каждый UTUN_INSTANCE владеет отдельным inst->icmp_proxy, глобального контекста нет.
init(inst, ua) задаёт таймаут 50000 tb (5 с), случайное начало next_token и
регистрирует общий binding. Raw socket отдельно запускает icmp_proxy_enable_exit(inst):
один неблокирующий AF_INET/SOCK_RAW/IPPROTO_ICMP регистрируется в uasync до публикации.
Повторный init сохраняет существующий контекст и возвращает 0. Ошибка создания
контекста/случайного token/binding возвращает отрицательный код без публикации контекста.
enable_exit возвращает -errno при отказе raw socket/nonblocking/регистрации, сохраняя
общий контекст и приём клиентских REPLY. Instance трактует отсутствие прав/поддержки raw
как отключение только ICMP exit с WARN; отказ обязательного ресурса прерывает создание instance.
TCP client/server не создают и не уничтожают общий контекст.
test_loopback по умолчанию выключен. icmp_proxy_set_test_loopback(inst, 1) разрешает
виртуальный REPLY явно, закрывая ранее открытый raw socket. Переключение при pending-запросах
отклоняется с WARN; без контекста setter ничего не делает. Production fallback без сети отсутствует.
Запрос и сопоставление ответа
REQUEST принимается при текущем server.enabled. Exit заменяет исходные echo id/seq
32-битным возрастающим token: старшие 16 бит — wire_id, младшие — wire_seq (в сетевом порядке).
При совпадении wire_id/wire_seq/dst_ip с pending выбирает следующий token.
Сопоставление ответа использует эту тройку; исходные id/seq и адрес приложения хранятся
в icmp_request. Разные клиенты с одинаковыми исходными id/seq не смешиваются.
Raw reader проверяет IPv4 IHL/протокол, Echo Reply type/code и ICMP checksum, ищет pending по wire id/seq и адресу отправителя, удаляет найденный запрос и отправляет REPLY ingress-узлу. Данные ответа берутся из raw пакета. Сохранённый в request префикс payload до 1500 байт для формирования ответа не используется; он не ограничивает длину реального echo payload.
Таймер резервируется до первого raw send, затем продлевается каждые 5 с, пока pending
не пуст. Удаляет записи с возрастом больше 5 с на очередном обходе; удаление не происходит
точно в момент достижения порога. destroy снимает binding, отменяет таймер, закрывает
raw socket/регистрацию, освобождает pending и обнуляет inst->icmp_proxy.
Все действия — в uasync; inst и ua заимствуются до destroy.
Отказ таймера до отправки отклоняет запрос; отказ продления освобождает все pending с ERROR.
Запрос без действующего таймера в pending не публикуется.
Сообщения и доставка
Вход etcp_route_send:
svc:1, cmd:1, dst_ip:4, orig_src_ip:4, echo_id:2, echo_seq:2, payload.
REQUEST=1, REPLY=2, заголовок отправки — 14 байт. В REPLY dst_ip остаётся адресом
цели ping, orig_src_ip — адресом приложения. Router callback получает
26-байтовые метаданные перед cmd, итого заголовок приёма — 39 байт.
Peer находится в метаданных router; отдельного sender ID в ICMP payload нет.
IP/id/seq передаются в network byte order.
send_to_exit копирует payload 0..65507 и возвращает результат router
(0 = принято в очередь, <0 = ошибка). Локальный ICMP-контекст для этой отправки не требуется.
Используются force=0, mode=0, собственного повтора REQUEST/REPLY нет.
Клиент допускает REPLY только от tcp_proxy_client->via_node_id.
deliver_reply(inst, dst_ip, src_ip, ...) требует клиентский TUN, допускает payload
0..65506, собирает IPv4 src_ip→dst_ip с Echo Reply, TTL 64 и обеими checksum.
Нечётный хвост checksum дополняется нулём без чтения за границей.
Результат с префиксом версии 4 ставится в tun->input_queue:
0 = поставлен в очередь, -1 = ошибка. Payload заимствован на время вызова.
recv_cb принимает владение входным entry/dgram при любом исходе.