Merge "DHCP DOS prevention"

This commit is contained in:
Jenkins 2016-05-19 12:55:10 +00:00 committed by Gerrit Code Review
commit 81eacc1207
2 changed files with 85 additions and 12 deletions

View File

@ -12,7 +12,6 @@
# 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 netaddr
import struct
@ -31,6 +30,7 @@ from ryu.lib.packet import packet as ryu_packet
from ryu.lib.packet import udp
from ryu.ofproto import ether
from dragonflow.common import utils as df_utils
from dragonflow._i18n import _, _LI, _LE, _LW
from dragonflow.controller.common import constants as const
from dragonflow.controller.df_base_app import DFlowApp
@ -41,6 +41,10 @@ DF_DHCP_OPTS = [
help=_('Comma-separated list of the DNS servers which will be used.')),
cfg.IntOpt('df_default_network_device_mtu', default=1460,
help=_('default MTU setting for interface.')),
cfg.IntOpt('df_dhcp_max_rate_per_sec', default=3,
help=_('Port Max rate of DHCP messages per second')),
cfg.IntOpt('df_dhcp_block_time_in_sec', default=100,
help=_('Time to block port that passe the max rate')),
]
LOG = log.getLogger(__name__)
@ -65,6 +69,7 @@ class DHCPApp(DFlowApp):
self.lease_time = cfg.CONF.dhcp_lease_duration
self.domain_name = cfg.CONF.dns_domain
self.advertise_mtu = cfg.CONF.advertise_mtu
self.block_hard_timeout = cfg.CONF.df_dhcp_block_time_in_sec
self.default_interface_mtu = cfg.CONF.df_default_network_device_mtu
self.local_tunnel_to_pid_map = {}
@ -98,7 +103,18 @@ class DHCPApp(DFlowApp):
port_tunnel_key)
return
lport_id = self.local_tunnel_to_pid_map[port_tunnel_key]
(port_rate_limiter,
ofport_num,
lport_id) = self.local_tunnel_to_pid_map[port_tunnel_key]
if port_rate_limiter():
self._block_port_dhcp_traffic(
ofport_num,
self.block_hard_timeout)
LOG.warning(_LW("pass rate limit for %(port_id)s blocking DHCP"
" traffic for %(time)s sec") %
{'port_id': lport_id,
'time': self.block_hard_timeout})
return
lport = self.db_store.get_port(lport_id)
if lport is None:
LOG.error(
@ -338,7 +354,13 @@ class DHCPApp(DFlowApp):
lport_id = lport.get_id()
tunnel_key = lport.get_tunnel_key()
self.local_tunnel_to_pid_map[tunnel_key] = lport_id
ofport = lport.get_external_value('ofport')
port_rate_limiter = df_utils.RateLimiter(
max_rate=cfg.CONF.df_dhcp_max_rate_per_sec,
time_unit=1)
self.local_tunnel_to_pid_map[tunnel_key] = (port_rate_limiter,
ofport,
lport_id)
if not self._is_dhcp_enabled_on_network(lport, network_id):
return
@ -445,3 +467,16 @@ class DHCPApp(DFlowApp):
return (netaddr.IPNetwork(subnet.get_cidr()).version == 4)
except TypeError:
return False
def _block_port_dhcp_traffic(self, ofport_num, hard_timeout):
parser = self.get_datapath().ofproto_parser
match = parser.OFPMatch()
match.set_in_port(ofport_num)
drop_inst = None
self.mod_flow(
self.get_datapath(),
inst=drop_inst,
priority=const.PRIORITY_VERY_HIGH,
hard_timeout=hard_timeout,
table_id=const.DHCP_TABLE,
match=match)

View File

@ -16,6 +16,8 @@ import string
import sys
import time
from neutron.agent.linux.utils import wait_until_true
from dragonflow._i18n import _LI
from dragonflow.tests.common import app_testing_objects
from dragonflow.tests.common import utils as test_utils
@ -293,21 +295,23 @@ class TestDHCPApp(test_base.DFTestBase):
def _create_dhcp_renewal_request(self, offer_buf):
return self._create_dhcp_request(offer_buf, is_renewal=True)
def _create_port_policies(self):
def _create_port_policies(self, disable_rule=True):
ignore_action = app_testing_objects.IgnoreAction()
key1 = (self.subnet1.subnet_id, self.port1.port_id)
actions = [
app_testing_objects.SendAction(
self.subnet1.subnet_id,
self.port1.port_id,
self._create_dhcp_request
)]
if disable_rule:
actions.append(app_testing_objects.DisableRuleAction())
rules1 = [
app_testing_objects.PortPolicyRule(
# Detect dhcp offer
app_testing_objects.RyuDHCPOfferFilter(),
actions=[
app_testing_objects.SendAction(
self.subnet1.subnet_id,
self.port1.port_id,
self._create_dhcp_request
),
app_testing_objects.DisableRuleAction(),
]
actions
),
app_testing_objects.PortPolicyRule(
# Detect dhcp acknowledge
@ -377,6 +381,40 @@ class TestDHCPApp(test_base.DFTestBase):
if len(self.policy.exceptions) > 0:
raise self.policy.exceptions[0]
def _check_dhcp_block_rule(self, flows, ofport=None):
for flow in flows:
if flow['table'] == '11' and 'drop' in flow['actions']:
if ofport is None or 'inport=' + ofport in flow['match']:
return True
return False
def test_dhcp_app_dos_block(self):
def internal_predicate():
ovs = test_utils.OvsFlowsParser()
return (self._check_dhcp_block_rule(ovs.dump()))
dhcp_packet = self._create_dhcp_discover()
send_dhcp_offer = app_testing_objects.SendAction(
self.subnet1.subnet_id,
self.port1.port_id,
str(dhcp_packet)
)
port_policies = self._create_port_policies(disable_rule=False)
policy = self.store(
app_testing_objects.Policy(
initial_actions=[send_dhcp_offer,
send_dhcp_offer,
send_dhcp_offer,
send_dhcp_offer],
port_policies=port_policies,
unknown_port_action=app_testing_objects.IgnoreAction()
)
)
policy.start(self.topology)
wait_until_true(internal_predicate, 30, 1, None)
class TestL3App(test_base.DFTestBase):
def setUp(self):