Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions contrib/sni-router/Caddyfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
# to Caddy's access log. The `tls` wrapper must follow so that TLS
# is terminated on the unwrapped connection.
#
# `allow` lists the networks permitted to send PROXY headers. These
# ranges cover docker compose's default bridge networks; tighten
# them if you pin a specific subnet in docker-compose.yml.
# `allow` lists the networks permitted to send PROXY headers.
# HAProxy runs in the host netns and reaches Caddy via host loopback
# (see docker-compose.yml), so the only legitimate peer is loopback.
servers :8443 {
listener_wrappers {
proxy_protocol {
timeout 5s
allow 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
allow 127.0.0.0/8 ::1/128
}
tls
}
Expand Down
24 changes: 24 additions & 0 deletions contrib/sni-router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,30 @@ must stay in sync:
If you disable one, disable all four, otherwise the backend will fail
to parse the connection.

## Why host networking for HAProxy

HAProxy runs in the host network namespace (`network_mode: host` in
`docker-compose.yml`) so it sees the real client source IP on every
inbound connection. With the default bridge networking + published
ports the source IP is rewritten to the bridge gateway — by Docker's
userland proxy (`docker-proxy`), by rootless Podman's `slirp4netns`
or `pasta`, or by NAT on the Docker host — and the PROXY v2 header
HAProxy then sends to mtg and Caddy carries that useless address.
Host networking lifts HAProxy out of the rewrite path; mtg and Caddy
stay on the compose bridge and HAProxy dials them via host loopback
(`127.0.0.1`).

Trade-off: HAProxy occupies the host's `:443` and `:80` directly, so
nothing else on the host may listen on those ports. For a dedicated
mtg/SNI-router host that is the intended layout.

Rootless Podman users binding the privileged ports `:80`/`:443` need
the host-side sysctl once (rootful Docker handles this implicitly):

```bash
sudo sysctl -w net.ipv4.ip_unprivileged_port_start=80
```

## Fronting loop (why `[domain-fronting]` is set explicitly)

When mtg sees TLS that isn't valid Telegram (a probe or a browser
Expand Down
25 changes: 15 additions & 10 deletions contrib/sni-router/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ x-domain-env: &domain-env
services:
haproxy:
image: haproxy:lts-alpine
ports:
- "443:443"
- "80:80"
# Host networking so HAProxy sees the real client source IP on
# inbound (bridge networking with published ports rewrites it to
# the bridge gateway under both Docker's userland-proxy and
# rootless Podman's slirp4netns/pasta). See "Why host networking"
# in README.md.
network_mode: host
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro,Z
environment:
Expand All @@ -35,15 +38,14 @@ services:
- mtg
- web
restart: unless-stopped
sysctls:
- net.ipv4.ip_unprivileged_port_start=80

mtg:
image: nineseconds/mtg:2
volumes:
- ./mtg-config.toml:/config/config.toml:ro,Z
expose:
- "3128"
# Publish on host loopback so the host-mode HAProxy can reach it.
ports:
- "127.0.0.1:3128:3128"
restart: unless-stopped
extra_hosts:
- "host.containers.internal:host-gateway"
Expand All @@ -54,9 +56,12 @@ services:
- ./Caddyfile:/etc/caddy/Caddyfile:ro,Z
- caddy_data:/data
- ./www:/srv:ro,Z
expose:
- "80"
- "8443"
# Publish on host loopback so the host-mode HAProxy can reach it.
# Caddy's HTTP listener is mapped off :80 (occupied by HAProxy)
# to :8080; haproxy.cfg dials it on 127.0.0.1:8080 for ACME.
ports:
- "127.0.0.1:8080:80"
- "127.0.0.1:8443:8443"
environment:
<<: *domain-env
restart: unless-stopped
Expand Down
11 changes: 8 additions & 3 deletions contrib/sni-router/haproxy.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,19 @@ backend mtg
# send-proxy-v2 prepends a PROXY protocol v2 header so mtg sees the
# real client IP instead of HAProxy's. mtg must have
# `proxy-protocol-listener = true` in its config.
server mtg mtg:3128 send-proxy-v2
#
# HAProxy runs in the host network namespace (see docker-compose.yml)
# and mtg publishes :3128 on host loopback, so we dial 127.0.0.1.
server mtg 127.0.0.1:3128 send-proxy-v2

backend web
# send-proxy-v2 prepends a PROXY protocol v2 header so Caddy logs the
# real client IP instead of HAProxy's. Caddy must enable the
# proxy_protocol listener wrapper on :8443 (see Caddyfile).
server web web:8443 send-proxy-v2
server web 127.0.0.1:8443 send-proxy-v2

backend web_acme
mode http
server web web:80
# Caddy's :80 is published on host 127.0.0.1:8080 (HAProxy occupies
# the host's :80 itself).
server web 127.0.0.1:8080
Loading