From b788e15a57825ee793e3b525ed903086ce793f20 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 22 Jun 2026 21:53:17 -0600 Subject: [PATCH 1/7] config: add Aliyun (Alibaba Cloud) CDN domain-fronting provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Alibaba Cloud / Aliyun CDN edges route by the HTTP Host header (h1) / :authority (h2) and ignore the TLS SNI for origin selection, so a censor filtering on SNI sees only an innocent front domain (img.alicdn.com) while the request reaches the real target. Verified 2026-06-23 cross-organization: a TLS session bearing Alibaba's *.tbcdn.cn cert (SNI img.alicdn.com) reached Bilibili (s1.hdslb.com) and Momo (img.momocdn.com) origins purely by Host. This adds aliyun-provider.yaml: a complete, parseable Config with the GlobalSign Root CA - R3 (anchors the Alibaba leaf chain), a verified seed pool of edge IPs across three /24s, and frontingsnis driving img/gw/a .alicdn.com SNIs. hostaliases + testurl are placeholders until Lantern origins are onboarded as Aliyun CDN distributions (the edge silently drops non-customer Host values). aliyun_live_test.go (guarded by DOMAINFRONT_LIVE=1) drives the real roundTripper: every edge completes TLS + GlobalSign verification under the production Chrome_131 hello and fronts a cross-org Host over HTTP/2 (200). Depends on #9 (HTTP/2 fronting) — Aliyun negotiates h2. Co-Authored-By: Claude Opus 4.8 (1M context) --- aliyun-provider.yaml | 103 +++++++++++++++++++++++++++++++++++++++++++ aliyun_live_test.go | 92 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 aliyun-provider.yaml create mode 100644 aliyun_live_test.go diff --git a/aliyun-provider.yaml b/aliyun-provider.yaml new file mode 100644 index 0000000..3d30412 --- /dev/null +++ b/aliyun-provider.yaml @@ -0,0 +1,103 @@ +# Alibaba Cloud (Aliyun) CDN domain-fronting provider. +# +# Built & verified 2026-06-23 (US vantage point). Aliyun CDN edges route by the +# HTTP Host header and ignore the TLS SNI for origin selection, so a censor doing +# SNI filtering sees only the innocent front domain (img.alicdn.com) while the +# request reaches the real target. Confirmed cross-organization: a TLS session +# bearing Alibaba's *.tbcdn.cn cert (SNI img.alicdn.com) reached Bilibili +# (s1.hdslb.com) and Momo (img.momocdn.com) origins purely via the Host header. +# +# Two operational constraints discovered: +# 1. TLS layer — the edge node must hold a cert valid for the SNI you present. +# img.alicdn.com is ideal: Alibaba's own *.tbcdn.cn cert (SAN *.alicdn.com, +# *.taobao.com, ...) is provisioned on ~every Aliyun node. +# 2. The target Host must be an onboarded Aliyun CDN customer. A non-customer +# Host (e.g. www.example.com) makes the edge silently drop the connection. +# => Lantern must onboard its own distribution to Aliyun CDN; the +# hostaliases + testurl below are PLACEHOLDERS until that exists. +# +# This file is a complete, standalone Config (parseable via ParseConfigYAML) so +# it can be loaded directly for testing. The `aliyun:` block under `providers:` +# is what gets merged into the upstream getlantern/fronted pipeline output. + +trustedcas: + # Anchor for the Alibaba leaf cert chain: + # *.tbcdn.cn -> GlobalSign GCC R3 OV TLS CA 2024 -> GlobalSign Root CA - R3 + - commonname: GlobalSign Root CA - R3 + cert: | + -----BEGIN CERTIFICATE----- + MIIDXzCCAkegAwIBAgILBAAAAAABIVhTCKIwDQYJKoZIhvcNAQELBQAwTDEgMB4G + A1UECxMXR2xvYmFsU2lnbiBSb290IENBIC0gUjMxEzARBgNVBAoTCkdsb2JhbFNp + Z24xEzARBgNVBAMTCkdsb2JhbFNpZ24wHhcNMDkwMzE4MTAwMDAwWhcNMjkwMzE4 + MTAwMDAwWjBMMSAwHgYDVQQLExdHbG9iYWxTaWduIFJvb3QgQ0EgLSBSMzETMBEG + A1UEChMKR2xvYmFsU2lnbjETMBEGA1UEAxMKR2xvYmFsU2lnbjCCASIwDQYJKoZI + hvcNAQEBBQADggEPADCCAQoCggEBAMwldpB5BngiFvXAg7aEyiie/QV2EcWtiHL8 + RgJDx7KKnQRfJMsuS+FggkbhUqsMgUdwbN1k0ev1LKMPgj0MK66X17YUhhB5uzsT + gHeMCOFJ0mpiLx9e+pZo34knlTifBtc+ycsmWQ1z3rDI6SYOgxXG71uL0gRgykmm + KPZpO/bLyCiR5Z2KYVc3rHQU3HTgOu5yLy6c+9C7v/U9AOEGM+iCK65TpjoWc4zd + QQ4gOsC0p6Hpsk+QLjJg6VfLuQSSaGjlOCZgdbKfd/+RFO+uIEn8rUAVSNECMWEZ + XriX7613t2Saer9fwRPvm2L7DWzgVGkWqQPabumDk3F2xmmFghcCAwEAAaNCMEAw + DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFI/wS3+o + LkUkrk1Q+mOai97i3Ru8MA0GCSqGSIb3DQEBCwUAA4IBAQBLQNvAUKr+yAzv95ZU + RUm7lgAJQayzE4aGKAczymvmdLm6AC2upArT9fHxD4q/c2dKg8dEe3jgr25sbwMp + jjM5RcOO5LlXbKr8EpbsU8Yt5CRsuZRj+9xTaGdWPoO4zzUhw8lo/s7awlOqzJCK + 6fBdRoyV3XpYKBovHd7NADdBj+1EbddTKJd+82cEHhXXipa0095MJ6RMG3NzdvQX + mcIfeg7jLQitChws/zyrVQ4PkX4268NXSb7hLi18YIvDQVETI53O9zJrlAGomecs + Mx86OyXShkDOOyyGeMlhLxS67ttVb9+E7gUJTb0o2HLO02JQZR7rkpeDMdmztcpH + WD9f + -----END CERTIFICATE----- + +providers: + aliyun: + # ---------------------------------------------------------------------- + # PLACEHOLDER — replace with real Lantern origins mapped to the Aliyun CDN + # distribution domains (the CNAME targets, typically *.w.kunlunaq.com or a + # custom CNAME) you create when onboarding each origin to Aliyun CDN. + # ---------------------------------------------------------------------- + hostaliases: + config.getiantem.org: REPLACE-ME.w.kunlunaq.com + api.getiantem.org: REPLACE-ME.w.kunlunaq.com + # testurl must resolve to an Aliyun-onboarded host that returns HTTP 202 + # on POST (see verifyWithPost). Point this at your distribution's /ping. + testurl: https://REPLACE-ME.w.kunlunaq.com/ping + # frontingsnis drives the wire SNI. ExpandedProvider regenerates each + # masquerade's SNI from this list (deterministic by IP hash), so the + # per-masquerade `sni:` values below are placeholders and get overwritten. + # NOTE: a non-empty country code MUST be passed (WithCountryCode) for SNI + # to be emitted; otherwise the no-SNI fallback verifies the cert against + # the masquerade Domain (img.alicdn.com), which also works here. + frontingsnis: + default: + usearbitrarysnis: true + arbitrarysnis: + - img.alicdn.com + - gw.alicdn.com + - a.alicdn.com + # Masquerades: Aliyun edge IPs verified 2026-06-23 to present the Alibaba + # *.tbcdn.cn cert. The upstream pipeline should harvest a much larger pool + # from in-region vantage points; this is a seed set across 3 /24 blocks. + masquerades: + - domain: img.alicdn.com + ipaddress: 155.102.54.137 + sni: "" + verifyhostname: null + - domain: img.alicdn.com + ipaddress: 155.102.54.138 + sni: "" + verifyhostname: null + - domain: img.alicdn.com + ipaddress: 163.181.66.200 + sni: "" + verifyhostname: null + - domain: img.alicdn.com + ipaddress: 163.181.66.201 + sni: "" + verifyhostname: null + - domain: img.alicdn.com + ipaddress: 8.25.82.183 + sni: "" + verifyhostname: null + - domain: img.alicdn.com + ipaddress: 8.25.82.184 + sni: "" + verifyhostname: null diff --git a/aliyun_live_test.go b/aliyun_live_test.go new file mode 100644 index 0000000..ec84fde --- /dev/null +++ b/aliyun_live_test.go @@ -0,0 +1,92 @@ +package domainfront + +import ( + "context" + "io" + "net/http" + "os" + "strings" + "testing" + "time" + + utls "github.com/refraction-networking/utls" + "github.com/stretchr/testify/require" +) + +// TestAliyunProviderLive exercises aliyun-provider.yaml against the real Aliyun +// CDN. It proves the security-critical path end to end: the config's GlobalSign +// root verifies the live Alibaba *.tbcdn.cn chain, the generated SNI is accepted +// by the edge, and the edge routes a *cross-organization* Host (Bilibili's +// s1.hdslb.com) over a TLS session that presents Alibaba's certificate — i.e. +// domain fronting works through this library's own dial + verify code. +// +// Skipped by default (it talks to the public internet). Run with: +// +// DOMAINFRONT_LIVE=1 go test -run TestAliyunProviderLive -v +func TestAliyunProviderLive(t *testing.T) { + if os.Getenv("DOMAINFRONT_LIVE") == "" { + t.Skip("live network test; set DOMAINFRONT_LIVE=1 to run") + } + + raw, err := os.ReadFile("aliyun-provider.yaml") + require.NoError(t, err) + + cfg, err := ParseConfigYAML(raw) + require.NoError(t, err) + + // The GlobalSign root parses into a usable pool. + pool, err := cfg.CertPool() + require.NoError(t, err) + + // Expand with a country code so FrontingSNIs drives the wire SNI + // (config.go only emits SNI when countryCode != ""). + p := ExpandedProvider(cfg.Providers["aliyun"], "cn") + require.NotEmpty(t, p.Masquerades) + + const crossOrgHost = "s1.hdslb.com" // Bilibili — unrelated to Alibaba + + // Phase 1: prove the security-critical path through the library's own + // dialFront — the config's GlobalSign root verifies the live Alibaba + // *.tbcdn.cn chain under the production Chrome_131 ClientHello, for every + // masquerade, using the SNI that FrontingSNIs generated. + // For each masquerade: dial with the production Chrome_131 ClientHello + // (which Aliyun answers with HTTP/2 over ALPN), then drive the library's + // real doRequest — proving the h2 transport path fronts a cross-org Host + // (Bilibili) over a TLS session bearing Alibaba's certificate. + var dialed int + var frontedOK bool + for _, m := range p.Masquerades { + require.NotEmpty(t, m.SNI, "expanded masquerade should carry an SNI") + + f := newFront(m, "aliyun") + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + res := dialFront(ctx, f, pool, utls.HelloChrome_131, NetDialer{}) + if res.err != nil { + cancel() + t.Logf("dial %s (SNI %s) failed: %v", m.IpAddress, m.SNI, res.err) + continue + } + dialed++ + proto := negotiatedProtocol(res.conn) + t.Logf("TLS+verify OK: ip=%s sni=%s alpn=%s (cert chained to config's GlobalSign root)", m.IpAddress, m.SNI, proto) + + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+crossOrgHost+"/", nil) + resp, rerr := (&roundTripper{}).doRequest(req, res.conn, crossOrgHost, nil) + if rerr != nil { + res.conn.Close() + cancel() + t.Logf("fronted GET via %s failed: %v", m.IpAddress, rerr) + continue + } + body, _ := io.ReadAll(io.LimitReader(resp.Body, 256)) + resp.Body.Close() // h2Body close tears down the conn + cancel() + t.Logf("fronted %s via %s (SNI %s, %s): HTTP %d, proto=HTTP/%d, body=%q", + crossOrgHost, m.IpAddress, m.SNI, proto, resp.StatusCode, resp.ProtoMajor, strings.TrimSpace(string(body))) + if resp.StatusCode == http.StatusOK { + frontedOK = true + } + } + require.NotZero(t, dialed, "no Aliyun edge IP completed TLS + GlobalSign verification") + require.True(t, frontedOK, "no edge served the cross-org Host (%s) — h2 fronting did not route", crossOrgHost) +} From 4421a649d284b758f05aef3ca56194aac95ae288 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Mon, 22 Jun 2026 22:12:05 -0600 Subject: [PATCH 2/7] Address Copilot review: enforce verifyhostname, trim ignored fields, guard test - Set provider-level verifyhostname: img.alicdn.com so the SNI path enforces leaf hostname verification instead of chain-only (which would accept any GlobalSign-R3-chained cert). All masquerade edges serve Alibaba's *.tbcdn.cn cert (SAN *.alicdn.com), so the single value validates every rotating frontingsni (img/gw/a.alicdn.com). - Drop per-masquerade sni/verifyhostname: ExpandedProvider ignores them (it propagates the provider-level values), so listing them was misleading. - Guard the live test with require.Contains before indexing Providers["aliyun"] to fail clearly instead of a nil-deref panic if the key changes. Co-Authored-By: Claude Opus 4.8 (1M context) --- aliyun-provider.yaml | 48 ++++++++++++++++++++++---------------------- aliyun_live_test.go | 1 + 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/aliyun-provider.yaml b/aliyun-provider.yaml index 3d30412..5ba030d 100644 --- a/aliyun-provider.yaml +++ b/aliyun-provider.yaml @@ -1,11 +1,12 @@ # Alibaba Cloud (Aliyun) CDN domain-fronting provider. # # Built & verified 2026-06-23 (US vantage point). Aliyun CDN edges route by the -# HTTP Host header and ignore the TLS SNI for origin selection, so a censor doing -# SNI filtering sees only the innocent front domain (img.alicdn.com) while the -# request reaches the real target. Confirmed cross-organization: a TLS session -# bearing Alibaba's *.tbcdn.cn cert (SNI img.alicdn.com) reached Bilibili -# (s1.hdslb.com) and Momo (img.momocdn.com) origins purely via the Host header. +# HTTP Host header (h1) / :authority (h2) and ignore the TLS SNI for origin +# selection, so a censor doing SNI filtering sees only the innocent front domain +# (img.alicdn.com) while the request reaches the real target. Confirmed +# cross-organization: a TLS session bearing Alibaba's *.tbcdn.cn cert (SNI +# img.alicdn.com) reached Bilibili (s1.hdslb.com) and Momo (img.momocdn.com) +# origins purely via the Host header. # # Two operational constraints discovered: # 1. TLS layer — the edge node must hold a cert valid for the SNI you present. @@ -60,12 +61,20 @@ providers: # testurl must resolve to an Aliyun-onboarded host that returns HTTP 202 # on POST (see verifyWithPost). Point this at your distribution's /ping. testurl: https://REPLACE-ME.w.kunlunaq.com/ping + # Pin TLS verification to a real Alibaba cert. Because SNI is set (see + # frontingsnis), dialFront takes the hostname-verifying path: without a + # verifyhostname it would be chain-only (any GlobalSign-R3-chained cert + # would pass). Every masquerade edge serves Alibaba's *.tbcdn.cn cert, + # whose SANs include *.alicdn.com, so this single value validates the + # leaf for all of the rotating frontingsnis (img/gw/a.alicdn.com). + # NOTE: ExpandedProvider applies this provider-level value to every + # masquerade; per-masquerade verifyhostname is NOT propagated. + verifyhostname: img.alicdn.com # frontingsnis drives the wire SNI. ExpandedProvider regenerates each - # masquerade's SNI from this list (deterministic by IP hash), so the - # per-masquerade `sni:` values below are placeholders and get overwritten. - # NOTE: a non-empty country code MUST be passed (WithCountryCode) for SNI - # to be emitted; otherwise the no-SNI fallback verifies the cert against - # the masquerade Domain (img.alicdn.com), which also works here. + # masquerade's SNI from this list (deterministic by IP hash). A non-empty + # country code MUST be passed (WithCountryCode) for SNI to be emitted; + # otherwise the no-SNI fallback verifies the cert against the masquerade + # Domain (img.alicdn.com), which also works here. frontingsnis: default: usearbitrarysnis: true @@ -74,30 +83,21 @@ providers: - gw.alicdn.com - a.alicdn.com # Masquerades: Aliyun edge IPs verified 2026-06-23 to present the Alibaba - # *.tbcdn.cn cert. The upstream pipeline should harvest a much larger pool - # from in-region vantage points; this is a seed set across 3 /24 blocks. + # *.tbcdn.cn cert. Each entry is just domain + ipaddress — SNI comes from + # frontingsnis and the verify hostname from the provider-level + # verifyhostname above (per-masquerade sni/verifyhostname are ignored by + # ExpandedProvider). The upstream pipeline should harvest a much larger + # pool from in-region vantage points; this is a seed set across 3 /24s. masquerades: - domain: img.alicdn.com ipaddress: 155.102.54.137 - sni: "" - verifyhostname: null - domain: img.alicdn.com ipaddress: 155.102.54.138 - sni: "" - verifyhostname: null - domain: img.alicdn.com ipaddress: 163.181.66.200 - sni: "" - verifyhostname: null - domain: img.alicdn.com ipaddress: 163.181.66.201 - sni: "" - verifyhostname: null - domain: img.alicdn.com ipaddress: 8.25.82.183 - sni: "" - verifyhostname: null - domain: img.alicdn.com ipaddress: 8.25.82.184 - sni: "" - verifyhostname: null diff --git a/aliyun_live_test.go b/aliyun_live_test.go index ec84fde..26f5419 100644 --- a/aliyun_live_test.go +++ b/aliyun_live_test.go @@ -40,6 +40,7 @@ func TestAliyunProviderLive(t *testing.T) { // Expand with a country code so FrontingSNIs drives the wire SNI // (config.go only emits SNI when countryCode != ""). + require.Contains(t, cfg.Providers, "aliyun", "aliyun-provider.yaml must define the 'aliyun' provider") p := ExpandedProvider(cfg.Providers["aliyun"], "cn") require.NotEmpty(t, p.Masquerades) From 008f0cb5ad76b648b3a4870ee049ef2242519855 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 23 Jun 2026 07:02:35 -0600 Subject: [PATCH 3/7] Address Copilot review: assert h2 path in live test, lowercase placeholders - The live test counted any HTTP 200 as success even if ALPN had fallen back to http/1.1, so it didn't actually prove the HTTP/2 fronting path. Now require that at least one cross-org front succeeded with proto=="h2" and an HTTP/2 response (frontedOverH2). - Lowercase the REPLACE-ME placeholder hostnames (replace-me.w.kunlunaq.com): ExpandedProvider doesn't lowercase alias values, and lowercase matches the README examples and avoids case-sensitive host routing surprises. Co-Authored-By: Claude Opus 4.8 (1M context) --- aliyun-provider.yaml | 6 +++--- aliyun_live_test.go | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/aliyun-provider.yaml b/aliyun-provider.yaml index 5ba030d..83a2ecc 100644 --- a/aliyun-provider.yaml +++ b/aliyun-provider.yaml @@ -56,11 +56,11 @@ providers: # custom CNAME) you create when onboarding each origin to Aliyun CDN. # ---------------------------------------------------------------------- hostaliases: - config.getiantem.org: REPLACE-ME.w.kunlunaq.com - api.getiantem.org: REPLACE-ME.w.kunlunaq.com + config.getiantem.org: replace-me.w.kunlunaq.com + api.getiantem.org: replace-me.w.kunlunaq.com # testurl must resolve to an Aliyun-onboarded host that returns HTTP 202 # on POST (see verifyWithPost). Point this at your distribution's /ping. - testurl: https://REPLACE-ME.w.kunlunaq.com/ping + testurl: https://replace-me.w.kunlunaq.com/ping # Pin TLS verification to a real Alibaba cert. Because SNI is set (see # frontingsnis), dialFront takes the hostname-verifying path: without a # verifyhostname it would be chain-only (any GlobalSign-R3-chained cert diff --git a/aliyun_live_test.go b/aliyun_live_test.go index 26f5419..75fa23b 100644 --- a/aliyun_live_test.go +++ b/aliyun_live_test.go @@ -55,7 +55,7 @@ func TestAliyunProviderLive(t *testing.T) { // real doRequest — proving the h2 transport path fronts a cross-org Host // (Bilibili) over a TLS session bearing Alibaba's certificate. var dialed int - var frontedOK bool + var frontedOK, frontedOverH2 bool for _, m := range p.Masquerades { require.NotEmpty(t, m.SNI, "expanded masquerade should carry an SNI") @@ -86,8 +86,14 @@ func TestAliyunProviderLive(t *testing.T) { crossOrgHost, m.IpAddress, m.SNI, proto, resp.StatusCode, resp.ProtoMajor, strings.TrimSpace(string(body))) if resp.StatusCode == http.StatusOK { frontedOK = true + // Only count it as proving the h2 path when ALPN actually + // negotiated h2 and the response came back as HTTP/2. + if proto == "h2" && resp.ProtoMajor == 2 { + frontedOverH2 = true + } } } require.NotZero(t, dialed, "no Aliyun edge IP completed TLS + GlobalSign verification") - require.True(t, frontedOK, "no edge served the cross-org Host (%s) — h2 fronting did not route", crossOrgHost) + require.True(t, frontedOK, "no edge served the cross-org Host (%s) — fronting did not route", crossOrgHost) + require.True(t, frontedOverH2, "no edge fronted %s over HTTP/2 — the h2 path was not exercised", crossOrgHost) } From 15f2b872327669029da221db67c24f6b8da20e48 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 23 Jun 2026 07:10:54 -0600 Subject: [PATCH 4/7] Address Copilot review: check errors in live test Check the http.NewRequestWithContext and io.ReadAll errors instead of discarding them; require a clean body read before counting a front as successful, so a truncated/failed response can't be miscounted. Co-Authored-By: Claude Opus 4.8 (1M context) --- aliyun_live_test.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/aliyun_live_test.go b/aliyun_live_test.go index 75fa23b..e85e9db 100644 --- a/aliyun_live_test.go +++ b/aliyun_live_test.go @@ -71,7 +71,8 @@ func TestAliyunProviderLive(t *testing.T) { proto := negotiatedProtocol(res.conn) t.Logf("TLS+verify OK: ip=%s sni=%s alpn=%s (cert chained to config's GlobalSign root)", m.IpAddress, m.SNI, proto) - req, _ := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+crossOrgHost+"/", nil) + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://"+crossOrgHost+"/", nil) + require.NoError(t, err) resp, rerr := (&roundTripper{}).doRequest(req, res.conn, crossOrgHost, nil) if rerr != nil { res.conn.Close() @@ -79,12 +80,14 @@ func TestAliyunProviderLive(t *testing.T) { t.Logf("fronted GET via %s failed: %v", m.IpAddress, rerr) continue } - body, _ := io.ReadAll(io.LimitReader(resp.Body, 256)) + body, readErr := io.ReadAll(io.LimitReader(resp.Body, 256)) resp.Body.Close() // h2Body close tears down the conn cancel() - t.Logf("fronted %s via %s (SNI %s, %s): HTTP %d, proto=HTTP/%d, body=%q", - crossOrgHost, m.IpAddress, m.SNI, proto, resp.StatusCode, resp.ProtoMajor, strings.TrimSpace(string(body))) - if resp.StatusCode == http.StatusOK { + t.Logf("fronted %s via %s (SNI %s, %s): HTTP %d, proto=HTTP/%d, body=%q (readErr=%v)", + crossOrgHost, m.IpAddress, m.SNI, proto, resp.StatusCode, resp.ProtoMajor, strings.TrimSpace(string(body)), readErr) + // Require a clean body read so a truncated/failed response can't be + // miscounted as a successful front. + if resp.StatusCode == http.StatusOK && readErr == nil { frontedOK = true // Only count it as proving the h2 path when ALPN actually // negotiated h2 and the response came back as HTTP/2. From 5547e86f8d710f53c86c73b445293e4b9c02e96a Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 23 Jun 2026 12:48:58 -0600 Subject: [PATCH 5/7] aliyun-provider: real validated hostaliases (DCDN distributions live) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the REPLACE-ME placeholders with the real Aliyun DCDN accelerated domains, now created and validated end-to-end (2026-06-23): fronting each via img.alicdn.com returns byte-identical responses to the canonical origin. config.getiantem.org -> config.dcdn.getiantem.org api.getiantem.org -> api.dcdn.getiantem.org geo.getiantem.org -> geo.dcdn.getiantem.org update.getlantern.org -> update.dcdn.getiantem.org The hostalias value is the accelerated domain (the fronted Host), not the *.w.kunlungr.com edge CNAME — the edge vhosts on the accelerated domain. Each distribution is overseas-scope, origin = the raw iantem.io backend (bypassing Cloudflare) with back-to-origin SNI = origin and Host = service domain. testurl notes the 202-ping follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) --- aliyun-provider.yaml | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/aliyun-provider.yaml b/aliyun-provider.yaml index 83a2ecc..098bc67 100644 --- a/aliyun-provider.yaml +++ b/aliyun-provider.yaml @@ -14,8 +14,8 @@ # *.taobao.com, ...) is provisioned on ~every Aliyun node. # 2. The target Host must be an onboarded Aliyun CDN customer. A non-customer # Host (e.g. www.example.com) makes the edge silently drop the connection. -# => Lantern must onboard its own distribution to Aliyun CDN; the -# hostaliases + testurl below are PLACEHOLDERS until that exists. +# => Lantern's own DCDN distributions (config/api/geo/update.dcdn.getiantem.org) +# are onboarded and the hostaliases below are validated end-to-end. # # This file is a complete, standalone Config (parseable via ParseConfigYAML) so # it can be loaded directly for testing. The `aliyun:` block under `providers:` @@ -50,17 +50,24 @@ trustedcas: providers: aliyun: - # ---------------------------------------------------------------------- - # PLACEHOLDER — replace with real Lantern origins mapped to the Aliyun CDN - # distribution domains (the CNAME targets, typically *.w.kunlunaq.com or a - # custom CNAME) you create when onboarding each origin to Aliyun CDN. - # ---------------------------------------------------------------------- + # Origin -> Aliyun DCDN accelerated domain. The value is the *accelerated + # domain* (sent as the fronted Host), NOT the *.w.kunlungr.com edge CNAME: + # the edge vhosts on the accelerated domain (Host=CNAME returns 403). + # Each accelerated domain is an "overseas"-scope DCDN distribution whose + # origin is the raw iantem.io backend (api.iantem.io / update.iantem.io, + # bypassing Cloudflare) with back-to-origin SNI = that origin and + # back-to-origin Host = the service domain. Validated 2026-06-23: fronting + # each via img.alicdn.com returns byte-identical responses to the origin. hostaliases: - config.getiantem.org: replace-me.w.kunlunaq.com - api.getiantem.org: replace-me.w.kunlunaq.com - # testurl must resolve to an Aliyun-onboarded host that returns HTTP 202 - # on POST (see verifyWithPost). Point this at your distribution's /ping. - testurl: https://replace-me.w.kunlunaq.com/ping + config.getiantem.org: config.dcdn.getiantem.org + api.getiantem.org: api.dcdn.getiantem.org + geo.getiantem.org: geo.dcdn.getiantem.org + update.getlantern.org: update.dcdn.getiantem.org + # testurl is POSTed through a vetted front (verifyWithPost expects 202). + # NOTE: the services above answer /ping with 404/301, not 202 — a + # dedicated 202 ping distribution (cf. Akamai's fronted-ping) should be + # added before relying on runtime vetting; this points at one for now. + testurl: https://config.dcdn.getiantem.org/ping # Pin TLS verification to a real Alibaba cert. Because SNI is set (see # frontingsnis), dialFront takes the hostname-verifying path: without a # verifyhostname it would be chain-only (any GlobalSign-R3-chained cert From 669b4d48c9d80e372b7d593a23db8a5d79d9b107 Mon Sep 17 00:00:00 2001 From: Adam Fisk Date: Tue, 23 Jun 2026 19:30:02 -0600 Subject: [PATCH 6/7] aliyun live test: log connection/body close errors (CodeRabbit) Capture and log the errors from res.conn.Close() and resp.Body.Close() in TestAliyunProviderLive rather than discarding them, matching the test's existing t.Logf diagnostics and satisfying errcheck. Co-Authored-By: Claude Opus 4.8 --- aliyun_live_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/aliyun_live_test.go b/aliyun_live_test.go index e85e9db..b57cb3a 100644 --- a/aliyun_live_test.go +++ b/aliyun_live_test.go @@ -75,13 +75,17 @@ func TestAliyunProviderLive(t *testing.T) { require.NoError(t, err) resp, rerr := (&roundTripper{}).doRequest(req, res.conn, crossOrgHost, nil) if rerr != nil { - res.conn.Close() + if cerr := res.conn.Close(); cerr != nil { + t.Logf("close conn for %s failed: %v", m.IpAddress, cerr) + } cancel() t.Logf("fronted GET via %s failed: %v", m.IpAddress, rerr) continue } body, readErr := io.ReadAll(io.LimitReader(resp.Body, 256)) - resp.Body.Close() // h2Body close tears down the conn + if cerr := resp.Body.Close(); cerr != nil { // h2Body close tears down the conn + t.Logf("close response body for %s failed: %v", m.IpAddress, cerr) + } cancel() t.Logf("fronted %s via %s (SNI %s, %s): HTTP %d, proto=HTTP/%d, body=%q (readErr=%v)", crossOrgHost, m.IpAddress, m.SNI, proto, resp.StatusCode, resp.ProtoMajor, strings.TrimSpace(string(body)), readErr) From fd616da234b4086489258e83a7126232aee7f2a9 Mon Sep 17 00:00:00 2001 From: Myles Horton Date: Tue, 23 Jun 2026 19:37:11 -0600 Subject: [PATCH 7/7] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- aliyun-provider.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/aliyun-provider.yaml b/aliyun-provider.yaml index 098bc67..1aa3855 100644 --- a/aliyun-provider.yaml +++ b/aliyun-provider.yaml @@ -58,6 +58,9 @@ providers: # bypassing Cloudflare) with back-to-origin SNI = that origin and # back-to-origin Host = the service domain. Validated 2026-06-23: fronting # each via img.alicdn.com returns byte-identical responses to the origin. + # NOTE: These hostaliases and testurl are real, validated mappings. + # aliyun_live_test.go reads this file directly; if you intend placeholders, + # update the PR description and replace these with clearly marked REPLACE-ME values. hostaliases: config.getiantem.org: config.dcdn.getiantem.org api.getiantem.org: api.dcdn.getiantem.org