go's http is enabled for 15 seconds by default

Posted by alwaysme on Fri, 14 Feb 2020 17:15:20 +0100

problem

http server:

package main

import (
	"log"
	"net/http"
)

func main() {
	// Start HTTP service
	addr := "127.0.0.1:8080"
	// websocket processing of adding agent
	http.HandleFunc("/agent", agentHandler)
	err := http.ListenAndServe(addr, nil)
	log.Fatal(err)
}

func agentHandler(w http.ResponseWriter, r *http.Request) {
}

http client is simulated by telnet (it does not involve the sending and receiving of http messages, only the connection)

telnet 127.0.0.1 8080

Using tcpdump and strace, it is found that the server side has enabled tcp keepalive for 15 seconds, but the code only has a default setting of 30 seconds, which is strange:

$ strace ./s 2>&1 | grep setsockopt
setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
// Above is the output before the client connects, and below is the output after the client connects
setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0
setsockopt(4, SOL_SOCKET, SO_KEEPALIVE, [1], 4) = 0
setsockopt(4, SOL_TCP, TCP_KEEPINTVL, [15], 4) = 0
setsockopt(4, SOL_TCP, TCP_KEEPIDLE, [15], 4) = 0

Analysis process

First check the local go source directory:

$ echo `go env GOROOT`/src
/usr/lib/go/src

By looking at the source code, it is found that tcp keepalive is finally set in tcpsockopt_unix.go

func setKeepAlivePeriod(fd *netFD, d time.Duration) error {                                                                          
        // The kernel expects seconds so round to next highest second.          
        d += (time.Second - time.Nanosecond)                                    
        secs := int(d.Seconds())                                                
        if err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPINTVL, secs); err != nil {
                return wrapSyscallError("setsockopt", err)                      
        }                                                                       
        err := fd.pfd.SetsockoptInt(syscall.IPPROTO_TCP, syscall.TCP_KEEPIDLE, secs)
        runtime.KeepAlive(fd)                                                   
        return wrapSyscallError("setsockopt", err)                              
} 

So here's the break:

$ gdb ./s
// Omit part of the content
(gdb) b tcpsockopt_unix.go:16
Breakpoint 1 at 0x55c0ae: file /usr/lib/go/src/net/tcpsockopt_unix.go, line 17.
(gdb) r
Starting program: /home/chuqq/work/work/temp/codeeveryday/golang/20200213_gorilla_websocket/s 
// Omit part of the content
Thread 1 "s" hit Breakpoint 1, net.setKeepAlivePeriod (fd=0xc0000ea080, d=15000000000, ~r2=...)
    at /usr/lib/go/src/net/tcpsockopt_unix.go:17
17		d += (time.Second - time.Nanosecond)
(gdb) bt
#0  net.setKeepAlivePeriod (fd=0xc0000ea080, d=15000000000, ~r2=...) at /usr/lib/go/src/net/tcpsockopt_unix.go:17
#1  0x000000000055bd75 in net.(*TCPListener).accept (ln=0xc00000e260, ~r0=<optimized out>, ~r1=...)
    at /usr/lib/go/src/net/tcpsock_posix.go:150
#2  0x000000000055aa57 in net.(*TCPListener).Accept (l=0xc00000e260, ~r0=..., ~r1=...) at /usr/lib/go/src/net/tcpsock.go:261
#3  0x000000000065867c in net/http.(*onceCloseListener).Accept (~r0=..., ~r1=...) at <autogenerated>:1
#4  0x0000000000637a50 in net/http.(*Server).Serve (srv=0xc0000e8000, l=..., ~r1=...) at /usr/lib/go/src/net/http/server.go:2896
#5  0x0000000000637777 in net/http.(*Server).ListenAndServe (srv=0xc0000e8000, ~r0=...) at /usr/lib/go/src/net/http/server.go:2825
#6  0x00000000006609b6 in net/http.ListenAndServe (handler=..., addr=...) at /usr/lib/go/src/net/http/server.go:3081
#7  main.main () at /home/chuqq/temp/codeeveryday/golang/20200213_gorilla_websocket/s.go:13

It was found that it was set in accept() in / usr/lib/go/src/net/tcpsock_posix.go:150:

func (ln *TCPListener) accept() (*TCPConn, error) {                             
        fd, err := ln.fd.accept()                                               
        if err != nil {                                                         
                return nil, err                                                 
        }                                                                       
        tc := newTCPConn(fd)                                                    
        if ln.lc.KeepAlive >= 0 {                                               
                setKeepAlive(fd, true)                                          
                ka := ln.lc.KeepAlive                                           
                if ln.lc.KeepAlive == 0 {                                       
                        ka = defaultTCPKeepAlive                                
                }                                                               
                setKeepAlivePeriod(fd, ka)                                                                                           
        }                                                                       
        return tc, nil                                                          
}

defaultTCPKeepAlive is defined in dial.go:

  // defaultTCPKeepAlive is a default constant value for TCPKeepAlive times       
  // See golang.org/issue/31510                                                   
  const (                                                                         
          defaultTCPKeepAlive = 15 * time.Second                                  
  )

The version I used is version 1.13.7, which seems to be quite different from the 1.11.x code I saw before. For details, see the above problem: golang.org/issue/31510