Highly-opinionated (ex-bullshit-free) MTPROTO proxy for Telegram. If you use v1.0 or upgrade broke you proxy, please read the chapter Version 2
Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

main.go 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Relay server — the process we measure.
  2. // Accepts TCP connections, connects to echo backend, relays bidirectionally.
  3. // Exposes /metrics HTTP endpoint for monitoring.
  4. package main
  5. import (
  6. "context"
  7. "encoding/json"
  8. "flag"
  9. "fmt"
  10. "io"
  11. "net"
  12. "net/http"
  13. "os"
  14. "runtime"
  15. "strconv"
  16. "strings"
  17. "sync"
  18. "sync/atomic"
  19. "time"
  20. )
  21. const (
  22. bufSize16K = 16379 // tls.MaxRecordPayloadSize
  23. bufSize4K = 4096
  24. )
  25. // --- Buffer strategies ---
  26. var pool16K = sync.Pool{New: func() any { b := make([]byte, bufSize16K); return &b }}
  27. var pool4K = sync.Pool{New: func() any { b := make([]byte, bufSize4K); return &b }}
  28. type strategy int
  29. const (
  30. stratStack16K strategy = iota
  31. stratPool16K
  32. stratPool4K
  33. )
  34. func (s strategy) String() string {
  35. switch s {
  36. case stratStack16K:
  37. return "stack-16k"
  38. case stratPool16K:
  39. return "pool-16k"
  40. case stratPool4K:
  41. return "pool-4k"
  42. }
  43. return "unknown"
  44. }
  45. func parseStrategy(s string) strategy {
  46. switch s {
  47. case "stack-16k", "stack":
  48. return stratStack16K
  49. case "pool-16k", "pool":
  50. return stratPool16K
  51. case "pool-4k":
  52. return stratPool4K
  53. default:
  54. fmt.Fprintf(os.Stderr, "unknown strategy: %s (use stack-16k, pool-16k, pool-4k)\n", s)
  55. os.Exit(1)
  56. return 0
  57. }
  58. }
  59. // pump copies src→dst using the given strategy. Returns bytes copied.
  60. func pump(strat strategy, dst, src net.Conn) int64 {
  61. var n int64
  62. var err error
  63. switch strat {
  64. case stratStack16K:
  65. var buf [bufSize16K]byte
  66. n, err = io.CopyBuffer(dst, src, buf[:])
  67. case stratPool16K:
  68. bp := pool16K.Get().(*[]byte)
  69. n, err = io.CopyBuffer(dst, src, *bp)
  70. pool16K.Put(bp)
  71. case stratPool4K:
  72. bp := pool4K.Get().(*[]byte)
  73. n, err = io.CopyBuffer(dst, src, *bp)
  74. pool4K.Put(bp)
  75. }
  76. _ = err
  77. return n
  78. }
  79. // --- Metrics ---
  80. type metrics struct {
  81. ActiveConns atomic.Int64
  82. TotalConns atomic.Int64
  83. TotalBytes atomic.Int64
  84. FailedConns atomic.Int64
  85. }
  86. var m metrics
  87. type metricsSnapshot struct {
  88. Strategy string `json:"strategy"`
  89. Uptime string `json:"uptime"`
  90. ActiveConns int64 `json:"active_conns"`
  91. TotalConns int64 `json:"total_conns"`
  92. TotalBytes int64 `json:"total_bytes"`
  93. FailedConns int64 `json:"failed_conns"`
  94. Goroutines int `json:"goroutines"`
  95. RSSKB int64 `json:"rss_kb"`
  96. VmRSSKB int64 `json:"vm_rss_kb"`
  97. StackInuse uint64 `json:"stack_inuse_bytes"`
  98. HeapInuse uint64 `json:"heap_inuse_bytes"`
  99. HeapAlloc uint64 `json:"heap_alloc_bytes"`
  100. HeapSys uint64 `json:"heap_sys_bytes"`
  101. StackSys uint64 `json:"stack_sys_bytes"`
  102. Sys uint64 `json:"sys_bytes"`
  103. NumGC uint32 `json:"num_gc"`
  104. GCPauseTotalUs int64 `json:"gc_pause_total_us"`
  105. GOMAXPROCS int `json:"gomaxprocs"`
  106. }
  107. func readRSSKB() int64 {
  108. data, err := os.ReadFile("/proc/self/status")
  109. if err != nil {
  110. return -1
  111. }
  112. for _, line := range strings.Split(string(data), "\n") {
  113. if strings.HasPrefix(line, "VmRSS:") {
  114. fields := strings.Fields(line)
  115. if len(fields) >= 2 {
  116. v, _ := strconv.ParseInt(fields[1], 10, 64)
  117. return v
  118. }
  119. }
  120. }
  121. return -1
  122. }
  123. func getMetrics(strat strategy, startTime time.Time) metricsSnapshot {
  124. var ms runtime.MemStats
  125. runtime.ReadMemStats(&ms)
  126. return metricsSnapshot{
  127. Strategy: strat.String(),
  128. Uptime: time.Since(startTime).Round(time.Second).String(),
  129. ActiveConns: m.ActiveConns.Load(),
  130. TotalConns: m.TotalConns.Load(),
  131. TotalBytes: m.TotalBytes.Load(),
  132. FailedConns: m.FailedConns.Load(),
  133. Goroutines: runtime.NumGoroutine(),
  134. RSSKB: readRSSKB(),
  135. VmRSSKB: readRSSKB(),
  136. StackInuse: ms.StackInuse,
  137. HeapInuse: ms.HeapInuse,
  138. HeapAlloc: ms.HeapAlloc,
  139. HeapSys: ms.HeapSys,
  140. StackSys: ms.StackSys,
  141. Sys: ms.Sys,
  142. NumGC: ms.NumGC,
  143. GCPauseTotalUs: int64(ms.PauseTotalNs / 1000),
  144. GOMAXPROCS: runtime.GOMAXPROCS(0),
  145. }
  146. }
  147. // --- Connection handler ---
  148. func handleConn(strat strategy, echoAddr string, conn net.Conn) {
  149. defer conn.Close()
  150. m.ActiveConns.Add(1)
  151. m.TotalConns.Add(1)
  152. defer m.ActiveConns.Add(-1)
  153. backend, err := net.DialTimeout("tcp", echoAddr, 10*time.Second)
  154. if err != nil {
  155. m.FailedConns.Add(1)
  156. return
  157. }
  158. defer backend.Close()
  159. done := make(chan struct{})
  160. go func() {
  161. defer close(done)
  162. n := pump(strat, backend, conn)
  163. m.TotalBytes.Add(n)
  164. conn.Close()
  165. backend.Close()
  166. }()
  167. n := pump(strat, conn, backend)
  168. m.TotalBytes.Add(n)
  169. conn.Close()
  170. backend.Close()
  171. <-done
  172. }
  173. // --- Metrics logger (writes to file every second) ---
  174. func metricsLogger(ctx context.Context, strat strategy, startTime time.Time, logPath string) {
  175. f, err := os.Create(logPath)
  176. if err != nil {
  177. fmt.Fprintf(os.Stderr, "cannot create metrics log: %v\n", err)
  178. return
  179. }
  180. defer f.Close()
  181. // CSV header
  182. fmt.Fprintf(f, "time_s,active_conns,total_conns,total_bytes_mb,rss_kb,stack_inuse_kb,heap_inuse_kb,heap_alloc_kb,sys_kb,goroutines,num_gc,gc_pause_us,failed_conns\n")
  183. ticker := time.NewTicker(1 * time.Second)
  184. defer ticker.Stop()
  185. for {
  186. select {
  187. case <-ctx.Done():
  188. return
  189. case <-ticker.C:
  190. snap := getMetrics(strat, startTime)
  191. elapsed := time.Since(startTime).Seconds()
  192. fmt.Fprintf(f, "%.0f,%d,%d,%.1f,%d,%d,%d,%d,%d,%d,%d,%d,%d\n",
  193. elapsed,
  194. snap.ActiveConns,
  195. snap.TotalConns,
  196. float64(snap.TotalBytes)/1024/1024,
  197. snap.RSSKB,
  198. snap.StackInuse/1024,
  199. snap.HeapInuse/1024,
  200. snap.HeapAlloc/1024,
  201. snap.Sys/1024,
  202. snap.Goroutines,
  203. snap.NumGC,
  204. snap.GCPauseTotalUs,
  205. snap.FailedConns,
  206. )
  207. f.Sync()
  208. }
  209. }
  210. }
  211. func main() {
  212. addr := flag.String("addr", "0.0.0.0:19998", "relay listen address")
  213. echoAddr := flag.String("echo", "72.56.22.248:19999", "echo server address")
  214. stratName := flag.String("strategy", "stack-16k", "buffer strategy: stack-16k, pool-16k, pool-4k")
  215. metricsAddr := flag.String("metrics", "0.0.0.0:19997", "HTTP metrics address")
  216. metricsLog := flag.String("metrics-log", "", "path to CSV metrics log file (optional)")
  217. flag.Parse()
  218. strat := parseStrategy(*stratName)
  219. startTime := time.Now()
  220. fmt.Printf("relay server: strategy=%s, listen=%s, echo=%s, metrics=%s\n",
  221. strat, *addr, *echoAddr, *metricsAddr)
  222. // HTTP metrics endpoint
  223. http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
  224. snap := getMetrics(strat, startTime)
  225. w.Header().Set("Content-Type", "application/json")
  226. json.NewEncoder(w).Encode(snap)
  227. })
  228. http.HandleFunc("/gc", func(w http.ResponseWriter, r *http.Request) {
  229. runtime.GC()
  230. fmt.Fprintf(w, "GC triggered\n")
  231. })
  232. http.HandleFunc("/reset", func(w http.ResponseWriter, r *http.Request) {
  233. m.TotalConns.Store(0)
  234. m.TotalBytes.Store(0)
  235. m.FailedConns.Store(0)
  236. fmt.Fprintf(w, "counters reset\n")
  237. })
  238. go http.ListenAndServe(*metricsAddr, nil)
  239. // Metrics logger
  240. if *metricsLog != "" {
  241. ctx, cancel := context.WithCancel(context.Background())
  242. defer cancel()
  243. go metricsLogger(ctx, strat, startTime, *metricsLog)
  244. }
  245. // TCP listener
  246. ln, err := net.Listen("tcp", *addr)
  247. if err != nil {
  248. fmt.Fprintf(os.Stderr, "listen: %v\n", err)
  249. os.Exit(1)
  250. }
  251. for {
  252. conn, err := ln.Accept()
  253. if err != nil {
  254. fmt.Fprintf(os.Stderr, "accept: %v\n", err)
  255. continue
  256. }
  257. go handleConn(strat, *echoAddr, conn)
  258. }
  259. }