1 /*
2  * Copyright (C) 2014 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 com.android.server.net;
18 
19 import android.net.IpConfiguration;
20 import android.net.IpConfiguration.IpAssignment;
21 import android.net.IpConfiguration.ProxySettings;
22 import android.net.LinkAddress;
23 import android.net.NetworkUtils;
24 import android.net.ProxyInfo;
25 import android.net.RouteInfo;
26 import android.net.StaticIpConfiguration;
27 import android.net.Uri;
28 import android.util.ArrayMap;
29 import android.util.Log;
30 import android.util.SparseArray;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.io.BufferedInputStream;
35 import java.io.DataInputStream;
36 import java.io.DataOutputStream;
37 import java.io.EOFException;
38 import java.io.FileInputStream;
39 import java.io.FileNotFoundException;
40 import java.io.IOException;
41 import java.io.InputStream;
42 import java.net.Inet4Address;
43 import java.net.InetAddress;
44 
45 public class IpConfigStore {
46     private static final String TAG = "IpConfigStore";
47     private static final boolean DBG = false;
48 
49     protected final DelayedDiskWrite mWriter;
50 
51     /* IP and proxy configuration keys */
52     protected static final String ID_KEY = "id";
53     protected static final String IP_ASSIGNMENT_KEY = "ipAssignment";
54     protected static final String LINK_ADDRESS_KEY = "linkAddress";
55     protected static final String GATEWAY_KEY = "gateway";
56     protected static final String DNS_KEY = "dns";
57     protected static final String PROXY_SETTINGS_KEY = "proxySettings";
58     protected static final String PROXY_HOST_KEY = "proxyHost";
59     protected static final String PROXY_PORT_KEY = "proxyPort";
60     protected static final String PROXY_PAC_FILE = "proxyPac";
61     protected static final String EXCLUSION_LIST_KEY = "exclusionList";
62     protected static final String EOS = "eos";
63 
64     protected static final int IPCONFIG_FILE_VERSION = 3;
65 
IpConfigStore(DelayedDiskWrite writer)66     public IpConfigStore(DelayedDiskWrite writer) {
67         mWriter = writer;
68     }
69 
IpConfigStore()70     public IpConfigStore() {
71         this(new DelayedDiskWrite());
72     }
73 
writeConfig(DataOutputStream out, String configKey, IpConfiguration config)74     private static boolean writeConfig(DataOutputStream out, String configKey,
75             IpConfiguration config) throws IOException {
76         return writeConfig(out, configKey, config, IPCONFIG_FILE_VERSION);
77     }
78 
79     @VisibleForTesting
writeConfig(DataOutputStream out, String configKey, IpConfiguration config, int version)80     public static boolean writeConfig(DataOutputStream out, String configKey,
81                                 IpConfiguration config, int version) throws IOException {
82         boolean written = false;
83 
84         try {
85             switch (config.ipAssignment) {
86                 case STATIC:
87                     out.writeUTF(IP_ASSIGNMENT_KEY);
88                     out.writeUTF(config.ipAssignment.toString());
89                     StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration;
90                     if (staticIpConfiguration != null) {
91                         if (staticIpConfiguration.ipAddress != null) {
92                             LinkAddress ipAddress = staticIpConfiguration.ipAddress;
93                             out.writeUTF(LINK_ADDRESS_KEY);
94                             out.writeUTF(ipAddress.getAddress().getHostAddress());
95                             out.writeInt(ipAddress.getPrefixLength());
96                         }
97                         if (staticIpConfiguration.gateway != null) {
98                             out.writeUTF(GATEWAY_KEY);
99                             out.writeInt(0);  // Default route.
100                             out.writeInt(1);  // Have a gateway.
101                             out.writeUTF(staticIpConfiguration.gateway.getHostAddress());
102                         }
103                         for (InetAddress inetAddr : staticIpConfiguration.dnsServers) {
104                             out.writeUTF(DNS_KEY);
105                             out.writeUTF(inetAddr.getHostAddress());
106                         }
107                     }
108                     written = true;
109                     break;
110                 case DHCP:
111                     out.writeUTF(IP_ASSIGNMENT_KEY);
112                     out.writeUTF(config.ipAssignment.toString());
113                     written = true;
114                     break;
115                 case UNASSIGNED:
116                 /* Ignore */
117                     break;
118                 default:
119                     loge("Ignore invalid ip assignment while writing");
120                     break;
121             }
122 
123             switch (config.proxySettings) {
124                 case STATIC:
125                     ProxyInfo proxyProperties = config.httpProxy;
126                     String exclusionList = proxyProperties.getExclusionListAsString();
127                     out.writeUTF(PROXY_SETTINGS_KEY);
128                     out.writeUTF(config.proxySettings.toString());
129                     out.writeUTF(PROXY_HOST_KEY);
130                     out.writeUTF(proxyProperties.getHost());
131                     out.writeUTF(PROXY_PORT_KEY);
132                     out.writeInt(proxyProperties.getPort());
133                     if (exclusionList != null) {
134                         out.writeUTF(EXCLUSION_LIST_KEY);
135                         out.writeUTF(exclusionList);
136                     }
137                     written = true;
138                     break;
139                 case PAC:
140                     ProxyInfo proxyPacProperties = config.httpProxy;
141                     out.writeUTF(PROXY_SETTINGS_KEY);
142                     out.writeUTF(config.proxySettings.toString());
143                     out.writeUTF(PROXY_PAC_FILE);
144                     out.writeUTF(proxyPacProperties.getPacFileUrl().toString());
145                     written = true;
146                     break;
147                 case NONE:
148                     out.writeUTF(PROXY_SETTINGS_KEY);
149                     out.writeUTF(config.proxySettings.toString());
150                     written = true;
151                     break;
152                 case UNASSIGNED:
153                     /* Ignore */
154                         break;
155                     default:
156                         loge("Ignore invalid proxy settings while writing");
157                         break;
158             }
159 
160             if (written) {
161                 out.writeUTF(ID_KEY);
162                 if (version < 3) {
163                     out.writeInt(Integer.valueOf(configKey));
164                 } else {
165                     out.writeUTF(configKey);
166                 }
167             }
168         } catch (NullPointerException e) {
169             loge("Failure in writing " + config + e);
170         }
171         out.writeUTF(EOS);
172 
173         return written;
174     }
175 
176     /**
177      * @Deprecated use {@link #writeIpConfigurations(String, ArrayMap)} instead.
178      * New method uses string as network identifier which could be interface name or MAC address or
179      * other token.
180      */
181     @Deprecated
writeIpAndProxyConfigurationsToFile(String filePath, final SparseArray<IpConfiguration> networks)182     public void writeIpAndProxyConfigurationsToFile(String filePath,
183                                               final SparseArray<IpConfiguration> networks) {
184         mWriter.write(filePath, out -> {
185             out.writeInt(IPCONFIG_FILE_VERSION);
186             for(int i = 0; i < networks.size(); i++) {
187                 writeConfig(out, String.valueOf(networks.keyAt(i)), networks.valueAt(i));
188             }
189         });
190     }
191 
writeIpConfigurations(String filePath, ArrayMap<String, IpConfiguration> networks)192     public void writeIpConfigurations(String filePath,
193                                       ArrayMap<String, IpConfiguration> networks) {
194         mWriter.write(filePath, out -> {
195             out.writeInt(IPCONFIG_FILE_VERSION);
196             for(int i = 0; i < networks.size(); i++) {
197                 writeConfig(out, networks.keyAt(i), networks.valueAt(i));
198             }
199         });
200     }
201 
readIpConfigurations(String filePath)202     public static ArrayMap<String, IpConfiguration> readIpConfigurations(String filePath) {
203         BufferedInputStream bufferedInputStream;
204         try {
205             bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
206         } catch (FileNotFoundException e) {
207             // Return an empty array here because callers expect an empty array when the file is
208             // not present.
209             loge("Error opening configuration file: " + e);
210             return new ArrayMap<>(0);
211         }
212         return readIpConfigurations(bufferedInputStream);
213     }
214 
215     /** @Deprecated use {@link #readIpConfigurations(String)} */
216     @Deprecated
readIpAndProxyConfigurations(String filePath)217     public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(String filePath) {
218         BufferedInputStream bufferedInputStream;
219         try {
220             bufferedInputStream = new BufferedInputStream(new FileInputStream(filePath));
221         } catch (FileNotFoundException e) {
222             // Return an empty array here because callers expect an empty array when the file is
223             // not present.
224             loge("Error opening configuration file: " + e);
225             return new SparseArray<>();
226         }
227         return readIpAndProxyConfigurations(bufferedInputStream);
228     }
229 
230     /** @Deprecated use {@link #readIpConfigurations(InputStream)} */
231     @Deprecated
readIpAndProxyConfigurations( InputStream inputStream)232     public static SparseArray<IpConfiguration> readIpAndProxyConfigurations(
233             InputStream inputStream) {
234         ArrayMap<String, IpConfiguration> networks = readIpConfigurations(inputStream);
235         if (networks == null) {
236             return null;
237         }
238 
239         SparseArray<IpConfiguration> networksById = new SparseArray<>();
240         for (int i = 0; i < networks.size(); i++) {
241             int id = Integer.valueOf(networks.keyAt(i));
242             networksById.put(id, networks.valueAt(i));
243         }
244 
245         return networksById;
246     }
247 
248     /** Returns a map of network identity token and {@link IpConfiguration}. */
readIpConfigurations( InputStream inputStream)249     public static ArrayMap<String, IpConfiguration> readIpConfigurations(
250             InputStream inputStream) {
251         ArrayMap<String, IpConfiguration> networks = new ArrayMap<>();
252         DataInputStream in = null;
253         try {
254             in = new DataInputStream(inputStream);
255 
256             int version = in.readInt();
257             if (version != 3 && version != 2 && version != 1) {
258                 loge("Bad version on IP configuration file, ignore read");
259                 return null;
260             }
261 
262             while (true) {
263                 String uniqueToken = null;
264                 // Default is DHCP with no proxy
265                 IpAssignment ipAssignment = IpAssignment.DHCP;
266                 ProxySettings proxySettings = ProxySettings.NONE;
267                 StaticIpConfiguration staticIpConfiguration = new StaticIpConfiguration();
268                 String proxyHost = null;
269                 String pacFileUrl = null;
270                 int proxyPort = -1;
271                 String exclusionList = null;
272                 String key;
273 
274                 do {
275                     key = in.readUTF();
276                     try {
277                         if (key.equals(ID_KEY)) {
278                             if (version < 3) {
279                                 int id = in.readInt();
280                                 uniqueToken = String.valueOf(id);
281                             } else {
282                                 uniqueToken = in.readUTF();
283                             }
284                         } else if (key.equals(IP_ASSIGNMENT_KEY)) {
285                             ipAssignment = IpAssignment.valueOf(in.readUTF());
286                         } else if (key.equals(LINK_ADDRESS_KEY)) {
287                             LinkAddress linkAddr = new LinkAddress(
288                                     NetworkUtils.numericToInetAddress(in.readUTF()), in.readInt());
289                             if (linkAddr.getAddress() instanceof Inet4Address &&
290                                     staticIpConfiguration.ipAddress == null) {
291                                 staticIpConfiguration.ipAddress = linkAddr;
292                             } else {
293                                 loge("Non-IPv4 or duplicate address: " + linkAddr);
294                             }
295                         } else if (key.equals(GATEWAY_KEY)) {
296                             LinkAddress dest = null;
297                             InetAddress gateway = null;
298                             if (version == 1) {
299                                 // only supported default gateways - leave the dest/prefix empty
300                                 gateway = NetworkUtils.numericToInetAddress(in.readUTF());
301                                 if (staticIpConfiguration.gateway == null) {
302                                     staticIpConfiguration.gateway = gateway;
303                                 } else {
304                                     loge("Duplicate gateway: " + gateway.getHostAddress());
305                                 }
306                             } else {
307                                 if (in.readInt() == 1) {
308                                     dest = new LinkAddress(
309                                             NetworkUtils.numericToInetAddress(in.readUTF()),
310                                             in.readInt());
311                                 }
312                                 if (in.readInt() == 1) {
313                                     gateway = NetworkUtils.numericToInetAddress(in.readUTF());
314                                 }
315                                 RouteInfo route = new RouteInfo(dest, gateway);
316                                 if (route.isIPv4Default() &&
317                                         staticIpConfiguration.gateway == null) {
318                                     staticIpConfiguration.gateway = gateway;
319                                 } else {
320                                     loge("Non-IPv4 default or duplicate route: " + route);
321                                 }
322                             }
323                         } else if (key.equals(DNS_KEY)) {
324                             staticIpConfiguration.dnsServers.add(
325                                     NetworkUtils.numericToInetAddress(in.readUTF()));
326                         } else if (key.equals(PROXY_SETTINGS_KEY)) {
327                             proxySettings = ProxySettings.valueOf(in.readUTF());
328                         } else if (key.equals(PROXY_HOST_KEY)) {
329                             proxyHost = in.readUTF();
330                         } else if (key.equals(PROXY_PORT_KEY)) {
331                             proxyPort = in.readInt();
332                         } else if (key.equals(PROXY_PAC_FILE)) {
333                             pacFileUrl = in.readUTF();
334                         } else if (key.equals(EXCLUSION_LIST_KEY)) {
335                             exclusionList = in.readUTF();
336                         } else if (key.equals(EOS)) {
337                             break;
338                         } else {
339                             loge("Ignore unknown key " + key + "while reading");
340                         }
341                     } catch (IllegalArgumentException e) {
342                         loge("Ignore invalid address while reading" + e);
343                     }
344                 } while (true);
345 
346                 if (uniqueToken != null) {
347                     IpConfiguration config = new IpConfiguration();
348                     networks.put(uniqueToken, config);
349 
350                     switch (ipAssignment) {
351                         case STATIC:
352                             config.staticIpConfiguration = staticIpConfiguration;
353                             config.ipAssignment = ipAssignment;
354                             break;
355                         case DHCP:
356                             config.ipAssignment = ipAssignment;
357                             break;
358                         case UNASSIGNED:
359                             loge("BUG: Found UNASSIGNED IP on file, use DHCP");
360                             config.ipAssignment = IpAssignment.DHCP;
361                             break;
362                         default:
363                             loge("Ignore invalid ip assignment while reading.");
364                             config.ipAssignment = IpAssignment.UNASSIGNED;
365                             break;
366                     }
367 
368                     switch (proxySettings) {
369                         case STATIC:
370                             ProxyInfo proxyInfo =
371                                     new ProxyInfo(proxyHost, proxyPort, exclusionList);
372                             config.proxySettings = proxySettings;
373                             config.httpProxy = proxyInfo;
374                             break;
375                         case PAC:
376                             ProxyInfo proxyPacProperties = new ProxyInfo(Uri.parse(pacFileUrl));
377                             config.proxySettings = proxySettings;
378                             config.httpProxy = proxyPacProperties;
379                             break;
380                         case NONE:
381                             config.proxySettings = proxySettings;
382                             break;
383                         case UNASSIGNED:
384                             loge("BUG: Found UNASSIGNED proxy on file, use NONE");
385                             config.proxySettings = ProxySettings.NONE;
386                             break;
387                         default:
388                             loge("Ignore invalid proxy settings while reading");
389                             config.proxySettings = ProxySettings.UNASSIGNED;
390                             break;
391                     }
392                 } else {
393                     if (DBG) log("Missing id while parsing configuration");
394                 }
395             }
396         } catch (EOFException ignore) {
397         } catch (IOException e) {
398             loge("Error parsing configuration: " + e);
399         } finally {
400             if (in != null) {
401                 try {
402                     in.close();
403                 } catch (Exception e) {}
404             }
405         }
406 
407         return networks;
408     }
409 
loge(String s)410     protected static void loge(String s) {
411         Log.e(TAG, s);
412     }
413 
log(String s)414     protected static void log(String s) {
415         Log.d(TAG, s);
416     }
417 }
418