shill: OpenVPNDriver: Work around netmask in remote

In some OpenVPN configurations, the netmask is sent in the
"ifconfig_remote" argument instead of "ifconfig_netmask" where it
should be.  Work around this by examining the content of the address.
Precedent for this processing can be found in other connection
managers, for example
https://mail.gnome.org/archives/commits-list/2009-July/msg03401.html

BUG=chromium:241264
TEST=Unit tests

Change-Id: I15c9746e8af528c357eec023327a6375e8dc2599
Reviewed-on: https://gerrit.chromium.org/gerrit/51529
Commit-Queue: Paul Stewart <pstew@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Tested-by: Paul Stewart <pstew@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 27662cc..bdb186f 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -60,6 +60,12 @@
 const char kOpenVPNVerbProperty[] = "OpenVPN.Verb";
 const char kVPNMTUProperty[] = "VPN.MTU";
 
+// Some configurations pass the netmask in the ifconfig_remote property.
+// This is due to some servers not explicitly indicating that they are using
+// a "broadcast mode" network instead of peer-to-peer.  See
+// http://crbug.com/241264 for an example of this issue.
+const char kSuspectedNetmaskPrefix[] = "255.";
+
 }  // namespace
 
 // TODO(petkov): Move to chromeos/dbus/service_constants.h.
@@ -354,7 +360,21 @@
       properties->subnet_prefix =
           IPAddress::GetPrefixLengthFromMask(properties->address_family, value);
     } else if (LowerCaseEqualsASCII(key, kOpenVPNIfconfigRemote)) {
-      properties->peer_address = value;
+      if (StartsWithASCII(value, kSuspectedNetmaskPrefix, false)) {
+        LOG(WARNING) << "Option " << key << " value " << value
+                     << " looks more like a netmask than a peer address; "
+                     << "assuming it is the former.";
+        // In this situation, the "peer_address" value will be left
+        // unset and Connection::UpdateFromIPConfig() will treat the
+        // interface as if it were a broadcast-style network.  The
+        // kernel will, automatically set the peer address equal to
+        // the local address.
+        properties->subnet_prefix =
+            IPAddress::GetPrefixLengthFromMask(properties->address_family,
+                                               value);
+      } else {
+        properties->peer_address = value;
+      }
     } else if (LowerCaseEqualsASCII(key, kOpenVPNRouteVPNGateway)) {
       properties->gateway = value;
     } else if (LowerCaseEqualsASCII(key, kOpenVPNTrustedIP)) {
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 2bfa153..1221c37 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -509,6 +509,13 @@
   OpenVPNDriver::ParseIPConfiguration(config, &props);
   EXPECT_EQ(18, props.subnet_prefix);
 
+  // An "ifconfig_remote" parameter that looks like a netmask should be
+  // applied to the subnet prefix instead of to the peer address.
+  config["ifconfig_remotE"] = "255.255.0.0";
+  OpenVPNDriver::ParseIPConfiguration(config, &props);
+  EXPECT_EQ(16, props.subnet_prefix);
+  EXPECT_EQ("", props.peer_address);
+
   config["ifconfig_loCal"] = "4.5.6.7";
   config["ifconfiG_broadcast"] = "1.2.255.255";
   config["ifconFig_netmAsk"] = "255.255.255.0";