/* * Copyright (C) 2011 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 android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import com.android.net.module.util.NetUtils; import com.android.net.module.util.NetworkStackConstants; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; import java.util.Objects; /** * Represents a network route. *

* This is used both to describe static network configuration and live network * configuration information. * * A route contains three pieces of information: *

* Either the destination or the gateway may be {@code null}, but not both. If the * destination and gateway are both specified, they must be of the same address family * (IPv4 or IPv6). */ public final class RouteInfo implements Parcelable { /** @hide */ @IntDef(value = { RTN_UNICAST, RTN_UNREACHABLE, RTN_THROW, }) @Retention(RetentionPolicy.SOURCE) public @interface RouteType {} /** * The IP destination address for this route. */ @NonNull private final IpPrefix mDestination; /** * The gateway address for this route. */ @UnsupportedAppUsage @Nullable private final InetAddress mGateway; /** * The interface for this route. */ @Nullable private final String mInterface; /** * Unicast route. * * Indicates that destination is reachable directly or via gateway. **/ public static final int RTN_UNICAST = 1; /** * Unreachable route. * * Indicates that destination is unreachable. **/ public static final int RTN_UNREACHABLE = 7; /** * Throw route. * * Indicates that routing information about this destination is not in this table. * Routing lookup should continue in another table. **/ public static final int RTN_THROW = 9; /** * The type of this route; one of the RTN_xxx constants above. */ private final int mType; /** * The maximum transmission unit size for this route. */ private final int mMtu; // Derived data members. // TODO: remove these. @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final boolean mIsHost; private final boolean mHasGateway; /** * 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 the 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 * @param type the type of this route * * @hide */ @SystemApi public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, @Nullable String iface, @RouteType int type) { this(destination, gateway, iface, type, 0); } /** * 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 the 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 * @param type the type of this route * @param mtu the maximum transmission unit size for this route * * @hide */ @SystemApi public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, @Nullable String iface, @RouteType int type, int mtu) { switch (type) { case RTN_UNICAST: case RTN_UNREACHABLE: case RTN_THROW: // TODO: It would be nice to ensure that route types that don't have nexthops or // interfaces, such as unreachable or throw, can't be created if an interface or // a gateway is specified. This is a bit too complicated to do at the moment // because: // // - LinkProperties sets the interface on routes added to it, and modifies the // interfaces of all the routes when its interface name changes. // - Even when the gateway is null, we store a non-null gateway here. // // For now, we just rely on the code that sets routes to do things properly. break; default: throw new IllegalArgumentException("Unknown route type " + type); } if (destination == null) { if (gateway != null) { if (gateway instanceof Inet4Address) { destination = new IpPrefix(NetworkStackConstants.IPV4_ADDR_ANY, 0); } else { destination = new IpPrefix(NetworkStackConstants.IPV6_ADDR_ANY, 0); } } else { // no destination, no gateway. invalid. throw new IllegalArgumentException("Invalid arguments passed in: " + gateway + "," + destination); } } // TODO: set mGateway to null if there is no gateway. This is more correct, saves space, and // matches the documented behaviour. Before we can do this we need to fix all callers (e.g., // ConnectivityService) to stop doing things like r.getGateway().equals(), ... . if (gateway == null) { if (destination.getAddress() instanceof Inet4Address) { gateway = NetworkStackConstants.IPV4_ADDR_ANY; } else { gateway = NetworkStackConstants.IPV6_ADDR_ANY; } } mHasGateway = (!gateway.isAnyLocalAddress()); if ((destination.getAddress() instanceof Inet4Address && !(gateway instanceof Inet4Address)) || (destination.getAddress() instanceof Inet6Address && !(gateway instanceof Inet6Address))) { throw new IllegalArgumentException("address family mismatch in RouteInfo constructor"); } mDestination = destination; // IpPrefix objects are immutable. mGateway = gateway; // InetAddress objects are immutable. mInterface = iface; // Strings are immutable. mType = type; mIsHost = isHost(); mMtu = mtu; } /** * Constructs a {@code 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 the 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 address and prefix in an {@link IpPrefix} * @param gateway the {@link InetAddress} to route packets through * @param iface the interface name to send packets on * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway, @Nullable String iface) { this(destination, gateway, iface, RTN_UNICAST); } /** * @hide */ @UnsupportedAppUsage public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway, @Nullable String iface) { this(destination == null ? null : new IpPrefix(destination.getAddress(), destination.getPrefixLength()), gateway, iface); } /** * Constructs a {@code 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 the 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 address and prefix in an {@link IpPrefix} * @param gateway the {@link InetAddress} to route packets through * * @hide */ public RouteInfo(@Nullable IpPrefix destination, @Nullable InetAddress gateway) { this(destination, gateway, null); } /** * @hide * * TODO: Remove this. */ @UnsupportedAppUsage public RouteInfo(@Nullable LinkAddress destination, @Nullable InetAddress gateway) { this(destination, gateway, null); } /** * Constructs a default {@code RouteInfo} object. * * @param gateway the {@link InetAddress} to route packets through * * @hide */ @UnsupportedAppUsage public RouteInfo(@NonNull InetAddress gateway) { this((IpPrefix) null, gateway, null); } /** * Constructs a {@code RouteInfo} object representing a direct connected subnet. * * @param destination the {@link IpPrefix} describing the address and prefix * length of the subnet. * * @hide */ public RouteInfo(@NonNull IpPrefix destination) { this(destination, null, null); } /** * @hide */ public RouteInfo(@NonNull LinkAddress destination) { this(destination, null, null); } /** * @hide */ public RouteInfo(@NonNull IpPrefix destination, @RouteType int type) { this(destination, null, null, type); } /** * @hide */ public static RouteInfo makeHostRoute(@NonNull InetAddress host, @Nullable String iface) { return makeHostRoute(host, null, iface); } /** * @hide */ public static RouteInfo makeHostRoute(@Nullable InetAddress host, @Nullable InetAddress gateway, @Nullable String iface) { if (host == null) return null; if (host instanceof Inet4Address) { return new RouteInfo(new IpPrefix(host, 32), gateway, iface); } else { return new RouteInfo(new IpPrefix(host, 128), gateway, iface); } } @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private boolean isHost() { return (mDestination.getAddress() instanceof Inet4Address && mDestination.getPrefixLength() == 32) || (mDestination.getAddress() instanceof Inet6Address && mDestination.getPrefixLength() == 128); } /** * Retrieves the destination address and prefix length in the form of an {@link IpPrefix}. * * @return {@link IpPrefix} specifying the destination. This is never {@code null}. */ @NonNull public IpPrefix getDestination() { return mDestination; } /** * TODO: Convert callers to use IpPrefix and then remove. * @hide */ @NonNull public LinkAddress getDestinationLinkAddress() { return new LinkAddress(mDestination.getAddress(), mDestination.getPrefixLength()); } /** * Retrieves the gateway or next hop {@link InetAddress} for this route. * * @return {@link InetAddress} specifying the gateway or next hop. This may be * {@code null} for a directly-connected route." */ @Nullable public InetAddress getGateway() { return mGateway; } /** * Retrieves the interface used for this route if specified, else {@code null}. * * @return The name of the interface used for this route. */ @Nullable public String getInterface() { return mInterface; } /** * Retrieves the type of this route. * * @return The type of this route; one of the {@code RTN_xxx} constants defined in this class. */ @RouteType public int getType() { return mType; } /** * Retrieves the MTU size for this route. * * @return The MTU size, or 0 if it has not been set. * @hide */ @SystemApi public int getMtu() { return mMtu; } /** * Indicates if this route is a default route (ie, has no destination specified). * * @return {@code true} if the destination has a prefix length of 0. */ public boolean isDefaultRoute() { return mType == RTN_UNICAST && mDestination.getPrefixLength() == 0; } /** * Indicates if this route is an unreachable default route. * * @return {@code true} if it's an unreachable route with prefix length of 0. * @hide */ private boolean isUnreachableDefaultRoute() { return mType == RTN_UNREACHABLE && mDestination.getPrefixLength() == 0; } /** * Indicates if this route is an IPv4 default route. * @hide */ public boolean isIPv4Default() { return isDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; } /** * Indicates if this route is an IPv4 unreachable default route. * @hide */ public boolean isIPv4UnreachableDefault() { return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet4Address; } /** * Indicates if this route is an IPv6 default route. * @hide */ public boolean isIPv6Default() { return isDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; } /** * Indicates if this route is an IPv6 unreachable default route. * @hide */ public boolean isIPv6UnreachableDefault() { return isUnreachableDefaultRoute() && mDestination.getAddress() instanceof Inet6Address; } /** * Indicates if this route is a host route (ie, matches only a single host address). * * @return {@code true} if the destination has a prefix length of 32 or 128 for IPv4 or IPv6, * respectively. * @hide */ public boolean isHostRoute() { return mIsHost; } /** * Indicates if this route has a next hop ({@code true}) or is directly-connected * ({@code false}). * * @return {@code true} if a gateway is specified */ public boolean hasGateway() { return mHasGateway; } /** * Determines whether the destination and prefix of this route includes the specified * address. * * @param destination A {@link InetAddress} to test to see if it would match this route. * @return {@code true} if the destination and prefix length cover the given address. */ public boolean matches(InetAddress destination) { return mDestination.contains(destination); } /** * Find the route from a Collection of routes that best matches a given address. * May return null if no routes are applicable. * @param routes a Collection of RouteInfos to chose from * @param dest the InetAddress your trying to get to * @return the RouteInfo from the Collection that best fits the given address * * @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @Nullable public static RouteInfo selectBestRoute(Collection routes, InetAddress dest) { return NetUtils.selectBestRoute(routes, dest); } /** * Returns a human-readable description of this object. */ public String toString() { String val = ""; if (mDestination != null) val = mDestination.toString(); if (mType == RTN_UNREACHABLE) { val += " unreachable"; } else if (mType == RTN_THROW) { val += " throw"; } else { val += " ->"; if (mGateway != null) val += " " + mGateway.getHostAddress(); if (mInterface != null) val += " " + mInterface; if (mType != RTN_UNICAST) { val += " unknown type " + mType; } } val += " mtu " + mMtu; return val; } /** * Compares this RouteInfo object against the specified object and indicates if they are equal. * @return {@code true} if the objects are equal, {@code false} otherwise. */ public boolean equals(@Nullable Object obj) { if (this == obj) return true; if (!(obj instanceof RouteInfo)) return false; RouteInfo target = (RouteInfo) obj; return Objects.equals(mDestination, target.getDestination()) && Objects.equals(mGateway, target.getGateway()) && Objects.equals(mInterface, target.getInterface()) && mType == target.getType() && mMtu == target.getMtu(); } /** * A helper class that contains the destination, the gateway and the interface in a * {@code RouteInfo}, used by {@link ConnectivityService#updateRoutes} or * {@link LinkProperties#addRoute} to calculate the list to be updated. * {@code RouteInfo} objects with different interfaces are treated as different routes because * *usually* on Android different interfaces use different routing tables, and moving a route * to a new routing table never constitutes an update, but is always a remove and an add. * * @hide */ public static class RouteKey { @NonNull private final IpPrefix mDestination; @Nullable private final InetAddress mGateway; @Nullable private final String mInterface; RouteKey(@NonNull IpPrefix destination, @Nullable InetAddress gateway, @Nullable String iface) { mDestination = destination; mGateway = gateway; mInterface = iface; } @Override public boolean equals(@Nullable Object o) { if (!(o instanceof RouteKey)) { return false; } RouteKey p = (RouteKey) o; // No need to do anything special for scoped addresses. Inet6Address#equals does not // consider the scope ID, but the route IPCs (e.g., RoutingCoordinatorManager#addRoute) // and the kernel ignore scoped addresses both in the prefix and in the nexthop and only // look at RTA_OIF. return Objects.equals(p.mDestination, mDestination) && Objects.equals(p.mGateway, mGateway) && Objects.equals(p.mInterface, mInterface); } @Override public int hashCode() { return Objects.hash(mDestination, mGateway, mInterface); } } /** * Get {@code RouteKey} of this {@code RouteInfo}. * @return a {@code RouteKey} object. * * @hide */ @NonNull public RouteKey getRouteKey() { return new RouteKey(mDestination, mGateway, mInterface); } /** * Returns a hashcode for this RouteInfo object. */ public int hashCode() { return (mDestination.hashCode() * 41) + (mGateway == null ? 0 :mGateway.hashCode() * 47) + (mInterface == null ? 0 :mInterface.hashCode() * 67) + (mType * 71) + (mMtu * 89); } /** * Implement the Parcelable interface */ public int describeContents() { return 0; } /** * Implement the Parcelable interface */ public void writeToParcel(Parcel dest, int flags) { dest.writeParcelable(mDestination, flags); byte[] gatewayBytes = (mGateway == null) ? null : mGateway.getAddress(); dest.writeByteArray(gatewayBytes); dest.writeString(mInterface); dest.writeInt(mType); dest.writeInt(mMtu); } /** * Implement the Parcelable interface. */ public static final @android.annotation.NonNull Creator CREATOR = new Creator() { public RouteInfo createFromParcel(Parcel in) { IpPrefix dest = in.readParcelable(null); InetAddress gateway = null; byte[] addr = in.createByteArray(); try { gateway = InetAddress.getByAddress(addr); } catch (UnknownHostException e) {} String iface = in.readString(); int type = in.readInt(); int mtu = in.readInt(); return new RouteInfo(dest, gateway, iface, type, mtu); } public RouteInfo[] newArray(int size) { return new RouteInfo[size]; } }; }