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:
ginuerzh
2026-06-04 23:01:12 +08:00
parent e6c9952ad4
commit e45328d1bb
8 changed files with 510 additions and 241 deletions
+70 -6
View File
@@ -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
}