Class VpnTunnel
The packet loop, written once for both platforms.
public class MyTunnel extends VpnTunnel {
protected void onStart(TunnelConfiguration cfg) { }
protected void onPacket(PacketBuffer p) { forward(p); }
protected void onStop(TunnelStopReason r) { }
}
Where this runs
On Android, in the app's own process, inside the port's VpnService.
On iOS, in a Network Extension: a separate process, with a virtual
machine of its own, which the build generates for a project that sets
ios.vpn.tunnel.
Write the tunnel as though it ran somewhere else, because on one of the
two platforms it does. Everything it needs travels in
TunnelConfiguration.getData(); a static the app set happens to be there
on Android and is not a thing to rely on -- on iOS it is not there at
all, and reaching for the app's own classes drags them into the
extension's translation, where the ones backed by UIKit fail its link.
What is not there, on iOS
The extension carries the translated program and the virtual machine and
no networking stack. Socket, ConnectionRequest and
anything else that reaches the implementation find nothing there, and
ParparVM's java.net is URI and URL -- there are no sockets in it
either. So an iOS tunnel inspects, rewrites, drops and forward(PacketBuffer)s
packets; it cannot open a connection to a remote server. On Android it
can, because it runs in the app's own process. A tunnel that needs to
relay is not the same class on both platforms, and the difference is
worth an interface rather than a surprise.
Constructing it
On Android the app constructs the tunnel and hands it to
Tunnels.start(VpnTunnel, TunnelSetup). On iOS the EXTENSION constructs it, because
Tunnels.start(VpnTunnel, TunnelSetup) ran in another process that has since gone away -- so an
iOS tunnel needs an accessible no-argument constructor, and the class
named by ios.vpn.tunnel.class is the one it calls. A tunnel with only a
parameterised constructor fails the build rather than the device.
What is expensive
A host process for a tunnel runs under a memory budget far below an
app's, and is killed rather than warned when it exceeds it. The buffers
handed to onPacket(PacketBuffer) are reused for exactly that reason, so copying
every packet -- or holding one past the call -- gives back the headroom
the pooling was for. See PacketBuffer.
-
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected final voidforward(byte[] packet, int offset, int length) Sends bytes the tunnel produced itself.protected final voidforward(PacketBuffer packet) Sends a packet back to the device.protected abstract voidonPacket(PacketBuffer packet) One packet arrived from the device, bound for the far end.protected abstract voidonStart(TunnelConfiguration configuration) The tunnel is up and the platform has configured the link.protected abstract voidonStop(TunnelStopReason reason) The tunnel is going down, for the given reason.
-
Constructor Details
-
VpnTunnel
public VpnTunnel()
-
-
Method Details
-
onStart
The tunnel is up and the platform has configured the link. -
onPacket
One packet arrived from the device, bound for the far end.
The buffer is REUSED once this returns; see
PacketBuffer. -
onStop
The tunnel is going down, for the given reason. -
forward
Sends a packet back to the device.
Safe to call with the buffer
onPacketwas given, which is the ordinary pass-through case and copies nothing. -
forward
protected final void forward(byte[] packet, int offset, int length) Sends bytes the tunnel produced itself.
-