Skip to content

Add --interface option to bind to a specific network interface#235

Merged
balloob merged 4 commits intomainfrom
copilot/feature-bind-to-specific-interface
Apr 28, 2026
Merged

Add --interface option to bind to a specific network interface#235
balloob merged 4 commits intomainfrom
copilot/feature-bind-to-specific-interface

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 26, 2026

Users on multi-homed machines (e.g., a home server acting as a LAN router) have no way to prevent Sendspin from being accessible on all interfaces, including WAN-facing ones. This adds a --interface <IP> option to restrict mDNS discovery and daemon listener binding to a single interface.

Changes

  • settings.py: Added interface: str | None to ClientSettings with full load/save/update support.
  • discovery.py: ServiceDiscovery accepts interfaces: list[str] | None; passes it to AsyncZeroconf to restrict mDNS to that interface. Same parameter threaded through discover_servers() and discover_clients().
  • cli.py: --interface added to both TUI player and daemon argument parsers; settings default applied; passed through to AppArgs / DaemonArgs.
  • tui/app.py: AppArgs gains interface; SendspinApp passes [interface] to ServiceDiscovery.
  • daemon/daemon.py: DaemonArgs gains interface; server-initiated listener uses host=interface (falls back to 0.0.0.0) so the aiohttp server only binds on that IP.
  • README.md: New "Network Interface Binding" section; interface added to settings table and example config.

Usage

# Restrict TUI mDNS discovery to LAN interface
sendspin --interface 192.168.1.5

# Restrict daemon listener and mDNS to LAN interface only
sendspin daemon --interface 192.168.1.5

Can also be persisted in ~/.config/sendspin/settings-daemon.json:

{ "interface": "192.168.1.5" }

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • python-zeroconf.readthedocs.io
    • Triggering command: /home/REDACTED/work/_temp/ghcca-node/node/bin/node /home/REDACTED/work/_temp/ghcca-node/node/bin/node --enable-source-maps /home/REDACTED/work/_temp/copilot-developer-action-main/dist/index.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot AI changed the title [WIP] Add option to bind sendspin client to specific interface Add --interface option to bind to a specific network interface Apr 26, 2026
Copilot AI requested a review from balloob April 26, 2026 12:59
Comment thread README.md Outdated
sendspin servers list
```

### Network Interface Binding
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot move this to bottom of option descriptions ni README, it is not important

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 404586e — moved the "Network Interface Binding" section to just before the "Install as Daemon" heading.

Copilot AI requested a review from balloob April 28, 2026 13:37
@balloob balloob added the new-feature Request or implement a new feature label Apr 28, 2026
@balloob balloob marked this pull request as ready for review April 28, 2026 13:45
Copilot AI review requested due to automatic review settings April 28, 2026 13:45
@balloob balloob merged commit 07e41f2 into main Apr 28, 2026
3 checks passed
@balloob balloob deleted the copilot/feature-bind-to-specific-interface branch April 28, 2026 13:46
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new --interface <IP> option (and corresponding persisted setting) to restrict Sendspin’s network behavior on multi-homed machines, primarily to avoid exposing discovery/listeners on unintended interfaces.

Changes:

  • Introduces interface: str | None in ClientSettings and loads it from the settings JSON.
  • Threads an optional interfaces list through mDNS discovery (ServiceDiscovery, discover_servers(), discover_clients()) and uses it when creating AsyncZeroconf.
  • Adds --interface to the player and daemon CLIs, passes it through AppArgs/DaemonArgs, and binds the daemon’s server-initiated listener to that address.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
sendspin/tui/app.py Passes a single interface (when configured) into ServiceDiscovery for mDNS discovery scoping.
sendspin/settings.py Adds persisted interface setting and loads it from JSON.
sendspin/discovery.py Adds interface scoping support to ServiceDiscovery, discover_servers(), and discover_clients() via AsyncZeroconf(interfaces=...).
sendspin/daemon/daemon.py Binds the server-initiated listener to the configured interface (else 0.0.0.0).
sendspin/cli.py Adds --interface for player + daemon, applies settings defaults, and passes through to args structs.
README.md Documents the new interface setting and adds a “Network Interface Binding” section.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread sendspin/cli.py
Comment on lines +800 to +801
if args.interface is None:
args.interface = settings.interface
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

args.interface is accepted from both CLI and persisted settings, but it's never validated. If a user provides an invalid IP (or a hostname/interface name that aiohttp/zeroconf can't bind to), this can raise an OSError during listener startup and currently bubbles up to an unhandled exception in main(). Consider validating --interface early (e.g., with ipaddress.ip_address) and turning failures into a CLIError with a clear message.

Copilot uses AI. Check for mistakes.
Comment thread sendspin/discovery.py
Comment on lines +137 to +138
interfaces: Optional list of IP addresses or interface names to restrict
mDNS discovery to. If None, all interfaces are used.
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new interfaces parameter is documented as accepting "IP addresses or interface names", but the CLI/README describe --interface as an IP address only. Please align the documentation and supported inputs (either document/validate interface names everywhere, or narrow this docstring to IP addresses) so users know what is actually supported.

Suggested change
interfaces: Optional list of IP addresses or interface names to restrict
mDNS discovery to. If None, all interfaces are used.
interfaces: Optional list of IP addresses to restrict mDNS
discovery to. If None, all interfaces are used.

Copilot uses AI. Check for mistakes.
Comment thread sendspin/settings.py
self.manufacturer = data.get("manufacturer")
self.product_name = data.get("product_name")
self.last_played_server_id = data.get("last_played_server_id")
self.interface = data.get("interface")
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClientSettings now persists an interface field, but there isn't corresponding unit coverage. Since there are already tests asserting settings load behavior for other fields, it would be good to add a small test that writes {"interface": "192.168.1.5"} and asserts it is loaded (and/or that to_dict() includes it on save).

Copilot uses AI. Check for mistakes.
Comment thread sendspin/cli.py
help=(
"IP address of the network interface to bind to. "
"In server-initiated mode (no --url), restricts the listening server to this "
"interface only. Also restricts mDNS discovery to this interface. "
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The daemon subcommand's --interface help text says it "also restricts mDNS discovery", but the daemon code path doesn't use ServiceDiscovery/discover_*() at all. As implemented, --interface only affects the incoming listener bind address (and is ignored in client-initiated mode). Please adjust the help text to match the actual behavior to avoid misleading users.

Suggested change
"interface only. Also restricts mDNS discovery to this interface. "
"interface only. Ignored in client-initiated mode (--url). "

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

new-feature Request or implement a new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bind to specific interface only? (help or feature request)

3 participants