diff --git a/pbgpp/Application/CLI.py b/pbgpp/Application/CLI.py index 1ce121f..ffcf7b4 100755 --- a/pbgpp/Application/CLI.py +++ b/pbgpp/Application/CLI.py @@ -56,7 +56,8 @@ def main(): group_4.add_argument("--filter-message-size", help="only print messages with given message size in bytes (e.g., 128)", nargs="+", action="append", dest="filter_message_size") group_4.add_argument("--filter-message-type", help="only print messages with given BGP message type (KEEPALIVE, NOTIFICATION, OPEN, ROUTE-REFRESH, UPDATE, WITHDRAWAL)", nargs="+", action="append", dest="filter_message_type") group_4.add_argument("--filter-message-subtype", help="only print UPDATE messages with given message sub type (WITHDRAWAL, ANNOUNCE, BOTH, NONE)", nargs="+", action="append", dest="filter_message_subtype") - group_4.add_argument("--filter-nlri", help="only print messages containing the given nlri prefix (e.g., '80.81.82.0/24'", nargs="+", action="append", dest="filter_nlri") + group_4.add_argument("--filter-pathid", help="only print messages with the given path identifier (e.g., '7')", nargs="+", action="append", dest="filter_pathid") + group_4.add_argument("--filter-nlri", help="only print messages containing the given nlri prefix (e.g., '80.81.82.0/24')", nargs="+", action="append", dest="filter_nlri") group_4.add_argument("--filter-withdrawn", help="only print messages containing the given withdrawn routes (e.g., '80.81.82.0/24'", nargs="+", action="append", dest="filter_withdrawn") group_4.add_argument("--filter-next-hop", help="only print messages containing the given next hop (e.g., '80.81.82.83')", nargs="+", action="append", dest="filter_next_hop") group_4.add_argument("--filter-as", help="only print messages containing the given ASN in path AS_PATH attribute (e.g., '12345')", nargs="+", action="append", dest="filter_asn") diff --git a/pbgpp/Application/Handler.py b/pbgpp/Application/Handler.py index 348d63f..c466499 100755 --- a/pbgpp/Application/Handler.py +++ b/pbgpp/Application/Handler.py @@ -46,6 +46,7 @@ from pbgpp.Output.Filters.MessageTypeFilter import MessageTypeFilter from pbgpp.Output.Filters.NLRIFilter import NLRIFilter from pbgpp.Output.Filters.NextHopFilter import NextHopFilter +from pbgpp.Output.Filters.PathIdentifierFilter import PathIdentifierFilter from pbgpp.Output.Filters.TimestampFilter import TimestampFilter from pbgpp.Output.Filters.WithdrawnFilter import WithdrawnFilter from pbgpp.Output.Formatters.HumanReadable import HumanReadableFormatter @@ -163,6 +164,12 @@ def __parse_filters(self): self.filters.append(MessageSubTypeFilter(filters)) logger.debug("Added " + str(len(filters)) + " filter(s) of MessageSubTypeFilter") + if self.args.filter_pathid: + values = self.args.filter_pathid + filters = list(chain(*values)) + self.filters.append(PathIdentifierFilter(filters)) + logger.debug("Added " + str(len(filters)) + " filter(s) of PathIdentfierFilter") + if self.args.filter_nlri: values = self.args.filter_nlri filters = list(chain(*values)) @@ -244,7 +251,7 @@ def __parse_filters(self): if self.args.filter_destination_mac: values = self.args.filter_destination_mac filters = list(chain(*values)) - self.prefilters.append(MACDestinationFilter(MACSourceFilter.clear_input(filters))) + self.prefilters.append(MACDestinationFilter(MACDestinationFilter.clear_input(filters))) logger.debug("Added " + str(len(filters)) + " pre-filter(s) of MACDestinationFilter") if self.args.filter_timestamp: @@ -318,13 +325,13 @@ def __packet_handler(self, header, payload): eth = PCAPEthernet(payload) # Check for raw ethernet packet - if not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV4: + if not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV4 and not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV6: # Check for SLL-packet eth = PCAPCookedCapture(payload) - if not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV4: - logger.debug("Discarding PCAP packet " + str(self.__packet_counter) + " due to non-IPv4 ethernet type.") + if not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV4 and not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV6: + logger.debug("Discarding PCAP packet " + str(self.__packet_counter) + " due to non-IPv4 or non-IPv6 ethernet type.") return False ip = PCAPIP(eth.get_eth_payload()) @@ -335,7 +342,7 @@ def __packet_handler(self, header, payload): tcp = PCAPTCP(ip.get_ip_payload()) - pcap_information = PCAPInformation(header.getts(), eth.mac, ip.addresses, tcp.ports) + pcap_information = PCAPInformation(header.getts(), eth.get_mac(), ip.get_addresses(), tcp.get_ports()) for filter in self.prefilters: if not filter.apply(pcap_information): diff --git a/pbgpp/BGP/Update/Message.py b/pbgpp/BGP/Update/Message.py index fb48ab4..da977ea 100755 --- a/pbgpp/BGP/Update/Message.py +++ b/pbgpp/BGP/Update/Message.py @@ -64,33 +64,15 @@ def __parse(self): # 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 + # AddPath assumption? + current_byte_position = self.__addpath_routine(current_byte_position) - 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: @@ -179,34 +161,7 @@ def __parse(self): 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 + current_byte_position = self.__addpath_routine(current_byte_position) # 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) @@ -259,3 +214,34 @@ def __parse(self): self.error = True self.error = False + + def __addpath_routine(self, current_byte_position): + """ + 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 + return current_byte_position \ No newline at end of file diff --git a/pbgpp/BGP/Update/PathAttributes/MPNextHop.py b/pbgpp/BGP/Update/PathAttributes/MPNextHop.py new file mode 100644 index 0000000..db83070 --- /dev/null +++ b/pbgpp/BGP/Update/PathAttributes/MPNextHop.py @@ -0,0 +1,60 @@ +# +# This file is part of PCAP BGP Parser (pbgpp) +# +# Copyright 2016-2020 DE-CIX Management GmbH +# Author: Christopher Moeller +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from pbgpp.BGP.Statics import BGPStatics +from pbgpp.BGP.Update.Route import BGPRoute + +import struct +import socket + +class MPNextHop: + "This class is an extension of the MP_REACH field" + def __init__(self, payload, proto): + self.payload = payload + self.proto = proto + + self.next_hop = None #string representation of address + + self.__parse() + + def __parse(self): + try: + self.parsed = True + self.error = False + + if self.proto == socket.AF_INET: + fields = struct.unpack("!4B", self.payload) + self.next_hop = str(fields[0]) + "." + str(fields[1]) + "." + str(fields[2]) + "." + str(fields[3]) + + else: + fields = struct.unpack("!8H", self.payload) + next_hop = "" + for i in fields: + next_hop += str( hex(i)[2:] ) + ":" + self.next_hop = next_hop[:-1] + + except Exception as e: + self.error = True + + def __str__(self): + if self.parsed and not self.error: + return self.next_hop + else: + return None + diff --git a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py index aa0ea2f..a952165 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py @@ -18,16 +18,130 @@ # from pbgpp.BGP.Statics import BGPStatics +from pbgpp.BGP.Translation import BGPTranslation + +from pbgpp.BGP.Update.Route import BGPRoute +from pbgpp.BGP.Update.Route6 import BGPRoute6 from pbgpp.BGP.Update.PathAttribute import BGPPathAttribute +from pbgpp.BGP.Update.PathAttributes.MPNextHop import MPNextHop +import struct +import socket +import math class PathAttributeMPReachNLRI(BGPPathAttribute): + """ + RFC 4760 + +---------------------------------------------------------+ + | Address Family Identifier (2 octets) | + +---------------------------------------------------------+ + | Subsequent Address Family Identifier (1 octet) | + +---------------------------------------------------------+ + | Length of Next Hop Network Address (1 octet) | + +---------------------------------------------------------+ + | Network Address of Next Hop (variable) | + +---------------------------------------------------------+ + | Reserved (1 octet) | + +---------------------------------------------------------+ + | Network Layer Reachability Information (variable) | + +---------------------------------------------------------+ + """ + + def __init__(self, payload): BGPPathAttribute.__init__(self, payload) self.type = BGPStatics.UPDATE_ATTRIBUTE_MP_REACH_NLRI + self.afi = 0 + self.safi = 0 + self.next_hop = [] + self.nlri = [] + self.__parse() def __parse(self): self.parsed = True - self.error = False + 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(BGPRoute6.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer])) + + payload_pointer += prefix_len_bytes + 1 + + else: + raise NotImplementedError + + except Exception as e: + self.error = True + + def __str__(self): + output = "REACH_NLRI: NEXT_HOP: [" + for i in self.next_hop: + output += str(i) + ", " + output = output[:-2] + "] NLRI: [" + for i in self.nlri: + output += str(i) + ", " + output = output[:-2] + "]" + return output + + def json(self): #overload of parentclass function + json = { + "afi": self.afi, + "safi": self.safi, + + "reach_nlri": [], + "next_hop": [], + } + + for nlri in self.nlri: + json["reach_nlri"].append(str(nlri)) + for nh in self.next_hop: + json["next_hop"].append(str(nh)) + + return json diff --git a/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py b/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py index 6bab738..0c414e3 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py @@ -18,16 +18,92 @@ # from pbgpp.BGP.Statics import BGPStatics +from pbgpp.BGP.Translation import BGPTranslation + +from pbgpp.BGP.Update.Route import BGPRoute +from pbgpp.BGP.Update.Route6 import BGPRoute6 from pbgpp.BGP.Update.PathAttribute import BGPPathAttribute +from pbgpp.BGP.Update.PathAttributes.MPNextHop import MPNextHop +import struct +import socket +import math class PathAttributeMPUnReachNLRI(BGPPathAttribute): + """ + RFC 4760 + + +---------------------------------------------------------+ + | Address Family Identifier (2 octets) | + +---------------------------------------------------------+ + | Subsequent Address Family Identifier (1 octet) | + +---------------------------------------------------------+ + | Withdrawn Routes (variable) | + +---------------------------------------------------------+ + """ + def __init__(self, payload): BGPPathAttribute.__init__(self, payload) self.type = BGPStatics.UPDATE_ATTRIBUTE_MP_UNREACH_NLRI + + self.afi = 0 + self.safi = 0 + self.nlri = [] + self.__parse() def __parse(self): self.parsed = True - self.error = False + 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(BGPRoute6.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer])) + + payload_pointer += prefix_len_bytes + 1 + + else: + raise NotImplementedError + + except Exception as e: + self.error = True + + def __str__(self): + output = "UNREACH_NLRI: NLRI: [" + for i in self.nlri: + output += str(i) + ", " + output = output[:-2] + "]" + return output + + def json(self): #overload of parentclass function + json= { + "afi": self.afi, + "safi": self.safi, + + "unreach_nlri": [], + } + + for nlri in self.nlri: + json["unreach_nlri"].append(str(nlri)) + + return json diff --git a/pbgpp/BGP/Update/Route.py b/pbgpp/BGP/Update/Route.py index 8aff6e1..406f716 100755 --- a/pbgpp/BGP/Update/Route.py +++ b/pbgpp/BGP/Update/Route.py @@ -56,8 +56,7 @@ def __str__(self): def __eq__(self, other): # Compare two routes by comparing the prefix and its length if isinstance(other, BGPRoute): - if self.prefix == other.prefix and self.prefix_length == other.prefix_length: - return True + return self.prefix == other.prefix and self.prefix_length == other.prefix_length else: # This wont work for any other classes. Just for BGPRoute objects. return NotImplemented diff --git a/pbgpp/BGP/Update/Route6.py b/pbgpp/BGP/Update/Route6.py new file mode 100644 index 0000000..6ec2fbf --- /dev/null +++ b/pbgpp/BGP/Update/Route6.py @@ -0,0 +1,101 @@ +# +# This file is part of PCAP BGP Parser (pbgpp) +# +# Copyright 2016-2020 DE-CIX Management GmbH +# Author: Christopher Moeller +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT 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 socket +import struct +import math + +from pbgpp.BGP.Exceptions import BGPRouteInitializeError, BGPRouteConvertionError + + +class BGPRoute6: + def __init__(self, prefix, prefix_length): + # Prefix = e.g. 1337:1337:1337:1337:: + # Length = 64 + # To String: 1337:1337:1337:1337::/64 (CIDR notation) + + # Assign values + self.prefix = prefix + self.prefix_length = prefix_length + self.prefix_length_decimal = None + + # Values that need to be assigned due to parsing + self.prefix_string = None + self.prefix_length_string = None + + self._parse() + + @classmethod + def from_binary(cls, prefix, prefix_length): + # Create a class instance from bytes + if isinstance(prefix, bytes) and isinstance(prefix_length, bytes): + return cls(prefix, prefix_length) + else: + raise BGPRouteInitializeError("prefix and prefix_length must be instance of bytes.") + + def __str__(self): + # Return the prefix string that was created during parsing + return self.prefix_string + + def __eq__(self, other): + # Compare two routes by comparing the prefix and its length + if isinstance(other, BGPRoute6): + return self.prefix == other.prefix and self.prefix_length == other.prefix_length + else: + # This wont work for any other classes. Just for BGPRoute objects. + return NotImplemented + + def _parse(self): + # Check the prefix length at first as that length is needed to determine + # how many bytes we need to parse afterwards + self.prefix_string = "" + + self.prefix_length_decimal = struct.unpack("!B", self.prefix_length)[0] + self.prefix_length_string = str(self.prefix_length_decimal) + + byte_len = int(math.ceil(self.prefix_length_decimal / 8)) + + if byte_len == 0: + self.prefix_string += "::" + else: + + i=0 + while i < byte_len: + + if i+1 < byte_len: # interpet two bytes + field = struct.unpack("!H", self.prefix[i:i+2])[0] + self.prefix_string += str( hex(field)[2:] ) + ":" + i+=1 + + else: # interpret one byte + field = struct.unpack("!B", self.prefix[i])[0] + if field == 0: # if zero, use the approriate formatting + self.prefix_string += "0:" + else: + self.prefix_string += str( hex(field)[2:] ) + "00:" + + i+=1 + + if byte_len == 16: + self.prefix_string = self.prefix_length_string[:-1] + else: + self.prefix_string += ":" + + self.prefix_string += "/" + str(self.prefix_length_string) + \ No newline at end of file diff --git a/pbgpp/Output/Filters/PathIdentifierFilter.py b/pbgpp/Output/Filters/PathIdentifierFilter.py new file mode 100644 index 0000000..8f9c04e --- /dev/null +++ b/pbgpp/Output/Filters/PathIdentifierFilter.py @@ -0,0 +1,53 @@ +# +# This file is part of PCAP BGP Parser (pbgpp) +# +# Copyright 2016-2017 DE-CIX Management GmbH +# Author: Christopher Moeller +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from pbgpp.Output.Filter import BGPFilter +from pbgpp.BGP.Statics import BGPStatics + + +class PathIdentifierFilter(BGPFilter): + def __init__(self, values=[]): + BGPFilter.__init__(self, values) + + def apply(self, message): + try: + # 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 + + if message.add_path: + # add_path enabled + for value in self.values: + negated = False + if value[0:1] == "~": + negated = True + value = value[1:] + + if not negated and str(message.path_id) == value: + return message + + if negated and str(message.path_id) != value: + 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 diff --git a/pbgpp/Output/Formatters/HumanReadable.py b/pbgpp/Output/Formatters/HumanReadable.py index 2be7c0d..898fff6 100755 --- a/pbgpp/Output/Formatters/HumanReadable.py +++ b/pbgpp/Output/Formatters/HumanReadable.py @@ -23,6 +23,8 @@ from pbgpp.BGP.Update.Route import BGPRoute from pbgpp.Output.Exceptions import OutputFormatterError from pbgpp.Output.Formatter import BGPFormatter +from pbgpp.PCAP.Information import PCAPLayer3Information + class HumanReadableFormatter(BGPFormatter): @@ -51,7 +53,12 @@ def apply(self, message): # 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" + + if message.pcap_information.get_ip().version == PCAPLayer3Information.IP_VERSION_4: + 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" + else: + 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 @@ -117,8 +124,8 @@ def apply(self, message): if message.path_attributes_length > 0: # Process path attributes + string += self.prefix(0) + "Path Attributes: \n" 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 @@ -126,6 +133,21 @@ def apply(self, message): for community in attribute.extended_communities: string += self.prefix(2) + str(community) + "\n" + + elif attribute.type == BGPStatics.UPDATE_ATTRIBUTE_MP_REACH_NLRI: + string += self.prefix(1) + BGPTranslation.path_attribute(attribute.type) + ":\n" + self.prefix(2) + "Next Hop:\n" + for hop in attribute.next_hop: + string += self.prefix(2) + str(hop) + "\n" + + string += self.prefix(2) + "\n" + self.prefix(2) + "NLRI:\n" + for nlri in attribute.nlri: + string += self.prefix(2) + str(nlri) + "\n" + + elif attribute.type == BGPStatics.UPDATE_ATTRIBUTE_MP_UNREACH_NLRI: + string += self.prefix(1) + BGPTranslation.path_attribute(attribute.type) + ":\n" + for nlri in attribute.nlri: + string += self.prefix(2) + str(nlri) + "\n" + else: # We got a "normal" path attribute string += self.prefix(1) + BGPTranslation.path_attribute(attribute.type) + ": " + str(attribute) + "\n" diff --git a/pbgpp/Output/Formatters/LineBased.py b/pbgpp/Output/Formatters/LineBased.py index bf56c4e..3b1695d 100755 --- a/pbgpp/Output/Formatters/LineBased.py +++ b/pbgpp/Output/Formatters/LineBased.py @@ -24,10 +24,11 @@ from pbgpp.BGP.Update.PathAttributes.LargeCommunities import PathAttributeLargeCommunities from pbgpp.BGP.Update.PathAttributes.NextHop import PathAttributeNextHop from pbgpp.BGP.Update.PathAttributes.Origin import PathAttributeOrigin +from pbgpp.BGP.Update.PathAttributes.MPReachNLRI import PathAttributeMPReachNLRI +from pbgpp.BGP.Update.PathAttributes.MPUnReachNLRI import PathAttributeMPUnReachNLRI from pbgpp.Output.Formatter import BGPFormatter from itertools import chain - class LineBasedFormatter(BGPFormatter): FIELD_MESSAGE_TIMESTAMP = ["timestamp"] FIELD_MESSAGE_IP_SOURCE = ["source_ip", "src_ip"] @@ -44,6 +45,9 @@ class LineBasedFormatter(BGPFormatter): FIELD_UPDATE_WITHDRAWN_ROUTES = ["withdrawn_routes", "withdrawn_route", "withdrawals"] FIELD_UPDATE_NLRI = ["prefixes", "prefix", "nlri"] FIELD_UPDATE_NLRI_LENGTH = ["prefix_length"] + FIELD_UPDATE_ATTRIBUTE_MP_REACH_NLRI = ["mp_reach_prefixes", "mp_reach_prefix", "mp_reach_nlri"] + FIELD_UPDATE_ATTRIBUTE_MP_UNREACH_NLRI = ["mp_unreach_prefixes", "mp_unreach_prefix", "mp_unreach_nlri"] + FIELD_UPDATE_ATTRIBUTE_MP_NEXT_HOP = ["mp_next_hop", "mp_nexthop"] FIELD_UPDATE_ATTRIBUTE_ORIGIN = ["origin"] FIELD_UPDATE_ATTRIBUTE_AS_PATH = ["as_path"] FIELD_UPDATE_ATTRIBUTE_AS_PATH_LAST_ASN = ["as_path_last_asn"] @@ -70,6 +74,9 @@ class LineBasedFormatter(BGPFormatter): FIELD_UPDATE_WITHDRAWN_ROUTES, FIELD_UPDATE_NLRI, FIELD_UPDATE_NLRI_LENGTH, + FIELD_UPDATE_ATTRIBUTE_MP_REACH_NLRI, + FIELD_UPDATE_ATTRIBUTE_MP_UNREACH_NLRI, + FIELD_UPDATE_ATTRIBUTE_MP_NEXT_HOP, FIELD_UPDATE_ATTRIBUTE_ORIGIN, FIELD_UPDATE_ATTRIBUTE_AS_PATH, FIELD_UPDATE_ATTRIBUTE_AS_PATH_LAST_ASN, @@ -215,6 +222,45 @@ def get_field_value(self, f, message): return [r.prefix_length_string for r in prefixes] return None + # Attribute: Mulitprotocol: REACHable NLRI + if f in self.FIELD_UPDATE_ATTRIBUTE_MP_REACH_NLRI: + path_attributes = getattr(message, "path_attributes", False) + if path_attributes: + result = [] + for a in path_attributes: + if isinstance(a, PathAttributeMPReachNLRI): + for nlri in a.nlri: + result.append(nlri) + if not len(result) == 0: + return result + return None + + # Attribute: Mulitprotocol: NEXT HOP + if f in self.FIELD_UPDATE_ATTRIBUTE_MP_NEXT_HOP: + path_attributes = getattr(message, "path_attributes", False) + if path_attributes: + result = [] + for a in path_attributes: + if isinstance(a, PathAttributeMPReachNLRI): + for next_hop in a.next_hop: + result.append(next_hop) + if not len(result) == 0: + return result + return None + + # Attribute: Mulitprotocol: UNREACHable NLRI + if f in self.FIELD_UPDATE_ATTRIBUTE_MP_UNREACH_NLRI: + path_attributes = getattr(message, "path_attributes", False) + if path_attributes: + result = [] + for a in path_attributes: + if isinstance(a, PathAttributeMPUnReachNLRI): + for nlri in a.nlri: + result.append(nlri) + if not len(result) == 0: + return result + return None + # Attribute: Origin if f in self.FIELD_UPDATE_ATTRIBUTE_ORIGIN: path_attributes = getattr(message, "path_attributes", False) diff --git a/pbgpp/PCAP/CookedCapture.py b/pbgpp/PCAP/CookedCapture.py index 76d950f..6e39ce3 100644 --- a/pbgpp/PCAP/CookedCapture.py +++ b/pbgpp/PCAP/CookedCapture.py @@ -26,6 +26,7 @@ class PCAPCookedCapture: ETH_TYPE_IPV4 = 0x0800 + ETH_TYPE_IPV6 = 0x86DD SLL_SENT_TO_US = 0x0000 SLL_BROADCAST = 0x0001 diff --git a/pbgpp/PCAP/Ethernet.py b/pbgpp/PCAP/Ethernet.py index f2614ef..b1dceac 100755 --- a/pbgpp/PCAP/Ethernet.py +++ b/pbgpp/PCAP/Ethernet.py @@ -26,6 +26,7 @@ class PCAPEthernet: ETH_TYPE_IPV4 = 0x0800 + ETH_TYPE_IPV6 = 0x86DD def __init__(self, payload): self.payload = payload diff --git a/pbgpp/PCAP/IP.py b/pbgpp/PCAP/IP.py index 5c42551..8caf698 100755 --- a/pbgpp/PCAP/IP.py +++ b/pbgpp/PCAP/IP.py @@ -23,7 +23,19 @@ class PCAPIP: PROTO_TCP = 0x0006 - BITMASK_IP_HEADER_LENGTH = 0xf + BITMASK_IP_HEADER_LENGTH = 0xF + + IP6_STATIC_HEADER_LENGTH = 40 + + IP6_HEADER_HOP_BY_HOP = 0x0 + IP6_HEADER_ROUTING = 0x2B + IP6_HEADER_FRAGMENT = 0x2C #not implemented + IP6_HEADER_ESP = 0x32 #not implemented + IP6_HEADER_AUTH = 0x33 #used for ipsec + IP6_HEADER_DESTINATION_OPTIONS = 0x3C + + IP6_HEADER_EXTENSIONS = [IP6_HEADER_HOP_BY_HOP, IP6_HEADER_ROUTING, IP6_HEADER_FRAGMENT, + IP6_HEADER_ESP, IP6_HEADER_AUTH, IP6_HEADER_DESTINATION_OPTIONS] def __init__(self, payload): # Assign variables @@ -45,11 +57,37 @@ def __parse(self): self.header_length = (version_length & self.BITMASK_IP_HEADER_LENGTH) * 4 self.version = (version_length >> 4) - self.total_length = struct.unpack("!H", self.payload[2:4])[0] - self.protocol = struct.unpack("!B", self.payload[9:10])[0] + if self.version == PCAPLayer3Information.IP_VERSION_4: + self.total_length = struct.unpack("!H", self.payload[2:4])[0] + self.protocol = struct.unpack("!B", self.payload[9:10])[0] + + ip_set = struct.unpack("!BBBBBBBB", self.payload[12:20]) + self.addresses = PCAPLayer3Information(ip_set[0:4], ip_set[4:8], PCAPLayer3Information.IP_VERSION_4) + + if self.version == PCAPLayer3Information.IP_VERSION_6: + self.flow_label = struct.unpack("!L", self.payload[:4])[0] & 0x000FFFFF + + # Extract sender and receiver address + ip_set = struct.unpack("!16H", self.payload[8:40]) + self.addresses = PCAPLayer3Information(ip_set[:8], ip_set[8:], PCAPLayer3Information.IP_VERSION_6) + + # IPv6 header length, discard packet if Jumbo Frame (not implemented till now) + self.header_length = self.IP6_STATIC_HEADER_LENGTH + self.total_length = struct.unpack("!H", self.payload[4:6])[0] + self.header_length + if self.total_length == self.header_length: # no jumbo frame support + raise NotImplementedError('Jumbo Frames are not supported') + + # Check if there are header extensions + self.protocol = struct.unpack("!B", self.payload[6])[0] + if self.protocol in self.IP6_HEADER_EXTENSIONS: + + if self.protocol == self.IP6_HEADER_FRAGMENT or self.protocol == self.IP6_HEADER_ESP or self.protocol == self.IP6_HEADER_AUTH: + raise NotImplementedError('Unsupported IP6 extended header extension') - ip_set = struct.unpack("!BBBBBBBB", self.payload[12:20]) - self.addresses = PCAPLayer3Information(ip_set[0:4], ip_set[4:8]) + self.protocol = struct.unpack("!B", self.payload[self.IP6_STATIC_HEADER_LENGTH]) + self.header_length +=struct.unpack("!B", self.payload[self.IP6_STATIC_HEADER_LENGTH + 1]) + 1 + + #--HOP LIMIT-- We dont care about that since its not important for the parser def get_protocol(self): return self.protocol diff --git a/pbgpp/PCAP/Information.py b/pbgpp/PCAP/Information.py index 332c90e..92a8010 100755 --- a/pbgpp/PCAP/Information.py +++ b/pbgpp/PCAP/Information.py @@ -108,16 +108,37 @@ def __str__(self): class PCAPLayer3Information: - def __init__(self, source, destination): + IP_VERSION_4 = 0x4 + IP_VERSION_6 = 0x6 + + def __init__(self, source, destination, version): # Store source and destination IP address + self.version = version self.source = source self.destination = destination def get_source_string(self): - return str(self.source[0]) + "." + str(self.source[1]) + "." + str(self.source[2]) + "." + str(self.source[3]) + if self.version == self.IP_VERSION_4: + return str(self.source[0]) + "." + str(self.source[1]) + "." + str(self.source[2]) + "." + str(self.source[3]) + + if self.version == self.IP_VERSION_6: + output = "" + for i in self.source: + output += str( hex(i)[2:] ) + ":" + + return output[:-1] def get_destination_string(self): - return str(self.destination[0]) + "." + str(self.destination[1]) + "." + str(self.destination[2]) + "." + str(self.destination[3]) + if self.version == self.IP_VERSION_4: + return str(self.destination[0]) + "." + str(self.destination[1]) + "." + str(self.destination[2]) + "." + str(self.destination[3]) + + if self.version == self.IP_VERSION_6: + output = "" + for i in self.destination: + output += str( hex(i)[2:] ) + ":" + + return output[:-1] + def __str__(self): return "".format(self.get_source_string(), self.get_destination_string())