[Openvpn-devel,OpenVPN3] tun/mac: make server bypass route idempotent to avoid orphaned route
Commit Message
On macOS the server bypass host route (remote /32 via the physical default
gateway, installed when redirect-gateway is active) is added with a plain
"route add". If a previous session terminated uncleanly (sleep, network
change or process crash) the teardown ActionList never runs and a stale copy
of this route survives in the routing table.
On the next connection the "route add" fails because the route already
exists. Command::execute() detects "File exists" and throws; ActionList::
execute() records the route's mark as failed and remove_marked() then drops
the *paired delete* from the teardown list (tunsetup.hpp: "since we should
not undo failed actions, remove them"). The route is therefore never cleaned
up, and because the stale entry still points at the previous network's
gateway, once that gateway is no longer on-link every datagram to the server
fails with EADDRNOTAVAIL:
UDP send exception: send: Can't assign requested address
The client then loops RECONNECTING/RESOLVE until it times out, while the
server and all other users are unaffected.
Make the bypass route installation idempotent by issuing a best-effort
"route delete" immediately before the "route add". Deleting a non-existent
route is harmless ("not in table", non-fatal), while a stale orphan is
removed so the add always installs a fresh route bound to the current
gateway and stays managed for teardown. This mirrors the REPLACE semantics
already used by the Linux sitnl backend on EEXIST.
Signed-off-by: Luan Camara <luancamara@gmail.com>
---
openvpn/tun/mac/client/tunsetup.hpp | 13 +++++++++++++
1 file changed, 13 insertions(+)
@@ -414,6 +414,19 @@ class Setup : public TunBuilderSetup::Base
{
Action::Ptr c, d;
add_del_route(pull.remote_address.address, 32, gw4.gateway_addr_str(), gw4.iface(), 0, c, d);
+ // Make the server bypass route idempotent. This host route must
+ // point at the *current* physical default gateway. If a previous
+ // session ended uncleanly (sleep, network change or crash) a stale
+ // copy of this route can survive with an outdated gateway. A plain
+ // "route add" then fails with "File exists"; execute() flags the add
+ // as failed and remove_marked() drops the paired delete from the
+ // teardown list, so the stale route is never cleaned up. Once the old
+ // gateway is no longer on-link it produces a permanent
+ // "UDP send: Can't assign requested address" and the client can only
+ // time out. Issue a best-effort delete first (a no-op "not in table"
+ // when absent, non-fatal) so the add always installs a fresh route
+ // bound to the current gateway and stays managed for teardown.
+ create.add(d); // pre-delete any stale/orphaned copy, then add
create.add(c);
destroy.add(d);
}