CCF
Loading...
Searching...
No Matches
tcp.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
5#include "before_io.h"
6#include "ccf/ds/logger.h"
7#include "ccf/pal/locking.h"
8#include "dns.h"
9#include "ds/pending_io.h"
10#include "proxy.h"
11#include "socket.h"
12
13#include <netinet/in.h>
14#include <optional>
15
16namespace asynchost
17{
18 class TCPImpl;
20
21 class TCPImpl : public with_uv_handle<uv_tcp_t>
22 {
23 private:
24 friend class close_ptr<TCPImpl>;
25
26 static constexpr int backlog = 128;
27 static constexpr size_t max_read_size = 16384;
28
29 // Each uv iteration, read only a capped amount from all sockets.
30 static constexpr auto max_read_quota = max_read_size * 4;
31 static size_t remaining_read_quota;
32 static bool alloc_quota_logged;
33
34 enum Status
35 {
36 FRESH,
37 LISTENING_RESOLVING,
38 LISTENING,
39 BINDING,
40 BINDING_FAILED,
41 CONNECTING_RESOLVING,
42 CONNECTING,
43 CONNECTED,
44 DISCONNECTED,
45 RESOLVING_FAILED,
46 LISTENING_FAILED,
47 CONNECTING_FAILED,
48 RECONNECTING
49 };
50
51 bool is_client;
52 std::optional<std::chrono::milliseconds> connection_timeout = std::nullopt;
53 Status status;
54 std::unique_ptr<SocketBehaviour<TCP>> behaviour;
55 using PendingWrites = std::vector<PendingIO<uv_write_t>>;
56 PendingWrites pending_writes;
57
58 std::string host;
59 std::string port;
60 std::optional<std::string> client_host = std::nullopt;
61 std::optional<std::string> listen_name = std::nullopt;
62
63 addrinfo* client_addr_base = nullptr;
64 addrinfo* addr_base = nullptr;
65 addrinfo* addr_current = nullptr;
66
67 bool port_assigned() const
68 {
69 return port != "0";
70 }
71
72 std::string get_address_name() const
73 {
74 const std::string port_suffix =
75 port_assigned() ? fmt::format(":{}", port) : "";
76
77 if (addr_current != nullptr && addr_current->ai_family == AF_INET6)
78 {
79 return fmt::format("[{}]{}", host, port_suffix);
80 }
81 else
82 {
83 return fmt::format("{}{}", host, port_suffix);
84 }
85 }
86
87 TCPImpl(
88 bool is_client_ = false,
89 std::optional<std::chrono::milliseconds> connection_timeout_ =
90 std::nullopt) :
91 is_client(is_client_),
92 connection_timeout(connection_timeout_),
93 status(FRESH)
94 {
95 if (!init())
96 {
97 throw std::logic_error("uv tcp initialization failed");
98 }
99
100 uv_handle.data = this;
101 }
102
103 ~TCPImpl()
104 {
105 {
106 std::unique_lock<ccf::pal::Mutex> guard(pending_resolve_requests_mtx);
107 for (auto& req : pending_resolve_requests)
108 {
109 // The UV request objects can stay, but if there are any references
110 // to `this` left, we need to remove them.
111 if (req->data == this)
112 {
113 req->data = nullptr;
114 }
115 }
116 }
117 if (addr_base != nullptr)
118 {
119 uv_freeaddrinfo(addr_base);
120 }
121 if (client_addr_base != nullptr)
122 {
123 uv_freeaddrinfo(client_addr_base);
124 }
125 }
126
127 public:
128 static void reset_read_quota()
129 {
130 remaining_read_quota = max_read_quota;
131 alloc_quota_logged = false;
132 }
133
134 void set_behaviour(std::unique_ptr<SocketBehaviour<TCP>> b)
135 {
136 behaviour = std::move(b);
137 }
138
139 std::string get_host() const
140 {
141 return host;
142 }
143
144 std::string get_port() const
145 {
146 return port;
147 }
148
149 std::string get_peer_name() const
150 {
151 sockaddr_storage sa = {};
152 int name_len = sizeof(sa);
153 if (uv_tcp_getpeername(&uv_handle, (sockaddr*)&sa, &name_len) < 0)
154 {
155 LOG_FAIL_FMT("uv_tcp_getpeername failed");
156 return "";
157 }
158 switch (sa.ss_family)
159 {
160 case AF_INET:
161 {
162 char tmp[INET_ADDRSTRLEN];
163 sockaddr_in* sa4 = (sockaddr_in*)&sa;
164 uv_ip4_name(sa4, tmp, sizeof(tmp));
165 return tmp;
166 }
167 case AF_INET6:
168 {
169 char tmp[INET6_ADDRSTRLEN];
170 sockaddr_in6* sa6 = (sockaddr_in6*)&sa;
171 uv_ip6_name(sa6, tmp, sizeof(tmp));
172 return tmp;
173 }
174 default:
175 return fmt::format("unknown family: {}", sa.ss_family);
176 }
177 }
178
179 std::optional<std::string> get_listen_name() const
180 {
181 return listen_name;
182 }
183
185 {
186 int rc;
187 if ((rc = uv_tcp_bind(&uv_handle, client_addr_base->ai_addr, 0)) < 0)
188 {
189 assert_status(BINDING, BINDING_FAILED);
190 LOG_FAIL_FMT("uv_tcp_bind failed: {}", uv_strerror(rc));
191 behaviour->on_bind_failed();
192 }
193 else
194 {
195 assert_status(BINDING, CONNECTING_RESOLVING);
196 if (addr_current != nullptr)
197 {
198 connect_resolved();
199 }
200 else
201 {
202 resolve(this->host, this->port, true);
203 }
204 }
205 }
206
208 uv_getaddrinfo_t* req, int rc, struct addrinfo*)
209 {
210 static_cast<TCPImpl*>(req->data)->on_client_resolved(req, rc);
211 }
212
213 void on_client_resolved(uv_getaddrinfo_t* req, int rc)
214 {
215 if (!uv_is_closing((uv_handle_t*)&uv_handle))
216 {
217 if (rc < 0)
218 {
219 assert_status(BINDING, BINDING_FAILED);
220 LOG_DEBUG_FMT("TCP client resolve failed: {}", uv_strerror(rc));
221 behaviour->on_bind_failed();
222 }
223 else
224 {
225 client_addr_base = req->addrinfo;
226 client_bind();
227 }
228 }
229
230 delete req;
231 }
232
234 void start(int64_t id) {}
235
237 const std::string& host_,
238 const std::string& port_,
239 const std::optional<std::string>& client_host_ = std::nullopt)
240 {
241 // If a client host is set, bind to this first. Otherwise, connect
242 // straight away.
243 if (client_host_.has_value())
244 {
245 client_host = client_host_;
246 host = host_;
247 port = port_;
248
249 if (client_addr_base != nullptr)
250 {
251 uv_freeaddrinfo(client_addr_base);
252 client_addr_base = nullptr;
253 }
254
255 status = BINDING;
256 if (!DNS::resolve(
257 client_host.value(), "0", this, on_client_resolved, false))
258 {
259 LOG_DEBUG_FMT("Bind to '{}' failed", client_host.value());
260 status = BINDING_FAILED;
261 return false;
262 }
263 }
264 else
265 {
266 assert_status(FRESH, CONNECTING_RESOLVING);
267 return resolve(host_, port_, true);
268 }
269
270 return true;
271 }
272
274 {
275 switch (status)
276 {
277 case BINDING_FAILED:
278 {
279 // Try again, from the start.
280 LOG_DEBUG_FMT("Reconnect from initial state");
281 assert_status(BINDING_FAILED, BINDING);
282 return connect(host, port, client_host);
283 }
284 case RESOLVING_FAILED:
285 case CONNECTING_FAILED:
286 {
287 // Try again, starting with DNS.
288 LOG_DEBUG_FMT("Reconnect from DNS");
289 status = CONNECTING_RESOLVING;
290 return resolve(host, port, true);
291 }
292
293 case DISCONNECTED:
294 {
295 // It's possible there was a request to close the uv_handle in the
296 // meanwhile; in that case we abort the reconnection attempt.
297 if (!uv_is_closing((uv_handle_t*)&uv_handle))
298 {
299 // Close and reset the uv_handle before trying again with the same
300 // addr_current that succeeded previously.
301 LOG_DEBUG_FMT("Reconnect from resolved address");
302 status = RECONNECTING;
303 uv_close((uv_handle_t*)&uv_handle, on_reconnect);
304 }
305 return true;
306 }
307
308 default:
309 {
311 "Unexpected status during reconnect, ignoring: {}", status);
312 }
313 }
314
315 return false;
316 }
317
318 bool listen(
319 const std::string& host_,
320 const std::string& port_,
321 const std::optional<std::string>& name = std::nullopt)
322 {
323 assert_status(FRESH, LISTENING_RESOLVING);
324 bool ret = resolve(host_, port_, false);
325 listen_name = name;
326 return ret;
327 }
328
329 bool write(size_t len, const uint8_t* data, sockaddr addr = {})
330 {
331 auto req = new uv_write_t;
332 char* copy = new char[len];
333 if (data)
334 memcpy(copy, data, len);
335 req->data = copy;
336
337 switch (status)
338 {
339 case BINDING:
340 case BINDING_FAILED:
341 case CONNECTING_RESOLVING:
342 case CONNECTING:
343 case RESOLVING_FAILED:
344 case CONNECTING_FAILED:
345 case RECONNECTING:
346 {
347 pending_writes.emplace_back(req, len, sockaddr{}, free_write);
348 break;
349 }
350
351 case CONNECTED:
352 {
353 return send_write(req, len);
354 }
355
356 case DISCONNECTED:
357 {
358 LOG_DEBUG_FMT("Disconnected: Ignoring write of size {}", len);
359 free_write(req);
360 break;
361 }
362
363 default:
364 {
365 free_write(req);
366 throw std::logic_error(
367 fmt::format("Unexpected status during write: {}", status));
368 }
369 }
370
371 return true;
372 }
373
374 private:
375 bool init()
376 {
377 assert_status(FRESH, FRESH);
378
379 int rc;
380 if ((rc = uv_tcp_init(uv_default_loop(), &uv_handle)) < 0)
381 {
382 LOG_FAIL_FMT("uv_tcp_init failed: {}", uv_strerror(rc));
383 return false;
384 }
385
386 if ((rc = uv_tcp_nodelay(&uv_handle, true)) < 0)
387 {
388 LOG_FAIL_FMT("uv_tcp_nodelay failed: {}", uv_strerror(rc));
389 return false;
390 }
391
392 if (is_client)
393 {
394 uv_os_sock_t sock;
395 if ((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
396 {
397 LOG_FAIL_FMT("socket creation failed: {}", strerror(errno));
398 return false;
399 }
400
401 if (connection_timeout.has_value())
402 {
403 const unsigned int ms = connection_timeout->count();
404 const auto ret =
405 setsockopt(sock, IPPROTO_TCP, TCP_USER_TIMEOUT, &ms, sizeof(ms));
406 if (ret != 0)
407 {
409 "Failed to set socket option (TCP_USER_TIMEOUT): {}",
410 strerror(errno));
411 return false;
412 }
413 }
414
415 if ((rc = uv_tcp_open(&uv_handle, sock)) < 0)
416 {
417 LOG_FAIL_FMT("uv_tcp_open failed: {}", uv_strerror(rc));
418 return false;
419 }
420 }
421
422 if ((rc = uv_tcp_keepalive(&uv_handle, 1, 30)) < 0)
423 {
424 LOG_FAIL_FMT("uv_tcp_keepalive failed: {}", uv_strerror(rc));
425 return false;
426 }
427
428 uv_handle.data = this;
429 return true;
430 }
431
432 bool send_write(uv_write_t* req, size_t len)
433 {
434 char* copy = (char*)req->data;
435
436 uv_buf_t buf;
437 buf.base = copy;
438 buf.len = len;
439
440 int rc;
441
442 if ((rc = uv_write(req, (uv_stream_t*)&uv_handle, &buf, 1, on_write)) < 0)
443 {
444 free_write(req);
445 LOG_FAIL_FMT("uv_write failed: {}", uv_strerror(rc));
446 assert_status(CONNECTED, DISCONNECTED);
447 behaviour->on_disconnect();
448 return false;
449 }
450
451 return true;
452 }
453
454 void update_resolved_address(int address_family, sockaddr* sa)
455 {
456 auto [h, p] = addr_to_str(sa, address_family);
457 host = h;
458 port = p;
459 LOG_TRACE_FMT("TCP update address to {}:{}", host, port);
460 }
461
462 void listen_resolved()
463 {
464 int rc;
465
466 while (addr_current != nullptr)
467 {
468 update_resolved_address(addr_current->ai_family, addr_current->ai_addr);
469
470 if ((rc = uv_tcp_bind(&uv_handle, addr_current->ai_addr, 0)) < 0)
471 {
472 addr_current = addr_current->ai_next;
474 "uv_tcp_bind failed on {}: {}",
475 get_address_name(),
476 uv_strerror(rc));
477 continue;
478 }
479
480 if ((rc = uv_listen((uv_stream_t*)&uv_handle, backlog, on_accept)) < 0)
481 {
483 "uv_listen failed on {}: {}", get_address_name(), uv_strerror(rc));
484 addr_current = addr_current->ai_next;
485 continue;
486 }
487
488 // If bound on port 0 (ie - asking the OS to assign a port), then we
489 // need to call uv_tcp_getsockname to retrieve the bound port
490 // (addr_current will not contain it)
491 if (!port_assigned())
492 {
493 sockaddr_storage sa_storage;
494 const auto sa = (sockaddr*)&sa_storage;
495 int sa_len = sizeof(sa_storage);
496 if ((rc = uv_tcp_getsockname(&uv_handle, sa, &sa_len)) != 0)
497 {
498 LOG_FAIL_FMT("uv_tcp_getsockname failed: {}", uv_strerror(rc));
499 }
500 update_resolved_address(addr_current->ai_family, sa);
501 }
502
503 assert_status(LISTENING_RESOLVING, LISTENING);
504 behaviour->on_listening(host, port);
505 return;
506 }
507
508 assert_status(LISTENING_RESOLVING, LISTENING_FAILED);
509 behaviour->on_listen_failed();
510 }
511
512 bool connect_resolved()
513 {
514 auto req = new uv_connect_t;
515 int rc;
516
517 while (addr_current != nullptr)
518 {
519 if (
520 (rc = uv_tcp_connect(
521 req, &uv_handle, addr_current->ai_addr, on_connect)) < 0)
522 {
523 LOG_DEBUG_FMT("uv_tcp_connect retry: {}", uv_strerror(rc));
524 addr_current = addr_current->ai_next;
525 continue;
526 }
527
528 assert_status(CONNECTING_RESOLVING, CONNECTING);
529 return true;
530 }
531
532 assert_status(CONNECTING_RESOLVING, CONNECTING_FAILED);
533 delete req;
534
535 // This should show even when verbose logs are off
537 "Unable to connect: all resolved addresses failed: {}:{}", host, port);
538
539 behaviour->on_connect_failed();
540 return false;
541 }
542
543 void assert_status(Status from, Status to)
544 {
545 if (status != from)
546 {
547 throw std::logic_error(fmt::format(
548 "Trying to transition from {} to {} but current status is {}",
549 from,
550 to,
551 status));
552 }
553
554 status = to;
555 }
556
557 bool resolve(
558 const std::string& host_, const std::string& port_, bool async = true)
559 {
560 host = host_;
561 port = port_;
562
563 if (addr_base != nullptr)
564 {
565 uv_freeaddrinfo(addr_base);
566 addr_base = nullptr;
567 addr_current = nullptr;
568 }
569
570 if (!DNS::resolve(host, port, this, on_resolved, async))
571 {
572 LOG_DEBUG_FMT("Resolving '{}' failed", host);
573 status = RESOLVING_FAILED;
574 return false;
575 }
576
577 return true;
578 }
579
580 static void on_resolved(uv_getaddrinfo_t* req, int rc, struct addrinfo* res)
581 {
582 std::unique_lock<ccf::pal::Mutex> guard(pending_resolve_requests_mtx);
583 pending_resolve_requests.erase(req);
584
585 if (req->data)
586 {
587 static_cast<TCPImpl*>(req->data)->on_resolved(req, rc);
588 }
589 else
590 {
591 // The TCPImpl that submitted the request has been destroyed, but we
592 // need to clean up the request object.
593 uv_freeaddrinfo(res);
594 delete req;
595 }
596 }
597
598 void on_resolved(uv_getaddrinfo_t* req, int rc)
599 {
600 // It is possible that on_resolved is triggered after there has been a
601 // request to close uv_handle. In this scenario, we should not try to
602 // do anything with the handle and return immediately (otherwise,
603 // uv_close cb will abort).
604 if (uv_is_closing((uv_handle_t*)&uv_handle))
605 {
606 LOG_DEBUG_FMT("on_resolved: closing");
607 uv_freeaddrinfo(req->addrinfo);
608 delete req;
609 return;
610 }
611
612 if (rc < 0)
613 {
614 status = RESOLVING_FAILED;
615 LOG_DEBUG_FMT("TCP resolve failed: {}", uv_strerror(rc));
616 behaviour->on_resolve_failed();
617 }
618 else
619 {
620 addr_base = req->addrinfo;
621 addr_current = addr_base;
622
623 switch (status)
624 {
625 case CONNECTING_RESOLVING:
626 {
627 connect_resolved();
628 break;
629 }
630
631 case LISTENING_RESOLVING:
632 {
633 listen_resolved();
634 break;
635 }
636
637 default:
638 {
639 throw std::logic_error(
640 fmt::format("Unexpected status during on_resolved: {}", status));
641 }
642 }
643 }
644
645 delete req;
646 }
647
648 static void on_accept(uv_stream_t* handle, int rc)
649 {
650 static_cast<TCPImpl*>(handle->data)->on_accept(rc);
651 }
652
653 void on_accept(int rc)
654 {
655 if (rc < 0)
656 {
657 LOG_DEBUG_FMT("on_accept failed: {}", uv_strerror(rc));
658 return;
659 }
660
661 TCP peer;
662
663 if (
664 (rc = uv_accept(
665 (uv_stream_t*)&uv_handle, (uv_stream_t*)&peer->uv_handle)) < 0)
666 {
667 LOG_DEBUG_FMT("uv_accept failed: {}", uv_strerror(rc));
668 return;
669 }
670
671 peer->assert_status(FRESH, CONNECTED);
672
673 if (!peer->read_start())
674 return;
675
676 behaviour->on_accept(peer);
677 }
678
679 static void on_connect(uv_connect_t* req, int rc)
680 {
681 auto self = static_cast<TCPImpl*>(req->handle->data);
682 delete req;
683
684 if (rc == UV_ECANCELED)
685 {
686 // Break reconnection loop early if cancelled
687 LOG_FAIL_FMT("on_connect: cancelled");
688 return;
689 }
690
691 self->on_connect(rc);
692 }
693
694 void on_connect(int rc)
695 {
696 if (rc < 0)
697 {
698 // Try again on the next address.
699 LOG_DEBUG_FMT("uv_tcp_connect async retry: {}", uv_strerror(rc));
700 addr_current = addr_current->ai_next;
701 assert_status(CONNECTING, CONNECTING_RESOLVING);
702 connect_resolved();
703 }
704 else
705 {
706 assert_status(CONNECTING, CONNECTED);
707
708 if (!read_start())
709 {
710 return;
711 }
712
713 for (auto& w : pending_writes)
714 {
715 send_write(w.req, w.len);
716 w.req = nullptr;
717 }
718
719 PendingWrites().swap(pending_writes);
720 behaviour->on_connect();
721 }
722 }
723
724 bool read_start()
725 {
726 int rc;
727
728 if ((rc = uv_read_start((uv_stream_t*)&uv_handle, on_alloc, on_read)) < 0)
729 {
730 assert_status(CONNECTED, DISCONNECTED);
731 LOG_FAIL_FMT("uv_read_start failed: {}", uv_strerror(rc));
732
733 if (behaviour)
734 {
735 behaviour->on_disconnect();
736 }
737
738 return false;
739 }
740
741 return true;
742 }
743
744 static void on_alloc(
745 uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf)
746 {
747 static_cast<TCPImpl*>(handle->data)->on_alloc(suggested_size, buf);
748 }
749
750 void on_alloc(size_t suggested_size, uv_buf_t* buf)
751 {
752 auto alloc_size = std::min(suggested_size, max_read_size);
753
754 alloc_size = std::min(alloc_size, remaining_read_quota);
755 remaining_read_quota -= alloc_size;
756 if (alloc_size != 0)
757 {
759 "Allocating {} bytes for TCP read ({} of quota remaining)",
760 alloc_size,
761 remaining_read_quota);
762 }
763
764 buf->base = new char[alloc_size];
765 buf->len = alloc_size;
766 }
767
768 void on_free(const uv_buf_t* buf)
769 {
770 delete[] buf->base;
771 }
772
773 static void on_read(uv_stream_t* handle, ssize_t sz, const uv_buf_t* buf)
774 {
775 static_cast<TCPImpl*>(handle->data)->on_read(sz, buf);
776 }
777
778 void on_read(ssize_t sz, const uv_buf_t* buf)
779 {
780 if (sz == 0)
781 {
782 on_free(buf);
783 return;
784 }
785
786 if (sz == UV_ENOBUFS)
787 {
788 if (!alloc_quota_logged)
789 {
790 LOG_DEBUG_FMT("TCP on_read reached allocation quota");
791 alloc_quota_logged = true;
792 }
793 on_free(buf);
794 return;
795 }
796
797 if (sz < 0)
798 {
799 assert_status(CONNECTED, DISCONNECTED);
800 on_free(buf);
801 uv_read_stop((uv_stream_t*)&uv_handle);
802
803 LOG_DEBUG_FMT("TCP on_read: {}", uv_strerror(sz));
804 behaviour->on_disconnect();
805 return;
806 }
807
808 uint8_t* p = (uint8_t*)buf->base;
809 const bool read_good = behaviour->on_read((size_t)sz, p, {});
810
811 if (p != nullptr)
812 {
813 on_free(buf);
814 }
815
816 if (!read_good)
817 {
818 behaviour->on_disconnect();
819 return;
820 }
821 }
822
823 static void on_write(uv_write_t* req, int)
824 {
825 free_write(req);
826 }
827
828 static void free_write(uv_write_t* req)
829 {
830 if (req == nullptr)
831 {
832 return;
833 }
834
835 char* copy = (char*)req->data;
836 delete[] copy;
837 delete req;
838 }
839
840 static void on_reconnect(uv_handle_t* handle)
841 {
842 static_cast<TCPImpl*>(handle->data)->on_reconnect();
843 }
844
845 void on_reconnect()
846 {
847 assert_status(RECONNECTING, FRESH);
848
849 if (!init())
850 {
851 assert_status(FRESH, CONNECTING_FAILED);
852 behaviour->on_connect_failed();
853 return;
854 }
855
856 if (client_addr_base != nullptr)
857 {
858 assert_status(FRESH, BINDING);
859 client_bind();
860 }
861 else
862 {
863 assert_status(FRESH, CONNECTING_RESOLVING);
864 connect_resolved();
865 }
866 }
867 };
868
870 {
871 public:
873
875 {
877 }
878 };
879
881}
static bool resolve(const std::string &host_, const std::string &service, void *ud, uv_getaddrinfo_cb cb, bool async)
Definition dns.h:19
void before_io()
Definition tcp.h:874
ResetTCPReadQuotaImpl()
Definition tcp.h:872
Callback service for user-specific behaviour for TCP and UDP connections.
Definition socket.h:20
Definition tcp.h:22
bool write(size_t len, const uint8_t *data, sockaddr addr={})
Definition tcp.h:329
void client_bind()
Definition tcp.h:184
static void on_client_resolved(uv_getaddrinfo_t *req, int rc, struct addrinfo *)
Definition tcp.h:207
bool listen(const std::string &host_, const std::string &port_, const std::optional< std::string > &name=std::nullopt)
Definition tcp.h:318
std::string get_port() const
Definition tcp.h:144
std::string get_host() const
Definition tcp.h:139
void set_behaviour(std::unique_ptr< SocketBehaviour< TCP > > b)
Definition tcp.h:134
std::optional< std::string > get_listen_name() const
Definition tcp.h:179
void on_client_resolved(uv_getaddrinfo_t *req, int rc)
Definition tcp.h:213
std::string get_peer_name() const
Definition tcp.h:149
bool reconnect()
Definition tcp.h:273
void start(int64_t id)
This is to mimic UDP's implementation. TCP's start is on_accept.
Definition tcp.h:234
static void reset_read_quota()
Definition tcp.h:128
bool connect(const std::string &host_, const std::string &port_, const std::optional< std::string > &client_host_=std::nullopt)
Definition tcp.h:236
Definition proxy.h:15
Definition proxy.h:82
uv_tcp_t uv_handle
Definition proxy.h:84
#define LOG_INFO_FMT
Definition logger.h:362
#define LOG_TRACE_FMT
Definition logger.h:356
#define LOG_DEBUG_FMT
Definition logger.h:357
#define LOG_FAIL_FMT
Definition logger.h:363
Definition after_io.h:8
std::pair< std::string, std::string > addr_to_str(const sockaddr *addr, int address_family=AF_INET)
Definition socket.h:83
proxy_ptr< TCPImpl > TCP
Definition tcp.h:19
auto handle
Definition kv_helpers.h:85
Definition configuration.h:14