mirror of
https://github.com/aljazceru/kata-containers.git
synced 2026-01-07 16:34:28 +01:00
Because the network monitor will be listening to every event received through the netlink socket, it will be notified everytime a new link will be added/updated/modified in the network namespace it's running into. The goal being to detect new interface added by Docker such as a veth pair. The problem is that kata-runtime will add other internal interfaces when the network monitor will ask for the addition of the new veth pair. And we need a way to ignore those new interfaces being created as they relate to the veth pair that is being added. That's why, in order to prevent from running into an infinite loop, virtcontainers needs to tag the internal interfaces with the "kata" suffix so that the network monitor will be able to ignore them. Signed-off-by: Sebastien Boeuf <sebastien.boeuf@intel.com>
602 lines
13 KiB
Go
602 lines
13 KiB
Go
// Copyright (c) 2016 Intel Corporation
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
//
|
|
|
|
package virtcontainers
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/containernetworking/plugins/pkg/ns"
|
|
"github.com/kata-containers/agent/protocols/grpc"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netns"
|
|
)
|
|
|
|
func testNetworkModelSet(t *testing.T, value string, expected NetworkModel) {
|
|
var netModel NetworkModel
|
|
|
|
err := netModel.Set(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if netModel != expected {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestNoopNetworkModelSet(t *testing.T) {
|
|
testNetworkModelSet(t, "noop", NoopNetworkModel)
|
|
}
|
|
|
|
func TestDefaultNetworkModelSet(t *testing.T) {
|
|
testNetworkModelSet(t, "default", DefaultNetworkModel)
|
|
}
|
|
|
|
func TestNetworkModelSetFailure(t *testing.T) {
|
|
var netModel NetworkModel
|
|
|
|
err := netModel.Set("wrong-value")
|
|
if err == nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func testNetworkModelString(t *testing.T, netModel *NetworkModel, expected string) {
|
|
result := netModel.String()
|
|
|
|
if result != expected {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestNoopNetworkModelString(t *testing.T) {
|
|
netModel := NoopNetworkModel
|
|
testNetworkModelString(t, &netModel, string(NoopNetworkModel))
|
|
}
|
|
|
|
func TestDefaultNetworkModelString(t *testing.T) {
|
|
netModel := DefaultNetworkModel
|
|
testNetworkModelString(t, &netModel, string(DefaultNetworkModel))
|
|
}
|
|
|
|
func TestWrongNetworkModelString(t *testing.T) {
|
|
var netModel NetworkModel
|
|
testNetworkModelString(t, &netModel, "")
|
|
}
|
|
|
|
func testNewNetworkFromNetworkModel(t *testing.T, netModel NetworkModel, expected interface{}) {
|
|
result := newNetwork(netModel)
|
|
|
|
if reflect.DeepEqual(result, expected) == false {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestNewNoopNetworkFromNetworkModel(t *testing.T) {
|
|
testNewNetworkFromNetworkModel(t, NoopNetworkModel, &noopNetwork{})
|
|
}
|
|
|
|
func TestNewDefaultNetworkFromNetworkModel(t *testing.T) {
|
|
testNewNetworkFromNetworkModel(t, DefaultNetworkModel, &defNetwork{})
|
|
}
|
|
|
|
func TestNewUnknownNetworkFromNetworkModel(t *testing.T) {
|
|
var netModel NetworkModel
|
|
testNewNetworkFromNetworkModel(t, netModel, &noopNetwork{})
|
|
}
|
|
|
|
func TestCreateDeleteNetNS(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip(testDisabledAsNonRoot)
|
|
}
|
|
|
|
netNSPath, err := createNetNS()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if netNSPath == "" {
|
|
t.Fatal()
|
|
}
|
|
|
|
_, err = os.Stat(netNSPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = deleteNetNS(netNSPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func testEndpointTypeSet(t *testing.T, value string, expected EndpointType) {
|
|
//var netModel NetworkModel
|
|
var endpointType EndpointType
|
|
|
|
err := endpointType.Set(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if endpointType != expected {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestPhysicalEndpointTypeSet(t *testing.T) {
|
|
testEndpointTypeSet(t, "physical", PhysicalEndpointType)
|
|
}
|
|
|
|
func TestVirtualEndpointTypeSet(t *testing.T) {
|
|
testEndpointTypeSet(t, "virtual", VirtualEndpointType)
|
|
}
|
|
|
|
func TestEndpointTypeSetFailure(t *testing.T) {
|
|
var endpointType EndpointType
|
|
|
|
err := endpointType.Set("wrong-value")
|
|
if err == nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func testEndpointTypeString(t *testing.T, endpointType *EndpointType, expected string) {
|
|
result := endpointType.String()
|
|
|
|
if result != expected {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestPhysicalEndpointTypeString(t *testing.T) {
|
|
endpointType := PhysicalEndpointType
|
|
testEndpointTypeString(t, &endpointType, string(PhysicalEndpointType))
|
|
}
|
|
|
|
func TestVirtualEndpointTypeString(t *testing.T) {
|
|
endpointType := VirtualEndpointType
|
|
testEndpointTypeString(t, &endpointType, string(VirtualEndpointType))
|
|
}
|
|
|
|
func TestIncorrectEndpointTypeString(t *testing.T) {
|
|
var endpointType EndpointType
|
|
testEndpointTypeString(t, &endpointType, "")
|
|
}
|
|
|
|
func TestCreateVhostUserEndpoint(t *testing.T) {
|
|
macAddr := net.HardwareAddr{0x02, 0x00, 0xCA, 0xFE, 0x00, 0x48}
|
|
ifcName := "vhost-deadbeef"
|
|
socket := "/tmp/vhu_192.168.0.1"
|
|
|
|
netinfo := NetworkInfo{
|
|
Iface: NetlinkIface{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
HardwareAddr: macAddr,
|
|
Name: ifcName,
|
|
},
|
|
},
|
|
}
|
|
|
|
expected := &VhostUserEndpoint{
|
|
SocketPath: socket,
|
|
HardAddr: macAddr.String(),
|
|
IfaceName: ifcName,
|
|
EndpointType: VhostUserEndpointType,
|
|
}
|
|
|
|
result, err := createVhostUserEndpoint(netinfo, socket)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if reflect.DeepEqual(result, expected) == false {
|
|
t.Fatalf("\n\tGot %v\n\tExpecting %v", result, expected)
|
|
}
|
|
}
|
|
|
|
func TestCreateVirtualNetworkEndpoint(t *testing.T) {
|
|
macAddr := net.HardwareAddr{0x02, 0x00, 0xCA, 0xFE, 0x00, 0x04}
|
|
|
|
expected := &VirtualEndpoint{
|
|
NetPair: NetworkInterfacePair{
|
|
ID: "uniqueTestID-4",
|
|
Name: "br4_kata",
|
|
VirtIface: NetworkInterface{
|
|
Name: "eth4",
|
|
HardAddr: macAddr.String(),
|
|
},
|
|
TAPIface: NetworkInterface{
|
|
Name: "tap4_kata",
|
|
},
|
|
NetInterworkingModel: DefaultNetInterworkingModel,
|
|
},
|
|
EndpointType: VirtualEndpointType,
|
|
}
|
|
|
|
result, err := createVirtualNetworkEndpoint(4, "", DefaultNetInterworkingModel)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// the resulting ID will be random - so let's overwrite to test the rest of the flow
|
|
result.NetPair.ID = "uniqueTestID-4"
|
|
|
|
if reflect.DeepEqual(result, expected) == false {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestCreateVirtualNetworkEndpointChooseIfaceName(t *testing.T) {
|
|
macAddr := net.HardwareAddr{0x02, 0x00, 0xCA, 0xFE, 0x00, 0x04}
|
|
|
|
expected := &VirtualEndpoint{
|
|
NetPair: NetworkInterfacePair{
|
|
ID: "uniqueTestID-4",
|
|
Name: "br4_kata",
|
|
VirtIface: NetworkInterface{
|
|
Name: "eth1",
|
|
HardAddr: macAddr.String(),
|
|
},
|
|
TAPIface: NetworkInterface{
|
|
Name: "tap4_kata",
|
|
},
|
|
NetInterworkingModel: DefaultNetInterworkingModel,
|
|
},
|
|
EndpointType: VirtualEndpointType,
|
|
}
|
|
|
|
result, err := createVirtualNetworkEndpoint(4, "eth1", DefaultNetInterworkingModel)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
// the resulting ID will be random - so let's overwrite to test the rest of the flow
|
|
result.NetPair.ID = "uniqueTestID-4"
|
|
|
|
if reflect.DeepEqual(result, expected) == false {
|
|
t.Fatal()
|
|
}
|
|
}
|
|
|
|
func TestCreateVirtualNetworkEndpointInvalidArgs(t *testing.T) {
|
|
type endpointValues struct {
|
|
idx int
|
|
ifName string
|
|
}
|
|
|
|
// all elements are expected to result in failure
|
|
failingValues := []endpointValues{
|
|
{-1, "bar"},
|
|
{-1, ""},
|
|
}
|
|
|
|
for _, d := range failingValues {
|
|
result, err := createVirtualNetworkEndpoint(d.idx, d.ifName, DefaultNetInterworkingModel)
|
|
if err == nil {
|
|
t.Fatalf("expected invalid endpoint for %v, got %v", d, result)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestIsPhysicalIface(t *testing.T) {
|
|
if os.Geteuid() != 0 {
|
|
t.Skip(testDisabledAsNonRoot)
|
|
}
|
|
|
|
testNetIface := "testIface0"
|
|
testMTU := 1500
|
|
testMACAddr := "00:00:00:00:00:01"
|
|
|
|
hwAddr, err := net.ParseMAC(testMACAddr)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
link := &netlink.Bridge{
|
|
LinkAttrs: netlink.LinkAttrs{
|
|
Name: testNetIface,
|
|
MTU: testMTU,
|
|
HardwareAddr: hwAddr,
|
|
TxQLen: -1,
|
|
},
|
|
}
|
|
|
|
n, err := ns.NewNS()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer n.Close()
|
|
|
|
netnsHandle, err := netns.GetFromPath(n.Path())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer netnsHandle.Close()
|
|
|
|
netlinkHandle, err := netlink.NewHandleAt(netnsHandle)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer netlinkHandle.Delete()
|
|
|
|
if err := netlinkHandle.LinkAdd(link); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
var isPhysical bool
|
|
err = doNetNS(n.Path(), func(_ ns.NetNS) error {
|
|
var err error
|
|
isPhysical, err = isPhysicalIface(testNetIface)
|
|
return err
|
|
})
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if isPhysical == true {
|
|
t.Fatalf("Got %+v\nExpecting %+v", isPhysical, false)
|
|
}
|
|
}
|
|
|
|
func TestNetInterworkingModelIsValid(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
n NetInterworkingModel
|
|
want bool
|
|
}{
|
|
{"Invalid Model", NetXConnectInvalidModel, false},
|
|
{"Default Model", NetXConnectDefaultModel, true},
|
|
{"Bridged Model", NetXConnectBridgedModel, true},
|
|
{"Macvtap Model", NetXConnectMacVtapModel, true},
|
|
{"Enlightened Model", NetXConnectEnlightenedModel, true},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if got := tt.n.IsValid(); got != tt.want {
|
|
t.Errorf("NetInterworkingModel.IsValid() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestNetInterworkingModelSetModel(t *testing.T) {
|
|
var n NetInterworkingModel
|
|
tests := []struct {
|
|
name string
|
|
modelName string
|
|
wantErr bool
|
|
}{
|
|
{"Invalid Model", "Invalid", true},
|
|
{"default Model", "default", false},
|
|
{"bridged Model", "bridged", false},
|
|
{"macvtap Model", "macvtap", false},
|
|
{"enlightened Model", "enlightened", false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := n.SetModel(tt.modelName); (err != nil) != tt.wantErr {
|
|
t.Errorf("NetInterworkingModel.SetModel() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVhostUserSocketPath(t *testing.T) {
|
|
|
|
// First test case: search for existing:
|
|
addresses := []netlink.Addr{
|
|
{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 2),
|
|
Mask: net.IPv4Mask(192, 168, 0, 2),
|
|
},
|
|
},
|
|
{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 1),
|
|
Mask: net.IPv4Mask(192, 168, 0, 1),
|
|
},
|
|
},
|
|
}
|
|
|
|
expectedPath := "/tmp/vhostuser_192.168.0.1"
|
|
expectedFileName := "vhu.sock"
|
|
expectedResult := fmt.Sprintf("%s/%s", expectedPath, expectedFileName)
|
|
|
|
err := os.Mkdir(expectedPath, 0777)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
_, err = os.Create(expectedResult)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
netinfo := NetworkInfo{
|
|
Addrs: addresses,
|
|
}
|
|
|
|
path, _ := vhostUserSocketPath(netinfo)
|
|
|
|
if path != expectedResult {
|
|
t.Fatalf("Got %+v\nExpecting %+v", path, expectedResult)
|
|
}
|
|
|
|
// Second test case: search doesn't include matching vsock:
|
|
addressesFalse := []netlink.Addr{
|
|
{
|
|
IPNet: &net.IPNet{
|
|
IP: net.IPv4(192, 168, 0, 4),
|
|
Mask: net.IPv4Mask(192, 168, 0, 4),
|
|
},
|
|
},
|
|
}
|
|
netinfoFail := NetworkInfo{
|
|
Addrs: addressesFalse,
|
|
}
|
|
|
|
path, _ = vhostUserSocketPath(netinfoFail)
|
|
if path != "" {
|
|
t.Fatalf("Got %+v\nExpecting %+v", path, "")
|
|
}
|
|
|
|
err = os.Remove(expectedResult)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
err = os.Remove(expectedPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestVhostUserEndpointAttach(t *testing.T) {
|
|
v := &VhostUserEndpoint{
|
|
SocketPath: "/tmp/sock",
|
|
HardAddr: "mac-addr",
|
|
EndpointType: VhostUserEndpointType,
|
|
}
|
|
|
|
h := &mockHypervisor{}
|
|
|
|
err := v.Attach(h)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func TestVhostUserEndpoint_HotAttach(t *testing.T) {
|
|
assert := assert.New(t)
|
|
v := &VhostUserEndpoint{
|
|
SocketPath: "/tmp/sock",
|
|
HardAddr: "mac-addr",
|
|
EndpointType: VhostUserEndpointType,
|
|
}
|
|
|
|
h := &mockHypervisor{}
|
|
|
|
err := v.HotAttach(h)
|
|
assert.Error(err)
|
|
}
|
|
|
|
func TestVhostUserEndpoint_HotDetach(t *testing.T) {
|
|
assert := assert.New(t)
|
|
v := &VhostUserEndpoint{
|
|
SocketPath: "/tmp/sock",
|
|
HardAddr: "mac-addr",
|
|
EndpointType: VhostUserEndpointType,
|
|
}
|
|
|
|
h := &mockHypervisor{}
|
|
|
|
err := v.HotDetach(h, true, "")
|
|
assert.Error(err)
|
|
}
|
|
|
|
func TestPhysicalEndpoint_HotAttach(t *testing.T) {
|
|
assert := assert.New(t)
|
|
v := &PhysicalEndpoint{
|
|
IfaceName: "eth0",
|
|
HardAddr: net.HardwareAddr{0x02, 0x00, 0xca, 0xfe, 0x00, 0x04}.String(),
|
|
}
|
|
|
|
h := &mockHypervisor{}
|
|
|
|
err := v.HotAttach(h)
|
|
assert.Error(err)
|
|
}
|
|
|
|
func TestPhysicalEndpoint_HotDetach(t *testing.T) {
|
|
assert := assert.New(t)
|
|
v := &PhysicalEndpoint{
|
|
IfaceName: "eth0",
|
|
HardAddr: net.HardwareAddr{0x02, 0x00, 0xca, 0xfe, 0x00, 0x04}.String(),
|
|
}
|
|
|
|
h := &mockHypervisor{}
|
|
|
|
err := v.HotDetach(h, true, "")
|
|
assert.Error(err)
|
|
}
|
|
|
|
func TestGenerateInterfacesAndRoutes(t *testing.T) {
|
|
//
|
|
//Create a couple of addresses
|
|
//
|
|
address1 := &net.IPNet{IP: net.IPv4(172, 17, 0, 2), Mask: net.CIDRMask(16, 32)}
|
|
address2 := &net.IPNet{IP: net.IPv4(182, 17, 0, 2), Mask: net.CIDRMask(16, 32)}
|
|
|
|
addrs := []netlink.Addr{
|
|
{IPNet: address1, Label: "phyaddr1"},
|
|
{IPNet: address2, Label: "phyaddr2"},
|
|
}
|
|
|
|
// Create a couple of routes:
|
|
dst2 := &net.IPNet{IP: net.IPv4(172, 17, 0, 0), Mask: net.CIDRMask(16, 32)}
|
|
src2 := net.IPv4(172, 17, 0, 2)
|
|
gw2 := net.IPv4(172, 17, 0, 1)
|
|
|
|
routes := []netlink.Route{
|
|
{LinkIndex: 329, Dst: nil, Src: nil, Gw: net.IPv4(172, 17, 0, 1), Scope: netlink.Scope(254)},
|
|
{LinkIndex: 329, Dst: dst2, Src: src2, Gw: gw2},
|
|
}
|
|
|
|
networkInfo := NetworkInfo{
|
|
Iface: NetlinkIface{
|
|
LinkAttrs: netlink.LinkAttrs{MTU: 1500},
|
|
Type: "",
|
|
},
|
|
Addrs: addrs,
|
|
Routes: routes,
|
|
}
|
|
|
|
ep0 := &PhysicalEndpoint{
|
|
IfaceName: "eth0",
|
|
HardAddr: net.HardwareAddr{0x02, 0x00, 0xca, 0xfe, 0x00, 0x04}.String(),
|
|
EndpointProperties: networkInfo,
|
|
}
|
|
|
|
endpoints := []Endpoint{ep0}
|
|
|
|
nns := NetworkNamespace{NetNsPath: "foobar", NetNsCreated: true, Endpoints: endpoints}
|
|
|
|
resInterfaces, resRoutes, err := generateInterfacesAndRoutes(nns)
|
|
|
|
//
|
|
// Build expected results:
|
|
//
|
|
expectedAddresses := []*grpc.IPAddress{
|
|
{Family: 0, Address: "172.17.0.2", Mask: "16"},
|
|
{Family: 0, Address: "182.17.0.2", Mask: "16"},
|
|
}
|
|
|
|
expectedInterfaces := []*grpc.Interface{
|
|
{Device: "eth0", Name: "eth0", IPAddresses: expectedAddresses, Mtu: 1500, HwAddr: "02:00:ca:fe:00:04"},
|
|
}
|
|
|
|
expectedRoutes := []*grpc.Route{
|
|
{Dest: "", Gateway: "172.17.0.1", Device: "eth0", Source: "", Scope: uint32(254)},
|
|
{Dest: "172.17.0.0/16", Gateway: "172.17.0.1", Device: "eth0", Source: "172.17.0.2"},
|
|
}
|
|
|
|
assert.Nil(t, err, "unexpected failure when calling generateKataInterfacesAndRoutes")
|
|
assert.True(t, reflect.DeepEqual(resInterfaces, expectedInterfaces),
|
|
"Interfaces returned didn't match: got %+v, expecting %+v", resInterfaces, expectedInterfaces)
|
|
assert.True(t, reflect.DeepEqual(resRoutes, expectedRoutes),
|
|
"Routes returned didn't match: got %+v, expecting %+v", resRoutes, expectedRoutes)
|
|
|
|
}
|