Skip to content

Commit b18b091

Browse files
author
alberto lalama
authored
Enable restart behavior on start API (#8)
1 parent c0a1c2f commit b18b091

File tree

9 files changed

+78
-31
lines changed

9 files changed

+78
-31
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ This plugin targets Android devices running Marshmellow (API 23), or higher. Thi
1515
`start(socksServerAddress:string) : Promise<string>;`
1616

1717
Starts the VPN service, and tunnels all the traffic to the SOCKS5 server at `socksServerAddress`.
18+
Restarts tunneling while preserving the VPN connection if called when the plugin is already running.
1819

1920
`stop(): Promise<string>;`
2021

src/android/org/uproxy/tun2socks/DnsResolverService.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ public int onStartCommand(Intent intent, int flags, int startId) {
5656
Log.e(LOG_TAG, "Failed to receive socks server address.");
5757
return START_NOT_STICKY;
5858
}
59+
60+
if (m_dnsResolver != null) {
61+
m_dnsResolver.interrupt(); // Stop resolver to handle reconnect.
62+
}
5963
m_dnsResolver = new DnsUdpToSocksResolver(DnsResolverService.this);
6064
m_dnsResolver.start();
6165

@@ -169,6 +173,7 @@ public void run() {
169173
e.printStackTrace();
170174
continue;
171175
}
176+
172177
if (!writeUdpPacketToStream(dnsOutputStream, udpPacket)) {
173178
continue;
174179
}

src/android/org/uproxy/tun2socks/Tun2Socks.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ protected void startTunnelService(Context context) {
140140
Log.i(LOG_TAG, "starting tunnel service");
141141
if (isServiceRunning()) {
142142
Log.w(LOG_TAG, "already running service");
143+
TunnelManager tunnelManager = TunnelState.getTunnelState().getTunnelManager();
144+
if (tunnelManager != null) {
145+
tunnelManager.restartTunnel(m_socksServerAddress);
146+
}
143147
return;
144148
}
145149
Intent startTunnelVpn = new Intent(context, TunnelVpnService.class);

src/android/org/uproxy/tun2socks/Tun2SocksJni.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ public class Tun2SocksJni {
1515
// udpgw server.
1616
//
1717
// The tun device file descriptor should be set to non-blocking mode.
18-
// tun2Socks takes ownership of the tun device file descriptor and will close
19-
// it when tun2socks is stopped.
18+
// tun2Socks does *not* take ownership of the tun device file descriptor; the
19+
// caller is responsible for closing it after tun2socks terminates.
2020
//
2121
// runTun2Socks blocks until tun2socks is stopped by calling terminateTun2Socks.
2222
// It's safe to call terminateTun2Socks from a different thread.

src/android/org/uproxy/tun2socks/Tunnel.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ public synchronized boolean startTunneling(String socksServerAddress, String dns
107107
return routeThroughTunnel(socksServerAddress, dnsServerAddress);
108108
}
109109

110+
// Stops routing traffic through the tunnel by stopping tun2socks.
111+
// The VPN is unaffected by this method.
112+
public synchronized void stopTunneling() {
113+
stopRoutingThroughTunnel();
114+
}
115+
110116
// Note: to avoid deadlock, do not call directly from a HostService callback;
111117
// instead post to a Handler if necessary to trigger from a HostService callback.
112118
public synchronized void stop() {
@@ -174,7 +180,7 @@ private boolean routeThroughTunnel(String socksServerAddress, String dnsServerAd
174180
if (!mRoutingThroughTunnel.compareAndSet(false, true)) {
175181
return false;
176182
}
177-
ParcelFileDescriptor tunFd = mTunFd.getAndSet(null);
183+
ParcelFileDescriptor tunFd = mTunFd.get();
178184
if (tunFd == null) {
179185
return false;
180186
}
@@ -196,6 +202,10 @@ private boolean routeThroughTunnel(String socksServerAddress, String dnsServerAd
196202
return true;
197203
}
198204

205+
private void stopRoutingThroughTunnel() {
206+
stopTun2Socks();
207+
}
208+
199209
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
200210
public void protectSocket(long fileDescriptor) {
201211
if (!((VpnService) mHostService.getVpnService()).protect((int) fileDescriptor)) {
@@ -208,11 +218,11 @@ private void stopVpn() {
208218
ParcelFileDescriptor tunFd = mTunFd.getAndSet(null);
209219
if (tunFd != null) {
210220
try {
221+
mHostService.onDiagnosticMessage("closing VPN interface");
211222
tunFd.close();
212223
} catch (IOException e) {
213224
}
214225
}
215-
mRoutingThroughTunnel.set(false);
216226
}
217227

218228
//----------------------------------------------------------------------------
@@ -237,7 +247,7 @@ private void startTun2Socks(
237247
@Override
238248
public void run() {
239249
Tun2SocksJni.runTun2Socks(
240-
vpnInterfaceFileDescriptor.detachFd(),
250+
vpnInterfaceFileDescriptor.getFd(),
241251
vpnInterfaceMTU,
242252
vpnIpAddress,
243253
vpnNetMask,
@@ -259,6 +269,7 @@ private void stopTun2Socks() {
259269
Thread.currentThread().interrupt();
260270
}
261271
mTun2SocksThread = null;
272+
mRoutingThroughTunnel.set(false);
262273
mHostService.onDiagnosticMessage("tun2socks stopped");
263274
}
264275
}

src/android/org/uproxy/tun2socks/TunnelManager.java

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,17 @@ public class TunnelManager implements Tunnel.HostService {
4141
public static final String SOCKS_SERVER_ADDRESS_EXTRA = "socksServerAddress";
4242

4343
private Service m_parentService = null;
44-
private boolean m_firstStart = true;
45-
private boolean m_signalledStop = false;
4644
private CountDownLatch m_tunnelThreadStopSignal;
4745
private Thread m_tunnelThread;
4846
private AtomicBoolean m_isStopping;
4947
private Tunnel m_tunnel = null;
5048
private String m_socksServerAddress;
49+
private AtomicBoolean m_isReconnecting;
5150

5251
public TunnelManager(Service parentService) {
5352
m_parentService = parentService;
5453
m_isStopping = new AtomicBoolean(false);
54+
m_isReconnecting = new AtomicBoolean(false);
5555
m_tunnel = Tunnel.newTunnel(this);
5656
}
5757

@@ -108,30 +108,40 @@ public void onDestroy() {
108108
// 1. VpnService doesn't respond to stopService calls
109109
// 2. The UI will not block while waiting for stopService to return
110110
public void signalStopService() {
111-
m_signalledStop = true;
112111
if (m_tunnelThreadStopSignal != null) {
113112
m_tunnelThreadStopSignal.countDown();
114113
}
115114
}
116115

117-
public boolean signalledStop() {
118-
return m_signalledStop;
116+
// Stops the tunnel thread and restarts it with |socksServerAddress|.
117+
public void restartTunnel(final String socksServerAddress) {
118+
Log.i(LOG_TAG, "Restarting tunnel.");
119+
if (socksServerAddress == null ||
120+
socksServerAddress.equals(m_socksServerAddress)) {
121+
// Don't reconnect if the socks server address hasn't changed.
122+
return;
123+
}
124+
m_socksServerAddress = socksServerAddress;
125+
m_isReconnecting.set(true);
126+
127+
// Signaling stop to the tunnel thread with the reconnect flag set causes
128+
// the thread to stop the tunnel (but not the VPN or the service) and send
129+
// the new SOCKS server address to the DNS resolver before exiting itself.
130+
// When the DNS broadcasts its local address, the tunnel will restart.
131+
signalStopService();
119132
}
120133

121134
private void startTunnel(final String dnsResolverAddress) {
122-
if (m_firstStart) {
123-
m_firstStart = false;
124-
m_tunnelThreadStopSignal = new CountDownLatch(1);
125-
m_tunnelThread =
126-
new Thread(
127-
new Runnable() {
128-
@Override
129-
public void run() {
130-
runTunnel(m_socksServerAddress, dnsResolverAddress);
131-
}
132-
});
133-
m_tunnelThread.start();
134-
}
135+
m_tunnelThreadStopSignal = new CountDownLatch(1);
136+
m_tunnelThread =
137+
new Thread(
138+
new Runnable() {
139+
@Override
140+
public void run() {
141+
runTunnel(m_socksServerAddress, dnsResolverAddress);
142+
}
143+
});
144+
m_tunnelThread.start();
135145
}
136146

137147
private void runTunnel(String socksServerAddress, String dnsResolverAddress) {
@@ -154,12 +164,20 @@ private void runTunnel(String socksServerAddress, String dnsResolverAddress) {
154164
} catch (Tunnel.Exception e) {
155165
Log.e(LOG_TAG, String.format("Start tunnel failed: %s", e.getMessage()));
156166
} finally {
157-
Log.i(LOG_TAG, "Stopping tunnel...");
158-
m_tunnel.stop();
159-
160-
// Stop service
161-
m_parentService.stopForeground(true);
162-
m_parentService.stopSelf();
167+
if (m_isReconnecting.get()) {
168+
// Stop tunneling only, not VPN, if reconnecting.
169+
Log.i(LOG_TAG, "Stopping tunnel.");
170+
m_tunnel.stopTunneling();
171+
// Start the DNS resolver service with the new SOCKS server address.
172+
startDnsResolverService();
173+
} else {
174+
// Stop VPN tunnel and service only if not reconnecting.
175+
Log.i(LOG_TAG, "Stopping VPN and tunnel.");
176+
m_tunnel.stop();
177+
m_parentService.stopForeground(true);
178+
m_parentService.stopSelf();
179+
}
180+
m_isReconnecting.set(false);
163181
}
164182
}
165183

@@ -201,6 +219,10 @@ public void onTunnelConnected() {
201219
@TargetApi(Build.VERSION_CODES.M)
202220
public void onVpnEstablished() {
203221
Log.i(LOG_TAG, "VPN established.");
222+
startDnsResolverService();
223+
}
224+
225+
private void startDnsResolverService() {
204226
Intent dnsResolverStart = new Intent(m_parentService, DnsResolverService.class);
205227
dnsResolverStart.putExtra(SOCKS_SERVER_ADDRESS_EXTRA, m_socksServerAddress);
206228
m_parentService.startService(dnsResolverStart);

src/android/org/uproxy/tun2socks/TunnelVpnService.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ public void onCreate() {
6868

6969
@Override
7070
public void onDestroy() {
71+
Log.d(LOG_TAG, "on destroy");
7172
TunnelState.getTunnelState().setTunnelManager(null);
7273
m_tunnelManager.onDestroy();
7374
}

src/badvpn/tun2socks/tun2socks.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ static void udp_fd_handler(UdpPcb* udp_pcb, int event) {
298298
dns_get_header_id_str(dns_id_str, udp_pcb->udp_recv_buffer);
299299
const char* local_addr_str = BStringMap_Get(&udp_pcb->map, dns_id_str);
300300
if (!local_addr_str) {
301-
BLog(BLOG_ERROR, "udp_fd_handler: no address for dns reqeust id %s",
301+
BLog(BLOG_ERROR, "udp_fd_handler: no address for DNS request id %s",
302302
dns_id_str);
303303
return;
304304
}

src/badvpn/tuntap/BTap.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,10 @@ int BTap_InitWithFD (BTap *o, BReactor *reactor, int fd, int mtu, BTap_handler_e
488488
o->handler_error_user = handler_error_user;
489489
o->frame_mtu = mtu;
490490
o->fd = fd;
491-
o->close_fd = 1;
491+
// ===== UPROXY =====
492+
// Do not take ownership or close the TUN file descriptor
493+
o->close_fd = 0;
494+
// ===== /UPROXY =====
492495

493496
// TODO: use BTap_Init2? Still some different behavior (we don't want the fcntl block; we do want close to be called)
494497

0 commit comments

Comments
 (0)