Add rate limiter to icmp handler(L3 proactive app)
To support traceroute or its backend network functionality, some icmp messages are generated or re-processed at local controller. This provides the possiblity of DDoS attack. To prevent that, several global rate limters are added. So that the controller won't be overwhelmed. Global rate limters should be enough, as icmp works as control message. Change-Id: I2d588a7db246bce4bef8e4f32a4fdd0dfb14384d Implements: blueprint traceroute-support
This commit is contained in:
parent
9f456ed1d8
commit
d1762b7aa5
|
@ -23,6 +23,8 @@ from ryu.ofproto import ether
|
|||
|
||||
from dragonflow._i18n import _LI, _LW
|
||||
from dragonflow.common import exceptions
|
||||
from dragonflow.common import utils as df_utils
|
||||
from dragonflow import conf as cfg
|
||||
from dragonflow.controller.common import arp_responder
|
||||
from dragonflow.controller.common import constants as const
|
||||
from dragonflow.controller.common import icmp_error_generator
|
||||
|
@ -43,6 +45,13 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
self.router_port_rarp_cache = {}
|
||||
self.api.register_table_handler(const.L3_LOOKUP_TABLE,
|
||||
self.packet_in_handler)
|
||||
self.conf = cfg.CONF.df_l3_app
|
||||
self.ttl_invalid_handler_rate_limit = df_utils.RateLimiter(
|
||||
max_rate=self.conf.router_ttl_invalid_max_rate,
|
||||
time_unit=1)
|
||||
self.port_icmp_unreach_respond_rate_limit = df_utils.RateLimiter(
|
||||
max_rate=self.conf.router_port_unreach_max_rate,
|
||||
time_unit=1)
|
||||
self.register_local_cookie_bits(COOKIE_NAME, 24)
|
||||
|
||||
def switch_features_handler(self, ev):
|
||||
|
@ -51,12 +60,20 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
def packet_in_handler(self, event):
|
||||
msg = event.msg
|
||||
ofproto = self.ofproto
|
||||
pkt = packet.Packet(msg.data)
|
||||
e_pkt = pkt.get_protocol(ethernet.ethernet)
|
||||
|
||||
if msg.reason == 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(
|
||||
_LW("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
|
||||
|
||||
pkt = packet.Packet(msg.data)
|
||||
e_pkt = pkt.get_protocol(ethernet.ethernet)
|
||||
router_port_ip = self.router_port_rarp_cache.get(e_pkt.dst)
|
||||
if router_port_ip:
|
||||
icmp_ttl_pkt = icmp_error_generator.generate(
|
||||
|
@ -70,6 +87,15 @@ class L3ProactiveApp(df_base_app.DFlowApp):
|
|||
return
|
||||
|
||||
# The packet's IP destination is router interface.
|
||||
if self.port_icmp_unreach_respond_rate_limit():
|
||||
LOG.warning(
|
||||
_LW("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
|
||||
|
||||
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:
|
||||
|
|
|
@ -908,33 +908,76 @@ class TestL3App(test_base.DFTestBase):
|
|||
key = (self.subnet1.subnet_id, self.port1.port_id)
|
||||
return {key: policy}
|
||||
|
||||
def test_icmp_ttl_packet(self):
|
||||
def _create_rate_limit_port_policies(self, rate, icmp_filter):
|
||||
ignore_action = app_testing_objects.IgnoreAction()
|
||||
port_policy = self._create_icmp_test_port_policies(
|
||||
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.RyuARPGratuitousFilter(),
|
||||
actions=[ignore_action]
|
||||
),
|
||||
app_testing_objects.PortPolicyRule(
|
||||
# Ignore IPv6 packets
|
||||
app_testing_objects.RyuIPv6Filter(),
|
||||
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.RyuICMPTimeExceedFilter)
|
||||
initial_packet = self._create_packet(
|
||||
self.port2.port.get_logical_port().get_ip(),
|
||||
ryu.lib.packet.ipv4.inet.IPPROTO_ICMP,
|
||||
ttl=1)
|
||||
send_action = app_testing_objects.SendAction(
|
||||
self.subnet1.subnet_id,
|
||||
self.port1.port_id,
|
||||
str(initial_packet))
|
||||
policy = self.store(
|
||||
app_testing_objects.Policy(
|
||||
initial_actions=[
|
||||
app_testing_objects.SendAction(
|
||||
self.subnet1.subnet_id,
|
||||
self.port1.port_id,
|
||||
str(initial_packet)
|
||||
),
|
||||
send_action,
|
||||
send_action,
|
||||
send_action,
|
||||
send_action
|
||||
],
|
||||
port_policies=port_policy,
|
||||
unknown_port_action=ignore_action
|
||||
)
|
||||
)
|
||||
policy.start(self.topology)
|
||||
policy.wait(const.DEFAULT_RESOURCE_READY_TIMEOUT)
|
||||
# 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 _test_udp_router_interface(self):
|
||||
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(
|
||||
|
@ -959,12 +1002,7 @@ class TestL3App(test_base.DFTestBase):
|
|||
if len(policy.exceptions) > 0:
|
||||
raise policy.exceptions[0]
|
||||
|
||||
def test_udp_concrete_router_interface(self):
|
||||
# By default, fullstack will start l3 agent. So there will be concrete
|
||||
# router interface.
|
||||
self._test_udp_router_interface()
|
||||
|
||||
def test_udp_virtual_router_interface(self):
|
||||
def test_udp_virtual_router_interface_with_rate_limit(self):
|
||||
# Delete the concrete router interface.
|
||||
router_port_id = self.router.router_interfaces[
|
||||
self.subnet1.subnet_id]['port_id']
|
||||
|
@ -983,7 +1021,39 @@ class TestL3App(test_base.DFTestBase):
|
|||
self.nb_api.update_lrouter(**lrouter.inner_obj)
|
||||
|
||||
time.sleep(const.DEFAULT_CMD_TIMEOUT)
|
||||
self._test_udp_router_interface()
|
||||
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.RyuICMPUnreachFilter)
|
||||
initial_packet = self._create_packet(
|
||||
"192.168.12.1", ryu.lib.packet.ipv4.inet.IPPROTO_UDP)
|
||||
send_action = app_testing_objects.SendAction(
|
||||
self.subnet1.subnet_id,
|
||||
self.port1.port_id,
|
||||
str(initial_packet))
|
||||
|
||||
policy = self.store(
|
||||
app_testing_objects.Policy(
|
||||
initial_actions=[
|
||||
send_action,
|
||||
send_action,
|
||||
send_action,
|
||||
send_action
|
||||
],
|
||||
port_policies=port_policy,
|
||||
unknown_port_action=ignore_action
|
||||
)
|
||||
)
|
||||
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]
|
||||
|
||||
|
||||
class TestSGApp(test_base.DFTestBase):
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
prelude: >
|
||||
Traceroute support.
|
||||
features:
|
||||
- As a Openflow based SDN, traceroute and its back-end network
|
||||
functionalities are not naturally supported. Dragonflow supports traceroute
|
||||
base on RFC792, RFC4884, RFC1812 and RFC5508. Traceroute in Dragonflow
|
||||
cluster works for virtual router and NAT device.
|
||||
security:
|
||||
- To prevent DDoS, the rate of responding ICMP error message is controlled by
|
||||
several configuration options. They are 'dnat_ttl_invalid_max_rate',
|
||||
'dnat_icmp_error_max_rate', 'router_ttl_invalid_max_rate' and
|
||||
'router_port_unreach_max_rate'.
|
Loading…
Reference in New Issue