• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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