package irohimport ()// ProtocolHandler handles connections accepted for a single ALPN// (Application-Layer Protocol Negotiation) value. A [Router] dispatches each// incoming connection to the handler registered for its negotiated ALPN.//// Accept is called in its own goroutine for every accepted connection; it should// run for the lifetime of the connection and return when done. A returned error// is logged. A handler must not panic; a panic is recovered and logged, closing// only that connection while the router continues accepting. It is the Go// analog of the Rust ProtocolHandler trait (iroh/src/protocol.rs:228).typeProtocolHandlerinterface {// Accept handles an accepted connection. ctx is cancelled when the router // shuts down.Accept(ctx context.Context, conn *Conn) error}// ProtocolHandlerFunc adapts a function to [ProtocolHandler].typeProtocolHandlerFuncfunc(ctx context.Context, conn *Conn) error// Accept calls f(ctx, conn).func ( ProtocolHandlerFunc) ( context.Context, *Conn) error {return (, )}// AcceptingHandler is an optional interface a [ProtocolHandler] may implement// to intercept an incoming connection before it is converted to a verified// [Conn]. The default behavior is [Accepting.Connection].typeAcceptingHandlerinterface {OnAccepting(ctx context.Context, accepting *Accepting) (*Conn, error)}// ShutdownHandler is an optional interface a [ProtocolHandler] may implement to// run cleanup when its [Router] shuts down. The router calls Shutdown on every// registered handler that implements it before closing the endpoint, giving// handlers a chance to close connections gracefully. It mirrors the Rust// ProtocolHandler::shutdown hook (iroh/src/protocol.rs:284).typeShutdownHandlerinterface {// Shutdown is called once when the router is shutting down.Shutdown(ctx context.Context)}// IncomingFilterOutcome is the decision an [IncomingFilter] returns for an// incoming connection. It mirrors the Rust IncomingFilterOutcome// (iroh/src/protocol.rs).typeIncomingFilterOutcomeintconst (// FilterAccept accepts the connection and dispatches it to a handler.FilterAcceptIncomingFilterOutcome = iota// FilterRetry asks the peer to retry. Router evaluates this outcome before // qng constructs a connection, so it emits a real QUIC Retry packet.FilterRetry// FilterReject refuses the connection.FilterReject// FilterIgnore closes the incoming connection without dispatching it.FilterIgnore)// IncomingFilter decides whether to accept each incoming connection. Router// evaluates FilterRetry at QUIC Initial admission time, before ALPN negotiation;// other outcomes are evaluated in the accept loop after qng has an early// connection. It mirrors the Rust IncomingFilter (iroh/src/protocol.rs).typeIncomingFilterfunc(*Incoming) IncomingFilterOutcome// RouterConfig configures a [Router].typeRouterConfigstruct {// IncomingFilter is consulted for each incoming connection. IncomingFilter IncomingFilter// Logger records handler errors and recovered panics. If nil, // [slog.Default] is used. Logger *slog.Logger}// NewRouter registers every handler ALPN on ep, starts the accept loop, and// returns the running router. The endpoint must not already be listening (do not// pass [WithALPNs] to [Bind] when using a Router).//// The handlers map is keyed by exact ALPN string. ALPN values are opaque byte// strings represented as Go strings; printable ASCII protocol names are// conventional, but binary values compare byte-for-byte. NewRouter copies the// map before returning.func ( *Endpoint, map[string]ProtocolHandler, *RouterConfig) (*Router, error) {if := .acquireAcceptOwner(acceptOwnerRouter); != nil {returnnil, } := truedeferfunc() {if { .releaseAcceptOwner(acceptOwnerRouter) } }() = maps.Clone() := slog.Default() := IncomingFilter(nil)if != nil { = .IncomingFilter = .Logger }if == nil { = slog.Default() } := make([]string, 0, len())for := range { = append(, ) } := .sourceAddressValidation()if != nil { .setSourceAddressValidation(func( net.Addr) bool {if != nil && () {returntrue }return (&Incoming{ep: , remote: }) == FilterRetry }) }if := .setALPNs(, acceptOwnerRouter); != nil { .setSourceAddressValidation()returnnil, fmt.Errorf("iroh: new router: %w", ) }for , := range {if , := .(streamListenerHandler); { .l.addr = net.UDPAddrFromAddrPort(.LocalAddr()) } } , := context.WithCancel(context.Background()) := &Router{ep: ,handlers: ,filter: ,restoreSourceValidation: func() { .setSourceAddressValidation() },logger: ,cancel: ,ctx: , } .wg.Add(1)go .acceptLoop() = falsereturn , nil}// Router accepts incoming connections on an [Endpoint] and dispatches each to// the [ProtocolHandler] registered for its negotiated ALPN. Start one with// [NewRouter]; stop it with [Router.Shutdown]. It is the Go analog of the Rust// Router (iroh/src/protocol.rs:97).//// Dispatch is by exact ALPN string. One goroutine runs the accept loop; each// accepted connection is handled in a child goroutine with a context derived// from the router's. A panic in a handler goroutine is recovered, logged, and// stops the accept loop.typeRouterstruct { ep *Endpoint handlers map[string]ProtocolHandler filter IncomingFilter restoreSourceValidation func() logger *slog.Logger ctx context.Context cancel context.CancelFunc wg sync.WaitGroup mu sync.Mutex shutdown bool}// Endpoint returns the endpoint the router accepts on.func ( *Router) () *Endpoint { return .ep }// IsShutdown reports whether the router has been shut down.func ( *Router) () bool { .mu.Lock()defer .mu.Unlock()return .shutdown}// acceptLoop accepts connections until ctx is cancelled or the endpoint closes.// Each connection is dispatched in a child goroutine.func ( *Router) ( context.Context) {defer .wg.Done()for {select {case<-.Done():returndefault: } , := .ep.acceptIncoming()if != nil {// A cancelled context or a closed endpoint ends the loop cleanly.if .Err() != nil || errors.Is(, ErrEndpointClosed) {return }// A failed accept (e.g. a peer aborting the handshake) is logged and // the loop continues. The endpoint surfaces a hard close via the // checks above. .logger.Warn("router: accept failed", "err", )continue }if .filter != nil {switch .filter() {caseFilterAccept:caseFilterRetry: .Ignore()continuecaseFilterReject: .Refuse()continuecaseFilterIgnore: .Ignore()continue } } , := .Accept()if != nil { .logger.Warn("router: incoming accept failed", "err", )continue } .wg.Add(1)gofunc( *Accepting) {defer .wg.Done()varstringdeferfunc() {if := recover(); != nil { .qc.CloseWithError(0, "handler panic") .logger.Error("router: handler panicked", "alpn", , "panic", ) } }()varerror , = .ALPN()if != nil {if !handlerShutdownErr(, ) { .logger.Warn("router: accepting ALPN failed", "err", ) }return } , := .handlers[]if ! { .logger.Warn("router: no handler for ALPN", "alpn", ) .qc.CloseWithError(0, "unsupported ALPN")return }var *Connif , := .(AcceptingHandler); { , = .OnAccepting(, ) } else { , = .Connection() }if != nil { .logger.Warn("router: on accepting failed", "alpn", , "err", )return }if := .Accept(, ); != nil && !handlerShutdownErr(, ) { .logger.Warn("router: handler returned error", "alpn", .ALPN(), "err", ) } }() }}func handlerShutdownErr( context.Context, error) bool {if .Err() != nil && errors.Is(, .Err()) {returntrue }returnerrors.Is(, net.ErrClosed)}// Shutdown stops the router: it cancels the accept loop and all handler// contexts, calls Shutdown on every registered handler that implements// [ShutdownHandler], closes the endpoint, and waits for the accept loop and// handler goroutines to finish or ctx to be done. It is idempotent and the Go// analog of the Rust Router::shutdown (iroh/src/protocol.rs:429).func ( *Router) ( context.Context) error { .mu.Lock()if .shutdown { .mu.Unlock()returnnil } .shutdown = true .mu.Unlock()defer .ep.releaseAcceptOwner(acceptOwnerRouter)if .restoreSourceValidation != nil { .restoreSourceValidation() }// Stop accepting and cancel handler contexts. .cancel()// Give handlers a chance to close connections gracefully before the endpoint // force-closes them. Rust awaits all protocol shutdown futures // concurrently; do the same so one slow protocol does not block the rest.varsync.WaitGroupfor , := range .handlers {if , := .(ShutdownHandler); { .Add(1)gofunc( ShutdownHandler) {defer .Done() .Shutdown() }() } } := make(chanstruct{})gofunc() { .Wait()close() }()select {case<-:case<-.Done():return .Err() } := .ep.Shutdown()// Wait for the accept loop and handler goroutines, bounded by ctx. := make(chanstruct{})gofunc() { .wg.Wait()close() }()select {case<-:case<-.Done():return .Err() }return}
The pages are generated with Goldsv0.8.4. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.