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
17package com.android.server.net;
18
19import android.net.IpConfiguration;
20import android.net.IpConfiguration.IpAssignment;
21import android.net.IpConfiguration.ProxySettings;
22import android.net.LinkAddress;
23import android.net.NetworkUtils;
24import android.net.ProxyInfo;
25import android.net.RouteInfo;
26import android.net.StaticIpConfiguration;
27import android.util.ArrayMap;
28import android.util.Log;
29import android.util.SparseArray;
30
31import com.android.internal.annotations.VisibleForTesting;
32
33import java.io.BufferedInputStream;
34import java.io.DataInputStream;
35import java.io.DataOutputStream;
36import java.io.EOFException;
37import java.io.FileInputStream;
38import java.io.FileNotFoundException;
39import java.io.IOException;
40import java.io.InputStream;
41import java.net.Inet4Address;
42import java.net.InetAddress;
43
44public 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
65    public IpConfigStore(DelayedDiskWrite writer) {
66        mWriter = writer;
67    }
68
69    public IpConfigStore() {
70        this(new DelayedDiskWrite());
71    }
72
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
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
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
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
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
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
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}. */
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
409    protected static void loge(String s) {
410        Log.e(TAG, s);
411    }
412
413    protected static void log(String s) {
414        Log.d(TAG, s);
415    }
416}
417