Files
bambuddy/backend/tests/unit/test_vp_mqtt_server.py
T
maziggy 82d329d85c [Fix] Virtual Printer FTP routed to wrong VP with different access codes (#735)
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
2026-03-18 09:04:31 +01:00

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."
)