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