Netgate Discussion Forum
    • Categories
    • Recent
    • Tags
    • Popular
    • Users
    • Search
    • Register
    • Login
    Introducing Netgate Nexus: Multi-Instance Management at Your Fingertips.

    **[Package] AmneziaWG for pfSense — WireGuard obfuscation that gets a tunnel past DPI**

    Scheduled Pinned Locked Moved WireGuard
    15 Posts 2 Posters 1.4k Views 2 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • M Offline
      marcelomayo74
      last edited by

      Hi all,

      Sharing a package I built for the case where WireGuard is not blocked by port, but by shape: the handshake has a fixed layout that DPI can fingerprint, and where that gets filtered the tunnel simply never comes up — nothing logs an error, it just sits there connecting. AmneziaWG is a WireGuard fork that changes what the packets look like on the wire while keeping the same cryptography. There was no pfSense package for it, so I wrote one. It adds VPN → AmneziaWG.

      What it does:

      • Tunnels and peers managed from the GUI at the level of the official WireGuard package: tunnels, peers, settings, status page and a dashboard widget.
      • The Obfuscation section, which is the part no other package has: the 16 parameters (Jc/Jmin/Jmax, S1–S4, H1–H4, I1–I5), each one drawn per tunnel instead of shipped as a constant. A value that arrives the same in every installation is a signature of its own — it trades the fingerprint you are hiding for a brand new one.
      • A compatibility selector per tunnel (AmneziaWG 1.x or 2.0) that states what the weakest end understands, and leaves anything above it out of both configuration files even when a value is stored. One parameter different on one side means the handshake never completes and nothing reports why, so that is the part I most wanted to make hard to get wrong.
      • One form creates the peer and hands you the client file: a .zip download, a QR to scan from the app, or delivery by email through the SMTP the firewall already has under System → Advanced → Notifications. It auto-fills what it can — next free address on the tunnel, your Dynamic DNS hostnames as endpoint options, the listen port, the MTU, DNS presets.
      • Or untick the client box and register a peer from a public key you were given, which is what a site to site link needs.

      Important — how it works under the hood:

      Each tunnel is an amneziawg-go process in userspace, supervised by the package's service. There is no kernel module: AmneziaWG has one (if_amn.ko), but it will not load on pfSense — it is pinned to an exact __FreeBSD_version and pfSense runs Netgate's own kernels. Interfaces are named tun9000–tun9999 on purpose, because a plain tunN name is what makes pfSense list them under Interfaces → Assignments, so a tunnel can be assigned and get firewall rules and a gateway like any other interface.

      It depends on no other package and it does not touch the official WireGuard package — separate paths, separate control sockets, separate service. Both run side by side on the same firewall; that is how mine is set up.

      Three trade-offs I want to be upfront about:

      1. Userspace costs CPU. Measured on an Intel i5-3570 (4 cores, Ivy Bridge): ~830 Mbps of payload with both encryption and decryption on the same box. The obfuscation itself is free — 835 vs 828 Mbps with it turned off — but kernel WireGuard moves about 2.5x more per core. Plenty for a home or small office link; if you need line rate, kernel WireGuard is still the right tool. This exists for when WireGuard is the thing being blocked.
      2. The bundled amneziawg-go is not the stock binary. It is built from source with a FreeBSD sticky sockets patch and reports -sticky1. Without it, a firewall with more than one WAN cannot complete a handshake on any WAN that does not hold the default gateway: the reply leaves through the default gateway's interface carrying the wrong source address, and the client discards it. The patch is upstream at amnezia-vpn/amneziawg-go#180. A plain pkg install of amneziawg-go would silently undo it.
      3. Generating a client file stores that client's private key in config.xml, so the file can be handed out again later — re-download it, show the QR again, resend the mail. That means it also lands in your backups and in AutoConfigBackup. It is a checkbox: untick it and the peer is registered from a public key with nothing stored, which is the safer practice.

      Requires:

      Nothing else installed. Tested only on pfSense CE 2.9.0-BETA (FreeBSD 16), amd64 — that version, on a single firewall, and nothing else. 2.8.1 (FreeBSD 15) is deliberately out of scope: the .pkg declares the FreeBSD:16:amd64 ABI, so pkg add refuses to install it there rather than putting something unverified on a firewall.

      Install:

      Download the .pkg from the release: https://github.com/MarceloMayo74/pfsense-amneziawg/releases/tag/v1.0.0

      With SSH:

      scp pfSense-pkg-AmneziaWG-1.0.0-FreeBSD-16-amd64.pkg root@FIREWALL:/root/
      ssh root@FIREWALL
      pkg add /root/pfSense-pkg-AmneziaWG-1.0.0-FreeBSD-16-amd64.pkg
      

      Or without leaving the browser: Diagnostics → Command Prompt, "Upload File", pick the .pkg — it lands in /tmp — then under "Execute Shell Command":

      pkg add /tmp/pfSense-pkg-AmneziaWG-1.0.0-FreeBSD-16-amd64.pkg
      

      pkg add warns that the package doesn't come from a repository — that's expected, it isn't in the official repo or any unofficial one. Then open VPN → AmneziaWG.

      To remove it: pkg delete -y pfSense-pkg-AmneziaWG. Tunnels and peers stay in config.xml unless you turn off Keep Configuration in Settings first. And note that a pfSense system upgrade only reinstalls packages that come from the official repository, so run pkg add again after upgrading.

      Repo with the full technical detail, and where to file anything: https://github.com/MarceloMayo74/pfsense-amneziawg

      Apache 2.0. The GPLv2 awg(8) binary that ships inside has its corresponding source published beside the .pkg in the same release, as the licence requires. Feedback, issues and PRs all welcome. Thanks for reading!

      obfuscation.png

      peer-client.png

      peers-list.png

      status.png

      widget.png

      1 Reply Last reply Reply Quote 1
      • yon 0Y Offline
        yon 0
        last edited by

        it is support AWG3.0?

        M 1 Reply Last reply Reply Quote 0
        • M Offline
          marcelomayo74 @yon 0
          last edited by

          @yon-0 yes, as of today. That question is what 1.1.0 answers.

          1.1.0 — AmneziaWG 3.0 and 3.1, and importing a configuration

          https://github.com/MarceloMayo74/pfsense-amneziawg/releases/tag/v1.1.0

          Both bundled binaries are now 3.1 (amneziawg-go 3.1.20260814, awg 3.1.20260812), and the compatibility selector has four rungs instead of two: 1.x, 2.0, 3.0 and 3.1. They stay separate because a 3.0 client rejects the whole file over RandomTrailers, exactly as a 1.x one rejects it over S3 — the question the selector asks is still what the weakest end understands. The 16 obfuscation values from the first post are now 25.

          A fourth trade-off, in the spirit of the other three. The part of 3.0 that actually earns its keep is HeaderProtectionKey: ChaCha20 over the packet header, so the message type stops being readable. But its nonce is taken from the first 12 bytes of each packet's padding, which means S1 through S4 must all reach 12 — including S4, the only padding paid for on every data packet. So header protection costs MTU, always. That is the protocol's rule, not a choice this package makes; it is undocumented upstream, and getting it wrong makes the backend answer Invalid argument naming no field at all, so the package now refuses the combination before it can reach a config file and raises the four values for you while a key is set.

          ContentPaddingAddition and the five new protocol timings are deliberately left empty: the first is paid per data packet too, and the timings must match on both ends, where an unusual value makes a tunnel more distinguishable, not less.

          Also new: importing a configuration. Paste a .conf from another firewall or a provider and it becomes a tunnel plus its peers. Connecting to someone else's tunnel was always possible — a peer with an Endpoint is exactly that — but transcribing 25 values by hand, where one wrong digit produces no error and the handshake simply never completes, was not.

          Verified between two pfSense boxes over the internet, through the WAN that does not hold the default gateway — the case the sticky-sockets patch exists for. A 2.0 client still connects to a 3.1 backend unchanged, so upgrading touches nothing.

          One caveat worth stating plainly: the published Android and Windows clients are still 2.0 and will reject a 3.0 file, even though their sources already carry 3.1 — the Windows client's go.mod pins the same amneziawg-go version bundled here. Until those releases ship, 3.x is firewall-to-firewall. Everything below 3.0 works with the apps exactly as before.

          Filed upstream on the way: ipc-freebsd.h has not compiled since the 3.0 line opened, so amneziawg-tools cannot be built for FreeBSD without disabling the kernel IPC path — amnezia-vpn/amneziawg-tools#68.

          obfuscation1.png

          obfuscation2.png

          tunnels.png

          import.png

          yon 0Y 1 Reply Last reply Reply Quote 0
          • yon 0Y Offline
            yon 0 @marcelomayo74
            last edited by

            @marcelomayo74 said in **[Package] AmneziaWG for pfSense — WireGuard obfuscation that gets a tunnel past DPI**:

            所用广域网不包含默认网关——粘性套接字补丁正是针对这种情况设计的。2.0 客户端仍然可以连接到 3.1 后端,因此升级不会影响任何内容。

            需要特别说明的是:已发布的 Android 和 Windows 客户端版本仍为 2.0,即使其源代码已包含 3.1 版本,它们也会拒绝 3.0 文件——Windows 客户端的 go.mod 文件锁定了与此处捆绑的 amneziawg-go 版本相同的版本。在 3.x 版本正式发布之前,3.x 版本只能在防火墙之间使用。所有低于 3.0 版本的应用都能像以前一样正常工作。

            已向上游提交:ipc-freebsd.h 自 3.0 版本开启以来一直无法编译,因此如果不禁用内核 IPC 路径,就无法为 FreeBSD 构建 amneziawg-tools — amnezia-vpn/amneziawg-tools#68

            I used the tool here to generate the configuration, and I'll install it and try it out. AWG2.0 has already been detected by the GFW. Currently, AWG3.0 is usable.
            https://architect.vai-rice.space/amneziawg/

            yon 0Y 1 Reply Last reply Reply Quote 0
            • yon 0Y Offline
              yon 0 @yon 0
              last edited by

              The peer's page cannot edit the remote server's IP address Endpoint; it can only work by importing configuration files.

              yon 0Y 1 Reply Last reply Reply Quote 0
              • yon 0Y Offline
                yon 0 @yon 0
                last edited by

                We need to add a designated interface to connect to the remote server IP, such as a specified WAN port, to avoid interference from other VPNs.

                yon 0Y 1 Reply Last reply Reply Quote 0
                • yon 0Y Offline
                  yon 0 @yon 0
                  last edited by

                  The network speed is a bit slow compared to regular WG.

                  M 1 Reply Last reply Reply Quote 0
                  • M Offline
                    marcelomayo74 @yon 0
                    last edited by

                    @yon-0 — both of the things you found are in 1.2.0, along with a correction to something I got wrong in the 1.1.0 post.

                    https://github.com/MarceloMayo74/pfsense-amneziawg/releases/tag/v1.2.0

                    The peer endpoint. You were right, and it was worse than not being editable: the page saved every peer as dynamic, which discards the endpoint and the port. So an imported peer lost the remote server's address the moment it was saved, whether or not you had touched the field — the tunnel stopped dialing and nothing said so. The word names the two opposite ends of the same tunnel and only one of them had a place to live. Now the field says which one it means, depending on whether you are generating a client file or registering a peer this firewall dials out to.

                    Choosing the WAN. Also in. It cannot be done inside amneziawg-go on FreeBSD: Send() takes the source address from the endpoint's src, which sticky sockets fill in from IP_RECVDSTADDR — that only exists once the peer has dialed us. And forcing only the source makes it worse, because FreeBSD has no SO_BINDTODEVICE: the packet still leaves by whatever the route says, carrying one WAN's address out of another, which is what an ISP drops. So the mechanism is a route, and the package now keeps a host route to the endpoint through the gateway you pick. One thing to know before turning it on: the route is to an address, so all traffic to that address takes that gateway, not only the tunnel.

                    And a correction. In the 1.1.0 post I wrote that the five 3.0 timings must match on both ends. They must not — every one is read locally by the side that owns it and none travel in the handshake. What is real is that two of them constrain each other on the same side, and the backend checks neither, so 1.2.0 does. If you had set any of those by hand on your side, that is worth a second look.

                    The speed you noticed. That is userspace, not the obfuscation. amneziawg-go runs in Go against pfSense's kernel WireGuard; measuring here, userspace costs about 2.5x, while the obfuscation itself costs essentially no throughput. The one part that does cost is S4, because it is the only padding paid on every data packet — and its real limit is not the "32 bytes" some generators claim (that number is in none of the sources), it is the MTU. The tunnel page now prints the arithmetic with your interface's actual MTU.

                    Verified between two pfSense boxes at different sites over the internet, at 3.1 with ranged headers and header protection: handshake completes and traffic crosses in both directions, arriving on the WAN that does not hold the default gateway.

                    Since you are testing against the GFW and I am not, your result is the one that counts — particularly whether the per-packet header ranges survive on your path. If something does not work, the tunnel page and the peer page now explain what each field costs, and I would rather hear it does not than assume it does.

                    yon 0Y 2 Replies Last reply Reply Quote 0
                    • yon 0Y Offline
                      yon 0 @marcelomayo74
                      last edited by

                      @marcelomayo74

                      After installing your program on PF 23.06.1, the system crashed after running for a while and restarted to the older 23.06 system, resulting in the loss of all data. Now I have to install PF CE 2.7.2 to 2.9.

                      One of my uses for AWG is to connect to remote BGP nodes, so I need to configure the Table = off fe80::73e3:b2ff:fe1d:6fc9/64 parameter. Also, AllowedIPs needs to exclude endpoint IPs. I would appreciate it if this could be added.
                      Because I frequently use different networks for research and testing, and keep up with new technological developments, testing Hysteria is currently a good choice. AWG2.0 is too slow and prone to network interruptions, resulting in unresponsiveness. WAG3.0 has improved configuration. I don't know where to find information on AWG 3.1. I compiled the AWG 3.0 kernel to an Ubuntu 26.04 system on a remote server. I haven't been able to find any information on AWG 3.1. pfSense has always been too conservative, failing to keep up with many new technologies and applications.

                      1 Reply Last reply Reply Quote 0
                      • yon 0Y Offline
                        yon 0 @marcelomayo74
                        last edited by

                        @marcelomayo74 said in **[Package] AmneziaWG for pfSense — WireGuard obfuscation that gets a tunnel past DPI**:

                        https://github.com/MarceloMayo74/pfsense-amneziawg/

                        We need to consider adding the ability to import certain configuration parameters. For example, if some configuration parameters need to be changed, they can be imported directly instead of all of them. You can refer to this tool: https://architect.vai-rice.space/amneziawg/. After all, many people don't know how to configure the parameters, so ease of use is essential.

                        yon 0Y 1 Reply Last reply Reply Quote 0
                        • yon 0Y Offline
                          yon 0 @yon 0
                          last edited by

                          To facilitate more real-time communication, I created a Telegram group.

                          https://t.me/+i8iDoeP3EqAxN2I5

                          1 Reply Last reply Reply Quote 0
                          • M Offline
                            marcelomayo74
                            last edited by

                            @yon-0 — several answers, and one thing I need from you first.

                            The crash. Please do this before you reinstall. Run ls -la /var/crash/. If there is a file, paste cat /var/crash/info.0. It is a small text file with the panic message. Also uname -a and the exact version. After the reinstall this is gone and we will never know.

                            What I can say now: this package has no kernel component. The data plane is amneziawg-go in userspace. Nothing of mine loads into your kernel. That does not prove the package was innocent. It means I cannot even guess without the dump.

                            And one thing here is my fault. The package declares ABI FreeBSD:16:amd64. I wrote that this makes pkg add reject it everywhere else. That is only true for FreeBSD 15 and older. pfSense Plus 25.11, 25.11.1, 26.03, 26.03.1, 26.07 and 26.10 are all FreeBSD 16. pkg add accepts this package on all of them, with no warning. I have tested on 2.9.0 CE only. The package should refuse to install on a version I have not tested. Today it does not. That is a real gap and I will close it.

                            About "loss of all data". On pfSense the configuration lives inside the boot environment — the dataset is pfSense/ROOT/default/cf, mounted at /cf. So booting the older BE takes the whole firewall configuration back with it. That is how pfSense works, not something the package did. Worth knowing before you test further. Logs are different: /var/log is outside the BE and survives — unless you have RAM disks enabled, in which case it is in memory and dies at every reboot.

                            Table = off — this is already the behaviour. This package does not use wg-quick, and Table is a wg-quick directive, not a wg(8) one. Here, AllowedIPs never create system routes. They are only the cryptokey routing table inside the interface. The only routes this package installs are host routes to a peer endpoint, and only when you choose a Gateway on that peer. There is nothing to configure and nothing for me to add.

                            Excluding the endpoint from AllowedIPs — already there since 1.2.0. Set a Gateway on the peer. The package installs a host route to the endpoint address through that gateway. That is exactly the mechanism you are asking for. One thing to know first: the route is to an address, so all traffic to that address takes that gateway, not only the tunnel.

                            fe80::/64 on the tunnel — I tested this today on 2.9.0. It works. I ran the exact command the package builds:

                            ifconfig tun9099 inet6 fe80::73e3:b2ff:fe1d:6fc9/64 alias
                            accepted, no error
                            FreeBSD adds the scope itself. It appears as fe80::73e3:b2ff:fe1d:6fc9%tun9099. You do not type %iface
                            it coexists with the interface's automatic link-local
                            DAD completes. The address does not stay tentative
                            ping6 to it works, 0% loss
                            the package's own validation accepts it
                            And you may not need it at all. net.inet6.ip6.auto_linklocal is on, so every tunnel interface is born with a link-local already. Setting one by hand only gives you a stable, known address to configure as neighbor on the far side. You can start testing BGP today with the one it already has.

                            One limit, so I do not oversell this: I tested at the ifconfig layer, which is the layer this package drives. When a tunnel is assigned, pfSense sets the addresses from the Interfaces page, not the package. I have not tested whether that page accepts a link-local.

                            AWG 3.1 — it exists. For the Linux kernel module the tag is v3.1.20260812, committed 2026-08-12, message feat: awg 3.1 features. The reason you could not find it: these repositories publish no GitHub Releases, only tags. The Releases page is empty. I checked the API. Nothing is announced anywhere.

                            Which exact 3.0 tag did you compile? This matters. There are six 3.0 tags, and only the last one, v3.0.20260805, contains this fix:

                            fix: keepalives are ignored with S4 and ContentPaddingAddition
                            If you built an earlier tag and you have S4 set, your side stops sending keepalives. The tunnel goes silent, NAT state expires, and it stops responding. That would explain the interruptions you describe. My side already has this fix — I verified it in the source I ship. Rebuild from v3.1.20260812 and it is fixed on your side too.

                            Also: the levels must match. If your Ubuntu side stays on 3.0, set the pfSense tunnel to 3.0. If they differ, the handshake never completes and nothing on screen explains why.

                            The speed — here is the part I had not explained properly. On FreeBSD you must choose one of two, and you cannot have both:

                            kernel (if_amn): fast, but the FreeBSD port is v2.0.11. It is 2.0 only.
                            userspace (amneziawg-go): about 2.5x slower, but it reaches 3.1.
                            The OPNsense plugin uses the kernel module. That is why it is faster, and also why it offers only nine obfuscation parameters — Jc, Jmin, Jmax, S1, S2, H1-H4. That is 2.0. The generation you told me the GFW already detects.

                            So the slowness you measured is the exact price of the only thing that makes this package useful to you. If I took the fast path it would be quicker and it would not solve your problem. I would rather be honest about that than pretend there is a tuning knob.

                            Partial import — good idea, but I need you to pick one. Which do you mean?

                            paste a full .conf, then choose which fields to apply
                            paste only the obfuscation lines (Jc, S1, H1…) with no [Interface] header
                            a merge mode: update the obfuscation only, keep keys, addresses and peers
                            They are three different features. Tell me which one matches how you actually work and I will build that one.

                            One caution about generators. In my 1.2.0 post I mentioned that some of them claim a 32-byte limit for S4. That number is in none of the sources. The real limit is the MTU. Importing a wrong value cleanly is still a wrong value, so I want to see what that tool emits before I accept it without checking.

                            And about Hysteria. For what you described, it is a reasonable choice, and I will not argue against it. It solves a problem this package structurally cannot. WireGuard holds one UDP conversation on one fixed port, which is exactly the pattern your ISPs restrict — Hysteria's own port hopping documentation says so directly. Obfuscation hides the content of packets. Port hopping attacks the metadata of the connection. Those are different layers, and you are being blocked at the one I cannot reach.

                            If you do come back to this, the answers above are all real and tested. And I would still like that info.0.

                            yon 0Y 1 Reply Last reply Reply Quote 0
                            • yon 0Y Offline
                              yon 0 @marcelomayo74
                              last edited by

                              @marcelomayo74

                              I am using AllowedIPs = 0.0.0.0/0, ::/0 for bgp tunnel, so it is need config the disallowedIPs. I have chat in TG for you

                              yon 0Y 1 Reply Last reply Reply Quote 0
                              • yon 0Y Offline
                                yon 0 @yon 0
                                last edited by

                                cat /sys/module/amneziawg/version
                                3.0.20260805
                                root@jp:~# awg --version
                                amneziawg-tools v3.0.20260805 - https://amnezia.org

                                yon 0Y 1 Reply Last reply Reply Quote 0
                                • patient0P patient0 referenced this topic
                                • yon 0Y Offline
                                  yon 0 @yon 0
                                  last edited by

                                  Aug 20 17:07:37 php_awg 26166 ERROR AmneziaWG: 2a0d:2400:511::1 already has a host route that this package did not create; leaving it alone and not routing that endpoint through fe80::769d:8fff:fe05:c78b%pppoe3.
                                  Aug 20 17:02:36 php_awg 26166 ERROR AmneziaWG: 2a0d:2400:511::1 already has a host route that this package did not create; leaving it alone and not routing that endpoint through fe80::769d:8fff:fe05:c78b%pppoe3.
                                  Aug 20 16:57:35 php_awg 26166 ERROR AmneziaWG: 2a0d:2400:511::1 already has a host route that this package did not create; leaving it alone and not routing that endpoint through fe80::769d:8fff:fe05:c78b%pppoe3.

                                  1 Reply Last reply Reply Quote 0
                                  • First post
                                    Last post
                                  Copyright 2026 Rubicon Communications LLC (Netgate). All rights reserved.
                                  Privacy Policy · Cookie Policy