1 /*
2 * Copyright (c) 2018-present, Facebook, Inc.
3 * All rights reserved.
4 *
5 * This source code is licensed under the BSD-style license found in the
6 * LICENSE file in the root directory of this source tree.
7 */
8
9 #include <fizz/crypto/aead/AESGCM128.h>
10 #include <fizz/crypto/aead/OpenSSLEVPCipher.h>
11 #include <fizz/experimental/batcher/Batcher.h>
12 #include <fizz/experimental/server/BatchSignatureAsyncSelfCert.h>
13 #include <fizz/extensions/delegatedcred/DelegatedCredentialCertManager.h>
14 #include <fizz/protocol/DefaultCertificateVerifier.h>
15 #include <fizz/server/AsyncFizzServer.h>
16 #include <fizz/server/SlidingBloomReplayCache.h>
17 #include <fizz/server/TicketTypes.h>
18 #include <fizz/tool/FizzCommandCommon.h>
19 #include <fizz/util/KeyLogWriter.h>
20 #include <fizz/util/Parse.h>
21
22 #include <folly/Format.h>
23 #include <folly/executors/IOThreadPoolExecutor.h>
24 #include <folly/futures/Future.h>
25 #include <folly/io/async/AsyncSSLSocket.h>
26 #include <folly/io/async/AsyncServerSocket.h>
27
28 #include <string>
29 #include <vector>
30
31 using namespace fizz::server;
32 using namespace folly;
33
34 namespace fizz {
35 namespace tool {
36 namespace {
37
38 /**
39 * Server Side Benchmark Tool:
40 * We simpilfy the settings to focus on the performance evaluation.
41 *
42 * - Enforce verification of server certificate
43 * - Enforce TLS_AES_128_GCM_SHA256 for cipher
44 * - No early data in ClientHello
45 * - No Application Layer Protocol Negotiation (ALPN)
46 * - No certificate compression
47 * - No client side certificate verification
48 * - Enforce loop of the server
49 * - Disable HTTP
50 */
51
printUsage()52 void printUsage() {
53 // clang-format off
54 std::cerr
55 << "Usage: server_benchmark args\n"
56 << "\n"
57 << "Supported arguments:\n"
58 << " -accept port (set port to accept connections on. Default: 8443)\n"
59 << " -threads num (set # of threads used for handling TLS handshakes)\n"
60 << " -cert cert (PEM format server certificate. Default: none, generates a self-signed cert)\n"
61 << " -key key (PEM format private key for server certificate. Default: none)\n"
62 << " -pass password (private key password. Default: none)\n"
63 << " -backlog num (maximum number of queued connections; a small backlog can lead to potential\n"
64 << " connection drop or long latency. Default: 100)\n"
65 << " -batch (use the batch signature scheme ecdsa_secp256r1_sha256_batch)\n";
66 // clang-format on
67 }
68
69 class ServerTask : public AsyncFizzServer::HandshakeCallback {
70 public:
ServerTask(EventBase * evb,std::shared_ptr<FizzServerContext> serverContext)71 explicit ServerTask(
72 EventBase* evb,
73 std::shared_ptr<FizzServerContext> serverContext)
74 : evb_(evb), serverContext_(serverContext) {}
75
start(int fd)76 void start(int fd) {
77 auto sock = new AsyncSocket(evb_, folly::NetworkSocket::fromFd(fd));
78 fizzServer_ = AsyncFizzServer::UniquePtr(
79 new AsyncFizzServer(AsyncSocket::UniquePtr(sock), serverContext_));
80 fizzServer_->accept(this);
81 }
82
endTask()83 void endTask() {
84 delete this;
85 }
86
fizzHandshakeSuccess(AsyncFizzServer *)87 void fizzHandshakeSuccess(AsyncFizzServer*) noexcept override {
88 endTask();
89 }
90
fizzHandshakeError(AsyncFizzServer *,exception_wrapper ex)91 void fizzHandshakeError(AsyncFizzServer*, exception_wrapper ex) noexcept
92 override {
93 VLOG(1) << "Error: " << ex.what();
94 endTask();
95 }
96
fizzHandshakeAttemptFallback(std::unique_ptr<folly::IOBuf>)97 virtual void fizzHandshakeAttemptFallback(
98 std::unique_ptr<folly::IOBuf>) noexcept override {
99 endTask();
100 }
101
102 private:
103 EventBase* evb_;
104 std::shared_ptr<FizzServerContext> serverContext_;
105 AsyncFizzServer::UniquePtr fizzServer_;
106 };
107
108 class FizzServerAcceptor : AsyncServerSocket::AcceptCallback {
109 public:
FizzServerAcceptor(uint16_t port,size_t backlog,EventBase * evb,std::shared_ptr<FizzServerContext> serverContext,std::shared_ptr<IOThreadPoolExecutor> threadExe)110 explicit FizzServerAcceptor(
111 uint16_t port,
112 size_t backlog,
113 EventBase* evb,
114 std::shared_ptr<FizzServerContext> serverContext,
115 std::shared_ptr<IOThreadPoolExecutor> threadExe)
116 : evb_(evb), serverContext_(serverContext), threadExe_(threadExe) {
117 socket_ = AsyncServerSocket::UniquePtr(new AsyncServerSocket(evb_));
118 socket_->bind(port);
119 socket_->listen(backlog);
120 socket_->addAcceptCallback(this, evb_);
121 socket_->startAccepting();
122 LOG(INFO) << "Started listening on " << socket_->getAddress();
123 }
124
connectionAccepted(folly::NetworkSocket fdNetworkSocket,const SocketAddress & clientAddr,AcceptInfo)125 void connectionAccepted(
126 folly::NetworkSocket fdNetworkSocket,
127 const SocketAddress& clientAddr,
128 AcceptInfo /* info */) noexcept override {
129 int fd = fdNetworkSocket.toFd();
130 LOG(INFO) << "Connection accepted from " << clientAddr;
131
132 via(threadExe_->weakRef()).thenValue([=](auto&&) {
133 auto evb = folly::EventBaseManager::get()->getEventBase();
134 auto task = new ServerTask(evb, serverContext_);
135 task->start(fd);
136 });
137 }
138
acceptError(folly::exception_wrapper ex)139 void acceptError(folly::exception_wrapper ex) noexcept override {
140 LOG(ERROR) << "Failed to accept connection: " << ex;
141 }
142
143 private:
144 EventBase* evb_;
145 std::shared_ptr<FizzServerContext> serverContext_;
146 std::shared_ptr<IOThreadPoolExecutor> threadExe_;
147 AsyncServerSocket::UniquePtr socket_;
148 };
149
150 } // namespace
151
fizzServerBenchmarkCommand(const std::vector<std::string> & args)152 int fizzServerBenchmarkCommand(const std::vector<std::string>& args) {
153 // configurable parameters and their default values
154 uint16_t port = 8443;
155 std::string certPath;
156 std::string keyPath;
157 std::string keyPass;
158 int threadNum = 1;
159 size_t backlog = 100;
160 std::vector<std::vector<CipherSuite>> ciphers{
161 {CipherSuite::TLS_AES_128_GCM_SHA256}};
162 std::vector<ProtocolVersion> versions{
163 ProtocolVersion::tls_1_3, ProtocolVersion::tls_1_3_28};
164 bool enableBatch = false;
165 size_t batchNumMsgThreshold = 0;
166 std::shared_ptr<SynchronizedBatcher<Sha256>> batcher;
167
168 // Argument Handler Map
169 // clang-format off
170 FizzArgHandlerMap handlers = {
171 {"-accept", {true, [&port](const std::string& arg) {
172 port = portFromString(arg, true);
173 }}},
174 {"-cert", {true, [&certPath](const std::string& arg) {
175 certPath = arg;
176 }}},
177 {"-key", {true, [&keyPath](const std::string& arg) {
178 keyPath = arg;
179 }}},
180 {"-pass", {true, [&keyPass](const std::string& arg) {
181 keyPass = arg;
182 }}},
183 {"-threads", {true, [&threadNum](const std::string& arg) {
184 threadNum = std::stoi(arg);
185 }}},
186 {"-backlog", {true, [&backlog](const std::string& arg) {
187 backlog = std::stoi(arg);
188 }}},
189 {"-batch", {true, [&enableBatch, &batchNumMsgThreshold](const std::string& arg) {
190 enableBatch = true;
191 batchNumMsgThreshold = std::stoi(arg);
192 }}}
193 };
194 // clang-format on
195
196 // parse arguments
197 try {
198 if (parseArguments(args, handlers, printUsage)) {
199 // Parsing failed, return
200 return 1;
201 }
202 } catch (const std::exception& e) {
203 LOG(ERROR) << "Error: " << e.what();
204 return 1;
205 }
206 if (certPath.empty() || keyPath.empty()) {
207 LOG(ERROR)
208 << "-cert and -key are both required for the server benchmark tool";
209 return 1;
210 }
211
212 // set up the IO Thread Pool and get the EventBase
213 EventBase evb; // main thread event base, used for accepting new connections
214 auto threadExe = std::make_shared<IOThreadPoolExecutor>(
215 threadNum,
216 std::make_shared<NamedThreadFactory>("ServerBenchmarkPool"),
217 folly::EventBaseManager::get(),
218 true);
219
220 // prepare FizzServerContext
221 auto serverContext = std::make_shared<FizzServerContext>();
222 serverContext->setSupportedCiphers(std::move(ciphers));
223 auto ticketCipher = std::make_shared<
224 Aead128GCMTicketCipher<TicketCodec<CertificateStorage::X509>>>(
225 std::make_shared<OpenSSLFactory>(), std::make_shared<CertManager>());
226 auto ticketSeed = RandomGenerator<32>().generateRandom();
227 ticketCipher->setTicketSecrets({{range(ticketSeed)}});
228 serverContext->setTicketCipher(ticketCipher);
229 serverContext->setSupportedVersions(std::move(versions));
230
231 // load Server's certificate and private key
232 std::unique_ptr<CertManager> certManager =
233 std::make_unique<fizz::extensions::DelegatedCredentialCertManager>();
234 std::vector<std::shared_ptr<CertificateCompressor>> compressors;
235 {
236 std::string certData;
237 std::string keyData;
238 if (!readFile(certPath.c_str(), certData)) {
239 LOG(ERROR) << "Failed to read certificate";
240 return 1;
241 } else if (!readFile(keyPath.c_str(), keyData)) {
242 LOG(ERROR) << "Failed to read private key";
243 return 1;
244 }
245 std::unique_ptr<SelfCert> cert;
246 if (!keyPass.empty()) {
247 cert = CertUtils::makeSelfCert(certData, keyData, keyPass, compressors);
248 } else {
249 cert = CertUtils::makeSelfCert(certData, keyData, compressors);
250 }
251 std::shared_ptr<SelfCert> sharedCert = std::move(cert);
252 if (enableBatch) {
253 batcher = std::make_shared<SynchronizedBatcher<Sha256>>(
254 batchNumMsgThreshold, sharedCert, CertificateVerifyContext::Server);
255 auto batchCert =
256 std::make_shared<BatchSignatureAsyncSelfCert<Sha256>>(batcher);
257 serverContext->setSupportedSigSchemes(batchCert->getSigSchemes());
258 certManager->addCert(batchCert, true);
259 } else {
260 serverContext->setSupportedSigSchemes(sharedCert->getSigSchemes());
261 certManager->addCert(sharedCert, true);
262 }
263 }
264 serverContext->setCertManager(std::move(certManager));
265
266 // start to listen to new connections
267 FizzServerAcceptor acceptor(port, backlog, &evb, serverContext, threadExe);
268 evb.loop();
269 return 0;
270 }
271
272 } // namespace tool
273 } // namespace fizz
274