summaryrefslogtreecommitdiff
path: root/src/de/blinkt
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2014-01-21 20:37:31 +0100
committerArne Schwabe <arne@rfc2549.org>2014-01-21 20:37:31 +0100
commitb7968faa2a6dac1bd9641309ccf4c9a387bca26c (patch)
treeb73b91ede0a7b3257dda85c056873ceb8ef0db07 /src/de/blinkt
parentf3957386eb230ab85fa7d727c96d9ca6fe122ee3 (diff)
Add to code that allows excluding routes from the VPN
--HG-- extra : rebase_source : 7e20e643cb0949520b92f7ab7b623d6856ea4ef7
Diffstat (limited to 'src/de/blinkt')
-rw-r--r--src/de/blinkt/openvpn/VpnProfile.java4
-rw-r--r--src/de/blinkt/openvpn/core/ConfigParser.java2
-rw-r--r--src/de/blinkt/openvpn/core/NetworkSpace.java244
-rw-r--r--src/de/blinkt/openvpn/core/OpenVpnManagementThread.java12
-rw-r--r--src/de/blinkt/openvpn/core/OpenVpnService.java109
-rw-r--r--src/de/blinkt/openvpn/core/VpnStatus.java7
6 files changed, 327 insertions, 51 deletions
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 :)
// --<foo>
// bar
// </foo>
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<ipAddress> {
+ 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<String> parts = new Vector<String>();
+ 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<ipAddress> ipAddresses = new TreeSet<ipAddress>();
+
+
+ public Collection<ipAddress> getNetworks(boolean included) {
+ Vector<ipAddress> ips = new Vector<ipAddress>();
+ 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<ipAddress> generateIPList() {
+ TreeSet<ipAddress> ipsSorted = new TreeSet<ipAddress>(ipAddresses);
+ Iterator<ipAddress> 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<ipAddress> getPositiveIPList() {
+ TreeSet<ipAddress> ipsSorted = generateIPList();
+
+ Vector<ipAddress> ips = new Vector<ipAddress>();
+ 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<String> mDnslist = new Vector<String>();
- private final Vector<CIDRIP> mRoutes = new Vector<CIDRIP>();
- private final Vector<String> mRoutesv6 = new Vector<String>();
+ 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 <T> String joinString(Vector<T> 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();