fix(handler/router): 3 bugs fixed + comprehensive data-flow documentation
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.
This commit is contained in:
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// Error sentinel values returned by the router handler.
|
||||
var (
|
||||
ErrBadVersion = errors.New("bad version")
|
||||
ErrUnknownCmd = errors.New("unknown command")
|
||||
@@ -36,20 +37,45 @@ func init() {
|
||||
registry.HandlerRegistry().Register("router", NewHandler)
|
||||
}
|
||||
|
||||
// routerHandler is the main handler for the GOST relay router protocol.
|
||||
//
|
||||
// It acts as the server-side component of a tunnel mesh: accepts TCP
|
||||
// connections from client nodes, authenticates them, and routes IP
|
||||
// packets between the mesh participants.
|
||||
//
|
||||
// # Architecture
|
||||
//
|
||||
// ┌───────────────┐
|
||||
// │ routerHandler │
|
||||
// ├───────────────┤
|
||||
// │ pool │ ── ConnectorPool: manages all active connectors
|
||||
// │ epConn │ ── UDP packet conn for inter-node forwarding
|
||||
// │ sdCache │ ── Cache for service discovery lookups
|
||||
// │ routeCache │ ── Cache for route lookups
|
||||
// │ stats │ ── Per-connector traffic statistics
|
||||
// │ limiter │ ── Traffic rate limiter (per-client)
|
||||
// └───────────────┘
|
||||
//
|
||||
// The handler only supports relay.CmdAssociate (IP packet forwarding).
|
||||
// Other commands return ErrUnknownCmd.
|
||||
type routerHandler struct {
|
||||
id string
|
||||
options handler.Options
|
||||
pool *ConnectorPool
|
||||
epConn net.PacketConn
|
||||
epConn net.PacketConn // UDP socket for inter-node packet forwarding
|
||||
md metadata
|
||||
log logger.Logger
|
||||
stats *stats_util.HandlerStats
|
||||
limiter traffic.TrafficLimiter
|
||||
cancel context.CancelFunc
|
||||
sdCache *cache.Cache
|
||||
routeCache *cache.Cache
|
||||
cancel context.CancelFunc // cancels background goroutines (observeStats)
|
||||
sdCache *cache.Cache // service discovery address cache
|
||||
routeCache *cache.Cache // route lookup cache
|
||||
}
|
||||
|
||||
// NewHandler creates a new router handler.
|
||||
//
|
||||
// Caches are initialized with a 1-minute default TTL; individual entries
|
||||
// may override this via metadata configuration.
|
||||
func NewHandler(opts ...handler.Option) handler.Handler {
|
||||
options := handler.Options{}
|
||||
for _, opt := range opts {
|
||||
@@ -63,6 +89,15 @@ func NewHandler(opts ...handler.Option) handler.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// Init initializes the handler with the given metadata.
|
||||
//
|
||||
// Initialization sequence:
|
||||
// 1. Parse metadata (read timeout, buffer size, entrypoint, etc.)
|
||||
// 2. Generate a random node ID (UUID)
|
||||
// 3. Create the connector pool
|
||||
// 4. Initialize the UDP entrypoint listener (if configured)
|
||||
// 5. Start the observer stats goroutine (if an observer is set)
|
||||
// 6. Initialize the traffic limiter (if configured)
|
||||
func (h *routerHandler) Init(md md.Metadata) (err error) {
|
||||
if err := h.parseMetadata(md); err != nil {
|
||||
return err
|
||||
@@ -102,6 +137,12 @@ func (h *routerHandler) Init(md md.Metadata) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// initEntrypoint creates the UDP listening socket for inter-node
|
||||
// packet forwarding and starts the background read loop.
|
||||
//
|
||||
// The entrypoint is optional — if no entrypoint address is configured,
|
||||
// this node cannot receive packets from other mesh nodes (it can only
|
||||
// forward packets to connectors that were established via TCP).
|
||||
func (h *routerHandler) initEntrypoint() (err error) {
|
||||
if h.md.entryPoint == "" {
|
||||
return
|
||||
@@ -131,6 +172,23 @@ func (h *routerHandler) initEntrypoint() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
// Handle processes an incoming TCP connection using the relay protocol.
|
||||
//
|
||||
// # Protocol flow
|
||||
//
|
||||
// 1. Read the relay request from the client (with optional read timeout).
|
||||
// 2. Validate the protocol version.
|
||||
// 3. Parse features: user authentication, addresses, tunnel ID, network.
|
||||
// 4. Authenticate the client (if an auther is configured).
|
||||
// 5. Dispatch to handleAssociate for CmdAssociate, or reject with error.
|
||||
//
|
||||
// The connection is always closed on return (via defer).
|
||||
//
|
||||
// # Feature parsing order
|
||||
//
|
||||
// The relay protocol allows multiple AddrFeatures. The first AddrFeature
|
||||
// is treated as the source address, the second as the destination. This
|
||||
// mirrors the behavior of other relay-based handlers (e.g., handler/relay).
|
||||
func (h *routerHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler.HandleOption) (err error) {
|
||||
defer conn.Close()
|
||||
|
||||
@@ -163,6 +221,8 @@ func (h *routerHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
|
||||
return err
|
||||
}
|
||||
|
||||
// Clear the read deadline — subsequent data transfer (IP packets
|
||||
// through the packetConn) should not be time-limited.
|
||||
conn.SetReadDeadline(time.Time{})
|
||||
|
||||
resp := relay.Response{
|
||||
@@ -210,6 +270,7 @@ func (h *routerHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
|
||||
log = log.WithFields(map[string]any{"user": user})
|
||||
}
|
||||
|
||||
// Authenticate before establishing the tunnel association.
|
||||
if h.options.Auther != nil {
|
||||
clientID, ok := h.options.Auther.Authenticate(ctx, user, pass, auth.WithService(h.options.Service))
|
||||
if !ok {
|
||||
@@ -233,7 +294,11 @@ func (h *routerHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl
|
||||
}
|
||||
}
|
||||
|
||||
// Close implements io.Closer interface.
|
||||
// Close shuts down the handler: closes the entrypoint UDP socket,
|
||||
// closes the connector pool (which closes all connectors), and
|
||||
// cancels background goroutines.
|
||||
//
|
||||
// Implements io.Closer.
|
||||
func (h *routerHandler) Close() error {
|
||||
if h.epConn != nil {
|
||||
h.epConn.Close()
|
||||
@@ -246,4 +311,3 @@ func (h *routerHandler) Close() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user