OpenVPN 3 Core Library
Loading...
Searching...
No Matches
httpcli.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#pragma once
13
14// General purpose HTTP/HTTPS/Web-services client.
15// Supports:
16// * asynchronous I/O through Asio
17// * http/https
18// * chunking
19// * keepalive
20// * connect and overall timeouts
21// * GET, POST, etc.
22// * any OpenVPN SSL module (OpenSSL, MbedTLS)
23// * server CA bundle
24// * client certificate
25// * HTTP basic auth
26// * limits on content-size, header-size, and number of headers
27// * cURL not needed
28//
29// See test/ws/wstest.cpp for usage examples including Dropwizard REST/JSON API client.
30// See test/ws/asprof.cpp for sample AS REST API client.
31
32#include <string>
33#include <vector>
34#include <sstream>
35#include <ostream>
36#include <cstdint>
37#include <utility>
38#include <memory>
39#include <algorithm> // for std::min, std::max
40
41#ifdef USE_ASYNC_RESOLVE
43#endif
44
53#include <openvpn/addr/ip.hpp>
67
68#ifdef VPN_BINDING_PROFILES
69#ifdef USE_ASYNC_RESOLVE
70#error VPN_BINDING_PROFILES and USE_ASYNC_RESOLVE cannot be used together
71#endif
73#include <openvpn/dns/dnscli.hpp>
74#endif
75
76#ifdef SIMULATE_HTTPCLI_FAILURES
77// debugging -- simulate network failures
78#include <openvpn/common/periodic_fail.hpp>
79#endif
80
81#if defined(OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING)
82#include <openvpn/asio/alt_routing.hpp>
83#endif
84
85#if defined(OPENVPN_PLATFORM_WIN)
88#endif
89
91
92OPENVPN_EXCEPTION(http_client_exception);
93
94struct Status
95{
96 // Error codes
97 enum
98 {
119 E_BOGON, // simulated fault injection for testing
120
122 };
123
124 static std::string error_str(const int status)
125 {
126 static const char *error_names[] = {
127 "E_SUCCESS",
128 "E_RESOLVE",
129 "E_CONNECT",
130 "E_TRANSPORT",
131 "E_PROXY",
132 "E_TCP",
133 "E_HTTP",
134 "E_EXCEPTION",
135 "E_BAD_REQUEST",
136 "E_HEADER_SIZE",
137 "E_CONTENT_SIZE",
138 "E_CONTENT_TYPE",
139 "E_EOF_SSL",
140 "E_EOF_TCP",
141 "E_CONNECT_TIMEOUT",
142 "E_GENERAL_TIMEOUT",
143 "E_KEEPALIVE_TIMEOUT",
144 "E_SHUTDOWN",
145 "E_ABORTED",
146 "E_HOST_UPDATE",
147 "E_BOGON",
148 };
149
150 static_assert(N_ERRORS == array_size(error_names), "HTTP error names array inconsistency");
151 if (status >= 0 && status < N_ERRORS)
152 return error_names[status];
153 else if (status == -1)
154 return "E_UNDEF";
155 else
156 return "E_?/" + openvpn::to_string(status);
157 }
158
159 static bool is_error(const int status)
160 {
161 switch (status)
162 {
163 case E_SUCCESS:
164 case E_SHUTDOWN:
165 return false;
166 default:
167 return true;
168 }
169 }
170};
171
172struct Host;
173
174#ifdef OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING
175struct AltRoutingShimFactory : public RC<thread_unsafe_refcount>
176{
178
179 virtual AltRouting::Shim::Ptr shim(const Host &host) = 0;
180 virtual void report_error(const Host &host, const bool alt_routing)
181 {
182 }
183 virtual bool is_reset(const Host &host, const bool alt_routing)
184 {
185 return false;
186 }
187 virtual int connect_timeout()
188 {
189 return -1;
190 }
191 virtual IP::Addr remote_ip()
192 {
193 return IP::Addr();
194 }
195 virtual std::vector<IP::Addr> local_addrs()
196 {
197 return std::vector<IP::Addr>();
198 }
199 virtual int remote_port()
200 {
201 return -1;
202 }
203 virtual int alt_routing_debug_level()
204 {
205 return 0;
206 }
207};
208#endif
209
210struct Config : public RCCopyable<thread_unsafe_refcount>
211{
213
216 std::string user_agent;
217 unsigned int connect_timeout = 0;
218 unsigned int general_timeout = 0;
219 unsigned int keepalive_timeout = 0;
220 unsigned int websocket_timeout = 0; // becomes general_timeout when websocket begins streaming
221 unsigned int max_headers = 0;
222 unsigned int max_header_bytes = 0;
223 bool enable_cache = false; // if true, supports TLS session resumption tickets
225 unsigned int msg_overhead_bytes = 0;
226 int debug_level = 0;
230
231#ifdef OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING
232 AltRoutingShimFactory::Ptr shim_factory;
233#endif
234};
235
236struct Host
237{
238 std::string host;
239 std::string hint; // overrides host for transport, may be IP address
240 std::string cn; // host for CN verification, defaults to host if empty
241 std::string key; // host for TLS session ticket cache key, defaults to host if empty
242 std::string head; // host to send in HTTP header, defaults to host if empty
243 std::string port;
244
245 std::string local_addr; // bind to local address
246 std::string local_addr_alt; // alt local addr for different IP version (optional)
247 std::string local_port; // bind to local port (optional)
248
249#ifdef VPN_BINDING_PROFILES
250 // use a VPN binding profile to obtain hint
251 // and local_addr and possibly DNS resolvers as well
252 ViaVPN::Ptr via_vpn;
253#endif
254
255 const std::string &host_transport() const
256 {
257 return hint.empty() ? host : hint;
258 }
259
260 const std::string *host_cn_ptr() const
261 {
262 return cn.empty() ? &host : &cn;
263 }
264
265 const std::string &host_cn() const
266 {
267 return *host_cn_ptr();
268 }
269
270 const std::string &host_head() const
271 {
272 return head.empty() ? host : head;
273 }
274
275 std::string host_port_str() const
276 {
277 std::string ret;
278 const std::string &ht = host_transport();
279 if (ht == host)
280 {
281 ret += '[';
282 ret += host;
283 ret += "]:";
284 ret += port;
285 }
286 else
287 {
288 ret += host;
289 ret += '[';
290 ret += ht;
291 ret += "]:";
292 ret += port;
293 }
294 return ret;
295 }
296
297 std::string cache_key() const
298 {
299 return key.empty() ? (host + '/' + port) : key;
300 }
301};
302
304{
305 bool creds_defined() const
306 {
307 return !username.empty() || !password.empty();
308 }
309
310 void set_creds(const Creds &creds)
311 {
312 username = creds.username;
313 password = creds.password;
314 }
315
316 std::string method;
317 std::string uri;
318 std::string username;
319 std::string password;
320};
321
323{
324 // content length if Transfer-Encoding: chunked
325 static constexpr olong CHUNKED = -1;
326
327 std::string type;
328 std::string content_encoding;
330 bool keepalive = false;
331 bool lean_headers = false;
332 std::vector<std::string> extra_headers;
334};
335
337{
338 // Timeout overrides in seconds.
339 // Set to -1 to disable.
340 int connect = -1;
341 int general = -1;
342 int keepalive = -1;
343 int websocket = -1;
344};
345
346class HTTPCore;
348
349class HTTPCore : public Base, public TransportClientParent
350#ifdef USE_ASYNC_RESOLVE
351 ,
352 public AsyncResolvableTCP
353#endif
354{
355 public:
356 friend Base;
357
359#ifndef USE_ASYNC_RESOLVE
360 using results_type = openvpn_io::ip::tcp::resolver::results_type;
361#endif
362
364 {
366 };
367
368 HTTPCore(openvpn_io::io_context &io_context_arg,
369 Config::Ptr config_arg)
370 : Base(std::move(config_arg)),
371#ifdef USE_ASYNC_RESOLVE
372 AsyncResolvableTCP(io_context_arg),
373#endif
374 io_context(io_context_arg),
375#ifndef USE_ASYNC_RESOLVE
376 resolver(io_context_arg),
377#endif
378 connect_timer(io_context_arg),
379 general_timer(io_context_arg),
380 general_timeout_coarse(Time::Duration::binary_ms(512), Time::Duration::binary_ms(1024))
381 {
382 }
383
384 virtual ~HTTPCore()
385 {
386 stop(false);
387 }
388
389 // Should be called before start_request().
391 {
392 to = std::move(to_arg);
393 }
394
395 bool is_alive() const
396 {
397 return alive;
398 }
399
401 {
402 return link && !halt;
403 }
404
405 // return true if the alt-routing state for this session
406 // has changed, requiring a reset
408 {
409#ifdef OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING
410 if (config->shim_factory
411 && socket
412 && config->shim_factory->is_reset(host, socket->alt_routing_enabled()))
413 return true;
414#endif
415 return false;
416 }
417
418 void check_ready() const
419 {
420 if (!is_ready())
421 throw http_client_exception("not ready");
422 }
423
425 {
426 check_ready();
427 ready = false;
429 openvpn_io::post(io_context, [self = Ptr(this)]()
430 { self->handle_request(); });
431 }
432
433 void start_request_after(const Time::Duration dur)
434 {
435 check_ready();
436 ready = false;
438 if (!req_timer)
440 req_timer->expires_after(dur);
441 req_timer->async_wait([self = Ptr(this)](const openvpn_io::error_code &error)
442 {
443 if (!error)
444 self->handle_request(); });
445 }
446
447 void stop(const bool shutdown)
448 {
449 if (!halt)
450 {
451 halt = true;
452 ready = false;
453 alive = false;
454 if (transcli)
455 transcli->stop();
456 if (link)
457 link->stop();
458 if (socket)
459 {
460 if (shutdown)
462 socket->close();
463 }
464#ifdef USE_ASYNC_RESOLVE
465 async_resolve_cancel();
466#else
467 resolver.cancel();
468#endif
469#ifdef VPN_BINDING_PROFILES
470 if (alt_resolve)
471 alt_resolve->stop();
472#endif
473 if (req_timer)
474 req_timer->cancel();
478 }
479 }
480
481 void abort(const std::string &message, const int status = Status::E_ABORTED)
482 {
483 if (!halt)
484 error_handler(status, message);
485 }
486
487 const HTTP::Reply &reply() const
488 {
489 return request_reply();
490 }
491
492 std::string remote_endpoint_str() const
493 {
494 try
495 {
496 if (socket)
497 return socket->remote_endpoint_str();
498 }
499 catch (const std::exception &)
500 {
501 }
502 return "[unknown endpoint]";
503 }
504
505 bool remote_ip_port(IP::Addr &addr, unsigned int &port) const
506 {
507 if (socket)
508 return socket->remote_ip_port(addr, port);
509 else
510 return false;
511 }
512
513 // Return the current Host object, but
514 // set the hint/port fields to the live
515 // IP address/port of the connection.
517 {
518 Host h = host;
519 if (socket)
520 {
521 IP::Addr addr;
522 unsigned int port;
523 if (socket->remote_ip_port(addr, port))
524 {
525 h.hint = addr.to_string();
527 }
528 }
529 return h;
530 }
531
532 bool host_match(const std::string &host_arg) const
533 {
534 if (host.host.empty())
535 return false;
536 else
537 return host.host == host_arg;
538 }
539
541 {
542 return socket.get();
543 }
544
546 {
547 const unsigned int sec = to.websocket >= 0 ? to.websocket : config->websocket_timeout;
548 if (sec)
549 reset_general_timeout(sec, true);
550 else
552 }
553
561
563 {
565 throw http_client_exception("streaming_restart() called when content-out is still in hold state");
566 if (is_deferred())
568 }
569
571 {
572 return !content_out_hold;
573 }
574
575 bool is_streaming_hold() const
576 {
577 return content_out_hold;
578 }
579
580 // virtual methods
581
582 virtual Host http_host() = 0;
583
584 virtual Request http_request() = 0;
585
587 {
588 return ContentInfo();
589 }
590
592 {
593 return BufferPtr();
594 }
595
597 {
598 }
599
601 {
602 }
603
604 virtual void http_headers_sent(const Buffer &buf)
605 {
606 }
607
609 {
610 }
611
612 virtual void http_content_in(BufferAllocated &buf) = 0;
613
614 virtual void http_done(const int status, const std::string &description) = 0;
615
616 virtual void http_keepalive_close(const int status, const std::string &description)
617 {
618 }
619
621 {
622 }
623
624 private:
626 friend LinkImpl::Base; // calls tcp_* handlers
627
629 {
630 if (!frame)
631 throw http_client_exception("frame undefined");
632 }
633
634#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
635 bool inject_fault(const char *caller)
636 {
637 if (periodic_fail.trigger("httpcli", SIMULATE_HTTPCLI_FAILURES))
638 {
639 OPENVPN_LOG("HTTPCLI BOGON on " << host.host_port_str() << " (" << caller << ')');
641 return true;
642 }
643 else
644 return false;
645 }
646#endif
647
648 void activity(const bool init)
649 {
650 const Time now = Time::now();
651 if (general_timeout_duration.defined())
652 {
653 const Time next = now + general_timeout_duration;
654 if (init || !general_timeout_coarse.similar(next))
655 {
658 general_timer.async_wait([self = Ptr(this)](const openvpn_io::error_code &error)
659 {
660 if (!error)
661 self->general_timeout_handler(error); });
662 }
663 }
664 else if (init)
665 {
668 }
669 }
670
671 void handle_request() // called by Asio
672 {
673 if (halt)
674 return;
675
676 try
677 {
678 if (ready)
679 throw http_client_exception("handle_request called in ready state");
680
681 verify_frame();
682
683 reset_general_timeout(to.general >= 0 ? to.general : config->general_timeout, false);
684
685 // already in persistent session?
686 if (alive)
687 {
689 return;
690 }
691
692 // get new Host object
693 host = http_host();
694
695#ifdef VPN_BINDING_PROFILES
696 // support VPN binding profile
697 Json::Value via_vpn_conf;
698 if (host.via_vpn)
699 via_vpn_conf = host.via_vpn->client_update_host(host);
700#endif
701
702#ifdef ASIO_HAS_LOCAL_SOCKETS
703 // unix domain socket?
704 if (host.port == "unix")
705 {
706 openvpn_io::local::stream_protocol::endpoint ep(host.host_transport());
707 AsioPolySock::Unix *s = new AsioPolySock::Unix(io_context, 0);
708 socket.reset(s);
709 s->socket.async_connect(ep,
710 [self = Ptr(this)](const openvpn_io::error_code &error)
711 {
712 self->handle_unix_connect(error);
713 });
714 set_connect_timeout(config->connect_timeout);
715 return;
716 }
717#endif
718#ifdef OPENVPN_PLATFORM_WIN
719 // windows named pipe?
720 if (host.port == "np")
721 {
722 const std::string &ht = host.host_transport();
723 const HANDLE h = ::CreateFileA(
724 ht.c_str(),
725 GENERIC_READ | GENERIC_WRITE,
726 0,
727 NULL,
728 OPEN_EXISTING,
729 FILE_FLAG_OVERLAPPED,
730 NULL);
731 if (!Win::Handle::defined(h))
732 {
733 const Win::LastError err;
734 OPENVPN_THROW(http_client_exception, "failed to open existing named pipe: " << ht << " : " << err.message());
735 }
736 socket.reset(new AsioPolySock::NamedPipe(openvpn_io::windows::stream_handle(io_context, h), 0));
737 do_connect(true);
738 set_connect_timeout(config->connect_timeout);
739 return;
740 }
741#endif
742#if defined(OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING)
743 // alt routing?
744 if (config->shim_factory)
745 {
746 AltRouting::Shim::Ptr shim = config->shim_factory->shim(host);
747 if (shim)
748 {
749 alt_routing_connect(std::move(shim));
750 return;
751 }
752 }
753#endif
754
755 // standard TCP (with or without SSL)
756 if (host.port.empty())
757 host.port = config->ssl_factory ? "443" : "80";
758
759 if (config->ssl_factory)
760 {
761 if (config->enable_cache)
762 {
763 std::string cache_key = host.cache_key();
764 ssl_sess = config->ssl_factory->ssl(host.host_cn_ptr(), &cache_key);
765 }
766 else
767 ssl_sess = config->ssl_factory->ssl(host.host_cn_ptr(), nullptr);
768 }
769
770 if (config->transcli)
771 {
772 transcli = config->transcli->new_transport_client_obj(io_context, this);
774 }
775 else
776 {
777#ifdef USE_ASYNC_RESOLVE
778 async_resolve_name(host.host_transport(), host.port);
779#else
780#ifdef VPN_BINDING_PROFILES
781 if (via_vpn_conf)
782 {
783 DNSClient::ResolverList::Ptr resolver_list(new DNSClient::ResolverList(via_vpn_conf));
784 alt_resolve = DNSClient::async_resolve(io_context,
785 std::move(resolver_list),
786 config->prng.get(),
788 host.port,
789 [self = Ptr(this)](const openvpn_io::error_code &error,
790 results_type results) mutable
791 { self->resolve_callback(error, std::move(results)); });
792 }
793 else
794#endif
795 resolver.async_resolve(host.host_transport(),
796 host.port,
797 [self = Ptr(this)](const openvpn_io::error_code &error,
798 results_type results) mutable
799 { self->resolve_callback(error, std::move(results)); });
800#endif
801 }
802 set_connect_timeout(config->connect_timeout);
803 }
804 catch (const std::exception &e)
805 {
806 handle_exception("handle_request", e);
807 }
808 }
809
810 void resolve_callback(const openvpn_io::error_code &error, // called by Asio
811 results_type results)
812 {
813 if (halt)
814 return;
815
816#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
817 if (inject_fault("resolve_callback"))
818 return;
819#endif
820
821 if (error)
822 {
823 asio_error_handler(Status::E_RESOLVE, "resolve_callback", error);
824 return;
825 }
826
827 try
828 {
830 if (results.empty())
831 OPENVPN_THROW_EXCEPTION("no results");
832
834 socket.reset(s);
836
837 if (config->debug_level >= 2)
838 OPENVPN_LOG("TCP HTTP CONNECT to " << s->remote_endpoint_str() << " res=" << asio_resolver_results_to_string(results));
839
840 openvpn_io::async_connect(s->socket,
841 std::move(results),
842 [self = Ptr(this)](const openvpn_io::error_code &error_, const openvpn_io::ip::tcp::endpoint &endpoint)
843 { self->handle_tcp_connect(error_, endpoint); });
844 }
845 catch (const std::exception &e)
846 {
847 handle_exception("resolve_callback", e);
848 }
849 }
850
851 void handle_tcp_connect(const openvpn_io::error_code &error, // called by Asio
852 const openvpn_io::ip::tcp::endpoint &endpoint)
853 {
854 if (halt)
855 return;
856
857#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
858 if (inject_fault("handle_tcp_connect"))
859 return;
860#endif
861
862 if (error)
863 {
864 asio_error_handler(Status::E_CONNECT, "handle_tcp_connect", error);
865 return;
866 }
867
868 try
869 {
870 do_connect(true);
871 }
872 catch (const std::exception &e)
873 {
874 handle_exception("handle_tcp_connect", e);
875 }
876 }
877
878#ifdef ASIO_HAS_LOCAL_SOCKETS
879 void handle_unix_connect(const openvpn_io::error_code &error) // called by Asio
880 {
881 if (halt)
882 return;
883
884#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
885 if (inject_fault("handle_unix_connect"))
886 return;
887#endif
888
889 if (error)
890 {
891 asio_error_handler(Status::E_CONNECT, "handle_unix_connect", error);
892 return;
893 }
894
895 try
896 {
897 do_connect(true);
898 }
899 catch (const std::exception &e)
900 {
901 handle_exception("handle_unix_connect", e);
902 }
903 }
904#endif
905
906#if defined(OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING)
907 void alt_routing_connect(AltRouting::Shim::Ptr shim)
908 {
909 AltRoutingShimFactory &sf = *config->shim_factory;
910
911 // build socket and assign shim
912 AsioPolySock::TCP *s = new AsioPolySock::TCP(io_context, 0);
913 socket.reset(s);
914
915 // bind local?
916 const std::vector<IP::Addr> local_addrs = sf.local_addrs();
917 for (const auto &la : local_addrs)
918 s->socket.bind_local(la, 0);
919
920 // add shim to socket
921 s->socket.shim = std::move(shim);
922
923 // build results
924 int port = sf.remote_port();
925 if (port < 0)
926 port = HostPort::parse_port(host.port, "AltRouting");
927 IP::Addr addr = sf.remote_ip();
928 if (!addr.defined())
929 addr = IP::Addr(host.host_transport(), "AltRouting");
930 // TODO: the static_cast of port is not proven safe
931 results_type results = results_type::create(openvpn_io::ip::tcp::endpoint(addr.to_asio(),
932 static_cast<asio::ip::port_type>(port)),
933 host.host,
934 "");
935
936 if (sf.alt_routing_debug_level() >= 2)
937 OPENVPN_LOG("ALT_ROUTING HTTP CONNECT to " << s->remote_endpoint_str() << " res=" << asio_resolver_results_to_string(results));
938
939 // do async connect
940 openvpn_io::async_connect(s->socket,
941 std::move(results),
942 [self = Ptr(this)](const openvpn_io::error_code &error, const openvpn_io::ip::tcp::endpoint &endpoint)
943 { self->handle_tcp_connect(error, endpoint); });
944
945 // set connect timeout
946 {
947 int ct = sf.connect_timeout();
948 if (ct < 0)
949 ct = config->connect_timeout;
951 }
952 }
953#endif
954
955 void do_connect(const bool use_link)
956 {
959
960 if (use_link)
961 {
962 socket->set_cloexec();
963 socket->tcp_nodelay();
965 link.reset(new LinkImpl(this,
966 *socket,
967 0, // send_queue_max_size (unlimited)
968 8, // free_list_max_size
970 stats));
971 link->set_raw_mode(true);
972 link->start();
973 }
974
975 if (ssl_sess)
977
978 // xmit the request
980 }
981
982 void set_connect_timeout(unsigned int connect_timeout)
983 {
984 if (config->connect_timeout)
985 {
986 connect_timer.expires_after(Time::Duration::seconds(to.connect >= 0
987 ? to.connect
988 : connect_timeout));
989 connect_timer.async_wait([self = Ptr(this)](const openvpn_io::error_code &error)
990 {
991 if (!error)
992 self->connect_timeout_handler(error); });
993 }
994 }
995
997 {
998 // optionally bind to local addr/port
999 if (!host.local_addr.empty())
1000 {
1001#if defined(OPENVPN_POLYSOCK_SUPPORTS_BIND) || defined(OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING)
1002 const IP::Addr local_addr(host.local_addr, "local_addr");
1003 unsigned short local_port = 0;
1004 if (!host.local_port.empty())
1005 local_port = HostPort::parse_port(host.local_port, "local_port");
1006 s->socket.bind_local(local_addr, local_port);
1007
1008 if (!host.local_addr_alt.empty())
1009 {
1010 const IP::Addr local_addr_alt(host.local_addr_alt, "local_addr_alt");
1011 if (local_addr.version() == local_addr_alt.version())
1012 throw Exception("local bind addresses having the same IP version don't make sense: " + local_addr.to_string() + ' ' + local_addr_alt.to_string());
1013 s->socket.bind_local(local_addr_alt, local_port);
1014 }
1015#else
1016 throw Exception("httpcli must be built with OPENVPN_POLYSOCK_SUPPORTS_BIND or OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING to support local bind");
1017#endif
1018 }
1019 }
1020
1022 {
1023 if (config->keepalive_timeout || to.keepalive >= 0)
1024 {
1025 const Time::Duration dur = Time::Duration::seconds(to.keepalive >= 0
1026 ? to.keepalive
1027 : config->keepalive_timeout);
1028 if (!keepalive_timer)
1030 keepalive_timer->expires_after(dur);
1031 keepalive_timer->async_wait([self = Ptr(this)](const openvpn_io::error_code &error)
1032 {
1033 if (!self->halt && !error && self->ready)
1034 {
1035 self->error_handler(Status::E_KEEPALIVE_TIMEOUT, "Keepalive timeout");
1036 } });
1037 }
1038 }
1039
1041 {
1042 if (keepalive_timer)
1043 keepalive_timer->cancel();
1044 }
1045
1046 void reset_general_timeout(const unsigned int seconds,
1047 const bool register_activity_on_input_only_arg)
1048 {
1049 general_timeout_duration = Time::Duration::seconds(seconds);
1051 activity(true);
1052 register_activity_on_input_only = register_activity_on_input_only_arg;
1053 }
1054
1061
1062 void general_timeout_handler(const openvpn_io::error_code &e) // called by Asio
1063 {
1064 if (!halt && !e)
1065 error_handler(Status::E_GENERAL_TIMEOUT, "General timeout");
1066 }
1067
1068 void connect_timeout_handler(const openvpn_io::error_code &e) // called by Asio
1069 {
1070 if (!halt && !e)
1071 error_handler(Status::E_CONNECT_TIMEOUT, "Connect timeout");
1072 }
1073
1075 {
1076 if (!stats)
1077 stats.reset(new SessionStats());
1078 }
1079
1081 {
1082 rr_reset();
1084
1085 const Request req = http_request();
1087
1090
1091 if (content_info.websocket)
1092 {
1093 // no content-out until after server reply (content_out_hold kept at true)
1095 }
1096 else
1097 {
1098 // non-websocket allows immediate content-out
1099 content_out_hold = false;
1100 generate_request_http(os, req);
1101 }
1102
1104 http_out();
1105 }
1106
1107 void generate_request_http(std::ostream &os, const Request &req)
1108 {
1109 os << req.method << ' ' << req.uri << " HTTP/1.1\r\n";
1110 if (!content_info.lean_headers)
1111 {
1112 os << "Host: " << host.host_head() << "\r\n";
1113 if (!config->user_agent.empty())
1114 os << "User-Agent: " << config->user_agent << "\r\n";
1115 }
1117 if (content_info.length)
1118 os << "Content-Type: " << content_info.type << "\r\n";
1119 if (content_info.length > 0)
1120 os << "Content-Length: " << content_info.length << "\r\n";
1121 else if (content_info.length == ContentInfo::CHUNKED)
1122 os << "Transfer-Encoding: chunked"
1123 << "\r\n";
1124 for (auto &h : content_info.extra_headers)
1125 os << h << "\r\n";
1126 if (!content_info.content_encoding.empty())
1127 os << "Content-Encoding: " << content_info.content_encoding << "\r\n";
1128 if (content_info.keepalive)
1129 os << "Connection: keep-alive\r\n";
1130 if (!content_info.lean_headers)
1131 os << "Accept: */*\r\n";
1132 os << "\r\n";
1133 }
1134
1135 void generate_request_websocket(std::ostream &os, const Request &req)
1136 {
1137 os << req.method << ' ' << req.uri << " HTTP/1.1\r\n";
1138 os << "Host: " << host.host_head() << "\r\n";
1139 if (!config->user_agent.empty())
1140 os << "User-Agent: " << config->user_agent << "\r\n";
1142 if (content_info.length)
1143 os << "Content-Type: " << content_info.type << "\r\n";
1144 if (content_info.websocket)
1145 content_info.websocket->client_headers(os);
1146 for (auto &h : content_info.extra_headers)
1147 os << h << "\r\n";
1148 os << "\r\n";
1149 }
1150
1151 void generate_basic_auth_headers(std::ostream &os, const Request &req)
1152 {
1153 if (!req.username.empty() || !req.password.empty())
1154 os << "Authorization: Basic "
1155 << base64->encode(req.username + ':' + req.password)
1156 << "\r\n";
1157 }
1158
1159 // error handlers
1160
1161 void asio_error_handler(int errcode, const char *func_name, const openvpn_io::error_code &error)
1162 {
1163 error_handler(errcode, std::string("HTTPCore Asio ") + func_name + ": " + error.message());
1164 }
1165
1166 void handle_exception(const char *func_name, const std::exception &e)
1167 {
1168 error_handler(Status::E_EXCEPTION, std::string("HTTPCore Exception ") + func_name + ": " + e.what());
1169 }
1170
1171 void error_handler(const int errcode, const std::string &err)
1172 {
1173 const bool in_transaction = !ready;
1174 const bool keepalive = alive;
1175 const bool error = Status::is_error(errcode);
1176#if defined(OPENVPN_POLYSOCK_SUPPORTS_ALT_ROUTING)
1177 if (config->shim_factory && error && in_transaction && socket)
1178 config->shim_factory->report_error(host, socket->alt_routing_enabled());
1179#endif
1180 stop(!error);
1181 if (in_transaction)
1182 http_done(errcode, err);
1183 else if (keepalive)
1184 http_keepalive_close(errcode, err); // keepalive connection close outside of transaction
1185 }
1186
1187 // methods called by LinkImpl
1188
1190 {
1191 if (halt)
1192 return false;
1193
1194 try
1195 {
1196#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
1197 if (inject_fault("tcp_read_handler"))
1198 return false;
1199#endif
1200 activity(false);
1201 tcp_in(b); // call Base
1202 return true;
1203 }
1204 catch (const std::exception &e)
1205 {
1206 handle_exception("tcp_read_handler", e);
1207 return false;
1208 }
1209 }
1210
1212 {
1213 if (halt)
1214 return;
1215
1216 try
1217 {
1218 http_out();
1219 }
1220 catch (const std::exception &e)
1221 {
1222 handle_exception("tcp_write_queue_needs_send", e);
1223 }
1224 }
1225
1227 {
1228 if (halt)
1229 return;
1230
1231 try
1232 {
1233 error_handler(Status::E_EOF_TCP, "TCP EOF");
1234 return;
1235 }
1236 catch (const std::exception &e)
1237 {
1238 handle_exception("tcp_eof_handler", e);
1239 }
1240 }
1241
1242 void tcp_error_handler(const char *error)
1243 {
1244 if (halt)
1245 return;
1246 error_handler(Status::E_TCP, std::string("HTTPCore TCP: ") + error);
1247 }
1248
1249 // methods called by Base
1250
1252 {
1253 return http_content_out();
1254 }
1255
1261
1263 {
1264 if (websocket)
1265 {
1266 stop(true);
1267 http_done(Status::E_SUCCESS, "Succeeded");
1268 }
1269 }
1270
1272 {
1273 if (content_info.websocket)
1274 websocket = true; // enable websocket in httpcommon
1276 return true; // continue to receive content
1277 }
1278
1280 {
1281 http_content_in(buf);
1282 }
1283
1285 {
1286 try
1287 {
1288#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
1289 if (inject_fault("base_link_send"))
1290 return false;
1291#endif
1293 activity(false);
1294 if (transcli)
1295 return transcli->transport_send(buf);
1296 else
1297 return link->send(buf);
1298 }
1299 catch (const std::exception &e)
1300 {
1301 handle_exception("base_link_send", e);
1302 return false;
1303 }
1304 }
1305
1307 {
1308 if (transcli)
1310 else
1311 return link->send_queue_empty();
1312 }
1313
1315 const bool parent_handoff)
1316 {
1317 if (halt)
1318 return;
1319 if ((content_info.keepalive || parent_handoff) && !websocket)
1320 {
1323 alive = true;
1324 ready = true;
1325 }
1326 else
1327 stop(true);
1328 http_done(Status::E_SUCCESS, "Succeeded");
1329 }
1330
1331 void base_error_handler(const int errcode, const std::string &err)
1332 {
1333 error_handler(errcode, err);
1334 }
1335
1336 // TransportClientParent methods
1337
1339 {
1340 return false;
1341 }
1342
1344 {
1345 tcp_read_handler(buf);
1346 }
1347
1348 void transport_needs_send() override
1349 {
1351 }
1352
1353 std::string err_fmt(const Error::Type fatal_err, const std::string &err_text)
1354 {
1355 std::ostringstream os;
1356 if (fatal_err != Error::SUCCESS)
1357 os << Error::name(fatal_err) << " : ";
1358 os << err_text;
1359 return os.str();
1360 }
1361
1362 void transport_error(const Error::Type fatal_err, const std::string &err_text) override
1363 {
1364 return error_handler(Status::E_TRANSPORT, err_fmt(fatal_err, err_text));
1365 }
1366
1367 void proxy_error(const Error::Type fatal_err, const std::string &err_text) override
1368 {
1369 return error_handler(Status::E_PROXY, err_fmt(fatal_err, err_text));
1370 }
1371
1373 {
1374 }
1375
1376 void transport_wait_proxy() override
1377 {
1378 }
1379
1380 void transport_wait() override
1381 {
1382 }
1383
1384 bool is_keepalive_enabled() const override
1385 {
1386 return false;
1387 }
1388
1389 void disable_keepalive(unsigned int &keepalive_ping,
1390 unsigned int &keepalive_timeout) override
1391 {
1392 }
1393
1394 void transport_connecting() override
1395 {
1396 do_connect(false);
1397 }
1398
1399 openvpn_io::io_context &io_context;
1400
1402
1404
1405#ifndef USE_ASYNC_RESOLVE
1406 openvpn_io::ip::tcp::resolver resolver;
1407#endif
1408#ifdef VPN_BINDING_PROFILES
1409 DNSClient::Context::Ptr alt_resolve;
1410#endif
1412
1414
1416
1419 std::unique_ptr<AsioTimerSafe> req_timer;
1420 std::unique_ptr<AsioTimerSafe> keepalive_timer;
1421
1425
1426 bool content_out_hold = true;
1427 bool alive = false;
1428
1429#ifdef SIMULATE_HTTPCLI_FAILURES // debugging -- simulate network failures
1430 PeriodicFail periodic_fail;
1431#endif
1432};
1433
1434template <typename PARENT>
1436{
1437 public:
1438 OPENVPN_EXCEPTION(http_delegate_error);
1439
1441
1442 HTTPDelegate(openvpn_io::io_context &io_context,
1444 PARENT *parent)
1445 : WS::Client::HTTPCore(io_context, std::move(config)),
1447 {
1448 }
1449
1450 void attach(PARENT *parent)
1451 {
1452 parent_ = parent;
1453 }
1454
1455 void detach(const bool keepalive, const bool shutdown)
1456 {
1457 if (parent_)
1458 {
1459 parent_ = nullptr;
1460 if (!keepalive)
1461 stop(shutdown);
1462 }
1463 }
1464
1465 PARENT *parent()
1466 {
1467 return parent_;
1468 }
1469
1470 Host http_host() override
1471 {
1472 if (parent_)
1473 return parent_->http_host(*this);
1474 else
1475 throw http_delegate_error("http_host");
1476 }
1477
1479 {
1480 if (parent_)
1481 return parent_->http_request(*this);
1482 else
1483 throw http_delegate_error("http_request");
1484 }
1485
1487 {
1488 if (parent_)
1489 return parent_->http_content_info(*this);
1490 else
1491 throw http_delegate_error("http_content_info");
1492 }
1493
1495 {
1496 if (parent_)
1497 return parent_->http_content_out(*this);
1498 else
1499 throw http_delegate_error("http_content_out");
1500 }
1501
1503 {
1504 if (parent_)
1505 parent_->http_content_out_needed(*this);
1506 else
1507 throw http_delegate_error("http_content_out_needed");
1508 }
1509
1511 {
1512 if (parent_)
1513 parent_->http_headers_received(*this);
1514 }
1515
1516 void http_headers_sent(const Buffer &buf) override
1517 {
1518 if (parent_)
1519 parent_->http_headers_sent(*this, buf);
1520 }
1521
1523 {
1524 if (parent_)
1525 parent_->http_mutate_resolver_results(*this, results);
1526 }
1527
1529 {
1530 if (parent_)
1531 parent_->http_content_in(*this, buf);
1532 }
1533
1534 void http_done(const int status, const std::string &description) override
1535 {
1536 if (parent_)
1537 parent_->http_done(*this, status, description);
1538 }
1539
1540 void http_keepalive_close(const int status, const std::string &description) override
1541 {
1542 if (parent_)
1543 parent_->http_keepalive_close(*this, status, description);
1544 }
1545
1547 {
1548 if (parent_)
1549 parent_->http_post_connect(*this, sock);
1550 }
1551
1552 private:
1553 PARENT *parent_;
1554};
1555} // namespace openvpn::WS::Client
std::size_t expires_at(const Time &t)
std::size_t expires_after(const Time::Duration &d)
void async_wait(F &&func)
std::string encode(const V &data) const
Definition base64.hpp:139
bool similar(const Time &t) const
void reset(const Time &t)
Version version() const
Definition ip.hpp:895
std::string to_string() const
Definition ip.hpp:528
Reference count base class for objects tracked by RCPtr. Allows copying and assignment.
Definition rc.hpp:979
The smart pointer class.
Definition rc.hpp:119
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
T * get() const noexcept
Returns the raw pointer to the object T, or nullptr.
Definition rc.hpp:321
Reference count base class for objects tracked by RCPtr. Disallows copying and assignment.
Definition rc.hpp:912
static Ptr Create(ArgsT &&...args)
Creates a new instance of RcEnable with the given arguments.
Definition make_rc.hpp:43
virtual void start_handshake()=0
static TimeType now()
Definition time.hpp:305
void start_request_after(const Time::Duration dur)
Definition httpcli.hpp:433
void transport_needs_send() override
Definition httpcli.hpp:1348
void handle_tcp_connect(const openvpn_io::error_code &error, const openvpn_io::ip::tcp::endpoint &endpoint)
Definition httpcli.hpp:851
void stop(const bool shutdown)
Definition httpcli.hpp:447
openvpn_io::io_context & io_context
Definition httpcli.hpp:1399
virtual void http_content_in(BufferAllocated &buf)=0
HTTPCore(openvpn_io::io_context &io_context_arg, Config::Ptr config_arg)
Definition httpcli.hpp:368
virtual void http_headers_sent(const Buffer &buf)
Definition httpcli.hpp:604
openvpn_io::ip::tcp::resolver::results_type results_type
Definition httpcli.hpp:360
virtual void http_post_connect(AsioPolySock::Base &sock)
Definition httpcli.hpp:620
void override_timeouts(TimeoutOverride to_arg)
Definition httpcli.hpp:390
void transport_error(const Error::Type fatal_err, const std::string &err_text) override
Definition httpcli.hpp:1362
openvpn_io::ip::tcp::resolver resolver
Definition httpcli.hpp:1406
std::string err_fmt(const Error::Type fatal_err, const std::string &err_text)
Definition httpcli.hpp:1353
bool is_streaming_restartable() const
Definition httpcli.hpp:570
void set_connect_timeout(unsigned int connect_timeout)
Definition httpcli.hpp:982
void disable_keepalive(unsigned int &keepalive_ping, unsigned int &keepalive_timeout) override
Definition httpcli.hpp:1389
void base_http_content_in(BufferAllocated &buf)
Definition httpcli.hpp:1279
void base_error_handler(const int errcode, const std::string &err)
Definition httpcli.hpp:1331
void reset_general_timeout(const unsigned int seconds, const bool register_activity_on_input_only_arg)
Definition httpcli.hpp:1046
virtual Request http_request()=0
void transport_recv(BufferAllocated &buf) override
Definition httpcli.hpp:1343
bool is_keepalive_enabled() const override
Definition httpcli.hpp:1384
TCPTransport::TCPLink< AsioProtocol, HTTPCore *, false > LinkImpl
Definition httpcli.hpp:625
void generate_basic_auth_headers(std::ostream &os, const Request &req)
Definition httpcli.hpp:1151
void connect_timeout_handler(const openvpn_io::error_code &e)
Definition httpcli.hpp:1068
void base_http_done_handler(BufferAllocated &residual, const bool parent_handoff)
Definition httpcli.hpp:1314
virtual void http_content_out_needed()
Definition httpcli.hpp:596
void general_timeout_handler(const openvpn_io::error_code &e)
Definition httpcli.hpp:1062
bool remote_ip_port(IP::Addr &addr, unsigned int &port) const
Definition httpcli.hpp:505
void tcp_error_handler(const char *error)
Definition httpcli.hpp:1242
bool tcp_read_handler(BufferAllocated &b)
Definition httpcli.hpp:1189
void transport_wait_proxy() override
Definition httpcli.hpp:1376
void proxy_error(const Error::Type fatal_err, const std::string &err_text) override
Definition httpcli.hpp:1367
void transport_pre_resolve() override
Definition httpcli.hpp:1372
virtual void http_headers_received()
Definition httpcli.hpp:600
void handle_exception(const char *func_name, const std::exception &e)
Definition httpcli.hpp:1166
void bind_local_addr(AsioPolySock::TCP *s)
Definition httpcli.hpp:996
void asio_error_handler(int errcode, const char *func_name, const openvpn_io::error_code &error)
Definition httpcli.hpp:1161
std::string remote_endpoint_str() const
Definition httpcli.hpp:492
void error_handler(const int errcode, const std::string &err)
Definition httpcli.hpp:1171
bool host_match(const std::string &host_arg) const
Definition httpcli.hpp:532
virtual void http_keepalive_close(const int status, const std::string &description)
Definition httpcli.hpp:616
void activity(const bool init)
Definition httpcli.hpp:648
virtual void http_mutate_resolver_results(results_type &results)
Definition httpcli.hpp:608
bool base_link_send(BufferAllocated &buf)
Definition httpcli.hpp:1284
virtual BufferPtr http_content_out()
Definition httpcli.hpp:591
const HTTP::Reply & reply() const
Definition httpcli.hpp:487
Time::Duration general_timeout_duration
Definition httpcli.hpp:1422
void transport_wait() override
Definition httpcli.hpp:1380
TransportClient::Ptr transcli
Definition httpcli.hpp:1415
void generate_request_websocket(std::ostream &os, const Request &req)
Definition httpcli.hpp:1135
virtual void http_done(const int status, const std::string &description)=0
std::unique_ptr< AsioTimerSafe > keepalive_timer
Definition httpcli.hpp:1420
bool is_alt_routing_reset() const
Definition httpcli.hpp:407
virtual ContentInfo http_content_info()
Definition httpcli.hpp:586
bool transport_is_openvpn_protocol() override
Definition httpcli.hpp:1338
std::unique_ptr< AsioTimerSafe > req_timer
Definition httpcli.hpp:1419
void do_connect(const bool use_link)
Definition httpcli.hpp:955
void generate_request_http(std::ostream &os, const Request &req)
Definition httpcli.hpp:1107
AsioPolySock::Base * get_socket()
Definition httpcli.hpp:540
void resolve_callback(const openvpn_io::error_code &error, results_type results)
Definition httpcli.hpp:810
void abort(const std::string &message, const int status=Status::E_ABORTED)
Definition httpcli.hpp:481
AsioPolySock::Base::Ptr socket
Definition httpcli.hpp:1403
void transport_connecting() override
Definition httpcli.hpp:1394
OPENVPN_EXCEPTION(http_delegate_error)
void http_headers_received() override
Definition httpcli.hpp:1510
void http_content_in(BufferAllocated &buf) override
Definition httpcli.hpp:1528
void http_headers_sent(const Buffer &buf) override
Definition httpcli.hpp:1516
void http_done(const int status, const std::string &description) override
Definition httpcli.hpp:1534
void http_post_connect(AsioPolySock::Base &sock) override
Definition httpcli.hpp:1546
BufferPtr http_content_out() override
Definition httpcli.hpp:1494
RCPtr< HTTPDelegate > Ptr
Definition httpcli.hpp:1440
void detach(const bool keepalive, const bool shutdown)
Definition httpcli.hpp:1455
ContentInfo http_content_info() override
Definition httpcli.hpp:1486
void http_mutate_resolver_results(results_type &results) override
Definition httpcli.hpp:1522
HTTPDelegate(openvpn_io::io_context &io_context, WS::Client::Config::Ptr config, PARENT *parent)
Definition httpcli.hpp:1442
Request http_request() override
Definition httpcli.hpp:1478
void http_content_out_needed() override
Definition httpcli.hpp:1502
void attach(PARENT *parent)
Definition httpcli.hpp:1450
void http_keepalive_close(const int status, const std::string &description) override
Definition httpcli.hpp:1540
bool is_ready() const
void tcp_in(BufferAllocated &b)
bool is_deferred() const
CONTENT_INFO content_info
const REQUEST_REPLY::State & request_reply() const
SessionStats::Ptr stats
#define OPENVPN_THROW_EXCEPTION(stuff)
#define OPENVPN_EXCEPTION(C)
#define OPENVPN_THROW(exc, stuff)
#define OPENVPN_LOG(args)
const char * name(const size_t type)
Definition error.hpp:117
unsigned short parse_port(const std::string &port, const std::string &title)
Definition hostport.hpp:46
HTTPBase< HTTPCore, Config, Status, HTTP::ReplyType, ContentInfo, olong, RC< thread_unsafe_refcount > > Base
Definition httpcli.hpp:347
bool defined(HANDLE handle)
Definition handle.hpp:25
std::string to_string(T value)
Definition to_string.hpp:33
constexpr std::size_t array_size(T(&)[N])
Definition arraysize.hpp:19
std::string asio_resolver_results_to_string(const EPRANGE &endpoint_range)
const Base64 * base64
Definition base64.hpp:299
RCPtr< BufferAllocatedRc > BufferPtr
Definition buffer.hpp:1859
long olong
Definition olong.hpp:23
Implementation of the base classes for random number generators.
openvpn_io::ip::tcp::socket socket
std::string remote_endpoint_str() const override
@ GROW
if enabled, buffer will grow (otherwise buffer_full exception will be thrown)
Definition buffer.hpp:872
virtual void transport_start()=0
virtual bool transport_send_queue_empty()=0
virtual bool transport_send(BufferAllocated &buf)=0
virtual void stop()=0
unsigned int websocket_timeout
Definition httpcli.hpp:220
SSLFactoryAPI::Ptr ssl_factory
Definition httpcli.hpp:214
unsigned int keepalive_timeout
Definition httpcli.hpp:219
SessionStats::Ptr stats
Definition httpcli.hpp:228
unsigned int max_header_bytes
Definition httpcli.hpp:222
TransportClientFactory::Ptr transcli
Definition httpcli.hpp:215
unsigned int msg_overhead_bytes
Definition httpcli.hpp:225
WebSocket::Client::PerRequest::Ptr websocket
Definition httpcli.hpp:333
std::vector< std::string > extra_headers
Definition httpcli.hpp:332
static constexpr olong CHUNKED
Definition httpcli.hpp:325
std::string cache_key() const
Definition httpcli.hpp:297
const std::string & host_head() const
Definition httpcli.hpp:270
std::string host_port_str() const
Definition httpcli.hpp:275
const std::string & host_cn() const
Definition httpcli.hpp:265
const std::string * host_cn_ptr() const
Definition httpcli.hpp:260
const std::string & host_transport() const
Definition httpcli.hpp:255
void set_creds(const Creds &creds)
Definition httpcli.hpp:310
static bool is_error(const int status)
Definition httpcli.hpp:159
static std::string error_str(const int status)
Definition httpcli.hpp:124
std::string password
std::string username
proxy_host_port port
proxy_host_port host
std::string ret
const char message[]