From d67ae93bb23d7e2bcc893263abfc63e138407ae7 Mon Sep 17 00:00:00 2001 From: Milan Lenco Date: Mon, 18 Nov 2024 12:34:21 +0100 Subject: [PATCH] Add example for VLAN sub-interfaces The example demonstrates the use case of VLANs separating application traffic from management traffic while still utilizing the underlying Ethernet port for untagged traffic. This scenario is not addressed in the vlans-and-lags example. Signed-off-by: Milan Lenco --- pkg/defaults/defaults.go | 2 +- sdn/examples/vlan-subinterfaces/README.md | 103 +++++++ .../vlan-subinterfaces/device-config.json | 269 ++++++++++++++++++ .../vlan-subinterfaces/network-model.json | 148 ++++++++++ sdn/vm/cmd/sdnagent/parse.go | 26 -- sdn/vm/pkg/configitems/veth.go | 8 + 6 files changed, 529 insertions(+), 27 deletions(-) create mode 100644 sdn/examples/vlan-subinterfaces/README.md create mode 100644 sdn/examples/vlan-subinterfaces/device-config.json create mode 100644 sdn/examples/vlan-subinterfaces/network-model.json diff --git a/pkg/defaults/defaults.go b/pkg/defaults/defaults.go index d3d57a821..38dc04a98 100644 --- a/pkg/defaults/defaults.go +++ b/pkg/defaults/defaults.go @@ -53,7 +53,7 @@ const ( DefaultRegistryPort = 5050 //tags, versions, repos - DefaultEVETag = "13.3.0" // DefaultEVETag tag for EVE image + DefaultEVETag = "13.7.0" // DefaultEVETag tag for EVE image DefaultAdamTag = "0.0.57" DefaultRedisTag = "7" DefaultRegistryTag = "2.7" diff --git a/sdn/examples/vlan-subinterfaces/README.md b/sdn/examples/vlan-subinterfaces/README.md new file mode 100644 index 000000000..a0fa8c7e7 --- /dev/null +++ b/sdn/examples/vlan-subinterfaces/README.md @@ -0,0 +1,103 @@ +# SDN Example with VLAN sub-interfaces + +VLANs enable the segmentation of a physical network into multiple logical networks, +allowing for better traffic control, security, and resource optimization. +On EVE, the use of VLANs helps isolate the management traffic from application traffic +or even to split applications and their traffic into different logical networks. +This allows the external networks to give preferential treatment and apply different +policies as per their requirements. + +VLAN configurations supported by EVE: + +1. VLAN filtering for switch network instances +2. VLAN sub-interfaces over physical NICs used for management traffic or for Local NIs +3. VLAN sub-interfaces over LAGs used for management traffic or for Local NIs + +In this example, we focus on the second use-case, where VLANs are used to separate management +traffic from the application traffic routed via Local network instances. + +Network topology diagram: + +```text + +-----+ +----------------+ + | EVE |--| VLAN 10 (mgmt) |----- + +-----+ +----------------+ | + | ++------+ +--------------+ +---------+ +------+ +| app1 |--| NI1 (local) |--| VLAN 20 |--| eth0 | ++------+ +--------------+ +---------+ +------+ + | ++------+ +--------------+ | +| app2 |--| NI2 (local) |-------- ++------+ +--------------+ +``` + +Deploy example with: + +```shell +make clean && make build-tests +./eden config add default +./eden config set default --key sdn.disable --value false +./eden setup --eve-bootstrap-file $(pwd)/sdn/examples/vlan-subinterfaces/device-config.json +./eden start --sdn-network-model $(pwd)/sdn/examples/vlan-subinterfaces/network-model.json +./eden eve onboard +./eden controller edge-node set-config --file $(pwd)/sdn/examples/vlan-subinterfaces/device-config.json +``` + +Note that VLAN IP subnets are `172.22..0/24`. EVE will therefore use IP address from +the subnet `172.22.10.0/24` to access the controller. Network traffic from `app1` will be NATed +to an IP address from `172.22.20.0/24` before it leaves the edge node. +`app2` will be using the underlying `eth0` interface instead of a VLAN sub-interface to access +the untagged portion of the network with subnet `192.168.77.0/24`. + +Once deployed, check DHCP-assigned IPs: + +```shell +./eden eve ssh +$ ifconfig vlan10 +vlan10 Link encap:Ethernet HWaddr 02:FE:22:1A:87:00 + inet addr:172.22.10.13 Bcast:172.22.10.255 Mask:255.255.255.0 + ... + +$ ifconfig vlan20 +vlan20 Link encap:Ethernet HWaddr 02:FE:22:1A:87:00 + inet addr:172.22.20.13 Bcast:172.22.20.255 Mask:255.255.255.0 + +$ ifconfig eth0 +eth0 Link encap:Ethernet HWaddr 02:FE:22:1A:87:00 + inet addr:192.168.77.13 Bcast:192.168.77.255 Mask:255.255.255.0 + ... +``` + +Check that `app1` can access HTTP server deployed for VLAN 20 (`httpserver-20.sdn`), +but not HTTP server deployed for VLAN 10 (`httpserver-10.sdn`) or for the untagged +network (`httpserver-untagged.sdn`): + +```shell +./eden eve ssh +CONSOLE="$(eve list-app-consoles | grep cee082fd-3a43-4599-bbd3-8216ffa8652d | grep CONTAINER | awk '{print $4}')" +eve attach-app-console "$CONSOLE" + +app1$ curl httpserver-20.sdn/helloworld +Hello world from HTTP server for VLAN 20 +app1$ curl httpserver-10.sdn/helloworld +curl: (7) Failed to connect to httpserver-10.sdn port 80 after 44 ms: Couldn't connect to server +app1$ curl httpserver-untagged.sdn/helloworld +curl: (7) Failed to connect to httpserver-untagged.sdn port 80 after 48 ms: Couldn't connect to server +``` + +Check that `app2` can access HTTP server deployed for the untagged network (`httpserver-untagged.sdn`), +but not HTTP server deployed for VLAN 10 (`httpserver-10.sdn`) or for VLAN 20 (`httpserver-20.sdn`): + +```shell +./eden eve ssh +CONSOLE="$(eve list-app-consoles | grep 45ff198d-b295-4ff2-bf69-76977af809fd | grep CONTAINER | awk '{print $4}')" +eve attach-app-console "$CONSOLE" + +app2$ curl httpserver-untagged.sdn/helloworld +Hello world from HTTP server for untagged network +app1$ curl httpserver-10.sdn/helloworld +curl: (7) Failed to connect to httpserver-10.sdn port 80 after 47 ms: Couldn't connect to server +app1$ curl httpserver-20.sdn/helloworld +curl: (7) Failed to connect to httpserver-20.sdn port 80 after 47 ms: Couldn't connect to server +``` \ No newline at end of file diff --git a/sdn/examples/vlan-subinterfaces/device-config.json b/sdn/examples/vlan-subinterfaces/device-config.json new file mode 100644 index 000000000..a2d467d71 --- /dev/null +++ b/sdn/examples/vlan-subinterfaces/device-config.json @@ -0,0 +1,269 @@ +{ + "deviceIoList": [ + { + "ptype": 1, + "phylabel": "eth0", + "phyaddrs": { + "Ifname": "eth0" + }, + "logicallabel": "ethernet0", + "assigngrp": "eth0", + "usage": 1 + } + ], + "vlans": [ + { + "logicallabel": "mgmt-vlan", + "interfaceName": "vlan10", + "lowerLayerName": "ethernet0", + "vlanId": 10 + }, + { + "logicallabel": "app-vlan", + "interfaceName": "vlan20", + "lowerLayerName": "ethernet0", + "vlanId": 20 + } + ], + "networks": [ + { + "id": "6605d17b-3273-4108-8e6e-4965441ebe01", + "type": 4, + "ip": { + "dhcp": 4 + } + }, + { + "id": "b970ac70-2ef7-4c6b-8bb8-ff8626321313", + "type": 4, + "ip": { + "dhcp": 2 + } + } + ], + "systemAdapterList": [ + { + "name": "mgmt-vlan", + "uplink": true, + "networkUUID": "6605d17b-3273-4108-8e6e-4965441ebe01" + }, + { + "name": "app-vlan", + "networkUUID": "6605d17b-3273-4108-8e6e-4965441ebe01" + }, + { + "name": "ethernet0", + "networkUUID": "6605d17b-3273-4108-8e6e-4965441ebe01" + } + ], + "networkInstances": [ + { + "uuidandversion": { + "uuid": "9ca83da9-94e8-48b4-9ae8-3f188c5c694a", + "version": "1" + }, + "displayname": "ni1", + "instType": 2, + "activate": true, + "port": { + "type": 1, + "name": "app-vlan" + }, + "ipType": 1, + "ip": { + "subnet": "10.50.10.0/24", + "gateway": "10.50.10.1", + "dns": [ + "10.50.10.1" + ], + "dhcpRange": { + "start": "10.50.10.2", + "end": "10.50.10.254" + } + } + }, + { + "uuidandversion": { + "uuid": "0125b9fa-9054-446d-9e9a-1b99175d24a7", + "version": "1" + }, + "displayname": "ni2", + "instType": 2, + "activate": true, + "port": { + "type": 1, + "name": "ethernet0" + }, + "ipType": 1, + "ip": { + "subnet": "10.50.77.0/24", + "gateway": "10.50.77.1", + "dns": [ + "10.50.77.1" + ], + "dhcpRange": { + "start": "10.50.77.2", + "end": "10.50.77.254" + } + } + } + ], + "apps": [ + { + "uuidandversion": { + "uuid": "cee082fd-3a43-4599-bbd3-8216ffa8652d", + "version": "1" + }, + "displayname": "app1", + "fixedresources": { + "memory": 512000, + "maxmem": 512000, + "vcpus": 1, + "virtualizationMode": 1 + }, + "drives": [ + { + "image": { + "uuidandversion": { + "uuid": "398710ca-bf4f-46b0-b012-0d4e32214ba4", + "version": "1" + }, + "name": "lfedge/eden-eclient:8a279cd", + "iformat": 8, + "dsId": "f204830d-cce1-4316-aa5e-3e8567cd09a9" + } + } + ], + "activate": true, + "interfaces": [ + { + "name": "eth0", + "networkId": "9ca83da9-94e8-48b4-9ae8-3f188c5c694a", + "acls": [ + { + "matches": [ + { + "type": "ip", + "value": "0.0.0.0/0" + } + ], + "id": 1 + } + ] + } + ], + "volumeRefList": [ + { + "uuid": "d8fe3e53-cc6c-4cee-8562-b406a1a8ada7", + "mount_dir": "/" + } + ] + }, + { + "uuidandversion": { + "uuid": "45ff198d-b295-4ff2-bf69-76977af809fd", + "version": "1" + }, + "displayname": "app2", + "fixedresources": { + "memory": 512000, + "maxmem": 512000, + "vcpus": 1, + "virtualizationMode": 1 + }, + "drives": [ + { + "image": { + "uuidandversion": { + "uuid": "3eec1356-a469-43e3-80e2-67467d06deaf", + "version": "1" + }, + "name": "lfedge/eden-eclient:8a279cd", + "iformat": 8, + "dsId": "f204830d-cce1-4316-aa5e-3e8567cd09a9" + } + } + ], + "activate": true, + "interfaces": [ + { + "name": "eth0", + "networkId": "0125b9fa-9054-446d-9e9a-1b99175d24a7", + "acls": [ + { + "matches": [ + { + "type": "ip", + "value": "0.0.0.0/0" + } + ], + "id": 1 + } + ] + } + ], + "volumeRefList": [ + { + "uuid": "cee944a3-ae6f-4887-9d8d-adcc0ed02370", + "mount_dir": "/" + } + ] + } + ], + "volumes": [ + { + "uuid": "d8fe3e53-cc6c-4cee-8562-b406a1a8ada7", + "origin": { + "type": 2, + "downloadContentTreeID": "63d3b01f-f44f-4007-ba33-6e720bd52992" + }, + "displayName": "app1-volume" + }, + { + "uuid": "cee944a3-ae6f-4887-9d8d-adcc0ed02370", + "origin": { + "type": 2, + "downloadContentTreeID": "63d3b01f-f44f-4007-ba33-6e720bd52992" + }, + "displayName": "app2-volume" + } + ], + "contentInfo": [ + { + "uuid": "63d3b01f-f44f-4007-ba33-6e720bd52992", + "dsId": "f204830d-cce1-4316-aa5e-3e8567cd09a9", + "URL": "lfedge/eden-eclient:8a279cd", + "iformat": 8, + "displayName": "eden-eclient" + } + ], + "datastores": [ + { + "id": "f204830d-cce1-4316-aa5e-3e8567cd09a9", + "dType": 5, + "fqdn": "docker://index.docker.io" + } + ], + "configItems": [ + { + "key": "newlog.allow.fastupload", + "value": "true" + }, + { + "key": "timer.config.interval", + "value": "10" + }, + { + "key": "timer.download.retry", + "value": "60" + }, + { + "key": "debug.default.loglevel", + "value": "debug" + }, + { + "key": "debug.disable.dhcp.all-ones.netmask", + "value": "false" + } + ] +} diff --git a/sdn/examples/vlan-subinterfaces/network-model.json b/sdn/examples/vlan-subinterfaces/network-model.json new file mode 100644 index 000000000..6ba171c9a --- /dev/null +++ b/sdn/examples/vlan-subinterfaces/network-model.json @@ -0,0 +1,148 @@ +{ + "ports": [ + { + "logicalLabel": "eveport0", + "adminUP": true + } + ], + "bridges": [ + { + "logicalLabel": "bridge0", + "ports": ["eveport0"] + } + ], + "networks": [ + { + "logicalLabel": "network-10", + "bridge": "bridge0", + "vlanID": 10, + "subnet": "172.22.10.0/24", + "gwIP": "172.22.10.1", + "dhcp": { + "enable": true, + "ipRange": { + "fromIP": "172.22.10.10", + "toIP": "172.22.10.20" + }, + "domainName": "sdn", + "privateDNS": ["dns-server"] + }, + "router": { + "outsideReachability": true, + "reachableEndpoints": ["dns-server", "httpserver-10"] + } + }, + { + "logicalLabel": "network-20", + "bridge": "bridge0", + "vlanID": 20, + "subnet": "172.22.20.0/24", + "gwIP": "172.22.20.1", + "dhcp": { + "enable": true, + "ipRange": { + "fromIP": "172.22.20.10", + "toIP": "172.22.20.20" + }, + "domainName": "sdn", + "privateDNS": ["dns-server"] + }, + "router": { + "outsideReachability": false, + "reachableEndpoints": ["dns-server", "httpserver-20"] + } + }, + { + "logicalLabel": "network-untagged", + "bridge": "bridge0", + "subnet": "192.168.77.0/24", + "gwIP": "192.168.77.1", + "dhcp": { + "enable": true, + "ipRange": { + "fromIP": "192.168.77.10", + "toIP": "192.168.77.20" + }, + "domainName": "sdn", + "privateDNS": ["dns-server"] + }, + "router": { + "outsideReachability": false, + "reachableEndpoints": ["dns-server", "httpserver-untagged"] + } + } + ], + "endpoints": { + "dnsServers": [ + { + "logicalLabel": "dns-server", + "fqdn": "dns-server.sdn", + "subnet": "10.16.16.0/24", + "ip": "10.16.16.25", + "staticEntries": [ + { + "fqdn": "mydomain.adam", + "ip": "adam-ip" + }, + { + "fqdn": "endpoint-fqdn.httpserver-10", + "ip": "endpoint-ip.httpserver-10" + }, + { + "fqdn": "endpoint-fqdn.httpserver-20", + "ip": "endpoint-ip.httpserver-20" + }, + { + "fqdn": "endpoint-fqdn.httpserver-untagged", + "ip": "endpoint-ip.httpserver-untagged" + } + ], + "upstreamServers": [ + "1.1.1.1", + "8.8.8.8" + ] + } + ], + "httpServers": [ + { + "logicalLabel": "httpserver-10", + "fqdn": "httpserver-10.sdn", + "subnet": "10.16.10.0/24", + "ip": "10.16.10.70", + "httpPort": 80, + "paths": { + "/helloworld": { + "contentType": "text/plain", + "content": "Hello world from HTTP server for VLAN 10\n" + } + } + }, + { + "logicalLabel": "httpserver-20", + "fqdn": "httpserver-20.sdn", + "subnet": "10.16.20.0/24", + "ip": "10.16.20.70", + "httpPort": 80, + "paths": { + "/helloworld": { + "contentType": "text/plain", + "content": "Hello world from HTTP server for VLAN 20\n" + } + } + }, + { + "logicalLabel": "httpserver-untagged", + "fqdn": "httpserver-untagged.sdn", + "subnet": "10.16.77.0/24", + "ip": "10.16.77.70", + "httpPort": 80, + "paths": { + "/helloworld": { + "contentType": "text/plain", + "content": "Hello world from HTTP server for untagged network\n" + } + } + } + ] + } +} \ No newline at end of file diff --git a/sdn/vm/cmd/sdnagent/parse.go b/sdn/vm/cmd/sdnagent/parse.go index 26da43f81..9479369f1 100644 --- a/sdn/vm/cmd/sdnagent/parse.go +++ b/sdn/vm/cmd/sdnagent/parse.go @@ -210,32 +210,6 @@ func (a *agent) validateNetworks(netModel *parsedNetModel) (err error) { } } - // Do not mix VLAN and non-VLAN network/endpoint with the same bridge - for _, bridge := range netModel.Bridges { - var netWithVlan, netWithoutVlan bool - labeledItem := netModel.items.getItem(api.Bridge{}.ItemType(), bridge.LogicalLabel) - for refKey, refBy := range labeledItem.referencedBy { - var vlanID uint16 - if strings.HasPrefix(refKey, api.NetworkBridgeRefPrefix) { - network := netModel.items[refBy].LabeledItem - vlanID = network.(api.Network).VlanID - } else if strings.HasPrefix(refKey, api.EndpointBridgeRefPrefix) { - endpoint := a.labeledItemToEndpoint(netModel.items[refBy]) - vlanID = endpoint.DirectL2Connect.VlanID - } - if (vlanID == 0 && netWithVlan) || (vlanID != 0 && netWithoutVlan) { - err = fmt.Errorf("bridge %s with both VLAN and non-VLAN networks/endpoints", - bridge.LogicalLabel) - return - } - if vlanID == 0 { - netWithoutVlan = true - } else { - netWithVlan = true - } - } - } - // Validate routes towards EVE. for _, network := range netModel.Networks { if network.Router == nil { diff --git a/sdn/vm/pkg/configitems/veth.go b/sdn/vm/pkg/configitems/veth.go index 18f087d91..bebf010ae 100644 --- a/sdn/vm/pkg/configitems/veth.go +++ b/sdn/vm/pkg/configitems/veth.go @@ -208,6 +208,14 @@ func (c *VethConfigurator) configurePeer(peer VethPeer) error { return fmt.Errorf("failed to add VLAN %d to veth peer %s: %w", peer.MasterBridge.VLAN, peer.IfName, err) } + // Do not let untagged traffic to come through this VETH interface. + err = netlink.BridgeVlanDel(link, 1, false, true, false, false) + // Ignore error if the rule for native VLAN was not automatically added. + if err != nil { + log.Warnf("Failed to remove native VLAN from veth peer %s: %v", + peer.IfName, err) + err = nil + } } } // Set link UP.