561 lines
21 KiB
Python
561 lines
21 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import copy
|
|
import netaddr
|
|
import time
|
|
|
|
from neutron.agent.common import utils
|
|
import os_ken.lib.packet
|
|
from oslo_log import log
|
|
import testtools
|
|
|
|
from dragonflow import conf as cfg
|
|
from dragonflow.db.models import l2
|
|
from dragonflow.db.models import l3
|
|
from dragonflow.tests.common import app_testing_objects
|
|
from dragonflow.tests.common import constants as const
|
|
from dragonflow.tests.fullstack import apps
|
|
from dragonflow.tests.fullstack import test_base
|
|
from dragonflow.tests.fullstack import test_objects as objects
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class TestL3App(test_base.DFTestBase):
|
|
def setUp(self):
|
|
super(TestL3App, self).setUp()
|
|
self.topology = None
|
|
self.policy = None
|
|
self._ping = None
|
|
self.topology = app_testing_objects.Topology(self.neutron,
|
|
self.nb_api)
|
|
self.addCleanup(self.topology.close)
|
|
self.subnet1 = self.topology.create_subnet(cidr='192.168.12.0/24')
|
|
self.subnet2 = self.topology.create_subnet(cidr='192.168.13.0/24')
|
|
self.port1 = self.subnet1.create_port()
|
|
self.port2 = self.subnet2.create_port()
|
|
self.router = self.topology.create_router([
|
|
self.subnet1.subnet_id,
|
|
self.subnet2.subnet_id,
|
|
])
|
|
time.sleep(const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
|
|
|
def _create_port_policies(self, connected=True):
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
raise_action = app_testing_objects.RaiseAction("Unexpected packet")
|
|
key1 = (self.subnet1.subnet_id, self.port1.port_id)
|
|
if connected:
|
|
actions = [app_testing_objects.DisableRuleAction(),
|
|
app_testing_objects.StopSimulationAction()]
|
|
else:
|
|
actions = [raise_action]
|
|
|
|
rules1 = [
|
|
app_testing_objects.PortPolicyRule(
|
|
# Detect pong, end simulation
|
|
app_testing_objects.OsKenICMPPongFilter(self._get_ping),
|
|
actions=actions
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore gratuitous ARP packets
|
|
app_testing_objects.OsKenARPGratuitousFilter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore IPv6 packets
|
|
app_testing_objects.OsKenIPv6Filter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
]
|
|
key2 = (self.subnet2.subnet_id, self.port2.port_id)
|
|
if connected:
|
|
actions = [app_testing_objects.SendAction(self.subnet2.subnet_id,
|
|
self.port2.port_id,
|
|
self._create_pong_packet
|
|
),
|
|
app_testing_objects.DisableRuleAction()]
|
|
else:
|
|
actions = [raise_action]
|
|
|
|
rules2 = [
|
|
app_testing_objects.PortPolicyRule(
|
|
# Detect ping, reply with pong
|
|
app_testing_objects.OsKenICMPPingFilter(),
|
|
actions=actions
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore gratuitous ARP packets
|
|
app_testing_objects.OsKenARPGratuitousFilter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore ARP requests from active port detection app for
|
|
# ports with allowed_address_pairs
|
|
app_testing_objects.OsKenARPRequestFilter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore IPv6 packets
|
|
app_testing_objects.OsKenIPv6Filter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
]
|
|
policy1 = app_testing_objects.PortPolicy(
|
|
rules=rules1,
|
|
default_action=raise_action
|
|
)
|
|
policy2 = app_testing_objects.PortPolicy(
|
|
rules=rules2,
|
|
default_action=raise_action
|
|
)
|
|
return {
|
|
key1: policy1,
|
|
key2: policy2,
|
|
}
|
|
|
|
def _create_packet(self, dst_ip, proto, ttl=255):
|
|
router_interface = self.router.router_interfaces[
|
|
self.subnet1.subnet_id
|
|
]
|
|
router_interface_port = self.neutron.show_port(
|
|
router_interface['port_id']
|
|
)
|
|
ethernet = os_ken.lib.packet.ethernet.ethernet(
|
|
src=self.port1.port.get_logical_port().mac,
|
|
dst=router_interface_port['port']['mac_address'],
|
|
ethertype=os_ken.lib.packet.ethernet.ether.ETH_TYPE_IP,
|
|
)
|
|
ip = os_ken.lib.packet.ipv4.ipv4(
|
|
src=str(self.port1.port.get_logical_port().ip),
|
|
dst=str(dst_ip),
|
|
ttl=ttl,
|
|
proto=proto,
|
|
)
|
|
if proto == os_ken.lib.packet.ipv4.inet.IPPROTO_ICMP:
|
|
ip_data = os_ken.lib.packet.icmp.icmp(
|
|
type_=os_ken.lib.packet.icmp.ICMP_ECHO_REQUEST,
|
|
data=os_ken.lib.packet.icmp.echo(
|
|
data=self._create_random_string())
|
|
)
|
|
self._ping = ip_data
|
|
elif proto == os_ken.lib.packet.ipv4.inet.IPPROTO_UDP:
|
|
ip_data = os_ken.lib.packet.udp.udp(
|
|
dst_port=33534,
|
|
)
|
|
self._ip = ip
|
|
result = os_ken.lib.packet.packet.Packet()
|
|
result.add_protocol(ethernet)
|
|
result.add_protocol(ip)
|
|
result.add_protocol(ip_data)
|
|
result.serialize()
|
|
return result.data
|
|
|
|
def _get_ping(self):
|
|
return self._ping
|
|
|
|
def _get_ip(self):
|
|
return self._ip
|
|
|
|
def _create_pong_packet(self, buf):
|
|
pkt = os_ken.lib.packet.packet.Packet(buf)
|
|
ether = pkt.get_protocol(os_ken.lib.packet.ethernet.ethernet)
|
|
ip = pkt.get_protocol(os_ken.lib.packet.ipv4.ipv4)
|
|
icmp = pkt.get_protocol(os_ken.lib.packet.icmp.icmp)
|
|
|
|
ether.src, ether.dst = ether.dst, ether.src
|
|
|
|
lport2 = self.port2.port.get_logical_port()
|
|
self.assertIn(
|
|
ether.src,
|
|
lport2.macs + [
|
|
p.mac_address for p in lport2.allowed_address_pairs or ()
|
|
]
|
|
)
|
|
router_interface = self.router.router_interfaces[
|
|
self.subnet2.subnet_id
|
|
]
|
|
router_interface_port = self.neutron.show_port(
|
|
router_interface['port_id']
|
|
)
|
|
router_mac = router_interface_port['port']['mac_address']
|
|
self.assertEqual(
|
|
ether.dst,
|
|
router_mac,
|
|
)
|
|
|
|
ip.src, ip.dst = ip.dst, ip.src
|
|
self.assertIn(
|
|
netaddr.IPAddress(ip.src),
|
|
lport2.ips + [
|
|
p.ip_address for p in lport2.allowed_address_pairs or ()
|
|
]
|
|
)
|
|
self.assertEqual(
|
|
netaddr.IPAddress(ip.dst),
|
|
self.port1.port.get_logical_port().ip
|
|
)
|
|
|
|
icmp.type = os_ken.lib.packet.icmp.ICMP_ECHO_REPLY
|
|
icmp.csum = 0
|
|
result = os_ken.lib.packet.packet.Packet()
|
|
result.add_protocol(ether)
|
|
result.add_protocol(ip)
|
|
result.add_protocol(icmp)
|
|
result.serialize()
|
|
return result.data
|
|
|
|
def _test_icmp_address(self, dst_ip):
|
|
port_policies = self._create_port_policies()
|
|
initial_packet = self._create_packet(
|
|
dst_ip, os_ken.lib.packet.ipv4.inet.IPPROTO_ICMP)
|
|
policy = app_testing_objects.Policy(
|
|
initial_actions=[
|
|
app_testing_objects.SendAction(
|
|
self.subnet1.subnet_id,
|
|
self.port1.port_id,
|
|
initial_packet,
|
|
),
|
|
],
|
|
port_policies=port_policies,
|
|
unknown_port_action=app_testing_objects.IgnoreAction()
|
|
)
|
|
self.addCleanup(policy.close)
|
|
apps.start_policy(policy, self.topology,
|
|
const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
|
|
|
def test_icmp_ping_pong(self):
|
|
self._test_icmp_address(self.port2.port.get_logical_port().ip)
|
|
|
|
def test_icmp_ping_pong_allowed_address_pair(self):
|
|
port3 = objects.PortTestObj(self.neutron, self.nb_api)
|
|
port3.create(
|
|
port={
|
|
'admin_state_up': True,
|
|
'fixed_ips': [{
|
|
'subnet_id': self.subnet2.subnet.subnet_id,
|
|
}],
|
|
'network_id': self.subnet2.network.network_id,
|
|
},
|
|
)
|
|
|
|
try:
|
|
lport3 = port3.get_logical_port()
|
|
self.port2.port.update(
|
|
{
|
|
'allowed_address_pairs': [
|
|
{'ip_address': lport3.ip, 'mac_address': lport3.mac},
|
|
],
|
|
},
|
|
)
|
|
lport2 = self.port2.port.get_logical_port()
|
|
self._test_icmp_address(lport2.allowed_address_pairs[0].ip_address)
|
|
finally:
|
|
port3.close()
|
|
|
|
def test_icmp_router_interfaces(self):
|
|
self._test_icmp_address('192.168.12.1')
|
|
|
|
def test_icmp_other_router_interface(self):
|
|
self._test_icmp_address('192.168.13.1')
|
|
|
|
def test_reconnect_of_controller(self):
|
|
cmd = ["ovs-vsctl", "get-controller", cfg.CONF.df.integration_bridge]
|
|
controller = utils.execute(cmd, run_as_root=True).strip()
|
|
|
|
cmd[1] = "del-controller"
|
|
utils.execute(cmd, run_as_root=True)
|
|
|
|
dst_ip = self.port2.port.get_logical_port().ip
|
|
port_policies = self._create_port_policies(connected=False)
|
|
initial_packet = self._create_packet(
|
|
dst_ip, os_ken.lib.packet.ipv4.inet.IPPROTO_ICMP)
|
|
policy = app_testing_objects.Policy(
|
|
initial_actions=[
|
|
app_testing_objects.SendAction(
|
|
self.subnet1.subnet_id,
|
|
self.port1.port_id,
|
|
initial_packet,
|
|
),
|
|
],
|
|
port_policies=port_policies,
|
|
unknown_port_action=app_testing_objects.IgnoreAction()
|
|
)
|
|
self.addCleanup(policy.close)
|
|
policy.start(self.topology)
|
|
# Since there is no OpenFlow in vswitch, we are expecting timeout
|
|
# exception here.
|
|
self.assertRaises(
|
|
app_testing_objects.TimeoutException,
|
|
policy.wait,
|
|
const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
|
policy.stop()
|
|
if len(policy.exceptions) > 0:
|
|
raise policy.exceptions[0]
|
|
|
|
cmd[1] = "set-controller"
|
|
cmd.append(controller)
|
|
utils.execute(cmd, run_as_root=True)
|
|
time.sleep(apps.CONTROLLER_RECONNECT_TIMEOUT)
|
|
self._test_icmp_address(dst_ip)
|
|
|
|
def _create_icmp_test_port_policies(self, icmp_filter):
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
raise_action = app_testing_objects.RaiseAction("Unexpected packet")
|
|
rules = [
|
|
app_testing_objects.PortPolicyRule(
|
|
# Detect ICMP, end simulation
|
|
icmp_filter(self._get_ip),
|
|
actions=[app_testing_objects.DisableRuleAction(),
|
|
app_testing_objects.StopSimulationAction()]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore gratuitous ARP packets
|
|
app_testing_objects.OsKenARPGratuitousFilter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore IPv6 packets
|
|
app_testing_objects.OsKenIPv6Filter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
]
|
|
policy = app_testing_objects.PortPolicy(
|
|
rules=rules,
|
|
default_action=raise_action
|
|
)
|
|
key = (self.subnet1.subnet_id, self.port1.port_id)
|
|
return {key: policy}
|
|
|
|
def _create_rate_limit_port_policies(self, rate, icmp_filter):
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
raise_action = app_testing_objects.RaiseAction("Unexpected packet")
|
|
# Disable port policy rule, so that any further packets will hit the
|
|
# default action, which is raise_action in this case.
|
|
count_action = app_testing_objects.CountAction(
|
|
rate, app_testing_objects.DisableRuleAction())
|
|
|
|
key = (self.subnet1.subnet_id, self.port1.port_id)
|
|
rules = [
|
|
app_testing_objects.PortPolicyRule(
|
|
# Detect ICMP, end simulation
|
|
icmp_filter(self._get_ip),
|
|
actions=[count_action]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore gratuitous ARP packets
|
|
app_testing_objects.OsKenARPGratuitousFilter(),
|
|
actions=[ignore_action]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore IPv6 packets
|
|
app_testing_objects.OsKenIPv6Filter(),
|
|
actions=[ignore_action]
|
|
),
|
|
]
|
|
policy = app_testing_objects.PortPolicy(
|
|
rules=rules,
|
|
default_action=raise_action
|
|
)
|
|
return {key: policy}
|
|
|
|
def test_icmp_ttl_packet_with_rate_limit(self):
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
port_policy = self._create_rate_limit_port_policies(
|
|
cfg.CONF.df_l3_app.router_ttl_invalid_max_rate,
|
|
app_testing_objects.OsKenICMPTimeExceedFilter)
|
|
initial_packet = self._create_packet(
|
|
self.port2.port.get_logical_port().ip,
|
|
os_ken.lib.packet.ipv4.inet.IPPROTO_ICMP,
|
|
ttl=1)
|
|
send_action = app_testing_objects.SendAction(
|
|
self.subnet1.subnet_id,
|
|
self.port1.port_id,
|
|
initial_packet)
|
|
policy = app_testing_objects.Policy(
|
|
initial_actions=[
|
|
send_action,
|
|
send_action,
|
|
send_action,
|
|
send_action
|
|
],
|
|
port_policies=port_policy,
|
|
unknown_port_action=ignore_action
|
|
)
|
|
self.addCleanup(policy.close)
|
|
policy.start(self.topology)
|
|
# Since the rate limit, we expect timeout to wait for 4th packet hit
|
|
# the policy.
|
|
self.assertRaises(
|
|
app_testing_objects.TimeoutException,
|
|
policy.wait,
|
|
const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
|
if len(policy.exceptions) > 0:
|
|
raise policy.exceptions[0]
|
|
|
|
@testtools.skip("bug/1706065")
|
|
def test_udp_concrete_router_interface(self):
|
|
# By default, fullstack will start l3 agent. So there will be concrete
|
|
# router interface.
|
|
self.port1.port.update({"security_groups": []})
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
port_policy = self._create_icmp_test_port_policies(
|
|
app_testing_objects.OsKenICMPUnreachFilter)
|
|
initial_packet = self._create_packet(
|
|
"192.168.12.1", os_ken.lib.packet.ipv4.inet.IPPROTO_UDP)
|
|
policy = app_testing_objects.Policy(
|
|
initial_actions=[
|
|
app_testing_objects.SendAction(
|
|
self.subnet1.subnet_id,
|
|
self.port1.port_id,
|
|
initial_packet,
|
|
),
|
|
],
|
|
port_policies=port_policy,
|
|
unknown_port_action=ignore_action
|
|
)
|
|
self.addCleanup(policy.close)
|
|
apps.start_policy(policy, self.topology,
|
|
const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
|
|
|
def test_udp_virtual_router_interface_with_rate_limit(self):
|
|
if 'zmq_pubsub_driver' == cfg.CONF.df.pub_sub_driver:
|
|
# NOTE(nick-ma-z): This test case directly calls nb_api which
|
|
# relies on a publisher running on local process. In ZMQ driver,
|
|
# a socket needs to be binded which causes conflicts with other
|
|
# df-services. But in Redis driver, the publisher is virtual and
|
|
# does not actually run which makes this test case work.
|
|
self.skipTest("ZMQ_PUBSUB does not support this test case")
|
|
# Delete the concrete router interface.
|
|
router_port_id = self.router.router_interfaces[
|
|
self.subnet1.subnet_id]['port_id']
|
|
topic = self.router.router_interfaces[
|
|
self.subnet1.subnet_id]['tenant_id']
|
|
self.nb_api.delete(l2.LogicalPort(id=router_port_id, topic=topic))
|
|
lrouter = self.nb_api.get(l3.LogicalRouter(
|
|
id=self.router.router.router_id,
|
|
topic=topic))
|
|
lrouter.version += 1
|
|
original_lrouter = copy.deepcopy(lrouter)
|
|
lrouter.remove_router_port(router_port_id)
|
|
self.nb_api.update(lrouter)
|
|
# Update router with virtual router interface.
|
|
original_lrouter.version += 1
|
|
self.nb_api.update(original_lrouter)
|
|
|
|
time.sleep(const.DEFAULT_CMD_TIMEOUT)
|
|
self.port1.port.update({"security_groups": []})
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
port_policy = self._create_rate_limit_port_policies(
|
|
cfg.CONF.df_l3_app.router_port_unreach_max_rate,
|
|
app_testing_objects.OsKenICMPUnreachFilter)
|
|
initial_packet = self._create_packet(
|
|
"192.168.12.1", os_ken.lib.packet.ipv4.inet.IPPROTO_UDP)
|
|
send_action = app_testing_objects.SendAction(
|
|
self.subnet1.subnet_id,
|
|
self.port1.port_id,
|
|
initial_packet)
|
|
|
|
policy = app_testing_objects.Policy(
|
|
initial_actions=[
|
|
send_action,
|
|
send_action,
|
|
send_action,
|
|
send_action
|
|
],
|
|
port_policies=port_policy,
|
|
unknown_port_action=ignore_action
|
|
)
|
|
self.addCleanup(policy.close)
|
|
policy.start(self.topology)
|
|
# Since the rate limit, we expect timeout to wait for 4th packet hit
|
|
# the policy.
|
|
self.assertRaises(
|
|
app_testing_objects.TimeoutException,
|
|
policy.wait,
|
|
const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
|
if len(policy.exceptions) > 0:
|
|
raise policy.exceptions[0]
|
|
|
|
def _create_extra_route_policies(self, nexthop_port):
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
raise_action = app_testing_objects.RaiseAction("Unexpected packet")
|
|
rules = [
|
|
app_testing_objects.PortPolicyRule(
|
|
# The nexthop lport should get the icmp echo request whose
|
|
# destination is the cidr of extra route.
|
|
app_testing_objects.OsKenICMPPingFilter(self._get_ping),
|
|
actions=[app_testing_objects.DisableRuleAction(),
|
|
app_testing_objects.StopSimulationAction()]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore gratuitous ARP packets
|
|
app_testing_objects.OsKenARPGratuitousFilter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
app_testing_objects.PortPolicyRule(
|
|
# Ignore IPv6 packets
|
|
app_testing_objects.OsKenIPv6Filter(),
|
|
actions=[
|
|
ignore_action
|
|
]
|
|
),
|
|
]
|
|
policy = app_testing_objects.PortPolicy(
|
|
rules=rules,
|
|
default_action=raise_action
|
|
)
|
|
key = (self.subnet1.subnet_id, nexthop_port.port_id)
|
|
return {key: policy}
|
|
|
|
def test_router_extra_route(self):
|
|
nexthop_port = self.subnet1.create_port()
|
|
nexthop_ip = nexthop_port.port.get_logical_port().ip
|
|
self.router.router.update({"routes": [{"nexthop": nexthop_ip,
|
|
"destination": "30.0.0.0/24"}]})
|
|
time.sleep(const.DEFAULT_CMD_TIMEOUT)
|
|
ignore_action = app_testing_objects.IgnoreAction()
|
|
port_policy = self._create_extra_route_policies(nexthop_port)
|
|
initial_packet = self._create_packet(
|
|
"30.0.0.12",
|
|
os_ken.lib.packet.ipv4.inet.IPPROTO_ICMP)
|
|
send_action = app_testing_objects.SendAction(
|
|
self.subnet1.subnet_id,
|
|
self.port1.port_id,
|
|
initial_packet)
|
|
policy = app_testing_objects.Policy(
|
|
initial_actions=[
|
|
send_action
|
|
],
|
|
port_policies=port_policy,
|
|
unknown_port_action=ignore_action
|
|
)
|
|
self.addCleanup(policy.close)
|
|
apps.start_policy(policy, self.topology,
|
|
const.DEFAULT_RESOURCE_READY_TIMEOUT)
|