listen: Only error out if not able to bind any interface (#17353)

This commit is contained in:
Anis Eleuch 2023-06-12 17:09:28 +01:00 committed by GitHub
parent be45ffd8a4
commit bb24346e04
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 108 additions and 107 deletions

View File

@ -612,7 +612,14 @@ func serverMain(ctx *cli.Context) {
httpServer.TCPOptions.Trace = bootstrapTrace httpServer.TCPOptions.Trace = bootstrapTrace
go func() { go func() {
globalHTTPServerErrorCh <- httpServer.Start(GlobalContext) serveFn, err := httpServer.Init(GlobalContext, func(listenAddr string, err error) {
logger.LogIf(GlobalContext, fmt.Errorf("Unable to listen on `%s`: %v", listenAddr, err))
})
if err != nil {
globalHTTPServerErrorCh <- err
return
}
globalHTTPServerErrorCh <- serveFn()
}() }()
bootstrapTrace("setHTTPServer") bootstrapTrace("setHTTPServer")

View File

@ -21,7 +21,6 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"strings"
"syscall" "syscall"
) )
@ -129,77 +128,46 @@ type TCPOptions struct {
// httpListener is capable to // httpListener is capable to
// * listen to multiple addresses // * listen to multiple addresses
// * controls incoming connections only doing HTTP protocol // * controls incoming connections only doing HTTP protocol
func newHTTPListener(ctx context.Context, serverAddrs []string, opts TCPOptions) (listener *httpListener, err error) { func newHTTPListener(ctx context.Context, serverAddrs []string, opts TCPOptions) (listener *httpListener, listenErrs []error) {
var tcpListeners []*net.TCPListener tcpListeners := make([]*net.TCPListener, 0, len(serverAddrs))
listenErrs = make([]error, len(serverAddrs))
// Close all opened listeners on error
defer func() {
if err == nil {
return
}
for _, tcpListener := range tcpListeners {
// Ignore error on close.
tcpListener.Close()
}
}()
isLocalhost := false
for _, serverAddr := range serverAddrs {
host, _, err := net.SplitHostPort(serverAddr)
if err == nil {
if strings.EqualFold(host, "localhost") {
isLocalhost = true
}
}
}
// Unix listener with special TCP options. // Unix listener with special TCP options.
listenCfg := net.ListenConfig{ listenCfg := net.ListenConfig{
Control: setTCPParametersFn(opts), Control: setTCPParametersFn(opts),
} }
// Silently ignore failure to bind on DNS cached ipv6 loopback if user specifies "localhost" for i, serverAddr := range serverAddrs {
for _, serverAddr := range serverAddrs { var (
var l net.Listener l net.Listener
if l, err = listenCfg.Listen(ctx, "tcp", serverAddr); err != nil { e error
)
if l, e = listenCfg.Listen(ctx, "tcp", serverAddr); e != nil {
if opts.Trace != nil { if opts.Trace != nil {
opts.Trace(fmt.Sprint("listenCfg.Listen: ", err.Error())) opts.Trace(fmt.Sprint("listenCfg.Listen: ", e.Error()))
} }
if isLocalhost && strings.HasPrefix(serverAddr, "[::1") { listenErrs[i] = e
continue continue
} }
return nil, err
}
tcpListener, ok := l.(*net.TCPListener) tcpListener, ok := l.(*net.TCPListener)
if !ok { if !ok {
err = fmt.Errorf("unexpected listener type found %v, expected net.TCPListener", l) listenErrs[i] = fmt.Errorf("unexpected listener type found %v, expected net.TCPListener", l)
if opts.Trace != nil { if opts.Trace != nil {
opts.Trace(fmt.Sprint("net.TCPListener: ", err.Error())) opts.Trace(fmt.Sprint("net.TCPListener: ", listenErrs[i].Error()))
} }
if isLocalhost && strings.HasPrefix(serverAddr, "[::1") {
continue continue
} }
return nil, err
}
if opts.Trace != nil { if opts.Trace != nil {
opts.Trace(fmt.Sprint("adding listener to ", tcpListener.Addr())) opts.Trace(fmt.Sprint("adding listener to ", tcpListener.Addr()))
} }
tcpListeners = append(tcpListeners, tcpListener) tcpListeners = append(tcpListeners, tcpListener)
} }
// Fail if no listeners found
if len(tcpListeners) == 0 { if len(tcpListeners) == 0 {
// Report specific issue // No listeners initialized, no need to continue
if err != nil { return
return nil, err
}
// Report general issue
err = fmt.Errorf("%v listeners found, expected at least 1", 0)
return nil, err
} }
listener = &httpListener{ listener = &httpListener{
@ -212,5 +180,5 @@ func newHTTPListener(ctx context.Context, serverAddrs []string, opts TCPOptions)
} }
listener.start() listener.start()
return listener, nil return
} }

View File

@ -136,34 +136,37 @@ func TestNewHTTPListener(t *testing.T) {
tcpKeepAliveTimeout time.Duration tcpKeepAliveTimeout time.Duration
readTimeout time.Duration readTimeout time.Duration
writeTimeout time.Duration writeTimeout time.Duration
expectedErr bool expectedListenErrs []bool
}{ }{
{[]string{"93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, {[]string{"93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}}, // 1
{[]string{"example.org:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, {[]string{"example.org:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}}, // 2
{[]string{"unknown-host"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, {[]string{"unknown-host"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}}, // 3
{[]string{"unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, {[]string{"unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true}}, // 4
{[]string{"localhost:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), false}, {[]string{"localhost:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}}, // 5
{[]string{"localhost:65432", "93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, {[]string{"localhost:65432", "93.184.216.34:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false, true}}, // 6
{[]string{"localhost:65432", "unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), true}, {[]string{"localhost:65432", "unknown-host:65432"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false, true}}, // 7
{[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false}, {[]string{"[::1:65432", "unknown-host:-1"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{true, true}}, // 7
{[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false}, {[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}}, // 8
{[]string{"[::1]:9090", "localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), false}, {[]string{"localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}}, // 9
{[]string{"[::1]:9090", "127.0.0.1:90900"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}}, // 10
{[]string{"[::1]:9090", "localhost:0"}, time.Duration(0), time.Duration(0), time.Duration(0), []bool{false}}, // 10
} }
for _, testCase := range testCases { for testIdx, testCase := range testCases {
listener, err := newHTTPListener(context.Background(), listener, listenErrs := newHTTPListener(context.Background(),
testCase.serverAddrs, testCase.serverAddrs,
TCPOptions{}, TCPOptions{},
) )
if !testCase.expectedErr { for i, expectedListenErr := range testCase.expectedListenErrs {
if err != nil { if !expectedListenErr {
t.Fatalf("error: expected = <nil>, got = %v", err) if listenErrs[i] != nil {
t.Fatalf("Test %d:, listenErrs[%d] error: expected = <nil>, got = %v", testIdx+1, i, listenErrs[i])
} }
} else if err == nil { } else if listenErrs[i] == nil {
t.Fatalf("error: expected = %v, got = <nil>", testCase.expectedErr) t.Fatalf("Test %d: listenErrs[%d]: expected = %v, got = <nil>", testIdx+1, i, expectedListenErr)
} }
}
if err == nil { if listener != nil {
listener.Close() listener.Close()
} }
} }
@ -187,21 +190,24 @@ func TestHTTPListenerStartClose(t *testing.T) {
{[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}}, {[]string{"127.0.0.1:0", nonLoopBackIP + ":0"}},
} }
nextTest:
for i, testCase := range testCases { for i, testCase := range testCases {
listener, err := newHTTPListener(context.Background(), listener, errs := newHTTPListener(context.Background(),
testCase.serverAddrs, testCase.serverAddrs,
TCPOptions{}, TCPOptions{},
) )
for _, err := range errs {
if err != nil { if err != nil {
if strings.Contains(err.Error(), "The requested address is not valid in its context") { if strings.Contains(err.Error(), "The requested address is not valid in its context") {
// Ignore if IP is unbindable. // Ignore if IP is unbindable.
continue continue nextTest
} }
if strings.Contains(err.Error(), "bind: address already in use") { if strings.Contains(err.Error(), "bind: address already in use") {
continue continue nextTest
} }
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
} }
}
for _, serverAddr := range listener.Addrs() { for _, serverAddr := range listener.Addrs() {
conn, err := net.Dial("tcp", serverAddr.String()) conn, err := net.Dial("tcp", serverAddr.String())
@ -238,21 +244,24 @@ func TestHTTPListenerAddr(t *testing.T) {
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, "0.0.0.0:" + casePorts[5]}, {[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, "0.0.0.0:" + casePorts[5]},
} }
nextTest:
for i, testCase := range testCases { for i, testCase := range testCases {
listener, err := newHTTPListener(context.Background(), listener, errs := newHTTPListener(context.Background(),
testCase.serverAddrs, testCase.serverAddrs,
TCPOptions{}, TCPOptions{},
) )
for _, err := range errs {
if err != nil { if err != nil {
if strings.Contains(err.Error(), "The requested address is not valid in its context") { if strings.Contains(err.Error(), "The requested address is not valid in its context") {
// Ignore if IP is unbindable. // Ignore if IP is unbindable.
continue continue nextTest
} }
if strings.Contains(err.Error(), "bind: address already in use") { if strings.Contains(err.Error(), "bind: address already in use") {
continue continue nextTest
} }
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
} }
}
addr := listener.Addr() addr := listener.Addr()
if addr.String() != testCase.expectedAddr { if addr.String() != testCase.expectedAddr {
@ -286,21 +295,24 @@ func TestHTTPListenerAddrs(t *testing.T) {
{[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, set.CreateStringSet("127.0.0.1:"+casePorts[5], nonLoopBackIP+":"+casePorts[5])}, {[]string{"127.0.0.1:" + casePorts[5], nonLoopBackIP + ":" + casePorts[5]}, set.CreateStringSet("127.0.0.1:"+casePorts[5], nonLoopBackIP+":"+casePorts[5])},
} }
nextTest:
for i, testCase := range testCases { for i, testCase := range testCases {
listener, err := newHTTPListener(context.Background(), listener, errs := newHTTPListener(context.Background(),
testCase.serverAddrs, testCase.serverAddrs,
TCPOptions{}, TCPOptions{},
) )
for _, err := range errs {
if err != nil { if err != nil {
if strings.Contains(err.Error(), "The requested address is not valid in its context") { if strings.Contains(err.Error(), "The requested address is not valid in its context") {
// Ignore if IP is unbindable. // Ignore if IP is unbindable.
continue continue nextTest
} }
if strings.Contains(err.Error(), "bind: address already in use") { if strings.Contains(err.Error(), "bind: address already in use") {
continue continue nextTest
} }
t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err) t.Fatalf("Test %d: error: expected = <nil>, got = %v", i+1, err)
} }
}
addrs := listener.Addrs() addrs := listener.Addrs()
addrSet := set.NewStringSet() addrSet := set.NewStringSet()

View File

@ -74,8 +74,8 @@ func (srv *Server) GetRequestCount() int {
return int(atomic.LoadInt32(&srv.requestCount)) return int(atomic.LoadInt32(&srv.requestCount))
} }
// Start - start HTTP server // Init - init HTTP server
func (srv *Server) Start(ctx context.Context) (err error) { func (srv *Server) Init(listenCtx context.Context, listenErrCallback func(listenAddr string, err error)) (serve func() error, err error) {
// Take a copy of server fields. // Take a copy of server fields.
var tlsConfig *tls.Config var tlsConfig *tls.Config
if srv.TLSConfig != nil { if srv.TLSConfig != nil {
@ -85,13 +85,22 @@ func (srv *Server) Start(ctx context.Context) (err error) {
// Create new HTTP listener. // Create new HTTP listener.
var listener *httpListener var listener *httpListener
listener, err = newHTTPListener( listener, listenErrs := newHTTPListener(
ctx, listenCtx,
srv.Addrs, srv.Addrs,
srv.TCPOptions, srv.TCPOptions,
) )
if err != nil {
return err var interfaceFound bool
for i := range listenErrs {
if listenErrs[i] != nil {
listenErrCallback(srv.Addrs[i], listenErrs[i])
} else {
interfaceFound = true
}
}
if !interfaceFound {
return nil, errors.New("no available interface found")
} }
// Wrap given handler to do additional // Wrap given handler to do additional
@ -118,11 +127,16 @@ func (srv *Server) Start(ctx context.Context) (err error) {
srv.listener = listener srv.listener = listener
srv.listenerMutex.Unlock() srv.listenerMutex.Unlock()
// Start servicing with listener. var l net.Listener = listener
if tlsConfig != nil { if tlsConfig != nil {
return srv.Server.Serve(tls.NewListener(listener, tlsConfig)) l = tls.NewListener(listener, tlsConfig)
} }
return srv.Server.Serve(listener)
serve = func() error {
return srv.Server.Serve(l)
}
return
} }
// Shutdown - shuts down HTTP server. // Shutdown - shuts down HTTP server.