diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java index b9362dabf2..ec8d77e757 100644 --- a/core/java/android/net/LinkProperties.java +++ b/core/java/android/net/LinkProperties.java @@ -92,6 +92,11 @@ public class LinkProperties implements Parcelable { public void setInterfaceName(String iface) { mIfaceName = iface; + ArrayList newRoutes = new ArrayList(mRoutes.size()); + for (RouteInfo route : mRoutes) { + newRoutes.add(routeWithInterface(route)); + } + mRoutes = newRoutes; } public String getInterfaceName() { @@ -130,9 +135,25 @@ public class LinkProperties implements Parcelable { mDomains = domains; } - public void addRoute(RouteInfo route) { - if (route != null) mRoutes.add(route); + private RouteInfo routeWithInterface(RouteInfo route) { + return new RouteInfo( + route.getDestination(), + route.getGateway(), + mIfaceName); } + + public void addRoute(RouteInfo route) { + if (route != null) { + String routeIface = route.getInterface(); + if (routeIface != null && !routeIface.equals(mIfaceName)) { + throw new IllegalStateException( + "Route added with non-matching interface: " + routeIface + + " vs. mIfaceName"); + } + mRoutes.add(routeWithInterface(route)); + } + } + public Collection getRoutes() { return Collections.unmodifiableCollection(mRoutes); } diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java index 112e14397e..46b6cbb557 100644 --- a/core/java/android/net/RouteInfo.java +++ b/core/java/android/net/RouteInfo.java @@ -29,6 +29,17 @@ import java.util.Collection; /** * A simple container for route information. * + * In order to be used, a route must have a destination prefix and: + * + * - A gateway address (next-hop, for gatewayed routes), or + * - An interface (for directly-connected routes), or + * - Both a gateway and an interface. + * + * This class does not enforce these constraints because there is code that + * uses RouteInfo objects to store directly-connected routes without interfaces. + * Such objects cannot be used directly, but can be put into a LinkProperties + * object which then specifies the interface. + * * @hide */ public class RouteInfo implements Parcelable { @@ -42,10 +53,30 @@ public class RouteInfo implements Parcelable { */ private final InetAddress mGateway; + /** + * The interface for this route. + */ + private final String mInterface; + private final boolean mIsDefault; private final boolean mIsHost; - public RouteInfo(LinkAddress destination, InetAddress gateway) { + /** + * Constructs a RouteInfo object. + * + * If @destination is null, then @gateway must be specified and the + * constructed route is either the IPv4 default route 0.0.0.0 + * if @gateway is an instance of {@link Inet4Address}, or the IPv6 default + * route ::/0 if @gateway is an instance of + * {@link Inet6Address}. + * + * @destination and @gateway may not both be null. + * + * @param destination the destination prefix + * @param gateway the IP address to route packets through + * @param iface the interface name to send packets on + */ + public RouteInfo(LinkAddress destination, InetAddress gateway, String iface) { if (destination == null) { if (gateway != null) { if (gateway instanceof Inet4Address) { @@ -55,7 +86,8 @@ public class RouteInfo implements Parcelable { } } else { // no destination, no gateway. invalid. - throw new RuntimeException("Invalid arguments passed in."); + throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," + + destination); } } if (gateway == null) { @@ -68,16 +100,21 @@ public class RouteInfo implements Parcelable { mDestination = new LinkAddress(NetworkUtils.getNetworkPart(destination.getAddress(), destination.getNetworkPrefixLength()), destination.getNetworkPrefixLength()); mGateway = gateway; + mInterface = iface; mIsDefault = isDefault(); mIsHost = isHost(); } + public RouteInfo(LinkAddress destination, InetAddress gateway) { + this(destination, gateway, null); + } + public RouteInfo(InetAddress gateway) { - this(null, gateway); + this(null, gateway, null); } public RouteInfo(LinkAddress host) { - this(host, null); + this(host, null, null); } public static RouteInfo makeHostRoute(InetAddress host) { @@ -119,6 +156,10 @@ public class RouteInfo implements Parcelable { return mGateway; } + public String getInterface() { + return mInterface; + } + public boolean isDefaultRoute() { return mIsDefault; } @@ -153,6 +194,8 @@ public class RouteInfo implements Parcelable { dest.writeByte((byte) 1); dest.writeByteArray(mGateway.getAddress()); } + + dest.writeString(mInterface); } @Override @@ -171,14 +214,19 @@ public class RouteInfo implements Parcelable { target.getGateway() == null : mGateway.equals(target.getGateway()); - return sameDestination && sameAddress + boolean sameInterface = (mInterface == null) ? + target.getInterface() == null + : mInterface.equals(target.getInterface()); + + return sameDestination && sameAddress && sameInterface && mIsDefault == target.mIsDefault; } @Override public int hashCode() { - return (mDestination == null ? 0 : mDestination.hashCode()) - + (mGateway == null ? 0 :mGateway.hashCode()) + return (mDestination == null ? 0 : mDestination.hashCode() * 41) + + (mGateway == null ? 0 :mGateway.hashCode() * 47) + + (mInterface == null ? 0 :mInterface.hashCode() * 67) + (mIsDefault ? 3 : 7); } @@ -206,13 +254,15 @@ public class RouteInfo implements Parcelable { } catch (UnknownHostException e) {} } + String iface = in.readString(); + LinkAddress dest = null; if (destAddr != null) { dest = new LinkAddress(destAddr, prefix); } - return new RouteInfo(dest, gateway); + return new RouteInfo(dest, gateway, iface); } public RouteInfo[] newArray(int size) { @@ -220,13 +270,9 @@ public class RouteInfo implements Parcelable { } }; - private boolean matches(InetAddress destination) { + protected boolean matches(InetAddress destination) { if (destination == null) return false; - // if the destination is present and the route is default. - // return true - if (isDefault()) return true; - // match the route destination and destination with prefix length InetAddress dstNet = NetworkUtils.getNetworkPart(destination, mDestination.getNetworkPrefixLength()); diff --git a/core/tests/coretests/src/android/net/RouteInfoTest.java b/core/tests/coretests/src/android/net/RouteInfoTest.java new file mode 100644 index 0000000000..59eb601ff0 --- /dev/null +++ b/core/tests/coretests/src/android/net/RouteInfoTest.java @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.net; + +import java.lang.reflect.Method; +import java.net.InetAddress; + +import android.net.LinkAddress; +import android.net.RouteInfo; +import android.os.Parcel; + +import junit.framework.TestCase; +import android.test.suitebuilder.annotation.SmallTest; + +public class RouteInfoTest extends TestCase { + + private InetAddress Address(String addr) { + return InetAddress.parseNumericAddress(addr); + } + + private LinkAddress Prefix(String prefix) { + String[] parts = prefix.split("/"); + return new LinkAddress(Address(parts[0]), Integer.parseInt(parts[1])); + } + + @SmallTest + public void testConstructor() { + RouteInfo r; + + // Invalid input. + try { + r = new RouteInfo(null, null, "rmnet0"); + fail("Expected RuntimeException: destination and gateway null"); + } catch(RuntimeException e) {} + + // Null destination is default route. + r = new RouteInfo(null, Address("2001:db8::1"), null); + assertEquals(Prefix("::/0"), r.getDestination()); + assertEquals(Address("2001:db8::1"), r.getGateway()); + assertNull(r.getInterface()); + + r = new RouteInfo(null, Address("192.0.2.1"), "wlan0"); + assertEquals(Prefix("0.0.0.0/0"), r.getDestination()); + assertEquals(Address("192.0.2.1"), r.getGateway()); + assertEquals("wlan0", r.getInterface()); + + // Null gateway sets gateway to unspecified address (why?). + r = new RouteInfo(Prefix("2001:db8:beef:cafe::/48"), null, "lo"); + assertEquals(Prefix("2001:db8:beef::/48"), r.getDestination()); + assertEquals(Address("::"), r.getGateway()); + assertEquals("lo", r.getInterface()); + + r = new RouteInfo(Prefix("192.0.2.5/24"), null); + assertEquals(Prefix("192.0.2.0/24"), r.getDestination()); + assertEquals(Address("0.0.0.0"), r.getGateway()); + assertNull(r.getInterface()); + } + + public void testMatches() { + class PatchedRouteInfo extends RouteInfo { + public PatchedRouteInfo(LinkAddress destination, InetAddress gateway, String iface) { + super(destination, gateway, iface); + } + + public boolean matches(InetAddress destination) { + return super.matches(destination); + } + } + + RouteInfo r; + + r = new PatchedRouteInfo(Prefix("2001:db8:f00::ace:d00d/127"), null, "rmnet0"); + assertTrue(r.matches(Address("2001:db8:f00::ace:d00c"))); + assertTrue(r.matches(Address("2001:db8:f00::ace:d00d"))); + assertFalse(r.matches(Address("2001:db8:f00::ace:d00e"))); + assertFalse(r.matches(Address("2001:db8:f00::bad:d00d"))); + assertFalse(r.matches(Address("2001:4868:4860::8888"))); + + r = new PatchedRouteInfo(Prefix("192.0.2.0/23"), null, "wlan0"); + assertTrue(r.matches(Address("192.0.2.43"))); + assertTrue(r.matches(Address("192.0.3.21"))); + assertFalse(r.matches(Address("192.0.0.21"))); + assertFalse(r.matches(Address("8.8.8.8"))); + + RouteInfo ipv6Default = new PatchedRouteInfo(Prefix("::/0"), null, "rmnet0"); + assertTrue(ipv6Default.matches(Address("2001:db8::f00"))); + assertFalse(ipv6Default.matches(Address("192.0.2.1"))); + + RouteInfo ipv4Default = new PatchedRouteInfo(Prefix("0.0.0.0/0"), null, "rmnet0"); + assertTrue(ipv4Default.matches(Address("255.255.255.255"))); + assertTrue(ipv4Default.matches(Address("192.0.2.1"))); + assertFalse(ipv4Default.matches(Address("2001:db8::f00"))); + } + + private void assertAreEqual(Object o1, Object o2) { + assertTrue(o1.equals(o2)); + assertTrue(o2.equals(o1)); + } + + private void assertAreNotEqual(Object o1, Object o2) { + assertFalse(o1.equals(o2)); + assertFalse(o2.equals(o1)); + } + + public void testEquals() { + // IPv4 + RouteInfo r1 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0"); + RouteInfo r2 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "wlan0"); + assertAreEqual(r1, r2); + + RouteInfo r3 = new RouteInfo(Prefix("2001:db8:ace::/49"), Address("2001:db8::1"), "wlan0"); + RouteInfo r4 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::2"), "wlan0"); + RouteInfo r5 = new RouteInfo(Prefix("2001:db8:ace::/48"), Address("2001:db8::1"), "rmnet0"); + assertAreNotEqual(r1, r3); + assertAreNotEqual(r1, r4); + assertAreNotEqual(r1, r5); + + // IPv6 + r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0"); + r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "wlan0"); + assertAreEqual(r1, r2); + + r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0"); + r4 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.2"), "wlan0"); + r5 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), "rmnet0"); + assertAreNotEqual(r1, r3); + assertAreNotEqual(r1, r4); + assertAreNotEqual(r1, r5); + + // Interfaces (but not destinations or gateways) can be null. + r1 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null); + r2 = new RouteInfo(Prefix("192.0.2.0/25"), Address("192.0.2.1"), null); + r3 = new RouteInfo(Prefix("192.0.2.0/24"), Address("192.0.2.1"), "wlan0"); + assertAreEqual(r1, r2); + assertAreNotEqual(r1, r3); + } + + public RouteInfo passThroughParcel(RouteInfo r) { + Parcel p = Parcel.obtain(); + RouteInfo r2 = null; + try { + r.writeToParcel(p, 0); + p.setDataPosition(0); + r2 = RouteInfo.CREATOR.createFromParcel(p); + } finally { + p.recycle(); + } + assertNotNull(r2); + return r2; + } + + public void assertParcelingIsLossless(RouteInfo r) { + RouteInfo r2 = passThroughParcel(r); + assertEquals(r, r2); + } + + public void testParceling() { + RouteInfo r; + + r = new RouteInfo(Prefix("::/0"), Address("2001:db8::"), null); + assertParcelingIsLossless(r); + + r = new RouteInfo(Prefix("192.0.2.0/24"), null, "wlan0"); + assertParcelingIsLossless(r); + } +}