Signed-off-by: Jan Tluka jtluka@redhat.com --- lnst/Recipes/ENRT/__init__.py | 1 + 1 file changed, 1 insertion(+)
diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index 0b7f4f86..e0c802d9 100644 --- a/lnst/Recipes/ENRT/__init__.py +++ b/lnst/Recipes/ENRT/__init__.py @@ -87,6 +87,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.L2TPTunnelRecipe import L2TPTunnelRecipe
from lnst.Recipes.ENRT.BaseEnrtRecipe import BaseEnrtRecipe from lnst.Recipes.ENRT.BaseTunnelRecipe import BaseTunnelRecipe
Signed-off-by: Jan Tluka jtluka@redhat.com --- docs/source/ip6gre_netns_tunnel_recipe.rst | 6 + docs/source/specific_scenarios.rst | 1 + lnst/Recipes/ENRT/Ip6GreNetnsTunnelRecipe.py | 248 +++++++++++++++++++ lnst/Recipes/ENRT/__init__.py | 1 + 4 files changed, 256 insertions(+) create mode 100644 docs/source/ip6gre_netns_tunnel_recipe.rst create mode 100644 lnst/Recipes/ENRT/Ip6GreNetnsTunnelRecipe.py
diff --git a/docs/source/ip6gre_netns_tunnel_recipe.rst b/docs/source/ip6gre_netns_tunnel_recipe.rst new file mode 100644 index 00000000..f4b4c7d4 --- /dev/null +++ b/docs/source/ip6gre_netns_tunnel_recipe.rst @@ -0,0 +1,6 @@ +Ip6GreNetnsTunnelRecipe +^^^^^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe.Ip6GreNetnsTunnelRecipe + :members: + :show-inheritance: diff --git a/docs/source/specific_scenarios.rst b/docs/source/specific_scenarios.rst index 011db055..74573461 100644 --- a/docs/source/specific_scenarios.rst +++ b/docs/source/specific_scenarios.rst @@ -10,6 +10,7 @@ Specific ENRT scenarios gre_tunnel_recipe gre_tunnel_over_bond_recipe ip6gre_tunnel_recipe + ip6gre_netns_tunnel_recipe sit_tunnel_recipe ipip_tunnel_recipe ip6tnl_tunnel_recipe diff --git a/lnst/Recipes/ENRT/Ip6GreNetnsTunnelRecipe.py b/lnst/Recipes/ENRT/Ip6GreNetnsTunnelRecipe.py new file mode 100644 index 00000000..05e847a3 --- /dev/null +++ b/lnst/Recipes/ENRT/Ip6GreNetnsTunnelRecipe.py @@ -0,0 +1,248 @@ +from lnst.Controller import HostReq, DeviceReq, RecipeParam +from lnst.Controller.NetNamespace import NetNamespace +from lnst.Common.IpAddress import ( + AF_INET6, + ipaddress, + Ip4Address, + Ip6Address, +) +from lnst.Common.Parameters import Param +from lnst.Devices import Ip6GreDevice, 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.CommonHWSubConfigMixin import ( + CommonHWSubConfigMixin, +) + + +class Ip6GreNetnsTunnelRecipe( + CommonHWSubConfigMixin, OffloadSubConfigMixin, BaseTunnelRecipe +): + """ + This class implements a recipe that configures a IP6GRE tunnel between + two network namespaces on two hosts. + + .. code-block:: none + + .--------. + .------------| switch |--------. + | '--------' | + | | + .-------|------------. .-------|------------. + | .--'---. | | .--'---. | + | | eth0 | | | | eth0 | | + | '------' | | '------' | + | \ | | \ | + | .--------. | | .--------. | + | | bridge | | | | bridge | | + | '--------' | | '--------' | + | / | | / | + | .-------. | | .-------. | + | | veth0 | | | | veth0 | | + | '--.----' | | '--.----' | + | .-------------. | | .-------------. | + | | \ netns | | | | \ netns | | + | | .------. | | | | .------. | | + | | |veth0_1| | | | | |veth0_1| | | + | | '-------' | | | | '-------' | | + | | | | | | | | | | | | + | | ----' '---- | | | | ----' '---- | | + | | gre6 tunnel | | | | gre6 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 IPv6 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("fc00:0:0:0::" + str(i + 1) + "/64")) + device.up() + configuration.test_wide_devices.append(device) + + for i, device in enumerate([host1.veth0, host2.veth0]): + device.up() + + self.wait_tentative_ips(configuration.test_wide_devices) + configuration.tunnel_endpoints = (host1.newns.veth1, host2.newns.veth1) + + def create_tunnel(self, configuration): + """ + The GRE tunnel devices are created in the network namespaces and + configured with local and remote ip addresses matching the veth + devices IP addresses. + + The IP6GRE 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_INET6, "is_link_local": False} + 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.gre6_tunnel = Ip6GreDevice(local=endpoint1_ip, remote=endpoint2_ip) + m2.gre6_tunnel = Ip6GreDevice(local=endpoint2_ip, remote=endpoint1_ip) + + # A + m1.gre6_tunnel.up() + m1.gre6_tunnel.ip_add(a_ip4) + m1.gre6_tunnel.ip_add(a_ip6) + m1.run("ip -4 route add {} dev {}".format(b_net4, m1.gre6_tunnel.name)) + m1.run("ip -6 route add {} dev {}".format(b_net6, m1.gre6_tunnel.name)) + + # B + m2.gre6_tunnel.up() + m2.gre6_tunnel.ip_add(b_ip4) + m2.gre6_tunnel.ip_add(b_ip6) + m2.run("ip -4 route add {} dev {}".format(a_net4, m2.gre6_tunnel.name)) + m2.run("ip -6 route add {} dev {}".format(a_net6, m2.gre6_tunnel.name)) + + configuration.tunnel_devices.extend([m1.gre6_tunnel, m2.gre6_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.gre6_tunnel, self.matched.host2.newns.gre6_tunnel)] + """ + return [ + PingEndpoints( + self.matched.host1.newns.gre6_tunnel, + self.matched.host2.newns.gre6_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 GRE. + """ + ip_filter = {"family": AF_INET6, "is_link_local": False} + 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"] = "ip6" + + if isinstance(ip2, Ip4Address): + pat1 = "{} > {}:( DSTOPT)? GREv0, .* IP {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + pat2 = "{} > {}:( DSTOPT)? GREv0 | {} > {}: ICMP echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + grep_pattern = ["({})|({})".format(pat1, pat2)] + elif isinstance(ip2, Ip6Address): + pat1 = ( + "{} > {}:( DSTOPT)? GREv0, .* IP6 {} > {}: ICMP6, echo request".format( + m1_carrier_ip, m2_carrier_ip, ip1, ip2 + ) + ) + pat2 = "{} > {}:( DSTOPT)? 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] diff --git a/lnst/Recipes/ENRT/__init__.py b/lnst/Recipes/ENRT/__init__.py index e0c802d9..0b198e25 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.Ip6GreTunnelRecipe import Ip6GreTunnelRecipe +from lnst.Recipes.ENRT.Ip6GreNetnsTunnelRecipe import Ip6GreNetnsTunnelRecipe from lnst.Recipes.ENRT.SitTunnelRecipe import SitTunnelRecipe from lnst.Recipes.ENRT.IpIpTunnelRecipe import IpIpTunnelRecipe from lnst.Recipes.ENRT.Ip6TnlTunnelRecipe import Ip6TnlTunnelRecipe
lnst-developers@lists.fedorahosted.org