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 }