def __parse(self): self.parsed = True self.error = False payload_pointer = 0 self.afi = struct.unpack("!H", self.payload[:2])[0] self.safi = struct.unpack("!B", self.payload[2])[0] # @todo use this if wanted, atm its not neccesary self.next_hop_length = struct.unpack("!B", self.payload[3])[0] payload_pointer = 4 if not self.next_hop_length == 0: #next_hop parsing try: if self.afi == 1: #IPv4 if not self.next_hop_length % 4 == 0: self.error = True else: for i in range(self.next_hop_length / 4): self.next_hop.append( MPNextHop(self.payload[payload_pointer:payload_pointer+4], socket.AF_INET) ) payload_pointer += 4 elif self.afi == 2: #IPv6 if not self.next_hop_length % 16 == 0: self.error = True else: for i in range(self.next_hop_length / 16): self.next_hop.append( MPNextHop(self.payload[payload_pointer:payload_pointer+16], socket.AF_INET6) ) payload_pointer += 16 else: raise NotImplementedError except Exception as e: self.error = True if not len(self.payload) == self.next_hop_length + 5: # afi + safi + hop_length + reserved = 5bytes payload_pointer += 1 #skip reservation byte try: if self.afi == 1: #IPv4 while payload_pointer < len(self.payload): prefix_len = struct.unpack("!B", self.payload[payload_pointer])[0] prefix_len_bytes = int(math.ceil(prefix_len / 8.0)) self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer])) payload_pointer += prefix_len_bytes + 1 elif self.afi == 2: #IPv6 while payload_pointer < len(self.payload): prefix_len = struct.unpack("!B", self.payload[payload_pointer])[0] prefix_len_bytes = int(math.ceil(prefix_len / 8.0)) self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer], BGPStatics.IP6_CODE)) payload_pointer += prefix_len_bytes + 1 else: raise NotImplementedError except Exception as e: self.error = True
def apply(self, message): try: # NEXT_HOP and COMMUNITIES are attributes of a BGP UPDATE message if message.type is not BGPStatics.MESSAGE_TYPE_UPDATE: # Skip messages that are no UPDATE messages return None for attribute in message.path_attributes: # Skip attributes that are no NEXT_HOP attributes if attribute.type is BGPStatics.UPDATE_ATTRIBUTE_NEXT_HOP: # Here we found the NEXT_HOP attribute - check for blackhole next_hop for value in self.values: if str( BGPRoute.decimal_ip_to_string( attribute.next_hop)) == str(value): # Match on NEXT_HOP attribute - Return message return message # Alternatively check if well-known BGP community is set (RFC 7999) if attribute.type is BGPStatics.UPDATE_ATTRIBUTE_COMMUNITIES: for community in attribute.communities: if community.asn == 65535 and community.value == 666: return message # Searched value was not found return None except Exception as e: # On error the filtering was not successful (due to wrong fields, etc.) return None
def json(self): return { "type": self.type, "type_string": BGPTranslation.path_attribute(self.type), "error": self.error, "next_hop": str(BGPRoute.decimal_ip_to_string(self.next_hop)) }
def apply(self, message): try: # NEXT_HOP is a path attribute of BGP UPDATE message # Therefore we first need to make that we are currently handling an UPDATE message if message.type is not BGPStatics.MESSAGE_TYPE_UPDATE: # Skip messages that are no UPDATE messages return None for attribute in message.path_attributes: # Skip attributes that are no NEXT_HOP attributes if attribute.type is not BGPStatics.UPDATE_ATTRIBUTE_NEXT_HOP: continue # Here we found the NEXT_HOP attribute - loop through allowed values for value in self.values: if str(BGPRoute.decimal_ip_to_string( attribute.next_hop)) == str(value): # Match on NEXT_HOP attribute - Return message return message # Searched value was not found return None except Exception as e: # On error the filtering was not successful (due to wrong fields, etc.) return None
def __parse(self): self.parsed = True self.error = False payload_pointer = 0 self.afi = struct.unpack("!H", self.payload[:2])[0] self.safi = struct.unpack("!B", self.payload[2])[0] payload_pointer = 3 try: if self.afi == 1: #IPv4 while payload_pointer < len(self.payload): prefix_len = struct.unpack( "!B", self.payload[payload_pointer])[0] prefix_len_bytes = int(math.ceil(prefix_len / 8.0)) self.nlri.append( BGPRoute.from_binary( self.payload[payload_pointer + 1:payload_pointer + 1 + prefix_len_bytes], self.payload[payload_pointer])) payload_pointer += prefix_len_bytes + 1 elif self.afi == 2: #IPv6 while payload_pointer < len(self.payload): prefix_len = struct.unpack( "!B", self.payload[payload_pointer])[0] prefix_len_bytes = int(math.ceil(prefix_len / 8.0)) self.nlri.append( BGPRoute.from_binary( self.payload[payload_pointer + 1:payload_pointer + 1 + prefix_len_bytes], self.payload[payload_pointer], BGPStatics.IP6_CODE)) payload_pointer += prefix_len_bytes + 1 else: raise NotImplementedError except Exception as e: self.error = True
def apply(self, message): # Example return: # # [BGPMessage UPDATE] - [123.123.123.123 -> 123.123.123.123] # |- IP: 123.123.123.123 -> 123.123.123.123 # |- MAC: 11:11:11:11:11:11 -> 11:11:11:11:11 # |- Unix Time: 1412416346.123245123 # | # |- Withdrawn Routes Length: 0 # |- Total Path Attribute Length: 55 # |- Path Attributes # |--- ORIGIN: IGP # |--- AS_PATH: (9498 9430) # |--- NEXT_HOP: 80.81.194.250 # |--- COMMUNITIES: 9498:1 9498:11 9498:91 # |- NLRI # |--- 203.190.42.0/24 ## # Initialize basic return string and PCAP information string = "[BGPMessage " + BGPTranslation.message_type( message.type) + "] - " + str(message.length) + " Bytes\n" string += self.prefix(0) + "MAC: " + message.pcap_information.get_mac( ).get_source_string( separated=True) + " -> " + message.pcap_information.get_mac( ).get_destination_string(separated=True) + "\n" string += self.prefix(0) + "IP: " + message.pcap_information.get_ip( ).get_source_string() + ":" + message.pcap_information.get_ports( ).get_source_string() + " -> " + message.pcap_information.get_ip( ).get_destination_string() + ":" + message.pcap_information.get_ports( ).get_destination_string() + "\n" string += self.prefix( 0) + "Timestamp: " + message.pcap_information.get_timestmap_utc( ) + " (" + str( message.pcap_information.get_timestamp()[0]) + "." + str( message.pcap_information.get_timestamp()[1]) + ")\n" # Display additional information if BGPStatics.MESSAGE_TYPE_KEEPALIVE == message.type: pass if BGPStatics.MESSAGE_TYPE_OPEN == message.type: # --- Divider for PCAP information string += self.prefix(-1) + "\n" string += self.prefix(0) + "Version: " + str( message.version) + "\n" string += self.prefix(0) + "My ASN: " + str(message.asn) + "\n" string += self.prefix(0) + "Hold Time: " + str( message.hold_time) + "\n" string += self.prefix(0) + "BGP Identifier: " + str( BGPRoute.decimal_ip_to_string(message.identifier)) + "\n" # --- Optional Parameters string += self.prefix(0) + "Optional Parameters Length: " + str( message.optional_parameter_length) + " Bytes" + "\n" # Process optional parameters if message.optional_parameter_length > 0: string += self.prefix(0) + "Optional Parameters:" + "\n" for parameter in message.optional_parameter: if parameter.type == BGPStatics.OPEN_CAPABILITY: string += self.prefix( 1) + "Parameter: Capability" + "\n" # Process capabilities for capability in parameter.capability_list: if capability.type is not BGPStatics.CAPABILITY_UNKNOWN: string += self.prefix( 2) + BGPTranslation.capability( capability.type) + " (" + str( capability.type) + ")\n" else: string += self.prefix(2) + str( capability) + "\n" elif parameter.type == BGPStatics.OPEN_AUTHENTICATION: string += self.prefix( 1) + "Parameter: Authentication" + "\n" elif parameter.type == BGPStatics.OPEN_RESERVED: string += self.prefix(1) + "Parameter: Reserved" + "\n" if BGPStatics.MESSAGE_TYPE_UPDATE == message.type: # --- Divider for PCAP information string += self.prefix(-1) + "\n" # --- Update Message Sub-Type string += self.prefix( 0 ) + "Update Message Sub-Type: " + BGPTranslation.update_subtype( message.subtype) + "\n" # --- Lengths string += self.prefix(0) + "Withdrawn Routes Length: " + str( message.withdrawn_routes_length) + " Bytes\n" string += self.prefix(0) + "Total Path Attribute Length: " + str( message.path_attributes_length) + " Bytes\n" # --- NLRI if len(message.nlri) > 0: string += self.prefix(0) + "Prefix (NLRI):" if message.add_path: string += " (AddPath)\n" + self.prefix( 0) + "Path Identifier: " + str(message.path_id) string += "\n" # Process NLRI for route in message.nlri: string += self.prefix(1) + str(route) + "\n" # --- Path Attributes if message.path_attributes_length > 0: # Process path attributes for attribute in message.path_attributes: string += self.prefix(0) + "Path Attributes:" + "\n" if attribute.type == BGPStatics.UPDATE_ATTRIBUTE_EXTENDED_COMMUNITIES: # Extended Communities must be displayed in another way than other attributes string += self.prefix( 1) + BGPTranslation.path_attribute( attribute.type) + ":\n" for community in attribute.extended_communities: string += self.prefix(2) + str(community) + "\n" else: # We got a "normal" path attribute string += self.prefix( 1) + BGPTranslation.path_attribute( attribute.type) + ": " + str(attribute) + "\n" # --- Withdrawn Routes if message.withdrawn_routes_length > 0: string += self.prefix(0) + "Withdrawn Routes:" if message.add_path: string += " (AddPath)\n" + self.prefix( 0) + "Path Identifier: " + str(message.path_id) string += "\n" # Process withdrawn routes for route in message.withdrawn_routes: string += self.prefix(1) + str(route) + "\n" if BGPStatics.MESSAGE_TYPE_NOTIFICATION == message.type: pass if BGPStatics.MESSAGE_TYPE_ROUTE_REFRESH == message.type: pass if BGPStatics.MESSAGE_TYPE_RESERVED == message.type: pass # Return assembled string plus final line break return string + "\n"
def __parse(self): self.parsed = True try: # Check last two bytes of byte payload - if they are set to zero we don't have any path attributes if struct.unpack("!H", self.payload[-2:])[0] == 0: self.path_attributes_length = 0 # Unpack the length of withdrawn routes field and add 2 bytes to the current byte marker position self.withdrawn_routes_length = struct.unpack( "!H", self.payload[:2])[0] current_byte_position = 2 # Start parsing withdrawn routes if self.withdrawn_routes_length is not 0: continue_loop = True # Loop through withdrawals while continue_loop: # First of all we need to parse the length of the withdrawn prefix. Depending on the prefix length # we can determine the length following prefix itself prefix_length_bytes = self.payload[ current_byte_position:current_byte_position + 1] prefix_length = struct.unpack("!B", prefix_length_bytes)[0] current_byte_position += 1 if 0 <= prefix_length <= 8: # Length of prefix field: 1 Byte prefix_bytes = self.payload[ current_byte_position:current_byte_position + 1] current_byte_position += 1 elif 9 <= prefix_length <= 16: # Length of prefix field: 2 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 2] current_byte_position += 2 elif 17 <= prefix_length <= 24: # Length of prefix field: 3 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 3] current_byte_position += 3 elif 25 <= prefix_length: # Length of prefix field: 4 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 4] current_byte_position += 4 else: self.error = True raise BGPWithdrawnPrefixError( "can't match prefix length.") # Add BGPRoute object with information about the withdrawn route to list self.withdrawn_routes.append( BGPRoute.from_binary(prefix_bytes, prefix_length_bytes)) # Check if we are at the end of the payload if self.withdrawn_routes_length <= current_byte_position: # If yes we need to stop the iteration continue_loop = False # Second step: Continue with the path attributes if self.path_attributes_length is None: # First of all get the attributes length field and update the current byte position self.path_attributes_length = struct.unpack( "!H", self.payload[current_byte_position:current_byte_position + 2])[0] current_byte_position += 2 # Now we have a correct path_attributes_length stored. If this length is zero we don't need to do anything if self.path_attributes_length is not 0: continue_loop = True # Loop through path attributes while continue_loop: # Now comes a tricky part of UPDATE message parsing. Each path attribute has a flag bitfield. # One of those flags is called 'extended length'. If it's set to 1 the following attribute fields # are 2 bytes long. But if it's set to zero it's just 1 byte long ... # So first of all: Flag parsing! attribute_flags = BGPUpdateFlags( struct.unpack( "!B", self.payload[ current_byte_position:current_byte_position + 1])[0]) current_byte_position += 1 if attribute_flags.length: # We got an extended length flag attribute_fields = struct.unpack( "!BH", self.payload[ current_byte_position:current_byte_position + 3]) current_byte_position += 3 else: # We got a normal length flag attribute_fields = struct.unpack( "!BB", self.payload[ current_byte_position:current_byte_position + 2]) current_byte_position += 2 # Finally assign the variables attribute_type = attribute_fields[0] attribute_length = attribute_fields[1] # Now we are using the factory pattern again to determine # which kind of attribute we have to add the list self.path_attributes.append( BGPPathAttribute.factory( attribute_type, self.payload[ current_byte_position:current_byte_position + attribute_length], attribute_flags)) # Add length of attribute to position pointer current_byte_position += attribute_length # Check if there are further path attributes to parse if current_byte_position >= self.path_attributes_length: continue_loop = False # Third step: NLRIs if len(self.payload) > (self.path_attributes_length + 4 + self.withdrawn_routes_length): continue_loop = True # The four bytes that get added to the attributes and routes length are the two two-byte fields # for path attribute length and withdrawn routes length current_byte_position = self.path_attributes_length + 4 + self.withdrawn_routes_length while continue_loop: # First of all we have to check the prefix length as byte-length of the following # prefix depends on its prefix length (This is a 1-byte-field) prefix_length_bytes = self.payload[ current_byte_position:current_byte_position + 1] prefix_length = struct.unpack("!B", prefix_length_bytes)[0] current_byte_position += 1 if 0 <= prefix_length <= 8: # Length of prefix field: 1 Byte prefix_bytes = self.payload[ current_byte_position:current_byte_position + 1] current_byte_position += 1 elif 9 <= prefix_length <= 16: # Length of prefix field: 2 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 2] current_byte_position += 2 elif 17 <= prefix_length <= 24: # Length of prefix field: 3 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 3] current_byte_position += 3 elif 25 <= prefix_length: # Length of prefix field: 4 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 4] current_byte_position += 4 else: self.error = True raise BGPWithdrawnPrefixError( "can't match prefix length.") try: self.nlri.append( BGPRoute.from_binary(prefix_bytes, prefix_length_bytes)) except BGPError as e: raise BGPNLRIError( "can't append NLRI to message (error: " + str(e) + ")") if current_byte_position >= len(self.payload): continue_loop = False # Determine sub-type for filtering # Using bit flags for easier assignment if len(self.nlri) > 0: self.subtype = (self.subtype | BGPStatics.UPDATE_TYPE_ANNOUNCE) if len(self.withdrawn_routes) > 0: self.subtype = (self.subtype | BGPStatics.UPDATE_TYPE_WITHDRAWAL) except BGPWithdrawnPrefixError as p: self.error = True logging.info(p) except Exception as e: self.error = True self.error = False
def __str__(self): if self.parsed and not self.error: return str(BGPRoute.decimal_ip_to_string(self.next_hop)) else: return None
def __parse(self): self.parsed = True try: # Unpack the length of withdrawn routes field and add 2 bytes to the current byte marker position self.withdrawn_routes_length = struct.unpack( "!H", self.payload[:2])[0] current_byte_position = 2 # Start parsing withdrawn routes if self.withdrawn_routes_length is not 0: continue_loop = True # Loop through withdrawals while continue_loop: # AddPath assumption? look for description in the method for NLRI parsing if self.flags["addpath"].get_value( ) == 0: # No AddPath messages pass else: pathId_length_bytes = self.payload[ current_byte_position:current_byte_position + 4] pathId = struct.unpack("!I", pathId_length_bytes)[0] if self.flags["addpath"].get_value( ) == 1: # Only AddPath self.add_path = True self.path_id = pathId current_byte_position += 4 else: # Try to find out (using metric) if pathId < 65536: self.add_path = True self.path_id = pathId current_byte_position += 4 #else: drop the Path Id, its likely that this is not an AddPath msg # First of all we need to parse the length of the withdrawn prefix. Depending on the prefix length # we can determine the length following prefix itself prefix_length_bytes = self.payload[ current_byte_position:current_byte_position + 1] prefix_length = struct.unpack("!B", prefix_length_bytes)[0] current_byte_position += 1 if prefix_length == 0: prefix_bytes = prefix_length_bytes elif 0 < prefix_length <= 8: # Length of prefix field: 1 Byte prefix_bytes = self.payload[ current_byte_position:current_byte_position + 1] current_byte_position += 1 elif 9 <= prefix_length <= 16: # Length of prefix field: 2 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 2] current_byte_position += 2 elif 17 <= prefix_length <= 24: # Length of prefix field: 3 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 3] current_byte_position += 3 elif 25 <= prefix_length: # Length of prefix field: 4 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 4] current_byte_position += 4 else: self.error = True raise BGPWithdrawnPrefixError( "can't match prefix length.") # Add BGPRoute object with information about the withdrawn route to list self.withdrawn_routes.append( BGPRoute.from_binary(prefix_bytes, prefix_length_bytes)) # Check if we are at the end of the payload if self.withdrawn_routes_length <= current_byte_position: # If yes we need to stop the iteration continue_loop = False # Second step: Continue with the path attributes if self.path_attributes_length is None: # First of all get the attributes length field and update the current byte position self.path_attributes_length = struct.unpack( "!H", self.payload[current_byte_position:current_byte_position + 2])[0] current_byte_position += 2 # Now we have a correct path_attributes_length stored. If this length is zero we don't need to do anything if self.path_attributes_length is not 0: continue_loop = True # Loop through path attributes while continue_loop: # Now comes a tricky part of UPDATE message parsing. Each path attribute has a flag bitfield. # One of those flags is called 'extended length'. If it's set to 1 the following attribute fields # are 2 bytes long. But if it's set to zero it's just 1 byte long ... # So first of all: Flag parsing! attribute_flags = BGPUpdateFlags( struct.unpack( "!B", self.payload[ current_byte_position:current_byte_position + 1])[0]) current_byte_position += 1 if attribute_flags.length: # We got an extended length flag attribute_fields = struct.unpack( "!BH", self.payload[ current_byte_position:current_byte_position + 3]) current_byte_position += 3 else: # We got a normal length flag attribute_fields = struct.unpack( "!BB", self.payload[ current_byte_position:current_byte_position + 2]) current_byte_position += 2 # Finally assign the variables attribute_type = attribute_fields[0] attribute_length = attribute_fields[1] # Now we are using the factory pattern again to determine # which kind of attribute we have to add the list self.path_attributes.append( BGPPathAttribute.factory( attribute_type, self.payload[ current_byte_position:current_byte_position + attribute_length], attribute_flags)) # Add length of attribute to position pointer current_byte_position += attribute_length # Check if there are further path attributes to parse if current_byte_position >= self.path_attributes_length: continue_loop = False # Third step: NLRIs if len(self.payload) > (self.path_attributes_length + 4 + self.withdrawn_routes_length): continue_loop = True # The four bytes that get added to the attributes and routes length are the two two-byte fields # for path attribute length and withdrawn routes length current_byte_position = self.path_attributes_length + 4 + self.withdrawn_routes_length while continue_loop: """ The Following is a Fix for missing Add_Path feature. Due to the lack of a definition for this case, we need depend on the users decision. See RFC 7911 Chapter 6 p.5 (22.07.2020). In most cases, the pathId is lower than 2**16. Also it is uncommon, that one BGP UPDATE message contains the 0.0.0.0/0 prefix 2 times. This leads to the following metric if the user sets the add_path_flag to 2. """ # AddPath assumption? if self.flags["addpath"].get_value( ) == 0: # No AddPath messages pass else: pathId_length_bytes = self.payload[ current_byte_position:current_byte_position + 4] pathId = struct.unpack("!I", pathId_length_bytes)[0] if self.flags["addpath"].get_value( ) == 1: # Only AddPath self.add_path = True self.path_id = pathId current_byte_position += 4 else: # Try to find out (using metric) if pathId < 65536: self.add_path = True self.path_id = pathId current_byte_position += 4 #else: drop the Path Id, its likely that this is not an AddPath msg # First of all we have to check the prefix length as byte-length of the following # prefix depends on its prefix length (This is a 1-byte-field) prefix_length_bytes = self.payload[ current_byte_position:current_byte_position + 1] prefix_length = struct.unpack("!B", prefix_length_bytes)[0] current_byte_position += 1 if prefix_length == 0: #0.0.0.0/0 prefix_bytes = prefix_length_bytes elif 0 <= prefix_length <= 8: # Length of prefix field: 1 Byte prefix_bytes = self.payload[ current_byte_position:current_byte_position + 1] current_byte_position += 1 elif 9 <= prefix_length <= 16: # Length of prefix field: 2 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 2] current_byte_position += 2 elif 17 <= prefix_length <= 24: # Length of prefix field: 3 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 3] current_byte_position += 3 elif 25 <= prefix_length: # Length of prefix field: 4 Bytes prefix_bytes = self.payload[ current_byte_position:current_byte_position + 4] current_byte_position += 4 else: self.error = True raise BGPWithdrawnPrefixError( "can't match prefix length.") try: self.nlri.append( BGPRoute.from_binary(prefix_bytes, prefix_length_bytes)) except BGPError as e: raise BGPNLRIError( "can't append NLRI to message (error: " + str(e) + ")") if current_byte_position >= len(self.payload): continue_loop = False # Determine sub-type for filtering # Using bit flags for easier assignment if len(self.nlri) > 0: self.subtype = (self.subtype | BGPStatics.UPDATE_TYPE_ANNOUNCE) if len(self.withdrawn_routes) > 0: self.subtype = (self.subtype | BGPStatics.UPDATE_TYPE_WITHDRAWAL) except BGPWithdrawnPrefixError as p: self.error = True logging.info(p) except Exception as e: self.error = True self.error = False