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