Add support for simple VXLAN tunnels

This is just a first step to add VXLAN tunnels, much more remains.
Implemented right now is as GRE, local, remote and a VNI.

Also refactor gre_basic and gre_bridged to generic tunnel tests,
the same test now tests VXLAN as well.
This commit is contained in:
Mattias Walström
2025-01-14 14:31:21 +01:00
parent 907401f6f2
commit 4782cee201
31 changed files with 320 additions and 94 deletions
+2 -1
View File
@@ -17,8 +17,9 @@ All notable changes to the project are documented in this file.
RPC and add boot order to operational datastore.
- SSH Server is now configurable, issue #441
SSH Server and NETCONF Server now uses the same SSH hostkey in factory-config
- Add support for GRE/GRETAP tunnels
- Support for GRE/GRETAP tunnels
- Support for STP/RSTP on bridges
- Support for VXLAN tunnels
### Fixes
+12
View File
@@ -14,3 +14,15 @@ admin@example:/config/interface/gre1/> set gre local 192.168.3.1 remote 192.168.
admin@example:/config/interface/gre1/> leave
admin@example:/>
```
## Virtual eXtensible Local Area Network (VXLAN)
The support for VXLAN tunnels includes IPv4 and IPv6.
```
admin@example:/config/> edit interface vxlan100
admin@example:/config/interface/vxlan100/> set vxlan local 192.168.3.1
admin@example:/config/interface/vxlan100/> set vxlan remote 192.168.3.2
admin@example:/config/interface/vxlan100/> set vxlan vni 100
admin@example:/config/interface/vxlan100/> leave
```
+2 -1
View File
@@ -34,7 +34,8 @@ confd_plugin_la_SOURCES = \
infix-if-bridge-port.c \
infix-if-veth.c \
infix-if-vlan.c \
infix-if-gre.c \
infix-if-gre.c \
infix-if-vxlan.c \
ietf-keystore.c \
ietf-system.c \
ietf-syslog.c \
+9 -2
View File
@@ -91,7 +91,8 @@ static int ifchange_cand_infer_type(sr_session_ctx_t *session, const char *path)
inferred.data.string_val = "infix-if-type:gre";
else if (!fnmatch("gretap+([0-9])", ifname, FNM_EXTMATCH))
inferred.data.string_val = "infix-if-type:gretap";
else if (!fnmatch("vxlan+([0-9])", ifname, FNM_EXTMATCH))
inferred.data.string_val = "infix-if-type:vxlan";
free(ifname);
if (inferred.data.string_val)
@@ -401,6 +402,8 @@ static int netdag_gen_afspec_add(sr_session_ctx_t *session, struct dagger *net,
return -ENOENT;
} else if (!strcmp(iftype, "infix-if-type:gre") || !strcmp(iftype, "infix-if-type:gretap")) {
err = gre_gen(net, NULL, cif, ip);
} else if (!strcmp(iftype, "infix-if-type:vxlan")) {
err = vxlan_gen(net, NULL, cif, ip);
} else {
sr_session_set_error_message(net->session, "%s: unsupported interface type \"%s\"", ifname, iftype);
return -ENOSYS;
@@ -428,7 +431,8 @@ static int netdag_gen_afspec_set(sr_session_ctx_t *session, struct dagger *net,
return 0;
if (!strcmp(iftype, "infix-if-type:gretap"))
return 0;
if (!strcmp(iftype, "infix-if-type:vxlan"))
return 0;
ERROR("%s: unsupported interface type \"%s\"", ifname, iftype);
return -ENOSYS;
}
@@ -462,6 +466,9 @@ static bool netdag_must_del(struct lyd_node *dif, struct lyd_node *cif)
} else if (!strcmp(iftype, "infix-if-type:gre") || !strcmp(iftype, "infix-if-type:gretap")) {
if (lydx_get_descendant(lyd_child(dif), "gre", NULL))
return true;
} else if (!strcmp(iftype, "infix-if-type:vxlan")) {
if (lydx_get_descendant(lyd_child(dif), "vxlan", NULL))
return true;
/*
} else if (!strcmp(iftype, "infix-if-type:lag")) {
if (is_phys_addr_deleted(dif))
+5 -1
View File
@@ -76,6 +76,10 @@ int netdag_gen_vlan(struct dagger *net, struct lyd_node *dif,
/* infix-if-gre.c */
int gre_gen(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip);
struct lyd_node *cif, FILE *ip);
/* infix-if-vxlan.c */
int vxlan_gen(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip);
#endif /* CONFD_IETF_INTERFACES_H_ */
+1 -1
View File
@@ -4,7 +4,7 @@
#include "ietf-interfaces.h"
int gre_gen(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip)
struct lyd_node *cif, FILE *ip)
{
const char *ifname, *iftype, *local, *remote, *mac = NULL;
struct lyd_node *node = NULL;
+29
View File
@@ -0,0 +1,29 @@
/* SPDX-License-Identifier: BSD-3-Clause */
#include <srx/lyx.h>
#include "ietf-interfaces.h"
int vxlan_gen(struct dagger *net, struct lyd_node *dif,
struct lyd_node *cif, FILE *ip)
{
const char *ifname, *local, *remote, *mac = NULL;
const char *vni, *remote_port;
struct lyd_node *node = NULL;
ifname = lydx_get_cattr(cif, "name");
node = lydx_get_descendant(lyd_child(cif), "vxlan", NULL);
if (!node)
return -EINVAL;
local = lydx_get_cattr(node, "local");
remote = lydx_get_cattr(node, "remote");
vni = lydx_get_cattr(node, "vni");
remote_port = lydx_get_cattr(node, "remote-port");
fprintf(ip, "link add name %s type vxlan id %s local %s remote %s dstport %s", ifname, vni, local, remote, remote_port);
if (mac)
fprintf(ip, "address %s\n", mac);
else
fprintf(ip, "\n");
return 0;
}
+2 -1
View File
@@ -714,7 +714,8 @@ submodule infix-if-bridge {
"derived-from-or-self(if:type,'ianaift:ieee8023adLag') or "+
"derived-from-or-self(if:type,'ianaift:l2vlan') or "+
"derived-from-or-self(if:type,'ianaift:ilan') or "+
"derived-from-or-self(if:type,'infix-ift:gretap')" {
"derived-from-or-self(if:type,'infix-ift:gretap') or "+
"derived-from-or-self(if:type,'infix-ift:vxlan')" {
description "Applies when a Bridge interface exists.";
}
+3
View File
@@ -70,6 +70,9 @@ module infix-if-type {
identity gretap {
base infix-interface-type;
}
identity vxlan {
base infix-interface-type;
}
identity lag {
base infix-interface-type;
base ianaift:ieee8023adLag;
+54
View File
@@ -0,0 +1,54 @@
submodule infix-if-vxlan {
yang-version 1.1;
belongs-to infix-interfaces {
prefix infix-if;
}
import ietf-interfaces {
prefix if;
}
import ietf-inet-types {
prefix inet-types;
}
import infix-if-type {
prefix infixift;
}
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "VXLAN tunnel extension for ietf-interfaces";
revision 2025-01-13 {
description "Initial revision.";
reference "internal";
}
typedef vni {
type uint32 {
range "0..16777215";
}
}
augment "/if:interfaces/if:interface" {
when "derived-from-or-self(if:type, 'infixift:vxlan')" {
description "Only shown for if:type infixift:vxlan";
}
description "Augments the interface model with VXLAN tunnels.";
container vxlan {
uses local-remote;
leaf remote-port {
type inet-types:port-number;
default 4789;
description
"VXLAN destination UDP port. Valid range: 0..65535. Default is 4789 (IANA-assigned VXLAN UDP port).";
}
leaf vni {
type vni;
mandatory true;
description
"VXLAN Network Identifier (VNI), valid values are 0 to 16777215.";
}
}
}
}
+1
View File
@@ -0,0 +1 @@
infix-if-vxlan.yang
+2
View File
@@ -22,6 +22,7 @@ module infix-interfaces {
include infix-if-veth;
include infix-if-vlan;
include infix-if-gre;
include infix-if-vxlan;
organization "KernelKit";
contact "kernelkit@googlegroups.com";
@@ -106,6 +107,7 @@ module infix-interfaces {
mandatory true;
}
}
/*
* Data Nodes
*/
+2 -2
View File
@@ -49,6 +49,6 @@ include::veth_delete/Readme.adoc[]
include::vlan_iface_termination/Readme.adoc[]
include::gre_basic/Readme.adoc[]
include::tunnel_basic/Readme.adoc[]
include::gretap_bridged/Readme.adoc[]
include::tunnel_bridged/Readme.adoc[]
@@ -1 +0,0 @@
gretap_bridged.adoc
@@ -1,24 +0,0 @@
=== GRETAP interface bridged with physical
==== Description
Test that GRETAP works as it should and that it possible to bridge it.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/gretap_bridged/topology.svg[GRETAP interface bridged with physical topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::gretap_bridged/topology.svg[GRETAP interface bridged with physical topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[GRETAP interface bridged with physical topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Configure DUTs
. Test connectivity host:data to right:gre0 at 192.168.10.2
<<<
@@ -68,8 +68,8 @@
- name: vlan_iface_termination
case: vlan_iface_termination/test.py
- name: gre_basic
suite: gre_basic/test.yaml
- name: tunnel_basic
suite: tunnel_basic/test.yaml
- name: gretap_bridged
case: gretap_bridged/test.py
- name: tunnel_bridged
suite: tunnel_bridged/test.yaml
@@ -2,3 +2,5 @@ include::gre_basic.adoc[]
include::gretap_basic.adoc[]
include::vxlan_basic.adoc[]
@@ -1,15 +1,15 @@
=== GRE point-to-point
==== Description
Test setting up IP GRE tunnels using IPv4 and IPv6,
Test setting up gre tunnels using IPv4 and IPv6,
and ends with a connectivity test.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/gre_basic/topology.svg[GRE point-to-point topology]
image::../../test/case/ietf_interfaces/tunnel_basic/topology.svg[GRE point-to-point topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::gre_basic/topology.svg[GRE point-to-point topology]
image::tunnel_basic/topology.svg[GRE point-to-point topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[GRE point-to-point topology]
@@ -18,6 +18,7 @@ endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Configure DUTs
. Verify connectivity host:data to 10.0.0.2
. Verify connectivity host:data to 2001:db8::c0a8:0a02
@@ -1,15 +1,15 @@
=== GRETAP point-to-point
==== Description
Test setting up IP GRE tunnels using IPv4 and IPv6,
Test setting up gretap tunnels using IPv4 and IPv6,
and ends with a connectivity test.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/gre_basic/topology.svg[GRETAP point-to-point topology]
image::../../test/case/ietf_interfaces/tunnel_basic/topology.svg[GRETAP point-to-point topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::gre_basic/topology.svg[GRETAP point-to-point topology]
image::tunnel_basic/topology.svg[GRETAP point-to-point topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[GRETAP point-to-point topology]
@@ -18,6 +18,7 @@ endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Configure DUTs
. Verify connectivity host:data to 10.0.0.2
. Verify connectivity host:data to 2001:db8::c0a8:0a02
@@ -1,8 +1,8 @@
#!/usr/bin/env python3
"""
Basic GRE connectivity test
Basic tunnel connectivity test
Test setting up IP GRE tunnels using IPv4 and IPv6,
Test setting up {type} tunnels using IPv4 and IPv6,
and ends with a connectivity test.
"""
@@ -21,8 +21,41 @@ with infamy.Test() as test:
left = env.attach("left", "mgmt")
right = env.attach("right", "mgmt")
with test.step("Configure DUTs"):
container_left4 = {
"local": "192.168.50.1",
"remote": "192.168.50.2"
}
container_left6 = {
"local": "2001:db8:3c4d:50::1",
"remote": "2001:db8:3c4d:50::2",
}
container_right4 = {
"local": "192.168.50.2",
"remote": "192.168.50.1"
}
container_right6 = {
"local": "2001:db8:3c4d:50::2",
"remote": "2001:db8:3c4d:50::1",
}
if type == "gretap" or type == "gre":
container_name = "gre"
if type == "vxlan":
container_name = "vxlan"
container_left4.update({
"vni": 4
})
container_left6.update({
"vni": 6
})
container_right4.update({
"vni": 4
})
container_right6.update({
"vni": 6
})
left.put_config_dicts({ "ietf-interfaces": {
"interfaces": {
"interface": [
@@ -61,7 +94,7 @@ with infamy.Test() as test:
}
},
{
"name": "gre0",
"name": f"{type}4",
"type": f"infix-if-type:{type}",
"ipv4": {
"address": [{
@@ -70,14 +103,10 @@ with infamy.Test() as test:
}],
"forwarding": True
},
"gre": {
"local": "192.168.50.1",
"remote": "192.168.50.2"
}
container_name: container_left4
},
{
"name": "gre6",
"name": f"{type}6",
"type": f"infix-if-type:{type}",
"ipv6": {
"address": [{
@@ -85,10 +114,7 @@ with infamy.Test() as test:
"prefix-length": 64
}]
},
"gre": {
"local": "2001:db8:3c4d:50::1",
"remote": "2001:db8:3c4d:50::2",
}
container_name: container_left6
}]
}
}
@@ -115,7 +141,7 @@ with infamy.Test() as test:
}
},
{
"name": "gre1",
"name": f"{type}4",
"type": f"infix-if-type:{type}",
"ipv4": {
"address": [{
@@ -124,13 +150,10 @@ with infamy.Test() as test:
}],
"forwarding": True
},
"gre": {
"local": "192.168.50.2",
"remote": "192.168.50.1"
}
container_name: container_right4
},
{
"name": "gre6",
"name": f"{type}",
"type": f"infix-if-type:{type}",
"ipv6": {
"address": [{
@@ -138,10 +161,7 @@ with infamy.Test() as test:
"prefix-length": 64
}]
},
"gre": {
"local": "2001:db8:3c4d:50::2",
"remote": "2001:db8:3c4d:50::1",
}
container_name: container_right6
}]
}
},
@@ -175,7 +195,7 @@ with infamy.Test() as test:
}
})
_, hport = env.ltop.xlate("host", "data")
with test.step(f"Verify connectivity host:data to 10.0.0.2"):
with test.step("Verify connectivity host:data to 10.0.0.2"):
with infamy.IsolatedMacVlan(hport) as ns0:
ns0.addip("192.168.10.2")
ns0.addroute("192.168.30.0/24", "192.168.10.1")
@@ -10,3 +10,9 @@
opts: ["--type", "gretap"]
infamy:
title: GRETAP point-to-point
- name: vxlan_basic
case: test.py
opts: ["--type", "vxlan"]
infamy:
title: VXLAN point-to-point

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

@@ -0,0 +1,26 @@
=== VXLAN point-to-point
==== Description
Test setting up vxlan tunnels using IPv4 and IPv6,
and ends with a connectivity test.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/tunnel_basic/topology.svg[VXLAN point-to-point topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::tunnel_basic/topology.svg[VXLAN point-to-point topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[VXLAN point-to-point topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Configure DUTs
. Verify connectivity host:data to 10.0.0.2
. Verify connectivity host:data to 2001:db8::c0a8:0a02
<<<
@@ -0,0 +1,4 @@
include::gretap_bridged.adoc[]
include::vxlan_bridged.adoc[]
@@ -0,0 +1,24 @@
=== GRETAP bridged with physical interface
==== Description
Undefined
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/tunnel_bridged/topology.svg[GRETAP bridged with physical interface topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::tunnel_bridged/topology.svg[GRETAP bridged with physical interface topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[GRETAP bridged with physical interface topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Configure DUTs
. Test connectivity host:data to right:gretap0 at 192.168.10.2
<<<
@@ -1,29 +1,51 @@
#!/usr/bin/env python3
"""
GRETAP interface bridged with physical
Tunnel interface bridged with physical
Test that GRETAP works as it should and that it possible to bridge it.
Test that {type} works as it should and that it possible to bridge it.
"""
import infamy
class ArgumentParser(infamy.ArgumentParser):
def __init__(self):
super().__init__()
self.add_argument("--type")
with infamy.Test() as test:
with test.step("Set up topology and attach to target DUTs"):
env = infamy.Env()
left = env.attach("left", "mgmt")
right = env.attach("right", "mgmt")
_, leftlink = env.ltop.xlate("left", "link")
_, leftdata = env.ltop.xlate("left", "data")
_, rightlink = env.ltop.xlate("right", "link")
args=ArgumentParser()
env = infamy.Env(args=args)
left = env.attach("left", "mgmt")
right = env.attach("right", "mgmt")
type = env.args.type
with test.step("Configure DUTs"):
if type == "gretap":
container_name = "gre"
else:
container_name = type
container_left = {
"local": "192.168.50.1",
"remote": "192.168.50.2"
}
container_right = {
"local": "192.168.50.2",
"remote": "192.168.50.1"
}
container_left.update({
"vni": 4
})
container_right.update({
"vni": 4
})
left.put_config_dicts({ "ietf-interfaces": {
"interfaces": {
"interface": [
{
"name": leftlink,
"name": left["link"],
"ipv4": {
"address": [{
"ip": "192.168.50.1",
@@ -33,7 +55,7 @@ with infamy.Test() as test:
}
},
{
"name": leftdata,
"name": left["data"],
"bridge-port": {
"bridge": "br0"
}
@@ -43,12 +65,9 @@ with infamy.Test() as test:
"type": "infix-if-type:bridge"
},
{
"name": "gre0",
"type": "infix-if-type:gretap",
"gre": {
"local": "192.168.50.1",
"remote": "192.168.50.2"
},
"name": f"{type}0",
"type": f"infix-if-type:{type}",
container_name: container_left,
"bridge-port": {
"bridge": "br0"
}
@@ -64,7 +83,7 @@ with infamy.Test() as test:
"interfaces": {
"interface": [
{
"name": rightlink,
"name": right["link"],
"ipv4": {
"address": [{
"ip": "192.168.50.2",
@@ -80,8 +99,8 @@ with infamy.Test() as test:
}
},
{
"name": "gre0",
"type": "infix-if-type:gretap",
"name": f"{type}0",
"type": f"infix-if-type:{type}",
"ipv4": {
"address": [{
"ip": "192.168.10.2",
@@ -89,16 +108,13 @@ with infamy.Test() as test:
}],
"forwarding": True
},
"gre": {
"local": "192.168.50.2",
"remote": "192.168.50.1"
}
container_name: container_right,
}]
}
}
})
_, hport = env.ltop.xlate("host", "data")
with test.step("Test connectivity host:data to right:gre0 at 192.168.10.2"):
with test.step(f"Test connectivity host:data to right:{type}0 at 192.168.10.2"):
with infamy.IsolatedMacVlan(hport) as ns0:
ns0.addip("192.168.10.1")
ns0.must_reach("192.168.10.2")
@@ -0,0 +1,12 @@
---
- name: gretap_bridged
case: test.py
opts: ["--type", "gretap"]
infamy:
title: GRETAP bridged with physical interface
- name: vxlan_bridged
case: test.py
opts: ["--type", "vxlan"]
infamy:
title: VXLAN bridged with physical interface

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -0,0 +1,24 @@
=== VXLAN bridged with physical interface
==== Description
Undefined
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_interfaces/tunnel_bridged/topology.svg[VXLAN bridged with physical interface topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::tunnel_bridged/topology.svg[VXLAN bridged with physical interface topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[VXLAN bridged with physical interface topology]
endif::testgroup[]
endif::topdoc[]
==== Test sequence
. Set up topology and attach to target DUTs
. Configure DUTs
. Test connectivity host:data to right:vxlan0 at 192.168.10.2
<<<