org.uproxy.tun2socks.TunnelManager.java Source code

Java tutorial

Introduction

Here is the source code for org.uproxy.tun2socks.TunnelManager.java

Source

package org.uproxy.tun2socks;

/*
 * Copyright (c) 2016, Psiphon Inc.
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

import android.annotation.TargetApi;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.VpnService;
import android.os.Build;
import android.os.IBinder;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;

public class TunnelManager implements Tunnel.HostService {

    private static final String LOG_TAG = "TunnelManager";
    public static final String SOCKS_SERVER_ADDRESS_EXTRA = "socksServerAddress";

    private TunnelVpnService m_parentService = null;
    private CountDownLatch m_tunnelThreadStopSignal;
    private Thread m_tunnelThread;
    private AtomicBoolean m_isStopping;
    private Tunnel m_tunnel = null;
    private String m_socksServerAddress;
    private AtomicBoolean m_isReconnecting;

    public TunnelManager(TunnelVpnService parentService) {
        m_parentService = parentService;
        m_isStopping = new AtomicBoolean(false);
        m_isReconnecting = new AtomicBoolean(false);
        m_tunnel = Tunnel.newTunnel(this);
    }

    // Implementation of android.app.Service.onStartCommand
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.i(LOG_TAG, "onStartCommand");
        LocalBroadcastManager.getInstance(m_parentService).registerReceiver(m_broadcastReceiver,
                new IntentFilter(DnsResolverService.DNS_ADDRESS_BROADCAST));

        m_socksServerAddress = intent.getStringExtra(SOCKS_SERVER_ADDRESS_EXTRA);
        if (m_socksServerAddress == null) {
            Log.e(LOG_TAG, "Failed to receive the socks server address.");
            m_parentService.broadcastVpnStart(false /* success */);
            return 0;
        }

        try {
            if (!m_tunnel.startRouting()) {
                Log.e(LOG_TAG, "Failed to establish VPN");
                m_parentService.broadcastVpnStart(false /* success */);
            }
        } catch (Tunnel.Exception e) {
            Log.e(LOG_TAG, String.format("Failed to establish VPN: %s", e.getMessage()));
            m_parentService.broadcastVpnStart(false /* success */);
        }
        return android.app.Service.START_NOT_STICKY;
    }

    // Implementation of android.app.Service.onDestroy
    public void onDestroy() {
        if (m_tunnelThread == null) {
            return;
        }

        LocalBroadcastManager.getInstance(m_parentService).unregisterReceiver(m_broadcastReceiver);

        // Stop DNS resolver service
        m_parentService.stopService(new Intent(m_parentService, DnsResolverService.class));

        // signalStopService should have been called, but in case is was not, call here.
        // If signalStopService was not already called, the join may block the calling
        // thread for some time.
        signalStopService();

        try {
            m_tunnelThread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        m_tunnelThreadStopSignal = null;
        m_tunnelThread = null;
    }

    // Signals the runTunnel thread to stop. The thread will self-stop the service.
    // This is the preferred method for stopping the tunnel service:
    // 1. VpnService doesn't respond to stopService calls
    // 2. The UI will not block while waiting for stopService to return
    public void signalStopService() {
        if (m_tunnelThreadStopSignal != null) {
            m_tunnelThreadStopSignal.countDown();
        }
    }

    // Stops the tunnel thread and restarts it with |socksServerAddress|.
    public void restartTunnel(final String socksServerAddress) {
        Log.i(LOG_TAG, "Restarting tunnel.");
        if (socksServerAddress == null || socksServerAddress.equals(m_socksServerAddress)) {
            // Don't reconnect if the socks server address hasn't changed.
            m_parentService.broadcastVpnStart(true /* success */);
            return;
        }
        m_socksServerAddress = socksServerAddress;
        m_isReconnecting.set(true);

        // Signaling stop to the tunnel thread with the reconnect flag set causes
        // the thread to stop the tunnel (but not the VPN or the service) and send
        // the new SOCKS server address to the DNS resolver before exiting itself.
        // When the DNS broadcasts its local address, the tunnel will restart.
        signalStopService();
    }

    private void startTunnel(final String dnsResolverAddress) {
        m_tunnelThreadStopSignal = new CountDownLatch(1);
        m_tunnelThread = new Thread(new Runnable() {
            @Override
            public void run() {
                runTunnel(m_socksServerAddress, dnsResolverAddress);
            }
        });
        m_tunnelThread.start();
    }

    private void runTunnel(String socksServerAddress, String dnsResolverAddress) {
        m_isStopping.set(false);

        try {
            if (!m_tunnel.startTunneling(socksServerAddress, dnsResolverAddress)) {
                throw new Tunnel.Exception("application is not prepared or revoked");
            }
            Log.i(LOG_TAG, "VPN service running");
            m_parentService.broadcastVpnStart(true /* success */);

            try {
                m_tunnelThreadStopSignal.await();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }

            m_isStopping.set(true);

        } catch (Tunnel.Exception e) {
            Log.e(LOG_TAG, String.format("Start tunnel failed: %s", e.getMessage()));
            m_parentService.broadcastVpnStart(false /* success */);
        } finally {
            if (m_isReconnecting.get()) {
                // Stop tunneling only, not VPN, if reconnecting.
                Log.i(LOG_TAG, "Stopping tunnel.");
                m_tunnel.stopTunneling();
                // Start the DNS resolver service with the new SOCKS server address.
                startDnsResolverService();
            } else {
                // Stop VPN tunnel and service only if not reconnecting.
                Log.i(LOG_TAG, "Stopping VPN and tunnel.");
                m_tunnel.stop();
                m_parentService.stopForeground(true);
                m_parentService.stopSelf();
            }
            m_isReconnecting.set(false);
        }
    }

    //----------------------------------------------------------------------------
    // Tunnel.HostService
    //----------------------------------------------------------------------------

    @Override
    public String getAppName() {
        return "Tun2Socks";
    }

    @Override
    public Context getContext() {
        return m_parentService;
    }

    @Override
    public VpnService getVpnService() {
        return ((TunnelVpnService) m_parentService);
    }

    @Override
    public VpnService.Builder newVpnServiceBuilder() {
        return ((TunnelVpnService) m_parentService).newBuilder();
    }

    @Override
    public void onDiagnosticMessage(String message) {
        Log.d(LOG_TAG, message);
    }

    @Override
    public void onTunnelConnected() {
        Log.i(LOG_TAG, "Tunnel connected.");
    }

    @Override
    @TargetApi(Build.VERSION_CODES.M)
    public void onVpnEstablished() {
        Log.i(LOG_TAG, "VPN established.");
        startDnsResolverService();
    }

    private void startDnsResolverService() {
        Intent dnsResolverStart = new Intent(m_parentService, DnsResolverService.class);
        dnsResolverStart.putExtra(SOCKS_SERVER_ADDRESS_EXTRA, m_socksServerAddress);
        m_parentService.startService(dnsResolverStart);
    }

    private DnsResolverAddressBroadcastReceiver m_broadcastReceiver = new DnsResolverAddressBroadcastReceiver(
            TunnelManager.this);

    private class DnsResolverAddressBroadcastReceiver extends BroadcastReceiver {
        private TunnelManager m_tunnelManager;

        public DnsResolverAddressBroadcastReceiver(TunnelManager tunnelManager) {
            m_tunnelManager = tunnelManager;
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            String dnsResolverAddress = intent.getStringExtra(DnsResolverService.DNS_ADDRESS_EXTRA);
            if (dnsResolverAddress == null || dnsResolverAddress.isEmpty()) {
                Log.e(LOG_TAG, "Failed to receive DNS resolver address");
                return;
            }
            Log.d(LOG_TAG, "DNS resolver address: " + dnsResolverAddress);
            // Callback into tunnel manager to start tunneling.
            m_tunnelManager.startTunnel(dnsResolverAddress);
        }
    };
}