feat(netwatch): add a configure-socket hook to BindOptions#182
Conversation
SO_MARK covers Linux, but it has no equivalent on other platforms, and the option that replaces it there (IP_BOUND_IF / IPV6_BOUND_IF on Apple) pins the socket to an interface, so it needs the current default-route interface rather than a constant, and has to be re-resolved whenever the socket rebinds. Rather than grow a per-platform option for each of these, hand the caller the socket. The hook runs before bind and again on every rebind, so a hook that reads current network state re-reads it on each network change. An error from the hook fails the bind: a socket that was meant to be kept out of a tunnel and silently wasn't is worse than one that failed to bind. The socket's Domain is passed in because it cannot be read back off the socket portably (SO_DOMAIN is Linux-only) and the option to set is family-specific.
A named trait documents the contract (reruns per rebind, an error fails the bind, the Domain parameter) better than a bare Arc<dyn Fn> alias, and lets a caller implement it on a stateful type. Closures still work via a blanket impl. The stored form is a private newtype with an opaque Debug, so BindOptions and SocketState go back to plain derives.
Drops the newtype and its hand-written Debug impl: the field is stored as a plain Arc<dyn SocketConfigurator> and BindOptions/SocketState derive Debug through derive_more with #[debug(skip)], which the crate already uses elsewhere.
|
Does this obsolete #179? Should we revert it and work on something more general instead? |
…Options::set_mark A mark is a one-line configurator, so the dedicated option is redundant. Reverts the SO_MARK plumbing from n0-computer#179 (unreleased) in favor of the general hook.
@flub sadly yes. I realized I could do something similar for macos, but I need another type of manipulation for the socket. Hope you don't mind the sudden change of mind. I guess it is part of the iterative process. |
|
Also included an exmaple on how to use the mark flag in the comment |
|
CI needs similar treatment to n0-computer/noq@fcd420c due to cargo nightly changes. |
New nightly cargo lints fire inside the cfg_aliases macro; allow it until katharostech/cfg_aliases#15 lands, same as n0-computer/noq#748.
|
Well, that PR landed and a new version of |
…ter#185) Reverts n0-computer#179 See n0-computer#182: this needs more design.
Follow-up to #179. That change lets a caller set
SO_MARK, which covers full-tunnel loop prevention on Linux. This adds the general form toBindOptions: a configurator that runs on every socket right after creation and beforebind(), and again on every internal rebind.What
New
SocketConfiguratortrait and aBindOptions::configure_socket(impl SocketConfigurator)builder option.set_markis unchanged; the two compose.A blanket impl covers
Fn(SockRef<'_>, socket2::Domain) -> io::Result<()> + Send + Sync + 'staticclosures, so simple configurators need no named type.The configurator is stored in both
SocketStatevariants (like the mark), so it reruns on every rebind. The field is#[debug(skip)]ed (derive_more, already a dependency), soBindOptionsandSocketStatekeep derivedDebug.An error from the configurator fails the bind (fail closed): for loop-prevention users, a socket that silently misses its configuration would leak traffic into the tunnel it is carrying.
It receives the
socket2::Domainexplicitly becauseSockRef::domain()rests onSO_DOMAIN, which is Linux-only.BindOptionsnow holds anArc<dyn SocketConfigurator>, so it losesCopy/PartialEq/Eq(both unreleased, added in feat(netwatch): allow setting SO_MARK on UdpSocket #179). Tests cover the configurator running on bind and rebind, and the fail-closed path.Why
Same client as #179 (full tunnel over iroh, WireGuard/Tailscale-style loop prevention), on platforms without fwmark. On macOS the equivalent is binding the socket to the physical default-route interface (
IP_BOUND_IF), and that must be re-resolved and re-applied on every rebind because the right interface changes when the default route moves (wifi to ethernet, say). A one-shot option can't express that; a per-(re)bind configurator can, and it also covers whatever other setsockopt a caller needs without growingBindOptionsper platform.