LinkProperties.java revision 55187f18505aad67f824094061af1b6d0a0ebb38
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net;
18
19import android.net.ProxyProperties;
20import android.os.Parcelable;
21import android.os.Parcel;
22import android.text.TextUtils;
23
24import java.net.InetAddress;
25import java.net.Inet4Address;
26
27import java.net.UnknownHostException;
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.Collections;
31import java.util.Hashtable;
32
33/**
34 * Describes the properties of a network link.
35 *
36 * A link represents a connection to a network.
37 * It may have multiple addresses and multiple gateways,
38 * multiple dns servers but only one http proxy.
39 *
40 * Because it's a single network, the dns's
41 * are interchangeable and don't need associating with
42 * particular addresses.  The gateways similarly don't
43 * need associating with particular addresses.
44 *
45 * A dual stack interface works fine in this model:
46 * each address has it's own prefix length to describe
47 * the local network.  The dns servers all return
48 * both v4 addresses and v6 addresses regardless of the
49 * address family of the server itself (rfc4213) and we
50 * don't care which is used.  The gateways will be
51 * selected based on the destination address and the
52 * source address has no relavence.
53 *
54 * Links can also be stacked on top of each other.
55 * This can be used, for example, to represent a tunnel
56 * interface that runs on top of a physical interface.
57 *
58 * @hide
59 */
60public class LinkProperties implements Parcelable {
61    // The interface described by the network link.
62    private String mIfaceName;
63    private Collection<LinkAddress> mLinkAddresses = new ArrayList<LinkAddress>();
64    private Collection<InetAddress> mDnses = new ArrayList<InetAddress>();
65    private String mDomains;
66    private Collection<RouteInfo> mRoutes = new ArrayList<RouteInfo>();
67    private ProxyProperties mHttpProxy;
68
69    // Stores the properties of links that are "stacked" above this link.
70    // Indexed by interface name to allow modification and to prevent duplicates being added.
71    private Hashtable<String, LinkProperties> mStackedLinks =
72        new Hashtable<String, LinkProperties>();
73
74    public static class CompareResult<T> {
75        public Collection<T> removed = new ArrayList<T>();
76        public Collection<T> added = new ArrayList<T>();
77
78        @Override
79        public String toString() {
80            String retVal = "removed=[";
81            for (T addr : removed) retVal += addr.toString() + ",";
82            retVal += "] added=[";
83            for (T addr : added) retVal += addr.toString() + ",";
84            retVal += "]";
85            return retVal;
86        }
87    }
88
89    public LinkProperties() {
90        clear();
91    }
92
93    // copy constructor instead of clone
94    public LinkProperties(LinkProperties source) {
95        if (source != null) {
96            mIfaceName = source.getInterfaceName();
97            for (LinkAddress l : source.getLinkAddresses()) mLinkAddresses.add(l);
98            for (InetAddress i : source.getDnses()) mDnses.add(i);
99            mDomains = source.getDomains();
100            for (RouteInfo r : source.getRoutes()) mRoutes.add(r);
101            mHttpProxy = (source.getHttpProxy() == null)  ?
102                    null : new ProxyProperties(source.getHttpProxy());
103            for (LinkProperties l: source.mStackedLinks.values()) {
104                addStackedLink(l);
105            }
106        }
107    }
108
109    public void setInterfaceName(String iface) {
110        mIfaceName = iface;
111        ArrayList<RouteInfo> newRoutes = new ArrayList<RouteInfo>(mRoutes.size());
112        for (RouteInfo route : mRoutes) {
113            newRoutes.add(routeWithInterface(route));
114        }
115        mRoutes = newRoutes;
116    }
117
118    public String getInterfaceName() {
119        return mIfaceName;
120    }
121
122    public Collection<String> getAllInterfaceNames() {
123        Collection interfaceNames = new ArrayList<String>(mStackedLinks.size() + 1);
124        if (mIfaceName != null) interfaceNames.add(new String(mIfaceName));
125        for (LinkProperties stacked: mStackedLinks.values()) {
126            interfaceNames.addAll(stacked.getAllInterfaceNames());
127        }
128        return interfaceNames;
129    }
130
131    public Collection<InetAddress> getAddresses() {
132        Collection<InetAddress> addresses = new ArrayList<InetAddress>();
133        for (LinkAddress linkAddress : mLinkAddresses) {
134            addresses.add(linkAddress.getAddress());
135        }
136        return Collections.unmodifiableCollection(addresses);
137    }
138
139    public void addLinkAddress(LinkAddress address) {
140        if (address != null) mLinkAddresses.add(address);
141    }
142
143    public Collection<LinkAddress> getLinkAddresses() {
144        return Collections.unmodifiableCollection(mLinkAddresses);
145    }
146
147    public void addDns(InetAddress dns) {
148        if (dns != null) mDnses.add(dns);
149    }
150
151    public Collection<InetAddress> getDnses() {
152        return Collections.unmodifiableCollection(mDnses);
153    }
154
155    public String getDomains() {
156        return mDomains;
157    }
158
159    public void setDomains(String domains) {
160        mDomains = domains;
161    }
162
163    private RouteInfo routeWithInterface(RouteInfo route) {
164        return new RouteInfo(
165            route.getDestination(),
166            route.getGateway(),
167            mIfaceName);
168    }
169
170    public void addRoute(RouteInfo route) {
171        if (route != null) {
172            String routeIface = route.getInterface();
173            if (routeIface != null && !routeIface.equals(mIfaceName)) {
174                throw new IllegalArgumentException(
175                   "Route added with non-matching interface: " + routeIface +
176                   " vs. " + mIfaceName);
177            }
178            mRoutes.add(routeWithInterface(route));
179        }
180    }
181
182    /**
183     * Returns all the routes on this link.
184     */
185    public Collection<RouteInfo> getRoutes() {
186        return Collections.unmodifiableCollection(mRoutes);
187    }
188
189    /**
190     * Returns all the routes on this link and all the links stacked above it.
191     */
192    public Collection<RouteInfo> getAllRoutes() {
193        Collection<RouteInfo> routes = new ArrayList();
194        routes.addAll(mRoutes);
195        for (LinkProperties stacked: mStackedLinks.values()) {
196            routes.addAll(stacked.getAllRoutes());
197        }
198        return routes;
199    }
200
201    public void setHttpProxy(ProxyProperties proxy) {
202        mHttpProxy = proxy;
203    }
204    public ProxyProperties getHttpProxy() {
205        return mHttpProxy;
206    }
207
208    /**
209     * Adds a stacked link.
210     *
211     * If there is already a stacked link with the same interfacename as link,
212     * that link is replaced with link. Otherwise, link is added to the list
213     * of stacked links. If link is null, nothing changes.
214     *
215     * @param link The link to add.
216     */
217    public void addStackedLink(LinkProperties link) {
218        if (link != null && link.getInterfaceName() != null) {
219            mStackedLinks.put(link.getInterfaceName(), link);
220        }
221    }
222
223    /**
224     * Removes a stacked link.
225     *
226     * If there a stacked link with the same interfacename as link, it is
227     * removed. Otherwise, nothing changes.
228     *
229     * @param link The link to add.
230     */
231    public void removeStackedLink(LinkProperties link) {
232        if (link != null && link.getInterfaceName() != null) {
233            mStackedLinks.remove(link.getInterfaceName());
234        }
235    }
236
237    /**
238     * Returns all the links stacked on top of this link.
239     */
240    public Collection<LinkProperties> getStackedLinks() {
241        Collection<LinkProperties> stacked = new ArrayList<LinkProperties>();
242        for (LinkProperties link : mStackedLinks.values()) {
243          stacked.add(new LinkProperties(link));
244        }
245        return Collections.unmodifiableCollection(stacked);
246    }
247
248    public void clear() {
249        mIfaceName = null;
250        mLinkAddresses.clear();
251        mDnses.clear();
252        mDomains = null;
253        mRoutes.clear();
254        mHttpProxy = null;
255        mStackedLinks.clear();
256    }
257
258    /**
259     * Implement the Parcelable interface
260     * @hide
261     */
262    public int describeContents() {
263        return 0;
264    }
265
266    @Override
267    public String toString() {
268        String ifaceName = (mIfaceName == null ? "" : "InterfaceName: " + mIfaceName + " ");
269
270        String linkAddresses = "LinkAddresses: [";
271        for (LinkAddress addr : mLinkAddresses) linkAddresses += addr.toString() + ",";
272        linkAddresses += "] ";
273
274        String dns = "DnsAddresses: [";
275        for (InetAddress addr : mDnses) dns += addr.getHostAddress() + ",";
276        dns += "] ";
277
278        String domainName = "Domains: " + mDomains;
279
280        String routes = " Routes: [";
281        for (RouteInfo route : mRoutes) routes += route.toString() + ",";
282        routes += "] ";
283        String proxy = (mHttpProxy == null ? "" : "HttpProxy: " + mHttpProxy.toString() + " ");
284
285        String stacked = "";
286        if (mStackedLinks.values().size() > 0) {
287            stacked += " Stacked: [";
288            for (LinkProperties link: mStackedLinks.values()) {
289                stacked += " [" + link.toString() + " ],";
290            }
291            stacked += "] ";
292        }
293        return ifaceName + linkAddresses + routes + dns + domainName + proxy + stacked;
294    }
295
296    /**
297     * Returns true if this link has an IPv4 address.
298     *
299     * @return {@code true} if there is an IPv4 address, {@code false} otherwise.
300     */
301    public boolean hasIPv4Address() {
302        for (LinkAddress address : mLinkAddresses) {
303          if (address.getAddress() instanceof Inet4Address) {
304            return true;
305          }
306        }
307        return false;
308    }
309
310    /**
311     * Compares this {@code LinkProperties} interface name against the target
312     *
313     * @param target LinkProperties to compare.
314     * @return {@code true} if both are identical, {@code false} otherwise.
315     */
316    public boolean isIdenticalInterfaceName(LinkProperties target) {
317        return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
318    }
319
320    /**
321     * Compares this {@code LinkProperties} interface addresses against the target
322     *
323     * @param target LinkProperties to compare.
324     * @return {@code true} if both are identical, {@code false} otherwise.
325     */
326    public boolean isIdenticalAddresses(LinkProperties target) {
327        Collection<InetAddress> targetAddresses = target.getAddresses();
328        Collection<InetAddress> sourceAddresses = getAddresses();
329        return (sourceAddresses.size() == targetAddresses.size()) ?
330                    sourceAddresses.containsAll(targetAddresses) : false;
331    }
332
333    /**
334     * Compares this {@code LinkProperties} DNS addresses against the target
335     *
336     * @param target LinkProperties to compare.
337     * @return {@code true} if both are identical, {@code false} otherwise.
338     */
339    public boolean isIdenticalDnses(LinkProperties target) {
340        Collection<InetAddress> targetDnses = target.getDnses();
341        String targetDomains = target.getDomains();
342        if (mDomains == null) {
343            if (targetDomains != null) return false;
344        } else {
345            if (mDomains.equals(targetDomains) == false) return false;
346        }
347        return (mDnses.size() == targetDnses.size()) ?
348                    mDnses.containsAll(targetDnses) : false;
349    }
350
351    /**
352     * Compares this {@code LinkProperties} Routes against the target
353     *
354     * @param target LinkProperties to compare.
355     * @return {@code true} if both are identical, {@code false} otherwise.
356     */
357    public boolean isIdenticalRoutes(LinkProperties target) {
358        Collection<RouteInfo> targetRoutes = target.getRoutes();
359        return (mRoutes.size() == targetRoutes.size()) ?
360                    mRoutes.containsAll(targetRoutes) : false;
361    }
362
363    /**
364     * Compares this {@code LinkProperties} HttpProxy against the target
365     *
366     * @param target LinkProperties to compare.
367     * @return {@code true} if both are identical, {@code false} otherwise.
368     */
369    public boolean isIdenticalHttpProxy(LinkProperties target) {
370        return getHttpProxy() == null ? target.getHttpProxy() == null :
371                    getHttpProxy().equals(target.getHttpProxy());
372    }
373
374    /**
375     * Compares this {@code LinkProperties} stacked links against the target
376     *
377     * @param target LinkProperties to compare.
378     * @return {@code true} if both are identical, {@code false} otherwise.
379     */
380    public boolean isIdenticalStackedLinks(LinkProperties target) {
381        if (!mStackedLinks.keys().equals(target.mStackedLinks.keys())) {
382            return false;
383        }
384        for (LinkProperties stacked : mStackedLinks.values()) {
385            // Hashtable values can never be null.
386            String iface = stacked.getInterfaceName();
387            if (!stacked.equals(target.mStackedLinks.get(iface))) {
388                return false;
389            }
390        }
391        return true;
392    }
393
394    @Override
395    /**
396     * Compares this {@code LinkProperties} instance against the target
397     * LinkProperties in {@code obj}. Two LinkPropertieses are equal if
398     * all their fields are equal in values.
399     *
400     * For collection fields, such as mDnses, containsAll() is used to check
401     * if two collections contains the same elements, independent of order.
402     * There are two thoughts regarding containsAll()
403     * 1. Duplicated elements. eg, (A, B, B) and (A, A, B) are equal.
404     * 2. Worst case performance is O(n^2).
405     *
406     * This method does not check that stacked interfaces are equal, because
407     * stacked interfaces are not so much a property of the link as a
408     * description of connections between links.
409     *
410     * @param obj the object to be tested for equality.
411     * @return {@code true} if both objects are equal, {@code false} otherwise.
412     */
413    public boolean equals(Object obj) {
414        if (this == obj) return true;
415
416        if (!(obj instanceof LinkProperties)) return false;
417
418        LinkProperties target = (LinkProperties) obj;
419
420        return isIdenticalInterfaceName(target) &&
421                isIdenticalAddresses(target) &&
422                isIdenticalDnses(target) &&
423                isIdenticalRoutes(target) &&
424                isIdenticalHttpProxy(target) &&
425                isIdenticalStackedLinks(target);
426    }
427
428    /**
429     * Return two lists, a list of addresses that would be removed from
430     * mLinkAddresses and a list of addresses that would be added to
431     * mLinkAddress which would then result in target and mLinkAddresses
432     * being the same list.
433     *
434     * @param target is a LinkProperties with the new list of addresses
435     * @return the removed and added lists.
436     */
437    public CompareResult<LinkAddress> compareAddresses(LinkProperties target) {
438        /*
439         * Duplicate the LinkAddresses into removed, we will be removing
440         * address which are common between mLinkAddresses and target
441         * leaving the addresses that are different. And address which
442         * are in target but not in mLinkAddresses are placed in the
443         * addedAddresses.
444         */
445        CompareResult<LinkAddress> result = new CompareResult<LinkAddress>();
446        result.removed = new ArrayList<LinkAddress>(mLinkAddresses);
447        result.added.clear();
448        if (target != null) {
449            for (LinkAddress newAddress : target.getLinkAddresses()) {
450                if (! result.removed.remove(newAddress)) {
451                    result.added.add(newAddress);
452                }
453            }
454        }
455        return result;
456    }
457
458    /**
459     * Return two lists, a list of dns addresses that would be removed from
460     * mDnses and a list of addresses that would be added to
461     * mDnses which would then result in target and mDnses
462     * being the same list.
463     *
464     * @param target is a LinkProperties with the new list of dns addresses
465     * @return the removed and added lists.
466     */
467    public CompareResult<InetAddress> compareDnses(LinkProperties target) {
468        /*
469         * Duplicate the InetAddresses into removed, we will be removing
470         * dns address which are common between mDnses and target
471         * leaving the addresses that are different. And dns address which
472         * are in target but not in mDnses are placed in the
473         * addedAddresses.
474         */
475        CompareResult<InetAddress> result = new CompareResult<InetAddress>();
476
477        result.removed = new ArrayList<InetAddress>(mDnses);
478        result.added.clear();
479        if (target != null) {
480            for (InetAddress newAddress : target.getDnses()) {
481                if (! result.removed.remove(newAddress)) {
482                    result.added.add(newAddress);
483                }
484            }
485        }
486        return result;
487    }
488
489    /**
490     * Return two lists, a list of routes that would be removed from
491     * mRoutes and a list of routes that would be added to
492     * mRoutes which would then result in target and mRoutes
493     * being the same list.
494     *
495     * @param target is a LinkProperties with the new list of routes
496     * @return the removed and added lists.
497     */
498    public CompareResult<RouteInfo> compareRoutes(LinkProperties target) {
499        /*
500         * Duplicate the RouteInfos into removed, we will be removing
501         * routes which are common between mRoutes and target
502         * leaving the routes that are different. And route address which
503         * are in target but not in mRoutes are placed in added.
504         */
505        CompareResult<RouteInfo> result = new CompareResult<RouteInfo>();
506
507        result.removed = getAllRoutes();
508        result.added.clear();
509        if (target != null) {
510            for (RouteInfo r : target.getAllRoutes()) {
511                if (! result.removed.remove(r)) {
512                    result.added.add(r);
513                }
514            }
515        }
516        return result;
517    }
518
519
520    @Override
521    /**
522     * generate hashcode based on significant fields
523     * Equal objects must produce the same hash code, while unequal objects
524     * may have the same hash codes.
525     */
526    public int hashCode() {
527        return ((null == mIfaceName) ? 0 : mIfaceName.hashCode()
528                + mLinkAddresses.size() * 31
529                + mDnses.size() * 37
530                + ((null == mDomains) ? 0 : mDomains.hashCode())
531                + mRoutes.size() * 41
532                + ((null == mHttpProxy) ? 0 : mHttpProxy.hashCode())
533                + mStackedLinks.hashCode() * 47);
534    }
535
536    /**
537     * Implement the Parcelable interface.
538     */
539    public void writeToParcel(Parcel dest, int flags) {
540        dest.writeString(getInterfaceName());
541        dest.writeInt(mLinkAddresses.size());
542        for(LinkAddress linkAddress : mLinkAddresses) {
543            dest.writeParcelable(linkAddress, flags);
544        }
545
546        dest.writeInt(mDnses.size());
547        for(InetAddress d : mDnses) {
548            dest.writeByteArray(d.getAddress());
549        }
550        dest.writeString(mDomains);
551
552        dest.writeInt(mRoutes.size());
553        for(RouteInfo route : mRoutes) {
554            dest.writeParcelable(route, flags);
555        }
556
557        if (mHttpProxy != null) {
558            dest.writeByte((byte)1);
559            dest.writeParcelable(mHttpProxy, flags);
560        } else {
561            dest.writeByte((byte)0);
562        }
563        ArrayList<LinkProperties> stackedLinks = new ArrayList(mStackedLinks.values());
564        dest.writeList(stackedLinks);
565    }
566
567    /**
568     * Implement the Parcelable interface.
569     */
570    public static final Creator<LinkProperties> CREATOR =
571        new Creator<LinkProperties>() {
572            public LinkProperties createFromParcel(Parcel in) {
573                LinkProperties netProp = new LinkProperties();
574
575                String iface = in.readString();
576                if (iface != null) {
577                    netProp.setInterfaceName(iface);
578                }
579                int addressCount = in.readInt();
580                for (int i=0; i<addressCount; i++) {
581                    netProp.addLinkAddress((LinkAddress)in.readParcelable(null));
582                }
583                addressCount = in.readInt();
584                for (int i=0; i<addressCount; i++) {
585                    try {
586                        netProp.addDns(InetAddress.getByAddress(in.createByteArray()));
587                    } catch (UnknownHostException e) { }
588                }
589                netProp.setDomains(in.readString());
590                addressCount = in.readInt();
591                for (int i=0; i<addressCount; i++) {
592                    netProp.addRoute((RouteInfo)in.readParcelable(null));
593                }
594                if (in.readByte() == 1) {
595                    netProp.setHttpProxy((ProxyProperties)in.readParcelable(null));
596                }
597                ArrayList<LinkProperties> stackedLinks = new ArrayList<LinkProperties>();
598                in.readList(stackedLinks, LinkProperties.class.getClassLoader());
599                for (LinkProperties stackedLink: stackedLinks) {
600                    netProp.addStackedLink(stackedLink);
601                }
602                return netProp;
603            }
604
605            public LinkProperties[] newArray(int size) {
606                return new LinkProperties[size];
607            }
608        };
609}
610