blob: 2efb1409b2a6ded047bf2060b1860649b4abdce5 [file] [log] [blame]
Divya Thaluru808d1432017-08-06 16:36:36 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20#define BOOST_TEST_MODULE TNonblockingSSLServerTest
21#include <boost/test/unit_test.hpp>
22#include <boost/smart_ptr.hpp>
23#include <boost/shared_ptr.hpp>
24#include <boost/filesystem.hpp>
25#include <boost/format.hpp>
26
27#include "thrift/server/TNonblockingServer.h"
28#include "thrift/transport/TSSLSocket.h"
29#include "thrift/transport/TNonblockingSSLServerSocket.h"
30
31#include "gen-cpp/ParentService.h"
32
33#include <event.h>
34
35using namespace apache::thrift;
36using apache::thrift::concurrency::Guard;
37using apache::thrift::concurrency::Monitor;
38using apache::thrift::concurrency::Mutex;
39using apache::thrift::server::TServerEventHandler;
40using apache::thrift::transport::TSSLSocketFactory;
41using apache::thrift::transport::TSSLSocket;
42
43struct Handler : public test::ParentServiceIf {
44 void addString(const std::string& s) { strings_.push_back(s); }
45 void getStrings(std::vector<std::string>& _return) { _return = strings_; }
46 std::vector<std::string> strings_;
47
48 // dummy overrides not used in this test
49 int32_t incrementGeneration() { return 0; }
50 int32_t getGeneration() { return 0; }
51 void getDataWait(std::string&, const int32_t) {}
52 void onewayWait() {}
53 void exceptionWait(const std::string&) {}
54 void unexpectedExceptionWait(const std::string&) {}
55};
56
57boost::filesystem::path keyDir;
58boost::filesystem::path certFile(const std::string& filename)
59{
60 return keyDir / filename;
61}
62
63struct GlobalFixtureSSL
64{
65 GlobalFixtureSSL()
66 {
67 using namespace boost::unit_test::framework;
68 for (int i = 0; i < master_test_suite().argc; ++i)
69 {
70 BOOST_TEST_MESSAGE(boost::format("argv[%1%] = \"%2%\"") % i % master_test_suite().argv[i]);
71 }
72
73#ifdef __linux__
74 // OpenSSL calls send() without MSG_NOSIGPIPE so writing to a socket that has
75 // disconnected can cause a SIGPIPE signal...
76 signal(SIGPIPE, SIG_IGN);
77#endif
78
79 TSSLSocketFactory::setManualOpenSSLInitialization(true);
80 apache::thrift::transport::initializeOpenSSL();
81
82 keyDir = boost::filesystem::current_path().parent_path().parent_path().parent_path() / "test" / "keys";
83 if (!boost::filesystem::exists(certFile("server.crt")))
84 {
85 keyDir = boost::filesystem::path(master_test_suite().argv[master_test_suite().argc - 1]);
86 if (!boost::filesystem::exists(certFile("server.crt")))
87 {
88 throw std::invalid_argument("The last argument to this test must be the directory containing the test certificate(s).");
89 }
90 }
91 }
92
93 virtual ~GlobalFixtureSSL()
94 {
95 apache::thrift::transport::cleanupOpenSSL();
96#ifdef __linux__
97 signal(SIGPIPE, SIG_DFL);
98#endif
99 }
100};
101
102#if (BOOST_VERSION >= 105900)
103BOOST_GLOBAL_FIXTURE(GlobalFixtureSSL);
104#else
105BOOST_GLOBAL_FIXTURE(GlobalFixtureSSL)
106#endif
107
James E. King, III82ae9572017-08-05 12:23:54 -0400108stdcxx::shared_ptr<TSSLSocketFactory> createServerSocketFactory() {
109 stdcxx::shared_ptr<TSSLSocketFactory> pServerSocketFactory;
Divya Thaluru808d1432017-08-06 16:36:36 -0700110
111 pServerSocketFactory.reset(new TSSLSocketFactory());
112 pServerSocketFactory->ciphers("ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
113 pServerSocketFactory->loadCertificate(certFile("server.crt").string().c_str());
114 pServerSocketFactory->loadPrivateKey(certFile("server.key").string().c_str());
115 pServerSocketFactory->server(true);
116 return pServerSocketFactory;
117}
118
James E. King, III82ae9572017-08-05 12:23:54 -0400119stdcxx::shared_ptr<TSSLSocketFactory> createClientSocketFactory() {
120 stdcxx::shared_ptr<TSSLSocketFactory> pClientSocketFactory;
Divya Thaluru808d1432017-08-06 16:36:36 -0700121
122 pClientSocketFactory.reset(new TSSLSocketFactory());
123 pClientSocketFactory->authenticate(true);
124 pClientSocketFactory->loadCertificate(certFile("client.crt").string().c_str());
125 pClientSocketFactory->loadPrivateKey(certFile("client.key").string().c_str());
126 pClientSocketFactory->loadTrustedCertificates(certFile("CA.pem").string().c_str());
127 return pClientSocketFactory;
128}
129
130class Fixture {
131private:
132 struct ListenEventHandler : public TServerEventHandler {
133 public:
134 ListenEventHandler(Mutex* mutex) : listenMonitor_(mutex), ready_(false) {}
135
136 void preServe() /* override */ {
137 Guard g(listenMonitor_.mutex());
138 ready_ = true;
139 listenMonitor_.notify();
140 }
141
142 Monitor listenMonitor_;
143 bool ready_;
144 };
145
146 struct Runner : public apache::thrift::concurrency::Runnable {
147 int port;
James E. King, III82ae9572017-08-05 12:23:54 -0400148 stdcxx::shared_ptr<event_base> userEventBase;
149 stdcxx::shared_ptr<TProcessor> processor;
150 stdcxx::shared_ptr<server::TNonblockingServer> server;
151 stdcxx::shared_ptr<ListenEventHandler> listenHandler;
152 stdcxx::shared_ptr<TSSLSocketFactory> pServerSocketFactory;
153 stdcxx::shared_ptr<transport::TNonblockingSSLServerSocket> socket;
Divya Thaluru808d1432017-08-06 16:36:36 -0700154 Mutex mutex_;
155
156 Runner() {
157 listenHandler.reset(new ListenEventHandler(&mutex_));
158 }
159
160 virtual void run() {
161 // When binding to explicit port, allow retrying to workaround bind failures on ports in use
162 int retryCount = port ? 10 : 0;
163 pServerSocketFactory = createServerSocketFactory();
164 startServer(retryCount);
165 }
166
167 void readyBarrier() {
168 // block until server is listening and ready to accept connections
169 Guard g(mutex_);
170 while (!listenHandler->ready_) {
171 listenHandler->listenMonitor_.wait();
172 }
173 }
174 private:
175 void startServer(int retry_count) {
176 try {
177 socket.reset(new transport::TNonblockingSSLServerSocket(port, pServerSocketFactory));
178 server.reset(new server::TNonblockingServer(processor, socket));
179 server->setServerEventHandler(listenHandler);
180 server->setNumIOThreads(1);
181 if (userEventBase) {
182 server->registerEvents(userEventBase.get());
183 }
184 server->serve();
185 } catch (const transport::TTransportException&) {
186 if (retry_count > 0) {
187 ++port;
188 startServer(retry_count - 1);
189 } else {
190 throw;
191 }
192 }
193 }
194 };
195
196 struct EventDeleter {
197 void operator()(event_base* p) { event_base_free(p); }
198 };
199
200protected:
James E. King, III82ae9572017-08-05 12:23:54 -0400201 Fixture() : processor(new test::ParentServiceProcessor(stdcxx::make_shared<Handler>())) {}
Divya Thaluru808d1432017-08-06 16:36:36 -0700202
203 ~Fixture() {
204 if (server) {
205 server->stop();
206 }
207 if (thread) {
208 thread->join();
209 }
210 }
211
212 void setEventBase(event_base* user_event_base) {
213 userEventBase_.reset(user_event_base, EventDeleter());
214 }
215
216 int startServer(int port) {
James E. King, III82ae9572017-08-05 12:23:54 -0400217 stdcxx::shared_ptr<Runner> runner(new Runner);
Divya Thaluru808d1432017-08-06 16:36:36 -0700218 runner->port = port;
219 runner->processor = processor;
220 runner->userEventBase = userEventBase_;
221
James E. King, III82ae9572017-08-05 12:23:54 -0400222 apache::thrift::stdcxx::scoped_ptr<apache::thrift::concurrency::ThreadFactory> threadFactory(
Divya Thaluru808d1432017-08-06 16:36:36 -0700223 new apache::thrift::concurrency::PlatformThreadFactory(
cyyc109e012019-01-05 13:45:07 +0800224#if !USE_STD_THREAD
Divya Thaluru808d1432017-08-06 16:36:36 -0700225 concurrency::PlatformThreadFactory::OTHER, concurrency::PlatformThreadFactory::NORMAL,
226 1,
227#endif
228 false));
229 thread = threadFactory->newThread(runner);
230 thread->start();
231 runner->readyBarrier();
232
233 server = runner->server;
234 return runner->port;
235 }
236
237 bool canCommunicate(int serverPort) {
James E. King, III82ae9572017-08-05 12:23:54 -0400238 stdcxx::shared_ptr<TSSLSocketFactory> pClientSocketFactory = createClientSocketFactory();
239 stdcxx::shared_ptr<TSSLSocket> socket = pClientSocketFactory->createSocket("localhost", serverPort);
Divya Thaluru808d1432017-08-06 16:36:36 -0700240 socket->open();
James E. King, III82ae9572017-08-05 12:23:54 -0400241 test::ParentServiceClient client(stdcxx::make_shared<protocol::TBinaryProtocol>(
242 stdcxx::make_shared<transport::TFramedTransport>(socket)));
Divya Thaluru808d1432017-08-06 16:36:36 -0700243 client.addString("foo");
244 std::vector<std::string> strings;
245 client.getStrings(strings);
246 return strings.size() == 1 && !(strings[0].compare("foo"));
247 }
248
249private:
James E. King, III82ae9572017-08-05 12:23:54 -0400250 stdcxx::shared_ptr<event_base> userEventBase_;
251 stdcxx::shared_ptr<test::ParentServiceProcessor> processor;
Divya Thaluru808d1432017-08-06 16:36:36 -0700252protected:
James E. King, III82ae9572017-08-05 12:23:54 -0400253 stdcxx::shared_ptr<server::TNonblockingServer> server;
Divya Thaluru808d1432017-08-06 16:36:36 -0700254private:
James E. King, III82ae9572017-08-05 12:23:54 -0400255 stdcxx::shared_ptr<apache::thrift::concurrency::Thread> thread;
Divya Thaluru808d1432017-08-06 16:36:36 -0700256
257};
258
259BOOST_AUTO_TEST_SUITE(TNonblockingSSLServerTest)
260
261BOOST_FIXTURE_TEST_CASE(get_specified_port, Fixture) {
262 int specified_port = startServer(12345);
263 BOOST_REQUIRE_GE(specified_port, 12345);
264 BOOST_REQUIRE_EQUAL(server->getListenPort(), specified_port);
265 BOOST_CHECK(canCommunicate(specified_port));
266
267 server->stop();
268}
269
270BOOST_FIXTURE_TEST_CASE(get_assigned_port, Fixture) {
271 int specified_port = startServer(0);
272 BOOST_REQUIRE_EQUAL(specified_port, 0);
273 int assigned_port = server->getListenPort();
274 BOOST_REQUIRE_NE(assigned_port, 0);
275 BOOST_CHECK(canCommunicate(assigned_port));
276
277 server->stop();
278}
279
280BOOST_FIXTURE_TEST_CASE(provide_event_base, Fixture) {
281 event_base* eb = event_base_new();
282 setEventBase(eb);
283 startServer(0);
284
285 // assert that the server works
286 BOOST_CHECK(canCommunicate(server->getListenPort()));
287#if LIBEVENT_VERSION_NUMBER > 0x02010400
288 // also assert that the event_base is actually used when it's easy
289 BOOST_CHECK_GT(event_base_get_num_events(eb, EVENT_BASE_COUNT_ADDED), 0);
290#endif
291}
292
293BOOST_AUTO_TEST_SUITE_END()