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. diff --git a/pbgpp/Application/CLI.py b/pbgpp/Application/CLI.py index 1ce121f..8873d5c 100755 --- a/pbgpp/Application/CLI.py +++ b/pbgpp/Application/CLI.py @@ -56,7 +56,8 @@ def main(): group_4.add_argument("--filter-message-size", help="only print messages with given message size in bytes (e.g., 128)", nargs="+", action="append", dest="filter_message_size") group_4.add_argument("--filter-message-type", help="only print messages with given BGP message type (KEEPALIVE, NOTIFICATION, OPEN, ROUTE-REFRESH, UPDATE, WITHDRAWAL)", nargs="+", action="append", dest="filter_message_type") group_4.add_argument("--filter-message-subtype", help="only print UPDATE messages with given message sub type (WITHDRAWAL, ANNOUNCE, BOTH, NONE)", nargs="+", action="append", dest="filter_message_subtype") - group_4.add_argument("--filter-nlri", help="only print messages containing the given nlri prefix (e.g., '80.81.82.0/24'", nargs="+", action="append", dest="filter_nlri") + group_4.add_argument("--filter-pathid", help="only print messages with the given path identifier (e.g., '7')", nargs="+", action="append", dest="filter_pathid") + group_4.add_argument("--filter-nlri", help="only print messages containing the given nlri prefix (e.g., '80.81.82.0/24')", nargs="+", action="append", dest="filter_nlri") group_4.add_argument("--filter-withdrawn", help="only print messages containing the given withdrawn routes (e.g., '80.81.82.0/24'", nargs="+", action="append", dest="filter_withdrawn") group_4.add_argument("--filter-next-hop", help="only print messages containing the given next hop (e.g., '80.81.82.83')", nargs="+", action="append", dest="filter_next_hop") group_4.add_argument("--filter-as", help="only print messages containing the given ASN in path AS_PATH attribute (e.g., '12345')", nargs="+", action="append", dest="filter_asn") @@ -65,6 +66,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..476b12b 100755 --- a/pbgpp/Application/Handler.py +++ b/pbgpp/Application/Handler.py @@ -46,7 +46,10 @@ 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.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 @@ -163,6 +166,12 @@ def __parse_filters(self): self.filters.append(MessageSubTypeFilter(filters)) logger.debug("Added " + str(len(filters)) + " filter(s) of MessageSubTypeFilter") + if self.args.filter_pathid: + values = self.args.filter_pathid + filters = list(chain(*values)) + self.filters.append(PathIdentifierFilter(filters)) + logger.debug("Added " + str(len(filters)) + " filter(s) of PathIdentfierFilter") + if self.args.filter_nlri: values = self.args.filter_nlri filters = list(chain(*values)) @@ -244,9 +253,21 @@ 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_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,13 +339,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 and not eth_type == PCAPEthernet.ETH_TYPE_VLAN and not eth_type == PCAPEthernet.ETH_TYPE_QINQ: # Check for SLL-packet eth = PCAPCookedCapture(payload) - if not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV4: - logger.debug("Discarding PCAP packet " + str(self.__packet_counter) + " due to non-IPv4 ethernet type.") + if not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV4 and not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV6: + logger.debug("Discarding PCAP packet " + str(self.__packet_counter) + " due to non-IPv4 or non-IPv6 ethernet type.") return False ip = PCAPIP(eth.get_eth_payload()) @@ -335,7 +356,7 @@ def __packet_handler(self, header, payload): tcp = PCAPTCP(ip.get_ip_payload()) - pcap_information = PCAPInformation(header.getts(), eth.mac, ip.addresses, tcp.ports) + pcap_information = PCAPInformation(header.getts(), eth.get_mac(), ip.get_addresses(), tcp.get_ports()) for filter in self.prefilters: if not filter.apply(pcap_information): diff --git a/pbgpp/BGP/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/Message.py b/pbgpp/BGP/Update/Message.py index fb48ab4..da977ea 100755 --- a/pbgpp/BGP/Update/Message.py +++ b/pbgpp/BGP/Update/Message.py @@ -64,33 +64,15 @@ def __parse(self): # Loop through withdrawals while continue_loop: - # AddPath assumption? look for description in the method for NLRI parsing - if self.flags["addpath"].get_value() == 0: # No AddPath messages - pass + # AddPath assumption? + current_byte_position = self.__addpath_routine(current_byte_position) - else: - pathId_length_bytes = self.payload[current_byte_position:current_byte_position + 4] - pathId = struct.unpack("!I", pathId_length_bytes)[0] - - if self.flags["addpath"].get_value() == 1: # Only AddPath - self.add_path = True - self.path_id = pathId - current_byte_position += 4 - - else: # Try to find out (using metric) - if pathId < 65536: - self.add_path = True - self.path_id = pathId - current_byte_position += 4 - #else: drop the Path Id, its likely that this is not an AddPath msg - # First of all we need to parse the length of the withdrawn prefix. Depending on the prefix length # we can determine the length following prefix itself prefix_length_bytes = self.payload[current_byte_position:current_byte_position + 1] prefix_length = struct.unpack("!B", prefix_length_bytes)[0] current_byte_position += 1 - if prefix_length == 0: prefix_bytes = prefix_length_bytes elif 0 < prefix_length <= 8: @@ -179,34 +161,7 @@ def __parse(self): current_byte_position = self.path_attributes_length + 4 + self.withdrawn_routes_length while continue_loop: - """ - The Following is a Fix for missing Add_Path feature. - Due to the lack of a definition for this case, we need depend on the users decision. - See RFC 7911 Chapter 6 p.5 (22.07.2020). - - In most cases, the pathId is lower than 2**16. Also it is uncommon, - that one BGP UPDATE message contains the 0.0.0.0/0 prefix 2 times. - This leads to the following metric if the user sets the add_path_flag to 2. - """ - # AddPath assumption? - if self.flags["addpath"].get_value() == 0: # No AddPath messages - pass - - else: - pathId_length_bytes = self.payload[current_byte_position:current_byte_position + 4] - pathId = struct.unpack("!I", pathId_length_bytes)[0] - - if self.flags["addpath"].get_value() == 1: # Only AddPath - self.add_path = True - self.path_id = pathId - current_byte_position += 4 - - else: # Try to find out (using metric) - if pathId < 65536: - self.add_path = True - self.path_id = pathId - current_byte_position += 4 - #else: drop the Path Id, its likely that this is not an AddPath msg + current_byte_position = self.__addpath_routine(current_byte_position) # First of all we have to check the prefix length as byte-length of the following # prefix depends on its prefix length (This is a 1-byte-field) @@ -259,3 +214,34 @@ def __parse(self): self.error = True self.error = False + + def __addpath_routine(self, current_byte_position): + """ + The Following is a Fix for missing Add_Path feature. + Due to the lack of a definition for this case, we need depend on the users decision. + See RFC 7911 Chapter 6 p.5 (22.07.2020). + + In most cases, the pathId is lower than 2**16. Also it is uncommon, + that one BGP UPDATE message contains the 0.0.0.0/0 prefix 2 times. + This leads to the following metric if the user sets the add_path_flag to 2. + """ + # AddPath assumption? + if self.flags["addpath"].get_value() == 0: # No AddPath messages + pass + + else: + pathId_length_bytes = self.payload[current_byte_position:current_byte_position + 4] + pathId = struct.unpack("!I", pathId_length_bytes)[0] + + if self.flags["addpath"].get_value() == 1: # Only AddPath + self.add_path = True + self.path_id = pathId + current_byte_position += 4 + + else: # Try to find out (using metric) + if pathId < 65536: + self.add_path = True + self.path_id = pathId + current_byte_position += 4 + #else: drop the Path Id, its likely that this is not an AddPath msg + return current_byte_position \ No newline at end of file diff --git a/pbgpp/BGP/Update/PathAttributes/MPNextHop.py b/pbgpp/BGP/Update/PathAttributes/MPNextHop.py new file mode 100644 index 0000000..db83070 --- /dev/null +++ b/pbgpp/BGP/Update/PathAttributes/MPNextHop.py @@ -0,0 +1,60 @@ +# +# This file is part of PCAP BGP Parser (pbgpp) +# +# Copyright 2016-2020 DE-CIX Management GmbH +# Author: Christopher Moeller +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from pbgpp.BGP.Statics import BGPStatics +from pbgpp.BGP.Update.Route import BGPRoute + +import struct +import socket + +class MPNextHop: + "This class is an extension of the MP_REACH field" + def __init__(self, payload, proto): + self.payload = payload + self.proto = proto + + self.next_hop = None #string representation of address + + self.__parse() + + def __parse(self): + try: + self.parsed = True + self.error = False + + if self.proto == socket.AF_INET: + fields = struct.unpack("!4B", self.payload) + self.next_hop = str(fields[0]) + "." + str(fields[1]) + "." + str(fields[2]) + "." + str(fields[3]) + + else: + fields = struct.unpack("!8H", self.payload) + next_hop = "" + for i in fields: + next_hop += str( hex(i)[2:] ) + ":" + self.next_hop = next_hop[:-1] + + except Exception as e: + self.error = True + + def __str__(self): + if self.parsed and not self.error: + return self.next_hop + else: + return None + diff --git a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py index aa0ea2f..060278e 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py @@ -18,16 +18,129 @@ # from pbgpp.BGP.Statics import BGPStatics +from pbgpp.BGP.Translation import BGPTranslation + +from pbgpp.BGP.Update.Route import BGPRoute from pbgpp.BGP.Update.PathAttribute import BGPPathAttribute +from pbgpp.BGP.Update.PathAttributes.MPNextHop import MPNextHop +import struct +import socket +import math class PathAttributeMPReachNLRI(BGPPathAttribute): + """ + RFC 4760 + +---------------------------------------------------------+ + | Address Family Identifier (2 octets) | + +---------------------------------------------------------+ + | Subsequent Address Family Identifier (1 octet) | + +---------------------------------------------------------+ + | Length of Next Hop Network Address (1 octet) | + +---------------------------------------------------------+ + | Network Address of Next Hop (variable) | + +---------------------------------------------------------+ + | Reserved (1 octet) | + +---------------------------------------------------------+ + | Network Layer Reachability Information (variable) | + +---------------------------------------------------------+ + """ + + def __init__(self, payload): BGPPathAttribute.__init__(self, payload) self.type = BGPStatics.UPDATE_ATTRIBUTE_MP_REACH_NLRI + self.afi = 0 + self.safi = 0 + self.next_hop = [] + self.nlri = [] + self.__parse() def __parse(self): self.parsed = True - self.error = False + self.error = False + payload_pointer = 0 + + self.afi = struct.unpack("!H", self.payload[:2])[0] + self.safi = struct.unpack("!B", self.payload[2])[0] # @todo use this if wanted, atm its not neccesary + self.next_hop_length = struct.unpack("!B", self.payload[3])[0] + payload_pointer = 4 + + if not self.next_hop_length == 0: #next_hop parsing + try: + if self.afi == 1: #IPv4 + if not self.next_hop_length % 4 == 0: + self.error = True + else: + for i in range(self.next_hop_length / 4): + self.next_hop.append( MPNextHop(self.payload[payload_pointer:payload_pointer+4], socket.AF_INET) ) + payload_pointer += 4 + + elif self.afi == 2: #IPv6 + if not self.next_hop_length % 16 == 0: + self.error = True + else: + for i in range(self.next_hop_length / 16): + self.next_hop.append( MPNextHop(self.payload[payload_pointer:payload_pointer+16], socket.AF_INET6) ) + payload_pointer += 16 + + else: + raise NotImplementedError + + except Exception as e: + self.error = True + + if not len(self.payload) == self.next_hop_length + 5: # afi + safi + hop_length + reserved = 5bytes + payload_pointer += 1 #skip reservation byte + try: + if self.afi == 1: #IPv4 + while payload_pointer < len(self.payload): + prefix_len = struct.unpack("!B", self.payload[payload_pointer])[0] + prefix_len_bytes = int(math.ceil(prefix_len / 8.0)) + + self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer])) + + payload_pointer += prefix_len_bytes + 1 + + elif self.afi == 2: #IPv6 + while payload_pointer < len(self.payload): + prefix_len = struct.unpack("!B", self.payload[payload_pointer])[0] + prefix_len_bytes = int(math.ceil(prefix_len / 8.0)) + + self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer], BGPStatics.IP6_CODE)) + + payload_pointer += prefix_len_bytes + 1 + + else: + raise NotImplementedError + + except Exception as e: + self.error = True + + def __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..c3b3be4 100755 --- a/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py +++ b/pbgpp/BGP/Update/PathAttributes/MPUnReachNLRI.py @@ -18,16 +18,91 @@ # from pbgpp.BGP.Statics import BGPStatics +from pbgpp.BGP.Translation import BGPTranslation + +from pbgpp.BGP.Update.Route import BGPRoute 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(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer], BGPStatics.IP6_CODE)) + + payload_pointer += prefix_len_bytes + 1 + + else: + raise NotImplementedError + + except Exception as e: + self.error = True + + def __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..f1ceb10 100755 --- a/pbgpp/BGP/Update/Route.py +++ b/pbgpp/BGP/Update/Route.py @@ -19,18 +19,26 @@ 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) # Assign values + self.proto = proto self.prefix = prefix self.prefix_length = prefix_length @@ -42,10 +50,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.") @@ -56,41 +64,84 @@ 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 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/Output/Filters/PathIdentifierFilter.py b/pbgpp/Output/Filters/PathIdentifierFilter.py new file mode 100644 index 0000000..8f9c04e --- /dev/null +++ b/pbgpp/Output/Filters/PathIdentifierFilter.py @@ -0,0 +1,53 @@ +# +# This file is part of PCAP BGP Parser (pbgpp) +# +# Copyright 2016-2017 DE-CIX Management GmbH +# Author: Christopher Moeller +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from pbgpp.Output.Filter import BGPFilter +from pbgpp.BGP.Statics import BGPStatics + + +class PathIdentifierFilter(BGPFilter): + def __init__(self, values=[]): + BGPFilter.__init__(self, values) + + def apply(self, message): + try: + # We first need to make that we are currently handling an UPDATE message + if message.type is not BGPStatics.MESSAGE_TYPE_UPDATE: + # Skip messages that are no UPDATE messages + return None + + if message.add_path: + # add_path enabled + for value in self.values: + negated = False + if value[0:1] == "~": + negated = True + value = value[1:] + + if not negated and str(message.path_id) == value: + return message + + if negated and str(message.path_id) != value: + return message + + # Searched value was not found + return None + except Exception as e: + # On error the filtering was not successful (due to wrong fields, etc.) + return None diff --git a/pbgpp/Output/Filters/VLANCustomerFilter.py b/pbgpp/Output/Filters/VLANCustomerFilter.py new file mode 100644 index 0000000..3c0e6b8 --- /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 str(pcap_information.get_customer_vlan()) == v: + return True + + if v[0:1] == "~" and str(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..3779404 --- /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 str(pcap_information.get_service_vlan()) == v: + return True + + if v[0:1] == "~" and str(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..a7cead7 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): @@ -48,10 +50,22 @@ 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" - 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" + + # 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" + + if message.pcap_information.get_ip().version == PCAPLayer3Information.IP_VERSION_4: + string += self.prefix(0) + "IP: " + message.pcap_information.get_ip().get_source_string() + ":" + message.pcap_information.get_ports().get_source_string() + " -> " + message.pcap_information.get_ip().get_destination_string() + ":" + message.pcap_information.get_ports().get_destination_string() + "\n" + else: + string += self.prefix(0) + "IP: [" + message.pcap_information.get_ip().get_source_string() + "]:" + message.pcap_information.get_ports().get_source_string() + " -> [" + message.pcap_information.get_ip().get_destination_string() + "]:" + message.pcap_information.get_ports().get_destination_string() + "\n" + string += self.prefix(0) + "Timestamp: " + message.pcap_information.get_timestmap_utc() + " (" + str(message.pcap_information.get_timestamp()[0]) + "." + str(message.pcap_information.get_timestamp()[1]) + ")\n" # Display additional information @@ -117,8 +131,8 @@ def apply(self, message): if message.path_attributes_length > 0: # Process path attributes + string += self.prefix(0) + "Path Attributes: \n" for attribute in message.path_attributes: - string += self.prefix(0) + "Path Attributes:" + "\n" if attribute.type == BGPStatics.UPDATE_ATTRIBUTE_EXTENDED_COMMUNITIES: # Extended Communities must be displayed in another way than other attributes @@ -126,6 +140,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/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..b617e2b 100755 --- a/pbgpp/Output/Formatters/LineBased.py +++ b/pbgpp/Output/Formatters/LineBased.py @@ -24,16 +24,19 @@ from pbgpp.BGP.Update.PathAttributes.LargeCommunities import PathAttributeLargeCommunities from pbgpp.BGP.Update.PathAttributes.NextHop import PathAttributeNextHop from pbgpp.BGP.Update.PathAttributes.Origin import PathAttributeOrigin +from pbgpp.BGP.Update.PathAttributes.MPReachNLRI import PathAttributeMPReachNLRI +from pbgpp.BGP.Update.PathAttributes.MPUnReachNLRI import PathAttributeMPUnReachNLRI from pbgpp.Output.Formatter import BGPFormatter from itertools import chain - class LineBasedFormatter(BGPFormatter): FIELD_MESSAGE_TIMESTAMP = ["timestamp"] FIELD_MESSAGE_IP_SOURCE = ["source_ip", "src_ip"] 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"] @@ -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"] @@ -61,6 +67,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, @@ -70,6 +78,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, @@ -130,6 +141,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) @@ -215,6 +234,45 @@ def get_field_value(self, f, message): return [r.prefix_length_string for r in prefixes] return None + # Attribute: Mulitprotocol: REACHable NLRI + if f in self.FIELD_UPDATE_ATTRIBUTE_MP_REACH_NLRI: + path_attributes = getattr(message, "path_attributes", False) + if path_attributes: + result = [] + for a in path_attributes: + if isinstance(a, PathAttributeMPReachNLRI): + for nlri in a.nlri: + result.append(nlri) + if not len(result) == 0: + return result + return None + + # Attribute: Mulitprotocol: NEXT HOP + if f in self.FIELD_UPDATE_ATTRIBUTE_MP_NEXT_HOP: + path_attributes = getattr(message, "path_attributes", False) + if path_attributes: + result = [] + for a in path_attributes: + if isinstance(a, PathAttributeMPReachNLRI): + for next_hop in a.next_hop: + result.append(next_hop) + if not len(result) == 0: + return result + return None + + # Attribute: Mulitprotocol: UNREACHable NLRI + if f in self.FIELD_UPDATE_ATTRIBUTE_MP_UNREACH_NLRI: + path_attributes = getattr(message, "path_attributes", False) + if path_attributes: + result = [] + for a in path_attributes: + if isinstance(a, PathAttributeMPUnReachNLRI): + for nlri in a.nlri: + result.append(nlri) + if not len(result) == 0: + return result + return None + # Attribute: Origin if f in self.FIELD_UPDATE_ATTRIBUTE_ORIGIN: path_attributes = getattr(message, "path_attributes", False) diff --git a/pbgpp/PCAP/CookedCapture.py b/pbgpp/PCAP/CookedCapture.py index 76d950f..49df41f 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 @@ -69,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/Ethernet.py b/pbgpp/PCAP/Ethernet.py index f2614ef..4423905 100755 --- a/pbgpp/PCAP/Ethernet.py +++ b/pbgpp/PCAP/Ethernet.py @@ -25,15 +25,24 @@ class PCAPEthernet: + ETH_TYPE_IPV4 = 0x0800 + ETH_TYPE_IPV6 = 0x86DD + + ETH_TYPE_VLAN = 0x8100 # Vlan + ETH_TYPE_QINQ = 0x88a8 # QinQ + + BITMASK_VLAN_ID_LENGTH = 0x0FFF 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 +53,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] & 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 - self.mac = PCAPLayer2Information(self.payload[6:12], self.payload[:6]) + 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: " + e.message + ")") #str(e) + logging.error("Parsing ethernet frame caused exception (message: " + str(e) + ")") self.parsing_error = True def get_type(self): @@ -57,11 +75,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..53fb4ff 100755 --- a/pbgpp/PCAP/IP.py +++ b/pbgpp/PCAP/IP.py @@ -22,8 +22,20 @@ class PCAPIP: - PROTO_TCP = 0x0006 - BITMASK_IP_HEADER_LENGTH = 0xf + PROTO_TCP = 0x0006 + BITMASK_IP_HEADER_LENGTH = 0xF + + IP6_STATIC_HEADER_LENGTH = 40 + + IP6_HEADER_HOP_BY_HOP = 0x0 + IP6_HEADER_ROUTING = 0x2B + IP6_HEADER_FRAGMENT = 0x2C #not implemented + IP6_HEADER_ESP = 0x32 #not implemented + IP6_HEADER_AUTH = 0x33 #used for ipsec + IP6_HEADER_DESTINATION_OPTIONS = 0x3C + + IP6_HEADER_EXTENSIONS = [IP6_HEADER_HOP_BY_HOP, IP6_HEADER_ROUTING, IP6_HEADER_FRAGMENT, + IP6_HEADER_ESP, IP6_HEADER_AUTH, IP6_HEADER_DESTINATION_OPTIONS] def __init__(self, payload): # Assign variables @@ -45,11 +57,37 @@ def __parse(self): self.header_length = (version_length & self.BITMASK_IP_HEADER_LENGTH) * 4 self.version = (version_length >> 4) - self.total_length = struct.unpack("!H", self.payload[2:4])[0] - self.protocol = struct.unpack("!B", self.payload[9:10])[0] + if self.version == PCAPLayer3Information.IP_VERSION_4: + self.total_length = struct.unpack("!H", self.payload[2:4])[0] + self.protocol = struct.unpack("!B", self.payload[9:10])[0] + + ip_set = struct.unpack("!BBBBBBBB", self.payload[12:20]) + self.addresses = PCAPLayer3Information(ip_set[0:4], ip_set[4:8], PCAPLayer3Information.IP_VERSION_4) + + if self.version == PCAPLayer3Information.IP_VERSION_6: + self.flow_label = struct.unpack("!L", self.payload[:4])[0] & 0x000FFFFF + + # Extract sender and receiver address + ip_set = struct.unpack("!16H", self.payload[8:40]) + self.addresses = PCAPLayer3Information(ip_set[:8], ip_set[8:], PCAPLayer3Information.IP_VERSION_6) + + # IPv6 header length, discard packet if Jumbo Frame (not implemented till now) + self.header_length = self.IP6_STATIC_HEADER_LENGTH + self.total_length = struct.unpack("!H", self.payload[4:6])[0] + self.header_length + if self.total_length == self.header_length: # no jumbo frame support + raise NotImplementedError('Jumbo Frames are not supported') + + # Check if there are header extensions + self.protocol = struct.unpack("!B", self.payload[6])[0] + if self.protocol in self.IP6_HEADER_EXTENSIONS: + + if self.protocol == self.IP6_HEADER_FRAGMENT or self.protocol == self.IP6_HEADER_ESP or self.protocol == self.IP6_HEADER_AUTH: + raise NotImplementedError('Unsupported IP6 extended header extension') - ip_set = struct.unpack("!BBBBBBBB", self.payload[12:20]) - self.addresses = PCAPLayer3Information(ip_set[0:4], ip_set[4:8]) + self.protocol = struct.unpack("!B", self.payload[self.IP6_STATIC_HEADER_LENGTH]) + self.header_length +=struct.unpack("!B", self.payload[self.IP6_STATIC_HEADER_LENGTH + 1]) + 1 + + #--HOP LIMIT-- We dont care about that since its not important for the parser def get_protocol(self): return self.protocol diff --git a/pbgpp/PCAP/Information.py b/pbgpp/PCAP/Information.py index 332c90e..582aac3 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): # 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,21 +110,54 @@ def get_destination_string(self, separated=False): else: return output + def get_customer_vlan(self): + if len(self.vlan) == 0: + return None + else: + return self.vlan[0] + + def get_service_vlan(self): + 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()) + return "".format(self.get_source_string(), self.get_destination_string(), self.get_customer_vlan(), self.get_service_vlan()) 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())