From f823b0a52f39cdad0e0aca9100b504b3933eb4fd Mon Sep 17 00:00:00 2001 From: Johann Schlamp Date: Wed, 30 Oct 2024 23:34:00 +0100 Subject: [PATCH] initial release --- .gitignore | 26 + LICENSE | 7 + README.md | 307 +++++++ pyproject.toml | 30 + src/ftlbgp/__init__.py | 54 ++ src/ftlbgp/__main__.py | 71 ++ src/ftlbgp/data/__init__.py | 12 + src/ftlbgp/data/const.py | 59 ++ src/ftlbgp/data/lgl/__init__.py | 12 + src/ftlbgp/data/lgl/unpack.py | 638 ++++++++++++++ src/ftlbgp/data/mrt/__init__.py | 12 + src/ftlbgp/data/mrt/bgp/__init__.py | 12 + src/ftlbgp/data/mrt/bgp/attr.py | 1162 +++++++++++++++++++++++++ src/ftlbgp/data/mrt/bgp/const.py | 264 ++++++ src/ftlbgp/data/mrt/bgp/msg.py | 853 ++++++++++++++++++ src/ftlbgp/data/mrt/bgp/nlri.py | 263 ++++++ src/ftlbgp/data/mrt/entry/__init__.py | 12 + src/ftlbgp/data/mrt/entry/bgp.py | 208 +++++ src/ftlbgp/data/mrt/entry/bgp4mp.py | 303 +++++++ src/ftlbgp/data/mrt/entry/const.py | 173 ++++ src/ftlbgp/data/mrt/entry/td.py | 240 +++++ src/ftlbgp/data/mrt/entry/tdv2.py | 516 +++++++++++ src/ftlbgp/data/mrt/unpack.py | 282 ++++++ src/ftlbgp/data/util.py | 143 +++ src/ftlbgp/model/__init__.py | 12 + src/ftlbgp/model/attr.py | 443 ++++++++++ src/ftlbgp/model/const.py | 724 +++++++++++++++ src/ftlbgp/model/error.py | 169 ++++ src/ftlbgp/model/record.py | 52 ++ src/ftlbgp/model/stats.py | 117 +++ src/ftlbgp/model/util.py | 312 +++++++ src/ftlbgp/parser.py | 373 ++++++++ src/ftlbgp/version.py | 14 + 33 files changed, 7875 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 pyproject.toml create mode 100644 src/ftlbgp/__init__.py create mode 100644 src/ftlbgp/__main__.py create mode 100644 src/ftlbgp/data/__init__.py create mode 100644 src/ftlbgp/data/const.py create mode 100644 src/ftlbgp/data/lgl/__init__.py create mode 100644 src/ftlbgp/data/lgl/unpack.py create mode 100644 src/ftlbgp/data/mrt/__init__.py create mode 100644 src/ftlbgp/data/mrt/bgp/__init__.py create mode 100644 src/ftlbgp/data/mrt/bgp/attr.py create mode 100644 src/ftlbgp/data/mrt/bgp/const.py create mode 100644 src/ftlbgp/data/mrt/bgp/msg.py create mode 100644 src/ftlbgp/data/mrt/bgp/nlri.py create mode 100644 src/ftlbgp/data/mrt/entry/__init__.py create mode 100644 src/ftlbgp/data/mrt/entry/bgp.py create mode 100644 src/ftlbgp/data/mrt/entry/bgp4mp.py create mode 100644 src/ftlbgp/data/mrt/entry/const.py create mode 100644 src/ftlbgp/data/mrt/entry/td.py create mode 100644 src/ftlbgp/data/mrt/entry/tdv2.py create mode 100644 src/ftlbgp/data/mrt/unpack.py create mode 100644 src/ftlbgp/data/util.py create mode 100644 src/ftlbgp/model/__init__.py create mode 100644 src/ftlbgp/model/attr.py create mode 100644 src/ftlbgp/model/const.py create mode 100644 src/ftlbgp/model/error.py create mode 100644 src/ftlbgp/model/record.py create mode 100644 src/ftlbgp/model/stats.py create mode 100644 src/ftlbgp/model/util.py create mode 100644 src/ftlbgp/parser.py create mode 100644 src/ftlbgp/version.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e411c47 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Python files +__pycache__/ +*.pyc +*.pyd +*.pyo + +# Archive files +*.bz2 +*.gz +*.zip +*.pickle + +# Temp files +*.swo +*.swp +*.swpx +*.swx +*.tmp + +# Log files +*.log +/log/ + +# Binaries +/bin/ +/dist/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..1737737 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (C) 2014-2024 Leitwert GmbH + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..468902f --- /dev/null +++ b/README.md @@ -0,0 +1,307 @@ +# ftlbgp + +A Python parser for BGP data in MRT or looking glass format. + +### Key features + +- Support for all MRT entry types, BGP messages, and BGP attributes +- Customizable record and attribute types (no parsing of unneeded data) +- Programmatic data access with optional CSV and JSON serialization +- Raw values and human-readable output (e.g. integers vs. strings) +- Rapid prototyping (namedtuple) and high-performance mode (tuple) +- Context manager with built-in statistics and flexible error handling +- Zero-copy operations on all data items (as fast as it gets in Python) + +## Compatibility + +This package is compatible with `python3.6` and `pypy3.6-v7.0.0` or greater. + +## Installation + +Run `pip install ftlbgp` or + +``` +~$ git clone https://github.com/leitwert-net/ftlbgp.git` +~$ cd ftlbgp.git/src +``` + +to download and run the source code manually. + +## Usage + +### Parsing MRT files + +``` +~$ python3 -m ftlbgp -h +usage: python3 -m ftlbgp [-h] [--pkg-help] [--json] [ ...] + +ftlbgp [v1.0.3] - Parse BGP archives in MRT or looking glass format + +positional arguments: + input file with BGP data (supports bz2/gz) + +optional arguments: + -h, --help show this help message and exit + --pkg-help show package help for BgpParser usage + --json output BGP records in JSON format + +``` + +### Programmatic use + +```python +# Import parser +from ftlbgp import BgpParser + +# Prepare input file +filename = ... + +# Parse default records and attributes +with BgpParser() as parse: + for record in parse(filename): + print(record) +``` + +## Customization + +```python +# Parse all records +with BgpParser(bgp_records=BgpParser.bgp.records.ALL) as parse: + for record in parse(filename): + print(record) + +# Parse specific records (route and error) +with BgpParser(bgp_records=BgpParser.bgp.records.route | BgpParser.bgp.records.error) as parse: + for record in parse(filename): + print(record) + +# Parse all route attributes +with BgpParser(bgp_route=BgpParser.bgp.route.ALL) as parse: + for record in parse(filename): + print(record) + +# Parse specific route attributes (default and local_pref) +with BgpParser(bgp_route=BgpParser.bgp.route.DEFAULT | BgpParser.bgp.route.local_pref) as parse: + for record in parse(filename): + print(record) +``` + +## Full specification + +``` +BgpParser() + +This @FtlParser instance is used to read input files and generate a set of BGP records (Python tuples). +It must be used with a context manager (see sample usage below) and accepts the following arguments. + +Keyword arguments: + named_records - Return named tuples instead of plain unnamed tuple records. [Default: True] + serialize - Convert output records to JSON (if named) or CSV (if unnamed). [Default: False] + use_cache - Cache expensive low-entropy values (memory consumption < 1MB). [Default: True] + raise_on_errors - Raise exceptions and stop parsing in case of data errors. [Default: False] + bgp_records - Select the types of BgpRecord entries to be returned by the parser. + Supports bitwise logical operators OR, AND, NOT to specify multiple records. + [Default: + BgpParser.bgp.records.route | + BgpParser.bgp.records.stats | + BgpParser.bgp.records.error + Available: + BgpParser.bgp.records.peer_table + BgpParser.bgp.records.state_change + BgpParser.bgp.records.keep_alive + BgpParser.bgp.records.route_refresh + BgpParser.bgp.records.notification + BgpParser.bgp.records.open + BgpParser.bgp.records.ALL + BgpParser.bgp.records.NONE + BgpParser.bgp.records.DEFAULT] + bgp_peer_table - Select [optionally human-readable] attribute to be included in BgpPeerTableRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.peer_table[.human].type | + BgpParser.bgp.peer_table[.human].peer_protocol | + BgpParser.bgp.peer_table[.human].peer_bgp_id | + BgpParser.bgp.peer_table[.human].peer_as | + BgpParser.bgp.peer_table[.human].peer_ip + Available: + BgpParser.bgp.peer_table[.human].collector_bgp_id + BgpParser.bgp.peer_table[.human].view_name + BgpParser.bgp.peer_table[.human].ALL + BgpParser.bgp.peer_table[.human].NONE + BgpParser.bgp.peer_table[.human].DEFAULT] + bgp_state_change - Select [optionally human-readable] attribute to be included in BgpStateChangeRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.state_change[.human].type | + BgpParser.bgp.state_change[.human].timestamp | + BgpParser.bgp.state_change[.human].peer_protocol | + BgpParser.bgp.state_change[.human].peer_as | + BgpParser.bgp.state_change[.human].peer_ip | + BgpParser.bgp.state_change[.human].old_state | + BgpParser.bgp.state_change[.human].new_state + Available: + BgpParser.bgp.state_change[.human].ALL + BgpParser.bgp.state_change[.human].NONE + BgpParser.bgp.state_change[.human].DEFAULT] + bgp_route - Select [optionally human-readable] attribute to be included in BgpRouteRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.route[.human].type | + BgpParser.bgp.route[.human].source | + BgpParser.bgp.route[.human].sequence | + BgpParser.bgp.route[.human].timestamp | + BgpParser.bgp.route[.human].peer_protocol | + BgpParser.bgp.route[.human].peer_bgp_id | + BgpParser.bgp.route[.human].peer_as | + BgpParser.bgp.route[.human].peer_ip | + BgpParser.bgp.route[.human].nexthop_protocol | + BgpParser.bgp.route[.human].nexthop_ip | + BgpParser.bgp.route[.human].prefix_protocol | + BgpParser.bgp.route[.human].prefix | + BgpParser.bgp.route[.human].path_id | + BgpParser.bgp.route[.human].aspath | + BgpParser.bgp.route[.human].origin | + BgpParser.bgp.route[.human].communities | + BgpParser.bgp.route[.human].large_communities + Available: + BgpParser.bgp.route[.human].extended_communities + BgpParser.bgp.route[.human].multi_exit_disc + BgpParser.bgp.route[.human].atomic_aggregate + BgpParser.bgp.route[.human].aggregator_protocol + BgpParser.bgp.route[.human].aggregator_as + BgpParser.bgp.route[.human].aggregator_ip + BgpParser.bgp.route[.human].only_to_customer + BgpParser.bgp.route[.human].originator_id + BgpParser.bgp.route[.human].cluster_list + BgpParser.bgp.route[.human].local_pref + BgpParser.bgp.route[.human].attr_set + BgpParser.bgp.route[.human].as_pathlimit + BgpParser.bgp.route[.human].aigp + BgpParser.bgp.route[.human].attrs_unknown + BgpParser.bgp.route[.human].ALL + BgpParser.bgp.route[.human].NONE + BgpParser.bgp.route[.human].DEFAULT] + bgp_keep_alive - Select [optionally human-readable] attribute to be included in BgpKeepAliveRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.keep_alive[.human].type | + BgpParser.bgp.keep_alive[.human].timestamp | + BgpParser.bgp.keep_alive[.human].peer_protocol | + BgpParser.bgp.keep_alive[.human].peer_as | + BgpParser.bgp.keep_alive[.human].peer_ip + Available: + BgpParser.bgp.keep_alive[.human].ALL + BgpParser.bgp.keep_alive[.human].NONE + BgpParser.bgp.keep_alive[.human].DEFAULT] + bgp_route_refresh - Select [optionally human-readable] attribute to be included in BgpRouteRefreshRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.route_refresh[.human].type | + BgpParser.bgp.route_refresh[.human].timestamp | + BgpParser.bgp.route_refresh[.human].peer_protocol | + BgpParser.bgp.route_refresh[.human].peer_as | + BgpParser.bgp.route_refresh[.human].peer_ip | + BgpParser.bgp.route_refresh[.human].refresh_protocol + Available: + BgpParser.bgp.route_refresh[.human].ALL + BgpParser.bgp.route_refresh[.human].NONE + BgpParser.bgp.route_refresh[.human].DEFAULT] + bgp_notification - Select [optionally human-readable] attribute to be included in BgpNotificationRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.notification[.human].type | + BgpParser.bgp.notification[.human].timestamp | + BgpParser.bgp.notification[.human].peer_protocol | + BgpParser.bgp.notification[.human].peer_as | + BgpParser.bgp.notification[.human].peer_ip | + BgpParser.bgp.notification[.human].error_code | + BgpParser.bgp.notification[.human].error_subcode | + BgpParser.bgp.notification[.human].data + Available: + BgpParser.bgp.notification[.human].ALL + BgpParser.bgp.notification[.human].NONE + BgpParser.bgp.notification[.human].DEFAULT] + bgp_open - Select [optionally human-readable] attribute to be included in BgpOpenRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.open[.human].type | + BgpParser.bgp.open[.human].timestamp | + BgpParser.bgp.open[.human].peer_protocol | + BgpParser.bgp.open[.human].peer_as | + BgpParser.bgp.open[.human].peer_ip | + BgpParser.bgp.open[.human].version | + BgpParser.bgp.open[.human].my_as | + BgpParser.bgp.open[.human].hold_time | + BgpParser.bgp.open[.human].bgp_id | + BgpParser.bgp.open[.human].capabilities + Available: + BgpParser.bgp.open[.human].params_unknown + BgpParser.bgp.open[.human].ALL + BgpParser.bgp.open[.human].NONE + BgpParser.bgp.open[.human].DEFAULT] + bgp_stats - Select [optionally human-readable] attribute to be included in BgpStatsRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.stats[.human].type | + BgpParser.bgp.stats[.human].parser_lifetime | + BgpParser.bgp.stats[.human].parser_runtime | + BgpParser.bgp.stats[.human].parser_errors | + BgpParser.bgp.stats[.human].lgl_runtime | + BgpParser.bgp.stats[.human].lgl_entries | + BgpParser.bgp.stats[.human].lgl_errors | + BgpParser.bgp.stats[.human].mrt_runtime | + BgpParser.bgp.stats[.human].mrt_entries | + BgpParser.bgp.stats[.human].mrt_errors | + BgpParser.bgp.stats[.human].mrt_fixes | + BgpParser.bgp.stats[.human].mrt_bgp_entry_types | + BgpParser.bgp.stats[.human].mrt_bgp_message_types | + BgpParser.bgp.stats[.human].mrt_bgp_attribute_types | + BgpParser.bgp.stats[.human].mrt_bgp_capability_types | + BgpParser.bgp.stats[.human].bgp_routes_rib_ipv4 | + BgpParser.bgp.stats[.human].bgp_routes_rib_ipv6 | + BgpParser.bgp.stats[.human].bgp_routes_announce_ipv4 | + BgpParser.bgp.stats[.human].bgp_routes_announce_ipv6 | + BgpParser.bgp.stats[.human].bgp_routes_withdraw_ipv4 | + BgpParser.bgp.stats[.human].bgp_routes_withdraw_ipv6 + Available: + BgpParser.bgp.stats[.human].ALL + BgpParser.bgp.stats[.human].NONE + BgpParser.bgp.stats[.human].DEFAULT] + bgp_error - Select [optionally human-readable] attribute to be included in BgpErrorRecord entries. + Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes. + [Default: + BgpParser.bgp.error[.human].type | + BgpParser.bgp.error[.human].source | + BgpParser.bgp.error[.human].scope | + BgpParser.bgp.error[.human].record | + BgpParser.bgp.error[.human].reason | + BgpParser.bgp.error[.human].message | + BgpParser.bgp.error[.human].data | + BgpParser.bgp.error[.human].trace + Available: + BgpParser.bgp.error[.human].ALL + BgpParser.bgp.error[.human].NONE + BgpParser.bgp.error[.human].DEFAULT] + +Returns: + parse() - Parse function that accepts a filename as single positional argument and generates + all specified BGP records on invocation. Input files can be provided in .bz2 or .gz + format. The parse function may be invoked multiple times within a single context. + +Raises: + FtlError - Generic parser or runtime error. + FtlFileError - Failure during access of input file. + FtlFormatError - Unexpected format of input file. + FtlDataError - Invalid data entry in input file. +``` + +## Author + +Johann SCHLAMP <[schlamp@leitwert.net](mailto:schlamp@leitwert.net)> + +## License + +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at [https://opensource.org/licenses/MIT](https://opensource.org/licenses/MIT). diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..32db9c2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "ftlbgp" +description = "A Python parser for BGP data in MRT or looking glass format" +authors = [{name = "Johann SCHLAMP", email = "schlamp@leitwert.net"}] +keywords = ["MRT", "BGP", "parser"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Intended Audience :: Telecommunications Industry", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +readme = "README.md" +requires-python = ">=3.6" +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/leitwert-net/ftlbgp" +Issues = "https://github.com/leitwert-net/ftlbgp/issues" + +[tool.hatch.version] +path = "src/ftlbgp/version.py" diff --git a/src/ftlbgp/__init__.py b/src/ftlbgp/__init__.py new file mode 100644 index 0000000..ad77643 --- /dev/null +++ b/src/ftlbgp/__init__.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +__author__ = 'Johann SCHLAMP' +__copyright__ = 'Copyright (C) 2014-2024 Leitwert GmbH' +__license__ = 'MIT license' + +__all__ = [ + 'MrtParser', + 'LglParser', + 'BgpParser', + 'FtlError', + 'FtlFileError', + 'FtlFormatError', + 'FtlDataError' +] + +# Local imports +from .version import __version__ +from .parser import FtlParser +from .model.error import FtlError +from .model.error import FtlFileError +from .model.error import FtlFormatError +from .model.error import FtlDataError +from .data.mrt.unpack import unpack_mrt_data +from .data.lgl.unpack import unpack_lgl_data + + +@FtlParser(unpack_mrt_data) +def MrtParser(): + """ Parse BGP data in MRT format. + """ + + +@FtlParser(unpack_lgl_data) +def LglParser(): + """ Parse BGP data in looking glass format. + """ + + +@FtlParser(unpack_mrt_data, unpack_lgl_data) +def BgpParser(): + """ Parse BGP data in MRT or looking glass format. + """ diff --git a/src/ftlbgp/__main__.py b/src/ftlbgp/__main__.py new file mode 100644 index 0000000..3b05f60 --- /dev/null +++ b/src/ftlbgp/__main__.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import argparse +import sys + +# Local imports +from . import __version__ +from . import BgpParser +from . import FtlError +from . import FtlDataError + + +def main(): + """ Main routine. + """ + # Prepare command line arguments + description = f'ftlbgp [v{__version__}] - Parse BGP data in MRT or looking glass format' + parser = argparse.ArgumentParser(prog='ftlbgp', description=description) + argparse._VersionAction.__call__ = lambda *_: (help(BgpParser), parser.exit()) # pylint: disable=protected-access + parser.add_argument('filelist', metavar='', nargs='+', help='input file with BGP data (supports bz2/gz)') + parser.add_argument('--pkg-help', action='version', help='show package help for BgpParser usage') + parser.add_argument('--json', default=False, action='store_true', help='output BGP records in JSON format') + parser.format_help = lambda: argparse.ArgumentParser.format_help(parser) + '\n' + parser._get_formatter = lambda: parser.formatter_class(prog='python3 -m ftlbgp') # pylint: disable=protected-access + args = parser.parse_args() + + # Initialize BGP parser + with BgpParser(named_records=args.json, serialize=True, raise_on_errors=False, + bgp_records=BgpParser.bgp.records.route, + bgp_route=( + BgpParser.bgp.route.human.DEFAULT + & ~BgpParser.bgp.route.human.type + & ~BgpParser.bgp.route.human.peer_bgp_id + )) as parse: + + # Parse input files + for filename in args.filelist: + try: + # Iterate and print records + for record in parse(filename): + print(record) + + # Ignore non-critical data errors + except FtlDataError: + continue + + # Abort on unknown errors + except FtlError as error: + print(str(error)) + sys.exit(1) + + # Handle command line induced errors + except (BrokenPipeError, KeyboardInterrupt): + return + + +# Main routine +if __name__ == '__main__': + main() diff --git a/src/ftlbgp/data/__init__.py b/src/ftlbgp/data/__init__.py new file mode 100644 index 0000000..093fb18 --- /dev/null +++ b/src/ftlbgp/data/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" diff --git a/src/ftlbgp/data/const.py b/src/ftlbgp/data/const.py new file mode 100644 index 0000000..16514c1 --- /dev/null +++ b/src/ftlbgp/data/const.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import socket +import struct +from datetime import datetime + +# IP protocols +IPV4 = 4 +IPV6 = 6 + +# IP protocol strings +IPV4_STR = 'ipv4' +IPV6_STR = 'ipv6' + +# Address families +AF_INET = int(socket.AF_INET) +AF_INET6 = int(socket.AF_INET6) + +# AFI values +AFI_IPV4 = 1 # Defined by IANA +AFI_IPV6 = 2 # Defined by IANA + +# Socket/struct functions +socket_inet_pton = socket.inet_pton +socket_inet_ntop = socket.inet_ntop +struct_unpack = struct.unpack +struct_pack = struct.pack + +# Struct bytes +STRUCT_2B = '!H' +STRUCT_4B = '!I' +STRUCT_8B = '!Q' +STRUCT_2B2B = '!HH' +STRUCT_8B8B = '!QQ' +STRUCT_2B2B2B = '!HHH' +STRUCT_4B4B4B = '!III' + +# Strings +UTF8 = 'utf-8' + +# Datetime functions +datetime_utcfromtimestamp = datetime.utcfromtimestamp + +# Datetime formats +DATETIME_FORMAT_USEC = '%Y-%m-%d %H:%M:%S.%f' +DATETIME_FORMAT_MIN = '%Y-%m-%d %H:%M' diff --git a/src/ftlbgp/data/lgl/__init__.py b/src/ftlbgp/data/lgl/__init__.py new file mode 100644 index 0000000..093fb18 --- /dev/null +++ b/src/ftlbgp/data/lgl/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" diff --git a/src/ftlbgp/data/lgl/unpack.py b/src/ftlbgp/data/lgl/unpack.py new file mode 100644 index 0000000..ddaf655 --- /dev/null +++ b/src/ftlbgp/data/lgl/unpack.py @@ -0,0 +1,638 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import re + +# Local imports +from ..const import IPV4 +from ..const import IPV6 +from ..const import IPV4_STR +from ..const import IPV6_STR +from ..const import AF_INET +from ..const import AF_INET6 +from ..const import STRUCT_4B +from ..const import STRUCT_8B8B +from ..const import socket_inet_pton +from ..const import struct_unpack +from ...parser import FtlParserFunc +from ...model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL +from ...model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID +from ...model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_AS +from ...model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL_HUMAN +from ...model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_SOURCE +from ...model.attr import FTL_ATTR_BGP_ROUTE_SEQUENCE +from ...model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL +from ...model.attr import FTL_ATTR_BGP_ROUTE_PEER_BGP_ID +from ...model.attr import FTL_ATTR_BGP_ROUTE_PEER_AS +from ...model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL +from ...model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_IP +from ...model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL +from ...model.attr import FTL_ATTR_BGP_ROUTE_PREFIX +from ...model.attr import FTL_ATTR_BGP_ROUTE_ASPATH +from ...model.attr import FTL_ATTR_BGP_ROUTE_ORIGIN +from ...model.attr import FTL_ATTR_BGP_ROUTE_LOCAL_PREF +from ...model.attr import FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC +from ...model.attr import FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_PEER_BGP_ID_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_IP_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN +from ...model.attr import FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN +from ...model.attr import FTL_ATTR_BGP_STATS_LGL_ENTRIES +from ...model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 +from ...model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 +from ...model.const import FTL_ATTR_BGP_ROUTE_SOURCE_RIB +from ...model.const import FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR +from ...model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_IGP +from ...model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_IGP_STR +from ...model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_EGP +from ...model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_EGP_STR +from ...model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE +from ...model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE_STR +from ...model.const import FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA +from ...model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR +from ...model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH +from ...model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX +from ...model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_AS +from ...model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_IP +from ...model.record import FTL_RECORD_BGP_PEER_TABLE +from ...model.record import FTL_RECORD_BGP_ROUTE +from ...model.record import FTL_RECORD_BGP_STATS +from ...model.error import FtlLglError +from ...model.error import FtlLglHeaderError +from ...model.error import FtlLglFormatError +from ...model.error import FtlLglDataError + +# Header parts +HEADER_PEER_AS = 'local AS' +HEADER_PEER_BGP_ID = 'local router ID is' +HEADER_LOCAL_PREF = 'Default local pref' + +# Header regex +HEADER_REGEX = re.compile(r'^\s+Network\s+Next Hop\s+Metric\s+LocPrf\s+Weight\s+Path\s*$') +HEADER_REGEX_ALT = re.compile(r'^\s+P\s+Pref\s+Time\s+Destination\s+Next Hop\s+If\s+Path\s*$') + +# Route parts +ROUTE_ASHOPS = r'(?:(?:\d+\s?)|(?:{(?:\d+,?)+}\s?))' +ROUTE_STATUS = '[sdh*>=irSR ]' +ROUTE_ORIGIN = '[ie?]' +ROUTE_STATUS_ALT = '[s*>i ]' +ROUTE_ORIGIN_ALT = '[ie?a]' + +# Route regex +ROUTE_REGEX = re.compile( + r'^{route_status}{{3,4}}' # Route status (ignored, required) + r'(\S+)?\s+' # Prefix (group 1, optional) + r'(\S+)\s+' # Next hop (group 2, required) + r'\s{{1,7}}(\d+)?' # MED metric (group 3, optional) + r'\s{{1,7}}(\d+)?' # Local-pref (group 4, optional) + r'\s{{1,7}}(?:\d+)?' # Weight (ignored, optional) + r'(?:\s({route_ashops}+))?' # AS path (group 5, optional) + r'\s({route_origin}{{1}})' # Route origin (group 6, required) + r''.format(route_status=ROUTE_STATUS, route_ashops=ROUTE_ASHOPS, route_origin=ROUTE_ORIGIN) +) +ROUTE_REGEX_ALT = re.compile( + r'^{route_status}{{2}}' # Route status (ignored, required) + r'(?:\S)?\s{{1,2}}' # P (unknown) (ignored, optional) + r'\s{{1,3}}(\d+)?' # Local-pref (group 1, optional) + r'\s+\d{{2}}:\d{{2}}:\d{{2}}' # Timestamp (ignored, required) + r'\s+(\S+)\s+' # Prefix (group 2, required) + r'(\S+)\s+' # Next hop (group 3, required) + r'(?:\S+)?\s+' # Interface (ignored, optional) + r'(?:\s({route_ashops}+))?' # AS path (group 4, optional) + r'\s({route_origin}{{1}})' # Route origin (group 5, required) + r''.format(route_status=ROUTE_STATUS_ALT, route_ashops=ROUTE_ASHOPS, route_origin=ROUTE_ORIGIN_ALT) +) + +# Totals regex +ROUTE_TOTAL1_REGEX = re.compile(r'^Displayed\s+(\d+) routes and\s+(\d+) total paths$') +ROUTE_TOTAL2_REGEX = re.compile(r'^Total number of prefixes (\d+)$') +ROUTE_HEADER_REGEX = re.compile(r'^View \S+ \S+ (\d+) routes$') + + +@FtlParserFunc(text_input='utf-8') +# pylint: disable-next=unused-argument +def unpack_lgl_data(inputfile, caches, stats_record, bgp_records, bgp_error): + """ Parse looking glass RIB dumps in plain text format. + """ + # Access BGP record templates + route_init, route_emit, route_error = bgp_records.route + peer_table_init, peer_table_emit, peer_table_error = bgp_records.peer_table + + # Initialize route record header + route_record_header = list(route_init) + + # Add source to route record header + if FTL_ATTR_BGP_ROUTE_SOURCE >= 0: + source = FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR if FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN else FTL_ATTR_BGP_ROUTE_SOURCE_RIB + route_record_header[FTL_ATTR_BGP_ROUTE_SOURCE] = source + + ############## + # BGP HEADER # + ############## + + # ------------------------ + # [PCH] % sh bgp ipv4 wide + # ------------------------ + # BGP table version is 147996423, local router ID is 74.80.112.4, vrf id 0 + # Default local pref 100, local AS 3856 + # Status codes: s suppressed, d damped, h history, * valid, > best, = multipath, + # i internal, r RIB-failure, S Stale, R Removed + # Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self + # Origin codes: i - IGP, e - EGP, ? - incomplete + # + # Network Next Hop Metric LocPrf Weight Path + + # ------------------------- + # [RIPE] % sh bgp ipv4 wide + # ------------------------- + # View #0 inet 64059 routes + # Status code: s suppressed, * valid, > best, i - internal + # Origin codes: i - IGP, e - EGP, ? - incomplete, a - aggregate + # + # P Pref Time Destination Next Hop If Path + + # Prepare dump data + line = None + + try: + # Prepare dump status + alt_mode = False + dump_valid = False + header_errors = list() + + # Prepare details + peer_as, peer_bgp_id = None, None + prefixes, routes = None, None + + # Parse dump header + try: + for line in inputfile: + + # Extract totals from first line for sanitarization + match = ROUTE_HEADER_REGEX.match(line) + if match is not None: + try: + prefixes = int(match.group(1)) + except ValueError: + pass + + # Extract default local-pref + if HEADER_LOCAL_PREF in line: + try: + # Try to parse local-pref + locpref = int(line.split(HEADER_LOCAL_PREF, 1)[1].strip().split(None, 1)[0].rstrip(',').strip()) + + # Add local-pref to route record header + if FTL_ATTR_BGP_ROUTE_LOCAL_PREF >= 0: + route_record_header[FTL_ATTR_BGP_ROUTE_LOCAL_PREF] = locpref + + # Store header error + except Exception as exc: # pylint: disable=broad-except + header_errors.append((route_error, FtlLglDataError('Unable to decode local-pref', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR, data=line, exception=exc))) + + # Extract peer AS + if HEADER_PEER_AS in line: + try: + # Try to parse peer AS + peer_as = int(line.split(HEADER_PEER_AS, 1)[1].strip().split(None, 1)[0].rstrip(',').strip()) + + # Add peer AS to route record header + if FTL_ATTR_BGP_ROUTE_PEER_AS >= 0: + route_record_header[FTL_ATTR_BGP_ROUTE_PEER_AS] = peer_as + + # Store header error + except Exception as exc: # pylint: disable=broad-except + header_errors.append((peer_table_error, FtlLglDataError('Unable to decode peer AS', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_AS, data=line, exception=exc))) + + # Extract peer BGP ID (IPv4 only) + if HEADER_PEER_BGP_ID in line: + try: + # Try to parse peer BGP ID + peer_bgp_id = line.split(HEADER_PEER_BGP_ID, 1)[1].strip().split(None, 1)[0].rstrip(',').strip() + peer_bgp_id_int = struct_unpack(STRUCT_4B, socket_inet_pton(AF_INET, peer_bgp_id))[0] + + # Add peer BGP ID to route record header + peer_bgp_id = peer_bgp_id if FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID_HUMAN else peer_bgp_id_int + if FTL_ATTR_BGP_ROUTE_PEER_BGP_ID >= 0: + peer_bgp_id_route = peer_bgp_id if FTL_ATTR_BGP_ROUTE_PEER_BGP_ID_HUMAN else peer_bgp_id_int + route_record_header[FTL_ATTR_BGP_ROUTE_PEER_BGP_ID] = peer_bgp_id_route + + # Store header error + except Exception as exc: # pylint: disable=broad-except + header_errors.append((peer_table_error, FtlLglDataError('Unable to decode peer BGP ID', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_IP, data=line, exception=exc))) + + # Check for valid table start + if HEADER_REGEX.match(line) is not None: + dump_valid = True + break + + # Check for alternative table start (early RIPE RIS) + if HEADER_REGEX_ALT.match(line) is not None: + alt_mode = True + dump_valid = True + break + + # Always raise critical file reading errors + except Exception as exc: + # pylint: disable-next=raise-missing-from + raise FtlLglHeaderError('Unknown lgl format', data=line, exception=exc) + + # Always raise critical header errors + if dump_valid is False: + raise FtlLglHeaderError('No BGP table found', data=line) + + # Yield (or re-raise) remaining header errors + for record_error, error in header_errors: + yield from record_error(error) + + # Prepare peer table record + peer_table_record = None + if peer_as is not None or peer_bgp_id is not None: + + # Initialize peer table record + peer_table_record = list(peer_table_init) + + # Add peer AS to peer table record + if peer_as is not None: + if FTL_ATTR_BGP_PEER_TABLE_PEER_AS >= 0: + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_AS] = peer_as + + # Add peer BGP ID to peer table record + if peer_bgp_id is not None: + if FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID >= 0: + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID] = peer_bgp_id + + ############# + # BGP TABLE # + ############# + + # ------------------------ + # [PCH] % sh bgp ipv4 wide + # ------------------------ + # Network Next Hop Metric LocPrf Weight Path + # * 1.0.0.0/24 206.126.236.19 0 0 3257 13335 i + # *> 206.126.237.30 0 0 13335 i + # *> 1.0.16.0/24 206.126.236.23 0 0 2497 2519 i + + # ------------------------- + # [RIPE] % sh bgp ipv4 wide + # ------------------------- + # P Pref Time Destination Next Hop If Path + # > B 0 11:53:59 3.0.0.0/8 193.0.0.56 eth0 3333 286 701 80 i + # * B 0 11:53:46 3.0.0.0/8 193.0.0.59 eth0 3333 286 701 80 i + + # Prepare record parser + n_routes4, n_routes6, n_prefixes = 0, 0, 0 + + # Initialize route record and parser state + route_record_prefix, route_record_proto, last_prefix = None, None, None + multiline = '' + + # Prepare regex + route_regex = ROUTE_REGEX + if alt_mode is True: + route_regex = ROUTE_REGEX_ALT + + # Iterate input lines + for line in inputfile: + + # Skip empty lines + line = line.rstrip() # pylint: disable=redefined-loop-name + if len(line) == 0: + route_record_prefix = None + route_record_proto = None + multiline = '' + continue + + # Update multiline + multiline += line + + # Parse multiline route (with or without prefix) + match = route_regex.match(multiline) + if match is not None: + + # Reset parser state + multiline = '' + + # Extract route details + prefix, nexthop_ip, med, locpref, aspath, origin = None, None, None, None, None, None + if alt_mode is False: + prefix, nexthop_ip, med, locpref, aspath, origin = match.groups() + else: + locpref, prefix, nexthop_ip, aspath, origin = match.groups() + + ############## + # BGP PREFIX # + ############## + + # Extract prefix + if prefix is not None: + + # Reset route record + route_record_prefix = None + route_record_proto = None + + # Prepare prefix + prefix_int, mask = 0, 0 + proto = IPV6 if ':' in prefix else IPV4 + try: + # Ignore shortened prefix notation for /8 and /16 + if prefix.endswith('.0.0') is True: + raise ValueError(f'indecisive classful {IPV4_STR} prefix ({prefix})') + + # Fix shortened prefix notation for /24 + if prefix.endswith('.0') is True: + prefix += '/24' + + # Parse prefix + prefix_str, mask = prefix.split('/', 1) + if proto == IPV6: + net, host = struct_unpack(STRUCT_8B8B, socket_inet_pton(AF_INET6, prefix_str)) + prefix_int = (net << 64) + host + else: + prefix_int = struct_unpack(STRUCT_4B, socket_inet_pton(AF_INET, prefix_str))[0] + + # Sanitize prefix + max_mask, mask = (128 if proto == IPV6 else 32), int(mask) + shift = max_mask - mask + if mask > max_mask: + raise ValueError(f'invalid {IPV6_STR if proto == IPV6 else IPV4_STR} mask (/{mask})') + if (prefix_int >> shift) << shift != prefix_int: + raise ValueError(f'misaligned prefix ({prefix})') + + # Handler errors + except ValueError as exc: + + # Yield (or re-raise) route error + yield from route_error(FtlLglDataError('Unable to decode prefix', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX, data=line, exception=exc)) + + # Reset parser state + multiline = '' + continue + + # Clone route record (filled with header data) + route_record_prefix = list(route_record_header) + route_record_proto = proto + + # Update prefix count + if prefix != last_prefix: + n_prefixes += 1 + last_prefix = prefix + + # Add sequence to route record prefix + if FTL_ATTR_BGP_ROUTE_SEQUENCE >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_SEQUENCE] = n_prefixes + + # Add peer protocol to route record prefix + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL >= 0: + peer_proto = proto + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN: + peer_proto = IPV6_STR if peer_proto == IPV6 else IPV4_STR + route_record_prefix[FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL] = peer_proto + + # Add prefix protocol to route record prefix + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + prefix_proto = proto + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN: + prefix_proto = IPV6_STR if prefix_proto == IPV6 else IPV4_STR + route_record_prefix[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = prefix_proto + + # Add prefix to route record prefix + if not FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN: + prefix = (prefix_int, mask) + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_PREFIX] = prefix + + # Add peer protocol to peer table record and yield on first prefix + # NOTE: We assume that all routes in a given looking glass dump share the same peer protocol + if FTL_RECORD_BGP_PEER_TABLE: + if peer_table_record is not None: + if FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL >= 0: + peer_proto = proto + if FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL_HUMAN: + peer_proto = IPV6_STR if peer_proto == IPV6 else IPV4_STR + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL] = peer_proto + + # Yield and nullify final peer table record + yield peer_table_emit(peer_table_record) + peer_table_record = None + + # Should not happen (only on errors or table start with missing first line) + if route_record_prefix is None: + continue + + ############# + # BGP ROUTE # + ############# + + # Clone route record (filled with header+prefix data) + route_record = list(route_record_prefix) + + # Extract nexthop protocol and IP + try: + nexthop_ip_int = None + nexthop_proto = IPV6 if ':' in nexthop_ip else IPV4 + if nexthop_proto == IPV6: + net, host = struct_unpack(STRUCT_8B8B, socket_inet_pton(AF_INET6, nexthop_ip)) + nexthop_ip_int = (net << 64) + host + else: + nexthop_ip_int = struct_unpack(STRUCT_4B, socket_inet_pton(AF_INET, nexthop_ip))[0] + if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL_HUMAN: + nexthop_proto = IPV6_STR if nexthop_proto == IPV6 else IPV4_STR + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL] = nexthop_proto + if FTL_ATTR_BGP_ROUTE_NEXTHOP_IP >= 0: + if not FTL_ATTR_BGP_ROUTE_NEXTHOP_IP_HUMAN: + nexthop_ip = nexthop_ip_int + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_IP] = nexthop_ip + + # Handle error + except ValueError as exc: + + # Yield (or re-raise) route error + yield from route_error(FtlLglDataError('Unable to decode nexthop IP', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_IP, data=line, exception=exc)) + + # Reset route record and parser state + route_record_prefix = None + route_record_proto = None + multiline = '' + continue + + # Extract MED metric + try: + if med is not None: + med_int = int(med) + + # Add MED metric to route record + if FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC >= 0: + route_record[FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC] = med_int + + # Handle error + except ValueError as exc: + + # Yield (or re-raise) route error + yield from route_error(FtlLglDataError('Unable to decode MED metric', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR, data=line, exception=exc)) + + # Reset route record and parser state + route_record_prefix = None + route_record_proto = None + multiline = '' + continue + + # Extract local pref + try: + if locpref is not None: + locpref_int = int(locpref) + + # Add local_pref to route record + if FTL_ATTR_BGP_ROUTE_LOCAL_PREF >= 0: + route_record[FTL_ATTR_BGP_ROUTE_LOCAL_PREF] = locpref_int + + # Handle error + except ValueError as exc: + + # Yield (or re-raise) route error + yield from route_error(FtlLglDataError('Unable to decode local-pref', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR, data=line, exception=exc)) + + # Reset route record and parser state + route_record_prefix = None + route_record_proto = None + multiline = '' + continue + + # Extract AS path + try: + aspath = (tuple() if aspath is None else + tuple(tuple(sorted(set(int(y) for y in x[1:-1].split(',') if len(y) > 0))) + if x[0] == '{' else int(x) for x in aspath.split())) + + # Add AS path to route record + if FTL_ATTR_BGP_ROUTE_ASPATH >= 0: + route_record[FTL_ATTR_BGP_ROUTE_ASPATH] = aspath + + # Handle error + except ValueError as exc: + + # Yield (or re-raise) route error + yield from route_error(FtlLglDataError('Unable to decode AS path', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH, data=line, exception=exc)) + + # Reset route record and parser state + route_record_prefix = None + route_record_proto = None + multiline = '' + continue + + # Add origin to route record + if FTL_ATTR_BGP_ROUTE_ORIGIN >= 0: + origin_value = None + if origin == 'i': + origin_value = (FTL_ATTR_BGP_ROUTE_ORIGIN_IGP_STR if FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN else + FTL_ATTR_BGP_ROUTE_ORIGIN_IGP) + elif origin == 'e': + origin_value = (FTL_ATTR_BGP_ROUTE_ORIGIN_EGP_STR if FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN else + FTL_ATTR_BGP_ROUTE_ORIGIN_EGP) + elif origin == '?': + origin_value = (FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE_STR if FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN else + FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE) + if origin_value is not None: + route_record[FTL_ATTR_BGP_ROUTE_ORIGIN] = origin_value + + # Update stats + if route_record_proto == IPV6: + n_routes6 += 1 + else: + n_routes4 += 1 + + # Yield final route + if FTL_RECORD_BGP_ROUTE: + yield route_emit(route_record) + + ############# + # BGP STATS # + ############# + + # ------------------------ + # [PCH] % sh bgp ipv4 wide + # ------------------------ + # Displayed 612636 routes and 1443316 total paths + + # ------------------------ + # [PCH] % sh bgp ipv4 wide + # ------------------------ + # Total number of prefixes 612636 + + # Extract totals from last line for sanitarization + match = ROUTE_TOTAL1_REGEX.match(multiline) + if match is not None: + try: + prefixes = int(match.group(1)) + routes = int(match.group(2)) + except ValueError: + pass + else: + match = ROUTE_TOTAL2_REGEX.match(multiline) + if match is not None: + try: + prefixes = int(match.group(1)) + except ValueError: + pass + + # Check prefix parsing result + if prefixes is not None and prefixes != n_prefixes: + message = 'Mismatching number of looking glass prefixes ({:,}/{:,})'.format(n_prefixes, prefixes) + yield from bgp_error(FtlLglDataError(message, reason=FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA, data=line)) + + # Check route parsing result + if routes is not None and routes != n_routes4 + n_routes6: + message = 'Mismatching number of looking glass routes ({:,}/{:,})'.format(n_routes4 + n_routes6, routes) + yield from bgp_error(FtlLglDataError(message, reason=FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA, data=line)) + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add number of entries + if FTL_ATTR_BGP_STATS_LGL_ENTRIES >= 0: + stats_record[FTL_ATTR_BGP_STATS_LGL_ENTRIES] += n_prefixes + + # Add number of RIB routes + if FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4] += n_routes4 + + # Add number of RIB routes + if FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6] += n_routes6 + + # Re-raise already handled header exceptions as format exceptions + except FtlLglHeaderError as error: + raise FtlLglFormatError(error=error) # pylint: disable=raise-missing-from + + # Re-raise already handled record errors + except FtlLglError as error: + raise error + + # Yield (or re-raise) unhandled exceptions + except Exception as exc: # pylint: disable=broad-exception-caught + yield from bgp_error(FtlLglDataError('Unhandled data error', data=line, trace=True, exception=exc)) diff --git a/src/ftlbgp/data/mrt/__init__.py b/src/ftlbgp/data/mrt/__init__.py new file mode 100644 index 0000000..093fb18 --- /dev/null +++ b/src/ftlbgp/data/mrt/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" diff --git a/src/ftlbgp/data/mrt/bgp/__init__.py b/src/ftlbgp/data/mrt/bgp/__init__.py new file mode 100644 index 0000000..093fb18 --- /dev/null +++ b/src/ftlbgp/data/mrt/bgp/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" diff --git a/src/ftlbgp/data/mrt/bgp/attr.py b/src/ftlbgp/data/mrt/bgp/attr.py new file mode 100644 index 0000000..a5a4593 --- /dev/null +++ b/src/ftlbgp/data/mrt/bgp/attr.py @@ -0,0 +1,1162 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import base64 + +# Local imports +from .const import BGP_SAFI_UNICAST +from .const import BGP_SAFI_MULTICAST +from .const import BGP_PATH_ATTR_ORIGIN +from .const import BGP_PATH_ATTR_AS_PATH +from .const import BGP_PATH_ATTR_AS4_PATH +from .const import BGP_PATH_ATTR_AS_PATH_SEGMENT_SET +from .const import BGP_PATH_ATTR_AS_PATH_SEGMENT_CONFED_SET +from .const import BGP_PATH_ATTR_AS4_PATH_AS_TRANS +from .const import BGP_PATH_ATTR_NEXT_HOP +from .const import BGP_PATH_ATTR_COMMUNITIES +from .const import BGP_PATH_ATTR_LARGE_COMMUNITIES +from .const import BGP_PATH_ATTR_EXTENDED_COMMUNITIES +from .const import BGP_PATH_ATTR_MULTI_EXIT_DISC +from .const import BGP_PATH_ATTR_MP_REACH_NLRI +from .const import BGP_PATH_ATTR_MP_UNREACH_NLRI +from .const import BGP_PATH_ATTR_ATOMIC_AGGREGATE +from .const import BGP_PATH_ATTR_AGGREGATOR +from .const import BGP_PATH_ATTR_AS4_AGGREGATOR +from .const import BGP_PATH_ATTR_ONLY_TO_CUSTOMER +from .const import BGP_PATH_ATTR_ORIGINATOR_ID +from .const import BGP_PATH_ATTR_CLUSTER_LIST +from .const import BGP_PATH_ATTR_LOCAL_PREF +from .const import BGP_PATH_ATTR_ATTR_SET +from .const import BGP_PATH_ATTR_AS_PATHLIMIT +from .const import BGP_PATH_ATTR_AIGP +from .nlri import unpack_mrt_bgp_nlri +from ...const import IPV4 +from ...const import IPV6 +from ...const import IPV4_STR +from ...const import IPV6_STR +from ...const import AF_INET +from ...const import AF_INET6 +from ...const import AFI_IPV4 +from ...const import AFI_IPV6 +from ...const import STRUCT_2B +from ...const import STRUCT_4B +from ...const import STRUCT_8B +from ...const import STRUCT_2B2B +from ...const import STRUCT_8B8B +from ...const import STRUCT_2B2B2B +from ...const import STRUCT_4B4B4B +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ....model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_ASPATH +from ....model.attr import FTL_ATTR_BGP_ROUTE_ORIGIN +from ....model.attr import FTL_ATTR_BGP_ROUTE_COMMUNITIES +from ....model.attr import FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES +from ....model.attr import FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES +from ....model.attr import FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC +from ....model.attr import FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE +from ....model.attr import FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS +from ....model.attr import FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER +from ....model.attr import FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID +from ....model.attr import FTL_ATTR_BGP_ROUTE_CLUSTER_LIST +from ....model.attr import FTL_ATTR_BGP_ROUTE_LOCAL_PREF +from ....model.attr import FTL_ATTR_BGP_ROUTE_ATTR_SET +from ....model.attr import FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT +from ....model.attr import FTL_ATTR_BGP_ROUTE_AIGP +from ....model.attr import FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN +from ....model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_COMMUNITIES_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_CLUSTER_LIST_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_FIXES +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES_HUMAN +from ....model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_IGP +from ....model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_IGP_STR +from ....model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_EGP +from ....model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_EGP_STR +from ....model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE +from ....model.const import FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE_STR +from ....model.const import FTL_ATTR_BGP_STATS_BGP_PATH_ATTR_TO_STR +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH_STR +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH_STR +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI_STR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH +from ....model.record import FTL_RECORD_BGP_STATS +from ....model.error import FtlMrtDataError + + +def unpack_mrt_bgp_attr(caches, stats_record, route_init, route_emit, route_record, attr_bytes, aslen=4, addpath=False, + rib=False): + """ Parse MRT attributes. + """ + # Prepare AS byte length + asbytelen = STRUCT_2B if aslen == 2 else STRUCT_4B + + # Initialize AS/AS4 path support + aspath, aggr_as, aggr_ip = None, None, None + as4path, aggr4_as, aggr4_ip = None, None, None + + # Initialize announced/withdrawn prefixes + nlri, nlui = tuple(), tuple() + + ##################### + # ATTRIBUTE PARSING # + ##################### + + # Prepare byte offset + offset = 0 + + # ------------------------------------ + # [RFC4271] 4.3. UPDATE Message Format + # ------------------------------------ + # 0 1 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Attr. Flags |Attr. Type Code| + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse attributes + attrlen = len(attr_bytes) + while offset < attrlen: + + # Parse attribute flags + flags = attr_bytes[offset] + offset += 1 + + # Parse attribute type + atype = attr_bytes[offset] + offset += 1 + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add attribute type + if FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES >= 0: + attrtype = atype + if FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES_HUMAN: + attrtype = FTL_ATTR_BGP_STATS_BGP_PATH_ATTR_TO_STR.get(atype, atype) + attrtype = str(attrtype) + stats_record_mrt_bgp_attr = stats_record[FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES] + stats_record_mrt_bgp_attr[attrtype] = stats_record_mrt_bgp_attr.get(attrtype, 0) + 1 + + # ------------------------------------ + # [RFC4271] 4.3. UPDATE Message Format + # ------------------------------------ + # The fourth high-order bit (bit 3) of the Attribute Flags octet + # is the Extended Length bit. It defines whether the Attribute + # Length is one octet (if set to 0) or two octets (if set to 1). + # + # If the Extended Length bit of the Attribute Flags octet is set + # to 0, the third octet of the Path Attribute contains the length + # of the attribute data in octets. + # + # If the Extended Length bit of the Attribute Flags octet is set + # to 1, the third and fourth octets of the path attribute contain + # the length of the attribute data in octets. + + # Parse attribute length + alen = 0 + if flags & 0b00010000: + alen = struct_unpack(STRUCT_2B, attr_bytes[offset:offset + 2])[0] + offset += 2 + else: + alen = attr_bytes[offset] + offset += 1 + + # Prepare attribute byte offsets (current/end) + cur_offset, end_offset = offset, offset + alen + offset = end_offset + + # Sanitize attribute + if offset > attrlen: + # NOTE: A few UPDATE messages (3-5) from RouteViews Oregon2 occasionally include incomplete BGP attributes + raise FtlMrtDataError(f'Incomplete BGP attribute ({attrlen - cur_offset}B<{alen}B)', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR, data=attr_bytes) + + #################### + # ORIGIN ATTRIBUTE # + #################### + + # -------------------------------------------------------------- + # [RFC4271] 4.3. UPDATE Message Format - a) ORIGIN (Type Code 1) + # -------------------------------------------------------------- + # ORIGIN is a well-known mandatory attribute that defines the + # origin of the path information. The data octet can assume + # the following values: + # + # Value Meaning + # + # 0 IGP - Network Layer Reachability Information + # is interior to the originating AS + # + # 1 EGP - Network Layer Reachability Information + # learned via the EGP protocol [RFC904] + # + # 2 INCOMPLETE - Network Layer Reachability + # Information learned by some other means + + # Parse origin attribute + if atype == BGP_PATH_ATTR_ORIGIN: + if FTL_ATTR_BGP_ROUTE_ORIGIN >= 0: + origin = attr_bytes[cur_offset] + if FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN: + if origin == FTL_ATTR_BGP_ROUTE_ORIGIN_IGP: + origin = FTL_ATTR_BGP_ROUTE_ORIGIN_IGP_STR + elif origin == FTL_ATTR_BGP_ROUTE_ORIGIN_EGP: + origin = FTL_ATTR_BGP_ROUTE_ORIGIN_EGP_STR + elif origin == FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE: + origin = FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE_STR + route_record[FTL_ATTR_BGP_ROUTE_ORIGIN] = origin + + ##################### + # AS_PATH ATTRIBUTE # + ##################### + + # --------------------------------------------------------------- + # [RFC4271] 4.3. UPDATE Message Format - b) AS_PATH (Type Code 2) + # --------------------------------------------------------------- + # AS_PATH is a well-known mandatory attribute that is composed + # of a sequence of AS path segments. Each AS path segment is + # represented by a triple . + # + # The path segment type is a 1-octet length field with the + # following values defined: + # + # Value Segment Type + # + # 1 AS_SET: unordered set of ASes a route in the + # UPDATE message has traversed + # + # 2 AS_SEQUENCE: ordered set of ASes a route in + # the UPDATE message has traversed + # + # The path segment length is a 1-octet length field, + # containing the number of ASes (not the number of octets) in + # the path segment value field. + # + # The path segment value field contains one or more AS + # numbers, each encoded as a 2-octet length field. + + # ----------------------------- + # [RFC6793] Protocol Extensions + # ----------------------------- + # This document defines a new BGP path attribute called AS4_PATH. + # This is an optional transitive attribute that contains the AS path + # encoded with four-octet AS numbers. The AS4_PATH attribute has the + # same semantics and the same encoding as the AS_PATH attribute, + # except that it is "optional transitive", and it carries four-octet + # AS numbers. + + # Parse AS/AS4 path attribute + # pylint: disable-next=confusing-consecutive-elif,consider-using-in + elif atype == BGP_PATH_ATTR_AS_PATH or atype == BGP_PATH_ATTR_AS4_PATH: + if FTL_ATTR_BGP_ROUTE_ASPATH >= 0: + + # Sanitize AS path attribute + # NOTE: Some MRT exporters fail to set correct AS/AS4 path type + hoplen, hopbytelen = aslen, asbytelen + c_offset2, c_offset4 = cur_offset, cur_offset + do_continue = True + while do_continue: + do_continue = False + if c_offset2 < end_offset: + c_offset2 += attr_bytes[c_offset2 + 1] * 2 + 2 + do_continue = True + if c_offset4 < end_offset: + c_offset4 += attr_bytes[c_offset4 + 1] * 4 + 2 + do_continue = True + if c_offset2 == c_offset4 == end_offset: + hoplen, hopbytelen = aslen, asbytelen + elif c_offset4 == end_offset: + hoplen, hopbytelen = 4, STRUCT_4B + elif c_offset2 == end_offset: + hoplen, hopbytelen = 2, STRUCT_2B + else: + hlen = end_offset - cur_offset + chlen = (c_offset4 if aslen == 4 else c_offset2) - cur_offset + raise FtlMrtDataError(f'Incomplete AS path attribute ({chlen}>{hlen}B)', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH, data=attr_bytes) + + # Check for AS length fixes + if hoplen != aslen: + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add AS length fix + if FTL_ATTR_BGP_STATS_MRT_FIXES >= 0: + fixtype = str(FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH if rib is False + else FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH) + if FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN: + fixtype = (FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH_STR if rib is False + else FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH_STR) + stats_record_fix = stats_record[FTL_ATTR_BGP_STATS_MRT_FIXES] + stats_record_fix[fixtype] = stats_record_fix.get(fixtype, 0) + 1 + + # Parse AS path segments + cur_aspath = list() + while cur_offset < end_offset: + + # Parse path segment type + stype = attr_bytes[cur_offset] + cur_offset += 1 + + # Parse path segment length + slen = attr_bytes[cur_offset] + cur_offset += 1 + + # Parse path segment or set + target, asset = cur_aspath, False + # pylint: disable-next=consider-using-in + if stype == BGP_PATH_ATTR_AS_PATH_SEGMENT_SET or stype == BGP_PATH_ATTR_AS_PATH_SEGMENT_CONFED_SET: + target, asset = list(), True + + # Parse path segment/set value + for cur_offset in range(cur_offset + hoplen, cur_offset + hoplen * slen + hoplen, hoplen): + target.append(struct_unpack(hopbytelen, attr_bytes[cur_offset - hoplen:cur_offset])[0]) + + # Finalize path set + if asset is True: + cur_aspath.append(tuple(sorted(set(target)))) + + # -------------------------------------------- + # [RFC6793] 4.2.3. Processing Received Updates + # -------------------------------------------- + # When a NEW BGP speaker receives an update from an OLD BGP speaker, it + # MUST be prepared to receive the AS4_PATH attribute along with the + # existing AS_PATH attribute. If the AS4_PATH attribute is also + # received, both of the attributes will be used to construct the exact + # AS path information, and therefore the information carried by both of + # the attributes will be considered for AS path loop detection. + + # Update AS path + if atype == BGP_PATH_ATTR_AS_PATH: + aspath = cur_aspath + elif atype == BGP_PATH_ATTR_AS4_PATH: + as4path = cur_aspath + + ######################### + # COMMUNITIES ATTRIBUTE # + ######################### + + # ------------------------------- + # [RFC1997] COMMUNITIES attribute + # ------------------------------- + # This document creates the COMMUNITIES path attribute is an optional + # transitive attribute of variable length. The attribute consists of a + # set of four octet values, each of which specify a community. All + # routes with this attribute belong to the communities listed in the + # attribute. + # + # The community attribute values shall be encoded using an autonomous + # system number in the first two octets. The semantics of the final + # two octets may be defined by the autonomous system. + + # Parse communities attribute + elif atype == BGP_PATH_ATTR_COMMUNITIES: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_COMMUNITIES >= 0: + if FTL_ATTR_BGP_ROUTE_COMMUNITIES_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_COMMUNITIES] = tuple( + '{}:{}'.format(*struct_unpack(STRUCT_2B2B, attr_bytes[c_offset:c_offset + 4])) + for c_offset in range(cur_offset, end_offset, 4)) + else: + route_record[FTL_ATTR_BGP_ROUTE_COMMUNITIES] = tuple( + struct_unpack(STRUCT_4B, attr_bytes[c_offset:c_offset + 4])[0] + for c_offset in range(cur_offset, end_offset, 4)) + + ###################### + # NEXT_HOP ATTRIBUTE # + ###################### + + # ---------------------------------------------------------------- + # [RFC4271] 4.3. UPDATE Message Format - c) NEXT_HOP (Type Code 3) + # ---------------------------------------------------------------- + # This is a well-known mandatory attribute that defines the + # (unicast) IP address of the router that SHOULD be used as + # the next hop to the destinations listed in the Network Layer + # Reachability Information field of the UPDATE message. + + # Parse nexthop IP attribute (IPv4 only) + elif atype == BGP_PATH_ATTR_NEXT_HOP: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL >= 0: + nexthop_proto = IPV4_STR if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL_HUMAN else IPV4 + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL] = nexthop_proto + if FTL_ATTR_BGP_ROUTE_NEXTHOP_IP >= 0: + nexthop_ip = attr_bytes[cur_offset:end_offset] + if FTL_ATTR_BGP_ROUTE_NEXTHOP_IP_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_IP] = socket_inet_ntop(AF_INET, nexthop_ip) + else: + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_IP] = struct_unpack(STRUCT_4B, nexthop_ip)[0] + + ########################### + # MP_REACH_NLRI ATTRIBUTE # + ########################### + + # ------------------------------------------------------------------------ + # [RFC4760] 3. Multiprotocol Reachable NLRI - MP_REACH_NLRI (Type Code 14) + # ------------------------------------------------------------------------ + # This is an optional non-transitive attribute that can be used for the + # following purposes: + # + # (a) to advertise a feasible route to a peer + # + # (b) to permit a router to advertise the Network Layer address of the + # router that should be used as the next hop to the destinations + # listed in the Network Layer Reachability Information field of the + # MP_NLRI attribute. + # + # +---------------------------------------------------------+ + # | 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) | + # +---------------------------------------------------------+ + # + # An UPDATE message that carries the MP_REACH_NLRI MUST also carry the + # ORIGIN and the AS_PATH attributes (both in EBGP and in IBGP + # exchanges). Moreover, in IBGP exchanges such a message MUST also + # carry the LOCAL_PREF attribute. + # + # An UPDATE message that carries no NLRI, other than the one encoded in + # the MP_REACH_NLRI attribute, SHOULD NOT carry the NEXT_HOP attribute. + # If such a message contains the NEXT_HOP attribute, the BGP speaker + # that receives the message SHOULD ignore this attribute. + + # Parse reachable prefix attribute + elif atype == BGP_PATH_ATTR_MP_REACH_NLRI: # pylint: disable=confusing-consecutive-elif + afinet = None + + # Parse UPDATE message (including AFI/SAFI) + if rib is False: + + # Parse AFI value + afi = struct_unpack(STRUCT_2B, attr_bytes[cur_offset:cur_offset + 2])[0] + cur_offset += 2 + + # Parse SAFI value + safi = attr_bytes[cur_offset] + cur_offset += 1 + + # Check AFI value + if afi != AFI_IPV4 and afi != AFI_IPV6: # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Invalid AFI value ({afi}) in MP_REACH_NLRI attribute', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=attr_bytes) + + # Check SAFI value + if safi != BGP_SAFI_UNICAST and safi != BGP_SAFI_MULTICAST: # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Unsupported SAFI value ({safi}) in MP_REACH_NLRI attribute', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=attr_bytes) + + # Parse protocol + afinet = AF_INET6 if afi == AFI_IPV6 else AF_INET + + # ---------------------------- + # [RFC6396] 4.3.4. RIB Entries + # ---------------------------- + # There is one exception to the encoding of BGP attributes for the BGP + # MP_REACH_NLRI attribute (BGP Type Code 14) [RFC4760]. Since the AFI, + # SAFI, and NLRI information is already encoded in the RIB Entry Header + # or RIB_GENERIC Entry Header, only the Next Hop Address Length and + # Next Hop Address fields are included. The Reserved field is omitted. + # The attribute length is also adjusted to reflect only the length of + # the Next Hop Address Length and Next Hop Address fields. + + # ---------------- + # [RFC6396] Errata + # ---------------- + # The encoding of the MP_REACH_NLRI attribute is not in the form + # according to Section 4.3.4. RIB Entries. + # + # NOTE: This seems to be an IPv6-related issue + # + # The example includes a full MP_REACH_NLRI attribute. This is a common + # issue with TABLE_DUMP_V2 and parsers need to implement a workaround to + # support the broken form. + # + # One way of solving this is to compare the attribute length of + # MP_REACH_NLRI with the first byte of the attribute. If the value of the + # first byte is equal to the attribute lenght - 1 then it is the RFC + # encoding else assume that a full MP_REACH_NLRI attribute was dumped in + # which case the parser needs to skip the first 3 bytes to get to the + # nexthop. + + # Parse RIB entry (with or without AFI/SAFI) + elif attr_bytes[cur_offset] != alen - 1: + cur_offset += 3 + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add ADD-PATH fix + if FTL_ATTR_BGP_STATS_MRT_FIXES >= 0: + fixtype = str(FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI) + if FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN: + fixtype = FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI_STR + stats_record_fix = stats_record[FTL_ATTR_BGP_STATS_MRT_FIXES] + stats_record_fix[fixtype] = stats_record_fix.get(fixtype, 0) + 1 + + # Parse nexthop length + nhlen = attr_bytes[cur_offset] + cur_offset += 1 + + # -------------------------------------------- + # [RFC2545] 3. Constructing the Next Hop field + # -------------------------------------------- + # A BGP speaker shall advertise to its peer in the Network Address of + # Next Hop field the global IPv6 address of the next hop, potentially + # followed by the link-local IPv6 address of the next hop. + # + # NOTE: We use global IPv6 addresses only, but skip link-local addresses + # + # The value of the Length of Next Hop Network Address field on a + # MP_REACH_NLRI attribute shall be set to 16, when only a global + # address is present, or 32 if a link-local address is also included in + # the Next Hop field. + + # Skip link-local address + nhiplen = nhlen if nhlen != 32 else 16 + mp_afinet = AF_INET6 if nhiplen == 16 else AF_INET + + # Parse nexthop protocol + mp_nexthop_proto = None + if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL_HUMAN: + mp_nexthop_proto = IPV6_STR if mp_afinet == AF_INET6 else IPV4_STR + else: + mp_nexthop_proto = IPV6 if mp_afinet == AF_INET6 else IPV4 + if rib is True: + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL] = mp_nexthop_proto + + # Parse nexthop IP + mp_nexthop_ip = None + if FTL_ATTR_BGP_ROUTE_NEXTHOP_IP >= 0: + nhiplen = nhlen if nhlen != 32 else 16 + mp_afinet = AF_INET6 if nhiplen == 16 else AF_INET + mp_nexthop_ip = attr_bytes[cur_offset:cur_offset + nhiplen] + if FTL_ATTR_BGP_ROUTE_NEXTHOP_IP_HUMAN: + mp_nexthop_ip = socket_inet_ntop(mp_afinet, mp_nexthop_ip) + elif mp_afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, mp_nexthop_ip) + mp_nexthop_ip = (net << 64) + host + else: + mp_nexthop_ip = struct_unpack(STRUCT_4B, mp_nexthop_ip)[0] + # NOTE: If there is an MP NEXT_HOP for RIB entries, we overwrite any existing pre-MP NEXT_HOP attribute, + # NOTE: since there is only one prefix per attribute set (provided outside of MP_REACH_NLRI attributes) + if rib is True: + route_record[FTL_ATTR_BGP_ROUTE_NEXTHOP_IP] = mp_nexthop_ip + cur_offset += nhlen + + # Parse prefix + if afinet is not None: + + # Skip reserved byte + cur_offset += 1 + + # Parse NLRI + # NOTE: For UPDATE messages, we do not overwrite the pre-MP NEXT_HOP attribute, but hand over the MP + # NOTE: NEXT_HOP to unpack_mrt_bgp_nlri() instead. + if cur_offset < end_offset: + nlri += unpack_mrt_bgp_nlri(caches, stats_record, attr_bytes[cur_offset:end_offset], afinet, + nexthop_proto=mp_nexthop_proto, nexthop_ip=mp_nexthop_ip, + addpath=addpath) + + ############################# + # MULTI_EXIT_DISC ATTRIBUTE # + ############################# + + # ----------------------------------------------------------------------- + # [RFC4271] 4.3. UPDATE Message Format - d) MULTI_EXIT_DISC (Type Code 4) + # ----------------------------------------------------------------------- + # This is an optional non-transitive attribute that is a + # four-octet unsigned integer. The value of this attribute + # MAY be used by a BGP speaker's Decision Process to + # discriminate among multiple entry points to a neighboring + # autonomous system. + + # Parse MED metric attribute + elif atype == BGP_PATH_ATTR_MULTI_EXIT_DISC: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC >= 0: + route_record[FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC] = struct_unpack(STRUCT_4B, + attr_bytes[cur_offset:end_offset])[0] + + ############################### + # LARGE COMMUNITIES ATTRIBUTE # + ############################### + + # -------------------------------------------- + # [RFC8092} 3. BGP Large Communities Attribute + # -------------------------------------------- + # This document defines the BGP Large Communities attribute as an + # optional transitive path attribute of variable length. All routes + # with the BGP Large Communities attribute belong to the communities + # specified in the attribute. + # + # Each BGP Large Community value is encoded as a 12-octet quantity, as + # follows: + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Global Administrator | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local Data Part 1 | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local Data Part 2 | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse large communities attribute + elif atype == BGP_PATH_ATTR_LARGE_COMMUNITIES: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES >= 0: + if FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES] = tuple( + '{}:{}:{}'.format(*struct_unpack(STRUCT_4B4B4B, attr_bytes[c_offset:c_offset + 12])) + for c_offset in range(cur_offset, end_offset, 12)) + else: + route_record[FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES] = tuple( + ((struct_unpack(STRUCT_8B, attr_bytes[c_offset:c_offset + 8])[0] << 32) + + struct_unpack(STRUCT_4B, attr_bytes[c_offset + 8:c_offset + 12])[0]) + for c_offset in range(cur_offset, end_offset, 12)) + + ############################# + # MP_UNREACH_NLRI ATTRIBUTE # + ############################# + + # ---------------------------------------------------------------------------- + # [RFC4760] 4. Multiprotocol Unreachable NLRI - MP_UNREACH_NLRI (Type Code 15) + # ---------------------------------------------------------------------------- + # This is an optional non-transitive attribute that can be used for the + # purpose of withdrawing multiple unfeasible routes from service. + # + # +---------------------------------------------------------+ + # | Address Family Identifier (2 octets) | + # +---------------------------------------------------------+ + # | Subsequent Address Family Identifier (1 octet) | + # +---------------------------------------------------------+ + # | Withdrawn Routes (variable) | + # +---------------------------------------------------------+ + # + # An UPDATE message that contains the MP_UNREACH_NLRI is not required + # to carry any other path attributes. + + # Parse unreachable prefix attribute + elif atype == BGP_PATH_ATTR_MP_UNREACH_NLRI: # pylint: disable=confusing-consecutive-elif + + # Parse UPDATE message + if rib is False: + + # Parse AFI value + afi = struct_unpack(STRUCT_2B, attr_bytes[cur_offset:cur_offset + 2])[0] + cur_offset += 2 + + # Parse SAFI value + safi = attr_bytes[cur_offset] + cur_offset += 1 + + # Check AFI value + if afi != AFI_IPV4 and afi != AFI_IPV6: # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Invalid AFI value ({afi}) in MP_UNREACH_NLRI attribute', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=attr_bytes) + + # Check SAFI value + if safi != BGP_SAFI_UNICAST and safi != BGP_SAFI_MULTICAST: # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Unsupported SAFI value ({safi}) in MP_UNREACH_NLRI attribute', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=attr_bytes) + + # Parse protocol + afinet = AF_INET6 if afi == AFI_IPV6 else AF_INET + + # Parse NLRI + if cur_offset < end_offset: + nlui += unpack_mrt_bgp_nlri(caches, stats_record, attr_bytes[cur_offset:end_offset], afinet, + addpath=addpath) + + ######################## + # AGGREGATOR ATTRIBUTE # + ######################## + + # ------------------------------------------------------------------ + # [RFC4271] 4.3. UPDATE Message Format - g) AGGREGATOR (Type Code 7) + # ------------------------------------------------------------------ + # AGGREGATOR is an optional transitive attribute of length 6. + # The attribute contains the last AS number that formed the + # aggregate route (encoded as 2 octets), followed by the IP + # address of the BGP speaker that formed the aggregate route + # (encoded as 4 octets). This SHOULD be the same address as + # the one used for the BGP Identifier of the speaker. + + # ----------------------------- + # [RFC6793] Protocol Extensions + # ----------------------------- + # This document defines a new BGP path attribute called + # AS4_AGGREGATOR, which is optional transitive. The AS4_AGGREGATOR + # attribute has the same semantics and the same encoding as the + # AGGREGATOR attribute, except that it carries a four-octet AS number. + + # Parse AS/AS4 aggregator attribute (IPv4 only) + # pylint: disable-next=consider-using-in,confusing-consecutive-elif + elif atype == BGP_PATH_ATTR_AGGREGATOR or atype == BGP_PATH_ATTR_AS4_AGGREGATOR: + if (FTL_ATTR_BGP_ROUTE_ASPATH >= 0 or FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL >= 0 + or FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS >= 0 or FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP >= 0): + + # Sanitize aggregator + # NOTE: Some MRT exporters fail to set correct AS/AS4 aggregator type + aggasbytelen = STRUCT_2B + if end_offset - cur_offset == 8: + aggasbytelen = STRUCT_4B + elif end_offset - cur_offset != 6: + cagglen = end_offset - cur_offset + agglen = 8 if atype == BGP_PATH_ATTR_AS4_AGGREGATOR else 6 + raise FtlMrtDataError(f'Incomplete aggregator attribute ({cagglen}!={agglen}B)', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR, data=attr_bytes) + + # Check for AS length fixes + if aggasbytelen != asbytelen: + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add AS length fix + if FTL_ATTR_BGP_STATS_MRT_FIXES >= 0: + fixtype = str(FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH if rib is False + else FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH) + if FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN: + fixtype = (FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH_STR if rib is False + else FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH_STR) + stats_record_fix = stats_record[FTL_ATTR_BGP_STATS_MRT_FIXES] + stats_record_fix[fixtype] = stats_record_fix.get(fixtype, 0) + 1 + + # Parse aggregator + aggas = struct_unpack(aggasbytelen, attr_bytes[cur_offset:end_offset - 4])[0] + aggip = attr_bytes[end_offset - 4:end_offset] + if atype == BGP_PATH_ATTR_AGGREGATOR: + aggr_as, aggr_ip = aggas, aggip + else: + aggr4_as, aggr4_ip = aggas, aggip + + ################################## + # EXTENDED COMMUNITIES ATTRIBUTE # + ################################## + + # ----------------------------------------------- + # [RFC4360] 2. BGP Extended Communities Attribute + # ----------------------------------------------- + # The Extended Communities Attribute is a transitive optional BGP + # attribute, with the Type Code 16. The attribute consists of a set of + # "extended communities". All routes with the Extended Communities + # attribute belong to the communities listed in the attribute. + # + # Each Extended Community is encoded as an 8-octet quantity, as + # follows: + # + # - Type Field : 1 or 2 octets + # - Value Field : Remaining octets + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type high | Type low(*) | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Value | + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # (*) Present for Extended types only, used for the Value field + # otherwise. + + # Parse extended communities attribute + elif atype == BGP_PATH_ATTR_EXTENDED_COMMUNITIES: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES >= 0: + if FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES] = tuple( + '0x{:02x}:0x{:02x}:{}:{}:{}'.format( + attr_bytes[c_offset], attr_bytes[c_offset + 1], + *struct_unpack(STRUCT_2B2B2B, attr_bytes[c_offset + 2:c_offset + 8]) + ) for c_offset in range(cur_offset, end_offset, 8) + ) + else: + route_record[FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES] = tuple( + struct_unpack(STRUCT_8B, attr_bytes[c_offset:c_offset + 8])[0] + for c_offset in range(cur_offset, end_offset, 8) + ) + + ############################## + # ATOMIC_AGGREGATE ATTRIBUTE # + ############################## + + # ------------------------------------------------------------------------ + # [RFC4271] 4.3. UPDATE Message Format - f) ATOMIC_AGGREGATE (Type Code 6) + # ------------------------------------------------------------------------ + # ATOMIC_AGGREGATE is a well-known discretionary attribute of + # length 0. + + # Parse atomic aggregate attribute + elif atype == BGP_PATH_ATTR_ATOMIC_AGGREGATE: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE >= 0: + route_record[FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE] = True + + ############################## + # ONLY_TO_CUSTOMER ATTRIBUTE # + ############################## + + # --------------------------------------------------------------------------- + # [DRAFT-IETF-IDR-BGP-OPEN-POLICY] 7. BGP Internal Only To Customer attribute + # --------------------------------------------------------------------------- + # The Internal Only To Customer (iOTC) attribute is a new optional, + # non-transitive BGP Path attribute with the Type Code . This + # attribute has zero length as it is used only as a flag. + # + # There are three rules of iOTC attribute usage: + # + # 1. The iOTC attribute MUST be added to all incoming routes if the + # receiver's Role is Customer or Peer; + # + # 2. Routes with the iOTC attribute set MUST NOT be announced by a + # sender whose Role is Customer or Peer; + # + # 3. A sender MUST NOT include this attribute in UPDATE messages if + # its Role is Customer, Provider or Peer. If it is contained in an + # UPDATE message from eBGP speaker and receiver's Role is Customer, + # Provider, Peer or unspecified, then this attribute MUST be + # removed. + # + # These three rules provide mechanism that strongly prevents route leak + # creation by an AS. + + # Parse only-to-customer attribute + elif atype == BGP_PATH_ATTR_ONLY_TO_CUSTOMER: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER >= 0: + route_record[FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER] = True + + ########################### + # ORIGINATOR_ID ATTRIBUTE # + ########################### + + # ----------------------------------------------- + # [RFC4456] 8. Avoiding Routing Information Loops + # ----------------------------------------------- + # ORIGINATOR_ID is a new optional, non-transitive BGP attribute of Type + # code 9. This attribute is 4 bytes long and it will be created by an + # RR in reflecting a route. This attribute will carry the BGP + # Identifier of the originator of the route in the local AS. A BGP + # speaker SHOULD NOT create an ORIGINATOR_ID attribute if one already + # exists. A router that recognizes the ORIGINATOR_ID attribute SHOULD + # ignore a route received with its BGP Identifier as the ORIGINATOR_ID. + + # Parse originator ID attribute (IPv4 only) + elif atype == BGP_PATH_ATTR_ORIGINATOR_ID: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID >= 0: + orig_bgp_id = attr_bytes[cur_offset:end_offset] + if FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID] = socket_inet_ntop(AF_INET, orig_bgp_id) + else: + route_record[FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID] = struct_unpack(STRUCT_4B, orig_bgp_id)[0] + + ########################## + # CLUSTER_LIST ATTRIBUTE # + ########################## + + # -------------------------- + # [RFC4456] 7. Redundant RRs + # -------------------------- + # Usually, a cluster of clients will have a single RR. In that case, + # the cluster will be identified by the BGP Identifier of the RR. + # However, this represents a single point of failure so to make it + # possible to have multiple RRs in the same cluster, all RRs in the + # same cluster can be configured with a 4-byte CLUSTER_ID so that an RR + # can discard routes from other RRs in the same cluster. + + # ----------------------------------------------- + # [RFC4456] 8. Avoiding Routing Information Loops + # ----------------------------------------------- + # CLUSTER_LIST is a new, optional, non-transitive BGP attribute of Type + # code 10. It is a sequence of CLUSTER_ID values representing the + # reflection path that the route has passed. + + # Parse cluster list attribute (IPv4 only) + elif atype == BGP_PATH_ATTR_CLUSTER_LIST: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_CLUSTER_LIST >= 0: + route_record[FTL_ATTR_BGP_ROUTE_CLUSTER_LIST] = tuple( + socket_inet_ntop(AF_INET, attr_bytes[c_offset:c_offset + 4]) + if FTL_ATTR_BGP_ROUTE_CLUSTER_LIST_HUMAN + else struct_unpack(STRUCT_4B, attr_bytes[c_offset:c_offset + 4])[0] + for c_offset in range(cur_offset, end_offset, 4)) + + ######################## + # LOCAL_PREF ATTRIBUTE # + ######################## + + # ------------------------------------------------------------------ + # [RFC4271] 4.3. UPDATE Message Format - e) LOCAL_PREF (Type Code 5) + # ------------------------------------------------------------------ + # LOCAL_PREF is a well-known attribute that is a four-octet + # unsigned integer. A BGP speaker uses it to inform its other + # internal peers of the advertising speaker's degree of + # preference for an advertised route. + + # Parse local-pref attribute + elif atype == BGP_PATH_ATTR_LOCAL_PREF: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_LOCAL_PREF >= 0: + route_record[FTL_ATTR_BGP_ROUTE_LOCAL_PREF] = struct_unpack(STRUCT_4B, + attr_bytes[cur_offset:end_offset])[0] + + ########################## + # AS_PATHLIMIT ATTRIBUTE # + ########################## + + # ----------------------------------------------------------- + # [DRAFT-IETF-IDR-AS-PATHLIMIT] 5. The AS_PATHLIMIT Attribute + # ----------------------------------------------------------- + # The AS_PATHLIMIT attribute is a transitive optional BGP path + # attribute, with Type Code 21. The AS_PATHLIMIT attribute has a fixed + # length of 5 octets. The first octet is an unsigned number that is + # the upper bound on the number of ASes in the AS_PATH attribute of the + # associated paths. One octet suffices because the TTL field of the IP + # header ensures that only one octet's worth of ASes can ever be + # traversed. The second thru fifth octets are the AS number of the AS + # that attached the AS_PATHLIMIT attribute to the NLRI. + + # Parse AS path limit attribute + elif atype == BGP_PATH_ATTR_AS_PATHLIMIT: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT >= 0: + pathlimit = attr_bytes[cur_offset] + pathlimit_origin = struct_unpack(STRUCT_4B, attr_bytes[cur_offset + 1:cur_offset + 5])[0] + route_record[FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT] = (pathlimit_origin, pathlimit) + + ###################### + # ATTR_SET ATTRIBUTE # + ###################### + + # ------------------------------------------ + # [RFC6368] 5. BGP Customer Route Attributes + # ------------------------------------------ + # ATTR_SET is an optional transitive attribute that carries a set of + # BGP path attributes. An attribute set (ATTR_SET) can include any + # BGP attribute that can occur in a BGP UPDATE message, except for + # the MP_REACH and MP_UNREACH attributes. + # + # The ATTR_SET attribute is encoded as follows: + # + # +------------------------------+ + # | Attr Flags (O|T) Code = 128 | + # +------------------------------+ + # | Attr. Length (1 or 2 octets) | + # +------------------------------+ + # | Origin AS (4 octets) | + # +------------------------------+ + # | Path Attributes (variable) | + # +------------------------------+ + # + # The Attribute Flags are encoded according to RFC 4271 [RFC4271]. The + # Extended Length bit determines whether the Attribute Length is one or + # two octets. + + # Parse attribute-set attribute + elif atype == BGP_PATH_ATTR_ATTR_SET: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_ATTR_SET >= 0: + route_record_attrset = list(route_init) + attrset_origin = struct_unpack(STRUCT_4B, attr_bytes[cur_offset:cur_offset + 4])[0] + unpack_mrt_bgp_attr(caches, stats_record, route_init, route_emit, route_record_attrset, + attr_bytes[cur_offset + 4:end_offset], aslen=aslen) + route_record[FTL_ATTR_BGP_ROUTE_ATTR_SET] = (attrset_origin, route_emit(route_record_attrset)) + + ################## + # AIGP ATTRIBUTE # + ################## + + # --------------------------- + # [RFC7311] 3. AIGP Attribute + # --------------------------- + # The AIGP attribute is an optional, non-transitive BGP path attribute. + # The attribute type code for the AIGP attribute is 26. + # + # The value field of the AIGP attribute is defined here to be a set of + # elements encoded as "Type/Length/Value" (i.e., a set of TLVs). Each + # such TLV is encoded as shown in Figure 1. + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Length | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | + # ~ ~ + # | Value | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+.......................... + # + # Figure 1: AIGP TLV + # + # - Type: A single octet encoding the TLV Type. Only type 1, "AIGP + # TLV", is defined in this document. Use of other TLV types is + # outside the scope of this document. + # + # - Length: Two octets encoding the length in octets of the TLV, + # including the Type and Length fields. The length is encoded as an + # unsigned binary integer. (Note that the minimum length is 3, + # indicating that no Value field is present.) + # + # - Value: A field containing zero or more octets. + # + # This document defines only a single such TLV, the "AIGP TLV". The + # AIGP TLV is encoded as follows: + # + # - Type: 1 + # + # - Length: 11 + # + # - Value: Accumulated IGP Metric. + # + # The value field of the AIGP TLV is always 8 octets long, and its + # value is interpreted as an unsigned 64-bit integer. IGP metrics + # are frequently expressed as 4-octet values. By using an 8-octet + # field, we ensure that the AIGP attribute can be used to hold the + # sum of an arbitrary number of 4-octet values. + + # Parse aigp attribute + elif atype == BGP_PATH_ATTR_AIGP: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_ROUTE_AIGP >= 0: + route_record[FTL_ATTR_BGP_ROUTE_AIGP] = tuple( + (attr_bytes[c_offset], struct_unpack(STRUCT_8B, attr_bytes[c_offset + 3: c_offset + 11])[0]) + for c_offset in range(cur_offset, end_offset, 11)) + + # Parse unsupported attributes + elif FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN >= 0: # pylint: disable=confusing-consecutive-elif + aflags, adata = bin(flags) if FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN_HUMAN else flags, None + if cur_offset < end_offset: + adata = attr_bytes[cur_offset:end_offset] + if FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN_HUMAN: + adata = ' '.join('{:02x}'.format(byte) for byte in adata) + else: + adata = base64.b64encode(adata).decode('ascii') + if route_record[FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN] is None: + route_record[FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN] = tuple([(aflags, atype, adata)]) + else: + route_record[FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN] += tuple([(aflags, atype, adata)]) + + ############################# + # ATTRIBUTE POST-PROCESSING # + ############################# + + # -------------------------------------------- + # [RFC6793] 4.2.3. Processing Received Updates + # -------------------------------------------- + # A NEW BGP speaker MUST also be prepared to receive the AS4_AGGREGATOR + # attribute along with the AGGREGATOR attribute from an OLD BGP + # speaker. When both of the attributes are received, if the AS number + # in the AGGREGATOR attribute is not AS_TRANS, then: + # + # - the AS4_AGGREGATOR attribute and the AS4_PATH attribute SHALL + # be ignored, + # + # - the AGGREGATOR attribute SHALL be taken as the information + # about the aggregating node, and + # + # - the AS_PATH attribute SHALL be taken as the AS path + # information. + # + # Otherwise, + # + # - the AGGREGATOR attribute SHALL be ignored, + # + # - the AS4_AGGREGATOR attribute SHALL be taken as the information + # about the aggregating node, and + # + # - the AS path information would need to be constructed, as in all + # other cases. + + # Merge AS/AS4 aggregator attributes + if aggr_as is not None and aggr4_as is not None: + if aggr_as != BGP_PATH_ATTR_AS4_PATH_AS_TRANS: + aggr4_as, aggr4_ip, as4path = None, None, None + else: + aggr_as, aggr_ip = aggr4_as, aggr4_ip + + # Add final aggregator attributes + if FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS >= 0 and aggr_as is not None: + route_record[FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS] = aggr_as + if aggr_ip is not None: + if FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL >= 0: + aggr_proto = IPV4_STR if FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL_HUMAN else IPV4 + route_record[FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL] = aggr_proto + if FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP >= 0: + if FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP] = socket_inet_ntop(AF_INET, aggr_ip) + else: + route_record[FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP] = struct_unpack(STRUCT_4B, aggr_ip)[0] + + # -------------------------------------------- + # [RFC6793] 4.2.3. Processing Received Updates + # -------------------------------------------- + # In order to construct the AS path information, it is necessary to + # first calculate the number of AS numbers in the AS_PATH and AS4_PATH + # attributes using the method specified in Section 9.1.2.2 of [RFC4271] + # and in [RFC5065] for route selection. + # + # If the number of AS numbers in the AS_PATH attribute is less than the + # number of AS numbers in the AS4_PATH attribute, then the AS4_PATH + # attribute SHALL be ignored, and the AS_PATH attribute SHALL be taken + # as the AS path information. + # + # If the number of AS numbers in the AS_PATH attribute is larger than + # or equal to the number of AS numbers in the AS4_PATH attribute, then + # the AS path information SHALL be constructed by taking as many AS + # numbers and path segments as necessary from the leading part of the + # AS_PATH attribute, and then prepending them to the AS4_PATH attribute + # so that the AS path information has a number of AS numbers identical + # to that of the AS_PATH attribute. + # + # NOTE: The following procedure is not implemented (would need a lot of state) + # + # Note that a valid AS_CONFED_SEQUENCE or AS_CONFED_SET path segment + # SHALL be prepended if it is either the leading path segment or is + # adjacent to a path segment that is prepended. + + # Merge AS/AS4 path attributes + if FTL_ATTR_BGP_ROUTE_ASPATH >= 0: + if aspath is not None: + if as4path is not None: + len_aspath, len_as4path = len(aspath), len(as4path) + if len_aspath >= len_as4path: + aspath = aspath[:len_aspath - len_as4path] + as4path + aspath = tuple(aspath) # pylint: disable=redefined-variable-type + + # Add final AS path attribute + # NOTE: RIB entries and announcements may include empty tuples, but should not include None (only for withdraws) + # NOTE: -> this is NOT guaranteed, though (depends on correct usage of BGP attributes) + route_record[FTL_ATTR_BGP_ROUTE_ASPATH] = aspath + + # Return announced/withdrawn prefixes + return nlri, nlui diff --git a/src/ftlbgp/data/mrt/bgp/const.py b/src/ftlbgp/data/mrt/bgp/const.py new file mode 100644 index 0000000..2fa698e --- /dev/null +++ b/src/ftlbgp/data/mrt/bgp/const.py @@ -0,0 +1,264 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +################### +# BGP SAFI VALUES # +################### + +BGP_SAFI_RESERVED = 0 # Defined in RFC4760 +BGP_SAFI_UNICAST = 1 # Defined in RFC4760 +BGP_SAFI_MULTICAST = 2 # Defined in RFC4760 +BGP_SAFI_BOTH = 3 # Deprecated in RFC4760 +BGP_SAFI_MPLS = 4 # Defined in RFC8277 +BGP_SAFI_MCAST_VPN = 5 # Defined in RFC6514 +BGP_SAFI_PSEUDOWIRE = 6 # Defined in RFC7267 +BGP_SAFI_ENCAPSULATION = 7 # Deprecated in RFC9012 +BGP_SAFI_MCAST_VPLS = 8 # Defined in RFC7117 +BGP_SAFI_BGP_SFC = 9 # Defined in RFC9015 +BGP_SAFI_TUNNEL = 64 # Proposed in draft_nalawade_kapoor_tunnel_safi +BGP_SAFI_VPLS = 65 # Defined in RFC4761 +BGP_SAFI_MDT = 66 # Defined in RFC6037 +BGP_SAFI_4OVER6 = 67 # Defined in RFC5747 +BGP_SAFI_6OVER4 = 68 # Proposed by Yong Cui +BGP_SAFI_L1VPN = 69 # Defined in RFC5195 +BGP_SAFI_EVPN = 70 # Defined in RFC7432 +BGP_SAFI_LS = 71 # Defined in RFC9552 +BGP_SAFI_LS_VPN = 72 # Defined in RFC9552 +BGP_SAFI_SR_TE_POLICY = 73 # Proposed in draft_ietf_idr_segment_routing_te_policy +BGP_SAFI_SD_WAN = 74 # Proposed in draft_ietf_idr_sdwan_edge_discovery +BGP_SAFI_NG_POLICY = 75 # Proposed in draft_ietf_idr_rpd +BGP_SAFI_CT = 76 # Proposed in draft_ietf_idr_ct +BGP_SAFI_TUNNEL_FLOWSPEC = 77 # Proposed in draft_ietf_idr_flowspec_nvo3 +BGP_SAFI_MCAST_TREE = 78 # Proposed in draft_ietf_bess_multicast +BGP_SAFI_DPS = 79 # Proposed by https://eos.arista.com/eos_4_26_2f/dps_vpn_scaling_using_bgp +BGP_SAFI_LSVR_SPF = 80 # Proposed in draft_ietf_lsvr_spf +BGP_SAFI_CAR = 83 # Proposed in draft_ietf_idr_car +BGP_SAFI_VPN_CAR = 84 # Proposed in draft_ietf_idr_car +BGP_SAFI_MUP = 85 # Proposed in draft_mpmz_bess_mup_safi +BGP_SAFI_L3VPN = 128 # Defined in RFC4364 +BGP_SAFI_L3VPN_MULTICAST = 129 # Defined in RFC6513 +BGP_SAFI_ROUTE_TARGET = 132 # Defined in RFC4684 +BGP_SAFI_FLOWSPEC = 133 # Defined in RFC8955 +BGP_SAFI_L3VPN_FLOWSPEC = 134 # Defined in RFC8955 +BGP_SAFI_L3VPN_AUTO = 140 # Proposed in draft_ietf_l3vpn_bgpvpn_auto +BGP_SAFI_RESERVED_255 = 255 # Defined in RFC4760 + +################ +# BGP MESSAGES # +################ + +# BGP messages +BGP_BGP4MP_RESERVED = 0 # Defined in RFC4271 +BGP_BGP4MP_OPEN = 1 # Defined in RFC4271 +BGP_BGP4MP_UPDATE = 2 # Defined in RFC4271 +BGP_BGP4MP_NOTIFICATION = 3 # Defined in RFC4271 +BGP_BGP4MP_KEEPALIVE = 4 # Defined in RFC4271 +BGP_BGP4MP_ROUTE_REFRESH = 5 # Defined in RFC2918 + +############## +# BGP STATES # +############## + +# BGP states +BGP_STATE_IDLE = 1 # Defined in RFC6396 +BGP_STATE_CONNECT = 2 # Defined in RFC6396 +BGP_STATE_ACTIVE = 3 # Defined in RFC6396 +BGP_STATE_OPEN_SENT = 4 # Defined in RFC6396 +BGP_STATE_OPEN_CONFIRM = 5 # Defined in RFC6396 +BGP_STATE_ESTABLISHED = 6 # Defined in RFC6396 +BGP_STATE_CLEARING = 7 # Quagga/FRR +BGP_STATE_DELETED = 8 # Quagga/FRR + +############## +# BGP PARAMS # +############## + +# BGP parameters +BGP_PARAMS_RESERVED = 0 # Defined in RFC5492 +BGP_PARAMS_AUTHENTICATION = 1 # Deprecated in RFC5492 +BGP_PARAMS_CAPABILITIES = 2 # Defined in RFC5492 +BGP_PARAMS_EXTENDED_LENGTH = 255 # Defined in RFC9072 + +#################### +# BGP CAPABILITIES # +#################### + +# BGP capabilities +BGP_CAPABILITY_RESERVED_0 = 0 # Defined in RFC5492 +BGP_CAPABILITY_BGP4MP = 1 # Defined in RFC2858 +BGP_CAPABILITY_ROUTE_REFRESH = 2 # Defined in RFC2918 +BGP_CAPABILITY_OUTBOUND_FILTER = 3 # Defined in RFC5291 +BGP_CAPABILITY_MULTIPLE_ROUTES = 4 # Deprecated in RFC8277 +BGP_CAPABILITY_EXTENDED_NEXT_HOP = 5 # Defined in RFC8950 +BGP_CAPABILITY_BGP4MP_ET = 6 # Defined in RFC8654 +BGP_CAPABILITY_BGPSEC = 7 # Defined in RFC8205 +BGP_CAPABILITY_MULTIPLE_LABELS = 8 # Defined in RFC8277 +BGP_CAPABILITY_BGP_ROLE = 9 # Defined in RFC9234 +BGP_CAPABILITY_GRACEFUL_RESTART = 64 # Defined in RFC4724 +BGP_CAPABILITY_AS4 = 65 # Defined in RFC6793 +BGP_CAPABILITY_UNKNOWN_66 = 66 # Deprecated on 2003-03-06 +BGP_CAPABILITY_DYNAMIC = 67 # Proposed in draft-ietf-idr-dynamic-cap +BGP_CAPABILITY_MULTISESSION = 68 # Proposed in draft-ietf-idr-bgp-multisession +BGP_CAPABILITY_ADDPATH = 69 # Defined in RFC7911 +BGP_CAPABILITY_ENHANCED_ROUTE_REFRESH = 70 # Defined in RFC7313 +BGP_CAPABILITY_LLGR = 71 # Proposed in draft-ietf-idr-long-lived-gr +BGP_CAPABILITY_POLICY_DISTRIBUTION = 72 # Proposed in draft-ietf-idr-rpd-04 +BGP_CAPABILITY_FQDN = 73 # Proposed in draft-walton-bgp-hostname-capability +BGP_CAPABILITY_BFD = 74 # Proposed in draft-ietf-idr-bgp-bfd-strict-mode +BGP_CAPABILITY_SOFTWARE_VERSION = 75 # Proposed in draft-abraitis-bgp-version-capability +BGP_CAPABILITY_PRESTD_ROUTE_REFRESH = 128 # Deprecated in RFC8810 +BGP_CAPABILITY_PRESTD_POLICY_DISTRIBUTION = 129 # Deprecated in RFC8810 +BGP_CAPABILITY_PRESTD_OUTBOUND_FILTER = 130 # Deprecated in RFC8810 +BGP_CAPABILITY_PRESTD_MULTISESSION = 131 # Deprecated in RFC8810 +BGP_CAPABILITY_PRESTD_FQDN = 184 # Deprecated in RFC8810 +BGP_CAPABILITY_PRESTD_OPERATIONAL = 185 # Deprecated in RFC8810 +BGP_CAPABILITY_RESERVED_255 = 255 # Defined in RFC8810 + +############## +# BGP ERRORS # +############## + +# BGP error codes +BGP_ERROR_RESERVED = 0 # Defined in RFC4271 +BGP_ERROR_MESSAGE_HEADER = 1 # Defined in RFC4271 +BGP_ERROR_OPEN_MESSAGE = 2 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE = 3 # Defined in RFC4271 +BGP_ERROR_HOLD_TIMER_EXPIRED = 4 # Defined in RFC4271 +BGP_ERROR_FSM = 5 # Defined in RFC4271 +BGP_ERROR_CEASE = 6 # Defined in RFC4271 +BGP_ERROR_ROUTE_REFRESH = 7 # Defined in RFC7313 + +# BGP message header error subcodes +BGP_ERROR_MESSAGE_HEADER_UNSPECIFIC = 0 # Defined in RFC4493 +BGP_ERROR_MESSAGE_HEADER_CONNECTION_NOT_SYNCHRONIZED = 1 # Defined in RFC4271 +BGP_ERROR_MESSAGE_HEADER_BAD_MESSAGE_LENGTH = 2 # Defined in RFC4271 +BGP_ERROR_MESSAGE_HEADER_BAD_MESSAGE_TYPE = 3 # Defined in RFC4271 + +# BGP OPEN message error subcodes +BGP_ERROR_OPEN_MESSAGE_UNSPECIFIC = 0 # Defined in RFC4493 +BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_VERSION_NUMBER = 1 # Defined in RFC4271 +BGP_ERROR_OPEN_MESSAGE_BAD_PEER_AS = 2 # Defined in RFC4271 +BGP_ERROR_OPEN_MESSAGE_BAD_BGP_IDENTIFIER = 3 # Defined in RFC4271 +BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_OPTIONAL_PARAMETER = 4 # Defined in RFC4271 +BGP_ERROR_OPEN_MESSAGE_AUTHENTICATION_FAILURE = 5 # Deprecated in RFC4271 +BGP_ERROR_OPEN_MESSAGE_UNACCEPTABLE_HOLD_TIME = 6 # Defined in RFC4271 +BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_CAPABILITY = 7 # Defined in RFC5492 +BGP_ERROR_OPEN_MESSAGE_DEPRECATED_8 = 8 # Deprecated in RFC9234 +BGP_ERROR_OPEN_MESSAGE_DEPRECATED_9 = 9 # Deprecated in RFC9234 +BGP_ERROR_OPEN_MESSAGE_DEPRECATED_10 = 10 # Deprecated in RFC9234 +BGP_ERROR_OPEN_MESSAGE_ROLE_MISMATCH = 11 # Defined in RFC9234 + +# BGP UPDATE message error subcodes +BGP_ERROR_UPDATE_MESSAGE_UNSPECIFIC = 0 # Defined in RFC4493 +BGP_ERROR_UPDATE_MESSAGE_MALFORMED_ATTRIBUTE_LIST = 1 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_UNRECOGNIZED_WELLKNOWN_ATTRIBUTE = 2 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_MISSING_WELLKNOWN_ATTRIBUTE = 3 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_INVALID_ATTRIBUTE_FLAGS = 4 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_INVALID_ATTRIBUTE_LENGTH = 5 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_INVALID_ORIGIN_ATTRIBUTE = 6 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_AS_ROUTING_LOOP = 7 # Deprecated in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_INVALID_NEXTHOP_ATTRIBUTE = 8 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_INVALID_OPTIONAL_ATTRIBUTE = 9 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_INVALID_NETWORK_FIELD = 10 # Defined in RFC4271 +BGP_ERROR_UPDATE_MESSAGE_MALFORMED_AS_PATH = 11 # Defined in RFC4271 + +# BGP finite state machine error subcodes +BGP_ERROR_FSM_UNSPECIFIED = 0 # Defined in RFC6608 +BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_OPEN_SENT_STATE = 1 # Defined in RFC6608 +BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_OPEN_CONFIRM_STATE = 2 # Defined in RFC6608 +BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_ESTABLISHED_STATE = 3 # Defined in RFC6608 + +# BGP cease error subcodes +BGP_ERROR_CEASE_RESERVED = 0 # Defined in RFC4486 +BGP_ERROR_CEASE_MAX_NUMBER_PREFIXES_REACHED = 1 # Defined in RFC4486 +BGP_ERROR_CEASE_ADMINISTRATIVE_SHUTDOWN = 2 # Defined in RFC9003 +BGP_ERROR_CEASE_PEER_DECONFIGURED = 3 # Defined in RFC4486 +BGP_ERROR_CEASE_ADMINISTRATIVE_RESET = 4 # Defined in RFC9003 +BGP_ERROR_CEASE_CONNECTION_REJECTED = 5 # Defined in RFC4486 +BGP_ERROR_CEASE_OTHER_CONFIGURATION_CHANGE = 6 # Defined in RFC4486 +BGP_ERROR_CEASE_CONNECTION_COLLISION_RESOLUTION = 7 # Defined in RFC4486 +BGP_ERROR_CEASE_OUT_OF_RESOURCES = 8 # Defined in RFC4486 +BGP_ERROR_CEASE_HARD_RESET = 9 # Defined in RFC8538 +BGP_ERROR_CEASE_BFD_DOWN = 10 # Defined in RFC9384 + +# BGP route refresh error subcodes +BGP_ERROR_ROUTE_REFRESH_RESERVED = 0 # Defined in RFC7313 +BGP_ERROR_ROUTE_REFRESH_INVALID_MESSAGE_LENGTH = 1 # Defined in RFC7313 + +####################### +# BGP PATH ATTRIBUTES # +####################### + +# BGP path attribute types +BGP_PATH_ATTR_RESERVED = 0 # Defined in RFC4271 +BGP_PATH_ATTR_ORIGIN = 1 # Defined in RFC4271 +BGP_PATH_ATTR_AS_PATH = 2 # Defined in RFC4271 +BGP_PATH_ATTR_NEXT_HOP = 3 # Defined in RFC4271 +BGP_PATH_ATTR_MULTI_EXIT_DISC = 4 # Defined in RFC4271 +BGP_PATH_ATTR_LOCAL_PREF = 5 # Defined in RFC4271 +BGP_PATH_ATTR_ATOMIC_AGGREGATE = 6 # Defined in RFC4271 +BGP_PATH_ATTR_AGGREGATOR = 7 # Defined in RFC4271 +BGP_PATH_ATTR_COMMUNITIES = 8 # Defined in RFC1997 +BGP_PATH_ATTR_ORIGINATOR_ID = 9 # Defined in RFC4456 +BGP_PATH_ATTR_CLUSTER_LIST = 10 # Defined in RFC4456 +BGP_PATH_ATTR_DPA = 11 # Deprecated in RFC6938 +BGP_PATH_ATTR_ADVERTISER = 12 # Deprecated in RFC6938 +BGP_PATH_ATTR_RCID_CLUSTER_ID = 13 # Deprecated in RFC6938 +BGP_PATH_ATTR_MP_REACH_NLRI = 14 # Defined in RFC4760 +BGP_PATH_ATTR_MP_UNREACH_NLRI = 15 # Defined in RFC4760 +BGP_PATH_ATTR_EXTENDED_COMMUNITIES = 16 # Defined in RFC4360 +BGP_PATH_ATTR_AS4_PATH = 17 # Defined in RFC6793 +BGP_PATH_ATTR_AS4_AGGREGATOR = 18 # Defined in RFC6793 +BGP_PATH_ATTR_SAFI_SPECIFIC = 19 # Proposed in draft-kapoor-nalawade-idr-bgp-ssa +BGP_PATH_ATTR_CONNECTOR = 20 # Deprecated in RFC6037 +BGP_PATH_ATTR_AS_PATHLIMIT = 21 # Proposed in draft-ietf-idr-as-pathlimit +BGP_PATH_ATTR_PMSI_TUNNEL = 22 # Defined in RFC6514 +BGP_PATH_ATTR_TUNNEL_ENCAPSULATION = 23 # Defined in RFC5512 +BGP_PATH_ATTR_TRAFFIC_ENGINEERING = 24 # Defined in RFC5543 +BGP_PATH_ATTR_IPV6_EXTENDED_COMMUNITIES = 25 # Defined in RFC5701 +BGP_PATH_ATTR_AIGP = 26 # Defined in RFC7311 +BGP_PATH_ATTR_PE_DISTINGUISHER_LABELS = 27 # Defined in RFC6514 +BGP_PATH_ATTR_ENTROPY_LABEL_CAPABILITY = 28 # Deprecated in RFC7447 +BGP_PATH_ATTR_LS = 29 # Defined in RFC7752 +BGP_PATH_ATTR_VENDOR_30 = 30 # Deprecated in RFC8093 +BGP_PATH_ATTR_VENDOR_31 = 31 # Deprecated in RFC8093 +BGP_PATH_ATTR_LARGE_COMMUNITIES = 32 # Defined in RFC8092 +BGP_PATH_ATTR_BGPSEC_PATH = 33 # Defined in RFC8205 +BGP_PATH_ATTR_COMMUNITY_CONTAINER = 34 # Proposed in draft-ietf-idr-wide-bgp-communities +BGP_PATH_ATTR_ONLY_TO_CUSTOMER = 35 # Proposed in draft-ietf-idr-bgp-open-policy +BGP_PATH_ATTR_DOMAIN_PATH = 36 # Proposed in draft-ietf-bess-evpn-ipvpn-interworking +BGP_PATH_ATTR_SFP = 37 # Defined in RFC9015 +BGP_PATH_ATTR_BFD_DISCRIMINATOR = 38 # Defined in RFC9026 +BGP_PATH_ATTR_NHC_TMP = 39 # Proposed in draft-ietf-idr-entropy-label +BGP_PATH_ATTR_PREFIX_SID = 40 # Defined in RFC8669 +BGP_PATH_ATTR_ATTR_SET = 128 # Defined in RFC6368 +BGP_PATH_ATTR_VENDOR_129 = 129 # Deprecated in RFC8093 +BGP_PATH_ATTR_VENDOR_241 = 241 # Deprecated in RFC8093 +BGP_PATH_ATTR_VENDOR_242 = 242 # Deprecated in RFC8093 +BGP_PATH_ATTR_VENDOR_243 = 243 # Deprecated in RFC8093 +BGP_PATH_ATTR_RESERVED_FOR_DEV = 255 # Defined in RFC2042 + +# AS path segment types +BGP_PATH_ATTR_AS_PATH_SEGMENT_SET = 1 # Defined in RFC4271 +BGP_PATH_ATTR_AS_PATH_SEGMENT_SEQUENCE = 2 # Defined in RFC4271 +BGP_PATH_ATTR_AS_PATH_SEGMENT_CONFED_SEQUENCE = 3 # Defined in RFC5065 +BGP_PATH_ATTR_AS_PATH_SEGMENT_CONFED_SET = 4 # Defined in RFC5065 + +# AS4 path transition +BGP_PATH_ATTR_AS4_PATH_AS_TRANS = 23456 # Defined in RFC6793 + +# Origin types +BGP_PATH_ATTR_ORIGIN_IGP = 0 # Defined in RFC4271 +BGP_PATH_ATTR_ORIGIN_EGP = 1 # Defined in RFC4271 +BGP_PATH_ATTR_ORIGIN_INCOMPLETE = 2 # Defined in RFC4271 diff --git a/src/ftlbgp/data/mrt/bgp/msg.py b/src/ftlbgp/data/mrt/bgp/msg.py new file mode 100644 index 0000000..4aaf3bd --- /dev/null +++ b/src/ftlbgp/data/mrt/bgp/msg.py @@ -0,0 +1,853 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import base64 + +# Local imports +from .const import BGP_SAFI_UNICAST +from .const import BGP_SAFI_MULTICAST +from .const import BGP_BGP4MP_UPDATE +from .const import BGP_BGP4MP_KEEPALIVE +from .const import BGP_BGP4MP_ROUTE_REFRESH +from .const import BGP_BGP4MP_NOTIFICATION +from .const import BGP_BGP4MP_OPEN +from .const import BGP_PARAMS_CAPABILITIES +from .nlri import unpack_mrt_bgp_nlri +from .attr import unpack_mrt_bgp_attr +from ...util import CACHE_TS +from ...const import IPV4 +from ...const import IPV6 +from ...const import IPV4_STR +from ...const import IPV6_STR +from ...const import AF_INET +from ...const import AF_INET6 +from ...const import AFI_IPV4 +from ...const import AFI_IPV6 +from ...const import STRUCT_2B +from ...const import STRUCT_4B +from ...const import STRUCT_8B8B +from ...const import DATETIME_FORMAT_USEC +from ...const import DATETIME_FORMAT_MIN +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ...const import datetime_utcfromtimestamp +from ....model.attr import FTL_ATTR_BGP_ROUTE_SOURCE +from ....model.attr import FTL_ATTR_BGP_ROUTE_SEQUENCE +from ....model.attr import FTL_ATTR_BGP_ROUTE_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_NEXTHOP_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX +from ....model.attr import FTL_ATTR_BGP_ROUTE_PATH_ID +from ....model.attr import FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_PEER_AS +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_PEER_IP +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_DATA +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_HUMAN +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_HUMAN +from ....model.attr import FTL_ATTR_BGP_NOTIFICATION_DATA_HUMAN +from ....model.attr import FTL_ATTR_BGP_OPEN_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_OPEN_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_OPEN_PEER_AS +from ....model.attr import FTL_ATTR_BGP_OPEN_PEER_IP +from ....model.attr import FTL_ATTR_BGP_OPEN_VERSION +from ....model.attr import FTL_ATTR_BGP_OPEN_MY_AS +from ....model.attr import FTL_ATTR_BGP_OPEN_HOLD_TIME +from ....model.attr import FTL_ATTR_BGP_OPEN_BGP_ID +from ....model.attr import FTL_ATTR_BGP_OPEN_CAPABILITIES +from ....model.attr import FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN +from ....model.attr import FTL_ATTR_BGP_OPEN_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_OPEN_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_OPEN_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_OPEN_BGP_ID_HUMAN +from ....model.attr import FTL_ATTR_BGP_OPEN_CAPABILITIES_HUMAN +from ....model.attr import FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4 +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6 +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4 +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6 +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES_HUMAN +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE_STR +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW_STR +from ....model.const import FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_TO_STR +from ....model.const import FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_TO_STR +from ....model.const import FTL_ATTR_BGP_OPEN_CAPABILITY_TO_STR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL +from ....model.record import FTL_RECORD_BGP_ROUTE +from ....model.record import FTL_RECORD_BGP_KEEP_ALIVE +from ....model.record import FTL_RECORD_BGP_ROUTE_REFRESH +from ....model.record import FTL_RECORD_BGP_NOTIFICATION +from ....model.record import FTL_RECORD_BGP_OPEN +from ....model.record import FTL_RECORD_BGP_STATS +from ....model.error import FtlMrtDataError + + +def unpack_mrt_bgp_msg(caches, stats_record, bgp_error, route_records, keep_alive_records, route_refresh_records, + notification_records, open_records, msg_bytes, mtype, sequence, ts, peer_as, peer_ip, peer_afinet, + aslen=4, addpath=False): + """ Parse MRT BGP/BGP4MP message. + """ + # Prepare byte offset + offset = 0 + + ###################### + # BGP UPDATE MESSAGE # + ###################### + + # ------------------------------------ + # [RFC4271] 4.3. UPDATE Message Format + # ------------------------------------ + # +-----------------------------------------------------+ + # | Withdrawn Routes Length (2 octets) | + # +-----------------------------------------------------+ + # | Withdrawn Routes (variable) | + # +-----------------------------------------------------+ + # | Total Path Attribute Length (2 octets) | + # +-----------------------------------------------------+ + # | Path Attributes (variable) | + # +-----------------------------------------------------+ + # | Network Layer Reachability Information (variable) | + # +-----------------------------------------------------+ + + # Parse UPDATE message + if mtype == BGP_BGP4MP_UPDATE: + + # Access route record template + route_init, route_emit, route_error = route_records + + # Initialize route record reach + route_record_reach = list(route_init) + + # Add source to route record reach + if FTL_ATTR_BGP_ROUTE_SOURCE >= 0: + if FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN: + route_record_reach[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE_STR + else: + route_record_reach[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE + + # Add sequence number to route record reach + if FTL_ATTR_BGP_ROUTE_SEQUENCE >= 0: + route_record_reach[FTL_ATTR_BGP_ROUTE_SEQUENCE] = sequence + + # Add timestamp to route record reach + if FTL_ATTR_BGP_ROUTE_TIMESTAMP >= 0: + ts_route = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_route, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_route = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_route = datetime_utcfromtimestamp(ts_route).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + route_record_reach[FTL_ATTR_BGP_ROUTE_TIMESTAMP] = ts_route + + # Add peer protocol to route record reach + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN: + route_record_reach[FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL] = IPV6_STR if peer_afinet == AF_INET6 else IPV4_STR + else: + route_record_reach[FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL] = IPV6 if peer_afinet == AF_INET6 else IPV4 + + # Add peer AS to route record reach + if FTL_ATTR_BGP_ROUTE_PEER_AS >= 0: + route_record_reach[FTL_ATTR_BGP_ROUTE_PEER_AS] = peer_as + + # Add peer IP to route record reach + if FTL_ATTR_BGP_ROUTE_PEER_IP >= 0: + peer_ip_route = peer_ip + if FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN: + peer_ip_route = socket_inet_ntop(peer_afinet, peer_ip_route) + elif peer_afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip_route) + peer_ip_route = (net << 64) + host + else: + peer_ip_route = struct_unpack(STRUCT_4B, peer_ip_route)[0] + route_record_reach[FTL_ATTR_BGP_ROUTE_PEER_IP] = peer_ip_route + + # Initialize route record unreach + route_record_unreach = list(route_record_reach) + + # Add source to route record unreach + if FTL_ATTR_BGP_ROUTE_SOURCE >= 0: + if FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN: + route_record_unreach[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW_STR + else: + route_record_unreach[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW + + # Initialize announced/withdrawn prefixes + nlri, nlui = tuple(), tuple() + + # Parse UPDATE message data + try: + + # Parse withdrawn routes length + ulen = struct_unpack(STRUCT_2B, msg_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse NLUI + if ulen > 0: + nlui = unpack_mrt_bgp_nlri(caches, stats_record, msg_bytes[offset:offset + ulen], AF_INET, + addpath=addpath) + offset += ulen + + # Parse attributes length + alen = struct_unpack(STRUCT_2B, msg_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse attributes + # NOTE: BGP path attributes are applied to announced routes only (not valid for withdrawn routes) + mp_nlri, mp_nlui = unpack_mrt_bgp_attr(caches, stats_record, route_init, route_emit, route_record_reach, + msg_bytes[offset:offset + alen], aslen=aslen, addpath=addpath) + offset += alen + + # Parse NLRI + rlen = len(msg_bytes) - offset + if rlen > 0: + nlri = unpack_mrt_bgp_nlri(caches, stats_record, msg_bytes[offset:offset + rlen], AF_INET, + addpath=addpath) + offset += rlen + + # Update NLUI/NLRI + nlui += mp_nlui + nlri += mp_nlri + + # Yield (or re-raise) UPDATE message errors + except FtlMrtDataError as error: + yield from route_error(error) + return + + # ----------------------------------- + # [RFC4271] 4.3 UPDATE Message Format + # ----------------------------------- + # + # NOTE: The following procedure should be implemented by clients + # + # An UPDATE message SHOULD NOT include the same address prefix in the + # WITHDRAWN ROUTES and Network Layer Reachability Information fields. + # However, a BGP speaker MUST be able to process UPDATE messages in + # this form. A BGP speaker SHOULD treat an UPDATE message of this form + # as though the WITHDRAWN ROUTES do not contain the address prefix. + + # Finalize route records unreach (withdrawls) + # NOTE: Human-readable conversions are done in unpack_mrt_bgp_attr() and unpack_mrt_bgp_nlri() + for afinet, prefix_proto, prefix, path_id, _, _ in nlui: + + # Add prefix protocol to route record unreach + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + route_record_unreach[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = prefix_proto + + # Add prefix to route record unreach + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + route_record_unreach[FTL_ATTR_BGP_ROUTE_PREFIX] = prefix + + # Add ADD-PATH path ID to route record unreach + if FTL_ATTR_BGP_ROUTE_PATH_ID >= 0: + route_record_unreach[FTL_ATTR_BGP_ROUTE_PATH_ID] = path_id + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Update IPv4 routes announced + if afinet == AF_INET: + if FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4] += 1 + + # Update IPv6 routes announced + elif afinet == AF_INET6: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6] += 1 + + # Yield final route record unreach + if FTL_RECORD_BGP_ROUTE: + yield route_emit(route_record_unreach) + + # Finalize route records reach (announcements) + # NOTE: Human-readable conversions are done in unpack_mrt_bgp_attr() and unpack_mrt_bgp_nlri() + for afinet, prefix_proto, prefix, path_id, nexthop_proto, nexthop_ip in nlri: + + # Initialize route record reach_mp + # NOTE: Route records might contain a pre-MP NEXT_HOP attribute that applies to all pre-MP NLRIs, + # NOTE: so records must be re-initialized for MP NLRI entries + route_record_reach_mp = route_record_reach + + # Add nexthop protocol to route record reach_mp + if FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL >= 0 and nexthop_proto is not None: + route_record_reach_mp = list(route_record_reach) + route_record_reach_mp[FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL] = nexthop_proto + + # Add nexthop IP to route record reach_mp + if FTL_ATTR_BGP_ROUTE_NEXTHOP_IP >= 0: + route_record_reach_mp[FTL_ATTR_BGP_ROUTE_NEXTHOP_IP] = nexthop_ip + + # Add prefix protocol to route record reach_mp + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + route_record_reach_mp[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = prefix_proto + + # Add prefix to route record reach_mp + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + route_record_reach_mp[FTL_ATTR_BGP_ROUTE_PREFIX] = prefix + + # Add ADD-PATH path ID to route record reach_mp + if FTL_ATTR_BGP_ROUTE_PATH_ID >= 0: + route_record_reach_mp[FTL_ATTR_BGP_ROUTE_PATH_ID] = path_id + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Update IPv4 routes withdrawn + if afinet == AF_INET: + if FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4] += 1 + + # Update IPv6 routes withdrawn + elif afinet == AF_INET6: # pylint: disable=confusing-consecutive-elif + if FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6] += 1 + + # Yield final route record reach_mp + if FTL_RECORD_BGP_ROUTE: + yield route_emit(route_record_reach_mp) + + ######################### + # BGP KEEPALIVE MESSAGE # + ######################### + + # --------------------------------------- + # [RFC4271] 4.4. KEEPALIVE Message Format + # --------------------------------------- + # A KEEPALIVE message consists of only the message header and has a + # length of 19 octets. + + # Parse KEEPALIVE message + elif mtype == BGP_BGP4MP_KEEPALIVE: + + # Access keep alive record template + keep_alive_init, keep_alive_emit, _ = keep_alive_records + + # Initialize keep alive record + keep_alive_record = list(keep_alive_init) + + # Add timestamp to keep alive record + if FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP >= 0: + ts_keep_alive = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_keep_alive, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_keep_alive = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_keep_alive = datetime_utcfromtimestamp(ts_keep_alive).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + keep_alive_record[FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP] = ts_keep_alive + + # Add peer protocol to keep alive record + if FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL_HUMAN: + keep_alive_record[FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL] = (IPV6_STR if peer_afinet == AF_INET6 + else IPV4_STR) + else: + keep_alive_record[FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL] = IPV6 if peer_afinet == AF_INET6 else IPV4 + + # Add peer AS to keep alive record + if FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS >= 0: + keep_alive_record[FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS] = peer_as + + # Add peer IP to keep alive record + if FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP >= 0: + peer_ip_keep_alive = peer_ip + if FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP_HUMAN: + peer_ip_keep_alive = socket_inet_ntop(peer_afinet, peer_ip_keep_alive) + elif peer_afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip_keep_alive) + peer_ip_keep_alive = (net << 64) + host + else: + peer_ip_keep_alive = struct_unpack(STRUCT_4B, peer_ip_keep_alive)[0] + keep_alive_record[FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP] = peer_ip_keep_alive + + # Yield final keep alive record + if FTL_RECORD_BGP_KEEP_ALIVE: + yield keep_alive_emit(keep_alive_record) + + ############################# + # BGP ROUTE_REFRESH MESSAGE # + ############################# + + # ---------------------------------- + # [RFC2918] 3. Route-REFRESH Message + # ---------------------------------- + # 0 7 15 23 31 + # +-------+-------+-------+-------+ + # | AFI | Res. | SAFI | + # +-------+-------+-------+-------+ + + # Parse ROUTE_REFRESH message + elif mtype == BGP_BGP4MP_ROUTE_REFRESH: # pylint: disable=confusing-consecutive-elif + + # Access route refresh template + route_refresh_init, route_refresh_emit, _ = route_refresh_records + + # Initialize route refresh record + route_refresh_record = list(route_refresh_init) + + # Add timestamp to route refresh record + if FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP >= 0: + ts_route_refresh = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_route_refresh, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_route_refresh = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_route_refresh = datetime_utcfromtimestamp(ts_route_refresh).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + route_refresh_record[FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP] = ts_route_refresh + + # Add peer protocol to route refresh record + if FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL_HUMAN: + route_refresh_record[FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL] = (IPV6_STR if peer_afinet == AF_INET6 + else IPV4_STR) + else: + route_refresh_record[FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL] = IPV6 if peer_afinet == AF_INET6 else IPV4 + + # Add peer AS to route refresh record + if FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS >= 0: + route_refresh_record[FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS] = peer_as + + # Add peer IP to route refresh record + if FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP >= 0: + peer_ip_route_refresh = peer_ip + if FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP_HUMAN: + peer_ip_route_refresh = socket_inet_ntop(peer_afinet, peer_ip_route_refresh) + elif peer_afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip_route_refresh) + peer_ip_route_refresh = (net << 64) + host + else: + peer_ip_route_refresh = struct_unpack(STRUCT_4B, peer_ip_route_refresh)[0] + route_refresh_record[FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP] = peer_ip_route_refresh + + # Parse AFI value + refresh_afi = struct_unpack(STRUCT_2B, msg_bytes[offset:offset + 2])[0] + offset += 2 + + # Skip reserved byte + offset += 1 + + # Parse SAFI value + refresh_safi = msg_bytes[offset] + + # Check AFI value + if refresh_afi != AFI_IPV4 and refresh_afi != AFI_IPV6: # pylint: disable=consider-using-in + yield from bgp_error(FtlMrtDataError(f'Invalid AFI value ({refresh_afi}) in BGP4MP route-refresh', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=msg_bytes)) + return + + # Check SAFI value + if refresh_safi != BGP_SAFI_UNICAST and refresh_safi != BGP_SAFI_MULTICAST: # pylint: disable=consider-using-in + yield from bgp_error(FtlMrtDataError(f'Unsupported SAFI value ({refresh_safi}) in BGP4MP route-refresh', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=msg_bytes)) + return + + # Parse protocol + if FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL >= 0: + refresh_proto = IPV6 if refresh_afi == AFI_IPV6 else IPV4 + if FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL_HUMAN: + refresh_proto = IPV6_STR if refresh_afi == AFI_IPV6 else IPV4_STR + route_refresh_record[FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL] = refresh_proto + + # Yield final route refresh record + if FTL_RECORD_BGP_ROUTE_REFRESH: + yield route_refresh_emit(route_refresh_record) + + ############################ + # BGP NOTIFICATION MESSAGE # + ############################ + + # ------------------------------------- + # [RFC4271] NOTIFICATION Message Format + # ------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Error code | Error subcode | Data (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse NOTIFICATION message + elif mtype == BGP_BGP4MP_NOTIFICATION: # pylint: disable=confusing-consecutive-elif + + # Access notification template + notification_init, notification_emit, _ = notification_records + + # Initialize notification record + notification_record = list(notification_init) + + # Add timestamp to notification record + if FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP >= 0: + ts_notification = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_notification, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_notification = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_notification = datetime_utcfromtimestamp(ts_notification).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + notification_record[FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP] = ts_notification + + # Add peer protocol to notification record + if FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL_HUMAN: + notification_record[FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL] = (IPV6_STR if peer_afinet == AF_INET6 + else IPV4_STR) + else: + notification_record[FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL] = IPV6 if peer_afinet == AF_INET6 else IPV4 + + # Add peer AS to notification record + if FTL_ATTR_BGP_NOTIFICATION_PEER_AS >= 0: + notification_record[FTL_ATTR_BGP_NOTIFICATION_PEER_AS] = peer_as + + # Add peer IP to notification record + if FTL_ATTR_BGP_NOTIFICATION_PEER_IP >= 0: + peer_ip_notification = peer_ip + if FTL_ATTR_BGP_NOTIFICATION_PEER_IP_HUMAN: + peer_ip_notification = socket_inet_ntop(peer_afinet, peer_ip_notification) + elif peer_afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip_notification) + peer_ip_notification = (net << 64) + host + else: + peer_ip_notification = struct_unpack(STRUCT_4B, peer_ip_notification)[0] + notification_record[FTL_ATTR_BGP_NOTIFICATION_PEER_IP] = peer_ip_notification + + # Parse error code + err_code_int = msg_bytes[offset] + if FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE >= 0: + err_code = err_code_int + if FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_HUMAN: + err_code = FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_TO_STR.get(err_code, str(err_code)) + notification_record[FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE] = err_code + offset += 1 + + # Parse error subcode + if FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE >= 0: + err_scode = msg_bytes[offset] + if FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_HUMAN: + err_scode = FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_TO_STR.get(err_code_int, + dict()).get(err_scode, str(err_scode)) + notification_record[FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE] = err_scode + offset += 1 + + # Parse data + if FTL_ATTR_BGP_NOTIFICATION_DATA >= 0: + if offset < len(msg_bytes): + data = msg_bytes[offset:] + if FTL_ATTR_BGP_NOTIFICATION_DATA_HUMAN: + notification_record[FTL_ATTR_BGP_NOTIFICATION_DATA] = ' '.join('{:02x}'.format(byte) for byte in data) + else: + notification_record[FTL_ATTR_BGP_NOTIFICATION_DATA] = base64.b64encode(data).decode('ascii') + + # Yield final notification record + if FTL_RECORD_BGP_NOTIFICATION: + yield notification_emit(notification_record) + + #################### + # BGP OPEN MESSAGE # + #################### + + # ---------------------------------- + # [RFC4271] 4.2. OPEN Message Format + # ---------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+ + # | Version | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | My Autonomous System | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Hold Time | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | BGP Identifier | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Opt Parm Len | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # | Optional Parameters (variable) | + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # + # 0 1 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-... + # | Parm. Type | Parm. Length | Parameter Value (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-... + # + # Parameter Type is a one octet field that unambiguously + # identifies individual parameters. Parameter Length is a one + # octet field that contains the length of the Parameter Value + # field in octets. Parameter Value is a variable length field + # that is interpreted according to the value of the Parameter + # Type field. + + # Parse OPEN message + elif mtype == BGP_BGP4MP_OPEN: # pylint: disable=confusing-consecutive-elif + + # Access open template + open_init, open_emit, _ = open_records + + # Initialize open record + open_record = list(open_init) + + # Add timestamp to open record + if FTL_ATTR_BGP_OPEN_TIMESTAMP >= 0: + ts_open = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_OPEN_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_open, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_open = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_open = datetime_utcfromtimestamp(ts_open).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + open_record[FTL_ATTR_BGP_OPEN_TIMESTAMP] = ts_open + + # Add peer protocol to open record + if FTL_ATTR_BGP_OPEN_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_OPEN_PEER_PROTOCOL_HUMAN: + open_record[FTL_ATTR_BGP_OPEN_PEER_PROTOCOL] = IPV6_STR if peer_afinet == AF_INET6 else IPV4_STR + else: + open_record[FTL_ATTR_BGP_OPEN_PEER_PROTOCOL] = IPV6 if peer_afinet == AF_INET6 else IPV4 + + # Add peer AS to open record + if FTL_ATTR_BGP_OPEN_PEER_AS >= 0: + open_record[FTL_ATTR_BGP_OPEN_PEER_AS] = peer_as + + # Add peer IP to open record + if FTL_ATTR_BGP_OPEN_PEER_IP >= 0: + peer_ip_open = peer_ip + if FTL_ATTR_BGP_OPEN_PEER_IP_HUMAN: + peer_ip_open = socket_inet_ntop(peer_afinet, peer_ip_open) + elif peer_afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip_open) + peer_ip_open = (net << 64) + host + else: + peer_ip_open = struct_unpack(STRUCT_4B, peer_ip_open)[0] + open_record[FTL_ATTR_BGP_OPEN_PEER_IP] = peer_ip_open + + # Parse version + if FTL_ATTR_BGP_OPEN_VERSION >= 0: + open_record[FTL_ATTR_BGP_OPEN_VERSION] = msg_bytes[offset] + offset += 1 + + # Parse my AS (2 byte only) + if FTL_ATTR_BGP_OPEN_MY_AS >= 0: + open_record[FTL_ATTR_BGP_OPEN_MY_AS] = struct_unpack(STRUCT_2B, msg_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse hold time + if FTL_ATTR_BGP_OPEN_HOLD_TIME >= 0: + open_record[FTL_ATTR_BGP_OPEN_HOLD_TIME] = struct_unpack(STRUCT_2B, msg_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse BGP ID (IPv4 only) + if FTL_ATTR_BGP_OPEN_BGP_ID >= 0: + bgp_id = msg_bytes[offset:offset + 4] + if FTL_ATTR_BGP_OPEN_BGP_ID_HUMAN: + open_record[FTL_ATTR_BGP_OPEN_BGP_ID] = socket_inet_ntop(AF_INET, bgp_id) + else: + open_record[FTL_ATTR_BGP_OPEN_BGP_ID] = struct_unpack(STRUCT_4B, bgp_id)[0] + offset += 4 + + ################### + # BGP OPEN PARAMS # + ################### + + # Parse optional param length + optlen = msg_bytes[offset] + offset += 1 + + # Parse optional params + cur_offset, end_offset = offset, offset + optlen + offset = end_offset + while cur_offset < end_offset: + + # Parse optional parameter type + otype = msg_bytes[cur_offset] + cur_offset += 1 + + # Parse optional parameter length + olen = msg_bytes[cur_offset] + cur_offset += 1 + + # Prepare optional parameter byte offsets + c_offset, e_offset = cur_offset, cur_offset + olen + cur_offset = e_offset + + # --------------------------------------------------------------- + # [RFC3392] 4. Capabilities Optional Parameter (Parameter Type 2) + # --------------------------------------------------------------- + # +------------------------------+ + # | Capability Code (1 octet) | + # +------------------------------+ + # | Capability Length (1 octet) | + # +------------------------------+ + # | Capability Value (variable) | + # +------------------------------+ + + # Parse capabilities optional parameter + if otype == BGP_PARAMS_CAPABILITIES: + if FTL_ATTR_BGP_OPEN_CAPABILITIES >= 0: + + # Parse capabilities + while c_offset < e_offset: + + # Parse capability code + ccode = msg_bytes[c_offset] + ccode_stats = ccode + if FTL_ATTR_BGP_OPEN_CAPABILITIES_HUMAN: + ccode = FTL_ATTR_BGP_OPEN_CAPABILITY_TO_STR.get(ccode, str(ccode)) + c_offset += 1 + + # Parse capability length + clen = msg_bytes[c_offset] + c_offset += 1 + + # Parse capability data + cdata = None + if clen > 0: + cdata = msg_bytes[c_offset:c_offset + clen] + if FTL_ATTR_BGP_OPEN_CAPABILITIES_HUMAN: + cdata = ' '.join('{:02x}'.format(byte) for byte in cdata) + else: + cdata = base64.b64encode(cdata).decode('ascii') + c_offset += clen + + # Add capability code and data + if open_record[FTL_ATTR_BGP_OPEN_CAPABILITIES] is None: + open_record[FTL_ATTR_BGP_OPEN_CAPABILITIES] = tuple([(ccode, cdata)]) + else: + open_record[FTL_ATTR_BGP_OPEN_CAPABILITIES] += tuple([(ccode, cdata)]) + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add BGP message type + if FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES >= 0: + if FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES_HUMAN: + ccode_stats = FTL_ATTR_BGP_OPEN_CAPABILITY_TO_STR.get(ccode_stats, ccode_stats) + ccode_stats = str(ccode_stats) + stats_record_mrt_bgp_cap = stats_record[FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES] + stats_record_mrt_bgp_cap[ccode_stats] = stats_record_mrt_bgp_cap.get(ccode_stats, 0) + 1 + + # Parse unsupported optional parameters + elif FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN >= 0: # pylint: disable=confusing-consecutive-elif + odata = msg_bytes[c_offset:e_offset] + if FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN_HUMAN: + odata = ' '.join('{:02x}'.format(byte) for byte in odata) + else: + odata = base64.b64encode(odata).decode('ascii') + if open_record[FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN] is None: + open_record[FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN] = tuple([(otype, odata)]) + else: + open_record[FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN] += tuple([(otype, odata)]) + + # Yield final open record + if FTL_RECORD_BGP_OPEN: + yield open_emit(open_record) diff --git a/src/ftlbgp/data/mrt/bgp/nlri.py b/src/ftlbgp/data/mrt/bgp/nlri.py new file mode 100644 index 0000000..cb01a2c --- /dev/null +++ b/src/ftlbgp/data/mrt/bgp/nlri.py @@ -0,0 +1,263 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from ...const import IPV4 +from ...const import IPV6 +from ...const import IPV4_STR +from ...const import IPV6_STR +from ...const import AF_INET +from ...const import AF_INET6 +from ...const import STRUCT_4B +from ...const import STRUCT_8B8B +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX +from ....model.attr import FTL_ATTR_BGP_ROUTE_PATH_ID +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_FIXES +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH_STR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX +from ....model.record import FTL_RECORD_BGP_STATS +from ....model.error import FtlMrtDataError + + +def unpack_mrt_bgp_nlri(caches, stats_record, nlri_bytes, afinet, nexthop_proto=None, nexthop_ip=None, addpath=False, + addpath_error=None, relaxed=False): + """ Parse MRT NLRI list. + """ + # Initialize NLRI list and protocol + nlri, prefix_proto = list(), None + + # Parse protocol + iplen, ipbits = (16, 128) if afinet == AF_INET6 else (4, 32) + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN: + prefix_proto = IPV6_STR if afinet == AF_INET6 else IPV4_STR + else: + prefix_proto = IPV6 if afinet == AF_INET6 else IPV4 + + # -------------------------- + # [RFC4760] 5. NLRI Encoding + # -------------------------- + # The Network Layer Reachability information is encoded as one or more + # 2-tuples of the form , whose fields are described + # below: + # + # +---------------------------+ + # | Length (1 octet) | + # +---------------------------+ + # | Prefix (variable) | + # +---------------------------+ + # + # The use and the meaning of these fields are as follows: + # + # a) Length: + # + # The Length field indicates the length, in bits, of the address + # prefix. A length of zero indicates a prefix that matches all (as + # specified by the address family) addresses (with prefix, itself, + # of zero octets). + # + # b) Prefix: + # + # The Prefix field contains an address prefix followed by enough + # trailing bits to make the end of the field fall on an octet + # boundary. Note that the value of trailing bits is irrelevant. + + # Prepare byte offset + offset = 0 + + # Parse NLRI list + try: + nlrilen = len(nlri_bytes) + while offset < nlrilen: + + # Initialize prefix and ADD-PATH path ID + prefix, path_id = None, None + + # ------------------------------------ + # [RFC7911] 3. Extended NLRI Encodings + # ------------------------------------ + # In order to carry the Path Identifier in an UPDATE message, the NLRI + # encoding MUST be extended by prepending the Path Identifier field, + # which is of four octets. + # + # For example, the NLRI encoding specified in [RFC4271] is extended as + # the following: + # + # +--------------------------------+ + # | Path Identifier (4 octets) | + # +--------------------------------+ + # | Length (1 octet) | + # +--------------------------------+ + # | Prefix (variable) | + # +--------------------------------+ + # + # The Path Identifier specified in Section 3 can be used to advertise + # multiple paths for the same address prefix without subsequent + # advertisements replacing the previous ones. Apart from the fact that + # this is now possible, the route advertisement rules of [RFC4271] are + # not changed. In particular, a new advertisement for a given address + # prefix and a given Path Identifier replaces a previous advertisement + # for the same address prefix and Path Identifier. If a BGP speaker + # receives a message to withdraw a prefix with a Path Identifier not + # seen before, it SHOULD silently ignore it. + # + # A BGP speaker SHOULD include the best route [RFC4271] when more than + # one path is advertised to a neighbor, unless it is a path received + # from that neighbor. + # + # As the Path Identifiers are locally assigned, and may or may not be + # persistent across a control plane restart of a BGP speaker. + + # Handle ADD-PATH path ID + if addpath is True: + + # Check remaining bytes + if offset + 5 > nlrilen: + raise ValueError('incomplete ADD-PATH prefix') + + # Parse ADD-PATH path ID + if FTL_ATTR_BGP_ROUTE_PATH_ID >= 0: + path_id = struct_unpack(STRUCT_4B, nlri_bytes[offset:offset + 4])[0] + offset += 4 + + # Parse prefix mask + mask = nlri_bytes[offset] + plen = (mask + 7) // 8 + offset += 1 + + # Check remaining bytes + if offset + plen > nlrilen: + raise ValueError('incomplete prefix') + + # Parse prefix bytes + prefix_bytes = nlri_bytes[offset:offset + plen] + if plen < iplen: + prefix_bytes = b''.join((prefix_bytes, bytearray(iplen - plen))) + offset += plen + + # Check for invalid mask + if mask > ipbits: + raise ValueError(f'invalid {IPV6_STR if afinet == AF_INET6 else IPV4_STR} mask (/{mask})') + + # Parse prefix address + prefix_int = 0 + if afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, prefix_bytes) + prefix_int = (net << 64) + host + else: + prefix_int = struct_unpack(STRUCT_4B, prefix_bytes)[0] + + # Check for invalid address (passes #1 and #2 only) + if relaxed is False: + + # Blacklist default routes + if mask == 0: + raise ValueError(f'invalid {IPV6_STR if afinet == AF_INET6 else IPV4_STR} default route') + + # Blacklist 0.0.0.0/8 and 240.0.0.0/4 [RFC791/RFC1112] + if afinet == AF_INET: + if not 0xffffff < prefix_int < 0xf0000000: + raise ValueError(f'invalid {IPV4_STR} address ({socket_inet_ntop(afinet, prefix_bytes)}/{mask})') + + # Whitelist 2000::/3 [RFC4291] + elif mask < 3 or prefix_int >> 125 != 1: # pylint: disable=confusing-consecutive-elif + + # Whitelist fc00::/7 [RFC8190] + if mask < 7 or prefix_int >> 120 not in {0xfc, 0xfd}: + + # Whitelist ff00::/8 [RFC2373] + if mask < 8 or prefix_int >> 120 != 0xff: + + # Whitelist fe80::/10 [RFC4291] + if mask < 10 or prefix_int >> 118 != 0x3fa: + + # Whitelist 2620:4f:8000::/48 and 64:ff9b:1::/48 [RFC7534/RFC8215] + if mask < 48 or prefix_int >> 80 not in {0x2620004f8000, 0x64ff9b0001}: + + # Whitelist 100::/64 [RFC6666] + if mask < 64 or prefix_int >> 64 != 0x100000000000000: + + # Whitelist 64:ff9b::/96 and ::ffff:0:0/96 (and 0::/96) [RFC6052/RFC4291] + if mask < 96 or prefix_int >> 32 not in {0x64ff9b0000000000000000, 0xffff, 0x0}: + + # Invalid ipv6 prefix + raise ValueError(f'invalid {IPV6_STR} address ' + f'({socket_inet_ntop(afinet, prefix_bytes)}/{mask})') + + # Check for invalid alignment + shift = ipbits - mask + if (prefix_int >> shift) << shift != prefix_int: + raise ValueError(f'misaligned prefix ({socket_inet_ntop(afinet, prefix_bytes)}/{mask})') + + # Parse prefix + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + if FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN: + prefix = f'{socket_inet_ntop(afinet, prefix_bytes)}/{mask}' + else: + prefix = (prefix_int, mask) + + # Update NLRI list + nlri.append((afinet, prefix_proto, prefix, path_id, nexthop_proto, nexthop_ip)) + + # Handle prefix errors + except ValueError as exc: + + # Prepare data error + first_try = False + if addpath_error is None: + first_try = True + addpath_error = FtlMrtDataError('Unable to decode NLRI entries', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX, data=nlri_bytes, + exception=exc) + + # Allow for less strict NLRI parsing + if relaxed is False: + + # Retry with ADD-PATH support + # NOTE: Some MRT exporters include path IDs but fail to set an ADD-PATH message subtype (RFC7911) + if addpath is False: + return unpack_mrt_bgp_nlri(caches, stats_record, nlri_bytes, afinet, nexthop_proto=nexthop_proto, + nexthop_ip=nexthop_ip, addpath=True, addpath_error=addpath_error) + + # Retry with relaxed prefix validation (after ADD-PATH retry failed) + if first_try is False: + return unpack_mrt_bgp_nlri(caches, stats_record, nlri_bytes, afinet, nexthop_proto=nexthop_proto, + nexthop_ip=nexthop_ip, addpath=False, addpath_error=addpath_error, + relaxed=True) + + # All retries failed + raise addpath_error # pylint: disable=raise-missing-from + + # Check for ADD-PATH fixes + if addpath is True and addpath_error is not None: + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add ADD-PATH fix + if FTL_ATTR_BGP_STATS_MRT_FIXES >= 0: + fixtype = str(FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH) + if FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN: + fixtype = FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH_STR + stats_record_fix = stats_record[FTL_ATTR_BGP_STATS_MRT_FIXES] + stats_record_fix[fixtype] = stats_record_fix.get(fixtype, 0) + 1 + + # Return NRLI listTTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH + return tuple(nlri) diff --git a/src/ftlbgp/data/mrt/entry/__init__.py b/src/ftlbgp/data/mrt/entry/__init__.py new file mode 100644 index 0000000..093fb18 --- /dev/null +++ b/src/ftlbgp/data/mrt/entry/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" diff --git a/src/ftlbgp/data/mrt/entry/bgp.py b/src/ftlbgp/data/mrt/entry/bgp.py new file mode 100644 index 0000000..8f14a95 --- /dev/null +++ b/src/ftlbgp/data/mrt/entry/bgp.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from .const import MRT_BGP_UPDATE +from .const import MRT_BGP_OPEN +from .const import MRT_BGP_NOTIFY +from .const import MRT_BGP_KEEPALIVE +from .const import MRT_BGP_STATE_CHANGE +from ..bgp.const import BGP_BGP4MP_UPDATE +from ..bgp.const import BGP_BGP4MP_KEEPALIVE +from ..bgp.const import BGP_BGP4MP_NOTIFICATION +from ..bgp.const import BGP_BGP4MP_OPEN +from ..bgp.msg import unpack_mrt_bgp_msg +from ...util import CACHE_TS +from ...const import IPV4 +from ...const import IPV4_STR +from ...const import AF_INET +from ...const import STRUCT_2B +from ...const import STRUCT_4B +from ...const import DATETIME_FORMAT_USEC +from ...const import DATETIME_FORMAT_MIN +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ...const import datetime_utcfromtimestamp +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE_HUMAN +from ....model.const import FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR +from ....model.record import FTL_RECORD_BGP_STATE_CHANGE + + +def unpack_mrt_entry_bgp(caches, stats_record, bgp_error, state_change_records, route_records, keep_alive_records, + route_refresh_records, notification_records, open_records, entry_bytes, mrt_subtype, sequence, + ts): + """ Parse MRT BGP entry. + """ + ############### + # BGP PARSING # + ############### + + # Prepare byte offset + offset = 0 + + #################### + # BGP STATE_CHANGE # + #################### + + # ------------------------------------------- + # [draft-ietf-grow-mrt-07] BGP_UPDATE Subtype + # ------------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer AS number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer IP address | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Old State | New State | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse state change message + if mrt_subtype == MRT_BGP_STATE_CHANGE: + + # Access state change template + state_change_init, state_change_emit, _ = state_change_records + + # Initialize state change record + state_change_record = list(state_change_init) + + # Add timestamp to state change record + if FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP >= 0: + ts_state_change = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_state_change, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_state_change = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_state_change = datetime_utcfromtimestamp(ts_state_change).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP] = ts_state_change + + # Add peer protocol to state change record + if FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL_HUMAN: + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL] = IPV4_STR + else: + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL] = IPV4 + + # Add peer AS to state change record + if FTL_ATTR_BGP_STATE_CHANGE_PEER_AS >= 0: + peer_as = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_AS] = peer_as + offset += 2 + + # Add peer IP to state change record + if FTL_ATTR_BGP_STATE_CHANGE_PEER_IP >= 0: + peer_ip = entry_bytes[offset:offset + 4] + if FTL_ATTR_BGP_STATE_CHANGE_PEER_IP_HUMAN: + peer_ip = socket_inet_ntop(AF_INET, peer_ip) + else: + peer_ip = struct_unpack(STRUCT_4B, peer_ip)[0] + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_IP] = peer_ip + offset += 4 + + # Add old state to state change record + if FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE >= 0: + old_state = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + if FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE_HUMAN: + old_state = FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR.get(old_state, str(old_state)) + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE] = old_state + offset += 2 + + # Add new state to state change record + if FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE >= 0: + new_state = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + if FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE_HUMAN: + new_state = FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR.get(new_state, str(new_state)) + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE] = new_state + offset += 2 + + # Yield final state change record + if FTL_RECORD_BGP_STATE_CHANGE: + yield state_change_emit(state_change_record) + return + + ############## + # BGP UPDATE # + ############## + + # ------------------------------------------- + # [draft-ietf-grow-mrt-07] BGP_UPDATE Subtype + # ------------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer AS number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer IP address | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local AS number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local IP address | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | BGP UPDATE Contents (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse peer AS + peer_as = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse peer IP + peer_ip = entry_bytes[offset:offset + 4] + offset += 4 + + # Skip local AS + offset += 2 + + # Skip local IP + offset += 4 + + # Parse message type + mtype = None + if mrt_subtype == MRT_BGP_UPDATE: + mtype = BGP_BGP4MP_UPDATE + elif mrt_subtype == MRT_BGP_KEEPALIVE: + mtype = BGP_BGP4MP_KEEPALIVE + elif mrt_subtype == MRT_BGP_NOTIFY: + mtype = BGP_BGP4MP_NOTIFICATION + elif mrt_subtype == MRT_BGP_OPEN: + mtype = BGP_BGP4MP_OPEN + + # Parse message + if mtype is not None: + msg_bytes = entry_bytes[offset:] + yield from unpack_mrt_bgp_msg(caches, stats_record, bgp_error, route_records, keep_alive_records, + route_refresh_records, notification_records, open_records, msg_bytes, mtype, + sequence, ts, peer_as, peer_ip, AF_INET, aslen=2) diff --git a/src/ftlbgp/data/mrt/entry/bgp4mp.py b/src/ftlbgp/data/mrt/entry/bgp4mp.py new file mode 100644 index 0000000..26bda6f --- /dev/null +++ b/src/ftlbgp/data/mrt/entry/bgp4mp.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from .const import MRT_BGP4MP_ENTRY_ANY +from .const import MRT_BGP4MP_ENTRY_AS4_ANY +from .const import MRT_BGP4MP_ENTRY_ADDPATH_ANY +from .const import MRT_BGP4MP_STATE_CHANGE +from .const import MRT_BGP4MP_STATE_CHANGE_AS4 +from ..bgp.msg import unpack_mrt_bgp_msg +from ...util import CACHE_TS +from ...const import IPV4 +from ...const import IPV6 +from ...const import IPV4_STR +from ...const import IPV6_STR +from ...const import AF_INET +from ...const import AF_INET6 +from ...const import AFI_IPV4 +from ...const import AFI_IPV6 +from ...const import STRUCT_2B +from ...const import STRUCT_4B +from ...const import STRUCT_8B8B +from ...const import DATETIME_FORMAT_USEC +from ...const import DATETIME_FORMAT_MIN +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ...const import datetime_utcfromtimestamp +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES_HUMAN +from ....model.const import FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR +from ....model.const import FTL_ATTR_BGP_STATS_BGP_MESSAGE_TYPE_TO_STR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL +from ....model.record import FTL_RECORD_BGP_STATE_CHANGE +from ....model.record import FTL_RECORD_BGP_STATS +from ....model.error import FtlMrtDataError + + +def unpack_mrt_entry_bgp4mp(caches, stats_record, bgp_error, state_change_records, route_records, keep_alive_records, + route_refresh_records, notification_records, open_records, entry_bytes, mrt_subtype, + sequence, ts): + """ Parse MRT BGP4MP entry. + """ + # Check for AS4 support + aslen, asbytelen = 2, STRUCT_2B + if mrt_subtype in MRT_BGP4MP_ENTRY_AS4_ANY: + aslen, asbytelen = 4, STRUCT_4B + + # Check MRT entry subtype + elif mrt_subtype not in MRT_BGP4MP_ENTRY_ANY: + yield from bgp_error(FtlMrtDataError(f'Unknown MRT subtype ({mrt_subtype}) for BGP4MP entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE, data=entry_bytes)) + return + + ################## + # BGP4MP PARSING # + ################## + + # ---------------------------------------------------- + # [RFC8050] 3. MRT Subtypes for Types BGP4MP/BGP4MP_ET + # ---------------------------------------------------- + # This document defines the following new subtypes: + # o BGP4MP_MESSAGE_ADDPATH + # o BGP4MP_MESSAGE_AS4_ADDPATH + # o BGP4MP_MESSAGE_LOCAL_ADDPATH + # o BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH + + # Check for ADD-PATH entry (RFC7911) + addpath = mrt_subtype in MRT_BGP4MP_ENTRY_ADDPATH_ANY + + # Prepare byte offset + offset = 0 + + # ---------------------------------------------------------------------------- + # [RFC6396] 4.4.1. BGP4MP_STATE_CHANGE Subtype / 4.4.2. BGP4MP_MESSAGE Subtype + # ---------------------------------------------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer AS Number | Local AS Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Interface Index | Address Family | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer IP Address (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local IP Address (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # ------------------------------------------------------------------------------------ + # [RFC6396] 4.4.3. BGP4MP_MESSAGE_AS4 Subtype / 4.4.4. BGP4MP_STATE_CHANGE_AS4 Subtype + # ------------------------------------------------------------------------------------ + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer AS Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local AS Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Interface Index | Address Family | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer IP Address (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Local IP Address (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse peer AS + peer_as = struct_unpack(asbytelen, entry_bytes[offset:offset + aslen])[0] + offset += aslen + + # Skip local AS + offset += aslen + + # Skip interface index + offset += 2 + + # Parse AFI value + afi = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Check AFI value + if afi != AFI_IPV4 and afi != AFI_IPV6: # pylint: disable=consider-using-in + yield from bgp_error(FtlMrtDataError(f'Unknown AFI value ({afi}) in BGP4MP entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=entry_bytes)) + return + + # Parse peer IP + afinet, iplen = (AF_INET6, 16) if afi == AFI_IPV6 else (AF_INET, 4) + peer_ip = entry_bytes[offset:offset + iplen] + offset += iplen + + # Skip local IP + offset += iplen + + ####################### + # BGP4MP STATE_CHANGE # + ####################### + + # ------------------------------------------------------------------------------------ + # [RFC6396] 4.4.1. BGP4MP_STATE_CHANGE Subtype / 4.4.4 BGP4MP_STATE_CHANGE_AS4 Subtype + # ------------------------------------------------------------------------------------ + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Old State | New State | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Handle state change entry + # pylint: disable-next=consider-using-in + if mrt_subtype == MRT_BGP4MP_STATE_CHANGE or mrt_subtype == MRT_BGP4MP_STATE_CHANGE_AS4: + + # Access state change template + state_change_init, state_change_emit, _ = state_change_records + + # Initialize state change record + state_change_record = list(state_change_init) + + # Add timestamp to state change record + if FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP >= 0: + ts_state_change = ts + + # Cache date string for minute-based timestamp + if FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP_HUMAN: + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts_state_change, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts_state_change = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts_state_change = datetime_utcfromtimestamp(ts_state_change).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP] = ts_state_change + + # Add peer protocol to state change record + if FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL_HUMAN: + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL] = (IPV6_STR if afinet == AF_INET6 + else IPV4_STR) + else: + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL] = IPV6 if afinet == AF_INET6 else IPV4 + + # Add peer AS to state change record + if FTL_ATTR_BGP_STATE_CHANGE_PEER_AS >= 0: + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_AS] = peer_as + + # Add peer IP to state change record + if FTL_ATTR_BGP_STATE_CHANGE_PEER_IP >= 0: + peer_ip_state_change = peer_ip + if FTL_ATTR_BGP_STATE_CHANGE_PEER_IP_HUMAN: + peer_ip_state_change = socket_inet_ntop(afinet, peer_ip_state_change) + elif afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip_state_change) + peer_ip_state_change = (net << 64) + host + else: + peer_ip_state_change = struct_unpack(STRUCT_4B, peer_ip_state_change)[0] + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_PEER_IP] = peer_ip_state_change + + # Parse old state + if FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE >= 0: + old_state = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + if FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE_HUMAN: + old_state = FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR.get(old_state, str(old_state)) + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE] = old_state + offset += 2 + + # Parse new state + if FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE >= 0: + new_state = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + if FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE_HUMAN: + new_state = FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR.get(new_state, str(new_state)) + state_change_record[FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE] = new_state + offset += 2 + + # Yield final state change record + if FTL_RECORD_BGP_STATE_CHANGE: + yield state_change_emit(state_change_record) + return + + ################## + # BGP4MP MESSAGE # + ################## + + # --------------------------------------------------------------------------- + # [RFC6396] 4.4.2. BGP4MP_MESSAGE Subtype / 4.4.3. BGP4MP_MESSAGE_AS4 Subtype + # --------------------------------------------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | BGP Message... (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # ----------------------------------- + # [RFC4271] 4.1 Message Header Format + # ----------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | | + # + + + # | | + # + + + # | Marker | + # + + + # | | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Length | Type | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Skip marker + offset += 16 + + # Skip message length + offset += 2 + + # Parse message type + mtype = entry_bytes[offset] + offset += 1 + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add BGP message type + if FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES >= 0: + bgptype = mtype + if FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES_HUMAN: + bgptype = FTL_ATTR_BGP_STATS_BGP_MESSAGE_TYPE_TO_STR.get(mtype, mtype) + bgptype = str(bgptype) + stats_record_mrt_bgp_msg = stats_record[FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES] + stats_record_mrt_bgp_msg[bgptype] = stats_record_mrt_bgp_msg.get(bgptype, 0) + 1 + + # Parse message + msg_bytes = entry_bytes[offset:] + yield from unpack_mrt_bgp_msg(caches, stats_record, bgp_error, route_records, keep_alive_records, + route_refresh_records, notification_records, open_records, msg_bytes, mtype, sequence, + ts, peer_as, peer_ip, afinet, aslen=aslen, addpath=addpath) diff --git a/src/ftlbgp/data/mrt/entry/const.py b/src/ftlbgp/data/mrt/entry/const.py new file mode 100644 index 0000000..f4020c9 --- /dev/null +++ b/src/ftlbgp/data/mrt/entry/const.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +################# +# MRT CONSTANTS # +################# + +# Byte sizes +MRT_HEADER_BYTES = 12 # Defined in RFC6396 +MRT_DATA_BYTES = 65535 # Defined in RFC8654 +MRT_DATA_BYTES *= 16 # RIB entries might be larger + +# MRT types +MRT_NULL = 0 # Deprecated in RFC6396 +MRT_START = 1 # Deprecated in RFC6396 +MRT_DIE = 2 # Deprecated in RFC6396 +MRT_I_AM_DEAD = 3 # Deprecated in RFC6396 +MRT_PEER_DOWN = 4 # Deprecated in RFC6396 +MRT_BGP = 5 # Deprecated in RFC6396 +MRT_RIP = 6 # Deprecated in RFC6396 +MRT_IDRP = 7 # Deprecated in RFC6396 +MRT_RIPNG = 8 # Deprecated in RFC6396 +MRT_BGP4PLUS = 9 # Deprecated in RFC6396 +MRT_BGP4PLUS_01 = 10 # Deprecated in RFC6396 +MRT_OSPFV2 = 11 # Defined in RFC6396 +MRT_TABLE_DUMP = 12 # Defined in RFC6396 +MRT_TABLE_DUMP_V2 = 13 # Defined in RFC6396 +MRT_BGP4MP = 16 # Defined in RFC6396 +MRT_BGP4MP_ET = 17 # Defined in RFC6396 +MRT_ISIS = 32 # Defined in RFC6396 +MRT_ISIS_ET = 33 # Defined in RFC6396 +MRT_OSPFV3 = 48 # Defined in RFC6396 +MRT_OSPFV3_ET = 49 # Defined in RFC6396 + +# BGP subtypes (obsolete) +MRT_BGP_NULL = 0 # Deprecated in RFC6396 +MRT_BGP_UPDATE = 1 # Deprecated in RFC6396 +MRT_BGP_PREF_UPDATE = 2 # Deprecated in RFC6396 +MRT_BGP_STATE_CHANGE = 3 # Deprecated in RFC6396 +MRT_BGP_SYNC = 4 # Deprecated in RFC6396 +MRT_BGP_OPEN = 5 # Deprecated in RFC6396 +MRT_BGP_NOTIFY = 6 # Deprecated in RFC6396 +MRT_BGP_KEEPALIVE = 7 # Deprecated in RFC6396 + +# BGP4MP/BGP4MP_ET subtypes +MRT_BGP4MP_STATE_CHANGE = 0 # Defined in RFC6396 +MRT_BGP4MP_MESSAGE = 1 # Defined in RFC6396 +MRT_BGP4MP_ENTRY = 2 # Deprecated in RFC6396 +MRT_BGP4MP_SNAPSHOT = 3 # Deprecated in RFC6396 +MRT_BGP4MP_MESSAGE_AS4 = 4 # Defined in RFC6396 +MRT_BGP4MP_STATE_CHANGE_AS4 = 5 # Defined in RFC6396 +MRT_BGP4MP_MESSAGE_LOCAL = 6 # Defined in RFC6396 +MRT_BGP4MP_MESSAGE_AS4_LOCAL = 7 # Defined in RFC6396 +MRT_BGP4MP_MESSAGE_ADDPATH = 8 # Defined in RFC8050 +MRT_BGP4MP_MESSAGE_AS4_ADDPATH = 9 # Defined in RFC8050 +MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH = 10 # Defined in RFC8050 +MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH = 11 # Defined in RFC8050 + +# TABLE_DUMP_V2 subtypes +MRT_TABLE_DUMP_V2_PEER_INDEX_TABLE = 1 # Defined in RFC6396 +MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST = 2 # Defined in RFC6396 +MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST = 3 # Defined in RFC6396 +MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST = 4 # Defined in RFC6396 +MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST = 5 # Defined in RFC6396 +MRT_TABLE_DUMP_V2_RIB_GENERIC = 6 # Defined in RFC6396 +MRT_TABLE_DUMP_V2_GEO_PEER_TABLE = 7 # Defined in RFC6397 +MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH = 8 # Defined in RFC8050 +MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH = 9 # Defined in RFC8050 +MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH = 10 # Defined in RFC8050 +MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH = 11 # Defined in RFC8050 +MRT_TABLE_DUMP_V2_RIB_GENERIC_ADDPATH = 12 # Defined in RFC8050 + +##################### +# MRT CONSTANT SETS # +##################### + +# Valid MRT types +MRT_VALID = { + MRT_NULL, + MRT_START, + MRT_DIE, + MRT_I_AM_DEAD, + MRT_PEER_DOWN, + MRT_BGP, + MRT_RIP, + MRT_IDRP, + MRT_RIPNG, + MRT_BGP4PLUS, + MRT_BGP4PLUS_01, + MRT_OSPFV2, + MRT_TABLE_DUMP, + MRT_TABLE_DUMP_V2, + MRT_BGP4MP, + MRT_BGP4MP_ET, + MRT_ISIS, + MRT_ISIS_ET, + MRT_OSPFV3, + MRT_OSPFV3_ET, +} + +# BGP4MP/BGP4MP_ET subtypes (2-byte ASN) +MRT_BGP4MP_ENTRY_ANY = { + MRT_BGP4MP_STATE_CHANGE, + MRT_BGP4MP_MESSAGE, + MRT_BGP4MP_MESSAGE_LOCAL, + MRT_BGP4MP_MESSAGE_ADDPATH, + MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH, +} + +# BGP4MP/BGP4MP_ET subtypes (4-byte ASN) +MRT_BGP4MP_ENTRY_AS4_ANY = { + MRT_BGP4MP_STATE_CHANGE_AS4, + MRT_BGP4MP_MESSAGE_AS4, + MRT_BGP4MP_MESSAGE_AS4_LOCAL, + MRT_BGP4MP_MESSAGE_AS4_ADDPATH, + MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH, +} + +# BGP4MP/BGP4MP_ET subtypes (ADD-PATH) +MRT_BGP4MP_ENTRY_ADDPATH_ANY = { + MRT_BGP4MP_MESSAGE_ADDPATH, + MRT_BGP4MP_MESSAGE_AS4_ADDPATH, + MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH, + MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH, +} + +# TABLE_DUMP_V2 subtypes (ADD-PATH) +MRT_TABLE_DUMP_V2_RIB_ADDPATH_ANY = { + MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH, + MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH, + MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH, + MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH, + MRT_TABLE_DUMP_V2_RIB_GENERIC_ADDPATH, +} + +# TABLE_DUMP_V2 subtypes (IPv4) +MRT_TABLE_DUMP_V2_RIB_IPV4_ANY = { + MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST, + MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST, + MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH, + MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH, +} + +# TABLE_DUMP_V2 subtypes (IPv6) +MRT_TABLE_DUMP_V2_RIB_IPV6_ANY = { + MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST, + MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST, + MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH, + MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH, +} + +# TABLE_DUMP_V2 subtypes (generic) +MRT_TABLE_DUMP_V2_RIB_GENERIC_ANY = { + MRT_TABLE_DUMP_V2_RIB_GENERIC, + MRT_TABLE_DUMP_V2_RIB_GENERIC_ADDPATH, +} + +# TABLE_DUMP_V2 subtypes (AFI/SAFI-specific) +MRT_TABLE_DUMP_V2_RIB_SPECIFIC_ANY = ( + MRT_TABLE_DUMP_V2_RIB_IPV4_ANY | + MRT_TABLE_DUMP_V2_RIB_IPV6_ANY +) diff --git a/src/ftlbgp/data/mrt/entry/td.py b/src/ftlbgp/data/mrt/entry/td.py new file mode 100644 index 0000000..494ded2 --- /dev/null +++ b/src/ftlbgp/data/mrt/entry/td.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from ..bgp.attr import unpack_mrt_bgp_attr +from ...util import CACHE_TS +from ...const import IPV4 +from ...const import IPV6 +from ...const import IPV4_STR +from ...const import IPV6_STR +from ...const import AF_INET +from ...const import AF_INET6 +from ...const import AFI_IPV4 +from ...const import AFI_IPV6 +from ...const import STRUCT_2B +from ...const import STRUCT_4B +from ...const import STRUCT_8B8B +from ...const import DATETIME_FORMAT_USEC +from ...const import DATETIME_FORMAT_MIN +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ...const import datetime_utcfromtimestamp +from ....model.attr import FTL_ATTR_BGP_ROUTE_SOURCE +from ....model.attr import FTL_ATTR_BGP_ROUTE_SEQUENCE +from ....model.attr import FTL_ATTR_BGP_ROUTE_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX +from ....model.attr import FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_RIB +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL +from ....model.record import FTL_RECORD_BGP_ROUTE +from ....model.record import FTL_RECORD_BGP_STATS +from ....model.error import FtlMrtDataError + + +def unpack_mrt_entry_td_rib(caches, stats_record, route_records, entry_bytes, mrt_subtype): + """ Parse MRT table dump entry. + """ + # ------------------------------ + # [RFC6396] 4.2. TABLE_DUMP Type + # ------------------------------ + # The Subtype field is used to encode whether the RIB entry contains + # IPv4 or IPv6 [RFC2460] addresses. There are two possible values for + # the Subtype as shown below. + # + # 1 AFI_IPv4 + # 2 AFI_IPv6 + # + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | View Number | Sequence Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Prefix (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Prefix Length | Status | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Originated Time | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer IP Address (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer AS | Attribute Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | BGP Attribute... (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Check AFI value + if (mrt_subtype != AFI_IPV4 and mrt_subtype != AFI_IPV6): # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Unknown AFI value ({mrt_subtype}) in table dump entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=entry_bytes) + + # Access route record template + route_init, route_emit, route_error = route_records + + # Initialize route record + route_record = list(route_init) + + # Add source to route record + if FTL_ATTR_BGP_ROUTE_SOURCE >= 0: + if FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR + else: + route_record[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_RIB + + # Add prefix protocol to route record + afinet = AF_INET6 if mrt_subtype == AFI_IPV6 else AF_INET + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = IPV6_STR if afinet == AF_INET6 else IPV4_STR + else: + route_record[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = IPV6 if afinet == AF_INET6 else IPV4 + + # Prepare byte offset + offset = 0 + + # Skip view number + offset += 2 + + # Add sequence number to route record + if FTL_ATTR_BGP_ROUTE_SEQUENCE >= 0: + route_record[FTL_ATTR_BGP_ROUTE_SEQUENCE] = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Add prefix protocol to route record + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = IPV6_STR if afinet == AF_INET6 else IPV4_STR + else: + route_record[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = IPV6 if afinet == AF_INET6 else IPV4 + + # Parse prefix bytes + preflen = 16 if afinet == AF_INET6 else 4 + prefix = entry_bytes[offset:offset + preflen] + if FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN: + prefix = socket_inet_ntop(afinet, prefix) + elif afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, prefix) + prefix = (net << 64) + host + else: + prefix = struct_unpack(STRUCT_4B, prefix)[0] + offset += preflen + + # Parse prefix mask + mask = entry_bytes[offset] + offset += 1 + + # Add prefix to route record + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + if FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_PREFIX] = f'{prefix}/{mask}' + else: + route_record[FTL_ATTR_BGP_ROUTE_PREFIX] = (prefix, mask) + + # Skip status + offset += 1 + + # Parse timestamp + if FTL_ATTR_BGP_ROUTE_TIMESTAMP >= 0: + ts = float(struct_unpack(STRUCT_4B, entry_bytes[offset:offset + 4])[0]) + if FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN: + + # Cache date string for minute-based timestamp + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp to route record + route_record[FTL_ATTR_BGP_ROUTE_TIMESTAMP] = ts + offset += 4 + + # Add peer protocol to route record + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL >= 0: + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN: + route_record[FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL] = (IPV6_STR if afinet == AF_INET6 else IPV4_STR) + else: + route_record[FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL] = IPV6 if afinet == AF_INET6 else IPV4 + + # Add peer IP to route record + iplen = 16 if afinet == AF_INET6 else 4 + peer_ip = entry_bytes[offset:offset + iplen] + if FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN: + peer_ip = socket_inet_ntop(afinet, peer_ip) + elif afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip) + peer_ip = (net << 64) + host + else: + peer_ip = struct_unpack(STRUCT_4B, peer_ip)[0] + if FTL_ATTR_BGP_ROUTE_PEER_IP >= 0: + route_record[FTL_ATTR_BGP_ROUTE_PEER_IP] = peer_ip + offset += iplen + + # Add peer AS to route record + peer_as = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + if FTL_ATTR_BGP_ROUTE_PEER_AS >= 0: + route_record[FTL_ATTR_BGP_ROUTE_PEER_AS] = peer_as + offset += 2 + + # Parse attributes length + alen = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse attributes + attr_bytes = entry_bytes[offset:offset + alen] + offset += alen + try: + unpack_mrt_bgp_attr(caches, stats_record, route_init, route_emit, route_record, attr_bytes, aslen=2, rib=True) + + # Yield (or re-raise) attribute errors + except FtlMrtDataError as error: + yield from route_error(error) + return + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Update IPv4 RIB routes + if afinet == AF_INET: + if FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4] += 1 + + # Update IPv6 RIB routes + if afinet == AF_INET6: + if FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6] += 1 + + # Yield final route record + if FTL_RECORD_BGP_ROUTE: + yield route_emit(route_record) diff --git a/src/ftlbgp/data/mrt/entry/tdv2.py b/src/ftlbgp/data/mrt/entry/tdv2.py new file mode 100644 index 0000000..319bb38 --- /dev/null +++ b/src/ftlbgp/data/mrt/entry/tdv2.py @@ -0,0 +1,516 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from .const import MRT_TABLE_DUMP_V2_RIB_SPECIFIC_ANY +from .const import MRT_TABLE_DUMP_V2_RIB_GENERIC_ANY +from .const import MRT_TABLE_DUMP_V2_RIB_IPV6_ANY +from ..bgp.attr import unpack_mrt_bgp_attr +from ..bgp.nlri import unpack_mrt_bgp_nlri +from ..bgp.const import BGP_SAFI_UNICAST +from ..bgp.const import BGP_SAFI_MULTICAST +from ...util import CACHE_TS +from ...const import IPV4 +from ...const import IPV6 +from ...const import IPV4_STR +from ...const import IPV6_STR +from ...const import AF_INET +from ...const import AF_INET6 +from ...const import AFI_IPV4 +from ...const import AFI_IPV6 +from ...const import STRUCT_2B +from ...const import STRUCT_4B +from ...const import STRUCT_8B8B +from ...const import UTF8 +from ...const import DATETIME_FORMAT_USEC +from ...const import DATETIME_FORMAT_MIN +from ...const import struct_unpack +from ...const import socket_inet_ntop +from ...const import datetime_utcfromtimestamp +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID_HUMAN +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID_HUMAN +from ....model.attr import FTL_ATTR_BGP_PEER_TABLE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_SOURCE +from ....model.attr import FTL_ATTR_BGP_ROUTE_SEQUENCE +from ....model.attr import FTL_ATTR_BGP_ROUTE_TIMESTAMP +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_BGP_ID +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_AS +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_IP +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL +from ....model.attr import FTL_ATTR_BGP_ROUTE_PREFIX +from ....model.attr import FTL_ATTR_BGP_ROUTE_PATH_ID +from ....model.attr import FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_BGP_ID_HUMAN +from ....model.attr import FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 +from ....model.attr import FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_FIXES +from ....model.attr import FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_RIB +from ....model.const import FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH +from ....model.const import FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH_STR +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL +from ....model.const import FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX +from ....model.record import FTL_RECORD_BGP_PEER_TABLE +from ....model.record import FTL_RECORD_BGP_ROUTE +from ....model.record import FTL_RECORD_BGP_STATS +from ....model.error import FtlMrtHeaderError +from ....model.error import FtlMrtDataError + + +# pylint: disable-next=unused-argument +def unpack_mrt_entry_tdv2_index(caches, stats_record, peer_table_records, entry_bytes, peer_table): + """ Parse MRT peer index table. + """ + # Access peer table record template + peer_table_init, peer_table_emit, _ = peer_table_records + + # Initialize peer table record view + peer_table_record_view = list(peer_table_init) + + # ----------------------------------------- + # [RFC6396] 4.3.1. PEER_INDEX_TABLE Subtype + # ----------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Collector BGP ID | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | View Name Length | View Name (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer Count | Peer Entries (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Prepare byte offset + offset = 0 + + # Parse collector BGP ID (IPv4 only) + if FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID >= 0: + col_bgp_id = entry_bytes[offset:offset + 4] + if FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID_HUMAN: + peer_table_record_view[FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID] = socket_inet_ntop(AF_INET, col_bgp_id) + else: + peer_table_record_view[FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID] = struct_unpack(STRUCT_4B, col_bgp_id)[0] + offset += 4 + + # Parse view name length + view_name_length = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse view name + if view_name_length > 0: + if FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME >= 0: + view_name = bytes(entry_bytes[offset:offset + view_name_length]).decode(UTF8) + peer_table_record_view[FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME] = view_name + offset += view_name_length + + # Parse peer count + peer_count = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse peer entries + for _ in range(peer_count): + + # Initialize peer table record + peer_table_record = list(peer_table_record_view) + + # ----------------------------------------- + # [RFC6396] 4.3.1. PEER_INDEX_TABLE Subtype + # ----------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer Type | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer BGP ID | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer IP Address (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer AS (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse peer type + peer_type = entry_bytes[offset] + offset += 1 + + # Parse peer protocol and AS length + afinet, iplen = (AF_INET6, 16) if peer_type & 1 else (AF_INET, 4) + asfmt, aslen = (STRUCT_4B, 4) if peer_type & 2 else (STRUCT_2B, 2) + + # Add peer protocol to peer table record + peer_proto_int, peer_proto_str = (IPV6, IPV6_STR) if afinet == AF_INET6 else (IPV4, IPV4_STR) + if FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL >= 0: + peer_proto = peer_proto_str if FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL_HUMAN else peer_proto_int + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL] = peer_proto + + # Add peer BGP ID to peer table record + peer_bgp_id = entry_bytes[offset:offset + 4] + peer_bgp_id_str = socket_inet_ntop(AF_INET, peer_bgp_id) + peer_bgp_id_int = struct_unpack(STRUCT_4B, peer_bgp_id)[0] + if FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID >= 0: + peer_bgp_id = peer_bgp_id_str if FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID_HUMAN else peer_bgp_id_int + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID] = peer_bgp_id + offset += 4 + + # Add peer IP to peer table record + peer_ip = entry_bytes[offset:offset + iplen] + peer_ip_str = socket_inet_ntop(afinet, peer_ip) + if afinet == AF_INET6: + net, host = struct_unpack(STRUCT_8B8B, peer_ip) + peer_ip_int = (net << 64) + host + else: + peer_ip_int = struct_unpack(STRUCT_4B, peer_ip)[0] + if FTL_ATTR_BGP_PEER_TABLE_PEER_IP >= 0: + peer_ip = peer_ip_str if FTL_ATTR_BGP_PEER_TABLE_PEER_IP_HUMAN else peer_ip_int + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_IP] = peer_ip + offset += iplen + + # Add peer AS to peer table record + peer_as = struct_unpack(asfmt, entry_bytes[offset:offset + aslen])[0] + if FTL_ATTR_BGP_PEER_TABLE_PEER_AS >= 0: + peer_table_record[FTL_ATTR_BGP_PEER_TABLE_PEER_AS] = peer_as + offset += aslen + + # Update peer table + peer_proto_route = peer_proto_str if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN else peer_proto_int + peer_ip_route = peer_ip_str if FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN else peer_ip_int + peer_bgp_id_route = peer_bgp_id_str if FTL_ATTR_BGP_ROUTE_PEER_BGP_ID_HUMAN else peer_bgp_id_int + peer_table.append((peer_proto_route, peer_bgp_id_route, peer_as, peer_ip_route)) + + # Yield peer table record + if FTL_RECORD_BGP_PEER_TABLE: + yield peer_table_emit(peer_table_record) + + +def unpack_mrt_entry_tdv2_rib(caches, stats_record, route_records, entry_bytes, mrt_subtype, peer_table, + addpath=False, addpath_error=None): + """ Parse MRT table dump v2. + """ + # Prepare ADD-PATH RIB flag and protocol + addpath_rib, afinet = addpath, None + + # Access route record template + route_init, route_emit, route_error = route_records + + # Initialize route record prefix + route_record_prefix = list(route_init) + + # Add source to route record prefix + if FTL_ATTR_BGP_ROUTE_SOURCE >= 0: + if FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN: + route_record_prefix[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR + else: + route_record_prefix[FTL_ATTR_BGP_ROUTE_SOURCE] = FTL_ATTR_BGP_ROUTE_SOURCE_RIB + + # Prepare byte offset + offset = 0 + + # Parse sequence number + if FTL_ATTR_BGP_ROUTE_SEQUENCE >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_SEQUENCE] = struct_unpack(STRUCT_4B, entry_bytes[offset:offset + 4])[0] + offset += 4 + + # Parse AFI/SAFI-specific RIB entries + if mrt_subtype in MRT_TABLE_DUMP_V2_RIB_SPECIFIC_ANY: + + # ----------------------------------------------- + # [RFC6396] 4.3.2. AFI/SAFI-Specific RIB Subtypes + # ----------------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Sequence Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Prefix Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Prefix (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Entry Count | RIB Entries (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + try: + # Parse prefix + # NOTE: Human-readable conversions are done in unpack_mrt_bgp_nlri() + preflen = (entry_bytes[offset] + 7) // 8 + 1 + afinet = AF_INET6 if mrt_subtype in MRT_TABLE_DUMP_V2_RIB_IPV6_ANY else AF_INET + _, prefix_proto, prefix, _, _, _ = next(iter(unpack_mrt_bgp_nlri(stats_record, caches, + entry_bytes[offset:offset + preflen], + afinet))) + offset += preflen + + # Add prefix protocol to route record prefix + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = prefix_proto + + # Add prefix to route record prefix + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_PREFIX] = prefix + + # Yield (or re-raise) prefix errors + except FtlMrtDataError as error: + yield from route_error(error) + return + except StopIteration: + yield from route_error(FtlMrtDataError('Unable to decode prefix for table dump v2 entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX, data=entry_bytes)) + return + + # Parse generic RIB entries + elif mrt_subtype in MRT_TABLE_DUMP_V2_RIB_GENERIC_ANY: + + # ------------------------------------- + # [RFC6396] 4.3.3. RIB_GENERIC Subtype + # ------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Sequence Number | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Address Family Identifier |Subsequent AFI | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Network Layer Reachability Information (variable) | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Entry Count | RIB Entries (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse AFI value + afi = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse SAFI value + safi = entry_bytes[offset] + offset += 1 + + # Parse prefix + try: + # Check AFI value + if afi != AFI_IPV4 and afi != AFI_IPV6: # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Invalid AFI value ({afi}) in table dump v2 RIB_GENERIC entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=entry_bytes) + + # Check SAFI value + if safi != BGP_SAFI_UNICAST and safi != BGP_SAFI_MULTICAST: # pylint: disable=consider-using-in + raise FtlMrtDataError(f'Unsupported SAFI value ({safi}) in table dump v2 RIB_GENERIC entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, data=entry_bytes) + + # Parse prefix + # NOTE: Human-readable conversions are done in unpack_mrt_bgp_nlri() + preflen = (entry_bytes[offset] + 7) // 8 + 1 + afinet = AF_INET6 if afi == AFI_IPV6 else AF_INET + _, prefix_proto, prefix, path_id, _, _ = next(iter(unpack_mrt_bgp_nlri(caches, stats_record, + entry_bytes[offset:offset + preflen], + afinet, addpath=addpath))) + offset += preflen + + # Add prefix protocol to route record prefix + if FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL] = prefix_proto + + # Add prefix to route record prefix + if FTL_ATTR_BGP_ROUTE_PREFIX >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_PREFIX] = prefix + + # Add ADD-PATH path ID to route record prefix + addpath_rib = False + if path_id is not None: + if FTL_ATTR_BGP_ROUTE_PATH_ID >= 0: + route_record_prefix[FTL_ATTR_BGP_ROUTE_PATH_ID] = path_id + + # Yield (or re-raise) prefix errors + except FtlMrtDataError as error: + yield from route_error(error) + return + except StopIteration: + yield from route_error(FtlMrtDataError('Unable to decode prefix for table dump v2 entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX, data=entry_bytes)) + return + + # Parse unknown MRT subtype + else: + yield from route_error(FtlMrtHeaderError(f'Unknown MRT subtype ({mrt_subtype}) for table dump v2 entry', + reason=FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE, data=entry_bytes)) + return + + # Parse entry count + entry_count = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # ---------------------------- + # [RFC6396] 4.3.4. RIB Entries + # ---------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer Index | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Originated Time | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Attribute Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | BGP Attributes... (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Sanitize RIB entries + static_offset = 8 if addpath is False else 12 + cur_entries, cur_offset, end_offset = 0, offset + static_offset, len(entry_bytes) + static_offset + while cur_offset < end_offset: + cur_offset += static_offset + struct_unpack(STRUCT_2B, entry_bytes[cur_offset - 2:cur_offset])[0] + cur_entries += 1 + + # --------------------------------------------- + # [RFC8050] 4.1. AFI/SAFI-Specific RIB Subtypes + # --------------------------------------------- + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Peer Index | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Originated Time | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Path Identifier | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Attribute Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | BGP Attributes... (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Handle invalid RIB entries + if cur_entries != entry_count or cur_offset != end_offset: + + # Prepare data error + if addpath_error is None: + addpath_error = FtlMrtDataError('Unable to decode table dump v2 RIB entries', + reason=FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA, data=entry_bytes) + + # Retry with ADD-PATH support + # NOTE: Some MRT exporters include path IDs but fail to set an ADD-PATH entry subtype (RFC7911) + if addpath is False: + yield from unpack_mrt_entry_tdv2_rib(caches, stats_record, route_records, entry_bytes, mrt_subtype, + peer_table, addpath=True, addpath_error=addpath_error) + return + + # Retry failed + yield from route_error(addpath_error) + return + + # Check for ADD-PATH fixes + if addpath is True and addpath_error is not None: + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add ADD-PATH fix + if FTL_ATTR_BGP_STATS_MRT_FIXES >= 0: + fixtype = str(FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH) + if FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN: + fixtype = FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH_STR + stats_record_fix = stats_record[FTL_ATTR_BGP_STATS_MRT_FIXES] + stats_record_fix[fixtype] = stats_record_fix.get(fixtype, 0) + 1 + + # Parse RIB entries + for _ in range(entry_count): + + # Initialize route record + route_record = list(route_record_prefix) + + # Parse peer index and access peer table + # NOTE: Human-readable conversions are done in unpack_mrt_entry_tdv2_index() + if (FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL >= 0 or FTL_ATTR_BGP_ROUTE_PEER_BGP_ID >= 0 + or FTL_ATTR_BGP_ROUTE_PEER_AS >= 0 or FTL_ATTR_BGP_ROUTE_PEER_IP >= 0): + peer_index = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + peer_proto, peer_bgp_id, peer_as, peer_ip = peer_table[peer_index] + if FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL >= 0: + route_record[FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL] = peer_proto + if FTL_ATTR_BGP_ROUTE_PEER_BGP_ID >= 0: + route_record[FTL_ATTR_BGP_ROUTE_PEER_BGP_ID] = peer_bgp_id + if FTL_ATTR_BGP_ROUTE_PEER_AS >= 0: + route_record[FTL_ATTR_BGP_ROUTE_PEER_AS] = peer_as + if FTL_ATTR_BGP_ROUTE_PEER_IP >= 0: + route_record[FTL_ATTR_BGP_ROUTE_PEER_IP] = peer_ip + offset += 2 + + # Parse timestamp + if FTL_ATTR_BGP_ROUTE_TIMESTAMP >= 0: + ts = float(struct_unpack(STRUCT_4B, entry_bytes[offset:offset + 4])[0]) + if FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN: + + # Cache date string for minute-based timestamp + if caches: + ts_cache = caches[CACHE_TS] + ts_min, ts_sec = divmod(ts, 60) + ts_cached = ts_cache.get(ts_min, None) + if ts_cached is None: + ts_cached = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_MIN) + ts_cache[ts_min] = ts_cached + + # Add seconds and microseconds + ts = f'{ts_cached}:{ts_sec:09.6f}' + + # Do not use cache + else: + ts = datetime_utcfromtimestamp(ts).strftime(DATETIME_FORMAT_USEC) + + # Add timestamp + route_record[FTL_ATTR_BGP_ROUTE_TIMESTAMP] = ts + offset += 4 + + # Parse ADD-PATH path ID + if addpath_rib is True: + route_record[FTL_ATTR_BGP_ROUTE_PATH_ID] = struct_unpack(STRUCT_4B, entry_bytes[offset:offset + 4])[0] + offset += 4 + + # Parse attributes length + alen = struct_unpack(STRUCT_2B, entry_bytes[offset:offset + 2])[0] + offset += 2 + + # Parse attributes + attr_bytes = entry_bytes[offset:offset + alen] + offset += alen + try: + unpack_mrt_bgp_attr(caches, stats_record, route_init, route_emit, route_record, attr_bytes, aslen=4, + addpath=addpath, rib=True) + + # Yield (or re-raise) attribute errors + except FtlMrtDataError as error: + yield from route_error(error) + continue + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Update IPv4 RIB routes + if afinet == AF_INET: + if FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4] += 1 + + # Update IPv6 RIB routes + if afinet == AF_INET6: + if FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 >= 0: + stats_record[FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6] += 1 + + # Yield final route record + if FTL_RECORD_BGP_ROUTE: + yield route_emit(route_record) diff --git a/src/ftlbgp/data/mrt/unpack.py b/src/ftlbgp/data/mrt/unpack.py new file mode 100644 index 0000000..37da380 --- /dev/null +++ b/src/ftlbgp/data/mrt/unpack.py @@ -0,0 +1,282 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from .entry.bgp import unpack_mrt_entry_bgp +from .entry.bgp4mp import unpack_mrt_entry_bgp4mp +from .entry.tdv2 import unpack_mrt_entry_tdv2_index +from .entry.tdv2 import unpack_mrt_entry_tdv2_rib +from .entry.td import unpack_mrt_entry_td_rib +from .entry.const import MRT_VALID +from .entry.const import MRT_BGP +from .entry.const import MRT_BGP4MP +from .entry.const import MRT_BGP4MP_ET +from .entry.const import MRT_TABLE_DUMP +from .entry.const import MRT_TABLE_DUMP_V2 +from .entry.const import MRT_TABLE_DUMP_V2_PEER_INDEX_TABLE +from .entry.const import MRT_TABLE_DUMP_V2_RIB_ADDPATH_ANY +from .entry.const import MRT_HEADER_BYTES +from .entry.const import MRT_DATA_BYTES +from ..const import STRUCT_2B +from ..const import STRUCT_4B +from ..const import struct_unpack +from ...parser import FtlParserFunc +from ...model.attr import FTL_ATTR_BGP_STATS_MRT_ENTRIES +from ...model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES +from ...model.attr import FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES_HUMAN +from ...model.const import FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPE_TO_STR +from ...model.const import FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TO_STR +from ...model.record import FTL_RECORD_BGP_STATS +from ...model.error import FtlMrtError +from ...model.error import FtlMrtFormatError +from ...model.error import FtlMrtHeaderError +from ...model.error import FtlMrtDataError + + +@FtlParserFunc +def unpack_mrt_data(inputfile, caches, stats_record, bgp_records, bgp_error): + """ Parse MRT byte stream. + """ + # Prepare peer table data structure and sequence number + peer_table, sequence = list(), 0 + + # Prepare memory for MRT header bytes (fixed length) + header_bytes = memoryview(bytearray(MRT_HEADER_BYTES)) + + # Prepare MRT header fields + ts, mrt_type, mrt_subtype, length = None, None, None, None + + # Prepare memory for MRT data bytes (dynamic length) + data_bytes = memoryview(bytearray(MRT_DATA_BYTES)) + entry_bytes = memoryview(bytearray()) + + # Access BGP record templates + peer_table_records = bgp_records.peer_table + state_change_records = bgp_records.state_change + route_records = bgp_records.route + keep_alive_records = bgp_records.keep_alive + route_refresh_records = bgp_records.route_refresh + notification_records = bgp_records.notification + open_records = bgp_records.open + + # Read MRT byte stream + first_record = True + while True: + try: + # Read MRT header bytes + n_read = 0 + try: + n_read = inputfile.readinto(header_bytes) + except Exception: # pylint: disable=broad-except + pass + + # Check MRT header bytes + if n_read < MRT_HEADER_BYTES: + if n_read > 0: + raise FtlMrtHeaderError(f'Incomplete MRT header ({n_read}B<{MRT_HEADER_BYTES}B)') + break + + # ------------------------------ + # [RFC6396] 2. MRT Common Header + # ------------------------------ + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Timestamp | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Subtype | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Message... (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Parse MRT header data + ts = float(struct_unpack(STRUCT_4B, header_bytes[:4])[0]) + mrt_type = struct_unpack(STRUCT_2B, header_bytes[4:6])[0] + mrt_subtype = struct_unpack(STRUCT_2B, header_bytes[6:8])[0] + length = struct_unpack(STRUCT_4B, header_bytes[8:12])[0] + + # Check MRT entry type + if mrt_type not in MRT_VALID: + raise FtlMrtHeaderError(f'Unknown MRT type ({mrt_type})', data=header_bytes) + + # Check MRT entry length + if length == 0: + raise FtlMrtHeaderError('Empty MRT record', data=header_bytes) + if length > MRT_DATA_BYTES: + raise FtlMrtHeaderError(f'MRT record too large ({length}B)', data=header_bytes) + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Add entry + if FTL_ATTR_BGP_STATS_MRT_ENTRIES >= 0: + stats_record[FTL_ATTR_BGP_STATS_MRT_ENTRIES] += 1 + + # Add entry type + if FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES >= 0: + stats_record_mrt_entry_types = stats_record[FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES] + etype, estype = mrt_type, mrt_subtype + if FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES_HUMAN: + estype = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TO_STR.get(etype, dict()).get(estype, estype) + etype = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPE_TO_STR.get(etype, etype) + etype_str = f'{etype}|{estype}' + stats_record_mrt_entry_types[etype_str] = stats_record_mrt_entry_types.get(etype_str, 0) + 1 + + # Slice MRT data bytes to current entry length + entry_bytes = data_bytes[:length] + + # Read MRT entry bytes + n_read = 0 + try: + n_read = inputfile.readinto(entry_bytes) + except Exception: # pylint: disable=broad-except + pass + + # Check MRT entry bytes + if n_read < length: + raise FtlMrtHeaderError(f'Incomplete MRT record ({n_read}B<{length}B)', data=header_bytes) + + ########## + # BGP4MP # + ########## + + # Parse MRT BGP4MP entry + if mrt_type == MRT_BGP4MP: + + # Increase sequence number + sequence += 1 + + # Yield BGP4MP records + yield from unpack_mrt_entry_bgp4mp(caches, stats_record, bgp_error, state_change_records, route_records, + keep_alive_records, route_refresh_records, notification_records, + open_records, entry_bytes, mrt_subtype, sequence, ts) + + ############# + # BGP4MP_ET # + ############# + + # Parse MRT BGP4MP_ET entry + elif mrt_type == MRT_BGP4MP_ET: + + # ------------------------------------------ + # [RFC6396] 3. Extended Timestamp MRT Header + # ------------------------------------------ + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Timestamp | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Type | Subtype | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Length | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Microsecond Timestamp | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | Message... (variable) + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + # Increase sequence number + sequence += 1 + + # Parse millisecond timestamp + ts += struct_unpack(STRUCT_4B, entry_bytes[:4])[0] / 1000000.0 + entry_bytes = entry_bytes[4:] + + # Yield BGP4MP records + yield from unpack_mrt_entry_bgp4mp(caches, stats_record, bgp_error, state_change_records, route_records, + keep_alive_records, route_refresh_records, notification_records, + open_records, entry_bytes, mrt_subtype, sequence, ts) + + ################# + # TABLE_DUMP_V2 # + ################# + + # Parse MRT TABLE_DUMP_V2 entry + elif mrt_type == MRT_TABLE_DUMP_V2: + + # Extract peer table + if mrt_subtype == MRT_TABLE_DUMP_V2_PEER_INDEX_TABLE: + yield from unpack_mrt_entry_tdv2_index(caches, stats_record, peer_table_records, entry_bytes, + peer_table) + + # Finalize peer table + peer_table = tuple(peer_table) + continue + + # ------------------------------------------------ + # [RFC8050] 4. MRT Subtypes for Type TABLE_DUMP_V2 + # ------------------------------------------------ + # This document defines the following new subtypes: + # o RIB_IPV4_UNICAST_ADDPATH + # o RIB_IPV4_MULTICAST_ADDPATH + # o RIB_IPV6_UNICAST_ADDPATH + # o RIB_IPV6_MULTICAST_ADDPATH + # o RIB_GENERIC_ADDPATH + + # Check for ADD-PATH entry (RFC7911) + addpath = mrt_subtype in MRT_TABLE_DUMP_V2_RIB_ADDPATH_ANY + + # Yield RIB records + yield from unpack_mrt_entry_tdv2_rib(caches, stats_record, route_records, entry_bytes, mrt_subtype, + peer_table, addpath=addpath) + + ############## + # TABLE_DUMP # + ############## + + # Parse MRT TABLE_DUMP entry + elif mrt_type == MRT_TABLE_DUMP: + + # Yield RIB records + yield from unpack_mrt_entry_td_rib(caches, stats_record, route_records, entry_bytes, mrt_subtype) + + ####### + # BGP # + ####### + + # Parse MRT BGP entry + elif mrt_type == MRT_BGP: + + # Increase sequence number + sequence += 1 + + # Yield BGP records + yield from unpack_mrt_entry_bgp(caches, stats_record, bgp_error, state_change_records, route_records, + keep_alive_records, route_refresh_records, notification_records, + open_records, entry_bytes, mrt_subtype, sequence, ts) + + # Successfully parsed first record + first_record = False + + # Re-raise already handled header exceptions as format exceptions + except FtlMrtHeaderError as error: + + # Raise format error for invalid first header + if first_record is True: + raise FtlMrtFormatError(error=error) # pylint: disable=raise-missing-from + + # Yield (or re-raise) other header errors + # NOTE: We abort parsing in case of any header error + yield from bgp_error(error) + return + + # Re-raise already handled record errors + except FtlMrtError as error: + raise error + + # Yield (or re-raise) unhandled exceptions + except Exception as exc: # pylint: disable=broad-exception-caught + yield from bgp_error(FtlMrtDataError('Unhandled data error', data=b''.join((header_bytes, entry_bytes)), + trace=True, exception=exc)) diff --git a/src/ftlbgp/data/util.py b/src/ftlbgp/data/util.py new file mode 100644 index 0000000..101ec39 --- /dev/null +++ b/src/ftlbgp/data/util.py @@ -0,0 +1,143 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import base64 +import json + +# Local imports +from ..model.attr import FTL_ATTR_BGP_STATS_MRT_ERRORS +from ..model.attr import FTL_ATTR_BGP_STATS_LGL_ERRORS +from ..model.attr import FTL_ATTR_BGP_STATS_PARSER_ERRORS +from ..model.attr import FTL_ATTR_BGP_ERROR_RECORD +from ..model.attr import FTL_ATTR_BGP_ERROR_SOURCE +from ..model.attr import FTL_ATTR_BGP_ERROR_SCOPE +from ..model.attr import FTL_ATTR_BGP_ERROR_REASON +from ..model.attr import FTL_ATTR_BGP_ERROR_MESSAGE +from ..model.attr import FTL_ATTR_BGP_ERROR_DATA +from ..model.attr import FTL_ATTR_BGP_ERROR_TRACE +from ..model.attr import FTL_ATTR_BGP_ERROR_SOURCE_HUMAN +from ..model.attr import FTL_ATTR_BGP_ERROR_SCOPE_HUMAN +from ..model.attr import FTL_ATTR_BGP_ERROR_REASON_HUMAN +from ..model.attr import FTL_ATTR_BGP_ERROR_DATA_HUMAN +from ..model.const import FTL_ATTR_BGP_ERROR_SOURCE_MRT +from ..model.const import FTL_ATTR_BGP_ERROR_SOURCE_LGL +from ..model.const import FTL_ATTR_BGP_ERROR_SOURCE_PARSER +from ..model.const import FTL_ATTR_BGP_ERROR_SOURCE_TO_STR +from ..model.const import FTL_ATTR_BGP_ERROR_SCOPE_TO_STR +from ..model.const import FTL_ATTR_BGP_ERROR_REASON_TO_STR +from ..model.record import FTL_RECORD_BGP_STATS +from ..model.record import FTL_RECORD_BGP_ERROR + +# Cache keys +CACHE_TS = 0 + + +def init_caches(): + """ Initialize data caches. + """ + # Return data caches + # NOTE: We currently support timestamp caches only + return (dict(), ) + + +def handle_bgp_error(raise_on_errors, error_init, error_emit, error_type, stats_emit, stats_record, error): + """ Yield BGP error record or re-raise given exception. + """ + # Extract error source + error_source = error.source + if FTL_ATTR_BGP_ERROR_SOURCE_HUMAN: + error_source = FTL_ATTR_BGP_ERROR_SOURCE_TO_STR.get(error_source, str(error_source)) + + # Extract error scope + error_scope = error.scope + if FTL_ATTR_BGP_ERROR_SCOPE_HUMAN: + error_scope = FTL_ATTR_BGP_ERROR_SCOPE_TO_STR.get(error_scope, str(error_scope)) + + # Extract error reason + error_reason = error.reason + if FTL_ATTR_BGP_ERROR_REASON_HUMAN: + error_reason = FTL_ATTR_BGP_ERROR_REASON_TO_STR.get(error_reason, str(error_reason)) + + # Update stats record + if FTL_RECORD_BGP_STATS: + + # Identify error source + source_errors = None + if error.source == FTL_ATTR_BGP_ERROR_SOURCE_MRT: + source_errors = FTL_ATTR_BGP_STATS_MRT_ERRORS + elif error.source == FTL_ATTR_BGP_ERROR_SOURCE_LGL: + source_errors = FTL_ATTR_BGP_STATS_LGL_ERRORS + elif error.source == FTL_ATTR_BGP_ERROR_SOURCE_PARSER: + source_errors = FTL_ATTR_BGP_STATS_PARSER_ERRORS + + # Add error scope/record/reason + if source_errors: + error_str = '|'.join((str(err) for err in (error_scope, error_reason, error_type))) + stats_record[source_errors][error_str] = stats_record[source_errors].get(error_str, 0) + 1 + + # Yield final stats record (if exception will be raised) + if raise_on_errors: + yield stats_emit(stats_record) + + # Yield error record if requested + if FTL_RECORD_BGP_ERROR: + + # Initialize error record + error_record = list(error_init) + + # Add error source to record + if FTL_ATTR_BGP_ERROR_SOURCE >= 0: + error_record[FTL_ATTR_BGP_ERROR_SOURCE] = error_source + + # Add error scope to record + if FTL_ATTR_BGP_ERROR_SCOPE >= 0: + error_record[FTL_ATTR_BGP_ERROR_SCOPE] = error_scope + + # Add error record type to record + if FTL_ATTR_BGP_ERROR_RECORD >= 0: + error_record[FTL_ATTR_BGP_ERROR_RECORD] = error_type + + # Add error reason to record + if FTL_ATTR_BGP_ERROR_REASON >= 0: + error_record[FTL_ATTR_BGP_ERROR_REASON] = error_reason + + # Add error message to record + if FTL_ATTR_BGP_ERROR_MESSAGE >= 0: + error_record[FTL_ATTR_BGP_ERROR_MESSAGE] = error.message + + # Add error data to record + if FTL_ATTR_BGP_ERROR_DATA >= 0: + data = error.data + if data is not None: + if isinstance(data, (bytes, bytearray, memoryview)) is True: + if FTL_ATTR_BGP_ERROR_DATA_HUMAN: + data = ' '.join('{:02x}'.format(byte) for byte in data) + else: + data = base64.b64encode(data).decode('ascii') + elif FTL_ATTR_BGP_ERROR_DATA_HUMAN: + data = json.dumps(data) + error_record[FTL_ATTR_BGP_ERROR_DATA] = data + + # Add error trace to record + if FTL_ATTR_BGP_ERROR_TRACE >= 0: + error_record[FTL_ATTR_BGP_ERROR_TRACE] = error.trace + + # Yield final error record + yield error_emit(error_record) + + # Re-raise exception if requested + if raise_on_errors: + + # Clone and raise error + raise type(error)(record=error_type, error=error) diff --git a/src/ftlbgp/model/__init__.py b/src/ftlbgp/model/__init__.py new file mode 100644 index 0000000..093fb18 --- /dev/null +++ b/src/ftlbgp/model/__init__.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" diff --git a/src/ftlbgp/model/attr.py b/src/ftlbgp/model/attr.py new file mode 100644 index 0000000..6a20f3b --- /dev/null +++ b/src/ftlbgp/model/attr.py @@ -0,0 +1,443 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from .util import FTL_ATTR_SHIFT_HUMAN +from .util import generate_spec_record +from .util import generate_spec_attributes + +# Generic BGP record type attribute +FTL_ATTR_BGP_RECORD_TYPE = 1 << 0 + +# Generic BGP record type attribute (human-readable) +FTL_ATTR_BGP_RECORD_TYPE_HUMAN = FTL_ATTR_BGP_RECORD_TYPE << FTL_ATTR_SHIFT_HUMAN + +############################# +# BGP PEER_TABLE ATTRIBUTES # +############################# + +# Available BGP peer_table attributes +FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID = 1 << 1 +FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME = 1 << 2 +FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL = 1 << 3 +FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID = 1 << 4 +FTL_ATTR_BGP_PEER_TABLE_PEER_AS = 1 << 5 +FTL_ATTR_BGP_PEER_TABLE_PEER_IP = 1 << 6 + +# Available BGP peer_table attributes (human-readable) +FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID_HUMAN = FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME_HUMAN = FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID_HUMAN = FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_PEER_TABLE_PEER_AS_HUMAN = FTL_ATTR_BGP_PEER_TABLE_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_PEER_TABLE_PEER_IP_HUMAN = FTL_ATTR_BGP_PEER_TABLE_PEER_IP << FTL_ATTR_SHIFT_HUMAN + +# BGP peer_table attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpPeerTable = generate_spec_record('FtlAttrsBgpPeerTable', ( + ('collector_bgp_id', FTL_ATTR_BGP_PEER_TABLE_COLLECTOR_BGP_ID, False), + ('view_name', FTL_ATTR_BGP_PEER_TABLE_VIEW_NAME, False), + ('peer_protocol', FTL_ATTR_BGP_PEER_TABLE_PEER_PROTOCOL, True), + ('peer_bgp_id', FTL_ATTR_BGP_PEER_TABLE_PEER_BGP_ID, True), + ('peer_as', FTL_ATTR_BGP_PEER_TABLE_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_PEER_TABLE_PEER_IP, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +############################### +# BGP STATE_CHANGE ATTRIBUTES # +############################### + +# Available BGP state_change attributes +FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP = 1 << 1 +FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL = 1 << 2 +FTL_ATTR_BGP_STATE_CHANGE_PEER_AS = 1 << 3 +FTL_ATTR_BGP_STATE_CHANGE_PEER_IP = 1 << 4 +FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE = 1 << 5 +FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE = 1 << 6 + +# Available BGP state_change attributes (human-readable) +FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP_HUMAN = FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATE_CHANGE_PEER_AS_HUMAN = FTL_ATTR_BGP_STATE_CHANGE_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATE_CHANGE_PEER_IP_HUMAN = FTL_ATTR_BGP_STATE_CHANGE_PEER_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE_HUMAN = FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE_HUMAN = FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE << FTL_ATTR_SHIFT_HUMAN + +# BGP state_change attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpStateChange = generate_spec_record('FtlAttrsBgpStateChange', ( + ('timestamp', FTL_ATTR_BGP_STATE_CHANGE_TIMESTAMP, True), + ('peer_protocol', FTL_ATTR_BGP_STATE_CHANGE_PEER_PROTOCOL, True), + ('peer_as', FTL_ATTR_BGP_STATE_CHANGE_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_STATE_CHANGE_PEER_IP, True), + ('old_state', FTL_ATTR_BGP_STATE_CHANGE_OLD_STATE, True), + ('new_state', FTL_ATTR_BGP_STATE_CHANGE_NEW_STATE, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +######################## +# BGP ROUTE ATTRIBUTES # +######################## + +# Available BGP route attributes +FTL_ATTR_BGP_ROUTE_SOURCE = 1 << 1 +FTL_ATTR_BGP_ROUTE_SEQUENCE = 1 << 2 +FTL_ATTR_BGP_ROUTE_TIMESTAMP = 1 << 3 +FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL = 1 << 4 +FTL_ATTR_BGP_ROUTE_PEER_BGP_ID = 1 << 5 +FTL_ATTR_BGP_ROUTE_PEER_AS = 1 << 6 +FTL_ATTR_BGP_ROUTE_PEER_IP = 1 << 7 +FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL = 1 << 8 +FTL_ATTR_BGP_ROUTE_NEXTHOP_IP = 1 << 9 +FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL = 1 << 10 +FTL_ATTR_BGP_ROUTE_PREFIX = 1 << 11 +FTL_ATTR_BGP_ROUTE_PATH_ID = 1 << 12 +FTL_ATTR_BGP_ROUTE_ASPATH = 1 << 13 +FTL_ATTR_BGP_ROUTE_ORIGIN = 1 << 14 +FTL_ATTR_BGP_ROUTE_COMMUNITIES = 1 << 15 +FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES = 1 << 16 +FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES = 1 << 17 +FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC = 1 << 18 +FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE = 1 << 19 +FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL = 1 << 20 +FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS = 1 << 21 +FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP = 1 << 22 +FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER = 1 << 23 +FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID = 1 << 24 +FTL_ATTR_BGP_ROUTE_CLUSTER_LIST = 1 << 25 +FTL_ATTR_BGP_ROUTE_LOCAL_PREF = 1 << 26 +FTL_ATTR_BGP_ROUTE_ATTR_SET = 1 << 27 +FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT = 1 << 28 +FTL_ATTR_BGP_ROUTE_AIGP = 1 << 29 +FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN = 1 << 30 + +# Available BGP route attributes (human-readable) +FTL_ATTR_BGP_ROUTE_SOURCE_HUMAN = FTL_ATTR_BGP_ROUTE_SOURCE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_SEQUENCE_HUMAN = FTL_ATTR_BGP_ROUTE_SEQUENCE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_TIMESTAMP_HUMAN = FTL_ATTR_BGP_ROUTE_TIMESTAMP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PEER_BGP_ID_HUMAN = FTL_ATTR_BGP_ROUTE_PEER_BGP_ID << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PEER_AS_HUMAN = FTL_ATTR_BGP_ROUTE_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PEER_IP_HUMAN = FTL_ATTR_BGP_ROUTE_PEER_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL_HUMAN = FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_NEXTHOP_IP_HUMAN = FTL_ATTR_BGP_ROUTE_NEXTHOP_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL_HUMAN = FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PREFIX_HUMAN = FTL_ATTR_BGP_ROUTE_PREFIX << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_PATH_ID_HUMAN = FTL_ATTR_BGP_ROUTE_PATH_ID << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ASPATH_HUMAN = FTL_ATTR_BGP_ROUTE_ASPATH << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ORIGIN_HUMAN = FTL_ATTR_BGP_ROUTE_ORIGIN << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_COMMUNITIES_HUMAN = FTL_ATTR_BGP_ROUTE_COMMUNITIES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES_HUMAN = FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES_HUMAN = FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC_HUMAN = FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE_HUMAN = FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL_HUMAN = FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS_HUMAN = FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP_HUMAN = FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER_HUMAN = FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID_HUMAN = FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_CLUSTER_LIST_HUMAN = FTL_ATTR_BGP_ROUTE_CLUSTER_LIST << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_LOCAL_PREF_HUMAN = FTL_ATTR_BGP_ROUTE_LOCAL_PREF << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ATTR_SET_HUMAN = FTL_ATTR_BGP_ROUTE_ATTR_SET << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT_HUMAN = FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_AIGP_HUMAN = FTL_ATTR_BGP_ROUTE_AIGP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN_HUMAN = FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN << FTL_ATTR_SHIFT_HUMAN + +# BGP route attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpRoute = generate_spec_record('FtlAttrsBgpRoute', ( + ('source', FTL_ATTR_BGP_ROUTE_SOURCE, True), + ('sequence', FTL_ATTR_BGP_ROUTE_SEQUENCE, True), + ('timestamp', FTL_ATTR_BGP_ROUTE_TIMESTAMP, True), + ('peer_protocol', FTL_ATTR_BGP_ROUTE_PEER_PROTOCOL, True), + ('peer_bgp_id', FTL_ATTR_BGP_ROUTE_PEER_BGP_ID, True), + ('peer_as', FTL_ATTR_BGP_ROUTE_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_ROUTE_PEER_IP, True), + ('nexthop_protocol', FTL_ATTR_BGP_ROUTE_NEXTHOP_PROTOCOL, True), + ('nexthop_ip', FTL_ATTR_BGP_ROUTE_NEXTHOP_IP, True), + ('prefix_protocol', FTL_ATTR_BGP_ROUTE_PREFIX_PROTOCOL, True), + ('prefix', FTL_ATTR_BGP_ROUTE_PREFIX, True), + ('path_id', FTL_ATTR_BGP_ROUTE_PATH_ID, True), + ('aspath', FTL_ATTR_BGP_ROUTE_ASPATH, True), + ('origin', FTL_ATTR_BGP_ROUTE_ORIGIN, True), + ('communities', FTL_ATTR_BGP_ROUTE_COMMUNITIES, True), + ('large_communities', FTL_ATTR_BGP_ROUTE_LARGE_COMMUNITIES, True), + ('extended_communities', FTL_ATTR_BGP_ROUTE_EXTENDED_COMMUNITIES, False), + ('multi_exit_disc', FTL_ATTR_BGP_ROUTE_MULTI_EXIT_DISC, False), + ('atomic_aggregate', FTL_ATTR_BGP_ROUTE_ATOMIC_AGGREGATE, False), + ('aggregator_protocol', FTL_ATTR_BGP_ROUTE_AGGREGATOR_PROTOCOL, False), + ('aggregator_as', FTL_ATTR_BGP_ROUTE_AGGREGATOR_AS, False), + ('aggregator_ip', FTL_ATTR_BGP_ROUTE_AGGREGATOR_IP, False), + ('only_to_customer', FTL_ATTR_BGP_ROUTE_ONLY_TO_CUSTOMER, False), + ('originator_id', FTL_ATTR_BGP_ROUTE_ORIGINATOR_ID, False), + ('cluster_list', FTL_ATTR_BGP_ROUTE_CLUSTER_LIST, False), + ('local_pref', FTL_ATTR_BGP_ROUTE_LOCAL_PREF, False), + ('attr_set', FTL_ATTR_BGP_ROUTE_ATTR_SET, False), + ('as_pathlimit', FTL_ATTR_BGP_ROUTE_AS_PATHLIMIT, False), + ('aigp', FTL_ATTR_BGP_ROUTE_AIGP, False), + ('attrs_unknown', FTL_ATTR_BGP_ROUTE_ATTRS_UNKNOWN, False), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +############################# +# BGP KEEP_ALIVE ATTRIBUTES # +############################# + +# Available BGP keep_alive attributes +FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP = 1 << 1 +FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL = 1 << 2 +FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS = 1 << 3 +FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP = 1 << 4 + +# Available BGP keep_alive attributes (human-readable) +FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP_HUMAN = FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS_HUMAN = FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP_HUMAN = FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP << FTL_ATTR_SHIFT_HUMAN + +# BGP keep_alive attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpKeepAlive = generate_spec_record('FtlAttrsBgpKeepAlive', ( + ('timestamp', FTL_ATTR_BGP_KEEP_ALIVE_TIMESTAMP, True), + ('peer_protocol', FTL_ATTR_BGP_KEEP_ALIVE_PEER_PROTOCOL, True), + ('peer_as', FTL_ATTR_BGP_KEEP_ALIVE_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_KEEP_ALIVE_PEER_IP, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +################################ +# BGP ROUTE_REFRESH ATTRIBUTES # +################################ + +# Available BGP route_refresh attributes +FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP = 1 << 1 +FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL = 1 << 2 +FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS = 1 << 3 +FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP = 1 << 4 +FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL = 1 << 5 + +# Available BGP route_refresh attributes (human-readable) +FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP_HUMAN = FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS_HUMAN = FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP_HUMAN = FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL_HUMAN = FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL << FTL_ATTR_SHIFT_HUMAN + +# BGP route_refresh attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpRouteRefresh = generate_spec_record('FtlAttrsBgpRouteRefresh', ( + ('timestamp', FTL_ATTR_BGP_ROUTE_REFRESH_TIMESTAMP, True), + ('peer_protocol', FTL_ATTR_BGP_ROUTE_REFRESH_PEER_PROTOCOL, True), + ('peer_as', FTL_ATTR_BGP_ROUTE_REFRESH_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_ROUTE_REFRESH_PEER_IP, True), + ('refresh_protocol', FTL_ATTR_BGP_ROUTE_REFRESH_REFRESH_PROTOCOL, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +############################### +# BGP NOTIFICATION ATTRIBUTES # +############################### + +# Available BGP notification attributes +FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP = 1 << 1 +FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL = 1 << 2 +FTL_ATTR_BGP_NOTIFICATION_PEER_AS = 1 << 3 +FTL_ATTR_BGP_NOTIFICATION_PEER_IP = 1 << 4 +FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE = 1 << 5 +FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE = 1 << 6 +FTL_ATTR_BGP_NOTIFICATION_DATA = 1 << 7 + +# Available BGP notification attributes (human-readable) +FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP_HUMAN = FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_NOTIFICATION_PEER_AS_HUMAN = FTL_ATTR_BGP_NOTIFICATION_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_NOTIFICATION_PEER_IP_HUMAN = FTL_ATTR_BGP_NOTIFICATION_PEER_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_HUMAN = FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_HUMAN = FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_NOTIFICATION_DATA_HUMAN = FTL_ATTR_BGP_NOTIFICATION_DATA << FTL_ATTR_SHIFT_HUMAN + +# BGP notification attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpNotification = generate_spec_record('FtlAttrsBgpNotification', ( + ('timestamp', FTL_ATTR_BGP_NOTIFICATION_TIMESTAMP, True), + ('peer_protocol', FTL_ATTR_BGP_NOTIFICATION_PEER_PROTOCOL, True), + ('peer_as', FTL_ATTR_BGP_NOTIFICATION_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_NOTIFICATION_PEER_IP, True), + ('error_code', FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE, True), + ('error_subcode', FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE, True), + ('data', FTL_ATTR_BGP_NOTIFICATION_DATA, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +####################### +# BGP OPEN ATTRIBUTES # +####################### + +# Available BGP open attributes +FTL_ATTR_BGP_OPEN_TIMESTAMP = 1 << 1 +FTL_ATTR_BGP_OPEN_PEER_PROTOCOL = 1 << 2 +FTL_ATTR_BGP_OPEN_PEER_AS = 1 << 3 +FTL_ATTR_BGP_OPEN_PEER_IP = 1 << 4 +FTL_ATTR_BGP_OPEN_VERSION = 1 << 5 +FTL_ATTR_BGP_OPEN_MY_AS = 1 << 6 +FTL_ATTR_BGP_OPEN_HOLD_TIME = 1 << 7 +FTL_ATTR_BGP_OPEN_BGP_ID = 1 << 8 +FTL_ATTR_BGP_OPEN_CAPABILITIES = 1 << 9 +FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN = 1 << 10 + +# Available BGP open attributes (human-readable) +FTL_ATTR_BGP_OPEN_TIMESTAMP_HUMAN = FTL_ATTR_BGP_OPEN_TIMESTAMP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_PEER_PROTOCOL_HUMAN = FTL_ATTR_BGP_OPEN_PEER_PROTOCOL << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_PEER_AS_HUMAN = FTL_ATTR_BGP_OPEN_PEER_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_PEER_IP_HUMAN = FTL_ATTR_BGP_OPEN_PEER_IP << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_VERSION_HUMAN = FTL_ATTR_BGP_OPEN_VERSION << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_MY_AS_HUMAN = FTL_ATTR_BGP_OPEN_MY_AS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_HOLD_TIME_HUMAN = FTL_ATTR_BGP_OPEN_HOLD_TIME << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_BGP_ID_HUMAN = FTL_ATTR_BGP_OPEN_BGP_ID << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_CAPABILITIES_HUMAN = FTL_ATTR_BGP_OPEN_CAPABILITIES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN_HUMAN = FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN << FTL_ATTR_SHIFT_HUMAN + +# BGP open attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpOpen = generate_spec_record('FtlAttrsBgpOpen', ( + ('timestamp', FTL_ATTR_BGP_OPEN_TIMESTAMP, True), + ('peer_protocol', FTL_ATTR_BGP_OPEN_PEER_PROTOCOL, True), + ('peer_as', FTL_ATTR_BGP_OPEN_PEER_AS, True), + ('peer_ip', FTL_ATTR_BGP_OPEN_PEER_IP, True), + ('version', FTL_ATTR_BGP_OPEN_VERSION, True), + ('my_as', FTL_ATTR_BGP_OPEN_MY_AS, True), + ('hold_time', FTL_ATTR_BGP_OPEN_HOLD_TIME, True), + ('bgp_id', FTL_ATTR_BGP_OPEN_BGP_ID, True), + ('capabilities', FTL_ATTR_BGP_OPEN_CAPABILITIES, True), + ('params_unknown', FTL_ATTR_BGP_OPEN_PARAMS_UNKNOWN, False), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +######################## +# BGP STATS ATTRIBUTES # +######################## + +# Available BGP stats attributes +FTL_ATTR_BGP_STATS_PARSER_LIFETIME = 1 << 1 +FTL_ATTR_BGP_STATS_PARSER_RUNTIME = 1 << 2 +FTL_ATTR_BGP_STATS_PARSER_ERRORS = 1 << 3 +FTL_ATTR_BGP_STATS_LGL_RUNTIME = 1 << 4 +FTL_ATTR_BGP_STATS_LGL_ENTRIES = 1 << 5 +FTL_ATTR_BGP_STATS_LGL_ERRORS = 1 << 6 +FTL_ATTR_BGP_STATS_MRT_RUNTIME = 1 << 7 +FTL_ATTR_BGP_STATS_MRT_ENTRIES = 1 << 8 +FTL_ATTR_BGP_STATS_MRT_ERRORS = 1 << 9 +FTL_ATTR_BGP_STATS_MRT_FIXES = 1 << 10 +FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES = 1 << 11 +FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES = 1 << 12 +FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES = 1 << 13 +FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES = 1 << 14 +FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 = 1 << 15 +FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 = 1 << 16 +FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4 = 1 << 17 +FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6 = 1 << 18 +FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4 = 1 << 19 +FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6 = 1 << 20 + +# Available BGP stats attributes (human-readable) +FTL_ATTR_BGP_STATS_PARSER_LIFETIME_HUMAN = FTL_ATTR_BGP_STATS_PARSER_LIFETIME << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_PARSER_RUNTIME_HUMAN = FTL_ATTR_BGP_STATS_PARSER_RUNTIME << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_PARSER_ERRORS_HUMAN = FTL_ATTR_BGP_STATS_PARSER_ERRORS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_LGL_RUNTIME_HUMAN = FTL_ATTR_BGP_STATS_LGL_RUNTIME << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_LGL_ENTRIES_HUMAN = FTL_ATTR_BGP_STATS_LGL_ENTRIES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_LGL_ERRORS_HUMAN = FTL_ATTR_BGP_STATS_LGL_ERRORS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_RUNTIME_HUMAN = FTL_ATTR_BGP_STATS_MRT_RUNTIME << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_ENTRIES_HUMAN = FTL_ATTR_BGP_STATS_MRT_ENTRIES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_ERRORS_HUMAN = FTL_ATTR_BGP_STATS_MRT_ERRORS << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_FIXES_HUMAN = FTL_ATTR_BGP_STATS_MRT_FIXES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES_HUMAN = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES_HUMAN = FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES_HUMAN = FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES_HUMAN = FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4_HUMAN = FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4 << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6_HUMAN = FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6 << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4_HUMAN = FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4 << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6_HUMAN = FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6 << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4_HUMAN = FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4 << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6_HUMAN = FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6 << FTL_ATTR_SHIFT_HUMAN + +# BGP stats attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpStats = generate_spec_record('FtlAttrsBgpStats', ( + ('parser_lifetime', FTL_ATTR_BGP_STATS_PARSER_LIFETIME, True), + ('parser_runtime', FTL_ATTR_BGP_STATS_PARSER_RUNTIME, True), + ('parser_errors', FTL_ATTR_BGP_STATS_PARSER_ERRORS, True), + ('lgl_runtime', FTL_ATTR_BGP_STATS_LGL_RUNTIME, True), + ('lgl_entries', FTL_ATTR_BGP_STATS_LGL_ENTRIES, True), + ('lgl_errors', FTL_ATTR_BGP_STATS_LGL_ERRORS, True), + ('mrt_runtime', FTL_ATTR_BGP_STATS_MRT_RUNTIME, True), + ('mrt_entries', FTL_ATTR_BGP_STATS_MRT_ENTRIES, True), + ('mrt_errors', FTL_ATTR_BGP_STATS_MRT_ERRORS, True), + ('mrt_fixes', FTL_ATTR_BGP_STATS_MRT_FIXES, True), + ('mrt_bgp_entry_types', FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPES, True), + ('mrt_bgp_message_types', FTL_ATTR_BGP_STATS_MRT_BGP_MESSAGE_TYPES, True), + ('mrt_bgp_attribute_types', FTL_ATTR_BGP_STATS_MRT_BGP_ATTRIBUTE_TYPES, True), + ('mrt_bgp_capability_types', FTL_ATTR_BGP_STATS_MRT_BGP_CAPABILITY_TYPES, True), + ('bgp_routes_rib_ipv4', FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV4, True), + ('bgp_routes_rib_ipv6', FTL_ATTR_BGP_STATS_BGP_ROUTES_RIB_IPV6, True), + ('bgp_routes_announce_ipv4', FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV4, True), + ('bgp_routes_announce_ipv6', FTL_ATTR_BGP_STATS_BGP_ROUTES_ANNOUNCE_IPV6, True), + ('bgp_routes_withdraw_ipv4', FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV4, True), + ('bgp_routes_withdraw_ipv6', FTL_ATTR_BGP_STATS_BGP_ROUTES_WITHDRAW_IPV6, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +######################## +# BGP ERROR ATTRIBUTES # +######################## + +# Available BGP error attributes +FTL_ATTR_BGP_ERROR_SOURCE = 1 << 1 +FTL_ATTR_BGP_ERROR_SCOPE = 1 << 2 +FTL_ATTR_BGP_ERROR_RECORD = 1 << 3 +FTL_ATTR_BGP_ERROR_REASON = 1 << 4 +FTL_ATTR_BGP_ERROR_MESSAGE = 1 << 5 +FTL_ATTR_BGP_ERROR_DATA = 1 << 6 +FTL_ATTR_BGP_ERROR_TRACE = 1 << 7 + +# Available BGP error attributes (human-readable) +FTL_ATTR_BGP_ERROR_SOURCE_HUMAN = FTL_ATTR_BGP_ERROR_SOURCE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ERROR_SCOPE_HUMAN = FTL_ATTR_BGP_ERROR_SCOPE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ERROR_RECORD_HUMAN = FTL_ATTR_BGP_ERROR_RECORD << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ERROR_REASON_HUMAN = FTL_ATTR_BGP_ERROR_REASON << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ERROR_MESSAGE_HUMAN = FTL_ATTR_BGP_ERROR_MESSAGE << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ERROR_DATA_HUMAN = FTL_ATTR_BGP_ERROR_DATA << FTL_ATTR_SHIFT_HUMAN +FTL_ATTR_BGP_ERROR_TRACE_HUMAN = FTL_ATTR_BGP_ERROR_TRACE << FTL_ATTR_SHIFT_HUMAN + +# BGP error attribute specification +# [Format] spec := (field, value, default) +FtlAttrsBgpError = generate_spec_record('FtlAttrsBgpError', ( + ('source', FTL_ATTR_BGP_ERROR_SOURCE, True), + ('scope', FTL_ATTR_BGP_ERROR_SCOPE, True), + ('record', FTL_ATTR_BGP_ERROR_RECORD, True), + ('reason', FTL_ATTR_BGP_ERROR_REASON, True), + ('message', FTL_ATTR_BGP_ERROR_MESSAGE, True), + ('data', FTL_ATTR_BGP_ERROR_DATA, True), + ('trace', FTL_ATTR_BGP_ERROR_TRACE, True), +), typed=FTL_ATTR_BGP_RECORD_TYPE) + +###################### +# ALL BGP ATTRIBUTES # +###################### + +# BGP attribute spec +FtlAttrsBgp = generate_spec_attributes('FtlAttrsBgp', ( + FtlAttrsBgpPeerTable, + FtlAttrsBgpStateChange, + FtlAttrsBgpRoute, + FtlAttrsBgpKeepAlive, + FtlAttrsBgpRouteRefresh, + FtlAttrsBgpNotification, + FtlAttrsBgpOpen, + FtlAttrsBgpStats, + FtlAttrsBgpError, +)) diff --git a/src/ftlbgp/model/const.py b/src/ftlbgp/model/const.py new file mode 100644 index 0000000..1d389c8 --- /dev/null +++ b/src/ftlbgp/model/const.py @@ -0,0 +1,724 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from ..data.const import AFI_IPV4 +from ..data.const import AFI_IPV6 +from ..data.mrt.entry.const import MRT_NULL +from ..data.mrt.entry.const import MRT_START +from ..data.mrt.entry.const import MRT_DIE +from ..data.mrt.entry.const import MRT_I_AM_DEAD +from ..data.mrt.entry.const import MRT_PEER_DOWN +from ..data.mrt.entry.const import MRT_BGP +from ..data.mrt.entry.const import MRT_RIP +from ..data.mrt.entry.const import MRT_IDRP +from ..data.mrt.entry.const import MRT_RIPNG +from ..data.mrt.entry.const import MRT_BGP4PLUS +from ..data.mrt.entry.const import MRT_BGP4PLUS_01 +from ..data.mrt.entry.const import MRT_OSPFV2 +from ..data.mrt.entry.const import MRT_TABLE_DUMP +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2 +from ..data.mrt.entry.const import MRT_BGP4MP +from ..data.mrt.entry.const import MRT_BGP4MP_ET +from ..data.mrt.entry.const import MRT_ISIS +from ..data.mrt.entry.const import MRT_ISIS_ET +from ..data.mrt.entry.const import MRT_OSPFV3 +from ..data.mrt.entry.const import MRT_OSPFV3_ET +from ..data.mrt.entry.const import MRT_BGP_NULL +from ..data.mrt.entry.const import MRT_BGP_UPDATE +from ..data.mrt.entry.const import MRT_BGP_PREF_UPDATE +from ..data.mrt.entry.const import MRT_BGP_STATE_CHANGE +from ..data.mrt.entry.const import MRT_BGP_SYNC +from ..data.mrt.entry.const import MRT_BGP_OPEN +from ..data.mrt.entry.const import MRT_BGP_NOTIFY +from ..data.mrt.entry.const import MRT_BGP_KEEPALIVE +from ..data.mrt.entry.const import MRT_BGP4MP_ENTRY +from ..data.mrt.entry.const import MRT_BGP4MP_SNAPSHOT +from ..data.mrt.entry.const import MRT_BGP4MP_STATE_CHANGE +from ..data.mrt.entry.const import MRT_BGP4MP_STATE_CHANGE_AS4 +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_ADDPATH +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_LOCAL +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_AS4 +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_AS4_ADDPATH +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_AS4_LOCAL +from ..data.mrt.entry.const import MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_PEER_INDEX_TABLE +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_GEO_PEER_TABLE +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_GENERIC +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_GENERIC_ADDPATH +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH +from ..data.mrt.entry.const import MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH +from ..data.mrt.bgp.const import BGP_BGP4MP_RESERVED +from ..data.mrt.bgp.const import BGP_BGP4MP_OPEN +from ..data.mrt.bgp.const import BGP_BGP4MP_UPDATE +from ..data.mrt.bgp.const import BGP_BGP4MP_NOTIFICATION +from ..data.mrt.bgp.const import BGP_BGP4MP_KEEPALIVE +from ..data.mrt.bgp.const import BGP_BGP4MP_ROUTE_REFRESH +from ..data.mrt.bgp.const import BGP_STATE_IDLE +from ..data.mrt.bgp.const import BGP_STATE_CONNECT +from ..data.mrt.bgp.const import BGP_STATE_ACTIVE +from ..data.mrt.bgp.const import BGP_STATE_OPEN_SENT +from ..data.mrt.bgp.const import BGP_STATE_OPEN_CONFIRM +from ..data.mrt.bgp.const import BGP_STATE_ESTABLISHED +from ..data.mrt.bgp.const import BGP_STATE_CLEARING +from ..data.mrt.bgp.const import BGP_STATE_DELETED +from ..data.mrt.bgp.const import BGP_CAPABILITY_RESERVED_0 +from ..data.mrt.bgp.const import BGP_CAPABILITY_BGP4MP +from ..data.mrt.bgp.const import BGP_CAPABILITY_ROUTE_REFRESH +from ..data.mrt.bgp.const import BGP_CAPABILITY_OUTBOUND_FILTER +from ..data.mrt.bgp.const import BGP_CAPABILITY_MULTIPLE_ROUTES +from ..data.mrt.bgp.const import BGP_CAPABILITY_EXTENDED_NEXT_HOP +from ..data.mrt.bgp.const import BGP_CAPABILITY_BGP4MP_ET +from ..data.mrt.bgp.const import BGP_CAPABILITY_BGPSEC +from ..data.mrt.bgp.const import BGP_CAPABILITY_MULTIPLE_LABELS +from ..data.mrt.bgp.const import BGP_CAPABILITY_BGP_ROLE +from ..data.mrt.bgp.const import BGP_CAPABILITY_GRACEFUL_RESTART +from ..data.mrt.bgp.const import BGP_CAPABILITY_AS4 +from ..data.mrt.bgp.const import BGP_CAPABILITY_UNKNOWN_66 +from ..data.mrt.bgp.const import BGP_CAPABILITY_DYNAMIC +from ..data.mrt.bgp.const import BGP_CAPABILITY_MULTISESSION +from ..data.mrt.bgp.const import BGP_CAPABILITY_ADDPATH +from ..data.mrt.bgp.const import BGP_CAPABILITY_ENHANCED_ROUTE_REFRESH +from ..data.mrt.bgp.const import BGP_CAPABILITY_LLGR +from ..data.mrt.bgp.const import BGP_CAPABILITY_POLICY_DISTRIBUTION +from ..data.mrt.bgp.const import BGP_CAPABILITY_FQDN +from ..data.mrt.bgp.const import BGP_CAPABILITY_BFD +from ..data.mrt.bgp.const import BGP_CAPABILITY_SOFTWARE_VERSION +from ..data.mrt.bgp.const import BGP_CAPABILITY_PRESTD_ROUTE_REFRESH +from ..data.mrt.bgp.const import BGP_CAPABILITY_PRESTD_POLICY_DISTRIBUTION +from ..data.mrt.bgp.const import BGP_CAPABILITY_PRESTD_OUTBOUND_FILTER +from ..data.mrt.bgp.const import BGP_CAPABILITY_PRESTD_MULTISESSION +from ..data.mrt.bgp.const import BGP_CAPABILITY_PRESTD_FQDN +from ..data.mrt.bgp.const import BGP_CAPABILITY_PRESTD_OPERATIONAL +from ..data.mrt.bgp.const import BGP_CAPABILITY_RESERVED_255 +from ..data.mrt.bgp.const import BGP_ERROR_RESERVED +from ..data.mrt.bgp.const import BGP_ERROR_MESSAGE_HEADER +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE +from ..data.mrt.bgp.const import BGP_ERROR_HOLD_TIMER_EXPIRED +from ..data.mrt.bgp.const import BGP_ERROR_FSM +from ..data.mrt.bgp.const import BGP_ERROR_CEASE +from ..data.mrt.bgp.const import BGP_ERROR_ROUTE_REFRESH +from ..data.mrt.bgp.const import BGP_ERROR_MESSAGE_HEADER_UNSPECIFIC +from ..data.mrt.bgp.const import BGP_ERROR_MESSAGE_HEADER_CONNECTION_NOT_SYNCHRONIZED +from ..data.mrt.bgp.const import BGP_ERROR_MESSAGE_HEADER_BAD_MESSAGE_LENGTH +from ..data.mrt.bgp.const import BGP_ERROR_MESSAGE_HEADER_BAD_MESSAGE_TYPE +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_UNSPECIFIC +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_VERSION_NUMBER +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_BAD_PEER_AS +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_BAD_BGP_IDENTIFIER +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_OPTIONAL_PARAMETER +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_AUTHENTICATION_FAILURE +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_UNACCEPTABLE_HOLD_TIME +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_CAPABILITY +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_DEPRECATED_8 +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_DEPRECATED_9 +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_DEPRECATED_10 +from ..data.mrt.bgp.const import BGP_ERROR_OPEN_MESSAGE_ROLE_MISMATCH +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_UNSPECIFIC +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_MALFORMED_ATTRIBUTE_LIST +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_UNRECOGNIZED_WELLKNOWN_ATTRIBUTE +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_MISSING_WELLKNOWN_ATTRIBUTE +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_INVALID_ATTRIBUTE_FLAGS +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_INVALID_ATTRIBUTE_LENGTH +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_INVALID_ORIGIN_ATTRIBUTE +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_AS_ROUTING_LOOP +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_INVALID_NEXTHOP_ATTRIBUTE +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_INVALID_OPTIONAL_ATTRIBUTE +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_INVALID_NETWORK_FIELD +from ..data.mrt.bgp.const import BGP_ERROR_UPDATE_MESSAGE_MALFORMED_AS_PATH +from ..data.mrt.bgp.const import BGP_ERROR_FSM_UNSPECIFIED +from ..data.mrt.bgp.const import BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_OPEN_SENT_STATE +from ..data.mrt.bgp.const import BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_OPEN_CONFIRM_STATE +from ..data.mrt.bgp.const import BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_ESTABLISHED_STATE +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_RESERVED +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_MAX_NUMBER_PREFIXES_REACHED +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_ADMINISTRATIVE_SHUTDOWN +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_PEER_DECONFIGURED +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_ADMINISTRATIVE_RESET +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_CONNECTION_REJECTED +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_OTHER_CONFIGURATION_CHANGE +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_CONNECTION_COLLISION_RESOLUTION +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_OUT_OF_RESOURCES +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_HARD_RESET +from ..data.mrt.bgp.const import BGP_ERROR_CEASE_BFD_DOWN +from ..data.mrt.bgp.const import BGP_ERROR_ROUTE_REFRESH_RESERVED +from ..data.mrt.bgp.const import BGP_ERROR_ROUTE_REFRESH_INVALID_MESSAGE_LENGTH +from ..data.mrt.bgp.const import BGP_PATH_ATTR_RESERVED +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ORIGIN +from ..data.mrt.bgp.const import BGP_PATH_ATTR_AS_PATH +from ..data.mrt.bgp.const import BGP_PATH_ATTR_NEXT_HOP +from ..data.mrt.bgp.const import BGP_PATH_ATTR_MULTI_EXIT_DISC +from ..data.mrt.bgp.const import BGP_PATH_ATTR_LOCAL_PREF +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ATOMIC_AGGREGATE +from ..data.mrt.bgp.const import BGP_PATH_ATTR_AGGREGATOR +from ..data.mrt.bgp.const import BGP_PATH_ATTR_COMMUNITIES +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ORIGINATOR_ID +from ..data.mrt.bgp.const import BGP_PATH_ATTR_CLUSTER_LIST +from ..data.mrt.bgp.const import BGP_PATH_ATTR_DPA +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ADVERTISER +from ..data.mrt.bgp.const import BGP_PATH_ATTR_RCID_CLUSTER_ID +from ..data.mrt.bgp.const import BGP_PATH_ATTR_MP_REACH_NLRI +from ..data.mrt.bgp.const import BGP_PATH_ATTR_MP_UNREACH_NLRI +from ..data.mrt.bgp.const import BGP_PATH_ATTR_EXTENDED_COMMUNITIES +from ..data.mrt.bgp.const import BGP_PATH_ATTR_AS4_PATH +from ..data.mrt.bgp.const import BGP_PATH_ATTR_AS4_AGGREGATOR +from ..data.mrt.bgp.const import BGP_PATH_ATTR_SAFI_SPECIFIC +from ..data.mrt.bgp.const import BGP_PATH_ATTR_CONNECTOR +from ..data.mrt.bgp.const import BGP_PATH_ATTR_AS_PATHLIMIT +from ..data.mrt.bgp.const import BGP_PATH_ATTR_PMSI_TUNNEL +from ..data.mrt.bgp.const import BGP_PATH_ATTR_TUNNEL_ENCAPSULATION +from ..data.mrt.bgp.const import BGP_PATH_ATTR_TRAFFIC_ENGINEERING +from ..data.mrt.bgp.const import BGP_PATH_ATTR_IPV6_EXTENDED_COMMUNITIES +from ..data.mrt.bgp.const import BGP_PATH_ATTR_AIGP +from ..data.mrt.bgp.const import BGP_PATH_ATTR_PE_DISTINGUISHER_LABELS +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ENTROPY_LABEL_CAPABILITY +from ..data.mrt.bgp.const import BGP_PATH_ATTR_LS +from ..data.mrt.bgp.const import BGP_PATH_ATTR_VENDOR_30 +from ..data.mrt.bgp.const import BGP_PATH_ATTR_VENDOR_31 +from ..data.mrt.bgp.const import BGP_PATH_ATTR_LARGE_COMMUNITIES +from ..data.mrt.bgp.const import BGP_PATH_ATTR_BGPSEC_PATH +from ..data.mrt.bgp.const import BGP_PATH_ATTR_COMMUNITY_CONTAINER +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ONLY_TO_CUSTOMER +from ..data.mrt.bgp.const import BGP_PATH_ATTR_DOMAIN_PATH +from ..data.mrt.bgp.const import BGP_PATH_ATTR_SFP +from ..data.mrt.bgp.const import BGP_PATH_ATTR_BFD_DISCRIMINATOR +from ..data.mrt.bgp.const import BGP_PATH_ATTR_NHC_TMP +from ..data.mrt.bgp.const import BGP_PATH_ATTR_PREFIX_SID +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ATTR_SET +from ..data.mrt.bgp.const import BGP_PATH_ATTR_VENDOR_129 +from ..data.mrt.bgp.const import BGP_PATH_ATTR_VENDOR_241 +from ..data.mrt.bgp.const import BGP_PATH_ATTR_VENDOR_242 +from ..data.mrt.bgp.const import BGP_PATH_ATTR_VENDOR_243 +from ..data.mrt.bgp.const import BGP_PATH_ATTR_RESERVED_FOR_DEV +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ORIGIN_IGP +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ORIGIN_EGP +from ..data.mrt.bgp.const import BGP_PATH_ATTR_ORIGIN_INCOMPLETE + +# Main data sources +FTL_MRT = 'mrt' +FTL_LGL = 'lgl' +FTL_BGP = 'bgp' +FTL_PARSER = 'parser' + +# Generic record name +FTL_RECORD = 'FTL_RECORD' + +# Generic attribute name +FTL_ATTR = 'FTL_ATTR' + + +def init_const_mappings(**consts): + """ Generate type-to-string/string-to-type constant mappings. + """ + # Iterate constants and populate constant mappings + const_to_str, const_from_str = dict(), dict() + for const_name, const_value in consts.items(): + const_to_str[const_value] = const_name + const_from_str[const_name] = const_value + + # Return constant mappings + return const_to_str, const_from_str + + +############################## +# BGP STATE_CHANGE CONSTANTS # +############################## + +# BGP state_change state constants +(FTL_ATTR_BGP_STATE_CHANGE_STATE_TO_STR, + FTL_ATTR_BGP_STATE_CHANGE_STATE_FROM_STR) = init_const_mappings( + idle = BGP_STATE_IDLE, + connect = BGP_STATE_CONNECT, + active = BGP_STATE_ACTIVE, + open_sent = BGP_STATE_OPEN_SENT, + open_confirm = BGP_STATE_OPEN_CONFIRM, + established = BGP_STATE_ESTABLISHED, + clearing = BGP_STATE_CLEARING, + deleted = BGP_STATE_DELETED, +) + +####################### +# BGP ROUTE CONSTANTS # +####################### + +# BGP route source integer types +FTL_ATTR_BGP_ROUTE_SOURCE_RIB = 0 +FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE = 1 +FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW = 2 + +# BGP route source string types +FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR = 'rib' +FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE_STR = 'announce' +FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW_STR = 'withdraw' + +# BGP route source constants +(FTL_ATTR_BGP_ROUTE_SOURCE_TO_STR, + FTL_ATTR_BGP_ROUTE_SOURCE_FROM_STR) = init_const_mappings(**{ + FTL_ATTR_BGP_ROUTE_SOURCE_RIB_STR: FTL_ATTR_BGP_ROUTE_SOURCE_RIB, + FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE_STR: FTL_ATTR_BGP_ROUTE_SOURCE_ANNOUNCE, + FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW_STR: FTL_ATTR_BGP_ROUTE_SOURCE_WITHDRAW, +}) + +# BGP route origin integer types +FTL_ATTR_BGP_ROUTE_ORIGIN_IGP = BGP_PATH_ATTR_ORIGIN_IGP +FTL_ATTR_BGP_ROUTE_ORIGIN_EGP = BGP_PATH_ATTR_ORIGIN_EGP +FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE = BGP_PATH_ATTR_ORIGIN_INCOMPLETE + +# BGP route origin string types +FTL_ATTR_BGP_ROUTE_ORIGIN_IGP_STR = 'igp' +FTL_ATTR_BGP_ROUTE_ORIGIN_EGP_STR = 'egp' +FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE_STR = 'incomplete' + +# BGP route origin constants +(FTL_ATTR_BGP_ROUTE_ORIGIN_TO_STR, + FTL_ATTR_BGP_ROUTE_ORIGIN_FROM_STR) = init_const_mappings(**{ + FTL_ATTR_BGP_ROUTE_ORIGIN_IGP_STR: BGP_PATH_ATTR_ORIGIN_IGP, + FTL_ATTR_BGP_ROUTE_ORIGIN_EGP_STR: BGP_PATH_ATTR_ORIGIN_EGP, + FTL_ATTR_BGP_ROUTE_ORIGIN_INCOMPLETE_STR: BGP_PATH_ATTR_ORIGIN_INCOMPLETE, +}) + +############################## +# BGP NOTIFICATION CONSTANTS # +############################## + +# BGP notification error code constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_CODE_FROM_STR) = init_const_mappings( + reserved = BGP_ERROR_RESERVED, + message_header = BGP_ERROR_MESSAGE_HEADER, + open_message = BGP_ERROR_OPEN_MESSAGE, + update_message = BGP_ERROR_UPDATE_MESSAGE, + hold_timer_expired = BGP_ERROR_HOLD_TIMER_EXPIRED, + fsm = BGP_ERROR_FSM, + cease = BGP_ERROR_CEASE, + route_refresh = BGP_ERROR_ROUTE_REFRESH, +) + +# BGP message header error subcode constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_MESSAGE_HEADER_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_MESSAGE_HEADER_FROM_STR) = init_const_mappings( + unspecific = BGP_ERROR_MESSAGE_HEADER_UNSPECIFIC, + connection_not_synchronized = BGP_ERROR_MESSAGE_HEADER_CONNECTION_NOT_SYNCHRONIZED, + message_length = BGP_ERROR_MESSAGE_HEADER_BAD_MESSAGE_LENGTH, + message_tgype = BGP_ERROR_MESSAGE_HEADER_BAD_MESSAGE_TYPE, +) + +# BGP OPEN message error subcode constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_OPEN_MESSAGE_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_OPEN_MESSAGE_FROM_STR) = init_const_mappings( + unspecific = BGP_ERROR_OPEN_MESSAGE_UNSPECIFIC, + unsupported_version_number = BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_VERSION_NUMBER, + bad_peer_as = BGP_ERROR_OPEN_MESSAGE_BAD_PEER_AS, + bad_bgp_identifier = BGP_ERROR_OPEN_MESSAGE_BAD_BGP_IDENTIFIER, + unsupported_optional_parameter = BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_OPTIONAL_PARAMETER, + authentication_failure = BGP_ERROR_OPEN_MESSAGE_AUTHENTICATION_FAILURE, + unacceptable_hold_time = BGP_ERROR_OPEN_MESSAGE_UNACCEPTABLE_HOLD_TIME, + unsupported_capability = BGP_ERROR_OPEN_MESSAGE_UNSUPPORTED_CAPABILITY, + deprecated_8 = BGP_ERROR_OPEN_MESSAGE_DEPRECATED_8, + deprecated_9 = BGP_ERROR_OPEN_MESSAGE_DEPRECATED_9, + deprecated_10 = BGP_ERROR_OPEN_MESSAGE_DEPRECATED_10, + role_mismatch = BGP_ERROR_OPEN_MESSAGE_ROLE_MISMATCH, +) + +# BGP UPDATE message error subcode constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_UPDATE_MESSAGE_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_UPDATE_MESSAGE_FROM_STR) = init_const_mappings( + unspecific = BGP_ERROR_UPDATE_MESSAGE_UNSPECIFIC, + malformed_attribute_list = BGP_ERROR_UPDATE_MESSAGE_MALFORMED_ATTRIBUTE_LIST, + unrecognized_wellknown_attribute = BGP_ERROR_UPDATE_MESSAGE_UNRECOGNIZED_WELLKNOWN_ATTRIBUTE, + missing_wellknown_attribute = BGP_ERROR_UPDATE_MESSAGE_MISSING_WELLKNOWN_ATTRIBUTE, + invalid_attribute_flags = BGP_ERROR_UPDATE_MESSAGE_INVALID_ATTRIBUTE_FLAGS, + invalid_attribute_length = BGP_ERROR_UPDATE_MESSAGE_INVALID_ATTRIBUTE_LENGTH, + invalid_origin_attribute = BGP_ERROR_UPDATE_MESSAGE_INVALID_ORIGIN_ATTRIBUTE, + as_routing_loop = BGP_ERROR_UPDATE_MESSAGE_AS_ROUTING_LOOP, + invalid_nexthop_attribute = BGP_ERROR_UPDATE_MESSAGE_INVALID_NEXTHOP_ATTRIBUTE, + invalid_optional_attribute = BGP_ERROR_UPDATE_MESSAGE_INVALID_OPTIONAL_ATTRIBUTE, + invalid_network_field = BGP_ERROR_UPDATE_MESSAGE_INVALID_NETWORK_FIELD, + malformed_as_path = BGP_ERROR_UPDATE_MESSAGE_MALFORMED_AS_PATH, +) + +# BGP finite state machine error subcode constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_FSM_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_FSM_FROM_STR) = init_const_mappings( + unspecified = BGP_ERROR_FSM_UNSPECIFIED, + unexpected_message_in_open_sent_state = BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_OPEN_SENT_STATE, + unexpected_message_in_open_confirm_state = BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_OPEN_CONFIRM_STATE, + unexpected_message_in_established_state = BGP_ERROR_FSM_UNEXPECTED_MESSAGE_IN_ESTABLISHED_STATE, +) + +# BGP cease error subcode constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_CEASE_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_CEASE_FROM_STR) = init_const_mappings( + reserved = BGP_ERROR_CEASE_RESERVED, + max_number_prefixes_reached = BGP_ERROR_CEASE_MAX_NUMBER_PREFIXES_REACHED, + administrative_shutdown = BGP_ERROR_CEASE_ADMINISTRATIVE_SHUTDOWN, + peer_deconfigured = BGP_ERROR_CEASE_PEER_DECONFIGURED, + administrative_reset = BGP_ERROR_CEASE_ADMINISTRATIVE_RESET, + connection_rejected = BGP_ERROR_CEASE_CONNECTION_REJECTED, + other_configuration_change = BGP_ERROR_CEASE_OTHER_CONFIGURATION_CHANGE, + connection_collision_resolution = BGP_ERROR_CEASE_CONNECTION_COLLISION_RESOLUTION, + out_of_resources = BGP_ERROR_CEASE_OUT_OF_RESOURCES, + hard_reset = BGP_ERROR_CEASE_HARD_RESET, + bfd_down = BGP_ERROR_CEASE_BFD_DOWN, +) + +# BGP route refresh error subcode constants +(FTL_ATTR_BGP_NOTIFICATION_ERROR_ROUTE_REFRESH_TO_STR, + FTL_ATTR_BGP_NOTIFICATION_ERROR_ROUTE_REFRESH_FROM_STR) = init_const_mappings( + refresh_reserved = BGP_ERROR_ROUTE_REFRESH_RESERVED, + refresh_invalid_message_length = BGP_ERROR_ROUTE_REFRESH_INVALID_MESSAGE_LENGTH, +) + +# BGP notification code/subcode code-to-constant map +FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_TO_STR = { + BGP_ERROR_MESSAGE_HEADER: FTL_ATTR_BGP_NOTIFICATION_ERROR_MESSAGE_HEADER_TO_STR, + BGP_ERROR_OPEN_MESSAGE: FTL_ATTR_BGP_NOTIFICATION_ERROR_OPEN_MESSAGE_TO_STR, + BGP_ERROR_UPDATE_MESSAGE: FTL_ATTR_BGP_NOTIFICATION_ERROR_UPDATE_MESSAGE_TO_STR, + BGP_ERROR_FSM: FTL_ATTR_BGP_NOTIFICATION_ERROR_FSM_TO_STR, + BGP_ERROR_CEASE: FTL_ATTR_BGP_NOTIFICATION_ERROR_CEASE_TO_STR, + BGP_ERROR_ROUTE_REFRESH: FTL_ATTR_BGP_NOTIFICATION_ERROR_ROUTE_REFRESH_TO_STR, +} + +# BGP notification code/subcode constant-to-code map +FTL_ATTR_BGP_NOTIFICATION_ERROR_SUBCODE_FROM_STR = dict( + message_header = FTL_ATTR_BGP_NOTIFICATION_ERROR_MESSAGE_HEADER_FROM_STR, + open_message = FTL_ATTR_BGP_NOTIFICATION_ERROR_OPEN_MESSAGE_FROM_STR, + update_message = FTL_ATTR_BGP_NOTIFICATION_ERROR_UPDATE_MESSAGE_FROM_STR, + fsm = FTL_ATTR_BGP_NOTIFICATION_ERROR_FSM_FROM_STR, + cease = FTL_ATTR_BGP_NOTIFICATION_ERROR_CEASE_FROM_STR, + route_refresh = FTL_ATTR_BGP_NOTIFICATION_ERROR_ROUTE_REFRESH_FROM_STR, +) + +###################### +# BGP OPEN CONSTANTS # +###################### + +# BGP open capability types +(FTL_ATTR_BGP_OPEN_CAPABILITY_TO_STR, + FTL_ATTR_BGP_OPEN_CAPABILITY_FROM_STR) = init_const_mappings( + reserved_0 = BGP_CAPABILITY_RESERVED_0, + bgp4mp = BGP_CAPABILITY_BGP4MP, + route_refresh = BGP_CAPABILITY_ROUTE_REFRESH, + outbound_filter = BGP_CAPABILITY_OUTBOUND_FILTER, + multiple_routes = BGP_CAPABILITY_MULTIPLE_ROUTES, + extended_next_hop = BGP_CAPABILITY_EXTENDED_NEXT_HOP, + bgp4mp_et = BGP_CAPABILITY_BGP4MP_ET, + bgpsec = BGP_CAPABILITY_BGPSEC, + multiple_labels = BGP_CAPABILITY_MULTIPLE_LABELS, + bgp_role = BGP_CAPABILITY_BGP_ROLE, + graceful_restart = BGP_CAPABILITY_GRACEFUL_RESTART, + as4 = BGP_CAPABILITY_AS4, + unknown_66 = BGP_CAPABILITY_UNKNOWN_66, + dynamic = BGP_CAPABILITY_DYNAMIC, + multisession = BGP_CAPABILITY_MULTISESSION, + addpath = BGP_CAPABILITY_ADDPATH, + enhanced_route_refresh = BGP_CAPABILITY_ENHANCED_ROUTE_REFRESH, + llgr = BGP_CAPABILITY_LLGR, + policy_distribution = BGP_CAPABILITY_POLICY_DISTRIBUTION, + fqdn = BGP_CAPABILITY_FQDN, + bfd = BGP_CAPABILITY_BFD, + software_version = BGP_CAPABILITY_SOFTWARE_VERSION, + prestd_route_refresh = BGP_CAPABILITY_PRESTD_ROUTE_REFRESH, + prestd_policy_distribution = BGP_CAPABILITY_PRESTD_POLICY_DISTRIBUTION, + prestd_outbound_filter = BGP_CAPABILITY_PRESTD_OUTBOUND_FILTER, + prestd_multisession = BGP_CAPABILITY_PRESTD_MULTISESSION, + prestd_fqdn = BGP_CAPABILITY_PRESTD_FQDN, + prestd_operational = BGP_CAPABILITY_PRESTD_OPERATIONAL, + reserved_255 = BGP_CAPABILITY_RESERVED_255, +) + +####################### +# BGP STATS CONSTANTS # +####################### + +# BGP stats MRT fixes integer types +FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH = 0 +FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH = 1 +FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH = 2 +FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH = 3 +FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI = 4 + +# BGP stats MRT fixes string types +FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH_STR = 'bgp4mp_addpath' +FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH_STR = 'bgp4mp_aslength' +FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH_STR = 'tdv2_addpath' +FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH_STR = 'tdv2_aslength' +FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI_STR = 'tdv2_nlri' + +# BGP stats MRT fixes constants +(FTL_ATTR_BGP_STATS_MRT_FIXES_TO_STR, + FTL_ATTR_BGP_STATS_MRT_FIXES_FROM_STR) = init_const_mappings(**{ + FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH_STR: FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ADDPATH, + FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH_STR: FTL_ATTR_BGP_STATS_MRT_FIXES_BGP4MP_ASLENGTH, + FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH_STR: FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ADDPATH, + FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH_STR: FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_ASLENGTH, + FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI_STR: FTL_ATTR_BGP_STATS_MRT_FIXES_TDV2_NLRI, +}) + +# BGP stats MRT entry type constants +(FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPE_TO_STR, + FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_TYPE_FROM_STR) = init_const_mappings( + null = MRT_NULL, + start = MRT_START, + die = MRT_DIE, + i_am_dead = MRT_I_AM_DEAD, + peer_down = MRT_PEER_DOWN, + bgp = MRT_BGP, + rip = MRT_RIP, + idrp = MRT_IDRP, + ripng = MRT_RIPNG, + bgp4plus = MRT_BGP4PLUS, + bgp4plus_01 = MRT_BGP4PLUS_01, + ospfv2 = MRT_OSPFV2, + table_dump = MRT_TABLE_DUMP, + table_dump_v2 = MRT_TABLE_DUMP_V2, + bgp4mp = MRT_BGP4MP, + bgp4mp_et = MRT_BGP4MP_ET, + isis = MRT_ISIS, + isis_et = MRT_ISIS_ET, + ospfv3 = MRT_OSPFV3, + ospfv3_et = MRT_OSPFV3_ET, +) + +# BGP stats MRT entry subtype BGP4MP constants +(FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_TO_STR, + FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_FROM_STR) = init_const_mappings( + entry = MRT_BGP4MP_ENTRY, + snapshot = MRT_BGP4MP_SNAPSHOT, + state_change = MRT_BGP4MP_STATE_CHANGE, + state_change_as4 = MRT_BGP4MP_STATE_CHANGE_AS4, + message = MRT_BGP4MP_MESSAGE, + message_addpath = MRT_BGP4MP_MESSAGE_ADDPATH, + message_local = MRT_BGP4MP_MESSAGE_LOCAL, + message_local_addpath = MRT_BGP4MP_MESSAGE_LOCAL_ADDPATH, + message_as4 = MRT_BGP4MP_MESSAGE_AS4, + message_as4_addpath = MRT_BGP4MP_MESSAGE_AS4_ADDPATH, + message_as4_local = MRT_BGP4MP_MESSAGE_AS4_LOCAL, + message_as4_local_addpath = MRT_BGP4MP_MESSAGE_AS4_LOCAL_ADDPATH, +) + +# BGP stats MRT entry subtype BGP4MP_ET constants +FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_ET_TO_STR = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_TO_STR +FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_ET_FROM_STR = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_FROM_STR + +# BGP stats MRT entry subtype table dump v2 constants +(FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_V2_TO_STR, + FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_V2_FROM_STR) = init_const_mappings( + peer_index_table = MRT_TABLE_DUMP_V2_PEER_INDEX_TABLE, + geo_peer_table = MRT_TABLE_DUMP_V2_GEO_PEER_TABLE, + rib_generic = MRT_TABLE_DUMP_V2_RIB_GENERIC, + rib_generic_addpath = MRT_TABLE_DUMP_V2_RIB_GENERIC_ADDPATH, + rib_ipv4_unicast = MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST, + rib_ipv4_multicast = MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST, + rib_ipv6_unicast = MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST, + rib_ipv6_multicast = MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST, + rib_ipv4_unicast_addpath = MRT_TABLE_DUMP_V2_RIB_IPV4_UNICAST_ADDPATH, + rib_ipv4_multicast_addpath = MRT_TABLE_DUMP_V2_RIB_IPV4_MULTICAST_ADDPATH, + rib_ipv6_unicast_addpath = MRT_TABLE_DUMP_V2_RIB_IPV6_UNICAST_ADDPATH, + rib_ipv6_multicast_addpath = MRT_TABLE_DUMP_V2_RIB_IPV6_MULTICAST_ADDPATH, +) + +# BGP stats MRT entry subtype BGP constants +(FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP_TO_STR, + FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP_FROM_STR) = init_const_mappings( + null = MRT_BGP_NULL, + update = MRT_BGP_UPDATE, + pref_update = MRT_BGP_PREF_UPDATE, + state_change = MRT_BGP_STATE_CHANGE, + sync = MRT_BGP_SYNC, + open = MRT_BGP_OPEN, + notify = MRT_BGP_NOTIFY, + keepalive = MRT_BGP_KEEPALIVE, +) + + # BGP stats MRT entry subtype table dump constants +(FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_TO_STR, + FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_FROM_STR) = init_const_mappings( + afi_ipv4 = AFI_IPV4, + afi_ipv6 = AFI_IPV6, +) + +# BGP stats MRT entry type/subtype type-to-constant map +FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TO_STR = { + MRT_BGP4MP: FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_TO_STR, + MRT_BGP4MP_ET: FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_ET_TO_STR, + MRT_TABLE_DUMP_V2: FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_V2_TO_STR, + MRT_BGP: FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP_TO_STR, + MRT_TABLE_DUMP: FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_TO_STR, +} + +# BGP stats MRT entry type/subtype constant-to-type map +FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_FROM_STR = dict( + bgp4mp = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_FROM_STR, + bgp4mp_et = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_BGP4MP_ET_FROM_STR, + table_dump_v2 = FTL_ATTR_BGP_STATS_MRT_BGP_ENTRY_SUBTYPE_TABLE_DUMP_V2_FROM_STR, +) + +# BGP stats BGP message type constants +(FTL_ATTR_BGP_STATS_BGP_MESSAGE_TYPE_TO_STR, + FTL_ATTR_BGP_STATS_BGP_MESSAGE_TYPE_FROM_STR) = init_const_mappings( + reserved = BGP_BGP4MP_RESERVED, + open = BGP_BGP4MP_OPEN, + update = BGP_BGP4MP_UPDATE, + notification = BGP_BGP4MP_NOTIFICATION, + keepalive = BGP_BGP4MP_KEEPALIVE, + route_refresh = BGP_BGP4MP_ROUTE_REFRESH, +) + +# BGP stats capability type constants +FTL_ATTR_BGP_STATS_BGP_CAPABILITY_TYPE_TO_STR = FTL_ATTR_BGP_OPEN_CAPABILITY_TO_STR +FTL_ATTR_BGP_STATS_BGP_CAPABILITY_TYPE_FROM_STR = FTL_ATTR_BGP_OPEN_CAPABILITY_FROM_STR + +# BGP stats BGP path attribute constants +(FTL_ATTR_BGP_STATS_BGP_PATH_ATTR_TO_STR, + FTL_ATTR_BGP_STATS_BGP_PATH_ATTR_FORM_STR) = init_const_mappings( + reserved = BGP_PATH_ATTR_RESERVED, + origin = BGP_PATH_ATTR_ORIGIN, + as_path = BGP_PATH_ATTR_AS_PATH, + next_hop = BGP_PATH_ATTR_NEXT_HOP, + multi_exit_disc = BGP_PATH_ATTR_MULTI_EXIT_DISC, + local_pref = BGP_PATH_ATTR_LOCAL_PREF, + atomic_aggregate = BGP_PATH_ATTR_ATOMIC_AGGREGATE, + aggregator = BGP_PATH_ATTR_AGGREGATOR, + communities = BGP_PATH_ATTR_COMMUNITIES, + originator_id = BGP_PATH_ATTR_ORIGINATOR_ID, + cluster_list = BGP_PATH_ATTR_CLUSTER_LIST, + dpa = BGP_PATH_ATTR_DPA, + advertiser = BGP_PATH_ATTR_ADVERTISER, + rcid_cluster_id = BGP_PATH_ATTR_RCID_CLUSTER_ID, + mp_reach_nlri = BGP_PATH_ATTR_MP_REACH_NLRI, + mp_unreach_nlri = BGP_PATH_ATTR_MP_UNREACH_NLRI, + extended_communities = BGP_PATH_ATTR_EXTENDED_COMMUNITIES, + as4_path = BGP_PATH_ATTR_AS4_PATH, + as4_aggregator = BGP_PATH_ATTR_AS4_AGGREGATOR, + safi_specific = BGP_PATH_ATTR_SAFI_SPECIFIC, + connector = BGP_PATH_ATTR_CONNECTOR, + as_pathlimit = BGP_PATH_ATTR_AS_PATHLIMIT, + pmsi_tunnel = BGP_PATH_ATTR_PMSI_TUNNEL, + tunnel_encapsulation = BGP_PATH_ATTR_TUNNEL_ENCAPSULATION, + traffic_engineering = BGP_PATH_ATTR_TRAFFIC_ENGINEERING, + ipv6_extended_communities = BGP_PATH_ATTR_IPV6_EXTENDED_COMMUNITIES, + aigp = BGP_PATH_ATTR_AIGP, + pe_distinguisher_labels = BGP_PATH_ATTR_PE_DISTINGUISHER_LABELS, + entropy_label_capability = BGP_PATH_ATTR_ENTROPY_LABEL_CAPABILITY, + ls = BGP_PATH_ATTR_LS, + vendor_30 = BGP_PATH_ATTR_VENDOR_30, + vendor_31 = BGP_PATH_ATTR_VENDOR_31, + large_communities = BGP_PATH_ATTR_LARGE_COMMUNITIES, + bgpsec_path = BGP_PATH_ATTR_BGPSEC_PATH, + community_container = BGP_PATH_ATTR_COMMUNITY_CONTAINER, + only_to_customer = BGP_PATH_ATTR_ONLY_TO_CUSTOMER, + domain_path = BGP_PATH_ATTR_DOMAIN_PATH, + sfp = BGP_PATH_ATTR_SFP, + bfd_discriminator = BGP_PATH_ATTR_BFD_DISCRIMINATOR, + nhc_tmp = BGP_PATH_ATTR_NHC_TMP, + prefix_sid = BGP_PATH_ATTR_PREFIX_SID, + attr_set = BGP_PATH_ATTR_ATTR_SET, + vendor_129 = BGP_PATH_ATTR_VENDOR_129, + vendor_241 = BGP_PATH_ATTR_VENDOR_241, + vendor_242 = BGP_PATH_ATTR_VENDOR_242, + vendor_243 = BGP_PATH_ATTR_VENDOR_243, + reserved_for_dev = BGP_PATH_ATTR_RESERVED_FOR_DEV, +) + +####################### +# BGP ERROR CONSTANTS # +####################### + +# BGP error source integer types +FTL_ATTR_BGP_ERROR_SOURCE_MRT = 0 +FTL_ATTR_BGP_ERROR_SOURCE_LGL = 1 +FTL_ATTR_BGP_ERROR_SOURCE_PARSER = 2 + +# BGP error source string types +FTL_ATTR_BGP_ERROR_SOURCE_MRT_STR = FTL_MRT +FTL_ATTR_BGP_ERROR_SOURCE_LGL_STR = FTL_LGL +FTL_ATTR_BGP_ERROR_SOURCE_PARSER_STR = FTL_PARSER + +# BGP error source constants +(FTL_ATTR_BGP_ERROR_SOURCE_TO_STR, + FTL_ATTR_BGP_ERROR_SOURCE_FROM_STR) = init_const_mappings(**{ + FTL_ATTR_BGP_ERROR_SOURCE_MRT_STR: FTL_ATTR_BGP_ERROR_SOURCE_MRT, + FTL_ATTR_BGP_ERROR_SOURCE_LGL_STR: FTL_ATTR_BGP_ERROR_SOURCE_LGL, + FTL_ATTR_BGP_ERROR_SOURCE_PARSER_STR: FTL_ATTR_BGP_ERROR_SOURCE_PARSER, +}) + +# BGP error scope integer types +FTL_ATTR_BGP_ERROR_SCOPE_BASE = 0 +FTL_ATTR_BGP_ERROR_SCOPE_FILE = 1 +FTL_ATTR_BGP_ERROR_SCOPE_HEADER = 2 +FTL_ATTR_BGP_ERROR_SCOPE_FORMAT = 3 +FTL_ATTR_BGP_ERROR_SCOPE_DATA = 4 + +# BGP error scope string types +FTL_ATTR_BGP_ERROR_SCOPE_BASE_STR = 'base' +FTL_ATTR_BGP_ERROR_SCOPE_FILE_STR = 'file' +FTL_ATTR_BGP_ERROR_SCOPE_HEADER_STR = 'header' +FTL_ATTR_BGP_ERROR_SCOPE_FORMAT_STR = 'format' +FTL_ATTR_BGP_ERROR_SCOPE_DATA_STR = 'data' + +# BGP error scope constants +(FTL_ATTR_BGP_ERROR_SCOPE_TO_STR, + FTL_ATTR_BGP_ERROR_SCOPE_FROM_STR) = init_const_mappings(**{ + FTL_ATTR_BGP_ERROR_SCOPE_BASE_STR: FTL_ATTR_BGP_ERROR_SCOPE_BASE, + FTL_ATTR_BGP_ERROR_SCOPE_FILE_STR: FTL_ATTR_BGP_ERROR_SCOPE_FILE, + FTL_ATTR_BGP_ERROR_SCOPE_HEADER_STR: FTL_ATTR_BGP_ERROR_SCOPE_HEADER, + FTL_ATTR_BGP_ERROR_SCOPE_FORMAT_STR: FTL_ATTR_BGP_ERROR_SCOPE_FORMAT, + FTL_ATTR_BGP_ERROR_SCOPE_DATA_STR: FTL_ATTR_BGP_ERROR_SCOPE_DATA, +}) + +# BGP error reason integer types +FTL_ATTR_BGP_ERROR_REASON_RUNTIME = 0 +FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA = 1 +FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE = 2 +FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR = 3 +FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL = 4 +FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX = 5 +FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH = 6 +FTL_ATTR_BGP_ERROR_REASON_INVALID_AS = 7 +FTL_ATTR_BGP_ERROR_REASON_INVALID_IP = 8 + +# BGP error reason string types +FTL_ATTR_BGP_ERROR_REASON_RUNTIME_STR = 'runtime' +FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA_STR = 'missing_data' +FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE_STR = 'invalid_type' +FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR_STR = 'invalid_attr' +FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL_STR = 'invalid_protocol' +FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX_STR = 'invalid_prefix' +FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH_STR = 'invalid_aspath' +FTL_ATTR_BGP_ERROR_REASON_INVALID_AS_STR = 'invalid_as' +FTL_ATTR_BGP_ERROR_REASON_INVALID_IP_STR = 'invalid_ip' + +# BGP error reason constants +(FTL_ATTR_BGP_ERROR_REASON_TO_STR, + FTL_ATTR_BGP_ERROR_REASON_FROM_STR) = init_const_mappings(**{ + FTL_ATTR_BGP_ERROR_REASON_RUNTIME_STR: FTL_ATTR_BGP_ERROR_REASON_RUNTIME, + FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA_STR: FTL_ATTR_BGP_ERROR_REASON_MISSING_DATA, + FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_TYPE, + FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_ATTR, + FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_PROTOCOL, + FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_PREFIX, + FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_ASPATH, + FTL_ATTR_BGP_ERROR_REASON_INVALID_AS_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_AS, + FTL_ATTR_BGP_ERROR_REASON_INVALID_IP_STR: FTL_ATTR_BGP_ERROR_REASON_INVALID_IP, +}) diff --git a/src/ftlbgp/model/error.py b/src/ftlbgp/model/error.py new file mode 100644 index 0000000..bdcbcf9 --- /dev/null +++ b/src/ftlbgp/model/error.py @@ -0,0 +1,169 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import sys +import traceback + +# Local imports +from .const import FTL_ATTR_BGP_ERROR_SOURCE_MRT +from .const import FTL_ATTR_BGP_ERROR_SOURCE_LGL +from .const import FTL_ATTR_BGP_ERROR_SOURCE_PARSER +from .const import FTL_ATTR_BGP_ERROR_SCOPE_BASE +from .const import FTL_ATTR_BGP_ERROR_SCOPE_FILE +from .const import FTL_ATTR_BGP_ERROR_SCOPE_HEADER +from .const import FTL_ATTR_BGP_ERROR_SCOPE_FORMAT +from .const import FTL_ATTR_BGP_ERROR_SCOPE_DATA +from .const import FTL_ATTR_BGP_ERROR_REASON_RUNTIME + + +################## +# GENERIC ERRORS # +################## + +class FtlError(Exception): + """ Generic exception. + """ + # Prepare internals + source = FTL_ATTR_BGP_ERROR_SOURCE_PARSER + scope = FTL_ATTR_BGP_ERROR_SCOPE_BASE + + def __init__(self, message=None, reason=FTL_ATTR_BGP_ERROR_REASON_RUNTIME, record=None, data=None, trace=None, + error=None, exception=None, **_): + """ Initialize exception instance. + """ + # Prepare error details + self.message = message + self.reason = reason + self.record = record + self.data = data + self.trace = trace + + # Extract stack trace + if trace is True: + tbline = 'Traceback (most recent call last):' + self.trace, (cls, exc, tb) = list(), sys.exc_info() + for line in [tbline] + traceback.format_tb(tb) + traceback.format_exception_only(cls, exc): + for err in line.rstrip().split('\n'): + errline, indent = err.lstrip(), len(err) + self.trace.append((errline, indent - len(errline))) + + # Update with parent error + if error is not None: + self.__cause__ = None + self.__traceback__ = error.__traceback__ + if self.source == FTL_ATTR_BGP_ERROR_SOURCE_PARSER: + self.source = error.source + if self.reason == FTL_ATTR_BGP_ERROR_SCOPE_BASE: + self.scope = error.scope + if self.reason == FTL_ATTR_BGP_ERROR_REASON_RUNTIME: + self.reason = error.reason + if self.message is None: + self.message = error.message + if self.record is None: + self.record = record + if self.data is None: + self.data = error.data + if self.trace is None: + self.trace = error.trace + + # Update with parent exception + if exception is not None: + self.__cause__ = None + self.__traceback__ = exception.__traceback__ + excmsg = f'[{exception.__class__.__name__}] {str(exception)}' + self.message = f'{self.message}: {excmsg}' if self.message is not None else excmsg + + # Invoke super constructor + errmsg = f'ERR|{self.source}.{self.scope}.{self.reason}' + errmsg = f'[{errmsg}]' if self.record is None else f'[{errmsg}-{self.record}]'.upper() + if self.message is not None: + errmsg = f'{errmsg} {self.message}' + super().__init__(errmsg) + + +class FtlFileError(FtlError): + """ Generic file exception. + """ + # Update internals + scope = FTL_ATTR_BGP_ERROR_SCOPE_FILE + + +class FtlFormatError(FtlError): + """ Generic file format exception. + """ + # Update internals + scope = FTL_ATTR_BGP_ERROR_SCOPE_FORMAT + + +class FtlDataError(FtlError): + """ Generic file data exception. + """ + # Update internals + scope = FTL_ATTR_BGP_ERROR_SCOPE_DATA + + +############## +# MRT ERRORS # +############## + +class FtlMrtError(FtlError): + """ Base MRT exception. + """ + # Update internals + source = FTL_ATTR_BGP_ERROR_SOURCE_MRT + + +class FtlMrtHeaderError(FtlMrtError): + """ Exception for invalid MRT header. + """ + # Update internals + scope = FTL_ATTR_BGP_ERROR_SCOPE_HEADER + + +class FtlMrtFormatError(FtlMrtError, FtlFormatError): + """ Exception for invalid MRT format. + """ + + +class FtlMrtDataError(FtlMrtError, FtlDataError): + """ Exception for invalid MRT data. + """ + + +############## +# LGL ERRORS # +############## + +class FtlLglError(FtlError): + """ Base looking glass exception. + """ + # Update internals + source = FTL_ATTR_BGP_ERROR_SOURCE_LGL + + +class FtlLglHeaderError(FtlLglError): + """ Exception for invalid looking glass header. + """ + # Update internals + scope = FTL_ATTR_BGP_ERROR_SCOPE_HEADER + + +class FtlLglFormatError(FtlLglError, FtlFormatError): + """ Exception for invalid looking glass format. + """ + + +class FtlLglDataError(FtlLglError, FtlDataError): + """ Exception for invalid looking glass data. + """ diff --git a/src/ftlbgp/model/record.py b/src/ftlbgp/model/record.py new file mode 100644 index 0000000..ca0c5fd --- /dev/null +++ b/src/ftlbgp/model/record.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# Local imports +from .util import generate_spec_record + +# Available BGP records +FTL_RECORD_BGP_PEER_TABLE = 1 << 0 +FTL_RECORD_BGP_STATE_CHANGE = 1 << 1 +FTL_RECORD_BGP_ROUTE = 1 << 2 +FTL_RECORD_BGP_KEEP_ALIVE = 1 << 3 +FTL_RECORD_BGP_ROUTE_REFRESH = 1 << 4 +FTL_RECORD_BGP_NOTIFICATION = 1 << 5 +FTL_RECORD_BGP_OPEN = 1 << 6 +FTL_RECORD_BGP_STATS = 1 << 7 +FTL_RECORD_BGP_ERROR = 1 << 8 + +# Availabe BGP record names +FTL_RECORD_BGP_PEER_TABLE_NAME = 'peer_table' +FTL_RECORD_BGP_STATE_CHANGE_NAME = 'state_change' +FTL_RECORD_BGP_ROUTE_NAME = 'route' +FTL_RECORD_BGP_KEEP_ALIVE_NAME = 'keep_alive' +FTL_RECORD_BGP_ROUTE_REFRESH_NAME = 'route_refresh' +FTL_RECORD_BGP_NOTIFICATION_NAME = 'notification' +FTL_RECORD_BGP_OPEN_NAME = 'open' +FTL_RECORD_BGP_STATS_NAME = 'stats' +FTL_RECORD_BGP_ERROR_NAME = 'error' + +# BGP record specification +# [Format] spec := (field, value, default) +FtlRecordsBgp = generate_spec_record('FtlRecordsBgp', ( + (FTL_RECORD_BGP_PEER_TABLE_NAME, FTL_RECORD_BGP_PEER_TABLE, False), + (FTL_RECORD_BGP_STATE_CHANGE_NAME, FTL_RECORD_BGP_STATE_CHANGE, False), + (FTL_RECORD_BGP_ROUTE_NAME, FTL_RECORD_BGP_ROUTE, True), + (FTL_RECORD_BGP_KEEP_ALIVE_NAME, FTL_RECORD_BGP_KEEP_ALIVE, False), + (FTL_RECORD_BGP_ROUTE_REFRESH_NAME, FTL_RECORD_BGP_ROUTE_REFRESH, False), + (FTL_RECORD_BGP_NOTIFICATION_NAME, FTL_RECORD_BGP_NOTIFICATION, False), + (FTL_RECORD_BGP_OPEN_NAME, FTL_RECORD_BGP_OPEN, False), + (FTL_RECORD_BGP_STATS_NAME, FTL_RECORD_BGP_STATS, True), + (FTL_RECORD_BGP_ERROR_NAME, FTL_RECORD_BGP_ERROR, True), +), human=False) diff --git a/src/ftlbgp/model/stats.py b/src/ftlbgp/model/stats.py new file mode 100644 index 0000000..490fc50 --- /dev/null +++ b/src/ftlbgp/model/stats.py @@ -0,0 +1,117 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +from collections import namedtuple + +# Local imports +from .attr import FtlAttrsBgp +from .util import FTL_ATTR_SHIFT_HUMAN + + +def filter_stats_spec(no_support): + """ Remove unsupported stats fields from attributes spec. + """ + # Update supported stats attributes + stats, stats_fields, stats_human = FtlAttrsBgp.stats, dict(), None + update_fields, unsupported_fields = list(), 0 + for attr_name, attr_value in stats._asdict().items(): + if attr_value != stats.human: + if attr_value in {stats.ALL, stats.NONE, stats.DEFAULT}: + update_fields.append(attr_name) + stats_fields[attr_name] = attr_value + elif attr_name in stats.internal or any(attr_name.startswith(skip) for skip in no_support) is False: + stats_fields[attr_name] = attr_value + else: + unsupported_fields |= attr_value + else: + stats_fields[attr_name] = attr_value + stats_human = attr_name + + # Update supported stats attributes (human-readable) + stats_fields_human = {field: value << FTL_ATTR_SHIFT_HUMAN for field, value in stats_fields.items() + if field not in stats.internal or field in update_fields} + for field in update_fields: + stats_fields[field] &= ~unsupported_fields + stats_fields_human[field] = stats_fields[field] << FTL_ATTR_SHIFT_HUMAN + stats_fields[stats_human] = namedtuple(stats.human.__class__.__name__, + stats_fields_human.keys())(*stats_fields_human.values()) + + # Clone and return filtered attributes spec + attrs = FtlAttrsBgp._asdict() + attrs[stats.name] = namedtuple(stats.__class__.__name__, stats_fields.keys())(*stats_fields.values()) + return namedtuple(FtlAttrsBgp.__class__.__name__, attrs.keys())(*attrs.values()) + + +def init_stats_record(attributes, attrindexes, spec_attributes, stats_record): + """ Initialize default values for stats record. + """ + # Initialize parser stats values + try: + if spec_attributes & (attributes.parser_lifetime | attributes.human.parser_lifetime): + stats_record[attrindexes.parser_lifetime] = 0.0 + if spec_attributes & (attributes.parser_runtime | attributes.human.parser_runtime): + stats_record[attrindexes.parser_runtime] = 0.0 + if spec_attributes & (attributes.parser_errors | attributes.human.parser_errors): + stats_record[attrindexes.parser_errors] = dict() + except AttributeError: + pass + + # Initialize looking-glass stats values + try: + if spec_attributes & (attributes.lgl_runtime | attributes.human.lgl_runtime): + stats_record[attrindexes.lgl_runtime] = 0.0 + if spec_attributes & (attributes.lgl_entries | attributes.human.lgl_entries): + stats_record[attrindexes.lgl_entries] = 0 + if spec_attributes & (attributes.lgl_errors | attributes.human.lgl_errors): + stats_record[attrindexes.lgl_errors] = dict() + except AttributeError: + pass + + # Initialize MRT stats values + try: + if spec_attributes & (attributes.mrt_runtime | attributes.human.mrt_runtime): + stats_record[attrindexes.mrt_runtime] = 0.0 + if spec_attributes & (attributes.mrt_entries | attributes.human.mrt_entries): + stats_record[attrindexes.mrt_entries] = 0 + if spec_attributes & (attributes.mrt_errors | attributes.human.mrt_errors): + stats_record[attrindexes.mrt_errors] = dict() + if spec_attributes & (attributes.mrt_fixes | attributes.human.mrt_fixes): + stats_record[attrindexes.mrt_fixes] = dict() + if spec_attributes & (attributes.mrt_bgp_entry_types | attributes.human.mrt_bgp_entry_types): + stats_record[attrindexes.mrt_bgp_entry_types] = dict() + if spec_attributes & (attributes.mrt_bgp_message_types | attributes.human.mrt_bgp_message_types): + stats_record[attrindexes.mrt_bgp_message_types] = dict() + if spec_attributes & (attributes.mrt_bgp_attribute_types | attributes.human.mrt_bgp_attribute_types): + stats_record[attrindexes.mrt_bgp_attribute_types] = dict() + if spec_attributes & (attributes.mrt_bgp_capability_types | attributes.human.mrt_bgp_capability_types): + stats_record[attrindexes.mrt_bgp_capability_types] = dict() + except AttributeError: + pass + + # Initialize BGP stats values + try: + if spec_attributes & (attributes.bgp_routes_rib_ipv4 | attributes.human.bgp_routes_rib_ipv4): + stats_record[attrindexes.bgp_routes_rib_ipv4] = 0 + if spec_attributes & (attributes.bgp_routes_rib_ipv6 | attributes.human.bgp_routes_rib_ipv6): + stats_record[attrindexes.bgp_routes_rib_ipv6] = 0 + if spec_attributes & (attributes.bgp_routes_announce_ipv6 | attributes.human.bgp_routes_announce_ipv6): + stats_record[attrindexes.bgp_routes_announce_ipv6] = 0 + if spec_attributes & (attributes.bgp_routes_announce_ipv4 | attributes.human.bgp_routes_announce_ipv4): + stats_record[attrindexes.bgp_routes_announce_ipv4] = 0 + if spec_attributes & (attributes.bgp_routes_withdraw_ipv4 | attributes.human.bgp_routes_withdraw_ipv4): + stats_record[attrindexes.bgp_routes_withdraw_ipv4] = 0 + if spec_attributes & (attributes.bgp_routes_withdraw_ipv6 | attributes.human.bgp_routes_withdraw_ipv6): + stats_record[attrindexes.bgp_routes_withdraw_ipv6] = 0 + except AttributeError: + pass diff --git a/src/ftlbgp/model/util.py b/src/ftlbgp/model/util.py new file mode 100644 index 0000000..82abed9 --- /dev/null +++ b/src/ftlbgp/model/util.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +# flake8: noqa +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import functools +import json +import re +import sys +import threading +from collections import namedtuple + +# Local imports +from .const import FTL_ATTR +from .const import FTL_RECORD +from .error import FtlError + +# Module patching +FTL_PATCH_THREAD_LOCK = threading.Lock() +FTL_PATCH_LOCKED = '__patchlock__' +FTL_PATCH_HASH = '__patchhash__' + +# Bit shift for human-readable attributes +FTL_ATTR_SHIFT_HUMAN = 32 + + +def generate_spec_attributes(cls, spec): + """ Generate generic attributes specification instance. + """ + # Prepare spec fields + fields = dict(**{attr.name: attr for attr in spec}) + + # Return spec instance + return namedtuple(cls, fields.keys())(**fields) + + +def generate_spec_record(cls, spec, human=True, typed=None): + """ Generate generic record specification instance. + """ + # Extract name from class + parts = re.sub('([A-Z]+)', r' \1', cls).split() + parts = parts[2:] if len(parts) > 3 else parts[1:] + spectype, name = parts[0].lower(), '_'.join(p.lower() for p in parts[1:]) + argname = '_'.join((spectype, name) if typed is not None else (name, spectype)) + istyped = typed is not None + + # Prepare base record spec + # [Format] spec := (field, value, default) + base_spec = ( + ('internal', set(), None), + ('spec', spectype, None), + ('name', name, None), + ('argname', argname, None), + ('typed', istyped, None), + ('ALL', 0, None), + ('NONE', 0, None), + ('DEFAULT', 0, None), + ) + + # Prepare human-readable record spec + # [Format] spec := (field, value, default) + if human is True: + base_spec += ( + ('human', None, None), + ) + + # Prepare typed record spec + # [Format] spec := (field, value, default) + if istyped is True: + base_spec += ( + ('type', typed, True), + ) + + # Prepare record spec data + fields, values, internal = dict(), set(), {field for field, _, _ in base_spec} + + # Iterate record spec fields + for field, value, default in base_spec + spec: + + # Check values for duplicates + if default is not None: + if field in fields: + field_error = 'Internal' if field in internal else 'Existing' + raise FtlError(f'{field_error} field "{field}" redefined in model specification {cls}') + if value in values: + field_error = value.bit_length() - 1 + raise FtlError(f'Existing value "1 << {field_error}" redefined in model specification {cls}') + values.add(value) + + # Add value to all/default-type + fields['ALL'] |= value + if default is True: + fields['DEFAULT'] |= value + + # Update value fields + fields[field] = value + + # Update internal fields + if default is None: + fields['internal'].add(field) + + # Add human-readable record spec + if human is True: + fields_human = {field: value << FTL_ATTR_SHIFT_HUMAN for field, value in fields.items() + if field not in fields['internal'] or field in {'ALL', 'NONE', 'DEFAULT'}} + fields['human'] = namedtuple(f'{cls}Human', fields_human.keys())(**fields_human) + + # Return record spec instance + return namedtuple(cls, fields.keys())(**fields) + + +def apply_spec_records(cls, named_records, serialize, raise_on_errors, handle_error, records, attributes, spec_records, + spec_attributes): + """ Setup generic record templates based on given record/attributes spec. + """ + # Prepare attribute-to-index mapping + records_field_to_value = dict() + attrs_field_to_idx = dict() + attrs_field_to_human = dict() + + # Prepare template fields + template_fields = dict() + for record_name, record_value in records._asdict().items(): + if record_name not in records.internal: + record_attrs = getattr(attributes, record_name) + spec_attrs = getattr(spec_attributes, record_name) + + # Skip unrequested records + if not spec_records & record_value: + template_fields[record_name] = (tuple(), tuple, None) + continue + + # Prepare record type and template + record_attrs_field_to_idx = attrs_field_to_idx.setdefault(record_name, dict()) + record_attrs_field_to_human = attrs_field_to_human.setdefault(record_name, dict()) + record_type = record_name if (spec_attrs >> FTL_ATTR_SHIFT_HUMAN) & record_attrs.type else record_value + records_field_to_value[record_name] = record_type + record_init = list() + + # Map attributes to tuple fields/indexes + attr_fields, attr_index = list(), 0 + for attr_field, attr_value in record_attrs._asdict().items(): + if attr_field not in record_attrs.internal: + if spec_attrs & attr_value or (spec_attrs >> FTL_ATTR_SHIFT_HUMAN) & attr_value: + + # Prepare initial record value (None or auto-typed) + record_init_value = None + if record_attrs.typed is True: + if record_attrs.type == attr_value: + record_init_value = record_type + + # Update record template and index mapping + record_init.append(record_init_value) + record_attrs_field_to_idx[attr_field] = attr_index + record_attrs_field_to_human[attr_field] = (spec_attrs >> FTL_ATTR_SHIFT_HUMAN) & attr_value != 0 + + # Update record fields + attr_fields.append(attr_field) + attr_index += 1 + + # Prepare emit method + record = ''.join((part.capitalize() for part in record_name.split('_'))) + record_emit = namedtuple(f'{cls}{record}Record', attr_fields)._make if named_records else tuple + if serialize: + if named_records: + # pylint: disable-next=unnecessary-lambda-assignment + record_emit = lambda record, attr_fields=attr_fields: json.dumps(dict(zip(attr_fields, record))) + else: + # pylint: disable-next=unnecessary-lambda-assignment + record_emit = lambda record: ','.join(str(e)[1:-1].replace(',', '').replace('\'', '') + if isinstance(e, tuple) else str(e) + if e is not None else '' for e in record) + + # Register initialized record template + template_fields[record_name] = (tuple(record_init), record_emit, record_type) + + # Access stats record template + stats_init, stats_emit, stats_record = tuple(), tuple, None + try: + stats_init, stats_emit, _ = template_fields[attributes.stats.name] + stats_record = list(stats_init) + except AttributeError: + pass + + # Access error record template + error_init, error_emit, error_type = tuple(), tuple, None + try: + error_init, error_emit, error_type = template_fields[attributes.error.name] + except AttributeError: + pass + + # Finalize record templates + for record_name, (record_init, record_emit, record_type) in template_fields.items(): + + # Parametrize record error function + record_error = functools.partial(handle_error, raise_on_errors, error_init, error_emit, record_type, stats_emit, + stats_record) + + # Add record error function to record template + template_fields[record_name] = (record_init, record_emit, record_error) + + # Finalize record templates and generic record error function + record_templates = namedtuple(f'FtlRecordTemplates{cls}', template_fields.keys())(**template_fields) + record_error = functools.partial(handle_error, raise_on_errors, error_init, error_emit, error_type, stats_emit, + stats_record) + + # Return finalized record spec + return (record_templates, record_error, records_field_to_value, attrs_field_to_idx, attrs_field_to_human, stats_emit, + stats_record) + + +def patch_spec_records(module, records, attributes, records_field_to_value, attrs_field_to_idx, attrs_field_to_human): + """ Patch modules with given record/attributes spec. + """ + # Prepare record flags + const_to_record_flag = dict() + records_name = records.name.upper() + for record_field in records._fields: + if record_field not in records.internal: + record_flag = record_field in records_field_to_value + const_to_record_flag['_'.join((FTL_RECORD, records_name, record_field.upper()))] = record_flag + + # Prepare attribute indexes + const_to_attrs_idx = dict() + for attrs in attributes: + attrs_name = attrs.argname.upper() + field_to_idx = attrs_field_to_idx.get(attrs.name, dict()) + for attr_field in attrs._fields: + if attr_field not in attrs.internal: + attr_idx = field_to_idx.get(attr_field, -1) + const_to_attrs_idx['_'.join((FTL_ATTR, attrs_name, attr_field.upper()))] = attr_idx + + # Prepare human-readable attribute flags + const_to_human_attrs_flag = dict() + for attrs in attributes: + attrs_name = attrs.argname.upper() + field_to_human = attrs_field_to_human.get(attrs.name, dict()) + for attr_field in attrs._fields: + if attr_field not in attrs.internal: + attr_flag = field_to_human.get(attr_field, False) + const_to_human_attrs_flag['_'.join((FTL_ATTR, attrs_name, attr_field.upper(), 'HUMAN'))] = attr_flag + + # Prepare module + module_path = module.split('.', 1)[0] + modules_patched = set() + + # Prepare patch hash value + patch_hash = hash(tuple(hash(tuple(sorted(c.items()))) + for c in (const_to_record_flag, const_to_attrs_idx, const_to_human_attrs_flag))) + + # Prepare module patcher + def patch_module(module_name): + """ Recursively patch spec-related constants in parser (sub-)modules. + """ + # Prevent patching the same module multiple times + modules_patched.add(module_name) + + # Access module instance + module = sys.modules[module_name] + + # Patch module members + for member in sorted(dir(module)): + + # Patch record flags + if member in const_to_record_flag: + setattr(module, member, const_to_record_flag[member]) + + # Patch attribute indexes + elif member in const_to_attrs_idx: + setattr(module, member, const_to_attrs_idx[member]) + + # Patch human-readable attribute flags + elif member in const_to_human_attrs_flag: + setattr(module, member, const_to_human_attrs_flag[member]) + + # Recurse down + sub_module = getattr(getattr(module, member), '__module__', None) + if sub_module is not None and sub_module.startswith(f'{module_path}.'): + if sub_module not in modules_patched: + patch_module(sub_module) + continue + + # Lock module access based on patch hash value + with FTL_PATCH_THREAD_LOCK: + if getattr(sys.modules[module], FTL_PATCH_HASH, patch_hash) != patch_hash: + raise FtlError(f'Unable to lock parser module "{module}" - parallel use not allowed') + setattr(sys.modules[module], FTL_PATCH_LOCKED, getattr(sys.modules[module], FTL_PATCH_LOCKED, 0) + 1) + setattr(sys.modules[module], FTL_PATCH_HASH, patch_hash) + + # Patch all modules + patch_module(module) + + +def unpatch_spec_records(module): + """ Undo module patch (remove lock). + """ + # Unlock module access + with FTL_PATCH_THREAD_LOCK: + setattr(sys.modules[module], FTL_PATCH_LOCKED, getattr(sys.modules[module], FTL_PATCH_LOCKED, 0) - 1) + if getattr(sys.modules[module], FTL_PATCH_LOCKED, 0) == 0: + delattr(sys.modules[module], FTL_PATCH_LOCKED) + delattr(sys.modules[module], FTL_PATCH_HASH) diff --git a/src/ftlbgp/parser.py b/src/ftlbgp/parser.py new file mode 100644 index 0000000..e71803a --- /dev/null +++ b/src/ftlbgp/parser.py @@ -0,0 +1,373 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +# System imports +import bz2 +import contextlib +import functools +import gzip +import time +from collections import namedtuple + +# Local imports +from .model.record import FtlRecordsBgp +from .model.error import FtlError +from .model.error import FtlFileError +from .model.error import FtlFormatError +from .model.const import FTL_MRT +from .model.const import FTL_LGL +from .model.const import FTL_BGP +from .model.const import FTL_PARSER +from .model.util import apply_spec_records +from .model.util import patch_spec_records +from .model.util import unpatch_spec_records +from .model.stats import filter_stats_spec +from .model.stats import init_stats_record +from .data.util import handle_bgp_error +from .data.util import init_caches + +# Magic bytes +BZ2_MAGIC_BYTES = b'\x42\x5a\x68' +GZIP_MAGIC_BYTES = b'\x1f\x8b' + + +def FtlParser(*unpack_functions): + """ BGP parser decorator for applying predefined arguments to given unpack functions. + + Positional arguments: + unpack_functions - One or more parsing functions decorated with @FtlParserFunc. + """ + # Check setup of unpack functions + for unpack_function in unpack_functions: + if hasattr(unpack_function, 'ftl_parser_open_file') is False: + raise FtlError(f'Parser function {unpack_function.__name__}() invalid (needs @FtlParserFunc decorator)') + + # Prepare supported data sources + parser_support = {FTL_PARSER, FTL_BGP} + parser_support |= {f.__name__.split('_')[1] for f in unpack_functions} & {FTL_MRT, FTL_LGL} + parser_no_support = {FTL_MRT, FTL_LGL} - parser_support + + # Prepare supported stats fields + FtlAttrsBgpSupport = filter_stats_spec(parser_no_support) + + # Initialize parser context + def decorator(parser): + """ Setup generic BGP parser. + """ + @functools.wraps(parser, assigned=set(functools.WRAPPER_ASSIGNMENTS) - {'__doc__'}) + def ftl_parser(named_records=True, serialize=False, use_cache=True, raise_on_errors=False, + bgp_records=FtlRecordsBgp.DEFAULT, **bgp_attributes): + """ This @FtlParser instance is used to read input files and generate a set of BGP records (Python tuples). + It must be used with a context manager (see sample usage below) and accepts the following arguments. + + Keyword arguments: + named_records - Return named tuples instead of plain unnamed tuple records. [Default: True] + serialize - Convert output records to JSON (if named) or CSV (if unnamed). [Default: False] + use_cache - Cache expensive low-entropy values (memory consumption < 1MB). [Default: True] + raise_on_errors - Raise exceptions and stop parsing in case of data errors. [Default: False] + bgp_records - Select the types of BgpRecord entries to be returned by the parser. + Supports bitwise logical operators OR, AND, NOT to specify multiple records. + {BGP_RECORDS_DOC} + {BGP_ATTRIBUTES_DOC} + + Returns: + parse() - Parse function that accepts a filename as single positional argument and generates + all specified BGP records on invocation. Input files can be provided in .bz2 or .gz + format. The parse function may be invoked multiple times within a single context. + + Raises: + FtlError - Generic parser or runtime error. + FtlFileError - Failure during access of input file. + FtlFormatError - Unexpected format of input file. + FtlDataError - Invalid data entry in input file. + + Sample usage: + + # Import parser + from ftlbgp import BgpParser + + # Parse all records + filename = ... + with BgpParser(bgp_records=BgpParser.bgp.records.ALL) as parse: + for record in parse(filename): + ... + + # Parse specific records (route and error) + filename = ... + with BgpParser(bgp_records=BgpParser.bgp.records.route | BgpParser.bgp.records.error) as parse: + for record in parse(filename): + ... + + # Parse all route attributes + filename = ... + with BgpParser(bgp_route=BgpParser.bgp.route.ALL) as parse: + for record in parse(filename): + ... + + # Parse specific route attributes (default and local_pref) + filename = ... + with BgpParser(bgp_route=BgpParser.bgp.route.DEFAULT | BgpParser.bgp.route.local_pref) as parse: + for record in parse(filename): + ... + """ + # Prepare total runtime of context maanager + parser_time_start = time.perf_counter() + + # Prepare record attributes and add default values + bgp_attrs = {attrs.name: bgp_attributes.pop(attrs.argname, attrs.DEFAULT) + for attrs in FtlAttrsBgpSupport if bgp_records & getattr(FtlRecordsBgp, attrs.name)} + + # Set empty record attributes for other records + bgp_attrs.update({attrs.name: attrs.NONE for attrs in FtlAttrsBgpSupport + if not bgp_records & getattr(FtlRecordsBgp, attrs.name)}) + + # Check remaining keywords + if len(bgp_attributes) > 0: + bgp_attribute = next(iter(bgp_attributes)) + if any(attrs.argname == bgp_attribute for attrs in FtlAttrsBgpSupport) is True: + raise FtlError(f'Argument "{bgp_attribute}" not allowed for parser - enable via "bgp_records"' + '(see @FtlParser decorator)') + raise FtlError(f'Invalid argument "{bgp_attribute}" for parser (see @FtlParser decorator)') + + # Create record attributes instance + bgp_attributes = namedtuple('FtlRecordAttrsBgp', bgp_attrs.keys())(**bgp_attrs) + + # Prepare BGP record templates + (bgp_templates, bgp_error, records_fields, attrs_fields, attrs_human, stats_emit, stats_record, + ) = apply_spec_records('Bgp', named_records, serialize, raise_on_errors, handle_bgp_error, FtlRecordsBgp, + FtlAttrsBgpSupport, bgp_records, bgp_attributes) + + # Patch BGP record specs + # NOTE: This call patches all spec-related constants that are imported + # NOTE: by the unpack functions' modules (including any child function modules) + for unpack_function in unpack_functions: + patch_spec_records(unpack_function.__module__, FtlRecordsBgp, FtlAttrsBgpSupport, records_fields, + attrs_fields, attrs_human) + + # Prepare record values and attr indexes + records_name, records_cls = FtlRecordsBgp.spec, FtlRecordsBgp.__class__.__name__ + records_spec = namedtuple(records_cls, records_fields.keys())(*records_fields.values()) + attrs_specs = dict() + for attrs_name, attrs in attrs_fields.items(): + attrs_cls = getattr(FtlAttrsBgpSupport, attrs_name).__class__.__name__ + attrs_specs[attrs_name] = namedtuple(attrs_cls, attrs.keys())(*attrs.values()) + unpack_bgp_spec = namedtuple('FtlBgp', (records_name, ) + tuple(attrs_specs)) + unpack_bgp_spec = unpack_bgp_spec(records_spec, *attrs_specs.values()) + + # Prepare human-readable caches + # NOTE: At the moment, there is only one simple timestamp cache (CACHE_TS) + # NOTE: We could easily introduce additional cache types (e.g. for CACHE_IP) + # NOTE: In that case, we should also add some cache fill stats below + unpack_caches = [init_caches() if use_cache else None for _ in range(len(unpack_functions))] + + # Initialize stats record + if bgp_records & FtlRecordsBgp.stats: + init_stats_record(FtlAttrsBgpSupport.stats, unpack_bgp_spec.stats, bgp_attributes.stats, stats_record) + + @contextlib.contextmanager + def parse(): + """ Context manager for BGP parsing. + """ + def unpack(filename): + """ Try to parse input file with given unpack functions. + """ + # Iterate unpack functions + idx, max_idx = 0, len(unpack_functions) - 1 + for idx, (unpack_function, caches) in enumerate(zip(unpack_functions, unpack_caches)): + source_runtime = f'{unpack_function.__name__.split("_")[1]}_runtime' + unpack_time_start = time.perf_counter() + + try: + # Invoke file opener + with unpack_function.ftl_parser_open_file(filename) as inputfile: + + # Yield unpacked records + yield from unpack_function(inputfile, caches, stats_record, bgp_templates, bgp_error) + + # Success + return + + # Handle format errors + except FtlFormatError: + + # Retry if unpack functions left + if idx < max_idx: + continue + + # Raise generic error if multiple parsers failed + if max_idx > 0: + raise FtlFormatError('Unknown input file format') # pylint: disable=raise-missing-from + + # Reraise specific format error + raise + + # Reraise already handled exceptions + except FtlError: + raise + + # Raise unhandled exceptions + except Exception as exc: + raise FtlError('Unhandled parser error', exception=exc) # pylint: disable=raise-missing-from + + # Update and yield stats record + finally: + + # Update stats record + if bgp_records & FtlRecordsBgp.stats: + now_time_end = time.perf_counter() + unpack_time = now_time_end - unpack_time_start + parser_time = now_time_end - parser_time_start + sattrs = FtlAttrsBgpSupport.stats + + # Add runtimes + if (bgp_attributes.stats + & (getattr(sattrs, source_runtime) | getattr(sattrs.human, source_runtime))): + stats_record[getattr(unpack_bgp_spec.stats, source_runtime)] += unpack_time + if bgp_attributes.stats & (sattrs.parser_runtime | sattrs.human.parser_runtime): + stats_record[unpack_bgp_spec.stats.parser_runtime] += unpack_time + if bgp_attributes.stats & (sattrs.parser_lifetime | sattrs.human.parser_lifetime): + stats_record[unpack_bgp_spec.stats.parser_lifetime] = parser_time + + # Yield final stats record + # NOTE: This record is cumulatively updated when parsing multiple files + yield stats_emit(stats_record) + + # Add record values and attr indexes + unpack.bgp = unpack_bgp_spec + + # Yield record generator + yield unpack + + # Unpatch BGP record specs + for unpack_function in unpack_functions: + unpatch_spec_records(unpack_function.__module__) + + # Return parser context + return parse() + + # Prepare parser documentation + line_indent = ''.join(('\n', ftl_parser.__doc__.split('\n')[-1], ' ' * 6)) + arg_indent = ' ' * (len(next(line for line in ftl_parser.__doc__.split('\n') + if FtlRecordsBgp.argname in line).split('-', 1)[0].lstrip())) + + def generate_doc(model, records=False): + """ Generate docstring for given record/attributes model. + """ + # Extract model defaults and available entries + model_doc_default, model_doc_available, model_doc_internal = list(), list(), list() + for field, value in model._asdict().items(): + if field not in model.internal or any(v is value for v in (model.ALL, model.NONE, model.DEFAULT)): + model_doc = model_doc_available + if value in {model.ALL, model.NONE, model.DEFAULT}: + model_doc = model_doc_internal + elif value & model.DEFAULT: + model_doc = model_doc_default + field_name = f'{parser.__name__}{".bgp" if records is False else ""}.{model.name}' + field_name += f'{".records" if records is True else "[.human]"}.{field}' + field_doc = ''.join((line_indent, arg_indent, ' ' * 4, field_name)) + model_doc.append(field_doc) + + # Generate and return model documentation + return ''.join(['[Default:', ' |'.join(model_doc_default), line_indent, arg_indent, ' ' * 2, ' Available:', + ''.join(model_doc_available + model_doc_internal).rstrip('|'), ']']) + + # Prepare records documentation + records_doc = generate_doc(FtlRecordsBgp, records=True).rstrip() + + # Prepare attributes documentation + attributes_doc = [line_indent[1:]] + for name, attrs in FtlAttrsBgpSupport._asdict().items(): + record_name = f'Bgp{"".join((p.capitalize() for p in name.split("_")))}Record' + attributes_doc.append(''.join(( + line_indent, attrs.argname, ' ' * (len(arg_indent) - len(attrs.argname)), + f'- Select [optionally human-readable] attribute to be included in {record_name} entries.', + line_indent, ' ' * (len(attrs.argname) + 2 + len(arg_indent) - len(attrs.argname)), + 'Supports bitwise logical operators (OR/AND/NOT) to specify multiple attributes.', line_indent, + ' ' * len(attrs.argname), ' ' * (len(arg_indent) - len(attrs.argname)), ' ' * 2, generate_doc(attrs) + ))) + + # Update parser documentation + attributes_doc_str = ''.join(attributes_doc).lstrip() + ftl_parser.__doc__ = ftl_parser.__doc__.format(BGP_RECORDS_DOC=records_doc, BGP_ATTRIBUTES_DOC=attributes_doc_str) + ftl_parser.__doc__ = ''.join((parser.__doc__.strip(), line_indent, line_indent[:-2], ftl_parser.__doc__.strip())) + + # Prepare spec filtering (generate key/value spec lists without internal fields) + # pylint: disable-next=unnecessary-lambda-assignment + filter_spec_record = lambda s: list(zip(*((k, v) for k, v in s._asdict().items() if k not in s.internal + or any(vs is v for vs in (s.ALL, s.NONE, s.DEFAULT))))) + # pylint: disable-next=unnecessary-lambda-assignment + filter_spec_attrs = lambda s: list(zip(*((k, v) for k, v in s._asdict().items() if k not in s.internal + or any(vs is v for vs in (s.ALL, s.NONE, s.DEFAULT, s.human))))) + + # Add record/attr specs (without internal fields) + records_name, (records_fields, records_values) = FtlRecordsBgp.spec, filter_spec_record(FtlRecordsBgp) + records_spec = namedtuple(FtlRecordsBgp.__class__.__name__, records_fields)(*records_values) + attrs_specs = dict() + for attrs_name, attrs in FtlAttrsBgpSupport._asdict().items(): + attrs_fields, attrs_values = filter_spec_attrs(attrs) + attrs_specs[attrs_name] = namedtuple(attrs.__class__.__name__, attrs_fields)(*attrs_values) + ftl_parser.bgp = namedtuple('FtlBgp', (records_name, ) + tuple(attrs_specs))(records_spec, *attrs_specs.values()) + + # Return parser + return ftl_parser + + # Return decorator for parser + return decorator + + +def FtlParserFunc(method=None, text_input=None): + """ Decorate BGP parsing function. + + Keyword arguments: + text_input - If given, open input file as text with specified encoding (e.g. UTF-8). + """ + def decorator(unpack): + """ Setup generic BGP parsing function. + """ + @functools.wraps(unpack) + def ftl_parser_open_file(filename): + """ Open BGP file for reading. + """ + try: + # Read magic bytes + ftype = '' + with open(filename, 'rb') as fh: + ftype = fh.read(max(len(BZ2_MAGIC_BYTES), len(GZIP_MAGIC_BYTES))) + + # Prepare access mode + mode = 'rb' if text_input is None else 'rt' + + # Support bzip2, gzip, and uncompressed files + if ftype.startswith(BZ2_MAGIC_BYTES): + return bz2.open(filename, mode=mode, encoding=text_input) + if ftype.startswith(GZIP_MAGIC_BYTES): + return gzip.open(filename, mode=mode, encoding=text_input) + return open(filename, mode=mode, encoding=text_input) + + # Invalid input file + except Exception as exc: + raise FtlFileError(exception=exc) # pylint: disable=raise-missing-from + + # Failure + return None + + # Add file opener to parser function + unpack.ftl_parser_open_file = ftl_parser_open_file + + # Return parser function + return unpack + + # Return decorator for parser function + if method is None: + return decorator + return decorator(method) diff --git a/src/ftlbgp/version.py b/src/ftlbgp/version.py new file mode 100644 index 0000000..ee1cc13 --- /dev/null +++ b/src/ftlbgp/version.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# pylint: disable=I0011,I0020,I0021,I0023 +# pylint: disable=W0149,W0717,R0902,R0904,R0911,R0912,R0913,R0914,R0915,R1702,R1734,R1735,R2044,R6103,C0103,C0209,C2001 +# pylint: enable=I0011 +""" ftlbgp +Copyright (C) 2014-2024 Leitwert GmbH + +This software is distributed under the terms of the MIT license. +It can be found in the LICENSE file or at https://opensource.org/licenses/MIT. + +Author Johann SCHLAMP +""" + +__version__ = '1.0.3'