Skip to content

Add configurable Remote Endpoint transport - #9

Closed
crossly wants to merge 1 commit into
seanhowarthdev:mainfrom
crossly:feat/remote-endpoint-pr
Closed

crossly wants to merge 1 commit into
seanhowarthdev:mainfrom
crossly:feat/remote-endpoint-pr

Conversation

@crossly

@crossly crossly commented Sep 16, 2026

Copy link
Copy Markdown

Summary

Adds an opt-in Remote Endpoint transport to Roam Control while preserving the existing LocalDevVPN path.

The feature is intended for a router TCP hairpin path. The default endpoint is 192.168.31.1:49152; the router must forward the iPhone's RPPairing port and subsequent high TCP ports back to that same iPhone.

Changes

  • Add Settings → Device → Connection Mode with LocalDevVPN and Remote Endpoint.
  • Persist and validate an IPv4/IPv6 endpoint and TCP port.
  • Probe the configured endpoint directly and never open or fall back to LocalDevVPN in remote mode.
  • Keep LocalDevVPN mDNS discovery, mobile-data guidance and recovery behavior unchanged for the local mode.
  • Keep cryptographic RPPairing verification in the native bridge while making mDNS metadata verification conditional for remote mode.
  • Add mode-aware Connection Health state, endpoint information, connection stages, errors and copied diagnostics.
  • Require/import an existing RPPairing file for Remote Endpoint mode; the pairing record remains in the iPhone Keychain.
  • Rebuild and commit both arm64 RoamPairingFFI.xcframework slices after the FFI signature change.
  • Update the installation, user, build and regression documentation.

Validation

  • cargo fmt --check --edition 2024 Native/RoamPairingFFI/src/lib.rs
  • cargo test --manifest-path Native/RoamPairingFFI/Cargo.toml — 3 passed
  • Release invariant checks — passed
  • Swift syntax parsing for affected files — passed
  • GitHub Actions run 35075766453 on the fork's release commit — passed with both native slices, an unsigned iPhoneOS Release archive, IPA packaging and metadata checks

A physical iPhone has not yet been used to validate the Remote Endpoint fixed-location, update, Stop & Restore, interrupted-session recovery or long-running background flows. Those checks remain required before treating the transport as generally stable.

Privacy and safety

No pairing record, credential, signing material, private location or analytics configuration is included. Remote Endpoint forwards encrypted RPPairing/CDTunnel traffic; it does not send the pairing file to the router.

@seanhowarthdev

Copy link
Copy Markdown
Owner

Thanks for putting this together. This looks like a pretty substantial piece of work and an interesting alternative to the current LocalDevVPN connection path.

Before I properly review the implementation, I'd be interested to understand a bit more about the use case that led you to build it. In particular, have you designed this so Roam Control could work while another VPN, such as WireGuard, remains active on the iPhone, without needing to switch over to LocalDevVPN? That's one scenario that could be useful to me personally.

I noticed from the PR that it hasn't been tested on a physical iPhone yet. Given how much of the pairing and connection behaviour depends on the real device, I'd definitely want to see real-device testing before considering integration.

Just to set expectations as well, I'm currently concentrating on my own stability work, fixes and an existing roadmap for Roam Control, so reviewing and integrating PRs isn't my top priority at the moment. This one also touches enough of the connection architecture that I'd want to take my time understanding and testing it rather than rush it in.

I'm happy to keep the PR open though and discuss the approach with you in the meantime. Thanks again for taking the time to work on it.

@crossly

crossly commented Sep 16, 2026

Copy link
Copy Markdown
Author

First, SideStore’s settings include an option that allows users to use a remote endpoint instead of LocalDevVPN. This PR introduces that functionality.
In China, we generally keep a VPN proxy enabled to access the global Internet freely. However, LocalDevVPN occupies the only available IOA VPN channel for network tools.
Therefore, I deployed the core functionality of LocalDevVPN on my router. This allows me to use Roam Control to change my location seamlessly, without having to run LocalDevVPN itself.

The core functionality of LocalDevVPN is a loopback tunnel, which can be implemented on a router using iptables.

With this setup, as long as I am connected to my home network, I can seamlessly refresh my apps through SideStore and use Roam Control at the same time.

The only requirement for this version is that the device must first be paired once using LocalDevVPN. After that, the remote endpoint can be used.

Finally, I built an IPA from this modified version and installed it on my iPhone for testing. I submitted this PR as a friendly contribution to the project. Please decide whether to adopt it based on your RoamMap, and I fully respect your project plans.

@seanhowarthdev

Copy link
Copy Markdown
Owner

Thanks, that explains the use case much better, especially why being able to use a remote endpoint matters when

LocalDevVPN would otherwise take up the VPN connection on the phone.

It's also really helpful to know you've now built the modified version as an IPA and tested it on your own iPhone. The requirement to pair once through LocalDevVPN first makes sense for this initial implementation too.

I'm definitely interested in this. I don't want to bring a connection architecture change into the current release work at this stage, but I'm going to keep the PR open and look at it properly for a future release.

I'll want to go through the implementation and think about how the endpoint should be configured and presented in Roam Control, as well as what validation and security checks we should have around it, rather than just merging it straight in.

Thanks again for putting the work into this and for explaining your setup. It's a really interesting use case.

@crossly

crossly commented Sep 17, 2026

Copy link
Copy Markdown
Author

A few additional points

  1. The working principle of LocalDevVPN on the router
ROUTER_IP = 192.168.31.1
VPN_PEER_IP = 10.7.0.1
TARGET_DEVICE = target iPhone DHCP-reserved IP or MAC
PORT_RANGE = TCP 49152..65535

on LAN prerouting:
    if packet.src == TARGET_DEVICE
       and packet.protocol == TCP
       and packet.dst in {ROUTER_IP, VPN_PEER_IP}
       and packet.dst_port in PORT_RANGE:
        DNAT packet.dst_ip -> TARGET_DEVICE
        mark packet as hairpin

on LAN postrouting:
    if packet.mark == hairpin
       and packet.dst_ip == TARGET_DEVICE
       and packet.protocol == TCP
       and packet.dst_port in PORT_RANGE:
        SNAT packet.src_ip -> ROUTER_IP

enable TCP conntrack
persist the rules
run fw4 check
run fw4 reload

Traffic flow:

iPhone -> 192.168.31.1:49152
       -> DNAT to the same iPhone
       -> SNAT source to 192.168.31.1
       -> loop back into the iPhone's Remote Pairing service
  1. Works video
ScreenRecording_09-17-2026_1080p_under10MB.mp4

@seanhowarthdev

Copy link
Copy Markdown
Owner

Thanks for adding the extra detail and the video. Seeing the router setup and the actual traffic flow makes the approach much clearer, and it's a clever way of avoiding the LocalDevVPN limitation while keeping another VPN active.

I've had a bit more time to think about this and also about how I want to handle development of Roam Control generally. I'm going to keep implementation and architecture changes on my side rather than merge external feature PRs, particularly for something this deep in the connection stack.

I don't want that to take away from the work you've done here though. The use case you've identified is genuinely interesting, and the explanation, router configuration and testing you've shared are really useful. I may explore alternative connection options myself in the future, especially for situations where LocalDevVPN conflicts with another VPN, but I don't want to promise a particular implementation or release at this point.

I'm going to close the PR rather than leave your implementation sitting open indefinitely when I don't intend to merge it. Thanks again for putting so much work into it and for taking the time to explain how you're using it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants