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