1 // Copyright (c) 2009 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/tools/flip_server/sm_connection.h"
6
7 #include <errno.h>
8 #include <netinet/tcp.h>
9 #include <sys/socket.h>
10
11 #include <list>
12 #include <string>
13
14 #include "net/tools/flip_server/constants.h"
15 #include "net/tools/flip_server/flip_config.h"
16 #include "net/tools/flip_server/http_interface.h"
17 #include "net/tools/flip_server/spdy_interface.h"
18 #include "net/tools/flip_server/spdy_ssl.h"
19 #include "net/tools/flip_server/streamer_interface.h"
20
21 namespace net {
22
23 // static
24 bool SMConnection::force_spdy_ = false;
25
SMConnection(EpollServer * epoll_server,SSLState * ssl_state,MemoryCache * memory_cache,FlipAcceptor * acceptor,std::string log_prefix)26 SMConnection::SMConnection(EpollServer* epoll_server,
27 SSLState* ssl_state,
28 MemoryCache* memory_cache,
29 FlipAcceptor* acceptor,
30 std::string log_prefix)
31 : last_read_time_(0),
32 fd_(-1),
33 events_(0),
34 registered_in_epoll_server_(false),
35 initialized_(false),
36 protocol_detected_(false),
37 connection_complete_(false),
38 connection_pool_(NULL),
39 epoll_server_(epoll_server),
40 ssl_state_(ssl_state),
41 memory_cache_(memory_cache),
42 acceptor_(acceptor),
43 read_buffer_(kSpdySegmentSize * 40),
44 sm_spdy_interface_(NULL),
45 sm_http_interface_(NULL),
46 sm_streamer_interface_(NULL),
47 sm_interface_(NULL),
48 log_prefix_(log_prefix),
49 max_bytes_sent_per_dowrite_(4096),
50 ssl_(NULL) {
51 }
52
~SMConnection()53 SMConnection::~SMConnection() {
54 if (initialized())
55 Reset();
56 }
57
epoll_server()58 EpollServer* SMConnection::epoll_server() {
59 return epoll_server_;
60 }
61
ReadyToSend()62 void SMConnection::ReadyToSend() {
63 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
64 << "Setting ready to send: EPOLLIN | EPOLLOUT";
65 epoll_server_->SetFDReady(fd_, EPOLLIN | EPOLLOUT);
66 }
67
EnqueueDataFrame(DataFrame * df)68 void SMConnection::EnqueueDataFrame(DataFrame* df) {
69 output_list_.push_back(df);
70 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "EnqueueDataFrame: "
71 << "size = " << df->size << ": Setting FD ready.";
72 ReadyToSend();
73 }
74
InitSMConnection(SMConnectionPoolInterface * connection_pool,SMInterface * sm_interface,EpollServer * epoll_server,int fd,std::string server_ip,std::string server_port,std::string remote_ip,bool use_ssl)75 void SMConnection::InitSMConnection(SMConnectionPoolInterface* connection_pool,
76 SMInterface* sm_interface,
77 EpollServer* epoll_server,
78 int fd,
79 std::string server_ip,
80 std::string server_port,
81 std::string remote_ip,
82 bool use_ssl) {
83 if (initialized_) {
84 LOG(FATAL) << "Attempted to initialize already initialized server";
85 return;
86 }
87
88 client_ip_ = remote_ip;
89
90 if (fd == -1) {
91 // If fd == -1, then we are initializing a new connection that will
92 // connect to the backend.
93 //
94 // ret: -1 == error
95 // 0 == connection in progress
96 // 1 == connection complete
97 // TODO(kelindsay): is_numeric_host_address value needs to be detected
98 server_ip_ = server_ip;
99 server_port_ = server_port;
100 int ret = CreateConnectedSocket(&fd_,
101 server_ip,
102 server_port,
103 true,
104 acceptor_->disable_nagle_);
105
106 if (ret < 0) {
107 LOG(ERROR) << "-1 Could not create connected socket";
108 return;
109 } else if (ret == 1) {
110 DCHECK_NE(-1, fd_);
111 connection_complete_ = true;
112 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
113 << "Connection complete to: " << server_ip_ << ":"
114 << server_port_ << " ";
115 }
116 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
117 << "Connecting to server: " << server_ip_ << ":"
118 << server_port_ << " ";
119 } else {
120 // If fd != -1 then we are initializing a connection that has just been
121 // accepted from the listen socket.
122 connection_complete_ = true;
123 if (epoll_server_ && registered_in_epoll_server_ && fd_ != -1) {
124 epoll_server_->UnregisterFD(fd_);
125 }
126 if (fd_ != -1) {
127 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
128 << "Closing pre-existing fd";
129 close(fd_);
130 fd_ = -1;
131 }
132
133 fd_ = fd;
134 }
135
136 registered_in_epoll_server_ = false;
137 // Set the last read time here as the idle checker will start from
138 // now.
139 last_read_time_ = time(NULL);
140 initialized_ = true;
141
142 connection_pool_ = connection_pool;
143 epoll_server_ = epoll_server;
144
145 if (sm_interface) {
146 sm_interface_ = sm_interface;
147 protocol_detected_ = true;
148 }
149
150 read_buffer_.Clear();
151
152 epoll_server_->RegisterFD(fd_, this, EPOLLIN | EPOLLOUT | EPOLLET);
153
154 if (use_ssl) {
155 ssl_ = CreateSSLContext(ssl_state_->ssl_ctx);
156 SSL_set_fd(ssl_, fd_);
157 PrintSslError();
158 }
159 }
160
CorkSocket()161 void SMConnection::CorkSocket() {
162 int state = 1;
163 int rv = setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &state, sizeof(state));
164 if (rv < 0)
165 VLOG(1) << "setsockopt(CORK): " << errno;
166 }
167
UncorkSocket()168 void SMConnection::UncorkSocket() {
169 int state = 0;
170 int rv = setsockopt(fd_, IPPROTO_TCP, TCP_CORK, &state, sizeof(state));
171 if (rv < 0)
172 VLOG(1) << "setsockopt(CORK): " << errno;
173 }
174
Send(const char * data,int len,int flags)175 int SMConnection::Send(const char* data, int len, int flags) {
176 int rv = 0;
177 CorkSocket();
178 if (ssl_) {
179 ssize_t bytes_written = 0;
180 // Write smallish chunks to SSL so that we don't have large
181 // multi-packet TLS records to receive before being able to handle
182 // the data. We don't have to be too careful here, because our data
183 // frames are already getting chunked appropriately, and those are
184 // the most likely "big" frames.
185 while (len > 0) {
186 const int kMaxTLSRecordSize = 1500;
187 const char* ptr = &(data[bytes_written]);
188 int chunksize = std::min(len, kMaxTLSRecordSize);
189 rv = SSL_write(ssl_, ptr, chunksize);
190 VLOG(2) << "SSLWrite(" << chunksize << " bytes): " << rv;
191 if (rv <= 0) {
192 switch (SSL_get_error(ssl_, rv)) {
193 case SSL_ERROR_WANT_READ:
194 case SSL_ERROR_WANT_WRITE:
195 case SSL_ERROR_WANT_ACCEPT:
196 case SSL_ERROR_WANT_CONNECT:
197 rv = -2;
198 break;
199 default:
200 PrintSslError();
201 break;
202 }
203 break;
204 }
205 bytes_written += rv;
206 len -= rv;
207 if (rv != chunksize)
208 break; // If we couldn't write everything, we're implicitly stalled
209 }
210 // If we wrote some data, return that count. Otherwise
211 // return the stall error.
212 if (bytes_written > 0)
213 rv = bytes_written;
214 } else {
215 rv = send(fd_, data, len, flags);
216 }
217 if (!(flags & MSG_MORE))
218 UncorkSocket();
219 return rv;
220 }
221
OnRegistration(EpollServer * eps,int fd,int event_mask)222 void SMConnection::OnRegistration(EpollServer* eps, int fd, int event_mask) {
223 registered_in_epoll_server_ = true;
224 }
225
OnEvent(int fd,EpollEvent * event)226 void SMConnection::OnEvent(int fd, EpollEvent* event) {
227 events_ |= event->in_events;
228 HandleEvents();
229 if (events_) {
230 event->out_ready_mask = events_;
231 events_ = 0;
232 }
233 }
234
OnUnregistration(int fd,bool replaced)235 void SMConnection::OnUnregistration(int fd, bool replaced) {
236 registered_in_epoll_server_ = false;
237 }
238
OnShutdown(EpollServer * eps,int fd)239 void SMConnection::OnShutdown(EpollServer* eps, int fd) {
240 Cleanup("OnShutdown");
241 return;
242 }
243
Cleanup(const char * cleanup)244 void SMConnection::Cleanup(const char* cleanup) {
245 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Cleanup: " << cleanup;
246 if (!initialized_)
247 return;
248 Reset();
249 if (connection_pool_)
250 connection_pool_->SMConnectionDone(this);
251 if (sm_interface_)
252 sm_interface_->ResetForNewConnection();
253 last_read_time_ = 0;
254 }
255
HandleEvents()256 void SMConnection::HandleEvents() {
257 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Received: "
258 << EpollServer::EventMaskToString(events_).c_str();
259
260 if (events_ & EPOLLIN) {
261 if (!DoRead())
262 goto handle_close_or_error;
263 }
264
265 if (events_ & EPOLLOUT) {
266 // Check if we have connected or not
267 if (connection_complete_ == false) {
268 int sock_error;
269 socklen_t sock_error_len = sizeof(sock_error);
270 int ret = getsockopt(fd_, SOL_SOCKET, SO_ERROR, &sock_error,
271 &sock_error_len);
272 if (ret != 0) {
273 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
274 << "getsockopt error: " << errno << ": " << strerror(errno);
275 goto handle_close_or_error;
276 }
277 if (sock_error == 0) {
278 connection_complete_ = true;
279 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
280 << "Connection complete to " << server_ip_ << ":"
281 << server_port_ << " ";
282 } else if (sock_error == EINPROGRESS) {
283 return;
284 } else {
285 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
286 << "error connecting to server";
287 goto handle_close_or_error;
288 }
289 }
290 if (!DoWrite())
291 goto handle_close_or_error;
292 }
293
294 if (events_ & (EPOLLHUP | EPOLLERR)) {
295 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "!!! Got HUP or ERR";
296 goto handle_close_or_error;
297 }
298 return;
299
300 handle_close_or_error:
301 Cleanup("HandleEvents");
302 }
303
304 // Decide if SPDY was negotiated.
WasSpdyNegotiated()305 bool SMConnection::WasSpdyNegotiated() {
306 if (force_spdy())
307 return true;
308
309 // If this is an SSL connection, check if NPN specifies SPDY.
310 if (ssl_) {
311 const unsigned char *npn_proto;
312 unsigned int npn_proto_len;
313 SSL_get0_next_proto_negotiated(ssl_, &npn_proto, &npn_proto_len);
314 if (npn_proto_len > 0) {
315 std::string npn_proto_str((const char *)npn_proto, npn_proto_len);
316 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
317 << "NPN protocol detected: " << npn_proto_str;
318 if (!strncmp(reinterpret_cast<const char*>(npn_proto),
319 "spdy/2", npn_proto_len))
320 return true;
321 }
322 }
323
324 return false;
325 }
326
SetupProtocolInterfaces()327 bool SMConnection::SetupProtocolInterfaces() {
328 DCHECK(!protocol_detected_);
329 protocol_detected_ = true;
330
331 bool spdy_negotiated = WasSpdyNegotiated();
332 bool using_ssl = ssl_ != NULL;
333
334 if (using_ssl)
335 VLOG(1) << (SSL_session_reused(ssl_) ? "Resumed" : "Renegotiated")
336 << " SSL Session.";
337
338 if (acceptor_->spdy_only_ && !spdy_negotiated) {
339 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
340 << "SPDY proxy only, closing HTTPS connection.";
341 return false;
342 }
343
344 switch (acceptor_->flip_handler_type_) {
345 case FLIP_HANDLER_HTTP_SERVER:
346 {
347 DCHECK(!spdy_negotiated);
348 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
349 << (sm_http_interface_ ? "Creating" : "Reusing")
350 << " HTTP interface.";
351 if (!sm_http_interface_)
352 sm_http_interface_ = new HttpSM(this,
353 NULL,
354 epoll_server_,
355 memory_cache_,
356 acceptor_);
357 sm_interface_ = sm_http_interface_;
358 }
359 break;
360 case FLIP_HANDLER_PROXY:
361 {
362 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
363 << (sm_streamer_interface_ ? "Creating" : "Reusing")
364 << " PROXY Streamer interface.";
365 if (!sm_streamer_interface_) {
366 sm_streamer_interface_ = new StreamerSM(this,
367 NULL,
368 epoll_server_,
369 acceptor_);
370 sm_streamer_interface_->set_is_request();
371 }
372 sm_interface_ = sm_streamer_interface_;
373 // If spdy is not negotiated, the streamer interface will proxy all
374 // data to the origin server.
375 if (!spdy_negotiated)
376 break;
377 }
378 // Otherwise fall through into the case below.
379 case FLIP_HANDLER_SPDY_SERVER:
380 {
381 DCHECK(spdy_negotiated);
382 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
383 << (sm_spdy_interface_ ? "Creating" : "Reusing")
384 << " SPDY interface.";
385 if (!sm_spdy_interface_)
386 sm_spdy_interface_ = new SpdySM(this,
387 NULL,
388 epoll_server_,
389 memory_cache_,
390 acceptor_);
391 sm_interface_ = sm_spdy_interface_;
392 }
393 break;
394 }
395
396 CorkSocket();
397 if (!sm_interface_->PostAcceptHook())
398 return false;
399
400 return true;
401 }
402
DoRead()403 bool SMConnection::DoRead() {
404 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "DoRead()";
405 while (!read_buffer_.Full()) {
406 char* bytes;
407 int size;
408 if (fd_ == -1) {
409 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
410 << "DoRead(): fd_ == -1. Invalid FD. Returning false";
411 return false;
412 }
413 read_buffer_.GetWritablePtr(&bytes, &size);
414 ssize_t bytes_read = 0;
415 if (ssl_) {
416 bytes_read = SSL_read(ssl_, bytes, size);
417 if (bytes_read < 0) {
418 int err = SSL_get_error(ssl_, bytes_read);
419 switch (err) {
420 case SSL_ERROR_WANT_READ:
421 case SSL_ERROR_WANT_WRITE:
422 case SSL_ERROR_WANT_ACCEPT:
423 case SSL_ERROR_WANT_CONNECT:
424 events_ &= ~EPOLLIN;
425 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
426 << "DoRead: SSL WANT_XXX: " << err;
427 goto done;
428 default:
429 PrintSslError();
430 goto error_or_close;
431 }
432 }
433 } else {
434 bytes_read = recv(fd_, bytes, size, MSG_DONTWAIT);
435 }
436 int stored_errno = errno;
437 if (bytes_read == -1) {
438 switch (stored_errno) {
439 case EAGAIN:
440 events_ &= ~EPOLLIN;
441 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
442 << "Got EAGAIN while reading";
443 goto done;
444 case EINTR:
445 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
446 << "Got EINTR while reading";
447 continue;
448 default:
449 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
450 << "While calling recv, got error: "
451 << (ssl_?"(ssl error)":strerror(stored_errno));
452 goto error_or_close;
453 }
454 } else if (bytes_read > 0) {
455 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "read " << bytes_read
456 << " bytes";
457 last_read_time_ = time(NULL);
458 // If the protocol hasn't been detected yet, set up the handlers
459 // we'll need.
460 if (!protocol_detected_) {
461 if (!SetupProtocolInterfaces()) {
462 LOG(ERROR) << "Error setting up protocol interfaces.";
463 goto error_or_close;
464 }
465 }
466 read_buffer_.AdvanceWritablePtr(bytes_read);
467 if (!DoConsumeReadData())
468 goto error_or_close;
469 continue;
470 } else { // bytes_read == 0
471 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
472 << "0 bytes read with recv call.";
473 }
474 goto error_or_close;
475 }
476 done:
477 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "DoRead done!";
478 return true;
479
480 error_or_close:
481 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
482 << "DoRead(): error_or_close. "
483 << "Cleaning up, then returning false";
484 Cleanup("DoRead");
485 return false;
486 }
487
DoConsumeReadData()488 bool SMConnection::DoConsumeReadData() {
489 char* bytes;
490 int size;
491 read_buffer_.GetReadablePtr(&bytes, &size);
492 while (size != 0) {
493 size_t bytes_consumed = sm_interface_->ProcessReadInput(bytes, size);
494 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "consumed "
495 << bytes_consumed << " bytes";
496 if (bytes_consumed == 0) {
497 break;
498 }
499 read_buffer_.AdvanceReadablePtr(bytes_consumed);
500 if (sm_interface_->MessageFullyRead()) {
501 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
502 << "HandleRequestFullyRead: Setting EPOLLOUT";
503 HandleResponseFullyRead();
504 events_ |= EPOLLOUT;
505 } else if (sm_interface_->Error()) {
506 LOG(ERROR) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
507 << "Framer error detected: Setting EPOLLOUT: "
508 << sm_interface_->ErrorAsString();
509 // this causes everything to be closed/cleaned up.
510 events_ |= EPOLLOUT;
511 return false;
512 }
513 read_buffer_.GetReadablePtr(&bytes, &size);
514 }
515 return true;
516 }
517
HandleResponseFullyRead()518 void SMConnection::HandleResponseFullyRead() {
519 sm_interface_->Cleanup();
520 }
521
DoWrite()522 bool SMConnection::DoWrite() {
523 size_t bytes_sent = 0;
524 int flags = MSG_NOSIGNAL | MSG_DONTWAIT;
525 if (fd_ == -1) {
526 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
527 << "DoWrite: fd == -1. Returning false.";
528 return false;
529 }
530 if (output_list_.empty()) {
531 VLOG(2) << log_prefix_ << "DoWrite: Output list empty.";
532 if (sm_interface_) {
533 sm_interface_->GetOutput();
534 }
535 if (output_list_.empty()) {
536 events_ &= ~EPOLLOUT;
537 }
538 }
539 while (!output_list_.empty()) {
540 VLOG(2) << log_prefix_ << "DoWrite: Items in output list: "
541 << output_list_.size();
542 if (bytes_sent >= max_bytes_sent_per_dowrite_) {
543 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
544 << " byte sent >= max bytes sent per write: Setting EPOLLOUT: "
545 << bytes_sent;
546 events_ |= EPOLLOUT;
547 break;
548 }
549 if (sm_interface_ && output_list_.size() < 2) {
550 sm_interface_->GetOutput();
551 }
552 DataFrame* data_frame = output_list_.front();
553 const char* bytes = data_frame->data;
554 int size = data_frame->size;
555 bytes += data_frame->index;
556 size -= data_frame->index;
557 DCHECK_GE(size, 0);
558 if (size <= 0) {
559 output_list_.pop_front();
560 delete data_frame;
561 continue;
562 }
563
564 flags = MSG_NOSIGNAL | MSG_DONTWAIT;
565 // Look for a queue size > 1 because |this| frame is remains on the list
566 // until it has finished sending.
567 if (output_list_.size() > 1) {
568 VLOG(2) << log_prefix_ << "Outlist size: " << output_list_.size()
569 << ": Adding MSG_MORE flag";
570 flags |= MSG_MORE;
571 }
572 VLOG(2) << log_prefix_ << "Attempting to send " << size << " bytes.";
573 ssize_t bytes_written = Send(bytes, size, flags);
574 int stored_errno = errno;
575 if (bytes_written == -1) {
576 switch (stored_errno) {
577 case EAGAIN:
578 events_ &= ~EPOLLOUT;
579 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
580 << "Got EAGAIN while writing";
581 goto done;
582 case EINTR:
583 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
584 << "Got EINTR while writing";
585 continue;
586 default:
587 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
588 << "While calling send, got error: " << stored_errno
589 << ": " << (ssl_?"":strerror(stored_errno));
590 goto error_or_close;
591 }
592 } else if (bytes_written > 0) {
593 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Wrote: "
594 << bytes_written << " bytes";
595 data_frame->index += bytes_written;
596 bytes_sent += bytes_written;
597 continue;
598 } else if (bytes_written == -2) {
599 // -2 handles SSL_ERROR_WANT_* errors
600 events_ &= ~EPOLLOUT;
601 goto done;
602 }
603 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
604 << "0 bytes written with send call.";
605 goto error_or_close;
606 }
607 done:
608 UncorkSocket();
609 return true;
610
611 error_or_close:
612 VLOG(1) << log_prefix_ << ACCEPTOR_CLIENT_IDENT
613 << "DoWrite: error_or_close. Returning false "
614 << "after cleaning up";
615 Cleanup("DoWrite");
616 UncorkSocket();
617 return false;
618 }
619
Reset()620 void SMConnection::Reset() {
621 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Resetting";
622 if (ssl_) {
623 SSL_shutdown(ssl_);
624 PrintSslError();
625 SSL_free(ssl_);
626 PrintSslError();
627 ssl_ = NULL;
628 }
629 if (registered_in_epoll_server_) {
630 epoll_server_->UnregisterFD(fd_);
631 registered_in_epoll_server_ = false;
632 }
633 if (fd_ >= 0) {
634 VLOG(2) << log_prefix_ << ACCEPTOR_CLIENT_IDENT << "Closing connection";
635 close(fd_);
636 fd_ = -1;
637 }
638 read_buffer_.Clear();
639 initialized_ = false;
640 protocol_detected_ = false;
641 events_ = 0;
642 for (std::list<DataFrame*>::iterator i =
643 output_list_.begin();
644 i != output_list_.end();
645 ++i) {
646 delete *i;
647 }
648 output_list_.clear();
649 }
650
651 // static
NewSMConnection(EpollServer * epoll_server,SSLState * ssl_state,MemoryCache * memory_cache,FlipAcceptor * acceptor,std::string log_prefix)652 SMConnection* SMConnection::NewSMConnection(EpollServer* epoll_server,
653 SSLState *ssl_state,
654 MemoryCache* memory_cache,
655 FlipAcceptor *acceptor,
656 std::string log_prefix) {
657 return new SMConnection(epoll_server, ssl_state, memory_cache,
658 acceptor, log_prefix);
659 }
660
661 } // namespace net
662
663
664