From fd144336035aa620187e8e69f03b0a589e5e3210 Mon Sep 17 00:00:00 2001 From: acidvegas Date: Sun, 3 Sep 2023 23:37:29 -0400 Subject: [PATCH] Initial commit --- .screens/preview.png | Bin 0 -> 62091 bytes README.md | 37 ++++++ examples/asnpaths.py | 55 ++++++++ examples/build-cone.py | 153 ++++++++++++++++++++++ examples/download_asn_paths.py | 92 +++++++++++++ examples/ip_asn.py | 95 ++++++++++++++ examples/ip_asn_pyipmeta.py | 90 +++++++++++++ examples/pybgpstream-aspath.py | 43 ++++++ examples/pybgpstream-communities.py | 33 +++++ examples/pybgpstream-moas.py | 35 +++++ examples/pybgpstream-ris-live.py | 13 ++ examples/pybgpstream-routeviews-stream.py | 12 ++ examples/records.py | 48 +++++++ examples/rpki.py | 44 +++++++ 14 files changed, 750 insertions(+) create mode 100644 .screens/preview.png create mode 100644 README.md create mode 100644 examples/asnpaths.py create mode 100644 examples/build-cone.py create mode 100644 examples/download_asn_paths.py create mode 100644 examples/ip_asn.py create mode 100644 examples/ip_asn_pyipmeta.py create mode 100644 examples/pybgpstream-aspath.py create mode 100644 examples/pybgpstream-communities.py create mode 100644 examples/pybgpstream-moas.py create mode 100644 examples/pybgpstream-ris-live.py create mode 100644 examples/pybgpstream-routeviews-stream.py create mode 100644 examples/records.py create mode 100644 examples/rpki.py diff --git a/.screens/preview.png b/.screens/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..ee9208491ef24e8157c8f72e7ee3802e993e8a59 GIT binary patch literal 62091 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%0AyOCC}{)Q1s_cGr=S+2d= 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%0DivY3;%+WySgf+ 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 [www](https://bgpstream.caida.org/) | [github](https://github.com/caida/libbgpstream) | [bgpstream-info@caida.org](mailto:bgpstream-info@caida.org) + +![](.screens/preview.png) + +### Overview +- [BGP Stream: A framework for BGP analysis](https://ripe70.ripe.net/presentations/55-bgpstream.pdf) *(pdf)* + +# Install BGP Stream + +You can visit the official [install page](https://bgpstream.caida.org/docs/install) to see if there is a different approach you want to take. There is also a [docker](https://hub.docker.com/r/caida/bgpstream) image. + +The follow outlines compiling it from source for simplicity across distros. + +###### Requirements +- [libcurl](https://curl.se/libcurl/) +- [wandio 4.2.4-1](https://github.com/LibtraceTeam/wandio/releases/tag/4.2.4-1) *(wandio 4.2.5 was released recently, but not sure if it will break bgpstream)* + +###### Compiling wandio +1. Install the required packages: `build-essential curl zlib1g-dev libbz2-dev libcurl4-openssl-dev librdkafka-dev automake1.11 libtool` +2. Grab the source: `curl -LO https://github.com/LibtraceTeam/wandio/archive/refs/tags/4.2.4-1.tar.gz` +3. Extract the archive & then compile with `./configure && make && sudo make install` +4. Lastly, run `sudo ldconfig` + +###### Compiling libbgpstream +1. Install required packages: `sudo apt-get install -y curl apt-transport-https ssl-cert ca-certificates gnupg lsb-release` +1. Grab the source: `curl -LO https://github.com/CAIDA/libbgpstream/releases/download/v2.2.0/libbgpstream-2.2.0.tar.gz` +2. Extract the archive & then compile with `./configure && make && sudo make install` +3. Lastly, run `sudo ldconfig` + +This will create `/usr/local/bin/bgpreader` *([documentation](https://bgpstream.caida.org/docs/tools/bgpreader))* + +Lastly, for Python support, `pip install pybgpstream` *([documentation](https://bgpstream.caida.org/docs/api/pybgpstream))* *([pypi](https://pypi.org/project/pybgpstream/))* + +**NOTE:** `sudo apt-get install python3-pybgpstream` + +**NOTE:** The [Broker HTTP API](https://bgpstream.caida.org/docs/api/broker) may come to use... diff --git a/examples/asnpaths.py b/examples/asnpaths.py new file mode 100644 index 0000000..5f80242 --- /dev/null +++ b/examples/asnpaths.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 + +#import the low level _pybgpsteam library and other necessary libraries +from pybgpstream import BGPStream +from ipaddress import ip_network +import time +import sys +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("target", nargs="*", type=str, help="ASNs we are looking up") +parser.add_argument("-d", "--debug", type=int, help="Number of traces") +args = parser.parse_args() + +# Initialize BGPStream with RIPE RIS LIVE and collector rrc00 +stream = BGPStream(project="ris-live", + collectors=["rrc00"], + filter="collector rrc00") + +# The stream will not load new data till it's done with the current pulled data. +stream.set_live_mode() +print("starting stream...", file=sys.stderr) + +# Counter +counter = 0 + +for record in stream.records(): + # Handles debug option + if args.debug is None: + pass + elif counter >= args.debug: + break + else: + counter += 1 + + rec_time = time.strftime('%y-%m-%d %H:%M:%S', time.localtime(record.time)) + for elem in record: + try: + prefix = ip_network(elem.fields['prefix']) + # Only print elements that are announcements (BGPElem.type = "A") + # or ribs (BGPElem.type = "R") + if elem.type == "A" or elem.type == "R": + as_path = elem.fields['as-path'].split(" ") + # Print all elements with specified in args.target + for target in args.target: + if target in as_path: + print(f"Peer asn: {elem.peer_asn} AS Path: {as_path} " + f"Communities: {elem.fields['communities']} " + f"Timestamp: {rec_time}") + break + + # Reports and skips all KeyError + except KeyError as e: + print("KEY ERROR, element ignored: KEY=" + str(e), file=sys.stderr) + continue diff --git a/examples/build-cone.py b/examples/build-cone.py new file mode 100644 index 0000000..7e1f280 --- /dev/null +++ b/examples/build-cone.py @@ -0,0 +1,153 @@ +#!/usr/bin/env python2 +__author__ = "Bradley Huffaker" +__email__ = "" +# This software is Copyright (C) 2022 The Regents of the University of +# California. All Rights Reserved. Permission to copy, modify, and +# distribute this software and its documentation for educational, research +# and non-profit purposes, without fee, and without a written agreement is +# hereby granted, provided that the above copyright notice, this paragraph +# and the following three paragraphs appear in all copies. Permission to +# make commercial use of this software may be obtained by contacting: +# +# Office of Innovation and Commercialization +# 9500 Gilman Drive, Mail Code 0910 +# University of California +# La Jolla, CA 92093-0910 +# (858) 534-5815 +# +# invent@ucsd.edu +# +# This software program and documentation are copyrighted by The Regents of +# the University of California. The software program and documentation are +# supplied $B!H(Bas is$B!I(B, without any accompanying services from The Regents. The +# Regents does not warrant that the operation of the program will be +# uninterrupted or error-free. The end-user understands that the program +# was developed for research purposes and is advised not to rely +# exclusively on the program for any reason. +# +# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +# INCLUDING LOST PR OFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +# DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY +# DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +# SOFTWARE PROVIDED HEREUNDER IS ON AN $B!H(BAS IS$B!I(B BASIS, AND THE UNIVERSITY OF +# CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +# ENHANCEMENTS, OR MODIFICATIONS. +# + +#import the low level _pybgpsteam library and other necessary libraries +from pybgpstream import BGPStream +from datetime import date +from datetime import timedelta +import argparse + +import sys +import bz2 + +parser = argparse.ArgumentParser() +parser.add_argument("link_file", nargs=1, type=str) +parser.add_argument("-d", "--debug", type=int, default=-1) +args = parser.parse_args() + +def main(): + sys.stderr.write("This is going to take some time\n") + + # Handle debug option + debug_count = args.debug + + peer_provider = download_links(args.link_file[0]) + asn__cone = download_paths(peer_provider, debug_count) + + print "# ASN followed by ASNs in it's customer cone" + print "# '1 23 4' means ASN 1's customer cone includes ASN 23 and ASN 4" + for asn,cone in sorted(asn__cone.items(), key=lambda a_c: len(a_c[1]),reverse=True): + print asn + " "+" ".join(sorted(cone,key=lambda a: int(a.lstrip('{').rstrip('}')))) + +# Find the set of AS Relationships that are +# peer to peer or provider to customer. +def download_links(filename): + sys.stderr.write("loading relationships\n") + first = 1000 + offset = 0 + hasNextPage = True + + peer_provider = set() + with bz2.BZ2File(filename, mode='r') as fin: + for line in fin: + # skip comments + if len(line) == 0 or line[0] == "#": + continue + asn0, asn1, rel = line.rstrip().split("|") + + # peers work in both directions + if rel == 0: + peer_provider.add(asn1+" "+asn0) + peer_provider.add(asn0+" "+asn1) + + # store the link from provider to customer + elif rel == -1: + peer_provider.add(asn1+" "+asn0) + else: + peer_provider.add(asn0+" "+asn1) + + return peer_provider + +# download the AS paths from BGPStream +# crop the path to the section after the +# first peer or provider link, then add +# all the remaining ASes to the preceeding +# ASes in the cropped path +def download_paths(peer_provider, debug_count): + # The set of ASes reachable through an AS's customers + asn__cone = {} + + sys.stderr.write("downloading paths\n") + + # Return a rib from yesterday's first second + from_time = date.today() - timedelta(days=1) + until_time = from_time + timedelta(seconds=1) + stream = BGPStream( + from_time=from_time.strftime("%Y-%m-%d %H:%M:%S"), until_time=until_time.strftime("%Y-%m-%d %H:%M:%S"), + record_type="ribs" + ) + stream.add_rib_period_filter(86400) # This should limit BGPStream to download the full first BGP dump + + # counter + count = 0 + for elem in stream: + # Break when debug mode activated and count equals debug count + if (debug_count >= 0) and (count >= debug_count): + break + + asns = elem.fields['as-path'].split(" ") + + # Skip until the first peer-peer or provider->customer link + i = 0 + while i+1 < len(asns): + link = asns[i]+" "+asns[i+1] + i += 1 + if link in peer_provider: + break + + # Since an AS only announces it's customer cone to it's peer or provider, + # the remaining ASes in the path are in the preciding ASes customer cone. + while i+1 < len(asns): + if asns[i] not in asn__cone: + asn__cone[asns[i]] = set() + cone = asn__cone[asns[i]] + j = i+1 + while j < len(asns): + print (">",i,j,asns[i], asns[j]) + cone.add(asns[j]) + j += 1 + i += 1 + + # Increment count + count += 1 + + return asn__cone + +#run the main method +main() diff --git a/examples/download_asn_paths.py b/examples/download_asn_paths.py new file mode 100644 index 0000000..3c94afd --- /dev/null +++ b/examples/download_asn_paths.py @@ -0,0 +1,92 @@ +#!/usr/bin/env python +__author__ = "Pooja Pathak" +__email__ = "" +# This software is Copyright © 2020 The Regents of the University of +# California. All Rights Reserved. Permission to copy, modify, and +# distribute this software and its documentation for educational, research +# and non-profit purposes, without fee, and without a written agreement is +# hereby granted, provided that the above copyright notice, this paragraph +# and the following three paragraphs appear in all copies. Permission to +# make commercial use of this software may be obtained by contacting: +# +# Office of Innovation and Commercialization +# +# 9500 Gilman Drive, Mail Code 0910 +# +# University of California +# +# La Jolla, CA 92093-0910 +# +# (858) 534-5815 +# +# invent@ucsd.edu +# +# This software program and documentation are copyrighted by The Regents of +# the University of California. The software program and documentation are +# supplied “as is”, without any accompanying services from The Regents. The +# Regents does not warrant that the operation of the program will be +# uninterrupted or error-free. The end-user understands that the program +# was developed for research purposes and is advised not to rely +# exclusively on the program for any reason. +# +# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +# INCLUDING LOST PR OFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +# DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY +# DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +# SOFTWARE PROVIDED HEREUNDER IS ON AN “AS IS” BASIS, AND THE UNIVERSITY OF +# CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +# ENHANCEMENTS, OR MODIFICATIONS. + +#!/usr/bin/env python + +import pybgpstream +import os.path + +# Create pybgpstream +stream = pybgpstream.BGPStream( + from_time="2017-07-07 00:00:00", until_time="2017-07-07 00:10:00 UTC", + collectors=["route-views.sg", "route-views.eqix"], + record_type="updates", +) + +prefix_asn = dict() +for elem in stream: + # record fields can be accessed directly from elem + if "as-path" in elem.fields: + asns = elem.fields["as-path"].rstrip().split(" ") + if "prefix" in elem.fields: + prefix = elem.fields["prefix"] + + + if len(asns) < 1: + continue + + # Get origin as + asn = asns[-1] + + # Drop origin as sets + if len(asn.split(",")) > 1: + continue + + if asn[0] == '{': + continue + + # Populate prefix_asn with prefix to asn mapping + if asn not in prefix_asn: + prefix_asn[prefix] = set() + prefix_asn[prefix].add(asn) + +# Write prefix-asn mapping to prefix2asn.dat + +fout = open('prefix2asn.dat', "w") +for prefix,asns in prefix_asn.items(): + if len(asns) == 1: + fout.write(prefix) + fout.write("\t") + fout.write("".join(prefix_asn[prefix])) + fout.write("\n") + +fout.close() diff --git a/examples/ip_asn.py b/examples/ip_asn.py new file mode 100644 index 0000000..8282cb4 --- /dev/null +++ b/examples/ip_asn.py @@ -0,0 +1,95 @@ +__author__ = "Pooja Pathak" +__email__ = "" +# This software is Copyright © 2020 The Regents of the University of +# California. All Rights Reserved. Permission to copy, modify, and +# distribute this software and its documentation for educational, research +# and non-profit purposes, without fee, and without a written agreement is +# hereby granted, provided that the above copyright notice, this paragraph +# and the following three paragraphs appear in all copies. Permission to +# make commercial use of this software may be obtained by contacting: +# +# Office of Innovation and Commercialization +# +# 9500 Gilman Drive, Mail Code 0910 +# +# University of California +# +# La Jolla, CA 92093-0910 +# +# (858) 534-5815 +# +# invent@ucsd.edu +# +# This software program and documentation are copyrighted by The Regents of +# the University of California. The software program and documentation are +# supplied “as is”, without any accompanying services from The Regents. The +# Regents does not warrant that the operation of the program will be +# uninterrupted or error-free. The end-user understands that the program +# was developed for research purposes and is advised not to rely +# exclusively on the program for any reason. +# +# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +# INCLUDING LOST PR OFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +# DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY +# DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +# SOFTWARE PROVIDED HEREUNDER IS ON AN “AS IS” BASIS, AND THE UNIVERSITY OF +# CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +# ENHANCEMENTS, OR MODIFICATIONS. + +#!/usr/bin/env python + +import pyasn +import argparse +import datetime +import resource +import os +import psutil + +def returnTime(): + return datetime.datetime.now() + +def returnMemUsage(): + process = psutil.Process(os.getpid()) + return process.memory_info()[0] + + +parser = argparse.ArgumentParser() +parser.add_argument('-p', dest = 'prefix2asn_file', default = '', help = 'Please enter the prefix2asn file name') +parser.add_argument('-i', dest = 'ips_file', default = '', help = 'Please enter the file name of the ips file') +args = parser.parse_args() + + +# Get list of ips +ips = [] +with open(args.ips_file) as f: + for line in f: + line = line.rstrip().split("\t")[1] + ips.append(line) + + +asndb = pyasn.pyasn(args.prefix2asn_file) + +begin_time = returnTime() +begin_mem = returnMemUsage() + +# Create ip2asn mapping +ip2asn = {} +for ip in ips: + if asndb.lookup(ip): + asn,prefix = asndb.lookup(ip) + if asn: + ip2asn[ip] = asn + +# print(ip2asn) +end_time = returnTime() +end_mem = returnMemUsage() + +# hour:minute:second:microsecond +print("Delta time:" , end_time - begin_time) +print("Delta memory use:", end_mem - begin_mem) + + + diff --git a/examples/ip_asn_pyipmeta.py b/examples/ip_asn_pyipmeta.py new file mode 100644 index 0000000..e5144d2 --- /dev/null +++ b/examples/ip_asn_pyipmeta.py @@ -0,0 +1,90 @@ +__author__ = "Pooja Pathak" +__email__ = "" +# This software is Copyright © 2020 The Regents of the University of +# California. All Rights Reserved. Permission to copy, modify, and +# distribute this software and its documentation for educational, research +# and non-profit purposes, without fee, and without a written agreement is +# hereby granted, provided that the above copyright notice, this paragraph +# and the following three paragraphs appear in all copies. Permission to +# make commercial use of this software may be obtained by contacting: +# +# Office of Innovation and Commercialization +# +# 9500 Gilman Drive, Mail Code 0910 +# +# University of California +# +# La Jolla, CA 92093-0910 +# +# (858) 534-5815 +# +# invent@ucsd.edu +# +# This software program and documentation are copyrighted by The Regents of +# the University of California. The software program and documentation are +# supplied “as is”, without any accompanying services from The Regents. The +# Regents does not warrant that the operation of the program will be +# uninterrupted or error-free. The end-user understands that the program +# was developed for research purposes and is advised not to rely +# exclusively on the program for any reason. +# +# IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, +# INCLUDING LOST PR OFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS +# DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF +# THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY +# DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE +# SOFTWARE PROVIDED HEREUNDER IS ON AN “AS IS” BASIS, AND THE UNIVERSITY OF +# CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +# ENHANCEMENTS, OR MODIFICATIONS. + +#!/usr/bin/env python + +import _pyipmeta +import datetime +import os +import psutil + +def returnTime(): + return datetime.datetime.now() + +def returnMemUsage(): + process = psutil.Process(os.getpid()) + return process.memory_info()[0] + + +ipm = _pyipmeta.IpMeta() +# print(ipm) + +# print("Getting/enabling pfx2as provider (using included test data)") +prov = ipm.get_provider_by_name("pfx2as") +# print(prov) +print(ipm.enable_provider(prov, "-f /test/pfx2as/routeviews-rv2-20170329-0200.pfx2as.gz")) +print() + + +ips = [] +with open('ips.txt') as f: + for line in f: + line = line.rstrip().split("\t")[1] + ips.append(line) + +begin_time = returnTime() +begin_mem = returnMemUsage() + +ip2asn = {} +for ip in ips: + if ipm.lookup(ip): + (res,) = ipm.lookup(ip) + if res.get('asns'): + ip2asn[ip] = res.get('asns') + + +# print(ip2asn) +end_time = returnTime() +end_mem = returnMemUsage() + +# hour:minute:second:microsecond +print("Delta time:" , end_time - begin_time) +print("Delta memory:", end_mem - begin_mem) diff --git a/examples/pybgpstream-aspath.py b/examples/pybgpstream-aspath.py new file mode 100644 index 0000000..99bc9cf --- /dev/null +++ b/examples/pybgpstream-aspath.py @@ -0,0 +1,43 @@ +import pybgpstream +import networkx as nx +from collections import defaultdict +from itertools import groupby + +# Create an instance of a simple undirected graph +as_graph = nx.Graph() + +bgp_lens = defaultdict(lambda: defaultdict(lambda: None)) + +stream = pybgpstream.BGPStream( + # Consider this time interval: + # Sat, 01 Aug 2015 7:50:00 GMT - 08:10:00 GMT + from_time="2015-08-01 07:50:00", until_time="2015-08-01 08:10:00", + collectors=["rrc00"], + record_type="ribs", +) + +for rec in stream.records(): + for elem in rec: + # Get the peer ASn + peer = str(elem.peer_asn) + # Get the array of ASns in the AS path and remove repeatedly prepended ASns + hops = [k for k, g in groupby(elem.fields['as-path'].split(" "))] + if len(hops) > 1 and hops[0] == peer: + # Get the origin ASn + origin = hops[-1] + # Add new edges to the NetworkX graph + for i in range(0,len(hops)-1): + as_graph.add_edge(hops[i],hops[i+1]) + # Update the AS path length between 'peer' and 'origin' + bgp_lens[peer][origin] = \ + min(list(filter(bool,[bgp_lens[peer][origin],len(hops)]))) + +# For each 'peer' and 'origin' pair +for peer in bgp_lens: + for origin in bgp_lens[peer]: + # compute the shortest path in the NetworkX graph + nxlen = len(nx.shortest_path(as_graph, peer, origin)) + # and compare it to the BGP hop length + print((peer, origin, bgp_lens[peer][origin], nxlen)) + + diff --git a/examples/pybgpstream-communities.py b/examples/pybgpstream-communities.py new file mode 100644 index 0000000..141d69e --- /dev/null +++ b/examples/pybgpstream-communities.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +import pybgpstream +from collections import defaultdict + +stream = pybgpstream.BGPStream( + # Consider this time interval: + # Sat, 01 Aug 2015 7:50:00 GMT - 08:10:00 GMT + from_time="2015-08-01 07:50:00", until_time="2015-08-01 08:10:00", + collectors=["rrc06"], + record_type="ribs", + filter="peer 25152 and prefix more 185.84.166.0/23 and community *:3400" +) + +# dictionary +community_prefix = defaultdict(set) + +# Get next record +for rec in stream.records(): + for elem in rec: + # Get the prefix + pfx = elem.fields['prefix'] + # Get the associated communities + communities = elem.fields['communities'] + # for each community save the set of prefixes + # that are affected + for c in communities: + community_prefix[c].add(pfx) + +# Print the list of MOAS prefix and their origin ASns +for ct in community_prefix: + print("Community:", ct, "==>", ",".join(community_prefix[ct])) + diff --git a/examples/pybgpstream-moas.py b/examples/pybgpstream-moas.py new file mode 100644 index 0000000..46449ed --- /dev/null +++ b/examples/pybgpstream-moas.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python + +from collections import defaultdict +import pybgpstream + +stream = pybgpstream.BGPStream( + # Consider this time interval: + # Sat, 01 Aug 2015 7:50:00 GMT - 08:10:00 GMT + from_time="2015-08-01 07:50:00", until_time="2015-08-01 08:10:00", + collectors=["rrc00"], + record_type="ribs", +) + +# dictionary +prefix_origin = defaultdict(set) + +for rec in stream.records(): + for elem in rec: + # Get the prefix + pfx = elem.fields["prefix"] + # Get the list of ASes in the AS path + ases = elem.fields["as-path"].split(" ") + if len(ases) > 0: + # Get the origin ASn (rightmost) + origin = ases[-1] + # Insert the origin ASn in the set of + # origins for the prefix + prefix_origin[pfx].add(origin) + +# Print the list of MOAS prefix and their origin ASns +for pfx in prefix_origin: + if len(prefix_origin[pfx]) > 1: + print((pfx, ",".join(prefix_origin[pfx]))) + + diff --git a/examples/pybgpstream-ris-live.py b/examples/pybgpstream-ris-live.py new file mode 100644 index 0000000..9e217f1 --- /dev/null +++ b/examples/pybgpstream-ris-live.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +import pybgpstream +stream = pybgpstream.BGPStream( + # accessing ris-live + project="ris-live", + # filter to show only stream from rrc00 + filter="collector rrc00", +) + +for elem in stream: + print(type(elem)) + print(elem) diff --git a/examples/pybgpstream-routeviews-stream.py b/examples/pybgpstream-routeviews-stream.py new file mode 100644 index 0000000..3ca0bd8 --- /dev/null +++ b/examples/pybgpstream-routeviews-stream.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python + +import pybgpstream +stream = pybgpstream.BGPStream( + # accessing routeview-stream + project="routeviews-stream", + # filter to show only stream from amsix bmp stream + filter="router amsix", +) + +for elem in stream: + print(elem) diff --git a/examples/records.py b/examples/records.py new file mode 100644 index 0000000..e07fe7d --- /dev/null +++ b/examples/records.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +# Import pybgpstream and other necessary libraries +from pybgpstream import BGPStream +import time +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument("print_amount", nargs=1, type=int, help="Number of prints") +args = parser.parse_args() + +# Initialize BGPStream, with data from routeviews-stream for router amsix. +stream = BGPStream(project='routeviews-stream', filter="router amsix") + +# Counter to stop BGPStream after X amount of prints. +counter = 0 + +# Print records yielded from stream.records() in a bgpreader-like format. +for record in stream.records(): + # Print the first X records found. + if counter >= args.print_amount[0]: + break + else: + counter += 1 + + print(record.project, record.collector, record.router) + # Make the date is human readable + rec_time = time.strftime('%y-%m-%d %H:%M:%S', time.localtime(record.time)) + for elem in record: + # Print the current element in the record. Both are equivelent. + # print(elem) + print("{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}|{}".format( + elem.record_type, + elem.type, + rec_time, + elem.project, + elem.collector, + elem.router, + elem.router_ip, + elem.peer_asn, + elem.peer_address, + elem._maybe_field("prefix"), + elem._maybe_field("next-hop"), + elem._maybe_field("as-path"), + " ".join(elem.fields["communities"]) if "communities" in elem.fields else None, + elem._maybe_field("old-state"), + elem._maybe_field("new-state") + )) diff --git a/examples/rpki.py b/examples/rpki.py new file mode 100644 index 0000000..146010e --- /dev/null +++ b/examples/rpki.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 + +from pybgpstream import BGPStream +from ipaddress import ip_network +import requests +import sys +import json +import argparse + +# Initialize BGPStream, with routeviews-stream project, filtering for amsix. +stream = BGPStream(project="routeviews-stream", filter="router amsix") +print("starting stream...", file=sys.stderr) + +# Debug Option to limit number of traces +parser = argparse.ArgumentParser() +parser.add_argument("-d", "--debug", type=int, help="Number of traces") +args = parser.parse_args() + +# Counter +counter = 0 +for record in stream.records(): + # Handles debug option + if args.debug is None: + pass + elif counter >= args.debug: + break + else: + counter += 1 + + for elem in record: + prefix = ip_network(elem.fields['prefix']) + if elem.type == "A": + # Lookup RPKI state based on announced route. + request = requests.get(f"https://api.routeviews.org/rpki?prefix={prefix}", verify=False) + response = request.json() + # Skip all None responses + if response[str(prefix)] is not None: + data = { + "prefix": str(prefix), + "rpki": response[str(prefix)], + "timestamp": response[str(prefix)]['timestamp'] + } + # Output json to stdout + print(json.dumps(data))