pyportscan 1.2.0__py3-none-any.whl

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.
pyportscan/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """pyportscan - 一个多线程 TCP 端口扫描器"""
2
+
3
+ from .parse import parse_ports
4
+ from .scan import scan_port, scan_ports
5
+ from .banner import grab_banner
6
+
7
+ __version__ = "0.5.0"
8
+
9
+ __all__ = [
10
+ "parse_ports",
11
+ "scan_port",
12
+ "scan_ports",
13
+ "grab_banner",
14
+ ]
pyportscan/banner.py ADDED
@@ -0,0 +1,38 @@
1
+ """服务 Banner 抓取模块"""
2
+
3
+ import socket
4
+
5
+
6
+ def grab_banner(host, port, timeout=2.0):
7
+ """连接端口,尝试获取服务 banner。返回字符串,失败返回 None。"""
8
+ sock = None
9
+ try:
10
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
11
+ sock.settimeout(timeout)
12
+ sock.connect((host, port))
13
+
14
+ # 尝试1:被动接收(SSH/FTP/SMTP 主动发)
15
+ try:
16
+ data = sock.recv(1024)
17
+ if data:
18
+ return data.decode(errors="ignore").strip()
19
+ except socket.timeout:
20
+ pass
21
+
22
+ # 尝试2:主动发 HTTP/1.1 请求
23
+ try:
24
+ req = (b"HEAD / HTTP/1.1\r\nHost: " + host.encode()
25
+ + b"\r\nConnection: close\r\n\r\n")
26
+ sock.send(req)
27
+ data = sock.recv(1024)
28
+ if data:
29
+ return data.decode(errors="ignore").strip()
30
+ except socket.timeout:
31
+ pass
32
+
33
+ return None
34
+ except (socket.error, OSError):
35
+ return None
36
+ finally:
37
+ if sock:
38
+ sock.close()
pyportscan/parse.py ADDED
@@ -0,0 +1,20 @@
1
+ """端口范围解析模块"""
2
+
3
+
4
+ def parse_ports(s):
5
+ """解析端口字符串,返回整数列表。
6
+
7
+ 支持格式:
8
+ "80" → [80]
9
+ "22,80,443" → [22, 80, 443]
10
+ "8000-8005" → [8000, 8001, ..., 8005]
11
+ "22,8000-8002" → [22, 8000, 8001, 8002]
12
+ """
13
+ ports = []
14
+ for part in s.split(","):
15
+ if "-" in part:
16
+ start, end = part.split("-")
17
+ ports.extend(range(int(start), int(end) + 1))
18
+ else:
19
+ ports.append(int(part))
20
+ return ports
pyportscan/ports.py ADDED
@@ -0,0 +1,12 @@
1
+ """常用端口列表(参考 Nmap Top 100)"""
2
+
3
+ TOP_PORTS = [
4
+ 7, 9, 13, 21, 22, 23, 25, 26, 37, 53, 79, 80, 81, 88, 106, 110, 111, 113,
5
+ 119, 135, 139, 143, 144, 179, 199, 389, 427, 443, 444, 445, 465, 513, 514,
6
+ 515, 543, 544, 548, 554, 587, 631, 646, 873, 990, 993, 995, 1025, 1026,
7
+ 1027, 1028, 1029, 1110, 1433, 1720, 1723, 1755, 1900, 2000, 2001, 2049,
8
+ 2121, 2717, 3000, 3128, 3306, 3389, 3986, 4899, 5000, 5009, 5051, 5060,
9
+ 5101, 5190, 5357, 5432, 5631, 5666, 5800, 5900, 6000, 6001, 6646, 7070,
10
+ 8000, 8008, 8009, 8080, 8081, 8443, 8888, 9100, 9999, 10000, 32768,
11
+ 49152, 49153, 49154, 49155, 49156, 49157,
12
+ ]
pyportscan/scan.py ADDED
@@ -0,0 +1,106 @@
1
+ """TCP 端口扫描模块"""
2
+
3
+ import socket
4
+ from concurrent.futures import ThreadPoolExecutor
5
+
6
+
7
+ def scan_port(host, port, timeout=1.0):
8
+ """扫描单个 TCP 端口。返回 True 表示开放。"""
9
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10
+ sock.settimeout(timeout)
11
+ try:
12
+ result = sock.connect_ex((host, port))
13
+ return result == 0
14
+ finally:
15
+ sock.close()
16
+
17
+
18
+ def udp_scan_port(host,port,timeout = 2.0):
19
+ sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
20
+ sock.settimeout(timeout)
21
+ try:
22
+ sock.sendto(b"",(host,port))
23
+ try:
24
+ data, _ = sock.recvfrom(1024)
25
+ return "open"
26
+ except socket.timeout:
27
+ return "open|filtered"
28
+ except (ConnectionRefusedError,ConnectionResetError):
29
+ return "closed"
30
+ finally:
31
+ sock.close()
32
+
33
+
34
+ def scan_ports(host, ports, timeout=1.0, max_workers=200, grab=False,udp=False,on_progress=None):
35
+ """
36
+ on_prograss:可选回调,每扫完一个端口调用一次
37
+ 签名:on_progress(done,total,open_ports)
38
+ """
39
+
40
+ """并发扫描多个端口。
41
+
42
+ 参数:
43
+ host: 目标主机
44
+ ports: 端口列表
45
+ timeout: 单个端口超时
46
+ max_workers: 并发线程数
47
+ grab: 是否抓取 banner
48
+
49
+ 返回:
50
+ TCP (udp=False):
51
+ grab=False → [(port, None), ...](仅开放端口)
52
+ grab=True → [(port, banner), ...]
53
+ UDP (udp=True):
54
+ [(port, state), ...] # state ∈ {"open", "closed", "open|filtered"}
55
+ """
56
+
57
+ ports = list(ports)
58
+ total = len(ports)
59
+ done = 0
60
+
61
+ # 第一步:并发扫描端口
62
+ open_ports = []
63
+ #UDP
64
+ if udp:
65
+ results = []
66
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
67
+ futures = {executor.submit(udp_scan_port,host,port,timeout):port
68
+ for port in ports}
69
+ for future, port in futures.items():
70
+ done += 1
71
+ if on_progress:
72
+ on_progress(done,total,results)
73
+ state = future.result()
74
+ if state != "closed":
75
+ results.append((port,state))
76
+ results.sort()
77
+ return results
78
+
79
+
80
+ #TCP
81
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
82
+ futures = {executor.submit(scan_port, host, port, timeout): port
83
+ for port in ports}
84
+ for future, port in futures.items():
85
+ done += 1
86
+ if on_progress:
87
+ on_progress(done,total,open_ports)
88
+ if future.result():
89
+ open_ports.append(port)
90
+
91
+ # 第二步:不抓 banner 直接返回
92
+ if not grab:
93
+ return sorted([(p, None) for p in open_ports])
94
+
95
+ # 第三步:并发抓 banner
96
+ from .banner import grab_banner # 延迟导入,避免循环依赖
97
+ results = []
98
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
99
+ futures = {executor.submit(grab_banner, host, port, timeout): port
100
+ for port in open_ports}
101
+ for future, port in futures.items():
102
+ banner = future.result()
103
+ results.append((port, banner))
104
+
105
+ results.sort()
106
+ return results
@@ -0,0 +1,63 @@
1
+ """基于asyncio的异步端口扫描"""
2
+
3
+ import asyncio
4
+
5
+
6
+ async def scan_port_async(host,port,timeout=1.0,semaphore=None):
7
+ """异步扫描单个TCP端口
8
+ 参数:
9
+ semaphore:可选的 asyncio.Semaphore,用于限流
10
+ 返回:
11
+ True表示端口开放,False表示端口关闭/超时
12
+ """
13
+
14
+ if semaphore is not None:
15
+ async with semaphore:
16
+ return await _try_connect(host,port,timeout)
17
+ return await _try_connect(host,port,timeout)
18
+
19
+
20
+
21
+ async def _try_connect(host,port,timeout):
22
+ #内部:尝试连接
23
+
24
+ try:
25
+ reader,writer = await asyncio.wait_for(
26
+ asyncio.open_connection(host,port),
27
+ timeout=timeout,
28
+ )
29
+ writer.close()
30
+ try:
31
+ await writer.wait_closed()
32
+ except Exception:
33
+ pass
34
+ return True
35
+ except(asyncio.TimeoutError,ConnectionRefusedError,OSError):
36
+ return False
37
+
38
+
39
+
40
+ async def scan_ports_async(host,ports,timeout=1.0,max_concurrent=500):
41
+ """异步并发扫描多个端口
42
+ 参数:
43
+ host:目标主机
44
+ ports:端口列表(或range)
45
+ timeout:单个端口超时
46
+ max_concurrent:最大并发数
47
+
48
+ 返回:
49
+ [(port,None),...](暂不抓banner,格式与scan_ports一致)
50
+ """
51
+
52
+ ports = list(ports)
53
+ semaphore = asyncio.Semaphore(max_concurrent)
54
+
55
+ tasks = [
56
+ scan_port_async(host,port,timeout,semaphore)
57
+ for port in ports
58
+ ]
59
+ results = await asyncio.gather(*tasks)
60
+
61
+ open_ports = [p for p, ok in zip(ports,results)if ok]
62
+ open_ports.sort()
63
+ return [(p,None) for p in open_ports]
@@ -0,0 +1,189 @@
1
+ Metadata-Version: 2.4
2
+ Name: pyportscan
3
+ Version: 1.2.0
4
+ Summary: A multi-threaded Python port scanner with async support and banner grabbing.
5
+ Author-email: JinGu123123 <gujin123123@outlook.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/JinGu123123/pyportscan
8
+ Project-URL: Issues, https://github.com/JinGu123123/pyportscan/issues
9
+ Keywords: port-scanner,network,security,asyncio
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Topic :: Security
13
+ Requires-Python: >=3.8
14
+ Description-Content-Type: text/markdown
15
+ License-File: LICENSE
16
+ Dynamic: license-file
17
+
18
+ # pyportscan
19
+
20
+ 一个用 Python 实现的多线程 TCP 端口扫描器,支持端口范围解析、并发扫描和 Banner 抓取。用于学习网络安全与网络编程。
21
+
22
+ ## 功能
23
+
24
+ - [x] 单端口 / 多端口 / 端口范围扫描
25
+ - [x] 多线程并发加速
26
+ - [x] 灵活的命令行参数(argparse)
27
+ - [x] Banner Grabbing 服务识别
28
+ - [x] 模块化设计,可作为 Python 库使用
29
+ - [x] 单元测试覆盖核心逻辑
30
+ - [x] UDP 扫描(计划中)
31
+ - [ ] 导出扫描结果到 JSON/CSV(计划中)
32
+
33
+ ## 环境要求
34
+
35
+ - Python 3.8+
36
+ - 依赖见 `requirements.txt`
37
+
38
+ ## 开发环境
39
+
40
+ ### Python 部分
41
+ - Python 3.8+
42
+ - pytest(测试)
43
+
44
+ ### C 语言 SYN 扫描(阶段 7.3 准备)
45
+ - WSL2(Windows Subsystem for Linux 2)
46
+ - Ubuntu 26.04 LTS
47
+ - gcc 15.2.0
48
+ - make 4.4.1
49
+ - libpcap-dev
50
+
51
+ #### 在 WSL2 里安装
52
+
53
+ ```bash
54
+ sudo apt update
55
+ sudo apt install -y gcc make libpcap-dev
56
+
57
+ ## 安装
58
+
59
+ ```bash
60
+ git clone git@github.com:JinGu123123/pyportscan.git
61
+ cd pyportscan
62
+ pip install -r requirements.txt
63
+ ```
64
+
65
+ ## 使用
66
+
67
+ ### 基本用法
68
+
69
+ ```bash
70
+ # 扫描本机默认端口(1-1024)
71
+ python main.py 127.0.0.1
72
+
73
+ # 扫描指定端口
74
+ python main.py 127.0.0.1 -p 22,80,443,8000
75
+
76
+ # 扫描端口范围
77
+ python main.py 127.0.0.1 -p 1-1024
78
+
79
+ # 混合格式
80
+ python main.py 127.0.0.1 -p 22,80,8000-8100,3306
81
+
82
+ # 不抓取 banner(更快)
83
+ python main.py 127.0.0.1 -p 1-1024 --no-banner
84
+
85
+ # UDP 扫描
86
+ python main.py 127.0.0.1 -p 53,123,161 --udp
87
+
88
+ # 查看帮助
89
+ python main.py -h
90
+ ```
91
+
92
+ ### 参数说明
93
+
94
+ | 参数 | 说明 | 默认值 |
95
+ |------|------|--------|
96
+ | `host` | 目标主机(IP 或域名,必填) | - |
97
+ | `-p, --ports` | 端口范围 | `1-1024` |
98
+ | `-t, --timeout` | 连接超时(秒) | `1.0` |
99
+ | `-w, --workers` | 并发线程数 | `100` |
100
+ | `--no-banner` | 不抓取服务 banner | 关闭 |
101
+ | `--udp` | 使用 UDP 扫描(默认 TCP) | 关闭 |
102
+ | `-h, --help` | 显示帮助 | - |
103
+
104
+ ### 示例输出
105
+
106
+ ```
107
+ $ python main.py 127.0.0.1 -p 1-1024
108
+ 开始扫描 127.0.0.1,端口数 1024
109
+ 开放端口:
110
+ 135/tcp (无法识别)
111
+ 445/tcp (无法识别)
112
+ 902/tcp 220 VMware Authentication Daemon Version 1.10: SSL Required...
113
+ 912/tcp 220 VMware Authentication Daemon Version 1.0, ServerDaemonProtocol:SOAP...
114
+ ```
115
+
116
+ ## Python API 用法
117
+
118
+ 除了命令行,`pyportscan` 也可以作为 Python 库使用:
119
+
120
+ ```python
121
+ from pyportscan import parse_ports, scan_ports, grab_banner
122
+
123
+ # 解析端口字符串
124
+ ports = parse_ports("22,80,8000-8100") # [22, 80, 8000, ..., 8100]
125
+
126
+ # 扫描端口(不抓 banner)
127
+ results = scan_ports("127.0.0.1", ports, grab=False)
128
+
129
+ # 扫描端口并抓取 banner
130
+ results = scan_ports("127.0.0.1", [8000], grab=True)
131
+ for port, banner in results:
132
+ print(port, banner)
133
+
134
+ # 单独抓取某个端口的 banner
135
+ banner = grab_banner("127.0.0.1", 8000)
136
+
137
+ # UDP 扫描
138
+ results = scan_ports("127.0.0.1", [53, 123], udp=True)
139
+ # [(53, 'open|filtered'), ...]
140
+ ```
141
+
142
+ ## 项目结构
143
+
144
+ ```
145
+ pyportscan/
146
+ ├── pyportscan/ # 核心包
147
+ │ ├── __init__.py # 统一暴露 API
148
+ │ ├── parse.py # 端口范围解析
149
+ │ ├── scan.py # 端口扫描
150
+ │ └── banner.py # banner 抓取
151
+ ├── tests/ # 单元测试
152
+ │ ├── test_parse.py
153
+ │ ├── test_scan.py
154
+ │ └── test_banner.py
155
+ ├── main.py # CLI 入口
156
+ ├── requirements.txt
157
+ ├── README.md
158
+ └── LICENSE
159
+ ```
160
+
161
+ ## 运行测试
162
+
163
+ ```bash
164
+ # 安装测试依赖
165
+ pip install -r requirements.txt
166
+
167
+ # 运行全部测试
168
+ pytest tests/ -v
169
+
170
+ # 只运行某个测试文件
171
+ pytest tests/test_parse.py -v
172
+ ```
173
+
174
+ ## 开发进度
175
+
176
+ - [x] 阶段 0-3:基础扫描 + 并发扫描
177
+ - [x] 阶段 4:命令行工具(argparse)
178
+ - [x] 阶段 5:Banner Grabbing 服务识别
179
+ - [x] 阶段 6:单元测试 + 项目整理
180
+ - [x] 阶段 7.1:UDP 扫描
181
+ - [x] 阶段 7.2:搭建 WSL2 开发环境
182
+ - [ ] 阶段 7.3:C 语言 SYN 半开扫描
183
+ ## 免责声明
184
+
185
+ 本工具仅供学习与授权测试使用。请勿用于未授权的目标,否则后果自负。
186
+
187
+ ## License
188
+
189
+ [MIT](LICENSE)
@@ -0,0 +1,12 @@
1
+ pyportscan/__init__.py,sha256=jh4SeRwmFJt2nPhGk-VpdiHLhZN40tC-peizKVD7QDY,270
2
+ pyportscan/banner.py,sha256=Wxm-7gdGFbNYnjiljAJXneY77743B20dw86Jk7434TY,1083
3
+ pyportscan/parse.py,sha256=v4BCtEXGLF5ozXtrsSn7BpfCo-agnumpxQbl-kJrMU0,556
4
+ pyportscan/ports.py,sha256=S9wmhw2wH6SdVTr90xCX1mOyyFmWUhYZ170xUFTE2ck,646
5
+ pyportscan/scan.py,sha256=LgDWIUMTCPvH5XjYCW8NwTJkw2VoKhe4UfmbtkEoedo,3283
6
+ pyportscan/scan_async.py,sha256=TV7fuWX0-r_NEmqcWU70_nrhCBPEmyHJjbeGGClaeUE,1682
7
+ pyportscan-1.2.0.dist-info/licenses/LICENSE,sha256=Yf9TlBPFI80gE71BltBT6BJBWizr7LYx8x3al-x83no,1068
8
+ pyportscan-1.2.0.dist-info/METADATA,sha256=AWRPg0S8Jrr9w8f9VtqeXzj2CRCjas8QpdE0szV-sGM,4922
9
+ pyportscan-1.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
10
+ pyportscan-1.2.0.dist-info/entry_points.txt,sha256=Uk-453EiJcfNykGnzhEv8JcipMjOKs4Lr6D0IL0op8U,41
11
+ pyportscan-1.2.0.dist-info/top_level.txt,sha256=HdZkBhLcTORXYfdz_DUdTq4blFUCGJkXb0ByLyfH9J8,11
12
+ pyportscan-1.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pyportscan = main:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 JinGu123123
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ pyportscan