diff --git a/pkg/components/networking.go b/pkg/components/networking.go index 02fd72d0754..f8e433287e5 100644 --- a/pkg/components/networking.go +++ b/pkg/components/networking.go @@ -8,6 +8,7 @@ import ( "github.com/openshift/microshift/pkg/assets" "github.com/openshift/microshift/pkg/config" "github.com/openshift/microshift/pkg/config/ovn" + "github.com/vishvananda/netlink" "k8s.io/klog/v2" ) @@ -52,7 +53,16 @@ func startCNIPlugin(ctx context.Context, cfg *config.Config, kubeconfigPath stri } } - ovnConfig, err := ovn.NewOVNKubernetesConfigFromFileOrDefault(filepath.Dir(config.ConfigFile), cfg.MultiNode.Enabled) + ipFamily := netlink.FAMILY_ALL + if cfg.IsIPv4() && !cfg.IsIPv6() { + ipFamily = netlink.FAMILY_V4 + } + + if cfg.IsIPv6() && !cfg.IsIPv4() { + ipFamily = netlink.FAMILY_V6 + } + + ovnConfig, err := ovn.NewOVNKubernetesConfigFromFileOrDefault(filepath.Dir(config.ConfigFile), cfg.MultiNode.Enabled, ipFamily) if err != nil { return fmt.Errorf("failed to create OVN-K configuration from %q: %w", config.ConfigFile, err) } diff --git a/pkg/config/ovn/ovn.go b/pkg/config/ovn/ovn.go index 7616a7b34cf..393fe9a75ca 100644 --- a/pkg/config/ovn/ovn.go +++ b/pkg/config/ovn/ovn.go @@ -8,6 +8,8 @@ import ( "path/filepath" "regexp" + "github.com/openshift/microshift/pkg/util" + "github.com/vishvananda/netlink" "k8s.io/klog/v2" "sigs.k8s.io/yaml" ) @@ -40,10 +42,6 @@ func (o *OVNKubernetesConfig) Validate() error { if err != nil { return fmt.Errorf("failed to validate OVS bridge: %w", err) } - err = o.validateConfig() - if err != nil { - return fmt.Errorf("failed to validate OVN-K configuration: %w", err) - } return nil } @@ -56,28 +54,29 @@ func (o *OVNKubernetesConfig) validateOVSBridge() error { return nil } -// validateConfig validates the user defined configuration in /etc/microshift/ovn.yaml -func (o *OVNKubernetesConfig) validateConfig() error { - // validate MTU conf - iface, err := net.InterfaceByName(OVNGatewayInterface) - if err != nil { - return fmt.Errorf("failed to find OVN gateway interface %q: %w", OVNGatewayInterface, err) - } - - if iface.MTU < o.MTU { - return fmt.Errorf("interface MTU (%d) is too small for specified overlay (%d)", iface.MTU, o.MTU) - } - return nil -} - -// getClusterMTU retrieves MTU from ovn-kubernetes gateway interface "br-ex", -// and falls back to use 1500 when "br-ex" mtu is unable to get or less than 0. -func (o *OVNKubernetesConfig) getClusterMTU(multinode bool) { - link, err := net.InterfaceByName(OVNGatewayInterface) - if err == nil && link.MTU > 0 { - o.MTU = link.MTU +// getClusterMTU retrieves MTU from the default route network interface, +// and falls back to use 1500 when unable to get the mtu or ipFamily than 0. +func (o *OVNKubernetesConfig) getClusterMTU(multinode bool, ipFamily int) { + klog.Infof("getClusterMTU: finding default route interface") + o.MTU = defaultMTU + + // if configure both IPV4 and IPV6 check the smallest + //nolint:nestif + if ipFamily == netlink.FAMILY_ALL { + mtu, err := util.FindDefaultRouteMinMTU() + + if err == nil { + o.MTU = mtu + } else { + klog.Infof("getClusterMTU: error %s.", err) + } } else { - o.MTU = defaultMTU + mtu, err := util.FindDefaultRouteMTU(ipFamily) + if err == nil { + o.MTU = mtu + } else { + klog.Infof("getClusterMTU: error %s.", err) + } } if multinode { @@ -86,12 +85,12 @@ func (o *OVNKubernetesConfig) getClusterMTU(multinode bool) { } // withDefaults returns the default values when ovn.yaml is not provided -func (o *OVNKubernetesConfig) withDefaults(multinode bool) *OVNKubernetesConfig { - o.getClusterMTU(multinode) +func (o *OVNKubernetesConfig) withDefaults(multinode bool, ipFamily int) *OVNKubernetesConfig { + o.getClusterMTU(multinode, ipFamily) return o } -func newOVNKubernetesConfigFromFile(path string, multinode bool) (*OVNKubernetesConfig, error) { +func newOVNKubernetesConfigFromFile(path string, multinode bool, ipFamily int) (*OVNKubernetesConfig, error) { o := new(OVNKubernetesConfig) buf, err := os.ReadFile(path) if err != nil { @@ -104,24 +103,24 @@ func newOVNKubernetesConfigFromFile(path string, multinode bool) (*OVNKubernetes } // in case mtu is not defined if o.MTU == 0 { - o.getClusterMTU(multinode) + o.getClusterMTU(multinode, ipFamily) } klog.Infof("parsed OVNKubernetes config from file %q: %+v", path, o) return o, nil } -func NewOVNKubernetesConfigFromFileOrDefault(dir string, multinode bool) (*OVNKubernetesConfig, error) { +func NewOVNKubernetesConfigFromFileOrDefault(dir string, multinode bool, ipFamily int) (*OVNKubernetesConfig, error) { path := filepath.Join(dir, ovnConfigFileName) if _, err := os.Stat(path); err != nil { if errors.Is(err, os.ErrNotExist) { klog.Infof("OVNKubernetes config file not found, assuming default values") - return new(OVNKubernetesConfig).withDefaults(multinode), nil + return new(OVNKubernetesConfig).withDefaults(multinode, ipFamily), nil } return nil, fmt.Errorf("failed to get OVNKubernetes config file: %v", err) } - o, err := newOVNKubernetesConfigFromFile(path, multinode) + o, err := newOVNKubernetesConfigFromFile(path, multinode, ipFamily) if err == nil { return o, nil } diff --git a/pkg/config/ovn/ovn_test.go b/pkg/config/ovn/ovn_test.go index 54a885b0fe9..89a1bc51888 100644 --- a/pkg/config/ovn/ovn_test.go +++ b/pkg/config/ovn/ovn_test.go @@ -2,6 +2,8 @@ package ovn import ( "testing" + + "github.com/vishvananda/netlink" ) // tests to make sure that the config file is parsed correctly @@ -15,7 +17,7 @@ func TestNewOVNKubernetesConfigFromFileOrDefault(t *testing.T) { } for _, tt := range ttests { - _, err := NewOVNKubernetesConfigFromFileOrDefault(tt.configFile, false) + _, err := NewOVNKubernetesConfigFromFileOrDefault(tt.configFile, false, netlink.FAMILY_V4) if (err != nil) != (tt.err != nil) { t.Errorf("NewOVNKubernetesConfigFromFileOrDefault() error = %v, wantErr %v", err, tt.err) } diff --git a/pkg/util/net.go b/pkg/util/net.go index fcdc4918955..bfb0328efe8 100644 --- a/pkg/util/net.go +++ b/pkg/util/net.go @@ -22,6 +22,7 @@ import ( tcpnet "net" "net/http" "os" + "slices" "sort" "strings" "time" @@ -34,6 +35,14 @@ import ( var previousGatewayIP string = "" +type routeStruct struct { + // Name of interface + Iface string + + // big-endian hex string + Gateway string +} + // Remember whether we have successfully found the hard-coded nodeIP // on this host. var foundHardCodedNodeIP bool @@ -266,3 +275,74 @@ func GetHostIPv6(ipHint string) (string, error) { return "", fmt.Errorf("unable to find host IPv6 address") } + +func FindDefaultRouteMinMTU() (mtu int, err error) { + ipFamilies := []int{netlink.FAMILY_V4, netlink.FAMILY_V6} + + mtu_slice := []int{} + + for _, ipFamily := range ipFamilies { + new_mtu, err := FindDefaultRouteMTU(ipFamily) + if err != nil { + continue + } + mtu_slice = append(mtu_slice, new_mtu) + } + if len(mtu_slice) > 0 { + return slices.Min(mtu_slice), nil + } + return 0, fmt.Errorf("could not find minimal MTU") +} + +func FindDefaultRouteMTU(ipFamily int) (mtu int, err error) { + link, err := FindDefaultRouteIface(ipFamily) + if err != nil || link.MTU == 0 { + return 0, err + } + + klog.Infof("using IP %d on Interface %s with MTU %d ", ipFamily, link.Name, link.MTU) + + return link.MTU, nil +} + +// Find the Default route Interface based on ipv4 or ipv6 routes. +func FindDefaultRouteIface(ipFamily int) (iface *tcpnet.Interface, err error) { + parsedStruct, err := findDefaultRouteForFamily(ipFamily) + if err != nil { + return nil, err + } + + iface, err = tcpnet.InterfaceByName(parsedStruct.Iface) + if err != nil { + return nil, err + } + + return iface, nil +} + +func findDefaultRouteForFamily(family int) (routeStruct, error) { + handle, err := netlink.NewHandle() + if err != nil { + return routeStruct{}, err + } + + routeList, err := handle.RouteList(nil, family) + if err != nil { + return routeStruct{}, err + } + + for _, route := range routeList { + // for Default route the Destination should be nil (0) + if route.Dst == nil { + link, err := handle.LinkByIndex(route.LinkIndex) + if err != nil { + return routeStruct{}, err + } + return routeStruct{ + Iface: link.Attrs().Name, + Gateway: route.Gw.String(), + }, nil + } + } + return routeStruct{}, fmt.Errorf("no default gateway found") +}