From 84c39e385efdffe7db5a1d9220226b651b308b8a Mon Sep 17 00:00:00 2001 From: Evgeny Slutsky Date: Wed, 4 Dec 2024 16:04:36 +0100 Subject: [PATCH] OCPBUGS-38468: use default route mtu Signed-off-by: Evgeny Slutsky --- pkg/config/ovn/ovn.go | 7 +-- pkg/util/net.go | 119 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 3 deletions(-) diff --git a/pkg/config/ovn/ovn.go b/pkg/config/ovn/ovn.go index 8adf9f38afb..32a31d1fb84 100644 --- a/pkg/config/ovn/ovn.go +++ b/pkg/config/ovn/ovn.go @@ -8,6 +8,7 @@ import ( "path/filepath" "regexp" + "github.com/openshift/microshift/pkg/util" "k8s.io/klog/v2" "sigs.k8s.io/yaml" ) @@ -70,10 +71,10 @@ func (o *OVNKubernetesConfig) validateConfig() error { 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. +// getClusterMTU retrieves MTU from the default route network interface, +// and falls back to use 1500 when unable to get the mtu or less than 0. func (o *OVNKubernetesConfig) getClusterMTU(multinode bool) { - link, err := net.InterfaceByName(OVNGatewayInterface) + link, err := util.FindDefaultIface() if err == nil && link.MTU > 0 { o.MTU = link.MTU } else { diff --git a/pkg/util/net.go b/pkg/util/net.go index fcdc4918955..4ca8c16d7e7 100644 --- a/pkg/util/net.go +++ b/pkg/util/net.go @@ -16,9 +16,12 @@ limitations under the License. package util import ( + "bufio" + "bytes" "context" "crypto/tls" "fmt" + "io" tcpnet "net" "net/http" "os" @@ -34,6 +37,18 @@ import ( var previousGatewayIP string = "" +const ( + ipv4RouteFile = "/proc/net/route" +) + +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 +281,107 @@ func GetHostIPv6(ipHint string) (string, error) { return "", fmt.Errorf("unable to find host IPv6 address") } + +// Find the Default route Interface based on /proc/net/route entries +func FindDefaultIface() (ip string, err error) { + bytes, err := readRoutes() + if err != nil { + return "", err + } + return parseGatewayIface(bytes) +} + +func ipAddrExistsAtInterface(ipAddr tcpnet.IP, iface tcpnet.Interface) (bool, error) { + + if iface.Name == "lo" { + return false, fmt.Errorf("loopback interface found") + } + + addrs, err := iface.Addrs() + + if err != nil { + return false, err + } + + for _, a := range addrs { + if ipnet, ok := a.(*tcpnet.IPNet); ok { + if ipnet.IP.Equal(ipAddr) { + return true, nil + } + } + } + return false, nil +} + +func readRoutes() ([]byte, error) { + f, err := os.Open(ipv4RouteFile) + if err != nil { + return nil, fmt.Errorf("can't access %s", ipv4RouteFile) + } + defer f.Close() + + bytes, err := io.ReadAll(f) + if err != nil { + return nil, fmt.Errorf("can't read %s", ipv4RouteFile) + } + + return bytes, nil +} + +func parseGatewayIface(output []byte) (string, error) { + parsedStruct, err := parseTorouteStruct(output) + if err != nil { + return "", err + } + return parsedStruct.Iface, nil +} + +func parseTorouteStruct(output []byte) (routeStruct, error) { + // parseLinuxProcNetRoute parses the route file located at /proc/net/route + // and returns the IP address of the default gateway. The default gateway + // is the one with Destination value of 0.0.0.0. + // + // The Linux route file has the following format: + // + // $ cat /proc/net/route + // + // Iface Destination Gateway Flags RefCnt Use Metric Mask + // eno1 00000000 C900A8C0 0003 0 0 100 00000000 0 00 + // eno1 0000A8C0 00000000 0001 0 0 100 00FFFFFF 0 00 + const ( + sep = "\t" // field separator + destinationField = 1 // field containing hex destination address + gatewayField = 2 // field containing hex gateway address + maskField = 7 // field containing hex mask + ) + scanner := bufio.NewScanner(bytes.NewReader(output)) + + // Skip header line + if !scanner.Scan() { + err := scanner.Err() + if err == nil { + return routeStruct{}, fmt.Errorf("no gateway found") + } + + return routeStruct{}, err + } + + for scanner.Scan() { + row := scanner.Text() + tokens := strings.Split(row, sep) + if len(tokens) < 11 { + return routeStruct{}, fmt.Errorf("invalid route file") + } + + // The default interface is the one that's 0 for both destination and mask. + if !(tokens[destinationField] == "00000000" && tokens[maskField] == "00000000") { + continue + } + + return routeStruct{ + Iface: tokens[0], + Gateway: tokens[2], + }, nil + } + return routeStruct{}, fmt.Errorf("no gateway found") +}