OpenVPN 3 Core Library
Loading...
Searching...
No Matches
ovpnagent.cpp
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// OpenVPN agent for Mac
13
14// #define OPENVPN_EXIT_IN 30
15
16// #define OPENVPN_SSL_DEBUG 9 // MbedTLS debugging max level
17
18#include <iostream>
19#include <string>
20#include <utility>
21
22#include <unistd.h>
23
24// VERSION version can be passed on build command line
26#ifdef VERSION
27#define HTTP_SERVER_VERSION OPENVPN_STRINGIZE(VERSION)
28#else
29#define HTTP_SERVER_VERSION "0.1.1"
30#endif
31
32#ifdef OVPNAGENT_NAME
33#define OVPNAGENT_NAME_STRING OPENVPN_STRINGIZE(OVPNAGENT_NAME)
34#else
35#define OVPNAGENT_NAME_STRING "ovpnagent"
36#endif
37
39
43#include <openvpn/common/rc.hpp>
58
60{
61 std::cout << "OpenVPN Agent (Mac) " HTTP_SERVER_VERSION " [" SSL_LIB_NAME "]"
62#ifdef OPENVPN_DEBUG
63 << " built on " __DATE__ " " __TIME__
64#endif
65 << std::endl;
66}
67
68using namespace openvpn;
69
71{
72 public:
74
75 void error(const size_t err_type, const std::string *text = nullptr) override
76 {
77 OPENVPN_LOG(Error::name(err_type));
78 }
79
80 std::string dump() const
81 {
82 std::ostringstream os;
83 os << "OpenVPN Agent Stats" << std::endl;
84 return os.str();
85 }
86};
87
89{
90 ThreadCommon(const char *unix_sock, const char *user, const char *group)
91 : listen_list(build_listen_list(unix_sock)),
92 user_group(user, group, true),
95 {
96 }
97
98 static Listen::List build_listen_list(const char *unix_sock)
99 {
100 Listen::List ll;
101 if (unix_sock)
102 {
103 Listen::Item li;
104 li.directive = "http-listen";
105 li.addr = unix_sock;
107 li.n_threads = 1;
108 ll.push_back(std::move(li));
109 }
110 return ll;
111 }
112
114 {
115 }
116
121};
122
124{
125 // handles ungraceful exit of client and closes tun
126 class WatchdogThread : public RC<thread_safe_refcount>
127 {
128 private:
130
131 friend class MyListener;
133
134 public:
135 WatchdogThread(MyListener *parent_arg, openvpn_io::io_context &io_context_arg)
136 : parent(parent_arg), io_context(io_context_arg)
137 {
138 }
139
141 {
142 if (th.joinable())
143 {
144 OPENVPN_LOG("Reaping watchdog thread");
145 th.join();
146 }
147 }
148
149 // starts a thread which blocks until:
150 // - process with given pid exits
151 // - there is a data in pipe
152 void watch(pid_t pid)
153 {
154 if (client_pid != -1)
155 {
156 OPENVPN_LOG("Watchdog already set for pid " << client_pid << ", won't set for pid " << pid);
157 return;
158 }
159
160 OPENVPN_LOG("Setting up watchdog for pid " << pid << " exit notification");
161
162 // self-pipe trick to be able to interrupt wait when agent exits
163 if (pipe(fds) == -1)
164 {
165 OPENVPN_LOG("pipe() failed: " << strerror(errno));
166 return;
167 }
168 // make write nonblocking
169 fcntl(fds[1], F_SETFL, O_NONBLOCK);
170
171 kq = kqueue();
172 if (kq == -1)
173 {
174 OPENVPN_LOG("kqueue() failed: " << strerror(errno));
176 return;
177 }
178
179 // add pid exit and self-pipe read_fd to kevent changelist
180 struct kevent chlist[2];
181 EV_SET(&chlist[0], pid, EVFILT_PROC, EV_ADD | EV_RECEIPT, NOTE_EXIT, 0, NULL);
182 EV_SET(&chlist[1], fds[0], EVFILT_READ, EV_ADD, 0, 0, NULL);
183 if (kevent(kq, chlist, 2, NULL, 0, NULL) == -1)
184 {
185 OPENVPN_LOG("kevent() failed: " << strerror(errno));
187 return;
188 }
189
190 // reap thread if previous client didn't exit gracefullt
191 if (th.joinable())
192 {
193 OPENVPN_LOG("Reaping watchdog thread");
194 th.join();
195 }
196
197 client_pid = pid;
198
199 th = std::thread([self = Ptr(this), pid]()
200 {
201 struct kevent evlist[2];
202 int nev = kevent(self->kq, 0, 0, evlist, 2, NULL);
203 if (nev == -1)
204 {
205 OPENVPN_LOG("kevent() failed: " << strerror(errno));
206 self->close_pipe_fds();
207 return;
208 }
209
210 for (int i = 0; i < nev; ++ i)
211 {
212 if (evlist[i].filter == EVFILT_PROC)
213 {
214 openvpn_io::post(self->io_context, [self, pid]() {
215 OPENVPN_LOG("Process " << pid << " has exited, destroy tun");
216 std::ostringstream os;
217 self->parent->destroy_tun(os);
218 });
219 }
220 }
221
222 self->client_pid = -1;
223 self->close_pipe_fds(); });
224 }
225
226 void unwatch()
227 {
228 OPENVPN_LOG("Stopping watchdog thread");
229 write(fds[1], "x", 1);
230 if (th.joinable())
231 {
232 OPENVPN_LOG("Reaping watchdog thread");
233 th.join();
234 }
235 }
236
237 private:
239 {
240 close(fds[0]);
241 close(fds[1]);
242 }
243
244 pid_t client_pid = -1;
245 openvpn_io::io_context &io_context;
246 int kq; // kqueue
247 int fds[2]; // file descriptors for self-pipe trick
248 std::thread th;
249 };
250
251 public:
253
262
265 Stop *stop,
266 std::ostream &os)
267 {
268 if (!tun)
270 return ScopedFD(tun->establish(tbc, config, stop, os));
271 }
272
273 void destroy_tun(std::ostream &os)
274 {
275 if (tun)
276 {
277 tun->destroy(os);
278 tun.reset();
279 }
280
283 bypass_host.clear();
284 }
285
286 void set_watchdog(pid_t pid)
287 {
288 watchdog->watch(pid);
289 }
290
292 {
293 watchdog->unwatch();
294 }
295
296 void add_bypass_route(const std::string &host, bool ipv6)
297 {
298 if (host != bypass_host)
299 {
301
302 std::ostringstream os;
303
306
307 ActionList add_cmds;
309 add_cmds.execute(os);
310
311 OPENVPN_LOG(os.str());
312 }
313 }
314
315 private:
316 bool allow_client(AsioPolySock::Base &sock) override
317 {
318 return true;
319 }
320
321 std::string bypass_host;
323
326};
327
329{
330 public:
332
337
338 virtual ~MyClientInstance() = default;
339
340 private:
341 void generate_reply(const Json::Value &jout)
342 {
343 out = buf_from_string(jout.toStyledString());
344
347 ci.type = "application/json";
348 ci.length = out->size();
351 }
352
353 void http_request_received() override
354 {
355 // alloc output buffer
356 std::ostringstream os;
357
358 try
359 {
360 const HTTP::Request &req = request();
361 OPENVPN_LOG("HTTP request received from " << sock->remote_endpoint_str() << '\n'
362 << req.to_string());
363
364 // get content-type
365 const std::string content_type = req.headers.get_value_trim("content-type");
366
367 if (req.method == "POST")
368 {
369 // verify correct content-type
370 if (string::strcasecmp(content_type, "application/json"))
371 throw Exception("bad content-type");
372
373 // parse the json dict
374 const Json::Value root = json::parse(in.to_string(), "JSON request");
375 if (!root.isObject())
376 throw Exception("json parse error: top level json object is not a dictionary");
377
378 if (req.uri == "/tun-setup")
379 {
380 send_fd.reset();
381
382 // get PID
383 pid_t pid = json::get_int_optional(root, "pid", -1);
384 if (pid != -1)
385 parent()->set_watchdog(pid);
386
387 // parse JSON data into a TunBuilderCapture object
389 tbc->validate();
390
391 // get config
393 config.from_json(json::get_dict(root, "config", false), "config");
394
395 // establish the tun setup object
396 send_fd = parent()->establish_tun(*tbc, &config, nullptr, os);
397
398 // build JSON return dictionary
399 Json::Value jout(Json::objectValue);
400 jout["log_txt"] = Json::Value(string::remove_blanks(os.str()));
401 jout["config"] = config.to_json();
402 generate_reply(jout);
403 }
404 else if (req.uri == "/add-bypass-route")
405 {
406 pid_t pid = json::get_int_optional(root, "pid", -1);
407 if (pid != -1)
408 parent()->set_watchdog(pid);
409 bool ipv6 = json::get_bool(root, "ipv6");
410 std::string host = json::get_string(root, "host");
411
413
414 Json::Value jout(Json::objectValue);
415 generate_reply(jout);
416 }
417 }
418 else if (req.method == "GET" && req.uri == "/tun-destroy")
419 {
420 // destroy tun object
422
423 // build JSON return dictionary
424 Json::Value jout(Json::objectValue);
425 jout["log_txt"] = Json::Value(string::remove_blanks(os.str()));
426 generate_reply(jout);
427 }
428 else
429 {
430 out = buf_from_string("page not found\n");
433 ci.type = "text/plain";
434 ci.length = out->size();
436 }
437 }
438 catch (const std::exception &e)
439 {
440 out = buf_from_string(string::remove_blanks(os.str() + e.what() + '\n'));
443 ci.type = "text/plain";
444 ci.length = out->size();
446 }
447 }
448
450 {
451 if (buf.defined())
452 in.emplace_back(BufferAllocatedRc::Create(std::move(buf)));
453 }
454
456 {
458 ret.swap(out);
459 return ret;
460 }
461
462 // Normally true is returned, however return false if we
463 // are planning to send the tun file descriptor to the client.
464 bool http_out_eof() override
465 {
466 // OPENVPN_LOG("HTTP output EOF send_fd=" << send_fd());
467 return !send_fd.defined();
468 }
469
470 // After HTTP reply has been transmitted, wait for client to
471 // send a 't' message. On receipt, reply with a 'T' message
472 // that bundles the tun file descriptor.
474 {
475 // OPENVPN_LOG("HTTP PIPELINE PEEK send_fd=" << send_fd() << " CONTENT=" << buf_to_string(buf));
476 if (send_fd.defined())
477 {
478 if (buf.size() == 1 && buf.front() == 't')
479 {
480 const int fd = unix_fd();
481 if (fd < 0)
482 OPENVPN_THROW_EXCEPTION("http_pipeline_peek: not a unix socket");
483 XmitFD::xmit_fd(fd, send_fd(), "T", 5000);
484 external_stop("FD transmitted");
485 }
486 else
487 OPENVPN_THROW_EXCEPTION("bad FD request message");
488 }
489 }
490
491 bool http_stop(const int status, const std::string &description) override
492 {
493 OPENVPN_LOG("INSTANCE STOP : " << WS::Server::Status::error_str(status) << " : " << description);
494
495 // if the shutdown happened due to an unexpected error, the TUN status has
496 // to be cleaned up to avoid configuration inconsistency.
497 //
498 // A problem we have witnessed was that the DNS settings were not being
499 // reverted when the HTTP connection with the core was interrupted in the
500 // middle of the establish() call.
503 {
504 std::ostringstream os;
506 }
507
508 // returning true here triggers socket shutdown, which triggers "Socket is not connected" error
509 return false;
510 }
511
513 {
514 return static_cast<MyListener *>(get_parent());
515 }
516
520};
521
532
534{
535 public:
537
538 ServerThread(openvpn_io::io_context &io_context_arg,
539 ThreadCommon &tc)
540 : io_context(io_context_arg),
541 halt(false)
542 {
543 Frame::Ptr frame = frame_init_simple(2048);
544
547 config->frame = frame;
548 config->stats = tc.stats;
549 config->unix_mode = 0777;
550
552 listener.reset(new MyListener(io_context_arg, config, tc.listen_list, factory));
553 }
554
555 void start()
556 {
557 if (!halt)
558 {
559 listener->start();
560 }
561 }
562
563 void stop()
564 {
565 if (!halt)
566 {
567 halt = true;
568 listener->stop();
570 }
571 }
572
573 void thread_safe_stop() override
574 {
575 if (!halt)
576 {
577 openvpn_io::post(io_context, [self = Ptr(this)]()
578 { self->stop(); });
579 }
580 }
581
582 private:
583 openvpn_io::io_context &io_context;
585 bool halt;
586};
587
589
590void work(openvpn_io::io_context &io_context,
591 ThreadCommon &tc,
592 MyRunContext &runctx,
593 const unsigned int unit)
594{
596
597 try
598 {
599 serv.reset(new ServerThread(io_context, tc));
600 runctx.set_server(unit, serv.get());
601
602 serv->start();
603
604 // barrier prior to event-loop entry
606
607 // privilege has now been downgraded
608
609 // run i/o reactor
610 io_context.run();
611 runctx.clear_server(unit);
612 serv->stop();
613 }
614 catch (...)
615 {
617 if (serv)
618 {
619 runctx.clear_server(unit);
620 serv->stop(); // on exception, stop server
621 }
622 io_context.poll(); // execute completion handlers
623 throw;
624 }
625}
626
628 MyRunContext &runctx,
629 const unsigned int unit)
630{
631 SignalBlockerDefault signal_blocker;
632 openvpn_io::io_context io_context(1); // concurrency hint=1
633 Log::Context log_context(runctx.log_wrapper());
634 MyRunContext::ThreadContext thread_ctx(runctx);
635
636 try
637 {
638 work(io_context, tc, runctx, unit);
639 }
640 catch (const std::exception &e)
641 {
642 OPENVPN_LOG("Worker thread exception: " << e.what());
643 }
644}
645
646int ovpnagent(const char *sock_fn,
647 const char *log_fn,
648 const bool log_append,
649 const char *pid_fn,
650 const char *user,
651 const char *group)
652{
653 if (log_fn)
654 daemonize(log_fn, nullptr, log_append, 0);
655
656 if (pid_fn)
657 write_pid(pid_fn);
658
659 log_version();
660
661 MyRunContext::Ptr runctx(new MyRunContext());
662 ThreadCommon tc(sock_fn, user, group);
663
664 // Give runctx visibility into global stats
665 // for SIGUSR2 dump.
666 runctx->set_stats_obj(tc.stats);
667
668 // Main worker thread
669 {
670 const unsigned int thread_num = 0;
671 std::thread *thread = new std::thread([&tc, &runctx]()
672 { worker_thread(tc, *runctx, thread_num); });
673 runctx->set_thread(thread_num, thread);
674 }
675
676 // wait for worker to exit
677 runctx->run();
678 runctx->join();
679
680 // dump final stats
682
683 // remove pidfile
684 if (pid_fn)
685 ::unlink(pid_fn);
686
687 return 0;
688}
689
691
692int main(int argc, char *argv[])
693{
694 static const struct option longopts[] = {
695 {"help", no_argument, nullptr, 'h'},
696 {"append", no_argument, nullptr, 'a'},
697 {"daemon", required_argument, nullptr, 'd'},
698 {"pidfile", required_argument, nullptr, 'p'},
699 {"user", required_argument, nullptr, 'u'},
700 {"group", required_argument, nullptr, 'g'},
701 {nullptr, 0, nullptr, 0}};
702
703 int ret = 0;
704
705 bool append = false;
706 const char *logfile = nullptr;
707 const char *pidfile = nullptr;
708 const char *user = nullptr;
709 const char *group = nullptr;
710
711 // process-wide initialization
713
714 // set global MbedTLS debug level
715#if defined(USE_MBEDTLS) && defined(OPENVPN_SSL_DEBUG)
716 debug_set_threshold(OPENVPN_SSL_DEBUG);
717#endif
718
719 try
720 {
721 if (argc < 1)
722 throw usage();
723
724 int ch;
725 while ((ch = getopt_long(argc, argv, "had:p:u:g:", longopts, nullptr)) != -1)
726 {
727 switch (ch)
728 {
729 case 'a':
730 append = true;
731 break;
732 case 'd':
733 logfile = optarg;
734 break;
735 case 'p':
736 pidfile = optarg;
737 break;
738 case 'u':
739 user = optarg;
740 break;
741 case 'g':
742 group = optarg;
743 break;
744 default:
745 throw usage();
746 }
747 }
748 argc -= optind;
749 argv += optind;
750
751 ret = ovpnagent("/var/run/" OVPNAGENT_NAME_STRING ".sock", logfile, append, pidfile, user, group);
752 }
753 catch (const usage &)
754 {
755 log_version();
756 std::cout << "usage: ovpnagent [options]" << std::endl;
757 std::cout << " --daemon <file>, -d : daemonize, log to file" << std::endl;
758 std::cout << " --append, -a : append to log file" << std::endl;
759 std::cout << " --pidfile <file>, -p : write pid to file" << std::endl;
760 std::cout << " --user <user>, -u : set UID to user" << std::endl;
761 std::cout << " --group <group>, -g : set group" << std::endl;
762 ret = 2;
763 }
764 catch (const std::exception &e)
765 {
766 std::cout << "Main thread exception: " << e.what() << std::endl;
767 ret = 1;
768 }
769
770 return ret;
771}
WS::Server::Listener::Client::Ptr new_client(WS::Server::Listener::Client::Initializer &ci) override
RCPtr< MyClientFactory > Ptr
MyListener * parent()
void http_content_in(BufferAllocated &buf) override
bool http_stop(const int status, const std::string &description) override
void generate_reply(const Json::Value &jout)
MyClientInstance(WS::Server::Listener::Client::Initializer &ci)
bool http_out_eof() override
void http_request_received() override
BufferPtr http_content_out() override
virtual ~MyClientInstance()=default
void http_pipeline_peek(BufferAllocated &buf) override
RCPtr< MyClientInstance > Ptr
WatchdogThread(MyListener *parent_arg, openvpn_io::io_context &io_context_arg)
openvpn_io::io_context & io_context
RCPtr< WatchdogThread > Ptr
bool allow_client(AsioPolySock::Base &sock) override
void set_watchdog(pid_t pid)
const MyConfig & config
ActionList remove_cmds_bypass_hosts
WatchdogThread::Ptr watchdog
void unset_watchdog()
void destroy_tun(std::ostream &os)
void add_bypass_route(const std::string &host, bool ipv6)
MyListener(openvpn_io::io_context &io_context, const WS::Server::Config::Ptr &config, const Listen::List &listen_list, const WS::Server::Listener::Client::Factory::Ptr &client_factory)
TunMac::Setup::Ptr tun
ScopedFD establish_tun(const TunBuilderCapture &tbc, TunBuilderSetup::Config *config, Stop *stop, std::ostream &os)
std::string bypass_host
RCPtr< MyListener > Ptr
void error(const size_t err_type, const std::string *text=nullptr) override
Definition ovpnagent.cpp:75
RCPtr< MySessionStats > Ptr
Definition ovpnagent.cpp:73
std::string dump() const
Definition ovpnagent.cpp:80
RCPtr< ServerThread > Ptr
MyListener::Ptr listener
ServerThread(openvpn_io::io_context &io_context_arg, ThreadCommon &tc)
openvpn_io::io_context & io_context
void thread_safe_stop() override
virtual std::unordered_set< std::string > execute(std::ostream &os)
Executes a sequence of actions and returns marks of failed actions.
Definition action.hpp:98
bool defined() const
Returns true if the buffer is not empty.
Definition buffer.hpp:1224
T front() const
Returns the first element of the buffer.
Definition buffer.hpp:1265
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1242
The smart pointer class.
Definition rc.hpp:119
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
void swap(RCPtr &rhs) noexcept
swaps the contents of two RCPtr<T>
Definition rc.hpp:311
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
void set_server(const unsigned int unit, ServerThread *serv)
void clear_server(const unsigned int unit)
const Log::Context::Wrapper & log_wrapper()
void reset(const int fd_arg)
Definition scoped_fd.hpp:68
bool defined() const
Definition scoped_fd.hpp:58
void validate() const
Validates the configuration of the tunnel.
Definition capture.hpp:946
static TunBuilderCapture::Ptr from_json(const Json::Value &root)
Creates a TunBuilderCapture instance from a JSON representation.
Definition capture.hpp:1049
bool add_bypass_route(const std::string &address, bool ipv6, std::ostream &os)
Definition tunsetup.hpp:78
void generate_reply_headers(ContentInfo ci)
Definition httpserv.hpp:293
void external_stop(const std::string &description)
Definition httpserv.hpp:341
const HTTP::Request & request() const
Definition httpserv.hpp:331
openvpn_io::io_context & io_context
Listener(openvpn_io::io_context &io_context_arg, const Config::Ptr &config_arg, const L &listen_item_or_list, const Client::Factory::Ptr &client_factory_arg)
Definition httpserv.hpp:777
Client::Factory::Ptr client_factory
static void xmit_fd(const int sock_fd, const int payload_fd, const std::string &message, const int timeout_ms)
Definition xmitfd.hpp:36
static void worker_thread()
Definition cli.cpp:822
#define OPENVPN_SIMPLE_EXCEPTION(C)
Definition exception.hpp:75
#define OPENVPN_THROW_EXCEPTION(stuff)
#define OPENVPN_LOG_NTNL(args)
#define OPENVPN_LOG(args)
int main(int argc, char *argv[])
void log_version()
Definition ovpnagent.cpp:59
void work(openvpn_io::io_context &io_context, ThreadCommon &tc, MyRunContext &runctx, const unsigned int unit)
RunContext< ServerThreadBase, MySessionStats > MyRunContext
int ovpnagent(const char *sock_fn, const char *log_fn, const bool log_append, const char *pid_fn, const char *user, const char *group)
#define HTTP_SERVER_VERSION
Definition ovpnagent.cpp:29
#define OVPNAGENT_NAME_STRING
Definition ovpnagent.cpp:35
const char * name(const size_t type)
Definition error.hpp:117
bool get_bool(const Json::Value &root, const NAME &name, const TITLE &title)
const Json::Value & get_dict(const Json::Value &root, const NAME &name, const bool optional, const TITLE &title)
int get_int_optional(const Json::Value &root, const NAME &name, const int default_value, const TITLE &title)
Json::Value parse(const std::string &str, const TITLE &title)
std::string get_string(const Json::Value &root, const NAME &name, const TITLE &title)
std::string remove_blanks(const std::string &str)
Definition string.hpp:620
int strcasecmp(const char *s1, const char *s2)
Definition string.hpp:29
Frame::Ptr frame_init_simple(const size_t payload)
void daemonize()
Definition daemon.hpp:100
void event_loop_wait_barrier(THREAD_COMMON &tc, const unsigned int seconds=WAIT_BARRIER_TIMEOUT)
void write_pid(const std::string &fn)
Definition daemon.hpp:124
BufferPtr buf_from_string(const std::string &str)
Definition bufstr.hpp:46
ThreadCommon(const char *unix_sock, const char *user, const char *group)
Definition ovpnagent.cpp:90
MySessionStats::Ptr stats
PThreadBarrier event_loop_bar
const SetUserGroup user_group
void show_unused_options() const
static Listen::List build_listen_list(const char *unix_sock)
Definition ovpnagent.cpp:98
const Listen::List listen_list
std::string to_string() const
Definition buflist.hpp:72
std::string get_value_trim(const std::string &key) const
Definition header.hpp:84
std::string to_string() const
Definition request.hpp:44
unsigned int n_threads
Scoped RAII for the global_log pointer.
void from_json(const Json::Value &root, const std::string &title) override
Definition tunsetup.hpp:68
static std::string error_str(const size_t status)
Definition httpserv.hpp:102
const TunBuilderCapture::Ptr tbc(new TunBuilderCapture)
remote_address ipv6
proxy_host_port host
std::string ret
std::ostringstream os
static const char config[]