com.elixsr.portforwarder.forwarding.ForwardingService.java Source code

Java tutorial

Introduction

Here is the source code for com.elixsr.portforwarder.forwarding.ForwardingService.java

Source

/*
 * Fwd: the port forwarding app
 * Copyright (C) 2016  Elixsr Ltd
 *
 * 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/>.
 */

package com.elixsr.portforwarder.forwarding;

import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import com.elixsr.portforwarder.FwdApplication;
import com.elixsr.portforwarder.ui.MainActivity;
import com.elixsr.portforwarder.R;
import com.elixsr.portforwarder.dao.RuleDao;
import com.elixsr.portforwarder.db.RuleDbHelper;
import com.elixsr.portforwarder.exceptions.ObjectNotFoundException;
import com.elixsr.portforwarder.models.RuleModel;
import com.google.android.gms.analytics.HitBuilders;
import com.google.android.gms.analytics.Tracker;

/**
 * The {@link ForwardingService} class acts as a controller of all all forwarding.
 *
 * The class is responsible for starting forwarding for all rules found within the SQLite database.
 *
 * The class creates a new thread for each Forwarding rule.
 */
public class ForwardingService extends IntentService {

    // Defines a custom Intent action
    public static final String BROADCAST_ACTION = "com.elixsr.portforwarder.forwarding.ForwardingService.BROADCAST";

    // Defines the key for the status "extra" in an Intent
    public static final String EXTENDED_DATA_STATUS = "com.elixsr.portforwarder.forwarding.ForwardingService.STATUS";

    public static final String PORT_FORWARD_SERVICE_STATE = "com.elixsr.portforwarder.forwarding.ForwardingService.PORT_FORWARD_STATE";

    public static final String PORT_FORWARD_SERVICE_ERROR_MESSAGE = "com.elixsr.portforwarder.forwarding.ForwardingService.PORT_FORWARD_ERROR_MESSAGE";

    private static final String PORT_FORWARD_SERVICE_WAKE_LOCK_TAG = "PortForwardServiceWakeLockTag";

    private static final String TAG = "ForwardingService";

    private static final String CATEGORY_FORWARDING = "Forwarding";

    private static final int NOTIFICATION_ID = 1;
    private static final String ACTION_START_FORWARDING = "Start - Java NIO";
    private static final String LABEL_FORWARDING_TYPE = "";
    private static final String ACTION_STOP_FORWARDING = "Stop - Java NIO";

    private String status = "Test";

    private boolean runService = false;

    //change the magic number
    private final ExecutorService executorService;

    //wake lock
    private PowerManager.WakeLock wakeLock;
    private Tracker tracker;

    /**
     * Default constructor for {@link ForwardingService}.#
     *
     * Creates a new instance of ForwardingService and initialises an {@link ExecutorService}
     * with a fixed thread pool of 30 threads.
     */
    public ForwardingService() {
        super(TAG);
        executorService = Executors.newFixedThreadPool(30);
    }

    public ForwardingService(ExecutorService executorService) {
        super(TAG);
        this.executorService = executorService;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        /*
        Sourced from: https://developer.android.com/intl/ja/training/scheduling/wakelock.html
         */
        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, PORT_FORWARD_SERVICE_WAKE_LOCK_TAG);
        wakeLock.acquire();

        tracker = ((FwdApplication) this.getApplication()).getDefaultTracker();
    }

    /**
     * Starts forwarding based on rules found in database.
     *
     * Acquires an instance of the Forwarding Manager to turn forwarding flag on.
     *
     * Creates a list off callbacks for each forward thread, and handle exceptions as they come.
     *
     * If an exception is thrown, the service immediately stops, and the #onDestroy method is
     * called.
     *
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {

        // Gets data from the incoming Intent
        //        String dataString = intent.getDataString();

        Log.i(TAG, "Ran the service");

        ForwardingManager.getInstance().enableForwarding();

        runService = true;

        /*
         * Creates a new Intent containing a Uri object
         * BROADCAST_ACTION is a custom Intent action
         */
        Intent localIntent = new Intent(BROADCAST_ACTION)
                // Puts the status into the Intent
                .putExtra(PORT_FORWARD_SERVICE_STATE, ForwardingManager.getInstance().isEnabled());
        // Broadcasts the Intent to receivers in this app.
        LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);

        showForwardingEnabledNotification();

        //load the rules from the datastore
        //TODO: inject the rules as extras
        RuleDao ruleDao = new RuleDao(new RuleDbHelper(this));
        List<RuleModel> ruleModels = ruleDao.getAllRuleModels();

        InetSocketAddress from;

        Forwarder forwarder = null;

        /*
         Sourced from: http://stackoverflow.com/questions/19348248/waiting-on-a-list-of-future
         */
        CompletionService<Void> completionService = new ExecutorCompletionService<>(executorService);

        // how many futures there are to check
        int remainingFutures = 0;

        for (RuleModel ruleModel : ruleModels) {

            try {
                from = generateFromIpUsingInterface(ruleModel.getFromInterfaceName(), ruleModel.getFromPort());

                if (ruleModel.isTcp()) {
                    completionService.submit(new TcpForwarder(from, ruleModel.getTarget(), ruleModel.getName()));
                    remainingFutures++;
                }

                if (ruleModel.isUdp()) {
                    completionService.submit(new UdpForwarder(from, ruleModel.getTarget(), ruleModel.getName()));
                    remainingFutures++;
                }

            } catch (SocketException | ObjectNotFoundException e) {
                Log.e(TAG, "Error generating IP Address for FROM interface with rule '" + ruleModel.getName() + "'",
                        e);

                // graceful UI Exception handling - broadcast this to ui - it will deal with display something to the user e.g. a Toast
                localIntent = new Intent(BROADCAST_ACTION)
                        // Puts the status into the Intent
                        .putExtra(PORT_FORWARD_SERVICE_ERROR_MESSAGE,
                                "Error while trying to start rule '" + ruleModel.getName() + "'");
                // Broadcasts the Intent to receivers in this app.
                LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
            }
        }

        // Build and send an Event.
        tracker.send(new HitBuilders.EventBuilder().setCategory(CATEGORY_FORWARDING)
                .setAction(ACTION_START_FORWARDING).setLabel(ruleModels.size() + " rules").build());

        Future<?> completedFuture;

        // loop through each callback, and handle an exception
        while (remainingFutures > 0) {

            // block until a callable completes
            try {
                completedFuture = completionService.take();
                remainingFutures--;

                completedFuture.get();
            } catch (ExecutionException e) {
                Throwable cause = e.getCause();

                Log.e(TAG, "Error when forwarding port.", e);
                localIntent = new Intent(BROADCAST_ACTION)
                        // Puts the status into the Intent
                        .putExtra(PORT_FORWARD_SERVICE_ERROR_MESSAGE, e.getCause().getMessage());
                // Broadcasts the Intent to receivers in this app.
                LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);

                break;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private InetSocketAddress generateFromIpUsingInterface(String interfaceName, int port)
            throws SocketException, ObjectNotFoundException {

        String address = null;
        InetSocketAddress inetSocketAddress;

        for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
            NetworkInterface intf = en.nextElement();

            Log.d(TAG, intf.getDisplayName() + " vs " + interfaceName);
            if (intf.getDisplayName().equals(interfaceName)) {

                Log.i(TAG, "Found the relevant Interface. Will attempt to fetch IP Address");

                for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {

                    InetAddress inetAddress = enumIpAddr.nextElement();

                    address = new String(inetAddress.getHostAddress().toString());

                    if (address != null & address.length() > 0 && inetAddress instanceof Inet4Address) {

                        inetSocketAddress = new InetSocketAddress(address, port);
                        return inetSocketAddress;
                    }
                }
            }
        }

        //Failed to find the relevant interface
        //TODO: complete
        //        Toast.makeText(this, "Could not find relevant network interface.",
        //                Toast.LENGTH_LONG).show();
        throw new ObjectNotFoundException("Could not find IP Address for Interface " + interfaceName);
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        Log.i(TAG, "onTaskRemoved: called");

        // Build and send an Event.
        tracker.send(new HitBuilders.EventBuilder().setCategory(CATEGORY_FORWARDING)
                .setAction(ACTION_STOP_FORWARDING).setLabel("Task Removed").build());

        this.onDestroy();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        runService = false;

        executorService.shutdownNow();

        ForwardingManager.getInstance().disableForwarding();

        hideForwardingEnabledNotification();

        //update the main activity
        Intent localIntent = new Intent(BROADCAST_ACTION)
                // Puts the status into the Intent
                .putExtra(PORT_FORWARD_SERVICE_STATE, ForwardingManager.getInstance().isEnabled());
        // Broadcasts the Intent to receivers in this app.
        LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);

        wakeLock.release();

        // Build and send an Event.
        tracker.send(new HitBuilders.EventBuilder().setCategory(CATEGORY_FORWARDING)
                .setAction(ACTION_STOP_FORWARDING).setLabel("Ended").build());
        Log.i(TAG, "Ended the ForwardingService. Cleanup finished.");
    }

    private void hideForwardingEnabledNotification() {

        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);

        mNotificationManager.cancel(NOTIFICATION_ID);
    }

    /**
     * Construct a notification
     */
    private void showForwardingEnabledNotification() {
        NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this)
                .setSmallIcon(R.drawable.ic_fwd_24dp).setContentTitle("Port Forwarding Active")
                .setContentText("Touch to disable");

        mBuilder.setColor(ContextCompat.getColor(this, R.color.colorPrimaryDark));

        // Creates an explicit intent for an Activity in your app
        Intent resultIntent = new Intent(this, MainActivity.class);

        // The stack builder object will contain an artificial back stack for the
        // started Activity.
        // This ensures that navigating backward from the Activity leads out of
        // your application to the Home screen.
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);

        // Adds the back stack for the Intent (but not the Intent itself)
        stackBuilder.addParentStack(MainActivity.class);

        // Adds the Intent that starts the Activity to the top of the stack
        stackBuilder.addNextIntent(resultIntent);
        PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
        mBuilder.setContentIntent(resultPendingIntent);
        NotificationManager mNotificationManager = (NotificationManager) getSystemService(
                Context.NOTIFICATION_SERVICE);

        Notification notification = mBuilder.build();
        notification.flags = Notification.FLAG_NO_CLEAR | Notification.FLAG_ONGOING_EVENT
                | Notification.DEFAULT_LIGHTS;

        // mId allows you to update the notification later on.
        mNotificationManager.notify(NOTIFICATION_ID, notification);
    }
}