WireGuard for Windows 达到 v1.0
WireGuard for Windows Reaches v1.0

原始链接: https://lists.zx2c4.com/pipermail/wireguard/2026-April/009580.html

## WireGuard for Windows v1.0 发布 WireGuard v1.0 for Windows(包括 WireGuardNT – 驱动程序组件 – 和 Windows 客户端)现已发布!此版本标志着在广泛的错误修复和关键开发障碍完成后的一个重要里程碑。下载地址为 [https://download.wireguard.com/windows-client/wireguard-installer.exe](https://download.wireguard.com/windows-client/wireguard-installer.exe) 和 [https://www.wireguard.com/install/](https://www.wireguard.com/install/)。 此版本的主要改进包括使用 `NdisWdfGetAdapterContextFromAdapterHandle()` 访问驱动程序状态的更可靠方法,以及正确处理 MTU 更改通知。 之前,MTU 更改需要轮询解决方法;新版本通过 NSI 驱动程序拦截更新,从而可以即时适应网络更改。 此版本还包括 Windows 客户端的 42 个错误修复,以及针对旧版 Windows 10 版本关于 DNS 设置的优化。该项目依赖捐赠以继续开发 – 捐款可以在 [https://www.wireguard.com/donations/](https://www.wireguard.com/donations/) 进行。

## WireGuard Windows 版达到 v1.0 – 一项重大成就 WireGuard 的 Windows 版本已发布稳定版 1.0,在大量开发工作后,这是一个重要的里程碑。这并非简单的移植;团队解决了 Windows 内核深层的问题。 主要改进包括针对 Microsoft API 关于 MTU 更改的错误的解决方法——需要一个筛选驱动程序才能直接访问网络接口信息——以及解决 NetAdapterCx 助手潜在可靠性问题的修复。这些解决方案展示了对 Windows 操作系统的深刻理解。 用户报告称 WireGuard 在 Windows 和 Linux 上都非常稳定,但 macOS 实现面临挑战,尤其是在使用 MDM 分发配置和用户访问方面。虽然该项目之前似乎停滞不前,但其持续维护令人欣慰。该版本凸显了为 Windows 开发的复杂性以及克服不完整 API 经常需要的“临时补丁”。
相关文章

原文
[ANNOUNCE] WireGuard for Windows and WireGuardNT, Version 1.0 Jason A. Donenfeld Jason at zx2c4.com
Sat Apr 18 16:23:52 UTC 2026
Hey again,

I’m happy to announce the v1.0 release of WireGuardNT and WireGuard
for Windows. The final “1.0 blockers” have been completed at last, and
I’m quite happy to have reached this milestone. It should now be
available from the built-in updater. And you can download it fresh
from:

- https://download.wireguard.com/windows-client/wireguard-installer.exe
- https://www.wireguard.com/install/

And to learn more about each of these two Windows projects:
- https://git.zx2c4.com/wireguard-windows/about/
- https://git.zx2c4.com/wireguard-nt/about/

Before I say more, I wanted to note that the WireGuard Project runs on
support from large companies and individuals alike. You can help out
at: https://www.wireguard.com/donations/ . If your company uses
WireGuard, consider talking to your employer about becoming a large
donor and appearing on that page. If you use a VPN from a VPN
provider, consider writing to them to suggest they donate to the
project. It does make a difference and is the reason the project is
able to live on.

The 1.0 release of WireGuardNT is a pile of bug fixes, after having
done a big read through of the source code and countless hours of new
testing. But it also has two big improvements, which have long been
considered release blockers for me.

Firstly, 1.0 now makes use of
NdisWdfGetAdapterContextFromAdapterHandle(). WireGuard’s IOCTL works
by piggybacking on the NDIS device node, so that it inherits NDIS’
setup and permissions. Each IOCTL is thus passed through the device’s
“functional device object”. There’s no documented function to go from
a pointer in the functional device object to the WireGuard-specific
state allocated. The functional device object’s DeviceExtension field
points to the NDIS_MINIPORT_BLOCK structure, which itself has a
pointer to the WireGuard-specific state. But that latter pointer is at
a potentially unstable offset, as it’s not within the documented part
of NDIS_MINIPORT_BLOCK. So, previously, I was using the “Reserved”
member of the functional device object to stuff a pointer in, but who
knows when that was to be used by something, a ticking time bomb.
Fortunately, every Windows 10 version since the first one has the
NdisWdfGetAdapterContextFromAdapterHandle() function, originally added
for NetAdapterCx, which means it’s not going away any time soon and
its behavior won’t change. This function simply goes to the right
offset in NDIS_MINIPORT_BLOCK where the driver-specific state is
stored. Put together, we get this handy function:

static WG_DEVICE *
WgDeviceFromFdo(_In_ DEVICE_OBJECT *DeviceObject)
{
    if (DeviceObject->DeviceType != FILE_DEVICE_PHYSICAL_NETCARD ||
!DeviceObject->DeviceExtension)
        return NULL;
    return NdisWdfGetAdapterContextFromAdapterHandle(DeviceObject->DeviceExtension);
}

This seems to work well and will hopefully ensure reliability into the future.

The second big 1.0 blocker that’s been solved is proper MTU change
notifications. As you may or may not know, WireGuard pads packets to
the nearest 16 bytes, but only up to the MTU of the interface, in
order to protect against traffic analysis attacks. This means the
WireGuard driver needs to know its own MTU. On Linux, we have full
access to this information, as its considered a property of the
network interface itself, so we can extract it trivially with
`skb->dev->mtu`, and do various calculations. But on Windows, the MTU
is a combined property of the network adapter’s minimum and maximum,
the tcp/ip interface’s selected MTU, which splits into v4 and v6
cases, and the same split cases for the tcp/ip interface’s
subinterfaces. This is sort of complicated, but I guess it fit a
device model that at one point made sense. The driver is responsible
for controlling the adapter’s minimum and maximum MTU. PowerShell’s
Set-NetIpInterface will change the interface-level MTU (via
SetIpInterfaceEntry()), while netsh.exe will change the
subinterface-level MTU; both of these wind up affecting the other in
subtle ways, and the net result is the same.

Typically, the normal way of getting notifications about these
changes, from userspace or from kernel space, is with
NotifyIpInterfaceChange(), which calls a callback function with
MibParameterNotification when something has changed. But, the callback
never fires for MTU changes! That’s the only one missing. The struct
the callback receives has a field for the MTU, but still, it’s never
fired. Somebody on the relevant team at Microsoft told me in 2021,
“this is a plain oversight and should be fixed,” and somebody else
mentioned backporting the fix to the 2019 release. But for whatever
reason, this never happened, and now it’s 2026. In the interim period,
I had a really horrific, but still stable, workaround: I started a
thread, and every 3 seconds I called GetIpInterfaceEntry() on the LUID
of every running WireGuard adapter. You heard that right… I just
polled with a sleep. Gross dot net. But it was the only documented way
of doing this! At the same time, I wrote a little program I could run
on each new release of Windows to see at which point they fixed the
bug, so that I could adjust the version check to avoid the poll loop
on old versions.

Unfortunately, the bug never got fixed. But I didn’t quite feel
comfortable shipping a 1.0 with such a distasteful workaround. So I
get to work… All userspace updates to the MTU go through a file called
\Device\Nsi. The NSI driver is then responsible for dispatching this
out to the various interfaces, and also keeping current with the
various changes in the various interfaces. After attaching to
\Device\Nsi using the standard NT filter-style pattern with
IoAttachDeviceToDeviceStack(), I then intercept the
IOCTL_NSI_SET_ALL_PARAMETERS message that I reverse engineered,
looking at the NSI_SET_ALL_PARAMETERS struct, matching on object types
NlInterfaceObject and NlSubInterfaceObject, and reading out the NlMTU
parameter from NSI_IP_INTERFACE_RW and NSI_IP_SUBINTERFACE_RW. The
parts of these structures we care about seem extremely stable. It
appears to work well, and now the WireGuard driver can adapt to new
MTU changes instantly, rather than within 3 seconds. And there’s no
ugly polling loop. You can peruse this code in driver/nsi.c and
driver/undocumented.h if you’re curious.

That’s a lot of work – it’s a whole separate .c file in the repo – for
just getting access to one value. But that’s how things go, and it’s
information that simply must be had in order to implement WireGuard
properly.

Finally, there are a bunch of other little changes and fixes and now
we compile in C23 mode, so we have access to the typeof() keyword. We
also in theory could move to using alignas(n) instead of
__declspec(align(n)), but C standard alignas() doesn’t work on types,
only members of structs and on variables, which makes it sort of
uglier to use. If you want a struct to always be aligned, then you put
the alignas(n) on the first member. I find this awkward, so we’re
sticking with __declspec(align(n)), which also seems pretty close to
gcc’s __attribute__((aligned(n))) (which is how Linux defines its
__aligned(n) macro).

On the WireGuard for Windows front – WireGuardNT, just discussed, is
the bundled driver component of that – there are 42 bug and
correctness fixes of various varieties. And then there’s one nice
improvement for older versions of Windows 10. Windows 10 1809 added
support for SetInterfaceDnsSettings(), for setting the system DNS
server programatically. Before that, the only documented way was to
shell out to netsh.exe, which is what we did. It was pretty ugly, and
the way of doing that involved some really gnarly parsing.
Fortunately, newer Windows doesn’t need to do this. But it occurred to
me – since these older versions of Windows are essentially complete, I
can just reverse engineer what netsh.exe is doing under the hood, and
then do that myself, and not worry about that ever changing, since
that’s only a fallback path used for these old Windows versions. It
turns out to be pretty easy – set two variables in a normal part of
the registry and send ControlService(SERVICE_CONTROL_PARAMCHANGE) to
the Dnscache service. Easy peasy.

Anyway, please let me know how it goes and if you encounter any issues.

Jason


More information about the WireGuard mailing list
联系我们 contact @ memedata.com