sync: merge upstream/master through 1565071 (Go 1.26, SNI-check consolidation, dcprobe, proxy-protocol listener)#33
Merged
Conversation
Closes 9seconds#486. The previous message read "Hostname X is resolved to Y addresses, not Z" with Z being either the detected IPv4 or IPv6 (whichever was set first), which made dual-stack mismatches confusing — a hostname resolving to v6 only on a host with v4 detected and v6 undetected printed "not <v4>" without hinting that v6 was the missing piece. The reworked template lists the resolved DNS records and both public addresses (or "<not detected>" when missing) so the gap is obvious: Hostname X resolves to "<v6>", but the proxy's public IP is 1.2.3.4 (IPv4) / <not detected> (IPv6) — none of the resolved addresses match Pure message change.
Mirrors the proxy-protocol-listener TOML config option so simple-run can sit behind a PROXY-protocol-emitting frontend (HAProxy, nginx stream) without dropping back to a config file. Discussed in 9seconds#502: with this flag the compose recipe collapses to 'simple-run $BIND $SECRET --proxy-protocol-listener', keeping the secret in the environment and no mtg-config.toml in the repo.
Follow-up to 9seconds#503, which introduced the (required) marker on bind-to. secret is the other top-level option without a sensible default, so it takes the same marker, on the first line of its description. Symmetric with 9seconds#504 ((default) on prefer-ipv6). Rolling the convention out to the rest of the file is left for separate PRs.
OpenWrt firewall zones are bound to interface names. With bare podman you can pin the static podman0 bridge into a zone, but podman-compose creates a project-scoped network and netavark spawns a fresh bridge (podman1, podman2, ...) per project — with no firewall rules — so containers lose outbound access. Mark the default network as external/name=podman to attach to the router-managed podman0 instead. Background: 9seconds#513.
Track `mtg-config.toml.example` with `secret = "${MTG_SECRET}"`; the
rendered `mtg-config.toml` and local `.env` are gitignored, so the
secret never lands in a tracked file.
Quick start switches from "paste the secret into mtg-config.toml" to
either `envsubst < mtg-config.toml.example > mtg-config.toml` or
`cp` + hand-edit `${MTG_SECRET}` for users without envsubst.
After 9seconds#502 made DOMAIN env-driven, the secret was the last hand-edit
of a tracked file in the example. Follow-up to 9seconds#506.
…ples `MTG_SECRET=<placeholder> envsubst < ...` was shell-broken on literal copy-paste — bash parses `<placeholder>` as redirection from a non-existent file. Two-line `export MTG_SECRET=...` + plain envsubst form removes the ambiguity. Applies to README, docker-compose.yml, and the .example header.
compose: fix non-functional 'host' option
…onvention docs: mark secret as (required) in example.config.toml
…ol-listener simple-run: add --proxy-protocol-listener flag
…ig-example contrib/sni-router: render mtg-config.toml from a tracked .example
doctor: surface both public IPs in SNI-DNS mismatch message
…-podman contrib/sni-router: document OpenWrt + podman-compose network workaround
Bridge ingress (Docker's docker-proxy userland forwarder, Podman's slirp4netns/pasta) rewrites the source IP of inbound connections on a published port to the bridge gateway address. HAProxy then stamps that gateway address into the PROXY v2 header it forwards to mtg and Caddy, so neither backend ever sees a real client IP. Move HAProxy into the host netns (network_mode: host) so it binds :443/:80 directly with no NAT in the path. mtg and Caddy stay on the compose bridge and are published on 127.0.0.1 only; HAProxy reaches them via host loopback and PROXY v2 carries the real client IP (v4 or v6) end-to-end. Also accept IPv6 clients explicitly on the HAProxy frontends — `bind *:443` is IPv4-only and missed v6 clients on hosts where the previous example happened to "work" only because of dual-stack quirks. Add 127.0.0.0/8 to Caddy's PROXY allow-list to cover the new loopback hop from HAProxy. README gains a short subsection explaining the host-mode choice and its trade-off (HAProxy occupies host :443/:80). Diagnosed and tested by @bam80 on Fedora + Docker 29. Fixes 9seconds#498.
…rrow Caddy allow) - Caddy allow: 127.0.0.0/8 → 127.0.0.1/32 (only loopback peer is HAProxy). - haproxy.cfg: rewrite v6only comment to describe what it actually does (suppresses v4-mapped accept, preventing conflict with the v4 bind), not the symptom. - docker-compose.yml: trim the 8-line haproxy comment to 3 lines and defer the rationale to README. Add one-line note explaining why web uses host port 8080 (HAProxy owns :80). - README: condense the "Why network_mode: host" subsection. Spell out trade-offs as a list: own-the-host-ports, Linux-only (Docker Desktop doesn't make this layout reachable), userns-remap incompatibility. Note that mtg-config.toml stays as-is because mtg/web remain on the compose bridge.
Switch to one-line `bind :80,[::]:80` and `bind :443,[::]:443` per review feedback in 9seconds#522. The v6only flag was self-documentation, not load-bearing: with SO_REUSEADDR (HAProxy's default) and bindv6only=0 the kernel routes v4 packets to the more-specific AF_INET socket regardless. Comment trimmed to match — the v6only paragraph is gone because v6only itself is gone. The shorter form also scales more cleanly when adding ports later, e.g. `bind :8080,[::]:8080` on a new line.
New leaf package that performs the first step of the MTProto handshake (req_pq_multi -> resPQ) over the existing obfuscated2 transport. No auth_key is generated; no long-lived state is introduced. Two TL messages, one round-trip, no new dependencies. A generic listener cannot fake the reply because it must echo back our random nonce in resPQ. Used by the doctor command in a follow-up commit to distinguish a real Telegram DC from a generic TCP listener bound to port 443.
Closes 9seconds#494. After a successful TCP connect, run an unauthenticated req_pq_multi -> resPQ exchange via mtglib/dcprobe. This rejects generic listeners that happen to bind 443 but cannot speak MTProto. Output now shows "(rpc <rtt>)" on success; on failure the wrapped error distinguishes "tcp connect to ...: ..." from "rpc handshake to ...: ...". The probe runs by default — an opt-in flag would defeat the purpose, since the existing TCP-only check is what motivated the issue.
`doctor`'s checkSecretHost and the proxy-startup warnSNIMismatch each carried their own copy of the same logic: resolve the secret hostname, determine the server's public IPv4/IPv6 (config first, getIP fallback), and compare the two sets. Extract that data-gathering into runSNICheck (internal/cli/sni_check.go), returning an sniCheckResult. The success decision stays with each caller because the rules genuinely differ — `doctor` reports OK when any family matches, while the startup warning requires every detected family to match — so only the gathering is shared, not the verdict. No behavior change: both callers produce byte-identical output and the same return values as before.
doctor: deepen DC verification with MTProto handshake probe
…al-ips sni-router: host-net HAProxy to preserve real client IPs
…i-check internal/cli: consolidate duplicated SNI-DNS check
Mention default value for tolerate-time-skewness
Upgrade go dependencies
…lidation, dcprobe, proxy-protocol listener) Upstream PRs included: - 9seconds#543 upgrade-go: Go 1.26 + goreleaser/gopls/govulncheck/gofumpt/golangci-lint bumps - 9seconds#540 tts-default-value: document default for tolerate-time-skewness - 9seconds#528 consolidate-sni-check: extract shared runSNICheck (doctor + startup warning) - 9seconds#522 sni-router-host-mode-real-ips: HAProxy host networking for real client IPs - 9seconds#496 doctor/rpc-probe: deepen DC verification with MTProto handshake probe (mtglib/dcprobe) [our PR, round-tripped] - 9seconds#505 doctor/sni-dns-message: surface both public IPs in SNI-DNS mismatch - 9seconds#523 docs/sni-router-openwrt-podman - 9seconds#525 contrib/sni-router-config-example: render mtg-config.toml from tracked .example - 9seconds#510 simple-run-proxy-protocol-listener: --proxy-protocol-listener flag - 9seconds#521 docs/required-default-convention; 9seconds#504 prefer-ipv6 default; 9seconds#514 MTG_SECRET envsubst fix Conflict resolutions (preserve fork behavior): - internal/cli: adopt upstream's runSNICheck refactor but parameterize it with an explicit host, so doctor keeps multi-secret semantics (getFirstSecretHost) while the startup warning keeps using conf.Secret.Host as before. - new files (mtglib/dcprobe, internal/cli/sni_check.go): rewrite module path 9seconds/mtg/v2 -> dolonet/mtg-multi. - contrib/sni-router/README.md: keep fork's "upstream discussion 9seconds#513" wording.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Merges 32 upstream commits from
9seconds/mtgmaster through1565071.Notable upstream changes
runSNICheckfor doctor + startup warningmtglib/dcprobe) (our PR, round-tripped)--proxy-protocol-listenerflagConflict resolutions (fork behavior preserved)
internal/cli(doctor / sni_check / run_proxy): adopted upstream'srunSNICheckrefactor but parameterized it with an explicithost, somtg doctorkeeps multi-secret semantics (getFirstSecretHost()) while the startup warning keeps usingconf.Secret.Host. Taking upstream verbatim would have made doctor report a spurious empty-host resolve error for[secrets]configs.mtglib/dcprobe,internal/cli/sni_check.go): module path rewritten9seconds/mtg/v2→dolonet/mtg-multi.contrib/sni-router/README.md: kept fork's "upstream discussion Контейнер с Caddy не имеет доступа в сеть на OpenWrt 9seconds/mtg#513" wording.Verification
go build ./...✓ ·go vet ./...✓ ·go test ./...all green (incl.mtglib/dcprobe)internal/clihas no unit tests upstream or here — doctor/sni_check changes are behavior-preserving by inspection, not machine-verified