def unpack (cls, exdata): data = exdata # Get the data length to understand if addresses are IPv4 or IPv6 datalen = len(data) rd = RouteDistinguisher.unpack(data[:8]) data = data[8:] esi = ESI.unpack(data[:10]) data = data[10:] etag = EthernetTag.unpack(data[:4]) data = data[4:] iplen = ord(data[0]) data = data[1:] if datalen == (26 + 8): # Using IPv4 addresses ip = IP.unpack(data[:4]) data = data[4:] gwip = IP.unpack(data[:4]) data = data[4:] elif datalen == (26 + 32): # Using IPv6 addresses ip = IP.unpack(data[:16]) data = data[16:] gwip = IP.unpack(data[:16]) data = data[16:] else: raise Notify(3,5,"Data field length is given as %d, but EVPN route currently support only IPv4 or IPv6(34 or 58)" % iplen) label = Labels.unpack(data[:3]) return cls(rd,esi,etag,label,ip,iplen,gwip,exdata)
def next_hop (self, scope, name, command, tokens): if scope[-1]['announce'][-1].attributes.has(Attribute.CODE.NEXT_HOP): return self.error.set(self.syntax) try: # next-hop self is unsupported ip = tokens.pop(0) if ip.lower() == 'self': if 'local-address' in scope[-1]: la = scope[-1]['local-address'] elif self._nexthopself: la = self._nexthopself else: return self.error.set('next-hop self can only be specified with a neighbor') nh = IP.unpack(la.pack()) else: nh = IP.create(ip) change = scope[-1]['announce'][-1] nlri = change.nlri afi = nlri.afi safi = nlri.safi nlri.nexthop = nh if afi == AFI.ipv4 and safi in (SAFI.unicast,SAFI.multicast): change.attributes.add(Attribute.unpack(NextHop.ID,NextHop.FLAG,nh.packed,None)) # NextHop(nh.ip,nh.packed) does not cache the result, using unpack does # change.attributes.add(NextHop(nh.ip,nh.packed)) return True except Exception: return self.error.set(self.syntax)
def test99_EVPNMACCreatePackUnpack(self): '''Test pack/unpack for E-VPN MAC routes''' nlri = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6*8, Labels([42], True), IP.create("1.1.1.1")) packed = nlri.pack() unpacked,leftover = EVPN.unpack_nlri(AFI(AFI.l2vpn), SAFI(SAFI.evpn), packed, OUT.UNSET, None) self.assertEqual(0, len(leftover)) # TODO: compare packed with a reference encoding verified # as conformant with RFC7432 self.assertTrue(isinstance(unpacked, EVPNMAC)) self.assertEqual("42.42.42.42:5", unpacked.rd._str()) self.assertEqual(ESI.DEFAULT, unpacked.esi.esi) self.assertEqual(EthernetTag(111), unpacked.etag) self.assertEqual(MAC("01:02:03:04:05:06"), unpacked.mac) self.assertEqual(IP.create("1.1.1.1"), unpacked.ip) self.assertEqual(1, len(unpacked.label.labels)) self.assertEqual(42, unpacked.label.labels[0])
def listen_on(self, local_addr, remote_addr, port, md5_password, md5_base64, ttl_in): try: if not remote_addr: remote_addr = IP.create( '0.0.0.0') if local_addr.ipv4() else IP.create('::') self._listen(local_addr, remote_addr, port, md5_password, md5_base64, ttl_in) self.logger.debug( 'listening for BGP session(s) on %s:%d%s' % (local_addr, port, ' with MD5' if md5_password else ''), 'network') return True except NetworkError as exc: if os.geteuid() != 0 and port <= 1024: self.logger.critical( 'can not bind to %s:%d, you may need to run ExaBGP as root' % (local_addr, port), 'network') else: self.logger.critical( 'can not bind to %s:%d (%s)' % (local_addr, port, str(exc)), 'network') self.logger.critical( 'unset exabgp.tcp.bind if you do not want listen for incoming connections', 'network') self.logger.critical( 'and check that no other daemon is already binding to port %d' % port, 'network') return False
def insert_static_route (self, scope, name, command, tokens): try: ip = tokens.pop(0) except IndexError: return self.error.set(self.syntax) try: ip,mask = ip.split('/') mask = int(mask) except ValueError: mask = 32 try: if 'rd' in tokens: klass = MPLS elif 'route-distinguisher' in tokens: klass = MPLS elif 'label' in tokens: klass = MPLS else: klass = INET # nexthop must be false and its str return nothing .. an empty string does that update = Change(klass(afi=IP.toafi(ip),safi=IP.tosafi(ip),packed=IP.pton(ip),mask=mask,nexthop=None,action=OUT.ANNOUNCE),Attributes()) except ValueError: return self.error.set(self.syntax) if 'announce' not in scope[-1]: scope[-1]['announce'] = [] scope[-1]['announce'].append(update) return True
def parse_api_attribute(self, command, peers, action): # This is a quick solution which does not support next-hop self attribute, nlris = command.split('nlri') route = '%s route 0.0.0.0/0 %s' % (action, ' '.join( attribute.split()[2:])) parsed = self.parse_api_route(route, peers, action) if parsed in (True, False, None): return parsed attributes = parsed[0][1].attributes nexthop = parsed[0][1].nlri.nexthop changes = [] for nlri in nlris.split(): ip, mask = nlri.split('/') klass = Prefix if 'path-information' in command else MPLS change = Change( klass(afi=IP.toafi(ip), safi=IP.tosafi(ip), packed=IP.pton(ip), mask=int(mask), nexthop=nexthop.packed, action=action), attributes) if action == 'withdraw': change.nlri.action = OUT.withdraw else: change.nlri.action = OUT.announce changes.append((peers.keys(), change)) return changes
def redirect (tokeniser): data = tokeniser() count = data.count(':') if count == 0: return IP.create(data), ExtendedCommunities().add(TrafficNextHopSimpson(False)) if count == 1: prefix,suffix = data.split(':',1) if prefix.count('.'): raise ValueError('this format has been deprecated as it does not make sense and it is not supported by other vendors') asn = int(prefix) route_target = int(suffix) if asn >= pow(2, 32): raise ValueError('asn is a 32 bits number, value too large %s' % asn) if asn >= pow(2,16): if route_target >= pow(2, 16): raise ValueError('asn is a 32 bits number, route target can only be 16 bit %s' % route_target) return NoNextHop, ExtendedCommunities().add(TrafficRedirectASN4(asn, route_target)) if route_target >= pow(2,32): raise ValueError('route target is a 32 bits number, value too large %s' % route_target) return NoNextHop,ExtendedCommunities().add(TrafficRedirect(asn,route_target)) else: elements = data.split(':') ip = ':'.join(elements[:-1]) asn = int(elements[-1]) return IP.create(ip), ExtendedCommunities().add(TrafficRedirectIPv6(ip,asn))
def api_attribute (self, command, peers, action): # This is a quick solution which does not support next-hop self attribute,nlris = command.split('nlri') route = '%s route 0.0.0.0/0 %s' % (action, ' '.join(attribute.split()[2:])) parsed = self.api_route(peers,route) if parsed in (True,False,None): return parsed attributes = parsed[0][1].attributes nexthop = parsed[0][1].nlri.nexthop changes = [] for nlri in nlris.split(): ip,mask = nlri.split('/') klass = MPLS if 'path-information' in command else INET change = Change( klass( afi=IP.toafi(ip), safi=IP.tosafi(ip), packed=IP.pton(ip), mask=int(mask), nexthop='', # could be NextHopSelf action=action ), attributes ) change.nlri.nexthop = nexthop if action == 'withdraw': change.nlri.action = OUT.WITHDRAW else: change.nlri.action = OUT.ANNOUNCE changes.append((peers.keys(),change)) return changes
def test100_EVPNMACHashEqual(self): ''' Two indistinct EVPN NLRI should hash to the same value, and be equal ''' nlri1 = EVPNMAC( RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1"), ) nlri2 = EVPNMAC( RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1"), ) self.assertEqual(hash(nlri1), hash(nlri2)) self.assertEqual(nlri1, nlri2)
def test99_EVPNMACCreatePackUnpack(self): '''Test pack/unpack for E-VPN MAC routes''' nlri = EVPNMAC( RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1"), ) packed = nlri.pack() unpacked, leftover = EVPN.unpack_nlri(AFI.l2vpn, SAFI.evpn, packed, OUT.UNSET, None) self.assertEqual(0, len(leftover)) # TODO: compare packed with a reference encoding verified # as conformant with RFC7432 self.assertTrue(isinstance(unpacked, EVPNMAC)) self.assertEqual("42.42.42.42:5", unpacked.rd._str()) self.assertEqual(ESI.DEFAULT, unpacked.esi.esi) self.assertEqual(EthernetTag(111), unpacked.etag) self.assertEqual(MAC("01:02:03:04:05:06"), unpacked.mac) self.assertEqual(IP.create("1.1.1.1"), unpacked.ip) self.assertEqual(1, len(unpacked.label.labels)) self.assertEqual(42, unpacked.label.labels[0])
def parse_api_attribute (self,command,peers,action): # This is a quick solution which does not support next-hop self attribute,nlris = command.split('nlri') route = '%s route 0.0.0.0/0 %s' % (action, ' '.join(attribute.split()[2:])) parsed = self.parse_api_route(route,peers,action) if parsed in (True,False,None): return parsed attributes = parsed[0][1].attributes nexthop = parsed[0][1].nlri.nexthop changes = [] for nlri in nlris.split(): ip,mask = nlri.split('/') klass = Prefix if 'path-information' in command else MPLS change = Change( klass( afi=IP.toafi(ip), safi=IP.tosafi(ip), packed=IP.pton(ip), mask=int(mask), nexthop=nexthop.packed, action=action ) ,attributes ) if action == 'withdraw': change.nlri.action = OUT.withdraw else: change.nlri.action = OUT.announce changes.append((peers.keys(),change)) return changes
def test99_EVPNPrefixCreatePackUnpack(self): '''Test pack/unpack for E-VPN Prefix routes''' nlri = EVPNPrefix(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), Labels([42], True), IP.create("1.1.1.0"),24, IP.create("2.2.2.2"), ) packed = nlri.pack() unpacked,leftover = EVPN.unpack_nlri(AFI.l2vpn, SAFI.evpn, packed, OUT.UNSET, None) self.assertEqual(0, len(leftover)) # TODO: compare packed with a reference encoding verified # as conformant with RFC7432 self.assertTrue(isinstance(unpacked, EVPNPrefix)) self.assertEqual("42.42.42.42:5", unpacked.rd._str()) self.assertEqual(ESI.DEFAULT, unpacked.esi.esi) self.assertEqual(EthernetTag(111), unpacked.etag) self.assertEqual(IP.create("1.1.1.0"), unpacked.ip) self.assertEqual(24, unpacked.iplen) self.assertEqual(IP.create("2.2.2.2"), unpacked.gwip) self.assertEqual(1, len(unpacked.label.labels)) self.assertEqual(42, unpacked.label.labels[0])
def test3_IPVPNHashEqual(self): ''' Two VPN NLRI distinct only by their *action* should hash to the same value, and be equal ''' packedPrefix, mask = prefixToPackedIPMask("1.1.1.1/32") nlri1 = IPVPN.new(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), packedPrefix, mask, Labels([42], True), RouteDistinguisher.fromElements("42.42.42.42", 5), IP.pton("45.45.45.45"), OUT.ANNOUNCE) nlri2 = IPVPN.new(AFI(AFI.ipv4), SAFI(SAFI.mpls_vpn), packedPrefix, mask, Labels([42], True), RouteDistinguisher.fromElements("42.42.42.42", 5), IP.pton("45.45.45.45"), OUT.WITHDRAW) self.assertEqual(hash(nlri1), hash(nlri2)) self.assertTrue(nlri1.eq(nlri2)) self.assertEqual(nlri1, nlri2)
def unpack (cls,data,length): if len(data) == 4: # IPv4 address terid = IP.unpack(data[:4]) elif len(data) == 16: # IPv6 terid = IP.unpack(data[:16]) return cls(terid=terid)
def inet(tokeniser): ipmask = prefix(tokeniser) inet = INET(afi=IP.toafi(ipmask.top()), safi=IP.tosafi(ipmask.top()), action=OUT.UNSET) inet.cidr = CIDR(ipmask.ton(), ipmask.mask) return Change(inet, Attributes())
def unpack (cls, data): if len(data) == 4: # IPv4 address addr = IP.unpack(data[:4]) elif len(data) == 16: # IPv6 addr = IP.unpack(data[:16]) return cls(iface_addr=addr)
def unpack(cls, data): if len(data) == 4: # IPv4 address addr = IP.unpack(data[:4]) elif len(data) == 16: # IPv6 addr = IP.unpack(data[:16]) return cls(iface_addr=addr)
def unpack(cls, data, length): if len(data) == 4: # IPv4 address terid = IP.unpack(data[:4]) elif len(data) == 16: # IPv6 terid = IP.unpack(data[:16]) return cls(terid=terid)
def mpls(tokeniser): ipmask = prefix(tokeniser) mpls = IPVPN(afi=IP.toafi(ipmask.top()), safi=IP.tosafi(ipmask.top()), action=OUT.ANNOUNCE) mpls.cidr = CIDR(ipmask.ton(), ipmask.mask) return Change(mpls, Attributes())
def unpack (cls, afi, safi, data, addpath, nexthop, action): local_node_id = "" local_asn = "" local_ipv4 = "" remote_node_id = "" remote_asn = "" remote_ipv4 = "" nlri_type = data[0:2] nlri_length, = unpack('!H',data[2:4]) protocol_id = data[4:5] topology_type = data[5:13] nlri_offset = 13 nlri_cur_pos = 0 while nlri_offset < nlri_length: tlv_type, tlv_length, tlv_value = decode_tlv(data, nlri_offset, 2, 2) if tlv_type == LSTLV.LOCAL_NODE_DESCRIPTORS: node_length = tlv_length node_offset = nlri_offset + 4 node_cur_pos = 0 while node_cur_pos < node_length: node_tlv_type, node_tlv_length, node_tlv_value = decode_tlv(data, node_offset, 2, 2) if node_tlv_type == LSTLV.IGP_ROUTER_ID: local_node_id = str(IP.unpack(node_tlv_value)) if node_tlv_type == LSTLV.ASN: local_asn = str(int(binascii.hexlify(node_tlv_value), 16)) node_offset = node_offset+2+2+node_tlv_length node_cur_pos = node_cur_pos+2+2+node_tlv_length if tlv_type == LSTLV.REMOTE_NODE_DESCRIPTORS: node_length = tlv_length node_offset = nlri_offset + 4 node_cur_pos = 0 while node_cur_pos < node_length: node_tlv_type, node_tlv_length, node_tlv_value = decode_tlv(data, node_offset, 2, 2) if node_tlv_type == LSTLV.IGP_ROUTER_ID: print str(int(binascii.hexlify(node_tlv_value), 16)) remote_node_id = str(IP.unpack(node_tlv_value)) if node_tlv_type == LSTLV.ASN: remote_asn = str(int(binascii.hexlify(node_tlv_value), 16)) node_offset = node_offset+2+2+node_tlv_length node_cur_pos = node_cur_pos+2+2+node_tlv_length if tlv_type == LSTLV.LOCAL_IPV4: local_ipv4 = str(IP.unpack(tlv_value)) if tlv_type == LSTLV.REMOTE_IPV4: remote_ipv4 = str(IP.unpack(tlv_value)) nlri_offset = nlri_offset+2+2+tlv_length nlri_cur_pos = nlri_cur_pos +2+2+tlv_length nlri = cls(local_node_id, local_asn, remote_node_id, remote_asn, local_ipv4, remote_ipv4, action) nlri.action = action if action == 1: nlri.nexthop = IP.unpack(nexthop) return len(data), nlri
def unpack(cls, data, igp): node_type, length = unpack('!HH', data[0:4]) packed = data[:4 + length] payload = packed[4:] remaining = data[4 + length:] node_id = None dr_id = None psn = None # autonomous-system if node_type == 512: if length != 4: raise Exception(cls._error_tlvs[node_type]) node_id = unpack('!L', payload)[0] return cls(node_id, node_type, psn, dr_id, packed), remaining # bgp-ls-id if node_type == 513: if length != 4: raise Exception(cls._error_tlvs[node_type]) node_id = unpack('!L', payload)[0] return cls(node_id, node_type, psn, dr_id, packed), remaining # ospf-area-id if node_type == 514: if length not in (4, 16): # FIXME: it may only need to be 4 raise Exception(cls._error_tlvs[node_type]) node_id = IP.unpack(payload) return cls(node_id, node_type, psn, dr_id, packed), remaining # IGP Router-ID: The TLV size in combination with the protocol # identifier enables the decoder to determine the node_typee # of the node: sec 3.2.1.4. if node_type == 515: # IS-IS non-pseudonode if igp in (1, 2): if length not in (6, 7): raise Exception(cls._error_tlvs[node_type]) node_id = ISO.unpack_sysid(payload), if length == 7: psn = unpack('!B', payload[6:7])[0] return cls(node_id, node_type, psn, dr_id, packed), remaining # OSPFv{2,3} non-pseudonode if igp in (3, 5, 6, 227): if length not in (4, 8): raise Exception(cls._error_tlvs[node_type]) node_id = IP.unpack(payload[:4]), if length == 8: dr_id = IP.unpack(payload[4:8]) return cls(node_id, node_type, psn, dr_id, packed), remaining raise Exception("unknown node descriptor sub-tlv ({}, {})".format( f'node-type: {node_type}', f'igp: {igp}', ))
def attributes(tokeniser): ipmask = prefix(lambda: tokeniser.tokens[-1]) if 'rd' in tokeniser.tokens or 'route-distinguisher' in tokeniser.tokens: nlri = IPVPN(IP.toafi(ipmask.top()), SAFI.mpls_vpn, OUT.ANNOUNCE) elif 'label' in tokeniser.tokens: nlri = Labelled(IP.toafi(ipmask.top()), SAFI.nlri_mpls, OUT.ANNOUNCE) else: nlri = INET(IP.toafi(ipmask.top()), IP.tosafi(ipmask.top()), OUT.ANNOUNCE) nlri.cidr = CIDR(ipmask.pack(), ipmask.mask) change = Change(nlri, Attributes()) while True: command = tokeniser() if not command: return [] if command == 'nlri': break action = ParseStatic.action[command] if action == 'attribute-add': change.attributes.add(ParseStatic.known[command](tokeniser)) elif action == 'nlri-set': change.nlri.assign(ParseStatic.assign[command], ParseStatic.known[command](tokeniser)) elif action == 'nexthop-and-attribute': nexthop, attribute = ParseStatic.known[command](tokeniser) change.nlri.nexthop = nexthop change.attributes.add(attribute) else: raise ValueError('route: unknown command "%s"' % command) attributes = change.attributes nexthop = change.nlri.nexthop changes = [] while True: nlri = tokeniser.peek() if not nlri: break ipmask = prefix(tokeniser) new = Change( change.nlri.__class__(change.nlri.afi, change.nlri.safi, OUT.UNSET), attributes) new.nlri.cidr = CIDR(ipmask.pack(), ipmask.mask) new.nlri.nexthop = nexthop changes.append(new) return changes
def unpack (cls,data,length): if len(data) == 4: # IPv4 address addr = IP.unpack(data[:4]) elif len(data) == 16: # IPv6 addr = IP.unpack(data[:16]) else: raise Notify(3,5, "Error parsing OSPF Forwarding Address. Wrong size") return cls(addr=addr)
def MD5(io, ip, port, md5): if md5: os = platform.system() if os == 'FreeBSD': if md5 != 'kernel': raise MD5Error( 'FreeBSD requires that you set your MD5 key via ipsec.conf.\n' 'Something like:\n' 'flush;\n' 'add <local ip> <peer ip> tcp 0x1000 -A tcp-md5 "password";' ) try: TCP_MD5SIG = 0x10 io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, 1) except socket.error: raise MD5Error( 'FreeBSD requires that you rebuild your kernel to enable TCP MD5 Signatures:\n' 'options IPSEC\n' 'options TCP_SIGNATURE\n' 'device crypto\n') elif os == 'Linux': try: # __kernel_sockaddr_storage n_af = IP.toaf(ip) n_addr = IP.pton(ip) n_port = socket.htons(port) # pack 'x' is padding, so we want the struct # Do not use '!' for the pack, the network (big) endian switch in # struct.pack is fighting against inet_pton and htons (note the n) if IP.toafi(ip) == AFI.ipv4: # SS_MAXSIZE is 128 but addr_family, port and ipaddr (8 bytes total) are written independently of the padding SS_MAXSIZE_PADDING = 128 - calcsize('HH4s') # 8 sockaddr = pack('HH4s%dx' % SS_MAXSIZE_PADDING, socket.AF_INET, n_port, n_addr) else: SS_MAXSIZE_PADDING = 128 - calcsize('HI16sI') # 28 SIN6_FLOWINFO = 0 SIN6_SCOPE_ID = 0 sockaddr = pack('HHI16sI%dx' % SS_MAXSIZE_PADDING, n_af, n_port, SIN6_FLOWINFO, n_addr, SIN6_SCOPE_ID) TCP_MD5SIG_MAXKEYLEN = 80 key = pack('2xH4x%ds' % TCP_MD5SIG_MAXKEYLEN, len(md5), md5) TCP_MD5SIG = 14 io.setsockopt(socket.IPPROTO_TCP, TCP_MD5SIG, sockaddr + key) except socket.error, exc: raise MD5Error( 'This linux machine does not support TCP_MD5SIG, you can not use MD5 (%s)' % errstr(exc)) else: raise MD5Error('ExaBGP has no MD5 support for %s' % os)
def unpack(cls, data, length): if len(data) == 4: # IPv4 address addr = IP.unpack(data[:4]) elif len(data) == 16: # IPv6 addr = IP.unpack(data[:16]) else: raise Notify(3, 5, "Error parsing OSPF Forwarding Address. Wrong size") return cls(addr=addr)
def prefix(tokeniser): # XXX: could raise ip = tokeniser() try: ip, mask = ip.split('/') mask = int(mask) except ValueError: mask = 32 tokeniser.afi = IP.toafi(ip) return Range(ip, IP.pton(ip), mask)
def redirect(tokeniser): data = tokeniser() count = data.count(':') if count == 0: return IP.create(data), ExtendedCommunities().add( TrafficNextHopSimpson(False)) if count == 1: prefix, suffix = data.split(':', 1) if prefix.count('.'): raise ValueError( 'this format has been deprecated as it does not make sense and it is not supported by other vendors' ) asn = int(prefix) route_target = int(suffix) if asn >= pow(2, 32): raise ValueError('asn is a 32 bits number, value too large %s' % asn) if asn >= pow(2, 16): if route_target >= pow(2, 16): raise ValueError( 'asn is a 32 bits number, route target can only be 16 bit %s' % route_target) return NoNextHop, ExtendedCommunities().add( TrafficRedirectASN4(asn, route_target)) if route_target >= pow(2, 32): raise ValueError( 'route target is a 32 bits number, value too large %s' % route_target) return NoNextHop, ExtendedCommunities().add( TrafficRedirect(asn, route_target)) else: explicit_v6 = ']:' in data # ipv4 if not explicit_v6 and data.count(':') == 1: return IP.create(data), ExtendedCommunities().add( TrafficNextHopSimpson(False)) # ipv6 using []: notation if explicit_v6: ip, asn = data.split(']:') ip = ip.replace('[', '', 1) # FIXME: should this be 2^16 ?? if asn >= pow(2, 32): raise ValueError( 'asn is a 32 bits number, value too large %s' % asn) return IP.create(ip), ExtendedCommunities().add( TrafficRedirectIPv6(ip, asn)) raise ValueError( 'it looks like you tried to use an IPv6 but did not enclose it in []' )
def unpack(cls, data, igp): dtype, dlength = unpack('!HH', data[0:4]) if dtype not in NODE_TLVS.keys(): raise Exception("Unknown Node Descriptor Sub-TLV") # OSPF Area-ID if dtype == 514: return cls(node_id=IP.unpack(data[4:4 + dlength]), dtype=dtype, packed=data[:4 + dlength]), data[4 + dlength:] # IGP Router-ID: The TLV size in combination with the protocol # identifier enables the decoder to determine the type # of the node: sec 3.2.1.4. elif dtype == 515: # OSPFv{2,3} non-pseudonode if (igp == 3 or igp == 6) and dlength == 4: r_id = IP.unpack(data[4:4 + 4]) return cls(node_id=r_id, dtype=dtype, packed=data[:4 + dlength]), data[4 + 4:] # OSPFv{2,3} LAN pseudonode if (igp == 3 or igp == 6) and dlength == 8: r_id = IP.unpack(data[4:4 + 4]) dr_id = IP.unpack(data[8:4 + 8]) return cls(node_id=r_id, dtype=dtype, psn=None, dr_id=dr_id, packed=data[:4 + dlength]), data[4 + 8:] # IS-IS non-pseudonode if (igp == 1 or igp == 2) and dlength == 6: return cls(node_id=ISO.unpack_sysid(data[4:4 + 6]), dtype=dtype, packed=data[:4 + dlength]), data[4 + 6:] # IS-IS LAN pseudonode = ISO Node-ID + PSN # Unpack ISO address if (igp == 1 or igp == 2) and dlength == 7: iso_node = ISO.unpack_sysid(data[4:4 + 6]) psn = unpack('!B', data[4 + 6:4 + 7])[0] return cls(node_id=iso_node, dtype=dtype, psn=psn, packed=data[:4 + dlength]), data[4 + 7:] elif dtype == 512 and dlength == 4: # ASN return cls(node_id=unpack('!L', data[4:4 + dlength])[0], dtype=dtype, packed=data[:4 + dlength]), data[4 + 4:] elif dtype == 513 and dlength == 4: # BGP-LS return cls(node_id=unpack('!L', data[4:4 + dlength])[0], dtype=dtype, packed=data[:4 + dlength]), data[4 + 4:] else: raise Notify(3, 5, 'could not decode Local Node descriptor')
def prefix (tokeniser): # XXX: could raise ip = tokeniser() try: ip,mask = ip.split('/') mask = int(mask) except ValueError: mask = 32 tokeniser.afi = IP.toafi(ip) return Range(ip,IP.pton(ip),mask)
def route (tokeniser): action = OUT.ANNOUNCE if tokeniser.announce else OUT.WITHDRAW ipmask = prefix(tokeniser) check = lambda change,afi: True if 'rd' in tokeniser.tokens or 'route-distinguisher' in tokeniser.tokens: nlri = IPVPN(IP.toafi(ipmask.top()),SAFI.mpls_vpn,action) check = AnnounceVPN.check elif 'label' in tokeniser.tokens: nlri = Label(IP.toafi(ipmask.top()),SAFI.nlri_mpls,action) check = AnnounceLabel.check else: nlri = INET(IP.toafi(ipmask.top()), IP.tosafi(ipmask.top()), action) check = AnnouncePath.check nlri.cidr = CIDR(ipmask.pack(),ipmask.mask) change = Change( nlri, Attributes() ) while True: command = tokeniser() if not command: break if command == 'label': nlri.labels = label(tokeniser) continue if command == 'rd' or command == 'route-distinguisher': nlri.rd = route_distinguisher(tokeniser) continue action = ParseStatic.action.get(command,'') if action == 'attribute-add': change.attributes.add(ParseStatic.known[command](tokeniser)) elif action == 'nlri-set': change.nlri.assign(ParseStatic.assign[command],ParseStatic.known[command](tokeniser)) elif action == 'nexthop-and-attribute': nexthop,attribute = ParseStatic.known[command](tokeniser) change.nlri.nexthop = nexthop change.attributes.add(attribute) else: raise ValueError('unknown command "%s"' % command) if not check(change,nlri.afi): raise ValueError('invalid route (missing next-hop, label or rd ?)') return list(ParseStatic.split(change))
def destination(tokeniser): data = tokeniser() if data.count('.') == 3 and data.count(':') == 0: ip, netmask = data.split('/') raw = b''.join(bytes([int(_)]) for _ in ip.split('.')) yield Flow4Destination(raw, int(netmask)) elif data.count('/') == 1: ip, netmask = data.split('/') offset = 0 yield Flow6Destination(IP.pton(ip), int(netmask), int(offset)) else: ip, netmask, offset = data.split('/') yield Flow6Destination(IP.pton(ip), int(netmask), int(offset))
def inet (tokeniser): ipmask = prefix(tokeniser) inet = INET( afi=IP.toafi(ipmask.top()), safi=IP.tosafi(ipmask.top()), action=OUT.UNSET ) inet.cidr = CIDR(ipmask.ton(),ipmask.mask) return Change( inet, Attributes() )
def mpls (tokeniser): ipmask = prefix(tokeniser) mpls = IPVPN( afi=IP.toafi(ipmask.top()), safi=IP.tosafi(ipmask.top()), action=OUT.UNSET ) mpls.cidr = CIDR(ipmask.ton(),ipmask.mask) return Change( mpls, Attributes() )
def destination(tokeniser): data = tokeniser() if data.count('.') == 3 and data.count(':') == 0: ip, netmask = data.split('/') raw = concat_bytes_i(character(int(_)) for _ in ip.split('.')) yield Flow4Destination(raw, int(netmask)) elif data.count('/') == 1: ip, netmask = data.split('/') offset = 0 yield Flow6Destination(IP.pton(ip), int(netmask), int(offset)) else: ip, netmask, offset = data.split('/') yield Flow6Destination(IP.pton(ip), int(netmask), int(offset))
def source(tokeniser): data = tokeniser() if data.count('.') == 3 and data.count(':') == 0: ip, netmask = data.split('/') raw = ''.join(chr(int(_)) for _ in ip.split('.')) yield Flow4Source(raw, int(netmask)) elif data.count('/') == 1: ip, netmask = data.split('/') offset = 0 yield Flow6Source(IP.pton(ip), int(netmask), int(offset)) else: ip, netmask, offset = data.split('/') yield Flow6Source(IP.pton(ip), int(netmask), int(offset))
def inet (tokeniser): ipmask = prefix(tokeniser) return Change( INET( afi=IP.toafi(ipmask.string), safi=IP.tosafi(ipmask.string), packed=IP.pton(ipmask.string), mask=ipmask.mask, nexthop=None, action=OUT.UNSET ), Attributes() )
def destination (tokeniser): data = tokeniser() if data.count('.') == 3 and data.count(':') == 0: ip,netmask = data.split('/') raw = concat_bytes_i(character(int(_)) for _ in ip.split('.')) yield Flow4Destination(raw,int(netmask)) elif data.count('/') == 1: ip,netmask = data.split('/') offset = 0 yield Flow6Destination(IP.pton(ip),int(netmask),int(offset)) else: ip,netmask,offset = data.split('/') yield Flow6Destination(IP.pton(ip),int(netmask),int(offset))
def listen_on (self, local_addr, remote_addr, port, md5_password, md5_base64, ttl_in): try: if not remote_addr: remote_addr = IP.create('0.0.0.0') if local_addr.ipv4() else IP.create('::') self._listen(local_addr, remote_addr, port, md5_password, md5_base64, ttl_in) self.logger.debug('listening for BGP session(s) on %s:%d%s' % (local_addr, port,' with MD5' if md5_password else ''),'network') return True except NetworkError as exc: if os.geteuid() != 0 and port <= 1024: self.logger.critical('can not bind to %s:%d, you may need to run ExaBGP as root' % (local_addr, port),'network') else: self.logger.critical('can not bind to %s:%d (%s)' % (local_addr, port,str(exc)),'network') self.logger.critical('unset exabgp.tcp.bind if you do not want listen for incoming connections','network') self.logger.critical('and check that no other daemon is already binding to port %d' % port,'network') return False
def test101_EVPNHashEqual_somefieldsvary(self): ''' Two EVPN MAC NLRIs differing by their ESI or label or RD, or nexthop, but otherwise identical should hash to the same value, and be equal ''' nlri0 = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1")) # Esi nlri1 = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(['1' for _ in range(0, 10)]), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1")) # label nlri2 = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([4444], True), IP.create("1.1.1.1")) # IP: different IPs, but same MACs: different route nlri3 = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("2.2.2.2")) # with a next hop... nlri4 = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1"), IP.pton("10.10.10.10")) nlri5 = EVPNMAC(RouteDistinguisher.fromElements("42.42.42.42", 5), ESI(), EthernetTag(111), MAC("01:02:03:04:05:06"), 6 * 8, Labels([42], True), IP.create("1.1.1.1"), IP.pton("11.11.11.11")) self.assertEqual(hash(nlri0), hash(nlri1)) self.assertEqual(hash(nlri0), hash(nlri2)) self.assertEqual(hash(nlri0), hash(nlri4)) self.assertEqual(nlri0, nlri1) self.assertEqual(nlri0, nlri2) self.assertEqual(nlri0, nlri4) self.assertEqual(nlri1, nlri2) self.assertEqual(nlri1, nlri4) self.assertEqual(nlri2, nlri4) self.assertEqual(nlri4, nlri5) self.assertNotEqual(hash(nlri0), hash(nlri3)) self.assertNotEqual(nlri0, nlri3) self.assertNotEqual(nlri1, nlri3) self.assertNotEqual(nlri2, nlri3) self.assertNotEqual(nlri3, nlri4)
def unpack(cls, data): length = len(data) if length not in (4, 16): raise Notify(3, 5, "Invalid remote-te size") return cls([str(IP.unpack(data))])
def connect(self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if not self.connection: local = self.neighbor.md5_ip.top( ) if not self.neighbor.auto_discovery else None peer = self.neighbor.peer_address.top() afi = self.neighbor.peer_address.afi md5 = self.neighbor.md5_password md5_base64 = self.neighbor.md5_base64 ttl_out = self.neighbor.ttl_out self.connection = Outgoing(afi, peer, local, self.port, md5, md5_base64, ttl_out) if not local and self.connection.init: self.neighbor.local_address = IP.create(self.connection.local) if self.neighbor.router_id is None and self.neighbor.local_address.afi == AFI.ipv4: self.neighbor.router_id = self.neighbor.local_address try: generator = self.connection.establish() while True: connected = six.next(generator) if not connected: yield False continue if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected( self.peer.neighbor) yield True return except StopIteration: # close called by the caller # self.close('could not connect to remote end') yield False return
def connect(self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if self.connection: return local = self.neighbor['md5-ip'].top( ) if not self.neighbor.auto_discovery else None peer = self.neighbor['peer-address'].top() afi = self.neighbor['peer-address'].afi md5 = self.neighbor['md5-password'] md5_base64 = self.neighbor['md5-base64'] ttl_out = self.neighbor['outgoing-ttl'] self.connection = Outgoing(afi, peer, local, self.port, md5, md5_base64, ttl_out) for connected in self.connection.establish(): yield False if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected(self.peer.neighbor) if not local: self.neighbor['local-address'] = IP.create(self.connection.local) if self.neighbor['router-id'] is None and self.neighbor[ 'local-address'].afi == AFI.ipv4: self.neighbor['router-id'] = self.neighbor['local-address'] yield True
def connect (self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if not self.connection: local = self.neighbor.md5_ip.top() if not self.neighbor.auto_discovery else None peer = self.neighbor.peer_address.top() afi = self.neighbor.peer_address.afi md5 = self.neighbor.md5_password md5_base64 = self.neighbor.md5_base64 ttl_out = self.neighbor.ttl_out self.connection = Outgoing(afi,peer,local,self.port,md5,md5_base64,ttl_out) if not self.connection.init: yield False return if not local: self.neighbor.local_address = IP.create(self.connection.local) if self.neighbor.router_id is None and self.neighbor.local_address.afi == AFI.ipv4: self.neighbor.router_id = self.neighbor.local_address for connected in self.connection.establish(): if not connected: yield False continue if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected(self.peer.neighbor) yield True return
def redirect (self, scope, name, command, tokens): try: if tokens[0].count(':') == 1: prefix,suffix = tokens[0].split(':',1) if prefix.count('.'): raise ValueError('this format has been deprecaded as it does not make sense and it is not supported by other vendors') else: asn = int(prefix) route_target = int(suffix) if asn >= pow(2,16): raise ValueError('asn is a 32 bits number, it can only be 16 bit %s' % route_target) if route_target >= pow(2,32): raise ValueError('route target is a 32 bits number, value too large %s' % route_target) scope[-1]['announce'][-1].attributes[Attribute.CODE.EXTENDED_COMMUNITY].add(TrafficRedirect(asn,route_target)) return True else: change = scope[-1]['announce'][-1] if change.nlri.nexthop is not NoNextHop: return self.error.set(self.syntax) nh = IP.create(tokens.pop(0)) change.nlri.nexthop = nh change.attributes[Attribute.CODE.EXTENDED_COMMUNITY].add(TrafficNextHop(False)) return True except (IndexError,ValueError): return self.error.set(self.syntax)
def _multi_neighbor (self, scope, tokens): if len(tokens) != 1: return self.error.set('syntax: neighbor <ip> { <options> }') address = tokens[0] scope.append({}) try: scope[-1]['peer-address'] = IP.create(address) except (IndexError,ValueError,socket.error): return self.error.set('"%s" is not a valid IP address' % address) while True: r = self._dispatch( scope,'neighbor', [ 'static','flow','l2vpn', 'process','family','capability','operational' ], [ 'description','router-id','local-address','local-as','peer-as', 'host-name','domain-name', 'passive','listen','hold-time','add-path','graceful-restart','md5', 'ttl-security','multi-session','group-updates','asn4','aigp', 'auto-flush','adj-rib-out' ] ) # XXX: THIS SHOULD ALLOW CAPABILITY AND NOT THE INDIVIDUAL SUB KEYS if r is False: return False if r is None: return True
def test200_IPVPNCreatePackUnpack(self): '''Test pack/unpack for IPVPN routes''' nlri = IPVPN.new( AFI.ipv4, SAFI.mpls_vpn, IP.pton("1.2.3.0"), 24, Labels([42], True), RouteDistinguisher.fromElements("42.42.42.42", 5), ) packed = nlri.pack() unpacked, leftover = IPVPN.unpack_nlri(AFI.ipv4, SAFI.mpls_vpn, packed, OUT.UNSET, None) self.assertEqual(0, len(leftover)) # TODO: compare packed with a reference encoding verified # as conformant with RFC4364 self.assertTrue(isinstance(unpacked, IPVPN)) self.assertEqual("1.2.3.0/24", unpacked.cidr.prefix()) self.assertEqual(1, len(unpacked.labels.labels)) self.assertEqual(42, unpacked.labels.labels[0]) self.assertEqual("42.42.42.42:5", unpacked.rd._str())
def _multi_neighbor (self, scope, name, command, tokens): if len(tokens) != 1: return self.error.set('syntax: neighbor <ip> { <options> }') address = tokens[0] scope.append({}) try: scope[-1]['peer-address'] = IP.create(address) except (IndexError,ValueError,socket.error): return self.error.set('"%s" is not a valid IP address' % address) while True: r = self._dispatch( scope,name,'neighbor', [ 'static','flow','l2vpn', 'process','family','capability','operational' ], self._command['neighbor'] ) # XXX: THIS SHOULD ALLOW CAPABILITY AND NOT THE INDIVIDUAL SUB KEYS if r is False: return False if r is None: return True
def __init__(self,afi,safi,packed,mask,nexthop,action): self.labels = Labels.NOLABEL self.rd = RouteDistinguisher.NORD self.nexthop = IP.unpack(nexthop) if nexthop else NoIP self.action = action NLRI.__init__(self,afi,safi) CIDR.__init__(self,packed,mask)
def unpack(cls, data, length): size = len(data) if size not in (4, 16): raise Notify(3, 5, "Invalid remote-te size") return cls([str(IP.unpack(data[:size]))])
def __init__ (self, afi=AFI.ipv4,safi=SAFI.flow_ip,nexthop=None,rd=None): NLRI.__init__(self,afi,safi) self.rules = {} self.action = OUT.UNSET self.nexthop = IP.unpack(nexthop) if nexthop else NoNextHop self.rd = rd if rd else RouteDistinguisher.NORD self.unique = unique.next()
def connect (self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if not self.connection: local = self.neighbor.md5_ip.top() if not self.neighbor.auto_discovery else None peer = self.neighbor.peer_address.top() afi = self.neighbor.peer_address.afi md5 = self.neighbor.md5_password md5_base64 = self.neighbor.md5_base64 ttl_out = self.neighbor.ttl_out self.connection = Outgoing(afi,peer,local,self.port,md5,md5_base64,ttl_out) if not local and self.connection.init: self.neighbor.local_address = IP.create(self.connection.local) if self.neighbor.router_id is None and self.neighbor.local_address.afi == AFI.ipv4: self.neighbor.router_id = self.neighbor.local_address try: generator = self.connection.establish() while True: connected = six.next(generator) if not connected: yield False continue if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected(self.peer.neighbor) yield True return except StopIteration: # close called by the caller # self.close('could not connect to remote end') yield False return
def unpack (cls, data): datalen = len(data) rd = RouteDistinguisher.unpack(data[:8]) esi = ESI.unpack(data[8:18]) etag = EthernetTag.unpack(data[18:22]) maclength = ord(data[22]) if (maclength > 48 or maclength < 0): raise Notify(3,5,'invalid MAC Address length in %s' % cls.NAME) end = 23 + 6 # MAC length MUST be 6 mac = MACQUAL.unpack(data[23:end]) length = ord(data[end]) iplen = length / 8 if datalen in [36,39]: # No IP information (1 or 2 labels) iplenUnpack = 0 if iplen != 0: raise Notify(3,5,"IP length is given as %d, but current MAC route has no IP information" % iplen) elif datalen in [40, 43]: # Using IPv4 addresses (1 or 2 labels) iplenUnpack = 4 if (iplen > 32 or iplen < 0): raise Notify(3,5,"IP field length is given as %d, but current MAC route is IPv4 and valus is out of range" % iplen) elif datalen in [52, 55]: # Using IPv6 addresses (1 or 2 labels) iplenUnpack = 16 if (iplen > 128 or iplen < 0): raise Notify(3,5,"IP field length is given as %d, but current MAC route is IPv6 and valus is out of range" % iplen) else: raise Notify(3,5,"Data field length is given as %d, but does not match one of the expected lengths" % datalen) ip = IP.unpack(data[end+1:end+1+iplenUnpack]) label = Labels.unpack(data[end+1+iplenUnpack:end+1+iplenUnpack+3]) return cls(rd,esi,etag,mac,maclength,label,ip,data)
def connect(self): # allows to test the protocol code using modified StringIO with a extra 'pending' function if not self.connection: local = self.neighbor.md5_ip.top( ) if not self.neighbor.auto_discovery else None peer = self.neighbor.peer_address.top() afi = self.neighbor.peer_address.afi md5 = self.neighbor.md5_password md5_base64 = self.neighbor.md5_base64 ttl_out = self.neighbor.ttl_out self.connection = Outgoing(afi, peer, local, self.port, md5, md5_base64, ttl_out) if not self.connection.init: yield False return if not local: self.neighbor.local_address = IP.create(self.connection.local) if self.neighbor.router_id is None and self.neighbor.local_address.afi == AFI.ipv4: self.neighbor.router_id = self.neighbor.local_address for connected in self.connection.establish(): if not connected: yield False continue if self.peer.neighbor.api['neighbor-changes']: self.peer.reactor.processes.connected(self.peer.neighbor) yield True return
def __init__ (self,afi=AFI.ipv4,safi=SAFI.flow_ip,nexthop=None,rd=None): NLRI.__init__(self,afi,safi) self.rules = {} self.action = OUT.announce self.nexthop = IP.unpack(nexthop) if nexthop else NoIP self.rd = rd self.unique = unique.next()
def __init__(self, afi=AFI.ipv4, safi=SAFI.flow_ip, nexthop=None, rd=None): NLRI.__init__(self, afi, safi) self.rules = {} self.action = OUT.ANNOUNCE self.nexthop = IP.unpack(nexthop) if nexthop else NoIP self.rd = rd self.unique = unique.next()
def __init__ (self, afi, safi, packed, mask, nexthop, action,path=None): self.path_info = PathInfo.NOPATH if path is None else path self.labels = Labels.NOLABEL self.rd = RouteDistinguisher.NORD self.nexthop = IP.unpack(nexthop) if nexthop else NoNextHop self.action = action NLRI.__init__(self,afi,safi) CIDR.__init__(self,packed,mask)
def route (tokeniser): ipmask = prefix(tokeniser) if 'rd' in tokeniser.tokens or 'route-distinguisher' in tokeniser.tokens: klass = MPLS safi = SAFI(SAFI.mpls_vpn) elif 'label' in tokeniser.tokens: # XXX: should we create a LABEL class ? klass = MPLS safi = SAFI(SAFI.nlri_mpls) else: klass = INET safi = IP.tosafi(ipmask.string) change = Change( klass( IP.toafi(ipmask.string), safi, ipmask.pack(), ipmask.mask, '', OUT.UNSET ), Attributes() ) while True: command = tokeniser() if not command: break action = ParseStatic.action[command] if action == 'attribute-add': change.attributes.add(ParseStatic.known[command](tokeniser)) elif action == 'nlri-set': change.nlri.assign(ParseStatic.assign[command],ParseStatic.known[command](tokeniser)) elif action == 'nexthop-and-attribute': nexthop,attribute = ParseStatic.known[command](tokeniser) change.nlri.nexthop = nexthop change.attributes.add(attribute) else: raise ValueError('route: unknown command "%s"' % command) return list(ParseStatic.split(change))
def ip (self, scope, command, tokens): try: ip = IP.create(tokens[0]) except (IndexError,ValueError): return self.error.set('"%s" is an invalid IP address' % ' '.join(tokens)) scope[-1][command] = ip return True