diff options
author | ISHIDA Wataru <ishida.wataru@lab.ntt.co.jp> | 2014-06-02 11:44:34 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2014-06-02 12:01:39 +0900 |
commit | 49dcfa5c0deb5097c0e0cce2a4fc666abfe0e176 (patch) | |
tree | 8ac7a0aad7f0aed7dab2de178787f33e3600b4dd | |
parent | 44725bce37ec67d84e59d176429eb45d6dadd79e (diff) |
packet/bgp: add route distinguisher classes and refine codes
Signed-off-by: ISHIDA Wataru <ishida.wataru@lab.ntt.co.jp>
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
-rw-r--r-- | ryu/lib/packet/bgp.py | 318 | ||||
-rw-r--r-- | ryu/services/protocols/bgp/info_base/base.py | 2 | ||||
-rw-r--r-- | ryu/tests/unit/packet/test_bgp.py | 8 |
3 files changed, 221 insertions, 107 deletions
diff --git a/ryu/lib/packet/bgp.py b/ryu/lib/packet/bgp.py index 13858db5..045e6f60 100644 --- a/ryu/lib/packet/bgp.py +++ b/ryu/lib/packet/bgp.py @@ -142,6 +142,72 @@ BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION = 7 BGP_ERROR_SUB_OUT_OF_RESOURCES = 8 +class _Value(object): + _VALUE_PACK_STR = None + _VALUE_FIELDS = ['value'] + + @staticmethod + def do_init(cls, self, kwargs, **extra_kwargs): + ourfields = {} + for f in cls._VALUE_FIELDS: + v = kwargs[f] + del kwargs[f] + ourfields[f] = v + kwargs.update(extra_kwargs) + super(cls, self).__init__(**kwargs) + self.__dict__.update(ourfields) + + @classmethod + def parse_value(cls, buf): + values = struct.unpack_from(cls._VALUE_PACK_STR, buffer(buf)) + return dict(zip(cls._VALUE_FIELDS, values)) + + def serialize_value(self): + args = [] + for f in self._VALUE_FIELDS: + args.append(getattr(self, f)) + buf = bytearray() + msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args) + return buf + + +class _TypeDisp(object): + _TYPES = {} + _REV_TYPES = None + _UNKNOWN_TYPE = None + + @classmethod + def register_unknown_type(cls): + def _register_type(subcls): + cls._UNKNOWN_TYPE = subcls + return subcls + return _register_type + + @classmethod + def register_type(cls, type_): + cls._TYPES = cls._TYPES.copy() + + def _register_type(subcls): + cls._TYPES[type_] = subcls + cls._REV_TYPES = None + return subcls + return _register_type + + @classmethod + def _lookup_type(cls, type_): + try: + return cls._TYPES[type_] + except KeyError: + return cls._UNKNOWN_TYPE + + @classmethod + def _rev_lookup_type(cls, targ_cls): + if cls._REV_TYPES is None: + rev = dict((v, k) for k, v in cls._TYPES.iteritems()) + cls._REV_TYPES = rev + return cls._REV_TYPES[targ_cls] + + class BgpExc(Exception): """Base bgp exception.""" @@ -505,12 +571,116 @@ RF_IPv4_VPN = RouteFamily(afi.IP, safi.MPLS_VPN) RF_IPv6_VPN = RouteFamily(afi.IP6, safi.MPLS_VPN) RF_RTC_UC = RouteFamily(afi.IP, safi.ROUTE_TARGET_CONSTRTAINS) +_rf_map = { + (afi.IP, safi.UNICAST): RF_IPv4_UC, + (afi.IP6, safi.UNICAST): RF_IPv6_UC, + (afi.IP, safi.MPLS_VPN): RF_IPv4_VPN, + (afi.IP6, safi.MPLS_VPN): RF_IPv6_VPN, + (afi.IP, safi.ROUTE_TARGET_CONSTRTAINS): RF_RTC_UC +} + def pad(bin, len_): assert len(bin) <= len_ return bin + (len_ - len(bin)) * '\0' +class _RouteDistinguisher(StringifyMixin, _TypeDisp, _Value): + _PACK_STR = '!H' + TWO_OCTET_AS = 0 + IPV4_ADDRESS = 1 + FOUR_OCTET_AS = 2 + + def __init__(self, type_, admin=0, assigned=0): + self.type = type_ + self.admin = admin + self.assigned = assigned + + @classmethod + def parser(cls, buf): + assert len(buf) == 8 + (type_,) = struct.unpack_from(cls._PACK_STR, buffer(buf)) + rest = buf[struct.calcsize(cls._PACK_STR):] + subcls = cls._lookup_type(type_) + return subcls(type_=type_, **subcls.parse_value(rest)) + + @classmethod + def from_str(cls, str_): + assert isinstance(str_, str) + + first, second = str_.split(':') + if '.' in first: + type_ = cls.IPV4_ADDRESS + elif int(first) > (1 << 16): + type_ = cls.FOUR_OCTET_AS + first = int(first) + else: + type_ = cls.TWO_OCTET_AS + first = int(first) + subcls = cls._lookup_type(type_) + return subcls(type_=type_, admin=first, assigned=int(second)) + + def serialize(self): + value = self.serialize_value() + buf = bytearray() + msg_pack_into(self._PACK_STR, buf, 0, self.type) + return buf + value + + @property + def formatted_str(self): + return "%s:%s" % (str(self.admin), str(self.assigned)) + + +@_RouteDistinguisher.register_type(_RouteDistinguisher.TWO_OCTET_AS) +class BGPTwoOctetAsRD(_RouteDistinguisher): + _VALUE_PACK_STR = '!HI' + _VALUE_FIELDS = ['admin', 'assigned'] + + def __init__(self, type_=_RouteDistinguisher.TWO_OCTET_AS, **kwargs): + self.do_init(BGPTwoOctetAsRD, self, kwargs, type_=type_) + + +@_RouteDistinguisher.register_type(_RouteDistinguisher.IPV4_ADDRESS) +class BGPIPv4AddressRD(_RouteDistinguisher): + _VALUE_PACK_STR = '!4sH' + _VALUE_FIELDS = ['admin', 'assigned'] + _TYPE = { + 'ascii': [ + 'admin' + ] + } + + def __init__(self, type_=_RouteDistinguisher.IPV4_ADDRESS, **kwargs): + self.do_init(BGPIPv4AddressRD, self, kwargs, type_=type_) + + @classmethod + def parse_value(cls, buf): + d_ = super(BGPIPv4AddressRD, cls).parse_value(buf) + d_['admin'] = addrconv.ipv4.bin_to_text(d_['admin']) + return d_ + + def serialize_value(self): + args = [] + for f in self._VALUE_FIELDS: + v = getattr(self, f) + if f == 'admin': + v = bytes(addrconv.ipv4.text_to_bin(v)) + args.append(v) + buf = bytearray() + msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args) + return buf + + +@_RouteDistinguisher.register_type(_RouteDistinguisher.FOUR_OCTET_AS) +class BGPFourOctetAsRD(_RouteDistinguisher): + _VALUE_PACK_STR = '!IH' + _VALUE_FIELDS = ['admin', 'assigned'] + + def __init__(self, type_=_RouteDistinguisher.FOUR_OCTET_AS, + **kwargs): + self.do_init(BGPFourOctetAsRD, self, kwargs, type_=type_) + + @six.add_metaclass(abc.ABCMeta) class _AddrPrefix(StringifyMixin): _PACK_STR = '!B' # length @@ -690,6 +860,10 @@ class _VPNAddrPrefix(_AddrPrefix): addr = addr[1:] else: length += struct.calcsize(self._RD_PACK_STR) * 8 + + if isinstance(route_dist, str): + route_dist = _RouteDistinguisher.from_str(route_dist) + prefixes = prefixes + (route_dist,) super(_VPNAddrPrefix, self).__init__(prefixes=prefixes, length=length, @@ -699,15 +873,14 @@ class _VPNAddrPrefix(_AddrPrefix): def _prefix_to_bin(cls, addr): rd = addr[0] rest = addr[1:] - binrd = bytearray() - msg_pack_into(cls._RD_PACK_STR, binrd, 0, rd) + binrd = rd.serialize() return binrd + super(_VPNAddrPrefix, cls)._prefix_to_bin(rest) @classmethod def _prefix_from_bin(cls, binaddr): binrd = binaddr[:8] binrest = binaddr[8:] - (rd,) = struct.unpack_from(cls._RD_PACK_STR, buffer(binrd)) + rd = _RouteDistinguisher.parser(binrd) return (rd,) + super(_VPNAddrPrefix, cls)._prefix_from_bin(binrest) @@ -741,11 +914,13 @@ class LabelledVPNIPAddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix, @property def prefix(self): - return self.addr[-1] + '/{0}'.format(self.length) + masklen = self.length - struct.calcsize(self._RD_PACK_STR) * 8 \ + - struct.calcsize(self._LABEL_PACK_STR) * 8 * len(self.addr[:-2]) + return self.addr[-1] + '/{0}'.format(masklen) @property - def route_disc(self): - return self.addr[-2] + def route_dist(self): + return self.addr[-2].formatted_str @property def label_list(self): @@ -762,11 +937,13 @@ class LabelledVPNIP6AddrPrefix(_LabelledAddrPrefix, _VPNAddrPrefix, @property def prefix(self): - return self.addr[-1] + '/{0}'.format(self.length) + masklen = self.length - struct.calcsize(self._RD_PACK_STR) * 8 \ + - struct.calcsize(self._LABEL_PACK_STR) * 8 * len(self.addr[:-2]) + return self.addr[-1] + '/{0}'.format(masklen) @property - def route_disc(self): - return self.addr[-2] + def route_dist(self): + return self.addr[-2].formatted_str @property def label_list(self): @@ -861,27 +1038,7 @@ class RouteTargetMembershipNLRI(StringifyMixin): idx += 4 # Extract route target. - route_target = '' - etype, esubtype, payload = struct.unpack_from('BB6s', buf, idx) - # RFC says: The value of the high-order octet of the Type field for the - # Route Target Community can be 0x00, 0x01, or 0x02. The value of the - # low-order octet of the Type field for this community is 0x02. - # TODO(PH): Remove this exception when it breaks something Here we make - # exception as Routem packs lower-order octet as 0x00 - if etype in (0, 2) and esubtype in (0, 2): - # If we have route target community in AS number format. - asnum, i = struct.unpack('!HI', payload) - route_target = ('%s:%s' % (asnum, i)) - elif etype == 1 and esubtype == 2: - # If we have route target community in IP address format. - ip_addr, i = struct.unpack('!4sH', payload) - ip_addr = socket.inet_ntoa(ip_addr) - route_target = ('%s:%s' % (ip_addr, i)) - elif etype == 0 and esubtype == 1: - # TODO(PH): Parsing of RtNlri 1:1:100:1 - asnum, i = struct.unpack('!HI', payload) - route_target = ('%s:%s' % (asnum, i)) - + route_target = _ExtendedCommunity(buf[idx:]) return cls(origin_as, route_target) def serialize(self): @@ -889,12 +1046,7 @@ class RouteTargetMembershipNLRI(StringifyMixin): if not self.is_default_rtnlri(): rt_nlri += struct.pack('!I', self.origin_as) # Encode route target - first, second = self.route_target.split(':') - if '.' in first: - ip_addr = socket.inet_aton(first) - rt_nlri += struct.pack('!BB4sH', 1, 2, ip_addr, int(second)) - else: - rt_nlri += struct.pack('!BBHI', 0, 2, int(first), int(second)) + rt_nlri += self.route_target.serialize() # RT Nlri is 12 octets return struct.pack('B', (8 * 12)) + rt_nlri @@ -917,72 +1069,6 @@ def _get_addr_class(afi, safi): return _BinAddrPrefix -class _Value(object): - _VALUE_PACK_STR = None - _VALUE_FIELDS = ['value'] - - @staticmethod - def do_init(cls, self, kwargs, **extra_kwargs): - ourfields = {} - for f in cls._VALUE_FIELDS: - v = kwargs[f] - del kwargs[f] - ourfields[f] = v - kwargs.update(extra_kwargs) - super(cls, self).__init__(**kwargs) - self.__dict__.update(ourfields) - - @classmethod - def parse_value(cls, buf): - values = struct.unpack_from(cls._VALUE_PACK_STR, buffer(buf)) - return dict(zip(cls._VALUE_FIELDS, values)) - - def serialize_value(self): - args = [] - for f in self._VALUE_FIELDS: - args.append(getattr(self, f)) - buf = bytearray() - msg_pack_into(self._VALUE_PACK_STR, buf, 0, *args) - return buf - - -class _TypeDisp(object): - _TYPES = {} - _REV_TYPES = None - _UNKNOWN_TYPE = None - - @classmethod - def register_unknown_type(cls): - def _register_type(subcls): - cls._UNKNOWN_TYPE = subcls - return subcls - return _register_type - - @classmethod - def register_type(cls, type_): - cls._TYPES = cls._TYPES.copy() - - def _register_type(subcls): - cls._TYPES[type_] = subcls - cls._REV_TYPES = None - return subcls - return _register_type - - @classmethod - def _lookup_type(cls, type_): - try: - return cls._TYPES[type_] - except KeyError: - return cls._UNKNOWN_TYPE - - @classmethod - def _rev_lookup_type(cls, targ_cls): - if cls._REV_TYPES is None: - rev = dict((v, k) for k, v in cls._TYPES.iteritems()) - cls._REV_TYPES = rev - return cls._REV_TYPES[targ_cls] - - class _OptParam(StringifyMixin, _TypeDisp, _Value): _PACK_STR = '!BB' # type, length @@ -1549,6 +1635,26 @@ class BGPPathAttributeExtendedCommunities(_PathAttribute): buf += comm.serialize() return buf + def _community_list(self, subtype): + _list = [] + for comm in (c for c in self.communities + if hasattr(c, "subtype") and c.subtype == subtype): + if comm.type == 0 or comm.type == 2: + _list.append('%d:%d' % (comm.as_number, + comm.local_administrator)) + elif comm.type == 1: + _list.append('%s:%d' % (comm.ipv4_address, + comm.local_administrator)) + return _list + + @property + def rt_list(self): + return self._community_list(2) + + @property + def soo_list(self): + return self._community_list(3) + class _ExtendedCommunity(StringifyMixin, _TypeDisp, _Value): _PACK_STR = '!B7s' # type high (+ type low) + value @@ -1716,6 +1822,10 @@ class BGPPathAttributeMpReachNLRI(_PathAttribute): buf += binnlri return buf + @property + def route_family(self): + return _rf_map[(self.afi, self.safi)] + @_PathAttribute.register_type(BGP_ATTR_TYPE_MP_UNREACH_NLRI) class BGPPathAttributeMpUnreachNLRI(_PathAttribute): @@ -1759,6 +1869,10 @@ class BGPPathAttributeMpUnreachNLRI(_PathAttribute): buf += binnlri return buf + @property + def route_family(self): + return _rf_map[(self.afi, self.safi)] + class BGPNLRI(IPAddrPrefix): pass diff --git a/ryu/services/protocols/bgp/info_base/base.py b/ryu/services/protocols/bgp/info_base/base.py index c735c309..9d177c59 100644 --- a/ryu/services/protocols/bgp/info_base/base.py +++ b/ryu/services/protocols/bgp/info_base/base.py @@ -765,7 +765,7 @@ class Path(object): if extcomm_attr is None: rts = [] else: - rts = extcomm_attr.rt_list[:] + rts = extcomm_attr.rt_list return rts def has_rts_in(self, interested_rts): diff --git a/ryu/tests/unit/packet/test_bgp.py b/ryu/tests/unit/packet/test_bgp.py index 6ae8dca4..00804dd2 100644 --- a/ryu/tests/unit/packet/test_bgp.py +++ b/ryu/tests/unit/packet/test_bgp.py @@ -80,10 +80,10 @@ class Test_bgp(unittest.TestCase): addr='192.0.2.13')] mp_nlri = [ bgp.LabelledVPNIPAddrPrefix(24, '192.0.9.0', - route_dist=100, + route_dist='100:100', labels=[1, 2, 3]), bgp.LabelledVPNIPAddrPrefix(26, '192.0.10.192', - route_dist=4000000000, + route_dist='10.0.0.1:10000', labels=[5, 6, 7, 8]), ] communities = [ @@ -223,10 +223,10 @@ class Test_bgp(unittest.TestCase): addr='192.0.2.13')] mp_nlri = [ bgp.LabelledVPNIPAddrPrefix(24, '192.0.9.0', - route_dist=100, + route_dist='100:100', labels=[1, 2, 3]), bgp.LabelledVPNIPAddrPrefix(26, '192.0.10.192', - route_dist=4000000000, + route_dist='10.0.0.1:10000', labels=[5, 6, 7, 8]), ] communities = [ |