Edit on GitHub

bin.monitor

Main execution, handle arguments, init each instances

  1#! /usr/bin/env python3.9
  2"""
  3Main execution, handle arguments, init each instances
  4"""
  5
  6import os
  7import sys
  8import signal
  9import bgpout
 10import argparse
 11import urllib.request
 12from configobj import ConfigObj
 13from bgpfilter import BGPFilter
 14from Databases.database import BGPDatabases
 15
 16DEFAULT_GEOOPEN_URL = (
 17    "https://cra.circl.lu/opendata/geo-open/mmdb-country-asn/latest.mmdb"
 18)
 19
 20# define bin/ as default workdir
 21os.chdir(os.path.dirname(os.path.abspath(__file__)))
 22
 23
 24def asnPrefixFromFile(file):
 25    """Read prefixes and AS numbers from files
 26
 27    Args:
 28        file (File): File that contains as numbers and prefixes
 29
 30    Returns:
 31        Dict: Dictionnary with prefix_list, asn_list, match type
 32    """
 33    res = {"asn_list": [], "prefix_list": [], "match": "more"}
 34
 35    for line in file.read().splitlines():
 36        if len(line) > 0:
 37            splitted_line = line.split()
 38            if splitted_line[0] == ">":
 39                res["prefix_list"].append(splitted_line[1])
 40            elif splitted_line[0] == "AS":
 41                res["asn_list"].append(splitted_line[1])
 42            elif splitted_line[0] == "MATCH":
 43                res["match"] = splitted_line[1]
 44    return res
 45
 46
 47if __name__ == "__main__":
 48    parser = argparse.ArgumentParser(
 49        description="Tool for BGP filtering and monitoring",
 50        allow_abbrev=True,
 51        formatter_class=argparse.RawTextHelpFormatter,
 52    )
 53    parser.add_argument("-v", "--version", action="version", version="%(prog)s 1.0")
 54    parser.add_argument(
 55        "--verbose", action="store_true", help="Print BGP records in console"
 56    )
 57
 58    parser.add_argument(
 59        "--filter_list",
 60        type=argparse.FileType("r"),
 61        nargs="?",
 62        help=(
 63            "Use separated file to define list of prefixes and/or AS Numbers.\n Check"
 64            " etc/filter_list.cfg.sample for file format"
 65        ),
 66        metavar="<path>",
 67    )
 68
 69    parser.add_argument(
 70        "--config",
 71        default="../etc/config.cfg",
 72        help="Use different config file",
 73        metavar="<path>",
 74    )
 75
 76    parser.add_argument(
 77        "-jo",
 78        "--json_output",
 79        nargs="?",
 80        type=argparse.FileType("w+"),
 81        help=(
 82            "File in which to display JSON output.\n If not set, default sys.stdout"
 83            " will be used."
 84        ),
 85        metavar="<path>",
 86    )
 87
 88    parser.add_argument(
 89        "-cf",
 90        "--country_filter",
 91        nargs="+",
 92        help="Filter using specified country codes.",
 93        metavar="<country code>",
 94    )
 95    parser.add_argument(
 96        "-af",
 97        "--asn_filter",
 98        nargs="+",
 99        help=(
100            "Filter using specified AS number list, skip a record if its AS-source is"
101            " not one of specified AS numbers.\n Use _ symbol for negation"
102        ),
103        metavar="<AS number>",
104    )
105
106    parser.add_argument(
107        "-ip",
108        "--ipversion",
109        choices=["4", "6"],
110        help="Filter specific ip address type. ipv4 or ipv6",
111        metavar="<version>",
112    )
113
114    parser.add_argument(
115        "-pf",
116        "--prefix_filter",
117        nargs="+",
118        help=(
119            "Filter using specified prefix list, CIDR format: ip/subnet.\n Example:"
120            " 130.0.192.0/21,130.0.100.0/21"
121        ),
122        metavar="<prefix>",
123    )
124
125    parser.add_argument(
126        "--match",
127        default="more",
128        choices=["more", "less", "exact", "any"],
129        help=(
130            "Type of match ->"
131            "exact: Exact match\n"
132            "more: Exact match or more specific (Contained by one of the prefixes)\n"
133            "less: Exact match or less specific (Contain one of the prefixes).\n"
134            "Default: more"
135        ),
136    )
137
138    parser.add_argument(
139        "-p",
140        "--project",
141        default="ris",
142        choices=["ris", "routeviews"],
143        help="Project name",
144    )
145
146    parser.add_argument(
147        "-c",
148        "--collectors",
149        nargs="+",
150        help=(
151            "Collectors. For complete list of collectors, see"
152            " https://bgpstream.caida.org/data"
153        ),
154        metavar="<collector>",
155    )
156
157    parser.add_argument(
158        "-r",
159        "--record",
160        action="store_true",
161        help=(
162            "Retrieve records in the interval --until_time and --f-time arguments"
163            " (which are required)"
164        ),
165    )
166    parser.add_argument(
167        "--start",
168        help=(
169            "Beginning of the interval.\n  -> Timestamp format : YYYY-MM-DD hh:mm:ss.\n"
170            "     Example: 2022-01-01 10:00:00"
171        ),
172        metavar="<begin>",
173    )
174    parser.add_argument(
175        "--stop",
176        help=(
177            "End of the interval.\n  -> Timestamp format : YYYY-MM-DD hh:mm:ss.\n    "
178            " Example: 2022-01-01 10:10:00"
179        ),
180        metavar="<end>",
181    )
182
183    parser.add_argument(
184        "-id",
185        "--input_data",
186        type=str,
187        help="Retrieve data from a single file instead of a broker.",
188        metavar="<path>",
189    )
190
191    parser.add_argument(
192        "-ir",
193        "--input_record_type",
194        choices=["upd", "rib"],
195        default="upd",
196        help=(
197            "Type of records contained in input_data file.\n Can be rib (Routing"
198            " Information Base) or upd (Updates eg. Anoun/Withdr).\n Default: upd"
199        ),
200    )
201
202    parser.add_argument(
203        "-if",
204        "--input_file_format",
205        choices=["mrt", "bmp", "ris-live"],
206        default="mrt",
207        help="input data type format. ris-live is avaible for updates only",
208    )
209
210    parser.add_argument(
211        "--expected_result",
212        "-expected",
213        nargs="?",
214        type=argparse.FileType("r"),
215        metavar="<path>",
216        help="Check that the result is the same as the expected result",
217    )
218
219    args = parser.parse_args()
220    if args.record and (args.from_time is None or args.until_time is None):
221        parser.error("--record requires --from_time and --until_time.")
222
223    if args.input_data and (
224        args.input_file_format is None or args.input_record_type is None
225    ):
226        parser.error(
227            "--input_data requires --input_file_format and --input_record_type."
228        )
229    if args.expected_result is not None and args.json_output is None:
230        parser.error("--expected_result requires --json_output")
231
232    # config
233    if os.path.isfile(args.config):
234        config = ConfigObj(args.config)
235    else:
236        raise FileNotFoundError(f"[-] No conf file found at {args.config}")
237
238    # BGPStream filter
239    filter = BGPFilter()
240
241    filter.project = args.project  # ris / routeviews
242    filter.collectors = args.collectors  # there are many collectors
243    filter.countries_filter = args.country_filter  # Country codes
244    filter.ipversion = args.ipversion  # 4 / 6
245
246    filter.record_mode(args.record, args.start, args.stop)
247    if args.input_data:
248        filter.data_source(
249            args.input_record_type, args.input_file_format, args.input_data
250        )
251
252    if "geo-open" in config:
253        url = config["geo-open"] or DEFAULT_GEOOPEN_URL  # if "download_url"
254        print("Downloading latest Geo Open Database", file=sys.stderr)
255        urllib.request.urlretrieve(
256            config["geo-open"]["download_url"], "../geo-open/latest.mmdb"
257        )
258
259        filter.country_file = config["geo-open"]["path"]
260
261    if args.filter_list is not None:
262        res = asnPrefixFromFile(args.filter_list)
263        filter.prefix_filter = (
264            res["prefix_list"] + (args.prefix_filter or []),
265            args.match,
266        )
267        filter.asn_filter = res["asn_list"] + (args.asn_filter or [])
268    else:
269        filter.asn_filter = args.asn_filter  # asn list
270        filter.prefix_filter = (args.prefix_filter, args.match)
271
272    # Output
273    bout = bgpout.BGPOut()
274    bout.json_out = args.json_output
275    bout.expected_result = args.expected_result
276    bout.verbose = args.verbose
277    bout.databases = BGPDatabases(config["databases"])
278
279    filter.out = bout
280
281    # end of program
282    def stop(x, y):
283        filter.stop()
284
285    signal.signal(signal.SIGINT, stop)
286    filter.start()
287    filter.stop()
def asnPrefixFromFile(file):
25def asnPrefixFromFile(file):
26    """Read prefixes and AS numbers from files
27
28    Args:
29        file (File): File that contains as numbers and prefixes
30
31    Returns:
32        Dict: Dictionnary with prefix_list, asn_list, match type
33    """
34    res = {"asn_list": [], "prefix_list": [], "match": "more"}
35
36    for line in file.read().splitlines():
37        if len(line) > 0:
38            splitted_line = line.split()
39            if splitted_line[0] == ">":
40                res["prefix_list"].append(splitted_line[1])
41            elif splitted_line[0] == "AS":
42                res["asn_list"].append(splitted_line[1])
43            elif splitted_line[0] == "MATCH":
44                res["match"] = splitted_line[1]
45    return res

Read prefixes and AS numbers from files

Args: file (File): File that contains as numbers and prefixes

Returns: Dict: Dictionnary with prefix_list, asn_list, match type