From 620ae819ab419440f36a93f876102297e9b36d53 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 08:50:10 +0200 Subject: [PATCH 01/10] Add helperfunction BGPUpdateMessage.__addpath_routine() --- pbgpp/BGP/Update/Message.py | 82 +++++++++++++++---------------------- 1 file changed, 34 insertions(+), 48 deletions(-) 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 From a05e55429d154cd24085d74dfcd196ff30b8dc4a Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 11:04:45 +0200 Subject: [PATCH 02/10] Add vlan tag parsing and filtering feature --- pbgpp/Application/CLI.py | 2 + pbgpp/Application/Handler.py | 17 +++++++- pbgpp/Output/Filters/VLANCustomerFilter.py | 45 ++++++++++++++++++++++ pbgpp/Output/Filters/VLANServiceFilter.py | 45 ++++++++++++++++++++++ pbgpp/Output/Formatters/HumanReadable.py | 7 ++++ pbgpp/Output/Formatters/JSON.py | 2 + pbgpp/Output/Formatters/LineBased.py | 12 ++++++ pbgpp/PCAP/Ethernet.py | 30 ++++++++++++--- pbgpp/PCAP/IP.py | 3 +- pbgpp/PCAP/Information.py | 17 +++++++- 10 files changed, 170 insertions(+), 10 deletions(-) create mode 100644 pbgpp/Output/Filters/VLANCustomerFilter.py create mode 100644 pbgpp/Output/Filters/VLANServiceFilter.py diff --git a/pbgpp/Application/CLI.py b/pbgpp/Application/CLI.py index 1ce121f..78110b5 100755 --- a/pbgpp/Application/CLI.py +++ b/pbgpp/Application/CLI.py @@ -65,6 +65,8 @@ def main(): group_4.add_argument("--filter-community-value", help="only print messages containing the given community value (e.g., '12345')", nargs="+", action="append", dest="filter_community_value") group_4.add_argument("--filter-source-ip", help="only print messages containing the given source IP address (e.g., '80.81.82.83')", nargs="+", action="append", dest="filter_source_ip") group_4.add_argument("--filter-source-mac", help="only print messages containing the given source MAC address (e.g., 'aabbccddeeff')", nargs="+", action="append", dest="filter_source_mac") + group_4.add_argument("--filter-customer-vlan", help="only print messages containing the given (customer) VLAN ID (e.g., '42')", nargs="+", action="append", dest="filter_customer_vlan") + group_4.add_argument("--filter-service-vlan", help="only print messages containing the given service VLAN ID (e.g., '1337')", nargs="+", action="append", dest="filter_service_vlan") group_4.add_argument("--filter-destination-ip", help="only print messages containing the given destination IP address (e.g., '80.81.82.83')", nargs="+", action="append", dest="filter_destination_ip") group_4.add_argument("--filter-destination-mac", help="only print messages containing the given destination MAC address (e.g., 'aabbccddeeff')", nargs="+", action="append", dest="filter_destination_mac") group_4.add_argument("--filter-large-community", help="only print messages containing one or more matching large communities (e.g., '11:22:33', '11:*:*', '*:22:33')", nargs="+", action="append", dest="filter_large_community") diff --git a/pbgpp/Application/Handler.py b/pbgpp/Application/Handler.py index 348d63f..bc9b326 100755 --- a/pbgpp/Application/Handler.py +++ b/pbgpp/Application/Handler.py @@ -247,6 +247,18 @@ def __parse_filters(self): self.prefilters.append(MACDestinationFilter(MACSourceFilter.clear_input(filters))) logger.debug("Added " + str(len(filters)) + " pre-filter(s) of MACDestinationFilter") + if self.args.filter_customer_vlan: + values = self.args.filter_customer_vlan + filters = list(chain(*values)) + self.prefilters.append(VLANCustomerFilter(filters)) + logger.debug("Added " + str(len(filters)) + " pre-filter(s) of VLANCustomerFilter") + + if self.args.filter_service_vlan: + values = self.args.filter_service_vlan + filters = list(chain(*values)) + self.prefilters.append(VLANServiceFilter(filters)) + logger.debug("Added " + str(len(filters)) + " pre-filter(s) of VLANServiceFilter") + if self.args.filter_timestamp: values = self.args.filter_timestamp filters = list(chain(*values)) @@ -318,8 +330,9 @@ def __packet_handler(self, header, payload): eth = PCAPEthernet(payload) # Check for raw ethernet packet - if not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV4: - + eth_type = eth.get_type() + if eth_type != PCAPEthernet.ETH_TYPE_IPV4 and eth_type != PCAPEthernet.ETH_TYPE_VLAN and eth_type != PCAPEthernet.ETH_TYPE_QINQ: + # Check for SLL-packet eth = PCAPCookedCapture(payload) diff --git a/pbgpp/Output/Filters/VLANCustomerFilter.py b/pbgpp/Output/Filters/VLANCustomerFilter.py new file mode 100644 index 0000000..de86bbd --- /dev/null +++ b/pbgpp/Output/Filters/VLANCustomerFilter.py @@ -0,0 +1,45 @@ +# +# 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 + + +class VLANCustomerFilter(BGPFilter): + def __init__(self, values=[]): + BGPFilter.__init__(self, values) + + def apply(self, pcap_information): + # !!! Attention: This is a pre-parsing filter! + # This filter must be applied BEFORE parsing, otherwise it will unnecessarily slow down + # the whole application. BGP messages don't have to be parsed when applying that filter + # directly after reading PCAP packet header + + try: + for v in self.values: + if pcap_information.get_customer_vlan() == v: + return True + + if v[0:1] == "~" and pcap_information.get_customer_vlan() != v[1:]: + return True + + # Searched value was not found + return False + except Exception as e: + # On error the filtering was not successful (due to wrong fields, etc.) + return False diff --git a/pbgpp/Output/Filters/VLANServiceFilter.py b/pbgpp/Output/Filters/VLANServiceFilter.py new file mode 100644 index 0000000..01eb8bb --- /dev/null +++ b/pbgpp/Output/Filters/VLANServiceFilter.py @@ -0,0 +1,45 @@ +# +# 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 + + +class VLANServiceFilter(BGPFilter): + def __init__(self, values=[]): + BGPFilter.__init__(self, values) + + def apply(self, pcap_information): + # !!! Attention: This is a pre-parsing filter! + # This filter must be applied BEFORE parsing, otherwise it will unnecessarily slow down + # the whole application. BGP messages don't have to be parsed when applying that filter + # directly after reading PCAP packet header + + try: + for v in self.values: + if pcap_information.get_service_vlan() == v: + return True + + if v[0:1] == "~" and pcap_information.get_service_vlan() != v[1:]: + return True + + # Searched value was not found + return False + except Exception as e: + # On error the filtering was not successful (due to wrong fields, etc.) + return False diff --git a/pbgpp/Output/Formatters/HumanReadable.py b/pbgpp/Output/Formatters/HumanReadable.py index 2be7c0d..47ca4b7 100755 --- a/pbgpp/Output/Formatters/HumanReadable.py +++ b/pbgpp/Output/Formatters/HumanReadable.py @@ -48,9 +48,16 @@ def apply(self, message): # |--- 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" + # Show VLAN tags only if existent + if message.pcap_information.get_customer_vlan() != None: + string += self.prefix(0) + "VLAN (Customer): " + message.pcap_information.get_customer_vlan() + "\n" + if message.pcap_information.get_service_vlan() != None: + string += self.prefix(0) + "VLAN (Service): " + message.pcap_information.get_service_vlan() + "\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" diff --git a/pbgpp/Output/Formatters/JSON.py b/pbgpp/Output/Formatters/JSON.py index 7675c2c..12ce49b 100755 --- a/pbgpp/Output/Formatters/JSON.py +++ b/pbgpp/Output/Formatters/JSON.py @@ -42,6 +42,8 @@ def apply(self, message): "destination_mac": message.pcap_information.get_mac().get_destination_string(), "source_ip": message.pcap_information.get_ip().get_source_string(), "destination_ip": message.pcap_information.get_ip().get_destination_string(), + "customer_vlan": message.pcap_information.get_customer_vlan(), + "service_vlan": message.pcap_information.get_service_vlan(), "message_data": None } diff --git a/pbgpp/Output/Formatters/LineBased.py b/pbgpp/Output/Formatters/LineBased.py index bf56c4e..2572f79 100755 --- a/pbgpp/Output/Formatters/LineBased.py +++ b/pbgpp/Output/Formatters/LineBased.py @@ -34,6 +34,8 @@ class LineBasedFormatter(BGPFormatter): FIELD_MESSAGE_IP_DESTINATION = ["destination_ip", "dst_ip"] FIELD_MESSAGE_MAC_SOURCE = ["source_mac", "src_mac", "mac_src", "mac_source"] FIELD_MESSAGE_MAC_DESTINATION = ["destination_mac", "dst_mac", "mac_dst", "mac_destination"] + FIELD_MESSAGE_VLAN_CUSTOMER = ["vlan_customer", "vlan_id_customer", "customer_vlan", "customer_vlan_id"] + FIELD_MESSAGE_VLAN_SERVICE = ["vlan_service", "vlan_id_service", "service_vlan", "service_vlan_id"] FIELD_MESSAGE_LENGTH = ["length"] FIELD_MESSAGE_TYPE = ["type"] @@ -61,6 +63,8 @@ class LineBasedFormatter(BGPFormatter): FIELD_MESSAGE_IP_DESTINATION, FIELD_MESSAGE_MAC_SOURCE, FIELD_MESSAGE_MAC_DESTINATION, + FIELD_MESSAGE_VLAN_CUSTOMER, + FIELD_MESSAGE_VLAN_SERVICE, FIELD_MESSAGE_LENGTH, FIELD_MESSAGE_TYPE, FIELD_UPDATE_SUBTYPE, @@ -130,6 +134,14 @@ def get_field_value(self, f, message): if f in self.FIELD_MESSAGE_MAC_DESTINATION: return message.pcap_information.get_mac().get_destination_string() + # Customer VLAN + if f in self.FIELD_MESSAGE_VLAN_CUSTOMER: + return message.pcap_information.get_customer_vlan() + + # Customer VLAN + if f in self.FIELD_MESSAGE_VLAN_SERVICE: + return message.pcap_information.get_service_vlan() + # ASN if f in self.FIELD_OPEN_MYASN: asn = getattr(message, "asn", False) diff --git a/pbgpp/PCAP/Ethernet.py b/pbgpp/PCAP/Ethernet.py index f2614ef..5fdad8a 100755 --- a/pbgpp/PCAP/Ethernet.py +++ b/pbgpp/PCAP/Ethernet.py @@ -25,15 +25,20 @@ class PCAPEthernet: - ETH_TYPE_IPV4 = 0x0800 + ETH_TYPE_IPV4 = 0x0800 # IPV4 + + ETH_TYPE_VLAN = 0x8100 # Vlan + ETH_TYPE_QINQ = 0x88a8 # QinQ def __init__(self, payload): self.payload = payload self.type = None self.mac = None + self.vlan_tags = [None, None] # [802.1q, 802.1ad] self.parsing_error = False self.parsed = False + self.payload_offset = 14 # default if there are no vlan tags self.__parse() @@ -44,11 +49,20 @@ def __parse(self): # Ethernet type self.type = struct.unpack("!H", self.payload[12:14])[0] + if self.type == PCAPEthernet.ETH_TYPE_VLAN: + self.vlan_tags[0] = struct.unpack("!H", self.payload[14:16])[0] & 0x0FFF #last 12 bits are vlan id + self.payload_offset = 16 + + if self.type == PCAPEthernet.ETH_TYPE_QINQ: + self.vlan_tags[0] = struct.unpack("!H", self.payload[18:20])[0] & 0x0FFF + self.vlan_tags[1] = struct.unpack("!H", self.payload[14:16])[0] & 0x0FFF + self.payload_offset = 20 + # MAC addresses - self.mac = PCAPLayer2Information(self.payload[6:12], self.payload[:6]) + self.mac = PCAPLayer2Information(self.payload[6:12], self.payload[:6], self.vlan_tags) #vlan? except Exception as e: - logging.error("Parsing ethernet frame caused exception (message: " + e.message + ")") #str(e) + logging.error("Parsing ethernet frame caused exception (message: " + str(e) + ")") self.parsing_error = True def get_type(self): @@ -57,11 +71,17 @@ def get_type(self): def get_mac(self): return self.mac + def get_service_vlan(self): + return self.vlan_tags[1] + + def get_customer_vlan(self): + return self.vlan_tags[0] + def get_payload(self): return self.payload - def get_eth_payload(self): - return self.payload[14:] + def get_eth_payload(self): + return self.payload[self.payload_offset:] def __str__(self): if self.parsed: diff --git a/pbgpp/PCAP/IP.py b/pbgpp/PCAP/IP.py index 5c42551..7a9b28d 100755 --- a/pbgpp/PCAP/IP.py +++ b/pbgpp/PCAP/IP.py @@ -22,7 +22,8 @@ class PCAPIP: - PROTO_TCP = 0x0006 + # TODO IPV6? + PROTO_TCP = 0x0006 # TODO: whats going on here? BITMASK_IP_HEADER_LENGTH = 0xf def __init__(self, payload): diff --git a/pbgpp/PCAP/Information.py b/pbgpp/PCAP/Information.py index 332c90e..7e6bbf0 100755 --- a/pbgpp/PCAP/Information.py +++ b/pbgpp/PCAP/Information.py @@ -58,6 +58,9 @@ def get_ports(self): def get_source_mac(self): return self.mac.source + + def get_customer_vlan(self): + return self.mac.vlan[0] def get_source_ip(self): return self.ip.source @@ -68,6 +71,9 @@ def get_source_port(self): def get_destination_mac(self): return self.mac.destination + def get_service_vlan(self): + return self.mac.vlan[1] + def get_destination_ip(self): return self.ip.destination @@ -76,10 +82,11 @@ def get_destination_port(self): class PCAPLayer2Information: - def __init__(self, source, destination): + def __init__(self, source, destination, vlan): #TODO vlan tag # Store source and destination MAC address self.source = source self.destination = destination + self.vlan = vlan # [802.1q, 802.1ad] def get_source_string(self, separated=False): if self.source is None: @@ -103,8 +110,14 @@ def get_destination_string(self, separated=False): else: return output + def get_customer_vlan(self): + return self.vlan[0] + + def get_service_vlan(self): + return self.vlan[1] + def __str__(self): - return "".format(self.get_source_string(), self.get_destination_string()) + return "".format(self.get_source_string(), self.get_destination_string(), self.get_customer_vlan(), self.get_service_vlan()) class PCAPLayer3Information: From c9a5f1c40168d76283d75b048a18f9177de95170 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 11:07:20 +0200 Subject: [PATCH 03/10] removed comment --- pbgpp/PCAP/Ethernet.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pbgpp/PCAP/Ethernet.py b/pbgpp/PCAP/Ethernet.py index 5fdad8a..ad06321 100755 --- a/pbgpp/PCAP/Ethernet.py +++ b/pbgpp/PCAP/Ethernet.py @@ -59,7 +59,7 @@ def __parse(self): self.payload_offset = 20 # MAC addresses - self.mac = PCAPLayer2Information(self.payload[6:12], self.payload[:6], self.vlan_tags) #vlan? + self.mac = PCAPLayer2Information(self.payload[6:12], self.payload[:6], self.vlan_tags) except Exception as e: logging.error("Parsing ethernet frame caused exception (message: " + str(e) + ")") From d2479f48ff2e48d409b061603b7c9321800cc744 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 11:09:48 +0200 Subject: [PATCH 04/10] MACDestinationFilter: fixed bug due to wrong functioncall --- pbgpp/Application/Handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pbgpp/Application/Handler.py b/pbgpp/Application/Handler.py index 348d63f..13a2120 100755 --- a/pbgpp/Application/Handler.py +++ b/pbgpp/Application/Handler.py @@ -244,7 +244,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: From 8abf30cee400f5399cd707446495912c2363d16d Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 12:46:55 +0200 Subject: [PATCH 05/10] Add PathIdentifierFilter --- pbgpp/Application/CLI.py | 1 + pbgpp/Application/Handler.py | 7 +++ pbgpp/Output/Filters/PathIdentifierFilter.py | 53 ++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 pbgpp/Output/Filters/PathIdentifierFilter.py diff --git a/pbgpp/Application/CLI.py b/pbgpp/Application/CLI.py index 1ce121f..b60d13f 100755 --- a/pbgpp/Application/CLI.py +++ b/pbgpp/Application/CLI.py @@ -56,6 +56,7 @@ 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-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") diff --git a/pbgpp/Application/Handler.py b/pbgpp/Application/Handler.py index 13a2120..706ee36 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)) 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 From 8c8e4209b83234a3bddc29505b19d3ec83d6507e Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 12:54:51 +0200 Subject: [PATCH 06/10] Small fixes --- pbgpp/Application/Handler.py | 2 ++ pbgpp/Output/Filters/VLANCustomerFilter.py | 4 ++-- pbgpp/Output/Filters/VLANServiceFilter.py | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pbgpp/Application/Handler.py b/pbgpp/Application/Handler.py index bc9b326..0e354b0 100755 --- a/pbgpp/Application/Handler.py +++ b/pbgpp/Application/Handler.py @@ -47,6 +47,8 @@ from pbgpp.Output.Filters.NLRIFilter import NLRIFilter from pbgpp.Output.Filters.NextHopFilter import NextHopFilter from pbgpp.Output.Filters.TimestampFilter import TimestampFilter +from pbgpp.Output.Filters.VLANCustomerFilter import VLANCustomerFilter +from pbgpp.Output.Filters.VLANServiceFilter import VLANServiceFilter from pbgpp.Output.Filters.WithdrawnFilter import WithdrawnFilter from pbgpp.Output.Formatters.HumanReadable import HumanReadableFormatter from pbgpp.Output.Formatters.JSON import JSONFormatter diff --git a/pbgpp/Output/Filters/VLANCustomerFilter.py b/pbgpp/Output/Filters/VLANCustomerFilter.py index de86bbd..3c0e6b8 100644 --- a/pbgpp/Output/Filters/VLANCustomerFilter.py +++ b/pbgpp/Output/Filters/VLANCustomerFilter.py @@ -32,10 +32,10 @@ def apply(self, pcap_information): try: for v in self.values: - if pcap_information.get_customer_vlan() == v: + if str(pcap_information.get_customer_vlan()) == v: return True - if v[0:1] == "~" and pcap_information.get_customer_vlan() != v[1:]: + if v[0:1] == "~" and str(pcap_information.get_customer_vlan()) != v[1:]: return True # Searched value was not found diff --git a/pbgpp/Output/Filters/VLANServiceFilter.py b/pbgpp/Output/Filters/VLANServiceFilter.py index 01eb8bb..3779404 100644 --- a/pbgpp/Output/Filters/VLANServiceFilter.py +++ b/pbgpp/Output/Filters/VLANServiceFilter.py @@ -32,10 +32,10 @@ def apply(self, pcap_information): try: for v in self.values: - if pcap_information.get_service_vlan() == v: + if str(pcap_information.get_service_vlan()) == v: return True - if v[0:1] == "~" and pcap_information.get_service_vlan() != v[1:]: + if v[0:1] == "~" and str(pcap_information.get_service_vlan()) != v[1:]: return True # Searched value was not found From 353c1ff8c445970e2cc84ee34c96231e237f70d6 Mon Sep 17 00:00:00 2001 From: cmoeller-dx <67698868+cmoeller-dx@users.noreply.github.com> Date: Mon, 3 Aug 2020 13:28:58 +0200 Subject: [PATCH 07/10] Update Ethernet.py --- pbgpp/PCAP/Ethernet.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pbgpp/PCAP/Ethernet.py b/pbgpp/PCAP/Ethernet.py index ad06321..1bae080 100755 --- a/pbgpp/PCAP/Ethernet.py +++ b/pbgpp/PCAP/Ethernet.py @@ -29,6 +29,8 @@ class PCAPEthernet: ETH_TYPE_VLAN = 0x8100 # Vlan ETH_TYPE_QINQ = 0x88a8 # QinQ + + BITMASK_VLAN_ID_LENGTH = 0x0FFF def __init__(self, payload): self.payload = payload @@ -54,8 +56,8 @@ def __parse(self): self.payload_offset = 16 if self.type == PCAPEthernet.ETH_TYPE_QINQ: - self.vlan_tags[0] = struct.unpack("!H", self.payload[18:20])[0] & 0x0FFF - self.vlan_tags[1] = struct.unpack("!H", self.payload[14:16])[0] & 0x0FFF + self.vlan_tags[0] = struct.unpack("!H", self.payload[18:20])[0] & BITMASK_VLAN_ID_LENGTH + self.vlan_tags[1] = struct.unpack("!H", self.payload[14:16])[0] & BITMASK_VLAN_ID_LENGTH self.payload_offset = 20 # MAC addresses From 7f835f811aa0ce9a97e111c045b0b19145c5d123 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Tue, 4 Aug 2020 12:42:57 +0200 Subject: [PATCH 08/10] Add ability to read packages from ipv6 speakers (excluding extended headers) --- pbgpp/Application/Handler.py | 8 +++--- pbgpp/Output/Formatters/HumanReadable.py | 9 +++++- pbgpp/PCAP/CookedCapture.py | 1 + pbgpp/PCAP/Ethernet.py | 1 + pbgpp/PCAP/IP.py | 35 ++++++++++++++++++++---- pbgpp/PCAP/Information.py | 27 ++++++++++++++++-- 6 files changed, 68 insertions(+), 13 deletions(-) diff --git a/pbgpp/Application/Handler.py b/pbgpp/Application/Handler.py index 706ee36..c466499 100755 --- a/pbgpp/Application/Handler.py +++ b/pbgpp/Application/Handler.py @@ -325,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()) @@ -342,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/Output/Formatters/HumanReadable.py b/pbgpp/Output/Formatters/HumanReadable.py index 2be7c0d..66aaf9a 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 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..f49764f 100755 --- a/pbgpp/PCAP/IP.py +++ b/pbgpp/PCAP/IP.py @@ -23,7 +23,13 @@ class PCAPIP: PROTO_TCP = 0x0006 - BITMASK_IP_HEADER_LENGTH = 0xf + BITMASK_IP_HEADER_LENGTH = 0xF + + IP6_HEADER_HOP_BY_HOP = 0x0 + IP6_HEADER_DESTINATION_OPTIONS = 0x3C + IP6_HEADER_ROUTING = 0x2B + IP6_HEADER_FRAGMENT = 0x2C + # TODO: Disassemble ipv6 extension headers and extract payload if necessary def __init__(self, payload): # Assign variables @@ -45,11 +51,30 @@ 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 + + self.header_length = 40 + self.total_length = struct.unpack("!H", self.payload[4:6])[0] + self.header_length + if self.total_length == 40: # no jumbo frame support + raise NotImplementedError('Jumbo Frames are not supported') + + self.protocol = struct.unpack("!B", self.payload[6])[0] # Next-Header + #TODO: ip6 extension headers are currently not supported + if self.protocol != self.PROTO_TCP: + raise NotImplementedError('Next Header has to be of instance TCP_PROTO (Extension Headers currently not supported)') + + #--HOP LIMIT-- We interpret that package, even if the Hop Limit is exceeded - ip_set = struct.unpack("!BBBBBBBB", self.payload[12:20]) - self.addresses = PCAPLayer3Information(ip_set[0:4], ip_set[4:8]) + ip_set = struct.unpack("!16H", self.payload[8:40]) + self.addresses = PCAPLayer3Information(ip_set[:8], ip_set[8:], PCAPLayer3Information.IP_VERSION_6) 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()) From c51d6a194a76df3947129a8bf1abcb67f530bb3b Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Tue, 11 Aug 2020 12:36:22 +0200 Subject: [PATCH 09/10] Feature add: Multiprotocol parsing and represntation (IPv6) --- pbgpp/Application/CLI.py | 4 +- pbgpp/BGP/Update/PathAttributes/MPNextHop.py | 60 +++++++++ .../BGP/Update/PathAttributes/MPReachNLRI.py | 117 +++++++++++++++++- .../Update/PathAttributes/MPUnReachNLRI.py | 78 +++++++++++- pbgpp/BGP/Update/Route.py | 3 +- pbgpp/BGP/Update/Route6.py | 101 +++++++++++++++ pbgpp/Output/Formatters/HumanReadable.py | 17 ++- pbgpp/Output/Formatters/LineBased.py | 48 +++++++ pbgpp/PCAP/IP.py | 39 ++++-- 9 files changed, 447 insertions(+), 20 deletions(-) create mode 100644 pbgpp/BGP/Update/PathAttributes/MPNextHop.py create mode 100644 pbgpp/BGP/Update/Route6.py diff --git a/pbgpp/Application/CLI.py b/pbgpp/Application/CLI.py index b60d13f..ffcf7b4 100755 --- a/pbgpp/Application/CLI.py +++ b/pbgpp/Application/CLI.py @@ -56,8 +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-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-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/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..ed2c57e 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py @@ -18,16 +18,131 @@ # 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 +#TODO 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/Formatters/HumanReadable.py b/pbgpp/Output/Formatters/HumanReadable.py index 66aaf9a..898fff6 100755 --- a/pbgpp/Output/Formatters/HumanReadable.py +++ b/pbgpp/Output/Formatters/HumanReadable.py @@ -124,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 @@ -133,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..afb6209 100755 --- a/pbgpp/Output/Formatters/LineBased.py +++ b/pbgpp/Output/Formatters/LineBased.py @@ -24,9 +24,12 @@ 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 +#TODO list UNREACHABLE und REACHABLE fields class LineBasedFormatter(BGPFormatter): FIELD_MESSAGE_TIMESTAMP = ["timestamp"] @@ -44,6 +47,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 +76,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 +224,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/IP.py b/pbgpp/PCAP/IP.py index f49764f..8caf698 100755 --- a/pbgpp/PCAP/IP.py +++ b/pbgpp/PCAP/IP.py @@ -25,11 +25,17 @@ class PCAPIP: PROTO_TCP = 0x0006 BITMASK_IP_HEADER_LENGTH = 0xF + IP6_STATIC_HEADER_LENGTH = 40 + IP6_HEADER_HOP_BY_HOP = 0x0 - IP6_HEADER_DESTINATION_OPTIONS = 0x3C IP6_HEADER_ROUTING = 0x2B - IP6_HEADER_FRAGMENT = 0x2C - # TODO: Disassemble ipv6 extension headers and extract payload if necessary + 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 @@ -61,20 +67,27 @@ def __parse(self): if self.version == PCAPLayer3Information.IP_VERSION_6: self.flow_label = struct.unpack("!L", self.payload[:4])[0] & 0x000FFFFF - self.header_length = 40 + # 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 == 40: # no jumbo frame support + if self.total_length == self.header_length: # no jumbo frame support raise NotImplementedError('Jumbo Frames are not supported') - self.protocol = struct.unpack("!B", self.payload[6])[0] # Next-Header - #TODO: ip6 extension headers are currently not supported - if self.protocol != self.PROTO_TCP: - raise NotImplementedError('Next Header has to be of instance TCP_PROTO (Extension Headers currently not supported)') - - #--HOP LIMIT-- We interpret that package, even if the Hop Limit is exceeded + # 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("!16H", self.payload[8:40]) - self.addresses = PCAPLayer3Information(ip_set[:8], ip_set[8:], PCAPLayer3Information.IP_VERSION_6) + 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 From 68f0cf4b62810e7f6ef282a7fb2289e1c420fd82 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Tue, 11 Aug 2020 12:46:16 +0200 Subject: [PATCH 10/10] removed old TODO marks --- pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py | 1 - pbgpp/Output/Formatters/LineBased.py | 2 -- 2 files changed, 3 deletions(-) diff --git a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py index ed2c57e..a952165 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py @@ -29,7 +29,6 @@ import socket import math -#TODO class PathAttributeMPReachNLRI(BGPPathAttribute): """ RFC 4760 diff --git a/pbgpp/Output/Formatters/LineBased.py b/pbgpp/Output/Formatters/LineBased.py index afb6209..3b1695d 100755 --- a/pbgpp/Output/Formatters/LineBased.py +++ b/pbgpp/Output/Formatters/LineBased.py @@ -29,8 +29,6 @@ from pbgpp.Output.Formatter import BGPFormatter from itertools import chain -#TODO list UNREACHABLE und REACHABLE fields - class LineBasedFormatter(BGPFormatter): FIELD_MESSAGE_TIMESTAMP = ["timestamp"] FIELD_MESSAGE_IP_SOURCE = ["source_ip", "src_ip"]