// main.go // camostream with shim-decoy RPS scheduling and TCP PCAP (size-capped) debug capture. // UDP/TCP, RTP-ish masking (UDP only), fps profiles, bitrate shaping, decoy insertion, // AES-GCM optional, selftest, and PCAP (LINKTYPE_RAW, IPv4/UDP|TCP). // For internal security testing only. package main import ( "context" "crypto/aes" "crypto/cipher" cryptoRand "crypto/rand" "encoding/binary" "encoding/hex" "errors" "expvar" "flag" "fmt" "io" "math" "math/rand" "net" "net/http" "os" "os/signal" "strings" "sync" "sync/atomic" "syscall" "time" ) /* ============ Metrics ============ */ var ( metricBytesUp = expvar.NewInt("bytes_up") metricBytesDown = expvar.NewInt("bytes_down") metricFramesUp = expvar.NewInt("frames_up") metricFramesDown = expvar.NewInt("frames_down") metricDecoyDropped = expvar.NewInt("decoy_dropped") metricSessions = expvar.NewInt("sessions_active") metricRTCPsrSent = expvar.NewInt("rtcp_sr_sent") metricRTCPrrSent = expvar.NewInt("rtcp_rr_sent") metricRTPkeepSent = expvar.NewInt("rtp_keepalive_sent") metricSTUNSent = expvar.NewInt("stun_sent") metricShimDecoySent = expvar.NewInt("shim_decoy_sent") ) /* ============ CLI / Config ============ */ type Role string type Mode string type Wire string const ( RoleServer Role = "server" RoleClient Role = "client" RoleSelf Role = "selftest" ModeUDP Mode = "udp" ModeTCP Mode = "tcp" WireShim Wire = "shim" // ShimHeader + Payload WireRTPish Wire = "rtpish" // RTP(12) + Shim + Payload (UDP only) ) type Config struct { Role Role Mode Mode Listen string ServerAddr string ForwardAddr string Wire Wire FPS int GOPMs int BitrateMbps int // shim-decoy controls (both TCP/UDP): DecoyPct int // legacy percentage DecoyRps float64 // per-second rate (sliding window); overrides DecoyPct when >0 // Extra decoys (UDP only): // Either use per-second rate (recommended), or fallback to percentage if rps == 0. RTCPSrPct int // legacy percentage for RTCP SR RTCPRrPct int // legacy percentage for RTCP RR RTPKeepPct int // legacy percentage for pure RTP keepalive STUNPct int // legacy percentage for STUN Binding RTCPSrRps float64 // per-second rate (sliding window) for RTCP SR RTCPRrRps float64 // per-second rate for RTCP RR RTPKeepRps float64 // per-second rate for pure RTP keepalive STUNRps float64 // per-second rate for STUN Binding AESKeyHex string PaceMs int JitterPct int FrameMin int FrameMax int UDPRcvBuf int UDPSndBuf int SessID uint32 MetricsAddr string PcapPath string PcapMaxMB int // Max PCAP size in MB (0 = unlimited) SelfDur time.Duration LogLevel string LogDrop bool } func defaultConfig() *Config { return &Config{ Role: RoleServer, Mode: ModeUDP, Listen: ":9001", ServerAddr: "127.0.0.1:9001", ForwardAddr: "127.0.0.1:18081", Wire: WireRTPish, FPS: 60, GOPMs: 2000, BitrateMbps: 20, // shim decoy DecoyPct: 10, DecoyRps: 0, // legacy percentages (only used if the corresponding RPS is 0) RTCPSrPct: 0, RTCPRrPct: 0, RTPKeepPct: 0, STUNPct: 0, // RPS default off (0). Set >0 to enable rate scheduling. RTCPSrRps: 0, RTCPRrRps: 0, RTPKeepRps: 0, STUNRps: 0, PaceMs: 16, // TCP: base rhythm JitterPct: 30, // 0~100 FrameMin: 800, // TCP split FrameMax: 1400, // TCP split UDPRcvBuf: 1 << 20, UDPSndBuf: 1 << 20, SessID: 0, MetricsAddr: ":9100", PcapPath: "", PcapMaxMB: 50, SelfDur: 15 * time.Second, LogLevel: "info", LogDrop: false, } } func parseFlags() *Config { cfg := defaultConfig() flag.StringVar((*string)(&cfg.Role), "role", string(cfg.Role), "server|client|selftest") flag.StringVar((*string)(&cfg.Mode), "mode", string(cfg.Mode), "udp|tcp") flag.StringVar(&cfg.Listen, "listen", cfg.Listen, "listen addr") flag.StringVar(&cfg.ServerAddr, "server", cfg.ServerAddr, "server addr (client)") flag.StringVar(&cfg.ForwardAddr, "forward", cfg.ForwardAddr, "forward addr (server)") flag.StringVar((*string)(&cfg.Wire), "wire", string(cfg.Wire), "shim|rtpish (udp)") flag.IntVar(&cfg.FPS, "fps", cfg.FPS, "60|120") flag.IntVar(&cfg.GOPMs, "gop", cfg.GOPMs, "keyframe interval ms") flag.IntVar(&cfg.BitrateMbps, "bitrate-mbps", cfg.BitrateMbps, "20|40 etc.") // shim decoy flag.IntVar(&cfg.DecoyPct, "decoy", cfg.DecoyPct, "shim-decoy percent 0-100 (ignored if -decoy-rps>0)") flag.Float64Var(&cfg.DecoyRps, "decoy-rps", cfg.DecoyRps, "shim-decoy frames per second (overrides -decoy)") // Extra decoys legacy percentages (UDP only; used only if corresponding RPS==0) flag.IntVar(&cfg.RTCPSrPct, "rtcp-sr-pct", cfg.RTCPSrPct, "insert RTCP SR decoy percent 0-100 (UDP only; ignored if -rtcp-sr-rps>0)") flag.IntVar(&cfg.RTCPRrPct, "rtcp-rr-pct", cfg.RTCPRrPct, "insert RTCP RR decoy percent 0-100 (UDP only; ignored if -rtcp-rr-rps>0)") flag.IntVar(&cfg.RTPKeepPct, "rtpkeep-pct", cfg.RTPKeepPct, "insert pure RTP keepalive decoy percent 0-100 (UDP only; ignored if -rtpkeep-rps>0)") flag.IntVar(&cfg.STUNPct, "stun-pct", cfg.STUNPct, "insert STUN Binding decoy percent 0-100 (UDP only; ignored if -stun-rps>0)") // Extra decoys per-second rates (UDP only; sliding window scheduling) flag.Float64Var(&cfg.RTCPSrRps, "rtcp-sr-rps", cfg.RTCPSrRps, "RTCP SR decoys per second (UDP only; overrides -rtcp-sr-pct)") flag.Float64Var(&cfg.RTCPRrRps, "rtcp-rr-rps", cfg.RTCPRrRps, "RTCP RR decoys per second (UDP only; overrides -rtcp-rr-pct)") flag.Float64Var(&cfg.RTPKeepRps, "rtpkeep-rps", cfg.RTPKeepRps, "pure RTP keepalive decoys per second (UDP only; overrides -rtpkeep-pct)") flag.Float64Var(&cfg.STUNRps, "stun-rps", cfg.STUNRps, "STUN Binding decoys per second (UDP only; overrides -stun-pct)") flag.StringVar(&cfg.AESKeyHex, "aes", cfg.AESKeyHex, "AES-GCM key hex (optional)") flag.IntVar(&cfg.PaceMs, "pace", cfg.PaceMs, "base pace ms (TCP)") flag.IntVar(&cfg.JitterPct, "jitter", cfg.JitterPct, "jitter percent") flag.IntVar(&cfg.FrameMin, "fmin", cfg.FrameMin, "tcp frame min") flag.IntVar(&cfg.FrameMax, "fmax", cfg.FrameMax, "tcp frame max") flag.IntVar(&cfg.UDPRcvBuf, "udp-rbuf", cfg.UDPRcvBuf, "udp read buffer") flag.IntVar(&cfg.UDPSndBuf, "udp-wbuf", cfg.UDPSndBuf, "udp write buffer") // sess flag (uint <-» uint32) sessFlag := uint(cfg.SessID) flag.UintVar(&sessFlag, "sess", sessFlag, "udp session id (0=random)") flag.StringVar(&cfg.MetricsAddr, "metrics", cfg.MetricsAddr, "metrics http addr") flag.StringVar(&cfg.PcapPath, "pcap", cfg.PcapPath, "pcap output path (IPv4/UDP RAW + TCP)") flag.IntVar(&cfg.PcapMaxMB, "pcap-max-mb", cfg.PcapMaxMB, "max PCAP size in MB (0=unlimited)") flag.DurationVar(&cfg.SelfDur, "duration", cfg.SelfDur, "selftest duration") flag.StringVar(&cfg.LogLevel, "log", cfg.LogLevel, "debug|info|warn|error") flag.BoolVar(&cfg.LogDrop, "showdrop", cfg.LogDrop, "log when dropping decoy") flag.Parse() cfg.SessID = uint32(sessFlag) if cfg.Mode == ModeTCP && cfg.Wire == WireRTPish { // 仅提示:RTP-ish 只在 UDP 生效 fmt.Println("[WARN] wire=rtpish is ignored in TCP mode.") } return cfg } /* ============ Logging ============ */ type LogLevel int const ( LDebug LogLevel = iota LInfo LWarn LError ) var gLogLevel = LInfo func setLog(s string) { switch strings.ToLower(s) { case "debug": gLogLevel = LDebug case "info": gLogLevel = LInfo case "warn": gLogLevel = LWarn case "error": gLogLevel = LError } } func logf(lv LogLevel, f string, a ...any) { if lv < gLogLevel { return } ts := time.Now().Format("15:04:05.000") p := [...]string{"DEBU", "INFO", "WARN", "ERRO"}[lv] fmt.Printf("%s [%s] %s\n", ts, p, fmt.Sprintf(f, a...)) } func must(err error) { if err != nil { logf(LError, "fatal: %v", err) os.Exit(2) } } /* ============ Random/Timing ============ */ var grnd *rand.Rand func init() { seed := time.Now().UnixNano() var b [8]byte if _, err := cryptoRand.Read(b[:]); err == nil { seed = int64(binary.BigEndian.Uint64(b[:])) } grnd = rand.New(rand.NewSource(seed)) } func randRange(min, max int) int { if max <= min { return min } return min + grnd.Intn(max-min+1) } func jitterSleep(paceMs, jitterPct int) { if paceMs <= 0 { return } j := float64(paceMs) * float64(jitterPct) / 100 delta := grnd.Float64()*2*j - j d := time.Duration(math.Max(0, float64(paceMs)+delta)) * time.Millisecond time.Sleep(d) } /* ============ Token Bucket (bitrate shaping) ============ */ type tokenBucket struct { rate int64 burst int64 tok int64 last time.Time mu sync.Mutex } func newTB(mbps int, burst int) *tokenBucket { if mbps <= 0 { return nil } return &tokenBucket{ rate: int64(mbps) * 1024 * 1024 / 8, // bytes/sec burst: int64(burst), last: time.Now(), } } func (tb *tokenBucket) wait(n int) { if tb == nil { return } tb.mu.Lock() defer tb.mu.Unlock() now := time.Now() el := now.Sub(tb.last).Seconds() tb.tok += int64(float64(tb.rate) * el) if tb.tok > tb.burst { tb.tok = tb.burst } tb.last = now need := int64(n) for tb.tok < need { def := float64(need-tb.tok) / float64(tb.rate) sleep := time.Duration(def*1000.0)*time.Millisecond + time.Millisecond tb.mu.Unlock() time.Sleep(sleep) tb.mu.Lock() now = time.Now() el = now.Sub(tb.last).Seconds() tb.tok += int64(float64(tb.rate) * el) if tb.tok > tb.burst { tb.tok = tb.burst } tb.last = now } tb.tok -= need } /* ============ Per-second sliding-window scheduler for decoys ============ */ type rateLimiter struct { rps float64 burst float64 tokens float64 last time.Time mu sync.Mutex } func newRL(rps float64) *rateLimiter { if rps <= 0 { return nil } burst := rps*1.5 + 1.0 // small burst to smooth boundary effects return &rateLimiter{rps: rps, burst: burst, tokens: burst, last: time.Now()} } // take up to max tokens (integer). returns how many allowed right now. func (rl *rateLimiter) takeMax(max int) int { if rl == nil { return 0 } rl.mu.Lock() defer rl.mu.Unlock() now := time.Now() el := now.Sub(rl.last).Seconds() rl.tokens += el * rl.rps if rl.tokens > rl.burst { rl.tokens = rl.burst } rl.last = now n := int(math.Floor(rl.tokens)) if n <= 0 { return 0 } if n > max { n = max } rl.tokens -= float64(n) return n } type decoyRL struct { sr *rateLimiter rr *rateLimiter keep *rateLimiter stun *rateLimiter } func newDecoyRL(cfg *Config, up bool) *decoyRL { // Split given total RPS 50/50 into up and down directions. f := 0.5 return &decoyRL{ sr: newRL(cfg.RTCPSrRps * f), rr: newRL(cfg.RTCPRrRps * f), keep: newRL(cfg.RTPKeepRps * f), stun: newRL(cfg.STUNRps * f), } } /* ============ Shim & RTP-ish headers ============ */ const ( magicConst uint32 = 0x5C10ADED version uint8 = 1 modeTCP uint8 = 1 modeUDP uint8 = 2 flagDecoy uint8 = 1 << 0 flagEnc uint8 = 1 << 1 ) type shimHeader struct { Magic uint32 Version uint8 Mode uint8 Flags uint8 Reserved uint8 SessionID uint32 TsMs uint32 Len uint32 } const shimLen = 20 func (h shimHeader) Marshal() []byte { b := make([]byte, shimLen) binary.BigEndian.PutUint32(b[0:4], h.Magic) b[4] = h.Version b[5] = h.Mode b[6] = h.Flags b[7] = h.Reserved binary.BigEndian.PutUint32(b[8:12], h.SessionID) binary.BigEndian.PutUint32(b[12:16], h.TsMs) binary.BigEndian.PutUint32(b[16:20], h.Len) return b } func parseShimFull(b []byte) (shimHeader, []byte, error) { if len(b) < shimLen { return shimHeader{}, nil, io.ErrUnexpectedEOF } h := shimHeader{ Magic: binary.BigEndian.Uint32(b[0:4]), Version: b[4], Mode: b[5], Flags: b[6], Reserved: b[7], SessionID: binary.BigEndian.Uint32(b[8:12]), TsMs: binary.BigEndian.Uint32(b[12:16]), Len: binary.BigEndian.Uint32(b[16:20]), } if h.Magic != magicConst || h.Version != version { return shimHeader{}, nil, errors.New("bad shim") } if int(h.Len) > len(b[shimLen:]) { return shimHeader{}, nil, errors.New("bad len") } return h, b[shimLen : shimLen+int(h.Len)], nil } // 仅解析头部(TCP 流模式) func parseShimHeader(hb []byte) (shimHeader, error) { if len(hb) < shimLen { return shimHeader{}, io.ErrUnexpectedEOF } h := shimHeader{ Magic: binary.BigEndian.Uint32(hb[0:4]), Version: hb[4], Mode: hb[5], Flags: hb[6], Reserved: hb[7], SessionID: binary.BigEndian.Uint32(hb[8:12]), TsMs: binary.BigEndian.Uint32(hb[12:16]), Len: binary.BigEndian.Uint32(hb[16:20]), } if h.Magic != magicConst || h.Version != version { return shimHeader{}, errors.New("bad shim header") } return h, nil } // RTP-ish (UDP only) type rtpState struct{ seq uint16; ssrc uint32 } func buildRTPHeader(seq uint16, ts uint32, pt uint8, marker bool, ssrc uint32) []byte { b := make([]byte, 12) b[0] = 0x80 // V=2 b[1] = pt if marker { b[1] |= 0x80 } binary.BigEndian.PutUint16(b[2:4], seq) binary.BigEndian.PutUint32(b[4:8], ts) binary.BigEndian.PutUint32(b[8:12], ssrc) return b } func stripRTP(b []byte) (pt uint8, marker bool, rest []byte, err error) { if len(b) < 12 { return 0, false, nil, io.ErrUnexpectedEOF } if (b[0]>>6)&0x3 != 2 { return 0, false, nil, errors.New("rtp ver") } marker = (b[1] & 0x80) != 0 pt = b[1] & 0x7F return pt, marker, b[12:], nil } /* ============ AES-GCM (optional) ============ */ type aeadBox struct{ aead cipher.AEAD } func newAEAD(hexKey string) (*aeadBox, error) { if strings.TrimSpace(hexKey) == "" { return nil, nil } kb, err := hex.DecodeString(hexKey) if err != nil { return nil, err } switch len(kb) { case 16, 24, 32: default: return nil, fmt.Errorf("aes key must be 16/24/32 bytes") } block, err := aes.NewCipher(kb) if err != nil { return nil, err } a, err := cipher.NewGCM(block) if err != nil { return nil, err } return &aeadBox{aead: a}, nil } func (b *aeadBox) seal(p []byte) (out []byte, nonce []byte, err error) { if b == nil { return p, nil, nil } nonce = make([]byte, b.aead.NonceSize()) if _, err = cryptoRand.Read(nonce); err != nil { return nil, nil, err } c := b.aead.Seal(nil, nonce, p, nil) return c, nonce, nil } func (b *aeadBox) open(nonce, c []byte) ([]byte, error) { if b == nil { return c, nil } return b.aead.Open(nil, nonce, c, nil) } /* ============ PCAP Writer (IPv4 RAW, LINKTYPE_RAW=101) ============ */ type pcapWriter struct { f *os.File mu sync.Mutex ipID uint16 limitBytes int64 written int64 tcpSeq map[string]uint32 } func newPCAP(path string, maxBytes int64) (*pcapWriter, error) { if strings.TrimSpace(path) == "" { return nil, nil } f, err := os.Create(path) if err != nil { return nil, err } gh := make([]byte, 24) binary.BigEndian.PutUint32(gh[0:4], 0xa1b2c3d4) binary.BigEndian.PutUint16(gh[4:6], 2) binary.BigEndian.PutUint16(gh[6:8], 4) binary.BigEndian.PutUint32(gh[16:20], 0xffff) binary.BigEndian.PutUint32(gh[20:24], 101) // LINKTYPE_RAW if _, err := f.Write(gh); err != nil { f.Close() return nil, err } return &pcapWriter{ f: f, ipID: uint16(rand.Intn(65535)), limitBytes: maxBytes, written: 24, // include global header tcpSeq: make(map[string]uint32), }, nil } func (w *pcapWriter) Close() { if w != nil && w.f != nil { _ = w.f.Close() } } func (w *pcapWriter) canWrite(n int) bool { if w == nil { return false } if w.limitBytes <= 0 { return true } return w.written+int64(n) <= w.limitBytes } func (w *pcapWriter) writeRecord(frame []byte) { if w == nil { return } w.mu.Lock() defer w.mu.Unlock() if !w.canWrite(16+len(frame)) { return } now := time.Now() pkth := make([]byte, 16) binary.BigEndian.PutUint32(pkth[0:4], uint32(now.Unix())) binary.BigEndian.PutUint32(pkth[4:8], uint32(now.Nanosecond()/1000)) binary.BigEndian.PutUint32(pkth[8:12], uint32(len(frame))) binary.BigEndian.PutUint32(pkth[12:16], uint32(len(frame))) _, _ = w.f.Write(pkth) _, _ = w.f.Write(frame) w.written += int64(16 + len(frame)) } func (w *pcapWriter) ipChecksum(hdr []byte) uint16 { var sum uint32 for i := 0; i < len(hdr); i += 2 { sum += uint32(binary.BigEndian.Uint16(hdr[i : i+2])) } for (sum >> 16) != 0 { sum = (sum & 0xffff) + (sum >> 16) } return ^uint16(sum) } func (w *pcapWriter) udpFrame(srcIP net.IP, srcPort int, dstIP net.IP, dstPort int, payload []byte) []byte { s4 := srcIP.To4() d4 := dstIP.To4() if s4 == nil || d4 == nil { return nil } udp := make([]byte, 8) binary.BigEndian.PutUint16(udp[0:2], uint16(srcPort)) binary.BigEndian.PutUint16(udp[2:4], uint16(dstPort)) binary.BigEndian.PutUint16(udp[4:6], uint16(8+len(payload))) udp[6] = 0; udp[7] = 0 ip := make([]byte, 20) ip[0] = (4 << 4) | 5 ip[1] = 0 total := 20 + 8 + len(payload) binary.BigEndian.PutUint16(ip[2:4], uint16(total)) w.ipID++ binary.BigEndian.PutUint16(ip[4:6], w.ipID) binary.BigEndian.PutUint16(ip[6:8], 0) ip[8] = 64 ip[9] = 17 // UDP copy(ip[12:16], s4) copy(ip[16:20], d4) cs := w.ipChecksum(ip) binary.BigEndian.PutUint16(ip[10:12], cs) frame := append(ip, udp...) frame = append(frame, payload...) return frame } func (w *pcapWriter) WriteUDP(srcIP net.IP, srcPort int, dstIP net.IP, dstPort int, payload []byte) { if w == nil { return } frame := w.udpFrame(srcIP, srcPort, dstIP, dstPort, payload) if frame == nil { return } w.writeRecord(frame) } func key4(srcIP net.IP, srcPort int, dstIP net.IP, dstPort int) string { return fmt.Sprintf("%s:%d>%s:%d", srcIP.String(), srcPort, dstIP.String(), dstPort) } func checksumTCP(srcIP, dstIP net.IP, tcpHdr, payload []byte) uint16 { // pseudo header + tcp header + payload s4 := srcIP.To4(); d4 := dstIP.To4() var sum uint32 // pseudo sum += uint32(binary.BigEndian.Uint16(s4[0:2])) sum += uint32(binary.BigEndian.Uint16(s4[2:4])) sum += uint32(binary.BigEndian.Uint16(d4[0:2])) sum += uint32(binary.BigEndian.Uint16(d4[2:4])) sum += uint32(6) // protocol tcpLen := len(tcpHdr) + len(payload) sum += uint32(tcpLen) // tcp header for i := 0; i < len(tcpHdr); i += 2 { sum += uint32(binary.BigEndian.Uint16(tcpHdr[i : i+2])) } // payload for i := 0; i+1 < len(payload); i += 2 { sum += uint32(binary.BigEndian.Uint16(payload[i : i+2])) } if len(payload)%2 == 1 { sum += uint32(uint16(payload[len(payload)-1]) << 8) } for (sum >> 16) != 0 { sum = (sum & 0xffff) + (sum >> 16) } return ^uint16(sum) } func (w *pcapWriter) tcpFrame(srcIP net.IP, srcPort int, dstIP net.IP, dstPort int, payload []byte) []byte { s4 := srcIP.To4(); d4 := dstIP.To4() if s4 == nil || d4 == nil { return nil } // maintain a fake seq space per 4-tuple for debug w.mu.Lock() key := key4(s4, srcPort, d4, dstPort) seq := w.tcpSeq[key] if seq == 0 { seq = uint32(rand.Int31()) } w.tcpSeq[key] = seq + uint32(len(payload)) w.mu.Unlock() tcp := make([]byte, 20) binary.BigEndian.PutUint16(tcp[0:2], uint16(srcPort)) binary.BigEndian.PutUint16(tcp[2:4], uint16(dstPort)) binary.BigEndian.PutUint32(tcp[4:8], seq) binary.BigEndian.PutUint32(tcp[8:12], 0) // ack=0 (we're not modeling handshake) tcp[12] = (5 << 4) // data offset=5 (20B) tcp[13] = 0x08 // PSH (no ACK to avoid bogus ack=0+ACK) binary.BigEndian.PutUint16(tcp[14:16], 65535) // window binary.BigEndian.PutUint16(tcp[16:18], 0) // checksum placeholder binary.BigEndian.PutUint16(tcp[18:20], 0) // urgent // IPv4 header ip := make([]byte, 20) ip[0] = (4 << 4) | 5 ip[1] = 0 total := 20 + 20 + len(payload) binary.BigEndian.PutUint16(ip[2:4], uint16(total)) w.ipID++ binary.BigEndian.PutUint16(ip[4:6], w.ipID) binary.BigEndian.PutUint16(ip[6:8], 0) ip[8] = 64 ip[9] = 6 // TCP copy(ip[12:16], s4) copy(ip[16:20], d4) // TCP checksum (needs pseudo header) csTCP := checksumTCP(s4, d4, tcp, payload) binary.BigEndian.PutUint16(tcp[16:18], csTCP) // IP checksum csIP := w.ipChecksum(ip) binary.BigEndian.PutUint16(ip[10:12], csIP) frame := append(ip, tcp...) frame = append(frame, payload...) return frame } func (w *pcapWriter) WriteTCP(srcIP net.IP, srcPort int, dstIP net.IP, dstPort int, payload []byte) { if w == nil { return } frame := w.tcpFrame(srcIP, srcPort, dstIP, dstPort, payload) if frame == nil { return } w.writeRecord(frame) } /* ============ Common runtime & helpers ============ */ type runtimeCtx struct { cfg *Config tb *tokenBucket ae *aeadBox pc *pcapWriter dropLog bool // Extra decoys upRL *decoyRL downRL *decoyRL // Shim decoy RPS shimUpRL *rateLimiter shimDownRL *rateLimiter } func runMetrics(addr string) { if addr == "" { return } go func() { _ = http.ListenAndServe(addr, nil) }() } func ip4OrLoopback(ip net.IP) net.IP { v4 := ip.To4() if v4 == nil || v4.IsUnspecified() { return net.ParseIP("127.0.0.1") } return v4 } func max(a, b int) int { if a > b { return a } ; return b } /* ============ Extra Decoys (UDP only): RTCP SR/RR, RTP keepalive, STUN ============ */ const ntpEpochOffset = 2208988800 // seconds between 1900 and 1970 func buildRTCP_SR(ssrc uint32, rtpTs uint32, pktCount uint32, octCount uint32) []byte { // RTCP SR (PT=200), RC=0, length=6 (7 words total: 28 bytes) b := make([]byte, 4+24) b[0] = 0x80 // V=2, P=0, RC=0 b[1] = 200 // SR binary.BigEndian.PutUint16(b[2:4], 6) binary.BigEndian.PutUint32(b[4:8], ssrc) now := time.Now() sec := uint32(uint64(now.Unix()) + ntpEpochOffset) frac := uint32(uint64(now.Nanosecond()) * (1<<32) / 1_000_000_000) binary.BigEndian.PutUint32(b[8:12], sec) binary.BigEndian.PutUint32(b[12:16], frac) binary.BigEndian.PutUint32(b[16:20], rtpTs) binary.BigEndian.PutUint32(b[20:24], pktCount) binary.BigEndian.PutUint32(b[24:28], octCount) return b } func buildRTCP_RR(ssrc uint32) []byte { // RTCP RR (PT=201), RC=0, length=1 (2 words total: 8 bytes) b := make([]byte, 4+4) b[0] = 0x80 // V=2, RC=0 b[1] = 201 // RR binary.BigEndian.PutUint16(b[2:4], 1) binary.BigEndian.PutUint32(b[4:8], ssrc) return b } func buildRTPKeepalive(seq uint16, ts uint32, pt uint8, ssrc uint32, payloadLen int) []byte { h := buildRTPHeader(seq, ts, pt, false, ssrc) if payloadLen < 0 { payloadLen = 0 } if payloadLen > 1400 { payloadLen = 1400 } if payloadLen == 0 { return h } p := make([]byte, payloadLen) _, _ = cryptoRand.Read(p) return append(h, p...) } func buildSTUNBindingRequest() []byte { b := make([]byte, 20) // Type: Binding Request 0x0001 binary.BigEndian.PutUint16(b[0:2], 0x0001) // Length: 0 (no attributes) binary.BigEndian.PutUint16(b[2:4], 0) // Magic cookie binary.BigEndian.PutUint32(b[4:8], 0x2112A442) // Transaction ID _, _ = cryptoRand.Read(b[8:20]) return b } func sendExtraDecoysUDP(conn *net.UDPConn, src *net.UDPAddr, dst *net.UDPAddr, rt *runtimeCtx, ssrc uint32, seq *uint16, ts *uint32, step int, upDirection bool) { srcIP := ip4OrLoopback(src.IP) dstIP := ip4OrLoopback(dst.IP) // Choose per-direction rate limiters var rl *decoyRL if upDirection { rl = rt.upRL } else { rl = rt.downRL } const maxBurstPerTick = 3 // avoid huge bursts on long silent gaps // --- RTCP SR --- if rl != nil && rl.sr != nil { n := rl.sr.takeMax(maxBurstPerTick) for i := 0; i < n; i++ { pktCount := uint32(metricFramesUp.Value()) octCount := uint32(metricBytesUp.Value()) sr := buildRTCP_SR(ssrc, *ts, pktCount, octCount) rt.tb.wait(len(sr) + 28) _, _ = conn.WriteToUDP(sr, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, sr) metricRTCPsrSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][rps] RTCP SR %dB (%s)", len(sr), dirStr(upDirection)) } } } else if rt.cfg.RTCPSrPct > 0 && grnd.Intn(100) < rt.cfg.RTCPSrPct { // fallback: percentage pktCount := uint32(metricFramesUp.Value()) octCount := uint32(metricBytesUp.Value()) sr := buildRTCP_SR(ssrc, *ts, pktCount, octCount) rt.tb.wait(len(sr) + 28) _, _ = conn.WriteToUDP(sr, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, sr) metricRTCPsrSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][pct] RTCP SR %dB (%s)", len(sr), dirStr(upDirection)) } } // --- RTCP RR --- if rl != nil && rl.rr != nil { n := rl.rr.takeMax(maxBurstPerTick) for i := 0; i < n; i++ { rr := buildRTCP_RR(ssrc) rt.tb.wait(len(rr) + 28) _, _ = conn.WriteToUDP(rr, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, rr) metricRTCPrrSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][rps] RTCP RR %dB (%s)", len(rr), dirStr(upDirection)) } } } else if rt.cfg.RTCPRrPct > 0 && grnd.Intn(100) < rt.cfg.RTCPRrPct { rr := buildRTCP_RR(ssrc) rt.tb.wait(len(rr) + 28) _, _ = conn.WriteToUDP(rr, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, rr) metricRTCPrrSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][pct] RTCP RR %dB (%s)", len(rr), dirStr(upDirection)) } } // --- pure RTP keepalive --- if rl != nil && rl.keep != nil { n := rl.keep.takeMax(maxBurstPerTick) for i := 0; i < n; i++ { *seq = *seq + 1 *ts = *ts + uint32(step) rtp := buildRTPKeepalive(*seq, *ts, 13, ssrc, randRange(0, 60)) rt.tb.wait(len(rtp) + 28) _, _ = conn.WriteToUDP(rtp, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, rtp) metricRTPkeepSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][rps] RTP keepalive %dB (%s)", len(rtp), dirStr(upDirection)) } } } else if rt.cfg.RTPKeepPct > 0 && grnd.Intn(100) < rt.cfg.RTPKeepPct { *seq = *seq + 1 *ts = *ts + uint32(step) rtp := buildRTPKeepalive(*seq, *ts, 13, ssrc, randRange(0, 60)) rt.tb.wait(len(rtp) + 28) _, _ = conn.WriteToUDP(rtp, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, rtp) metricRTPkeepSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][pct] RTP keepalive %dB (%s)", len(rtp), dirStr(upDirection)) } } // --- STUN Binding --- if rl != nil && rl.stun != nil { n := rl.stun.takeMax(maxBurstPerTick) for i := 0; i < n; i++ { stun := buildSTUNBindingRequest() rt.tb.wait(len(stun) + 28) _, _ = conn.WriteToUDP(stun, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, stun) metricSTUNSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][rps] STUN binding %dB (%s)", len(stun), dirStr(upDirection)) } } } else if rt.cfg.STUNPct > 0 && grnd.Intn(100) < rt.cfg.STUNPct { stun := buildSTUNBindingRequest() rt.tb.wait(len(stun) + 28) _, _ = conn.WriteToUDP(stun, dst) rt.pc.WriteUDP(srcIP, src.Port, dstIP, dst.Port, stun) metricSTUNSent.Add(1) if rt.dropLog { logf(LDebug, "[extra][pct] STUN binding %dB (%s)", len(stun), dirStr(upDirection)) } } } func dirStr(up bool) string { if up { return "up" } return "down" } /* ============ UDP Client/Server ============ */ func runUDPClient(ctx context.Context, rt *runtimeCtx) error { laddr, err := net.ResolveUDPAddr("udp", rt.cfg.Listen) if err != nil { return err } uc, err := net.ListenUDP("udp", laddr) if err != nil { return err } defer uc.Close() if rt.cfg.UDPRcvBuf > 0 { _ = uc.SetReadBuffer(rt.cfg.UDPRcvBuf) } if rt.cfg.UDPSndBuf > 0 { _ = uc.SetWriteBuffer(rt.cfg.UDPSndBuf) } saddr, err := net.ResolveUDPAddr("udp", rt.cfg.ServerAddr) if err != nil { return err } sess := rt.cfg.SessID if sess == 0 { sess = grnd.Uint32() } logf(LInfo, "[client][udp] %s -> server %s (sess=%d, wire=%s, %dfps, %dMbps)", rt.cfg.Listen, rt.cfg.ServerAddr, sess, rt.cfg.Wire, rt.cfg.FPS, rt.cfg.BitrateMbps) var appPeerMu sync.RWMutex var appPeer *net.UDPAddr // RTP 累加 var rtp = rtpState{seq: uint16(grnd.Uint32()), ssrc: grnd.Uint32()} var rtpTs uint32 step := 90000 / max(1, rt.cfg.FPS) go func() { buf := make([]byte, 64<<10) for { n, from, err := uc.ReadFromUDP(buf) if err != nil { return } // 来自 server:解壳 -> 发给应用 if from.IP.Equal(saddr.IP) && from.Port == saddr.Port { raw := append([]byte(nil), buf[:n]...) src := ip4OrLoopback(from.IP) dst := ip4OrLoopback(uc.LocalAddr().(*net.UDPAddr).IP) rt.pc.WriteUDP(src, from.Port, dst, uc.LocalAddr().(*net.UDPAddr).Port, raw) var payload []byte if rt.cfg.Wire == WireRTPish { _, _, rest, e := stripRTP(raw); if e != nil { continue } h, p, e2 := parseShimFull(rest); if e2 != nil { continue } if (h.Flags & flagDecoy) != 0 { metricDecoyDropped.Add(1); if rt.dropLog { logf(LDebug, "[client] drop decoy %dB", len(p)) }; continue } payload = p if (h.Flags & flagEnc) != 0 && rt.ae != nil { ns := rt.ae.aead.NonceSize(); if len(payload) >= ns { if plain, e := rt.ae.open(payload[:ns], payload[ns:]); e == nil { payload = plain } } } } else { h, p, e2 := parseShimFull(raw); if e2 != nil { continue } if (h.Flags & flagDecoy) != 0 { metricDecoyDropped.Add(1); if rt.dropLog { logf(LDebug, "[client] drop decoy %dB", len(p)) }; continue } payload = p if (h.Flags & flagEnc) != 0 && rt.ae != nil { ns := rt.ae.aead.NonceSize(); if len(payload) >= ns { if plain, e := rt.ae.open(payload[:ns], payload[ns:]); e == nil { payload = plain } } } } appPeerMu.RLock(); dstPeer := appPeer; appPeerMu.RUnlock() if dstPeer != nil { _, _ = uc.WriteToUDP(payload, dstPeer) metricFramesDown.Add(1); metricBytesDown.Add(int64(len(payload))) } continue } // 来自应用:加壳 -> 先发真实帧,再插播 shim 诱饵(RPS 优先)→ 插播额外伪报文 appPeerMu.Lock(); appPeer = from; appPeerMu.Unlock() p := append([]byte(nil), buf[:n]...) // a) 真实帧(带 shim) flags := uint8(0) pp := p if rt.ae != nil { if c, nonce, e := rt.ae.seal(p); e == nil { pp = append(nonce, c...); flags |= flagEnc } } h := shimHeader{Magic: magicConst, Version: version, Mode: modeUDP, Flags: flags, SessionID: sess, TsMs: uint32(time.Now().UnixMilli()), Len: uint32(len(pp))} frame := append(h.Marshal(), pp...) if rt.cfg.Wire == WireRTPish { rtpTs += uint32(step) rtpHdr := buildRTPHeader(rtp.seq, rtpTs, 96, false, rtp.ssrc) rtp.seq++ frame = append(rtpHdr, frame...) } rt.tb.wait(len(frame) + 28) _, _ = uc.WriteToUDP(frame, saddr) metricFramesUp.Add(1); metricBytesUp.Add(int64(n)) srcIP := ip4OrLoopback(uc.LocalAddr().(*net.UDPAddr).IP) dstIP := ip4OrLoopback(saddr.IP) rt.pc.WriteUDP(srcIP, uc.LocalAddr().(*net.UDPAddr).Port, dstIP, saddr.Port, frame) // b) shim 诱饵(带 shim,RPS 优先,否则按百分比) sendDecoy := false if rt.shimUpRL != nil && rt.shimUpRL.takeMax(1) > 0 { sendDecoy = true } else if rt.cfg.DecoyRps <= 0 && grnd.Intn(100) < rt.cfg.DecoyPct { sendDecoy = true } if sendDecoy { djunk := make([]byte, len(p)) _, _ = cryptoRand.Read(djunk) decFlags := uint8(flagDecoy) decPayload := djunk if rt.ae != nil { if c, nonce, e := rt.ae.seal(decPayload); e == nil { decPayload = append(nonce, c...) decFlags |= flagEnc } } hd := shimHeader{Magic: magicConst, Version: version, Mode: modeUDP, Flags: decFlags, SessionID: sess, TsMs: uint32(time.Now().UnixMilli()), Len: uint32(len(decPayload))} df := append(hd.Marshal(), decPayload...) if rt.cfg.Wire == WireRTPish { rtpTs += uint32(step) rtpHdr := buildRTPHeader(rtp.seq, rtpTs, 96, false, rtp.ssrc) rtp.seq++ df = append(rtpHdr, df...) } rt.tb.wait(len(df) + 28) _, _ = uc.WriteToUDP(df, saddr) metricFramesUp.Add(1); metricShimDecoySent.Add(1) rt.pc.WriteUDP(srcIP, uc.LocalAddr().(*net.UDPAddr).Port, dstIP, saddr.Port, df) } // c) 额外伪报文(无 shim;RTCP / pure RTP / STUN) sendExtraDecoysUDP(uc, uc.LocalAddr().(*net.UDPAddr), saddr, rt, rtp.ssrc, &rtp.seq, &rtpTs, step, true) // 节奏(fps) base := 1000 / max(1, rt.cfg.FPS) jitterSleep(base, rt.cfg.JitterPct) } }() <-ctx.Done() return nil } type udpSess struct { client *net.UDPAddr target *net.UDPConn last int64 } func runUDPServer(ctx context.Context, rt *runtimeCtx) error { laddr, err := net.ResolveUDPAddr("udp", rt.cfg.Listen); if err != nil { return err } ln, err := net.ListenUDP("udp", laddr); if err != nil { return err } defer ln.Close() if rt.cfg.UDPRcvBuf > 0 { _ = ln.SetReadBuffer(rt.cfg.UDPRcvBuf) } if rt.cfg.UDPSndBuf > 0 { _ = ln.SetWriteBuffer(rt.cfg.UDPSndBuf) } fwd, err := net.ResolveUDPAddr("udp", rt.cfg.ForwardAddr); if err != nil { return err } logf(LInfo, "[server][udp] %s -> %s (wire=%s, %dfps, %dMbps)", rt.cfg.Listen, rt.cfg.ForwardAddr, rt.cfg.Wire, rt.cfg.FPS, rt.cfg.BitrateMbps) type key struct{ ip string; port int; sess uint32 } var mu sync.Mutex smap := map[key]*udpSess{} // session GC go func() { t := time.NewTicker(60 * time.Second) for { select { case <-t.C: now := time.Now().Unix() mu.Lock() for k, s := range smap { if now-atomic.LoadInt64(&s.last) > 300 { if s.target != nil { s.target.Close() } delete(smap, k) metricSessions.Add(-1) } } mu.Unlock() case <-ctx.Done(): return } } }() buf := make([]byte, 64<<10) for { n, caddr, err := ln.ReadFromUDP(buf) if err != nil { select { case <-ctx.Done(): return nil ; default: return err } } raw := append([]byte(nil), buf[:n]...) src := ip4OrLoopback(caddr.IP) dst := ip4OrLoopback(ln.LocalAddr().(*net.UDPAddr).IP) rt.pc.WriteUDP(src, caddr.Port, dst, ln.LocalAddr().(*net.UDPAddr).Port, raw) // 剥线(RTP+Shim 或 Shim) var h shimHeader var p []byte if rt.cfg.Wire == WireRTPish { _, _, rest, e := stripRTP(raw); if e != nil { continue } hh, pp, e2 := parseShimFull(rest); if e2 != nil { continue } h, p = hh, pp } else { hh, pp, e2 := parseShimFull(raw); if e2 != nil { continue } h, p = hh, pp } k := key{ip: caddr.IP.String(), port: caddr.Port, sess: h.SessionID} // 丢诱饵 & 解密 if (h.Flags & flagDecoy) != 0 { metricDecoyDropped.Add(1) if rt.dropLog { logf(LDebug, "[server] drop shim-decoy %dB", len(p)) } continue } if (h.Flags & flagEnc) != 0 && rt.ae != nil { ns := rt.ae.aead.NonceSize() if len(p) >= ns { if plain, e := rt.ae.open(p[:ns], p[ns:]); e == nil { p = plain } } } mu.Lock() sess := smap[k] if sess == nil { tconn, err := net.DialUDP("udp", nil, fwd) if err != nil { mu.Unlock(); continue } sess = &udpSess{client: caddr, target: tconn, last: time.Now().Unix()} smap[k] = sess; metricSessions.Add(1) // 回程:目标 -> client(真实帧 + shim诱饵 + 额外伪报文) go func(k key, s *udpSess) { b := make([]byte, 64<<10) // RTP 构造(用于 RTP-ish + keepalive) var rtp = rtpState{seq: uint16(grnd.Uint32()), ssrc: grnd.Uint32()} var rtpTs uint32 step := 90000 / max(1, rt.cfg.FPS) for { n2, _, e2 := s.target.ReadFromUDP(b) if e2 != nil { return } pp := append([]byte(nil), b[:n2]...) // a) 真实帧(带 shim) flags := uint8(0) payload := pp if rt.ae != nil { if c, nonce, e := rt.ae.seal(payload); e == nil { payload = append(nonce, c...) flags |= flagEnc } } h := shimHeader{Magic: magicConst, Version: version, Mode: modeUDP, Flags: flags, SessionID: k.sess, TsMs: uint32(time.Now().UnixMilli()), Len: uint32(len(payload))} pkt := append(h.Marshal(), payload...) if rt.cfg.Wire == WireRTPish { rtpTs += uint32(step) rtpHdr := buildRTPHeader(rtp.seq, rtpTs, 96, false, rtp.ssrc) rtp.seq++ pkt = append(rtpHdr, pkt...) } rt.tb.wait(len(pkt) + 28) _, _ = ln.WriteToUDP(pkt, s.client) metricFramesDown.Add(1); metricBytesDown.Add(int64(n2)) src2 := ip4OrLoopback(ln.LocalAddr().(*net.UDPAddr).IP) dst2 := ip4OrLoopback(s.client.IP) rt.pc.WriteUDP(src2, ln.LocalAddr().(*net.UDPAddr).Port, dst2, s.client.Port, pkt) // b) shim 诱饵(RPS 优先,否则百分比) sendDecoy := false if rt.shimDownRL != nil && rt.shimDownRL.takeMax(1) > 0 { sendDecoy = true } else if rt.cfg.DecoyRps <= 0 && grnd.Intn(100) < rt.cfg.DecoyPct { sendDecoy = true } if sendDecoy { djunk := make([]byte, len(pp)) _, _ = cryptoRand.Read(djunk) decFlags := uint8(flagDecoy) decPayload := djunk if rt.ae != nil { if c, nonce, e := rt.ae.seal(decPayload); e == nil { decPayload = append(nonce, c...) decFlags |= flagEnc } } hd := shimHeader{Magic: magicConst, Version: version, Mode: modeUDP, Flags: decFlags, SessionID: k.sess, TsMs: uint32(time.Now().UnixMilli()), Len: uint32(len(decPayload))} df := append(hd.Marshal(), decPayload...) if rt.cfg.Wire == WireRTPish { rtpTs += uint32(step) rtpHdr := buildRTPHeader(rtp.seq, rtpTs, 96, false, rtp.ssrc) rtp.seq++ df = append(rtpHdr, df...) } rt.tb.wait(len(df) + 28) _, _ = ln.WriteToUDP(df, s.client) metricFramesDown.Add(1); metricShimDecoySent.Add(1) rt.pc.WriteUDP(src2, ln.LocalAddr().(*net.UDPAddr).Port, dst2, s.client.Port, df) } // c) 额外伪报文(无 shim;RTCP / pure RTP / STUN),发往 client sendExtraDecoysUDP(ln, ln.LocalAddr().(*net.UDPAddr), s.client, rt, rtp.ssrc, &rtp.seq, &rtpTs, step, false) // 节奏(fps) base := 1000 / max(1, rt.cfg.FPS) jitterSleep(base, rt.cfg.JitterPct) } }(k, sess) } atomic.StoreInt64(&sess.last, time.Now().Unix()) mu.Unlock() // 发往真实目标(原始负载) rt.tb.wait(len(p) + 28) _, _ = sess.target.Write(p) metricFramesUp.Add(1); metricBytesUp.Add(int64(len(p))) } } /* ============ TCP Client/Server ============ */ // Client:应用<->(加/解壳)<->Server func runTCPClient(ctx context.Context, rt *runtimeCtx) error { ln, err := net.Listen("tcp", rt.cfg.Listen); if err != nil { return err } defer ln.Close() logf(LInfo, "[client][tcp] %s -> server %s", rt.cfg.Listen, rt.cfg.ServerAddr) for { ac, err := ln.Accept() if err != nil { select { case <-ctx.Done(): return nil ; default: return err } } go func(app net.Conn) { defer app.Close() s, err := net.Dial("tcp", rt.cfg.ServerAddr); if err != nil { return } defer s.Close() // app->server: 加壳(写入 s,记录 TCP PCAP) go tcpUp(app, s, rt) // server->app: 解壳(从 s 读网络帧,记录 TCP PCAP) tcpDown(s, app, rt) }(ac) } } // Server:client<-(加/解壳)->target(注意方向) func runTCPServer(ctx context.Context, rt *runtimeCtx) error { ln, err := net.Listen("tcp", rt.cfg.Listen); if err != nil { return err } defer ln.Close() logf(LInfo, "[server][tcp] %s -> %s", rt.cfg.Listen, rt.cfg.ForwardAddr) for { c, err := ln.Accept() if err != nil { select { case <-ctx.Done(): return nil ; default: return err } } go func(cli net.Conn) { defer cli.Close() dst, err := net.Dial("tcp", rt.cfg.ForwardAddr) if err != nil { logf(LError, "dial forward: %v", err); return } defer dst.Close() // 从 client 读带壳 -> 解壳 -> 写给目标(记录来自 cli 的网络帧) go tcpDown(cli, dst, rt) // 从目标读原始 -> 加壳 -> 回 client(记录写给 cli 的网络帧) tcpUp(dst, cli, rt) }(c) } } // tcpUp:从 r 读“原始数据”,分片+抖动+(可选)加密,封成带壳帧写给 w(网络 conn) // 诱饵为“插播”(RPS 优先),不影响真实流 func tcpUp(r net.Conn, w net.Conn, rt *runtimeCtx) { buf := make([]byte, 64<<10) for { n, err := r.Read(buf) if n > 0 { p := append([]byte(nil), buf[:n]...) parts := splitTCP(p, rt.cfg.FrameMin, rt.cfg.FrameMax) for _, ck := range parts { // a) 真实帧 realFlags := uint8(0) realPayload := ck if rt.ae != nil { if c, nonce, e := rt.ae.seal(realPayload); e == nil { realPayload = append(nonce, c...) realFlags |= flagEnc } } h := shimHeader{Magic: magicConst, Version: version, Mode: modeTCP, Flags: realFlags, SessionID: grnd.Uint32(), TsMs: uint32(time.Now().UnixMilli()), Len: uint32(len(realPayload))} frame := append(h.Marshal(), realPayload...) rt.tb.wait(len(frame)) if _, err := w.Write(frame); err != nil { return } metricFramesUp.Add(1); metricBytesUp.Add(int64(len(ck))) // TCP PCAP(网络方向:w.Local->w.Remote) if rt.pc != nil { if la, ok1 := w.LocalAddr().(*net.TCPAddr); ok1 { if ra, ok2 := w.RemoteAddr().(*net.TCPAddr); ok2 { rt.pc.WriteTCP(ip4OrLoopback(la.IP), la.Port, ip4OrLoopback(ra.IP), ra.Port, frame) } } } jitterSleep(rt.cfg.PaceMs, rt.cfg.JitterPct) // b) shim 诱饵插播(RPS 优先,否则百分比) sendDecoy := false if rt.shimUpRL != nil && rt.shimUpRL.takeMax(1) > 0 { sendDecoy = true } else if rt.cfg.DecoyRps <= 0 && grnd.Intn(100) < rt.cfg.DecoyPct { sendDecoy = true } if sendDecoy { djunk := make([]byte, len(ck)) _, _ = cryptoRand.Read(djunk) decFlags := uint8(flagDecoy) decPayload := djunk if rt.ae != nil { if c, nonce, e := rt.ae.seal(decPayload); e == nil { decPayload = append(nonce, c...) decFlags |= flagEnc } } hd := shimHeader{Magic: magicConst, Version: version, Mode: modeTCP, Flags: decFlags, SessionID: grnd.Uint32(), TsMs: uint32(time.Now().UnixMilli()), Len: uint32(len(decPayload))} f2 := append(hd.Marshal(), decPayload...) rt.tb.wait(len(f2)) if _, err := w.Write(f2); err != nil { return } metricFramesUp.Add(1); metricShimDecoySent.Add(1) // PCAP 记录诱饵 if rt.pc != nil { if la, ok1 := w.LocalAddr().(*net.TCPAddr); ok1 { if ra, ok2 := w.RemoteAddr().(*net.TCPAddr); ok2 { rt.pc.WriteTCP(ip4OrLoopback(la.IP), la.Port, ip4OrLoopback(ra.IP), ra.Port, f2) } } } } } } if err != nil { return } } } // tcpDown:从 r(网络 conn)读“带壳帧”,解壳后写给 w(应用或另一侧网络) // 同时用 r 的 5元组记录“入向”PCAP(debug) func tcpDown(r net.Conn, w net.Conn, rt *runtimeCtx) { for { hb := make([]byte, shimLen) if _, err := io.ReadFull(r, hb); err != nil { return } h, err := parseShimHeader(hb) if err != nil { return } payload := make([]byte, h.Len) if _, err := io.ReadFull(r, payload); err != nil { return } // PCAP 记录“网络入向”帧(按 r.Local->r.Remote) if rt.pc != nil { if la, ok1 := r.LocalAddr().(*net.TCPAddr); ok1 { if ra, ok2 := r.RemoteAddr().(*net.TCPAddr); ok2 { frame := append(append([]byte{}, hb...), payload...) rt.pc.WriteTCP(ip4OrLoopback(la.IP), la.Port, ip4OrLoopback(ra.IP), ra.Port, frame) } } } if (h.Flags & flagDecoy) != 0 { metricDecoyDropped.Add(1) if rt.dropLog { logf(LDebug, "drop shim-decoy %dB", len(payload)) } continue } if (h.Flags & flagEnc) != 0 && rt.ae != nil { ns := rt.ae.aead.NonceSize() if len(payload) >= ns { if plain, e := rt.ae.open(payload[:ns], payload[ns:]); e == nil { payload = plain } } } if _, err := w.Write(payload); err != nil { return } metricFramesDown.Add(1); metricBytesDown.Add(int64(len(payload))) } } func splitTCP(buf []byte, min, max int) [][]byte { if min <= 0 { min = 800 } if max < min { max = min } var out [][]byte for len(buf) > 0 { n := randRange(min, max) if n > len(buf) { n = len(buf) } out = append(out, append([]byte(nil), buf[:n]...)) buf = buf[n:] } return out } /* ============ Self-test (UDP) ============ */ func runSelfTest(cfg *Config) { setLog(cfg.LogLevel) tb := newTB(cfg.BitrateMbps, 512*1024) ae, _ := newAEAD(cfg.AESKeyHex) pc, _ := newPCAP(cfg.PcapPath, int64(cfg.PcapMaxMB)*1024*1024) // build runtime with decoy RL upRL := newDecoyRL(cfg, true) downRL := newDecoyRL(cfg, false) shimUp := newRL(cfg.DecoyRps * 0.5) shimDown := newRL(cfg.DecoyRps * 0.5) rt := &runtimeCtx{ cfg: cfg, tb: tb, ae: ae, pc: pc, dropLog: cfg.LogDrop, upRL: upRL, downRL: downRL, shimUpRL: shimUp, shimDownRL: shimDown, } defer func() { if pc != nil { pc.Close() } }() // 内置 UDP Echo 目标(在 forward 上) go func() { addr, _ := net.ResolveUDPAddr("udp", cfg.ForwardAddr) ln, err := net.ListenUDP("udp", addr) if err != nil { logf(LError, "echo bind: %v", err); return } defer ln.Close() buf := make([]byte, 64<<10) for { n, from, err := ln.ReadFromUDP(buf); if err != nil { return } _, _ = ln.WriteToUDP(buf[:n], from) } }() // Server ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { _ = runUDPServer(ctx, rt) }() // Client ccfg := *cfg ccfg.Role = RoleClient ccfg.Listen = ":7001" rtC := &runtimeCtx{ cfg: &ccfg, tb: tb, ae: ae, pc: pc, dropLog: cfg.LogDrop, upRL: upRL, downRL: downRL, shimUpRL: shimUp, shimDownRL: shimDown, } go func() { _ = runUDPClient(ctx, rtC) }() // 负载发生器(应用 -> client.listen) go func() { dst, _ := net.ResolveUDPAddr("udp", ccfg.Listen) c, err := net.DialUDP("udp", nil, dst); if err != nil { return } defer c.Close() fps := max(1, cfg.FPS) avgBytesPerSec := cfg.BitrateMbps * 1024 * 1024 / 8 avgPerFrame := avgBytesPerSec / fps ticker := time.NewTicker(time.Second / time.Duration(fps)) defer ticker.Stop() lastKey := time.Now() for { select { case <-ticker.C: var size int isKey := time.Since(lastKey) >= time.Duration(cfg.GOPMs)*time.Millisecond if isKey { lastKey = time.Now() size = int(float64(avgPerFrame)*2.5) + randRange(2000, 6000) } else { size = int(float64(avgPerFrame)*0.6) + randRange(300, 1500) } if size < 200 { size = 200 } payload := make([]byte, size) _, _ = cryptoRand.Read(payload) _, _ = c.Write(payload) case <-ctx.Done(): return } } }() logf(LInfo, "[selftest] running %v ...", cfg.SelfDur) time.Sleep(cfg.SelfDur) cancel() logf(LInfo, "[selftest] done. pcap=%s metrics on %s (/debug/vars)", cfg.PcapPath, cfg.MetricsAddr) } /* ============ main ============ */ func main() { cfg := parseFlags() setLog(cfg.LogLevel) runMetrics(cfg.MetricsAddr) switch cfg.Role { case RoleSelf: runSelfTest(cfg) return } tb := newTB(cfg.BitrateMbps, 512*1024) ae, err := newAEAD(cfg.AESKeyHex); must(err) pc, err := newPCAP(cfg.PcapPath, int64(cfg.PcapMaxMB)*1024*1024); must(err) // build runtime with decoy RL upRL := newDecoyRL(cfg, true) downRL := newDecoyRL(cfg, false) shimUp := newRL(cfg.DecoyRps * 0.5) shimDown := newRL(cfg.DecoyRps * 0.5) rt := &runtimeCtx{ cfg: cfg, tb: tb, ae: ae, pc: pc, dropLog: cfg.LogDrop, upRL: upRL, downRL: downRL, shimUpRL: shimUp, shimDownRL: shimDown, } defer func() { if pc != nil { pc.Close() } }() ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer cancel() switch cfg.Role { case RoleClient: switch cfg.Mode { case ModeUDP: must(runUDPClient(ctx, rt)) case ModeTCP: must(runTCPClient(ctx, rt)) default: must(fmt.Errorf("unknown mode")) } case RoleServer: switch cfg.Mode { case ModeUDP: must(runUDPServer(ctx, rt)) case ModeTCP: must(runTCPServer(ctx, rt)) default: must(fmt.Errorf("unknown mode")) } default: must(fmt.Errorf("unknown role")) } }