shill: openvpn: Support "name:port" format for the VPN remote host.

BUG=chromium-os:30770
TEST=unit tests, tested on device and inspected logs

Change-Id: I790111e729295744df36a5e6bf1f4562f8f04af7
Reviewed-on: https://gerrit.chromium.org/gerrit/33770
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 75149d3..10a6ca7 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -452,6 +452,22 @@
   LOG_IF(WARNING, properties->routes.empty()) << "No routes provided.";
 }
 
+// static
+bool OpenVPNDriver::SplitPortFromHost(
+    const string &host, string *name, string *port) {
+  vector<string> tokens;
+  SplitString(host, ':', &tokens);
+  int port_number = 0;
+  if (tokens.size() != 2 || tokens[0].empty() || tokens[1].empty() ||
+      !IsAsciiDigit(tokens[1][0]) ||
+      !base::StringToInt(tokens[1], &port_number) || port_number > kuint16max) {
+    return false;
+  }
+  *name = tokens[0];
+  *port = tokens[1];
+  return true;
+}
+
 void OpenVPNDriver::Connect(const VPNServiceRefPtr &service, Error *error) {
   StartConnectTimeout();
   service_ = service;
@@ -473,8 +489,18 @@
   }
   options->push_back("--client");
   options->push_back("--tls-client");
+
   options->push_back("--remote");
-  options->push_back(vpnhost);
+  string host_name, host_port;
+  if (SplitPortFromHost(vpnhost, &host_name, &host_port)) {
+    DCHECK(!host_name.empty());
+    DCHECK(!host_port.empty());
+    options->push_back(host_name);
+    options->push_back(host_port);
+  } else {
+    options->push_back(vpnhost);
+  }
+
   options->push_back("--nobind");
   options->push_back("--persist-key");
   options->push_back("--persist-tun");
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 08fab65..86f7e94 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -90,6 +90,7 @@
   FRIEND_TEST(OpenVPNDriverTest, InitManagementChannelOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitNSSOptions);
   FRIEND_TEST(OpenVPNDriverTest, InitOptions);
+  FRIEND_TEST(OpenVPNDriverTest, InitOptionsHostWithPort);
   FRIEND_TEST(OpenVPNDriverTest, InitOptionsNoHost);
   FRIEND_TEST(OpenVPNDriverTest, InitPKCS11Options);
   FRIEND_TEST(OpenVPNDriverTest, Notify);
@@ -105,6 +106,7 @@
   FRIEND_TEST(OpenVPNDriverTest, ParseRouteOption);
   FRIEND_TEST(OpenVPNDriverTest, SetRoutes);
   FRIEND_TEST(OpenVPNDriverTest, SpawnOpenVPN);
+  FRIEND_TEST(OpenVPNDriverTest, SplitPortFromHost);
   FRIEND_TEST(OpenVPNDriverTest, VerifyPaths);
 
   static const char kOpenVPNPath[];
@@ -137,6 +139,12 @@
   static void SetRoutes(const RouteOptions &routes,
                         IPConfig::Properties *properties);
 
+  // If |host| is in the "name:port" format, sets up |name| and |port|
+  // appropriately and returns true. Otherwise, returns false.
+  static bool SplitPortFromHost(const std::string &host,
+                                std::string *name,
+                                std::string *port);
+
   void InitOptions(std::vector<std::string> *options, Error *error);
   bool InitNSSOptions(std::vector<std::string> *options, Error *error);
   void InitPKCS11Options(std::vector<std::string> *options);
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index eb20e7d..8407893 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -316,6 +316,29 @@
   EXPECT_EQ(2, props.routes.size());
 }
 
+TEST_F(OpenVPNDriverTest, SplitPortFromHost) {
+  string name, port;
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("", NULL, NULL));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost(":1234", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:f:1234", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:x", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:-1", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:+1", &name, &port));
+  EXPECT_FALSE(OpenVPNDriver::SplitPortFromHost("v.com:65536", &name, &port));
+  EXPECT_TRUE(OpenVPNDriver::SplitPortFromHost("v.com:0", &name, &port));
+  EXPECT_EQ("v.com", name);
+  EXPECT_EQ("0", port);
+  EXPECT_TRUE(OpenVPNDriver::SplitPortFromHost("w.com:65535", &name, &port));
+  EXPECT_EQ("w.com", name);
+  EXPECT_EQ("65535", port);
+  EXPECT_TRUE(OpenVPNDriver::SplitPortFromHost("x.com:12345", &name, &port));
+  EXPECT_EQ("x.com", name);
+  EXPECT_EQ("12345", port);
+}
+
 TEST_F(OpenVPNDriverTest, ParseForeignOption) {
   vector<string> domain_search;
   vector<string> dns_servers;
@@ -459,6 +482,27 @@
               options.end());
 }
 
+TEST_F(OpenVPNDriverTest, InitOptionsHostWithPort) {
+  SetArg(flimflam::kProviderHostProperty, "v.com:1234");
+  driver_->rpc_task_.reset(new RPCTask(&control_, this));
+  driver_->tunnel_interface_ = kInterfaceName;
+  EXPECT_CALL(*management_server_, Start(_, _, _)).WillOnce(Return(true));
+  ServiceRefPtr null_service;
+  EXPECT_CALL(manager_, GetDefaultService()).WillOnce(Return(null_service));
+
+  Error error;
+  vector<string> options;
+  driver_->InitOptions(&options, &error);
+  EXPECT_TRUE(error.IsSuccess());
+  vector<string>::const_iterator it =
+      std::find(options.begin(), options.end(), "--remote");
+  ASSERT_TRUE(it != options.end());
+  ASSERT_TRUE(++it != options.end());
+  EXPECT_EQ("v.com", *it);
+  ASSERT_TRUE(++it != options.end());
+  EXPECT_EQ("1234", *it);
+}
+
 TEST_F(OpenVPNDriverTest, InitNSSOptions) {
   static const char kHost[] = "192.168.2.254";
   static const char kCaCertNSS[] = "{1234}";