asndb 1.0.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.
- asndb-1.0.0/PKG-INFO +182 -0
- asndb-1.0.0/README.md +170 -0
- asndb-1.0.0/asndb/__init__.py +1 -0
- asndb-1.0.0/asndb/asndb.py +186 -0
- asndb-1.0.0/asndb/main.py +57 -0
- asndb-1.0.0/asndb.egg-info/PKG-INFO +182 -0
- asndb-1.0.0/asndb.egg-info/SOURCES.txt +12 -0
- asndb-1.0.0/asndb.egg-info/dependency_links.txt +1 -0
- asndb-1.0.0/asndb.egg-info/entry_points.txt +2 -0
- asndb-1.0.0/asndb.egg-info/requires.txt +4 -0
- asndb-1.0.0/asndb.egg-info/top_level.txt +1 -0
- asndb-1.0.0/pyproject.toml +34 -0
- asndb-1.0.0/setup.cfg +4 -0
- asndb-1.0.0/tests/test_asndb.py +133 -0
asndb-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asndb
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A simple Python CLI + library for looking up ASNs by IP or AS number. Uses BBOT.IO API.
|
|
5
|
+
Author: TheTechromancer
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: cachetools>=6.2.1
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: radixtarget>=4.0.1
|
|
11
|
+
Requires-Dist: typer>=0.19.2
|
|
12
|
+
|
|
13
|
+
# ASNDB
|
|
14
|
+
|
|
15
|
+
[](https://www.python.org)  [](https://github.com/blacklanternsecurity/radixtarget/blob/master/LICENSE) [](https://github.com/astral-sh/ruff) [](https://github.com/blacklanternsecurity/asndb/actions/workflows/tests.yml)
|
|
16
|
+
|
|
17
|
+
A simple Python CLI + library for instant lookup of ASN data by IP address, AS number, or organization. Uses BBOT.IO API (`asndb.api.bbot.io`).
|
|
18
|
+
|
|
19
|
+
ASNs are automatically cached for instant lookups and minimal network traffic.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pip install asndb
|
|
25
|
+
|
|
26
|
+
# or using uv
|
|
27
|
+
uv add asndb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage (CLI)
|
|
31
|
+
|
|
32
|
+
Note: To avoid rate limits, export your BBOT.IO API key as an environment variable:
|
|
33
|
+
```bash
|
|
34
|
+
export BBOT_IO_API_KEY=<your_api_key>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### IP Lookup
|
|
38
|
+
```bash
|
|
39
|
+
asndb ip 1.1.1.1
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Output:
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"asn": 13335,
|
|
46
|
+
"asn_name": "CLOUDFLARENET",
|
|
47
|
+
"country": "US",
|
|
48
|
+
"ip": "1.1.1.1",
|
|
49
|
+
"org": "Cloudflare, Inc.",
|
|
50
|
+
"org_id": "CLOUD14-ARIN",
|
|
51
|
+
"rir": "ARIN",
|
|
52
|
+
"subnets": [
|
|
53
|
+
"1.0.0.0/24",
|
|
54
|
+
"1.0.1.0/24"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### AS Number Lookup
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
asndb asn 13335
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Output:
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"asn": 13335,
|
|
69
|
+
"asn_name": "CLOUDFLARENET",
|
|
70
|
+
"country": "US",
|
|
71
|
+
"ip": "1.1.1.1",
|
|
72
|
+
"org": "Cloudflare, Inc.",
|
|
73
|
+
"org_id": "CLOUD14-ARIN",
|
|
74
|
+
"rir": "ARIN",
|
|
75
|
+
"subnets": [
|
|
76
|
+
"1.0.0.0/24",
|
|
77
|
+
"1.0.1.0/24"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Organization Lookup
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Look up an organization
|
|
86
|
+
asndb org CLOUD14-ARIN
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Output:
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"asns": [
|
|
93
|
+
13335,
|
|
94
|
+
14789,
|
|
95
|
+
395747,
|
|
96
|
+
394536
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### CLI Help
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
$ uv run asndb --help
|
|
105
|
+
|
|
106
|
+
Usage: asndb [OPTIONS] COMMAND [ARGS]...
|
|
107
|
+
|
|
108
|
+
╭─ Options ──────────────────────────────────────────────────────────────────────────────────╮
|
|
109
|
+
│ --install-completion Install completion for the current shell. │
|
|
110
|
+
│ --show-completion Show completion for the current shell, to copy it or │
|
|
111
|
+
│ customize the installation. │
|
|
112
|
+
│ --help Show this message and exit. │
|
|
113
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
114
|
+
╭─ Commands ─────────────────────────────────────────────────────────────────────────────────╮
|
|
115
|
+
│ ip Lookup ASN by IP address │
|
|
116
|
+
│ asn Lookup ASN by AS number │
|
|
117
|
+
│ org Get all the ASNs for an organization, by its registered organization ID, e.g. │
|
|
118
|
+
│ GOGL-ARIN │
|
|
119
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Usage (Python)
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from asndb import ASNDB
|
|
126
|
+
|
|
127
|
+
# Create a new ASNDB client
|
|
128
|
+
asndb = ASNDB()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Look up an IP address
|
|
132
|
+
asn = asndb.lookup_ip_sync("1.1.1.1")
|
|
133
|
+
# {
|
|
134
|
+
# "asn": 13335,
|
|
135
|
+
# "asn_name": "CLOUDFLARENET",
|
|
136
|
+
# "country": "US",
|
|
137
|
+
# "ip": "1.1.1.1",
|
|
138
|
+
# "org": "Cloudflare, Inc.",
|
|
139
|
+
# "org_id": "CLOUD14-ARIN",
|
|
140
|
+
# "rir": "ARIN",
|
|
141
|
+
# "subnets": [
|
|
142
|
+
# "1.0.0.0/24",
|
|
143
|
+
# "1.0.1.0/24"
|
|
144
|
+
# ]
|
|
145
|
+
# }
|
|
146
|
+
|
|
147
|
+
# Look up an AS number
|
|
148
|
+
asn = asndb.lookup_asn_sync(13335)
|
|
149
|
+
# {
|
|
150
|
+
# "asn": 13335,
|
|
151
|
+
# "asn_name": "CLOUDFLARENET",
|
|
152
|
+
# "country": "US",
|
|
153
|
+
# "ip": "1.1.1.1",
|
|
154
|
+
# "org": "Cloudflare, Inc.",
|
|
155
|
+
# "org_id": "CLOUD14-ARIN",
|
|
156
|
+
# "rir": "ARIN",
|
|
157
|
+
# "subnets": [
|
|
158
|
+
# "1.0.0.0/24",
|
|
159
|
+
# "1.0.1.0/24"
|
|
160
|
+
# ]
|
|
161
|
+
# }
|
|
162
|
+
|
|
163
|
+
# Look up an organization
|
|
164
|
+
org = asndb.lookup_org_sync("CLOUD14-ARIN")
|
|
165
|
+
# {
|
|
166
|
+
# "asns": [
|
|
167
|
+
# 13335,
|
|
168
|
+
# 14789,
|
|
169
|
+
# 395747,
|
|
170
|
+
# 394536
|
|
171
|
+
# ]
|
|
172
|
+
# }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Environment Variables
|
|
176
|
+
|
|
177
|
+
You can customize the behavior of the ASNDB client by exporting the following environment variables:
|
|
178
|
+
|
|
179
|
+
- `BBOT_IO_API_KEY`: Your BBOT.IO API key.
|
|
180
|
+
- `ASNDB_BASE_URL`: The base URL of the ASNDB API (default: https://asndb.api.bbot.io/v1).
|
|
181
|
+
- `ASNDB_TIMEOUT`: The timeout for the ASNDB API requests (default: 60 seconds).
|
|
182
|
+
- `ASNDB_CACHE_SIZE`: The size of the cache for the ASNDB API requests (default: 10000).
|
asndb-1.0.0/README.md
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# ASNDB
|
|
2
|
+
|
|
3
|
+
[](https://www.python.org)  [](https://github.com/blacklanternsecurity/radixtarget/blob/master/LICENSE) [](https://github.com/astral-sh/ruff) [](https://github.com/blacklanternsecurity/asndb/actions/workflows/tests.yml)
|
|
4
|
+
|
|
5
|
+
A simple Python CLI + library for instant lookup of ASN data by IP address, AS number, or organization. Uses BBOT.IO API (`asndb.api.bbot.io`).
|
|
6
|
+
|
|
7
|
+
ASNs are automatically cached for instant lookups and minimal network traffic.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install asndb
|
|
13
|
+
|
|
14
|
+
# or using uv
|
|
15
|
+
uv add asndb
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage (CLI)
|
|
19
|
+
|
|
20
|
+
Note: To avoid rate limits, export your BBOT.IO API key as an environment variable:
|
|
21
|
+
```bash
|
|
22
|
+
export BBOT_IO_API_KEY=<your_api_key>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### IP Lookup
|
|
26
|
+
```bash
|
|
27
|
+
asndb ip 1.1.1.1
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Output:
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"asn": 13335,
|
|
34
|
+
"asn_name": "CLOUDFLARENET",
|
|
35
|
+
"country": "US",
|
|
36
|
+
"ip": "1.1.1.1",
|
|
37
|
+
"org": "Cloudflare, Inc.",
|
|
38
|
+
"org_id": "CLOUD14-ARIN",
|
|
39
|
+
"rir": "ARIN",
|
|
40
|
+
"subnets": [
|
|
41
|
+
"1.0.0.0/24",
|
|
42
|
+
"1.0.1.0/24"
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### AS Number Lookup
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
asndb asn 13335
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Output:
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"asn": 13335,
|
|
57
|
+
"asn_name": "CLOUDFLARENET",
|
|
58
|
+
"country": "US",
|
|
59
|
+
"ip": "1.1.1.1",
|
|
60
|
+
"org": "Cloudflare, Inc.",
|
|
61
|
+
"org_id": "CLOUD14-ARIN",
|
|
62
|
+
"rir": "ARIN",
|
|
63
|
+
"subnets": [
|
|
64
|
+
"1.0.0.0/24",
|
|
65
|
+
"1.0.1.0/24"
|
|
66
|
+
]
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Organization Lookup
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Look up an organization
|
|
74
|
+
asndb org CLOUD14-ARIN
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Output:
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"asns": [
|
|
81
|
+
13335,
|
|
82
|
+
14789,
|
|
83
|
+
395747,
|
|
84
|
+
394536
|
|
85
|
+
]
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### CLI Help
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
$ uv run asndb --help
|
|
93
|
+
|
|
94
|
+
Usage: asndb [OPTIONS] COMMAND [ARGS]...
|
|
95
|
+
|
|
96
|
+
╭─ Options ──────────────────────────────────────────────────────────────────────────────────╮
|
|
97
|
+
│ --install-completion Install completion for the current shell. │
|
|
98
|
+
│ --show-completion Show completion for the current shell, to copy it or │
|
|
99
|
+
│ customize the installation. │
|
|
100
|
+
│ --help Show this message and exit. │
|
|
101
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
102
|
+
╭─ Commands ─────────────────────────────────────────────────────────────────────────────────╮
|
|
103
|
+
│ ip Lookup ASN by IP address │
|
|
104
|
+
│ asn Lookup ASN by AS number │
|
|
105
|
+
│ org Get all the ASNs for an organization, by its registered organization ID, e.g. │
|
|
106
|
+
│ GOGL-ARIN │
|
|
107
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Usage (Python)
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from asndb import ASNDB
|
|
114
|
+
|
|
115
|
+
# Create a new ASNDB client
|
|
116
|
+
asndb = ASNDB()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Look up an IP address
|
|
120
|
+
asn = asndb.lookup_ip_sync("1.1.1.1")
|
|
121
|
+
# {
|
|
122
|
+
# "asn": 13335,
|
|
123
|
+
# "asn_name": "CLOUDFLARENET",
|
|
124
|
+
# "country": "US",
|
|
125
|
+
# "ip": "1.1.1.1",
|
|
126
|
+
# "org": "Cloudflare, Inc.",
|
|
127
|
+
# "org_id": "CLOUD14-ARIN",
|
|
128
|
+
# "rir": "ARIN",
|
|
129
|
+
# "subnets": [
|
|
130
|
+
# "1.0.0.0/24",
|
|
131
|
+
# "1.0.1.0/24"
|
|
132
|
+
# ]
|
|
133
|
+
# }
|
|
134
|
+
|
|
135
|
+
# Look up an AS number
|
|
136
|
+
asn = asndb.lookup_asn_sync(13335)
|
|
137
|
+
# {
|
|
138
|
+
# "asn": 13335,
|
|
139
|
+
# "asn_name": "CLOUDFLARENET",
|
|
140
|
+
# "country": "US",
|
|
141
|
+
# "ip": "1.1.1.1",
|
|
142
|
+
# "org": "Cloudflare, Inc.",
|
|
143
|
+
# "org_id": "CLOUD14-ARIN",
|
|
144
|
+
# "rir": "ARIN",
|
|
145
|
+
# "subnets": [
|
|
146
|
+
# "1.0.0.0/24",
|
|
147
|
+
# "1.0.1.0/24"
|
|
148
|
+
# ]
|
|
149
|
+
# }
|
|
150
|
+
|
|
151
|
+
# Look up an organization
|
|
152
|
+
org = asndb.lookup_org_sync("CLOUD14-ARIN")
|
|
153
|
+
# {
|
|
154
|
+
# "asns": [
|
|
155
|
+
# 13335,
|
|
156
|
+
# 14789,
|
|
157
|
+
# 395747,
|
|
158
|
+
# 394536
|
|
159
|
+
# ]
|
|
160
|
+
# }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Environment Variables
|
|
164
|
+
|
|
165
|
+
You can customize the behavior of the ASNDB client by exporting the following environment variables:
|
|
166
|
+
|
|
167
|
+
- `BBOT_IO_API_KEY`: Your BBOT.IO API key.
|
|
168
|
+
- `ASNDB_BASE_URL`: The base URL of the ASNDB API (default: https://asndb.api.bbot.io/v1).
|
|
169
|
+
- `ASNDB_TIMEOUT`: The timeout for the ASNDB API requests (default: 60 seconds).
|
|
170
|
+
- `ASNDB_CACHE_SIZE`: The size of the cache for the ASNDB API requests (default: 10000).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from asndb.asndb import ASNDB
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import asyncio
|
|
4
|
+
from contextlib import contextmanager, suppress
|
|
5
|
+
|
|
6
|
+
import httpx
|
|
7
|
+
import cachetools
|
|
8
|
+
from radixtarget import RadixTarget
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ASNDBError(Exception):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ASNDBTimeoutError(ASNDBError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ASNDBClient:
|
|
20
|
+
BASE_URL = "https://asndb.api.bbot.io/v1"
|
|
21
|
+
DEFAULT_CACHE_SIZE = 10000
|
|
22
|
+
DEFAULT_TIMEOUT = 60
|
|
23
|
+
|
|
24
|
+
# Default record used when no ASN data can be found
|
|
25
|
+
UNKNOWN_ASN = {
|
|
26
|
+
"asn": 0,
|
|
27
|
+
"subnets": [],
|
|
28
|
+
"name": "Unknown",
|
|
29
|
+
"description": "Unknown ASN",
|
|
30
|
+
"country": "Unknown",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
def __init__(self, bbot_io_api_key=None):
|
|
34
|
+
self.bbot_io_api_key = os.getenv("BBOT_IO_API_KEY", None) or bbot_io_api_key
|
|
35
|
+
self.base_url = os.getenv("ASNDB_BASE_URL", None) or self.BASE_URL
|
|
36
|
+
self.timeout = int(os.getenv("ASNDB_TIMEOUT", self.DEFAULT_TIMEOUT))
|
|
37
|
+
self.client = httpx.AsyncClient(timeout=self.timeout)
|
|
38
|
+
|
|
39
|
+
self.headers = {}
|
|
40
|
+
if self.bbot_io_api_key:
|
|
41
|
+
self.headers["Authorization"] = f"Bearer {self.bbot_io_api_key}"
|
|
42
|
+
|
|
43
|
+
self._cache_size = os.getenv("ASNDB_CACHE_SIZE", self.DEFAULT_CACHE_SIZE)
|
|
44
|
+
# IPNetwork -> ASN Number
|
|
45
|
+
self._subnet_cache = RadixTarget()
|
|
46
|
+
# ASN Number -> ASN
|
|
47
|
+
self._asn_cache = cachetools.LRUCache(maxsize=self._cache_size)
|
|
48
|
+
|
|
49
|
+
self._event_loop = asyncio.get_event_loop()
|
|
50
|
+
|
|
51
|
+
async def request(self, url, **kwargs):
|
|
52
|
+
"""
|
|
53
|
+
Make a request to the ASNDB API, respecting its retry-after mechanism.
|
|
54
|
+
"""
|
|
55
|
+
start = time.time()
|
|
56
|
+
retry_after = 10
|
|
57
|
+
try:
|
|
58
|
+
while True:
|
|
59
|
+
elapsed = time.time() - start
|
|
60
|
+
if elapsed > self.timeout:
|
|
61
|
+
raise ASNDBTimeoutError(f"Timeout after {self.timeout} seconds")
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
response = await self.client.get(url, headers=self.headers, **kwargs)
|
|
65
|
+
except httpx.TimeoutException:
|
|
66
|
+
continue
|
|
67
|
+
except httpx.HTTPError as e:
|
|
68
|
+
raise ASNDBError(f"HTTP error: {e}")
|
|
69
|
+
|
|
70
|
+
status_code = getattr(response, "status_code", 0)
|
|
71
|
+
|
|
72
|
+
if status_code == 200:
|
|
73
|
+
try:
|
|
74
|
+
return response.json()
|
|
75
|
+
except Exception as e:
|
|
76
|
+
raise ASNDBError(f"Error parsing JSON: {e}")
|
|
77
|
+
elif status_code == 404:
|
|
78
|
+
return None
|
|
79
|
+
elif status_code == 429:
|
|
80
|
+
retry_after = int(response.headers.get("Retry-After", 10))
|
|
81
|
+
await asyncio.sleep(retry_after)
|
|
82
|
+
continue
|
|
83
|
+
else:
|
|
84
|
+
raise ASNDBError(f"Unexpected status code: {response.status_code}: {response.text}")
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
raise ASNDBError(f"Unexpected error while requesting {url}: {e}")
|
|
88
|
+
|
|
89
|
+
async def lookup_ip(self, ip: str):
|
|
90
|
+
"""
|
|
91
|
+
Given an IP address, return the ASN data and subnet.
|
|
92
|
+
|
|
93
|
+
For convenience, will put the parent subnet of the requested IP in the "subnet" key of the returned ASN data.
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
asn_number, subnet = self._cache_get_ip(ip)
|
|
97
|
+
asn = dict(self._cache_get_asn(asn_number))
|
|
98
|
+
asn["subnet"] = subnet
|
|
99
|
+
return asn
|
|
100
|
+
except KeyError:
|
|
101
|
+
url = f"{self.base_url}/ip/{ip}"
|
|
102
|
+
asn = await self.request(url)
|
|
103
|
+
if asn:
|
|
104
|
+
self._cache_put_asn(asn)
|
|
105
|
+
asn = dict(asn)
|
|
106
|
+
with suppress(KeyError):
|
|
107
|
+
asn_number, subnet = self._cache_get_ip(ip)
|
|
108
|
+
asn["subnet"] = subnet
|
|
109
|
+
return asn
|
|
110
|
+
return self.UNKNOWN_ASN
|
|
111
|
+
|
|
112
|
+
def lookup_ip_sync(self, ip: str):
|
|
113
|
+
return self._event_loop.run_until_complete(self.lookup_ip(ip))
|
|
114
|
+
|
|
115
|
+
async def ip_to_subnet(self, ip: str):
|
|
116
|
+
"""
|
|
117
|
+
Given an IP address, return the AS number and subnet it belongs to.
|
|
118
|
+
"""
|
|
119
|
+
# first make sure we have the ASN data for the IP
|
|
120
|
+
await self.lookup_ip(ip)
|
|
121
|
+
# then get it from the cache
|
|
122
|
+
result = self._cache_get_ip(ip)
|
|
123
|
+
if result is None:
|
|
124
|
+
raise KeyError(f"IP address {ip} not found in cache")
|
|
125
|
+
asn_number, subnet = result
|
|
126
|
+
return asn_number, subnet
|
|
127
|
+
|
|
128
|
+
def ip_to_subnet_sync(self, ip: str):
|
|
129
|
+
return self._event_loop.run_until_complete(self.ip_to_subnet(ip))
|
|
130
|
+
|
|
131
|
+
async def lookup_asn(self, asn: str):
|
|
132
|
+
try:
|
|
133
|
+
return self._cache_get_asn(asn)
|
|
134
|
+
except KeyError:
|
|
135
|
+
url = f"{self.base_url}/asn/{asn}"
|
|
136
|
+
asn = await self.request(url)
|
|
137
|
+
if asn:
|
|
138
|
+
self._cache_put_asn(asn)
|
|
139
|
+
return asn
|
|
140
|
+
return self.UNKNOWN_ASN
|
|
141
|
+
|
|
142
|
+
def lookup_asn_sync(self, asn: str):
|
|
143
|
+
return self._event_loop.run_until_complete(self.lookup_asn(asn))
|
|
144
|
+
|
|
145
|
+
async def lookup_org(self, org: str):
|
|
146
|
+
url = f"{self.base_url}/org/{org}"
|
|
147
|
+
return (await self.request(url)) or []
|
|
148
|
+
|
|
149
|
+
def lookup_org_sync(self, org: str):
|
|
150
|
+
return self._event_loop.run_until_complete(self.lookup_org(org))
|
|
151
|
+
|
|
152
|
+
def _cache_get_ip(self, ip: str) -> tuple[int, str]:
|
|
153
|
+
"""
|
|
154
|
+
ip -> asn, subnet
|
|
155
|
+
"""
|
|
156
|
+
result = self._subnet_cache.get(ip)
|
|
157
|
+
if result is None:
|
|
158
|
+
raise KeyError(f"IP address {ip} not found in cache")
|
|
159
|
+
asn, subnet = result
|
|
160
|
+
return asn, subnet
|
|
161
|
+
|
|
162
|
+
def _cache_get_asn(self, asn: int) -> dict:
|
|
163
|
+
"""
|
|
164
|
+
asn number -> asn data
|
|
165
|
+
"""
|
|
166
|
+
return self._asn_cache[asn]
|
|
167
|
+
|
|
168
|
+
def _cache_put_asn(self, asn: str):
|
|
169
|
+
asn_number = int(asn["asn"])
|
|
170
|
+
for subnet in asn.get("subnets", []):
|
|
171
|
+
self._subnet_cache.add(subnet, data=(asn_number, subnet))
|
|
172
|
+
self._asn_cache[asn_number] = asn
|
|
173
|
+
|
|
174
|
+
async def cleanup(self):
|
|
175
|
+
self._asn_cache.clear()
|
|
176
|
+
await self.client.aclose()
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
asndb_client = None
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def ASNDB():
|
|
183
|
+
global asndb_client
|
|
184
|
+
if asndb_client is None:
|
|
185
|
+
asndb_client = ASNDBClient()
|
|
186
|
+
return asndb_client
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import json
|
|
3
|
+
import typer
|
|
4
|
+
from contextlib import contextmanager
|
|
5
|
+
|
|
6
|
+
from asndb.asndb import ASNDB
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
app = typer.Typer()
|
|
10
|
+
|
|
11
|
+
asn_regex = re.compile(r"^(?:AS)?(\d+)$", re.IGNORECASE)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@contextmanager
|
|
15
|
+
def asndb_cli_context():
|
|
16
|
+
client = ASNDB()
|
|
17
|
+
try:
|
|
18
|
+
yield client
|
|
19
|
+
except Exception as e:
|
|
20
|
+
typer.echo(f"Error: {e}", err=True)
|
|
21
|
+
raise typer.Exit(1)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@app.command(help="Lookup ASN by IP address")
|
|
25
|
+
def ip(
|
|
26
|
+
ip_or_asn: str = typer.Argument(..., help="IP address to lookup"),
|
|
27
|
+
):
|
|
28
|
+
with asndb_cli_context() as client:
|
|
29
|
+
asn = client.lookup_ip_sync(ip_or_asn)
|
|
30
|
+
print(json.dumps(asn, indent=2))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@app.command(help="Lookup ASN by AS number")
|
|
34
|
+
def asn(
|
|
35
|
+
asn: str = typer.Argument(..., help="AS number to lookup"),
|
|
36
|
+
):
|
|
37
|
+
as_number = int(asn_regex.match(asn).group(1))
|
|
38
|
+
with asndb_cli_context() as client:
|
|
39
|
+
asn = client.lookup_asn_sync(as_number)
|
|
40
|
+
print(json.dumps(asn, indent=2))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.command(help="Get all the ASNs for an organization, by its registered organization ID, e.g. GOGL-ARIN")
|
|
44
|
+
def org(
|
|
45
|
+
org: str = typer.Argument(..., help="Organization to lookup, e.g. GOGL-ARIN or CLOUD14-ARIN"),
|
|
46
|
+
):
|
|
47
|
+
with asndb_cli_context() as client:
|
|
48
|
+
org = client.lookup_org_sync(org)
|
|
49
|
+
print(json.dumps(org, indent=2))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def main():
|
|
53
|
+
app()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
main()
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: asndb
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A simple Python CLI + library for looking up ASNs by IP or AS number. Uses BBOT.IO API.
|
|
5
|
+
Author: TheTechromancer
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: cachetools>=6.2.1
|
|
9
|
+
Requires-Dist: httpx>=0.28.1
|
|
10
|
+
Requires-Dist: radixtarget>=4.0.1
|
|
11
|
+
Requires-Dist: typer>=0.19.2
|
|
12
|
+
|
|
13
|
+
# ASNDB
|
|
14
|
+
|
|
15
|
+
[](https://www.python.org)  [](https://github.com/blacklanternsecurity/radixtarget/blob/master/LICENSE) [](https://github.com/astral-sh/ruff) [](https://github.com/blacklanternsecurity/asndb/actions/workflows/tests.yml)
|
|
16
|
+
|
|
17
|
+
A simple Python CLI + library for instant lookup of ASN data by IP address, AS number, or organization. Uses BBOT.IO API (`asndb.api.bbot.io`).
|
|
18
|
+
|
|
19
|
+
ASNs are automatically cached for instant lookups and minimal network traffic.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
pip install asndb
|
|
25
|
+
|
|
26
|
+
# or using uv
|
|
27
|
+
uv add asndb
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Usage (CLI)
|
|
31
|
+
|
|
32
|
+
Note: To avoid rate limits, export your BBOT.IO API key as an environment variable:
|
|
33
|
+
```bash
|
|
34
|
+
export BBOT_IO_API_KEY=<your_api_key>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### IP Lookup
|
|
38
|
+
```bash
|
|
39
|
+
asndb ip 1.1.1.1
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Output:
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"asn": 13335,
|
|
46
|
+
"asn_name": "CLOUDFLARENET",
|
|
47
|
+
"country": "US",
|
|
48
|
+
"ip": "1.1.1.1",
|
|
49
|
+
"org": "Cloudflare, Inc.",
|
|
50
|
+
"org_id": "CLOUD14-ARIN",
|
|
51
|
+
"rir": "ARIN",
|
|
52
|
+
"subnets": [
|
|
53
|
+
"1.0.0.0/24",
|
|
54
|
+
"1.0.1.0/24"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### AS Number Lookup
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
asndb asn 13335
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Output:
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"asn": 13335,
|
|
69
|
+
"asn_name": "CLOUDFLARENET",
|
|
70
|
+
"country": "US",
|
|
71
|
+
"ip": "1.1.1.1",
|
|
72
|
+
"org": "Cloudflare, Inc.",
|
|
73
|
+
"org_id": "CLOUD14-ARIN",
|
|
74
|
+
"rir": "ARIN",
|
|
75
|
+
"subnets": [
|
|
76
|
+
"1.0.0.0/24",
|
|
77
|
+
"1.0.1.0/24"
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Organization Lookup
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
# Look up an organization
|
|
86
|
+
asndb org CLOUD14-ARIN
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Output:
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"asns": [
|
|
93
|
+
13335,
|
|
94
|
+
14789,
|
|
95
|
+
395747,
|
|
96
|
+
394536
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### CLI Help
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
$ uv run asndb --help
|
|
105
|
+
|
|
106
|
+
Usage: asndb [OPTIONS] COMMAND [ARGS]...
|
|
107
|
+
|
|
108
|
+
╭─ Options ──────────────────────────────────────────────────────────────────────────────────╮
|
|
109
|
+
│ --install-completion Install completion for the current shell. │
|
|
110
|
+
│ --show-completion Show completion for the current shell, to copy it or │
|
|
111
|
+
│ customize the installation. │
|
|
112
|
+
│ --help Show this message and exit. │
|
|
113
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
114
|
+
╭─ Commands ─────────────────────────────────────────────────────────────────────────────────╮
|
|
115
|
+
│ ip Lookup ASN by IP address │
|
|
116
|
+
│ asn Lookup ASN by AS number │
|
|
117
|
+
│ org Get all the ASNs for an organization, by its registered organization ID, e.g. │
|
|
118
|
+
│ GOGL-ARIN │
|
|
119
|
+
╰────────────────────────────────────────────────────────────────────────────────────────────╯
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Usage (Python)
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from asndb import ASNDB
|
|
126
|
+
|
|
127
|
+
# Create a new ASNDB client
|
|
128
|
+
asndb = ASNDB()
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# Look up an IP address
|
|
132
|
+
asn = asndb.lookup_ip_sync("1.1.1.1")
|
|
133
|
+
# {
|
|
134
|
+
# "asn": 13335,
|
|
135
|
+
# "asn_name": "CLOUDFLARENET",
|
|
136
|
+
# "country": "US",
|
|
137
|
+
# "ip": "1.1.1.1",
|
|
138
|
+
# "org": "Cloudflare, Inc.",
|
|
139
|
+
# "org_id": "CLOUD14-ARIN",
|
|
140
|
+
# "rir": "ARIN",
|
|
141
|
+
# "subnets": [
|
|
142
|
+
# "1.0.0.0/24",
|
|
143
|
+
# "1.0.1.0/24"
|
|
144
|
+
# ]
|
|
145
|
+
# }
|
|
146
|
+
|
|
147
|
+
# Look up an AS number
|
|
148
|
+
asn = asndb.lookup_asn_sync(13335)
|
|
149
|
+
# {
|
|
150
|
+
# "asn": 13335,
|
|
151
|
+
# "asn_name": "CLOUDFLARENET",
|
|
152
|
+
# "country": "US",
|
|
153
|
+
# "ip": "1.1.1.1",
|
|
154
|
+
# "org": "Cloudflare, Inc.",
|
|
155
|
+
# "org_id": "CLOUD14-ARIN",
|
|
156
|
+
# "rir": "ARIN",
|
|
157
|
+
# "subnets": [
|
|
158
|
+
# "1.0.0.0/24",
|
|
159
|
+
# "1.0.1.0/24"
|
|
160
|
+
# ]
|
|
161
|
+
# }
|
|
162
|
+
|
|
163
|
+
# Look up an organization
|
|
164
|
+
org = asndb.lookup_org_sync("CLOUD14-ARIN")
|
|
165
|
+
# {
|
|
166
|
+
# "asns": [
|
|
167
|
+
# 13335,
|
|
168
|
+
# 14789,
|
|
169
|
+
# 395747,
|
|
170
|
+
# 394536
|
|
171
|
+
# ]
|
|
172
|
+
# }
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Environment Variables
|
|
176
|
+
|
|
177
|
+
You can customize the behavior of the ASNDB client by exporting the following environment variables:
|
|
178
|
+
|
|
179
|
+
- `BBOT_IO_API_KEY`: Your BBOT.IO API key.
|
|
180
|
+
- `ASNDB_BASE_URL`: The base URL of the ASNDB API (default: https://asndb.api.bbot.io/v1).
|
|
181
|
+
- `ASNDB_TIMEOUT`: The timeout for the ASNDB API requests (default: 60 seconds).
|
|
182
|
+
- `ASNDB_CACHE_SIZE`: The size of the cache for the ASNDB API requests (default: 10000).
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
asndb/__init__.py
|
|
4
|
+
asndb/asndb.py
|
|
5
|
+
asndb/main.py
|
|
6
|
+
asndb.egg-info/PKG-INFO
|
|
7
|
+
asndb.egg-info/SOURCES.txt
|
|
8
|
+
asndb.egg-info/dependency_links.txt
|
|
9
|
+
asndb.egg-info/entry_points.txt
|
|
10
|
+
asndb.egg-info/requires.txt
|
|
11
|
+
asndb.egg-info/top_level.txt
|
|
12
|
+
tests/test_asndb.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
asndb
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "asndb"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "A simple Python CLI + library for looking up ASNs by IP or AS number. Uses BBOT.IO API."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.9"
|
|
7
|
+
authors = [
|
|
8
|
+
{name = "TheTechromancer"}
|
|
9
|
+
]
|
|
10
|
+
dependencies = [
|
|
11
|
+
"cachetools>=6.2.1",
|
|
12
|
+
"httpx>=0.28.1",
|
|
13
|
+
"radixtarget>=4.0.1",
|
|
14
|
+
"typer>=0.19.2",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[tool.uv]
|
|
18
|
+
package = true
|
|
19
|
+
|
|
20
|
+
[tool.ruff]
|
|
21
|
+
line-length = 119
|
|
22
|
+
lint.ignore = ["E402", "E713", "E721", "E741", "F401", "F403", "F405"]
|
|
23
|
+
|
|
24
|
+
[dependency-groups]
|
|
25
|
+
dev = [
|
|
26
|
+
"maturin>=1.9.6",
|
|
27
|
+
"pytest>=8.4.2",
|
|
28
|
+
"pytest-asyncio>=1.2.0",
|
|
29
|
+
"pytest-cov>=7.0.0",
|
|
30
|
+
"ruff>=0.14.1",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
asndb = "asndb.main:main"
|
asndb-1.0.0/setup.cfg
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pytest
|
|
3
|
+
from asndb.asndb import ASNDBClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ASNDBTestClient(ASNDBClient):
|
|
7
|
+
def __init__(self, *args, **kwargs):
|
|
8
|
+
self.requested_urls = []
|
|
9
|
+
super().__init__(*args, **kwargs)
|
|
10
|
+
|
|
11
|
+
async def request(self, url, **kwargs):
|
|
12
|
+
self.requested_urls.append(url)
|
|
13
|
+
return await super().request(url, **kwargs)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.mark.asyncio
|
|
17
|
+
async def test_asndb():
|
|
18
|
+
asndb = ASNDBTestClient()
|
|
19
|
+
|
|
20
|
+
# IP lookup
|
|
21
|
+
asn = await asndb.lookup_ip("1.1.1.1")
|
|
22
|
+
assert asn["asn"] == 13335
|
|
23
|
+
assert asn["asn_name"] == "CLOUDFLARENET"
|
|
24
|
+
assert asn["subnet"] == "1.1.1.0/24"
|
|
25
|
+
assert asndb.requested_urls == ["https://asndb.api.bbot.io/v1/ip/1.1.1.1"]
|
|
26
|
+
|
|
27
|
+
# IP to subnet lookup
|
|
28
|
+
asn_number, subnet = await asndb.ip_to_subnet("1.1.1.1")
|
|
29
|
+
assert asn_number == 13335
|
|
30
|
+
assert subnet == "1.1.1.0/24"
|
|
31
|
+
|
|
32
|
+
# Bad IP
|
|
33
|
+
asn = await asndb.lookup_ip("127.0.0.1")
|
|
34
|
+
assert asn == ASNDBClient.UNKNOWN_ASN
|
|
35
|
+
assert asndb.requested_urls == [
|
|
36
|
+
"https://asndb.api.bbot.io/v1/ip/1.1.1.1",
|
|
37
|
+
"https://asndb.api.bbot.io/v1/ip/127.0.0.1",
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
# IP lookup with cache
|
|
41
|
+
asn = await asndb.lookup_ip("1.1.1.2")
|
|
42
|
+
assert asn["asn"] == 13335
|
|
43
|
+
assert asn["asn_name"] == "CLOUDFLARENET"
|
|
44
|
+
assert asn["subnet"] == "1.1.1.0/24"
|
|
45
|
+
assert len(asndb.requested_urls) == 2
|
|
46
|
+
|
|
47
|
+
# ASN Lookup
|
|
48
|
+
asn = await asndb.lookup_asn(13335)
|
|
49
|
+
assert asn["asn"] == 13335
|
|
50
|
+
assert asn["asn_name"] == "CLOUDFLARENET"
|
|
51
|
+
# we should not have made a second request
|
|
52
|
+
assert len(asndb.requested_urls) == 2
|
|
53
|
+
|
|
54
|
+
# Organization Lookup
|
|
55
|
+
org = await asndb.lookup_org("CLOUD14-ARIN")
|
|
56
|
+
assert org["asns"] == [13335, 14789, 395747, 394536]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_asndb_sync():
|
|
60
|
+
asndb = ASNDBTestClient()
|
|
61
|
+
|
|
62
|
+
# IP lookup
|
|
63
|
+
asn = asndb.lookup_ip_sync("1.1.1.1")
|
|
64
|
+
assert asn["asn"] == 13335
|
|
65
|
+
assert asn["asn_name"] == "CLOUDFLARENET"
|
|
66
|
+
assert asn["subnet"] == "1.1.1.0/24"
|
|
67
|
+
assert len(asndb.requested_urls) == 1
|
|
68
|
+
|
|
69
|
+
# IP to subnet lookup
|
|
70
|
+
asn_number, subnet = asndb.ip_to_subnet_sync("1.1.1.1")
|
|
71
|
+
assert asn_number == 13335
|
|
72
|
+
assert subnet == "1.1.1.0/24"
|
|
73
|
+
|
|
74
|
+
# Bad IP
|
|
75
|
+
asn = asndb.lookup_ip_sync("127.0.0.1")
|
|
76
|
+
assert asn == ASNDBClient.UNKNOWN_ASN
|
|
77
|
+
assert len(asndb.requested_urls) == 2
|
|
78
|
+
|
|
79
|
+
# IP lookup with cache
|
|
80
|
+
asn = asndb.lookup_ip_sync("1.1.1.2")
|
|
81
|
+
assert asn["asn"] == 13335
|
|
82
|
+
assert asn["subnet"] == "1.1.1.0/24"
|
|
83
|
+
assert len(asndb.requested_urls) == 2
|
|
84
|
+
|
|
85
|
+
# ASN Lookup
|
|
86
|
+
asn = asndb.lookup_asn_sync(13335)
|
|
87
|
+
assert asn["asn"] == 13335
|
|
88
|
+
assert asn["asn_name"] == "CLOUDFLARENET"
|
|
89
|
+
assert len(asndb.requested_urls) == 2
|
|
90
|
+
|
|
91
|
+
# Organization Lookup
|
|
92
|
+
org = asndb.lookup_org_sync("CLOUD14-ARIN")
|
|
93
|
+
assert org["asns"] == [13335, 14789, 395747, 394536]
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_asndb_cli(monkeypatch, capsys):
|
|
97
|
+
from asndb.main import main
|
|
98
|
+
import sys
|
|
99
|
+
|
|
100
|
+
# patch sys.exit to prevent the CLI from actually exiting
|
|
101
|
+
def mock_exit(code=0):
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
monkeypatch.setattr(sys, "exit", mock_exit)
|
|
105
|
+
|
|
106
|
+
# patch sys.argv to include the command
|
|
107
|
+
monkeypatch.setattr("sys.argv", ["asndb", "ip", "1.1.1.1"])
|
|
108
|
+
# run the CLI
|
|
109
|
+
main()
|
|
110
|
+
# capture the output
|
|
111
|
+
out, err = capsys.readouterr()
|
|
112
|
+
out_json = json.loads(out.strip())
|
|
113
|
+
assert err == ""
|
|
114
|
+
assert out_json["asn"] == 13335
|
|
115
|
+
assert out_json["asn_name"] == "CLOUDFLARENET"
|
|
116
|
+
assert out_json["subnet"] == "1.1.1.0/24"
|
|
117
|
+
|
|
118
|
+
# look up asn
|
|
119
|
+
monkeypatch.setattr("sys.argv", ["asndb", "asn", "13335"])
|
|
120
|
+
main()
|
|
121
|
+
out, err = capsys.readouterr()
|
|
122
|
+
out_json = json.loads(out.strip())
|
|
123
|
+
assert err == ""
|
|
124
|
+
assert out_json["asn"] == 13335
|
|
125
|
+
assert out_json["asn_name"] == "CLOUDFLARENET"
|
|
126
|
+
|
|
127
|
+
# look up org
|
|
128
|
+
monkeypatch.setattr("sys.argv", ["asndb", "org", "CLOUD14-ARIN"])
|
|
129
|
+
main()
|
|
130
|
+
out, err = capsys.readouterr()
|
|
131
|
+
out_json = json.loads(out.strip())
|
|
132
|
+
assert err == ""
|
|
133
|
+
assert out_json["asns"] == [13335, 14789, 395747, 394536]
|