Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IPv6 (Mulitprotocol support) #41

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pbgpp/Application/CLI.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
17 changes: 12 additions & 5 deletions pbgpp/Application/Handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -244,7 +251,7 @@ def __parse_filters(self):
if self.args.filter_destination_mac:
values = self.args.filter_destination_mac
filters = list(chain(*values))
self.prefilters.append(MACDestinationFilter(MACSourceFilter.clear_input(filters)))
self.prefilters.append(MACDestinationFilter(MACDestinationFilter.clear_input(filters)))
logger.debug("Added " + str(len(filters)) + " pre-filter(s) of MACDestinationFilter")

if self.args.filter_timestamp:
Expand Down Expand Up @@ -318,13 +325,13 @@ def __packet_handler(self, header, payload):
eth = PCAPEthernet(payload)

# Check for raw ethernet packet
if not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV4:
if not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV4 and not eth.get_type() == PCAPEthernet.ETH_TYPE_IPV6:

# Check for SLL-packet
eth = PCAPCookedCapture(payload)

if not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV4:
logger.debug("Discarding PCAP packet " + str(self.__packet_counter) + " due to non-IPv4 ethernet type.")
if not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV4 and not eth.get_type() == PCAPCookedCapture.ETH_TYPE_IPV6:
logger.debug("Discarding PCAP packet " + str(self.__packet_counter) + " due to non-IPv4 or non-IPv6 ethernet type.")
return False

ip = PCAPIP(eth.get_eth_payload())
Expand All @@ -335,7 +342,7 @@ def __packet_handler(self, header, payload):

tcp = PCAPTCP(ip.get_ip_payload())

pcap_information = PCAPInformation(header.getts(), eth.mac, ip.addresses, tcp.ports)
pcap_information = PCAPInformation(header.getts(), eth.get_mac(), ip.get_addresses(), tcp.get_ports())

for filter in self.prefilters:
if not filter.apply(pcap_information):
Expand Down
82 changes: 34 additions & 48 deletions pbgpp/BGP/Update/Message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
60 changes: 60 additions & 0 deletions pbgpp/BGP/Update/PathAttributes/MPNextHop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#
# This file is part of PCAP BGP Parser (pbgpp)
#
# Copyright 2016-2020 DE-CIX Management GmbH
# Author: Christopher Moeller <[email protected]>
#
# 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

116 changes: 115 additions & 1 deletion pbgpp/BGP/Update/PathAttributes/MPReachNLRI.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,130 @@
#

from pbgpp.BGP.Statics import BGPStatics
from pbgpp.BGP.Translation import BGPTranslation

from pbgpp.BGP.Update.Route import BGPRoute
from pbgpp.BGP.Update.Route6 import BGPRoute6
from pbgpp.BGP.Update.PathAttribute import BGPPathAttribute
from pbgpp.BGP.Update.PathAttributes.MPNextHop import MPNextHop

import struct
import socket
import math

class PathAttributeMPReachNLRI(BGPPathAttribute):
"""
RFC 4760
+---------------------------------------------------------+
| Address Family Identifier (2 octets) |
+---------------------------------------------------------+
| Subsequent Address Family Identifier (1 octet) |
+---------------------------------------------------------+
| Length of Next Hop Network Address (1 octet) |
+---------------------------------------------------------+
| Network Address of Next Hop (variable) |
+---------------------------------------------------------+
| Reserved (1 octet) |
+---------------------------------------------------------+
| Network Layer Reachability Information (variable) |
+---------------------------------------------------------+
"""


def __init__(self, payload):
BGPPathAttribute.__init__(self, payload)
self.type = BGPStatics.UPDATE_ATTRIBUTE_MP_REACH_NLRI

self.afi = 0
self.safi = 0
self.next_hop = []
self.nlri = []

self.__parse()

def __parse(self):
self.parsed = True
self.error = False
self.error = False
payload_pointer = 0

self.afi = struct.unpack("!H", self.payload[:2])[0]
self.safi = struct.unpack("!B", self.payload[2])[0] # @todo use this if wanted, atm its not neccesary
self.next_hop_length = struct.unpack("!B", self.payload[3])[0]
payload_pointer = 4

if not self.next_hop_length == 0: #next_hop parsing
try:
if self.afi == 1: #IPv4
if not self.next_hop_length % 4 == 0:
self.error = True
else:
for i in range(self.next_hop_length / 4):
self.next_hop.append( MPNextHop(self.payload[payload_pointer:payload_pointer+4], socket.AF_INET) )
payload_pointer += 4

elif self.afi == 2: #IPv6
if not self.next_hop_length % 16 == 0:
self.error = True
else:
for i in range(self.next_hop_length / 16):
self.next_hop.append( MPNextHop(self.payload[payload_pointer:payload_pointer+16], socket.AF_INET6) )
payload_pointer += 16

else:
raise NotImplementedError

except Exception as e:
self.error = True

if not len(self.payload) == self.next_hop_length + 5: # afi + safi + hop_length + reserved = 5bytes
payload_pointer += 1 #skip reservation byte
try:
if self.afi == 1: #IPv4
while payload_pointer < len(self.payload):
prefix_len = struct.unpack("!B", self.payload[payload_pointer])[0]
prefix_len_bytes = int(math.ceil(prefix_len / 8.0))

self.nlri.append(BGPRoute.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer]))

payload_pointer += prefix_len_bytes + 1

elif self.afi == 2: #IPv6
while payload_pointer < len(self.payload):
prefix_len = struct.unpack("!B", self.payload[payload_pointer])[0]
prefix_len_bytes = int(math.ceil(prefix_len / 8.0))

self.nlri.append(BGPRoute6.from_binary(self.payload[payload_pointer+1:payload_pointer+1+prefix_len_bytes], self.payload[payload_pointer]))

payload_pointer += prefix_len_bytes + 1

else:
raise NotImplementedError

except Exception as e:
self.error = True

def __str__(self):
output = "REACH_NLRI: NEXT_HOP: ["
for i in self.next_hop:
output += str(i) + ", "
output = output[:-2] + "] NLRI: ["
for i in self.nlri:
output += str(i) + ", "
output = output[:-2] + "]"
return output

def json(self): #overload of parentclass function
json = {
"afi": self.afi,
"safi": self.safi,

"reach_nlri": [],
"next_hop": [],
}

for nlri in self.nlri:
json["reach_nlri"].append(str(nlri))
for nh in self.next_hop:
json["next_hop"].append(str(nh))

return json
Loading