fix(handler/sshd): unwrap limiter wrappers to fix wrong connection type (issue #867)

The sshd handler performed a concrete type assertion on the accepted
connection, but the SSH listener's Accept() wraps each connection with
a traffic limiter (limitConn), obscuring the underlying *DirectForwardConn
type and causing 'sshd: wrong connection type' whenever a limiter was
configured.

Add an UnwrapConn() method to the traffic limiter wrapper and an
unwrapConn() helper in the handler that peels through wrapper layers
before the type switch. The handler uses the unwrapped conn only for
the type-specific DstAddr() call, while the original limiter-wrapped
conn drives data transfer so rate limiting remains effective.

Also track the last accept/bind error on service Status so callers can
retrieve the cause when a service enters the failed state.
This commit is contained in:
ginuerzh
2026-06-13 14:29:51 +08:00
parent 8b0806bad4
commit bc44baba55
4 changed files with 53 additions and 5 deletions
+26 -5
View File
@@ -130,9 +130,9 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
return rate_limiter.ErrRateLimit
}
switch cc := conn.(type) {
switch cc := unwrapConn(conn).(type) {
case *sshd_util.DirectForwardConn:
return h.handleDirectForward(ctx, cc, ro, log, &pStats)
return h.handleDirectForward(ctx, cc.DstAddr(), conn, ro, log, &pStats)
case *sshd_util.RemoteForwardConn:
return h.handleRemoteForward(ctx, cc, ro, log, &pStats)
default:
@@ -142,9 +142,8 @@ func (h *forwardHandler) Handle(ctx context.Context, conn net.Conn, opts ...hand
}
}
func (h *forwardHandler) handleDirectForward(ctx context.Context, c *sshd_util.DirectForwardConn, ro *xrecorder.HandlerRecorderObject, log logger.Logger, stats *xstats.Stats) error {
targetAddr := c.DstAddr()
conn := stats_wrapper.WrapConn(c, stats)
func (h *forwardHandler) handleDirectForward(ctx context.Context, targetAddr string, origConn net.Conn, ro *xrecorder.HandlerRecorderObject, log logger.Logger, stats *xstats.Stats) error {
conn := stats_wrapper.WrapConn(origConn, stats)
ro.Host = targetAddr
log = log.WithFields(map[string]any{
@@ -361,6 +360,28 @@ func (h *forwardHandler) checkRateLimit(addr net.Addr) bool {
return true
}
// connUnwrapper is an interface for unwrapping net.Conn wrappers to access
// the underlying connection type.
type connUnwrapper interface {
UnwrapConn() net.Conn
}
// unwrapConn peels through net.Conn wrapper layers (e.g. traffic limiters)
// to find the underlying concrete connection type.
func unwrapConn(c net.Conn) net.Conn {
for {
uw, ok := c.(connUnwrapper)
if !ok {
return c
}
if inner := uw.UnwrapConn(); inner != nil {
c = inner
} else {
return c
}
}
}
func getHostPortFromAddr(addr net.Addr) (host string, port int, err error) {
host, portString, err := net.SplitHostPort(addr.String())
if err != nil {