e45328d1bb
Bug fixes: - packetConn.Read: fix slice bounds panic when dlen > len(b); n was set to dlen (the wire size) but only len(b) bytes were copied to b; callers doing b[:n] would panic. Clamp n via copy(). - lockWriter.Close: add mutex lock to prevent data race with Write; Router.Close calls connector.Close (lockWriter.Close) while handlePacket concurrently calls Writer.Write — both touch w.w. - Router.DelConnector: delete empty host slices from the map after removing the last connector, preventing unbounded map growth. Documentation: - Package-level doc with architecture diagram, data flow, component hierarchy, thread-safety invariants, and connector weighting rules. - Documented every exported type (Connector, Router, ConnectorPool, ConnectorOptions, routerHandler, metadata, lockWriter, packetConn) and all non-trivial methods with purpose, parameters, lifecycle, and algorithmic details (e.g., GetConnector weighted selection). - Annotated critical code paths with step-by-step comments (handleAssociate stages, handlePacket routing algorithm, handleEntrypoint packet format and forwarding logic). - Explained observeStats retry pattern and metadata key conventions. Verification: build, vet, 98 tests race-clean.
124 lines
3.2 KiB
Go
124 lines
3.2 KiB
Go
package router
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"errors"
|
|
"io"
|
|
"math"
|
|
"net"
|
|
"sync"
|
|
|
|
"github.com/go-gost/core/common/bufpool"
|
|
)
|
|
|
|
// packetConn wraps a stream-oriented net.Conn and provides datagram-
|
|
// oriented Read/Write by adding a 2-byte big-endian length prefix.
|
|
//
|
|
// This adapter allows IP packets (which have variable length) to be
|
|
// sent over a TCP connection. Each logical "packet" is framed as:
|
|
//
|
|
// ┌──────────┬──────────────────┐
|
|
// │ 2 bytes │ N bytes │
|
|
// │ (length) │ (packet data) │
|
|
// └──────────┴──────────────────┘
|
|
//
|
|
// The maximum packet size is math.MaxUint16 (65535 bytes).
|
|
//
|
|
// # Read behavior
|
|
//
|
|
// Read reads exactly one framed packet. If the caller's buffer is
|
|
// large enough, the data is read directly into it. If the buffer is
|
|
// too small, the full frame is read into a temporary buffer and
|
|
// truncated to fit — the return value n is clamped to len(b) so
|
|
// b[:n] is always valid.
|
|
//
|
|
// # Write behavior
|
|
//
|
|
// Write prepends a 2-byte length header before writing to the
|
|
// underlying connection. Writes exceeding math.MaxUint16 are
|
|
// rejected with an error.
|
|
type packetConn struct {
|
|
net.Conn
|
|
}
|
|
|
|
func (c *packetConn) Read(b []byte) (n int, err error) {
|
|
var bb [2]byte
|
|
_, err = io.ReadFull(c.Conn, bb[:])
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
dlen := int(binary.BigEndian.Uint16(bb[:]))
|
|
if len(b) >= dlen {
|
|
return io.ReadFull(c.Conn, b[:dlen])
|
|
}
|
|
|
|
// The caller's buffer is too small for the full packet. Read the
|
|
// complete frame from the underlying connection into a temporary
|
|
// buffer, then copy as much as fits. n is clamped to len(b) so
|
|
// that b[:n] is never out of bounds — the excess data is silently
|
|
// truncated.
|
|
buf := bufpool.Get(dlen)
|
|
defer bufpool.Put(buf)
|
|
|
|
_, err = io.ReadFull(c.Conn, buf)
|
|
n = copy(b, buf)
|
|
|
|
return
|
|
}
|
|
|
|
func (c *packetConn) Write(b []byte) (n int, err error) {
|
|
if len(b) > math.MaxUint16 {
|
|
err = errors.New("write: data maximum exceeded")
|
|
return
|
|
}
|
|
|
|
buf := bufpool.Get(len(b) + 2)
|
|
defer bufpool.Put(buf)
|
|
|
|
binary.BigEndian.PutUint16(buf[:2], uint16(len(b)))
|
|
n = copy(buf[2:], b)
|
|
|
|
return c.Conn.Write(buf)
|
|
}
|
|
|
|
// lockWriter wraps an io.Writer with a mutex to serialize writes.
|
|
//
|
|
// This is used as the writer stored in a Connector. Two goroutines may
|
|
// concurrently write to the same connector:
|
|
// - handlePacket: writes when an IP packet is routed to the connector
|
|
// - handleEntrypoint: writes when a packet arrives from another node
|
|
//
|
|
// Without serialization, concurrent Write calls to the underlying
|
|
// packetConn would interleave the 2-byte length headers with data,
|
|
// corrupting the stream.
|
|
//
|
|
// Both Write and Close hold the mutex to prevent racing on the
|
|
// underlying writer.
|
|
type lockWriter struct {
|
|
w io.Writer
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// LockWriter creates a mutex-guarded wrapper around w.
|
|
func LockWriter(w io.Writer) io.Writer {
|
|
return &lockWriter{w: w}
|
|
}
|
|
|
|
func (w *lockWriter) Write(p []byte) (int, error) {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
return w.w.Write(p)
|
|
}
|
|
|
|
func (w *lockWriter) Close() error {
|
|
w.mu.Lock()
|
|
defer w.mu.Unlock()
|
|
|
|
if closer, ok := w.w.(io.Closer); ok {
|
|
return closer.Close()
|
|
}
|
|
return nil
|
|
}
|