Skip to content

Commit 5f33532

Browse files
committed
fact(tunnels): separate linux tunneling functionality
1 parent e17473a commit 5f33532

File tree

5 files changed

+423
-316
lines changed

5 files changed

+423
-316
lines changed

pkg/controllers/routing/network_routes_controller.go

Lines changed: 15 additions & 184 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/cloudnativelabs/kube-router/v2/pkg/metrics"
1919
"github.com/cloudnativelabs/kube-router/v2/pkg/options"
2020
"github.com/cloudnativelabs/kube-router/v2/pkg/routes"
21+
"github.com/cloudnativelabs/kube-router/v2/pkg/tunnels"
2122
"github.com/cloudnativelabs/kube-router/v2/pkg/utils"
2223
"github.com/coreos/go-iptables/iptables"
2324
gobgpapi "github.com/osrg/gobgp/v3/api"
@@ -67,15 +68,6 @@ const (
6768
bgpCommunityMaxPartSize = 16
6869
routeReflectorMaxID = 32
6970
ipv4MaskMinBits = 32
70-
71-
encapTypeFOU = "fou"
72-
encapTypeIPIP = "ipip"
73-
74-
ipipModev4 = "ipip"
75-
ipipModev6 = "ip6ip6"
76-
77-
maxPort = uint16(65535)
78-
minPort = uint16(1024)
7971
)
8072

8173
// NetworkRoutingController is struct to hold necessary information required by controller
@@ -111,8 +103,6 @@ type NetworkRoutingController struct {
111103
iptablesCmdHandlers map[v1core.IPFamily]utils.IPTablesHandler
112104
enableOverlays bool
113105
overlayType string
114-
overlayEncap string
115-
overlayEncapPort uint16
116106
peerMultihopTTL uint8
117107
MetricsEnabled bool
118108
bgpServerStarted bool
@@ -137,6 +127,7 @@ type NetworkRoutingController struct {
137127
ipsetMutex *sync.Mutex
138128
routeSyncer routes.RouteSyncer
139129
pbr routes.PBRer
130+
tunneler tunnels.Tunneler
140131

141132
nodeLister cache.Indexer
142133
svcLister cache.Indexer
@@ -185,7 +176,7 @@ func (nrc *NetworkRoutingController) Run(healthChan chan<- *healthcheck.Controll
185176
if err != nil {
186177
klog.Errorf("Failed to enable required policy based routing: %s", err.Error())
187178
}
188-
if nrc.overlayEncap == "fou" {
179+
if nrc.tunneler.EncapType() == tunnels.EncapTypeFOU {
189180
// enable FoU module for the overlay tunnel
190181
if _, err := exec.Command("modprobe", "fou").CombinedOutput(); err != nil {
191182
klog.Errorf("Failed to enable FoU for tunnel overlay: %s", err.Error())
@@ -581,7 +572,7 @@ func (nrc *NetworkRoutingController) injectRoute(path *gobgpapi.Path) error {
581572
return err
582573
}
583574

584-
tunnelName := generateTunnelName(nextHop.String())
575+
tunnelName := tunnels.GenerateTunnelName(nextHop.String())
585576
checkNHSameSubnet := func(needle net.IP, haystack []net.IP) bool {
586577
for _, nodeIP := range haystack {
587578
nodeSubnet, _, err := utils.GetNodeSubnet(nodeIP, nil)
@@ -622,7 +613,7 @@ func (nrc *NetworkRoutingController) injectRoute(path *gobgpapi.Path) error {
622613
nextHop.String())
623614
// Also delete route from state map so that it doesn't get re-synced after deletion
624615
nrc.routeSyncer.DelInjectedRoute(dst)
625-
nrc.cleanupTunnel(dst, tunnelName)
616+
tunnels.CleanupTunnel(dst, tunnelName)
626617
return nil
627618
}
628619

@@ -648,14 +639,14 @@ func (nrc *NetworkRoutingController) injectRoute(path *gobgpapi.Path) error {
648639
// if the user has disabled overlays, don't create tunnels. If we're not creating a tunnel, check to see if there is
649640
// any cleanup that needs to happen.
650641
if shouldCreateTunnel() {
651-
link, err = nrc.setupOverlayTunnel(tunnelName, nextHop, dst)
642+
link, err = nrc.tunneler.SetupOverlayTunnel(tunnelName, nextHop, dst)
652643
if err != nil {
653644
return err
654645
}
655646
} else {
656647
// knowing that a tunnel shouldn't exist for this route, check to see if there are any lingering tunnels /
657648
// routes that need to be cleaned up.
658-
nrc.cleanupTunnel(dst, tunnelName)
649+
tunnels.CleanupTunnel(dst, tunnelName)
659650
}
660651

661652
switch {
@@ -724,164 +715,6 @@ func (nrc *NetworkRoutingController) isPeerEstablished(peerIP string) (bool, err
724715
return peerConnected, nil
725716
}
726717

727-
// cleanupTunnel removes any traces of tunnels / routes that were setup by nrc.setupOverlayTunnel() and are no longer
728-
// needed. All errors are logged only, as we want to attempt to perform all cleanup actions regardless of their success
729-
func (nrc *NetworkRoutingController) cleanupTunnel(destinationSubnet *net.IPNet, tunnelName string) {
730-
klog.V(1).Infof("Cleaning up old routes for %s if there are any", destinationSubnet.String())
731-
if err := routes.DeleteByDestination(destinationSubnet); err != nil {
732-
klog.Errorf("Failed to cleanup routes: %v", err)
733-
}
734-
735-
klog.V(1).Infof("Cleaning up any lingering tunnel interfaces named: %s", tunnelName)
736-
if link, err := netlink.LinkByName(tunnelName); err == nil {
737-
if err = netlink.LinkDel(link); err != nil {
738-
klog.Errorf("Failed to delete tunnel link for the node due to " + err.Error())
739-
}
740-
}
741-
}
742-
743-
// setupOverlayTunnel attempts to create a tunnel link and corresponding routes for IPIP based overlay networks
744-
func (nrc *NetworkRoutingController) setupOverlayTunnel(tunnelName string, nextHop net.IP,
745-
nextHopSubnet *net.IPNet) (netlink.Link, error) {
746-
var out []byte
747-
link, err := netlink.LinkByName(tunnelName)
748-
749-
var bestIPForFamily net.IP
750-
var ipipMode, fouLinkType string
751-
isIPv6 := false
752-
ipBase := make([]string, 0)
753-
strFormattedEncapPort := strconv.FormatInt(int64(nrc.overlayEncapPort), 10)
754-
755-
if nextHop.To4() != nil {
756-
bestIPForFamily = nrc.krNode.FindBestIPv4NodeAddress()
757-
ipipMode = encapTypeIPIP
758-
fouLinkType = ipipModev4
759-
} else {
760-
// Need to activate the ip command in IPv6 mode
761-
ipBase = append(ipBase, "-6")
762-
bestIPForFamily = nrc.krNode.FindBestIPv6NodeAddress()
763-
ipipMode = ipipModev6
764-
fouLinkType = "ip6tnl"
765-
isIPv6 = true
766-
}
767-
if nil == bestIPForFamily {
768-
return nil, fmt.Errorf("not able to find an appropriate configured IP address on node for destination "+
769-
"IP family: %s", nextHop.String())
770-
}
771-
772-
// This indicated that the tunnel already exists, so it's possible that there might be nothing more needed. However,
773-
// it is also possible that the user changed the encap type, so we need to make sure that the encap type matches
774-
// and if it doesn't, create it
775-
recreate := false
776-
if err == nil {
777-
klog.V(1).Infof("Tunnel interface: %s with encap type %s for the node %s already exists.",
778-
tunnelName, link.Attrs().EncapType, nextHop.String())
779-
780-
switch nrc.overlayEncap {
781-
case encapTypeIPIP:
782-
if linkFOUEnabled(tunnelName) {
783-
klog.Infof("Was configured to use ipip tunnels, but found existing fou tunnels in place, cleaning up")
784-
recreate = true
785-
786-
// Even though we are setup for IPIP tunels we have existing tunnels that are FoU tunnels, remove them
787-
// so that we can recreate them as IPIP
788-
nrc.cleanupTunnel(nextHopSubnet, tunnelName)
789-
790-
// If we are transitioning from FoU to IPIP we also need to clean up the old FoU port if it exists
791-
if fouPortAndProtoExist(nrc.overlayEncapPort, isIPv6) {
792-
fouArgs := ipBase
793-
fouArgs = append(fouArgs, "fou", "del", "port", strFormattedEncapPort)
794-
out, err := exec.Command("ip", fouArgs...).CombinedOutput()
795-
if err != nil {
796-
klog.Warningf("failed to clean up previous FoU tunnel port (this is only a warning because it "+
797-
"won't stop kube-router from working for now, but still shouldn't have happened) - error: "+
798-
"%v, output %s", err, out)
799-
}
800-
}
801-
}
802-
case encapTypeFOU:
803-
if !linkFOUEnabled(tunnelName) {
804-
klog.Infof("Was configured to use fou tunnels, but found existing ipip tunnels in place, cleaning up")
805-
recreate = true
806-
// Even though we are setup for FoU tunels we have existing tunnels that are IPIP tunnels, remove them
807-
// so that we can recreate them as IPIP
808-
nrc.cleanupTunnel(nextHopSubnet, tunnelName)
809-
}
810-
}
811-
}
812-
813-
// an error here indicates that the tunnel didn't exist, so we need to create it, if it already exists there's
814-
// nothing to do here
815-
if err != nil || recreate {
816-
klog.Infof("Creating tunnel %s of type %s with encap %s for destination %s",
817-
tunnelName, fouLinkType, nrc.overlayEncap, nextHop.String())
818-
cmdArgs := ipBase
819-
switch nrc.overlayEncap {
820-
case encapTypeIPIP:
821-
// Plain IPIP tunnel without any encapsulation
822-
cmdArgs = append(cmdArgs, "tunnel", "add", tunnelName, "mode", ipipMode, "local", bestIPForFamily.String(),
823-
"remote", nextHop.String())
824-
825-
case encapTypeFOU:
826-
// Ensure that the FOU tunnel port is set correctly
827-
if !fouPortAndProtoExist(nrc.overlayEncapPort, isIPv6) {
828-
fouArgs := ipBase
829-
fouArgs = append(fouArgs, "fou", "add", "port", strFormattedEncapPort, "gue")
830-
out, err := exec.Command("ip", fouArgs...).CombinedOutput()
831-
if err != nil {
832-
//nolint:goconst // don't need to make error messages a constant
833-
return nil, fmt.Errorf("route not injected for the route advertised by the node %s "+
834-
"Failed to set FoU tunnel port - error: %s, output: %s", tunnelName, err, string(out))
835-
}
836-
}
837-
838-
// Prep IPIP tunnel for FOU encapsulation
839-
cmdArgs = append(cmdArgs, "link", "add", "name", tunnelName, "type", fouLinkType, "remote", nextHop.String(),
840-
"local", bestIPForFamily.String(), "ttl", "225", "encap", "gue", "encap-sport", "auto", "encap-dport",
841-
strFormattedEncapPort, "mode", ipipMode)
842-
843-
default:
844-
return nil, fmt.Errorf("unknown tunnel encapsulation was passed: %s, unable to continue with overlay "+
845-
"setup", nrc.overlayEncap)
846-
}
847-
848-
klog.V(2).Infof("Executing the following command to create tunnel: ip %s", cmdArgs)
849-
out, err := exec.Command("ip", cmdArgs...).CombinedOutput()
850-
if err != nil {
851-
return nil, fmt.Errorf("route not injected for the route advertised by the node %s "+
852-
"Failed to create tunnel interface %s. error: %s, output: %s",
853-
nextHop, tunnelName, err, string(out))
854-
}
855-
856-
link, err = netlink.LinkByName(tunnelName)
857-
if err != nil {
858-
return nil, fmt.Errorf("route not injected for the route advertised by the node %s "+
859-
"Failed to get tunnel interface by name error: %s", tunnelName, err)
860-
}
861-
if err = netlink.LinkSetUp(link); err != nil {
862-
return nil, errors.New("Failed to bring tunnel interface " + tunnelName + " up due to: " + err.Error())
863-
}
864-
}
865-
866-
// Now that the tunnel link exists, we need to add a route to it, so the node knows where to send traffic bound for
867-
// this interface
868-
//nolint:gocritic // we understand that we are appending to a new slice
869-
cmdArgs := append(ipBase, "route", "list", "table", routes.CustomTableID)
870-
out, err = exec.Command("ip", cmdArgs...).CombinedOutput()
871-
// This used to be "dev "+tunnelName+" scope" but this isn't consistent with IPv6's output, so we changed it to just
872-
// "dev "+tunnelName, but at this point I'm unsure if there was a good reason for adding scope on before, so that's
873-
// why this comment is here.
874-
if err != nil || !strings.Contains(string(out), "dev "+tunnelName) {
875-
//nolint:gocritic // we understand that we are appending to a new slice
876-
cmdArgs = append(ipBase, "route", "add", nextHop.String(), "dev", tunnelName, "table", routes.CustomTableID)
877-
if out, err = exec.Command("ip", cmdArgs...).CombinedOutput(); err != nil {
878-
return nil, fmt.Errorf("failed to add route in custom route table, err: %s, output: %s", err, string(out))
879-
}
880-
}
881-
882-
return link, nil
883-
}
884-
885718
// Cleanup performs the cleanup of configurations done
886719
func (nrc *NetworkRoutingController) Cleanup() {
887720
klog.Infof("Cleaning up NetworkRoutesController configurations")
@@ -1533,18 +1366,16 @@ func NewNetworkRoutingController(clientset kubernetes.Interface,
15331366
nrc.autoMTU = kubeRouterConfig.AutoMTU
15341367
nrc.enableOverlays = kubeRouterConfig.EnableOverlay
15351368
nrc.overlayType = kubeRouterConfig.OverlayType
1536-
nrc.overlayEncap = kubeRouterConfig.OverlayEncap
1537-
switch nrc.overlayEncap {
1538-
case encapTypeIPIP:
1539-
case encapTypeFOU:
1540-
default:
1541-
return nil, fmt.Errorf("unknown --overlay-encap option '%s' selected, unable to continue", nrc.overlayEncap)
1369+
overlayEncap, err := tunnels.ParseEncapType(kubeRouterConfig.OverlayEncap)
1370+
if err != nil {
1371+
return nil, fmt.Errorf("unknown --overlay-encap option '%s' selected, unable to continue", overlayEncap)
15421372
}
1543-
nrc.overlayEncapPort = kubeRouterConfig.OverlayEncapPort
1544-
if nrc.overlayEncapPort > maxPort || nrc.overlayEncapPort < minPort {
1545-
return nil, fmt.Errorf("specified encap port is out of range of valid ports: %d, valid range is from %d to %d",
1546-
nrc.overlayEncapPort, minPort, maxPort)
1373+
overlayEncapPort, err := tunnels.ParseEncapPort(kubeRouterConfig.OverlayEncapPort)
1374+
if err != nil {
1375+
return nil, fmt.Errorf("unknown --overlay-encap-port option '%d' selected, unable to continue, err: %v",
1376+
overlayEncapPort, err)
15471377
}
1378+
nrc.tunneler = tunnels.NewOverlayTunnel(nrc.krNode, overlayEncap, overlayEncapPort)
15481379
nrc.CNIFirewallSetup = sync.NewCond(&sync.Mutex{})
15491380

15501381
nrc.bgpPort = kubeRouterConfig.BGPPort

pkg/controllers/routing/network_routes_controller_test.go

Lines changed: 0 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import (
1212
"time"
1313

1414
"github.com/cloudnativelabs/kube-router/v2/pkg/utils"
15-
"github.com/stretchr/testify/assert"
1615
v1core "k8s.io/api/core/v1"
1716
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1817
"k8s.io/client-go/informers"
@@ -2835,39 +2834,6 @@ func Test_OnNodeUpdate(t *testing.T) {
28352834
}
28362835
*/
28372836

2838-
func Test_generateTunnelName(t *testing.T) {
2839-
testcases := []struct {
2840-
name string
2841-
nodeIP string
2842-
tunnelName string
2843-
}{
2844-
{
2845-
"IP less than 12 characters after removing '.'",
2846-
"10.0.0.1",
2847-
"tun-e443169117a",
2848-
},
2849-
{
2850-
"IP has 12 characters after removing '.'",
2851-
"100.200.300.400",
2852-
"tun-9033d7906c7",
2853-
},
2854-
{
2855-
"IPv6 tunnel names are properly handled and consistent",
2856-
"2001:db8:42:2::/64",
2857-
"tun-ba56986ef05",
2858-
},
2859-
}
2860-
2861-
for _, testcase := range testcases {
2862-
t.Run(testcase.name, func(t *testing.T) {
2863-
tunnelName := generateTunnelName(testcase.nodeIP)
2864-
assert.Lessf(t, len(tunnelName), 16, "the maximum length of the tunnel name should never exceed"+
2865-
"15 characters as 16 characters is the maximum length of a Unix interface name")
2866-
assert.Equal(t, testcase.tunnelName, tunnelName, "did not get expected tunnel interface name")
2867-
})
2868-
}
2869-
}
2870-
28712837
func createServices(clientset kubernetes.Interface, svcs []*v1core.Service) error {
28722838
for _, svc := range svcs {
28732839
_, err := clientset.CoreV1().Services(svc.ObjectMeta.Namespace).Create(context.Background(), svc, metav1.CreateOptions{})

0 commit comments

Comments
 (0)