OpenVPN 3 Core Library
Loading...
Searching...
No Matches
maclife.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#ifndef OPENVPN_APPLE_MACLIFE_H
13#define OPENVPN_APPLE_MACLIFE_H
14
15#include <string>
16#include <sstream>
17
18#include <thread>
19
28
29namespace openvpn {
31{
32 public:
33 OPENVPN_EXCEPTION(mac_lifecycle_error);
34
36 : ReachabilityTracker(true, false)
37 {
38 }
39
40 virtual ~MacLifeCycle()
41 {
43 }
44
45 bool network_available() override
46 {
47 return net_up();
48 }
49
50 void start(NotifyCallback *nc_arg) override
51 {
52 if (!thread && nc_arg)
53 {
54 nc = nc_arg;
55 thread = new std::thread(&MacLifeCycle::thread_func, this);
56 }
57 }
58
59 void stop() override
60 {
62 }
63
64 private:
65 struct State
66 {
68 : net_up(false),
69 sleep(false)
70 {
71 }
72
73 State(bool net_up_arg, const std::string &iface_arg, bool sleep_arg)
74 : net_up(net_up_arg),
75 iface(iface_arg),
76 sleep(sleep_arg)
77 {
78 }
79
80 bool operator==(const State &other) const
81 {
82 return net_up == other.net_up && iface == other.iface && sleep == other.sleep;
83 }
84
85 bool operator!=(const State &other) const
86 {
87 return !operator==(other);
88 }
89
90 std::string to_string() const
91 {
92 std::ostringstream os;
93 os << "[net_up=" << net_up << " iface=" << iface << " sleep=" << sleep << ']';
94 return os.str();
95 }
96
97 bool net_up;
98 std::string iface;
99 bool sleep;
100 };
101
103 {
104 if (thread)
105 {
106 halt.store(true);
107 if (runloop.defined())
108 CFRunLoopStop(runloop());
109 thread->join();
110 delete thread;
111 thread = nullptr;
112 }
113 }
114
116 {
117 runloop.reset(CFRunLoopGetCurrent(), CF::GET);
118 Log::Context logctx(logwrap);
119 try
120 {
121 // set up dynamic store query object
122 dstore.reset(SCDynamicStoreCreate(kCFAllocatorDefault,
123 CFSTR("OpenVPN_MacLifeCycle"),
124 nullptr,
125 nullptr));
126
127 // init state
128 state = State(net_up(), primary_interface(), false);
130 paused = false;
131
132 // enable sleep/wakeup notifications
134
135 // enable network reachability notifications
137
138 // enable interface change notifications
139 iface_watch();
140
141 // there could be a race between this lifecycle thread and a main thread
142 // where stop_thread() is called. After starting runloop, check
143 // if lifecycle thread has to be stopped
144 schedule_action_timer(0, true);
145
146 // process event loop until CFRunLoopStop is called from parent thread
147 CFRunLoopRun();
148 }
149 catch (const std::exception &e)
150 {
151 OPENVPN_LOG("MacLifeCycle exception: " << e.what());
152 }
153
154 // cleanup
158 dstore.reset();
159 }
160
161 std::string primary_interface()
162 {
163 CF::Dict dict(CF::DynamicStoreCopyDict(dstore, "State:/Network/Global/IPv4"));
164 return CF::dict_get_str(dict, "PrimaryInterface");
165 }
166
172
174 {
175 SCDynamicStoreContext context = {0, this, nullptr, nullptr, nullptr};
176 CF::DynamicStore ds(SCDynamicStoreCreate(kCFAllocatorDefault,
177 CFSTR("OpenVPN_MacLifeCycle_iface_watch"),
179 &context));
180 if (!ds.defined())
181 throw mac_lifecycle_error("SCDynamicStoreCreate");
182 CF::MutableArray watched_keys(CF::mutable_array());
183 CF::array_append_str(watched_keys, "State:/Network/Global/IPv4");
184 // CF::array_append_str(watched_keys, "State:/Network/Global/IPv6");
185 if (!watched_keys.defined())
186 throw mac_lifecycle_error("watched_keys is undefined");
187 if (!SCDynamicStoreSetNotificationKeys(ds(),
188 watched_keys(),
189 nullptr))
190 throw mac_lifecycle_error("SCDynamicStoreSetNotificationKeys failed");
191 CF::RunLoopSource rls(SCDynamicStoreCreateRunLoopSource(kCFAllocatorDefault, ds(), 0));
192 if (!rls.defined())
193 throw mac_lifecycle_error("SCDynamicStoreCreateRunLoopSource failed");
194 CFRunLoopAddSource(CFRunLoopGetCurrent(), rls(), kCFRunLoopDefaultMode);
195 }
196
197 static void iface_watch_callback_static(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
198 {
199 MacLifeCycle *self = (MacLifeCycle *)arg;
200 self->iface_watch_callback(store, changedKeys);
201 }
202
203 void iface_watch_callback(SCDynamicStoreRef store, CFArrayRef changedKeys)
204 {
206 OPENVPN_LOG("MacLifeCycle NET_IFACE " << state.iface);
208 }
209
210 void notify_sleep() override
211 {
212 OPENVPN_LOG("MacLifeCycle SLEEP");
213 state.sleep = true;
215 }
216
217 void notify_wakeup() override
218 {
219 OPENVPN_LOG("MacLifeCycle WAKEUP");
220 state.sleep = false;
222 }
223
224 void reachability_tracker_event(const ReachabilityBase &rb, SCNetworkReachabilityFlags flags) override
225 {
227 {
228 const ReachabilityBase::Status status = rb.vstatus(flags);
230 OPENVPN_LOG("MacLifeCycle NET_STATE " << state.net_up << " status=" << ReachabilityBase::render_status(status) << " flags=" << ReachabilityBase::render_flags(flags));
232 }
233 }
234
235 void schedule_action_timer(const int seconds, bool force_runloop = false)
236 {
238 if (seconds || force_runloop)
239 {
240 CFRunLoopTimerContext context = {0, this, nullptr, nullptr, nullptr};
241 action_timer.reset(CFRunLoopTimerCreate(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + seconds, 0, 0, 0, action_timer_callback_static, &context));
242 if (action_timer.defined())
243 CFRunLoopAddTimer(CFRunLoopGetCurrent(), action_timer(), kCFRunLoopCommonModes);
244 else
245 OPENVPN_LOG("MacLifeCycle::schedule_action_timer: failed to create timer");
246 }
247 else
248 action_timer_callback(nullptr);
249 }
250
252 {
253 if (action_timer.defined())
254 {
255 CFRunLoopTimerInvalidate(action_timer());
256 action_timer.reset(nullptr);
257 }
258 }
259
260 static void action_timer_callback_static(CFRunLoopTimerRef timer, void *info)
261 {
262 MacLifeCycle *self = (MacLifeCycle *)info;
263 self->action_timer_callback(timer);
264 }
265
266 void action_timer_callback(CFRunLoopTimerRef timer)
267 {
268 if (halt.load())
269 {
270 CFRunLoopStop(runloop());
271 return;
272 }
273
274 try
275 {
276 if (state != prev_state)
277 {
278 OPENVPN_LOG("MacLifeCycle ACTION pause=" << paused << " state=" << state.to_string() << " prev=" << prev_state.to_string());
279 if (paused)
280 {
281 if (!state.sleep && state.net_up)
282 {
283 nc->cln_resume();
284 paused = false;
285 }
286 }
287 else
288 {
289 if (state.sleep)
290 {
291 nc->cln_pause("sleep");
292 paused = true;
293 }
294 else if (!state.net_up)
295 {
296 nc->cln_pause("network-unavailable");
297 paused = true;
298 }
299 else
300 {
302 nc->cln_reconnect(0);
303 }
304 }
306 }
307 }
308 catch (const std::exception &e)
309 {
310 OPENVPN_LOG("MacLifeCycle::action_timer_callback exception: " << e.what());
311 }
312 }
313
314 NotifyCallback *nc = nullptr;
315 std::thread *thread = nullptr;
316 CF::RunLoop runloop; // run loop in thread
317 CF::DynamicStore dstore;
320 bool paused = false;
321 std::atomic<bool> halt{false};
322 CF::Timer action_timer;
323 Log::Context::Wrapper logwrap; // used to carry forward the log context from parent thread
324};
325} // namespace openvpn
326
327#endif
static void action_timer_callback_static(CFRunLoopTimerRef timer, void *info)
Definition maclife.hpp:260
virtual ~MacLifeCycle()
Definition maclife.hpp:40
void reachability_tracker_event(const ReachabilityBase &rb, SCNetworkReachabilityFlags flags) override
Definition maclife.hpp:224
void notify_sleep() override
Definition maclife.hpp:210
CF::RunLoop runloop
Definition maclife.hpp:316
void action_timer_callback(CFRunLoopTimerRef timer)
Definition maclife.hpp:266
std::atomic< bool > halt
Definition maclife.hpp:321
static void iface_watch_callback_static(SCDynamicStoreRef store, CFArrayRef changedKeys, void *arg)
Definition maclife.hpp:197
bool network_available() override
Definition maclife.hpp:45
void start(NotifyCallback *nc_arg) override
Definition maclife.hpp:50
void schedule_action_timer(const int seconds, bool force_runloop=false)
Definition maclife.hpp:235
void notify_wakeup() override
Definition maclife.hpp:217
Log::Context::Wrapper logwrap
Definition maclife.hpp:323
NotifyCallback * nc
Definition maclife.hpp:314
std::thread * thread
Definition maclife.hpp:315
CF::DynamicStore dstore
Definition maclife.hpp:317
std::string primary_interface()
Definition maclife.hpp:161
void stop() override
Definition maclife.hpp:59
OPENVPN_EXCEPTION(mac_lifecycle_error)
void iface_watch_callback(SCDynamicStoreRef store, CFArrayRef changedKeys)
Definition maclife.hpp:203
void mac_sleep_stop()
Definition macsleep.hpp:55
bool mac_sleep_start()
Definition macsleep.hpp:43
virtual Status vstatus(const SCNetworkReachabilityFlags flags) const =0
SCNetworkReachabilityFlags flags() const
static std::string render_flags(const SCNetworkReachabilityFlags flags)
virtual Type vtype() const =0
static std::string render_status(const Status status)
static Status status_from_flags(const SCNetworkReachabilityFlags flags)
#define OPENVPN_LOG(args)
MutableArray mutable_array(const CFIndex capacity=0)
Definition cf.hpp:306
Dict DynamicStoreCopyDict(const DynamicStore &ds, const KEY &key)
void array_append_str(MutableArray &array, const VALUE &value)
Definition cfhelper.hpp:217
std::string dict_get_str(const DICT &dict, const KEY &key)
Definition cfhelper.hpp:95
virtual void cln_reconnect(int seconds)=0
virtual void cln_pause(const std::string &reason)=0
Argument to construct a Context in a different thread.
Scoped RAII for the global_log pointer.
bool operator==(const State &other) const
Definition maclife.hpp:80
bool operator!=(const State &other) const
Definition maclife.hpp:85
std::string to_string() const
Definition maclife.hpp:90
State(bool net_up_arg, const std::string &iface_arg, bool sleep_arg)
Definition maclife.hpp:73
reroute_gw flags
std::ostringstream os