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 27 import java.net.UnknownHostException; 28 import java.util.ArrayList; 29 import java.util.Collection; 30 import java.util.Collections; 31 import 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 */ 60 public 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 toString()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 LinkProperties()89 public LinkProperties() { 90 clear(); 91 } 92 93 // copy constructor instead of clone LinkProperties(LinkProperties source)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 setInterfaceName(String iface)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 getInterfaceName()118 public String getInterfaceName() { 119 return mIfaceName; 120 } 121 getAllInterfaceNames()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 getAddresses()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 addLinkAddress(LinkAddress address)139 public void addLinkAddress(LinkAddress address) { 140 if (address != null) mLinkAddresses.add(address); 141 } 142 getLinkAddresses()143 public Collection<LinkAddress> getLinkAddresses() { 144 return Collections.unmodifiableCollection(mLinkAddresses); 145 } 146 addDns(InetAddress dns)147 public void addDns(InetAddress dns) { 148 if (dns != null) mDnses.add(dns); 149 } 150 getDnses()151 public Collection<InetAddress> getDnses() { 152 return Collections.unmodifiableCollection(mDnses); 153 } 154 getDomains()155 public String getDomains() { 156 return mDomains; 157 } 158 setDomains(String domains)159 public void setDomains(String domains) { 160 mDomains = domains; 161 } 162 routeWithInterface(RouteInfo route)163 private RouteInfo routeWithInterface(RouteInfo route) { 164 return new RouteInfo( 165 route.getDestination(), 166 route.getGateway(), 167 mIfaceName); 168 } 169 addRoute(RouteInfo route)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 */ getRoutes()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 */ getAllRoutes()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 setHttpProxy(ProxyProperties proxy)201 public void setHttpProxy(ProxyProperties proxy) { 202 mHttpProxy = proxy; 203 } getHttpProxy()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 */ addStackedLink(LinkProperties link)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 */ removeStackedLink(LinkProperties link)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 */ getStackedLinks()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 clear()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 */ describeContents()262 public int describeContents() { 263 return 0; 264 } 265 266 @Override toString()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 */ hasIPv4Address()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 */ isIdenticalInterfaceName(LinkProperties target)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 */ isIdenticalAddresses(LinkProperties target)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 */ isIdenticalDnses(LinkProperties target)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 */ isIdenticalRoutes(LinkProperties target)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 */ isIdenticalHttpProxy(LinkProperties target)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 */ isIdenticalStackedLinks(LinkProperties target)380 public boolean isIdenticalStackedLinks(LinkProperties target) { 381 if (!mStackedLinks.keySet().equals(target.mStackedLinks.keySet())) { 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 */ equals(Object obj)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 */ compareAddresses(LinkProperties target)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 */ compareDnses(LinkProperties target)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 */ compareRoutes(LinkProperties target)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 */ hashCode()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 */ writeToParcel(Parcel dest, int flags)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