mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-01 03:31:25 +02:00
When running multiple virtual printers with different access codes on separate bind IPs, FTP connections were always routed to the wrong VP. Root cause: the iptables REDIRECT rule (990→9990) rewrites the destination IP to the incoming interface's primary address. With Linux's weak host model (arp_filter=0), packets for secondary IPs arrive on the primary interface, and REDIRECT sends them all to the first VP's FTP server. MQTT was unaffected because port 8883 had no redirect. Fix: FTP server now binds directly to port 990 (standard implicit FTPS), eliminating the iptables redirect entirely. Requires CAP_NET_BIND_SERVICE (already set in the systemd service file and Docker image). Also removed a global asyncio set_exception_handler() in the MQTT server that was overwritten by each VP instance, causing spurious "Unhandled exception in client_connected_cb" errors on startup. Changes: - FTP_PORT: 9990 → 990 (ftp_server.py) - Removed set_exception_handler() from MQTT server - Updated Dockerfile, docker-compose.yml port mappings - Deprecated --redirect-990 in install script - Updated wiki: removed iptables instructions for all platforms - Added migration guide (docs/migration-vp-ftp-port.md) - Added unit tests for port constant and no-global-state invariant
29 lines
1.1 KiB
Python
29 lines
1.1 KiB
Python
"""Tests for Virtual Printer MQTT server."""
|
|
|
|
import ast
|
|
import inspect
|
|
|
|
from backend.app.services.virtual_printer.mqtt_server import SimpleMQTTServer
|
|
|
|
|
|
class TestMQTTServerNoGlobalState:
|
|
"""Ensure MQTT server doesn't set global asyncio state."""
|
|
|
|
def test_no_global_exception_handler(self):
|
|
"""MQTT server must not call set_exception_handler().
|
|
|
|
set_exception_handler() is global to the event loop. When multiple
|
|
VP instances run, each would overwrite the previous handler,
|
|
causing lost error context and spurious 'Unhandled exception in
|
|
client_connected_cb' messages.
|
|
"""
|
|
source = inspect.getsource(SimpleMQTTServer)
|
|
tree = ast.parse(source)
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Attribute) and node.attr == "set_exception_handler":
|
|
raise AssertionError(
|
|
"SimpleMQTTServer must not call set_exception_handler(). "
|
|
"It overwrites the global asyncio exception handler, "
|
|
"breaking multi-VP setups."
|
|
)
|