ulfrasark/masque-client

By ulfrasark

Updated 10 days ago

MASQUE (HTTP3 CONNECT) client for MikroTik ROS v7.21+ container — transparent gw SOCKS5 proxy.

Image
Networking
Security
1

443

ulfrasark/masque-client repository overview

ulfrasark/masque-client

MASQUE (HTTP/3 CONNECT) client for MikroTik RouterOS containers — routes your LAN through the tunnel transparently, or serves a SOCKS5 proxy.

Based on Alpine Linux. Multi-arch: x86_64, ARM64, ARMv7. Pairs with ulfrasark/masque-server over mutual TLS.


Platforms
PlatformHardware
linux/amd64CHR x86, x86 routers
linux/arm64RB5009, hAP ax series, CCR2004
linux/arm/v732-bit ARM (hAP ac2 / ac3)

The tag is a multi-arch manifest — RouterOS pulls the matching variant by image name.


Image size
VariantCompressedUnpacked
latest (gateway + SOCKS)~25 MB~65 MB
socks (SOCKS only)~8 MB~20 MB

How it works
LAN --> RouterOS (routing marks) --> veth --> container --> tunnel --> server

The container is a gateway: you point traffic at it, it carries that traffic over HTTP/3 CONNECT to the server. The server address itself stays reachable outside the tunnel automatically, so the tunnel never tries to carry its own packets.

Two modes, chosen automatically at startup:

ModeWhenResult
gateway/dev/net/tun is availabletransparent routing, clients need no config
socksno TUN availableSOCKS5 on 0.0.0.0:1080, clients set it up

Pin one with MODE=gateway or MODE=socks if you don't want it decided for you.


MikroTik settings
/interface/veth add name=veth-masque address=172.20.0.2/24 gateway=172.20.0.1
/ip/address/add address=172.20.0.1/24 interface=veth-masque
/interface/list/member/add list=LAN interface=veth-masque
/ip/firewall/nat/add chain=srcnat action=masquerade src-address=172.20.0.0/24

The /ip/address/add line is separate on purpose: veth ... gateway= does not create the router-side address. Without it, or without the masquerade rule, the container starts but never reaches the server — the log shows QUIC dial error: timeout while the server log stays empty.


Mounts (required)
/container/mounts/add name=masque-profile src=disk1/masque-client/config dst=/app/config
/container/mounts/add name=masque-dev     src=/dev                       dst=/dev
MountPurpose
/app/configthe .masque profile issued by the server
/devTUN access — required for transparent gateway mode

Put the profile at disk1/masque-client/config/client.masque (WinBox -> Files -> drag & drop). Any *.masque in that folder is picked up.

The profile needs insecure_skip_verify = false and filled ca_pem / cert_pem / key_pem — the server enforces mutual TLS and rejects clients without a valid certificate.


Environment variables

All optional — defaults work.

/container/envs/add list=masque-client key=MODE       value=auto
/container/envs/add list=masque-client key=TUN_MTU    value=1280
/container/envs/add list=masque-client key=DNS_SERVER value=1.1.1.1
VariableDefaultDescription
MODEautoauto / gateway / socks
TUN_MTU1280tunnel MTU
DNS_SERVER1.1.1.1DoH resolver used inside the tunnel
MASQUE_SOCKS_PORT1080SOCKS port (relevant in socks mode)

Install container
/container/config/set registry-url=https://registry-1.docker.io tmpdir=disk1/pull

/container/add \
  remote-image=ulfrasark/masque-client:v1.1 \
  interface=veth-masque \
  envlist=masque-client \
  mounts=masque-profile,masque-dev \
  root-dir=disk1/masque-client/root \
  dns=1.1.1.1 \
  start-on-boot=yes \
  logging=yes

Pin the version tag rather than latest: RouterOS does not re-pull on its own, so with latest you keep running whatever was downloaded first.


Start
/container/start [find remote-image~"masque-client"]

Check logs
/log print where topics~"container"

A healthy start:

masque-client: version: v1.1 (arch aarch64)
masque-client: capabilities: sing-box=yes tun=yes ip_forward=yes
masque-client: mode: gateway
QUIC connected to your-ip-or-domain:4433
masque-client: SOCKS ready
inbound/tun[tun-in]: started at tun0

tun=no means this device or ROS build does not expose TUN — the container comes up in socks mode instead. A failed first attempt is normal when the container starts before the router's WAN is up; it retries with backoff rather than exiting.


Routing — sending traffic through the tunnel

Start with a single host: easy to verify, easy to revert.

/routing/table/add name=via-masque fib

/ip/firewall/address-list/add list=local-nets address=192.168.0.0/16
/ip/firewall/address-list/add list=local-nets address=172.16.0.0/12
/ip/firewall/address-list/add list=local-nets address=10.0.0.0/8

/ip/firewall/mangle/add chain=prerouting action=accept \
    dst-address=YOUR.SERVER.IP place-before=0 comment="masque server bypass"
/ip/firewall/mangle/add chain=prerouting action=accept \
    dst-address-list=local-nets place-before=1 comment="keep LAN local"

/ip/firewall/mangle/add chain=prerouting action=mark-routing \
    new-routing-mark=via-masque passthrough=no src-address=192.168.88.50

/ip/route/add dst-address=0.0.0.0/0 gateway=172.20.0.2 routing-table=via-masque

Whole LAN instead of one host:

/ip/firewall/mangle/add chain=prerouting action=mark-routing \
    new-routing-mark=via-masque passthrough=no \
    in-interface-list=LAN dst-address-list=!local-nets

The two accept rules must sit above the mark-routing rule. If the server bypass ends up below it, the tunnel tries to carry its own traffic and nothing works. Check the order with /ip/firewall/mangle/print.


DNS

Handled inside the container over DoH — routed clients need no DNS setup, and the router's own DNS stays untouched. Change the resolver with DNS_SERVER if you prefer something other than 1.1.1.1.


Verify
curl https://2ip.io
curl -o /dev/null -w "%{size_download}\n" http://93.184.216.34

Do not test with ping. The tunnel carries TCP only, so ICMP never traverses it: a reply means the packet went around the tunnel, and ping some.host stays silent even when DNS resolves fine — which looks like broken DNS while everything actually works.


Port forwarding

None. The client only makes outbound connections — just don't block outbound UDP to the server port (default 4433).


Notes / limits
  • TCP only. App UDP/QUIC (game traffic, browser HTTP/3) and ICMP are not tunneled; browsers fall back to TCP.
  • Transport is encrypted end-to-end (QUIC / TLS 1.3) with mutual authentication.
  • In socks mode clients point at 172.20.0.2:1080 themselves.
  • Restarting the container drops active sessions.

GitHub

Source and Dockerfile: coming soon

Tag summary

Content type

Image

Digest

sha256:3b00a254f

Size

23.7 MB

Last updated

10 days ago

docker pull ulfrasark/masque-client