Skip to content
Merged
113 changes: 113 additions & 0 deletions aliyun-provider.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Alibaba Cloud (Aliyun) CDN domain-fronting provider.
#
# Built & verified 2026-06-23 (US vantage point). Aliyun CDN edges route by the
# 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.
# 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'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:`
# 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:
# 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.
# 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.
Comment on lines +61 to +63
hostaliases:
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
Comment thread
Copilot marked this conversation as resolved.
# 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). 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. 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
- domain: img.alicdn.com
ipaddress: 155.102.54.138
- domain: img.alicdn.com
ipaddress: 163.181.66.200
- domain: img.alicdn.com
ipaddress: 163.181.66.201
- domain: img.alicdn.com
ipaddress: 8.25.82.183
- domain: img.alicdn.com
ipaddress: 8.25.82.184
106 changes: 106 additions & 0 deletions aliyun_live_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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 != "").
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)

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, frontedOverH2 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, 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 {
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))
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)
// 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.
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) — fronting did not route", crossOrgHost)
require.True(t, frontedOverH2, "no edge fronted %s over HTTP/2 — the h2 path was not exercised", crossOrgHost)
}
Loading