Files
omv-dijiang/works/patch/protocol/obfhttp/protocol.go.patch
T
2026-04-25 09:04:39 +08:00

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")