1 /* 2 * Copyright (C) 2019 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.shared; 18 19 import static android.net.shared.ParcelableUtil.fromParcelableArray; 20 import static android.net.shared.ParcelableUtil.toParcelableArray; 21 import static android.text.TextUtils.join; 22 23 import android.net.InetAddresses; 24 import android.net.InitialConfigurationParcelable; 25 import android.net.IpPrefix; 26 import android.net.LinkAddress; 27 import android.net.RouteInfo; 28 29 import java.net.Inet4Address; 30 import java.net.InetAddress; 31 import java.util.Arrays; 32 import java.util.HashSet; 33 import java.util.List; 34 import java.util.Set; 35 import java.util.function.Predicate; 36 37 /** @hide */ 38 public class InitialConfiguration { 39 public final Set<LinkAddress> ipAddresses = new HashSet<>(); 40 public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>(); 41 public final Set<InetAddress> dnsServers = new HashSet<>(); 42 43 private static final int RFC6177_MIN_PREFIX_LENGTH = 48; 44 private static final int RFC7421_PREFIX_LENGTH = 64; 45 46 public static final InetAddress INET6_ANY = InetAddresses.parseNumericAddress("::"); 47 48 /** 49 * Create a InitialConfiguration that is a copy of the specified configuration. 50 */ copy(InitialConfiguration config)51 public static InitialConfiguration copy(InitialConfiguration config) { 52 if (config == null) { 53 return null; 54 } 55 InitialConfiguration configCopy = new InitialConfiguration(); 56 configCopy.ipAddresses.addAll(config.ipAddresses); 57 configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes); 58 configCopy.dnsServers.addAll(config.dnsServers); 59 return configCopy; 60 } 61 62 @Override toString()63 public String toString() { 64 return String.format( 65 "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})", 66 join(", ", ipAddresses), join(", ", directlyConnectedRoutes), 67 join(", ", dnsServers)); 68 } 69 70 /** 71 * Tests whether the contents of this IpConfiguration represent a valid configuration. 72 */ isValid()73 public boolean isValid() { 74 if (ipAddresses.isEmpty()) { 75 return false; 76 } 77 78 // For every IP address, there must be at least one prefix containing that address. 79 for (LinkAddress addr : ipAddresses) { 80 if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) { 81 return false; 82 } 83 } 84 // For every dns server, there must be at least one prefix containing that address. 85 for (InetAddress addr : dnsServers) { 86 if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) { 87 return false; 88 } 89 } 90 // All IPv6 LinkAddresses have an RFC7421-suitable prefix length 91 // (read: compliant with RFC4291#section2.5.4). 92 if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) { 93 return false; 94 } 95 // If directlyConnectedRoutes contains an IPv6 default route 96 // then ipAddresses MUST contain at least one non-ULA GUA. 97 if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute) 98 && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) { 99 return false; 100 } 101 // The prefix length of routes in directlyConnectedRoutes be within reasonable 102 // bounds for IPv6: /48-/64 just as we’d accept in RIOs. 103 if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) { 104 return false; 105 } 106 // There no more than one IPv4 address 107 if (ipAddresses.stream().filter(InitialConfiguration::isIPv4).count() > 1) { 108 return false; 109 } 110 111 return true; 112 } 113 114 /** 115 * @return true if the given list of addressess and routes satisfies provisioning for this 116 * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality 117 * because addresses and routes seen by Netlink will contain additional fields like flags, 118 * interfaces, and so on. If this InitialConfiguration has no IP address specified, the 119 * provisioning check always fails. 120 * 121 * If the given list of routes is null, only addresses are taken into considerations. 122 */ isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes)123 public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) { 124 if (ipAddresses.isEmpty()) { 125 return false; 126 } 127 128 for (LinkAddress addr : ipAddresses) { 129 if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) { 130 return false; 131 } 132 } 133 134 if (routes != null) { 135 for (IpPrefix prefix : directlyConnectedRoutes) { 136 if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) { 137 return false; 138 } 139 } 140 } 141 142 return true; 143 } 144 145 /** 146 * Convert this configuration to a {@link InitialConfigurationParcelable}. 147 */ toStableParcelable()148 public InitialConfigurationParcelable toStableParcelable() { 149 final InitialConfigurationParcelable p = new InitialConfigurationParcelable(); 150 p.ipAddresses = ipAddresses.toArray(new LinkAddress[0]); 151 p.directlyConnectedRoutes = directlyConnectedRoutes.toArray(new IpPrefix[0]); 152 p.dnsServers = toParcelableArray( 153 dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class); 154 return p; 155 } 156 157 /** 158 * Create an instance of {@link InitialConfiguration} based on the contents of the specified 159 * {@link InitialConfigurationParcelable}. 160 */ fromStableParcelable(InitialConfigurationParcelable p)161 public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) { 162 if (p == null) return null; 163 final InitialConfiguration config = new InitialConfiguration(); 164 config.ipAddresses.addAll(Arrays.asList(p.ipAddresses)); 165 config.directlyConnectedRoutes.addAll(Arrays.asList(p.directlyConnectedRoutes)); 166 config.dnsServers.addAll( 167 fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress)); 168 return config; 169 } 170 171 @Override equals(Object obj)172 public boolean equals(Object obj) { 173 if (!(obj instanceof InitialConfiguration)) return false; 174 final InitialConfiguration other = (InitialConfiguration) obj; 175 return ipAddresses.equals(other.ipAddresses) 176 && directlyConnectedRoutes.equals(other.directlyConnectedRoutes) 177 && dnsServers.equals(other.dnsServers); 178 } 179 isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix)180 private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) { 181 return !route.hasGateway() && prefix.equals(route.getDestination()); 182 } 183 isPrefixLengthCompliant(LinkAddress addr)184 private static boolean isPrefixLengthCompliant(LinkAddress addr) { 185 return isIPv4(addr) || isCompliantIPv6PrefixLength(addr.getPrefixLength()); 186 } 187 isPrefixLengthCompliant(IpPrefix prefix)188 private static boolean isPrefixLengthCompliant(IpPrefix prefix) { 189 return isIPv4(prefix) || isCompliantIPv6PrefixLength(prefix.getPrefixLength()); 190 } 191 isCompliantIPv6PrefixLength(int prefixLength)192 private static boolean isCompliantIPv6PrefixLength(int prefixLength) { 193 return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength) 194 && (prefixLength <= RFC7421_PREFIX_LENGTH); 195 } 196 isIPv4(IpPrefix prefix)197 private static boolean isIPv4(IpPrefix prefix) { 198 return prefix.getAddress() instanceof Inet4Address; 199 } 200 isIPv4(LinkAddress addr)201 private static boolean isIPv4(LinkAddress addr) { 202 return addr.getAddress() instanceof Inet4Address; 203 } 204 isIPv6DefaultRoute(IpPrefix prefix)205 private static boolean isIPv6DefaultRoute(IpPrefix prefix) { 206 return prefix.getAddress().equals(INET6_ANY); 207 } 208 isIPv6GUA(LinkAddress addr)209 private static boolean isIPv6GUA(LinkAddress addr) { 210 return addr.isIpv6() && addr.isGlobalPreferred(); 211 } 212 213 // TODO: extract out into CollectionUtils. 214 215 /** 216 * Indicate whether any element of the specified iterable verifies the specified predicate. 217 */ any(Iterable<T> coll, Predicate<T> fn)218 public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) { 219 for (T t : coll) { 220 if (fn.test(t)) { 221 return true; 222 } 223 } 224 return false; 225 } 226 227 /** 228 * Indicate whether all elements of the specified iterable verifies the specified predicate. 229 */ all(Iterable<T> coll, Predicate<T> fn)230 public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) { 231 return !any(coll, not(fn)); 232 } 233 234 /** 235 * Create a predicate that returns the opposite value of the specified predicate. 236 */ not(Predicate<T> fn)237 public static <T> Predicate<T> not(Predicate<T> fn) { 238 return (t) -> !fn.test(t); 239 } 240 } 241