shill: vpn: Parse openvpn IP configuration options.

The options are pushed to shill over DBus through the .Task.notify service
method.

BUG=chromium-os:27056
TEST=unit tests

Change-Id: I98d7d49c275f49daba5a6dbe0af0878bf82038a6
Reviewed-on: https://gerrit.chromium.org/gerrit/17219
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: mukesh agrawal <quiche@chromium.org>
Commit-Ready: Darin Petkov <petkov@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index e90ef20..69a6d33 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -4,13 +4,19 @@
 
 #include "shill/openvpn_driver.h"
 
+#include <arpa/inet.h>
+
 #include <base/logging.h>
+#include <base/string_number_conversions.h>
+#include <base/string_util.h>
 #include <chromeos/dbus/service_constants.h>
 
 #include "shill/device_info.h"
+#include "shill/dhcp_config.h"
 #include "shill/error.h"
 #include "shill/rpc_task.h"
 
+using std::map;
 using std::string;
 using std::vector;
 
@@ -18,7 +24,16 @@
 
 namespace {
 const char kOpenVPNScript[] = "/usr/lib/flimflam/scripts/openvpn-script";
-}  // namespace {}
+const char kOpenVPNForeignOptionPrefix[] = "foreign_option_";
+const char kOpenVPNIfconfigBroadcast[] = "ifconfig_broadcast";
+const char kOpenVPNIfconfigLocal[] = "ifconfig_local";
+const char kOpenVPNIfconfigNetmask[] = "ifconfig_netmask";
+const char kOpenVPNIfconfigRemote[] = "ifconfig_remote";
+const char kOpenVPNRouteOptionPrefix[] = "route_";
+const char kOpenVPNRouteVPNGateway[] = "route_vpn_gateway";
+const char kOpenVPNTrustedIP[] = "trusted_ip";
+const char kOpenVPNTunMTU[] = "tun_mtu";
+}  // namespace
 
 OpenVPNDriver::OpenVPNDriver(ControlInterface *control,
                              DeviceInfo *device_info,
@@ -43,6 +58,93 @@
   return true;
 }
 
+bool OpenVPNDriver::Notify(const string &reason,
+                           const map<string, string> &dict) {
+  VLOG(2) << __func__ << "(" << reason << ")";
+  if (reason != "up") {
+    return false;
+  }
+  IPConfig::Properties properties;
+  ParseIPConfiguration(dict, &properties);
+  // TODO(petkov): Apply the properties to a VPNDevice's IPConfig.
+  return true;
+}
+
+// static
+void OpenVPNDriver::ParseIPConfiguration(
+    const map<string, string> &configuration,
+    IPConfig::Properties *properties) {
+  ForeignOptions foreign_options;
+  string trusted_ip;
+  properties->address_family = IPAddress::kFamilyIPv4;
+  for (map<string, string>::const_iterator it = configuration.begin();
+       it != configuration.end(); ++it) {
+    const string &key = it->first;
+    const string &value = it->second;
+    VLOG(2) << "Processing: " << key << " -> " << value;
+    if (LowerCaseEqualsASCII(key, kOpenVPNIfconfigLocal)) {
+      properties->address = value;
+    } else if (LowerCaseEqualsASCII(key, kOpenVPNIfconfigBroadcast)) {
+      properties->broadcast_address = value;
+    } else if (LowerCaseEqualsASCII(key, kOpenVPNIfconfigNetmask)) {
+      properties->subnet_cidr =
+          IPAddress::GetPrefixLengthFromMask(properties->address_family, value);
+    } else if (LowerCaseEqualsASCII(key, kOpenVPNIfconfigRemote)) {
+      properties->peer_address = value;
+    } else if (LowerCaseEqualsASCII(key, kOpenVPNRouteVPNGateway)) {
+      properties->gateway = value;
+    } else if (LowerCaseEqualsASCII(key, kOpenVPNTrustedIP)) {
+      trusted_ip = value;
+    } else if (LowerCaseEqualsASCII(key, kOpenVPNTunMTU)) {
+      int mtu = 0;
+      if (base::StringToInt(value, &mtu) && mtu >= DHCPConfig::kMinMTU) {
+        properties->mtu = mtu;
+      } else {
+        LOG(ERROR) << "MTU " << value << " ignored.";
+      }
+    } else if (StartsWithASCII(key, kOpenVPNForeignOptionPrefix, false)) {
+      const string suffix = key.substr(strlen(kOpenVPNForeignOptionPrefix));
+      int order = 0;
+      if (base::StringToInt(suffix, &order)) {
+        foreign_options[order] = value;
+      } else {
+        LOG(ERROR) << "Ignored unexpected foreign option suffix: " << suffix;
+      }
+    } else if (StartsWithASCII(key, kOpenVPNRouteOptionPrefix, false)) {
+      // TODO(petkov): Process the route.
+    } else {
+      VLOG(2) << "Key ignored.";
+    }
+  }
+  // TODO(petkov): If gateway and trusted_ip, pin a host route to VPN server.
+  ParseForeignOptions(foreign_options, properties);
+}
+
+// static
+void OpenVPNDriver::ParseForeignOptions(const ForeignOptions &options,
+                                        IPConfig::Properties *properties) {
+  for (ForeignOptions::const_iterator it = options.begin();
+       it != options.end(); ++it) {
+    ParseForeignOption(it->second, properties);
+  }
+}
+
+// static
+void OpenVPNDriver::ParseForeignOption(const string &option,
+                                       IPConfig::Properties *properties) {
+  VLOG(2) << __func__ << "(" << option << ")";
+  vector<string> tokens;
+  if (Tokenize(option, " ", &tokens) != 3 ||
+      !LowerCaseEqualsASCII(tokens[0], "dhcp-option")) {
+    return;
+  }
+  if (LowerCaseEqualsASCII(tokens[1], "domain")) {
+    properties->domain_search.push_back(tokens[2]);
+  } else if (LowerCaseEqualsASCII(tokens[1], "dns")) {
+    properties->dns_servers.push_back(tokens[2]);
+  }
+}
+
 void OpenVPNDriver::Connect(Error *error) {
   // TODO(petkov): Allocate rpc_task_.
   error->Populate(Error::kNotSupported);