From d256712eeb5e5721b1b5e94cf2e9575c16615e3f Mon Sep 17 00:00:00 2001 From: xuans Date: Mon, 21 Sep 2026 01:20:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=9C=AC=E6=9C=BA=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=E8=BA=AB=E4=BB=BD=E6=A0=A1=E9=AA=8C=E4=B8=8E=E5=85=B3?= =?UTF-8?q?=E9=97=AD=E8=BF=9E=E6=8E=A5=E7=9A=84=E7=AB=9E=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../internal/localreset/authorization_test.go | 110 ++++++++++++++++++ backend/internal/localreset/protocol.go | 6 + 2 files changed, 116 insertions(+) create mode 100644 backend/internal/localreset/authorization_test.go diff --git a/backend/internal/localreset/authorization_test.go b/backend/internal/localreset/authorization_test.go new file mode 100644 index 0000000..3172a20 --- /dev/null +++ b/backend/internal/localreset/authorization_test.go @@ -0,0 +1,110 @@ +package localreset + +import ( + "context" + "net" + "sync" + "sync/atomic" + "testing" + "time" +) + +type authorizationTestConnection struct { + net.Conn + closed chan struct{} + once sync.Once +} + +func (c *authorizationTestConnection) Close() error { + c.once.Do(func() { close(c.closed) }) + return c.Conn.Close() +} + +type authorizationTestListener struct { + pending chan net.Conn + closed chan struct{} + once sync.Once +} + +func (l *authorizationTestListener) Accept() (net.Conn, error) { + select { + case conn := <-l.pending: + return conn, nil + case <-l.closed: + return nil, net.ErrClosed + } +} +func (l *authorizationTestListener) Close() error { + l.once.Do(func() { close(l.closed) }) + return nil +} +func (l *authorizationTestListener) Addr() net.Addr { return nil } + +// 校验原生句柄期间不能关闭连接;否则底层句柄可能已释放或被其他对象复用。 +func TestAuthorizationCannotRaceWithServerClose(t *testing.T) { + left, right := net.Pipe() + defer right.Close() + conn := &authorizationTestConnection{Conn: left, closed: make(chan struct{})} + defer conn.Close() + listener := &authorizationTestListener{pending: make(chan net.Conn, 1), closed: make(chan struct{})} + listener.pending <- conn + entered := make(chan struct{}) + release := make(chan struct{}) + var releaseOnce sync.Once + unblock := func() { releaseOnce.Do(func() { close(release) }) } + defer unblock() + var closedDuringAuthorization atomic.Bool + server := newServer(listener, func(net.Conn) (Peer, error) { + close(entered) + <-release + select { + case <-conn.closed: + closedDuringAuthorization.Store(true) + default: + } + return Peer{}, ErrUnauthorized + }, nil) + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + served := make(chan error, 1) + go func() { + served <- server.Serve(ctx, func(context.Context, Peer) (Session, error) { + t.Error("校验失败后仍调用了重置业务") + return nil, ErrUnavailable + }) + }() + select { + case <-entered: + case <-ctx.Done(): + t.Fatal("身份校验未启动") + } + closing := make(chan struct{}) + closed := make(chan error, 1) + go func() { close(closing); closed <- server.Close() }() + <-closing + select { + case <-conn.closed: + t.Error("身份校验尚未完成,连接却已被关闭") + case <-time.After(50 * time.Millisecond): + } + unblock() + select { + case err := <-closed: + if err != nil { + t.Error(err) + } + case <-ctx.Done(): + t.Fatal("释放校验后服务仍无法关闭") + } + select { + case err := <-served: + if err != nil { + t.Error(err) + } + case <-ctx.Done(): + t.Fatal("服务关闭没有等待连接处理完成") + } + if closedDuringAuthorization.Load() { + t.Fatal("身份校验读取了已经关闭的原生连接") + } +} diff --git a/backend/internal/localreset/protocol.go b/backend/internal/localreset/protocol.go index c77d23a..406d4d4 100644 --- a/backend/internal/localreset/protocol.go +++ b/backend/internal/localreset/protocol.go @@ -184,7 +184,13 @@ func (s *Server) handle(parent context.Context, conn net.Conn, factory Factory) if conn.SetDeadline(deadline) != nil { return } + s.mu.Lock() + if s.closed { + s.mu.Unlock() + return + } peer, err := s.authorize(conn) + s.mu.Unlock() if err != nil { _ = writeFrame(conn, Response{Version: Version, Code: "UNAUTHORIZED"}) return