// Copyright 2023 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build aix || darwin || dragonfly || freebsd || linux || netbsd || solaris || windows package net import ( "runtime" "testing" ) func TestTCPConnKeepAliveConfigDialer(t *testing.T) { maybeSkipKeepAliveTest(t) t.Cleanup(func() { testPreHookSetKeepAlive = func(*netFD) {} }) var ( errHook error oldCfg KeepAliveConfig ) testPreHookSetKeepAlive = func(nfd *netFD) { oldCfg, errHook = getCurrentKeepAliveSettings(fdType(nfd.pfd.Sysfd)) } handler := func(ls *localServer, ln Listener) { for { c, err := ln.Accept() if err != nil { return } c.Close() } } ln := newLocalListener(t, "tcp", &ListenConfig{ KeepAlive: -1, // prevent calling hook from accepting }) ls := (&streamListener{Listener: ln}).newLocalServer() defer ls.teardown() if err := ls.buildup(handler); err != nil { t.Fatal(err) } for _, cfg := range testConfigs { d := Dialer{ KeepAlive: defaultTCPKeepAliveIdle, // should be ignored KeepAliveConfig: cfg} c, err := d.Dial("tcp", ls.Listener.Addr().String()) if err != nil { t.Fatal(err) } defer c.Close() if errHook != nil { t.Fatal(errHook) } sc, err := c.(*TCPConn).SyscallConn() if err != nil { t.Fatal(err) } if err := sc.Control(func(fd uintptr) { verifyKeepAliveSettings(t, fdType(fd), oldCfg, cfg) }); err != nil { t.Fatal(err) } } } func TestTCPConnKeepAliveConfigListener(t *testing.T) { maybeSkipKeepAliveTest(t) t.Cleanup(func() { testPreHookSetKeepAlive = func(*netFD) {} }) var ( errHook error oldCfg KeepAliveConfig ) testPreHookSetKeepAlive = func(nfd *netFD) { oldCfg, errHook = getCurrentKeepAliveSettings(fdType(nfd.pfd.Sysfd)) } ch := make(chan Conn, 1) handler := func(ls *localServer, ln Listener) { c, err := ln.Accept() if err != nil { return } ch <- c } for _, cfg := range testConfigs { ln := newLocalListener(t, "tcp", &ListenConfig{ KeepAlive: defaultTCPKeepAliveIdle, // should be ignored KeepAliveConfig: cfg}) ls := (&streamListener{Listener: ln}).newLocalServer() defer ls.teardown() if err := ls.buildup(handler); err != nil { t.Fatal(err) } d := Dialer{KeepAlive: -1} // prevent calling hook from dialing c, err := d.Dial("tcp", ls.Listener.Addr().String()) if err != nil { t.Fatal(err) } defer c.Close() cc := <-ch defer cc.Close() if errHook != nil { t.Fatal(errHook) } sc, err := cc.(*TCPConn).SyscallConn() if err != nil { t.Fatal(err) } if err := sc.Control(func(fd uintptr) { verifyKeepAliveSettings(t, fdType(fd), oldCfg, cfg) }); err != nil { t.Fatal(err) } } } func TestTCPConnKeepAliveConfig(t *testing.T) { maybeSkipKeepAliveTest(t) handler := func(ls *localServer, ln Listener) { for { c, err := ln.Accept() if err != nil { return } c.Close() } } ls := newLocalServer(t, "tcp") defer ls.teardown() if err := ls.buildup(handler); err != nil { t.Fatal(err) } for _, cfg := range testConfigs { d := Dialer{KeepAlive: -1} // avoid setting default values before the test c, err := d.Dial("tcp", ls.Listener.Addr().String()) if err != nil { t.Fatal(err) } defer c.Close() sc, err := c.(*TCPConn).SyscallConn() if err != nil { t.Fatal(err) } var ( errHook error oldCfg KeepAliveConfig ) if err := sc.Control(func(fd uintptr) { oldCfg, errHook = getCurrentKeepAliveSettings(fdType(fd)) }); err != nil { t.Fatal(err) } if errHook != nil { t.Fatal(errHook) } err = c.(*TCPConn).SetKeepAliveConfig(cfg) if err != nil { if runtime.GOOS == "solaris" { // Solaris prior to 11.4 does not support TCP_KEEPINTVL and TCP_KEEPCNT, // so it will return syscall.ENOPROTOOPT when only one of Interval and Count // is negative. This is expected, so skip the error check in this case. if cfg.Interval >= 0 && cfg.Count >= 0 { t.Fatal(err) } } else { t.Fatal(err) } } if err := sc.Control(func(fd uintptr) { verifyKeepAliveSettings(t, fdType(fd), oldCfg, cfg) }); err != nil { t.Fatal(err) } } }