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.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OFF;
20 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_OPPORTUNISTIC;
21 import static android.net.ConnectivitySettingsManager.PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
22 import static android.net.shared.ParcelableUtil.fromParcelableArray;
23 import static android.net.shared.ParcelableUtil.toParcelableArray;
24 
25 import android.annotation.NonNull;
26 import android.annotation.Nullable;
27 import android.net.PrivateDnsConfigParcel;
28 import android.text.TextUtils;
29 
30 import java.net.InetAddress;
31 import java.util.Arrays;
32 
33 /** @hide */
34 public class PrivateDnsConfig {
35     // These fields store the private DNS configuration from setting.
36     public final int mode;
37     @NonNull
38     public final String hostname;
39 
40     // Stores the DoT server IP addresses resolved from A/AAAA lookups.
41     @NonNull
42     public final InetAddress[] ips;
43 
44     // These fields store the DoH information discovered from SVCB lookups.
45     @NonNull
46     public final String dohName;
47     @NonNull
48     public final InetAddress[] dohIps;
49     @NonNull
50     public final String dohPath;
51     public final int dohPort;
52 
53     /**
54      * A constructor for off mode private DNS configuration.
55      * TODO(b/261404136): Consider simplifying the constructors. One possible way is to
56      * use constants to represent private DNS modes:
57      *   public static PrivateDnsConfig OFF = new PrivateDnsConfig(false);
58      *   public static PrivateDnsConfig OPPORTUNISTIC = new PrivateDnsConfig(true);
59      *   public static PrivateDnsConfig STRICT = new PrivateDnsConfig(String hostname);
60      */
PrivateDnsConfig()61     public PrivateDnsConfig() {
62         this(false);
63     }
64 
65     /**
66      * A constructor for off/opportunistic mode private DNS configuration depending on `useTls`.
67      */
PrivateDnsConfig(boolean useTls)68     public PrivateDnsConfig(boolean useTls) {
69         this(useTls ? PRIVATE_DNS_MODE_OPPORTUNISTIC : PRIVATE_DNS_MODE_OFF, null /* hostname */,
70                 null /* ips */, null /* dohName */, null /* dohIps */, null /* dohPath */,
71                 -1 /* dohPort */);
72     }
73 
74     /**
75      * A constructor for off/strict mode private DNS configuration depending on `hostname`.
76      * If `hostname` is empty or null, this constructor creates a PrivateDnsConfig for off mode;
77      * otherwise, it creates a PrivateDnsConfig for strict mode.
78      */
PrivateDnsConfig(@ullable String hostname, @Nullable InetAddress[] ips)79     public PrivateDnsConfig(@Nullable String hostname, @Nullable InetAddress[] ips) {
80         this(TextUtils.isEmpty(hostname) ? PRIVATE_DNS_MODE_OFF :
81                 PRIVATE_DNS_MODE_PROVIDER_HOSTNAME, hostname, ips, null /* dohName */,
82                 null /* dohIps */, null /* dohPath */, -1 /* dohPort */);
83     }
84 
85     /**
86      * A constructor for all kinds of private DNS configuration with given DoH information.
87      * It treats both null values and empty strings as equivalent. Similarly, treats null values
88      * and empty arrays as equivalent.
89      */
PrivateDnsConfig(int mode, @Nullable String hostname, @Nullable InetAddress[] ips, @Nullable String dohName, @Nullable InetAddress[] dohIps, @Nullable String dohPath, int dohPort)90     public PrivateDnsConfig(int mode, @Nullable String hostname, @Nullable InetAddress[] ips,
91             @Nullable String dohName, @Nullable InetAddress[] dohIps, @Nullable String dohPath,
92             int dohPort) {
93         this.mode = mode;
94         this.hostname = (hostname != null) ? hostname : "";
95         this.ips = (ips != null) ? ips.clone() : new InetAddress[0];
96         this.dohName = (dohName != null) ? dohName : "";
97         this.dohIps = (dohIps != null) ? dohIps.clone() : new InetAddress[0];
98         this.dohPath = (dohPath != null) ? dohPath : "";
99         this.dohPort = dohPort;
100     }
101 
PrivateDnsConfig(PrivateDnsConfig cfg)102     public PrivateDnsConfig(PrivateDnsConfig cfg) {
103         mode = cfg.mode;
104         hostname = cfg.hostname;
105         ips = cfg.ips;
106         dohName = cfg.dohName;
107         dohIps = cfg.dohIps;
108         dohPath = cfg.dohPath;
109         dohPort = cfg.dohPort;
110     }
111 
112     /**
113      * Indicates whether this is a strict mode private DNS configuration.
114      */
inStrictMode()115     public boolean inStrictMode() {
116         return mode == PRIVATE_DNS_MODE_PROVIDER_HOSTNAME;
117     }
118 
119     /**
120      * Indicates whether this is an opportunistic mode private DNS configuration.
121      */
inOpportunisticMode()122     public boolean inOpportunisticMode() {
123         return mode == PRIVATE_DNS_MODE_OPPORTUNISTIC;
124     }
125 
126     @Override
toString()127     public String toString() {
128         return PrivateDnsConfig.class.getSimpleName()
129                 + "{" + modeAsString(mode) + ":" + hostname + "/" + Arrays.toString(ips)
130                 + ", dohName=" + dohName
131                 + ", dohIps=" + Arrays.toString(dohIps)
132                 + ", dohPath=" + dohPath
133                 + ", dohPort=" + dohPort
134                 + "}";
135     }
136 
137     @NonNull
modeAsString(int mode)138     private static String modeAsString(int mode) {
139         switch (mode) {
140             case PRIVATE_DNS_MODE_OFF: return "off";
141             case PRIVATE_DNS_MODE_OPPORTUNISTIC: return "opportunistic";
142             case PRIVATE_DNS_MODE_PROVIDER_HOSTNAME: return "strict";
143             default: return "unknown";
144         }
145     }
146 
147     /**
148      * Create a stable AIDL-compatible parcel from the current instance.
149      */
toParcel()150     public PrivateDnsConfigParcel toParcel() {
151         final PrivateDnsConfigParcel parcel = new PrivateDnsConfigParcel();
152         parcel.hostname = hostname;
153         parcel.ips = toParcelableArray(
154                 Arrays.asList(ips), IpConfigurationParcelableUtil::parcelAddress, String.class);
155         parcel.privateDnsMode = mode;
156         parcel.dohName = dohName;
157         parcel.dohIps = toParcelableArray(
158                 Arrays.asList(dohIps), IpConfigurationParcelableUtil::parcelAddress, String.class);
159         parcel.dohPath = dohPath;
160         parcel.dohPort = dohPort;
161         return parcel;
162     }
163 
164     /**
165      * Build a configuration from a stable AIDL-compatible parcel.
166      */
fromParcel(PrivateDnsConfigParcel parcel)167     public static PrivateDnsConfig fromParcel(PrivateDnsConfigParcel parcel) {
168         InetAddress[] ips = new InetAddress[parcel.ips.length];
169         ips = fromParcelableArray(parcel.ips, IpConfigurationParcelableUtil::unparcelAddress)
170                 .toArray(ips);
171 
172         // For compatibility. If the sender (Tethering module) is using an old version (< 19) of
173         // NetworkStack AIDL that `privateDnsMode` field is not present, `privateDnsMode` will be
174         // assigned from the default value -1. Let `privateDnsMode` assigned based on the hostname.
175         // In this case, there is a harmless bug that the receiver (NetworkStack module) can't
176         // convert the parcel to a PrivateDnsConfig that indicates opportunistic mode.
177         // The bug is harmless because 1) the bug exists for years without any problems and
178         // 2) NetworkMonitor cares PrivateDnsConfig that indicates strict/off mode only.
179         // If the sender is using new version (>=19) while the receiver is using an old version,
180         // the above mentioned harmless bug will persist. Except for that harmless bug, there
181         // should be no other issues. New version's toParcel() doesn't change how the pre-existing
182         // fields `hostname` and `ips` are assigned.
183         if (parcel.privateDnsMode == -1) {
184             return new PrivateDnsConfig(parcel.hostname, ips);
185         }
186 
187         InetAddress[] dohIps = new InetAddress[parcel.dohIps.length];
188         dohIps = fromParcelableArray(parcel.dohIps,
189                 IpConfigurationParcelableUtil::unparcelAddress).toArray(dohIps);
190         return new PrivateDnsConfig(parcel.privateDnsMode, parcel.hostname, ips, parcel.dohName,
191                 dohIps, parcel.dohPath, parcel.dohPort);
192     }
193 }
194