diff --git a/handler/serial/handler.go b/handler/serial/handler.go index e0643601..505c30c8 100644 --- a/handler/serial/handler.go +++ b/handler/serial/handler.go @@ -179,7 +179,10 @@ func (h *serialHandler) Handle(ctx context.Context, conn net.Conn, opts ...handl t := time.Now() log.Infof("%s <-> %s", conn.LocalAddr(), "@") // xnet.Transport(conn, cc) - xnet.Pipe(ctx, conn, cc) + if err := xnet.Pipe(ctx, conn, cc); err != nil { + log.Errorf("pipe: %v", err) + return err + } log.WithFields(map[string]any{ "duration": time.Since(t), }).Infof("%s >-< %s", conn.LocalAddr(), "@") @@ -236,6 +239,11 @@ func (h *serialHandler) forwardSerial(ctx context.Context, conn net.Conn, target // If the router chain returned an actual error, we skip the fallback // and propagate the error — a configured chain is authoritative. if port == nil && err == nil { + if h.options.Router != nil { + if opts := h.options.Router.Options(); opts != nil && opts.Chain != nil { + log.Warnf("chain dial returned no connection and no error, falling back to direct serial port") + } + } cfg.ReadTimeout = h.md.timeout port, err = serial.OpenPort(cfg) } @@ -251,7 +259,10 @@ func (h *serialHandler) forwardSerial(ctx context.Context, conn net.Conn, target // Pipe bidirectionally between the client connection and the serial port. // xnet.Pipe spawns two goroutines and returns on first error or when // both directions complete. - xnet.Pipe(ctx, conn, port) + if err := xnet.Pipe(ctx, conn, port); err != nil { + log.Errorf("pipe: %v", err) + return err + } log.WithFields(map[string]any{ "duration": time.Since(t), }).Infof("%s >-< %s", conn.LocalAddr(), target.Addr) diff --git a/handler/sni/handler.go b/handler/sni/handler.go index 1ea899ce..4959e662 100644 --- a/handler/sni/handler.go +++ b/handler/sni/handler.go @@ -122,12 +122,10 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. if h.md.readTimeout > 0 { conn.SetReadDeadline(time.Now().Add(h.md.readTimeout)) + defer conn.SetReadDeadline(time.Time{}) } br := bufio.NewReader(conn) proto, sniffErr := sniffing.Sniff(ctx, br) - if h.md.readTimeout > 0 { - conn.SetReadDeadline(time.Time{}) - } if sniffErr != nil { log.Debugf("sniff: %v", sniffErr) } @@ -189,7 +187,8 @@ func (h *sniHandler) Handle(ctx context.Context, conn net.Conn, opts ...handler. sniffing.WithLog(log), ) default: - return errors.New("unknown traffic") + log.Debugf("unrecognized traffic from %s", conn.RemoteAddr()) + return nil } } diff --git a/handler/sni/metadata.go b/handler/sni/metadata.go index 5f00e62d..00a64409 100644 --- a/handler/sni/metadata.go +++ b/handler/sni/metadata.go @@ -13,11 +13,11 @@ import ( ) type metadata struct { - // readTimeout is passed to SnifferBuilder as the timeout for reading - // upstream response headers during HTTP/TLS sniffing. It is NOT used - // as a deadline on the initial client connection (unlike socks/ss - // handlers), because SNI routing has no protocol handshake beyond - // the TLS ClientHello which is parsed during sniffing. + // readTimeout controls two behaviors: + // 1. A read deadline set on the client connection before sniffing the + // TLS ClientHello / HTTP request (see handler.go Handle). + // 2. Passed to SnifferBuilder as the timeout for reading upstream + // response headers during HTTP/TLS sniffing. // 0 or negative defaults to 15s. readTimeout time.Duration hash string