173 lines
4.5 KiB
Diff
173 lines
4.5 KiB
Diff
--- /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")
|