From 28de47ba271bdc8cb08cf74c3428b61eda7c6398 Mon Sep 17 00:00:00 2001 From: Manuel Buil Date: Thu, 29 Jun 2023 16:23:14 +0200 Subject: [PATCH] Check if we are on ipv4, ipv6 or dualStack when doing tailscale Signed-off-by: Manuel Buil --- pkg/agent/config/config.go | 8 ++-- pkg/cli/server/server.go | 25 +++++++++--- pkg/util/net.go | 79 ++++++++++++++++++-------------------- pkg/vpn/vpn.go | 11 +++--- 4 files changed, 68 insertions(+), 55 deletions(-) diff --git a/pkg/agent/config/config.go b/pkg/agent/config/config.go index a35410014a17..c54c3ac575f3 100644 --- a/pkg/agent/config/config.go +++ b/pkg/agent/config/config.go @@ -390,15 +390,17 @@ func get(ctx context.Context, envInfo *cmds.Agent, proxy proxy.Proxy) (*config.N if err != nil { return nil, err } - if len(vpnInfo.IPs) != 0 { - logrus.Infof("Node-ip changed to %v due to VPN", vpnInfo.IPs) + + vpnIPs := []net.IP{vpnInfo.IPv4Address, vpnInfo.IPv6Address} + if len(vpnIPs) != 0 { + logrus.Infof("Node-ip changed to %v due to VPN", vpnIPs) if len(envInfo.NodeIP) != 0 { logrus.Warn("VPN provider overrides configured node-ip parameter") } if len(envInfo.NodeExternalIP) != 0 { logrus.Warn("VPN provider overrides node-external-ip parameter") } - nodeIPs = vpnInfo.IPs + nodeIPs = vpnIPs flannelIface, err = net.InterfaceByName(vpnInfo.VPNInterface) if err != nil { return nil, errors.Wrapf(err, "unable to find vpn interface: %s", vpnInfo.VPNInterface) diff --git a/pkg/cli/server/server.go b/pkg/cli/server/server.go index caf86c254f9b..f41fe1aeda31 100644 --- a/pkg/cli/server/server.go +++ b/pkg/cli/server/server.go @@ -228,12 +228,27 @@ func run(app *cli.Context, cfg *cmds.Server, leaderControllers server.CustomCont if err != nil { return err } - if len(vpnInfo.IPs) != 0 { - logrus.Infof("Advertise-address changed to %v due to VPN", vpnInfo.IPs) - if serverConfig.ControlConfig.AdvertiseIP != "" { - logrus.Warn("Conflict in the config detected. VPN integration overwrites advertise-address but the config is setting the advertise-address parameter") + // If we are in ipv6-only mode, we should pass the ipv6 address. Otherwise, ipv4 + if utilsnet.IsIPv6CIDRString(util.JoinIPNets(serverConfig.ControlConfig.ClusterIPRanges)) { + if vpnInfo.IPv6Address != nil { + logrus.Infof("Advertise-address changed to %v due to VPN", vpnInfo.IPv6Address) + if serverConfig.ControlConfig.AdvertiseIP != "" { + logrus.Warn("Conflict in the config detected. VPN integration overwrites advertise-address but the config is setting the advertise-address parameter") + } + serverConfig.ControlConfig.AdvertiseIP = vpnInfo.IPv6Address.String() + } else { + return errors.New("tailscale does not provide an ipv6 address") + } + } else { + if vpnInfo.IPv4Address != nil { + logrus.Infof("Advertise-address changed to %v due to VPN", vpnInfo.IPv4Address) + if serverConfig.ControlConfig.AdvertiseIP != "" { + logrus.Warn("Conflict in the config detected. VPN integration overwrites advertise-address but the config is setting the advertise-address parameter") + } + serverConfig.ControlConfig.AdvertiseIP = vpnInfo.IPv4Address.String() + } else { + return errors.New("tailscale does not provide an ipv4 address") } - serverConfig.ControlConfig.AdvertiseIP = vpnInfo.IPs[0].String() } logrus.Warn("Etcd IP (PrivateIP) remains the local IP. Running etcd traffic over VPN is not recommended due to performance issues") } else { diff --git a/pkg/util/net.go b/pkg/util/net.go index 5a949a20a97f..1eb9ff663f23 100644 --- a/pkg/util/net.go +++ b/pkg/util/net.go @@ -10,6 +10,7 @@ import ( "github.com/sirupsen/logrus" "github.com/urfave/cli" apinet "k8s.io/apimachinery/pkg/util/net" + netutils "k8s.io/utils/net" ) // JoinIPs stringifies and joins a list of IP addresses with commas. @@ -34,7 +35,7 @@ func JoinIPNets(elems []*net.IPNet) string { // If no IPv4 addresses are found, an error is raised. func GetFirst4Net(elems []*net.IPNet) (*net.IPNet, error) { for _, elem := range elems { - if elem == nil || elem.IP.To4() == nil { + if elem == nil || netutils.IsIPv4(elem.IP) { continue } return elem, nil @@ -44,37 +45,37 @@ func GetFirst4Net(elems []*net.IPNet) (*net.IPNet, error) { // GetFirst4 returns the first IPv4 address from the list of IP addresses. // If no IPv4 addresses are found, an error is raised. -func GetFirst4(elems []net.IP) (net.IP, error) { +func GetFirst4(elems []net.IP) net.IP { for _, elem := range elems { - if elem == nil || elem.To4() == nil { + if elem == nil || netutils.IsIPv4(elem) { continue } - return elem, nil + return elem } - return nil, errors.New("no IPv4 address found") + return nil } // GetFirst4String returns the first IPv4 address from a list of IP address strings. // If no IPv4 addresses are found, an error is raised. -func GetFirst4String(elems []string) (string, error) { +func GetFirst4String(elems []string) string { ips := []net.IP{} for _, elem := range elems { for _, v := range strings.Split(elem, ",") { ips = append(ips, net.ParseIP(v)) } } - ip, err := GetFirst4(ips) - if err != nil { - return "", err + ip := GetFirst4(ips) + if ip == nil { + return "" } - return ip.String(), nil + return ip.String() } // JoinIP4Nets stringifies and joins a list of IPv4 networks with commas. func JoinIP4Nets(elems []*net.IPNet) string { var strs []string for _, elem := range elems { - if elem != nil && elem.IP.To4() != nil { + if elem != nil && netutils.IsIPv4(elem.IP) { strs = append(strs, elem.String()) } } @@ -82,50 +83,46 @@ func JoinIP4Nets(elems []*net.IPNet) string { } // GetFirst6 returns the first IPv6 address from the list of IP addresses. -// If no IPv6 addresses are found, an error is raised. -func GetFirst6(elems []net.IP) (net.IP, error) { +func GetFirst6(elems []net.IP) net.IP { for _, elem := range elems { - if elem == nil || elem.To16() == nil { - continue + if elem == nil || netutils.IsIPv6(elem) { + return elem } - return elem, nil } - return nil, errors.New("no IPv6 address found") + return nil } // GetFirst6Net returns the first IPv4 network from the list of IP networks. -// If no IPv6 addresses are found, an error is raised. -func GetFirst6Net(elems []*net.IPNet) (*net.IPNet, error) { +func GetFirst6Net(elems []*net.IPNet) *net.IPNet { for _, elem := range elems { - if elem == nil || elem.IP.To16() == nil { - continue + if elem == nil || netutils.IsIPv6(elem.IP) { + return elem } - return elem, nil } - return nil, errors.New("no IPv6 CIDRs found") + return nil } // GetFirst6String returns the first IPv6 address from a list of IP address strings. // If no IPv6 addresses are found, an error is raised. -func GetFirst6String(elems []string) (string, error) { +func GetFirst6String(elems []string) string { ips := []net.IP{} for _, elem := range elems { for _, v := range strings.Split(elem, ",") { ips = append(ips, net.ParseIP(v)) } } - ip, err := GetFirst6(ips) - if err != nil { - return "", err + ip := GetFirst6(ips) + if ip == nil { + return "" } - return ip.String(), nil + return ip.String() } // JoinIP6Nets stringifies and joins a list of IPv6 networks with commas. func JoinIP6Nets(elems []*net.IPNet) string { var strs []string for _, elem := range elems { - if elem != nil && elem.IP.To4() == nil { + if elem != nil && netutils.IsIPv6(elem.IP) { strs = append(strs, elem.String()) } } @@ -208,13 +205,13 @@ func GetFirstValidIPString(s cli.StringSlice) string { // if neither of IPv4 or IPv6 are found an error is raised. // Additionally matching listen address and IP version is returned. func GetFirstIP(nodeIPs []net.IP) (net.IP, string, bool, error) { - nodeIP, err := GetFirst4(nodeIPs) + nodeIP := GetFirst4(nodeIPs) ListenAddress := "0.0.0.0" IPv6only := false - if err != nil { - nodeIP, err = GetFirst6(nodeIPs) - if err != nil { - return nil, "", false, err + if nodeIP == nil { + nodeIPv6 := GetFirst6(nodeIPs) + if nodeIPv6 == nil { + return nil, "", false, errors.New("no ipv4 or ipv6 addresses found") } ListenAddress = "::" IPv6only = true @@ -228,8 +225,8 @@ func GetFirstIP(nodeIPs []net.IP) (net.IP, string, bool, error) { func GetFirstNet(elems []*net.IPNet) (*net.IPNet, error) { serviceIPRange, err := GetFirst4Net(elems) if err != nil { - serviceIPRange, err = GetFirst6Net(elems) - if err != nil { + serviceIPRange = GetFirst6Net(elems) + if serviceIPRange == nil { return nil, err } } @@ -240,12 +237,12 @@ func GetFirstNet(elems []*net.IPNet) (*net.IPNet, error) { // If no IPv4 addresses are found, returns the first IPv6 address // if neither of IPv4 or IPv6 are found an error is raised. func GetFirstString(elems []string) (string, bool, error) { - ip, err := GetFirst4String(elems) + ip := GetFirst4String(elems) IPv6only := false - if err != nil { - ip, err = GetFirst6String(elems) - if err != nil { - return "", false, err + if ip == "" { + ipv6 := GetFirst6String(elems) + if ipv6 == "" { + return "", false, errors.New("no ipv4 or ipv6 addresses found") } IPv6only = true } diff --git a/pkg/vpn/vpn.go b/pkg/vpn/vpn.go index 77a628d28c6f..e8acfe624c6f 100644 --- a/pkg/vpn/vpn.go +++ b/pkg/vpn/vpn.go @@ -22,7 +22,8 @@ type TailscaleOutput struct { // VPNInfo includes node information of the VPN. It is a general struct in case we want to add more vpn integrations type VPNInfo struct { - IPs []net.IP + IPv4Address net.IP + IPv6Address net.IP NodeID string ProviderName string VPNInterface string @@ -112,15 +113,13 @@ func getTailscaleInfo() (VPNInfo, error) { logrus.Debugf("Output from tailscale status --json: %v", output) var tailscaleOutput TailscaleOutput - var internalIPs []net.IP err = json.Unmarshal([]byte(output), &tailscaleOutput) if err != nil { return VPNInfo{}, fmt.Errorf("failed to unmarshal tailscale output: %v", err) } - for _, address := range tailscaleOutput.TailscaleIPs { - internalIPs = append(internalIPs, net.ParseIP(address)) - } + ipv4Address := util.GetFirst4String(tailscaleOutput.TailscaleIPs) + ipv6Address := util.GetFirst6String(tailscaleOutput.TailscaleIPs) - return VPNInfo{IPs: internalIPs, NodeID: "", ProviderName: "tailscale", VPNInterface: tailscaleIf}, nil + return VPNInfo{IPv4Address: net.ParseIP(ipv4Address), IPv6Address: net.ParseIP(ipv6Address), NodeID: "", ProviderName: "tailscale", VPNInterface: tailscaleIf}, nil }