shill: vpn: Parse INFO and STATE management interface messages.
This adds support for reconnect as well as missing OpenVPN management interface
unit tests.
BUG=chromium-os:26994
TEST=unit tests
Change-Id: Ib2c9911412154a81c8881618bf8b170346a21da0
Reviewed-on: https://gerrit.chromium.org/gerrit/19155
Commit-Ready: Darin Petkov <petkov@chromium.org>
Reviewed-by: Darin Petkov <petkov@chromium.org>
Tested-by: Darin Petkov <petkov@chromium.org>
diff --git a/mock_openvpn_driver.h b/mock_openvpn_driver.h
index 393c71b..7213582 100644
--- a/mock_openvpn_driver.h
+++ b/mock_openvpn_driver.h
@@ -16,6 +16,8 @@
MockOpenVPNDriver(const KeyValueStore &args);
virtual ~MockOpenVPNDriver();
+ MOCK_METHOD0(OnReconnecting, void());
+
private:
DISALLOW_COPY_AND_ASSIGN(MockOpenVPNDriver);
};
diff --git a/openvpn_driver.cc b/openvpn_driver.cc
index 6852cd4..ec78a8b 100644
--- a/openvpn_driver.cc
+++ b/openvpn_driver.cc
@@ -532,9 +532,20 @@
}
void OpenVPNDriver::Disconnect() {
+ VLOG(2) << __func__;
Cleanup(Service::kStateIdle);
}
+void OpenVPNDriver::OnReconnecting() {
+ VLOG(2) << __func__;
+ if (device_) {
+ device_->OnDisconnected();
+ }
+ if (service_) {
+ service_->SetState(Service::kStateAssociating);
+ }
+}
+
bool OpenVPNDriver::Load(StoreInterface *storage, const string &storage_id) {
for (size_t i = 0; i < arraysize(kProperties); i++) {
const string property = kProperties[i].property;
diff --git a/openvpn_driver.h b/openvpn_driver.h
index 91a9ff6..377ccd6 100644
--- a/openvpn_driver.h
+++ b/openvpn_driver.h
@@ -55,6 +55,8 @@
virtual bool Load(StoreInterface *storage, const std::string &storage_id);
virtual bool Save(StoreInterface *storage, const std::string &storage_id);
+ virtual void OnReconnecting();
+
virtual void InitPropertyStore(PropertyStore *store);
void ClearMappedProperty(const size_t &index, Error *error);
std::string GetMappedProperty(const size_t &index, Error *error);
@@ -77,6 +79,7 @@
FRIEND_TEST(OpenVPNDriverTest, Notify);
FRIEND_TEST(OpenVPNDriverTest, NotifyFail);
FRIEND_TEST(OpenVPNDriverTest, OnOpenVPNDied);
+ FRIEND_TEST(OpenVPNDriverTest, OnReconnecting);
FRIEND_TEST(OpenVPNDriverTest, ParseForeignOption);
FRIEND_TEST(OpenVPNDriverTest, ParseForeignOptions);
FRIEND_TEST(OpenVPNDriverTest, ParseIPConfiguration);
diff --git a/openvpn_driver_unittest.cc b/openvpn_driver_unittest.cc
index a4c531e..2032ef9 100644
--- a/openvpn_driver_unittest.cc
+++ b/openvpn_driver_unittest.cc
@@ -480,6 +480,15 @@
EXPECT_FALSE(driver_->service_);
}
+TEST_F(OpenVPNDriverTest, OnReconnecting) {
+ driver_->OnReconnecting(); // Expect no crash.
+ driver_->device_ = device_;
+ driver_->service_ = service_;
+ EXPECT_CALL(*device_, OnDisconnected());
+ EXPECT_CALL(*service_, SetState(Service::kStateAssociating));
+ driver_->OnReconnecting();
+}
+
MATCHER_P(IsIPAddress, address, "") {
IPAddress ip_address(IPAddress::kFamilyIPv4);
EXPECT_TRUE(ip_address.SetAddressFromString(address));
diff --git a/openvpn_management_server.cc b/openvpn_management_server.cc
index 71bff83..9c13ebd 100644
--- a/openvpn_management_server.cc
+++ b/openvpn_management_server.cc
@@ -13,6 +13,7 @@
#include <base/stringprintf.h>
#include "shill/event_dispatcher.h"
+#include "shill/openvpn_driver.h"
#include "shill/sockets.h"
using base::Bind;
@@ -46,7 +47,7 @@
return true;
}
- int socket = sockets_->Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+ int socket = sockets->Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socket < 0) {
PLOG(ERROR) << "Unable to create management server socket.";
return false;
@@ -68,6 +69,7 @@
}
VLOG(2) << "Listening socket: " << socket;
+ sockets_ = sockets;
socket_ = socket;
ready_handler_.reset(
dispatcher->CreateReadyHandler(
@@ -121,22 +123,59 @@
void OpenVPNManagementServer::ProcessMessage(const string &message) {
VLOG(2) << __func__ << "(" << message << ")";
- if (StartsWithASCII(message, ">INFO:", true)) {
- return;
+ LOG_IF(WARNING,
+ !ProcessInfoMessage(message) &&
+ !ProcessNeedPasswordMessage(message) &&
+ !ProcessFailedPasswordMessage(message) &&
+ !ProcessStateMessage(message))
+ << "OpenVPN management message ignored: " << message;
+}
+
+bool OpenVPNManagementServer::ProcessInfoMessage(const string &message) {
+ return StartsWithASCII(message, ">INFO:", true);
+}
+
+bool OpenVPNManagementServer::ProcessNeedPasswordMessage(
+ const string &message) {
+ if (!StartsWithASCII(message, ">PASSWORD:Need ", true)) {
+ return false;
}
- if (StartsWithASCII(message, ">PASSWORD:Need ", true)) {
- NOTIMPLEMENTED();
- return;
+ NOTIMPLEMENTED();
+ return true;
+}
+
+bool OpenVPNManagementServer::ProcessFailedPasswordMessage(
+ const string &message) {
+ if (!StartsWithASCII(message, ">PASSWORD:Verification Failed:", true)) {
+ return false;
}
- if (StartsWithASCII(message, ">PASSWORD:Verification Failed:", true)) {
- NOTIMPLEMENTED();
- return;
+ NOTIMPLEMENTED();
+ return true;
+}
+
+// >STATE:* message support. State messages are of the form:
+// >STATE:<date>,<state>,<detail>,<local-ip>,<remote-ip>
+// where:
+// <date> is the current time (since epoch) in seconds
+// <state> is one of:
+// INITIAL, CONNECTING, WAIT, AUTH, GET_CONFIG, ASSIGN_IP, ADD_ROUTES,
+// CONNECTED, RECONNECTING, EXITING, RESOLVE, TCP_CONNECT
+// <detail> is a free-form string giving details about the state change
+// <local-ip> is a dotted-quad for the local IPv4 address (when available)
+// <remote-ip> is a dotted-quad for the remote IPv4 address (when available)
+bool OpenVPNManagementServer::ProcessStateMessage(const string &message) {
+ if (!StartsWithASCII(message, ">STATE:", true)) {
+ return false;
}
- if (StartsWithASCII(message, ">STATE:", true)) {
- NOTIMPLEMENTED();
- return;
+ vector<string> details;
+ SplitString(message, ',', &details);
+ if (details.size() > 1) {
+ if (details[1] == "RECONNECTING") {
+ driver_->OnReconnecting();
+ }
+ // The rest of the states are currently ignored.
}
- LOG(WARNING) << "OpenVPN management message ignored: " << message;
+ return true;
}
void OpenVPNManagementServer::Send(const string &data) {
diff --git a/openvpn_management_server.h b/openvpn_management_server.h
index 9263f12..4ff41b7 100644
--- a/openvpn_management_server.h
+++ b/openvpn_management_server.h
@@ -8,6 +8,7 @@
#include <base/basictypes.h>
#include <base/cancelable_callback.h>
#include <base/memory/weak_ptr.h>
+#include <gtest/gtest_prod.h> // for FRIEND_TEST
namespace shill {
@@ -28,6 +29,18 @@
void Stop();
private:
+ friend class OpenVPNManagementServerTest;
+ FRIEND_TEST(OpenVPNManagementServerTest, OnInput);
+ FRIEND_TEST(OpenVPNManagementServerTest, OnReady);
+ FRIEND_TEST(OpenVPNManagementServerTest, OnReadyAcceptFail);
+ FRIEND_TEST(OpenVPNManagementServerTest, ProcessInfoMessage);
+ FRIEND_TEST(OpenVPNManagementServerTest, ProcessMessage);
+ FRIEND_TEST(OpenVPNManagementServerTest, ProcessStateMessage);
+ FRIEND_TEST(OpenVPNManagementServerTest, Send);
+ FRIEND_TEST(OpenVPNManagementServerTest, SendState);
+ FRIEND_TEST(OpenVPNManagementServerTest, Start);
+ FRIEND_TEST(OpenVPNManagementServerTest, Stop);
+
// IO handler callbacks.
void OnReady(int fd);
void OnInput(InputData *data);
@@ -36,6 +49,10 @@
void SendState(const std::string &state);
void ProcessMessage(const std::string &message);
+ bool ProcessInfoMessage(const std::string &message);
+ bool ProcessNeedPasswordMessage(const std::string &message);
+ bool ProcessFailedPasswordMessage(const std::string &message);
+ bool ProcessStateMessage(const std::string &message);
OpenVPNDriver *driver_;
base::WeakPtrFactory<OpenVPNManagementServer> weak_ptr_factory_;
diff --git a/openvpn_management_server_unittest.cc b/openvpn_management_server_unittest.cc
index 7ebcae7..2e8b037 100644
--- a/openvpn_management_server_unittest.cc
+++ b/openvpn_management_server_unittest.cc
@@ -4,13 +4,32 @@
#include "shill/openvpn_management_server.h"
+#include <netinet/in.h>
+
#include <gtest/gtest.h>
#include "shill/key_value_store.h"
+#include "shill/mock_event_dispatcher.h"
#include "shill/mock_openvpn_driver.h"
+#include "shill/mock_sockets.h"
+
+using std::string;
+using testing::_;
+using testing::Return;
+using testing::ReturnNew;
namespace shill {
+namespace {
+MATCHER_P(CallbackEq, callback, "") {
+ return arg.Equals(callback);
+}
+
+MATCHER_P(VoidStringEq, value, "") {
+ return value == reinterpret_cast<const char *>(arg);
+}
+} // namespace {}
+
class OpenVPNManagementServerTest : public testing::Test {
public:
OpenVPNManagementServerTest()
@@ -19,14 +38,171 @@
virtual ~OpenVPNManagementServerTest() {}
+ void SetSockets() { server_.sockets_ = &sockets_; }
+ void SetDispatcher() { server_.dispatcher_ = &dispatcher_; }
+ void ExpectNotStarted() { EXPECT_TRUE(server_.sockets_ == NULL); }
+
+ void ExpectSend(int socket, const string &value) {
+ EXPECT_CALL(sockets_, Send(socket, VoidStringEq(value), value.size(), 0))
+ .WillOnce(Return(value.size()));
+ }
+
+ InputData CreateInputDataFromString(const string &str) {
+ InputData data(
+ reinterpret_cast<unsigned char *>(const_cast<char *>(str.data())),
+ str.size());
+ return data;
+ }
+
protected:
KeyValueStore args_;
MockOpenVPNDriver driver_;
OpenVPNManagementServer server_;
+ MockSockets sockets_;
+ MockEventDispatcher dispatcher_;
};
+TEST_F(OpenVPNManagementServerTest, StartStarted) {
+ SetSockets();
+ EXPECT_TRUE(server_.Start(NULL, NULL));
+}
+
+TEST_F(OpenVPNManagementServerTest, StartSocketFail) {
+ EXPECT_CALL(sockets_, Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
+ .WillOnce(Return(-1));
+ EXPECT_FALSE(server_.Start(NULL, &sockets_));
+ ExpectNotStarted();
+}
+
+TEST_F(OpenVPNManagementServerTest, StartGetSockNameFail) {
+ const int kSocket = 123;
+ EXPECT_CALL(sockets_, Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
+ .WillOnce(Return(kSocket));
+ EXPECT_CALL(sockets_, Bind(kSocket, _, _)).WillOnce(Return(0));
+ EXPECT_CALL(sockets_, Listen(kSocket, 1)).WillOnce(Return(0));
+ EXPECT_CALL(sockets_, GetSockName(kSocket, _, _)).WillOnce(Return(-1));
+ EXPECT_CALL(sockets_, Close(kSocket)).WillOnce(Return(0));
+ EXPECT_FALSE(server_.Start(NULL, &sockets_));
+ ExpectNotStarted();
+}
+
TEST_F(OpenVPNManagementServerTest, Start) {
- // TODO(petkov): Add unit tests.
+ const int kSocket = 123;
+ EXPECT_CALL(sockets_, Socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))
+ .WillOnce(Return(kSocket));
+ EXPECT_CALL(sockets_, Bind(kSocket, _, _)).WillOnce(Return(0));
+ EXPECT_CALL(sockets_, Listen(kSocket, 1)).WillOnce(Return(0));
+ EXPECT_CALL(sockets_, GetSockName(kSocket, _, _)).WillOnce(Return(0));
+ EXPECT_CALL(dispatcher_,
+ CreateReadyHandler(kSocket, IOHandler::kModeInput,
+ CallbackEq(server_.ready_callback_)))
+ .WillOnce(ReturnNew<IOHandler>());
+ EXPECT_TRUE(server_.Start(&dispatcher_, &sockets_));
+ EXPECT_EQ(&sockets_, server_.sockets_);
+ EXPECT_EQ(kSocket, server_.socket_);
+ EXPECT_TRUE(server_.ready_handler_.get());
+ EXPECT_EQ(&dispatcher_, server_.dispatcher_);
+}
+
+TEST_F(OpenVPNManagementServerTest, Stop) {
+ SetSockets();
+ server_.input_handler_.reset(new IOHandler());
+ const int kConnectedSocket = 234;
+ server_.connected_socket_ = kConnectedSocket;
+ EXPECT_CALL(sockets_, Close(kConnectedSocket)).WillOnce(Return(0));
+ SetDispatcher();
+ server_.ready_handler_.reset(new IOHandler());
+ const int kSocket = 345;
+ server_.socket_ = kSocket;
+ EXPECT_CALL(sockets_, Close(kSocket)).WillOnce(Return(0));
+ server_.Stop();
+ EXPECT_FALSE(server_.input_handler_.get());
+ EXPECT_EQ(-1, server_.connected_socket_);
+ EXPECT_FALSE(server_.dispatcher_);
+ EXPECT_FALSE(server_.ready_handler_.get());
+ EXPECT_EQ(-1, server_.socket_);
+ ExpectNotStarted();
+}
+
+TEST_F(OpenVPNManagementServerTest, OnReadyAcceptFail) {
+ const int kSocket = 333;
+ SetSockets();
+ EXPECT_CALL(sockets_, Accept(kSocket, NULL, NULL)).WillOnce(Return(-1));
+ server_.OnReady(kSocket);
+ EXPECT_EQ(-1, server_.connected_socket_);
+}
+
+TEST_F(OpenVPNManagementServerTest, OnReady) {
+ const int kSocket = 111;
+ const int kConnectedSocket = 112;
+ SetSockets();
+ SetDispatcher();
+ EXPECT_CALL(sockets_, Accept(kSocket, NULL, NULL))
+ .WillOnce(Return(kConnectedSocket));
+ server_.ready_handler_.reset(new IOHandler());
+ EXPECT_CALL(dispatcher_,
+ CreateInputHandler(kConnectedSocket,
+ CallbackEq(server_.input_callback_)))
+ .WillOnce(ReturnNew<IOHandler>());
+ ExpectSend(kConnectedSocket, "state on\n");
+ server_.OnReady(kSocket);
+ EXPECT_EQ(kConnectedSocket, server_.connected_socket_);
+ EXPECT_FALSE(server_.ready_handler_.get());
+ EXPECT_TRUE(server_.input_handler_.get());
+}
+
+TEST_F(OpenVPNManagementServerTest, OnInput) {
+ {
+ string s;
+ InputData data = CreateInputDataFromString(s);
+ server_.OnInput(&data);
+ }
+ {
+ string s = "foo\n"
+ ">INFO:...\n"
+ ">STATE:123,RECONNECTING,detail,...,...";
+ InputData data = CreateInputDataFromString(s);
+ EXPECT_CALL(driver_, OnReconnecting());
+ server_.OnInput(&data);
+ }
+}
+
+TEST_F(OpenVPNManagementServerTest, ProcessMessage) {
+ server_.ProcessMessage("foo");
+ server_.ProcessMessage(">INFO:");
+
+ EXPECT_CALL(driver_, OnReconnecting());
+ server_.ProcessMessage(">STATE:123,RECONNECTING,detail,...,...");
+}
+
+TEST_F(OpenVPNManagementServerTest, ProcessInfoMessage) {
+ EXPECT_FALSE(server_.ProcessInfoMessage("foo"));
+ EXPECT_TRUE(server_.ProcessInfoMessage(">INFO:"));
+}
+
+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,...,..."));
+}
+
+TEST_F(OpenVPNManagementServerTest, Send) {
+ const int kSocket = 555;
+ const char kMessage[] = "foo\n";
+ server_.connected_socket_ = kSocket;
+ SetSockets();
+ ExpectSend(kSocket, kMessage);
+ server_.Send(kMessage);
+}
+
+TEST_F(OpenVPNManagementServerTest, SendState) {
+ const int kSocket = 555;
+ server_.connected_socket_ = kSocket;
+ SetSockets();
+ ExpectSend(kSocket, "state off\n");
+ server_.SendState("off");
}
} // namespace shill