[GH-ISSUE #1996] Зависание в драйвере раздельного туннелирования при установке соединения (вызов WindowsSplitTunnel::excludeApps) #867

Open
opened 2026-10-13 20:30:09 +00:00 by bsakaguchi · 7 comments
Owner

Originally created by @u07 on GitHub (Nov 17, 2025).
Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/1996

In ENGLISH: click to show

Hang inside wintun during connection setup (WindowsSplitTunnel::excludeApps)

Seeing this on two different PCs, Win11 23H2 and 25H2. The service/daemon hangs on connect, randomly, about 1 time out of 10. And once it hangs, only a reboot helps.

  • Hit “connect”
  • Infinite loading spinner…
  • CPU load of AmneziaVPN-service.exe jumps to ~10%
  • The service becomes non-responsive, can’t kill it via Task Manager, the process stays stuck until reboot

It hangs inside a syscall, in the kernel (NtDeviceIoControlFile). Here:

Image

And we get into it from here:

Image

This is the exact spot in the code, line 292 — and the call never returns:

github.com/amnezia-vpn/amnezia-client@3d6339e2dd/client/platforms/windows/daemon/windowssplittunnel.cpp (L292)

bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
  auto state = getState();
  if (state != STATE_READY && state != STATE_RUNNING) {
    logger.warning() << "Driver is not in the right State to set Rules"
                     << state;
    return false;
  }

  logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
  auto config = generateAppConfiguration(appPaths);

  DWORD bytesReturned;
  auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0],    <------- ЗДЕСЬ
                            (DWORD)config.size(), nullptr, 0, &bytesReturned,
                            nullptr);
  if (!ok) {
    auto err = GetLastError();
    WindowsUtils::windowsLog("Set Config Failed:");
    logger.error() << "Failed to set Config err code " << err;
    return false;
  }
  logger.debug() << "New Configuration applied: " << stateString();
  return true;
}

This uploads the WG config into the driver — more specifically, it pushes the app whitelist for split-tunneling. That’s why AmneziaWG works fine with the same Warp configs: there’s no split-tunnel exclusion logic there.

So the driver hangs, and the service calling it hangs right after.

Now the question is what exactly the driver doesn’t like in the config buffer so it gets stuck sometimes.

I didn’t dive deep, but maybe the config buffer needs alignment, or the driver expects a null terminator?

Feels like a big chunk of “Windows won’t connect / spinner forever” issues might be the same root cause.


Наблюдаю такое независимо на двух компьютерах Win11 23h2 и 25h2. Служба-демон зависает при подключении рандомно 1 раз из 10. И если зависла, помогает только перезагрузка.

  • Жмём подключиться,
  • Наблюдаем бесконечное колесо загрузки...
  • Нагрузка на процессор от AmneziaVPN-service.exe возрастает до 10%
  • Служба становится неотзывчивой, прибить её через диспетчер нельзя, процесс остаётся висеть в памяти, снимается только перезагрузкой.

Зависает внутри syscall, в ядре (NtDeviceIOControlFile). Вот здесь:

Image

Приходим мы в него отсюда:

Image

Вот это место в исходниках, строка 292, из которой мы уже не возвращаемся:

github.com/amnezia-vpn/amnezia-client@3d6339e2dd/client/platforms/windows/daemon/windowssplittunnel.cpp (L292)

bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) {
  auto state = getState();
  if (state != STATE_READY && state != STATE_RUNNING) {
    logger.warning() << "Driver is not in the right State to set Rules"
                     << state;
    return false;
  }

  logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state;
  auto config = generateAppConfiguration(appPaths);

  DWORD bytesReturned;
  auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0],    <------- ЗДЕСЬ
                            (DWORD)config.size(), nullptr, 0, &bytesReturned,
                            nullptr);
  if (!ok) {
    auto err = GetLastError();
    WindowsUtils::windowsLog("Set Config Failed:");
    logger.error() << "Failed to set Config err code " << err;
    return false;
  }
  logger.debug() << "New Configuration applied: " << stateString();
  return true;
}

Это выгрузка конфигурации wg в драйвер, а точнее - установка белого списка приложений. Собственно, поэтому AmneziaWG с теми же warp конфигами работает нормально, потому что там не предусмотрен механизм исключений.

В общем, зависает драйвер, следом за ним - служба, которая к нему обратилась.

Осталось понять, что не нравится драйверу в переданном конфиге, от чего его колбасит. Причём не гарантированно, т.е. баг плавающий.

Я глубоко не лез, хз конечно, но может быть буфер config должен как-то выравниваться в памяти, или драйвер ожидает терминирующий ноль?

Originally created by @u07 on GitHub (Nov 17, 2025). Original GitHub issue: https://github.com/amnezia-vpn/amnezia-client/issues/1996 <details> <summary> In ENGLISH: click to show</summary> **Hang inside wintun during connection setup (WindowsSplitTunnel::excludeApps)** Seeing this on two different PCs, Win11 23H2 and 25H2. The service/daemon hangs on connect, randomly, about 1 time out of 10. And once it hangs, only a reboot helps. * Hit “connect” * Infinite loading spinner… * CPU load of `AmneziaVPN-service.exe` jumps to ~10% * The service becomes non-responsive, can’t kill it via Task Manager, the process stays stuck until reboot It hangs inside a syscall, in the kernel (`NtDeviceIoControlFile`). Here: <img width="2559" height="1599" alt="Image" src="https://github.com/user-attachments/assets/d3dc739f-dd92-44b1-aa72-63ece2d57ca5" /> And we get into it from here: <img width="1280" height="799" alt="Image" src="https://github.com/user-attachments/assets/7fd66762-5dcd-4492-9ea5-45984b2f3b30" /> This is the exact spot in the code, line 292 — and the call never returns: https://github.com/amnezia-vpn/amnezia-client/blob/3d6339e2dd257d9b5d68abe6a77c8007d96dace6/client/platforms/windows/daemon/windowssplittunnel.cpp#L292 ```cpp bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) { auto state = getState(); if (state != STATE_READY && state != STATE_RUNNING) { logger.warning() << "Driver is not in the right State to set Rules" << state; return false; } logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state; auto config = generateAppConfiguration(appPaths); DWORD bytesReturned; auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0], <------- ЗДЕСЬ (DWORD)config.size(), nullptr, 0, &bytesReturned, nullptr); if (!ok) { auto err = GetLastError(); WindowsUtils::windowsLog("Set Config Failed:"); logger.error() << "Failed to set Config err code " << err; return false; } logger.debug() << "New Configuration applied: " << stateString(); return true; } ``` This uploads the WG config into the driver — more specifically, it pushes the app whitelist for split-tunneling. That’s why AmneziaWG works fine with the same Warp configs: there’s no split-tunnel exclusion logic there. So the driver hangs, and the service calling it hangs right after. Now the question is what exactly the driver doesn’t like in the config buffer so it gets stuck sometimes. I didn’t dive deep, but maybe the `config` buffer needs alignment, or the driver expects a null terminator? Feels like a big chunk of “Windows won’t connect / spinner forever” issues might be the same root cause. --------------- </details> Наблюдаю такое независимо на двух компьютерах Win11 23h2 и 25h2. Служба-демон зависает при подключении рандомно 1 раз из 10. И если зависла, помогает только перезагрузка. - Жмём подключиться, - Наблюдаем бесконечное колесо загрузки... - Нагрузка на процессор от AmneziaVPN-service.exe возрастает до 10% - Служба становится неотзывчивой, прибить её через диспетчер нельзя, процесс остаётся висеть в памяти, снимается только перезагрузкой. Зависает внутри syscall, в ядре (NtDeviceIOControlFile). Вот здесь: <img width="2559" height="1599" alt="Image" src="https://github.com/user-attachments/assets/d3dc739f-dd92-44b1-aa72-63ece2d57ca5" /> Приходим мы в него отсюда: <img width="1280" height="799" alt="Image" src="https://github.com/user-attachments/assets/7fd66762-5dcd-4492-9ea5-45984b2f3b30" /> Вот это место в исходниках, строка 292, из которой мы уже не возвращаемся: https://github.com/amnezia-vpn/amnezia-client/blob/3d6339e2dd257d9b5d68abe6a77c8007d96dace6/client/platforms/windows/daemon/windowssplittunnel.cpp#L292 ```cpp bool WindowsSplitTunnel::excludeApps(const QStringList& appPaths) { auto state = getState(); if (state != STATE_READY && state != STATE_RUNNING) { logger.warning() << "Driver is not in the right State to set Rules" << state; return false; } logger.debug() << "Pushing new Ruleset for Split-Tunnel " << state; auto config = generateAppConfiguration(appPaths); DWORD bytesReturned; auto ok = DeviceIoControl(m_driver, IOCTL_SET_CONFIGURATION, &config[0], <------- ЗДЕСЬ (DWORD)config.size(), nullptr, 0, &bytesReturned, nullptr); if (!ok) { auto err = GetLastError(); WindowsUtils::windowsLog("Set Config Failed:"); logger.error() << "Failed to set Config err code " << err; return false; } logger.debug() << "New Configuration applied: " << stateString(); return true; } ``` Это выгрузка конфигурации wg в драйвер, а точнее - установка белого списка приложений. Собственно, поэтому AmneziaWG с теми же warp конфигами работает нормально, потому что там не предусмотрен механизм исключений. В общем, зависает драйвер, следом за ним - служба, которая к нему обратилась. Осталось понять, что не нравится драйверу в переданном конфиге, от чего его колбасит. Причём не гарантированно, т.е. баг плавающий. Я глубоко не лез, хз конечно, но может быть буфер config должен как-то выравниваться в памяти, или драйвер ожидает терминирующий ноль?
Author
Owner

@u07 commented on GitHub (Nov 17, 2025):

Думаю, значительный процент issues типа "Не подключается на винде / бесконечно крутит подключение" может быть вызван этим.

<!-- gh-comment-id:3542738068 --> @u07 commented on GitHub (Nov 17, 2025): Думаю, значительный процент issues типа "Не подключается на винде / бесконечно крутит подключение" может быть вызван этим.
Author
Owner

@u07 commented on GitHub (Nov 18, 2025):

Image Image
<!-- gh-comment-id:3545258342 --> @u07 commented on GitHub (Nov 18, 2025): <img width="1280" height="799" alt="Image" src="https://github.com/user-attachments/assets/c5dbdf7f-1a63-49fd-8c7a-f87de46efd5b" /> <img width="671" height="257" alt="Image" src="https://github.com/user-attachments/assets/3cba2622-3839-406a-935a-0391ddd88f9b" />
Author
Owner

@GSemekhin commented on GitHub (Dec 6, 2025):

Не удалось собрать проект локально, однако, судя по коду, нашел вероятную причину:

Шаги:

  1. Синхронный вызов в клиенте Amnezia:
    Файл: amnezia-client/client/platforms/windows/daemon/windowssplittunnel.cpp (строка 202)

    auto driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
                                   nullptr, OPEN_EXISTING, 0, nullptr);
    

    6-й аргумент dwFlagsAndAttributes равен 0. Флаг FILE_FLAG_OVERLAPPED отсутствует.
    Это делает хендл синхронным.

    Далее (строка 292):

    auto ok = DeviceIoControl(..., nullptr, ...); // lpOverlapped = nullptr
    

    Вызов DeviceIoControl блокирует поток приложения до завершения обработки в драйвере.

  2. Поведение драйвера (Mullvad Split Tunnel):
    Драйвер (код которого используется в Amnezia) при обработке IOCTL выполняет коммит транзакции WFP.
    В коде драйвера (src/ioctl.cpp):

    status = firewall::TransactionCommit(Context->Firewall, ForceAleReauthorization);
    

    При высокой нагрузке WFP при применении правил (TransactionCommit) может ожидать освобождения ресурсов.

  3. Причина зависания:

    • Приложение вызывает драйвер и перестает обрабатывать сетевые события.
    • Драйвер (WFP) ждет, пока сетевой стек освободит буферы.
    • Сетевой стек не может освободиться, так как вызов был блокирующим.
    • Бесконечное ожидание.

Сравнение с реализацией Mullvad:
В оригинальном клиенте Mullvad (откуда взят драйвер) работа ведется асинхронно.
Файл: talpid-core/src/split_tunnel/windows/driver.rs

let handle = OpenOptions::new()
    .read(true)
    .write(true)
    .custom_flags(FILE_FLAG_OVERLAPPED) // <--- Асинхронный флаг
    .open(DRIVER_SYMBOLIC_NAME)

И далее в DeviceIoControl передается структура OVERLAPPED.

Документация Microsoft:

A synchronous handle behaves such that I/O function calls using that handle are blocked until they complete... As stated previously, this synchronous versus asynchronous behavior is determined by specifying FILE_FLAG_OVERLAPPED...
MSDN Link

Воспроизведение:

  1. Перезагрузить ПК.
  2. Загрузить канал до роутера (тестил с UDP, 1460 bytes, 100 Mbit/s full duplex).
  3. Нажать "Подключиться" в AmneziaVPN.
  4. Клиент зависает. При отсутствии нагрузки подключение проходит успешно.

Полагаю, необходимо переписать взаимодействие с драйвером windowssplittunnel.cpp на использование OVERLAPPED I/O, чтобы вызов DeviceIoControl не блокировал основной поток приложения.

<!-- gh-comment-id:3619704731 --> @GSemekhin commented on GitHub (Dec 6, 2025): Не удалось собрать проект локально, однако, судя по коду, нашел вероятную причину: **Шаги:** 1. **Синхронный вызов в клиенте Amnezia:** Файл: `amnezia-client/client/platforms/windows/daemon/windowssplittunnel.cpp` (строка 202) ```cpp auto driverFile = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); ``` 6-й аргумент `dwFlagsAndAttributes` равен `0`. Флаг `FILE_FLAG_OVERLAPPED` отсутствует. Это делает хендл синхронным. Далее (строка 292): ```cpp auto ok = DeviceIoControl(..., nullptr, ...); // lpOverlapped = nullptr ``` Вызов `DeviceIoControl` блокирует поток приложения до завершения обработки в драйвере. 2. **Поведение драйвера (Mullvad Split Tunnel):** Драйвер (код которого используется в Amnezia) при обработке IOCTL выполняет коммит транзакции WFP. В коде драйвера (`src/ioctl.cpp`): ```cpp status = firewall::TransactionCommit(Context->Firewall, ForceAleReauthorization); ``` При высокой нагрузке WFP при применении правил (`TransactionCommit`) может ожидать освобождения ресурсов. 3. **Причина зависания:** * Приложение вызывает драйвер и перестает обрабатывать сетевые события. * Драйвер (WFP) ждет, пока сетевой стек освободит буферы. * Сетевой стек не может освободиться, так как вызов был блокирующим. * Бесконечное ожидание. **Сравнение с реализацией Mullvad:** В оригинальном клиенте Mullvad (откуда взят драйвер) работа ведется асинхронно. Файл: `talpid-core/src/split_tunnel/windows/driver.rs` ```rust let handle = OpenOptions::new() .read(true) .write(true) .custom_flags(FILE_FLAG_OVERLAPPED) // <--- Асинхронный флаг .open(DRIVER_SYMBOLIC_NAME) ``` И далее в `DeviceIoControl` передается структура `OVERLAPPED`. **Документация Microsoft:** > A synchronous handle behaves such that I/O function calls using that handle are blocked until they complete... As stated previously, this synchronous versus asynchronous behavior is determined by specifying FILE_FLAG_OVERLAPPED... > [MSDN Link](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew#synchronous-and-asynchronous-i-o-handles) **Воспроизведение:** 1. Перезагрузить ПК. 2. **Загрузить канал** до роутера (тестил с UDP, 1460 bytes, 100 Mbit/s full duplex). 3. Нажать "Подключиться" в AmneziaVPN. 4. Клиент зависает. При отсутствии нагрузки подключение проходит успешно. Полагаю, необходимо переписать взаимодействие с драйвером `windowssplittunnel.cpp` на использование `OVERLAPPED` I/O, чтобы вызов `DeviceIoControl` не блокировал основной поток приложения.
Author
Owner

@ShadowStalin commented on GitHub (Feb 14, 2026):

Наблюдаю данный баг уже более полугода, все 1 в 1 как у автора данного поста.

<!-- gh-comment-id:3901699775 --> @ShadowStalin commented on GitHub (Feb 14, 2026): Наблюдаю данный баг уже более полугода, все 1 в 1 как у автора данного поста.
Author
Owner

@Hyde1703 commented on GitHub (Feb 24, 2026):

Наблюдаю данный баг уже более полугода, все 1 в 1 как у автора данного поста.

Смог как либо решить эту проблему? Из обсуждения выше не очень понял что нужно сделать

<!-- gh-comment-id:3953204531 --> @Hyde1703 commented on GitHub (Feb 24, 2026): > Наблюдаю данный баг уже более полугода, все 1 в 1 как у автора данного поста. Смог как либо решить эту проблему? Из обсуждения выше не очень понял что нужно сделать
Author
Owner

@u07 commented on GitHub (Feb 24, 2026):

что нужно сделать

Минимизировать сетевую активность в момент подключения, закрыть всё, что можно закрыть.

А вообще, тыкать разработчиков палочкой..

<!-- gh-comment-id:3953237060 --> @u07 commented on GitHub (Feb 24, 2026): > что нужно сделать Минимизировать сетевую активность в момент подключения, закрыть всё, что можно закрыть. А вообще, тыкать разработчиков палочкой..
Author
Owner

@ShadowStalin commented on GitHub (Mar 17, 2026):

Наблюдаю данный баг уже более полугода, все 1 в 1 как у автора данного поста.

Смог как либо решить эту проблему? Из обсуждения выше не очень понял что нужно сделать

Для своих устройств решил следующим образом:
Windows: Переход на клиент WireSock (релизная версия 2.4.23.1). Есть поддержка AWG 1.0 + обфускация хэндшейка через SOCKS5. В бета версиях есть поддержка AWG 2.0 + своя имплементация заголовков I1-I5. Раздельное туннелирование тоже есть.
Linux: awg-quick / amneziawg-tools. Есть поддержка всех версий AWG.

<!-- gh-comment-id:4075797812 --> @ShadowStalin commented on GitHub (Mar 17, 2026): > > Наблюдаю данный баг уже более полугода, все 1 в 1 как у автора данного поста. > > Смог как либо решить эту проблему? Из обсуждения выше не очень понял что нужно сделать Для своих устройств решил следующим образом: **Windows:** Переход на клиент WireSock (релизная версия 2.4.23.1). Есть поддержка AWG 1.0 + обфускация хэндшейка через SOCKS5. В бета версиях есть поддержка AWG 2.0 + своя имплементация заголовков I1-I5. Раздельное туннелирование тоже есть. **Linux:** awg-quick / amneziawg-tools. Есть поддержка всех версий AWG.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amnezia-client#867
No description provided.