server.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. package bwtest
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "fmt"
  7. "log"
  8. "math/rand"
  9. "net"
  10. "sync"
  11. "sync/atomic"
  12. "time"
  13. )
  14. // ServerSession tracks an active incoming bandwidth test session on the server
  15. type serverSession struct {
  16. id string
  17. sessionHash uint64
  18. clientTCP net.Conn
  19. clientAddr net.IP
  20. clientPort int
  21. handshake SessionHandshake
  22. cancel context.CancelFunc
  23. ctx context.Context
  24. // Statistics
  25. rxBytes atomic.Int64
  26. rxPackets atomic.Int64
  27. txBytes atomic.Int64
  28. txPackets atomic.Int64
  29. lostPackets atomic.Int64
  30. jitterMs atomic.Uint64 // float64 bits
  31. maxSeq atomic.Uint64
  32. seqTracker sync.Map // track recently seen sequences for loss detection
  33. mu sync.Mutex
  34. jitter JitterCalculator
  35. }
  36. // BwTestServer represents the local Bandwidth Test server
  37. type BwTestServer struct {
  38. config ServerConfig
  39. mu sync.RWMutex
  40. tcpListener net.Listener
  41. udpConn *net.UDPConn
  42. running bool
  43. cancel context.CancelFunc
  44. sessions map[string]*serverSession
  45. sessMu sync.RWMutex
  46. totalServed atomic.Int64
  47. totalBytes atomic.Int64
  48. }
  49. // NewServer creates a new BwTestServer instance
  50. func NewServer(cfg ServerConfig) *BwTestServer {
  51. if cfg.Port <= 0 {
  52. cfg.Port = 2000
  53. }
  54. if cfg.ListenAddr == "" {
  55. cfg.ListenAddr = "0.0.0.0"
  56. }
  57. return &BwTestServer{
  58. config: cfg,
  59. sessions: make(map[string]*serverSession),
  60. }
  61. }
  62. // Start launches the TCP control listener and UDP data listener
  63. func (s *BwTestServer) Start() error {
  64. s.mu.Lock()
  65. defer s.mu.Unlock()
  66. if s.running {
  67. return nil
  68. }
  69. ctx, cancel := context.WithCancel(context.Background())
  70. s.cancel = cancel
  71. tcpAddr := fmt.Sprintf("%s:%d", s.config.ListenAddr, s.config.Port)
  72. ln, err := net.Listen("tcp", tcpAddr)
  73. if err != nil {
  74. cancel()
  75. return fmt.Errorf("failed to bind BwTest TCP listener on %s: %w", tcpAddr, err)
  76. }
  77. s.tcpListener = ln
  78. udpAddr, err := net.ResolveUDPAddr("udp", tcpAddr)
  79. if err != nil {
  80. ln.Close()
  81. cancel()
  82. return fmt.Errorf("failed to resolve BwTest UDP address: %w", err)
  83. }
  84. uconn, err := net.ListenUDP("udp", udpAddr)
  85. if err != nil {
  86. ln.Close()
  87. cancel()
  88. return fmt.Errorf("failed to bind BwTest UDP listener on %s: %w", tcpAddr, err)
  89. }
  90. _ = uconn.SetReadBuffer(8 * 1024 * 1024) // 8MB OS UDP socket buffer
  91. s.udpConn = uconn
  92. s.running = true
  93. go s.acceptLoop(ctx)
  94. go s.udpReceiverLoop(ctx)
  95. log.Printf("[BwTestServer] Started Bandwidth Test Server listening on %s (TCP & UDP)", tcpAddr)
  96. return nil
  97. }
  98. // Stop terminates the server and all active sessions
  99. func (s *BwTestServer) Stop() {
  100. s.mu.Lock()
  101. if !s.running {
  102. s.mu.Unlock()
  103. return
  104. }
  105. s.running = false
  106. if s.cancel != nil {
  107. s.cancel()
  108. }
  109. if s.tcpListener != nil {
  110. _ = s.tcpListener.Close()
  111. }
  112. if s.udpConn != nil {
  113. _ = s.udpConn.Close()
  114. }
  115. s.mu.Unlock()
  116. s.sessMu.Lock()
  117. for _, sess := range s.sessions {
  118. if sess.cancel != nil {
  119. sess.cancel()
  120. }
  121. if sess.clientTCP != nil {
  122. _ = sess.clientTCP.Close()
  123. }
  124. }
  125. s.sessions = make(map[string]*serverSession)
  126. s.sessMu.Unlock()
  127. log.Printf("[BwTestServer] Bandwidth Test Server stopped")
  128. }
  129. // Status returns current operational metrics
  130. func (s *BwTestServer) Status() ServerStatus {
  131. s.mu.RLock()
  132. defer s.mu.RUnlock()
  133. s.sessMu.RLock()
  134. activeCount := len(s.sessions)
  135. s.sessMu.RUnlock()
  136. return ServerStatus{
  137. Enabled: s.config.Enabled,
  138. Port: s.config.Port,
  139. Running: s.running,
  140. ActiveSessions: activeCount,
  141. TotalSessionsServed: s.totalServed.Load(),
  142. TotalBytesProcessed: s.totalBytes.Load(),
  143. }
  144. }
  145. // SetPort updates the port and restarts the server if currently running
  146. func (s *BwTestServer) SetPort(port int) error {
  147. s.mu.Lock()
  148. wasRunning := s.running
  149. s.config.Port = port
  150. s.mu.Unlock()
  151. if wasRunning {
  152. s.Stop()
  153. return s.Start()
  154. }
  155. return nil
  156. }
  157. func (s *BwTestServer) acceptLoop(ctx context.Context) {
  158. for {
  159. conn, err := s.tcpListener.Accept()
  160. if err != nil {
  161. select {
  162. case <-ctx.Done():
  163. return
  164. default:
  165. time.Sleep(50 * time.Millisecond)
  166. continue
  167. }
  168. }
  169. go s.handleClientControl(ctx, conn)
  170. }
  171. }
  172. func (s *BwTestServer) handleClientControl(serverCtx context.Context, conn net.Conn) {
  173. defer conn.Close()
  174. remoteTCPAddr, _ := conn.RemoteAddr().(*net.TCPAddr)
  175. clientIP := net.IPv4(127, 0, 0, 1)
  176. if remoteTCPAddr != nil {
  177. clientIP = remoteTCPAddr.IP
  178. }
  179. reader := bufio.NewReader(conn)
  180. line, err := reader.ReadBytes('\n')
  181. if err != nil {
  182. return
  183. }
  184. var req SessionHandshake
  185. if err := json.Unmarshal(line, &req); err != nil {
  186. resp := SessionHandshake{Type: "reject", Message: "Invalid handshake JSON"}
  187. b, _ := json.Marshal(resp)
  188. _, _ = conn.Write(append(b, '\n'))
  189. return
  190. }
  191. if req.Type != "request" {
  192. return
  193. }
  194. // Session parameter sanitization
  195. if req.SessionID == "" {
  196. req.SessionID = fmt.Sprintf("bw-%d-%d", time.Now().UnixNano(), rand.Intn(10000))
  197. }
  198. if req.PacketSize <= 0 {
  199. req.PacketSize = 1472
  200. }
  201. if req.PacketSize < 64 {
  202. req.PacketSize = 64
  203. }
  204. if req.PacketSize > 65507 {
  205. req.PacketSize = 65507
  206. }
  207. if req.Streams <= 0 {
  208. req.Streams = 1
  209. }
  210. if req.Streams > 8 {
  211. req.Streams = 8
  212. }
  213. if req.Duration < 0 {
  214. req.Duration = 10
  215. }
  216. sessCtx, sessCancel := context.WithCancel(serverCtx)
  217. if req.Duration > 0 {
  218. sessCtx, sessCancel = context.WithTimeout(serverCtx, time.Duration(req.Duration+2)*time.Second)
  219. }
  220. defer sessCancel()
  221. sess := &serverSession{
  222. id: req.SessionID,
  223. sessionHash: HashSessionID(req.SessionID),
  224. clientTCP: conn,
  225. clientAddr: clientIP,
  226. clientPort: req.UDPPort,
  227. handshake: req,
  228. ctx: sessCtx,
  229. cancel: sessCancel,
  230. }
  231. s.sessMu.Lock()
  232. s.sessions[sess.id] = sess
  233. s.sessMu.Unlock()
  234. s.totalServed.Add(1)
  235. defer func() {
  236. s.sessMu.Lock()
  237. delete(s.sessions, sess.id)
  238. s.sessMu.Unlock()
  239. }()
  240. // Respond with accept
  241. s.mu.RLock()
  242. listenPort := s.config.Port
  243. s.mu.RUnlock()
  244. acceptResp := SessionHandshake{
  245. Type: "accept",
  246. SessionID: sess.id,
  247. UDPPort: listenPort,
  248. Protocol: req.Protocol,
  249. Direction: req.Direction,
  250. Message: "Ready",
  251. }
  252. acceptBytes, _ := json.Marshal(acceptResp)
  253. if _, err := conn.Write(append(acceptBytes, '\n')); err != nil {
  254. return
  255. }
  256. // Launch worker routines based on direction and protocol
  257. if req.Protocol == "udp" {
  258. // If client is receiving (server transmitting)
  259. if req.Direction == "rx" || req.Direction == "both" {
  260. go s.runServerUDPTx(sessCtx, sess)
  261. }
  262. } else if req.Protocol == "tcp" {
  263. if req.Direction == "rx" || req.Direction == "both" {
  264. go s.runServerTCPTx(sessCtx, sess)
  265. }
  266. if req.Direction == "tx" || req.Direction == "both" {
  267. go s.runServerTCPRx(sessCtx, sess)
  268. }
  269. }
  270. // Read control messages / reports from client until termination
  271. go s.reportLoop(sessCtx, sess)
  272. for {
  273. line, err := reader.ReadBytes('\n')
  274. if err != nil {
  275. break
  276. }
  277. var msg SessionHandshake
  278. if err := json.Unmarshal(line, &msg); err == nil {
  279. if msg.Type == "stop" {
  280. break
  281. }
  282. }
  283. }
  284. }
  285. // udpReceiverLoop processes incoming UDP test packets across all active sessions
  286. func (s *BwTestServer) udpReceiverLoop(ctx context.Context) {
  287. buf := make([]byte, 65535)
  288. for {
  289. select {
  290. case <-ctx.Done():
  291. return
  292. default:
  293. }
  294. n, remoteAddr, err := s.udpConn.ReadFromUDP(buf)
  295. if err != nil {
  296. select {
  297. case <-ctx.Done():
  298. return
  299. default:
  300. time.Sleep(10 * time.Millisecond)
  301. continue
  302. }
  303. }
  304. if n < HeaderSize {
  305. continue
  306. }
  307. hdr, err := DecodeHeader(buf[:n])
  308. if err != nil {
  309. continue
  310. }
  311. // Find matching session
  312. s.sessMu.RLock()
  313. var matched *serverSession
  314. for _, sess := range s.sessions {
  315. if sess.sessionHash == hdr.SessionHash {
  316. matched = sess
  317. break
  318. }
  319. }
  320. s.sessMu.RUnlock()
  321. if matched == nil {
  322. continue
  323. }
  324. // Update remote client UDP address for reverse transmission
  325. if matched.clientPort == 0 || matched.clientAddr == nil {
  326. matched.clientAddr = remoteAddr.IP
  327. matched.clientPort = remoteAddr.Port
  328. }
  329. nowNano := time.Now().UnixNano()
  330. matched.rxBytes.Add(int64(n))
  331. matched.rxPackets.Add(1)
  332. s.totalBytes.Add(int64(n))
  333. // Jitter calculation
  334. matched.mu.Lock()
  335. curJitter := matched.jitter.Update(hdr.TimestampNano, nowNano)
  336. matched.mu.Unlock()
  337. // Packet loss tracking via sequence gaps
  338. lastMax := matched.maxSeq.Load()
  339. if hdr.Sequence > lastMax {
  340. if lastMax > 0 && hdr.Sequence > lastMax+1 {
  341. gap := int64(hdr.Sequence - lastMax - 1)
  342. matched.lostPackets.Add(gap)
  343. }
  344. matched.maxSeq.Store(hdr.Sequence)
  345. }
  346. _ = curJitter
  347. }
  348. }
  349. // runServerUDPTx sends UDP packets from server to client (when client requested RX or Both)
  350. func (s *BwTestServer) runServerUDPTx(ctx context.Context, sess *serverSession) {
  351. // Wait briefly for client to send first packet so we know client's UDP port, or use negotiated port
  352. targetPort := sess.clientPort
  353. if targetPort == 0 {
  354. for i := 0; i < 20; i++ {
  355. if sess.clientPort != 0 {
  356. targetPort = sess.clientPort
  357. break
  358. }
  359. time.Sleep(50 * time.Millisecond)
  360. }
  361. }
  362. if targetPort == 0 {
  363. targetPort = s.config.Port
  364. }
  365. dstUDPAddr := &net.UDPAddr{
  366. IP: sess.clientAddr,
  367. Port: targetPort,
  368. }
  369. pktSize := sess.handshake.PacketSize
  370. if pktSize < HeaderSize {
  371. pktSize = HeaderSize
  372. }
  373. buf := make([]byte, pktSize)
  374. limiter := NewRateLimiter(sess.handshake.BandwidthLimit)
  375. var seq uint64
  376. hdr := PacketHeader{
  377. Magic: MagicBytes,
  378. Version: ProtoVer,
  379. PacketType: PacketTypeData,
  380. StreamID: 0,
  381. Flags: 0,
  382. SessionHash: sess.sessionHash,
  383. PayloadLength: uint32(pktSize - HeaderSize),
  384. }
  385. for {
  386. select {
  387. case <-ctx.Done():
  388. return
  389. default:
  390. }
  391. if limiter != nil {
  392. limiter.Wait(pktSize)
  393. }
  394. seq++
  395. hdr.Sequence = seq
  396. hdr.TimestampNano = time.Now().UnixNano()
  397. EncodeHeader(buf, hdr)
  398. n, err := s.udpConn.WriteToUDP(buf, dstUDPAddr)
  399. if err == nil {
  400. sess.txBytes.Add(int64(n))
  401. sess.txPackets.Add(1)
  402. s.totalBytes.Add(int64(n))
  403. }
  404. }
  405. }
  406. // runServerTCPTx generates continuous TCP stream data to client
  407. func (s *BwTestServer) runServerTCPTx(ctx context.Context, sess *serverSession) {
  408. chunk := make([]byte, 64*1024)
  409. limiter := NewRateLimiter(sess.handshake.BandwidthLimit)
  410. for {
  411. select {
  412. case <-ctx.Done():
  413. return
  414. default:
  415. }
  416. if limiter != nil {
  417. limiter.Wait(len(chunk))
  418. }
  419. n, err := sess.clientTCP.Write(chunk)
  420. if err != nil {
  421. return
  422. }
  423. sess.txBytes.Add(int64(n))
  424. s.totalBytes.Add(int64(n))
  425. }
  426. }
  427. // runServerTCPRx reads data sent from client over TCP
  428. func (s *BwTestServer) runServerTCPRx(ctx context.Context, sess *serverSession) {
  429. buf := make([]byte, 64*1024)
  430. for {
  431. select {
  432. case <-ctx.Done():
  433. return
  434. default:
  435. }
  436. n, err := sess.clientTCP.Read(buf)
  437. if err != nil {
  438. return
  439. }
  440. sess.rxBytes.Add(int64(n))
  441. s.totalBytes.Add(int64(n))
  442. }
  443. }
  444. // reportLoop periodically sends feedback reports back to the client over TCP
  445. func (s *BwTestServer) reportLoop(ctx context.Context, sess *serverSession) {
  446. ticker := time.NewTicker(300 * time.Millisecond)
  447. defer ticker.Stop()
  448. for {
  449. select {
  450. case <-ctx.Done():
  451. return
  452. case <-ticker.C:
  453. report := SessionHandshake{
  454. Type: "report",
  455. SessionID: sess.id,
  456. RxBytes: sess.rxBytes.Load(),
  457. RxPackets: sess.rxPackets.Load(),
  458. LostPackets: sess.lostPackets.Load(),
  459. }
  460. b, err := json.Marshal(report)
  461. if err == nil {
  462. _, _ = sess.clientTCP.Write(append(b, '\n'))
  463. }
  464. }
  465. }
  466. }