shill: openvpn: Use different reconnect timeouts for tls-error and offline.

When the system goes offline, use reconnect timeout of 2 minutes rather than one
to allow the user more time to reconnect. When the connect attempt fails due to
tls-error, limit the timeout to 20 seconds because it's most likely due to bad
certificates and 20 seconds should be enough to adjust for intermittent
failures.

Also, cleanup some FRIEND_TESTs for unit tests that this patch touches.

BUG=chromium-os:36355,chromium-os:38674
TEST=unit tests; tested by connecting to corp VPN, disconnect WiFi, re-connect
WiFi after a minute, observe VPN reconnect; tested by connecting test OpenVPN
with bad CA certificate and observe reduced reconnect timeout; inspected logs.

Change-Id: I2e7c7b34fbe46355a34f2c9a3b3125e25950bb3e
Reviewed-on: https://gerrit.chromium.org/gerrit/43036
Tested-by: Darin Petkov <petkov@chromium.org>
Reviewed-by: Paul Stewart <pstew@chromium.org>
Commit-Queue: Darin Petkov <petkov@chromium.org>
diff --git a/l2tp_ipsec_driver.cc b/l2tp_ipsec_driver.cc
index fdd18d4..0451bf5 100644
--- a/l2tp_ipsec_driver.cc
+++ b/l2tp_ipsec_driver.cc
@@ -96,7 +96,7 @@
 }
 
 void L2TPIPSecDriver::Connect(const VPNServiceRefPtr &service, Error *error) {
-  StartConnectTimeout();
+  StartConnectTimeout(kDefaultConnectTimeoutSeconds);
   service_ = service;
   service_->SetState(Service::kStateConfiguring);
   rpc_task_.reset(new RPCTask(control_, this));
diff --git a/l2tp_ipsec_driver.h b/l2tp_ipsec_driver.h
index 60722cf..12d38e1 100644
--- a/l2tp_ipsec_driver.h
+++ b/l2tp_ipsec_driver.h
@@ -72,7 +72,6 @@
   FRIEND_TEST(L2TPIPSecDriverTest, InitOptions);
   FRIEND_TEST(L2TPIPSecDriverTest, InitOptionsNoHost);
   FRIEND_TEST(L2TPIPSecDriverTest, InitPSKOptions);
-  FRIEND_TEST(L2TPIPSecDriverTest, Notify);
   FRIEND_TEST(L2TPIPSecDriverTest, NotifyDisconnected);
   FRIEND_TEST(L2TPIPSecDriverTest, OnConnectionDisconnected);
   FRIEND_TEST(L2TPIPSecDriverTest, OnL2TPIPSecVPNDied);
diff --git a/l2tp_ipsec_driver_unittest.cc b/l2tp_ipsec_driver_unittest.cc
index 9f2b9b6..9791ee5 100644
--- a/l2tp_ipsec_driver_unittest.cc
+++ b/l2tp_ipsec_driver_unittest.cc
@@ -88,6 +88,10 @@
     return driver_->GetProviderType();
   }
 
+  void SetDevice(const VPNRefPtr &device) {
+    driver_->device_ = device;
+  }
+
   void SetService(const VPNServiceRefPtr &service) {
     driver_->service_ = service;
   }
@@ -100,8 +104,8 @@
     driver_->OnConnectTimeout();
   }
 
-  void StartConnectTimeout() {
-    driver_->StartConnectTimeout();
+  void StartConnectTimeout(int timeout_seconds) {
+    driver_->StartConnectTimeout(timeout_seconds);
   }
 
   bool IsConnectTimeoutStarted() {
@@ -114,6 +118,12 @@
 
   FilePath SetupPSKFile();
 
+  FilePath GetPSKFile() { return driver_->psk_file_; }
+
+  void InvokeNotify(const string &reason, const map<string, string> &dict) {
+    driver_->Notify(reason, dict);
+  }
+
   // Inherited from RPCTaskDelegate.
   virtual void GetLogin(string *user, string *password);
   virtual void Notify(const string &reason, const map<string, string> &dict);
@@ -209,7 +219,7 @@
   EXPECT_CALL(*service_, SetState(Service::kStateFailure));
   driver_->rpc_task_.reset(new RPCTask(&control_, this));
   FilePath psk_file = SetupPSKFile();
-  driver_->StartConnectTimeout();
+  StartConnectTimeout(0);
   driver_->Cleanup(Service::kStateFailure);
   EXPECT_FALSE(file_util::PathExists(psk_file));
   EXPECT_TRUE(driver_->psk_file_.empty());
@@ -483,7 +493,7 @@
 }
 
 TEST_F(L2TPIPSecDriverTest, OnConnectTimeout) {
-  StartConnectTimeout();
+  StartConnectTimeout(0);
   SetService(service_);
   EXPECT_CALL(*service_, SetState(Service::kStateFailure));
   OnConnectTimeout();
@@ -572,13 +582,13 @@
       .WillOnce(Return(kInterfaceIndex));
   EXPECT_CALL(*device_, SetEnabled(true));
   EXPECT_CALL(*device_, UpdateIPConfig(_));
-  driver_->device_ = device_;
+  SetDevice(device_);
   FilePath psk_file = SetupPSKFile();
-  driver_->StartConnectTimeout();
-  driver_->Notify(kL2TPIPSecReasonConnect, config);
+  StartConnectTimeout(0);
+  InvokeNotify(kL2TPIPSecReasonConnect, config);
   EXPECT_FALSE(file_util::PathExists(psk_file));
-  EXPECT_TRUE(driver_->psk_file_.empty());
-  EXPECT_FALSE(driver_->IsConnectTimeoutStarted());
+  EXPECT_TRUE(GetPSKFile().empty());
+  EXPECT_FALSE(IsConnectTimeoutStarted());
 }
 
 TEST_F(L2TPIPSecDriverTest, NotifyDisconnected) {
diff --git a/mock_openvpn_driver.h b/mock_openvpn_driver.h
index 7bc6a89..33bfd4a 100644
--- a/mock_openvpn_driver.h
+++ b/mock_openvpn_driver.h
@@ -16,7 +16,7 @@
   MockOpenVPNDriver();
   virtual ~MockOpenVPNDriver();
 
-  MOCK_METHOD0(OnReconnecting, void());
+  MOCK_METHOD1(OnReconnecting, void(ReconnectReason reason));
   MOCK_METHOD1(Cleanup, void(Service::ConnectState state));
 
  private:
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 5551baf..c3c9afb 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -119,13 +119,12 @@
   { flimflam::kOpenVPNMgmtEnableProperty, 0 },
 };
 
-// static
 const char OpenVPNDriver::kLSBReleaseFile[] = "/etc/lsb-release";
-// static
 const char OpenVPNDriver::kChromeOSReleaseName[] = "CHROMEOS_RELEASE_NAME";
-//static
 const char OpenVPNDriver::kChromeOSReleaseVersion[] =
     "CHROMEOS_RELEASE_VERSION";
+const int OpenVPNDriver::kReconnectOfflineTimeoutSeconds = 2 * 60;
+const int OpenVPNDriver::kReconnectTLSErrorTimeoutSeconds = 20;
 
 OpenVPNDriver::OpenVPNDriver(ControlInterface *control,
                              EventDispatcher *dispatcher,
@@ -470,7 +469,7 @@
 }
 
 void OpenVPNDriver::Connect(const VPNServiceRefPtr &service, Error *error) {
-  StartConnectTimeout();
+  StartConnectTimeout(kDefaultConnectTimeoutSeconds);
   service_ = service;
   service_->SetState(Service::kStateConfiguring);
   if (!device_info_->CreateTunnelInterface(&tunnel_interface_)) {
@@ -745,7 +744,7 @@
   // and openvpn will not lead to a permanently stale connectivity state. Note
   // that a subsequent invocation of OnReconnecting due to a RECONNECTING
   // message will essentially be a no-op.
-  OnReconnecting();
+  OnReconnecting(kReconnectReasonOffline);
 }
 
 void OpenVPNDriver::OnConnectTimeout() {
@@ -753,9 +752,16 @@
   Cleanup(Service::kStateFailure);
 }
 
-void OpenVPNDriver::OnReconnecting() {
-  SLOG(VPN, 2) << __func__;
-  StartConnectTimeout();
+void OpenVPNDriver::OnReconnecting(ReconnectReason reason) {
+  LOG(INFO) << __func__ << "(" << reason << ")";
+  int timeout_seconds = GetReconnectTimeoutSeconds(reason);
+  if (reason == kReconnectReasonTLSError &&
+      timeout_seconds < connect_timeout_seconds()) {
+    // Reconnect due to TLS error happens during connect so we need to cancel
+    // the original connect timeout first and then reduce the time limit.
+    StopConnectTimeout();
+  }
+  StartConnectTimeout(timeout_seconds);
   // On restart/reconnect, drop the VPN connection, if any. The openvpn client
   // might be in hold state if the VPN connection was previously established
   // successfully. The hold will be released by OnDefaultServiceChanged when a
@@ -769,6 +775,19 @@
   }
 }
 
+// static
+int OpenVPNDriver::GetReconnectTimeoutSeconds(ReconnectReason reason) {
+  switch (reason) {
+    case kReconnectReasonOffline:
+      return kReconnectOfflineTimeoutSeconds;
+    case kReconnectReasonTLSError:
+      return kReconnectTLSErrorTimeoutSeconds;
+    default:
+      break;
+  }
+  return kDefaultConnectTimeoutSeconds;
+}
+
 string OpenVPNDriver::GetProviderType() const {
   return flimflam::kProviderOpenVpn;
 }
diff --git a/openvpn_driver.h b/openvpn_driver.h
index dc85576..72784ba 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -41,6 +41,12 @@
 class OpenVPNDriver : public VPNDriver,
                       public RPCTaskDelegate {
  public:
+  enum ReconnectReason {
+    kReconnectReasonUnknown,
+    kReconnectReasonOffline,
+    kReconnectReasonTLSError,
+  };
+
   OpenVPNDriver(ControlInterface *control,
                 EventDispatcher *dispatcher,
                 Metrics *metrics,
@@ -49,7 +55,7 @@
                 GLib *glib);
   virtual ~OpenVPNDriver();
 
-  virtual void OnReconnecting();
+  virtual void OnReconnecting(ReconnectReason reason);
 
   virtual void Cleanup(Service::ConnectState state);
 
@@ -99,7 +105,6 @@
   FRIEND_TEST(OpenVPNDriverTest, NotifyFail);
   FRIEND_TEST(OpenVPNDriverTest, OnDefaultServiceChanged);
   FRIEND_TEST(OpenVPNDriverTest, OnOpenVPNDied);
-  FRIEND_TEST(OpenVPNDriverTest, OnReconnecting);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOption);
   FRIEND_TEST(OpenVPNDriverTest, ParseForeignOptions);
   FRIEND_TEST(OpenVPNDriverTest, ParseIPConfiguration);
@@ -110,6 +115,11 @@
   FRIEND_TEST(OpenVPNDriverTest, SplitPortFromHost);
   FRIEND_TEST(OpenVPNDriverTest, VerifyPaths);
 
+  // The map is a sorted container that allows us to iterate through the options
+  // in order.
+  typedef std::map<int, std::string> ForeignOptions;
+  typedef std::map<int, IPConfig::Route> RouteOptions;
+
   static const char kOpenVPNCertProperty[];
   static const char kOpenVPNKeyProperty[];
 
@@ -123,10 +133,8 @@
   static const char kChromeOSReleaseName[];
   static const char kChromeOSReleaseVersion[];
 
-  // The map is a sorted container that allows us to iterate through the options
-  // in order.
-  typedef std::map<int, std::string> ForeignOptions;
-  typedef std::map<int, IPConfig::Route> RouteOptions;
+  static const int kReconnectOfflineTimeoutSeconds;
+  static const int kReconnectTLSErrorTimeoutSeconds;
 
   static void ParseIPConfiguration(
       const std::map<std::string, std::string> &configuration,
@@ -164,6 +172,8 @@
 
   bool SpawnOpenVPN();
 
+  static int GetReconnectTimeoutSeconds(ReconnectReason reason);
+
   // Called when the openpvn process exits.
   static void OnOpenVPNDied(GPid pid, gint status, gpointer data);
 
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index 900dfa6..268e9ce 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -18,6 +18,7 @@
 #include "shill/logging.h"
 #include "shill/mock_adaptors.h"
 #include "shill/mock_device_info.h"
+#include "shill/mock_event_dispatcher.h"
 #include "shill/mock_glib.h"
 #include "shill/mock_manager.h"
 #include "shill/mock_metrics.h"
@@ -143,14 +144,30 @@
     driver_->OnConnectTimeout();
   }
 
-  void StartConnectTimeout() {
-    driver_->StartConnectTimeout();
+  void StartConnectTimeout(int timeout_seconds) {
+    driver_->StartConnectTimeout(timeout_seconds);
   }
 
   bool IsConnectTimeoutStarted() {
     return driver_->IsConnectTimeoutStarted();
   }
 
+  static int GetDefaultConnectTimeoutSeconds() {
+    return OpenVPNDriver::kDefaultConnectTimeoutSeconds;
+  }
+
+  static int GetReconnectOfflineTimeoutSeconds() {
+    return OpenVPNDriver::kReconnectOfflineTimeoutSeconds;
+  }
+
+  static int GetReconnectTLSErrorTimeoutSeconds() {
+    return OpenVPNDriver::kReconnectTLSErrorTimeoutSeconds;
+  }
+
+  static int GetReconnectTimeoutSeconds(OpenVPNDriver::ReconnectReason reason) {
+    return OpenVPNDriver::GetReconnectTimeoutSeconds(reason);
+  }
+
   // Used to assert that a flag appears in the options.
   void ExpectInFlags(const vector<string> &options, const string &flag,
                      const string &value);
@@ -165,7 +182,7 @@
 
   NiceMockControl control_;
   NiceMock<MockDeviceInfo> device_info_;
-  EventDispatcher dispatcher_;
+  MockEventDispatcher dispatcher_;
   MockMetrics metrics_;
   MockGLib glib_;
   MockManager manager_;
@@ -283,7 +300,7 @@
   map<string, string> config;
   driver_->service_ = service_;
   driver_->device_ = device_;
-  driver_->StartConnectTimeout();
+  StartConnectTimeout(0);
   EXPECT_CALL(*device_,
               UpdateIPConfig(Field(&IPConfig::Properties::address, "")));
   driver_->Notify("up", config);
@@ -300,7 +317,7 @@
 TEST_F(OpenVPNDriverTest, NotifyFail) {
   map<string, string> dict;
   driver_->device_ = device_;
-  driver_->StartConnectTimeout();
+  StartConnectTimeout(0);
   EXPECT_CALL(*device_, OnDisconnected());
   driver_->Notify("fail", dict);
   EXPECT_TRUE(driver_->IsConnectTimeoutStarted());
@@ -792,7 +809,7 @@
   driver_->device_ = device_;
   driver_->service_ = service_;
   driver_->ip_properties_.address = "1.2.3.4";
-  driver_->StartConnectTimeout();
+  StartConnectTimeout(0);
   FilePath tls_auth_file;
   EXPECT_TRUE(file_util::CreateTemporaryFile(&tls_auth_file));
   EXPECT_FALSE(tls_auth_file.empty());
@@ -896,7 +913,7 @@
 }
 
 TEST_F(OpenVPNDriverTest, OnConnectTimeout) {
-  StartConnectTimeout();
+  StartConnectTimeout(0);
   SetService(service_);
   EXPECT_CALL(*service_, SetState(Service::kStateFailure));
   OnConnectTimeout();
@@ -904,14 +921,39 @@
   EXPECT_FALSE(IsConnectTimeoutStarted());
 }
 
-TEST_F(OpenVPNDriverTest, OnReconnecting) {
-  driver_->OnReconnecting();  // Expect no crash.
-  driver_->device_ = device_;
-  driver_->service_ = service_;
+TEST_F(OpenVPNDriverTest, OnReconnectingUnknown) {
+  EXPECT_FALSE(IsConnectTimeoutStarted());
+  EXPECT_CALL(dispatcher_,
+              PostDelayedTask(_, GetDefaultConnectTimeoutSeconds() * 1000))
+      .WillOnce(Return(true));
+  SetDevice(device_);
+  SetService(service_);
   EXPECT_CALL(*device_, OnDisconnected());
   EXPECT_CALL(*service_, SetState(Service::kStateAssociating));
-  driver_->OnReconnecting();
-  EXPECT_TRUE(driver_->IsConnectTimeoutStarted());
+  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonUnknown);
+  EXPECT_TRUE(IsConnectTimeoutStarted());
+}
+
+TEST_F(OpenVPNDriverTest, OnReconnectingTLSError) {
+  EXPECT_CALL(dispatcher_,
+              PostDelayedTask(_, GetReconnectOfflineTimeoutSeconds() * 1000))
+      .WillOnce(Return(true));
+  EXPECT_CALL(dispatcher_,
+              PostDelayedTask(_, GetReconnectTLSErrorTimeoutSeconds() * 1000))
+      .WillOnce(Return(true));
+
+  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonOffline);
+  EXPECT_TRUE(IsConnectTimeoutStarted());
+
+  // The scheduled timeout should not be affected for unknown reason.
+  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonUnknown);
+  EXPECT_TRUE(IsConnectTimeoutStarted());
+
+  // Reconnect on TLS error reschedules the timeout once.
+  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonTLSError);
+  EXPECT_TRUE(IsConnectTimeoutStarted());
+  driver_->OnReconnecting(OpenVPNDriver::kReconnectReasonTLSError);
+  EXPECT_TRUE(IsConnectTimeoutStarted());
 }
 
 TEST_F(OpenVPNDriverTest, VerifyPaths) {
@@ -1030,4 +1072,14 @@
   driver_->OnDefaultServiceChanged(mock_service);
 }
 
+TEST_F(OpenVPNDriverTest, GetReconnectTimeoutSeconds) {
+  EXPECT_EQ(GetDefaultConnectTimeoutSeconds(),
+            GetReconnectTimeoutSeconds(OpenVPNDriver::kReconnectReasonUnknown));
+  EXPECT_EQ(GetReconnectOfflineTimeoutSeconds(),
+            GetReconnectTimeoutSeconds(OpenVPNDriver::kReconnectReasonOffline));
+  EXPECT_EQ(GetReconnectTLSErrorTimeoutSeconds(),
+            GetReconnectTimeoutSeconds(
+                OpenVPNDriver::kReconnectReasonTLSError));
+}
+
 }  // namespace shill
diff --git a/openvpn_management_server.cc b/openvpn_management_server.cc
index ff1955a..4b0092d 100644
--- a/openvpn_management_server.cc
+++ b/openvpn_management_server.cc
@@ -318,11 +318,17 @@
   vector<string> details;
   SplitString(message, ',', &details);
   if (details.size() > 1) {
-    LOG(INFO) << "Processing state message: " << details[1];
     if (details[1] == "RECONNECTING") {
-      driver_->OnReconnecting();
+      OpenVPNDriver::ReconnectReason reason =
+          OpenVPNDriver::kReconnectReasonUnknown;
+      if (details.size() > 2 && details[2] == "tls-error") {
+        reason = OpenVPNDriver::kReconnectReasonTLSError;
+      }
+      driver_->OnReconnecting(reason);
+    } else {
+      // The rest of the states are currently ignored.
+      LOG(INFO) << "Ignoring state message: " << details[1];
     }
-    // The rest of the states are currently ignored.
   }
   return true;
 }
diff --git a/openvpn_management_server.h b/openvpn_management_server.h
index 5e7ef2d..72e0f74 100644
--- a/openvpn_management_server.h
+++ b/openvpn_management_server.h
@@ -52,7 +52,6 @@
   friend class OpenVPNManagementServerTest;
   FRIEND_TEST(OpenVPNManagementServerTest, EscapeToQuote);
   FRIEND_TEST(OpenVPNManagementServerTest, Hold);
-  FRIEND_TEST(OpenVPNManagementServerTest, OnInput);
   FRIEND_TEST(OpenVPNManagementServerTest, OnInputStop);
   FRIEND_TEST(OpenVPNManagementServerTest, OnReady);
   FRIEND_TEST(OpenVPNManagementServerTest, OnReadyAcceptFail);
@@ -64,12 +63,10 @@
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessFailedPasswordMessage);
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessHoldMessage);
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessInfoMessage);
-  FRIEND_TEST(OpenVPNManagementServerTest, ProcessMessage);
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessNeedPasswordMessageAuth);
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessNeedPasswordMessageAuthSC);
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessNeedPasswordMessageTPMToken);
   FRIEND_TEST(OpenVPNManagementServerTest, ProcessNeedPasswordMessageUnknown);
-  FRIEND_TEST(OpenVPNManagementServerTest, ProcessStateMessage);
   FRIEND_TEST(OpenVPNManagementServerTest, Send);
   FRIEND_TEST(OpenVPNManagementServerTest, SendHoldRelease);
   FRIEND_TEST(OpenVPNManagementServerTest, SendPassword);
diff --git a/openvpn_management_server_unittest.cc b/openvpn_management_server_unittest.cc
index c26ffaa..99295bc 100644
--- a/openvpn_management_server_unittest.cc
+++ b/openvpn_management_server_unittest.cc
@@ -19,6 +19,7 @@
 using std::vector;
 using testing::_;
 using testing::Assign;
+using testing::InSequence;
 using testing::Return;
 using testing::ReturnNew;
 
@@ -103,10 +104,24 @@
     server_.SendSignal(signal);
   }
 
+  void OnInput(InputData *data) {
+    server_.OnInput(data);
+  }
+
+  void ProcessMessage(const string &message) {
+    server_.ProcessMessage(message);
+  }
+
   bool ProcessSuccessMessage(const string &message) {
     return server_.ProcessSuccessMessage(message);
   }
 
+  bool ProcessStateMessage(const string &message) {
+    return server_.ProcessStateMessage(message);
+  }
+
+  bool GetHoldWaiting() { return server_.hold_waiting_; }
+
   GLib glib_;
   MockOpenVPNDriver driver_;
   OpenVPNManagementServer server_;
@@ -211,7 +226,7 @@
   {
     string s;
     InputData data = CreateInputDataFromString(s);
-    server_.OnInput(&data);
+    OnInput(&data);
   }
   {
     string s = "foo\n"
@@ -226,10 +241,10 @@
     ExpectStaticChallengeResponse();
     ExpectPINResponse();
     EXPECT_CALL(driver_, Cleanup(Service::kStateFailure));
-    EXPECT_CALL(driver_, OnReconnecting());
-    EXPECT_FALSE(server_.hold_waiting_);
-    server_.OnInput(&data);
-    EXPECT_TRUE(server_.hold_waiting_);
+    EXPECT_CALL(driver_, OnReconnecting(_));
+    EXPECT_FALSE(GetHoldWaiting());
+    OnInput(&data);
+    EXPECT_TRUE(GetHoldWaiting());
   }
 }
 
@@ -243,16 +258,16 @@
   EXPECT_CALL(driver_, Cleanup(Service::kStateFailure))
       .WillOnce(Assign(&server_.sockets_, reinterpret_cast<Sockets *>(NULL)));
   // The second message should not be processed.
-  EXPECT_CALL(driver_, OnReconnecting()).Times(0);
-  server_.OnInput(&data);
+  EXPECT_CALL(driver_, OnReconnecting(_)).Times(0);
+  OnInput(&data);
 }
 
 TEST_F(OpenVPNManagementServerTest, ProcessMessage) {
-  server_.ProcessMessage("foo");
-  server_.ProcessMessage(">INFO:");
+  ProcessMessage("foo");
+  ProcessMessage(">INFO:");
 
-  EXPECT_CALL(driver_, OnReconnecting());
-  server_.ProcessMessage(">STATE:123,RECONNECTING,detail,...,...");
+  EXPECT_CALL(driver_, OnReconnecting(_));
+  ProcessMessage(">STATE:123,RECONNECTING,detail,...,...");
 }
 
 TEST_F(OpenVPNManagementServerTest, ProcessSuccessMessage) {
@@ -266,11 +281,17 @@
 }
 
 TEST_F(OpenVPNManagementServerTest, ProcessStateMessage) {
-  EXPECT_FALSE(server_.ProcessStateMessage("foo"));
-  EXPECT_TRUE(server_.ProcessStateMessage(">STATE:123,WAIT,detail,...,..."));
-  EXPECT_CALL(driver_, OnReconnecting());
-  EXPECT_TRUE(
-      server_.ProcessStateMessage(">STATE:123,RECONNECTING,detail,...,..."));
+  EXPECT_FALSE(ProcessStateMessage("foo"));
+  EXPECT_TRUE(ProcessStateMessage(">STATE:123,WAIT,detail,...,..."));
+  {
+    InSequence seq;
+    EXPECT_CALL(driver_,
+                OnReconnecting(OpenVPNDriver::kReconnectReasonUnknown));
+    EXPECT_CALL(driver_,
+                OnReconnecting(OpenVPNDriver::kReconnectReasonTLSError));
+  }
+  EXPECT_TRUE(ProcessStateMessage(">STATE:123,RECONNECTING,detail,...,..."));
+  EXPECT_TRUE(ProcessStateMessage(">STATE:123,RECONNECTING,tls-error,...,..."));
 }
 
 TEST_F(OpenVPNManagementServerTest, ProcessNeedPasswordMessageAuthSC) {
diff --git a/vpn_driver.cc b/vpn_driver.cc
index c6fbf72..4705c8a 100644
--- a/vpn_driver.cc
+++ b/vpn_driver.cc
@@ -31,7 +31,7 @@
       manager_(manager),
       properties_(properties),
       property_count_(property_count),
-      connect_timeout_seconds_(kDefaultConnectTimeoutSeconds) {}
+      connect_timeout_seconds_(0) {}
 
 VPNDriver::~VPNDriver() {}
 
@@ -160,20 +160,23 @@
   return provider_properties;
 }
 
-void VPNDriver::StartConnectTimeout() {
-  SLOG(VPN, 2) << __func__;
+void VPNDriver::StartConnectTimeout(int timeout_seconds) {
   if (IsConnectTimeoutStarted()) {
     return;
   }
+  LOG(INFO) << "Schedule VPN connect timeout: "
+            << timeout_seconds << " seconds.";
+  connect_timeout_seconds_ = timeout_seconds;
   connect_timeout_callback_.Reset(
       Bind(&VPNDriver::OnConnectTimeout, weak_ptr_factory_.GetWeakPtr()));
   dispatcher_->PostDelayedTask(
-      connect_timeout_callback_.callback(), connect_timeout_seconds_ * 1000);
+      connect_timeout_callback_.callback(), timeout_seconds * 1000);
 }
 
 void VPNDriver::StopConnectTimeout() {
   SLOG(VPN, 2) << __func__;
   connect_timeout_callback_.Cancel();
+  connect_timeout_seconds_ = 0;
 }
 
 bool VPNDriver::IsConnectTimeoutStarted() const {
diff --git a/vpn_driver.h b/vpn_driver.h
index 4200b7a..ea6bb82 100644
--- a/vpn_driver.h
+++ b/vpn_driver.h
@@ -59,6 +59,8 @@
     int flags;
   };
 
+  static const int kDefaultConnectTimeoutSeconds;
+
   VPNDriver(EventDispatcher *dispatcher,
             Manager *manager,
             const Property *properties,
@@ -69,9 +71,10 @@
 
   virtual KeyValueStore GetProvider(Error *error);
 
-  // Initializes a callback that will invoke OnConnectTimeout. The timeout will
-  // not be restarted if it's already scheduled.
-  void StartConnectTimeout();
+  // Initializes a callback that will invoke OnConnectTimeout after
+  // |timeout_seconds|. The timeout will not be restarted if it's already
+  // scheduled.
+  void StartConnectTimeout(int timeout_seconds);
   // Cancels the connect timeout callback, if any, previously scheduled through
   // StartConnectTimeout.
   void StopConnectTimeout();
@@ -82,10 +85,10 @@
   // fires. Cancels the timeout callback.
   virtual void OnConnectTimeout();
 
- private:
-  FRIEND_TEST(VPNDriverTest, ConnectTimeout);
+  int connect_timeout_seconds() const { return connect_timeout_seconds_; }
 
-  static const int kDefaultConnectTimeoutSeconds;
+ private:
+  friend class VPNDriverTest;
 
   void ClearMappedProperty(const size_t &index, Error *error);
   std::string GetMappedProperty(const size_t &index, Error *error);
diff --git a/vpn_driver_unittest.cc b/vpn_driver_unittest.cc
index 951e17b..94d556b 100644
--- a/vpn_driver_unittest.cc
+++ b/vpn_driver_unittest.cc
@@ -94,6 +94,24 @@
   virtual ~VPNDriverTest() {}
 
  protected:
+  EventDispatcher *dispatcher() { return driver_.dispatcher_; }
+  void set_dispatcher(EventDispatcher *dispatcher) {
+    driver_.dispatcher_ = dispatcher;
+  }
+
+  const base::CancelableClosure &connect_timeout_callback() {
+    return driver_.connect_timeout_callback_;
+  }
+
+  bool IsConnectTimeoutStarted() { return driver_.IsConnectTimeoutStarted(); }
+  int connect_timeout_seconds() { return driver_.connect_timeout_seconds(); }
+
+  void StartConnectTimeout(int timeout_seconds) {
+    driver_.StartConnectTimeout(timeout_seconds);
+  }
+
+  void StopConnectTimeout() { driver_.StopConnectTimeout(); }
+
   void SetArg(const string &arg, const string &value) {
     driver_.args()->SetString(arg, value);
   }
@@ -281,20 +299,31 @@
 }
 
 TEST_F(VPNDriverTest, ConnectTimeout) {
-  EXPECT_EQ(&dispatcher_, driver_.dispatcher_);
-  EXPECT_TRUE(driver_.connect_timeout_callback_.IsCancelled());
-  EXPECT_FALSE(driver_.IsConnectTimeoutStarted());
-  EXPECT_EQ(VPNDriver::kDefaultConnectTimeoutSeconds,
-            driver_.connect_timeout_seconds_);
-  driver_.connect_timeout_seconds_ = 0;
-  driver_.StartConnectTimeout();
-  EXPECT_FALSE(driver_.connect_timeout_callback_.IsCancelled());
-  EXPECT_TRUE(driver_.IsConnectTimeoutStarted());
-  driver_.dispatcher_ = NULL;
-  driver_.StartConnectTimeout();  // Expect no crash.
+  EXPECT_EQ(&dispatcher_, dispatcher());
+  EXPECT_TRUE(connect_timeout_callback().IsCancelled());
+  EXPECT_FALSE(IsConnectTimeoutStarted());
+  StartConnectTimeout(0);
+  EXPECT_FALSE(connect_timeout_callback().IsCancelled());
+  EXPECT_TRUE(IsConnectTimeoutStarted());
+  set_dispatcher(NULL);
+  StartConnectTimeout(0);  // Expect no crash.
   dispatcher_.DispatchPendingEvents();
-  EXPECT_TRUE(driver_.connect_timeout_callback_.IsCancelled());
-  EXPECT_FALSE(driver_.IsConnectTimeoutStarted());
+  EXPECT_TRUE(connect_timeout_callback().IsCancelled());
+  EXPECT_FALSE(IsConnectTimeoutStarted());
+}
+
+TEST_F(VPNDriverTest, StartStopConnectTimeout) {
+  EXPECT_FALSE(IsConnectTimeoutStarted());
+  EXPECT_EQ(0, connect_timeout_seconds());
+  const int kTimeout = 123;
+  StartConnectTimeout(kTimeout);
+  EXPECT_TRUE(IsConnectTimeoutStarted());
+  EXPECT_EQ(kTimeout, connect_timeout_seconds());
+  StartConnectTimeout(kTimeout - 20);
+  EXPECT_EQ(kTimeout, connect_timeout_seconds());
+  StopConnectTimeout();
+  EXPECT_FALSE(IsConnectTimeoutStarted());
+  EXPECT_EQ(0, connect_timeout_seconds());
 }
 
 }  // namespace shill