-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
23 changed files
with
1,717 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
# To get started with Dependabot version updates, you'll need to specify which | ||
# package ecosystems to update and where the package manifests are located. | ||
# Please see the documentation for all configuration options: | ||
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file | ||
|
||
version: 2 | ||
updates: | ||
- package-ecosystem: "pip" # See documentation for possible values | ||
directory: "/" # Location of package manifests | ||
schedule: | ||
interval: "weekly" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
name: Publish to PyPI | ||
|
||
on: | ||
push: | ||
tags: | ||
- 'v*' | ||
|
||
jobs: | ||
publish: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
id-token: write | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
|
||
- name: Set up Python | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: '3.13' | ||
|
||
- name: Install UV and build tools | ||
run: | | ||
pip install --upgrade uv | ||
uv pip install build | ||
- name: Verify CHANGELOG | ||
run: | | ||
if ! grep -q "## \[$(git describe --tags --abbrev=0 | sed 's/v//')\]" CHANGELOG.md; then | ||
echo "CHANGELOG.md not updated for this version" | ||
exit 1 | ||
fi | ||
- name: Build package | ||
run: uv build | ||
|
||
- name: Publish to PyPI | ||
uses: pypa/gh-action-pypi-publish@release/v1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
name: Test (linter/formatter/coverage) | ||
|
||
on: [push] | ||
|
||
jobs: | ||
build: | ||
runs-on: ubuntu-latest | ||
strategy: | ||
matrix: | ||
python-version: ["3.13"] | ||
|
||
steps: | ||
- uses: actions/checkout@v4 | ||
- name: Set up Python ${{ matrix.python-version }} | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: ${{ matrix.python-version }} | ||
|
||
- name: Install dependencies | ||
run: | | ||
pip install --upgrade uv | ||
uv pip sync --system --break-system-packages requirements.lock | ||
- name: Install package | ||
run: | | ||
pip install -e . | ||
- name: Run all linters and formatters | ||
run: | | ||
pre-commit run --all-files |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
repos: | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: 'v5.0.0' | ||
hooks: | ||
- id: check-yaml | ||
- id: end-of-file-fixer | ||
- id: trailing-whitespace | ||
- id: check-toml | ||
- id: check-added-large-files | ||
|
||
- repo: local | ||
hooks: | ||
- id: isort | ||
name: isort | ||
entry: isort | ||
language: system | ||
types: [python] | ||
|
||
- id: black | ||
name: black | ||
entry: black | ||
language: python | ||
types_or: [python, pyi] | ||
|
||
- id: ruff | ||
name: ruff | ||
entry: ruff check | ||
language: python | ||
types_or: [python, pyi] | ||
|
||
- id: mypy | ||
name: mypy | ||
entry: mypy | ||
language: python | ||
types_or: [python, pyi] | ||
require_serial: true | ||
|
||
- id: pylint | ||
name: pylint | ||
entry: pylint | ||
language: system | ||
types: [python] | ||
args: | ||
- --rcfile=pyproject.toml | ||
|
||
- id: pytest | ||
name: pytest | ||
entry: pytest | ||
language: system | ||
types: [python] | ||
pass_filenames: false | ||
args: ['--cov=irrexplorer_cli', '--cov-fail-under=100', 'tests/'] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Changelog | ||
All notable changes to irrexplorer-cli will be documented in this file. | ||
|
||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), | ||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). | ||
|
||
## [0.0.1] - 2024-12-21 | ||
### Added | ||
- Initial development release | ||
- Command-line interface for IRRexplorer.net queries | ||
- Prefix information lookup functionality | ||
- ASN details lookup functionality | ||
- Multiple output formats (json, csv, text) | ||
- Async support for efficient data retrieval | ||
- Integration with IRRexplorer v2 service | ||
- Support for Python 3.13+ | ||
- Documentation and usage examples | ||
|
||
### Dependencies | ||
- httpx for HTTP requests | ||
- typer for CLI interface | ||
- rich for text formatting | ||
|
||
[0.0.1]: https://github.com/kiraum/irrexplorer-cli/releases/tag/v0.0.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,90 @@ | ||
# irrexplorer-cli | ||
# IRRexplorer CLI | ||
|
||
A command-line interface to query and explore IRR & BGP data from IRRexplorer.net in real-time. | ||
|
||
## Overview | ||
|
||
IRRexplorer CLI provides a simple way to access and analyze Internet Routing Registry (IRR) and BGP data through the command line. It interfaces with the IRRexplorer v2 service to help network operators and administrators debug routing data and verify filtering strategies. | ||
|
||
## Features | ||
|
||
- Query prefix information | ||
- Lookup ASN details | ||
- Real-time data access from IRRexplorer.net | ||
- Easy-to-use command-line interface | ||
- Async support for efficient data retrieval | ||
|
||
## Installation | ||
|
||
```bash | ||
pip install irrexplorer-cli | ||
``` | ||
|
||
## Usage | ||
|
||
Query Prefix Information | ||
```bash | ||
irrexplorer prefix 192.0.2.0/24 | ||
``` | ||
|
||
Query ASN Information | ||
```bash | ||
irrexplorer asn AS64496 | ||
``` | ||
|
||
The `-f` or `--format` flag allows you to specify the output format: | ||
|
||
* `json`: Output results in JSON format | ||
* `csv`: Output results in CSV format | ||
* Default format is human-readable text | ||
|
||
## Requirements | ||
|
||
* Python 3.13+ | ||
* httpx | ||
* typer | ||
* rich | ||
|
||
## Development | ||
|
||
1. Clone the repository: | ||
```bash | ||
git clone https://github.com/kiraum/irrexplorer-cli.git | ||
``` | ||
|
||
2. Create/activate venv: | ||
```bash | ||
python3 -m venv venv | ||
. venv/bin/activate | ||
``` | ||
|
||
3. Install dependencies: | ||
```bash | ||
pip install --upgrade uv | ||
uv pip sync requirements.lock | ||
``` | ||
|
||
4. Run pre-commit tests before to push: | ||
```bash | ||
pre-commit run --all-files | ||
``` | ||
|
||
## Data Sources | ||
|
||
The CLI tool queries data from IRRexplorer.net, which includes: | ||
|
||
* IRR objects and relations (route(6) and as-sets) | ||
* RPKI ROAs and validation status | ||
* BGP origins from DFZ | ||
* RIRstats | ||
|
||
## Contributing | ||
Contributions are welcome! Please feel free to submit a Pull Request. | ||
|
||
## License | ||
|
||
See [LICENSE](LICENSE) file for details. | ||
|
||
## Credits | ||
|
||
This tool interfaces with IRRexplorer v2, a project maintained by Stichting NLNOG and DashCare BV. |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
"""Helper functions for the CLI.""" | ||
|
||
import ipaddress | ||
import re | ||
from typing import Any, Dict, List | ||
|
||
from irrexplorer_cli.models import PrefixInfo, PrefixResult | ||
|
||
|
||
def validate_prefix_format(prefix_input: str) -> bool: | ||
"""Validate IPv4 or IPv6 prefix format.""" | ||
try: | ||
ipaddress.ip_network(prefix_input) | ||
return True | ||
except ValueError: | ||
return False | ||
|
||
|
||
def validate_asn_format(asn_input: str) -> bool: | ||
"""Validate ASN format.""" | ||
asn_pattern = r"^(?:AS|as)?(\d+)$" | ||
match = re.match(asn_pattern, asn_input) | ||
if match: | ||
asn_number = int(match.group(1)) | ||
return 0 <= asn_number <= 4294967295 | ||
return False | ||
|
||
|
||
def format_prefix_result(result: PrefixInfo, prefix_type: str) -> str: | ||
"""Format a single prefix result for CSV output.""" | ||
prefix_result = PrefixResult( | ||
prefix=result.prefix, | ||
categoryOverall=result.categoryOverall, | ||
rir=result.rir, | ||
rpkiRoutes=result.rpkiRoutes, | ||
bgpOrigins=result.bgpOrigins, | ||
irrRoutes=result.irrRoutes, | ||
messages=result.messages, | ||
) | ||
|
||
rpki_status = "NOT_FOUND" | ||
if prefix_result.rpkiRoutes: | ||
rpki_status = prefix_result.rpkiRoutes[0].rpkiStatus | ||
|
||
bgp_origins = "|".join(str(asn) for asn in prefix_result.bgpOrigins) | ||
|
||
irr_routes = [] | ||
for db, routes in prefix_result.irrRoutes.items(): | ||
for route in routes: | ||
irr_routes.append(f"{db}:AS{route.asn}:{route.rpkiStatus}") | ||
irr_routes_str = "|".join(irr_routes) | ||
|
||
messages = "|".join(msg.text for msg in prefix_result.messages) | ||
|
||
return ( | ||
f"{prefix_type},{prefix_result.prefix},{prefix_result.categoryOverall}," | ||
f"{prefix_result.rir},{rpki_status},{bgp_origins},{irr_routes_str},{messages}" | ||
) | ||
|
||
|
||
def format_direct_origins(as_number: str, results: Dict[str, List[Dict[str, Any]]]) -> None: | ||
"""Format and print direct origin prefixes.""" | ||
for pfx_dict in results.get("directOrigin", []): | ||
pfx = PrefixInfo(**pfx_dict) | ||
rpki_status = "NOT_FOUND" | ||
if pfx.rpkiRoutes: | ||
rpki_status = pfx.rpkiRoutes[0].rpkiStatus | ||
|
||
bgp_origins = "|".join(str(as_number) for as_number in pfx.bgpOrigins) | ||
irr_routes = [] | ||
for db, routes in pfx.irrRoutes.items(): | ||
for route in routes: | ||
irr_routes.append(f"{db}:AS{route.asn}:{route.rpkiStatus}") | ||
irr_routes_str = "|".join(irr_routes) | ||
messages = "|".join(msg.text for msg in pfx.messages) | ||
|
||
print( | ||
f"\nDIRECT,{as_number},{pfx.prefix},{pfx.categoryOverall},{pfx.rir}," | ||
f"{rpki_status},{bgp_origins},{irr_routes_str},{messages}", | ||
end="", | ||
) | ||
|
||
|
||
def format_overlapping_prefixes(as_number: str, results: Dict[str, List[Dict[str, Any]]]) -> None: | ||
"""Format and print overlapping prefixes.""" | ||
for pfx_dict in results.get("overlaps", []): | ||
pfx = PrefixInfo(**pfx_dict) | ||
rpki_status = "NOT_FOUND" | ||
if pfx.rpkiRoutes: | ||
rpki_status = pfx.rpkiRoutes[0].rpkiStatus | ||
|
||
bgp_origins = "|".join(str(as_number) for as_number in pfx.bgpOrigins) | ||
irr_routes = [] | ||
for db, routes in pfx.irrRoutes.items(): | ||
for route in routes: | ||
irr_routes.append(f"{db}:AS{route.asn}:{route.rpkiStatus}") | ||
irr_routes_str = "|".join(irr_routes) | ||
messages = "|".join(msg.text for msg in pfx.messages) | ||
|
||
print( | ||
f"\nOVERLAP,{as_number},{pfx.prefix},{pfx.categoryOverall},{pfx.rir}," | ||
f"{rpki_status},{bgp_origins},{irr_routes_str},{messages}", | ||
end="", | ||
) | ||
|
||
|
||
def format_as_sets(as_number: str, sets_data: Dict[str, Dict[str, List[str]]]) -> None: | ||
"""Format and print AS sets.""" | ||
if sets_data and sets_data.get("setsPerIrr"): | ||
for irr, sets in sets_data["setsPerIrr"].items(): | ||
for as_set in sets: | ||
print(f"\nSET,{as_number},{as_set},{irr},N/A,N/A,N/A,N/A,N/A", end="") | ||
|
||
|
||
async def find_least_specific_prefix(direct_overlaps: List[PrefixInfo]) -> str | None: | ||
"""Find the least specific prefix from the overlaps.""" | ||
least_specific = None | ||
for info in direct_overlaps: | ||
if "/" in info.prefix: | ||
_, mask = info.prefix.split("/") | ||
if least_specific is None or int(mask) < int(least_specific.split("/")[1]): | ||
least_specific = info.prefix | ||
return least_specific |
Oops, something went wrong.