-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
144 lines (122 loc) · 7.18 KB
/
main.py
File metadata and controls
144 lines (122 loc) · 7.18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
import socket
import threading
import time
from datetime import datetime, timedelta
from dhcp.config import config
from dhcp.ip_manager import (
allocate_ip, release_ip, is_ip_available, load_leases_from_disk, cleanup_expired_leases, mark_ip_conflict,
leases, used_ips, lock
)
from dhcp.ip_utils import ip_to_int
from dhcp.packet import build_dhcp_packet, parse_dhcp_packet
from dhcp.logging_config import logger
from dhcp.web_app import run_web_api
CLEANUP_INTERVAL = 60 # Интервал очистки в секундах
def run_cleanup_task(stop_event):
"""Фоновая задача для периодической очистки истекших аренд."""
while not stop_event.wait(CLEANUP_INTERVAL):
logger.info("Запуск фоновой очистки истекших аренд...")
cleaned_count = cleanup_expired_leases()
if cleaned_count > 0:
logger.info(f" -> Очищено {cleaned_count} истекших аренд.")
# -------------------------------
# Основной цикл сервера
# -------------------------------
def run_dhcp_server():
# Создаём UDP-сокет
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.bind(('', 67))
load_leases_from_disk()
logger.info(f"DHCP-сервер запущен на {config.server_ip}:67")
logger.info(f"Пул адресов: {config.pool_start} - {config.pool_end}")
logger.info(f"Загружено {len(leases)} активных аренд из файла.")
# Запуск фоновой задачи очистки
stop_event = threading.Event()
cleanup_thread = threading.Thread(
target=run_cleanup_task,
args=(stop_event,),
daemon=True
)
cleanup_thread.start()
logger.info(f"Фоновая очистка аренд будет запускаться каждые {CLEANUP_INTERVAL} секунд.")
# Запуск Web API в фоновом потоке
web_thread = threading.Thread(target=run_web_api, args=(8080,), daemon=True)
web_thread.start()
logger.info("Web-интерфейс запущен на порту 8080 (http://localhost:8080)")
logger.info("Ожидание запросов...")
while True:
try:
data, addr = sock.recvfrom(1024)
# addr[0] – IP отправителя, addr[1] – порт (обычно 68)
msg_type, mac, requested_ip, hostname = parse_dhcp_packet(data)
if msg_type is None:
logger.warning(f"Получен невалидный пакет от {addr[0]}")
continue
log_hostname = f" (hostname: {hostname})" if hostname else ""
logger.info(f"Получен {msg_type_to_name(msg_type)} от {mac}{log_hostname} (IP клиента: {addr[0]})")
if msg_type == 1: # DHCPDISCOVER
ip = allocate_ip(mac.lower(), hostname)
if ip:
logger.info(f" -> Предлагаем IP {ip} для {mac}{log_hostname}")
response = build_dhcp_packet(data, 2, ip, config.server_ip) # 2 = OFFER
# Отправляем широковещательно на порт 68
sock.sendto(response, ('255.255.255.255', 68))
elif mac.lower() in config.static_leases:
static_ip = config.static_leases[mac.lower()]
logger.error(f" -> Не удалось выдать статический IP {static_ip} для {mac}, так как он занят.")
else:
logger.warning(f" -> Нет свободных IP для {mac}")
elif msg_type == 3: # DHCPREQUEST
# Проверяем, какой IP запрашивается (option 50) или ciaddr
if requested_ip:
# Клиент просит конкретный IP
if is_ip_available(requested_ip, mac.lower()):
# Подтверждаем аренду
ip = requested_ip
# Обновляем аренду в хранилище
with lock:
leases[mac.lower()] = (ip, datetime.now() + timedelta(seconds=config.lease_time), hostname)
used_ips.add(ip_to_int(ip))
logger.info(f" -> Подтверждаем IP {ip} для {mac}{log_hostname}")
response = build_dhcp_packet(data, 5, ip, config.server_ip) # 5 = ACK
sock.sendto(response, ('255.255.255.255', 68))
else:
logger.warning(f" -> Отказ: IP {requested_ip} недоступен для {mac}. Отправка DHCPNAK.")
response = build_dhcp_packet(data, 6, '0.0.0.0', config.server_ip) # 6 = NAK
sock.sendto(response, ('255.255.255.255', 68))
else:
# Если нет option 50, возможно клиент использует ciaddr
# В этой упрощённой версии просто подтверждаем существующую аренду
with lock:
if mac.lower() in leases:
ip, expiry, old_hostname = leases[mac.lower()]
# Обновляем время аренды и, возможно, имя хоста
new_hostname = hostname if hostname else old_hostname
leases[mac.lower()] = (ip, datetime.now() + timedelta(seconds=config.lease_time), new_hostname)
logger.info(f" -> Подтверждаем существующую аренду {ip} для {mac}{log_hostname}")
response = build_dhcp_packet(data, 5, ip, config.server_ip)
sock.sendto(response, ('255.255.255.255', 68))
elif msg_type == 4: # DHCPDECLINE
if requested_ip:
logger.warning(f"DHCPDECLINE получен от {mac} для IP {requested_ip}.")
mark_ip_conflict(requested_ip)
else:
logger.warning(f"Получен DHCPDECLINE от {mac} без указания IP-адреса. Игнорируется.")
elif msg_type == 7: # DHCPRELEASE
release_ip(mac.lower())
logger.info(f" -> Освобождён IP для {mac}")
except KeyboardInterrupt:
logger.info("Завершение работы сервера...")
stop_event.set()
cleanup_thread.join()
break
except Exception as e:
logger.error(f"Критическая ошибка: {e}", exc_info=True)
sock.close()
def msg_type_to_name(t):
types = {1: "DISCOVER", 2: "OFFER", 3: "REQUEST", 4: "DECLINE", 5: "ACK", 6: "NAK", 7: "RELEASE"}
return types.get(t, "UNKNOWN")
if __name__ == "__main__":
run_dhcp_server()