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; - пишет runtime Xray JSON с приватными правами
0600; - хранит профили в
~/.config/pm-netlink-client/profilesс правами0600; - хранит GUI state, включая URL подписки, в
~/.config/pm-netlink-client/gui-state.yamlс правами0600; - передает URL подписок в privileged GUI helper через stdin, а не через argv.
- загружает подписки и remote split-lists только по
http/httpsи отклоняет локальные, private и link-local адреса.
Маршрутизация по доменам зависит от метаданных, которые может увидеть 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 и VLESS plain URI.
- YAML-профили для VLESS Reality, VLESS TLS, VLESS plain и Shadowsocks.
- VLESS TCP/raw/WebSocket/gRPC transports при импорте URI и генерации Xray.
- Подписки с несколькими 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.
- Диагностика профиля через временный локальный Xray HTTP proxy.
- 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.5:
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 pmnlc autostart enable
sudo pmnlc start
Или используйте встроенный 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 test --profile my-profile --probe-url https://www.google.com/generate_204
sudo pmnlc autostart enable
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
encryption: none
flow: null
network: tcp
header_type: none
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
VLESS plain:
type: vless-plain
server: example.com
port: 80
uuid: PUT_UUID_HERE
encryption: none
security: none
network: tcp
header_type: none
flow: null
fingerprint: null
label: Example VLESS Plain
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=none, security=tls и security=reality, а также transports type=tcp, type=raw, type=ws и type=grpc. Для Reality flow сохраняется только если он явно есть в URI: профили без flow не получают xtls-rprx-vision автоматически. Другие URI scheme и неподдержанные network types отклоняются.
Подписки
GUI умеет импортировать VLESS-подписки:
- plain text со строками URI;
- base64/base64url payload;
- комментарии и пустые строки игнорируются;
- имена профилей берутся из URI fragment, hostname или fallback
profile-N; - дубли имен получают суффикс
-2,-3и далее; - неподдержанные строки попадают в warnings, а не ломают весь импорт.
URL подписки должен использовать http или https. При загрузке helper отклоняет localhost, loopback, private, link-local, multicast и другие неподходящие для remote-fetch адреса, чтобы passwordless GUI helper не был каналом доступа к локальным сервисам root.
Если сервер возвращает 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
URL удаленного split-list проходит ту же проверку, что и URL подписки: разрешены только remote http/https endpoints, локальные/private адреса отклоняются.
Правила:
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]
autostart enable [--dry-run]
autostart disable [--dry-run]
autostart status
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]
subscription set URL [--config PATH] [--name NAME]
subscription update [URL] [--config PATH] [--name NAME]
subscription status [--config PATH]
subscription clear [--config PATH]
generate [--config PATH] [--dry-run]
test [--config PATH] [--profile NAME] [--probe-url URL] [--timeout SECONDS]
diagnose [--config PATH] [--target HOST] [--count N] [--timeout SECONDS] [--connectivity-url URL]
pm-netlink-client-gui
Root нужен для команд, которые меняют /etc, /run, nftables, policy routing или systemd: install, uninstall, start, stop, restart, autostart enable, autostart disable, 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 pmnlc autostart enable
pmnlc autostart status
autostart enable включает systemd services при загрузке, но не запускает VPN немедленно. Для текущего сеанса выполните sudo pmnlc start.
Отключить автозапуск, не останавливая текущий VPN-сеанс:
sudo pmnlc autostart disable
start генерирует Xray/nft runtime files и запускает systemd services. stop останавливает services, удаляет table inet pmnlc и policy routing rules/routes для выбранной config.
Диагностика
Проверить конфиг приложения и профиля:
sudo pmnlc test
Проверить неактивный профиль или сам outbound выбранного профиля через временный локальный Xray HTTP proxy:
sudo pmnlc test --profile my-profile
sudo pmnlc test --profile my-profile --probe-url https://www.google.com/generate_204
--probe-url не меняет nftables, systemd services или активный профиль: команда запускает временный Xray process с HTTP inbound на 127.0.0.1 и проверяет URL через этот proxy.
Проверить TCP-доступность сервера активного профиля, ICMP ping, состояние VPN services, nftables/policy routing и внешний доступ:
pmnlc diagnose
Если ICMP ping не проходит, но profile server tcp и остальные обязательные проверки ok, сервер доступен: многие провайдеры блокируют ping.
Проверить сгенерированный 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.