add credit based mux window

This commit is contained in:
iceBear67
2026-07-15 14:59:32 +00:00
parent ada07e0e36
commit bbe4efbe16
15 changed files with 651 additions and 139 deletions
+45 -14
View File
@@ -24,6 +24,8 @@ type Client struct {
mappings map[string]Mapping // normalized pattern -> mapping
pool *WorkerPool
streamWnd int // our advertised per-stream receive window (bytes)
mu sync.Mutex
ctrl *wire.FramedConn
}
@@ -44,16 +46,32 @@ func New(cfg *Config) *Client {
for _, m := range cfg.Mappings {
c.mappings[NormalizeAddress(m.Pattern)] = m
}
c.streamWnd = clampWindow(cfg.StreamWindowBytes)
c.pool = newWorkerPool(c, cfg.MaxConn)
return c
}
func clampWindow(w int) int {
if w <= 0 {
return DefaultStreamWindow
}
if w < MinStreamWindow {
return MinStreamWindow
}
if w > MaxStreamWindow {
return MaxStreamWindow
}
return w
}
// dialSession opens a TCP connection, performs the Intent-17 handshake, the
// Phase-A rekey, and reads SessionReady, returning an established frame conn.
func (c *Client) dialSession(magic byte) (*wire.FramedConn, error) {
// Phase-A rekey, and reads SessionReady, returning an established frame conn
// and the hub's advertised per-stream receive window. Per-stream flow control
// is mandatory: a hub that does not echo the STREAM_FC flag is rejected.
func (c *Client) dialSession(magic byte) (fc *wire.FramedConn, peerWnd int, err error) {
conn, err := net.DialTimeout("tcp", c.cfg.Server, 10*time.Second)
if err != nil {
return nil, err
return nil, 0, err
}
if tcp, ok := conn.(*net.TCPConn); ok {
_ = tcp.SetNoDelay(true)
@@ -68,24 +86,26 @@ func (c *Client) dialSession(magic byte) (*wire.FramedConn, error) {
// 1. plaintext Minecraft Handshake, Intent 17, address = hex(SHA3-224(PSK)).
hs := wire.BuildHandshake(ProtocolVersion, c.pskAddr, c.serverPort, IntentRedapricot)
if _, err := conn.Write(hs); err != nil {
return nil, err
return nil, 0, err
}
// 2. Phase-A ciphers derived from the PSK.
fc := wire.NewFramedConn(conn,
fc = wire.NewFramedConn(conn,
wire.CipherFor(c.pskBytes, wire.DirS2C), // in: server -> client
wire.CipherFor(c.pskBytes, wire.DirC2S), // out: client -> server
)
// 3. Rekey frame (Phase A).
// 3. Rekey frame (Phase A), including the mandatory feature flags and our
// per-stream receive window.
rnd := make([]byte, 16)
if _, err := crand.Read(rnd); err != nil {
return nil, err
return nil, 0, err
}
ts := time.Now().UnixMilli()
rekeyMsg := wire.NewWriter().U8(magic).VarInt(len(rnd)).Bytes(rnd).I64(ts).Out()
rekeyMsg := wire.NewWriter().U8(magic).VarInt(len(rnd)).Bytes(rnd).I64(ts).
VarInt(FlagStreamFC).VarInt(c.streamWnd).Out()
if err := fc.WriteFrame(rekeyMsg); err != nil {
return nil, err
return nil, 0, err
}
// 4. Switch to Phase-B ciphers: REKEY = Rand || Timestamp(I64 BE).
@@ -99,16 +119,26 @@ func (c *Client) dialSession(magic byte) (*wire.FramedConn, error) {
wire.CipherFor(rekey, wire.DirC2S),
)
// 5. SessionReady.
// 5. SessionReady: the type byte followed by the hub's accepted flags and
// its per-stream receive window. Both are required.
payload, err := fc.ReadFrame()
if err != nil {
return nil, err
return nil, 0, err
}
if len(payload) < 1 || payload[0] != CtlSessionReady {
return nil, fmt.Errorf("expected SessionReady, got %v", payload)
return nil, 0, fmt.Errorf("expected SessionReady, got %v", payload)
}
r := wire.NewReader(payload[1:])
flags, ferr := r.VarInt()
hubWnd, werr := r.VarInt()
if ferr != nil || werr != nil || flags&FlagStreamFC == 0 || hubWnd <= 0 {
return nil, 0, fmt.Errorf("hub did not accept per-stream flow control (unsupported hub version?)")
}
if hubWnd > MaxStreamWindow {
hubWnd = MaxStreamWindow
}
ok = true
return fc, nil
return fc, hubWnd, nil
}
// Start establishes the control session and registers all patterns. It returns
@@ -119,7 +149,7 @@ func (c *Client) Start(ctx context.Context) error {
}
func (c *Client) connectControl(ctx context.Context) error {
fc, err := c.dialSession(MagicControl)
fc, _, err := c.dialSession(MagicControl)
if err != nil {
return fmt.Errorf("control connect: %w", err)
}
@@ -233,6 +263,7 @@ func (c *Client) handleControlRequest(cid []byte, pattern, ip string, port int,
st := newStream(wc, sid, cid, mapping, ip, port)
wc.registerStream(sid, st)
wc.sendSyn(sid, cid)
go st.writeLoop()
go st.run()
}