pybgpkit 0.6.2__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pybgpkit-0.8.0/PKG-INFO +131 -0
- pybgpkit-0.8.0/README.md +110 -0
- pybgpkit-0.8.0/bgpkit/__init__.py +6 -0
- pybgpkit-0.8.0/bgpkit/bgpkit_asn.py +70 -0
- pybgpkit-0.8.0/bgpkit/bgpkit_broker.py +176 -0
- pybgpkit-0.8.0/bgpkit/bgpkit_community.py +63 -0
- pybgpkit-0.8.0/bgpkit/bgpkit_ip.py +49 -0
- pybgpkit-0.8.0/bgpkit/bgpkit_parser.py +3 -0
- pybgpkit-0.8.0/bgpkit/bgpkit_roas.py +64 -0
- pybgpkit-0.8.0/pybgpkit.egg-info/PKG-INFO +131 -0
- {pybgpkit-0.6.2 → pybgpkit-0.8.0}/pybgpkit.egg-info/SOURCES.txt +6 -3
- pybgpkit-0.8.0/pybgpkit.egg-info/requires.txt +5 -0
- pybgpkit-0.8.0/pyproject.toml +35 -0
- {pybgpkit-0.6.2/bgpkit → pybgpkit-0.8.0/tests}/test_integration.py +16 -16
- pybgpkit-0.8.0/tests/test_smoke.py +30 -0
- pybgpkit-0.6.2/PKG-INFO +0 -163
- pybgpkit-0.6.2/README.md +0 -142
- pybgpkit-0.6.2/bgpkit/__init__.py +0 -3
- pybgpkit-0.6.2/bgpkit/bgpkit_broker.py +0 -82
- pybgpkit-0.6.2/bgpkit/bgpkit_parser.py +0 -1
- pybgpkit-0.6.2/bgpkit/bgpkit_roas.py +0 -80
- pybgpkit-0.6.2/pybgpkit.egg-info/PKG-INFO +0 -163
- pybgpkit-0.6.2/pybgpkit.egg-info/requires.txt +0 -3
- pybgpkit-0.6.2/pyproject.toml +0 -6
- pybgpkit-0.6.2/setup.py +0 -26
- {pybgpkit-0.6.2 → pybgpkit-0.8.0}/LICENSE +0 -0
- {pybgpkit-0.6.2 → pybgpkit-0.8.0}/pybgpkit.egg-info/dependency_links.txt +0 -0
- {pybgpkit-0.6.2 → pybgpkit-0.8.0}/pybgpkit.egg-info/top_level.txt +0 -0
- {pybgpkit-0.6.2 → pybgpkit-0.8.0}/setup.cfg +0 -0
pybgpkit-0.8.0/PKG-INFO
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pybgpkit
|
|
3
|
+
Version: 0.8.0
|
|
4
|
+
Summary: BGPKIT tools Python bindings
|
|
5
|
+
Author-email: Mingwei Zhang <mingwei@bgpkit.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bgpkit/pybgpkit
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Requires-Python: >=3.9
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: pybgpkit-parser>=0.18.0
|
|
17
|
+
Requires-Dist: requests
|
|
18
|
+
Provides-Extra: dev
|
|
19
|
+
Requires-Dist: pytest; extra == "dev"
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
|
|
22
|
+
# PyBGPKIT
|
|
23
|
+
|
|
24
|
+
Python bindings for BGPKIT software. For all software offerings, please check out our GitHub
|
|
25
|
+
repository at <https://github.com/bgpkit>.
|
|
26
|
+
|
|
27
|
+
## SDKs
|
|
28
|
+
|
|
29
|
+
### BGPKIT Parser
|
|
30
|
+
|
|
31
|
+
See the [bgpkit-parser-py documentation](https://github.com/bgpkit/bgpkit-parser-py) for the full API.
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import bgpkit
|
|
35
|
+
|
|
36
|
+
# Full element parsing
|
|
37
|
+
parser = bgpkit.Parser(url="https://spaces.bgpkit.org/parser/update-example",
|
|
38
|
+
filters={"peer_ips": "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1"})
|
|
39
|
+
for elem in parser:
|
|
40
|
+
print(elem)
|
|
41
|
+
|
|
42
|
+
# Route-level parsing (faster, fewer fields)
|
|
43
|
+
for route in bgpkit.RouteParser(url="https://spaces.bgpkit.org/parser/update-example"):
|
|
44
|
+
print(route.prefix, route.as_path)
|
|
45
|
+
|
|
46
|
+
# Reusable filter objects
|
|
47
|
+
from bgpkit import Filter
|
|
48
|
+
f = Filter.peer_ip("185.1.8.65")
|
|
49
|
+
parser = bgpkit.Parser.from_filters(url, [f])
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
### BGPKIT Broker
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
import bgpkit
|
|
58
|
+
broker = bgpkit.Broker()
|
|
59
|
+
|
|
60
|
+
# Search MRT data files
|
|
61
|
+
items = broker.query(ts_start="2024-01-01T00:00:00Z", ts_end="2024-01-01T01:00:00Z")
|
|
62
|
+
print(len(items))
|
|
63
|
+
|
|
64
|
+
# Get latest files
|
|
65
|
+
latest = broker.latest()
|
|
66
|
+
|
|
67
|
+
# Query peer information
|
|
68
|
+
peers = broker.peers(asn=13335, full_feed=True)
|
|
69
|
+
|
|
70
|
+
# List collectors
|
|
71
|
+
collectors = broker.collectors(project="routeviews", active=True)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### BGPKIT IP Lookup
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
ip_info = bgpkit.IpLookup().query("1.1.1.1")
|
|
78
|
+
print(ip_info.country, ip_info.as_number, ip_info.as_name)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### BGPKIT ASN Lookup
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
result = bgpkit.AsnLookup().query(asn="13335", country="US")
|
|
85
|
+
for asn in result.data:
|
|
86
|
+
print(asn.asn, asn.name, asn.country)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### BGPKIT Community Lookup
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
entries = bgpkit.CommunityLookup().query(asn="13335", page_size=50)
|
|
93
|
+
for entry in entries:
|
|
94
|
+
print(entry.value, entry.description)
|
|
95
|
+
|
|
96
|
+
sources = bgpkit.CommunityLookup().sources()
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### BGPKIT ROAS Lookup
|
|
100
|
+
|
|
101
|
+
### BGPKIT ROAS Lookup
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
roas = bgpkit.Roas()
|
|
105
|
+
data = roas.query(asn=3333, date="2018-01-01")
|
|
106
|
+
for entry in data:
|
|
107
|
+
print(entry.prefix, entry.asn, entry.tal)
|
|
108
|
+
|
|
109
|
+
# Current ROAs only
|
|
110
|
+
current = roas.query(prefix="1.1.1.0/24", current=True)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Build and Publish
|
|
114
|
+
|
|
115
|
+
Push a version tag to build and publish automatically via GitHub Actions:
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
git tag v0.7.0
|
|
119
|
+
git push origin v0.7.0
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Manual release:
|
|
123
|
+
|
|
124
|
+
```
|
|
125
|
+
python -m pip install --upgrade build twine
|
|
126
|
+
python -m build
|
|
127
|
+
twine upload --skip-existing dist/*
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
PyPI Trusted Publishing is configured for automated releases. See `.github/workflows/release.yml`.
|
|
131
|
+
|
pybgpkit-0.8.0/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# PyBGPKIT
|
|
2
|
+
|
|
3
|
+
Python bindings for BGPKIT software. For all software offerings, please check out our GitHub
|
|
4
|
+
repository at <https://github.com/bgpkit>.
|
|
5
|
+
|
|
6
|
+
## SDKs
|
|
7
|
+
|
|
8
|
+
### BGPKIT Parser
|
|
9
|
+
|
|
10
|
+
See the [bgpkit-parser-py documentation](https://github.com/bgpkit/bgpkit-parser-py) for the full API.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
import bgpkit
|
|
14
|
+
|
|
15
|
+
# Full element parsing
|
|
16
|
+
parser = bgpkit.Parser(url="https://spaces.bgpkit.org/parser/update-example",
|
|
17
|
+
filters={"peer_ips": "185.1.8.65, 2001:7f8:73:0:3:fa4:0:1"})
|
|
18
|
+
for elem in parser:
|
|
19
|
+
print(elem)
|
|
20
|
+
|
|
21
|
+
# Route-level parsing (faster, fewer fields)
|
|
22
|
+
for route in bgpkit.RouteParser(url="https://spaces.bgpkit.org/parser/update-example"):
|
|
23
|
+
print(route.prefix, route.as_path)
|
|
24
|
+
|
|
25
|
+
# Reusable filter objects
|
|
26
|
+
from bgpkit import Filter
|
|
27
|
+
f = Filter.peer_ip("185.1.8.65")
|
|
28
|
+
parser = bgpkit.Parser.from_filters(url, [f])
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### BGPKIT Broker
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
import bgpkit
|
|
37
|
+
broker = bgpkit.Broker()
|
|
38
|
+
|
|
39
|
+
# Search MRT data files
|
|
40
|
+
items = broker.query(ts_start="2024-01-01T00:00:00Z", ts_end="2024-01-01T01:00:00Z")
|
|
41
|
+
print(len(items))
|
|
42
|
+
|
|
43
|
+
# Get latest files
|
|
44
|
+
latest = broker.latest()
|
|
45
|
+
|
|
46
|
+
# Query peer information
|
|
47
|
+
peers = broker.peers(asn=13335, full_feed=True)
|
|
48
|
+
|
|
49
|
+
# List collectors
|
|
50
|
+
collectors = broker.collectors(project="routeviews", active=True)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### BGPKIT IP Lookup
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
ip_info = bgpkit.IpLookup().query("1.1.1.1")
|
|
57
|
+
print(ip_info.country, ip_info.as_number, ip_info.as_name)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### BGPKIT ASN Lookup
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
result = bgpkit.AsnLookup().query(asn="13335", country="US")
|
|
64
|
+
for asn in result.data:
|
|
65
|
+
print(asn.asn, asn.name, asn.country)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### BGPKIT Community Lookup
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
entries = bgpkit.CommunityLookup().query(asn="13335", page_size=50)
|
|
72
|
+
for entry in entries:
|
|
73
|
+
print(entry.value, entry.description)
|
|
74
|
+
|
|
75
|
+
sources = bgpkit.CommunityLookup().sources()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### BGPKIT ROAS Lookup
|
|
79
|
+
|
|
80
|
+
### BGPKIT ROAS Lookup
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
roas = bgpkit.Roas()
|
|
84
|
+
data = roas.query(asn=3333, date="2018-01-01")
|
|
85
|
+
for entry in data:
|
|
86
|
+
print(entry.prefix, entry.asn, entry.tal)
|
|
87
|
+
|
|
88
|
+
# Current ROAs only
|
|
89
|
+
current = roas.query(prefix="1.1.1.0/24", current=True)
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Build and Publish
|
|
93
|
+
|
|
94
|
+
Push a version tag to build and publish automatically via GitHub Actions:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
git tag v0.7.0
|
|
98
|
+
git push origin v0.7.0
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Manual release:
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
python -m pip install --upgrade build twine
|
|
105
|
+
python -m build
|
|
106
|
+
twine upload --skip-existing dist/*
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
PyPI Trusted Publishing is configured for automated releases. See `.github/workflows/release.yml`.
|
|
110
|
+
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .bgpkit_parser import Filter, Parser, RouteElem, RouteParser
|
|
2
|
+
from .bgpkit_broker import Broker, BrokerItem, CollectorItem, PeerItem
|
|
3
|
+
from .bgpkit_roas import Roas, RoasItem
|
|
4
|
+
from .bgpkit_ip import IpLookup, IpInfo
|
|
5
|
+
from .bgpkit_asn import AsnLookup, AsnInfo, AsnLookupResult
|
|
6
|
+
from .bgpkit_community import CommunityLookup, CommunityEntry, CommunitySource
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Pagination:
|
|
9
|
+
next: Optional[str] = None
|
|
10
|
+
previous: Optional[str] = None
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class AsnInfo:
|
|
15
|
+
asn: int
|
|
16
|
+
name: str
|
|
17
|
+
country: str = ""
|
|
18
|
+
org_name: str = ""
|
|
19
|
+
org_id: str = ""
|
|
20
|
+
description: str = ""
|
|
21
|
+
website: str = ""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass
|
|
25
|
+
class AsnLookupResult:
|
|
26
|
+
data: List[AsnInfo]
|
|
27
|
+
count: int
|
|
28
|
+
page: int
|
|
29
|
+
page_size: int
|
|
30
|
+
updated_at: str = ""
|
|
31
|
+
pagination: Optional[Pagination] = None
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class AsnLookup:
|
|
35
|
+
"""BGPKIT ASN information lookup (v3/utils/asn)."""
|
|
36
|
+
|
|
37
|
+
def __init__(self, api_url: str = "https://api.bgpkit.com/v3/utils"):
|
|
38
|
+
self.base_url = api_url.rstrip("/")
|
|
39
|
+
|
|
40
|
+
def query(
|
|
41
|
+
self,
|
|
42
|
+
asn: str = None,
|
|
43
|
+
country: str = None,
|
|
44
|
+
search: str = None,
|
|
45
|
+
page: int = 1,
|
|
46
|
+
page_size: int = 100,
|
|
47
|
+
) -> AsnLookupResult:
|
|
48
|
+
params = {}
|
|
49
|
+
if asn:
|
|
50
|
+
params["asn"] = asn
|
|
51
|
+
if country:
|
|
52
|
+
params["country"] = country
|
|
53
|
+
if search:
|
|
54
|
+
params["search"] = search
|
|
55
|
+
if page:
|
|
56
|
+
params["page"] = page
|
|
57
|
+
if page_size:
|
|
58
|
+
params["page_size"] = min(page_size, 10000)
|
|
59
|
+
|
|
60
|
+
res = requests.get(f"{self.base_url}/asn", params=params).json()
|
|
61
|
+
data = [AsnInfo(**item) for item in res.get("data", [])]
|
|
62
|
+
pag = res.get("pagination", {})
|
|
63
|
+
return AsnLookupResult(
|
|
64
|
+
data=data,
|
|
65
|
+
count=res.get("count", 0),
|
|
66
|
+
page=res.get("page", page),
|
|
67
|
+
page_size=res.get("page_size", page_size),
|
|
68
|
+
updated_at=res.get("updatedAt", ""),
|
|
69
|
+
pagination=Pagination(**pag) if pag else None,
|
|
70
|
+
)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
import requests as requests
|
|
5
|
+
import urllib3
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def check_type(value: any, ty: type) -> bool:
|
|
9
|
+
try:
|
|
10
|
+
ty(value)
|
|
11
|
+
return True
|
|
12
|
+
except (ValueError, TypeError):
|
|
13
|
+
raise ValueError("invalid option input")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class BrokerItem:
|
|
18
|
+
ts_start: str
|
|
19
|
+
ts_end: str
|
|
20
|
+
collector_id: str
|
|
21
|
+
data_type: str
|
|
22
|
+
url: str
|
|
23
|
+
rough_size: int
|
|
24
|
+
exact_size: int
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class PeerItem:
|
|
29
|
+
ip: str
|
|
30
|
+
asn: int
|
|
31
|
+
collector: str
|
|
32
|
+
full_feed: bool = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class CollectorItem:
|
|
37
|
+
id: str
|
|
38
|
+
name: str
|
|
39
|
+
project: str
|
|
40
|
+
country: str
|
|
41
|
+
active: bool = True
|
|
42
|
+
latitude: Optional[float] = None
|
|
43
|
+
longitude: Optional[float] = None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@dataclass
|
|
47
|
+
class LatestResult:
|
|
48
|
+
"""Result of the /v3/broker/latest endpoint."""
|
|
49
|
+
items: List[BrokerItem] = field(default_factory=list)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Broker:
|
|
53
|
+
"""BGPKIT Broker v3 API wrapper.
|
|
54
|
+
|
|
55
|
+
Provides access to MRT data file search, peer information,
|
|
56
|
+
collector metadata, and latest file discovery.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def __init__(
|
|
60
|
+
self,
|
|
61
|
+
api_url: str = "https://api.bgpkit.com/v3/broker",
|
|
62
|
+
page_size: int = 100,
|
|
63
|
+
verify: bool = True,
|
|
64
|
+
):
|
|
65
|
+
self.base_url = api_url.rstrip("/")
|
|
66
|
+
self.page_size = int(page_size)
|
|
67
|
+
self.verify = verify
|
|
68
|
+
if not verify:
|
|
69
|
+
urllib3.disable_warnings()
|
|
70
|
+
|
|
71
|
+
def _paginate(self, endpoint: str, params: dict) -> dict:
|
|
72
|
+
"""Fetch all pages of a paginated broker endpoint."""
|
|
73
|
+
page = 1
|
|
74
|
+
all_data = []
|
|
75
|
+
|
|
76
|
+
while True:
|
|
77
|
+
params["page"] = page
|
|
78
|
+
params["page_size"] = self.page_size
|
|
79
|
+
res = requests.get(
|
|
80
|
+
f"{self.base_url}/{endpoint}",
|
|
81
|
+
params=params,
|
|
82
|
+
verify=self.verify,
|
|
83
|
+
).json()
|
|
84
|
+
|
|
85
|
+
if isinstance(res, dict):
|
|
86
|
+
data = res.get("data", [])
|
|
87
|
+
all_data.extend(data)
|
|
88
|
+
if len(data) < self.page_size:
|
|
89
|
+
break
|
|
90
|
+
elif isinstance(res, list):
|
|
91
|
+
all_data.extend(res)
|
|
92
|
+
break
|
|
93
|
+
else:
|
|
94
|
+
break
|
|
95
|
+
|
|
96
|
+
page += 1
|
|
97
|
+
|
|
98
|
+
result = res if isinstance(res, dict) else {"data": all_data}
|
|
99
|
+
result["data"] = all_data
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
def query(
|
|
103
|
+
self,
|
|
104
|
+
ts_start: str = None,
|
|
105
|
+
ts_end: str = None,
|
|
106
|
+
collector_id: str = None,
|
|
107
|
+
project: str = None,
|
|
108
|
+
data_type: str = None,
|
|
109
|
+
) -> List[BrokerItem]:
|
|
110
|
+
"""Search for MRT data files matching the given criteria."""
|
|
111
|
+
params = {}
|
|
112
|
+
if ts_start:
|
|
113
|
+
params["ts_start"] = ts_start
|
|
114
|
+
if ts_end:
|
|
115
|
+
params["ts_end"] = ts_end
|
|
116
|
+
if collector_id:
|
|
117
|
+
params["collector_id"] = collector_id
|
|
118
|
+
if project:
|
|
119
|
+
check_type(project, str)
|
|
120
|
+
params["project"] = project
|
|
121
|
+
if data_type:
|
|
122
|
+
check_type(data_type, str)
|
|
123
|
+
params["data_type"] = data_type
|
|
124
|
+
|
|
125
|
+
result = self._paginate("search", params)
|
|
126
|
+
return [BrokerItem(**item) for item in result.get("data", [])]
|
|
127
|
+
|
|
128
|
+
def latest(self) -> List[BrokerItem]:
|
|
129
|
+
"""Get latest MRT data files across projects."""
|
|
130
|
+
res = requests.get(
|
|
131
|
+
f"{self.base_url}/latest",
|
|
132
|
+
verify=self.verify,
|
|
133
|
+
).json()
|
|
134
|
+
if isinstance(res, list):
|
|
135
|
+
return [BrokerItem(**item) for item in res]
|
|
136
|
+
data = res.get("data", [])
|
|
137
|
+
return [BrokerItem(**item) for item in data]
|
|
138
|
+
|
|
139
|
+
def peers(
|
|
140
|
+
self,
|
|
141
|
+
full_feed: bool = None,
|
|
142
|
+
ip: str = None,
|
|
143
|
+
asn: int = None,
|
|
144
|
+
collector: str = None,
|
|
145
|
+
) -> List[PeerItem]:
|
|
146
|
+
"""Query BGP peer information."""
|
|
147
|
+
params = {}
|
|
148
|
+
if full_feed is not None:
|
|
149
|
+
params["full_feed"] = str(full_feed).lower()
|
|
150
|
+
if ip:
|
|
151
|
+
params["ip"] = ip
|
|
152
|
+
if asn:
|
|
153
|
+
params["asn"] = asn
|
|
154
|
+
if collector:
|
|
155
|
+
params["collector"] = collector
|
|
156
|
+
|
|
157
|
+
result = self._paginate("peers", params)
|
|
158
|
+
return [PeerItem(**item) for item in result.get("data", [])]
|
|
159
|
+
|
|
160
|
+
def collectors(
|
|
161
|
+
self,
|
|
162
|
+
project: str = None,
|
|
163
|
+
country: str = None,
|
|
164
|
+
active: bool = None,
|
|
165
|
+
) -> List[CollectorItem]:
|
|
166
|
+
"""Query MRT collector information."""
|
|
167
|
+
params = {}
|
|
168
|
+
if project:
|
|
169
|
+
params["project"] = project
|
|
170
|
+
if country:
|
|
171
|
+
params["country"] = country
|
|
172
|
+
if active is not None:
|
|
173
|
+
params["active"] = str(active).lower()
|
|
174
|
+
|
|
175
|
+
result = self._paginate("collectors", params)
|
|
176
|
+
return [CollectorItem(**item) for item in result.get("data", [])]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class CommunityEntry:
|
|
9
|
+
asn: int
|
|
10
|
+
value: str
|
|
11
|
+
description: str
|
|
12
|
+
as_name: str = ""
|
|
13
|
+
country: str = ""
|
|
14
|
+
source: str = ""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class CommunitySource:
|
|
19
|
+
id: str
|
|
20
|
+
name: str
|
|
21
|
+
url: str = ""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CommunityLookup:
|
|
25
|
+
"""BGPKIT community lookup (v3/communities)."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, api_url: str = "https://api.bgpkit.com/v3/communities"):
|
|
28
|
+
self.base_url = api_url.rstrip("/")
|
|
29
|
+
|
|
30
|
+
def query(
|
|
31
|
+
self,
|
|
32
|
+
asn: str = None,
|
|
33
|
+
value: str = None,
|
|
34
|
+
description: str = None,
|
|
35
|
+
as_name: str = None,
|
|
36
|
+
country: str = None,
|
|
37
|
+
page: int = 0,
|
|
38
|
+
page_size: int = 10,
|
|
39
|
+
) -> List[CommunityEntry]:
|
|
40
|
+
params = {}
|
|
41
|
+
if asn:
|
|
42
|
+
params["asn"] = asn
|
|
43
|
+
if value:
|
|
44
|
+
params["value"] = value
|
|
45
|
+
if description:
|
|
46
|
+
params["description"] = description
|
|
47
|
+
if as_name:
|
|
48
|
+
params["as_name"] = as_name
|
|
49
|
+
if country:
|
|
50
|
+
params["country"] = country
|
|
51
|
+
params["page"] = str(page)
|
|
52
|
+
params["page_size"] = str(min(page_size, 1000))
|
|
53
|
+
|
|
54
|
+
res = requests.get(self.base_url, params=params).json()
|
|
55
|
+
if isinstance(res, list):
|
|
56
|
+
return [CommunityEntry(**item) for item in res]
|
|
57
|
+
return [CommunityEntry(**item) for item in res.get("data", [])]
|
|
58
|
+
|
|
59
|
+
def sources(self) -> List[CommunitySource]:
|
|
60
|
+
res = requests.get(f"{self.base_url}/sources").json()
|
|
61
|
+
if isinstance(res, list):
|
|
62
|
+
return [CommunitySource(**item) for item in res]
|
|
63
|
+
return [CommunitySource(**item) for item in res.get("data", [])]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class AsnDetail:
|
|
9
|
+
asn: int
|
|
10
|
+
name: str
|
|
11
|
+
country: str
|
|
12
|
+
org_name: str = ""
|
|
13
|
+
org_id: str = ""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class IpInfo:
|
|
18
|
+
ip: str
|
|
19
|
+
country: str
|
|
20
|
+
asn: Optional[dict] = None
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def as_number(self) -> Optional[int]:
|
|
24
|
+
if self.asn:
|
|
25
|
+
return self.asn.get("asn")
|
|
26
|
+
return None
|
|
27
|
+
|
|
28
|
+
@property
|
|
29
|
+
def as_name(self) -> Optional[str]:
|
|
30
|
+
if self.asn:
|
|
31
|
+
return self.asn.get("name")
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class IpLookup:
|
|
36
|
+
"""BGPKIT IP address lookup (v3/utils/ip)."""
|
|
37
|
+
|
|
38
|
+
def __init__(self, api_url: str = "https://api.bgpkit.com/v3/utils"):
|
|
39
|
+
self.base_url = api_url.rstrip("/")
|
|
40
|
+
|
|
41
|
+
def query(self, ip: str = None, simple: bool = False) -> IpInfo:
|
|
42
|
+
params = {}
|
|
43
|
+
if ip:
|
|
44
|
+
params["ip"] = ip
|
|
45
|
+
if simple:
|
|
46
|
+
params["simple"] = "true"
|
|
47
|
+
|
|
48
|
+
res = requests.get(f"{self.base_url}/ip", params=params).json()
|
|
49
|
+
return IpInfo(**res)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import List, Optional
|
|
3
|
+
|
|
4
|
+
import requests
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class RoasItem:
|
|
9
|
+
prefix: str
|
|
10
|
+
asn: int
|
|
11
|
+
max_len: Optional[int] = None
|
|
12
|
+
tal: Optional[str] = None
|
|
13
|
+
current: bool = False
|
|
14
|
+
date_ranges: Optional[List[List[str]]] = None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Roas:
|
|
18
|
+
"""BGPKIT ROAS lookup (alpha API).
|
|
19
|
+
|
|
20
|
+
Queries the ROAS (Route Origination Authorization) database
|
|
21
|
+
for historical and current RPKI data.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, api_url: str = "https://alpha.api.bgpkit.com"):
|
|
25
|
+
self.base_url = api_url.rstrip("/")
|
|
26
|
+
|
|
27
|
+
def query(
|
|
28
|
+
self,
|
|
29
|
+
asn: int = None,
|
|
30
|
+
prefix: str = None,
|
|
31
|
+
date: str = None,
|
|
32
|
+
current: bool = None,
|
|
33
|
+
page: int = 1,
|
|
34
|
+
page_size: int = None,
|
|
35
|
+
) -> List[RoasItem]:
|
|
36
|
+
"""Query ROAS database.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
asn: AS number to filter by.
|
|
40
|
+
prefix: IP prefix to filter by.
|
|
41
|
+
date: Date string (YYYY-MM-DD) for historical lookup.
|
|
42
|
+
current: If True, return only currently valid ROAs.
|
|
43
|
+
page: Page number (1-indexed).
|
|
44
|
+
page_size: Results per page. Defaults to 5 (alpha API limitation).
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
List of RoasItem matching the query.
|
|
48
|
+
"""
|
|
49
|
+
params = {}
|
|
50
|
+
if asn is not None:
|
|
51
|
+
params["asn"] = asn
|
|
52
|
+
if prefix:
|
|
53
|
+
params["prefix"] = prefix
|
|
54
|
+
if date:
|
|
55
|
+
params["date"] = date
|
|
56
|
+
if current is not None:
|
|
57
|
+
params["current"] = str(current).lower()
|
|
58
|
+
params["page"] = str(page)
|
|
59
|
+
params["page_size"] = str(page_size if page_size is not None else 5)
|
|
60
|
+
|
|
61
|
+
res = requests.get(f"{self.base_url}/roas", params=params).json()
|
|
62
|
+
if isinstance(res, list):
|
|
63
|
+
return [RoasItem(**item) for item in res]
|
|
64
|
+
return [RoasItem(**item) for item in res.get("data", [])]
|