Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

podnetwork: Support CNI plugins like PTP and GKE #1920

Merged
merged 4 commits into from
Aug 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 43 additions & 30 deletions src/cloud-api-adaptor/pkg/podnetwork/podnetwork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,13 @@ func TestWorkerNode(t *testing.T) {
require.Equal(t, expected.workerNodeIP, config.WorkerNodeIP.String(), "hostInterface=%q", hostInterface)
require.Equal(t, mockTunnelType, config.TunnelType, "hostInterface=%q", hostInterface)

require.Equal(t, len(config.Routes), 1, "hostInterface=%q", hostInterface)
require.Equal(t, len(config.Routes), 2, "hostInterface=%q", hostInterface)
require.Equal(t, config.Routes[0].Dst.String(), "0.0.0.0/0", "hostInterface=%q", hostInterface)
require.Equal(t, config.Routes[0].GW.String(), "172.16.0.1", "hostInterface=%q", hostInterface)
require.Equal(t, config.Routes[0].Dev, "eth0", "hostInterface=%q", hostInterface)
require.Equal(t, config.Routes[1].Dst.String(), "172.16.0.0/24", "hostInterface=%q", hostInterface)
require.Equal(t, config.Routes[1].GW.IsValid(), false, "hostInterface=%q", hostInterface)
require.Equal(t, config.Routes[1].Dev, "eth0", "hostInterface=%q", hostInterface)

err = workerNode.Teardown(workerPodNS.Path(), config)
require.Nil(t, err, "hostInterface=%q", hostInterface)
Expand All @@ -128,9 +131,6 @@ func TestPodNode(t *testing.T) {
tuntest.AddrAdd(t, podNodeNS, "ens1", "192.168.1.3/24")
tuntest.RouteAdd(t, podNodeNS, "", "192.168.0.1", "ens0")

podNS := tuntest.NewNamedNS(t, "test-pod")
defer tuntest.DeleteNamedNS(t, podNS)

for hostInterface, expected := range map[string]struct {
podNodeIP string
workerNodeIP string
Expand All @@ -149,35 +149,48 @@ func TestPodNode(t *testing.T) {
},
} {

err := podNodeNS.Run(func() error {

config := &tunneler.Config{
PodIP: netip.MustParsePrefix("172.16.0.2/24"),
Routes: []*tunneler.Route{
{
GW: netip.MustParseAddr("172.16.0.1"),
Dev: "eth0",
podNS := tuntest.NewNamedNS(t, "test-pod")
func() {
defer tuntest.DeleteNamedNS(t, podNS)

tuntest.BridgeAdd(t, podNS, "eth0")
tuntest.AddrAdd(t, podNS, "eth0", "172.16.0.2/24")

err := podNodeNS.Run(func() error {

config := &tunneler.Config{
PodIP: netip.MustParsePrefix("172.16.0.2/24"),
Routes: []*tunneler.Route{
{
Dst: netip.MustParsePrefix("0.0.0.0/0"),
GW: netip.MustParseAddr("172.16.0.1"),
Dev: "eth0",
},
{
Dst: netip.MustParsePrefix("172.16.0.0/24"),
Dev: "eth0",
},
},
},
InterfaceName: "eth0",
MTU: 1500,
WorkerNodeIP: netip.MustParsePrefix(expected.workerNodeIP),
TunnelType: mockTunnelType,
Dedicated: hostInterface == "ens1",
}

podNode := NewPodNode(podNS.Path(), hostInterface, config)
require.NotNil(t, podNode, "hostInterface=%q", hostInterface)

err := podNode.Setup()
require.Nil(t, err, "hostInterface=%q", hostInterface)
InterfaceName: "eth0",
MTU: 1500,
WorkerNodeIP: netip.MustParsePrefix(expected.workerNodeIP),
TunnelType: mockTunnelType,
Dedicated: hostInterface == "ens1",
}

err = podNode.Teardown()
require.Nil(t, err, "hostInterface=%q", hostInterface)
podNode := NewPodNode(podNS.Path(), hostInterface, config)
require.NotNil(t, podNode, "hostInterface=%q", hostInterface)

return nil
})
require.Nil(t, err, "hostInterface=%q", hostInterface)
err := podNode.Setup()
require.Nil(t, err, "hostInterface=%q", hostInterface)

err = podNode.Teardown()
require.Nil(t, err, "hostInterface=%q", hostInterface)

return nil
})
require.Nil(t, err, "hostInterface=%q", hostInterface)
}()
}
}

Expand Down
43 changes: 43 additions & 0 deletions src/cloud-api-adaptor/pkg/podnetwork/podnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,49 @@ func (n *podNode) Setup() error {
return fmt.Errorf("failed to set up tunnel %q: %w", n.config.TunnelType, err)
}

if !n.config.PodIP.IsSingleIP() {
// Delete the nRoute that was automatically added by kernel for eth0
// CNI plugins like PTP and GKE need this trick, otherwise adding a route will fail in a later step.
// The deleted route will be restored again in the cases of usual CNI plugins such as Flannel and Calico.
// https://github.com/containernetworking/plugins/blob/acf8ddc8e1128e6f68a34f7fe91122afeb1fa93d/plugins/main/ptp/ptp.go#L58-L61

nRoute := netops.Route{
Destination: n.config.PodIP.Masked(),
Device: n.config.InterfaceName,
}
if err := podNS.RouteDel(&nRoute); err != nil {
return fmt.Errorf("failed to remove route %s dev %s: %v", nRoute.Destination, nRoute.Device, err)
}
logger.Printf("removed route %s dev %s", nRoute.Destination, nRoute.Device)
}

// We need to process routes without gateway address first. Processing routes with a gateway causes an error if the gateway is not reachable.
// Calico sets up routes with this pattern.
// https://github.com/projectcalico/cni-plugin/blob/7495c0279c34faac315b82c1838bca638e23dbbe/pkg/dataplane/linux/dataplane_linux.go#L158-L167

var first, second []*tunneler.Route
for _, route := range n.config.Routes {
if !route.GW.IsValid() {
first = append(first, route)
} else {
second = append(second, route)
}
}
routes := append(first, second...)

for _, route := range routes {
nRoute := netops.Route{
Destination: route.Dst,
Gateway: route.GW,
Device: route.Dev,
Protocol: route.Protocol,
Scope: route.Scope,
}
if err := podNS.RouteAdd(&nRoute); err != nil {
return fmt.Errorf("failed to add a route to %s via %s on pod network namespace %s: %w", route.Dst, route.GW, podNS.Path(), err)
}
}

return nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ func NewPodNodeTunneler() tunneler.Tunneler {
}

const (
podVEthName = "eth0"
hostVEthName = "veth0"

podTableID = 45001
Expand All @@ -39,6 +38,11 @@ func (t *podNodeTunneler) Setup(nsPath string, podNodeIPs []netip.Addr, config *
return errors.New("secondary pod node IP is not available")
}

podVEthName := config.InterfaceName
beraldoleal marked this conversation as resolved.
Show resolved Hide resolved
if podVEthName == "" {
return errors.New("InterfaceName is not specified")
}

podNodeIP := podNodeIPs[1]

podIP := config.PodIP
Expand Down Expand Up @@ -97,21 +101,7 @@ func (t *podNodeTunneler) Setup(nsPath string, podNodeIPs []netip.Addr, config *

var defaultRouteGateway netip.Addr

// We need to process routes without gateway address first. Processing routes with a gateway causes an error if the gateway is not reachable.
// Calico sets up routes with this pattern.
// https://github.com/projectcalico/cni-plugin/blob/7495c0279c34faac315b82c1838bca638e23dbbe/pkg/dataplane/linux/dataplane_linux.go#L158-L167

var first, second []*tunneler.Route
for _, route := range config.Routes {
if !route.GW.IsValid() {
first = append(first, route)
} else {
second = append(second, route)
}
}
routes := append(first, second...)

for _, route := range routes {
if err := podNS.RouteAdd(&netops.Route{Destination: route.Dst, Gateway: route.GW, Device: podVEthName}); err != nil {
return fmt.Errorf("failed to add a route to %s via %s on pod network namespace %s: %w", route.Dst, route.GW, nsPath, err)
}
Expand Down
10 changes: 7 additions & 3 deletions src/cloud-api-adaptor/pkg/podnetwork/tunneler/tunneler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package tunneler
import (
"fmt"
"net/netip"

"github.com/confidential-containers/cloud-api-adaptor/src/cloud-api-adaptor/pkg/util/netops"
)

type Tunneler interface {
Expand All @@ -28,9 +30,11 @@ type Config struct {
}

type Route struct {
Dst netip.Prefix
GW netip.Addr
Dev string
Dst netip.Prefix
GW netip.Addr
Dev string
Protocol netops.RouteProtocol
Scope netops.RouteScope
}

type driver struct {
Expand Down
40 changes: 15 additions & 25 deletions src/cloud-api-adaptor/pkg/podnetwork/tunneler/vxlan/podnode.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package vxlan

import (
"errors"
"fmt"
"net/netip"

Expand All @@ -12,8 +13,8 @@ import (
)

const (
podVxlanInterface = "vxlan0"
maxMTU = 1450
hostVxlanInterface = "vxlan0"
maxMTU = 1450
)

type podNodeTunneler struct {
Expand All @@ -25,6 +26,11 @@ func NewPodNodeTunneler() tunneler.Tunneler {

func (t *podNodeTunneler) Setup(nsPath string, podNodeIPs []netip.Addr, config *tunneler.Config) error {

podVxlanInterface := config.InterfaceName
beraldoleal marked this conversation as resolved.
Show resolved Hide resolved
if podVxlanInterface == "" {
return errors.New("InterfaceName is not specified")
}

nodeAddr := config.WorkerNodeIP

if !nodeAddr.IsValid() {
Expand Down Expand Up @@ -53,13 +59,17 @@ func (t *podNodeTunneler) Setup(nsPath string, podNodeIPs []netip.Addr, config *
ID: config.VXLANID,
Port: config.VXLANPort,
}
vxlan, err := hostNS.LinkAdd(podVxlanInterface, vxlanDevice)
vxlan, err := hostNS.LinkAdd(hostVxlanInterface, vxlanDevice)
if err != nil {
return fmt.Errorf("failed to add vxlan interface %s: %w", podVxlanInterface, err)
return fmt.Errorf("failed to add vxlan interface %s: %w", hostVxlanInterface, err)
}

if err := vxlan.SetNamespace(podNS); err != nil {
return fmt.Errorf("failed to move vxlan interface %s to netns %s: %w", podVxlanInterface, podNS.Path(), err)
return fmt.Errorf("failed to move vxlan interface %s to netns %s: %w", hostVxlanInterface, podNS.Path(), err)
}

if err := vxlan.SetName(podVxlanInterface); err != nil {
return fmt.Errorf("failed to rename vxlan interface %s on netns %s: %w", hostVxlanInterface, podNS.Path(), err)
}

if err := vxlan.SetHardwareAddr(config.PodHwAddr); err != nil {
Expand All @@ -82,26 +92,6 @@ func (t *podNodeTunneler) Setup(nsPath string, podNodeIPs []netip.Addr, config *
return err
}

// We need to process routes without gateway address first. Processing routes with a gateway causes an error if the gateway is not reachable.
// Calico sets up routes with this pattern.
// https://github.com/projectcalico/cni-plugin/blob/7495c0279c34faac315b82c1838bca638e23dbbe/pkg/dataplane/linux/dataplane_linux.go#L158-L167

var first, second []*tunneler.Route
for _, route := range config.Routes {
if !route.GW.IsValid() {
first = append(first, route)
} else {
second = append(second, route)
}
}
routes := append(first, second...)

for _, route := range routes {
if err := podNS.RouteAdd(&netops.Route{Destination: route.Dst, Gateway: route.GW, Device: podVxlanInterface}); err != nil {
return fmt.Errorf("failed to add a route to %s via %s on pod network namespace %s: %w", route.Dst, route.GW, nsPath, err)
}
}

return nil
}

Expand Down
8 changes: 5 additions & 3 deletions src/cloud-api-adaptor/pkg/podnetwork/workernode.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,11 @@ func (n *workerNode) Inspect(nsPath string) (*tunneler.Config, error) {

for _, route := range routes {
r := &tunneler.Route{
Dst: route.Destination,
Dev: route.Device,
GW: route.Gateway,
Dst: route.Destination,
Dev: route.Device,
GW: route.Gateway,
Protocol: route.Protocol,
Scope: route.Scope,
}
config.Routes = append(config.Routes, r)
}
Expand Down
Loading
Loading