Java tutorial
/* * 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); } } }