net/netutil: fix regression where peerapi would get closed after 1st req

I introduced a bug in 8fe503057d when unifying oneConnListener
implementations.

The NewOneConnListenerFrom API was easy to misuse (its Close method
closes the underlying Listener), and we did (via http.Serve, which
closes the listener after use, which meant we were close the peerapi's
listener, even though we only wanted its Addr)

Instead, combine those two constructors into one and pass in the Addr
explicitly, without delegating through to any Listener.

Change-Id: I061d7e5f842e0cada416e7b2dd62100d4f987125
Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
Brad Fitzpatrick 2022-02-22 13:29:17 -08:00 committed by Brad Fitzpatrick
parent e31d68d64e
commit bb94561c96
4 changed files with 86 additions and 17 deletions

View File

@ -411,7 +411,7 @@ func (s *peerAPIServer) listen(ip netaddr.IP, ifState *interfaces.State) (ln net
} }
// Make a best effort to pick a deterministic port number for // Make a best effort to pick a deterministic port number for
// the ip The lower three bytes are the same for IPv4 and IPv6 // the ip. The lower three bytes are the same for IPv4 and IPv6
// Tailscale addresses (at least currently), so we'll usually // Tailscale addresses (at least currently), so we'll usually
// get the same port number on both address families for // get the same port number on both address families for
// dev/debugging purposes, which is nice. But it's not so // dev/debugging purposes, which is nice. But it's not so
@ -507,7 +507,7 @@ func (pln *peerAPIListener) ServeConn(src netaddr.IPPort, c net.Conn) {
if addH2C != nil { if addH2C != nil {
addH2C(httpServer) addH2C(httpServer)
} }
go httpServer.Serve(netutil.NewOneConnListenerFrom(c, pln.ln)) go httpServer.Serve(netutil.NewOneConnListener(c, pln.ln.Addr()))
} }
// peerAPIHandler serves the Peer API for a source specific client. // peerAPIHandler serves the Peer API for a source specific client.

View File

@ -312,7 +312,7 @@ func (s *Server) serveConn(ctx context.Context, c net.Conn, logf logger.Logf) {
ErrorLog: logger.StdLogger(logf), ErrorLog: logger.StdLogger(logf),
Handler: s.localhostHandler(ci), Handler: s.localhostHandler(ci),
} }
httpServer.Serve(netutil.NewOneConnListener(&protoSwitchConn{s: s, br: br, Conn: c})) httpServer.Serve(netutil.NewOneConnListener(&protoSwitchConn{s: s, br: br, Conn: c}, nil))
return return
} }

View File

@ -8,36 +8,51 @@
import ( import (
"io" "io"
"net" "net"
"sync"
) )
// NewOneConnListener returns a net.Listener that returns c on its first // NewOneConnListener returns a net.Listener that returns c on its
// Accept and EOF thereafter. The Listener's Addr is a dummy address. // first Accept and EOF thereafter.
func NewOneConnListener(c net.Conn) net.Listener { //
return NewOneConnListenerFrom(c, dummyListener{}) // The returned Listener's Addr method returns addr if non-nil. If nil,
} // Addr returns a non-nil dummy address instead.
func NewOneConnListener(c net.Conn, addr net.Addr) net.Listener {
// NewOneConnListenerFrom returns a net.Listener wrapping ln where if addr == nil {
// its Accept returns c on the first call and io.EOF thereafter. addr = dummyAddr("one-conn-listener")
func NewOneConnListenerFrom(c net.Conn, ln net.Listener) net.Listener { }
return &oneConnListener{c, ln} return &oneConnListener{
addr: addr,
conn: c,
}
} }
type oneConnListener struct { type oneConnListener struct {
addr net.Addr
mu sync.Mutex
conn net.Conn conn net.Conn
net.Listener
} }
func (l *oneConnListener) Accept() (c net.Conn, err error) { func (ln *oneConnListener) Accept() (c net.Conn, err error) {
c = l.conn ln.mu.Lock()
defer ln.mu.Unlock()
c = ln.conn
if c == nil { if c == nil {
err = io.EOF err = io.EOF
return return
} }
err = nil err = nil
l.conn = nil ln.conn = nil
return return
} }
func (ln *oneConnListener) Addr() net.Addr { return ln.addr }
func (ln *oneConnListener) Close() error {
ln.Accept() // guarantee future call returns io.EOF
return nil
}
type dummyListener struct{} type dummyListener struct{}
func (dummyListener) Close() error { return nil } func (dummyListener) Close() error { return nil }

View File

@ -0,0 +1,54 @@
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package netutil
import (
"io"
"net"
"testing"
)
type conn struct {
net.Conn
}
func TestOneConnListener(t *testing.T) {
c1 := new(conn)
a1 := dummyAddr("a1")
// Two Accepts
ln := NewOneConnListener(c1, a1)
if got := ln.Addr(); got != a1 {
t.Errorf("Addr = %#v; want %#v", got, a1)
}
c, err := ln.Accept()
if err != nil {
t.Fatal(err)
}
if c != c1 {
t.Fatalf("didn't get c1; got %p", c)
}
c, err = ln.Accept()
if err != io.EOF {
t.Errorf("got %v; want EOF", err)
}
if c != nil {
t.Errorf("unexpected non-nil Conn")
}
// Close before Accept
ln = NewOneConnListener(c1, a1)
ln.Close()
_, err = ln.Accept()
if err != io.EOF {
t.Fatalf("got %v; want EOF", err)
}
// Implicit addr
ln = NewOneConnListener(c1, nil)
if ln.Addr() == nil {
t.Errorf("nil Addr")
}
}