L3 app: use port/router keys in PACKET_IN handler

L3 app used packet's MAC address to find the relevant port IP. Since
MACs are not unique across projects or networks, we can resolve an
incorrect IP address. This uses router/port keys to find the relevant
resource.

Closes-Bug: #1697439
Change-Id: I2925db5c00fcad256159306fec455b8b3c4cf8bc
This commit is contained in:
Dima Kuznetsov 2017-06-23 18:35:01 +03:00
parent c7163a4fb8
commit ab360211b7
2 changed files with 168 additions and 98 deletions

View File

@ -49,7 +49,6 @@ class L3AppMixin(object):
def __init__(self, *args, **kwargs):
super(L3AppMixin, self).__init__()
self.router_port_rarp_cache = {}
self.route_cache = {}
self.conf = cfg.CONF.df_l3_app
@ -63,9 +62,66 @@ class L3AppMixin(object):
self.packet_in_handler)
def switch_features_handler(self, ev):
self.router_port_rarp_cache.clear()
self.route_cache.clear()
def _handle_ttl_expired(self, msg):
if self.ttl_invalid_handler_rate_limit():
LOG.warning("Get more than %(rate)s TTL invalid packets per "
"second at table %(table)s",
{'rate': self.conf.router_ttl_invalid_max_rate,
'table': const.L3_LOOKUP_TABLE})
return
LOG.debug("Get an invalid TTL packet at table %s",
const.L3_LOOKUP_TABLE)
pkt = packet.Packet(msg.data)
e_pkt = pkt.get_protocol(ethernet.ethernet)
router_key = msg.match.get('reg5')
lrouter = self.db_store.get_one(
l3.LogicalRouter(unique_key=router_key),
index=l3.LogicalRouter.get_index('unique_key'),
)
router_port_ip = None
for port in lrouter.ports:
if port.lswitch.unique_key == msg.match.get('metadata'):
router_port_ip = port.network.ip
break
if router_port_ip:
icmp_ttl_pkt = icmp_error_generator.generate(
icmp.ICMP_TIME_EXCEEDED, icmp.ICMP_TTL_EXPIRED_CODE,
msg.data, str(router_port_ip), pkt)
unique_key = msg.match.get('reg6')
self.dispatch_packet(icmp_ttl_pkt, unique_key)
else:
LOG.warning("The invalid TTL packet's destination mac %s "
"can't be recognized.", e_pkt.dst)
def _handle_invalid_dest(self, msg):
# If the destination is router interface, the unique key of router
# interface will be set to reg7 before sending to local controller.
# Code will hit here only when the router interface is not
# concrete.
if self.port_icmp_unreach_respond_rate_limit():
LOG.warning(
"Get more than %(rate)s packets to router port "
"per second at table %(table)s",
{'rate': self.conf.router_port_unreach_max_rate,
'table': const.L3_LOOKUP_TABLE})
return
# Response icmp unreachable to udp or tcp.
pkt = packet.Packet(msg.data)
tcp_pkt = pkt.get_protocol(tcp.tcp)
udp_pkt = pkt.get_protocol(udp.udp)
if tcp_pkt or udp_pkt:
icmp_dst_unreach = icmp_error_generator.generate(
icmp.ICMP_DEST_UNREACH, icmp.ICMP_PORT_UNREACH_CODE,
msg.data, pkt=pkt)
unique_key = msg.match.get('reg6')
self.dispatch_packet(icmp_dst_unreach, unique_key)
def router_function_packet_in_handler(self, msg):
"""React to packet as what a normal router will do.
@ -75,59 +131,13 @@ class L3AppMixin(object):
"""
if msg.reason == self.ofproto.OFPR_INVALID_TTL:
LOG.debug("Get an invalid TTL packet at table %s",
const.L3_LOOKUP_TABLE)
if self.ttl_invalid_handler_rate_limit():
LOG.warning("Get more than %(rate)s TTL invalid packets per "
"second at table %(table)s",
{'rate': self.conf.router_ttl_invalid_max_rate,
'table': const.L3_LOOKUP_TABLE})
return True
self._handle_ttl_expired(msg)
elif msg.match.get('reg7'):
self._handle_invalid_dest(msg)
else:
return False
pkt = packet.Packet(msg.data)
e_pkt = pkt.get_protocol(ethernet.ethernet)
mac = netaddr.EUI(e_pkt.dst)
router_port_ip = self.router_port_rarp_cache.get(mac)
if router_port_ip:
icmp_ttl_pkt = icmp_error_generator.generate(
icmp.ICMP_TIME_EXCEEDED, icmp.ICMP_TTL_EXPIRED_CODE,
msg.data, str(router_port_ip), pkt)
unique_key = msg.match.get('reg6')
self.dispatch_packet(icmp_ttl_pkt, unique_key)
else:
LOG.warning("The invalid TTL packet's destination mac %s "
"can't be recognized.", e_pkt.dst)
return True
if msg.match.get('reg7'):
# If the destination is router interface, the unique key of router
# interface will be set to reg7 before sending to local controller.
# Code will hit here only when the router interface is not
# concrete.
if self.port_icmp_unreach_respond_rate_limit():
LOG.warning(
"Get more than %(rate)s packets to router port "
"per second at table %(table)s",
{'rate': self.conf.router_port_unreach_max_rate,
'table': const.L3_LOOKUP_TABLE})
return True
# Response icmp unreachable to udp or tcp.
pkt = packet.Packet(msg.data)
tcp_pkt = pkt.get_protocol(tcp.tcp)
udp_pkt = pkt.get_protocol(udp.udp)
if tcp_pkt or udp_pkt:
icmp_dst_unreach = icmp_error_generator.generate(
icmp.ICMP_DEST_UNREACH, icmp.ICMP_PORT_UNREACH_CODE,
msg.data, pkt=pkt)
unique_key = msg.match.get('reg6')
self.dispatch_packet(icmp_dst_unreach, unique_key)
# Silently drop packet of other protocol.
return True
# No match in previous code.
return False
return True
@df_base_app.register_event(l3.LogicalRouter,
model_constants.EVENT_CREATED)
@ -395,7 +405,6 @@ class L3AppMixin(object):
# Add router ARP & ICMP responder for IPv4 Addresses
if is_ipv4:
self.router_port_rarp_cache[mac] = dst_ip
arp_responder.ArpResponder(self,
local_network_id,
dst_ip, mac).add()
@ -452,8 +461,6 @@ class L3AppMixin(object):
# Delete ARP & ICMP responder for router interface
if ip.version == common_const.IP_VERSION_4:
self.router_port_rarp_cache.pop(mac, None)
arp_responder.ArpResponder(self, local_network_id, ip).remove()
icmp_responder.ICMPResponder(self, ip,
router_key=router_unique_key).remove()

View File

@ -13,9 +13,17 @@
import copy
import mock
from ryu.controller import ofp_event
from ryu.lib.packet import ethernet
from ryu.lib.packet import icmp
from ryu.lib.packet import in_proto
from ryu.lib.packet import ipv4
from ryu.lib.packet import packet
from ryu.lib.packet import udp
from ryu.ofproto import ofproto_v1_3_parser as ofproto_parser
from dragonflow.controller.common import constants as const
from dragonflow.db.models import l2
from dragonflow.db.models import l3
from dragonflow.tests.unit import test_app_base
@ -63,51 +71,106 @@ class L3AppTestCaseMixin(object):
self.assertEqual(const.L3_LOOKUP_TABLE, kwargs['table_id'])
def test_reply_ttl_invalid_message_with_rate_limit(self):
event = mock.Mock()
event.msg.reason = self.app.ofproto.OFPR_INVALID_TTL
with mock.patch.object(self.app, "router_port_rarp_cache") as rarp:
rarp.get = mock.Mock(return_value="10.0.0.1")
with mock.patch("ryu.lib.packet.packet.Packet"):
with mock.patch("dragonflow.controller.common."
"icmp_error_generator.generate") as icmp_error:
eui_patcher = mock.patch("netaddr.EUI")
eui_patcher.start()
self.addCleanup(eui_patcher.stop)
self.app.packet_in_handler(event)
self.app.packet_in_handler(event)
self.app.packet_in_handler(event)
self.app.packet_in_handler(event)
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(dst='aa:bb:cc:dd:ee:ff'))
pkt.add_protocol(ipv4.ipv4(proto=in_proto.IPPROTO_UDP))
pkt.add_protocol(udp.udp())
pkt.serialize()
self.assertEqual(self.app.conf.router_ttl_invalid_max_rate,
icmp_error.call_count)
icmp_error.assert_called_with(icmp.ICMP_TIME_EXCEEDED,
icmp.ICMP_TTL_EXPIRED_CODE,
mock.ANY, "10.0.0.1",
mock.ANY)
lswitch = l2.LogicalSwitch(
id='lswitch1',
topic='topic1',
unique_key=9,
version=1,
)
self.app.db_store.update(lswitch)
lrouter = l3.LogicalRouter(
id='lrouter1',
topic='topic1',
version=1,
unique_key=22,
ports=[
l3.LogicalRouterPort(
id='lrouter1-port1',
unique_key=55,
topic='topic1',
mac='aa:bb:cc:dd:ee:ff',
network='10.0.0.1/24',
lswitch='lswitch1',
),
],
)
self.app.db_store.update(lrouter)
event = ofp_event.EventOFPMsgBase(
msg=ofproto_parser.OFPPacketIn(
datapath=mock.Mock(),
reason=self.app.ofproto.OFPR_INVALID_TTL,
match=ofproto_parser.OFPMatch(
metadata=lswitch.unique_key,
reg5=lrouter.unique_key,
),
data=pkt.data,
)
)
with mock.patch("dragonflow.controller.common."
"icmp_error_generator.generate") as icmp_error:
for _ in range(self.app.conf.router_ttl_invalid_max_rate * 2):
self.app.packet_in_handler(event)
self.assertEqual(self.app.conf.router_ttl_invalid_max_rate,
icmp_error.call_count)
icmp_error.assert_called_with(icmp.ICMP_TIME_EXCEEDED,
icmp.ICMP_TTL_EXPIRED_CODE,
mock.ANY, "10.0.0.1", mock.ANY)
def test_reply_icmp_unreachable_with_rate_limit(self):
with mock.patch.object(self.app, "router_port_rarp_cache") as rarp:
rarp.values.return_value = ["10.0.0.1"]
event = mock.Mock()
fake_ip_pkt = mock.Mock()
fake_ip_pkt.dst = "10.0.0.1"
fake_pkt = mock.Mock()
fake_pkt.get_protocol.return_value = fake_ip_pkt
with mock.patch("ryu.lib.packet.packet.Packet",
return_value=fake_pkt):
with mock.patch("dragonflow.controller.common."
"icmp_error_generator.generate") as icmp_error:
self.app.packet_in_handler(event)
self.app.packet_in_handler(event)
self.app.packet_in_handler(event)
self.app.packet_in_handler(event)
pkt = packet.Packet()
pkt.add_protocol(ethernet.ethernet(dst='aa:bb:cc:dd:ee:ff'))
pkt.add_protocol(ipv4.ipv4(dst='10.0.0.1', proto=in_proto.IPPROTO_UDP))
pkt.add_protocol(udp.udp())
pkt.serialize()
self.assertEqual(
self.app.conf.router_port_unreach_max_rate,
icmp_error.call_count)
icmp_error.assert_called_with(icmp.ICMP_DEST_UNREACH,
icmp.ICMP_PORT_UNREACH_CODE,
mock.ANY, pkt=fake_pkt)
lrouter = l3.LogicalRouter(
id='lrouter1',
topic='topic1',
version=1,
unique_key=22,
ports=[
l3.LogicalRouterPort(
id='lrouter1-port1',
unique_key=55,
topic='topic1',
mac='aa:bb:cc:dd:ee:ff',
network='10.0.0.1/24',
),
],
)
self.app.db_store.update(lrouter)
event = ofp_event.EventOFPMsgBase(
msg=ofproto_parser.OFPPacketIn(
datapath=mock.Mock(),
reason=self.app.ofproto.OFPR_PACKET_IN,
match=ofproto_parser.OFPMatch(
reg7=lrouter.ports[0].unique_key,
),
data=pkt.data,
)
)
with mock.patch("dragonflow.controller.common."
"icmp_error_generator.generate") as icmp_error:
for _ in range(self.app.conf.router_port_unreach_max_rate * 2):
self.app.packet_in_handler(event)
self.assertEqual(
self.app.conf.router_port_unreach_max_rate,
icmp_error.call_count)
icmp_error.assert_called_with(icmp.ICMP_DEST_UNREACH,
icmp.ICMP_PORT_UNREACH_CODE,
pkt.data, pkt=mock.ANY)
def test_add_del_router_route_after_lport(self):
self.controller.update_lport(test_app_base.fake_local_port1)