skip to content

How do you set SO_REUSEADDR on a Go TCP listener's socket before it binds?

level: middleimportance: should knowfreq 26%

answer

  1. the option only matters at bind time
  2. net.Listen closes the window too early
  3. a hook that runs before binding
  4. ListenConfig.Control hands you a RawConn
  5. SetsockoptInt on the borrowed descriptor

basics

~10 s

Use net.ListenConfig with a Control function. Go calls it after creating the socket but before binding, hands you a syscall.RawConn, and inside its Control callback you call syscall.SetsockoptInt on the descriptor. Then call lc.Listen.

solid answer

~40 s

Address-reuse options only affect the `bind` call, so they have to be set on a socket that exists but is not yet bound — a window `net.Listen` never exposes. `net.ListenConfig` does: its `Control func(network, address string, c syscall.RawConn) error` field is invoked after the socket is created and before it is bound. Inside it you call `c.Control(func(fd uintptr) { ... })` and issue `syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)`, capturing the syscall's error in a closed-over variable because the inner callback returns nothing. Then `lc.Listen(ctx, "tcp", addr)` gives you the listener. Worth saying out loud: Go's `net` package already sets `SO_REUSEADDR` on Unix listening sockets, so in practice this hook is used for things it does not set — `SO_REUSEPORT`, transparent proxying, or a specific kernel buffer size.

code

go · 18 lines
go
lc := net.ListenConfig{
	Control: func(network, address string, c syscall.RawConn) error {
		var opErr error
		if err := c.Control(func(fd uintptr) {
			opErr = syscall.SetsockoptInt(int(fd),
				syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1)
		}); err != nil {
			return err
		}
		return opErr
	},
}

ln, err := lc.Listen(context.Background(), "tcp", ":8080")
if err != nil {
	return err
}
defer ln.Close()

go deeper

for a junior

Know that Go lets you run code on the socket before it is bound, through net.ListenConfig rather than net.Listen. You are not expected to recall the option constants.

for a middle

Explain the ordering: socket created, Control runs, bind, listen. Show the nested callback, the int(fd) conversion, and why the inner setsockopt error needs its own variable.

for a senior

Bring judgment about which options are worth touching at all. Go already sets SO_REUSEADDR on Unix listeners, so know what you are actually changing and how you would verify it on a live socket.

for a principal

Own where socket tuning lives. Options baked into code become undocumented production behaviour, so decide what is configurable, who reviews a change to it, and how a wrong value is detected.

## Why timing is the whole question Some socket options are read by the kernel at a particular moment. `SO_REUSEADDR` and `SO_REUSEPORT` are consulted during `bind`: they change which addresses the kernel will let this socket claim. Setting them afterwards is not an error, it is simply pointless — the bind that would have been affected already happened. `net.Listen` performs socket creation, `setsockopt`, `bind` and `listen` as one atomic-looking call, so by the time you hold a `net.Listener` the window is gone. `net.ListenConfig` exists to open that window. ## The ListenConfig.Control hook ```go type ListenConfig struct { Control func(network, address string, c syscall.RawConn) error // ... keep-alive fields } ``` Go calls `Control` after creating the network socket and before binding it. You receive: - `network` — the network being listened on, for example `"tcp4"`. - `address` — the address string being listened on. - `c` — a `syscall.RawConn` for the freshly created, unbound socket. Returning a non-nil error from `Control` aborts the listen and surfaces that error from `Listen`. ## Reaching the descriptor The `RawConn` does not expose a number directly; you borrow it: ```go lc := net.ListenConfig{ Control: func(network, address string, c syscall.RawConn) error { var opErr error if err := c.Control(func(fd uintptr) { opErr = syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, syscall.SO_REUSEADDR, 1) }); err != nil { return err } return opErr }, } ln, err := lc.Listen(context.Background(), "tcp", ":8080") ``` Note the two error paths. `c.Control` returns an error if the descriptor cannot be borrowed at all; the inner callback returns nothing, so the `setsockopt` result must be assigned to `opErr` and returned afterwards. Code that omits `opErr` looks correct and silently applies no option. Also note `int(fd)`: `RawConn` deals in `uintptr`, the `syscall` setters take an `int`. ## What SO_REUSEADDR actually does — and what it does not On Linux and the BSDs, `SO_REUSEADDR` on a TCP listener mainly means: allow `bind` to this port even though connections from a previous socket linger in `TIME_WAIT`. It is what stops the familiar "address already in use" for a few minutes after a restart. It does **not** let two live processes accept on the same port; that is `SO_REUSEPORT`, a separate option with load-balancing semantics on Linux, whose constant lives in `golang.org/x/sys/unix` rather than in the frozen `syscall` package on every platform. And a practical point that surprises people: Go's `net` package already sets `SO_REUSEADDR` on listening sockets on Unix. Writing this hook to "fix" restart failures usually changes nothing — the failure is more often a still-running process holding the port. The hook earns its keep for options Go does not set for you. ## Before bind versus any time A useful split to be able to state: - **Must precede bind:** `SO_REUSEADDR`, `SO_REUSEPORT`, and transparent-proxy style options that change what the socket may claim. - **Can be set later:** kernel send and receive buffer sizes (`SO_SNDBUF`, `SO_RCVBUF`), `TCP_NODELAY`, keep-alive parameters. For an accepted connection these can be applied through the connection's own `SyscallConn`, and Go exposes typed wrappers for several of them on `*net.TCPConn`. Accepted connections do not inherit everything from the listener either, so per-connection tuning belongs in the accept loop, not in `ListenConfig.Control`. ## Portability The hook is portable; the option constants are not. `syscall.SO_REUSEADDR` and `syscall.SOL_SOCKET` exist on Unix platforms, and code that reaches for platform-specific constants belongs in files with a `_linux.go` or `_darwin.go` suffix so a Windows build still compiles. Keep the `ListenConfig` construction platform-neutral and put only the option-setting function behind the suffixed file. ## Summary `ListenConfig.Control` is the supported window between socket creation and bind. Borrow the descriptor with `RawConn.Control`, set the option with `syscall.SetsockoptInt`, capture the inner error explicitly, and know which options actually care about being set before bind.

  • Why can't you set SO_REUSEADDR after net.Listen has returned?
    Because the option is only consulted by bind, and net.Listen has already created, bound and listened on the socket. Setting it afterwards succeeds and accomplishes nothing for that socket. Options that the kernel reads continuously, such as buffer sizes or TCP_NODELAY, can still be changed at any point through the listener's or connection's SyscallConn.
  • What arguments does the ListenConfig Control function receive?
    It has the signature func(network, address string, c syscall.RawConn) error. network and address are the ones being listened on, and c wraps the freshly created socket that has not yet been bound. Returning a non-nil error aborts the listen, and that error is what Listen reports to the caller.
  • Does SO_REUSEADDR let two processes accept on the same TCP port?
    No. On Linux and the BSDs it mainly relaxes the bind-time check against addresses left in TIME_WAIT. Sharing a live port between processes is SO_REUSEPORT, a distinct option with kernel-side load balancing on Linux, whose constant is not available from the frozen syscall package on every platform.

saying these in an interview costs you the question

  • Calls net.Listen first, then sets SO_REUSEADDR
  • Says SO_REUSEADDR lets two processes share a live port
  • Uses the listener's Fd instead of ListenConfig.Control
  • Drops the setsockopt error captured in the closure
  • Assumes accepted connections inherit every listener option