Page MenuHomeFreeBSD

Support for DPI-bypassing extension on top of WireGuard protocol
Needs ReviewPublic

Authored by vova_fbsd.ru on Jul 10 2025, 10:33 PM.
Tags
None
Referenced Files
Unknown Object (File)
Tue, Jul 29, 11:12 PM
Unknown Object (File)
Tue, Jul 29, 12:27 AM
Unknown Object (File)
Mon, Jul 28, 8:57 PM
Unknown Object (File)
Mon, Jul 28, 8:43 PM
Unknown Object (File)
Mon, Jul 28, 8:42 PM
Unknown Object (File)
Mon, Jul 28, 8:13 PM
Unknown Object (File)
Mon, Jul 28, 2:42 PM
Unknown Object (File)
Mon, Jul 28, 10:03 AM
Subscribers

Details

Summary

This patch upgrades if_wg to support DPI-bypassing techniques, while remaining compatible with the standard WireGuard protocol.

Features:

  1. Optional junk packets before handshake:
    • jc - number of junk packets to send before handshake (range: 1–128, recommended: 3–10)
    • jmin - minimum size of junk packets, jmin: < jmax (recomended ~ 50)
    • jmax - maximum size of junk packets, jmax: ≤ 1280 (recomended ~ 1000)

      These packets are ignored by both standard WireGuard and the Amnezia-WG extension.
  1. Optional junk preamble in handshake packets:
    • s1 - size of junk to prepend to the handshake initiation packet
    • s2 - size of junk to prepend to the handshake response packet

      both values must match on both ends; recommended: 15–150, range: 0–1280; s1 ≠ s2
  1. Optional custom packet type identifiers:
    • h1, h2, h3, h4 – unique 32-bit values (range: 0x5–0xFFFFFFFF) used as custom markers for initiation, response, cookie, and data packets, respectively.

      All values must be distinct (h1 ≠ h2 ≠ h3 ≠ h4) and identical on both ends.

When no parameters are specified, behavior remains identical to standard if_wg.

For more details, refer to the Amnezia-WG documentation:
https://docs.amnezia.org/documentation/amnezia-wg/

Test Plan
Using a standard WireGuard configuration, apply:

# wg set wg0 jc 7 jmin 150 jmax 1000 s1 117 s2 321 h1 2008066467 h2 2351746464 h3 3053333659 h4 1789444460

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Passed
Unit
No Test Coverage
Build Status
Buildable 65326
Build 62209: arc lint + arc unit

Event Timeline

vova_fbsd.ru edited the summary of this revision. (Show Details)
vova_fbsd.ru edited the test plan for this revision. (Show Details)
vova_fbsd.ru edited the test plan for this revision. (Show Details)
vova_fbsd.ru edited the test plan for this revision. (Show Details)
  • better compatibility with original if_wg code (accomodate recent changes)

I'm not too keen on bringing in non-standard extensions that haven't really been vetted usptream, let's loop them in.

"AdvancedSecurity" -- Windows 2000 branding comes to WireGuard!

Generally the WireGuard project has declined to add this or that willynilly obfuscation mechanism. The one you're proposing here might work for a bit, but it's not like it's the end all be all of obfuscation, and will probably become rapidly obsolete as soon as it's deployed and profiled. That's why, generally, obfuscation works better in lighter layers above WireGuard, where you can tweak and change things rapidly as you need, rather than a slow moving kernel component. WireGuard is the building block layer for having a good secure tunnel. Then you can do anything you want to those packets at some other layer.

So hard nack from me on this.

probably become rapidly obsolete as soon as it’s deployed and profiled

Interestingly, how exactly would it be profiled if the tunnel involves random packets and a per-link, custom-sized preamble filled with junk? The only feasible way I can imagine is by learning how an encrypted IPv4 packet header typically looks and then trying to detect it — assuming headers remain somehow distinguishable. But with sufficiently randomized traffic and junk padding, that doesn’t sound trivial or very effective.

WireGuard is the building block layer for having a good secure tunnel

Sure, but in some countries, WireGuard is becoming practically unusable because of how easily it can be identified. And WireGuard now feels like a solid, unchangeable component — there’s no real way to extend or adapt it at the same level of efficiency.

Running this kind of obfuscation from a userland process defeats the whole purpose of having an efficient in-kernel VPN.

That said, I agree it’s more logical to apply DPI-bypass logic on top of the tunnel, but the only efficient way I can think of to do that on top of WireGuard would be by introducing netgraph hooks to process in/out packets — since WireGuard also handles peer socket management internally.

What do you think about adding support for netgraph hooks?

One more thing — initially I was considering implementing this as a new awg device (not as a replacement for if_wg). But now I realize the current userland-to-kernel control is done via ioctl, and it’s not really bound to the interface — which makes it impossible to run both if_wg and if_awg side by side in a backward-compatible way.

So in that case, awg would need new ioctl numbers or a new control mechanism altogether.

Do you have any insights or suggestions on how to approach this in a cleaner way?

One more thing — initially I was considering implementing this as a new awg device (not as a replacement for if_wg). But now I realize the current userland-to-kernel control is done via ioctl, and it’s not really bound to the interface — which makes it impossible to run both if_wg and if_awg side by side in a backward-compatible way.

So in that case, awg would need new ioctl numbers or a new control mechanism altogether.

Do you have any insights or suggestions on how to approach this in a cleaner way?

Can you elaborate on this a little bit? You don't seem to have really changed the ioctl interface in a way that isn't compatible with if_wg (we would just ignore the new nvlist elements you've added) and requests get routed to the correct ioctl handler based on the interface named in the request passed to ioctl(2).

Can you elaborate on this a little bit? You don't seem to have really changed the ioctl interface in a way that isn't compatible with if_wg (we would just ignore the new nvlist elements you've added) and requests get routed to the correct ioctl handler based on the interface named in the request passed to ioctl(2).

Yes, it will work, but, now it is a replacement for if_wg,
but, if I would rename the driver (say if_wg -> if_awg), then, it would be quite natural that both will work in parallel,
but, to achive it, I will need to rename the driver, and also deal with these ioctl constatns

https://github.com/amnezia-vpn/amneziawg-tools/blob/master/src/uapi/freebsd/dev/wg/if_wg.h#L13

and also here

https://github.com/amnezia-vpn/amneziawg-tools/blob/master/src/ipc-freebsd.h#L24

(this is ported part of wireguard-tools in amnezia repo)

Problem that ioctl reference driver directly (just by _IOWR('i', 210, struct wg_data_io) )
so, to have (for any reason) two drivers in parallel - this should be changed somehow ...

but, in general - no much sense to do it as far as awg just an extension on top of wg

Can you elaborate on this a little bit? You don't seem to have really changed the ioctl interface in a way that isn't compatible with if_wg (we would just ignore the new nvlist elements you've added), and requests get routed to the correct ioctl handler based on the interface named in the request passed to ioctl(2).

found myself, actually I was wrong, ioctl is interface-specific, so always delivered to the matching driver, even when ioctl constants are the same.