Workflow status is tracked in GitHub: https://github.com/emulebb/emulebb/issues/35. This local document is retained as an engineering spec/evidence record.
FEAT-098 - Add strict bound public-IP guard for VPN profiles¶
Summary¶
Harden explicit VPN/interface-bound profiles by proving that the public IP observed by eMuleBB belongs to an operator-approved VPN public-IP range.
Current bind policy verifies the local P2P socket path: eMuleBB can resolve a
configured interface such as hide.me, bind listeners to its local address,
and apply IP_UNICAST_IF where Windows supports it. That is necessary, but it
does not prove that a split-tunnel VPN provider is actually routing
emulebb.exe through the VPN public egress path.
This feature adds a guard for explicit VPN binding. The RC1 slice is interface-first: when enabled, eMuleBB blocks public P2P networking unless the configured VPN interface is usable and runtime monitoring is armed. A user-configured public IPv4 CIDR allow-list adds public-exit validation; it is not required merely to enable the guard.
The remaining post-RC strict work is to centralize more protocol-observed public-IP evidence after eD2K/Kad connect, centralize peer-connect gating at the core peer connect primitive, and decide whether a future warning-only mode is worth shipping.
Current RC1 Slice¶
Current shipped behavior uses:
VpnGuardMode=Off|BlockVpnGuardAllowedPublicIpCidrs=<optional public IPv4 CIDRs>
VpnGuardMode=Block requires a named P2P bind interface. Empty CIDRs are
valid and mean interface-only guarding. Non-empty CIDRs trigger a bound HTTP
public IPv4 probe before public P2P startup; the first successful provider
response wins and must match the allow-list. Runtime checks repeat after
startup approval and on bind-interface change notifications; runtime failure
closes the app.
Public VPN live campaigns now require operator-local VPN Guard live config unless the scenario explicitly tests guard-off behavior. The config owns provider-specific split-tunnel connect, allow-list, check, and restore hooks.
Intended Shape¶
- Add a preference-controlled public-IP guard for explicit interface-bound profiles.
- Let users configure one or more allowed VPN public-IP CIDR ranges.
- Run a bound HTTP public-IP probe before public P2P connect.
- Compare eD2K/Kad-reported public IPs against the same CIDR allow-list after protocol login or discovery.
- Gate direct peer connection attempts at
CUpDownClient::TryToConnect()or an equivalent core primitive so download reasks, upload admission, direct callbacks, shared-file browse, and queued safe-send paths cannot bypass a guarded block. - Fail closed in
Blockmode when the guard has a definite public-IP mismatch or cannot complete a required public-IP check. Empty CIDRs are allowed and mean interface-only guard coverage. - Surface the guard state in Network Information, logs, and local controller diagnostics.
Preferences¶
Current preference surface:
VpnGuardMode=Block|OffVpnGuardAllowedPublicIpCidrs=<comma-or-space-separated public IPv4 CIDRs>
Default behavior for existing profiles remains Off. A profile with a
configured BindInterface and VpnGuardMode=Block should not connect to the
public P2P network unless the configured interface is usable and runtime
monitoring is armed. If VpnGuardAllowedPublicIpCidrs is non-empty, at least
one bound HTTP public-IP probe result must match the allow-list before startup
public P2P is allowed.
Users can turn the guard off for unusual network setups, but public live-test
profiles should keep it in Block mode. A future warning-only mode remains
open design work.
HTTP Public-IP Probe¶
The HTTP probe must not reuse the current general HttpTransfer path if that
path remains intentionally unbound. It needs a narrow bound probe path that uses
the same resolved P2P bind decision as the P2P sockets:
- bind to the resolved local P2P address where applicable
- apply the resolved interface index with
IP_UNICAST_IFwhere supported - call configured public IPv4 echo URLs in order
- accept only a plain valid public IPv4 result
- compare the result to the CIDR allow-list
The current RC1 slice uses built-in HTTP IPv4 echo providers and accepts the first valid public IPv4 result. Future work may make the provider list configurable if operators need it.
eD2K And Kad Public-IP Verification¶
Existing public-IP sources should feed the same guard:
- eD2K server-reported public IP during login and LowID handling
- peer
OP_PUBLICIP_ANSWERresponses - Kad public IPv4 state when available
This is remaining post-RC strict work. If a protocol-observed public IP is
outside the allowed CIDR list in Block mode, eMuleBB should disconnect or
block public P2P for the session and report the mismatch. A future Warn mode
would log and surface the mismatch without disconnecting if that mode is
accepted.
Scope Constraints¶
- Do not hardcode hide.me behavior in the app. Provider-specific split-tunnel registration and restart remain Python harness or operator responsibilities.
- Do not write
hide.meintoBindAddr; explicit interface binding remainsBindInterface=hide.mewith an empty P2PBindAddrfor hide.me profiles. - Do not claim this is a full VPN kill switch. It is an app-level public-IP guard for public P2P networking.
- Do not rely only on toolbar, web, REST, or Kad/server start-command gates. Any peer-level path that can open a public TCP or callback path must inherit the same guard decision from the core connect primitive.
- Do not use adapter name, local VPN address, route table, UPnP success, LowID, or interface metrics as proof of VPN public egress by themselves.
- Keep WebServer/REST binding separate from P2P bind policy.
- Keep default eD2K/Kad wire behavior stock-compatible; this item changes local connect gating and diagnostics, not protocol semantics.
Diagnostics¶
Expose low-cardinality guard evidence:
- configured guard mode
- configured CIDR allow-list status, redacted where needed
- resolved P2P bind interface, address, and interface index
- last HTTP probe result and decision
- last eD2K/Kad observed public IP and decision
- final guard state: unchecked, checking, matched, mismatched, unavailable, or disabled
- reason for any block or warning
An optional unbound or router-WAN public-IP can be shown as a canary if it is clearly labeled as diagnostic-only. It must not replace the CIDR allow-list as the enforcement source of truth.
Test And Harness Expectations¶
The Python live-wire and materialized-install harnesses should be able to seed
VpnGuardAllowedPublicIpCidrs into test profiles from local operator config.
This lets public live E2E runs prove:
- installer-backed profiles still use
BindInterface=hide.me - P2P
BindAddrremains empty unless the operator explicitly chooses an address bind - the executable is present in developer-local hide.me split-tunnel config when that opt-in harness feature is enabled
- HTTP public-IP probe matches the allowed CIDR list before connect when CIDRs are configured
- eD2K/Kad observed public IP matches the allowed CIDR list after connect when post-RC protocol-observed verification lands
- mismatch failures collect logs, REST status, Network Information evidence, and live-wire config redacted evidence
Acceptance Criteria¶
- [x] Preferences persist guard mode and allowed CIDRs.
- [x] CIDR parsing accepts valid IPv4 CIDRs and rejects malformed or private-only enforcement lists with clear diagnostics.
- [x] Bound HTTP public-IP probe uses the explicit P2P bind decision instead of the unbound general HTTP helper.
- [x]
Blockmode permits interface-only guard when no allowed CIDR is configured for an explicit VPN-bound profile. - [x]
Blockmode blocks startup public P2P when required HTTP public-IP evidence is outside the allow-list or unavailable. - [ ]
Blockmode prevents or tears down public P2P when eD2K, peer, or Kad public-IP evidence is outside the allow-list. - [ ] Peer-level connect attempts are centrally guard-gated so reasks, upload admission, callbacks, shared-file browse, and queued safe-send paths cannot bypass a VPN Guard block or unarmed runtime monitor.
- [ ]
Warnmode records the same evidence without blocking if accepted for a future release. - [x] Network Information, logs, and REST status expose the guard decision and startup block evidence.
- [x] Live-wire tests can seed allowed CIDRs and fail closed on mismatch without adding new PowerShell scripts.
Validation¶
python -m emule_workspace validate- focused native tests for IPv4/CIDR parsing and match decisions
- seam tests for bound HTTP probe success, mismatch, timeout, malformed response, and endpoint fallback
- live-wire public-network smoke with
BindInterface=hide.me, empty P2PBindAddr, and an allowed CIDR list - negative harness proof that a mismatching public IP fails closed and reports actionable evidence