package fq.router2; import android.app.PendingIntent; import android.content.Intent; import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.VpnService; import android.os.ParcelFileDescriptor; import fq.router2.feedback.HandleFatalErrorIntent; import fq.router2.life_cycle.ExitService; import fq.router2.utils.LogUtils; import java.io.*; import java.net.*; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SocksVpnService extends VpnService { private static ParcelFileDescriptor tunPFD; private Set<String> skippedFds = new HashSet<String>(); private Set<Integer> stagingFds = new HashSet<Integer>(); @Override public void onStart(Intent intent, int startId) { startVpn(); LogUtils.i("on start"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { startVpn(); return START_STICKY; } @Override public void onRevoke() { stopSelf(); stopVpn(); } @Override public void onDestroy() { stopVpn(); } private void startVpn() { try { if (tunPFD != null) { throw new RuntimeException("another VPN is still running"); } Intent statusActivityIntent = new Intent(this, MainActivity.class); PendingIntent pIntent = PendingIntent.getActivity(this, 0, statusActivityIntent, 0); tunPFD = new Builder() .setConfigureIntent(pIntent) .setSession("fqrouter2") .addAddress("10.25.1.1", 24) .addRoute("1.0.0.0", 8) .addRoute("2.0.0.0", 7) .addRoute("4.0.0.0", 6) .addRoute("8.0.0.0", 7) // 10.0.0.0 - 10.255.255.255 .addRoute("11.0.0.0", 8) .addRoute("12.0.0.0", 6) .addRoute("16.0.0.0", 4) .addRoute("32.0.0.0", 3) .addRoute("64.0.0.0", 2) .addRoute("139.0.0.0", 8) .addRoute("140.0.0.0", 6) .addRoute("144.0.0.0", 4) .addRoute("160.0.0.0", 5) .addRoute("168.0.0.0", 6) .addRoute("172.0.0.0", 12) // 172.16.0.0 - 172.31.255.255 .addRoute("172.32.0.0", 11) .addRoute("172.64.0.0", 10) .addRoute("172.128.0.0", 9) .addRoute("173.0.0.0", 8) .addRoute("174.0.0.0", 7) .addRoute("176.0.0.0", 4) .addRoute("192.0.0.0", 9) .addRoute("192.128.0.0", 11) .addRoute("192.160.0.0", 13) // 192.168.0.0 - 192.168.255.255 .addRoute("192.169.0.0", 16) .addRoute("192.170.0.0", 15) .addRoute("192.172.0.0", 14) .addRoute("192.176.0.0", 12) .addRoute("192.192.0.0", 10) .addRoute("193.0.0.0", 8) .addRoute("194.0.0.0", 7) .addRoute("196.0.0.0", 6) .addRoute("200.0.0.0", 5) .addRoute("208.0.0.0", 4) .addRoute("224.0.0.0", 4) .addRoute("240.0.0.0",5) .addRoute("248.0.0.0",6) .addRoute("252.0.0.0",7) .addRoute("254.0.0.0",8) .addDnsServer("8.8.8.8") .establish(); if (tunPFD == null) { stopSelf(); return; } final int tunFD = tunPFD.getFd(); LogUtils.i("tunFD is " + tunFD); LogUtils.i("Started in VPN mode"); sendBroadcast(new SocksVpnConnectedIntent()); new Thread(new Runnable() { @Override public void run() { try { listenFdServerSocket(tunPFD.getFileDescriptor()); } catch (Exception e) { LogUtils.e("fdsock failed " + e, e); } } }).start(); } catch (Exception e) { handleFatalError(LogUtils.e("VPN establish failed", e)); } } private void listenFdServerSocket(final FileDescriptor tunFD) throws Exception { final LocalServerSocket fdServerSocket = new LocalServerSocket("fdsock2"); try { ExecutorService executorService = Executors.newFixedThreadPool(16); int count = 0; while (isRunning()) { try { final LocalSocket fdSocket = fdServerSocket.accept(); executorService.submit(new Runnable() { @Override public void run() { try { passFileDescriptor(fdSocket, tunFD); } catch (Exception e) { LogUtils.e("failed to handle fdsock", e); } } }); count += 1; if (count % 200 == 0) { garbageCollectFds(); } } catch (Exception e) { LogUtils.e("failed to handle fdsock", e); } } executorService.shutdown(); } finally { fdServerSocket.close(); } } private void garbageCollectFds() { if (listFds() == null) { LogUtils.e("can not gc fd as can not list them"); } else { if (skippedFds.isEmpty()) { initSkippedFds(); } else { closeStagingFds(); } } } private String[] listFds() { return new File("/proc/self/fd").list(); } private void initSkippedFds() { String[] fileNames = listFds(); LogUtils.i("init skipped fd count: " + fileNames.length); Collections.addAll(skippedFds, fileNames); } private void closeStagingFds() { int count = 0; for (int stagingFd : stagingFds) { try { if (isSocket(stagingFd)) { ParcelFileDescriptor.adoptFd(stagingFd).close(); count += 1; } } catch (Exception e) { // ignore } } LogUtils.i("closed fd count: " + count); stagingFds.clear(); String[] fileNames = listFds(); LogUtils.i("current total fd count: " + fileNames.length); for (String fileName : fileNames) { if (skippedFds.contains(fileName)) { continue; } try { if (isSocket(fileName)) { stagingFds.add(Integer.parseInt(fileName)); } } catch (Exception e) { skippedFds.add(fileName); continue; } } } private boolean isSocket(Object fileName) throws IOException { return new File("/proc/self/fd/" + fileName).getCanonicalPath().contains("socket:"); } public static boolean isRunning() { return tunPFD != null; } private void passFileDescriptor(LocalSocket fdSocket, FileDescriptor tunFD) throws Exception { OutputStream outputStream = fdSocket.getOutputStream(); InputStream inputStream = fdSocket.getInputStream(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream), 1); String request = reader.readLine(); String[] parts = request.split(","); if ("TUN".equals(parts[0])) { fdSocket.setFileDescriptorsForSend(new FileDescriptor[]{tunFD}); outputStream.write('*'); } else if ("PING".equals(parts[0])) { OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); outputStreamWriter.write("PONG"); outputStreamWriter.close(); } else if ("OPEN UDP".equals(parts[0])) { passUdpFileDescriptor(fdSocket, outputStream); } else if ("OPEN TCP".equals(parts[0])) { String dstIp = parts[1]; int dstPort = Integer.parseInt(parts[2]); int connectTimeout = Integer.parseInt(parts[3]); passTcpFileDescriptor(fdSocket, outputStream, dstIp, dstPort, connectTimeout); } else { throw new UnsupportedOperationException("fdsock unable to handle: " + request); } } finally { try { inputStream.close(); } catch (Exception e) { LogUtils.e("failed to close input stream", e); } try { outputStream.close(); } catch (Exception e) { LogUtils.e("failed to close output stream", e); } fdSocket.close(); } } private void passTcpFileDescriptor( LocalSocket fdSocket, OutputStream outputStream, String dstIp, int dstPort, int connectTimeout) throws Exception { Socket sock = new Socket(); sock.setTcpNoDelay(true); // force file descriptor being created try { ParcelFileDescriptor fd = ParcelFileDescriptor.fromSocket(sock); if (protect(fd.getFd())) { try { sock.connect(new InetSocketAddress(dstIp, dstPort), connectTimeout); try { fdSocket.setFileDescriptorsForSend(new FileDescriptor[]{fd.getFileDescriptor()}); outputStream.write('*'); outputStream.flush(); } finally { sock.close(); fd.close(); } } catch (ConnectException e) { LogUtils.e("connect " + dstIp + ":" + dstPort + " failed"); outputStream.write('!'); } catch (SocketTimeoutException e) { LogUtils.e("connect " + dstIp + ":" + dstPort + " failed"); outputStream.write('!'); } finally { outputStream.flush(); } } else { LogUtils.e("protect tcp socket failed"); } } finally { sock.close(); } } private void passUdpFileDescriptor(LocalSocket fdSocket, OutputStream outputStream) throws Exception { DatagramSocket sock = new DatagramSocket(); try { ParcelFileDescriptor fd = ParcelFileDescriptor.fromDatagramSocket(sock); if (protect(fd.getFd())) { try { fdSocket.setFileDescriptorsForSend(new FileDescriptor[]{fd.getFileDescriptor()}); outputStream.write('*'); outputStream.flush(); } finally { sock.close(); fd.close(); } } else { LogUtils.e("protect udp socket failed"); } } finally { sock.close(); } } private void stopVpn() { if (tunPFD != null) { try { tunPFD.close(); } catch (IOException e) { LogUtils.e("failed to stop tunPFD", e); } tunPFD = null; } ExitService.execute(this); } private void handleFatalError(String message) { sendBroadcast(new HandleFatalErrorIntent(message)); } }