Merge "DHCP DOS prevention"
This commit is contained in:
commit
81eacc1207
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
Loading…
Reference in New Issue