From b7968faa2a6dac1bd9641309ccf4c9a387bca26c Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Tue, 21 Jan 2014 20:37:31 +0100 Subject: Add to code that allows excluding routes from the VPN --HG-- extra : rebase_source : 7e20e643cb0949520b92f7ab7b623d6856ea4ef7 --- src/de/blinkt/openvpn/VpnProfile.java | 4 +- src/de/blinkt/openvpn/core/ConfigParser.java | 2 +- src/de/blinkt/openvpn/core/NetworkSpace.java | 244 +++++++++++++++++++++ .../openvpn/core/OpenVpnManagementThread.java | 12 +- src/de/blinkt/openvpn/core/OpenVpnService.java | 109 +++++---- src/de/blinkt/openvpn/core/VpnStatus.java | 7 +- 6 files changed, 327 insertions(+), 51 deletions(-) create mode 100644 src/de/blinkt/openvpn/core/NetworkSpace.java (limited to 'src/de/blinkt/openvpn') diff --git a/src/de/blinkt/openvpn/VpnProfile.java b/src/de/blinkt/openvpn/VpnProfile.java index d580829d..b016fb64 100644 --- a/src/de/blinkt/openvpn/VpnProfile.java +++ b/src/de/blinkt/openvpn/VpnProfile.java @@ -305,10 +305,10 @@ public class VpnProfile implements Serializable { String routes = ""; int numroutes = 0; if (mUseDefaultRoute) - routes += "route 0.0.0.0 0.0.0.0\n"; + routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n"; else for (String route : getCustomRoutes()) { - routes += "route " + route + "\n"; + routes += "route " + route + "vpn_gateway\n"; numroutes++; } diff --git a/src/de/blinkt/openvpn/core/ConfigParser.java b/src/de/blinkt/openvpn/core/ConfigParser.java index 103c208b..895f048e 100644 --- a/src/de/blinkt/openvpn/core/ConfigParser.java +++ b/src/de/blinkt/openvpn/core/ConfigParser.java @@ -12,7 +12,7 @@ import java.util.Vector; //! Openvpn Config FIle Parser, probably not 100% accurate but close enough -// And rember, this is valid :) +// And remember, this is valid :) // -- // bar // diff --git a/src/de/blinkt/openvpn/core/NetworkSpace.java b/src/de/blinkt/openvpn/core/NetworkSpace.java new file mode 100644 index 00000000..3701c43d --- /dev/null +++ b/src/de/blinkt/openvpn/core/NetworkSpace.java @@ -0,0 +1,244 @@ +package de.blinkt.openvpn.core; + +import android.text.TextUtils; + +import java.math.BigInteger; +import java.net.Inet6Address; +import java.util.*; + +public class NetworkSpace { + + + static class ipAddress implements Comparable { + private BigInteger netAddress; + public int networkMask; + private boolean included; + private boolean isV4; + + + @Override + public int compareTo(ipAddress another) { + int comp = getFirstAddress().compareTo(another.getFirstAddress()); + if (comp != 0) + return comp; + + // bigger mask means smaller address block + if (networkMask > another.networkMask) + return -1; + else if (another.networkMask == networkMask) + return 0; + else + return 1; + + + } + + public ipAddress(CIDRIP ip, boolean include) { + included = include; + netAddress = BigInteger.valueOf(ip.getInt()); + networkMask = ip.len; + isV4 = true; + } + + public ipAddress(Inet6Address address, int mask, boolean include) { + networkMask = mask; + included = include; + + int s = 128; + + netAddress = BigInteger.ZERO; + for (byte b : address.getAddress()) { + s -= 16; + netAddress = netAddress.add(BigInteger.valueOf(b).shiftLeft(s)); + } + } + + public BigInteger getLastAddress() { + return getMaskedAddress(true); + } + + + public BigInteger getFirstAddress() { + return getMaskedAddress(false); + } + + + private BigInteger getMaskedAddress(boolean one) { + BigInteger numAddress = netAddress; + + int numBits; + if (isV4) { + numBits = 32 - networkMask; + } else { + numBits = 128 - networkMask; + } + + for (int i = 0; i < numBits; i++) { + if (one) + numAddress = numAddress.setBit(i); + else + numAddress = numAddress.clearBit(i); + } + return numAddress; + } + + + @Override + public String toString() { + //String in = included ? "+" : "-"; + if (isV4) + return String.format("%s/%d", getIPv4Address(), networkMask); + else + return String.format("%s/%d", getIPv6Address(), networkMask); + } + + ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) { + this.netAddress = baseAddress; + this.networkMask = mask; + this.included = included; + this.isV4 = isV4; + } + + + public ipAddress[] split() { + ipAddress firsthalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4); + ipAddress secondhalf = new ipAddress(firsthalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4); + assert secondhalf.getLastAddress().equals(getLastAddress()); + return new ipAddress[]{firsthalf, secondhalf}; + } + + String getIPv4Address() { + assert (isV4); + assert (netAddress.longValue() <= 0xffffffffl); + assert (netAddress.longValue() >= 0); + long ip = netAddress.longValue(); + return String.format("%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256); + } + + String getIPv6Address() { + assert (!isV4); + BigInteger r = netAddress; + if (r.longValue() == 0) + return "::"; + + Vector parts = new Vector(); + while (r.compareTo(BigInteger.ZERO) == 1) { + parts.add(0, String.format("%x", r.mod(BigInteger.valueOf(256)).longValue())); + r = r.shiftRight(16); + } + + return TextUtils.join(":", parts); + } + + public boolean containsNet(ipAddress network) { + return getFirstAddress().compareTo(network.getFirstAddress()) != 1 && + getLastAddress().compareTo(network.getLastAddress()) != -1; + } + } + + + TreeSet ipAddresses = new TreeSet(); + + + public Collection getNetworks(boolean included) { + Vector ips = new Vector(); + for (ipAddress ip : ipAddresses) { + if (ip.included == included) + ips.add(ip); + } + return ips; + } + + public void clear() { + ipAddresses.clear(); + } + + + void addIP(CIDRIP cidrIp, boolean include) { + + ipAddresses.add(new ipAddress(cidrIp, include)); + } + + void addIPv6(Inet6Address address, int mask, boolean included) { + ipAddresses.add(new ipAddress(address, mask, included)); + } + + TreeSet generateIPList() { + TreeSet ipsSorted = new TreeSet(ipAddresses); + Iterator it = ipsSorted.iterator(); + + ipAddress currentNet = null; + if (it.hasNext()) + currentNet = it.next(); + while (it.hasNext()) { + // Check if it and the next of it are compatbile + ipAddress nextNet = it.next(); + + assert currentNet != null; + if (currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { + // Everything good, no overlapping nothing to do + currentNet = nextNet; + } else { + // This network is smaller or equal to the next but has the same base address + if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) { + if (currentNet.included == nextNet.included) { + ipsSorted.remove(currentNet); + } else { + + // our currentnet is included in next and nextnet needs to be split + ipsSorted.remove(nextNet); + ipAddress[] newNets = nextNet.split(); + + if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) { + assert (newNets[0].networkMask == currentNet.networkMask); + // Don't add the lower half that would conflict with currentNet + } else { + ipsSorted.add(newNets[0]); + } + + ipsSorted.add(newNets[1]); + } + } else { + assert (currentNet.networkMask < nextNet.networkMask); + assert (nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); + // This network is bigger than the next and last ip of current >= next + assert (currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); + + if (currentNet.included == nextNet.included) { + ipsSorted.remove(nextNet); + } else { + ipsSorted.remove(currentNet); + ipAddress[] newNets = currentNet.split(); + + ipsSorted.add(newNets[0]); + + if (newNets[1].networkMask == nextNet.networkMask) { + assert (newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); + assert (newNets[1].getLastAddress().equals(currentNet.getLastAddress())); + } else { + ipsSorted.add(newNets[1]); + } + } + } + // Reset iterator + it = ipsSorted.iterator(); + currentNet = it.next(); + } + + } + + return ipsSorted; + } + + Collection getPositiveIPList() { + TreeSet ipsSorted = generateIPList(); + + Vector ips = new Vector(); + for (ipAddress ia : ipsSorted) { + if (ia.included) + ips.add(ia); + } + return ips; + } + +} diff --git a/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java index 2fa407ca..5fa70cc8 100644 --- a/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/src/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -379,9 +379,17 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { mOpenVPNService.setDomain(extra); } else if (needed.equals("ROUTE")) { String[] routeparts = extra.split(" "); - mOpenVPNService.addRoute(routeparts[0], routeparts[1]); + + if(routeparts.length>3) { + assert(routeparts[3].equals("dev")); + mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); + } else { + mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); + } + } else if (needed.equals("ROUTE6")) { - mOpenVPNService.addRoutev6(extra); + String[] routeparts = extra.split(" "); + mOpenVPNService.addRoutev6(routeparts[0],routeparts[1]); } else if (needed.equals("IFCONFIG")) { String[] ifconfigparts = extra.split(" "); int mtu = Integer.parseInt(ifconfigparts[2]); diff --git a/src/de/blinkt/openvpn/core/OpenVpnService.java b/src/de/blinkt/openvpn/core/OpenVpnService.java index 3de701b1..e485ee73 100644 --- a/src/de/blinkt/openvpn/core/OpenVpnService.java +++ b/src/de/blinkt/openvpn/core/OpenVpnService.java @@ -25,10 +25,15 @@ import de.blinkt.openvpn.core.VpnStatus.StateListener; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; import java.util.HashMap; import java.util.Locale; import java.util.Vector; +import static de.blinkt.openvpn.core.NetworkSpace.*; import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.*; public class OpenVpnService extends VpnService implements StateListener, Callback, ByteCountListener { @@ -41,8 +46,8 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac private static final int OPENVPN_STATUS = 1; private static boolean mNotificationAlwaysVisible = false; private final Vector mDnslist = new Vector(); - private final Vector mRoutes = new Vector(); - private final Vector mRoutesv6 = new Vector(); + private final NetworkSpace mRoutes = new NetworkSpace(); + private final NetworkSpace mRoutesv6 = new NetworkSpace(); private final IBinder mBinder = new LocalBinder(); private Thread mProcessThread = null; private VpnProfile mProfile; @@ -255,8 +260,7 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac mDeviceStateReceiver = null; } - public void userPause (boolean shouldBePaused) - { + public void userPause(boolean shouldBePaused) { if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(shouldBePaused); } @@ -349,7 +353,6 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac } - Runnable processThread; if (mOvpn3) { @@ -396,21 +399,21 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac } - private String getTunConfigString() - { + private String getTunConfigString() { // The format of the string is not important, only that // two identical configurations produce the same result - String cfg="TUNCFG UNQIUE STRING ips:"; - - if (mLocalIP!=null) - cfg+=mLocalIP.toString(); - if (mLocalIPv6!=null) - cfg+=mLocalIPv6.toString(); - - cfg+= "routes: " + TextUtils.join("|",mRoutes) + TextUtils.join("|",mRoutesv6); - cfg+= "dns: " + TextUtils.join("|",mDnslist); - cfg+= "domain: " + mDomain; - cfg+= "mtu: " + mMtu; + String cfg = "TUNCFG UNQIUE STRING ips:"; + + if (mLocalIP != null) + cfg += mLocalIP.toString(); + if (mLocalIPv6 != null) + cfg += mLocalIPv6.toString(); + + cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true)); + cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false)); + cfg += "dns: " + TextUtils.join("|", mDnslist); + cfg += "domain: " + mDomain; + cfg += "mtu: " + mMtu; return cfg; } @@ -455,20 +458,19 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac builder.setMtu(mMtu); - for (CIDRIP route : mRoutes) { + for (NetworkSpace.ipAddress route : mRoutes.getPositiveIPList()) { try { - builder.addRoute(route.mIp, route.len); + builder.addRoute(route.getIPv4Address(), route.networkMask); } catch (IllegalArgumentException ia) { VpnStatus.logError(getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); } } - for (String v6route : mRoutesv6) { + for (NetworkSpace.ipAddress route6 : mRoutesv6.getPositiveIPList()) { try { - String[] v6parts = v6route.split("/"); - builder.addRoute(v6parts[0], Integer.parseInt(v6parts[1])); + builder.addRoute(route6.getIPv6Address(), route6.networkMask); } catch (IllegalArgumentException ia) { - VpnStatus.logError(getString(R.string.route_rejected) + v6route + " " + ia.getLocalizedMessage()); + VpnStatus.logError(getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage()); } } @@ -477,9 +479,10 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac VpnStatus.logInfo(R.string.last_openvpn_tun_config); VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu); - VpnStatus.logInfo(R.string.dns_server_info, joinString(mDnslist), mDomain); - VpnStatus.logInfo(R.string.routes_info, joinString(mRoutes)); - VpnStatus.logInfo(R.string.routes_info6, joinString(mRoutesv6)); + VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain); + VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true))); + VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)),TextUtils.join(", ", mRoutesv6.getNetworks(false))); + VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", mRoutes.getPositiveIPList()), TextUtils.join(", ", mRoutesv6.getPositiveIPList())); String session = mProfile.mName; if (mLocalIP != null && mLocalIPv6 != null) @@ -518,18 +521,6 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac } - // Ugly, but java has no such method - private String joinString(Vector vec) { - String ret = ""; - if (vec.size() > 0) { - ret = vec.get(0).toString(); - for (int i = 1; i < vec.size(); i++) { - ret = ret + ", " + vec.get(i).toString(); - } - } - return ret; - } - public void addDNS(String dns) { mDnslist.add(dns); } @@ -540,8 +531,17 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac } } - public void addRoute(String dest, String mask) { + public void addRoute(String dest, String mask, String gateway, String device) { CIDRIP route = new CIDRIP(dest, mask); + boolean include = isAndroidTunDevice(device); + + NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32),false); + + NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP,true); + if (localNet.containsNet(gatewayIP)) + include =true; + + if (route.len == 32 && !mask.equals("255.255.255.255")) { VpnStatus.logWarning(R.string.route_not_cidr, dest, mask); } @@ -549,11 +549,30 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac if (route.normalise()) VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp); - mRoutes.add(route); + mRoutes.addIP(route, include); + } + + public void addRoutev6(String network, String device) { + String[] v6parts = network.split("/"); + boolean included = isAndroidTunDevice(device); + + // Tun is opened after ROUTE6, no device name may be present + + try { + Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0]; + int mask = Integer.parseInt(v6parts[1]); + mRoutesv6.addIPv6(ip, mask, included); + + } catch (UnknownHostException e) { + VpnStatus.logException(e); + } + + } - public void addRoutev6(String extra) { - mRoutesv6.add(extra); + private boolean isAndroidTunDevice(String device) { + return device!=null && + (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device)); } public void setMtu(int mtu) { @@ -657,9 +676,9 @@ public class OpenVpnService extends VpnService implements StateListener, Callbac public String getTunReopenStatus() { String currentConfiguration = getTunConfigString(); - if(currentConfiguration.equals(mLastTunCfg)) + if (currentConfiguration.equals(mLastTunCfg)) return "NOACTION"; - else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) return "OPEN_AFTER_CLOSE"; else return "OPEN_BEFORE_CLOSE"; diff --git a/src/de/blinkt/openvpn/core/VpnStatus.java b/src/de/blinkt/openvpn/core/VpnStatus.java index f2953240..d146aef8 100644 --- a/src/de/blinkt/openvpn/core/VpnStatus.java +++ b/src/de/blinkt/openvpn/core/VpnStatus.java @@ -479,7 +479,12 @@ public class VpnStatus { newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); } - private synchronized static void newLogItem(LogItem logItem) { + public static void logDebug(int resourceId, Object... args) { + newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); + } + + + private synchronized static void newLogItem(LogItem logItem) { logbuffer.addLast(logItem); if(logbuffer.size()>MAXLOGENTRIES) logbuffer.removeFirst(); -- cgit v1.2.3