README.md

PM-NetLink Client

PM-NetLink Client - Linux-контроллер раздельного туннелирования для Xray-core. Он не реализует VPN-протокол, не поднимает tun-интерфейс и не подменяет сертификаты. Вместо этого проект собирает в один управляемый контур:

  • Xray transparent inbound dokodemo-door с TProxy;
  • nftables table inet pmnlc;
  • policy routing через ip rule и отдельную route table;
  • systemd units для routing/nftables и Xray daemon;
  • CLI и дополнительный Qt GUI.

Основная идея: в режиме whitelist только выбранные домены, доменные списки и будущие process-aware правила идут через proxy outbound Xray. Остальной трафик остается прямым. В режиме blacklist GUI показывает это как full tunnel: неподходящий под правила трафик идет через proxy, но локальные/private сети по-прежнему обходятся.

Статус и безопасность

MVP ориентирован на Arch Linux и AUR/yay, но большая часть кода подходит для обычных Linux-систем с systemd, nftables и iproute2.

PM-NetLink Client:

  • не читает содержимое HTTPS;
  • не делает MITM;
  • не устанавливает пользовательские CA-сертификаты;
  • не меняет глобальные DNS-настройки;
  • не вызывает flush ruleset;
  • создает только свою nftables table inet pmnlc;
  • хранит профили в ~/.config/pm-netlink-client/profiles с правами 0600;
  • хранит GUI state, включая URL подписки, в ~/.config/pm-netlink-client/gui-state.yaml с правами 0600;
  • передает URL подписок в privileged GUI helper через stdin, а не через argv.

Маршрутизация по доменам зависит от метаданных, которые может увидеть Xray sniffing: SNI, HTTP Host, видимые QUIC-метаданные, IP, порт и протокол. ECH, DoH, некоторые QUIC-сценарии и CDN могут снижать точность domain-aware routing.

Возможности

  • CLI-first управление через pmnlc (pm-netlink-client остается legacy alias).
  • Нативный Qt GUI через pm-netlink-client-gui.
  • Импорт VLESS Reality и VLESS TLS URI.
  • YAML-профили для VLESS Reality, VLESS TLS и Shadowsocks.
  • Подписки с несколькими VLESS-профилями, обычные и base64 payload.
  • Чтение subscription-userinfo quota header: upload, download, total, expire, unlimited/lifetime.
  • Раздельные пользовательские и удаленные split-rules.
  • Автообновление подписки в GUI.
  • Обновление удаленного списка доменов в GUI.
  • Переключение whitelist split tunneling и blacklist/full tunnel.
  • Светлая/темная тема GUI.
  • Просмотр systemd logs и GUI-событий.
  • Автоочистка GUI-событий.
  • Dry-run для системных операций CLI.
  • Redaction proxy URI и секретов в логах.

Лицензия

Copyright (C) 2026 PM-NetLink Client contributors.

PM-NetLink Client - свободное ПО: вы можете распространять и/или изменять его на условиях GNU General Public License, опубликованной Free Software Foundation, версии 3 этой лицензии или любой более поздней версии на ваш выбор.

PM-NetLink Client распространяется в надежде, что он будет полезен, но БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ; даже без подразумеваемых гарантий ТОВАРНОЙ ПРИГОДНОСТИ или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ. Подробности см. в GNU General Public License.

Копия GNU General Public License находится в файле LICENSE.

Зависимости

CLI на Arch Linux:

sudo pacman -S python polkit xray nftables iproute2 systemd

GUI дополнительно:

sudo pacman -S pyside6

Python-зависимости CLI:

  • Python 3.11+
  • pydantic
  • pyyaml
  • rich
  • typer

Dev/test зависимости:

  • build
  • pytest

GUI-зависимость PySide6 вынесена в optional extra gui, поэтому CLI-only установка не подтягивает Qt.

Установка через yay/AUR

После публикации релиза v0.1.3:

yay -S pm-netlink-client

GUI-дополнение:

yay -S pm-netlink-client-gui

pm-netlink-client-gui зависит от точной версии pm-netlink-client, поэтому CLI будет установлен автоматически.

AUR-пакет устанавливает:

  • /usr/bin/pmnlc;
  • /usr/bin/pm-netlink-client как legacy alias;
  • /usr/bin/pm-netlink-client-gui только в GUI-пакете;
  • /usr/bin/pm-netlink-client-gui-helper для privileged GUI RPC;
  • /usr/lib/systemd/system/pm-netlink-client-routing.service;
  • /usr/lib/systemd/system/pm-netlink-clientd.service;
  • polkit action/rule для GUI-helper;
  • man pages для CLI и GUI;
  • документацию и examples в /usr/share/doc.

Службы не включаются автоматически. После установки подготовьте пользовательский config-dir и systemd units с явным путем к нему, затем импортируйте профиль, выберите его и включите сервисы:

sudo pmnlc install
sudo pmnlc import-uri my-profile 'vless://...'
sudo pmnlc use-profile my-profile
sudo pmnlc add-domain claude.ai
sudo systemctl enable --now pm-netlink-client-routing.service pm-netlink-clientd.service

Или используйте встроенный lifecycle:

sudo pmnlc start
sudo pmnlc status

Для одного root-сеанса без повторения sudo можно открыть интерактивную оболочку:

sudo pmnlc shell

Установка из checkout

CLI:

pipx install .

или:

uv tool install .

CLI + GUI:

pipx install '.[gui]'

или:

uv tool install '.[gui]'

Затем подготовьте системные файлы:

sudo pmnlc install

Команда install:

  • проверяет наличие xray, nft, ip, systemctl;
  • создает ~/.config/pm-netlink-client/config.yaml, если его еще нет;
  • создает ~/.config/pm-netlink-client/profiles;
  • пишет начальный nftables config в ~/.config/pm-netlink-client/pmnlc.nft;
  • устанавливает systemd units в /etc/systemd/system;
  • прописывает в units абсолютный путь к пользовательскому config.yaml;
  • вызывает systemctl daemon-reload;
  • не включает службы без --enable.

Если CLI установлен в пользовательский путь, передайте абсолютный путь:

sudo pmnlc install --cli-path "$(command -v pmnlc)"

Для проверки без изменений:

sudo pmnlc install --dry-run

Быстрый старт CLI

sudo pmnlc install
sudo pmnlc import-uri my-profile 'vless://00000000-0000-4000-8000-000000000000@example.com:443?encryption=none&security=tls&type=tcp#Demo'
sudo pmnlc use-profile my-profile
sudo pmnlc add-domain claude.ai
sudo pmnlc generate
sudo pmnlc test
sudo pmnlc start
sudo pmnlc status

Интерактивная альтернатива после установки и импорта профиля:

sudo pmnlc shell
PM-NetLink Client shell
For help, type "help".
Type "apropos word" to search for commands related to "word".
(pmnlc) status
(pmnlc) add-domain claude.ai
(pmnlc) restart
(pmnlc) quit

add-domain в CLI добавляет точное доменное правило в rules.proxy_domains. Для правила “домен и поддомены” используйте GUI, удаленный split-list или добавьте домен в rules.proxy_domain_suffixes вручную.

Первый запуск GUI

Запуск:

pm-netlink-client-gui

GUI работает от обычного desktop user. Для privileged-операций с systemd, nftables и с файлами ~/.config/pm-netlink-client, если helper запущен через pkexec, он пишет в config-dir исходного desktop-пользователя, а не в /root:

pm-netlink-client-gui-helper

В packaged GUI установлен polkit action/rule для org.pm-netlink-client.gui-helper: активный локальный desktop-пользователь может запускать только этот helper через pkexec без ввода пароля. Inactive, remote и другие вызовы остаются на обычном polkit prompt. В рамках одной сессии окна GUI держит privileged helper открытым и отправляет newline-delimited JSON через stdin.

При первом запуске GUI попросит URL подписки. После импорта можно:

  • выбрать активный профиль;
  • включить/выключить VPN;
  • переключить split tunneling/full tunnel;
  • обновить подписку;
  • настроить автообновление;
  • обновить удаленный список доменов;
  • добавить свои сайты отдельно от remote list;
  • посмотреть и очистить GUI/systemd logs;
  • выбрать светлую или темную тему.

Архитектура

Высокоуровневый поток:

CLI/GUI
  -> ~/.config/pm-netlink-client/config.yaml
  -> /run/pm-netlink-client/xray.json
  -> /run/pm-netlink-client/pmnlc.nft
  -> systemd starts routing/nftables and Xray daemon
  -> nftables marks tcp/udp traffic
  -> policy routing sends marked packets to loopback
  -> Xray TProxy inbound receives original destination
  -> Xray routing chooses proxy or direct outbound

Systemd units:

  • pm-netlink-client-routing.service - oneshot service, применяет policy routing и nftables, снимает их в ExecStop.
  • pm-netlink-clientd.service - long-running daemon, генерирует Xray config и запускает xray run -config.

Runtime-файлы:

/run/pm-netlink-client/xray.json
/run/pm-netlink-client/pmnlc.nft

Конфигурация и состояние:

~/.config/pm-netlink-client/config.yaml
~/.config/pm-netlink-client/gui-state.yaml
~/.config/pm-netlink-client/profiles/*.yaml

Конфигурация

Пример полной конфигурации:

mode: whitelist
profiles_dir: ~/.config/pm-netlink-client/profiles
xray:
  binary: /usr/bin/xray
  active_profile: null
  generated_config: /run/pm-netlink-client/xray.json
  tproxy_port: 12345
  loglevel: warning
  bypass_mark: 255
  exempt_uid: null
routing:
  fwmark: 1
  table: 100
  enable_ipv6: true
  bypass_private_networks: true
  bypass_loopback: true
dns:
  enabled: false
  listen: 127.0.0.1
  port: 5353
rules:
  proxy_domains: []
  proxy_domain_suffixes: []
  remote_proxy_domains: []
  remote_proxy_domain_suffixes: []
  proxy_apps: []
  direct_domains: []
  direct_ips:
    - 127.0.0.0/8
    - 10.0.0.0/8
    - 172.16.0.0/12
    - 192.168.0.0/16
    - ::1/128
    - fc00::/7
    - fe80::/10

Важные поля:

  • mode: whitelist - fallback DIRECT, proxy только по правилам.
  • mode: blacklist - fallback PROXY, локальные/private сети остаются DIRECT.
  • profiles_dir - абсолютный путь или путь относительно файла config.
  • xray.generated_config - куда писать generated Xray JSON.
  • xray.tproxy_port - порт transparent inbound.
  • xray.bypass_mark - SO_MARK для outbound sockets Xray, чтобы не поймать Xray повторно в nftables.
  • xray.exempt_uid - необязательный UID, чей traffic не надо перенаправлять.
  • routing.fwmark - mark для policy routing.
  • routing.table - route table для local route на loopback.
  • rules.direct_ips - сети, которые обходят proxy на уровне Xray и nftables.
  • rules.proxy_apps - экспериментальное поле MVP; сейчас сохраняется, но надежное process-aware применение еще не реализовано.
  • dns.enabled - зарезервировано для будущей DNS-aware маршрутизации.

Профили

Профили лежат в profiles_dir как YAML-файлы. Новые сохранения используют служебное имя файла вида profile-<hash>.yaml; отображаемое имя хранится внутри YAML, поэтому пробелы, скобки и / в имени не теряются. Старый плоский формат <name>.yaml продолжает читаться для совместимости.

version: 1
name: Demo/Profile (RU)
profile:
  type: vless-tls
  server: example.com
  uuid: 00000000-0000-4000-8000-000000000000

VLESS Reality:

type: vless-reality
server: example.com
port: 443
uuid: PUT_UUID_HERE
flow: xtls-rprx-vision
sni: www.microsoft.com
public_key: PUT_PUBLIC_KEY_HERE
short_id: PUT_SHORT_ID_HERE
fingerprint: chrome
spider_x: /
label: Example VLESS Reality

VLESS TLS:

type: vless-tls
server: example.com
port: 443
uuid: PUT_UUID_HERE
encryption: none
security: tls
network: tcp
header_type: none
sni: example.com
flow: null
fingerprint: chrome
label: Example VLESS TLS

Shadowsocks:

type: shadowsocks
server: example.com
port: 8388
method: 2022-blake3-aes-128-gcm
password: PUT_PASSWORD_HERE
label: Example Shadowsocks

Импорт URI:

sudo pmnlc import-uri my-profile 'vless://...'
sudo pmnlc use-profile my-profile

Поддерживаются VLESS URI с security=tls и security=reality. Другие URI scheme и VLESS network types в MVP отклоняются.

Подписки

GUI умеет импортировать VLESS-подписки:

  • plain text со строками URI;
  • base64/base64url payload;
  • комментарии и пустые строки игнорируются;
  • имена профилей берутся из URI fragment, hostname или fallback profile-N;
  • дубли имен получают суффикс -2, -3 и далее;
  • неподдержанные строки попадают в warnings, а не ломают весь импорт.

Если сервер возвращает header subscription-userinfo, GUI сохраняет quota metadata:

upload=1073741824; download=2147483648; total=10737418240; expire=1893456000

total=0, total=-1, total=unlimited и похожие значения считаются безлимитом. expire=0, expire=-1, expire=lifetime и похожие значения считаются бессрочной подпиской.

Split Rules

Удаленный split-list - обычный text file:

# комментарии и пустые строки игнорируются
example.com
.telegram.org
domain:youtube.com
full:login.example.com
https://chat.openai.com/path

Правила:

  • example.com, .example.com и domain:example.com добавляются как suffix domain и матчят домен с поддоменами;
  • full:login.example.com добавляется как exact domain;
  • URL нормализуется до hostname;
  • домены приводятся к lower-case;
  • remote rules сохраняются в remote_proxy_domains и remote_proxy_domain_suffixes;
  • пользовательские сайты GUI сохраняются отдельно в proxy_domains и proxy_domain_suffixes, поэтому обновление remote list их не стирает.

Команды CLI

Глобальные опции:

pmnlc --version
pmnlc --verbose ...
pmnlc --debug ...
pmnlc --log-level info ...

Лог-уровень также можно задать через:

PMNLC_LOG_LEVEL=debug pmnlc status

Команды:

install [--config PATH] [--enable] [--force] [--cli-path PATH] [--dry-run]
uninstall [--config PATH] [--purge] [--dry-run]
start [--config PATH] [--dry-run]
stop [--config PATH] [--dry-run]
restart [--config PATH] [--dry-run]
status [--config PATH]
shell [--config PATH]
add-domain DOMAIN [--config PATH]
remove-domain DOMAIN [--config PATH]
list-domains [--config PATH]
add-app PROCESS_NAME [--config PATH]
remove-app PROCESS_NAME [--config PATH]
list-apps [--config PATH]
add-profile NAME PATH [--config PATH] [--overwrite]
use-profile NAME [--config PATH]
list-profiles [--config PATH]
import-uri NAME URI [--config PATH] [--overwrite]
generate [--config PATH] [--dry-run]
test [--config PATH]
pm-netlink-client-gui

Root нужен для команд, которые меняют /etc, /run, nftables, policy routing или systemd: install, uninstall, start, stop, restart, generate если target path находится в /run, импорт/изменение профилей в /etc, а также действия GUI через privileged helper.

sudo pmnlc shell [--config PATH] открывает постоянную интерактивную оболочку с prompt (pmnlc). Внутри доступны публичные команды CLI, help, help COMMAND, apropos WORD, exit и quit; hidden/internal команды остаются недоступны.

Hidden/internal команды используются systemd и GUI:

internal apply-routing
internal remove-routing
internal apply-system
internal remove-system
internal gui-rpc
daemon
print-xray

Обычно их не нужно запускать вручную.

Lifecycle служб

Запуск:

sudo pmnlc start

Или один раз войти в интерактивный root-сеанс:

sudo pmnlc shell

Остановка:

sudo pmnlc stop

Включить автозапуск:

sudo systemctl enable --now pm-netlink-client-routing.service pm-netlink-clientd.service

Отключить:

sudo systemctl disable --now pm-netlink-clientd.service pm-netlink-client-routing.service

start генерирует Xray/nft runtime files и запускает systemd services. stop останавливает services, удаляет table inet pmnlc и policy routing rules/routes для выбранной config.

Диагностика

Проверить конфиг приложения и профиля:

sudo pmnlc test

Проверить сгенерированный Xray config:

sudo pmnlc generate
sudo xray run -test -config /run/pm-netlink-client/xray.json

Проверить nftables config:

sudo nft -c -f /run/pm-netlink-client/pmnlc.nft
sudo nft list table inet pmnlc

Проверить policy routing:

ip rule show
ip route show table 100
ip -6 rule show
ip -6 route show table 100

Логи:

journalctl -u pm-netlink-clientd -e
journalctl -u pm-netlink-client-routing -e

Если unit, установленный из checkout, пишет env: 'pmnlc': No such file or directory, переустановите unit files с абсолютным CLI path:

sudo pmnlc install --force --cli-path "$(command -v pmnlc)"
sudo systemctl daemon-reload
sudo systemctl restart pm-netlink-client-routing.service pm-netlink-clientd.service

Экстренный откат:

sudo pmnlc stop
sudo nft delete table inet pmnlc
sudo ip rule del fwmark 1 table 100
sudo ip route flush table 100
sudo ip -6 rule del fwmark 1 table 100
sudo ip -6 route flush table 100
sudo systemctl stop pm-netlink-clientd

Разработка

Установить dev-зависимости:

uv sync --extra dev

Тесты:

PYTHONPATH=. uv run pytest

pyproject.toml также содержит pythonpath = ["."], чтобы pytest не импортировал случайно установленный системный пакет вместо checkout.

Компиляционная проверка:

python -m compileall -q pm_netlink_client tests

Wheel:

python -m build --wheel --no-isolation

GUI smoke test пропускается автоматически, если PySide6 не установлен.

Локальная проверка PKGBUILD

Для проверки packaging без git tag используйте локальный PKGBUILD, который собирает текущий checkout вместе с незакоммиченными изменениями:

makepkg -p packaging/aur/PKGBUILD.local -Ccf

Если запускаете makepkg из packaging/aur, передайте путь к checkout явно:

cd packaging/aur
PMNLC_LOCAL_SRC=/home/dragonnp/programming/pm-netlink-client makepkg -p PKGBUILD.local -Ccf

Чтобы установить собранные пакеты локально:

sudo pacman -U pm-netlink-client-*.pkg.tar.zst pm-netlink-client-gui-*.pkg.tar.zst

Ограничения MVP

  • QUIC может скрывать полезные metadata.
  • ECH может скрывать TLS SNI.
  • DoH может обходить domain-aware routing.
  • CDN-сервисы могут делить IP-адреса с несвязанными доменами.
  • CLI add-app пока только сохраняет process names; надежное enforcement планируется через cgroup или nftables integration.
  • DNS-aware routing зарезервирован для будущих версий.
  • GUI ориентирован на desktop-сессии с polkit/pkexec.
Описание
Релизы
2026-05-22
последний
Конвейеры
0 успешных
0 с ошибкой
Разработчики