Documentation
¶
Overview ¶
Package linuxsys — системная часть Linux для Э4: выбор физического интерфейса и подписка на смену сети (plan-e4.md, блок N), дальше — адаптер, маршруты и DNS. Аналог winsys.
Код пакета живёт в файлах *_linux.go, и только они импортируют netlink из github.com/vishvananda/netlink. Под другие системы пакет пустой.
Index ¶
- Constants
- func ClearTunnelRoutes(index int, prefixes []netip.Prefix) error
- func ConfigureAdapter(index int, addrs []netip.Addr, mtu int, logf func(string, ...any)) error
- func FirewalldStrictIPv6() (bool, error)
- func LinkIndex(name string) (int, error)
- func LinkKind(name string) (kind string, ok bool, err error)
- func ReversePathFilter(name string) (int, error)
- func SetTunnelRoutes(index int, prefixes []netip.Prefix) error
- func WatchNetwork(notify func(index uint64)) (stop func(), err error)
- type DNSGuard
- type DNSMethod
- type Interface
- type SystemDNS
Constants ¶
const ( RPFilterOff = 0 RPFilterStrict = 1 RPFilterLoose = 2 )
RPFilter* — значения rp_filter IPv4.
Variables ¶
This section is empty.
Functions ¶
func ClearTunnelRoutes ¶
ClearTunnelRoutes снимает маршруты prefixes с интерфейса index; маршрута уже нет — не ошибка. Снимает все, что может, и возвращает первую ошибку.
func ConfigureAdapter ¶
ConfigureAdapter задаёт своему адаптеру MTU и адреса и поднимает его. Аналог winsys.ConfigureAdapter: каждый адрес — префиксом во всю длину (/32, /128), маршруты ставит вызывающий. Повторный вызов — перенастройка: адреса глобальной области, которых нет в addrs, снимаются (на своём адаптере все они наши), автоматический fe80:: не трогается.
MTU меньше 1280 IPv6 на интерфейсе не оставляет: адреса IPv6 тогда не ставятся, о чём пишется в logf, — IPv6 через туннель всё равно не работает, а подъём из-за него не падает (как на Windows).
func FirewalldStrictIPv6 ¶
FirewalldStrictIPv6 — работает ли firewalld со строгим обратным фильтром IPv6 (IPv6_rpfilter в firewalld.conf: yes — умолчание, или strict). Его правило (fib saddr . mark . iif oif missing drop) отбрасывает ответы IPv6 сокетам ядра так же, как строгий rp_filter — IPv4 (проверено опытом в T3). Режимы loose и *-forward входящие ответы не трогают.
firewalld узнаётся по процессу (comm «firewalld»): правила у него в nftables, но служба может быть установлена и не запущена.
func LinkKind ¶
LinkKind — тип интерфейса с именем name, как его называет ip -d link («tuntap», «wireguard», «dummy»); ok = false — интерфейса нет.
func ReversePathFilter ¶
ReversePathFilter — действующий rp_filter IPv4 интерфейса name: ядро берёт большее из net.ipv4.conf.all и значения самого интерфейса.
func SetTunnelRoutes ¶
SetTunnelRoutes ставит маршруты prefixes через интерфейс index таблицы main: без шлюза, область link, как wg-quick. Повторный вызов безвреден (RouteReplace). Маршруты висят на интерфейсе и уходят вместе с ним. Аналог winsys.SetTunnelRoutes.
func WatchNetwork ¶
WatchNetwork подписывается через netlink на смену маршрутов, интерфейсов и адресов обоих семейств и зовёт notify с индексом интерфейса, которого коснулось изменение; 0 — интерфейс неизвестен (маршрут с несколькими шлюзами, обрыв подписки). Аналог winsys.WatchNetwork.
Сами события ничего не решают: сворачивает серию и решает, сменилась ли сеть, вызывающий (netguard.Guard.Watch).
notify зовётся из нескольких горутин, возможно одновременно. stop снимает подписку и ждёт уже начатых вызовов notify; после stop новых нет.
Types ¶
type DNSGuard ¶
type DNSGuard struct {
// contains filtered or unexported fields
}
DNSGuard — защита от утечки DNS на Linux (plan-e4.md, D1), аналог winsys.DNSGuard: правила nftables запрещают обращения на порт 53, UDP и TCP, ко всем адресам, кроме резолверов туннеля, loopback и сокетов tt.
Таблица inet tt создаётся с флагом owner: ядро держит её, пока открыт netlink-сокет, который её создал, и удаляет вместе с ним — в том числе когда tt падает. Уборка при запуске поэтому не нужна. Менять таблицу с owner может только этот сокет, так что всё идёт через одно долгоживущее соединение (nftables.AsLasting).
Сокеты ядра tt опознаются по метке SO_MARK (netguard.SocketMark): фильтр по uid пропустил бы под sudo все процессы root. Метка только опознаёт — маршрутизации по ней нет.
Цепочка forward — ради контейнеров и ВМ: их запросы к резолверу в локальной сети (маршрут конкретнее /1) иначе ушли бы мимо туннеля.
Таблица и цепочки создаются один раз (OpenDNSGuard), а правила ставятся и снимаются по ходу (Enable, Lockdown, Disable): отказ так узнаётся до подключения, а не посреди него. В тех же цепочках живёт и kill-switch (plan-e3.md, K2) — набор «закрыто», см. Lockdown.
func OpenDNSGuard ¶
OpenDNSGuard создаёт таблицу inet tt с пустыми цепочками; правил ещё нет. mark — метка сокетов tt, им DNS разрешён к любым адресам. Нужны CAP_NET_ADMIN и ядро 5.12 или новее (флаг owner).
func (*DNSGuard) BlockIPv6 ¶
BlockIPv6 задаёт, закрывать ли при следующем Enable весь IPv6 системы, кроме loopback и сокетов tt: туннель его не везёт (MTU < 1280, ядро снимает IPv6 с адаптера), и без запрета он шёл бы мимо туннеля.
func (*DNSGuard) Disable ¶
Disable снимает правила; таблица и цепочки остаются пустыми и ничего не запрещают — ни DNS, ни (после Lockdown) прочий трафик. Повторный вызов безвреден.
func (*DNSGuard) Enable ¶
Enable ставит правила: DNS — только к резолверам dns. Прежние правила заменяются одним пакетом nftables, так что повторный вызов безвреден, а на отказе ядро не применяет ничего. Тогда правила снимаются, чтобы не осталось и старых, — кроме набора «закрыто»: он строже защиты DNS, и открывать систему из-за отказа незачем.
func (*DNSGuard) Lockdown ¶
Lockdown ставит набор «закрыто» — kill-switch (plan-e3.md, K2), пока туннель не поднят: системе разрешены только сокеты tt, loopback, служебные протоколы (DHCP, ICMPv6 ND и RA) и локальная сеть, а DNS — никуда, кроме сокетов tt и loopback, даже к локальной сети: резолвер роутера отдал бы запросы провайдеру. Прочее отбрасывается; отправка UDP тогда кончается EPERM, TCP — таймаутом.
Правила заменяются одним пакетом, как в Enable: перехода через пустую таблицу нет. На отказе ядро не применяет ничего, и остаётся прежний набор.
type Interface ¶
type Interface struct {
Index uint32 // индекс для netguard (SO_BINDTOIFINDEX)
Name string // имя, как в ip link: «enp7s0», «wlan0»
}
Interface — выбранный физический интерфейс.
func DefaultInterface ¶
DefaultInterface выбирает интерфейс маршрута по умолчанию (0.0.0.0/0 или ::/0) таблицы main для семейства: с наименьшей метрикой маршрута, только интерфейсы в состоянии UP, без интерфейса exclude (индекс своего адаптера; 0 — исключать нечего). Маршруты туннеля — две половины /1, в выбор они не попадают, а свой адаптер исключается ещё и явно, поэтому выбор работает и при поднятом туннеле. Аналог winsys.DefaultInterface.
ok = false — маршрута по умолчанию для семейства нет (например, сеть без IPv6). Это не ошибка.
type SystemDNS ¶
type SystemDNS struct {
// contains filtered or unexported fields
}
SystemDNS — DNS туннеля в системе для адаптера с именем name. Не потокобезопасен: зовёт его одна горутина адаптера.
func DetectSystemDNS ¶
DetectSystemDNS узнаёт, кто ведёт /etc/resolv.conf, и способ, которым tt задаст DNS туннеля адаптеру name. Ничего не меняет. Ссылка на чужой генератор — ошибка: перезаписать файл значило бы сломать его хозяину resolv.conf до выхода tt.
func (*SystemDNS) Recover ¶
Recover убирает то, что оставил упавший прошлый запуск: запись resolvconf и перезаписанный resolv.conf. Звать до Set и только когда второго tt на машине быть не может (после блокировки адаптера) — иначе снялся бы DNS работающего. Возвращает, что убрано; "" — ничего.
func (*SystemDNS) Revert ¶
Revert снимает то, что поставил Set; без Set ничего не делает. Повторный вызов безвреден.