package main import ( "bytes" "context" "encoding/binary" "fmt" "net" "sync" "time" "dragontcp/internal/wire" ) type streamSession struct { sid wire.SessionID target net.Conn targetName string maxChunk int maxBuffer int debug *serverDebug mu sync.Mutex notify chan struct{} buf []byte base uint64 eof bool closed bool lastSeen time.Time upMu sync.Mutex expectedUp uint64 } func newStreamSession(sid wire.SessionID, target net.Conn, targetName string, maxChunk, maxBuffer int, debug *serverDebug) *streamSession { s := &streamSession{ sid: sid, target: target, targetName: targetName, maxChunk: maxChunk, maxBuffer: maxBuffer, debug: debug, notify: make(chan struct{}), lastSeen: time.Now(), } go s.readTarget() return s } func (s *streamSession) signalLocked() { close(s.notify) s.notify = make(chan struct{}) } func (s *streamSession) touchLocked() { s.lastSeen = time.Now() } func (s *streamSession) readTarget() { tmp := make([]byte, 64*1024) for { n, err := s.target.Read(tmp) if n > 0 { data := append([]byte(nil), tmp[:n]...) for len(data) > 0 { s.mu.Lock() for !s.closed && len(s.buf) >= s.maxBuffer { ch := s.notify s.mu.Unlock() <-ch s.mu.Lock() } if s.closed { s.mu.Unlock() return } room := s.maxBuffer - len(s.buf) take := len(data) if take > room { take = room } s.buf = append(s.buf, data[:take]...) data = data[take:] s.touchLocked() s.signalLocked() s.mu.Unlock() if s.debug != nil && s.debug.enabled { s.debug.bytesDown.Add(uint64(take)) } } } if err != nil { s.mu.Lock() if !s.closed { s.eof = true s.touchLocked() s.signalLocked() } s.mu.Unlock() return } } } func (s *streamSession) ackLocked(offset uint64) { if offset <= s.base { return } end := s.base + uint64(len(s.buf)) if offset > end { offset = end } drop := int(offset - s.base) if drop <= 0 { return } s.buf = s.buf[drop:] s.base = offset if len(s.buf) == 0 { s.buf = nil } else if cap(s.buf) > 4*len(s.buf) && cap(s.buf) > 1024*1024 { compact := append([]byte(nil), s.buf...) s.buf = compact } s.signalLocked() } func (s *streamSession) ack(offset uint64) { s.mu.Lock() s.ackLocked(offset) s.touchLocked() s.mu.Unlock() } func (s *streamSession) readAt(offset uint64, limit int, wait time.Duration) ([]byte, byte, error) { if limit < 1 || limit > s.maxChunk { return nil, wire.StatusError, fmt.Errorf("invalid download limit %d", limit) } deadline := time.Now().Add(wait) firstDataAt := time.Time{} for { s.mu.Lock() s.touchLocked() if offset < s.base { s.mu.Unlock() return nil, wire.StatusError, fmt.Errorf("download offset %d was already acknowledged (base=%d)", offset, s.base) } rel64 := offset - s.base if rel64 <= uint64(len(s.buf)) { rel := int(rel64) available := len(s.buf) - rel if available > 0 { if firstDataAt.IsZero() { firstDataAt = time.Now() } // Coalesce tiny target reads briefly. This prevents a 1-2 byte // producer read from becoming a permanent tiny tunnel record. if available < limit && !s.eof && wait > 0 && time.Since(firstDataAt) < 2*time.Millisecond { ch := s.notify s.mu.Unlock() select { case <-ch: case <-time.After(2 * time.Millisecond): } continue } n := available if n > limit { n = limit } out := append([]byte(nil), s.buf[rel:rel+n]...) s.mu.Unlock() return out, wire.StatusData, nil } if s.eof || s.closed { s.mu.Unlock() return nil, wire.StatusEOF, nil } } else { s.mu.Unlock() return nil, wire.StatusError, fmt.Errorf("download offset %d is beyond buffered stream end %d", offset, s.base+uint64(len(s.buf))) } if wait <= 0 || time.Now().After(deadline) { s.mu.Unlock() return nil, wire.StatusWait, nil } ch := s.notify remaining := time.Until(deadline) s.mu.Unlock() select { case <-ch: case <-time.After(remaining): return nil, wire.StatusWait, nil } } } func (s *streamSession) upload(offset uint64, data []byte) error { if len(data) == 0 || len(data) > s.maxChunk { return fmt.Errorf("invalid upload size %d", len(data)) } s.upMu.Lock() defer s.upMu.Unlock() if offset < s.expectedUp { // Idempotent retry after a lost ACK. if offset+uint64(len(data)) <= s.expectedUp { return nil } return fmt.Errorf("overlapping upload retry at %d", offset) } if offset != s.expectedUp { return fmt.Errorf("upload gap: got %d expected %d", offset, s.expectedUp) } if _, err := s.target.Write(data); err != nil { return err } s.expectedUp += uint64(len(data)) s.mu.Lock() s.touchLocked() s.mu.Unlock() if s.debug != nil && s.debug.enabled { s.debug.bytesUp.Add(uint64(len(data))) s.debug.pushRecords.Add(1) } return nil } func (s *streamSession) close() { s.mu.Lock() if s.closed { s.mu.Unlock() return } s.closed = true s.signalLocked() s.mu.Unlock() _ = s.target.Close() } type streamManager struct { mu sync.RWMutex sessions map[string]*streamSession timeout time.Duration debug *serverDebug } func sidKey(sid wire.SessionID) string { return string(sid[:]) } func newStreamManager(timeout time.Duration, debug *serverDebug) *streamManager { m := &streamManager{sessions: make(map[string]*streamSession), timeout: timeout, debug: debug} go m.cleanupLoop() return m } func (m *streamManager) get(sid wire.SessionID) *streamSession { m.mu.RLock() s := m.sessions[sidKey(sid)] m.mu.RUnlock() return s } func (m *streamManager) addOrGet(sid wire.SessionID, s *streamSession) (*streamSession, bool) { key := sidKey(sid) m.mu.Lock() if old := m.sessions[key]; old != nil { m.mu.Unlock() s.close() return old, false } m.sessions[key] = s m.mu.Unlock() return s, true } func (m *streamManager) remove(sid wire.SessionID) { key := sidKey(sid) m.mu.Lock() s := m.sessions[key] delete(m.sessions, key) m.mu.Unlock() if s != nil { s.close() if m.debug != nil && m.debug.enabled { m.debug.sessionsClosed.Add(1) m.debug.activeSessions.Add(-1) } } } func (m *streamManager) count() int { m.mu.RLock(); n := len(m.sessions); m.mu.RUnlock(); return n } func (m *streamManager) cleanupLoop() { ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() for range ticker.C { cutoff := time.Now().Add(-m.timeout) var stale []wire.SessionID m.mu.RLock() for _, s := range m.sessions { s.mu.Lock() last := s.lastSeen closed := s.closed sid := s.sid s.mu.Unlock() if closed || last.Before(cutoff) { stale = append(stale, sid) } } m.mu.RUnlock() for _, sid := range stale { m.remove(sid) } } } func parseProbe(payload []byte) (kind byte, value int, token string, err error) { if len(payload) < 11 || !bytes.Equal(payload[:4], wire.ProbeMagic[:]) { return 0, 0, "", fmt.Errorf("bad probe payload") } kind = payload[4] tl := int(binary.BigEndian.Uint16(payload[5:7])) value = int(binary.BigEndian.Uint32(payload[7:11])) if 11+tl > len(payload) { return 0, 0, "", fmt.Errorf("bad probe token length") } token = string(payload[11 : 11+tl]) return } func probePattern(n int) []byte { out := make([]byte, n) for i := range out { out[i] = byte((i*31 + 17) & 0xff) } return out } func parseOpen(payload []byte) (token, host string, port int, err error) { if len(payload) < 6 { return "", "", 0, fmt.Errorf("bad OPEN payload") } tl := int(binary.BigEndian.Uint16(payload[0:2])) hl := int(binary.BigEndian.Uint16(payload[2:4])) port = int(binary.BigEndian.Uint16(payload[4:6])) if port < 1 || 6+tl+hl != len(payload) { return "", "", 0, fmt.Errorf("bad OPEN lengths") } token = string(payload[6 : 6+tl]) host = string(payload[6+tl:]) if host == "" { return "", "", 0, fmt.Errorf("empty target host") } return } func processWireRequest(conn net.Conn, req wire.Request, token string, allowPrivate bool, cache *dnsCache, tcpBuffer int, manager *streamManager, maxChunk, maxBuffer int, pollWait time.Duration, debug *serverDebug) error { switch req.Mode { case wire.ModeProbe: kind, value, supplied, err := parseProbe(req.Payload) if err != nil { return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error())) } if !tokenEqual(supplied, token) { return wire.WriteResponse(conn, wire.StatusError, []byte("authentication failed")) } switch kind { case wire.ProbeUpload: if len(req.Payload) > maxChunk { return wire.WriteResponse(conn, wire.StatusError, []byte("probe too large")) } return wire.WriteResponse(conn, wire.StatusOK, nil) case wire.ProbeDownload: if value < 1 || value > maxChunk { return wire.WriteResponse(conn, wire.StatusError, []byte("probe too large")) } return wire.WriteMaskedResponse(conn, wire.StatusData, probePattern(value), req.Session, wire.ModeProbe, req.Seq) case wire.ProbeKeepalive: return wire.WriteResponse(conn, wire.StatusOK, nil) case wire.ProbeBatch: count := value if count < 1 { count = 1 } if count > 16 { count = 16 } for i := 0; i < count; i++ { data := probePattern(32) if err := wire.WriteMaskedResponse(conn, wire.StatusData, data, req.Session, wire.ModeProbe, req.Seq+uint64(i)); err != nil { return err } } return nil default: return wire.WriteResponse(conn, wire.StatusError, []byte("unknown probe kind")) } case wire.ModeOpen: supplied, host, port, err := parseOpen(req.Payload) if err != nil { return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error())) } if !tokenEqual(supplied, token) { return wire.WriteResponse(conn, wire.StatusError, []byte("authentication failed")) } if old := manager.get(req.Session); old != nil { body := make([]byte, 4) binary.BigEndian.PutUint32(body, uint32(maxChunk)) return wire.WriteMaskedResponse(conn, wire.StatusOK, body, req.Session, wire.ModeOpen, req.Seq) } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) target, err := dialTarget(ctx, host, port, allowPrivate, cache, tcpBuffer) cancel() if err != nil { return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error())) } session := newStreamSession(req.Session, target, fmt.Sprintf("%s:%d", host, port), maxChunk, maxBuffer, debug) _, created := manager.addOrGet(req.Session, session) if created && debug != nil && debug.enabled { debug.sessionsOpened.Add(1) debug.activeSessions.Add(1) debug.logf("SESSION OPEN sid=%x target=%s:%d active_sessions=%d", req.Session[:4], host, port, manager.count()) } body := make([]byte, 4) binary.BigEndian.PutUint32(body, uint32(maxChunk)) return wire.WriteMaskedResponse(conn, wire.StatusOK, body, req.Session, wire.ModeOpen, req.Seq) case wire.ModeUpload: s := manager.get(req.Session) if s == nil { return wire.WriteResponse(conn, wire.StatusError, []byte("unknown session")) } if len(req.Payload) > maxChunk { return wire.WriteResponse(conn, wire.StatusError, []byte("upload too large")) } if err := s.upload(req.Seq, req.Payload); err != nil { return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error())) } return wire.WriteResponse(conn, wire.StatusOK, nil) case wire.ModeDownload: s := manager.get(req.Session) if s == nil { return wire.WriteResponse(conn, wire.StatusError, []byte("unknown session")) } if len(req.Payload) != 14 { return wire.WriteResponse(conn, wire.StatusError, []byte("bad download request")) } ack := binary.BigEndian.Uint64(req.Payload[0:8]) limit := int(binary.BigEndian.Uint32(req.Payload[8:12])) count := int(binary.BigEndian.Uint16(req.Payload[12:14])) if limit < 1 { limit = 1 } if limit > maxChunk { limit = maxChunk } if count < 1 { count = 1 } if count > 256 { count = 256 } s.ack(ack) offset := req.Seq if debug != nil && debug.enabled { debug.pullRequests.Add(1) } for i := 0; i < count; i++ { wait := time.Duration(0) if i == 0 { wait = pollWait } data, status, err := s.readAt(offset, limit, wait) if err != nil { return wire.WriteResponse(conn, wire.StatusError, []byte(err.Error())) } switch status { case wire.StatusData: if debug != nil && debug.enabled { debug.dataRecords.Add(1) } if err := wire.WriteMaskedResponse(conn, wire.StatusData, data, req.Session, wire.ModeDownload, offset); err != nil { return err } offset += uint64(len(data)) case wire.StatusWait: if debug != nil && debug.enabled { debug.waitRecords.Add(1) } return wire.WriteResponse(conn, wire.StatusWait, nil) case wire.StatusEOF: return wire.WriteResponse(conn, wire.StatusEOF, nil) default: return wire.WriteResponse(conn, wire.StatusError, []byte("invalid session read status")) } } return nil case wire.ModeClose: manager.remove(req.Session) return wire.WriteResponse(conn, wire.StatusOK, nil) default: return wire.WriteResponse(conn, wire.StatusError, []byte("unknown mode")) } }