Загрузить файлы в «/»
This commit is contained in:
commit
913a567520
488
1,py
Normal file
488
1,py
Normal file
@ -0,0 +1,488 @@
|
|||||||
|
"""
|
||||||
|
Программа для проверки открытых портов.
|
||||||
|
|
||||||
|
Как работает:
|
||||||
|
1. Python запускает локальный веб-сервер.
|
||||||
|
2. В браузере открывается HTML-страница с формой.
|
||||||
|
3. Пользователь выбирает хост, диапазон портов, таймаут и количество потоков.
|
||||||
|
4. Python пробует подключиться к каждому порту через TCP.
|
||||||
|
5. На странице показываются только открытые порты и примерное имя сервиса.
|
||||||
|
|
||||||
|
Важно: проверяй только свои устройства, локальный компьютер или сеть,
|
||||||
|
для которой у тебя есть разрешение.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
||||||
|
from socket import AF_INET, SOCK_STREAM, socket
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
import html
|
||||||
|
import json
|
||||||
|
import socket as socket_module
|
||||||
|
import webbrowser
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_HOST = "127.0.0.1"
|
||||||
|
DEFAULT_START_PORT = 1
|
||||||
|
DEFAULT_END_PORT = 65535
|
||||||
|
DEFAULT_TIMEOUT = 0.25
|
||||||
|
DEFAULT_WORKERS = 300
|
||||||
|
|
||||||
|
|
||||||
|
PAGE = """<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Проверка портов</title>
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
background: #eef2f5;
|
||||||
|
color: #1d2730;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
width: min(980px, calc(100% - 32px));
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 32px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0 0 18px;
|
||||||
|
font-size: clamp(28px, 5vw, 44px);
|
||||||
|
line-height: 1.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d8e0e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 18px;
|
||||||
|
box-shadow: 0 12px 30px rgba(20, 40, 60, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.explanation {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explanation h2 {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explanation p {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
color: #485761;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explanation ol {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 21px;
|
||||||
|
color: #485761;
|
||||||
|
line-height: 1.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1.4fr repeat(4, minmax(92px, 1fr)) auto;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: grid;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #51606b;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 1px solid #bfccd6;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 11px;
|
||||||
|
font: inherit;
|
||||||
|
color: #16212a;
|
||||||
|
background: #fbfdff;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:focus {
|
||||||
|
outline: 3px solid #b8ddff;
|
||||||
|
border-color: #3985c6;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 11px 18px;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #146c94;
|
||||||
|
cursor: pointer;
|
||||||
|
min-height: 42px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background: #0d587b;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
cursor: wait;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-top: 16px;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 28px;
|
||||||
|
padding: 4px 9px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #e6eef5;
|
||||||
|
color: #26343f;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
accent-color: #146c94;
|
||||||
|
}
|
||||||
|
|
||||||
|
.results {
|
||||||
|
margin-top: 18px;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d8e0e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 11px 12px;
|
||||||
|
border-bottom: 1px solid #e5ebf0;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
background: #f5f8fa;
|
||||||
|
color: #44525d;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:last-child td {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.open {
|
||||||
|
color: #0d7a44;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty {
|
||||||
|
padding: 18px;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #d8e0e6;
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #52616d;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 820px) {
|
||||||
|
form {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
label:first-child,
|
||||||
|
button {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
<h1>Проверка портов</h1>
|
||||||
|
<section class="panel explanation">
|
||||||
|
<h2>Пояснение</h2>
|
||||||
|
<p>Эта программа запускает локальную веб-страницу и проверяет, какие TCP-порты открыты на выбранном хосте.</p>
|
||||||
|
<ol>
|
||||||
|
<li>Вводится адрес хоста, например 127.0.0.1.</li>
|
||||||
|
<li>Задается диапазон портов от 1 до 65535.</li>
|
||||||
|
<li>Python пробует подключиться к каждому порту.</li>
|
||||||
|
<li>Если подключение получилось, порт считается открытым и появляется в таблице.</li>
|
||||||
|
</ol>
|
||||||
|
</section>
|
||||||
|
<section class="panel">
|
||||||
|
<form id="scan-form">
|
||||||
|
<label>
|
||||||
|
Хост
|
||||||
|
<input id="host" name="host" value="127.0.0.1" autocomplete="off">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
От
|
||||||
|
<input id="start" name="start" type="number" min="1" max="65535" value="1">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
До
|
||||||
|
<input id="end" name="end" type="number" min="1" max="65535" value="65535">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Таймаут
|
||||||
|
<input id="timeout" name="timeout" type="number" min="0.05" max="5" step="0.05" value="0.25">
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Потоки
|
||||||
|
<input id="workers" name="workers" type="number" min="1" max="1000" value="300">
|
||||||
|
</label>
|
||||||
|
<button id="scan-button" type="submit">Проверить</button>
|
||||||
|
</form>
|
||||||
|
<div class="status">
|
||||||
|
<span class="badge" id="state">Готово</span>
|
||||||
|
<span class="badge" id="count">Открытых портов: 0</span>
|
||||||
|
<span class="badge" id="time">Время: 0 c</span>
|
||||||
|
</div>
|
||||||
|
<progress id="progress" value="0" max="100" hidden></progress>
|
||||||
|
</section>
|
||||||
|
<section class="results" id="results">
|
||||||
|
<div class="empty">Запусти проверку, и здесь появятся открытые порты.</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const form = document.querySelector("#scan-form");
|
||||||
|
const button = document.querySelector("#scan-button");
|
||||||
|
const state = document.querySelector("#state");
|
||||||
|
const count = document.querySelector("#count");
|
||||||
|
const time = document.querySelector("#time");
|
||||||
|
const progress = document.querySelector("#progress");
|
||||||
|
const results = document.querySelector("#results");
|
||||||
|
|
||||||
|
function renderPorts(data) {
|
||||||
|
count.textContent = `Открытых портов: ${data.open_ports.length}`;
|
||||||
|
time.textContent = `Время: ${data.elapsed_seconds} c`;
|
||||||
|
|
||||||
|
if (!data.open_ports.length) {
|
||||||
|
results.innerHTML = '<div class="empty">Открытые порты не найдены.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = data.open_ports.map(item => `
|
||||||
|
<tr>
|
||||||
|
<td>${item.port}</td>
|
||||||
|
<td class="open">Открыт</td>
|
||||||
|
<td>${item.service || "неизвестно"}</td>
|
||||||
|
</tr>
|
||||||
|
`).join("");
|
||||||
|
|
||||||
|
results.innerHTML = `
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Порт</th>
|
||||||
|
<th>Статус</th>
|
||||||
|
<th>Сервис</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>${rows}</tbody>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener("submit", async event => {
|
||||||
|
event.preventDefault();
|
||||||
|
const params = new URLSearchParams(new FormData(form));
|
||||||
|
button.disabled = true;
|
||||||
|
state.textContent = "Проверяю...";
|
||||||
|
progress.hidden = false;
|
||||||
|
progress.removeAttribute("value");
|
||||||
|
results.innerHTML = '<div class="empty">Идет проверка портов. Полный диапазон может занять время.</div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/scan?${params.toString()}`);
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (!response.ok || data.error) {
|
||||||
|
throw new Error(data.error || "Ошибка проверки");
|
||||||
|
}
|
||||||
|
|
||||||
|
state.textContent = `Проверено: ${data.host}:${data.start_port}-${data.end_port}`;
|
||||||
|
renderPorts(data);
|
||||||
|
} catch (error) {
|
||||||
|
state.textContent = "Ошибка";
|
||||||
|
results.innerHTML = `<div class="empty">${error.message}</div>`;
|
||||||
|
} finally {
|
||||||
|
button.disabled = false;
|
||||||
|
progress.hidden = true;
|
||||||
|
progress.value = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def clamp_int(value, default, minimum, maximum):
|
||||||
|
try:
|
||||||
|
number = int(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
return max(minimum, min(maximum, number))
|
||||||
|
|
||||||
|
|
||||||
|
def clamp_float(value, default, minimum, maximum):
|
||||||
|
try:
|
||||||
|
number = float(value)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return default
|
||||||
|
return max(minimum, min(maximum, number))
|
||||||
|
|
||||||
|
|
||||||
|
def service_name(port):
|
||||||
|
try:
|
||||||
|
return socket_module.getservbyport(port)
|
||||||
|
except OSError:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def check_port(host, port, timeout):
|
||||||
|
with socket(AF_INET, SOCK_STREAM) as sock:
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
result = sock.connect_ex((host, port))
|
||||||
|
if result == 0:
|
||||||
|
return {"port": port, "service": service_name(port)}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def scan_ports(host, start_port, end_port, timeout, workers):
|
||||||
|
open_ports = []
|
||||||
|
ports = range(start_port, end_port + 1)
|
||||||
|
|
||||||
|
with ThreadPoolExecutor(max_workers=workers) as executor:
|
||||||
|
futures = [executor.submit(check_port, host, port, timeout) for port in ports]
|
||||||
|
for future in as_completed(futures):
|
||||||
|
result = future.result()
|
||||||
|
if result:
|
||||||
|
open_ports.append(result)
|
||||||
|
|
||||||
|
return sorted(open_ports, key=lambda item: item["port"])
|
||||||
|
|
||||||
|
|
||||||
|
class PortScannerHandler(BaseHTTPRequestHandler):
|
||||||
|
def do_GET(self):
|
||||||
|
parsed_url = urlparse(self.path)
|
||||||
|
|
||||||
|
if parsed_url.path == "/":
|
||||||
|
self.send_html(PAGE)
|
||||||
|
return
|
||||||
|
|
||||||
|
if parsed_url.path == "/scan":
|
||||||
|
self.handle_scan(parsed_url.query)
|
||||||
|
return
|
||||||
|
|
||||||
|
self.send_error(404, "Страница не найдена")
|
||||||
|
|
||||||
|
def handle_scan(self, query):
|
||||||
|
params = parse_qs(query)
|
||||||
|
host = params.get("host", [DEFAULT_HOST])[0].strip() or DEFAULT_HOST
|
||||||
|
host = html.escape(host, quote=True)
|
||||||
|
start_port = clamp_int(params.get("start", [DEFAULT_START_PORT])[0], DEFAULT_START_PORT, 1, 65535)
|
||||||
|
end_port = clamp_int(params.get("end", [DEFAULT_END_PORT])[0], DEFAULT_END_PORT, 1, 65535)
|
||||||
|
timeout = clamp_float(params.get("timeout", [DEFAULT_TIMEOUT])[0], DEFAULT_TIMEOUT, 0.05, 5.0)
|
||||||
|
workers = clamp_int(params.get("workers", [DEFAULT_WORKERS])[0], DEFAULT_WORKERS, 1, 1000)
|
||||||
|
|
||||||
|
if start_port > end_port:
|
||||||
|
start_port, end_port = end_port, start_port
|
||||||
|
|
||||||
|
try:
|
||||||
|
socket_module.gethostbyname(host)
|
||||||
|
except OSError:
|
||||||
|
self.send_json({"error": "Не удалось найти такой хост."}, status=400)
|
||||||
|
return
|
||||||
|
|
||||||
|
from time import perf_counter
|
||||||
|
|
||||||
|
started = perf_counter()
|
||||||
|
try:
|
||||||
|
open_ports = scan_ports(host, start_port, end_port, timeout, workers)
|
||||||
|
except OSError as error:
|
||||||
|
self.send_json({"error": str(error)}, status=400)
|
||||||
|
return
|
||||||
|
|
||||||
|
elapsed = round(perf_counter() - started, 2)
|
||||||
|
self.send_json(
|
||||||
|
{
|
||||||
|
"host": host,
|
||||||
|
"start_port": start_port,
|
||||||
|
"end_port": end_port,
|
||||||
|
"timeout": timeout,
|
||||||
|
"workers": workers,
|
||||||
|
"elapsed_seconds": elapsed,
|
||||||
|
"open_ports": open_ports,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def send_html(self, content):
|
||||||
|
data = content.encode("utf-8")
|
||||||
|
self.send_response(200)
|
||||||
|
self.send_header("Content-Type", "text/html; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(data)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(data)
|
||||||
|
|
||||||
|
def send_json(self, content, status=200):
|
||||||
|
data = json.dumps(content, ensure_ascii=False).encode("utf-8")
|
||||||
|
self.send_response(status)
|
||||||
|
self.send_header("Content-Type", "application/json; charset=utf-8")
|
||||||
|
self.send_header("Content-Length", str(len(data)))
|
||||||
|
self.end_headers()
|
||||||
|
self.wfile.write(data)
|
||||||
|
|
||||||
|
def log_message(self, format, *args):
|
||||||
|
return
|
||||||
|
|
||||||
|
|
||||||
|
def find_free_port(start=8000, end=8100):
|
||||||
|
for port in range(start, end + 1):
|
||||||
|
try:
|
||||||
|
server = ThreadingHTTPServer(("127.0.0.1", port), PortScannerHandler)
|
||||||
|
return server, port
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
raise OSError("Не удалось найти свободный порт для веб-страницы.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
httpd, port = find_free_port()
|
||||||
|
url = f"http://127.0.0.1:{port}"
|
||||||
|
print(f"Открываю страницу: {url}")
|
||||||
|
print("Закрыть сервер: Ctrl+C")
|
||||||
|
webbrowser.open(url)
|
||||||
|
httpd.serve_forever()
|
||||||
Loading…
Reference in New Issue
Block a user