z2k/mtproxy-client/dcmap.go
Necronicle 9c5ffa77ca feat: IPv6 support, web panel, ECH detection, integration tests
IPv6 (mtproxy-client):
- listener.go: dual-stack SO_ORIGINAL_DST — tries IPv4 first, falls
  back to IPv6 via SOL_IPV6/IP6T_SO_ORIGINAL_DST (sockaddr_in6 parsing)
- dcmap.go: add Telegram IPv6 CIDR ranges (2001:b28:f23d::/48 → DC2,
  2001:b28:f23f::/48 → DC5, 2001:67c:4e8::/48 → DC2)
- main.go: resolveIP() prefers IPv4, accepts IPv6; connectWS uses "tcp"
  dual-stack dial as fallback
- transparent.go: resolveIPCached replaces resolveIPv4Cached, supports
  both address families
- Tests: TestLookupDC_IPv6 covers all new ranges

Web monitoring panel:
- z2k-webpanel.sh: CGI script for busybox httpd with dark theme,
  service status, strategies, autocircular state, logs, system info,
  action buttons (restart/stop/start/clearstate), auto-refresh 30s
- z2k-webpanel-install.sh: installer for busybox httpd setup
- Integrated into install.sh and z2k.sh bootstrap downloads

ECH (Encrypted Client Hello) support:
- z2k_detect_ech(): detects TLS extension type 0xfe0d in ClientHello
- z2k_ech_passthrough(): desync action that skips processing when ECH
  is present (DPI cannot see SNI, desync unnecessary)
- z2k_strategy_profile(): latency/success tracking per strategy

Lua hardening:
- TOCTOU in file permission checks documented — actual safety via
  lock+rename pattern (already correct, added explanation comment)

Integration test framework (86 tests total):
- test_config_official.sh: NFQWS2_OPT generation, Austerus mode,
  circular nld2 injection, failure detector injection
- test_strategies.sh: strategy parsing, empty/malformed input handling,
  category file creation, get_strategy retrieval
- test_validator.sh: config validation, port ranges, hostlist checks,
  missing config/variables detection
- run_all.sh: test runner with summary

Other:
- UPSTREAM_PROPOSALS.md: 6 improvement proposals for bol-van/zapret2
- Fix grep -c whitespace in generate_strategies_conf

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-11 01:17:22 +03:00

101 lines
2.2 KiB
Go

package main
import "net"
// dcEntry maps a CIDR to a Telegram DC number.
type dcEntry struct {
cidr *net.IPNet
dc int16
}
var dcTable []dcEntry
func init() {
// Telegram DC IP ranges -> DC number.
// Negative DC = media DC (abs value is the DC).
entries := []struct {
cidr string
dc int16
}{
// DC1
{"149.154.175.0/24", 1},
// DC2
{"149.154.167.0/24", 2},
{"95.161.76.0/24", 2},
// DC3
{"149.154.175.100/32", 3},
// DC4
{"149.154.167.91/32", 4},
// DC5
{"149.154.171.0/24", 5},
{"91.108.56.0/22", 5},
// General Telegram ranges (default to DC2)
{"91.108.4.0/22", 2},
{"91.108.8.0/22", 2},
{"91.108.12.0/22", 2},
{"91.108.16.0/22", 2},
{"91.108.20.0/22", 2},
{"149.154.160.0/20", 2},
{"185.76.151.0/24", 2},
{"91.105.192.0/23", 2},
{"95.161.64.0/20", 2},
// IPv6 Telegram ranges
{"2001:b28:f23d::/48", 2}, // DC2 main IPv6
{"2001:b28:f23f::/48", 5}, // DC5 IPv6
{"2001:67c:4e8::/48", 2}, // General Telegram IPv6
}
for _, e := range entries {
_, cidr, err := net.ParseCIDR(e.cidr)
if err != nil {
continue
}
dcTable = append(dcTable, dcEntry{cidr: cidr, dc: e.dc})
}
}
// LookupDC returns the Telegram DC number for the given IP.
// Returns 2 (default DC) if no match found. Supports both IPv4 and IPv6.
func LookupDC(ip net.IP) int16 {
isV6 := ip.To4() == nil
if !isV6 {
// IPv4: check most specific first (single IPs for DC3/DC4).
for _, e := range dcTable {
ones, _ := e.cidr.Mask.Size()
if ones == 32 && e.cidr.IP.Equal(ip) {
return e.dc
}
}
}
// Check all CIDR ranges (both v4 and v6).
// For IPv4, skip /32 (already checked above). For IPv6, check all.
bestOnes := -1
bestDC := int16(0)
for _, e := range dcTable {
ones, bits := e.cidr.Mask.Size()
if !isV6 && ones == 32 {
continue // already handled above for v4
}
if e.cidr.Contains(ip) {
// Pick the most specific (longest prefix) match.
// Normalize: compare relative specificity (ones out of bits).
// For same address family, just compare ones directly.
specificity := ones
if bits == 128 {
// IPv6 range
specificity = ones
}
if specificity > bestOnes {
bestOnes = specificity
bestDC = e.dc
}
}
}
if bestOnes >= 0 {
return bestDC
}
return 2
}