me.xingrz.prox.ProxVpnService.java Source code

Java tutorial

Introduction

Here is the source code for me.xingrz.prox.ProxVpnService.java

Source

/*
 * Copyright (C) 2015 XiNGRZ <chenxingyu92@gmail.com>
 *
 * 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 2 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, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package me.xingrz.prox;

import android.content.Intent;
import android.net.VpnService;
import android.os.ParcelFileDescriptor;

import com.crashlytics.android.Crashlytics;

import org.apache.commons.io.IOUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.UnknownHostException;

import me.xingrz.prox.internet.IPHeader;
import me.xingrz.prox.internet.IPv4Header;
import me.xingrz.prox.internet.IpUtils;
import me.xingrz.prox.logging.FormattingLogger;
import me.xingrz.prox.logging.FormattingLoggers;
import me.xingrz.prox.pac.AutoConfigManager;
import me.xingrz.prox.tcp.TcpHeader;
import me.xingrz.prox.tcp.TcpProxy;
import me.xingrz.prox.tcp.TcpProxySession;
import me.xingrz.prox.transport.TransportProxyRunner;
import me.xingrz.prox.udp.UdpHeader;
import me.xingrz.prox.udp.UdpProxy;
import me.xingrz.prox.udp.UdpProxySession;

public class ProxVpnService extends VpnService implements Runnable, AutoConfigManager.ConfigLoadCallback {

    private static final FormattingLogger logger = FormattingLoggers.getContextLogger();

    private static InetAddress getAddressQuietly(String address) {
        try {
            return InetAddress.getByName(address);
        } catch (UnknownHostException e) {
            return null;
        }
    }

    /**
     * VPN ??
     */
    public static final InetAddress PROXY_ADDRESS = getAddressQuietly("10.80.19.20");

    /**
     * ???? VPN 
     */
    public static final InetAddress FAKE_CLIENT_ADDRESS = getAddressQuietly("10.80.7.20");

    private static ProxVpnService instance;

    public static ProxVpnService getInstance() {
        return instance;
    }

    public static final String EXTRA_PAC_URL = "pac_url";

    private int startId;
    private volatile boolean running;

    private Thread thread;

    private ParcelFileDescriptor intf;
    private FileOutputStream ingoing;

    private byte[] packet;

    private IPHeader ipHeader;
    private IPv4Header iPv4Header;

    private TcpHeader tcpHeader;
    private UdpHeader udpHeader;

    private TransportProxyRunner proxyRunner;

    private TcpProxy tcpProxy;
    private UdpProxy udpProxy;

    @Override
    public void onCreate() {
        super.onCreate();
        Crashlytics.start(this);

        instance = this;

        thread = new Thread(this, "VpnServer");

        packet = new byte[0xFFFF];

        ipHeader = new IPHeader(packet);
        iPv4Header = new IPv4Header(packet);

        tcpHeader = new TcpHeader(packet);
        udpHeader = new UdpHeader(packet);

        AutoConfigManager.createInstance();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        String configUrl = intent.getStringExtra(EXTRA_PAC_URL);

        if (running) {
            logger.d("Service already running, just reload config");
            AutoConfigManager.getInstance().load(configUrl, null);
            return START_NOT_STICKY;
        } else {
            this.startId = startId;
            AutoConfigManager.getInstance().load(configUrl, this);
            return START_REDELIVER_INTENT;
        }
    }

    @Override
    public void onConfigLoad() {
        if (!running) {
            running = true;
            thread.start();
        }
    }

    @Override
    public void onRevoke() {
        running = false;

        logger.d("VPN service revoked");

        stopSelf(startId);
    }

    @Override
    public void onDestroy() {
        AutoConfigManager.destroy();

        instance = null;

        logger.d("VPN service destroyed");

        super.onDestroy();
    }

    @Override
    public void run() {
        logger.d("VPN service started");

        FileInputStream outgoing = null;

        try {
            proxyRunner = new TransportProxyRunner();

            tcpProxy = proxyRunner.create(TcpProxy.class);
            logger.d("TCP proxy started");

            udpProxy = proxyRunner.create(UdpProxy.class);
            logger.d("UDP proxy started");

            proxyRunner.start();

            intf = establish();
            logger.d("VPN interface established");

            ingoing = new FileOutputStream(intf.getFileDescriptor());
            outgoing = new FileInputStream(intf.getFileDescriptor());

            int size;
            while (running && (size = outgoing.read(packet)) != -1) {
                if (!tcpProxy.isRunning()) {
                    logger.e("TCP proxy unexpectedly stopped");
                    break;
                }

                if (!udpProxy.isRunning()) {
                    logger.e("UDP proxy unexpectedly stopped");
                    break;
                }

                if (size > 0) {
                    onIPPacketReceived(size);
                }
            }

            logger.d("VPN thread finished");
        } catch (IOException e) {
            logger.w(e, "VPN ended with exception");
        } finally {
            IOUtils.closeQuietly(outgoing);
            IOUtils.closeQuietly(ingoing);

            IOUtils.closeQuietly(intf);

            IOUtils.closeQuietly(tcpProxy);
            IOUtils.closeQuietly(udpProxy);

            IOUtils.closeQuietly(proxyRunner);

            logger.d("Cleaned up");
        }
    }

    private ParcelFileDescriptor establish() {
        return new Builder().addAddress(PROXY_ADDRESS, 24).addRoute("0.0.0.0", 0).establish();
    }

    private void onIPPacketReceived(int size) throws IOException {
        if (ipHeader.version() == IPHeader.VERSION_4) {
            onIPv4PacketReceived(size);
        }
    }

    private void onIPv4PacketReceived(int size) throws IOException {
        if (iPv4Header.totalLength() != size) {
            logger.w("Ignored IP packet with wrong length");
            return;
        }

        switch (iPv4Header.protocol()) {
        case IPHeader.PROTOCOL_TCP:
            onTCPPacketReceived();
            break;
        case IPHeader.PROTOCOL_UDP:
            onUDPPacketReceived();
            break;
        }
    }

    private void onTCPPacketReceived() throws IOException {
        if (tcpHeader.getSourceIp() != IpUtils.toInteger(PROXY_ADDRESS)) {
            return;
        }

        if (tcpHeader.getSourcePort() == tcpProxy.port()) {
            // ? TCP ??? VPN

            TcpProxySession session = tcpProxy.getSession(tcpHeader.getDestinationPort());
            if (session == null) {
                return;
            }

            session.active();

            if (tcpHeader.fin()) {
                session.finish();
                tcpProxy.finishSession(tcpHeader.getDestinationPort());
            }

            //  TCP ?? VPN 
            // ? TCP ? TCPProxy
            // ? TCPProxy ????
            // ??

            tcpHeader.setSourceIp(session.getRemoteAddress());
            tcpHeader.setSourcePort(session.getRemotePort());
            tcpHeader.setDestinationIp(PROXY_ADDRESS);
            tcpHeader.recomputeChecksum();
            tcpHeader.writeTo(ingoing);
        } else {
            // ????? TCP ?
            TcpProxySession session = tcpProxy.pickSession(tcpHeader.getSourcePort(),
                    tcpHeader.getDestinationIpAddress(), tcpHeader.getDestinationPort());

            session.active();

            tcpHeader.setSourceIp(FAKE_CLIENT_ADDRESS);
            tcpHeader.setDestinationIp(PROXY_ADDRESS);
            tcpHeader.setDestinationPort(tcpProxy.port());
            tcpHeader.recomputeChecksum();
            tcpHeader.writeTo(ingoing);
        }
    }

    private void onUDPPacketReceived() throws IOException {
        if (udpHeader.getSourceIp() != IpUtils.toInteger(PROXY_ADDRESS)) {
            return;
        }

        if (udpHeader.getDestinationIp() == IpUtils.toInteger(FAKE_CLIENT_ADDRESS)) {
            // UDP ? VPN 

            UdpProxySession session = udpProxy.finishSession(udpHeader.getDestinationPort());
            if (session == null) {
                return;
            }

            udpHeader.setSourceIp(session.getRemoteAddress());
            udpHeader.setSourcePort(session.getRemotePort());
            udpHeader.setDestinationIp(PROXY_ADDRESS);
            udpHeader.recomputeChecksum();
            udpHeader.writeTo(ingoing);
        } else {
            // ?? VPN 

            // ? UDP ???
            UdpProxySession session = udpProxy.pickSession(udpHeader.getSourcePort(),
                    udpHeader.getDestinationIpAddress(), udpHeader.getDestinationPort());

            // ??? VPN 
            protect(session.socket());

            udpHeader.setSourceIp(FAKE_CLIENT_ADDRESS);
            udpHeader.setDestinationIp(PROXY_ADDRESS);
            udpHeader.setDestinationPort(udpProxy.port());
            udpHeader.recomputeChecksum();
            udpHeader.writeTo(ingoing);
        }
    }

}