Прикладной уровень: возможности операционной системы
Поток / датаграмма / whatever получены. Что дальше?
Цели:
- Активация интерпретатора
- Сопоставление потока данных интерпретатору
- Обеспечение готовности сервиса-интерпретатора
- Интерпретация
- (…) а откуда нам знать? Сколько протоколов, столько и логик.
Задачи:
Связывание порта и интерпретатора
Запуск и обслуживание интерпретатора - Анонс прикладных служб по сети и распознавание этого анонса
- Учёт потребления ресурсов прикладными службами: иногда изнутри самого приложения не видно, насколько всё плохо
Систем запуска и контроля сетевых сервисов в Linux несколько, остановимся на самом «универсальном» — systemd.
Коротко об управлении systemd
Понятие службы systemd
Команды управления systemctl:
- start / restart / stop / status
- enable / disable
- cat
edit и его
-грабельки --full --force
$SYSTEMD_EDITOR
--stdin
Назначение systemd.socket
- Есть ещё таймеры, точки монтирования, чёрт в ступе…
Связь с интерпретатором
Пространство имён: /etc/services — только well-known и нет прямой связи с приложением
В роли «служб» мы использовали
netcat -l
socat TCP6-LISTEN…,fork (или UDP…)
Два варианта сетевой службы в Linux:
- Полноценный сервис.
- Делает подсистема запуска:
- Запуск и останов по расписанию / заспросу
- Делает сама служба:
Создание и обслуживание сокета (bind() + listen() + accept())
- (одно соединение) Закрытие сокета + respawn при закрытии соединения
(несколько соединений) Обработка соединения отдельным процессом accept() + fork()
- Количество одновременных соединений?
- Интерпретация потоков В/В
- Управление сервисом — сигналы или специальный сокет
- Делает подсистема запуска:
- «Сокет-активация»
- Делает подсистема запуска:
Создание и обслуживание сокета (bind() + listen() + accept())
- Обслуживание одного соединения
Обслуживание нескольких соединений accept() + fork() (в т. ч. общие для прикладного уровня ограничения по ресурсам, конкуренции между соединениями и т. п.)
Запуск соответствующего интерпретатора потока (stdin / stdout) и преобразование потока в пакеты
- Управление сервисом посредством сигналов
- Делает сама служба:
- Интерпретация потоков В/В
- Управление сервисом через специальный сокет / утилиту
- Делает подсистема запуска:
Есть ещё сокет-активация для полноценных сервисов с передачей им открытого сокета, (с указанием Accept=no):
- В документации написано, что именно так и надо делать (например, понижение привилегий становится вырожденно простой операцией),
…но
почти никто так не делает, …потому что экономия от замены accept → fork → exec на accept → pthread* видна только на крупных серверах, а они и сами сокет открывать умеют,
…и единственный пример, который я нашёл — это systemd-socket-proxyd, странный вариант socat от тех же авторов)
Есть и другие аргументы в пользу Accept=no для доступных по сети служб (в спойлере)
Вариант Accept=no встречается повсеместно, когда надо открыть unix domain сокет (обычно для управления сервисом).
Примеры
netstat (или ss — sockstat)
-ltp (и -lup)
-ntp (и -nup)
socat как служба запуска:
socat TCP6-LISTEN:1234 EXEC:/usr/bin/cal
В Linux при повторном запуске можете получить TIME_WAIT ⇒ TCP6-LISTEN:1234,reuseaddr
Несколько соединений: базовый сервис + fork() : TCP6-LISTEN:1234,reuseaddr,fork
Запуск собственных служб с помощью systemd
Полноценный сервис
Сервис должен сам уметь в транспортный уровень (bind() → listen() → accept() etc.)
Запуск socat как службы:
Создание .service файла:
[root@srv ~]# systemctl edit --force --full cal.service` … не забываем про $SYSTEMD_EDITOR [root@srv ~]# systemctl cat cal.service # /etc/systemd/system/cal.service [Unit] Description=SOCAT Example [Service] ExecStart=/usr/bin/socat TCP6-LISTEN:1234 EXEC:/usr/bin/cal
- Запуск:
[root@srv ~]# systemctl start cal.service … [root@srv ~]# systemctl status cal … [root@srv ~]# ps | grep cal … [root@srv ~]# ss -ltp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process … LISTEN 0 5 [::]:1234 [::]:* users:(("socat",pid=1473,fd=5)) - Проверка с соседней машины
[root@router ~]# netcat fe80::a00:27ff:fe15:56a8%eth1 1234 April 2024 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 - Служба отработала:
[root@srv ~]# systemctl status cal … Apr 04 16:55:40 srv systemd[1]: cal.service: Deactivated successfully.
- Если хотим, чтобы служба перезапускалась, надо самостоятельно бороться с REUSEADDR:
- Изменить сервис файл — добавить
… [Service] Restart=always ExecStart=/usr/bin/socat TCP6-LISTEN:1234,reuseaddr EXEC:/usr/bin/cal
Не забыть вызвать systemctl daemon-reload (перечитать файлы сервисов)
- Изменить сервис файл — добавить
Такая служба обрабатывает только одно подключение, но у socat TCP6-LISTEN есть параметр ,fork
Сокет-активация
- Сам сервис — только прикладной
- Масштабирование с помощью шаблонов
Пример:
- Надо создать два юнита:
- Сокет-активатор
[root@srv ~]# systemctl edit --full --force hexdump.socket … [root@srv ~]# systemctl cat hexdump.socket # /etc/systemd/system/hexdump.socket [Unit] Description = Hexdump Socket [Socket] ListenStream = 1234 Accept = yes
Accept = yes — это ещё и приём нескольких соединений в стиле accept() + fork()`
Сервис. Поскольку сервисов будет запускаться несколько одновременно, мы задаём шаблон (@ заменится на ID сервиса,
[root@srv ~]# systemctl edit --full --force hexdump@.service … [root@srv ~]# systemctl cat hexdump@.service # /etc/systemd/system/hexdump@.service [Unit] Description=Hexdump Service [Service] ExecStart=/usr/bin/hexdump -C StandardInput=socket # StandardOutput=socket is also set by previous line
- Сокет-активатор
- Запустим сокет-активацию:
[root@srv ~]# systemctl start hexdump.socket [root@srv ~]# ss -ltp State Recv-Q Send-Q Local Address:Port Peer Address:Port Process … LISTEN 0 4096 *:1234 *:* users:(("systemd",pid=1,fd=53)) На порту слушает сам systemd, сервис поднимется только после подключения:
[root@router ~]# netcat fe80::a00:27ff:fe15:56a8%eth1 1234
[root@srv ~]# systemctl | grep hexdump hexdump@3-7-fe80::a00:27ff:feda:b8ab:1234-fe80::a00:27ff:fe87:1ad8:36802.service loaded active running Hexdump Provider ([fe80::a00:27ff:fe87:1ad8]:36802%eth1) system-hexdump.slice loaded active active Slice /system/hexdump hexdump.socket loaded active listening Hexdump Provider
- как видим, ID сервиса, активированного сокетом, основан на знакомой нам четвёрке
- Проверим, как обрабатывается несколько подключений
При задании сервиса можно использовать разные динамические значения
Например, можно добавить ExecStartPre=echo "Hexdump on %H %i" или что-то подобное в hexdump@.service
Для того, чтобы сервис (или сокет-активация) стартовали автоматом после перезагрузки, необходимо
поставить соответствующий юнит в зависимость от какого-либо этапа загрузки системы, вписав в .service-файл примерно такое:
… [Install] WantedBy=multi-user.target
Активировать сервис командой systemctl enable юнит
Перманентная настройка сети с помощью systemd-networkd
Различные дистрибутивы и сообщества Linux пользуются несколькими разными высокоуровневыми системами настройки сети; стандарта или общей договорённости нет. Раз уже мы начали с systemd, рассмотрим, как настраивать сеть с его помощью.
Отступление 0: Просмотр, останов и запуск служб systemd
Отступление 1: .d-схема хранения конфигурационных файлов.
.d-каталог /etc/systemd/network/
Сам systemd-networkd и его соседи — systemd-udevd и systemd-resolved:
systemd.link: переименование и настройка физических сетевых интерфейсов (это отслеживается udev-ом)
systemd.netdev: создание виртуальных сетевых устройств (например, бриджей и VLAN-ов)
См. примеры
systemd.network: настройки параметров сети на интерфейсах и настройки специальных подсистем
См. примеры
Запустим его:
[root@client ~]# systemctl start systemd-networkd.service
Настроим IP клиента и маршрутизацию через «внутреннюю» сеть VirtualBox и машину srv (аналогично примеру по ссылке выше):
Match по имени — самая простая, но не самая лучшая идея: в зависимости от (стихийно складывающегося!) порядка просмотра устройств номер может и поменяться. Варианты: MACAddress=, Path= и т. п. Подсеть и адрес — 2001:db8:0:b::915/64
Шлюз по умолчанию: fe80::2 (интерфейс известен)
[root@client ~]# cat /etc/systemd/network/80-uplink.network [Match] Name=eth1 [Network] Address=2001:db8:0:b::915/64 Gateway=fe80::2
Перечитаем настройки сети и обновим настройки интерфейса:
[root@client ~]# networkctl reload [root@client ~]# networkctl reconfigure eth1 [root@client ~]# networkctl status eth1 * 2: eth1 Link File: /usr/lib/systemd/network/99-default.link Network File: /etc/systemd/network/80-uplink.network Type: ether State: routable (configured) Online state: online Alternative Names: enp0s8 Path: pci-0000:00:03.0 Driver: pcnet32 Vendor: Advanced Micro Devices, Inc. [AMD] Model: 79c970 [PCnet32 LANCE] (PCnet - Fast 79C971) HW Address: 08:00:27:65:59:a2 (PCS Systemtechnik GmbH) MTU: 1500 (min: 68, max: 1500) QDisc: fq_codel IPv6 Address Generation Mode: eui64 Queue Length (Tx/Rx): 1/1 Auto negotiation: no Speed: 1Gbps Duplex: full Port: tp Address: 2001:db8:0:b::915 fe80::a00:27ff:fe65:59a2 Gateway: fe80::2 Activation Policy: up Required For Online: yes DHCP6 Client DUID: DUID-EN/Vendor:0000ab11222f46bae4798c040000 Apr 14 18:09:47 client systemd-networkd[1096]: eth1: Re-configuring with /etc/systemd/network/80-uplink.network Apr 14 18:09:47 client systemd-networkd[1096]: eth1: Link UP Apr 14 18:09:47 client systemd-networkd[1096]: eth1: Gained carrier Apr 14 18:09:51 client systemd-networkd[1096]: eth1: Re-configuring with /etc/systemd/network/80-uplink.networkФайл обязан называться что-то-там.network (других настроек не нужно)
Например, /etc/systemd/network/10-inet.network
10 — это для .d-схемы лексикографической сортировки
Эта настройка перманентна — после перезагрузки должна восстановиться. Надо только не забыть самому networkd сказать systemctl enable systemd-networkd
Леннарт и
ошибки в конфигах. Если в .network-файле есть какая-то ошибка:
networkctl reconfigure проходит успешно,
systemctl restart systemd-networkd проходит успешно,
…и вообще ничего не свидетельствует о неполадке, кроме записи в системном журнале: journalctl -u systemd-networkd, а дальше ищем глазками, где что не так ☹
Настройка статического маршрута
Тот же файл ….network +
[Route] Gateway=маршрутизатор Destination=сеть
Настройка net.ipv6.conf.all.forwarding:
/etc/systemd/networkd.conf:
[Network] … IPv6Forwarding=yes
Из известного нам: ND proxy, Policy routing, …
Настройка автоматической конфигурации
Самый простой вариант — RAdv.
При включённом networkd на интерфейсе параметр net.ipv6.conf.интерфейс.accept_ra всегда выключен — разбором RAdv занимается сам systemd-networkd.
Опция IPv6AcceptRA по умолчанию включена, если мы не роутер.
На клиенте:
… [Network] IPv6AcceptRA=yes
systemd-networkd умеет в DNS, но только посредством systemd-resolved — в следующей лекции
systemd-networkd умеет анонсировать RA! На роутере:
… [Network] IPv6SendRA=yes [IPv6SendRA] RouterLifetimeSec=200 [IPv6Prefix] Prefix=2001:db8:0:c::/64 [Route] Destination=2001:db8:0:c::/64
Можно и без явного маршрута, если префикс on-link и роутер берёт в нём себе адрес:
… [Network] IPv6SendRA=yes [IPv6SendRA] RouterLifetimeSec=200 [IPv6Prefix] Prefix=2001:db8:0:c::/64 Assign=yes
Флаги AddressAutoconfiguration= и OnLink= для анонсируемого префикса по умолчанию включены.
Д/З
образ не изменился
Задание 8
Суть: три виртуалки; на маршрутизаторе выдать двум другим виртуалкам RAdv с помощью systemd-networkd; на client и srv запустить три сетевых сервиса: два вырожденных и один простой
Первый сервис получает на вход произвольный файл, и выдаёт на выход hexdump этого файла в любом удобном для решения задачи формате
- Второй сервис выполняет обратную задачу
Третий сервис запускается на client, и он посложнее.
- На вход подаётся три или более текстовых строк в формате ASCII
- Шаблон (первая строка)
- Замена (вторая строка)
- Текст из одной или более строк
- На выходе должен получиться «текст», в котором все вхождения «шаблона» заменены на «замену».
- На вход подаётся три или более текстовых строк в формате ASCII
Задача:
Настроить на двух интерфейсах router сеть с помощью systemd-networkd (в отчёт не входит)
client и srv должны получить глобальные адреса и иметь между собой связность
Перманентно настроить сеть на client и srv (в отчёт не входит)
Оформить все три сервиса как чисто текстовые обработчики с сокет-активацией. С помощью этих сервисов организовать замену произвольного байта на любой другой произвольный байт (каждый байт — это две шестнадцатеричные цифры).
- Настройка сети и запуск служб производятся заранее и в отчёт не входят
Подсказка: почитайте systemd.exec: имеет смысл отделить stderr сервиса от stdout
Спойлер: первые два сервиса за нас сделает xxd с ключами -c1 -p и -r -p соответственно.
- Площадка:
srv — хост с двумя вырожденными сервисами (1) и (2)
router — маршрутизатор с двумя интерфейсами в сторону srv и client
client — хост с сервисом замены текста (3). Для программирования на client доступны (можно пользоваться чем угодно, что найдёте в образе; я выбрал python3):
bash (на bash+sed это три строки), awk, … прочие стандартные утилиты linux, python3, perl
- Отчёт:
report 8 client
Сделать networkctl status eth1 -n 1
Сделать systemctl cat сервису (3) и его сокет-активатору
Если для обработки написан какой-то скрипт на каком-то языке, сделать ему cat
Подать на вход сервису три строки: 33, 44 и 33, и убедиться, что он возвращает 44:
echo -e '33\n44\n33' | netcat localhost порт
Сделать systemctl status сокет-активатору: должно показать нулевое количество соединений в данный момент (Connected:) и ненулевое — обслуженных соединений (Accepted:)
report 8 router
Сделать networkctl status eth1 -n 1
Сделать networkctl status eth2 -n 1
Запустить tcpdump -nv на любом из этих двух интерфейсов так, чтобы поймать передаваемые между службами данные
report 8 srv
Сделать networkctl status eth1 -n 1
Сделать systemctl cat обоим сервисам и их сокет-активаторам
Сделать systemctl status обоим сокет-активаторам
- Проверить, что преобразование в сервисах обратимо:
date > d; netcat localhost порт1 < d | netcat localhost порт2 | cmp d -
Собрать следующий конвейер из netcat-ов:
{ echo 09; echo 2d; netcat localhost порт1 < /etc/crontab.template; } | netcat клиент порт | netcat localhost порт2
- (табуляции должны замениться минуcами)
Сделать systemctl status обоим сокет-активаторам: должно показать нулевое количество соединений в данный момент (Connected:) и ненулевое — обслуженных соединений (Accepted:)
Три отчёта (названия сохранить, должно быть: report.08.client, report.08.router, report.08.srv) переслать одним письмом в качестве приложений на uneexlectures@cs.msu.ru
В теме письма должно встречаться слово LinuxNetwork2026


Можно перезапустить службу (например, для обновления её программы), не пересоздавая слушающий сокет. Исчезает окно времени, в которое клиентам отвечают ECONNREFUSED
Все .socket-юниты стартуют до запуска служб и тем более до сеансов; угнать порт у службы, опередив её, нельзя