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.