1 /* 2 * Copyright (C) 2018 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.ipmemorystore; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.net.networkstack.aidl.quirks.IPv6ProvisioningLossQuirk; 22 23 import com.android.internal.annotations.VisibleForTesting; 24 25 import java.net.Inet4Address; 26 import java.net.InetAddress; 27 import java.net.UnknownHostException; 28 import java.util.ArrayList; 29 import java.util.Collections; 30 import java.util.List; 31 import java.util.Objects; 32 import java.util.StringJoiner; 33 34 /** 35 * A POD object to represent attributes of a single L2 network entry. 36 * @hide 37 */ 38 public class NetworkAttributes { 39 private static final boolean DBG = true; 40 41 // Weight cutoff for grouping. To group, a similarity score is computed with the following 42 // algorithm : if both fields are non-null and equals() then add their assigned weight, else if 43 // both are null then add a portion of their assigned weight (see NULL_MATCH_WEIGHT), 44 // otherwise add nothing. 45 // As a guideline, this should be something like 60~75% of the total weights in this class. The 46 // design states "in essence a reader should imagine that if two important columns don't match, 47 // or one important and several unimportant columns don't match then the two records are 48 // considered a different group". 49 private static final float TOTAL_WEIGHT_CUTOFF = 520.0f; 50 // The portion of the weight that is earned when scoring group-sameness by having both columns 51 // being null. This is because some networks rightfully don't have some attributes (e.g. a 52 // V6-only network won't have an assigned V4 address) and both being null should count for 53 // something, but attributes may also be null just because data is unavailable. 54 private static final float NULL_MATCH_WEIGHT = 0.25f; 55 56 // The v4 address that was assigned to this device the last time it joined this network. 57 // This typically comes from DHCP but could be something else like static configuration. 58 // This does not apply to IPv6. 59 // TODO : add a list of v6 prefixes for the v6 case. 60 @Nullable 61 public final Inet4Address assignedV4Address; 62 private static final float WEIGHT_ASSIGNEDV4ADDR = 300.0f; 63 64 // The lease expiry timestamp of v4 address allocated from DHCP server, in milliseconds. 65 @Nullable 66 public final Long assignedV4AddressExpiry; 67 // lease expiry doesn't imply any correlation between "the same lease expiry value" and "the 68 // same L3 network". 69 private static final float WEIGHT_ASSIGNEDV4ADDREXPIRY = 0.0f; 70 71 // Optionally supplied by the client to signify belonging to a notion of a group owned by 72 // the client. For example, this could be a hash of the SSID on WiFi. 73 @Nullable 74 public final String cluster; 75 private static final float WEIGHT_CLUSTER = 300.0f; 76 77 // The list of DNS server addresses. 78 @Nullable 79 public final List<InetAddress> dnsAddresses; 80 private static final float WEIGHT_DNSADDRESSES = 200.0f; 81 82 // The mtu on this network. 83 @Nullable 84 public final Integer mtu; 85 private static final float WEIGHT_MTU = 50.0f; 86 87 // IPv6 provisioning quirk info about this network, if applicable. 88 @Nullable 89 public final IPv6ProvisioningLossQuirk ipv6ProvisioningLossQuirk; 90 // quirk information doesn't imply any correlation between "the same quirk detection count and 91 // expiry" and "the same L3 network". 92 private static final float WEIGHT_V6PROVLOSSQUIRK = 0.0f; 93 94 // The sum of all weights in this class. Tests ensure that this stays equal to the total of 95 // all weights. 96 /** @hide */ 97 @VisibleForTesting 98 public static final float TOTAL_WEIGHT = WEIGHT_ASSIGNEDV4ADDR 99 + WEIGHT_ASSIGNEDV4ADDREXPIRY 100 + WEIGHT_CLUSTER 101 + WEIGHT_DNSADDRESSES 102 + WEIGHT_MTU 103 + WEIGHT_V6PROVLOSSQUIRK; 104 105 /** @hide */ 106 @VisibleForTesting NetworkAttributes( @ullable final Inet4Address assignedV4Address, @Nullable final Long assignedV4AddressExpiry, @Nullable final String cluster, @Nullable final List<InetAddress> dnsAddresses, @Nullable final Integer mtu, @Nullable final IPv6ProvisioningLossQuirk ipv6ProvisioningLossQuirk)107 public NetworkAttributes( 108 @Nullable final Inet4Address assignedV4Address, 109 @Nullable final Long assignedV4AddressExpiry, 110 @Nullable final String cluster, 111 @Nullable final List<InetAddress> dnsAddresses, 112 @Nullable final Integer mtu, 113 @Nullable final IPv6ProvisioningLossQuirk ipv6ProvisioningLossQuirk) { 114 if (mtu != null && mtu < 0) throw new IllegalArgumentException("MTU can't be negative"); 115 if (assignedV4AddressExpiry != null && assignedV4AddressExpiry <= 0) { 116 throw new IllegalArgumentException("lease expiry can't be negative or zero"); 117 } 118 this.assignedV4Address = assignedV4Address; 119 this.assignedV4AddressExpiry = assignedV4AddressExpiry; 120 this.cluster = cluster; 121 this.dnsAddresses = null == dnsAddresses ? null : 122 Collections.unmodifiableList(new ArrayList<>(dnsAddresses)); 123 this.mtu = mtu; 124 this.ipv6ProvisioningLossQuirk = ipv6ProvisioningLossQuirk; 125 } 126 127 @VisibleForTesting NetworkAttributes(@onNull final NetworkAttributesParcelable parcelable)128 public NetworkAttributes(@NonNull final NetworkAttributesParcelable parcelable) { 129 // The call to the other constructor must be the first statement of this constructor, 130 // so everything has to be inline 131 this((Inet4Address) getByAddressOrNull(parcelable.assignedV4Address), 132 parcelable.assignedV4AddressExpiry > 0 133 ? parcelable.assignedV4AddressExpiry : null, 134 parcelable.cluster, 135 blobArrayToInetAddressList(parcelable.dnsAddresses), 136 parcelable.mtu >= 0 ? parcelable.mtu : null, 137 IPv6ProvisioningLossQuirk.fromStableParcelable( 138 parcelable.ipv6ProvisioningLossQuirk)); 139 } 140 141 @Nullable getByAddressOrNull(@ullable final byte[] address)142 private static InetAddress getByAddressOrNull(@Nullable final byte[] address) { 143 if (null == address) return null; 144 try { 145 return InetAddress.getByAddress(address); 146 } catch (UnknownHostException e) { 147 return null; 148 } 149 } 150 151 @Nullable blobArrayToInetAddressList(@ullable final Blob[] blobs)152 private static List<InetAddress> blobArrayToInetAddressList(@Nullable final Blob[] blobs) { 153 if (null == blobs) return null; 154 final ArrayList<InetAddress> list = new ArrayList<>(blobs.length); 155 for (final Blob b : blobs) { 156 final InetAddress addr = getByAddressOrNull(b.data); 157 if (null != addr) list.add(addr); 158 } 159 return list; 160 } 161 162 @Nullable inetAddressListToBlobArray(@ullable final List<InetAddress> addresses)163 private static Blob[] inetAddressListToBlobArray(@Nullable final List<InetAddress> addresses) { 164 if (null == addresses) return null; 165 final ArrayList<Blob> blobs = new ArrayList<>(); 166 for (int i = 0; i < addresses.size(); ++i) { 167 final InetAddress addr = addresses.get(i); 168 if (null == addr) continue; 169 final Blob b = new Blob(); 170 b.data = addr.getAddress(); 171 blobs.add(b); 172 } 173 return blobs.toArray(new Blob[0]); 174 } 175 176 /** Converts this NetworkAttributes to a parcelable object */ 177 @NonNull toParcelable()178 public NetworkAttributesParcelable toParcelable() { 179 final NetworkAttributesParcelable parcelable = new NetworkAttributesParcelable(); 180 parcelable.assignedV4Address = 181 (null == assignedV4Address) ? null : assignedV4Address.getAddress(); 182 parcelable.assignedV4AddressExpiry = 183 (null == assignedV4AddressExpiry) ? 0 : assignedV4AddressExpiry; 184 parcelable.cluster = cluster; 185 parcelable.dnsAddresses = inetAddressListToBlobArray(dnsAddresses); 186 parcelable.mtu = (null == mtu) ? -1 : mtu; 187 parcelable.ipv6ProvisioningLossQuirk = (null == ipv6ProvisioningLossQuirk) 188 ? null : ipv6ProvisioningLossQuirk.toStableParcelable(); 189 return parcelable; 190 } 191 samenessContribution(final float weight, @Nullable final Object o1, @Nullable final Object o2)192 private float samenessContribution(final float weight, 193 @Nullable final Object o1, @Nullable final Object o2) { 194 if (null == o1) { 195 return (null == o2) ? weight * NULL_MATCH_WEIGHT : 0f; 196 } 197 return Objects.equals(o1, o2) ? weight : 0f; 198 } 199 200 /** @hide */ getNetworkGroupSamenessConfidence(@onNull final NetworkAttributes o)201 public float getNetworkGroupSamenessConfidence(@NonNull final NetworkAttributes o) { 202 // TODO: Remove the useless comparison for members which are associated with 0 weight. 203 final float samenessScore = 204 samenessContribution(WEIGHT_ASSIGNEDV4ADDR, assignedV4Address, o.assignedV4Address) 205 + samenessContribution(WEIGHT_ASSIGNEDV4ADDREXPIRY, assignedV4AddressExpiry, 206 o.assignedV4AddressExpiry) 207 + samenessContribution(WEIGHT_CLUSTER, cluster, o.cluster) 208 + samenessContribution(WEIGHT_DNSADDRESSES, dnsAddresses, o.dnsAddresses) 209 + samenessContribution(WEIGHT_MTU, mtu, o.mtu) 210 + samenessContribution(WEIGHT_V6PROVLOSSQUIRK, ipv6ProvisioningLossQuirk, 211 o.ipv6ProvisioningLossQuirk); 212 // The minimum is 0, the max is TOTAL_WEIGHT and should be represented by 1.0, and 213 // TOTAL_WEIGHT_CUTOFF should represent 0.5, but there is no requirement that 214 // TOTAL_WEIGHT_CUTOFF would be half of TOTAL_WEIGHT (indeed, it should not be). 215 // So scale scores under the cutoff between 0 and 0.5, and the scores over the cutoff 216 // between 0.5 and 1.0. 217 if (samenessScore < TOTAL_WEIGHT_CUTOFF) { 218 return samenessScore / (TOTAL_WEIGHT_CUTOFF * 2); 219 } else { 220 return (samenessScore - TOTAL_WEIGHT_CUTOFF) / (TOTAL_WEIGHT - TOTAL_WEIGHT_CUTOFF) / 2 221 + 0.5f; 222 } 223 } 224 225 /** @hide */ 226 public static class Builder { 227 @Nullable 228 private Inet4Address mAssignedAddress; 229 @Nullable 230 private Long mAssignedAddressExpiry; 231 @Nullable 232 private String mCluster; 233 @Nullable 234 private List<InetAddress> mDnsAddresses; 235 @Nullable 236 private Integer mMtu; 237 @Nullable 238 private IPv6ProvisioningLossQuirk mIpv6ProvLossQuirk; 239 240 /** 241 * Constructs a new Builder. 242 */ Builder()243 public Builder() {} 244 245 /** 246 * Constructs a Builder from the passed NetworkAttributes. 247 */ Builder(@onNull final NetworkAttributes attributes)248 public Builder(@NonNull final NetworkAttributes attributes) { 249 mAssignedAddress = attributes.assignedV4Address; 250 mAssignedAddressExpiry = attributes.assignedV4AddressExpiry; 251 mCluster = attributes.cluster; 252 mDnsAddresses = new ArrayList<>(attributes.dnsAddresses); 253 mMtu = attributes.mtu; 254 mIpv6ProvLossQuirk = attributes.ipv6ProvisioningLossQuirk; 255 } 256 257 /** 258 * Set the assigned address. 259 * @param assignedV4Address The assigned address. 260 * @return This builder. 261 */ setAssignedV4Address(@ullable final Inet4Address assignedV4Address)262 public Builder setAssignedV4Address(@Nullable final Inet4Address assignedV4Address) { 263 mAssignedAddress = assignedV4Address; 264 return this; 265 } 266 267 /** 268 * Set the lease expiry timestamp of assigned v4 address. Long.MAX_VALUE is used 269 * to represent "infinite lease". 270 * 271 * @param assignedV4AddressExpiry The lease expiry timestamp of assigned v4 address. 272 * @return This builder. 273 */ setAssignedV4AddressExpiry( @ullable final Long assignedV4AddressExpiry)274 public Builder setAssignedV4AddressExpiry( 275 @Nullable final Long assignedV4AddressExpiry) { 276 if (null != assignedV4AddressExpiry && assignedV4AddressExpiry <= 0) { 277 throw new IllegalArgumentException("lease expiry can't be negative or zero"); 278 } 279 mAssignedAddressExpiry = assignedV4AddressExpiry; 280 return this; 281 } 282 283 /** 284 * Set the cluster. 285 * @param cluster The cluster. 286 * @return This builder. 287 */ setCluster(@ullable final String cluster)288 public Builder setCluster(@Nullable final String cluster) { 289 mCluster = cluster; 290 return this; 291 } 292 293 /** 294 * Set the DNS addresses. 295 * @param dnsAddresses The DNS addresses. 296 * @return This builder. 297 */ setDnsAddresses(@ullable final List<InetAddress> dnsAddresses)298 public Builder setDnsAddresses(@Nullable final List<InetAddress> dnsAddresses) { 299 if (DBG && null != dnsAddresses) { 300 // Parceling code crashes if one of the addresses is null, therefore validate 301 // them when running in debug. 302 for (final InetAddress address : dnsAddresses) { 303 if (null == address) throw new IllegalArgumentException("Null DNS address"); 304 } 305 } 306 this.mDnsAddresses = dnsAddresses; 307 return this; 308 } 309 310 /** 311 * Set the MTU. 312 * @param mtu The MTU. 313 * @return This builder. 314 */ setMtu(@ullable final Integer mtu)315 public Builder setMtu(@Nullable final Integer mtu) { 316 if (null != mtu && mtu < 0) throw new IllegalArgumentException("MTU can't be negative"); 317 mMtu = mtu; 318 return this; 319 } 320 321 /** 322 * Set the IPv6 Provisioning Loss Quirk information. 323 * @param quirk The IPv6 Provisioning Loss Quirk. 324 * @return This builder. 325 */ setIpv6ProvLossQuirk(@ullable final IPv6ProvisioningLossQuirk quirk)326 public Builder setIpv6ProvLossQuirk(@Nullable final IPv6ProvisioningLossQuirk quirk) { 327 mIpv6ProvLossQuirk = quirk; 328 return this; 329 } 330 331 /** 332 * Return the built NetworkAttributes object. 333 * @return The built NetworkAttributes object. 334 */ build()335 public NetworkAttributes build() { 336 return new NetworkAttributes(mAssignedAddress, mAssignedAddressExpiry, 337 mCluster, mDnsAddresses, mMtu, mIpv6ProvLossQuirk); 338 } 339 } 340 341 /** @hide */ isEmpty()342 public boolean isEmpty() { 343 return (null == assignedV4Address) && (null == assignedV4AddressExpiry) 344 && (null == cluster) && (null == dnsAddresses) && (null == mtu) 345 && (null == ipv6ProvisioningLossQuirk); 346 } 347 348 @Override equals(@ullable final Object o)349 public boolean equals(@Nullable final Object o) { 350 if (!(o instanceof NetworkAttributes)) return false; 351 final NetworkAttributes other = (NetworkAttributes) o; 352 return Objects.equals(assignedV4Address, other.assignedV4Address) 353 && Objects.equals(assignedV4AddressExpiry, other.assignedV4AddressExpiry) 354 && Objects.equals(cluster, other.cluster) 355 && Objects.equals(dnsAddresses, other.dnsAddresses) 356 && Objects.equals(mtu, other.mtu) 357 && Objects.equals(ipv6ProvisioningLossQuirk, other.ipv6ProvisioningLossQuirk); 358 } 359 360 @Override hashCode()361 public int hashCode() { 362 return Objects.hash(assignedV4Address, assignedV4AddressExpiry, 363 cluster, dnsAddresses, mtu, ipv6ProvisioningLossQuirk); 364 } 365 366 /** Pretty print */ 367 @Override toString()368 public String toString() { 369 final StringJoiner resultJoiner = new StringJoiner(" ", "{", "}"); 370 final ArrayList<String> nullFields = new ArrayList<>(); 371 372 if (null != assignedV4Address) { 373 resultJoiner.add("assignedV4Addr :"); 374 resultJoiner.add(assignedV4Address.toString()); 375 } else { 376 nullFields.add("assignedV4Addr"); 377 } 378 379 if (null != assignedV4AddressExpiry) { 380 resultJoiner.add("assignedV4AddressExpiry :"); 381 resultJoiner.add(assignedV4AddressExpiry.toString()); 382 } else { 383 nullFields.add("assignedV4AddressExpiry"); 384 } 385 386 if (null != cluster) { 387 resultJoiner.add("cluster :"); 388 resultJoiner.add(cluster); 389 } else { 390 nullFields.add("cluster"); 391 } 392 393 if (null != dnsAddresses) { 394 resultJoiner.add("dnsAddr : ["); 395 for (final InetAddress addr : dnsAddresses) { 396 resultJoiner.add(addr.getHostAddress()); 397 } 398 resultJoiner.add("]"); 399 } else { 400 nullFields.add("dnsAddr"); 401 } 402 403 if (null != mtu) { 404 resultJoiner.add("mtu :"); 405 resultJoiner.add(mtu.toString()); 406 } else { 407 nullFields.add("mtu"); 408 } 409 410 if (null != ipv6ProvisioningLossQuirk) { 411 resultJoiner.add("ipv6ProvisioningLossQuirk : ["); 412 resultJoiner.add(ipv6ProvisioningLossQuirk.toString()); 413 resultJoiner.add("]"); 414 } else { 415 nullFields.add("ipv6ProvisioningLossQuirk"); 416 } 417 418 if (!nullFields.isEmpty()) { 419 resultJoiner.add("; Null fields : ["); 420 for (final String field : nullFields) { 421 resultJoiner.add(field); 422 } 423 resultJoiner.add("]"); 424 } 425 426 return resultJoiner.toString(); 427 } 428 } 429