github.com/cilium/cilium@v1.16.2/pkg/datapath/linux/bigtcp/bigtcp.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package bigtcp
     5  
     6  import (
     7  	"errors"
     8  	"log/slog"
     9  
    10  	"github.com/cilium/hive/cell"
    11  	"github.com/cilium/statedb"
    12  	"github.com/spf13/pflag"
    13  	"github.com/vishvananda/netlink"
    14  
    15  	datapathOption "github.com/cilium/cilium/pkg/datapath/option"
    16  	"github.com/cilium/cilium/pkg/datapath/tables"
    17  	"github.com/cilium/cilium/pkg/logging/logfields"
    18  	"github.com/cilium/cilium/pkg/math"
    19  	"github.com/cilium/cilium/pkg/option"
    20  )
    21  
    22  const (
    23  	defaultGROMaxSize = 65536
    24  	defaultGSOMaxSize = 65536
    25  
    26  	bigTCPGROMaxSize = 196608
    27  	bigTCPGSOMaxSize = 196608
    28  
    29  	probeDevice = "lo"
    30  
    31  	EnableIPv4BIGTCPFlag = "enable-ipv4-big-tcp"
    32  	EnableIPv6BIGTCPFlag = "enable-ipv6-big-tcp"
    33  )
    34  
    35  // UserConfig are the configuration flags that the user can modify.
    36  type UserConfig struct {
    37  	// EnableIPv6BIGTCP enables IPv6 BIG TCP (larger GSO/GRO limits) for the node including pods.
    38  	EnableIPv6BIGTCP bool
    39  
    40  	// EnableIPv4BIGTCP enables IPv4 BIG TCP (larger GSO/GRO limits) for the node including pods.
    41  	EnableIPv4BIGTCP bool
    42  }
    43  
    44  var defaultUserConfig = UserConfig{
    45  	EnableIPv6BIGTCP: false,
    46  	EnableIPv4BIGTCP: false,
    47  }
    48  
    49  func (def UserConfig) Flags(flags *pflag.FlagSet) {
    50  	flags.Bool(EnableIPv4BIGTCPFlag, def.EnableIPv4BIGTCP, "Enable IPv4 BIG TCP option which increases device's maximum GRO/GSO limits for IPv4")
    51  	flags.Bool(EnableIPv6BIGTCPFlag, def.EnableIPv6BIGTCP, "Enable IPv6 BIG TCP option which increases device's maximum GRO/GSO limits for IPv6")
    52  }
    53  
    54  var Cell = cell.Module(
    55  	"bigtcp",
    56  	"BIG TCP support",
    57  
    58  	cell.Config(defaultUserConfig),
    59  	cell.Provide(newBIGTCP),
    60  	cell.Invoke(func(*Configuration) {}),
    61  )
    62  
    63  func newDefaultConfiguration(userConfig UserConfig) *Configuration {
    64  	return &Configuration{
    65  		UserConfig:     userConfig,
    66  		groIPv4MaxSize: defaultGROMaxSize,
    67  		gsoIPv4MaxSize: defaultGSOMaxSize,
    68  		groIPv6MaxSize: defaultGROMaxSize,
    69  		gsoIPv6MaxSize: defaultGSOMaxSize,
    70  	}
    71  }
    72  
    73  // Configuration is the BIG TCP configuration. The values are finalized after
    74  // BIG TCP has started and must not be read before that.
    75  type Configuration struct {
    76  	UserConfig
    77  
    78  	// gsoIPv{4,6}MaxSize is the GSO maximum size used when configuring
    79  	// devices.
    80  	//
    81  	// Note that this is a singleton for the process including this
    82  	// package. This means, for instance, that when using this from the
    83  	// ``pkg/plugins/*`` sources, it will not respect the settings
    84  	// configured inside the ``daemon/``.
    85  	gsoIPv4MaxSize int
    86  	gsoIPv6MaxSize int
    87  
    88  	// groIPv{4,6}MaxSize is the GRO maximum size used when configuring
    89  	// devices.
    90  	//
    91  	// Note that this is a singleton for the process including this
    92  	// package. This means, for instance, that when using this from the
    93  	// ``pkg/plugins/*`` sources, it will not respect the settings
    94  	// configured inside the ``daemon/``.
    95  	groIPv4MaxSize int
    96  	groIPv6MaxSize int
    97  }
    98  
    99  func (c *Configuration) GetGROIPv6MaxSize() int {
   100  	return c.groIPv6MaxSize
   101  }
   102  
   103  func (c *Configuration) GetGSOIPv6MaxSize() int {
   104  	return c.gsoIPv6MaxSize
   105  }
   106  
   107  func (c *Configuration) GetGROIPv4MaxSize() int {
   108  	return c.groIPv4MaxSize
   109  }
   110  
   111  func (c *Configuration) GetGSOIPv4MaxSize() int {
   112  	return c.gsoIPv4MaxSize
   113  }
   114  
   115  // If an error is returned the caller is responsible for rolling back
   116  // any partial changes.
   117  func setGROGSOIPv6MaxSize(log *slog.Logger, userConfig UserConfig, device string, GROMaxSize, GSOMaxSize int) error {
   118  	link, err := netlink.LinkByName(device)
   119  	if err != nil {
   120  		log.Warn("Link does not exist", logfields.Device, device, logfields.Error, err)
   121  		return nil
   122  	}
   123  
   124  	attrs := link.Attrs()
   125  	// The check below is needed to avoid trying to change GSO/GRO max sizes
   126  	// when that is not necessary (e.g. BIG TCP was never enabled or current
   127  	// size matches the target size we need).
   128  	if (int(attrs.GROMaxSize) == GROMaxSize && int(attrs.GSOMaxSize) == GSOMaxSize) ||
   129  		(!userConfig.EnableIPv6BIGTCP &&
   130  			int(attrs.GROMaxSize) <= GROMaxSize &&
   131  			int(attrs.GSOMaxSize) <= GSOMaxSize) {
   132  		return nil
   133  	}
   134  
   135  	err = netlink.LinkSetGROMaxSize(link, GROMaxSize)
   136  	if err != nil {
   137  		return err
   138  	}
   139  
   140  	return netlink.LinkSetGSOMaxSize(link, GSOMaxSize)
   141  }
   142  
   143  // If an error is returned the caller is responsible for rolling back
   144  // any partial changes.
   145  func setGROGSOIPv4MaxSize(log *slog.Logger, userConfig UserConfig, device string, GROMaxSize, GSOMaxSize int) error {
   146  	link, err := netlink.LinkByName(device)
   147  	if err != nil {
   148  		log.Warn("Link does not exist", logfields.Device, device, logfields.Error, err)
   149  		return nil
   150  	}
   151  
   152  	attrs := link.Attrs()
   153  	// The check below is needed to avoid trying to change GSO/GRO max sizes
   154  	// when that is not necessary (e.g. BIG TCP was never enabled or current
   155  	// size matches the target size we need).
   156  	if (int(attrs.GROIPv4MaxSize) == GROMaxSize && int(attrs.GSOIPv4MaxSize) == GSOMaxSize) ||
   157  		(!userConfig.EnableIPv4BIGTCP &&
   158  			int(attrs.GROIPv4MaxSize) <= GROMaxSize &&
   159  			int(attrs.GSOIPv4MaxSize) <= GSOMaxSize) {
   160  		return nil
   161  	}
   162  
   163  	err = netlink.LinkSetGROIPv4MaxSize(link, GROMaxSize)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	return netlink.LinkSetGSOIPv4MaxSize(link, GSOMaxSize)
   169  }
   170  
   171  func haveIPv4MaxSize() bool {
   172  	link, err := netlink.LinkByName(probeDevice)
   173  	if err != nil {
   174  		return false
   175  	}
   176  	if link.Attrs().GROIPv4MaxSize > 0 && link.Attrs().GSOIPv4MaxSize > 0 {
   177  		return true
   178  	}
   179  	return false
   180  }
   181  
   182  func haveIPv6MaxSize() bool {
   183  	link, err := netlink.LinkByName(probeDevice)
   184  	if err != nil {
   185  		return false
   186  	}
   187  	if link.Attrs().TSOMaxSize > 0 {
   188  		return true
   189  	}
   190  	return false
   191  }
   192  
   193  func probeTSOMaxSize(log *slog.Logger, devices []string) int {
   194  	maxSize := math.IntMin(bigTCPGSOMaxSize, bigTCPGROMaxSize)
   195  	for _, device := range devices {
   196  		link, err := netlink.LinkByName(device)
   197  		if err == nil {
   198  			tso := link.Attrs().TSOMaxSize
   199  			tsoMax := int(tso)
   200  			if tsoMax > defaultGSOMaxSize && tsoMax < maxSize {
   201  				log.Info("Lowering GRO/GSO max size", "from", maxSize, "to", tsoMax, logfields.Device, device)
   202  				maxSize = tsoMax
   203  			}
   204  		}
   205  	}
   206  	return maxSize
   207  }
   208  
   209  type params struct {
   210  	cell.In
   211  
   212  	Log          *slog.Logger
   213  	DaemonConfig *option.DaemonConfig
   214  	UserConfig   UserConfig
   215  	DB           *statedb.DB
   216  	Devices      statedb.Table[*tables.Device]
   217  }
   218  
   219  func validateConfig(cfg UserConfig, daemonCfg *option.DaemonConfig) error {
   220  	if cfg.EnableIPv6BIGTCP || cfg.EnableIPv4BIGTCP {
   221  		if daemonCfg.DatapathMode == datapathOption.DatapathModeLBOnly {
   222  			return errors.New("BIG TCP is supported only in veth & netkit datapath mode")
   223  		}
   224  		if daemonCfg.TunnelingEnabled() {
   225  			return errors.New("BIG TCP is not supported in tunneling mode")
   226  		}
   227  		if daemonCfg.EncryptionEnabled() {
   228  			return errors.New("BIG TCP is not supported with encryption enabled")
   229  		}
   230  		if daemonCfg.EnableHostLegacyRouting {
   231  			return errors.New("BIG TCP is not supported with legacy host routing")
   232  		}
   233  	}
   234  	return nil
   235  }
   236  
   237  func newBIGTCP(lc cell.Lifecycle, p params) (*Configuration, error) {
   238  	if err := validateConfig(p.UserConfig, p.DaemonConfig); err != nil {
   239  		return nil, err
   240  	}
   241  	cfg := newDefaultConfiguration(p.UserConfig)
   242  	lc.Append(cell.Hook{
   243  		OnStart: func(cell.HookContext) error {
   244  			return startBIGTCP(p, cfg)
   245  		},
   246  	})
   247  	return cfg, nil
   248  }
   249  
   250  func startBIGTCP(p params, cfg *Configuration) error {
   251  	var err error
   252  
   253  	nativeDevices, _ := tables.SelectedDevices(p.Devices, p.DB.ReadTxn())
   254  	deviceNames := tables.DeviceNames(nativeDevices)
   255  
   256  	disableMsg := ""
   257  	if len(deviceNames) == 0 {
   258  		if p.UserConfig.EnableIPv4BIGTCP || p.UserConfig.EnableIPv6BIGTCP {
   259  			p.Log.Warn("BIG TCP could not detect host devices. Disabling the feature.")
   260  		}
   261  		p.UserConfig.EnableIPv4BIGTCP = false
   262  		p.UserConfig.EnableIPv6BIGTCP = false
   263  		return nil
   264  	}
   265  
   266  	haveIPv4 := haveIPv4MaxSize()
   267  	haveIPv6 := haveIPv6MaxSize()
   268  
   269  	if !haveIPv4 {
   270  		if p.UserConfig.EnableIPv4BIGTCP {
   271  			p.Log.Warn("Cannot enable --" + EnableIPv4BIGTCPFlag + ", needs kernel 6.3 or newer")
   272  		}
   273  		p.UserConfig.EnableIPv4BIGTCP = false
   274  	}
   275  	if !haveIPv6 {
   276  		if p.UserConfig.EnableIPv6BIGTCP {
   277  			p.Log.Warn("Cannot enable --" + EnableIPv6BIGTCPFlag + ", needs kernel 5.19 or newer")
   278  		}
   279  		p.UserConfig.EnableIPv6BIGTCP = false
   280  	}
   281  	if !haveIPv4 && !haveIPv6 {
   282  		return nil
   283  	}
   284  
   285  	if haveIPv4 {
   286  		cfg.groIPv4MaxSize = defaultGROMaxSize
   287  		cfg.gsoIPv4MaxSize = defaultGSOMaxSize
   288  	}
   289  	if haveIPv6 {
   290  		cfg.groIPv6MaxSize = defaultGROMaxSize
   291  		cfg.gsoIPv6MaxSize = defaultGSOMaxSize
   292  	}
   293  
   294  	if p.UserConfig.EnableIPv6BIGTCP || p.UserConfig.EnableIPv4BIGTCP {
   295  		p.Log.Info("Setting up BIG TCP")
   296  		tsoMax := probeTSOMaxSize(p.Log, deviceNames)
   297  		if p.UserConfig.EnableIPv4BIGTCP && haveIPv4 {
   298  			cfg.groIPv4MaxSize = tsoMax
   299  			cfg.gsoIPv4MaxSize = tsoMax
   300  		}
   301  		if p.UserConfig.EnableIPv6BIGTCP && haveIPv6 {
   302  			cfg.groIPv6MaxSize = tsoMax
   303  			cfg.gsoIPv6MaxSize = tsoMax
   304  		}
   305  		disableMsg = ", disabling BIG TCP"
   306  	}
   307  
   308  	bigv6 := p.UserConfig.EnableIPv6BIGTCP
   309  	bigv4 := p.UserConfig.EnableIPv4BIGTCP
   310  
   311  	modifiedDevices := []string{}
   312  	for _, device := range deviceNames {
   313  		// We always add the device because we might do only a partial
   314  		// modification and end up with an error, so best to be conservative
   315  		// and always reset all on error.
   316  		modifiedDevices = append(modifiedDevices, device)
   317  		// For compatibility, the kernel will also update the net device's
   318  		// {gso,gro}_ipv4_max_size, if the new size of {gso,gro}_max_size
   319  		// isn't greater than 64KB. So it needs to set the IPv6 one first
   320  		// as otherwise the IPv4 BIG TCP value will be reset.
   321  		if haveIPv6 {
   322  			err = setGROGSOIPv6MaxSize(p.Log, p.UserConfig, device,
   323  				cfg.groIPv6MaxSize, cfg.gsoIPv6MaxSize)
   324  			if err != nil {
   325  				p.Log.Warn("Could not modify IPv6 gro_max_size and gso_max_size"+disableMsg, logfields.Device, device, logfields.Error, err)
   326  				p.UserConfig.EnableIPv4BIGTCP = false
   327  				p.UserConfig.EnableIPv6BIGTCP = false
   328  				break
   329  			}
   330  			p.Log.Info("Setting IPv6", logfields.Device, device, "gso_max_size", cfg.gsoIPv6MaxSize, "gro_max_size", cfg.groIPv6MaxSize)
   331  		}
   332  		if haveIPv4 {
   333  			err = setGROGSOIPv4MaxSize(p.Log, p.UserConfig, device,
   334  				cfg.groIPv4MaxSize, cfg.gsoIPv4MaxSize)
   335  			if err != nil {
   336  				p.Log.Warn("Could not modify IPv4 gro_max_size and gso_max_size"+disableMsg, logfields.Device, device, logfields.Error, err)
   337  				p.UserConfig.EnableIPv4BIGTCP = false
   338  				p.UserConfig.EnableIPv6BIGTCP = false
   339  				break
   340  			}
   341  			p.Log.Info("Setting IPv4", logfields.Device, device, "gso_max_size", cfg.gsoIPv4MaxSize, "gro_max_size", cfg.groIPv4MaxSize)
   342  		}
   343  	}
   344  
   345  	if err != nil {
   346  		if haveIPv4 {
   347  			cfg.groIPv4MaxSize = defaultGROMaxSize
   348  			cfg.gsoIPv4MaxSize = defaultGSOMaxSize
   349  		}
   350  		if haveIPv6 {
   351  			cfg.groIPv6MaxSize = defaultGROMaxSize
   352  			cfg.gsoIPv6MaxSize = defaultGSOMaxSize
   353  		}
   354  		for _, device := range modifiedDevices {
   355  			if bigv4 {
   356  				err = setGROGSOIPv4MaxSize(p.Log, p.UserConfig, device,
   357  					defaultGROMaxSize, defaultGSOMaxSize)
   358  				if err != nil {
   359  					p.Log.Warn("Could not reset IPv4 gro_max_size and gso_max_size", logfields.Device, device, logfields.Error, err)
   360  					continue
   361  				}
   362  				p.Log.Info("Resetting IPv4 gso_max_size and gro_max_size", logfields.Device, device)
   363  			}
   364  			if bigv6 {
   365  				err = setGROGSOIPv6MaxSize(p.Log, p.UserConfig, device,
   366  					defaultGROMaxSize, defaultGSOMaxSize)
   367  				if err != nil {
   368  					p.Log.Warn("Could not reset IPv6 gro_max_size and gso_max_size", logfields.Device, device, logfields.Error, err)
   369  					continue
   370  				}
   371  				p.Log.Info("Resetting IPv6 gso_max_size and gro_max_size", logfields.Device, device)
   372  			}
   373  		}
   374  	}
   375  
   376  	return nil
   377  }