Преглед на файлове

Fix TCP keepalive setup on OpenBSD

Fixes #457.

OpenBSD has no user-settable per-socket TCP keepalive options:
TCP_KEEPIDLE, TCP_KEEPINTVL and TCP_KEEPCNT do not exist on OpenBSD,
keepalive timing is controlled system-wide via the sysctls
net.inet.tcp.keepidle and net.inet.tcp.keepintvl. Go reflects this in
src/net/tcpsockopt_openbsd.go: setKeepAliveIdle / Interval / Count
return ENOPROTOOPT for any non-negative value, and only short-circuit
to nil for negative values that explicitly mean "leave alone".

mtg builds a net.KeepAliveConfig with zero-valued Idle / Interval /
Count whenever the user does not override them in the config (which
is the default and the documented expectation). It then hands that
config to (*TCPConn).SetKeepAliveConfig in two places:

  - network/sockopts.go: applied to every connection accepted by
    internal/utils.Listener.Accept and to every server-side dial that
    goes through the v1 default network.
  - network/v2/sockopts.go: applied to every connection produced by
    the v2 network's DialContext.

On OpenBSD both calls fail with "set tcp ...: protocol not available".
The user-visible effect is that:

  - `mtg doctor` reports the error for every Telegram DC.
  - `mtg run` accepts incoming TCP connections at the kernel level but
    Listener.Accept then closes each one before the proxy server ever
    sees it, so the client appears to hang on a half-open socket and
    nothing is logged.
  - There is no configuration workaround. Setting [network]
    keep-alive.disabled = true only zeroes Enable; Go still calls
    setKeepAliveIdle / Interval / Count, which still fail.

This change extracts the keepalive setup behind an applyKeepAlive
helper that has a per-platform implementation, following the same
build-tag pattern already used for sockopts_lowat, sockopts_congestion,
sockopts_reuseaddr and sockopts_usertimeout. On every supported
platform except OpenBSD it still calls SetKeepAliveConfig and the
behaviour is unchanged. On OpenBSD it calls SetKeepAlive(cfg.Enable)
instead, which only flips SO_KEEPALIVE on or off and never touches
the missing per-socket options. OpenBSD users get the system-wide
sysctl-controlled keepalive timing, which is the only thing the
kernel exposes anyway.

Verified by cross-building (`GOOS=openbsd GOARCH=amd64 go build ./...`
and `GOARCH=arm64`) and by running `go test ./network/...` on linux.
pull/459/head
dolonet преди 3 седмици
родител
ревизия
2fa0e5ed94

+ 1
- 1
network/sockopts.go Целия файл

@@ -20,7 +20,7 @@ func SetServerSocketOptions(conn net.Conn, bufferSize int) error {
20 20
 }
21 21
 
22 22
 func setCommonSocketOptions(conn *net.TCPConn) error {
23
-	if err := conn.SetKeepAliveConfig(net.KeepAliveConfig{
23
+	if err := applyKeepAlive(conn, net.KeepAliveConfig{
24 24
 		Enable:   true,
25 25
 		Idle:     DefaultKeepAliveIdle,
26 26
 		Interval: DefaultKeepAliveInterval,

+ 11
- 0
network/sockopts_keepalive.go Целия файл

@@ -0,0 +1,11 @@
1
+//go:build !openbsd
2
+
3
+package network
4
+
5
+import "net"
6
+
7
+// applyKeepAlive enables TCP keepalive on conn and applies the per-socket
8
+// idle/interval/count tuning from cfg.
9
+func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
10
+	return conn.SetKeepAliveConfig(cfg) //nolint: wrapcheck
11
+}

+ 20
- 0
network/sockopts_keepalive_openbsd.go Целия файл

@@ -0,0 +1,20 @@
1
+package network
2
+
3
+import "net"
4
+
5
+// applyKeepAlive enables (or disables) TCP keepalive on conn.
6
+//
7
+// OpenBSD has no user-settable per-socket TCP keepalive options: TCP_KEEPIDLE,
8
+// TCP_KEEPINTVL and TCP_KEEPCNT do not exist on OpenBSD, and Go's
9
+// (*TCPConn).SetKeepAliveConfig therefore returns ENOPROTOOPT ("protocol not
10
+// available") for any non-negative Idle/Interval/Count value (see
11
+// src/net/tcpsockopt_openbsd.go in the Go source tree). Calling
12
+// SetKeepAliveConfig with mtg's defaults (zero values) breaks every accepted
13
+// listener connection and every outbound dial on OpenBSD.
14
+//
15
+// On OpenBSD we only flip SO_KEEPALIVE on or off; the keepalive timing is
16
+// controlled system-wide via the sysctl knobs net.inet.tcp.keepidle and
17
+// net.inet.tcp.keepintvl.
18
+func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
19
+	return conn.SetKeepAlive(cfg.Enable) //nolint: wrapcheck
20
+}

+ 1
- 1
network/v2/sockopts.go Целия файл

@@ -6,7 +6,7 @@ import (
6 6
 )
7 7
 
8 8
 func setCommonSocketOptions(conn *net.TCPConn, keepAliveConfig net.KeepAliveConfig) error {
9
-	if err := conn.SetKeepAliveConfig(keepAliveConfig); err != nil {
9
+	if err := applyKeepAlive(conn, keepAliveConfig); err != nil {
10 10
 		return fmt.Errorf("cannot configure TCP keepalive: %w", err)
11 11
 	}
12 12
 

+ 11
- 0
network/v2/sockopts_keepalive.go Целия файл

@@ -0,0 +1,11 @@
1
+//go:build !openbsd
2
+
3
+package network
4
+
5
+import "net"
6
+
7
+// applyKeepAlive enables TCP keepalive on conn and applies the per-socket
8
+// idle/interval/count tuning from cfg.
9
+func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
10
+	return conn.SetKeepAliveConfig(cfg) //nolint: wrapcheck
11
+}

+ 20
- 0
network/v2/sockopts_keepalive_openbsd.go Целия файл

@@ -0,0 +1,20 @@
1
+package network
2
+
3
+import "net"
4
+
5
+// applyKeepAlive enables (or disables) TCP keepalive on conn.
6
+//
7
+// OpenBSD has no user-settable per-socket TCP keepalive options: TCP_KEEPIDLE,
8
+// TCP_KEEPINTVL and TCP_KEEPCNT do not exist on OpenBSD, and Go's
9
+// (*TCPConn).SetKeepAliveConfig therefore returns ENOPROTOOPT ("protocol not
10
+// available") for any non-negative Idle/Interval/Count value (see
11
+// src/net/tcpsockopt_openbsd.go in the Go source tree). Calling
12
+// SetKeepAliveConfig with mtg's defaults (zero values) breaks every accepted
13
+// listener connection and every outbound dial on OpenBSD.
14
+//
15
+// On OpenBSD we only flip SO_KEEPALIVE on or off; the keepalive timing is
16
+// controlled system-wide via the sysctl knobs net.inet.tcp.keepidle and
17
+// net.inet.tcp.keepintvl.
18
+func applyKeepAlive(conn *net.TCPConn, cfg net.KeepAliveConfig) error {
19
+	return conn.SetKeepAlive(cfg.Enable) //nolint: wrapcheck
20
+}

Loading…
Отказ
Запис