Skip to content

Add Obfuscation features support and udp over tcp obfuscator on Linux and Android#11275

Open
encrypt94 wants to merge 49 commits into
mainfrom
marco/add-obfuscators
Open

Add Obfuscation features support and udp over tcp obfuscator on Linux and Android#11275
encrypt94 wants to merge 49 commits into
mainfrom
marco/add-obfuscators

Conversation

@encrypt94

@encrypt94 encrypt94 commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Description

This PR adds support for obfuscation features and udp over tcp obfuscators.
A new "Obfuscation" option in "Privacy Features" , allows users to configure the following obfuscation features when available on the platform (the menu item will be hidden if a feature is not available.)

  • Always connect using port 53 - not an obfuscation method, simply exposes an existing feature and allows the user to always use port 53 (compatible with LWO)
  • UDP over TCP - the only obfuscator implemented in this pr, uses udp over tcp crate to tunnel WireGuard traffic over TCP
  • LWO - lighweight WireGuard scrambler, planned as the next obfuscator
  • LWO over port 53 - lighweight WireGuard scrambler, planned as the next obfuscator
  • MASQUE - tunnel WireGuard traffic over CONNECT-UDP
  • Shadowsocks - tunnel WireGuard traffic over shadowsocks

This menu lets users select the ObfuscationPolicy to use. An obfuscation policy can specify a port selection, an obfuscation method, or a combination of both.

SettingsHolder::ObfuscationPolicy is converted to Server::ObfuscationMethod in Controller and ServerData. This allows the server selection logic to be obfuscation method aware.

When obfuscation is turned on or changed, servers that were previously unreachable may become available again so this action triggers a reset on all server in cooldown.

In the unlikely event that no server in a city matches the selected obfuscation configuration, a "Server Unavailable" dialog is displayed.

When a server is not available the modal will propose user to enable obfuscation or try a different obfuscation method.

Obfuscators

Obfuscators are implemented in a Rust crate located under the obfuscators/ directory.
An obfuscator acts as a local UDP proxy running on a local UDP port, the outbound sockets must be protected to avoid traffic loops.

On Linux (and Windows in the future except for the marking part):

  • The crate produces a standalone binary (mozilla-obfuscator) installed alongside the daemon.
  • When the VPN is turned on or a server switch occurs, the obfuscator is launched.
  • The obfuscator requires CAP_NET_ADMIN privileges to set SO_MARK.
  • The obfuscator listens on a free port and writes the port to stdout.
  • The MozillaVPN daemon parses the port from stdout and rewrites the server/port in WireGuard's peer config (alternatively, we can probe a free port in the daemon and send it to the obfuscator but this approach may be prone to race contitions)

On Android (and likely iOS in a similar way):

  • The crate is accessed via FFI using JNA.
  • VPNActivity starts the obfuscator.
  • VPNActivity calls protect on the obfuscator's sockets.
  • VPNActivity rewrites the local port in the configuration.

This architecture was chosen because most of the libraries required to implement obfuscation protocols are available in the Rust ecosystem: UDP-over-TCP, Shadowsocks, and QUIC stacks.

Impacts on the rust build bits:

  • added add_rust_binary in rustlang.cmake and refactored add_rust_library to split out shared parts
  • updated rust to 1.91 because:
    • udp-over-tcp requires rust >= 1.87
    • only rust 1.89 and 1.91 are available in Ubuntu repost
    • i hit a weird bug with cargo 1.8x installed from ubuntu repos being unable to install crates from git (solved in 1.91)
  • rust 1.91 is not available in debian:bookworm so is now installed using rustup in linux-qt6-build

UI

When Obfuscation features are enabled, a “Obfuscation is on” label appears below the timer.
Screenshot from 2026-07-16 16-51-32
A new Obfuscation menu is available in Settings > Privacy Features.
menu

When Anti-Censorship features are available on the platform, a radio will be displayed in the menu with name and brief description
Screenshot from 2026-07-16 16-59-10
Screenshot from 2026-07-16 16-59-19

Disabled
Screenshot from 2026-07-16 17-15-37
Screenshot from 2026-07-16 17-15-41

Help text
Screenshot from 2026-07-16 17-17-48

Server unavailable dialog and notification show when obfuscation is enabled
sun2
sun2n

Server unavailable dialog and notification show when obfuscation is not enabled
sun1
sun1n

Reference

VPN-7588 - Create generic obfuscation interface for anti censorship features
VPN-7566 - Create anti censorship features menu
VPN-7582 - Implement UDP over TCP on Linux
VPN-7583 - Implement UDP over TCP on Android

Checklist

  • My code follows the style guidelines for this project
  • I have not added any packages that contain high risk or unknown licenses (GPL, LGPL, MPL, etc. consult with DevOps if in question)
  • I have performed a self review of my own code
  • I have commented my code PARTICULARLY in hard to understand areas
  • I have added thorough tests where needed

@encrypt94 encrypt94 changed the title Add obfuscators and udp over tcp obfuscator on Linux DRAFT Add obfuscators and udp over tcp obfuscator on Linux May 13, 2026
@encrypt94 encrypt94 changed the title DRAFT Add obfuscators and udp over tcp obfuscator on Linux DRAFT Add Anti-Censorship features support and udp over tcp obfuscator (Linux only) May 13, 2026
@encrypt94
encrypt94 force-pushed the marco/add-obfuscators branch 3 times, most recently from cbd6d7e to ba41012 Compare May 13, 2026 22:48
@oskirby

oskirby commented May 13, 2026

Copy link
Copy Markdown
Collaborator

I've had kind of a love/hate relationship with building rust binaries over the years, but this might be a good case for splitting the obfuscator into a standalone binary that can be invoked by the daemon as needed rather than baking it into the client.

The obfuscator should only ever operate on unprivileged information, should require no root permissions, and it probably has a pretty heavy workload given that it needs to live in the packet processing loop.

QProcess * obfuscator = new QProcess(this);
QStringList args;
args << "--method" << "lwo";
args << "--pubkey" << config.m_serverPublicKey;
args << "--server" << config.m_serverIpv4AddrIn;
obfuscator->start("/usr/bin/mozillavpn-obfuscator", args);

connect(obfuscator, &QProcess::started, this, &Example::handleObfuscatorStarted);
connect(obfuscator, &QProcess::finished, this, &Example::handleObfuscatorFinished);

@encrypt94
encrypt94 force-pushed the marco/add-obfuscators branch from ba41012 to 6b6f676 Compare May 13, 2026 23:09
@encrypt94 encrypt94 changed the title DRAFT Add Anti-Censorship features support and udp over tcp obfuscator (Linux only) DRAFT Add Anti-Censorship features support and udp over tcp obfuscator (Linux and Android only) Jun 1, 2026
@encrypt94 encrypt94 changed the title DRAFT Add Anti-Censorship features support and udp over tcp obfuscator (Linux and Android only) DRAFT Add Anti-Censorship features support and udp over tcp obfuscator on Linux and Android Jun 1, 2026
@encrypt94
encrypt94 force-pushed the marco/add-obfuscators branch 6 times, most recently from 5eb093d to dbfe054 Compare June 1, 2026 15:17
@encrypt94
encrypt94 force-pushed the marco/add-obfuscators branch 5 times, most recently from 5005e81 to ef8ab9b Compare June 2, 2026 15:04
@encrypt94 encrypt94 changed the title DRAFT Add Anti-Censorship features support and udp over tcp obfuscator on Linux and Android Add Anti-Censorship features support and udp over tcp obfuscator on Linux and Android Jun 2, 2026
@encrypt94
encrypt94 marked this pull request as ready for review June 2, 2026 19:30
@encrypt94
encrypt94 requested review from a team and flodolo as code owners June 2, 2026 19:30
@encrypt94
encrypt94 removed the request for review from a team June 2, 2026 19:30
@encrypt94
encrypt94 force-pushed the marco/add-obfuscators branch from 143cfd0 to 0f2aee9 Compare July 16, 2026 14:46
@encrypt94 encrypt94 changed the title Add Anti-Censorship features support and udp over tcp obfuscator on Linux and Android Add Obfuscation features support and udp over tcp obfuscator on Linux and Android Jul 16, 2026
@encrypt94
encrypt94 requested review from artfwo and flodolo July 16, 2026 15:40
Comment thread src/cmake/linux.cmake
add_go_library(netfilter ${CMAKE_SOURCE_DIR}/linux/netfilter/netfilter.go)
target_link_libraries(mozillavpn PRIVATE netfilter)

# Add the obfuscator binary

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why we can't use the obfuscator on Flatpaks? Or is that work for a future PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

planned for a future PR!

Comment thread taskcluster/docker/base/Dockerfile Outdated
obfuscationShadowsocksTitle:
value: Shadowsocks
comment: Shadowsocks is a protocol name, it should NOT be translated.
obfuscationShadowsocksBody: May help bypass restrictive or heavily filtered networks, and could work when all other approaches do not.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Compared to the other obfuscation methods, this text does little to explain what Shadowsocks does, it reads more as a generic recommendation to use it rather than any kind of technical explanation.

Comment thread nebula/ui/components/MZInterLabel.qml Outdated
lineHeight: MZTheme.theme.labelLineHeight
wrapMode: Text.Wrap
color: MZTheme.colors.fontColor
opacity: enabled ? 1 : 0.5

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather change the color than add opacity - we've run into issues before, as it looks different depending on the background color.

We could create a fontColorMuted, similar to how we use fontColorInvertedMuted.

Comment thread nebula/ui/components/MZTextBlock.qml Outdated
// MZTextBlock
Text {
color: MZTheme.colors.fontColor
opacity: enabled ? 1 : 0.5

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above - would prefer a color rather than opacity

Comment thread src/ui/screens/settings/privacy/obfuscation/ViewObfuscation.qml Outdated
Comment thread src/ui/popups/ServerUnavailablePopup.qml
Comment thread src/translations/strings.yaml Outdated
bodyText: This server location is temporarily unavailable. Choose a new location.
bodyText1:
value: This server location is temporarily unavailable.
comment: First part of the server unavailable modal body. Followed by bodyText2NoObfuscation when obfuscation is disabled, or bodyText2Obfuscation when obfuscation is enabled.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use this approach. You end up with 3 strings and poorer localizability, instead of 2 full strings.

  • This server location is temporarily unavailable. Choose a new location or try a different obfuscation method.
  • This server location is temporarily unavailable. Choose a new location or try to enable obfuscation.

Comment thread src/translations/strings.yaml Outdated
bodyText: This server location is temporarily unavailable. Please open the app to choose a new location.
bodyText1:
value: This server location is temporarily unavailable.
comment: First part of the server unavailable notification. Followed by bodyText2NoObfuscation when obfuscation is disabled, or bodyText2Obfuscation when obfuscation is enabled.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, use 2 full sentences.

Comment thread src/translations/strings.yaml
Comment thread src/daemon/daemon.cpp
// The exit hop never needs an obfuscator, assign the empty obfuscator to
// allow changing of the obfuscation method on server switch.
if (config.m_hopType != InterfaceConfig::MultiHopExit) {
m_obfuscator = std::move(obfuscator);

@artfwo artfwo Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When m_obfuscator is assigned and updatePeer(peerConfig) is called later, what happens if updatePeer fails, e.g. because of network error? would it be safer to call updatePeer first and move obfuscator if that succeeds?

@encrypt94
encrypt94 force-pushed the marco/add-obfuscators branch from e11d496 to 0b78fd5 Compare July 20, 2026 14:01
@encrypt94
encrypt94 requested a review from flodolo July 20, 2026 14:17
sudo mk-build-deps --tool 'apt-get -y -o Debug::pkgProblemResolver=yes --no-install-recommends' \
--install --remove $MK_BUILD_DEPS_ARGS "$(pwd)/mozillavpn-source/debian/control"

# /usr/bin/cargo and /usr/bin/rustc may be installed as cargo-<version> and rustc-<version>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this still necessary after the update to debian/rules for Rust 1.91?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants