super dupper system update (it runs)
This commit is contained in:
167
wip/SNIP/server/main.py
Normal file
167
wip/SNIP/server/main.py
Normal file
@@ -0,0 +1,167 @@
|
||||
import asyncio
|
||||
import struct
|
||||
import ipaddress
|
||||
import websockets
|
||||
|
||||
HOST = "0.0.0.0"
|
||||
PORT = 8765
|
||||
ENABLE_PACKET_LOGGING = True
|
||||
ENABLE_IP_INFO_LOGGING = True
|
||||
|
||||
NETWORK = ipaddress.IPv4Network("0.0.0.0/0")
|
||||
STATIC_IPS: set[int] = set()
|
||||
|
||||
clients: dict[int, websockets.WebSocketServerProtocol] = {}
|
||||
leases: dict[websockets.WebSocketServerProtocol, int] = {}
|
||||
|
||||
clients_lock = asyncio.Lock()
|
||||
leases_lock = asyncio.Lock()
|
||||
|
||||
DATA_HEADER = "!4s4sI"
|
||||
DATA_HEADER_SIZE = struct.calcsize(DATA_HEADER)
|
||||
|
||||
NET_START = int(NETWORK.network_address) + 1
|
||||
NET_END = int(NETWORK.broadcast_address) - 1
|
||||
|
||||
|
||||
def int_to_bytes(ip: int) -> bytes:
|
||||
return ip.to_bytes(4, "big")
|
||||
|
||||
|
||||
def bytes_to_int(b: bytes) -> int:
|
||||
return int.from_bytes(b, "big")
|
||||
|
||||
|
||||
def allocate_dhcp_ip() -> int | None:
|
||||
"""Lazy allocation of first free IP."""
|
||||
for ip in range(NET_START, NET_END + 1):
|
||||
if ip not in clients and ip not in STATIC_IPS:
|
||||
return ip
|
||||
return None
|
||||
|
||||
|
||||
async def safe_send(ws: websockets.WebSocketServerProtocol, msg: bytes):
|
||||
"""Fire-and-forget safe send with cleanup."""
|
||||
try:
|
||||
await ws.send(msg)
|
||||
except websockets.ConnectionClosed:
|
||||
async with leases_lock:
|
||||
ip = leases.pop(ws, None)
|
||||
if ip is not None:
|
||||
async with clients_lock:
|
||||
clients.pop(ip, None)
|
||||
if ENABLE_IP_INFO_LOGGING:
|
||||
print(f"Released {ipaddress.IPv4Address(ip)} (peer closed)")
|
||||
|
||||
async def debug():
|
||||
while True:
|
||||
print(len(clients))
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def handle_websocket(ws: websockets.WebSocketServerProtocol):
|
||||
try:
|
||||
async for msg in ws:
|
||||
if not msg:
|
||||
continue
|
||||
|
||||
if not isinstance(msg, bytes):
|
||||
msg = msg.encode()
|
||||
|
||||
opcode = msg[0]
|
||||
|
||||
# --------------------
|
||||
# IP REQUEST
|
||||
# --------------------
|
||||
if opcode == 0x01:
|
||||
async with leases_lock:
|
||||
if ws in leases:
|
||||
continue
|
||||
|
||||
flags = msg[1]
|
||||
static = False
|
||||
|
||||
if flags == 0x00: # DHCP
|
||||
ip = allocate_dhcp_ip()
|
||||
if ip is None:
|
||||
await ws.send(b"\x02\x02\x00\x00\x00\x00")
|
||||
continue
|
||||
|
||||
elif flags == 0x01 and len(msg) == 6:
|
||||
requested = bytes_to_int(msg[2:6])
|
||||
if requested not in STATIC_IPS:
|
||||
await ws.send(b"\x02\x01\x00\x00\x00\x00")
|
||||
continue
|
||||
async with clients_lock:
|
||||
if requested in clients:
|
||||
await ws.send(b"\x02\x01\x00\x00\x00\x00")
|
||||
continue
|
||||
ip = requested
|
||||
static = True
|
||||
else:
|
||||
continue
|
||||
|
||||
async with leases_lock:
|
||||
leases[ws] = ip
|
||||
async with clients_lock:
|
||||
clients[ip] = ws
|
||||
|
||||
await ws.send(b"\x02\x00" + int_to_bytes(ip))
|
||||
label = "static" if static else "dynamic"
|
||||
if ENABLE_IP_INFO_LOGGING:
|
||||
print(f"Assigned {label} {ipaddress.IPv4Address(ip)}")
|
||||
|
||||
# --------------------
|
||||
# DATA PLANE
|
||||
# --------------------
|
||||
elif opcode == 0x10:
|
||||
async with leases_lock:
|
||||
if ws not in leases:
|
||||
continue
|
||||
|
||||
if len(msg) < 1 + DATA_HEADER_SIZE:
|
||||
continue
|
||||
|
||||
try:
|
||||
src_b, dst_b, length = struct.unpack(
|
||||
DATA_HEADER, msg[1:1 + DATA_HEADER_SIZE]
|
||||
)
|
||||
except struct.error:
|
||||
continue
|
||||
|
||||
payload = msg[1 + DATA_HEADER_SIZE:]
|
||||
if len(payload) != length:
|
||||
continue
|
||||
|
||||
src = bytes_to_int(src_b)
|
||||
dst = bytes_to_int(dst_b)
|
||||
|
||||
if ENABLE_PACKET_LOGGING:
|
||||
print(f"{ipaddress.IPv4Address(src)} > {ipaddress.IPv4Address(dst)}: {payload}")
|
||||
|
||||
async with clients_lock:
|
||||
receiver = clients.get(dst)
|
||||
if receiver:
|
||||
# Fire-and-forget send
|
||||
asyncio.create_task(safe_send(receiver, msg))
|
||||
|
||||
except websockets.ConnectionClosed:
|
||||
pass
|
||||
|
||||
finally:
|
||||
async with leases_lock:
|
||||
ip = leases.pop(ws, None)
|
||||
if ip is not None:
|
||||
async with clients_lock:
|
||||
clients.pop(ip, None)
|
||||
if ENABLE_IP_INFO_LOGGING:
|
||||
print(f"Released {ipaddress.IPv4Address(ip)}")
|
||||
|
||||
|
||||
async def main():
|
||||
async with websockets.serve(handle_websocket, HOST, PORT, max_size=2**20, max_queue=65535):
|
||||
print(f"IPoW router listening on {PORT}")
|
||||
await asyncio.Future()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
6
wip/SNIP/server/registered.json
Normal file
6
wip/SNIP/server/registered.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"1.1.1.1": {
|
||||
"salt": "",
|
||||
"sha256": ""
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user