netscout 1.0.1__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.
- netscout-1.0.1/PKG-INFO +210 -0
- netscout-1.0.1/README.md +184 -0
- netscout-1.0.1/cidr.py +195 -0
- netscout-1.0.1/enumerator.py +406 -0
- netscout-1.0.1/exporter.py +114 -0
- netscout-1.0.1/main.py +279 -0
- netscout-1.0.1/netscout.egg-info/PKG-INFO +210 -0
- netscout-1.0.1/netscout.egg-info/SOURCES.txt +18 -0
- netscout-1.0.1/netscout.egg-info/dependency_links.txt +1 -0
- netscout-1.0.1/netscout.egg-info/entry_points.txt +2 -0
- netscout-1.0.1/netscout.egg-info/requires.txt +6 -0
- netscout-1.0.1/netscout.egg-info/top_level.txt +7 -0
- netscout-1.0.1/output.py +368 -0
- netscout-1.0.1/pyproject.toml +42 -0
- netscout-1.0.1/resolver.py +113 -0
- netscout-1.0.1/scanner.py +369 -0
- netscout-1.0.1/setup.cfg +4 -0
- netscout-1.0.1/tests/test_cidr.py +97 -0
- netscout-1.0.1/tests/test_exporter.py +64 -0
- netscout-1.0.1/tests/test_scanner.py +37 -0
netscout-1.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: netscout
|
|
3
|
+
Version: 1.0.1
|
|
4
|
+
Summary: Production-ready pentest recon CLI — speed first.
|
|
5
|
+
Author: NetScout Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: pentest,recon,scanner,network,cli
|
|
8
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Information Technology
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Security
|
|
18
|
+
Classifier: Topic :: System :: Networking
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
Requires-Dist: rich>=13.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
25
|
+
Requires-Dist: pytest-cov; extra == "dev"
|
|
26
|
+
|
|
27
|
+
# NetScout
|
|
28
|
+
|
|
29
|
+
**Production-ready pentest recon CLI — speed first.**
|
|
30
|
+
|
|
31
|
+
NetScout is a fast, async network reconnaissance tool designed for penetration testers and security professionals. It discovers live hosts, checks ports, grabs banners, fingerprints OS, and exports results — all with minimal packets and maximum parallelism.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- **Blazing fast** — async I/O with semaphore-gated concurrency (default 150 threads)
|
|
38
|
+
- **Multi-CIDR input** — CIDR, single IP, ranges, hostnames, file input, or interactive prompt
|
|
39
|
+
- **Smart discovery** — nmap ping sweep (preferred) with asyncio TCP/ICMP fallback
|
|
40
|
+
- **Port scanning** — async TCP connect with banner grabbing
|
|
41
|
+
- **OS fingerprinting** — zero-cost TTL heuristic + optional deep nmap -O
|
|
42
|
+
- **Service detection** — nmap -sV with intensity 0 (single probe per port)
|
|
43
|
+
- **Script scanning** — nmap default scripts on demand
|
|
44
|
+
- **Rich terminal UI** — live progress dashboard with hacker aesthetics
|
|
45
|
+
- **Multi-format export** — txt, json, csv, gnmap (auto-detected from extension)
|
|
46
|
+
- **Graceful degradation** — works without nmap, without root, behind firewalls
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# Clone and install in editable mode
|
|
54
|
+
git clone https://github.com/youruser/netscout.git
|
|
55
|
+
cd netscout
|
|
56
|
+
pip install -e .
|
|
57
|
+
|
|
58
|
+
# Now available globally
|
|
59
|
+
netscout --version
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Requirements
|
|
63
|
+
|
|
64
|
+
- Python ≥ 3.10
|
|
65
|
+
- [nmap](https://nmap.org/) (optional but recommended for full functionality)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Quick Start
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Scan a single subnet
|
|
73
|
+
netscout 10.10.10.0/24
|
|
74
|
+
|
|
75
|
+
# Multiple ranges with port check
|
|
76
|
+
netscout 10.10.10.0/24 172.20.0.0/16 --port 22 80 443 8080
|
|
77
|
+
|
|
78
|
+
# Fast enum (OS + banner + DNS, zero extra packets)
|
|
79
|
+
netscout 192.168.1.0/24 --enum
|
|
80
|
+
|
|
81
|
+
# Deep scan with export
|
|
82
|
+
netscout 10.10.10.0/24 --deep --fast -o results.json
|
|
83
|
+
|
|
84
|
+
# Read targets from file
|
|
85
|
+
netscout --targets-file ranges.txt --enum -o scan.csv
|
|
86
|
+
|
|
87
|
+
# Interactive mode (no args)
|
|
88
|
+
netscout
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Usage
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
netscout [OPTIONS] [TARGET ...]
|
|
97
|
+
|
|
98
|
+
Targets (positional, or interactive prompt if omitted):
|
|
99
|
+
10.10.10.0/24 Single CIDR
|
|
100
|
+
10.10.10.0/24 172.20.0.0/16 Multiple CIDRs
|
|
101
|
+
10.10.10.1-50 Range shorthand
|
|
102
|
+
10.10.10.5 Single IP
|
|
103
|
+
--targets-file FILE One target per line
|
|
104
|
+
|
|
105
|
+
Discovery:
|
|
106
|
+
--tcp TCP fallback for ICMP-dark hosts
|
|
107
|
+
--ports TEXT Ports for TCP fallback probe (default: 22,80,443,445,3389)
|
|
108
|
+
--threads INT Concurrent threads (default: 150)
|
|
109
|
+
--timeout FLOAT Timeout per probe in seconds (default: 1.0)
|
|
110
|
+
--fast Use nmap T5 + max-parallelism (fastest, noisier)
|
|
111
|
+
|
|
112
|
+
Enumeration:
|
|
113
|
+
--port INT [INT ...] Check if specific port(s) are open on live hosts
|
|
114
|
+
--os Guess OS via TTL (zero extra packets)
|
|
115
|
+
--services Service version detection (nmap -sV intensity 0)
|
|
116
|
+
--scripts Run nmap default scripts (slow, explicit only)
|
|
117
|
+
--enum All fast enum: OS + banner + DNS (recommended)
|
|
118
|
+
--deep Full nmap -O -sV on live hosts (slowest, most info)
|
|
119
|
+
|
|
120
|
+
Output:
|
|
121
|
+
-o, --output FILE Save results (auto-format: .txt .json .csv .gnmap)
|
|
122
|
+
--no-color Disable colors (for piping)
|
|
123
|
+
-v, --verbose Show dead hosts
|
|
124
|
+
-q, --quiet Only print IP:PORT, no UI chrome
|
|
125
|
+
--no-banner Suppress ASCII banner
|
|
126
|
+
--force Scan ranges > 65536 hosts
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Architecture
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
netscout/
|
|
135
|
+
├── main.py # CLI entrypoint + interactive prompt
|
|
136
|
+
├── scanner.py # Async ping sweep + TCP SYN probe
|
|
137
|
+
├── cidr.py # Multi-CIDR parsing, expansion, dedup
|
|
138
|
+
├── enumerator.py # Port check, OS fingerprint, banner grab
|
|
139
|
+
├── resolver.py # DNS forward/reverse with cache
|
|
140
|
+
├── output.py # Rich terminal UI: live dashboard, summary
|
|
141
|
+
├── exporter.py # txt / json / csv / gnmap output
|
|
142
|
+
├── tests/
|
|
143
|
+
├── pyproject.toml
|
|
144
|
+
└── README.md
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Speed Optimizations
|
|
148
|
+
|
|
149
|
+
1. **Single nmap call per phase** — never loops nmap per-host
|
|
150
|
+
2. **Semaphore-gated async** for all socket ops
|
|
151
|
+
3. **Zero duplicate probes** — cached results, never re-probe same IP
|
|
152
|
+
4. **Randomised scan order** — shuffles IPs to evade rate limiting
|
|
153
|
+
5. **Connect timeout 0.5s** for TCP port checks
|
|
154
|
+
6. **Batch hostname resolution** before scan starts
|
|
155
|
+
7. **Progress bar at max 20fps** — rendering never slows down scanning
|
|
156
|
+
|
|
157
|
+
### Graceful Degradation
|
|
158
|
+
|
|
159
|
+
| Condition | Behaviour |
|
|
160
|
+
|-----------|-----------|
|
|
161
|
+
| No nmap | Warns once, falls back to asyncio TCP/ICMP |
|
|
162
|
+
| No root/sudo | Skips raw socket features, uses connect() |
|
|
163
|
+
| ICMP blocked | Auto-enables TCP fallback silently |
|
|
164
|
+
| KeyboardInterrupt | Prints partial results + summary, clean exit |
|
|
165
|
+
| All errors | Go to stderr; results to stdout (pipeable) |
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Output Formats
|
|
170
|
+
|
|
171
|
+
### Quiet mode (`-q`)
|
|
172
|
+
```
|
|
173
|
+
10.10.10.5:22
|
|
174
|
+
10.10.10.5:80
|
|
175
|
+
10.10.10.12:445
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### JSON (`-o results.json`)
|
|
179
|
+
```json
|
|
180
|
+
{
|
|
181
|
+
"total_hosts": 256,
|
|
182
|
+
"alive_hosts": 12,
|
|
183
|
+
"results": [
|
|
184
|
+
{
|
|
185
|
+
"ip": "10.10.10.5",
|
|
186
|
+
"hostname": "htb-target.local",
|
|
187
|
+
"os_guess": "Linux/Unix",
|
|
188
|
+
"open_ports": [22, 80, 443],
|
|
189
|
+
"ports": {
|
|
190
|
+
"22": {"state": "open", "service": "ssh", "banner": "SSH-2.0-OpenSSH_8.9"}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
pip install -e ".[dev]"
|
|
203
|
+
pytest --cov=netscout
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
MIT
|
netscout-1.0.1/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# NetScout
|
|
2
|
+
|
|
3
|
+
**Production-ready pentest recon CLI — speed first.**
|
|
4
|
+
|
|
5
|
+
NetScout is a fast, async network reconnaissance tool designed for penetration testers and security professionals. It discovers live hosts, checks ports, grabs banners, fingerprints OS, and exports results — all with minimal packets and maximum parallelism.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Blazing fast** — async I/O with semaphore-gated concurrency (default 150 threads)
|
|
12
|
+
- **Multi-CIDR input** — CIDR, single IP, ranges, hostnames, file input, or interactive prompt
|
|
13
|
+
- **Smart discovery** — nmap ping sweep (preferred) with asyncio TCP/ICMP fallback
|
|
14
|
+
- **Port scanning** — async TCP connect with banner grabbing
|
|
15
|
+
- **OS fingerprinting** — zero-cost TTL heuristic + optional deep nmap -O
|
|
16
|
+
- **Service detection** — nmap -sV with intensity 0 (single probe per port)
|
|
17
|
+
- **Script scanning** — nmap default scripts on demand
|
|
18
|
+
- **Rich terminal UI** — live progress dashboard with hacker aesthetics
|
|
19
|
+
- **Multi-format export** — txt, json, csv, gnmap (auto-detected from extension)
|
|
20
|
+
- **Graceful degradation** — works without nmap, without root, behind firewalls
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Clone and install in editable mode
|
|
28
|
+
git clone https://github.com/youruser/netscout.git
|
|
29
|
+
cd netscout
|
|
30
|
+
pip install -e .
|
|
31
|
+
|
|
32
|
+
# Now available globally
|
|
33
|
+
netscout --version
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Requirements
|
|
37
|
+
|
|
38
|
+
- Python ≥ 3.10
|
|
39
|
+
- [nmap](https://nmap.org/) (optional but recommended for full functionality)
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
# Scan a single subnet
|
|
47
|
+
netscout 10.10.10.0/24
|
|
48
|
+
|
|
49
|
+
# Multiple ranges with port check
|
|
50
|
+
netscout 10.10.10.0/24 172.20.0.0/16 --port 22 80 443 8080
|
|
51
|
+
|
|
52
|
+
# Fast enum (OS + banner + DNS, zero extra packets)
|
|
53
|
+
netscout 192.168.1.0/24 --enum
|
|
54
|
+
|
|
55
|
+
# Deep scan with export
|
|
56
|
+
netscout 10.10.10.0/24 --deep --fast -o results.json
|
|
57
|
+
|
|
58
|
+
# Read targets from file
|
|
59
|
+
netscout --targets-file ranges.txt --enum -o scan.csv
|
|
60
|
+
|
|
61
|
+
# Interactive mode (no args)
|
|
62
|
+
netscout
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## Usage
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
netscout [OPTIONS] [TARGET ...]
|
|
71
|
+
|
|
72
|
+
Targets (positional, or interactive prompt if omitted):
|
|
73
|
+
10.10.10.0/24 Single CIDR
|
|
74
|
+
10.10.10.0/24 172.20.0.0/16 Multiple CIDRs
|
|
75
|
+
10.10.10.1-50 Range shorthand
|
|
76
|
+
10.10.10.5 Single IP
|
|
77
|
+
--targets-file FILE One target per line
|
|
78
|
+
|
|
79
|
+
Discovery:
|
|
80
|
+
--tcp TCP fallback for ICMP-dark hosts
|
|
81
|
+
--ports TEXT Ports for TCP fallback probe (default: 22,80,443,445,3389)
|
|
82
|
+
--threads INT Concurrent threads (default: 150)
|
|
83
|
+
--timeout FLOAT Timeout per probe in seconds (default: 1.0)
|
|
84
|
+
--fast Use nmap T5 + max-parallelism (fastest, noisier)
|
|
85
|
+
|
|
86
|
+
Enumeration:
|
|
87
|
+
--port INT [INT ...] Check if specific port(s) are open on live hosts
|
|
88
|
+
--os Guess OS via TTL (zero extra packets)
|
|
89
|
+
--services Service version detection (nmap -sV intensity 0)
|
|
90
|
+
--scripts Run nmap default scripts (slow, explicit only)
|
|
91
|
+
--enum All fast enum: OS + banner + DNS (recommended)
|
|
92
|
+
--deep Full nmap -O -sV on live hosts (slowest, most info)
|
|
93
|
+
|
|
94
|
+
Output:
|
|
95
|
+
-o, --output FILE Save results (auto-format: .txt .json .csv .gnmap)
|
|
96
|
+
--no-color Disable colors (for piping)
|
|
97
|
+
-v, --verbose Show dead hosts
|
|
98
|
+
-q, --quiet Only print IP:PORT, no UI chrome
|
|
99
|
+
--no-banner Suppress ASCII banner
|
|
100
|
+
--force Scan ranges > 65536 hosts
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Architecture
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
netscout/
|
|
109
|
+
├── main.py # CLI entrypoint + interactive prompt
|
|
110
|
+
├── scanner.py # Async ping sweep + TCP SYN probe
|
|
111
|
+
├── cidr.py # Multi-CIDR parsing, expansion, dedup
|
|
112
|
+
├── enumerator.py # Port check, OS fingerprint, banner grab
|
|
113
|
+
├── resolver.py # DNS forward/reverse with cache
|
|
114
|
+
├── output.py # Rich terminal UI: live dashboard, summary
|
|
115
|
+
├── exporter.py # txt / json / csv / gnmap output
|
|
116
|
+
├── tests/
|
|
117
|
+
├── pyproject.toml
|
|
118
|
+
└── README.md
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Speed Optimizations
|
|
122
|
+
|
|
123
|
+
1. **Single nmap call per phase** — never loops nmap per-host
|
|
124
|
+
2. **Semaphore-gated async** for all socket ops
|
|
125
|
+
3. **Zero duplicate probes** — cached results, never re-probe same IP
|
|
126
|
+
4. **Randomised scan order** — shuffles IPs to evade rate limiting
|
|
127
|
+
5. **Connect timeout 0.5s** for TCP port checks
|
|
128
|
+
6. **Batch hostname resolution** before scan starts
|
|
129
|
+
7. **Progress bar at max 20fps** — rendering never slows down scanning
|
|
130
|
+
|
|
131
|
+
### Graceful Degradation
|
|
132
|
+
|
|
133
|
+
| Condition | Behaviour |
|
|
134
|
+
|-----------|-----------|
|
|
135
|
+
| No nmap | Warns once, falls back to asyncio TCP/ICMP |
|
|
136
|
+
| No root/sudo | Skips raw socket features, uses connect() |
|
|
137
|
+
| ICMP blocked | Auto-enables TCP fallback silently |
|
|
138
|
+
| KeyboardInterrupt | Prints partial results + summary, clean exit |
|
|
139
|
+
| All errors | Go to stderr; results to stdout (pipeable) |
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Output Formats
|
|
144
|
+
|
|
145
|
+
### Quiet mode (`-q`)
|
|
146
|
+
```
|
|
147
|
+
10.10.10.5:22
|
|
148
|
+
10.10.10.5:80
|
|
149
|
+
10.10.10.12:445
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### JSON (`-o results.json`)
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"total_hosts": 256,
|
|
156
|
+
"alive_hosts": 12,
|
|
157
|
+
"results": [
|
|
158
|
+
{
|
|
159
|
+
"ip": "10.10.10.5",
|
|
160
|
+
"hostname": "htb-target.local",
|
|
161
|
+
"os_guess": "Linux/Unix",
|
|
162
|
+
"open_ports": [22, 80, 443],
|
|
163
|
+
"ports": {
|
|
164
|
+
"22": {"state": "open", "service": "ssh", "banner": "SSH-2.0-OpenSSH_8.9"}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Development
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
pip install -e ".[dev]"
|
|
177
|
+
pytest --cov=netscout
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
MIT
|
netscout-1.0.1/cidr.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Multi-CIDR input parsing, expansion, deduplication, and merging.
|
|
3
|
+
|
|
4
|
+
Handles:
|
|
5
|
+
- CIDR notation: 10.10.10.0/24
|
|
6
|
+
- Single IPs: 10.10.10.5
|
|
7
|
+
- Ranges: 10.10.10.1-50 OR 10.10.10.1-10.10.10.254
|
|
8
|
+
- Hostnames: auto-resolved to IP
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import ipaddress
|
|
14
|
+
import re
|
|
15
|
+
import socket
|
|
16
|
+
from dataclasses import dataclass, field
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Sequence
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Constants
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
MAX_HOSTS_NO_FORCE = 65_536
|
|
24
|
+
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
26
|
+
# Dataclass for parsed input
|
|
27
|
+
# ---------------------------------------------------------------------------
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class TargetSpec:
|
|
31
|
+
"""Represents a parsed target specification."""
|
|
32
|
+
raw: str
|
|
33
|
+
networks: list[ipaddress.IPv4Network] = field(default_factory=list)
|
|
34
|
+
individual_ips: list[ipaddress.IPv4Address] = field(default_factory=list)
|
|
35
|
+
resolved_hostname: str = ""
|
|
36
|
+
error: str = ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# Parsing helpers
|
|
41
|
+
# ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
_RANGE_SHORT_RE = re.compile(
|
|
44
|
+
r"^(\d{1,3}\.\d{1,3}\.\d{1,3})\.(\d{1,3})-(\d{1,3})$"
|
|
45
|
+
)
|
|
46
|
+
_RANGE_FULL_RE = re.compile(
|
|
47
|
+
r"^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})-(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _parse_single(raw: str) -> TargetSpec:
|
|
52
|
+
"""Parse a single target string into a TargetSpec."""
|
|
53
|
+
spec = TargetSpec(raw=raw)
|
|
54
|
+
text = raw.strip()
|
|
55
|
+
if not text:
|
|
56
|
+
spec.error = "empty input"
|
|
57
|
+
return spec
|
|
58
|
+
|
|
59
|
+
# 1) Try CIDR
|
|
60
|
+
if "/" in text:
|
|
61
|
+
try:
|
|
62
|
+
net = ipaddress.IPv4Network(text, strict=False)
|
|
63
|
+
spec.networks.append(net)
|
|
64
|
+
return spec
|
|
65
|
+
except (ipaddress.AddressValueError, ValueError) as exc:
|
|
66
|
+
spec.error = str(exc)
|
|
67
|
+
return spec
|
|
68
|
+
|
|
69
|
+
# 2) Try short range: 10.10.10.1-50
|
|
70
|
+
m = _RANGE_SHORT_RE.match(text)
|
|
71
|
+
if m:
|
|
72
|
+
prefix, start_s, end_s = m.group(1), int(m.group(2)), int(m.group(3))
|
|
73
|
+
if start_s > end_s or end_s > 255:
|
|
74
|
+
spec.error = f"invalid range {text}"
|
|
75
|
+
return spec
|
|
76
|
+
for octet in range(start_s, end_s + 1):
|
|
77
|
+
spec.individual_ips.append(ipaddress.IPv4Address(f"{prefix}.{octet}"))
|
|
78
|
+
return spec
|
|
79
|
+
|
|
80
|
+
# 3) Try full range: 10.10.10.1-10.10.10.254
|
|
81
|
+
m = _RANGE_FULL_RE.match(text)
|
|
82
|
+
if m:
|
|
83
|
+
try:
|
|
84
|
+
start_ip = ipaddress.IPv4Address(m.group(1))
|
|
85
|
+
end_ip = ipaddress.IPv4Address(m.group(2))
|
|
86
|
+
except ipaddress.AddressValueError as exc:
|
|
87
|
+
spec.error = str(exc)
|
|
88
|
+
return spec
|
|
89
|
+
if int(start_ip) > int(end_ip):
|
|
90
|
+
spec.error = f"start > end in range {text}"
|
|
91
|
+
return spec
|
|
92
|
+
for ip_int in range(int(start_ip), int(end_ip) + 1):
|
|
93
|
+
spec.individual_ips.append(ipaddress.IPv4Address(ip_int))
|
|
94
|
+
return spec
|
|
95
|
+
|
|
96
|
+
# 4) Try single IP
|
|
97
|
+
try:
|
|
98
|
+
addr = ipaddress.IPv4Address(text)
|
|
99
|
+
spec.individual_ips.append(addr)
|
|
100
|
+
return spec
|
|
101
|
+
except ipaddress.AddressValueError:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
# 5) Try hostname resolution
|
|
105
|
+
try:
|
|
106
|
+
resolved = socket.gethostbyname(text)
|
|
107
|
+
spec.individual_ips.append(ipaddress.IPv4Address(resolved))
|
|
108
|
+
spec.resolved_hostname = text
|
|
109
|
+
return spec
|
|
110
|
+
except (socket.gaierror, socket.herror) as exc:
|
|
111
|
+
spec.error = f"cannot resolve hostname '{text}': {exc}"
|
|
112
|
+
return spec
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# ---------------------------------------------------------------------------
|
|
116
|
+
# Public API
|
|
117
|
+
# ---------------------------------------------------------------------------
|
|
118
|
+
|
|
119
|
+
def parse_targets(raw_inputs: Sequence[str]) -> tuple[list[TargetSpec], list[str]]:
|
|
120
|
+
"""
|
|
121
|
+
Parse a list of raw target strings.
|
|
122
|
+
|
|
123
|
+
Returns (specs, errors) where *specs* is successfully parsed targets
|
|
124
|
+
and *errors* is a list of human-readable error strings.
|
|
125
|
+
"""
|
|
126
|
+
specs: list[TargetSpec] = []
|
|
127
|
+
errors: list[str] = []
|
|
128
|
+
for raw in raw_inputs:
|
|
129
|
+
# Split on commas and whitespace for multi-target pasting
|
|
130
|
+
tokens = re.split(r"[,\s]+", raw.strip())
|
|
131
|
+
for tok in tokens:
|
|
132
|
+
if not tok:
|
|
133
|
+
continue
|
|
134
|
+
spec = _parse_single(tok)
|
|
135
|
+
if spec.error:
|
|
136
|
+
errors.append(f" ✗ {tok}: {spec.error}")
|
|
137
|
+
else:
|
|
138
|
+
specs.append(spec)
|
|
139
|
+
return specs, errors
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def load_targets_file(path: str | Path) -> list[str]:
|
|
143
|
+
"""Read one target per line from a file, stripping comments & blanks."""
|
|
144
|
+
p = Path(path)
|
|
145
|
+
if not p.exists():
|
|
146
|
+
raise FileNotFoundError(f"targets file not found: {p}")
|
|
147
|
+
lines: list[str] = []
|
|
148
|
+
for line in p.read_text().splitlines():
|
|
149
|
+
line = line.strip()
|
|
150
|
+
if line and not line.startswith("#"):
|
|
151
|
+
lines.append(line)
|
|
152
|
+
return lines
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def deduplicate_and_merge(
|
|
156
|
+
specs: list[TargetSpec],
|
|
157
|
+
) -> tuple[list[ipaddress.IPv4Network], int]:
|
|
158
|
+
"""
|
|
159
|
+
Collapse all parsed specs into a minimal set of non-overlapping networks.
|
|
160
|
+
|
|
161
|
+
Returns (networks, total_hosts).
|
|
162
|
+
"""
|
|
163
|
+
all_nets: list[ipaddress.IPv4Network] = []
|
|
164
|
+
for spec in specs:
|
|
165
|
+
all_nets.extend(spec.networks)
|
|
166
|
+
for ip in spec.individual_ips:
|
|
167
|
+
all_nets.append(ipaddress.IPv4Network(f"{ip}/32"))
|
|
168
|
+
|
|
169
|
+
collapsed = list(ipaddress.collapse_addresses(all_nets))
|
|
170
|
+
total = sum(net.num_addresses for net in collapsed)
|
|
171
|
+
return collapsed, total
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def expand_hosts(networks: list[ipaddress.IPv4Network]) -> list[ipaddress.IPv4Address]:
|
|
175
|
+
"""Expand collapsed networks into an ordered list of host addresses."""
|
|
176
|
+
hosts: list[ipaddress.IPv4Address] = []
|
|
177
|
+
for net in networks:
|
|
178
|
+
if net.prefixlen == 32:
|
|
179
|
+
hosts.append(net.network_address)
|
|
180
|
+
else:
|
|
181
|
+
hosts.extend(net.hosts())
|
|
182
|
+
return hosts
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def check_host_count(total: int, force: bool) -> bool:
|
|
186
|
+
"""
|
|
187
|
+
Return True if scan should proceed.
|
|
188
|
+
Raises SystemExit if total > MAX_HOSTS_NO_FORCE and --force not set.
|
|
189
|
+
"""
|
|
190
|
+
if total > MAX_HOSTS_NO_FORCE and not force:
|
|
191
|
+
raise SystemExit(
|
|
192
|
+
f"[!] Target range contains {total:,} hosts (limit: {MAX_HOSTS_NO_FORCE:,}). "
|
|
193
|
+
f"Use --force to proceed."
|
|
194
|
+
)
|
|
195
|
+
return True
|