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:
parent
c7163a4fb8
commit
ab360211b7
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue