diff options
author | Shinpei Muraoka <shinpei.muraoka@gmail.com> | 2017-03-21 09:50:42 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2017-03-28 09:55:29 +0900 |
commit | 4838463ab3fb090cdc44e8ff73859af3cd5f6c90 (patch) | |
tree | d35a0d11f1276395f0aeb4b245fa7a325cd97e38 | |
parent | 35e12d858cfad80ed8a0cf851e5c4dff92874d8e (diff) |
packet/bgp: Implement user interface of Flow Specification
Signed-off-by: Shinpei Muraoka <shinpei.muraoka@gmail.com>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/bgp.py | 247 |
1 files changed, 244 insertions, 3 deletions
diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index cfe8cfff..47feeb39 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -29,6 +29,7 @@ import functools import io import itertools import math +import re import socket import struct @@ -2059,6 +2060,16 @@ class _FlowSpecNLRIBase(StringifyMixin, TypeDisp): return buf + rules_bin + @classmethod + def _from_user(cls, **kwargs): + rules = [] + for k, v in kwargs.items(): + subcls = _FlowSpecComponentBase.lookup_type_name(k) + rule = subcls.from_str(str(v)) + rules.extend(rule) + rules.sort(key=lambda x: x.type) + return cls(rules=rules) + class FlowSpecIPv4NLRI(_FlowSpecNLRIBase): """ @@ -2066,18 +2077,119 @@ class FlowSpecIPv4NLRI(_FlowSpecNLRIBase): """ ROUTE_FAMILY = RF_IPv4_FLOW_SPEC + @classmethod + def from_user(cls, **kwargs): + """ + Utility method for creating a NLRI instance. + + This function returns a NLRI instance from human readable format value. + + :param kwargs: The following arguments are available. + + =========== ============= ========= ============================== + Argument Value Operator Description + =========== ============= ========= ============================== + dst_prefix IPv4 Prefix Nothing Destination Prefix. + src_prefix IPv4 Prefix Nothing Source Prefix. + ip_proto Integer Numeric IP Protocol. + port Integer Numeric Port number. + dst_port Integer Numeric Destination port number. + src_port Integer Numeric Source port number. + icmp_type Integer Numeric ICMP type. + icmp_code Integer Numeric ICMP code. + tcp_flags Fixed string Bitmask TCP flags. + Supported values are + ``CWR``, ``ECN``, ``URGENT``, + ``ACK``, ``PUSH``, ``RST``, + ``SYN`` and ``FIN``. + packet_len Integer Numeric Packet length. + dscp Integer Numeric Differentiated Services + Code Point. + fragment Fixed string Bitmask Fragment. + Supported values are + ``DF`` (Don't fragment), + ``ISF`` (Is a fragment), + ``FF`` (First fragment) and + ``LF`` (Last fragment) + =========== ============= ========= ============================== + + Example:: + >>> msg = bgp.FlowSpecIPv4NLRI.from_user( + ... dst_prefix='10.0.0.0/24', + ... src_prefix='20.0.0.1/24', + ... ip_proto='==6', + ... port='==80 | ==8000', + ... dst_port='>9000 & <9050', + ... src_port='>=8500 & <=9000', + ... icmp_type='==0', + ... icmp_code='==6', + ... tcp_flags='SYN+ACK & !=URGENT', + ... packet_len='==1000', + ... dscp='==22 | ==24', + ... fragment='LF | ==FF') + >>> + + You can specify conditions with the following keywords. + + The following keywords can be used when the operator type is Numeric. + + ========== ============================================================ + Keyword Description + ========== ============================================================ + < Less than comparison between data and value. + <= Less than or equal to comparison between data and value. + > Greater than comparison between data and value. + >= Greater than or equal to comparison between data and value. + == Equality between data and value. + This operator can be omitted. + ========== ============================================================ + + The following keywords can be used when the operator type is Bitmask. + + ========== ================================================ + Keyword Description + ========== ================================================ + != Not equal operation. + == Exact match operation if specified. + Otherwise partial match operation. + `+` Used for the summation of bitmask values. + (e.g., SYN+ACK) + ========== ================================================ + + You can combine the multiple conditions with the following operators. + + ========== ======================================= + Keyword Description + ========== ======================================= + `|` Logical OR operation + & Logical AND operation + ========== ======================================= + + :return: A instance of FlowSpecVPNv4NLRI. + """ + return cls._from_user(**kwargs) + class FlowSpecVPNv4NLRI(_FlowSpecNLRIBase): """ Flow Specification NLRI class for VPNv4 [RFC 5575] + + See :py:mod:`ryu.lib.packet.bgp.FlowSpecIPv4NLRI` """ ROUTE_FAMILY = RF_VPNv4_FLOW_SPEC + @classmethod + def from_user(cls, **kwargs): + + return cls._from_user(**kwargs) + class _FlowSpecComponentBase(StringifyMixin, TypeDisp): """ Base class for Flow Specification NLRI component """ + COMPONENT_NAME = None + _BASE_STR = '!B' _BASE_STR_SIZE = struct.calcsize(_BASE_STR) @@ -2094,12 +2206,34 @@ class _FlowSpecComponentBase(StringifyMixin, TypeDisp): TYPE_DIFFSERV_CODE_POINT = 0x0b TYPE_FRAGMENT = 0x0c + # Dictionary of COMPONENT_NAME to subclass. + # e.g.) + # _NAMES = {'dst_prefix': FlowSpecDestPrefix, ...} + _NAMES = {} + def __init__(self, type_=None): if type_ is None: type_ = self._rev_lookup_type(self.__class__) self.type = type_ @classmethod + def register_type(cls, type_): + cls._TYPES = cls._TYPES.copy() + cls._NAMES = cls._NAMES.copy() + + def _register_type(subcls): + cls._TYPES[type_] = subcls + cls._NAMES[subcls.COMPONENT_NAME] = subcls + cls._REV_TYPES = None + return subcls + + return _register_type + + @classmethod + def lookup_type_name(cls, type_name): + return cls._NAMES[type_name] + + @classmethod def parse_header(cls, rest): (type_,) = struct.unpack_from( cls._BASE_STR, six.binary_type(rest)) @@ -2145,6 +2279,13 @@ class _FlowSpecPrefixBase(_FlowSpecComponentBase, IPAddrPrefix): def serialize_body(self): return self.serialize() + @classmethod + def from_str(cls, value): + rule = [] + addr, length = value.split('/') + rule.append(cls(int(length), addr)) + return rule + @_FlowSpecComponentBase.register_type( _FlowSpecComponentBase.TYPE_DESTINATION_PREFIX) @@ -2152,6 +2293,7 @@ class FlowSpecDestPrefix(_FlowSpecPrefixBase): """ Destination Prefix for Flow Specification NLRI component """ + COMPONENT_NAME = 'dst_prefix' @_FlowSpecComponentBase.register_type( @@ -2160,6 +2302,7 @@ class FlowSpecSrcPrefix(_FlowSpecPrefixBase): """ Source Prefix for Flow Specification NLRI component """ + COMPONENT_NAME = 'src_prefix' class _FlowSpecOperatorBase(_FlowSpecComponentBase): @@ -2172,15 +2315,21 @@ class _FlowSpecOperatorBase(_FlowSpecComponentBase): value Value of component. ===================== =============================================== """ - _OPE_PACK_STR = '!B' _OPE_PACK_STR_SIZE = struct.calcsize(_OPE_PACK_STR) _VAL_PACK_STR = '!%ds' END_OF_LIST = 1 << 7 # END OF LIST bit AND = 1 << 6 # AND bit + OR = 0 # OR _LENGTH_BIT_MASK = 0x30 # The mask for length of the value + _logical_conditions = { + "|": OR, + "&": AND, + } + _comparison_conditions = {} + def __init__(self, operator, value, type_=None): super(_FlowSpecOperatorBase, self).__init__(type_) self.operator = operator @@ -2208,6 +2357,52 @@ class _FlowSpecOperatorBase(_FlowSpecComponentBase): return buf + @classmethod + def from_str(cls, val): + operator = 0 + rules = [] + + # e.g.) + # value = '80 | ==90|>=8000&<=9000 | <100 & >110' + # elements = ['80', '|', '==', '90', '|', '>=', '8000', '&', + # '<=', '9000', '|', '<', '100', '&', '>', '110'] + elements = [v.strip() for v in re.split( + r'([0-9]+)|([A-Z]+)|(\|&\+)|([!=<>]+)', val) if v and v.strip()] + + elms_iter = iter(elements) + + for elm in elms_iter: + if elm in cls._logical_conditions: + # ['&', '|'] + operator |= cls._logical_conditions[elm] + continue + elif elm in cls._comparison_conditions: + # ['=', '<', '>', '<=', '>=' ] or ['=', '!='] + operator |= cls._comparison_conditions[elm] + continue + elif elm == '+': + # If keyword "+" is used, add the value to the previous rule. + # e.g.) 'SYN+ACK' or '!=SYN+ACK' + rules[-1].value |= cls._to_value(next(elms_iter)) + continue + + value = cls._to_value(elm) + + operator = cls.normalize_operator(operator) + + rules.append(cls(operator, value)) + operator = 0 + + return rules + + @classmethod + def _to_value(cls, value): + return value + + @classmethod + def normalize_operator(cls, operator): + return operator + class _FlowSpecNumeric(_FlowSpecOperatorBase): """ @@ -2223,6 +2418,29 @@ class _FlowSpecNumeric(_FlowSpecOperatorBase): GT = 1 << 1 # Greater than comparison bit EQ = 1 << 0 # Equality bit + _comparison_conditions = { + '==': EQ, + '<': LT, + '>': GT, + '<=': LT | EQ, + '>=': GT | EQ + } + + @classmethod + def _to_value(cls, value): + try: + return int(str(value), 0) + except ValueError: + raise ValueError('Invalid params: %s="%s"' % ( + cls.COMPONENT_NAME, value)) + + @classmethod + def normalize_operator(cls, operator): + if operator & (cls.LT | cls.GT | cls.EQ): + return operator + else: + return operator | cls.EQ + class _FlowSpecBitmask(_FlowSpecOperatorBase): """ @@ -2237,6 +2455,19 @@ class _FlowSpecBitmask(_FlowSpecOperatorBase): NOT = 1 << 1 # NOT bit MATCH = 1 << 0 # MATCH bit + _comparison_conditions = { + '!=': NOT, + '==': MATCH, + } + + @classmethod + def _to_value(cls, value): + try: + return cls.__dict__[value] + except KeyError: + raise ValueError('Invalid params: %s="%s"' % ( + cls.COMPONENT_NAME, value)) + @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_PROTOCOL) class FlowSpecIPProtocol(_FlowSpecNumeric): @@ -2244,6 +2475,7 @@ class FlowSpecIPProtocol(_FlowSpecNumeric): Set the IP protocol number at value. """ + COMPONENT_NAME = 'ip_proto' @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_PORT) @@ -2252,6 +2484,7 @@ class FlowSpecPort(_FlowSpecNumeric): Set the source or destination TCP/UDP ports at value. """ + COMPONENT_NAME = 'port' @_FlowSpecComponentBase.register_type( @@ -2261,6 +2494,7 @@ class FlowSpecDestPort(_FlowSpecNumeric): Set the destination port of a TCP or UDP packet at value. """ + COMPONENT_NAME = 'dst_port' @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_SOURCE_PORT) @@ -2269,6 +2503,7 @@ class FlowSpecSrcPort(_FlowSpecNumeric): Set the source port of a TCP or UDP packet at value. """ + COMPONENT_NAME = 'src_port' @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_ICMP) @@ -2277,6 +2512,7 @@ class FlowSpecIcmpType(_FlowSpecNumeric): Set the type field of an ICMP packet at value. """ + COMPONENT_NAME = 'icmp_type' @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_ICMP_CODE) @@ -2285,6 +2521,7 @@ class FlowSpecIcmpCode(_FlowSpecNumeric): Set the code field of an ICMP packet at value. """ + COMPONENT_NAME = 'icmp_code' @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_TCP_FLAGS) @@ -2293,6 +2530,7 @@ class FlowSpecTCPFlags(_FlowSpecBitmask): Supported TCP flags are CWR, ECN, URGENT, ACK, PUSH, RST, SYN and FIN. """ + COMPONENT_NAME = 'tcp_flags' # bitmask format # 0 1 2 3 4 5 6 7 @@ -2300,8 +2538,8 @@ class FlowSpecTCPFlags(_FlowSpecBitmask): # |CWR |ECN |URG |ACK |PSH |RST |SYN |FIN | # +----+----+----+----+----+----+----+----+ - CWR = 1 << 6 - ECN = 1 << 5 + CWR = 1 << 7 + ECN = 1 << 6 URGENT = 1 << 5 ACK = 1 << 4 PUSH = 1 << 3 @@ -2317,6 +2555,7 @@ class FlowSpecPacketLen(_FlowSpecNumeric): Set the total IP packet length at value. """ + COMPONENT_NAME = 'packet_len' @_FlowSpecComponentBase.register_type( @@ -2326,6 +2565,7 @@ class FlowSpecDSCP(_FlowSpecNumeric): Set the 6-bit DSCP field at value. [RFC2474] """ + COMPONENT_NAME = 'dscp' @_FlowSpecComponentBase.register_type(_FlowSpecComponentBase.TYPE_FRAGMENT) @@ -2344,6 +2584,7 @@ class FlowSpecFragment(_FlowSpecBitmask): DF Don't fragment ========== =============================================== """ + COMPONENT_NAME = 'fragment' # bitmask format # 0 1 2 3 4 5 6 7 |