--- /dev/null +++ b/protocol/obfhttp/protocol.go @@ -0,0 +1,169 @@ +// OMV +package obfhttp + +import ( + "errors" + "net" + "sync" + "sync/atomic" + "time" +) + +// Wire protocol actions +const ( + actionOpen = "o" + actionData = "d" + actionRecv = "r" + actionClose = "c" +) + +// request is the JSON structure sent by the client. +type request struct { + Session string `json:"s"` + Action string `json:"a"` + Destination string `json:"dst,omitempty"` + Network string `json:"n,omitempty"` + Payload string `json:"p,omitempty"` + Padding string `json:"t,omitempty"` +} + +// response is the JSON structure sent by the server. +type response struct { + Session string `json:"s"` + OK bool `json:"ok"` + Payload string `json:"p,omitempty"` + Padding string `json:"t,omitempty"` + Error string `json:"e,omitempty"` +} + +// sessionConn bridges HTTP requests with a net.Conn interface for the router. +// HTTP handler pushes upstream data and pulls downstream data through this. +// +// Uses separate sync.Cond for upstream and downstream to avoid spurious wakeups: +// - upCond: signaled by pushUpstream, waited by Read +// - downCond: signaled by Write, waited by pullDownstream +type sessionConn struct { + localAddr net.Addr + remoteAddr net.Addr + + upMu sync.Mutex + upCond *sync.Cond + upBuf []byte // data from client → router + + downMu sync.Mutex + downCond *sync.Cond + downBuf []byte // data from router → client + + closed atomic.Bool +} + +func newSessionConn(local, remote net.Addr) *sessionConn { + sc := &sessionConn{ + localAddr: local, + remoteAddr: remote, + } + sc.upCond = sync.NewCond(&sc.upMu) + sc.downCond = sync.NewCond(&sc.downMu) + return sc +} + +// pushUpstream is called by the HTTP handler to feed data from the client. +func (sc *sessionConn) pushUpstream(data []byte) { + sc.upMu.Lock() + defer sc.upMu.Unlock() + sc.upBuf = append(sc.upBuf, data...) + sc.upCond.Broadcast() +} + +// pullDownstream is called by the HTTP handler to get data destined for the client. +// It waits up to timeout for data to become available, correctly handling spurious wakeups. +func (sc *sessionConn) pullDownstream(timeout time.Duration) []byte { + sc.downMu.Lock() + defer sc.downMu.Unlock() + + if len(sc.downBuf) > 0 || sc.closed.Load() { + data := sc.downBuf + sc.downBuf = nil + return data + } + + // Use time.AfterFunc to broadcast on timeout, then loop-wait on the condition. + // The loop correctly handles spurious wakeups by re-checking the condition. + deadline := time.Now().Add(timeout) + timer := time.AfterFunc(timeout, func() { + sc.downCond.Broadcast() + }) + defer timer.Stop() + + for len(sc.downBuf) == 0 && !sc.closed.Load() && time.Now().Before(deadline) { + sc.downCond.Wait() + } + + data := sc.downBuf + sc.downBuf = nil + return data +} + +// Read implements net.Conn. Called by the router/outbound to read upstream data. +func (sc *sessionConn) Read(p []byte) (int, error) { + sc.upMu.Lock() + defer sc.upMu.Unlock() + + for len(sc.upBuf) == 0 && !sc.closed.Load() { + sc.upCond.Wait() + } + if len(sc.upBuf) == 0 && sc.closed.Load() { + return 0, net.ErrClosed + } + + n := copy(p, sc.upBuf) + sc.upBuf = sc.upBuf[n:] + return n, nil +} + +// Write implements net.Conn. Called by the router/outbound to send downstream data. +func (sc *sessionConn) Write(p []byte) (int, error) { + if sc.closed.Load() { + return 0, net.ErrClosed + } + + sc.downMu.Lock() + sc.downBuf = append(sc.downBuf, p...) + sc.downCond.Broadcast() + sc.downMu.Unlock() + return len(p), nil +} + +func (sc *sessionConn) Close() error { + if sc.closed.Swap(true) { + return nil + } + sc.upCond.Broadcast() + sc.downCond.Broadcast() + return nil +} + +func (sc *sessionConn) LocalAddr() net.Addr { return sc.localAddr } +func (sc *sessionConn) RemoteAddr() net.Addr { return sc.remoteAddr } +func (sc *sessionConn) SetDeadline(t time.Time) error { return nil } +func (sc *sessionConn) SetReadDeadline(t time.Time) error { return nil } +func (sc *sessionConn) SetWriteDeadline(t time.Time) error { return nil } + +// session tracks a single proxied connection. +type session struct { + conn *sessionConn + lastActive time.Time + user string +} + +// simpleAddr implements net.Addr for virtual addresses. +type simpleAddr struct { + network string + address string +} + +func (a *simpleAddr) Network() string { return a.network } +func (a *simpleAddr) String() string { return a.address } + +var errSessionNotFound = errors.New("session not found") +var errUnauthorized = errors.New("unauthorized")