From 620ae819ab419440f36a93f876102297e9b36d53 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Mon, 3 Aug 2020 08:50:10 +0200 Subject: [PATCH 01/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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/14] 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"] From d11c45b48f1e6e90f76a06eac3e2791e31fa73f1 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Wed, 12 Aug 2020 14:41:14 +0200 Subject: [PATCH 11/14] CookedCapture: fixed function call --- pbgpp/PCAP/CookedCapture.py | 2 +- pbgpp/PCAP/Information.py | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/pbgpp/PCAP/CookedCapture.py b/pbgpp/PCAP/CookedCapture.py index 6e39ce3..49df41f 100644 --- a/pbgpp/PCAP/CookedCapture.py +++ b/pbgpp/PCAP/CookedCapture.py @@ -70,7 +70,7 @@ def __parse(self): raise Exception("SLL address length does not equal 6 (which means we don't got a MAC address here)") # MAC addresses - self.mac = PCAPLayer2Information(self.payload[6:12], None) + self.mac = PCAPLayer2Information(self.payload[6:12], None, None) # IP Type self.type = struct.unpack("!H", self.payload[14:16])[0] diff --git a/pbgpp/PCAP/Information.py b/pbgpp/PCAP/Information.py index a391f8f..582aac3 100755 --- a/pbgpp/PCAP/Information.py +++ b/pbgpp/PCAP/Information.py @@ -82,7 +82,7 @@ def get_destination_port(self): class PCAPLayer2Information: - def __init__(self, source, destination, vlan): #TODO vlan tag + def __init__(self, source, destination, vlan): # Store source and destination MAC address self.source = source self.destination = destination @@ -111,10 +111,16 @@ def get_destination_string(self, separated=False): return output def get_customer_vlan(self): - return self.vlan[0] + if len(self.vlan) == 0: + return None + else: + return self.vlan[0] def get_service_vlan(self): - return self.vlan[1] + if len(self.vlan) <= 1: + return None + else: + return self.vlan[1] def __str__(self): return "".format(self.get_source_string(), self.get_destination_string(), self.get_customer_vlan(), self.get_service_vlan()) From 6d038a6df9e3f8de8af56def4321942924cc57c6 Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Wed, 12 Aug 2020 15:02:08 +0200 Subject: [PATCH 12/14] updated: README and Changelog --- CHANGELOG.md | 4 +++- README.md | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb674fb..55b0974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,4 +40,6 @@ **Version 0.2.20** - Fixing bug output of large communities when using the LineBased formatter -**Version 0.2.20** - Add-Path capability added (RFC7911) \ No newline at end of file +**Version 0.2.20** - Add-Path capability added (RFC7911) + +**Version 0.3.1** - Add basic IPv6 support. Unpacking and filtering of VLAN and QinQ tagged ethernetframes is now possible. \ No newline at end of file diff --git a/README.md b/README.md index 6c00755..5a15369 100755 --- a/README.md +++ b/README.md @@ -72,7 +72,9 @@ Therefore we implemented the following feature. A user is now able to toggle a F If the NLRI field contains two 0-Bytes (translated to two 0.0.0.0/0 prefixes which should not occur at all) the programm assumes that the first 4 bytes are a Path Identifier and treates this field as an Add-Path message. ## Limitations -Currently, the parser doesn't perform a reassembly on fragmented TCP packets. This may leads into parsing errors and application warnings when you are trying to parse large BGP packets with several messages. +Currently, the parser doesn't perform a reassembly on fragmented TCP packets. This may leads into parsing errors and application warnings when you are trying to parse large BGP packets with several messages. + +In general, IPv6 BGP messages are understood, even filtering for sender/receiver addresses is possible (except for shorthand ipv6 notation i.e. 1337::42 has to be extended to 1337:0:0:0:0:0:0:42). Filtering REACH_NLRI and UNREACH_NLRI is not implemented. Currently, we are looking into some problems with running pbgpp with Python 2.7 and streaming the output to Kafka. However, Python 3.x works just fine. From 389a975535bdf27649cf70ee24e0021e4fcfd3bf Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Wed, 12 Aug 2020 16:14:30 +0200 Subject: [PATCH 13/14] merged Route6 class into Route class --- pbgpp/BGP/Statics.py | 4 + .../BGP/Update/PathAttributes/MPReachNLRI.py | 3 +- .../Update/PathAttributes/MPUnReachNLRI.py | 3 +- pbgpp/BGP/Update/Route.py | 113 +++++++++++++----- pbgpp/BGP/Update/Route6.py | 101 ---------------- 5 files changed, 89 insertions(+), 135 deletions(-) delete mode 100644 pbgpp/BGP/Update/Route6.py diff --git a/pbgpp/BGP/Statics.py b/pbgpp/BGP/Statics.py index c3e3c7d..04d7b95 100755 --- a/pbgpp/BGP/Statics.py +++ b/pbgpp/BGP/Statics.py @@ -443,3 +443,7 @@ class BGPStatics: # ** BGPUpdate Path Attribute EXTENDED_COMMUNITIES trans. Generic Experimental Part 3 ** EXT_COMMUNITY_T_GENERIC_EXPERIMENTAL_PART3_FLOW_SPEC_AS_4BYTE_FORMAT = 8 + + # IPV4 and IPV6 needs to be differentiated sometimes: + IP4_CODE = 4 + IP6_CODE = 6 diff --git a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py index a952165..060278e 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py @@ -21,7 +21,6 @@ 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 @@ -110,7 +109,7 @@ def __parse(self): 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])) + self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer], BGPStatics.IP6_CODE)) payload_pointer += prefix_len_bytes + 1 diff --git a/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py b/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py index 0c414e3..c3b3be4 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py @@ -21,7 +21,6 @@ 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 @@ -78,7 +77,7 @@ def __parse(self): 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])) + self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer], BGPStatics.IP6_CODE)) payload_pointer += prefix_len_bytes + 1 diff --git a/pbgpp/BGP/Update/Route.py b/pbgpp/BGP/Update/Route.py index 406f716..eeac4d3 100755 --- a/pbgpp/BGP/Update/Route.py +++ b/pbgpp/BGP/Update/Route.py @@ -19,18 +19,27 @@ import socket import struct +import math +from pbgpp.BGP.Statics import BGPStatics from pbgpp.BGP.Exceptions import BGPRouteInitializeError, BGPRouteConvertionError class BGPRoute: - def __init__(self, prefix, prefix_length): + def __init__(self, prefix, prefix_length, proto=BGPStatics.IP4_CODE): #If ipv6, proto needs to be set. otherwise, this operation # A route is universally used # Prefix = e.g. 123.123.123.123 # Length = 32 # To String: 123.123.123.123/32 (CIDR notation) + # + # Or IPv6 + # Prefix = e.g. 1337:1337:1337:1337:: + # Length = 64 + # To String: 1337:1337:1337:1337::/64 (CIDR notation) + print("this is the proto: {}".format(proto)) # Assign values + self.proto = proto self.prefix = prefix self.prefix_length = prefix_length @@ -42,10 +51,10 @@ def __init__(self, prefix, prefix_length): self._parse() @classmethod - def from_binary(cls, prefix, prefix_length): + def from_binary(cls, prefix, prefix_length, proto=BGPStatics.IP4_CODE): # Create a class instance from bytes if isinstance(prefix, bytes) and isinstance(prefix_length, bytes): - return cls(prefix, prefix_length) + return cls(prefix, prefix_length, proto) else: raise BGPRouteInitializeError("prefix and prefix_length must be instance of bytes.") @@ -62,34 +71,78 @@ def __eq__(self, other): 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_length_decimal = struct.unpack("!B", self.prefix_length)[0] - self.prefix_length_string = str(self.prefix_length_decimal) - - if 0 <= self.prefix_length_decimal <= 8: - # Length of prefix field: 1 Byte - fields = struct.unpack("!B", self.prefix) - self.prefix_string = str(fields[0]) + ".0.0.0/" + self.prefix_length_string - - elif 9 <= self.prefix_length_decimal <= 16: - # Length of prefix field: 2 Bytes - fields = struct.unpack("!BB", self.prefix) - self.prefix_string = str(fields[0]) + "." + str(fields[1]) + ".0.0/" + self.prefix_length_string - - elif 17 <= self.prefix_length_decimal <= 24: - # Length of prefix field: 3 Bytes - fields = struct.unpack("!BBB", self.prefix) - self.prefix_string = str(fields[0]) + "." + str(fields[1]) + "." + str(fields[2]) + ".0/" + self.prefix_length_string - - elif 25 <= self.prefix_length_decimal: - # Length of prefix field: 4 Bytes - fields = struct.unpack("!BBBB", self.prefix) - self.prefix_string = str(fields[0]) + "." + str(fields[1]) + "." + str(fields[2]) + "." + str(fields[3]) + "/" + self.prefix_length_string - + if self.proto == BGPStatics.IP4_CODE: + # Check the prefix length at first as that length is needed to determine + # how many bytes we need to parse afterwards + self.prefix_length_decimal = struct.unpack("!B", self.prefix_length)[0] + self.prefix_length_string = str(self.prefix_length_decimal) + + if 0 <= self.prefix_length_decimal <= 8: + # Length of prefix field: 1 Byte + fields = struct.unpack("!B", self.prefix) + self.prefix_string = str(fields[0]) + ".0.0.0/" + self.prefix_length_string + + elif 9 <= self.prefix_length_decimal <= 16: + # Length of prefix field: 2 Bytes + fields = struct.unpack("!BB", self.prefix) + self.prefix_string = str(fields[0]) + "." + str(fields[1]) + ".0.0/" + self.prefix_length_string + + elif 17 <= self.prefix_length_decimal <= 24: + # Length of prefix field: 3 Bytes + fields = struct.unpack("!BBB", self.prefix) + self.prefix_string = str(fields[0]) + "." + str(fields[1]) + "." + str(fields[2]) + ".0/" + self.prefix_length_string + + elif 25 <= self.prefix_length_decimal: + # Length of prefix field: 4 Bytes + fields = struct.unpack("!BBBB", self.prefix) + self.prefix_string = str(fields[0]) + "." + str(fields[1]) + "." + str(fields[2]) + "." + str(fields[3]) + "/" + self.prefix_length_string + + else: + raise BGPRouteConvertionError("was not able to parse bytes.") + else: - raise BGPRouteConvertionError("was not able to parse bytes.") + # 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 self.prefix_length_decimal >= 0 and self.prefix_length_decimal <= 128: + 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) + + else: + raise BGPRouteConvertionError("was not able to parse bytes.") + @staticmethod - def decimal_ip_to_string(decimal): + def decimal_ip_to_string(decimal): # implement 16byte int to convert to ipv6 addresses return socket.inet_ntoa(struct.pack('!L', decimal)) diff --git a/pbgpp/BGP/Update/Route6.py b/pbgpp/BGP/Update/Route6.py deleted file mode 100644 index 6ec2fbf..0000000 --- a/pbgpp/BGP/Update/Route6.py +++ /dev/null @@ -1,101 +0,0 @@ -# -# 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 From 7db344298c94dc11a86cdd448aa455f188f6362e Mon Sep 17 00:00:00 2001 From: cmoeller-dx Date: Wed, 12 Aug 2020 16:42:20 +0200 Subject: [PATCH 14/14] removed unnecessary print statement --- pbgpp/BGP/Update/Route.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pbgpp/BGP/Update/Route.py b/pbgpp/BGP/Update/Route.py index eeac4d3..f1ceb10 100755 --- a/pbgpp/BGP/Update/Route.py +++ b/pbgpp/BGP/Update/Route.py @@ -36,7 +36,6 @@ def __init__(self, prefix, prefix_length, proto=BGPStatics.IP4_CODE): #If ipv6, # Prefix = e.g. 1337:1337:1337:1337:: # Length = 64 # To String: 1337:1337:1337:1337::/64 (CIDR notation) - print("this is the proto: {}".format(proto)) # Assign values self.proto = proto