summaryrefslogtreecommitdiff
path: root/services/net/java/android/net/shared/InitialConfiguration.java
blob: bc2373f4aabc6ca8bdd0774cd5ab598f5c0c789e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.net.shared;

import static android.net.shared.ParcelableUtil.fromParcelableArray;
import static android.net.shared.ParcelableUtil.toParcelableArray;
import static android.text.TextUtils.join;

import android.net.InitialConfigurationParcelable;
import android.net.IpPrefix;
import android.net.IpPrefixParcelable;
import android.net.LinkAddress;
import android.net.LinkAddressParcelable;
import android.net.RouteInfo;

import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;

/** @hide */
public class InitialConfiguration {
    public final Set<LinkAddress> ipAddresses = new HashSet<>();
    public final Set<IpPrefix> directlyConnectedRoutes = new HashSet<>();
    public final Set<InetAddress> dnsServers = new HashSet<>();

    private static final int RFC6177_MIN_PREFIX_LENGTH = 48;
    private static final int RFC7421_PREFIX_LENGTH = 64;

    /**
     * Create a InitialConfiguration that is a copy of the specified configuration.
     */
    public static InitialConfiguration copy(InitialConfiguration config) {
        if (config == null) {
            return null;
        }
        InitialConfiguration configCopy = new InitialConfiguration();
        configCopy.ipAddresses.addAll(config.ipAddresses);
        configCopy.directlyConnectedRoutes.addAll(config.directlyConnectedRoutes);
        configCopy.dnsServers.addAll(config.dnsServers);
        return configCopy;
    }

    @Override
    public String toString() {
        return String.format(
                "InitialConfiguration(IPs: {%s}, prefixes: {%s}, DNS: {%s})",
                join(", ", ipAddresses), join(", ", directlyConnectedRoutes),
                join(", ", dnsServers));
    }

    /**
     * Tests whether the contents of this IpConfiguration represent a valid configuration.
     */
    public boolean isValid() {
        if (ipAddresses.isEmpty()) {
            return false;
        }

        // For every IP address, there must be at least one prefix containing that address.
        for (LinkAddress addr : ipAddresses) {
            if (!any(directlyConnectedRoutes, (p) -> p.contains(addr.getAddress()))) {
                return false;
            }
        }
        // For every dns server, there must be at least one prefix containing that address.
        for (InetAddress addr : dnsServers) {
            if (!any(directlyConnectedRoutes, (p) -> p.contains(addr))) {
                return false;
            }
        }
        // All IPv6 LinkAddresses have an RFC7421-suitable prefix length
        // (read: compliant with RFC4291#section2.5.4).
        if (any(ipAddresses, not(InitialConfiguration::isPrefixLengthCompliant))) {
            return false;
        }
        // If directlyConnectedRoutes contains an IPv6 default route
        // then ipAddresses MUST contain at least one non-ULA GUA.
        if (any(directlyConnectedRoutes, InitialConfiguration::isIPv6DefaultRoute)
                && all(ipAddresses, not(InitialConfiguration::isIPv6GUA))) {
            return false;
        }
        // The prefix length of routes in directlyConnectedRoutes be within reasonable
        // bounds for IPv6: /48-/64 just as we’d accept in RIOs.
        if (any(directlyConnectedRoutes, not(InitialConfiguration::isPrefixLengthCompliant))) {
            return false;
        }
        // There no more than one IPv4 address
        if (ipAddresses.stream().filter(LinkAddress::isIPv4).count() > 1) {
            return false;
        }

        return true;
    }

    /**
     * @return true if the given list of addressess and routes satisfies provisioning for this
     * InitialConfiguration. LinkAddresses and RouteInfo objects are not compared with equality
     * because addresses and routes seen by Netlink will contain additional fields like flags,
     * interfaces, and so on. If this InitialConfiguration has no IP address specified, the
     * provisioning check always fails.
     *
     * If the given list of routes is null, only addresses are taken into considerations.
     */
    public boolean isProvisionedBy(List<LinkAddress> addresses, List<RouteInfo> routes) {
        if (ipAddresses.isEmpty()) {
            return false;
        }

        for (LinkAddress addr : ipAddresses) {
            if (!any(addresses, (addrSeen) -> addr.isSameAddressAs(addrSeen))) {
                return false;
            }
        }

        if (routes != null) {
            for (IpPrefix prefix : directlyConnectedRoutes) {
                if (!any(routes, (routeSeen) -> isDirectlyConnectedRoute(routeSeen, prefix))) {
                    return false;
                }
            }
        }

        return true;
    }

    /**
     * Convert this configuration to a {@link InitialConfigurationParcelable}.
     */
    public InitialConfigurationParcelable toStableParcelable() {
        final InitialConfigurationParcelable p = new InitialConfigurationParcelable();
        p.ipAddresses = toParcelableArray(ipAddresses,
                LinkPropertiesParcelableUtil::toStableParcelable, LinkAddressParcelable.class);
        p.directlyConnectedRoutes = toParcelableArray(directlyConnectedRoutes,
                LinkPropertiesParcelableUtil::toStableParcelable, IpPrefixParcelable.class);
        p.dnsServers = toParcelableArray(
                dnsServers, IpConfigurationParcelableUtil::parcelAddress, String.class);
        return p;
    }

    /**
     * Create an instance of {@link InitialConfiguration} based on the contents of the specified
     * {@link InitialConfigurationParcelable}.
     */
    public static InitialConfiguration fromStableParcelable(InitialConfigurationParcelable p) {
        if (p == null) return null;
        final InitialConfiguration config = new InitialConfiguration();
        config.ipAddresses.addAll(fromParcelableArray(
                p.ipAddresses, LinkPropertiesParcelableUtil::fromStableParcelable));
        config.directlyConnectedRoutes.addAll(fromParcelableArray(
                p.directlyConnectedRoutes, LinkPropertiesParcelableUtil::fromStableParcelable));
        config.dnsServers.addAll(
                fromParcelableArray(p.dnsServers, IpConfigurationParcelableUtil::unparcelAddress));
        return config;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof InitialConfiguration)) return false;
        final InitialConfiguration other = (InitialConfiguration) obj;
        return ipAddresses.equals(other.ipAddresses)
                && directlyConnectedRoutes.equals(other.directlyConnectedRoutes)
                && dnsServers.equals(other.dnsServers);
    }

    private static boolean isDirectlyConnectedRoute(RouteInfo route, IpPrefix prefix) {
        return !route.hasGateway() && prefix.equals(route.getDestination());
    }

    private static boolean isPrefixLengthCompliant(LinkAddress addr) {
        return addr.isIPv4() || isCompliantIPv6PrefixLength(addr.getPrefixLength());
    }

    private static boolean isPrefixLengthCompliant(IpPrefix prefix) {
        return prefix.isIPv4() || isCompliantIPv6PrefixLength(prefix.getPrefixLength());
    }

    private static boolean isCompliantIPv6PrefixLength(int prefixLength) {
        return (RFC6177_MIN_PREFIX_LENGTH <= prefixLength)
                && (prefixLength <= RFC7421_PREFIX_LENGTH);
    }

    private static boolean isIPv6DefaultRoute(IpPrefix prefix) {
        return prefix.getAddress().equals(Inet6Address.ANY);
    }

    private static boolean isIPv6GUA(LinkAddress addr) {
        return addr.isIPv6() && addr.isGlobalPreferred();
    }

    // TODO: extract out into CollectionUtils.

    /**
     * Indicate whether any element of the specified iterable verifies the specified predicate.
     */
    public static <T> boolean any(Iterable<T> coll, Predicate<T> fn) {
        for (T t : coll) {
            if (fn.test(t)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Indicate whether all elements of the specified iterable verifies the specified predicate.
     */
    public static <T> boolean all(Iterable<T> coll, Predicate<T> fn) {
        return !any(coll, not(fn));
    }

    /**
     * Create a predicate that returns the opposite value of the specified predicate.
     */
    public static <T> Predicate<T> not(Predicate<T> fn) {
        return (t) -> !fn.test(t);
    }
}