/* * 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 android.net.ProxyProperties; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; import java.net.InetAddress; import java.net.Inet4Address; import java.net.Inet6Address; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Hashtable; /** * Describes the properties of a network link. * * A link represents a connection to a network. * It may have multiple addresses and multiple gateways, * multiple dns servers but only one http proxy. * * Because it's a single network, the dns's * are interchangeable and don't need associating with * particular addresses. The gateways similarly don't * need associating with particular addresses. * * A dual stack interface works fine in this model: * each address has it's own prefix length to describe * the local network. The dns servers all return * both v4 addresses and v6 addresses regardless of the * address family of the server itself (rfc4213) and we * don't care which is used. The gateways will be * selected based on the destination address and the * source address has no relavence. * * Links can also be stacked on top of each other. * This can be used, for example, to represent a tunnel * interface that runs on top of a physical interface. * * @hide */ public class LinkProperties implements Parcelable { // The interface described by the network link. private String mIfaceName; private Collection mLinkAddresses = new ArrayList(); private Collection mDnses = new ArrayList(); private String mDomains; private Collection mRoutes = new ArrayList(); private ProxyProperties mHttpProxy; private int mMtu; // Stores the properties of links that are "stacked" above this link. // Indexed by interface name to allow modification and to prevent duplicates being added. private Hashtable mStackedLinks = new Hashtable(); public static class CompareResult { public Collection removed = new ArrayList(); public Collection added = new ArrayList(); @Override public String toString() { String retVal = "removed=["; for (T addr : removed) retVal += addr.toString() + ","; retVal += "] added=["; for (T addr : added) retVal += addr.toString() + ","; retVal += "]"; return retVal; } } public LinkProperties() { clear(); } // copy constructor instead of clone public LinkProperties(LinkProperties source) { if (source != null) { mIfaceName = source.getInterfaceName(); for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l); for (InetAddress i : source.getDnses()) mDnses.add(i); mDomains = source.getDomains(); for (RouteInfo r : source.getRoutes()) mRoutes.add(r); mHttpProxy = (source.getHttpProxy() == null) ? null : new ProxyProperties(source.getHttpProxy()); for (LinkProperties l: source.mStackedLinks.values()) { addStackedLink(l); } setMtu(source.getMtu()); } } 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() { return mIfaceName; } public Collection getAllInterfaceNames() { Collection interfaceNames = new ArrayList(mStackedLinks.size() + 1); if (mIfaceName != null) interfaceNames.add(new String(mIfaceName)); for (LinkProperties stacked: mStackedLinks.values()) { interfaceNames.addAll(stacked.getAllInterfaceNames()); } return interfaceNames; } /** * Returns all the addresses on this link. */ public Collection getAddresses() { Collection addresses = new ArrayList(); for (LinkAddress linkAddress : mLinkAddresses) { addresses.add(linkAddress.getAddress()); } return Collections.unmodifiableCollection(addresses); } /** * Returns all the addresses on this link and all the links stacked above it. */ public Collection getAllAddresses() { Collection addresses = new ArrayList(); for (LinkAddress linkAddress : mLinkAddresses) { addresses.add(linkAddress.getAddress()); } for (LinkProperties stacked: mStackedLinks.values()) { addresses.addAll(stacked.getAllAddresses()); } return addresses; } /** * Adds a link address if it does not exist, or update it if it does. * @param address The {@code LinkAddress} to add. * @return true if the address was added, false if it already existed. */ public boolean addLinkAddress(LinkAddress address) { // TODO: when the LinkAddress has other attributes beyond the // address and the prefix length, update them here. if (address != null && !mLinkAddresses.contains(address)) { mLinkAddresses.add(address); return true; } return false; } /** * Removes a link address. * @param address The {@code LinkAddress} to remove. * @return true if the address was removed, false if it did not exist. */ public boolean removeLinkAddress(LinkAddress toRemove) { return mLinkAddresses.remove(toRemove); } /** * Returns all the addresses on this link. */ public Collection getLinkAddresses() { return Collections.unmodifiableCollection(mLinkAddresses); } /** * Returns all the addresses on this link and all the links stacked above it. */ public Collection getAllLinkAddresses() { Collection addresses = new ArrayList(); addresses.addAll(mLinkAddresses); for (LinkProperties stacked: mStackedLinks.values()) { addresses.addAll(stacked.getAllLinkAddresses()); } return addresses; } /** * Replaces the LinkAddresses on this link with the given collection of addresses. */ public void setLinkAddresses(Collection addresses) { mLinkAddresses.clear(); for (LinkAddress address: addresses) { addLinkAddress(address); } } public void addDns(InetAddress dns) { if (dns != null) mDnses.add(dns); } public Collection getDnses() { return Collections.unmodifiableCollection(mDnses); } public String getDomains() { return mDomains; } public void setDomains(String domains) { mDomains = domains; } public void setMtu(int mtu) { mMtu = mtu; } public int getMtu() { return mMtu; } 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 IllegalArgumentException( "Route added with non-matching interface: " + routeIface + " vs. " + mIfaceName); } mRoutes.add(routeWithInterface(route)); } } /** * Returns all the routes on this link. */ public Collection getRoutes() { return Collections.unmodifiableCollection(mRoutes); } /** * Returns all the routes on this link and all the links stacked above it. */ public Collection getAllRoutes() { Collection routes = new ArrayList(); routes.addAll(mRoutes); for (LinkProperties stacked: mStackedLinks.values()) { routes.addAll(stacked.getAllRoutes()); } return routes; } public void setHttpProxy(ProxyProperties proxy) { mHttpProxy = proxy; } public ProxyProperties getHttpProxy() { return mHttpProxy; } /** * Adds a stacked link. * * If there is already a stacked link with the same interfacename as link, * that link is replaced with link. Otherwise, link is added to the list * of stacked links. If link is null, nothing changes. * * @param link The link to add. * @return true if the link was stacked, false otherwise. */ public boolean addStackedLink(LinkProperties link) { if (link != null && link.getInterfaceName() != null) { mStackedLinks.put(link.getInterfaceName(), link); return true; } return false; } /** * Removes a stacked link. * * If there a stacked link with the same interfacename as link, it is * removed. Otherwise, nothing changes. * * @param link The link to remove. * @return true if the link was removed, false otherwise. */ public boolean removeStackedLink(LinkProperties link) { if (link != null && link.getInterfaceName() != null) { LinkProperties removed = mStackedLinks.remove(link.getInterfaceName()); return removed != null; } return false; } /** * Returns all the links stacked on top of this link. */ public Collection getStackedLinks() { Collection stacked = new ArrayList(); for (LinkProperties link : mStackedLinks.values()) { stacked.add(new LinkProperties(link)); } return Collections.unmodifiableCollection(stacked); } public void clear() { mIfaceName = null; mLinkAddresses.clear(); mDnses.clear(); mDomains = null; mRoutes.clear(); mHttpProxy = null; mStackedLinks.clear(); mMtu = 0; } /** * Implement the Parcelable interface * @hide */ public int describeContents() { return 0; } @Override public String toString() { String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " "); String linkAddresses = "LinkAddresses: ["; for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ","; linkAddresses += "] "; String dns = "DnsAddresses: ["; for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ","; dns += "] "; String domainName = "Domains: " + mDomains; String mtu = "MTU: " + mMtu; String routes = " Routes: ["; for (RouteInfo route : mRoutes) routes += route.toString() + ","; routes += "] "; String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " "); String stacked = ""; if (mStackedLinks.values().size() > 0) { stacked += " Stacked: ["; for (LinkProperties link: mStackedLinks.values()) { stacked += " [" + link.toString() + " ],"; } stacked += "] "; } return "{" + ifaceName + linkAddresses + routes + dns + domainName + mtu + proxy + stacked + "}"; } /** * Returns true if this link has an IPv4 address. * * @return {@code true} if there is an IPv4 address, {@code false} otherwise. */ public boolean hasIPv4Address() { for (LinkAddress address : mLinkAddresses) { if (address.getAddress() instanceof Inet4Address) { return true; } } return false; } /** * Returns true if this link has an IPv6 address. * * @return {@code true} if there is an IPv6 address, {@code false} otherwise. */ public boolean hasIPv6Address() { for (LinkAddress address : mLinkAddresses) { if (address.getAddress() instanceof Inet6Address) { return true; } } return false; } /** * Compares this {@code LinkProperties} interface name against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalInterfaceName(LinkProperties target) { return TextUtils.equals(getInterfaceName(), target.getInterfaceName()); } /** * Compares this {@code LinkProperties} interface addresses against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalAddresses(LinkProperties target) { Collection targetAddresses = target.getAddresses(); Collection sourceAddresses = getAddresses(); return (sourceAddresses.size() == targetAddresses.size()) ? sourceAddresses.containsAll(targetAddresses) : false; } /** * Compares this {@code LinkProperties} DNS addresses against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalDnses(LinkProperties target) { Collection targetDnses = target.getDnses(); String targetDomains = target.getDomains(); if (mDomains == null) { if (targetDomains != null) return false; } else { if (mDomains.equals(targetDomains) == false) return false; } return (mDnses.size() == targetDnses.size()) ? mDnses.containsAll(targetDnses) : false; } /** * Compares this {@code LinkProperties} Routes against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalRoutes(LinkProperties target) { Collection targetRoutes = target.getRoutes(); return (mRoutes.size() == targetRoutes.size()) ? mRoutes.containsAll(targetRoutes) : false; } /** * Compares this {@code LinkProperties} HttpProxy against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalHttpProxy(LinkProperties target) { return getHttpProxy() == null ? target.getHttpProxy() == null : getHttpProxy().equals(target.getHttpProxy()); } /** * Compares this {@code LinkProperties} stacked links against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalStackedLinks(LinkProperties target) { if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) { return false; } for (LinkProperties stacked : mStackedLinks.values()) { // Hashtable values can never be null. String iface = stacked.getInterfaceName(); if (!stacked.equals(target.mStackedLinks.get(iface))) { return false; } } return true; } /** * Compares this {@code LinkProperties} MTU against the target * * @param target LinkProperties to compare. * @return {@code true} if both are identical, {@code false} otherwise. */ public boolean isIdenticalMtu(LinkProperties target) { return getMtu() == target.getMtu(); } @Override /** * Compares this {@code LinkProperties} instance against the target * LinkProperties in {@code obj}. Two LinkPropertieses are equal if * all their fields are equal in values. * * For collection fields, such as mDnses, containsAll() is used to check * if two collections contains the same elements, independent of order. * There are two thoughts regarding containsAll() * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal. * 2. Worst case performance is O(n^2). * * This method does not check that stacked interfaces are equal, because * stacked interfaces are not so much a property of the link as a * description of connections between links. * * @param obj the object to be tested for equality. * @return {@code true} if both objects are equal, {@code false} otherwise. */ public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof LinkProperties)) return false; LinkProperties target = (LinkProperties) obj; return isIdenticalInterfaceName(target) && isIdenticalAddresses(target) && isIdenticalDnses(target) && isIdenticalRoutes(target) && isIdenticalHttpProxy(target) && isIdenticalStackedLinks(target) && isIdenticalMtu(target); } /** * Compares the addresses in this LinkProperties with another * LinkProperties, examining only addresses on the base link. * * @param target a LinkProperties with the new list of addresses * @return the differences between the addresses. */ public CompareResult compareAddresses(LinkProperties target) { /* * Duplicate the LinkAddresses into removed, we will be removing * address which are common between mLinkAddresses and target * leaving the addresses that are different. And address which * are in target but not in mLinkAddresses are placed in the * addedAddresses. */ CompareResult result = new CompareResult(); result.removed = new ArrayList(mLinkAddresses); result.added.clear(); if (target != null) { for (LinkAddress newAddress : target.getLinkAddresses()) { if (! result.removed.remove(newAddress)) { result.added.add(newAddress); } } } return result; } /** * Compares the DNS addresses in this LinkProperties with another * LinkProperties, examining only DNS addresses on the base link. * * @param target a LinkProperties with the new list of dns addresses * @return the differences between the DNS addresses. */ public CompareResult compareDnses(LinkProperties target) { /* * Duplicate the InetAddresses into removed, we will be removing * dns address which are common between mDnses and target * leaving the addresses that are different. And dns address which * are in target but not in mDnses are placed in the * addedAddresses. */ CompareResult result = new CompareResult(); result.removed = new ArrayList(mDnses); result.added.clear(); if (target != null) { for (InetAddress newAddress : target.getDnses()) { if (! result.removed.remove(newAddress)) { result.added.add(newAddress); } } } return result; } /** * Compares all routes in this LinkProperties with another LinkProperties, * examining both the the base link and all stacked links. * * @param target a LinkProperties with the new list of routes * @return the differences between the routes. */ public CompareResult compareAllRoutes(LinkProperties target) { /* * Duplicate the RouteInfos into removed, we will be removing * routes which are common between mRoutes and target * leaving the routes that are different. And route address which * are in target but not in mRoutes are placed in added. */ CompareResult result = new CompareResult(); result.removed = getAllRoutes(); result.added.clear(); if (target != null) { for (RouteInfo r : target.getAllRoutes()) { if (! result.removed.remove(r)) { result.added.add(r); } } } return result; } @Override /** * generate hashcode based on significant fields * Equal objects must produce the same hash code, while unequal objects * may have the same hash codes. */ public int hashCode() { return ((null == mIfaceName) ? 0 : mIfaceName.hashCode() + mLinkAddresses.size() * 31 + mDnses.size() * 37 + ((null == mDomains) ? 0 : mDomains.hashCode()) + mRoutes.size() * 41 + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode()) + mStackedLinks.hashCode() * 47) + mMtu * 51; } /** * Implement the Parcelable interface. */ public void writeToParcel(Parcel dest, int flags) { dest.writeString(getInterfaceName()); dest.writeInt(mLinkAddresses.size()); for(LinkAddress linkAddress : mLinkAddresses) { dest.writeParcelable(linkAddress, flags); } dest.writeInt(mDnses.size()); for(InetAddress d : mDnses) { dest.writeByteArray(d.getAddress()); } dest.writeString(mDomains); dest.writeInt(mMtu); dest.writeInt(mRoutes.size()); for(RouteInfo route : mRoutes) { dest.writeParcelable(route, flags); } if (mHttpProxy != null) { dest.writeByte((byte)1); dest.writeParcelable(mHttpProxy, flags); } else { dest.writeByte((byte)0); } ArrayList stackedLinks = new ArrayList(mStackedLinks.values()); dest.writeList(stackedLinks); } /** * Implement the Parcelable interface. */ public static final Creator CREATOR = new Creator() { public LinkProperties createFromParcel(Parcel in) { LinkProperties netProp = new LinkProperties(); String iface = in.readString(); if (iface != null) { netProp.setInterfaceName(iface); } int addressCount = in.readInt(); for (int i=0; i stackedLinks = new ArrayList(); in.readList(stackedLinks, LinkProperties.class.getClassLoader()); for (LinkProperties stackedLink: stackedLinks) { netProp.addStackedLink(stackedLink); } return netProp; } public LinkProperties[] newArray(int size) { return new LinkProperties[size]; } }; }