fix(chain): Copy returns wrong pointer, connection leaks in Connect/Handshake, deferred cancel accumulation in retry loops

- transport.go: Copy() returned original pointer instead of the allocated copy,
  causing data races and incorrect routing for multiplexed chains.
- route.go: Connect and Handshake failures in intermediate nodes leaked the
  returned connection. Handshake fix preserves the original Connect result
  (guaranteed non-nil) and closes both the Handshake return value and input.
- router.go: defer cancel() inside retry loops accumulated context timers
  across iterations. Wrapped each loop body in an IIFE so defer scopes
  per-iteration and timers are released immediately.
This commit is contained in:
ginuerzh
2026-05-23 16:30:53 +08:00
parent d015378654
commit 569572d6f7
3 changed files with 97 additions and 78 deletions
+10 -1
View File
@@ -271,20 +271,29 @@ func (r *chainRoute) connect(ctx context.Context, logger logger.Logger) (conn ne
} }
cc, err = preNode.Options().Transport.Connect(ctx, cn, "tcp", addr) cc, err = preNode.Options().Transport.Connect(ctx, cn, "tcp", addr)
if err != nil { if err != nil {
if cc != nil {
cc.Close()
}
cn.Close() cn.Close()
if marker != nil { if marker != nil {
marker.Mark() marker.Mark()
} }
return return
} }
cc, err = node.Options().Transport.Handshake(ctx, cc) var cc2 net.Conn
cc2, err = node.Options().Transport.Handshake(ctx, cc)
if err != nil { if err != nil {
if cc2 != nil {
cc2.Close()
}
cc.Close()
cn.Close() cn.Close()
if marker != nil { if marker != nil {
marker.Mark() marker.Mark()
} }
return return
} }
cc = cc2
if marker != nil { if marker != nil {
marker.Reset() marker.Reset()
} }
+86 -76
View File
@@ -90,58 +90,63 @@ func (r *Router) dial(ctx context.Context, network, address string, log logger.L
log.Debugf("dial %s/%s", address, network) log.Debugf("dial %s/%s", address, network)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
ctx := ctx func() {
if r.options.Timeout > 0 { ctx := ctx
var cancel context.CancelFunc if r.options.Timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout) var cancel context.CancelFunc
defer cancel() ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
} defer cancel()
}
buf := ictx.BufferFromContext(ctx) buf := ictx.BufferFromContext(ctx)
if buf != nil { if buf != nil {
buf.Reset() buf.Reset()
} }
var ipAddr string var ipAddr string
ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, log) ipAddr, err = xnet.Resolve(ctx, "ip", address, r.options.Resolver, r.options.HostMapper, log)
if err != nil { if err != nil {
log.Error(err) log.Error(err)
return
}
if buf != nil {
buf.Reset()
}
var route chain.Route
if r.options.Chain != nil {
route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address))
}
if buf == nil {
buf = &bytes.Buffer{}
}
for _, node := range routePath(route) {
fmt.Fprintf(buf, "%s@%s > ", node.Name, node.Addr)
}
fmt.Fprintf(buf, "%s", ipAddr)
log.Debugf("route(retry=%d) %s", i, buf.String())
if route == nil {
route = DefaultRoute
}
conn, err = route.Dial(ctx, network, ipAddr,
append([]chain.DialOption{
chain.InterfaceDialOption(r.options.IfceName),
chain.NetnsDialOption(r.options.Netns),
chain.SockOptsDialOption(r.options.SockOpts),
chain.LoggerDialOption(log),
}, callerOpts...)...,
)
if err == nil {
return
}
log.Errorf("route(retry=%d) %s", i, err)
}()
if conn != nil || err == nil {
break break
} }
if buf != nil {
buf.Reset()
}
var route chain.Route
if r.options.Chain != nil {
route = r.options.Chain.Route(ctx, network, ipAddr, chain.WithHostRouteOption(address))
}
if buf == nil {
buf = &bytes.Buffer{}
}
for _, node := range routePath(route) {
fmt.Fprintf(buf, "%s@%s > ", node.Name, node.Addr)
}
fmt.Fprintf(buf, "%s", ipAddr)
log.Debugf("route(retry=%d) %s", i, buf.String())
if route == nil {
route = DefaultRoute
}
conn, err = route.Dial(ctx, network, ipAddr,
append([]chain.DialOption{
chain.InterfaceDialOption(r.options.IfceName),
chain.NetnsDialOption(r.options.Netns),
chain.SockOptsDialOption(r.options.SockOpts),
chain.LoggerDialOption(log),
}, callerOpts...)...,
)
if err == nil {
break
}
log.Errorf("route(retry=%d) %s", i, err)
} }
return return
@@ -160,39 +165,44 @@ func (r *Router) Bind(ctx context.Context, network, address string, opts ...chai
log.Debugf("bind on %s/%s", address, network) log.Debugf("bind on %s/%s", address, network)
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
ctx := ctx func() {
if r.options.Timeout > 0 { ctx := ctx
var cancel context.CancelFunc if r.options.Timeout > 0 {
ctx, cancel = context.WithTimeout(ctx, r.options.Timeout) var cancel context.CancelFunc
defer cancel() ctx, cancel = context.WithTimeout(ctx, r.options.Timeout)
} defer cancel()
}
var route chain.Route var route chain.Route
if r.options.Chain != nil { if r.options.Chain != nil {
route = r.options.Chain.Route(ctx, network, address) route = r.options.Chain.Route(ctx, network, address)
if route == nil || len(route.Nodes()) == 0 { if route == nil || len(route.Nodes()) == 0 {
err = ErrEmptyRoute err = ErrEmptyRoute
return
}
}
if log.IsLevelEnabled(logger.DebugLevel) {
buf := bytes.Buffer{}
for _, node := range routePath(route) {
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
}
fmt.Fprintf(&buf, "%s", address)
log.Debugf("route(retry=%d) %s", i, buf.String())
}
if route == nil {
route = DefaultRoute
}
ln, err = route.Bind(ctx, network, address, opts...)
if err == nil {
return return
} }
} log.Errorf("route(retry=%d) %s", i, err)
}()
if log.IsLevelEnabled(logger.DebugLevel) { if ln != nil || err == nil {
buf := bytes.Buffer{}
for _, node := range routePath(route) {
fmt.Fprintf(&buf, "%s@%s > ", node.Name, node.Addr)
}
fmt.Fprintf(&buf, "%s", address)
log.Debugf("route(retry=%d) %s", i, buf.String())
}
if route == nil {
route = DefaultRoute
}
ln, err = route.Bind(ctx, network, address, opts...)
if err == nil {
break break
} }
log.Errorf("route(retry=%d) %s", i, err)
} }
return return
+1 -1
View File
@@ -102,5 +102,5 @@ func (tr *Transport) Options() *chain.TransportOptions {
func (tr *Transport) Copy() chain.Transporter { func (tr *Transport) Copy() chain.Transporter {
tr2 := &Transport{} tr2 := &Transport{}
*tr2 = *tr *tr2 = *tr
return tr return tr2
} }