This patch series extends the ENRT test recipe set with additional tunnel recipes.
The series also includes updates to VxlanDevice and a bug fix for Device class required by VxlanGpeTunnelRecipe.
v2:
- added a patch that enables use of individual HWConfigMixins - added config mixins to the tunnel recipe classes
Jan Tluka (14): Recipes.ENRT.ConfigMixins: enable usage of individual HWConfigMixins Recipes.ENRT: add GeneveLwtTunnelRecipe Recipes.ENRT: add GreLwtTunnelRecipe Recipes.ENRT: add VxlanLwtTunnelRecipe Recipes.ENRT: add GreOvsTunnelRecipe Recipes.ENRT: add GeneveOvsTunnelRecipe Recipes.ENRT: add VxlanOvsTunnelRecipe Recipes.ENRT: add GreTunnelOverVlanRecipe Recipes.ENRT: add GreTunnelOverMacvlanRecipe Recipes.ENRT: add VxlanNetnsTunnelRecipe Devices.VxlanDevice: add gpe property Devices.VxlanDevice: add learning property Devices.Device: handle RTM_NEWLINK messages without any IFLA_ADDRESS Recipes.ENRT: add VxlanGpeTunnelRecipe
docs/source/geneve_lwt_tunnel_recipe.rst | 6 + docs/source/geneve_ovs_tunnel_recipe.rst | 6 + docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/gre_ovs_tunnel_recipe.rst | 6 + .../source/gre_tunnel_over_macvlan_recipe.rst | 6 + docs/source/gre_tunnel_over_vlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 10 + docs/source/vxlan_gpe_tunnel_recipe.rst | 6 + docs/source/vxlan_lwt_tunnel_recipe.rst | 6 + docs/source/vxlan_netns_tunnel_recipe.rst | 6 + docs/source/vxlan_ovs_tunnel_recipe.rst | 6 + lnst/Devices/Device.py | 5 +- lnst/Devices/VxlanDevice.py | 19 ++ .../ENRT/ConfigMixins/BaseHWConfigMixin.py | 17 +- .../ConfigMixins/CommonHWSubConfigMixin.py | 16 +- lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py | 228 ++++++++++++++++ lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py | 166 ++++++++++++ lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 233 +++++++++++++++++ lnst/Recipes/ENRT/GreOvsTunnelRecipe.py | 174 +++++++++++++ .../ENRT/GreTunnelOverMacvlanRecipe.py | 211 +++++++++++++++ lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py | 212 +++++++++++++++ lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py | 227 ++++++++++++++++ lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py | 227 ++++++++++++++++ lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py | 245 ++++++++++++++++++ lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py | 185 +++++++++++++ lnst/Recipes/ENRT/__init__.py | 10 + 26 files changed, 2228 insertions(+), 17 deletions(-) create mode 100644 docs/source/geneve_lwt_tunnel_recipe.rst create mode 100644 docs/source/geneve_ovs_tunnel_recipe.rst create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 docs/source/gre_ovs_tunnel_recipe.rst create mode 100644 docs/source/gre_tunnel_over_macvlan_recipe.rst create mode 100644 docs/source/gre_tunnel_over_vlan_recipe.rst create mode 100644 docs/source/vxlan_gpe_tunnel_recipe.rst create mode 100644 docs/source/vxlan_lwt_tunnel_recipe.rst create mode 100644 docs/source/vxlan_netns_tunnel_recipe.rst create mode 100644 docs/source/vxlan_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreOvsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py create mode 100644 lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py create mode 100644 lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py
Signed-off-by: Jan Tluka jtluka@redhat.com --- .../ENRT/ConfigMixins/BaseHWConfigMixin.py | 17 ++++++++++++++++- .../ENRT/ConfigMixins/CommonHWSubConfigMixin.py | 16 +--------------- 2 files changed, 17 insertions(+), 16 deletions(-)
diff --git a/lnst/Recipes/ENRT/ConfigMixins/BaseHWConfigMixin.py b/lnst/Recipes/ENRT/ConfigMixins/BaseHWConfigMixin.py index 6fce028d..327f0efa 100644 --- a/lnst/Recipes/ENRT/ConfigMixins/BaseHWConfigMixin.py +++ b/lnst/Recipes/ENRT/ConfigMixins/BaseHWConfigMixin.py @@ -1,4 +1,19 @@ -class BaseHWConfigMixin(object): +from lnst.Recipes.ENRT.ConfigMixins.BaseSubConfigMixin import BaseSubConfigMixin + +class BaseHWConfigMixin(BaseSubConfigMixin): + def apply_sub_configuration(self, config): + super().apply_sub_configuration(config) + self.hw_config(config) + + def remove_sub_configuration(self, config): + self.hw_deconfig(config) + return super().remove_sub_configuration(config) + + def generate_sub_configuration_description(self, config): + desc = super().generate_sub_configuration_description(config) + desc.extend(self.describe_hw_config(config)) + return desc + def hw_config(self, config): config.hw_config = {}
diff --git a/lnst/Recipes/ENRT/ConfigMixins/CommonHWSubConfigMixin.py b/lnst/Recipes/ENRT/ConfigMixins/CommonHWSubConfigMixin.py index d119381f..cea7148a 100644 --- a/lnst/Recipes/ENRT/ConfigMixins/CommonHWSubConfigMixin.py +++ b/lnst/Recipes/ENRT/ConfigMixins/CommonHWSubConfigMixin.py @@ -11,7 +11,6 @@ from lnst.Recipes.ENRT.ConfigMixins.MTUHWConfigMixin import MTUHWConfigMixin from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( PauseFramesHWConfigMixin, ) -from lnst.Recipes.ENRT.ConfigMixins.BaseSubConfigMixin import BaseSubConfigMixin
class CommonHWSubConfigMixin( @@ -20,23 +19,10 @@ class CommonHWSubConfigMixin( DevInterruptHWConfigMixin, CoalescingHWConfigMixin, MTUHWConfigMixin, - BaseSubConfigMixin, ): """ This class groups few related :any:`BaseSubConfigMixin` s for user's convenience. For more details, see the documentation of the individual ancestor classes. """ - - def apply_sub_configuration(self, config): - super().apply_sub_configuration(config) - self.hw_config(config) - - def remove_sub_configuration(self, config): - self.hw_deconfig(config) - return super().remove_sub_configuration(config) - - def generate_sub_configuration_description(self, config): - desc = super().generate_sub_configuration_description(config) - desc.extend(self.describe_hw_config(config)) - return desc + pass
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/geneve_lwt_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py | 228 +++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 236 insertions(+) create mode 100644 docs/source/geneve_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py
diff --git a/docs/source/geneve_lwt_tunnel_recipe.rst b/docs/source/geneve_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..3cee5d5a --- /dev/null +++ b/docs/source/geneve_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GeneveLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GeneveLwtTunnelRecipe.GeneveLwtTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 74573461..4b952d48 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -15,4 +15,5 @@ Specific ENRT scenarios ipip_tunnel_recipe ip6tnl_tunnel_recipe geneve_tunnel_recipe + geneve_lwt_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py b/lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py new file mode 100644 index 00000000..6bd54283 --- /dev/null +++ b/lnst/Recipes/ENRT/GeneveLwtTunnelRecipe.py @@ -0,0 +1,228 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import GeneveDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class GeneveLwtTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Geneve lightweight + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gnv tunnel | | gnv tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4", "ipv6"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Geneve tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + Routes for IPv4 and IPv6 networks to be tunneled through the Geneve are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.gnv_tunnel = GeneveDevice(external=True) + m2.gnv_tunnel = GeneveDevice(external=True) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.gnv_tunnel.mtu = 1400 + m1.gnv_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.gnv_tunnel.mtu = 1400 + m2.gnv_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.gnv_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.gnv_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.gnv_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.gnv_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.gnv_tunnel, m2.gnv_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip or ip6 + protocol and grep patterns to match the ICMP or ICMP6 echo requests + encapsulated by Geneve. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + if self.params.carrier_ipversion == "ipv4": + pa_kwargs["p_filter"] = "ip host {}".format(m1_carrier_ip) + grep_pattern = "IP " + else: + pa_kwargs["p_filter"] = "ip6" + grep_pattern = "IP6 " + + grep_pattern += "{}.[0-9]+ > {}.[0-9]+: Geneve.*vni 0x4d2: ".format( + m1_carrier_ip, m2_carrier_ip + ) + + if isinstance(ip2, Ip4Address): + grep_pattern += "IP {} > {}: ICMP".format(ip1, ip2) + elif isinstance(ip2, Ip6Address): + grep_pattern += "IP6 {} > {}: ICMP6".format(ip1, ip2) + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 0b198e25..eb91b4c8 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -88,6 +88,7 @@ from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe +from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 233 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 241 insertions(+) create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py
diff --git a/docs/source/gre_lwt_tunnel_recipe.rst b/docs/source/gre_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..6a3d63e4 --- /dev/null +++ b/docs/source/gre_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GreLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreLwtTunnelRecipe.GreLwtTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 4b952d48..01459da1 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -8,6 +8,7 @@ Specific ENRT scenarios vlans_recipe vlans_over_bond_recipe gre_tunnel_recipe + gre_lwt_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py new file mode 100644 index 00000000..365c9f74 --- /dev/null +++ b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py @@ -0,0 +1,233 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import GreDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class GreLwtTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Gre lightweight + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre tunnel | | gre tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4", "ipv6"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Gre tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + Routes for IPv4 and IPv6 networks to be tunneled through the Gre are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.gre_tunnel = GreDevice(external=True) + m2.gre_tunnel = GreDevice(external=True) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.gre_tunnel.mtu = 1400 + m1.gre_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.gre_tunnel.mtu = 1400 + m2.gre_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests encapsulated + by Gre. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index eb91b4c8..454ab296 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -82,6 +82,7 @@ from lnst.Recipes.ENRT.VxlanMulticastRecipe import VxlanMulticastRecipe from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe +from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe
On Thu, May 27, 2021 at 02:04:21PM +0200, Jan Tluka wrote:
Signed-off-by: Jan Tluka jtluka@redhat.com
docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 233 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 241 insertions(+) create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py
diff --git a/docs/source/gre_lwt_tunnel_recipe.rst b/docs/source/gre_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..6a3d63e4 --- /dev/null +++ b/docs/source/gre_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GreLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^
+.. autoclass:: lnst.Recipes.ENRT.GreLwtTunnelRecipe.GreLwtTunnelRecipe
- :members:
- :show-inheritance:
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 4b952d48..01459da1 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -8,6 +8,7 @@ Specific ENRT scenarios vlans_recipe vlans_over_bond_recipe gre_tunnel_recipe
- gre_lwt_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe
diff --git a/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py new file mode 100644 index 00000000..365c9f74 --- /dev/null +++ b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py @@ -0,0 +1,233 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import (
- AF_INET,
- AF_INET6,
- ipaddress,
- Ip4Address,
- Ip6Address,
+) +from lnst.Devices import GreDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import (
- OffloadSubConfigMixin,
+) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import (
- PauseFramesHWConfigMixin,
+)
+class GreLwtTunnelRecipe(
- PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe
+):
- """
- This class implements a recipe that configures a simple Gre lightweight
- tunnel between two hosts.
- .. code-block:: none
.--------.
.------| switch |-----.
| '--------' |
| |
.-------|------. .-------|------.
| .--'-. | | .--'-. |
| |eth0| | | |eth0| |
| '----' | | '----' |
| | | | | | | |
| ----' '--- | | ----' '--- |
| gre tunnel | | gre tunnel |
| ---------- | | ---------- |
| | | |
| host1 | | host2 |
'--------------' '--------------'
- The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class.
- The test wide configuration is implemented in the :any:`BaseTunnelRecipe`
- class.
- The recipe provides additional parameter:
:param carrier_ipversion:
This parameter specifies whether IPv4 or IPv6 addresses are
used for the underlying (carrier) network. The value is either
**ipv4** or **ipv6**
- """
- host1 = HostReq()
- host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver"))
- host2 = HostReq()
- host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver"))
- offload_combinations = Param(
default=(
dict(gro="on", gso="on", tso="on"),
dict(gro="off", gso="on", tso="on"),
dict(gro="on", gso="off", tso="off"),
dict(gro="on", gso="on", tso="off"),
)
- )
- # TODO: ping over IPv6 does not work yet
Because of an issue with the configuration or because the device/tunnel doesn't support it?
-Ondrej
- ip_versions = Param(default=("ipv4",))
- carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4", "ipv6"]))
- def configure_underlying_network(self, configuration):
"""
The underlying network for the tunnel consists of the Ethernet
devices on the matched hosts.
"""
host1, host2 = self.matched.host1, self.matched.host2
for i, device in enumerate([host1.eth0, host2.eth0]):
if self.params.carrier_ipversion == "ipv4":
device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24"))
else:
device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64"))
device.up()
configuration.test_wide_devices.append(device)
self.wait_tentative_ips(configuration.test_wide_devices)
configuration.tunnel_endpoints = (host1.eth0, host2.eth0)
- def create_tunnel(self, configuration):
"""
The Gre tunnel devices are created with external flag specified
so that the encapsulation can be defined externally by routes.
Routes for IPv4 and IPv6 networks to be tunneled through the Gre are
added.
IPv4 and IPv6 addresses of the tunneled networks are configured on
the loopback devices of the matched hosts.
"""
endpoint1, endpoint2 = configuration.tunnel_endpoints
m1 = endpoint1.netns
m2 = endpoint2.netns
if self.params.carrier_ipversion == "ipv4":
ip_filter = {"family": AF_INET}
else:
ip_filter = {"family": AF_INET6, "is_link_local": False}
endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0]
endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0]
m1_dummy_ip = ipaddress("172.16.10.1/32")
m1_dummy_ip6 = ipaddress("fc00:a::1/128")
m2_dummy_ip = ipaddress("172.16.20.1/32")
m2_dummy_ip6 = ipaddress("fc00:b::1/128")
m1.gre_tunnel = GreDevice(external=True)
m2.gre_tunnel = GreDevice(external=True)
m1.lo = LoopbackDevice()
m2.lo = LoopbackDevice()
# A
m1.lo.ip_add(m1_dummy_ip)
m1.lo.ip_add(m1_dummy_ip6)
m1.gre_tunnel.mtu = 1400
m1.gre_tunnel.up()
# B
m2.lo.ip_add(m2_dummy_ip)
m2.lo.ip_add(m2_dummy_ip6)
m2.gre_tunnel.mtu = 1400
m2.gre_tunnel.up()
tunnel_id = 1234
encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6"
m1.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name
)
)
m2.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name
)
)
m1.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name
)
)
m2.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name
)
)
configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel])
self.wait_tentative_ips([m1.lo, m2.lo])
- def generate_ping_endpoints(self, config):
"""
The ping endpoints for this recipe are the loopback devices that
are configured with IP addresses of the tunnelled networks.
Returned as::
[PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)]
"""
return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)]
- def get_packet_assert_config(self, ping_config):
"""
The packet assert test configuration contains filter for gre protocol
and grep patterns to match the ICMP or ICMP6 echo requests encapsulated
by Gre.
"""
if self.params.carrier_ipversion == "ipv4":
ip_filter = {"family": AF_INET}
else:
ip_filter = {"family": AF_INET6, "is_link_local": False}
m1_carrier = self.matched.host1.eth0
m2_carrier = self.matched.host2.eth0
m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0]
m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0]
ip1 = ping_config.client_bind
ip2 = ping_config.destination_address
pa_kwargs = {}
pa_kwargs["p_filter"] = "proto gre"
if isinstance(ip2, Ip4Address):
pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
pat2 = "{} > {}: GREv0 \| {} > {}: ICMP echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
grep_pattern = ["({})|({})".format(pat1, pat2)]
elif isinstance(ip2, Ip6Address):
pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
pat2 = "{} > {}: GREv0 \| {} > {}: ICMP6, echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
grep_pattern = ["({})|({})".format(pat1, pat2)]
else:
raise Exception("The destination address is nor IPv4 or IPv6 address")
pa_kwargs["grep_for"] = grep_pattern
if ping_config.count:
pa_kwargs["p_min"] = ping_config.count
m2 = ping_config.destination
pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs)
return pa_config
- @property
- def offload_nics(self):
return [self.matched.host1.eth0, self.matched.host2.eth0]
- @property
- def pause_frames_dev_list(self):
return [self.matched.host1.eth0, self.matched.host2.eth0]
diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index eb91b4c8..454ab296 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -82,6 +82,7 @@ from lnst.Recipes.ENRT.VxlanMulticastRecipe import VxlanMulticastRecipe from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe +from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe -- 2.26.3 _______________________________________________ LNST-developers mailing list -- lnst-developers@lists.fedorahosted.org To unsubscribe send an email to lnst-developers-leave@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/lnst-developers@lists.fedorahos... Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure
Mon, May 31, 2021 at 02:39:25PM CEST, olichtne@redhat.com wrote:
On Thu, May 27, 2021 at 02:04:21PM +0200, Jan Tluka wrote:
Signed-off-by: Jan Tluka jtluka@redhat.com
docs/source/gre_lwt_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreLwtTunnelRecipe.py | 233 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 241 insertions(+) create mode 100644 docs/source/gre_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreLwtTunnelRecipe.py
diff --git a/docs/source/gre_lwt_tunnel_recipe.rst b/docs/source/gre_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..6a3d63e4 --- /dev/null +++ b/docs/source/gre_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GreLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^
+.. autoclass:: lnst.Recipes.ENRT.GreLwtTunnelRecipe.GreLwtTunnelRecipe
- :members:
- :show-inheritance:
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 4b952d48..01459da1 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -8,6 +8,7 @@ Specific ENRT scenarios vlans_recipe vlans_over_bond_recipe gre_tunnel_recipe
- gre_lwt_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe
diff --git a/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py new file mode 100644 index 00000000..365c9f74 --- /dev/null +++ b/lnst/Recipes/ENRT/GreLwtTunnelRecipe.py @@ -0,0 +1,233 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import (
- AF_INET,
- AF_INET6,
- ipaddress,
- Ip4Address,
- Ip6Address,
+) +from lnst.Devices import GreDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import (
- OffloadSubConfigMixin,
+) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import (
- PauseFramesHWConfigMixin,
+)
+class GreLwtTunnelRecipe(
- PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe
+):
- """
- This class implements a recipe that configures a simple Gre lightweight
- tunnel between two hosts.
- .. code-block:: none
.--------.
.------| switch |-----.
| '--------' |
| |
.-------|------. .-------|------.
| .--'-. | | .--'-. |
| |eth0| | | |eth0| |
| '----' | | '----' |
| | | | | | | |
| ----' '--- | | ----' '--- |
| gre tunnel | | gre tunnel |
| ---------- | | ---------- |
| | | |
| host1 | | host2 |
'--------------' '--------------'
- The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class.
- The test wide configuration is implemented in the :any:`BaseTunnelRecipe`
- class.
- The recipe provides additional parameter:
:param carrier_ipversion:
This parameter specifies whether IPv4 or IPv6 addresses are
used for the underlying (carrier) network. The value is either
**ipv4** or **ipv6**
- """
- host1 = HostReq()
- host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver"))
- host2 = HostReq()
- host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver"))
- offload_combinations = Param(
default=(
dict(gro="on", gso="on", tso="on"),
dict(gro="off", gso="on", tso="on"),
dict(gro="on", gso="off", tso="off"),
dict(gro="on", gso="on", tso="off"),
)
- )
- # TODO: ping over IPv6 does not work yet
Because of an issue with the configuration or because the device/tunnel doesn't support it?
-Ondrej
Generally because of the test configuration. But it seems that there's probably a generic issue with how the IPv6 works, but I'm not 100% sure.
-Jan
- ip_versions = Param(default=("ipv4",))
- carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4", "ipv6"]))
- def configure_underlying_network(self, configuration):
"""
The underlying network for the tunnel consists of the Ethernet
devices on the matched hosts.
"""
host1, host2 = self.matched.host1, self.matched.host2
for i, device in enumerate([host1.eth0, host2.eth0]):
if self.params.carrier_ipversion == "ipv4":
device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24"))
else:
device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64"))
device.up()
configuration.test_wide_devices.append(device)
self.wait_tentative_ips(configuration.test_wide_devices)
configuration.tunnel_endpoints = (host1.eth0, host2.eth0)
- def create_tunnel(self, configuration):
"""
The Gre tunnel devices are created with external flag specified
so that the encapsulation can be defined externally by routes.
Routes for IPv4 and IPv6 networks to be tunneled through the Gre are
added.
IPv4 and IPv6 addresses of the tunneled networks are configured on
the loopback devices of the matched hosts.
"""
endpoint1, endpoint2 = configuration.tunnel_endpoints
m1 = endpoint1.netns
m2 = endpoint2.netns
if self.params.carrier_ipversion == "ipv4":
ip_filter = {"family": AF_INET}
else:
ip_filter = {"family": AF_INET6, "is_link_local": False}
endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0]
endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0]
m1_dummy_ip = ipaddress("172.16.10.1/32")
m1_dummy_ip6 = ipaddress("fc00:a::1/128")
m2_dummy_ip = ipaddress("172.16.20.1/32")
m2_dummy_ip6 = ipaddress("fc00:b::1/128")
m1.gre_tunnel = GreDevice(external=True)
m2.gre_tunnel = GreDevice(external=True)
m1.lo = LoopbackDevice()
m2.lo = LoopbackDevice()
# A
m1.lo.ip_add(m1_dummy_ip)
m1.lo.ip_add(m1_dummy_ip6)
m1.gre_tunnel.mtu = 1400
m1.gre_tunnel.up()
# B
m2.lo.ip_add(m2_dummy_ip)
m2.lo.ip_add(m2_dummy_ip6)
m2.gre_tunnel.mtu = 1400
m2.gre_tunnel.up()
tunnel_id = 1234
encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6"
m1.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name
)
)
m2.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name
)
)
m1.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.gre_tunnel.name
)
)
m2.run(
"ip route add {} encap {} id {} dst {} dev {}".format(
m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.gre_tunnel.name
)
)
configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel])
self.wait_tentative_ips([m1.lo, m2.lo])
- def generate_ping_endpoints(self, config):
"""
The ping endpoints for this recipe are the loopback devices that
are configured with IP addresses of the tunnelled networks.
Returned as::
[PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)]
"""
return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)]
- def get_packet_assert_config(self, ping_config):
"""
The packet assert test configuration contains filter for gre protocol
and grep patterns to match the ICMP or ICMP6 echo requests encapsulated
by Gre.
"""
if self.params.carrier_ipversion == "ipv4":
ip_filter = {"family": AF_INET}
else:
ip_filter = {"family": AF_INET6, "is_link_local": False}
m1_carrier = self.matched.host1.eth0
m2_carrier = self.matched.host2.eth0
m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0]
m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0]
ip1 = ping_config.client_bind
ip2 = ping_config.destination_address
pa_kwargs = {}
pa_kwargs["p_filter"] = "proto gre"
if isinstance(ip2, Ip4Address):
pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
pat2 = "{} > {}: GREv0 \| {} > {}: ICMP echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
grep_pattern = ["({})|({})".format(pat1, pat2)]
elif isinstance(ip2, Ip6Address):
pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
pat2 = "{} > {}: GREv0 \| {} > {}: ICMP6, echo request".format(
m1_carrier_ip, m2_carrier_ip, ip1, ip2
)
grep_pattern = ["({})|({})".format(pat1, pat2)]
else:
raise Exception("The destination address is nor IPv4 or IPv6 address")
pa_kwargs["grep_for"] = grep_pattern
if ping_config.count:
pa_kwargs["p_min"] = ping_config.count
m2 = ping_config.destination
pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs)
return pa_config
- @property
- def offload_nics(self):
return [self.matched.host1.eth0, self.matched.host2.eth0]
- @property
- def pause_frames_dev_list(self):
return [self.matched.host1.eth0, self.matched.host2.eth0]
diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index eb91b4c8..454ab296 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -82,6 +82,7 @@ from lnst.Recipes.ENRT.VxlanMulticastRecipe import VxlanMulticastRecipe from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe +from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe -- 2.26.3 _______________________________________________ LNST-developers mailing list -- lnst-developers@lists.fedorahosted.org To unsubscribe send an email to lnst-developers-leave@lists.fedorahosted.org Fedora Code of Conduct: https://docs.fedoraproject.org/en-US/project/code-of-conduct/ List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines List Archives: https://lists.fedorahosted.org/archives/list/lnst-developers@lists.fedorahos... Do not reply to spam on the list, report it: https://pagure.io/fedora-infrastructure
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_lwt_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py | 227 ++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 235 insertions(+) create mode 100644 docs/source/vxlan_lwt_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 01459da1..d372af6d 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -17,4 +17,5 @@ Specific ENRT scenarios ip6tnl_tunnel_recipe geneve_tunnel_recipe geneve_lwt_tunnel_recipe + vxlan_lwt_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_lwt_tunnel_recipe.rst b/docs/source/vxlan_lwt_tunnel_recipe.rst new file mode 100644 index 00000000..f86caf2a --- /dev/null +++ b/docs/source/vxlan_lwt_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanLwtTunnelRecipe +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanLwtTunnelRecipe.VxlanLwtTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py new file mode 100644 index 00000000..8d675d02 --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanLwtTunnelRecipe.py @@ -0,0 +1,227 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import VxlanDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class VxlanLwtTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Vxlan lightweight + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | vxlan tunnel | | vxlan tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + # TODO: IPv6 does not work as carrier network + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Vxlan tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + Routes for IPv4 and IPv6 networks to be tunneled through the Vxlan are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.vxlan_tunnel = VxlanDevice(external=True) + m2.vxlan_tunnel = VxlanDevice(external=True) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.vxlan_tunnel.mtu = 1400 + m1.vxlan_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.vxlan_tunnel.mtu = 1400 + m2.vxlan_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for source + and destination addresses matching the carrier network with udp + header bits specific to VXLAN tunneling. The grep patterns match + the ICMP or ICMP6 echo requests encapsulated by Vxlan. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = ( + "src {} and dst {} " + "and udp[8:2] = 0x0800 & 0x0800 " + "and udp[11:4] = 1234 & 0x00FFFFFF".format(m2_carrier_ip, m1_carrier_ip) + ) + + if isinstance(ip2, Ip4Address): + grep_pattern = "IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 454ab296..f683d928 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -90,6 +90,7 @@ from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe +from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_ovs_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreOvsTunnelRecipe.py | 174 ++++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 182 insertions(+) create mode 100644 docs/source/gre_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreOvsTunnelRecipe.py
diff --git a/docs/source/gre_ovs_tunnel_recipe.rst b/docs/source/gre_ovs_tunnel_recipe.rst new file mode 100644 index 00000000..05f81f17 --- /dev/null +++ b/docs/source/gre_ovs_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GreOvsTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreOvsTunnelRecipe.GreOvsTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index d372af6d..13bbf6ac 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -9,6 +9,7 @@ Specific ENRT scenarios vlans_over_bond_recipe gre_tunnel_recipe gre_lwt_tunnel_recipe + gre_ovs_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreOvsTunnelRecipe.py b/lnst/Recipes/ENRT/GreOvsTunnelRecipe.py new file mode 100644 index 00000000..46597602 --- /dev/null +++ b/lnst/Recipes/ENRT/GreOvsTunnelRecipe.py @@ -0,0 +1,174 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Devices import OvsBridgeDevice +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class GreOvsTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple GRE tunnel using + OpenVSwitch between two hosts. + + .. code-block:: none + + .--------. + .----------| switch |-------. + | '--------' | + | | + .-------|----------. .-------|----------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | .----| |-------. | | .----| |-------. | + | | | | OvS | | | | | | OvS | | + | | | | | | | | | | | | + | | ---' '--- | | | | ---' '--- | | + | | gre tunnel | | | | gre tunnel | | + | | ---------- | | | | ---------- | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host2 | + '------------------' '------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + OvS bridges are created on each of the matched hosts with two ports. + One port as an integration port and another port of type GRE acting + as a tunnel interface connecting tunneled networks. + + Integration ports are configured with IPv4 and IPv6 addresses + of the tunneled networks. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + + for i, (host, endpoint) in enumerate([(m1, endpoint2), (m2, endpoint1)]): + remote_ip = endpoint.ips_filter(**ip_filter)[0] + host.br0 = OvsBridgeDevice() + host.int0 = host.br0.port_add(interface_options={"type": "internal"}) + configuration.tunnel_devices.append(host.int0) + host.int0.ip_add(ipaddress("192.168.200." + str(i + 1) + "/24")) + host.int0.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + + host.br0.tunnel_add("gre", {"options:remote_ip": remote_ip}) + + host.br0.up() + host.int0.up() + + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + """ + return [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index f683d928..b99872fe 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -83,6 +83,7 @@ from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe +from lnst.Recipes.ENRT.GreOvsTunnelRecipe import GreOvsTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/geneve_ovs_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py | 166 +++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 174 insertions(+) create mode 100644 docs/source/geneve_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py
diff --git a/docs/source/geneve_ovs_tunnel_recipe.rst b/docs/source/geneve_ovs_tunnel_recipe.rst new file mode 100644 index 00000000..448534cc --- /dev/null +++ b/docs/source/geneve_ovs_tunnel_recipe.rst @@ -0,0 +1,6 @@ +GeneveOvsTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GeneveOvsTunnelRecipe.GeneveOvsTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 13bbf6ac..bdb12396 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -18,5 +18,6 @@ Specific ENRT scenarios ip6tnl_tunnel_recipe geneve_tunnel_recipe geneve_lwt_tunnel_recipe + geneve_ovs_tunnel_recipe vxlan_lwt_tunnel_recipe l2tp_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py b/lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py new file mode 100644 index 00000000..f0626da0 --- /dev/null +++ b/lnst/Recipes/ENRT/GeneveOvsTunnelRecipe.py @@ -0,0 +1,166 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Devices import OvsBridgeDevice +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class GeneveOvsTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Geneve tunnel using + OpenVSwitch between two hosts. + + .. code-block:: none + + .--------. + .----------| switch |-------. + | '--------' | + | | + .-------|----------. .-------|----------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | .----| |-------. | | .----| |-------. | + | | | | OvS | | | | | | OvS | | + | | | | | | | | | | | | + | | ---' '--- | | | | ---' '--- | | + | | gnv tunnel | | | | gnv tunnel | | + | | ---------- | | | | ---------- | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host2 | + '------------------' '------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + OvS bridges are created on each of the matched hosts with two ports. + One port as an integration port and another port of type Geneve acting + as a tunnel interface connecting tunneled networks. + + Integration ports are configured with IPv4 and IPv6 addresses + of the tunneled networks. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + + for i, (host, endpoint) in enumerate([(m1, endpoint2), (m2, endpoint1)]): + remote_ip = endpoint.ips_filter(**ip_filter)[0] + host.br0 = OvsBridgeDevice() + host.int0 = host.br0.port_add(interface_options={"type": "internal"}) + configuration.tunnel_devices.append(host.int0) + host.int0.ip_add(ipaddress("192.168.200." + str(i + 1) + "/24")) + host.int0.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + + host.br0.tunnel_add( + "geneve", {"options:remote_ip": remote_ip, "options:key": 1234} + ) + + host.br0.up() + host.int0.up() + + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + """ + return [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "ip" + + grep_pattern = "IP {}.[0-9]+ > {}.[0-9]+: Geneve.*vni 0x4d2: ".format( + m1_carrier_ip, m2_carrier_ip + ) + + if isinstance(ip2, Ip4Address): + grep_pattern += "IP {} > {}: ICMP".format(ip1, ip2) + elif isinstance(ip2, Ip6Address): + grep_pattern += "IP6 {} > {}: ICMP6".format(ip1, ip2) + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index b99872fe..67bf3de7 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -91,6 +91,7 @@ from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe +from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_ovs_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py | 185 ++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 193 insertions(+) create mode 100644 docs/source/vxlan_ovs_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index bdb12396..00fd0d4f 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -20,4 +20,5 @@ Specific ENRT scenarios geneve_lwt_tunnel_recipe geneve_ovs_tunnel_recipe vxlan_lwt_tunnel_recipe + vxlan_ovs_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_ovs_tunnel_recipe.rst b/docs/source/vxlan_ovs_tunnel_recipe.rst new file mode 100644 index 00000000..e92beb92 --- /dev/null +++ b/docs/source/vxlan_ovs_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanOvsTunnelRecipe +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanOvsTunnelRecipe.VxlanOvsTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py new file mode 100644 index 00000000..e144ab5b --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanOvsTunnelRecipe.py @@ -0,0 +1,185 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Devices import OvsBridgeDevice +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class VxlanOvsTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Vxlan tunnel using + OpenVSwitch between two hosts. + + .. code-block:: none + + .--------. + .----------| switch |-------. + | '--------' | + | | + .-------|----------. .-------|----------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | .----| |-------. | | .----| |-------. | + | | | | OvS | | | | | | OvS | | + | | | | | | | | | | | | + | | ---' '------ | | | | ---' '------ | | + | | vxlan tunnel | | | | vxlan tunnel | | + | | ------------ | | | | ------------ | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host2 | + '------------------' '------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + OvS bridges are created on each of the matched hosts with two ports. + One port as an integration port and another port of type VXLAN acting + as a tunnel interface connecting tunneled networks. + + Integration ports are configured with IPv4 and IPv6 addresses + of the tunneled networks. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + + for i, (host, endpoint) in enumerate([(m1, endpoint2), (m2, endpoint1)]): + remote_ip = endpoint.ips_filter(**ip_filter)[0] + host.br0 = OvsBridgeDevice() + host.int0 = host.br0.port_add( + interface_options={"type": "internal", "ofport_request": 5} + ) + configuration.tunnel_devices.append(host.int0) + host.int0.ip_add(ipaddress("192.168.200." + str(i + 1) + "/24")) + host.int0.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + + host.br0.tunnel_add( + "vxlan", + { + "options:remote_ip": remote_ip, + "options:key": "flow", + "ofport_request": 10, + }, + ) + + host.br0.flows_add( + [ + "table=0,in_port=5,actions=set_field:1234->tun_id,output:10", + "table=0,in_port=10,tun_id=1234,actions=output:5", + "table=0,priority=100,actions=drop", + ] + ) + + host.br0.up() + host.int0.up() + + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + """ + return [PingEndpoints(self.matched.host1.int0, self.matched.host2.int0)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for source + and destination addresses matching the carrier network with udp + header bits specific to VXLAN tunneling. The grep patterns match + the ICMP or ICMP6 echo requests encapsulated by Vxlan. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = ( + "src {} and dst {} " + "and udp[8:2] = 0x0800 & 0x0800 " + "and udp[11:4] = 1234 & 0x00FFFFFF".format(m2_carrier_ip, m1_carrier_ip) + ) + + if isinstance(ip2, Ip4Address): + grep_pattern = "IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 67bf3de7..69db2b7b 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -93,6 +93,7 @@ from lnst.Recipes.ENRT.GeneveTunnelRecipe import GeneveTunnelRecipe from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe +from lnst.Recipes.ENRT.VxlanOvsTunnelRecipe import VxlanOvsTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/gre_tunnel_over_vlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py | 212 +++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 220 insertions(+) create mode 100644 docs/source/gre_tunnel_over_vlan_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py
diff --git a/docs/source/gre_tunnel_over_vlan_recipe.rst b/docs/source/gre_tunnel_over_vlan_recipe.rst new file mode 100644 index 00000000..cf4b1527 --- /dev/null +++ b/docs/source/gre_tunnel_over_vlan_recipe.rst @@ -0,0 +1,6 @@ +GreTunnelOverVlanRecipe +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreTunnelOverVlanRecipe.GreTunnelOverVlanRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 00fd0d4f..44b7da91 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -11,6 +11,7 @@ Specific ENRT scenarios gre_lwt_tunnel_recipe gre_ovs_tunnel_recipe gre_tunnel_over_bond_recipe + gre_tunnel_over_vlan_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe sit_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py b/lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py new file mode 100644 index 00000000..1b561c2a --- /dev/null +++ b/lnst/Recipes/ENRT/GreTunnelOverVlanRecipe.py @@ -0,0 +1,212 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import GreDevice, VlanDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.MTUHWConfigMixin import ( + MTUHWConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class GreTunnelOverVlanRecipe( + MTUHWConfigMixin, PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a GRE tunnel between + two hosts that are connected through a vlan device. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | + | vlan0(id=10) | | vlan0(id=10) | + | | | | | | | | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre tunnel | | gre tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of two Ethernet + devices on the matched hosts. A VLAN is configured on top of each + device. + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.vlan10 = VlanDevice(realdev=host1.eth0, vlan_id=10) + host2.vlan10 = VlanDevice(realdev=host2.eth0, vlan_id=10) + + for i, device in enumerate([host1.vlan10, host2.vlan10]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + configuration.test_wide_devices.append(device) + + for dev in [ + host1.eth0, + host1.vlan10, + host2.eth0, + host2.vlan10, + ]: + dev.up() + + configuration.tunnel_endpoints = (host1.vlan10, host2.vlan10) + + def create_tunnel(self, configuration): + """ + The GRE tunnel devices are configured with local and remote ip addresses + matching the VLAN device IP addresses. + + The GRE tunnel devices are configured with IPv4 and IPv6 addresses + of individual networks. Routes are configured accordingly. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + a_ip4 = Ip4Address("192.168.6.2/24") + a_net4 = "192.168.6.0/24" + b_ip4 = Ip4Address("192.168.7.2/24") + b_net4 = "192.168.7.0/24" + + a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") + a_net6 = "6001:db8:ac10:fe01::0/64" + b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") + b_net6 = "7001:db8:ac10:fe01::0/64" + + m1.gre_tunnel = GreDevice(local=endpoint1_ip, remote=endpoint2_ip) + m2.gre_tunnel = GreDevice(local=endpoint2_ip, remote=endpoint1_ip) + + # A + m1.gre_tunnel.up() + m1.gre_tunnel.ip_add(a_ip4) + m1.gre_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre_tunnel.name)) + + # B + m2.gre_tunnel.up() + m2.gre_tunnel.ip_add(b_ip4) + m2.gre_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre_tunnel.name)) + + configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel)] + """ + return [ + PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.vlan10 + m2_carrier = self.matched.host2.vlan10 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def mtu_hw_config_dev_list(self): + return [self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 69db2b7b..9b3fd537 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -82,6 +82,7 @@ from lnst.Recipes.ENRT.VxlanMulticastRecipe import VxlanMulticastRecipe from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe +from lnst.Recipes.ENRT.GreTunnelOverVlanRecipe import GreTunnelOverVlanRecipe from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.GreOvsTunnelRecipe import GreOvsTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- .../source/gre_tunnel_over_macvlan_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + .../ENRT/GreTunnelOverMacvlanRecipe.py | 211 ++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 219 insertions(+) create mode 100644 docs/source/gre_tunnel_over_macvlan_recipe.rst create mode 100644 lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py
diff --git a/docs/source/gre_tunnel_over_macvlan_recipe.rst b/docs/source/gre_tunnel_over_macvlan_recipe.rst new file mode 100644 index 00000000..de09c451 --- /dev/null +++ b/docs/source/gre_tunnel_over_macvlan_recipe.rst @@ -0,0 +1,6 @@ +GreTunnelOverMacvlanRecipe +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.GreTunnelOverMacvlanRecipe.GreTunnelOverMacvlanRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 44b7da91..879404c2 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -12,6 +12,7 @@ Specific ENRT scenarios gre_ovs_tunnel_recipe gre_tunnel_over_bond_recipe gre_tunnel_over_vlan_recipe + gre_tunnel_over_macvlan_recipe ip6gre_tunnel_recipe ip6gre_netns_tunnel_recipe sit_tunnel_recipe diff --git a/lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py b/lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py new file mode 100644 index 00000000..8348053b --- /dev/null +++ b/lnst/Recipes/ENRT/GreTunnelOverMacvlanRecipe.py @@ -0,0 +1,211 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import GreDevice, MacvlanDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.MTUHWConfigMixin import ( + MTUHWConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class GreTunnelOverMacvlanRecipe( + MTUHWConfigMixin, PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a GRE tunnel between + two hosts that are connected through a macvlan device. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | + | macvlan0 | | macvlan0 | + | | | | | | | | + | | | | | | | | + | ----' '--- | | ----' '--- | + | gre tunnel | | gre tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host1.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + host2.eth1 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of two MACVLAN + devices configured on top of the Ethernet devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.macvlan10 = MacvlanDevice(realdev=host1.eth0, hwaddr="0A:00:00:00:00:01") + host2.macvlan10 = MacvlanDevice(realdev=host2.eth0, hwaddr="0A:00:00:00:00:02") + + for i, device in enumerate([host1.macvlan10, host2.macvlan10]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + configuration.test_wide_devices.append(device) + + for dev in [ + host1.eth0, + host1.macvlan10, + host2.eth0, + host2.macvlan10, + ]: + dev.up() + + configuration.tunnel_endpoints = (host1.macvlan10, host2.macvlan10) + + def create_tunnel(self, configuration): + """ + The GRE tunnel devices are configured with local and remote ip addresses + matching the MACVLAN device IP addresses. + + The GRE tunnel devices are configured with IPv4 and IPv6 addresses + of individual networks. Routes are configured accordingly. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + ip_filter = {"family": AF_INET} + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + a_ip4 = Ip4Address("192.168.6.2/24") + a_net4 = "192.168.6.0/24" + b_ip4 = Ip4Address("192.168.7.2/24") + b_net4 = "192.168.7.0/24" + + a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") + a_net6 = "6001:db8:ac10:fe01::0/64" + b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") + b_net6 = "7001:db8:ac10:fe01::0/64" + + m1.gre_tunnel = GreDevice(local=endpoint1_ip, remote=endpoint2_ip) + m2.gre_tunnel = GreDevice(local=endpoint2_ip, remote=endpoint1_ip) + + # A + m1.gre_tunnel.up() + m1.gre_tunnel.ip_add(a_ip4) + m1.gre_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre_tunnel.name)) + + # B + m2.gre_tunnel.up() + m2.gre_tunnel.ip_add(b_ip4) + m2.gre_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre_tunnel.name)) + + configuration.tunnel_devices.extend([m1.gre_tunnel, m2.gre_tunnel]) + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel)] + """ + return [ + PingEndpoints(self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for gre protocol + and grep patterns to match the ICMP or ICMP6 echo requests. + """ + ip_filter = {"family": AF_INET} + m1_carrier = self.matched.host1.macvlan10 + m2_carrier = self.matched.host2.macvlan10 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "proto gre" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}: GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = "{} > {}: GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}: GREv0 | {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = grep_pattern + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def mtu_hw_config_dev_list(self): + return [self.matched.host1.gre_tunnel, self.matched.host2.gre_tunnel] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 9b3fd537..d18da30c 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -83,6 +83,7 @@ from lnst.Recipes.ENRT.VxlanRemoteRecipe import VxlanRemoteRecipe from lnst.Recipes.ENRT.GreTunnelRecipe import GreTunnelRecipe from lnst.Recipes.ENRT.GreTunnelOverBondRecipe import GreTunnelOverBondRecipe from lnst.Recipes.ENRT.GreTunnelOverVlanRecipe import GreTunnelOverVlanRecipe +from lnst.Recipes.ENRT.GreTunnelOverMacvlanRecipe import GreTunnelOverMacvlanRecipe from lnst.Recipes.ENRT.GreLwtTunnelRecipe import GreLwtTunnelRecipe from lnst.Recipes.ENRT.GreOvsTunnelRecipe import GreOvsTunnelRecipe from lnst.Recipes.ENRT.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_netns_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py | 245 ++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 253 insertions(+) create mode 100644 docs/source/vxlan_netns_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 879404c2..8687ed45 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -23,4 +23,5 @@ Specific ENRT scenarios geneve_ovs_tunnel_recipe vxlan_lwt_tunnel_recipe vxlan_ovs_tunnel_recipe + vxlan_netns_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_netns_tunnel_recipe.rst b/docs/source/vxlan_netns_tunnel_recipe.rst new file mode 100644 index 00000000..f7ee9be5 --- /dev/null +++ b/docs/source/vxlan_netns_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanNetnsTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanNetnsTunnelRecipe.VxlanNetnsTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py new file mode 100644 index 00000000..a3f87036 --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanNetnsTunnelRecipe.py @@ -0,0 +1,245 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Controller.NetNamespace import NetNamespace +from lnst.Common.IpAddress import ( + AF_INET, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import VxlanDevice, VethPair, BridgeDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class VxlanNetnsTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a VXLAN tunnel between + two network namespaces on two hosts. + + .. code-block:: none + + .--------. + .------------| switch |--------. + | '--------' | + | | + .-------|------------. .-------|------------. + | .--'---. | | .--'---. | + | | eth0 | | | | eth0 | | + | '------' | | '------' | + | \ | | \ | + | .--------. | | .--------. | + | | bridge | | | | bridge | | + | '--------' | | '--------' | + | / | | / | + | .-------. | | .-------. | + | | veth0 | | | | veth0 | | + | '--.----' | | '--.----' | + | .-------------. | | .-------------. | + | | \ netns | | | | \ netns | | + | | .------. | | | | .------. | | + | | |veth0_1| | | | | |veth0_1| | | + | | '-------' | | | | '-------' | | + | | | | | | | | | | | | + | | -----' '---- | | | | -----' '---- | | + | | vxlan tunnel | | | | vxlan tunnel | | + | | ------------ | | | | ------------ | | + | '--------------' | | '--------------' | + | | | | + | host1 | | host1 | + '--------------------' '--------------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of + + - an Ethernet device on each of the matched hosts + - a pair of veth devices on each host with one end of the pair + connected to Ethernet device through bridge and the second one + moved to a network namespace + + The veth devices in the namespaces are assigned an IPv4 address and + will be used as tunnel endpoints + """ + host1, host2 = self.matched.host1, self.matched.host2 + + host1.newns = NetNamespace("host1-net1") + host2.newns = NetNamespace("host2-net1") + + # veth_pair + host1.veth0, host1.veth1 = VethPair() + host2.veth0, host2.veth1 = VethPair() + + # one veth end to netns + host1.newns.veth1 = host1.veth1 + host2.newns.veth1 = host2.veth1 + + # second veth bridged with NIC + host1.bridge = BridgeDevice() + host1.bridge.slave_add(host1.veth0) + host1.bridge.slave_add(host1.eth0) + host2.bridge = BridgeDevice() + host2.bridge.slave_add(host2.veth0) + host2.bridge.slave_add(host2.eth0) + + for device in [ + host1.veth0, + host2.veth0, + host1.bridge, + host2.bridge, + host1.eth0, + host2.eth0, + ]: + device.up() + configuration.test_wide_devices.append(device) + + for i, device in enumerate([host1.newns.veth1, host2.newns.veth1]): + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + device.up() + configuration.test_wide_devices.append(device) + + for i, device in enumerate([host1.veth0, host2.veth0]): + device.up() + + configuration.tunnel_endpoints = (host1.newns.veth1, host2.newns.veth1) + + def create_tunnel(self, configuration): + """ + The VXLAN tunnel devices are created in the network namespaces and + configured with local and remote ip addresses matching the veth + devices IP addresses. + + The VXLAN tunnel devices are configured with IPv4 and IPv6 addresses + of individual networks. Routes are configured accordingly. + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + + a_ip4 = Ip4Address("192.168.6.2/24") + a_net4 = "192.168.6.0/24" + b_ip4 = Ip4Address("192.168.7.2/24") + b_net4 = "192.168.7.0/24" + + a_ip6 = Ip6Address("6001:db8:ac10:fe01::2/64") + a_net6 = "6001:db8:ac10:fe01::0/64" + b_ip6 = Ip6Address("7001:db8:ac10:fe01::2/64") + b_net6 = "7001:db8:ac10:fe01::0/64" + + vxlan_group_ip = "239.1.1.1" + + m1.vxlan_tunnel = VxlanDevice( + vxlan_id=1234, realdev=endpoint1, group=vxlan_group_ip + ) + m2.vxlan_tunnel = VxlanDevice( + vxlan_id=1234, realdev=endpoint2, group=vxlan_group_ip + ) + + # A + m1.vxlan_tunnel.up() + m1.vxlan_tunnel.ip_add(a_ip4) + m1.vxlan_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.vxlan_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.vxlan_tunnel.name)) + + # B + m2.vxlan_tunnel.up() + m2.vxlan_tunnel.ip_add(b_ip4) + m2.vxlan_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.vxlan_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.vxlan_tunnel.name)) + + configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel]) + self.wait_tentative_ips(configuration.tunnel_devices) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are simply the tunnel endpoints + + Returned as:: + + [PingEndpoints(self.matched.host1.newns.vxlan_tunnel, self.matched.host2.newns.vxlan_tunnel)] + """ + return [ + PingEndpoints( + self.matched.host1.newns.vxlan_tunnel, + self.matched.host2.newns.vxlan_tunnel, + ) + ] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for ip6 protocol + and grep patterns to match the ICMP or ICMP6 echo requests encapsulated + by VXLAN. + """ + ip_filter = {"family": AF_INET} + + m1_carrier = self.matched.host1.newns.veth1 + m2_carrier = self.matched.host2.newns.veth1 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = ( + "src {} and dst {} " + "and udp[8:2] = 0x0800 & 0x0800 " + "and udp[11:4] = 1234 & 0x00FFFFFF".format(m2_carrier_ip, m1_carrier_ip) + ) + + if isinstance(ip2, Ip4Address): + grep_pattern = "IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index d18da30c..900ed27c 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -96,6 +96,7 @@ from lnst.Recipes.ENRT.GeneveLwtTunnelRecipe import GeneveLwtTunnelRecipe from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.VxlanOvsTunnelRecipe import VxlanOvsTunnelRecipe +from lnst.Recipes.ENRT.VxlanNetnsTunnelRecipe import VxlanNetnsTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/VxlanDevice.py | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/lnst/Devices/VxlanDevice.py b/lnst/Devices/VxlanDevice.py index 1426d3e2..d3c7c55c 100644 --- a/lnst/Devices/VxlanDevice.py +++ b/lnst/Devices/VxlanDevice.py @@ -117,3 +117,13 @@ class VxlanDevice(SoftDevice): if val: self._set_linkinfo_data_attr("IFLA_VXLAN_COLLECT_METADATA", True) self._nl_link_sync("set") + + @property + def gpe(self): + return self._get_linkinfo_data_attr("IFLA_VXLAN_GPE") is not None + + @gpe.setter + def gpe(self, val): + if val: + self._set_linkinfo_data_attr("IFLA_VXLAN_GPE", True) + self._nl_link_sync("set")
On Thu, May 27, 2021 at 02:04:29PM +0200, Jan Tluka wrote:
Signed-off-by: Jan Tluka jtluka@redhat.com
lnst/Devices/VxlanDevice.py | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/lnst/Devices/VxlanDevice.py b/lnst/Devices/VxlanDevice.py index 1426d3e2..d3c7c55c 100644 --- a/lnst/Devices/VxlanDevice.py +++ b/lnst/Devices/VxlanDevice.py @@ -117,3 +117,13 @@ class VxlanDevice(SoftDevice): if val: self._set_linkinfo_data_attr("IFLA_VXLAN_COLLECT_METADATA", True) self._nl_link_sync("set")
- @property
- def gpe(self):
return self._get_linkinfo_data_attr("IFLA_VXLAN_GPE") is not None
- @gpe.setter
- def gpe(self, val):
if val:
self._set_linkinfo_data_attr("IFLA_VXLAN_GPE", True)
self._nl_link_sync("set")
what happens if you do this?
gpe = False
an empty nl message is sent to the nl link sync "set" method? And does this work as intended?
-Ondrej
Mon, May 31, 2021 at 04:23:01PM CEST, olichtne@redhat.com wrote:
On Thu, May 27, 2021 at 02:04:29PM +0200, Jan Tluka wrote:
Signed-off-by: Jan Tluka jtluka@redhat.com
lnst/Devices/VxlanDevice.py | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/lnst/Devices/VxlanDevice.py b/lnst/Devices/VxlanDevice.py index 1426d3e2..d3c7c55c 100644 --- a/lnst/Devices/VxlanDevice.py +++ b/lnst/Devices/VxlanDevice.py @@ -117,3 +117,13 @@ class VxlanDevice(SoftDevice): if val: self._set_linkinfo_data_attr("IFLA_VXLAN_COLLECT_METADATA", True) self._nl_link_sync("set")
- @property
- def gpe(self):
return self._get_linkinfo_data_attr("IFLA_VXLAN_GPE") is not None
- @gpe.setter
- def gpe(self, val):
if val:
self._set_linkinfo_data_attr("IFLA_VXLAN_GPE", True)
self._nl_link_sync("set")
what happens if you do this?
gpe = False
an empty nl message is sent to the nl link sync "set" method? And does this work as intended?
-Ondrej
Hmm, good question. Let me check. I'm not quite sure if it's possible to reset the flag independently.
And probably, the data attribute should be removed completely if it is set to False.
Let me check this.
J.
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/VxlanDevice.py | 9 +++++++++ 1 file changed, 9 insertions(+)
diff --git a/lnst/Devices/VxlanDevice.py b/lnst/Devices/VxlanDevice.py index d3c7c55c..380bdeb9 100644 --- a/lnst/Devices/VxlanDevice.py +++ b/lnst/Devices/VxlanDevice.py @@ -127,3 +127,12 @@ class VxlanDevice(SoftDevice): if val: self._set_linkinfo_data_attr("IFLA_VXLAN_GPE", True) self._nl_link_sync("set") + + @property + def learning(self): + return self._get_linkinfo_data_attr("IFLA_VXLAN_LEARNING") + + @learning.setter + def learning(self, val): + self._set_linkinfo_data_attr("IFLA_VXLAN_LEARNING", val) + self._nl_link_sync("set")
There are cases when IFLA_ADDRESS is not included in the netlink message, for example when vxlan device is created with gpe flag:
ip link add vxlan0 type vxlan external gpe
When the IFLA_ADDRESS is not present in the message the None value is passed to hwaddress() parser function that raises an exception.
A check is added to hwaddr property whether the IFLA_ADDRESS attribute is present. If it's not the parser function is bypassed and None is returned.
Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Devices/Device.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/lnst/Devices/Device.py b/lnst/Devices/Device.py index fd5ef86b..adebd117 100644 --- a/lnst/Devices/Device.py +++ b/lnst/Devices/Device.py @@ -416,7 +416,10 @@ class Device(object, metaclass=DeviceMeta): Returns a HWAddress object representing the hardware address of the device as reported by the kernel. """ - return hwaddress(self._nl_msg.get_attr("IFLA_ADDRESS")) + if self._nl_msg.get_attr("IFLA_ADDRESS"): + return hwaddress(self._nl_msg.get_attr("IFLA_ADDRESS")) + else: + return None
@hwaddr.setter def hwaddr(self, addr):
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/specific_scenarios.rst | 1 + docs/source/vxlan_gpe_tunnel_recipe.rst | 6 + lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py | 227 ++++++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 235 insertions(+) create mode 100644 docs/source/vxlan_gpe_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py
diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 8687ed45..8fbe9dd4 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -24,4 +24,5 @@ Specific ENRT scenarios vxlan_lwt_tunnel_recipe vxlan_ovs_tunnel_recipe vxlan_netns_tunnel_recipe + vxlan_gpe_tunnel_recipe l2tp_tunnel_recipe diff --git a/docs/source/vxlan_gpe_tunnel_recipe.rst b/docs/source/vxlan_gpe_tunnel_recipe.rst new file mode 100644 index 00000000..0a70b6ba --- /dev/null +++ b/docs/source/vxlan_gpe_tunnel_recipe.rst @@ -0,0 +1,6 @@ +VxlanGpeTunnelRecipe +^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.VxlanGpeTunnelRecipe.VxlanGpeTunnelRecipe + :members: + :show-inheritance: diff --git a/lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py b/lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py new file mode 100644 index 00000000..9f13f177 --- /dev/null +++ b/lnst/Recipes/ENRT/VxlanGpeTunnelRecipe.py @@ -0,0 +1,227 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Common.IpAddress import ( + AF_INET, + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Devices import VxlanDevice, LoopbackDevice +from lnst.RecipeCommon.Ping.PingEndpoints import PingEndpoints +from lnst.RecipeCommon.PacketAssert import PacketAssertConf +from lnst.Common.Parameters import Param, StrParam, ChoiceParam +from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe +from lnst.Recipes.ENRT.ConfigMixins.OffloadSubConfigMixin import ( + OffloadSubConfigMixin, +) +from lnst.Recipes.ENRT.ConfigMixins.PauseFramesHWConfigMixin import ( + PauseFramesHWConfigMixin, +) + + +class VxlanGpeTunnelRecipe( + PauseFramesHWConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a simple Vxlan GPE + tunnel between two hosts. + + .. code-block:: none + + .--------. + .------| switch |-----. + | '--------' | + | | + .-------|------. .-------|------. + | .--'-. | | .--'-. | + | |eth0| | | |eth0| | + | '----' | | '----' | + | | | | | | | | + | ----' '--- | | ----' '--- | + | vxlan tunnel | | vxlan tunnel | + | ---------- | | ---------- | + | | | | + | host1 | | host2 | + '--------------' '--------------' + + The actual test machinery is implemented in the :any:`BaseEnrtRecipe` class. + + The test wide configuration is implemented in the :any:`BaseTunnelRecipe` + class. + + The recipe provides additional parameter: + + :param carrier_ipversion: + This parameter specifies whether IPv4 or IPv6 addresses are + used for the underlying (carrier) network. The value is either + **ipv4** or **ipv6** + """ + + host1 = HostReq() + host1.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + host2 = HostReq() + host2.eth0 = DeviceReq(label="net1", driver=RecipeParam("driver")) + + offload_combinations = Param( + default=( + dict(gro="on", gso="on", tso="on"), + dict(gro="off", gso="on", tso="on"), + dict(gro="on", gso="off", tso="off"), + dict(gro="on", gso="on", tso="off"), + ) + ) + + # TODO: ping over IPv6 does not work yet + ip_versions = Param(default=("ipv4",)) + # TODO: IPv6 does not work as carrier network + carrier_ipversion = ChoiceParam(type=StrParam, choices=set(["ipv4"])) + + def configure_underlying_network(self, configuration): + """ + The underlying network for the tunnel consists of the Ethernet + devices on the matched hosts. + """ + host1, host2 = self.matched.host1, self.matched.host2 + for i, device in enumerate([host1.eth0, host2.eth0]): + if self.params.carrier_ipversion == "ipv4": + device.ip_add(ipaddress("192.168.101." + str(i + 1) + "/24")) + else: + device.ip_add(ipaddress("fc00::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.eth0, host2.eth0) + + def create_tunnel(self, configuration): + """ + The Vxlan tunnel devices are created with external flag specified + so that the encapsulation can be defined externally by routes. + + The devices are configured to use VXLAN-GPE. + + Routes for IPv4 and IPv6 networks to be tunneled through the Vxlan are + added. + + IPv4 and IPv6 addresses of the tunneled networks are configured on + the loopback devices of the matched hosts. + + """ + endpoint1, endpoint2 = configuration.tunnel_endpoints + m1 = endpoint1.netns + m2 = endpoint2.netns + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + endpoint1_ip = endpoint1.ips_filter(**ip_filter)[0] + endpoint2_ip = endpoint2.ips_filter(**ip_filter)[0] + + m1_dummy_ip = ipaddress("172.16.10.1/32") + m1_dummy_ip6 = ipaddress("fc00:a::1/128") + m2_dummy_ip = ipaddress("172.16.20.1/32") + m2_dummy_ip6 = ipaddress("fc00:b::1/128") + + m1.vxlan_tunnel = VxlanDevice(external=True, gpe=True, learning=0) + m2.vxlan_tunnel = VxlanDevice(external=True, gpe=True, learning=0) + m1.lo = LoopbackDevice() + m2.lo = LoopbackDevice() + + # A + m1.lo.ip_add(m1_dummy_ip) + m1.lo.ip_add(m1_dummy_ip6) + m1.vxlan_tunnel.mtu = 1400 + m1.vxlan_tunnel.up() + + # B + m2.lo.ip_add(m2_dummy_ip) + m2.lo.ip_add(m2_dummy_ip6) + m2.vxlan_tunnel.mtu = 1400 + m2.vxlan_tunnel.up() + + tunnel_id = 1234 + encap = "ip" if self.params.carrier_ipversion == "ipv4" else "ip6" + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + m1.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m2_dummy_ip6, encap, tunnel_id, endpoint2_ip, m1.vxlan_tunnel.name + ) + ) + m2.run( + "ip route add {} encap {} id {} dst {} dev {}".format( + m1_dummy_ip6, encap, tunnel_id, endpoint1_ip, m2.vxlan_tunnel.name + ) + ) + + configuration.tunnel_devices.extend([m1.vxlan_tunnel, m2.vxlan_tunnel]) + self.wait_tentative_ips([m1.lo, m2.lo]) + + def generate_ping_endpoints(self, config): + """ + The ping endpoints for this recipe are the loopback devices that + are configured with IP addresses of the tunnelled networks. + + Returned as:: + + [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + """ + return [PingEndpoints(self.matched.host1.lo, self.matched.host2.lo)] + + def get_packet_assert_config(self, ping_config): + """ + The packet assert test configuration contains filter for source + and destination addresses matching the carrier network with udp + header bits specific to VXLAN tunneling. The grep patterns match + the ICMP or ICMP6 echo requests encapsulated by Vxlan with + VXLAN-GPE extension. + """ + if self.params.carrier_ipversion == "ipv4": + ip_filter = {"family": AF_INET} + else: + ip_filter = {"family": AF_INET6, "is_link_local": False} + + m1_carrier = self.matched.host1.eth0 + m2_carrier = self.matched.host2.eth0 + m1_carrier_ip = m1_carrier.ips_filter(**ip_filter)[0] + m2_carrier_ip = m2_carrier.ips_filter(**ip_filter)[0] + + ip1 = ping_config.client_bind + ip2 = ping_config.destination_address + + pa_kwargs = {} + pa_kwargs["p_filter"] = "src {} and dst {}".format(m2_carrier_ip, m1_carrier_ip) + + if isinstance(ip2, Ip4Address): + grep_pattern = "VXLAN-GPE.*IP {} > {}: ICMP echo reply".format(ip2, ip1) + elif isinstance(ip2, Ip6Address): + grep_pattern = "VXLAN-GPE.*IP6 {} > {}: ICMP6, echo reply".format(ip2, ip1) + else: + raise Exception("The destination address is nor IPv4 or IPv6 address") + + pa_kwargs["grep_for"] = [grep_pattern] + + if ping_config.count: + pa_kwargs["p_min"] = ping_config.count + m2 = ping_config.destination + pa_config = PacketAssertConf(m2, m2_carrier, **pa_kwargs) + + return pa_config + + @property + def offload_nics(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] + + @property + def pause_frames_dev_list(self): + return [self.matched.host1.eth0, self.matched.host2.eth0] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 900ed27c..34eb4877 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -97,6 +97,7 @@ from lnst.Recipes.ENRT.GeneveOvsTunnelRecipe import GeneveOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanLwtTunnelRecipe import VxlanLwtTunnelRecipe from lnst.Recipes.ENRT.VxlanOvsTunnelRecipe import VxlanOvsTunnelRecipe from lnst.Recipes.ENRT.VxlanNetnsTunnelRecipe import VxlanNetnsTunnelRecipe +from lnst.Recipes.ENRT.VxlanGpeTunnelRecipe import VxlanGpeTunnelRecipe from lnst.Recipes.ENRT.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe
lnst-developers@lists.fedorahosted.org