diff --git a/src/runtime/Makefile b/src/runtime/Makefile index 69c86974c2..2a8dd255a9 100644 --- a/src/runtime/Makefile +++ b/src/runtime/Makefile @@ -282,6 +282,9 @@ DEFBINDMOUNTS := [] # Create Container Timeout in seconds DEFCREATECONTAINERTIMEOUT ?= 60 +# Default directory of directly attachable network config. +DEFDANCONF := /run/kata-containers/dans + SED = sed CLI_DIR = cmd @@ -772,6 +775,7 @@ USER_VARS += DEFSTATICRESOURCEMGMT_STRATOVIRT USER_VARS += DEFSTATICRESOURCEMGMT_TEE USER_VARS += DEFBINDMOUNTS USER_VARS += DEFCREATECONTAINERTIMEOUT +USER_VARS += DEFDANCONF USER_VARS += DEFVFIOMODE USER_VARS += BUILDFLAGS diff --git a/src/runtime/config/configuration-acrn.toml.in b/src/runtime/config/configuration-acrn.toml.in index 7297059cad..e8e933aec6 100644 --- a/src/runtime/config/configuration-acrn.toml.in +++ b/src/runtime/config/configuration-acrn.toml.in @@ -248,3 +248,12 @@ experimental=@DEFAULTEXPFEATURES@ # (https://kubernetes.io/docs/reference/command-line-tools-reference/kubelet/#:~:text=runtime%2Drequest%2Dtimeout) and create_container_timeout. # In essence, the timeout used for guest pull=runtime-request-timeout 0 { + if _, r.Dst, err = net.ParseCIDR(route.Dest); err != nil { + return nil, fmt.Errorf("bad route dest in DAN config: %v", err) + } + } + r.Src = net.ParseIP(route.Source) + r.Gw = net.ParseIP(route.Gateway) + r.Scope = netlink.Scope(route.Scope) + if len(r.Gw.To4()) == net.IPv4len { + r.Family = unix.AF_INET + } else { + r.Family = unix.AF_INET6 + } + + netInfo.Routes = append(netInfo.Routes, r) + } + + for _, neigh := range device.NetworkInfo.Neighbors { + var n netlink.Neigh + n.State = int(neigh.State) + n.Flags = int(neigh.Flags) + if n.HardwareAddr, err = net.ParseMAC(neigh.HardwareAddr); err != nil { + return nil, fmt.Errorf("bad neighbor hardware address in DAN config: %v", err) + } + + n.IP = net.ParseIP(neigh.IPAddress) + netInfo.Neighbors = append(netInfo.Neighbors, n) + } + + return &netInfo, nil +} + +// Load network config in DAN config +// Create the endpoints for the interfaces in Dan. +func (n *LinuxNetwork) addDanEndpoints() error { + + jsonData, err := os.ReadFile(n.danConfigPath) + if err != nil { + return fmt.Errorf("fail to load DAN config file: %v", err) + } + + var config vctypes.DanConfig + err = json.Unmarshal([]byte(jsonData), &config) + if err != nil { + return fmt.Errorf("fail to unmarshal DAN config: %v", err) + } + + for _, device := range config.Devices { + var endpoint Endpoint + networkLogger().WithField("interface", device.Name).Info("DAN interface found") + + _, err := convertDanDeviceToNetworkInfo(&device) + if err != nil { + return err + } + + // TODO: Add endpoints that are supported via DAN + switch device.Device.Type { + default: + return fmt.Errorf("unknown DAN device type: '%s'", device.Device.Type) + } + + // TODO: remove below `nolink` directive after one `case` is added for + // above `switch` block. + //nolint: govet + n.eps = append(n.eps, endpoint) + } + + sort.Slice(n.eps, func(i, j int) bool { + return n.eps[i].Name() < n.eps[j].Name() + }) + + return nil +} + // Run runs a callback in the specified network namespace. func (n *LinuxNetwork) Run(ctx context.Context, cb func() error) error { span, _ := n.trace(ctx, "Run") @@ -393,8 +493,15 @@ func (n *LinuxNetwork) AddEndpoints(ctx context.Context, s *Sandbox, endpointsIn defer span.End() if endpointsInfo == nil { - if err := n.addAllEndpoints(ctx, s, hotplug); err != nil { - return nil, err + // If a sandbox has a DAN configuration, it takes priority and will be used exclusively. + if n.danConfigPath != "" { + if err := n.addDanEndpoints(); err != nil { + return nil, err + } + } else { + if err := n.addAllEndpoints(ctx, s, hotplug); err != nil { + return nil, err + } } } else { for _, ep := range endpointsInfo { diff --git a/src/runtime/virtcontainers/network_linux_test.go b/src/runtime/virtcontainers/network_linux_test.go index 21f79e6a10..d959f3f28a 100644 --- a/src/runtime/virtcontainers/network_linux_test.go +++ b/src/runtime/virtcontainers/network_linux_test.go @@ -7,13 +7,18 @@ package virtcontainers import ( "context" + "encoding/json" "net" + "os" "reflect" "testing" + "golang.org/x/sys/unix" + "github.com/containernetworking/plugins/pkg/ns" ktu "github.com/kata-containers/kata-containers/src/runtime/pkg/katatestutils" pbTypes "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/pkg/agent/protocols" + vctypes "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/types" "github.com/kata-containers/kata-containers/src/runtime/virtcontainers/utils" "github.com/stretchr/testify/assert" "github.com/vishvananda/netlink" @@ -322,3 +327,51 @@ func TestTxRateLimiter(t *testing.T) { err = netHandle.LinkDel(link) assert.NoError(err) } + +func TestConvertDanDeviceToNetworkInfo(t *testing.T) { + + jsonData, err := os.ReadFile("testdata/dan-config.json") + assert.NoError(t, err) + var config vctypes.DanConfig + err = json.Unmarshal([]byte(jsonData), &config) + assert.NoError(t, err) + + ni, err := convertDanDeviceToNetworkInfo(&config.Devices[0]) + assert.NoError(t, err) + assert.Equal(t, 1500, ni.Iface.MTU) + + assert.Len(t, ni.Addrs, 1) + assert.Equal(t, "10.10.0.5/24", ni.Addrs[0].String()) + + dest, _ := netlink.ParseIPNet("10.10.0.0/16") + dest.IP = dest.IP.To4() + routes := []netlink.Route{ + {Family: unix.AF_INET, Dst: nil, Gw: net.ParseIP("10.0.0.1"), Src: nil, Scope: 0}, + {Family: unix.AF_INET, Dst: dest, Gw: net.ParseIP("10.0.0.1"), Src: nil, Scope: 0}, + } + assert.Equal(t, routes, ni.Routes) + + neighMac, _ := net.ParseMAC("0a:58:0a:0a:0a:0a") + neigh := netlink.Neigh{ + HardwareAddr: neighMac, + IP: net.ParseIP("10.10.10.10"), + } + assert.Len(t, ni.Neighbors, 1) + assert.Equal(t, neigh, ni.Neighbors[0]) +} + +func TestAddEndpoints_Dan(t *testing.T) { + + network := &LinuxNetwork{ + "net-123", + []Endpoint{}, + NetXConnectDefaultModel, + true, + "testdata/dan-config.json", + } + + ctx := context.TODO() + _, err := network.AddEndpoints(ctx, nil, nil, true) + // TODO: this will be updated after adding supported DAN device + assert.ErrorContains(t, err, "unknown DAN device type") +} diff --git a/src/runtime/virtcontainers/testdata/dan-config.json b/src/runtime/virtcontainers/testdata/dan-config.json new file mode 100644 index 0000000000..1e02d0d3f2 --- /dev/null +++ b/src/runtime/virtcontainers/testdata/dan-config.json @@ -0,0 +1,36 @@ +{ + "netns": "netns", + "devices": [ + { + "name": "eth0", + "guest_mac": "0a:58:0a:0a:00:05", + "device": { + "type": "vfio", + "pci_device_id": "0000:85:02.5" + }, + "network_info": { + "interface": { + "ip_addresses": [ + "10.10.0.5/24" + ], + "mtu": 1500 + }, + "routes": [ + { + "gateway": "10.0.0.1" + }, + { + "dest": "10.10.0.0/16", + "gateway": "10.0.0.1" + } + ], + "Neighbors": [ + { + "ip_address": "10.10.10.10", + "hardware_addr": "0a:58:0a:0a:0a:0a" + } + ] + } + } + ] +} diff --git a/src/runtime/virtcontainers/types/dan.go b/src/runtime/virtcontainers/types/dan.go new file mode 100644 index 0000000000..073977f0ce --- /dev/null +++ b/src/runtime/virtcontainers/types/dan.go @@ -0,0 +1,57 @@ +// Copyright (c) 2024 NVIDIA Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package types + +type DanConfig struct { + Netns *string `json:"netns"` + Devices []DanDevice `json:"devices"` +} + +type DanDevice struct { + Name string `json:"name"` + GuestMac string `json:"guest_mac"` + Device Device `json:"device"` + NetworkInfo NetworkInfo `json:"network_info"` +} + +// DanDeviceType identifies the type of the network interface. +type DanDeviceType string + +type Device struct { + Type DanDeviceType `json:"type"` + Path string `json:"path,omitempty"` + PciDeviceID string `json:"pci_device_id,omitempty"` + TapName string `json:"tap_name,omitempty"` + QueueNum int `json:"queue_num,omitempty"` + QueueSize int `json:"queue_size,omitempty"` +} + +type NetworkInfo struct { + Interface Interface `json:"interface,omitempty"` + Routes []Route `json:"routes,omitempty"` + Neighbors []ARPNeighbor `json:"neighbors,omitempty"` +} + +type Interface struct { + IPAddresses []string `json:"ip_addresses"` + MTU uint64 `json:"mtu"` + NType string `json:"ntype,omitempty"` + Flags uint32 `json:"flags,omitempty"` +} + +type Route struct { + Dest string `json:"dest,omitempty"` + Gateway string `json:"gateway"` + Source string `json:"source,omitempty"` + Scope uint32 `json:"scope,omitempty"` +} + +type ARPNeighbor struct { + IPAddress string `json:"ip_address"` + HardwareAddr string `json:"hardware_addr"` + State uint32 `json:"state,omitempty"` + Flags uint32 `json:"flags,omitempty"` +}