Add option to bind aMule to a specific network interface - #281
Conversation
The existing Address= setting binds to a local IP, which on Linux does not override the kernel's routing decision - outbound traffic can leak via the default-route interface even when the bind IP belongs to a VPN tunnel (amule-project#173). This adds a proper interface bind. Uses IP_UNICAST_IF / IPV6_UNICAST_IF (Linux, Windows) and IP_BOUND_IF / IPV6_BOUND_IF (macOS/BSD), which need no elevated privileges (unlike SO_BINDTODEVICE / CAP_NET_RAW). The interface is named (resolved via if_nametoindex) or a bare index (the Windows path); empty means any. Applied to every socket: TCP listen, outbound TCP, and the UDP socket shared by ed2k client, ed2k server and Kad. New 'Bind to network interface' field on the Connection preferences page, stored as /eMule/NetworkInterface, hidden in the remote GUI like the address bind.
Replace the plain text field with an editable combo box populated with the machine's network interfaces (getifaddrs on POSIX, GetAdaptersAddresses on Windows), loopback filtered out. It stays editable so an interface that is down when the dialog opens (a VPN tunnel) can still be typed in. The stored value is unchanged in kind - a POSIX interface name or a Windows adapter friendly name - and all resolution to an interface index is centralized in LibSocketAsio.cpp's SetBoundInterface: if_nametoindex on POSIX, GetAdaptersAddresses friendly-name lookup on Windows (needs iphlpapi, linked into mulesocket), with a bare-numeric-index fallback. So the daemon still owns resolution and works headless.
The first commit had LibSocketAsio.cpp (in the mulesocket library) call thePrefs::GetNetworkInterface() directly. EC-only tools (amulecmd, amuleweb) link mulesocket but not the full CPreferences, so they failed to link with 'undefined symbol: CPreferences::s_NetworkInterface'. Keep mulesocket free of CPreferences: it now reads a module-local string set by the core via SetSocketBindInterface(), which CamuleApp pushes from thePrefs at startup (before any socket opens). A changed interface is flagged restart-needed in the prefs dialog, consistent with the TCP/UDP port settings that likewise only take effect on new sockets.
When a network interface is configured, print a normal-level line at
startup ('Binding all network traffic to interface: <name>') so the user
gets visible confirmation the VPN-leak protection is active, alongside the
listen-socket lines. Nothing is printed in the default (empty) case. The
per-socket detail stays on the logAsio debug channel.
mulesocket is a static library, so a PRIVATE dependency doesn't propagate to consumers at link time. The main executables pulled iphlpapi in transitively via other libraries, but the minimal NetworkFunctionsTest unit-test exe did not, failing with 'undefined symbol: GetAdaptersAddresses'. Make the link PUBLIC so every consumer of mulesocket gets it.
A leak-prevention setting must fail loud, not silent. Resolve the interface once at startup (new exported ResolveBindInterfaceIndex) and, when it does not resolve, print a critical-level WARNING telling the user traffic is NOT bound and may leave via the default route. The per-socket path keeps its detail on the debug channel to avoid spamming the normal log on every connect.
The test compiles src/LibSocket.cpp directly rather than linking the mulesocket library, so mulesocket's PUBLIC iphlpapi doesn't reach it. Link iphlpapi on the test target too for GetAdaptersAddresses().
…ent) Testing revealed IP_UNICAST_IF is only a routing *preference* on Linux, not a constraint: when the bound interface can't reach the destination the kernel silently falls back to the default route, so it did not prevent leaks - the exact amule-project#173 case. Verified with a direct probe (binding to a no-internet interface still connected via the default route, unchanged source IP). Switch Linux to SO_BINDTODEVICE, which is a real egress bind (verified: a bind to a non-routable interface now fails to connect). macOS (IP_BOUND_IF) and Windows (IP_UNICAST_IF) are genuine constraints and are unchanged. SO_BINDTODEVICE may need CAP_NET_RAW on some kernels. Resolution and the platform setsockopt are centralized in ApplyBindToInterface; the core probes it once at startup via TestSocketBindInterface and, if the bind is denied, warns loudly that traffic is NOT contained - never a silent non-enforcement.
…Country, server.met) The socket-layer bind only covered ed2k/Kad. aMule's HTTP side-channels (version check to GitHub, IP2Country DB, server.met-from-URL) go through wxWebRequest, not LibSocketAsio, so they leaked past a bound interface - confirmed on macOS: with the socket bind active, the version-check request still reached GitHub via the default route. Route all HTTP through one helper (CreateAmuleWebRequest): a curl-backed wxWebSession (uniform across platforms - Linux/macOS already default to curl, Windows forced explicitly), the proxy pref applied, and, when an interface is set, CURLOPT_SOCKOPTFUNCTION binding the socket via the SAME per-platform logic as the P2P sockets (BindRawSocketToInterface). Falls back to the default session if the curl backend is unavailable at runtime. Verified on macOS: binding to a no-route interface now kills the GitHub version-check connection too, not just ed2k/Kad.
Forcing the libcurl wxWebRequest backend to get a bindable socket works on
Linux and macOS, but on Windows the wxMSW libcurl backend never drives the
transfer - a forced curl request hangs and times out ("Resolving timed out").
Confirmed with a standalone wxWebRequest program, so it's a wxMSW+MSYS2 curl
backend bug, not amuled-specific.
Gate the curl selection + the CURLOPT_SOCKOPTFUNCTION bind on !__WINDOWS__.
Windows keeps its working default (WinHTTP), which has no interface-bind API,
so HTTP side-channels there stay unbound; Linux and macOS bind HTTP as before.
P2P traffic remains bound on all three platforms.
On Windows only the ed2k/Kad sockets are bound; HTTP runs on WinHTTP which can't be interface-bound. Log 'Binding aMule's peer-to-peer traffic to interface: X (HTTP updates use the default route)' there instead of the all-traffic message, so a Windows user isn't misled about the HTTP side-channels. Linux/macOS keep the all-traffic message (HTTP is bound there).
|
Update: substantial changes since this PR was opened, all driven by real end-to-end enforcement testing (headless 1. Linux now uses 2. aMule's HTTP side-channels are now bound too (Linux/macOS). The socket-layer bind only covered ed2k/Kad. The version check, IP2Country DB, and server.met-from-URL go through 3. Windows HTTP stays on WinHTTP (a documented gap). Forcing the libcurl backend to get a bindable HTTP socket works on Linux/macOS but hangs on Windows — a forced curl Net coverage, each verified by flipping the bound interface between a working one and a no-route one:
Builds clean on all three platforms (monolithic, daemon, remote GUI, amulecmd, amuleweb, unit tests). |
Forcing the curl backend for all HTTP changed the stack for every user, not just those using this feature - notably on macOS, where the default is the native URLSession backend (its own TLS/proxy handling). Gate the curl switch on a non-empty NetworkInterface, so non-binding users keep the platform default and only binding users get curl (which is what makes HTTP bindable). No behavioural change on Linux (default is already curl) or Windows (always WinHTTP). Verified on macOS: empty interface uses URLSession and the version check still succeeds; a bound interface uses curl and binds.
…iles via ffprobe (#280) * feat(mediaprobe): ffprobe subprocess module for local media metadata Standalone module that fronts an ffprobe subprocess to extract length / bitrate / codec from local shared files. Wiring into share-add and Preferences follows in subsequent commits. MediaProbe::AutoDetectPath() locates a usable binary in two steps: first a bare `ffprobe -version` invocation to catch the PATH-installed case, then a per-platform well-known-paths scan (Homebrew + MacPorts on macOS; Chocolatey + Scoop + WinGet layouts on Windows; distro- standard prefixes plus /snap/bin on Linux + OpenBSD). Fallback matters because GUI-launched processes get a minimal PATH on macOS (launchd default lacks /opt/homebrew) and unreliable PATH on Windows (system- level updates from Chocolatey install don't always propagate to running processes). MediaProbe::Probe() forks ffprobe with `-of default=nk=0:nw=1` so the output is a bare `key=value` stream — no JSON parser dependency needed (the tree has none). Length rounds to whole seconds (FT_MEDIA_LENGTH's wire format is uint32), bit_rate is converted from bps to kbps (FT_MEDIA_BITRATE), and the first stream's codec_name wins (video for video containers, audio for audio-only). Failures emit debug log lines but never surface user-visible errors — a file the probe can't read still shares fine, just without media tags. Boolean return with out-param instead of std::optional so the module matches the existing tree's C++ style. Callers MUST run Probe() off the main thread; a subsequent commit extends SharedFileList's batch path to do so. Prep for #140 Phase B — advertising media metadata from own shared files to ed2k servers + Kad. * feat(sharing): publish FT_MEDIA_{LENGTH,BITRATE,CODEC} to ed2k + Kad The Kad publisher at kademlia/kademlia/Search.cpp:1422 already iterates the file's media tags — but gates the whole emit on `file->GetMetaDataVer() > 0`. GetMetaDataVer had been a longstanding stub returning a hardcoded 0 with a TODO comment, so the code path was dead: no ed2k client has ever seen media metadata from an aMule-shared file. The ed2k server publish path (CreateOfferedFilePacket) had a matching stub — a comment reading "There, we could add MetaData info, if we ever get to have that." that built its outgoing tag list without touching m_taglist for media entries. Un-stub both: - GetMetaDataVer now derives from FT_MEDIA_LENGTH tag presence. No new persisted field: MediaProbe is the only source of the tag, and the tag itself already rides through known.met via the existing m_taglist load/save. Non-zero length is the "we've probed and have data worth publishing" signal, which is exactly what Kad's gate needs. - CreateOfferedFilePacket appends FT_MEDIA_LENGTH / FT_MEDIA_BITRATE as CTagVarInt (VBT-encoded for capable eMule clients + TYPETAG- INTEGER-capable servers, fixed 32-bit otherwise) and FT_MEDIA_CODEC as CTagString when the corresponding tag is present and non-empty / non-zero. Each tag is optional per file; a file with only length publishes only length. Prep for #140 Phase B — once MediaProbe wiring lands in SharedFileList's share-add path, tags flow to peers automatically. No change to known.met format (m_taglist already covers the persistence layer). * feat(prefs): [MediaMetadata]/Enabled + FFProbePath + UI panel Preferences plumbing for #140's ffprobe subprocess. Cfg items live in a fresh /MediaMetadata/ config namespace (won't clash with any /eMule/ key) with Enabled default false — an upgraded install won't kick off background probing until the user opts in from Preferences -> Files. UI panel goes at the bottom of PreferencesFilesTab: an Enable checkbox plus a "Path to ffprobe:" row with text field, Browse and Detect buttons. Browse routes through the existing OnButtonBrowseApplication switch (same file-selector infra the video-player / browser fields use). Detect fires MediaProbe::AutoDetectPath() and either populates the field or shows a friendly "install ffmpeg or Browse manually" info dialog. The whole box hides in amulegui via PrefsUnifiedDlg's amuledOnlyPrefs[] — probing runs daemon-side, the remote GUI has no business setting the path. IDC IDs sit in the 10420-10423 functional band and 10370 orphan- label slot, chosen to leave a 10-ID cushion above bind-to- interface's IDC_INTERFACE = 10410 so parallel branches can grow without collision. MediaProbe moves to COMMON_SOURCES so the Detect button's handler still links in remote-GUI builds (the button is hidden there but the event-table binding still needs the symbol). Follow-ups on the same branch: SharedFileList wiring (probe on share-add + known.met load), extension gate, background-probe throttling. * feat(shared): probe media metadata off-main + attach to CKnownFile Closes the #140 wiring loop: shared audio / video files now get probed with ffprobe, and the resulting FT_MEDIA_LENGTH / _BITRATE / _CODEC tags are attached to the CKnownFile so the earlier ed2k + Kad publisher un-stubs pick them up. Threading + throttling: - New CMediaProbeTask (ETP_Low) rides on the existing CThreadScheduler queue. That queue serialises tasks — a large library at first-boot-after-upgrade retrofits one file at a time, never stepping on hashing or completion which run at higher priority. No custom thread pool. - Task ctor snapshots the ffprobe path so the worker never touches thePrefs, and remembers just the file's CMD4Hash + CPath. Result marshals back via CMediaProbeEvent (new MULE_EVT_MEDIA_PROBE), main thread resolves the hash to a live CKnownFile via CKnownFileList::FindKnownFileByID (the file may have been unshared while we were probing) and calls AddTagUnique for each populated field, MarkECChanged, and knownfiles->Save so the tags survive a crash. Gating: - Preference /MediaMetadata/Enabled must be true AND FFProbePath non-empty. - Extension gate: only ED2KFT_AUDIO or ED2KFT_VIDEO files (from GetED2KFileTypeID) — skips the mass of docs / archives / images a typical share tree carries. - Already-probed gate: skip when FT_MEDIA_LENGTH > 0. This is the retrofit cache — once a file has been probed successfully its tag rides through known.met and future launches skip re-probing. Hook site is CSharedFileList::AddFile — the single choke point for both runtime share-adds (user picks a new folder / drops a file in Incoming) AND Reload()'s known.met walk, so an upgraded install retrofits every existing shared file on the first launch after the user enables the feature. No separate load-vs-add code path. Event-table bindings for MULE_EVT_MEDIA_PROBE landed in both amuled.cpp (daemon) and amule-gui.cpp (monolithic / remote GUI) so the handler fires regardless of which app the scheduler dispatched from. * chore(clang-format): apply clang-format-18 to #140 files * i18n(pot): regenerate catalogs for #140 UI additions Rerun of scripts/update-po.sh on top of the post-#281 (bind-to- interface) master so the Media metadata strings coexist with the new bind-to-interface strings. Replaces the earlier standalone regen that was skipped during the rebase. * feat(prefs): gray out ffprobe path controls when Media metadata is off The [MediaMetadata] Enabled checkbox now drives the enabled state of IDC_MEDIAMETA_FFPROBEPATHTEXT / _FFPROBEPATH / _FFPROBEBROWSE / _FFPROBEDETECT via the existing OnCheckBoxChange handler, and the initial state is applied at dialog open. Previously the path input, Browse and Detect buttons stayed live-looking with the feature disabled, which read as "I can configure ffprobe here" while nothing downstream would ever run.
Closes amule-org#173. Bind all of aMule's egress to a chosen network interface, unprivileged, so traffic can't leak via the default route (e.g. a VPN tunnel). Uses the real per-platform egress constraints - SO_BINDTODEVICE (Linux), IP_BOUND_IF (macOS) and IP_UNICAST_IF (Windows) - with no elevated privileges, except where a kernel requires CAP_NET_RAW for SO_BINDTODEVICE, in which case the failed bind is warned about loudly instead of failing silently. Covers the TCP listen socket, outbound TCP (servers + peers) and the UDP sockets shared by the ed2k client, ed2k server and Kad. aMule's HTTP side-channels (version check, IP2Country, server.met) are bound on Linux and macOS via the libcurl backend; on Windows they stay on WinHTTP (its wxWidgets curl backend hangs, and WinHTTP has no interface-bind API), which the startup log states honestly. Peer-to-peer traffic is bound on all three platforms. Adds an editable "Bind to network interface" field on the Connection preferences page, populated with the machine's interfaces and stored as /eMule/NetworkInterface. Like the local-address bind it is daemon-side and hidden in the remote GUI; changing it is flagged restart-needed.
…iles via ffprobe (amule-org#280) * feat(mediaprobe): ffprobe subprocess module for local media metadata Standalone module that fronts an ffprobe subprocess to extract length / bitrate / codec from local shared files. Wiring into share-add and Preferences follows in subsequent commits. MediaProbe::AutoDetectPath() locates a usable binary in two steps: first a bare `ffprobe -version` invocation to catch the PATH-installed case, then a per-platform well-known-paths scan (Homebrew + MacPorts on macOS; Chocolatey + Scoop + WinGet layouts on Windows; distro- standard prefixes plus /snap/bin on Linux + OpenBSD). Fallback matters because GUI-launched processes get a minimal PATH on macOS (launchd default lacks /opt/homebrew) and unreliable PATH on Windows (system- level updates from Chocolatey install don't always propagate to running processes). MediaProbe::Probe() forks ffprobe with `-of default=nk=0:nw=1` so the output is a bare `key=value` stream — no JSON parser dependency needed (the tree has none). Length rounds to whole seconds (FT_MEDIA_LENGTH's wire format is uint32), bit_rate is converted from bps to kbps (FT_MEDIA_BITRATE), and the first stream's codec_name wins (video for video containers, audio for audio-only). Failures emit debug log lines but never surface user-visible errors — a file the probe can't read still shares fine, just without media tags. Boolean return with out-param instead of std::optional so the module matches the existing tree's C++ style. Callers MUST run Probe() off the main thread; a subsequent commit extends SharedFileList's batch path to do so. Prep for amule-org#140 Phase B — advertising media metadata from own shared files to ed2k servers + Kad. * feat(sharing): publish FT_MEDIA_{LENGTH,BITRATE,CODEC} to ed2k + Kad The Kad publisher at kademlia/kademlia/Search.cpp:1422 already iterates the file's media tags — but gates the whole emit on `file->GetMetaDataVer() > 0`. GetMetaDataVer had been a longstanding stub returning a hardcoded 0 with a TODO comment, so the code path was dead: no ed2k client has ever seen media metadata from an aMule-shared file. The ed2k server publish path (CreateOfferedFilePacket) had a matching stub — a comment reading "There, we could add MetaData info, if we ever get to have that." that built its outgoing tag list without touching m_taglist for media entries. Un-stub both: - GetMetaDataVer now derives from FT_MEDIA_LENGTH tag presence. No new persisted field: MediaProbe is the only source of the tag, and the tag itself already rides through known.met via the existing m_taglist load/save. Non-zero length is the "we've probed and have data worth publishing" signal, which is exactly what Kad's gate needs. - CreateOfferedFilePacket appends FT_MEDIA_LENGTH / FT_MEDIA_BITRATE as CTagVarInt (VBT-encoded for capable eMule clients + TYPETAG- INTEGER-capable servers, fixed 32-bit otherwise) and FT_MEDIA_CODEC as CTagString when the corresponding tag is present and non-empty / non-zero. Each tag is optional per file; a file with only length publishes only length. Prep for amule-org#140 Phase B — once MediaProbe wiring lands in SharedFileList's share-add path, tags flow to peers automatically. No change to known.met format (m_taglist already covers the persistence layer). * feat(prefs): [MediaMetadata]/Enabled + FFProbePath + UI panel Preferences plumbing for amule-org#140's ffprobe subprocess. Cfg items live in a fresh /MediaMetadata/ config namespace (won't clash with any /eMule/ key) with Enabled default false — an upgraded install won't kick off background probing until the user opts in from Preferences -> Files. UI panel goes at the bottom of PreferencesFilesTab: an Enable checkbox plus a "Path to ffprobe:" row with text field, Browse and Detect buttons. Browse routes through the existing OnButtonBrowseApplication switch (same file-selector infra the video-player / browser fields use). Detect fires MediaProbe::AutoDetectPath() and either populates the field or shows a friendly "install ffmpeg or Browse manually" info dialog. The whole box hides in amulegui via PrefsUnifiedDlg's amuledOnlyPrefs[] — probing runs daemon-side, the remote GUI has no business setting the path. IDC IDs sit in the 10420-10423 functional band and 10370 orphan- label slot, chosen to leave a 10-ID cushion above bind-to- interface's IDC_INTERFACE = 10410 so parallel branches can grow without collision. MediaProbe moves to COMMON_SOURCES so the Detect button's handler still links in remote-GUI builds (the button is hidden there but the event-table binding still needs the symbol). Follow-ups on the same branch: SharedFileList wiring (probe on share-add + known.met load), extension gate, background-probe throttling. * feat(shared): probe media metadata off-main + attach to CKnownFile Closes the amule-org#140 wiring loop: shared audio / video files now get probed with ffprobe, and the resulting FT_MEDIA_LENGTH / _BITRATE / _CODEC tags are attached to the CKnownFile so the earlier ed2k + Kad publisher un-stubs pick them up. Threading + throttling: - New CMediaProbeTask (ETP_Low) rides on the existing CThreadScheduler queue. That queue serialises tasks — a large library at first-boot-after-upgrade retrofits one file at a time, never stepping on hashing or completion which run at higher priority. No custom thread pool. - Task ctor snapshots the ffprobe path so the worker never touches thePrefs, and remembers just the file's CMD4Hash + CPath. Result marshals back via CMediaProbeEvent (new MULE_EVT_MEDIA_PROBE), main thread resolves the hash to a live CKnownFile via CKnownFileList::FindKnownFileByID (the file may have been unshared while we were probing) and calls AddTagUnique for each populated field, MarkECChanged, and knownfiles->Save so the tags survive a crash. Gating: - Preference /MediaMetadata/Enabled must be true AND FFProbePath non-empty. - Extension gate: only ED2KFT_AUDIO or ED2KFT_VIDEO files (from GetED2KFileTypeID) — skips the mass of docs / archives / images a typical share tree carries. - Already-probed gate: skip when FT_MEDIA_LENGTH > 0. This is the retrofit cache — once a file has been probed successfully its tag rides through known.met and future launches skip re-probing. Hook site is CSharedFileList::AddFile — the single choke point for both runtime share-adds (user picks a new folder / drops a file in Incoming) AND Reload()'s known.met walk, so an upgraded install retrofits every existing shared file on the first launch after the user enables the feature. No separate load-vs-add code path. Event-table bindings for MULE_EVT_MEDIA_PROBE landed in both amuled.cpp (daemon) and amule-gui.cpp (monolithic / remote GUI) so the handler fires regardless of which app the scheduler dispatched from. * chore(clang-format): apply clang-format-18 to amule-org#140 files * i18n(pot): regenerate catalogs for amule-org#140 UI additions Rerun of scripts/update-po.sh on top of the post-amule-org#281 (bind-to- interface) master so the Media metadata strings coexist with the new bind-to-interface strings. Replaces the earlier standalone regen that was skipped during the rebase. * feat(prefs): gray out ffprobe path controls when Media metadata is off The [MediaMetadata] Enabled checkbox now drives the enabled state of IDC_MEDIAMETA_FFPROBEPATHTEXT / _FFPROBEPATH / _FFPROBEBROWSE / _FFPROBEDETECT via the existing OnCheckBoxChange handler, and the initial state is applied at dialog open. Previously the path input, Browse and Detect buttons stayed live-looking with the feature disabled, which read as "I can configure ffprobe here" while nothing downstream would ever run.
The interface bind added in #281 (IP_UNICAST_IF / IP_BOUND_IF) pins every socket aMule opens — including the External Connection listener — to a single interface, so ed2k/Kad cannot run over a VPN tunnel while the EC control port stays on the LAN. Decouple the EC listener with a new daemon-side setting, /ExternalConnect/ECNetworkInterface (empty = any), that binds only aMule's EC acceptor, independent of the global P2P interface pin. It sits beside the existing ECAddress IP bind, giving the EC channel both its own IP and its own interface. CLibSocketServer gains a per-server interface override; only CExternalConnListener uses it. ed2k/Kad TCP and UDP, outbound connections and HTTP keep following the global setting untouched. A "Bind to network interface" selector is added to the Remote Controls page (reusing the existing P2P label); like the other EC-listener settings it is daemon-only — hidden in the remote GUI and not carried over EC — and flagged restart-needed.
Closes #173.
The existing
Address=setting binds to a local IP, which on Linux does not override the kernel's routing decision — outbound traffic can still leak via the default-route interface even when the bind IP belongs to a VPN tunnel. This adds a proper interface bind that pins the egress interface itself, closing that leak.It uses
IP_UNICAST_IF/IPV6_UNICAST_IFon Linux and Windows, andIP_BOUND_IF/IPV6_BOUND_IFon macOS/BSD. These require no elevated privileges, unlikeSO_BINDTODEVICEwhich needsCAP_NET_RAW— aMule keeps running as a normal user.The bind is applied to every socket aMule opens: the TCP listen socket, outbound TCP (client and server connections), and the UDP socket shared by the ed2k client, ed2k server and Kad.
The Connection preferences page gets a "Bind to network interface" control next to "Bind local address to". It is an editable drop-down populated with the machine's interfaces (
getifaddrson POSIX,GetAdaptersAddresseson Windows, loopback filtered out), but stays editable so an interface that is down when the dialog opens — a VPN tunnel — can still be typed in. The stored value is a POSIX interface name (en0,eth0,tun0), a Windows adapter friendly name (Ethernet,Wi-Fi), or a bare numeric index; empty means "any" (unchanged default). Like the address bind, it is a daemon-side setting stored as/eMule/NetworkInterfaceand hidden in the remote GUI. Changing it is flagged restart-needed, consistent with the TCP/UDP port settings.Because it is a leak-prevention setting, the outcome is made visible: on startup a valid interface is confirmed in the log (
Binding all network traffic to interface: <name>), and an unresolvable one produces a loudWARNINGthat traffic is not bound and may leave via the default route — so a typo can't silently defeat the protection. All resolution lives in one place in the socket layer, which the core reuses to validate the preference before any socket opens.Tested: builds clean on macOS, Linux and Windows (monolithic, daemon, remote GUI, amulecmd, amuleweb and the unit tests). The interface-bind syscall was verified returning success as an unprivileged user on all three platforms.