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