Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions core/option/option.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package option

import (
"fmt"
"time"

"golang.org/x/time/rate"
"gvisor.dev/gvisor/pkg/tcpip"
Expand Down Expand Up @@ -59,10 +60,30 @@ const (
// tcpDefaultReceiveBufferSize is the default size of the receive buffer
// for a transport endpoint.
tcpDefaultReceiveBufferSize = tcp.DefaultReceiveBufferSize

// tcpKeepAlivesEnabled is the value used to enable or disable keepalives on
// the socket.
tcpKeepAlivesEnabled = true

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tcpKeepAlivesEnabled = true
tcpKeepAliveEnabled = true

without -s?


// tcpDefaultKeepaliveCount is the maximum number of TCP keep-alive probes to
// send before giving up and killing the connection if no response is
// obtained from the other end.
tcpDefaultKeepaliveCount = 9

// tcpDefaultKeepaliveIdle specifies the time a connection must remain idle
// before the first TCP keepalive packet is sent.
tcpDefaultKeepaliveIdle = 60 * time.Second

// tcpDefaultKeepaliveInterval specifies the interval time between sending
// TCP keepalive packets.
tcpDefaultKeepaliveInterval = 30 * time.Second
)

type Option func(*stack.Stack) error

// TCPSocketOption is a function that applies TCP socket-level options to an endpoint.
type TCPSocketOption func(tcpip.Endpoint) tcpip.Error

// WithDefault sets all default values for stack.
func WithDefault() Option {
return func(s *stack.Stack) error {
Expand Down Expand Up @@ -114,6 +135,16 @@ func WithDefault() Option {
}
}

// DefaultTCPSocketOptions returns the default TCP socket options.
func DefaultTCPSocketOptions() []TCPSocketOption {
return []TCPSocketOption{
WithKeepAlivesEnabled(tcpKeepAlivesEnabled),
WithTCPKeepaliveIdleTime(tcpDefaultKeepaliveIdle),
WithTCPKeepaliveInterval(tcpDefaultKeepaliveInterval),
WithTCPKeepaliveCount(tcpDefaultKeepaliveCount),
}
}

// WithDefaultTTL sets the default TTL used by stack.
func WithDefaultTTL(ttl uint8) Option {
return func(s *stack.Stack) error {
Expand Down Expand Up @@ -256,3 +287,34 @@ func WithTCPRecovery(v tcpip.TCPRecovery) Option {
return nil
}
}

// WithKeepAlivesEnabled sets the keep-alive enabled setting
func WithKeepAlivesEnabled(enabled bool) TCPSocketOption {
return func(ep tcpip.Endpoint) tcpip.Error {
ep.SocketOptions().SetKeepAlive(enabled)
return nil
}
}

// WithTCPKeepaliveIdleTime sets the TCP keepalive idle time.
func WithTCPKeepaliveIdleTime(idle time.Duration) TCPSocketOption {
return func(ep tcpip.Endpoint) tcpip.Error {
opt := tcpip.KeepaliveIdleOption(idle)
return ep.SetSockOpt(&opt)
}
}

// WithTCPKeepaliveInterval sets the TCP keepalive interval.
func WithTCPKeepaliveInterval(interval time.Duration) TCPSocketOption {
return func(ep tcpip.Endpoint) tcpip.Error {
opt := tcpip.KeepaliveIntervalOption(interval)
return ep.SetSockOpt(&opt)
}
}

// WithTCPKeepaliveCount sets the TCP keepalive count.
func WithTCPKeepaliveCount(count int) TCPSocketOption {
return func(ep tcpip.Endpoint) tcpip.Error {
return ep.SetSockOptInt(tcpip.KeepaliveCountOption, count)
}
}
10 changes: 9 additions & 1 deletion core/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type Config struct {
// Options are supplement options to apply settings
// for the internal stack.
Options []option.Option

// TCPSocketOptions are TCP socket-level options to apply to each TCP endpoint.
TCPSocketOptions []option.TCPSocketOption
}

// CreateStack creates *stack.Stack with given config.
Expand All @@ -40,6 +43,11 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
opts = append(opts, cfg.Options...)
}

tcpSockOpts := append(
option.DefaultTCPSocketOptions(),
cfg.TCPSocketOptions...,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned about the potential for duplicate calls; the current way to call this option chain might not be ideal/efficient because they might be called for the same option with different values for every TCP endpoint.

I'm not sure what the best way to do it is, but I'd prefer to avoid calling an option multiple times.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya good callout. this is doing duplicate options every TCP conn. I can refactor a bit so we always register the Options, preferring user supplied values if present but using defaults otherwise, but never both

)

s := stack.New(stack.Options{
NetworkProtocols: []stack.NetworkProtocolFactory{
ipv4.NewProtocol,
Expand All @@ -61,7 +69,7 @@ func CreateStack(cfg *Config) (*stack.Stack, error) {
// before creating NIC, otherwise NIC would dispatch packets
// to stack and cause race condition.
// Initiate transport protocol (TCP/UDP) with given handler.
withTCPHandler(cfg.TransportHandler.HandleTCP),
withTCPHandler(cfg.TransportHandler.HandleTCP, tcpSockOpts),
withUDPHandler(cfg.TransportHandler.HandleUDP),

// Create stack NIC and then bind link endpoint to it.
Expand Down
44 changes: 8 additions & 36 deletions core/tcp.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package core

import (
"time"

glog "gvisor.dev/gvisor/pkg/log"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
Expand All @@ -23,25 +21,9 @@ const (
// maxConnAttempts specifies the maximum number
// of in-flight tcp connection attempts.
maxConnAttempts = 2 << 10

// tcpKeepaliveCount is the maximum number of
// TCP keep-alive probes to send before giving up
// and killing the connection if no response is
// obtained from the other end.
tcpKeepaliveCount = 9

// tcpKeepaliveIdle specifies the time a connection
// must remain idle before the first TCP keepalive
// packet is sent. Once this time is reached,
// tcpKeepaliveInterval option is used instead.
tcpKeepaliveIdle = 60 * time.Second

// tcpKeepaliveInterval specifies the interval
// time between sending TCP keepalive packets.
tcpKeepaliveInterval = 30 * time.Second
)

func withTCPHandler(handle func(adapter.TCPConn)) option.Option {
func withTCPHandler(handle func(adapter.TCPConn), tcpSockOpts []option.TCPSocketOption) option.Option {
return func(s *stack.Stack) error {
tcpForwarder := tcp.NewForwarder(s, defaultWndSize, maxConnAttempts, func(r *tcp.ForwarderRequest) {
var (
Expand All @@ -67,7 +49,7 @@ func withTCPHandler(handle func(adapter.TCPConn)) option.Option {
}
defer r.Complete(false)

err = setSocketOptions(s, ep)
err = setSocketOptions(s, ep, tcpSockOpts)

conn := &tcpConn{
TCPConn: gonet.NewTCPConn(&wq, ep),
Expand All @@ -80,22 +62,12 @@ func withTCPHandler(handle func(adapter.TCPConn)) option.Option {
}
}

func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint) tcpip.Error {
{ /* TCP keepalive options */
ep.SocketOptions().SetKeepAlive(true)

idle := tcpip.KeepaliveIdleOption(tcpKeepaliveIdle)
if err := ep.SetSockOpt(&idle); err != nil {
return err
}

interval := tcpip.KeepaliveIntervalOption(tcpKeepaliveInterval)
if err := ep.SetSockOpt(&interval); err != nil {
return err
}

if err := ep.SetSockOptInt(tcpip.KeepaliveCountOption, tcpKeepaliveCount); err != nil {
return err
func setSocketOptions(s *stack.Stack, ep tcpip.Endpoint, tcpSockOpts []option.TCPSocketOption) tcpip.Error {
{ /* Apply socket options (defaults + custom overrides)*/
for _, opt := range tcpSockOpts {
if err := opt(ep); err != nil {
return err
}
}
}
{ /* TCP recv/send buffer size */
Expand Down
17 changes: 17 additions & 0 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ func netstack(k *Key) (err error) {
}

var opts []option.Option
var tcpSocketOpts []option.TCPSocketOption
if k.TCPModerateReceiveBuffer {
opts = append(opts, option.WithTCPModerateReceiveBuffer(true))
}
Expand All @@ -225,11 +226,27 @@ func netstack(k *Key) (err error) {
opts = append(opts, option.WithTCPReceiveBufferSize(int(size)))
}

if k.TCPKeepaliveIdleTime > 0 {
tcpSocketOpts = append(tcpSocketOpts, option.WithTCPKeepaliveIdleTime(k.TCPKeepaliveIdleTime))
log.Infof("[TCP] keepalive idle time: %v", k.TCPKeepaliveIdleTime)

@xjasonlyu xjasonlyu Nov 23, 2025

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, logging these options seems good to me. My minor suggestion here is to use [STACK] instead of [TCP] since these are part of netstack settings.

}

if k.TCPKeepaliveInterval > 0 {
tcpSocketOpts = append(tcpSocketOpts, option.WithTCPKeepaliveInterval(k.TCPKeepaliveInterval))
log.Infof("[TCP] keepalive interval: %v", k.TCPKeepaliveInterval)
}

if k.TCPKeepaliveCount > 0 {
tcpSocketOpts = append(tcpSocketOpts, option.WithTCPKeepaliveCount(k.TCPKeepaliveCount))
log.Infof("[TCP] keepalive count: %d", k.TCPKeepaliveCount)
}

if _defaultStack, err = core.CreateStack(&core.Config{
LinkEndpoint: _defaultDevice,
TransportHandler: tunnel.T(),
MulticastGroups: multicastGroups,
Options: opts,
TCPSocketOptions: tcpSocketOpts,
}); err != nil {
return err
}
Expand Down
3 changes: 3 additions & 0 deletions engine/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ type Key struct {
Device string `yaml:"device"`
LogLevel string `yaml:"loglevel"`
Interface string `yaml:"interface"`
TCPKeepaliveCount int `yaml:"tcp-keepalive-count"`
TCPKeepaliveIdleTime time.Duration `yaml:"tcp-keepalive-idle-time"`
TCPKeepaliveInterval time.Duration `yaml:"tcp-keepalive-interval"`
TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"`
TCPSendBufferSize string `yaml:"tcp-send-buffer-size"`
TCPReceiveBufferSize string `yaml:"tcp-receive-buffer-size"`
Expand Down
5 changes: 5 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ func init() {
flag.StringVar(&key.MulticastGroups, "multicast-groups", "", "Set multicast groups, separated by commas")
flag.StringVar(&key.TUNPreUp, "tun-pre-up", "", "Execute a command before TUN device setup")
flag.StringVar(&key.TUNPostUp, "tun-post-up", "", "Execute a command after TUN device setup")

flag.DurationVar(&key.TCPKeepaliveIdleTime, "tcp-keepalive-idle-time", 0, "TCP keepalive idle time before first probe (e.g., 60s, 2m)")
flag.DurationVar(&key.TCPKeepaliveInterval, "tcp-keepalive-interval", 0, "TCP keepalive probe interval (e.g., 30s, 1m)")
flag.IntVar(&key.TCPKeepaliveCount, "tcp-keepalive-count", 0, "TCP keepalive probe count before giving up")

flag.BoolVar(&versionFlag, "version", false, "Show version and then quit")
flag.Parse()
}
Expand Down