OpenVPN 3 Core Library
Loading...
Searching...
No Matches
macdns.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// DNS utilities for Mac OS X.
13
14#ifndef OPENVPN_TUN_MAC_MACDNS_H
15#define OPENVPN_TUN_MAC_MACDNS_H
16
17#include <string>
18#include <sstream>
19#include <memory>
20#include <algorithm>
21
31
32namespace openvpn {
33class MacDNS : public RC<thread_unsafe_refcount>
34{
35 class Info;
36
37 public:
39
40 OPENVPN_EXCEPTION(macdns_error);
41
42 class Config : public RC<thread_safe_refcount>
43 {
44 public:
46
47 Config() = default;
48
49 Config(const TunBuilderCapture &settings)
50 {
51 for (const auto &[priority, server] : settings.dns_options.servers)
52 {
53 bool dnssec = server.dnssec == DnsServer::Security::Yes;
54 bool secure_transport = server.transport == DnsServer::Transport::HTTPS
55 || server.transport == DnsServer::Transport::TLS;
56 bool custom_port = std::any_of(server.addresses.begin(),
57 server.addresses.end(),
58 [&](const DnsAddress &a)
59 { return a.port != 0 && a.port != 53; });
60 if (dnssec || secure_transport || custom_port)
61 {
62 continue; // unsupported, try next server
63 }
64
67 break;
68 }
69
70 if (!settings.dns_options.servers.empty() && !CF::array_len(server_addresses))
71 {
72 throw macdns_error("no applicable DNS server config found");
73 }
74
76
77 if (!settings.dns_options.from_dhcp_options)
78 {
79 // With --dns options we redirect when there are no split domains
81 }
82 else
83 {
84 // We redirect DNS if either of the following is true:
85 // 1. redirect-gateway (IPv4) is pushed, or
86 // 2. DNS servers are pushed but no search domains are pushed
88 }
89 }
90
91 std::string to_string() const
92 {
93 std::ostringstream os;
94 os << "RD=" << redirect_dns;
95 os << " SO=" << search_order;
99 return os.str();
100 }
101
102 bool redirect_dns = false;
103 int search_order = 5000;
106 CF::Array search_domains;
107
108 private:
109 static CF::Array get_server_addresses(const DnsServer &server)
110 {
111 CF::MutableArray ret(CF::mutable_array());
112 for (const auto &ip : server.addresses)
113 {
114 CF::array_append_str(ret, ip.address);
115 }
116 return CF::const_array(ret);
117 }
118
119 static CF::Array get_resolve_domains(const DnsServer &server)
120 {
121 CF::MutableArray ret(CF::mutable_array());
122 for (const auto &rd : server.domains)
123 {
124 CF::array_append_str(ret, rd.domain);
125 }
126 return CF::const_array(ret);
127 }
128
129 static CF::Array get_search_domains(const TunBuilderCapture &settings)
130 {
131 CF::MutableArray ret(CF::mutable_array());
132 if (!settings.dns_options.servers.empty())
133 {
134 for (const auto &sd : settings.dns_options.search_domains)
135 {
136 CF::array_append_str(ret, sd.domain);
137 }
138 }
139 return CF::const_array(ret);
140 }
141 };
142
143 MacDNS(const std::string &sname_arg)
144 : sname(sname_arg)
145 {
146 }
147
149 {
150 const int v = ver.major();
152 OPENVPN_LOG("MacDNS: Error: No support for Mac OS X versions earlier than 10.6");
154 {
155 Argv args;
156 args.push_back("/usr/bin/dscacheutil");
157 args.push_back("-flushcache");
158 OPENVPN_LOG(args.to_string());
159 system_cmd(args);
160 }
161 if (v >= Mac::Version::OSX_10_7)
162 {
163 Argv args;
164 args.push_back("/usr/bin/killall");
165 args.push_back("-HUP");
166 args.push_back("mDNSResponder");
167 OPENVPN_LOG(args.to_string());
168 system_cmd(args);
169 }
170 }
171
176
177 bool setdns(const Config &config)
178 {
179 bool mod = false;
180
181 try
182 {
183 CF::DynamicStore sc = ds_create();
184 Info::Ptr info(new Info(sc, sname));
185
186 // cleanup settings applied to previous interface
188
189 if (config.redirect_dns)
190 {
191 // redirect all DNS
192 info->dns.will_modify();
193
194 // set DNS servers
195 if (CF::array_len(config.server_addresses))
196 {
197 info->dns.backup_orig("ServerAddresses");
198 CF::dict_set_obj(info->dns.mod, "ServerAddresses", config.server_addresses());
199 }
200
201 // set search domains
202 info->dns.backup_orig("SearchDomains");
203
204 if (CF::array_len(config.search_domains))
205 CF::dict_set_obj(info->dns.mod, "SearchDomains", config.search_domains());
206
207 // set search order
208 info->dns.backup_orig("SearchOrder");
209 CF::dict_set_int(info->dns.mod, "SearchOrder", config.search_order);
210
211 // push it
212 mod |= info->dns.push_to_store();
213 }
214 else
215 {
216 // split-DNS - resolve only specific domains
217 info->ovpn.mod_reset();
218
219 // set DNS servers
220 CF::dict_set_obj(info->ovpn.mod, "ServerAddresses", config.server_addresses());
221
222 // DNS will be used only for those domains
223 CF::dict_set_obj(info->ovpn.mod, "SupplementalMatchDomains", config.resolve_domains());
224
225 // do not use those domains in autocompletion
226 CF::dict_set_int(info->ovpn.mod, "SupplementalMatchDomainsNoSearch", 1);
227
228 // set search domains if there are any
229 if (CF::array_len(config.search_domains))
230 {
231 info->dns.backup_orig("SearchDomains");
232 CF::dict_set_obj(info->dns.mod, "SearchDomains", config.search_domains());
233 mod |= info->dns.push_to_store();
234 }
235
236 // in case of split-DNS macOS uses domain suffix of network adapter,
237 // not the one provided by VPN (which we put to SearchDomains)
238
239 // push it
240 mod |= info->ovpn.push_to_store();
241 }
242
243 if (mod)
244 {
245 // As a backup, save PrimaryService in private dict (if network goes down while
246 // we are set, we can lose info about PrimaryService in State:/Network/Global/IPv4
247 // and be unable to reset ourselves).
248 const CFTypeRef ps = CF::dict_get_obj(info->ipv4.dict, "PrimaryService");
249 if (ps)
250 {
251 info->info.mod_reset();
252 CF::dict_set_obj(info->info.mod, "PrimaryService", ps);
253 info->info.push_to_store();
254 }
255 }
256
257 prev = info;
258 if (mod)
259 OPENVPN_LOG("MacDNS: SETDNS " << ver.to_string() << std::endl
260 << info->to_string());
261 }
262 catch (const std::exception &e)
263 {
264 OPENVPN_LOG("MacDNS: setdns exception: " << e.what());
265 }
266 return mod;
267 }
268
269 bool resetdns()
270 {
271 bool mod = false;
272 try
273 {
274 CF::DynamicStore sc = ds_create();
275 Info::Ptr info(new Info(sc, sname));
276
277 // cleanup settings applied to previous interface
279
280 // undo primary dns changes
281 mod |= reset_primary_dns(info.get());
282
283 // undo non-redirect-gateway changes
284 if (CF::dict_len(info->ovpn.dict))
285 mod |= info->ovpn.remove_from_store();
286
287 // remove private info dict
288 if (CF::dict_len(info->info.dict))
289 mod |= info->info.remove_from_store();
290
291 if (mod)
292 OPENVPN_LOG("MacDNS: RESETDNS " << ver.to_string() << std::endl
293 << info->to_string());
294 }
295 catch (const std::exception &e)
296 {
297 OPENVPN_LOG("MacDNS: resetdns exception: " << e.what());
298 }
299 return mod;
300 }
301
302 std::string to_string() const
303 {
304 CF::DynamicStore sc = ds_create();
305 Info::Ptr info(new Info(sc, sname));
306 return info->to_string();
307 }
308
309 CF::Array dskey_array() const
310 {
311 CF::DynamicStore sc = ds_create();
312 Info::Ptr info(new Info(sc, sname));
313 CF::MutableArray ret(CF::mutable_array());
318 return CF::const_array(ret);
319 }
320
321 private:
323 {
324 if (info->interface_change(prev.get()))
325 {
327 prev.reset();
328 }
329 }
330
332 {
333 bool mod = false;
334 if (info)
335 {
336#if 1
337 // Restore previous DNS settings.
338 // Recommended for production.
339 info->dns.will_modify();
340 info->dns.restore_orig();
341 mod |= info->dns.push_to_store();
342#else
343 // Wipe DNS settings without restore.
344 // This can potentially wipe static IP/DNS settings.
345 info->dns.mod_reset();
346 mod |= info->dns.push_to_store();
347#endif
348 }
349 return mod;
350 }
351
352 class Info : public RC<thread_unsafe_refcount>
353 {
354 public:
356
357 Info(CF::DynamicStore &sc, const std::string &sname)
358 : ipv4(sc, sname, "State:/Network/Global/IPv4"),
359 info(sc, sname, "State:/Network/Service/" + sname + "/Info"),
360 ovpn(sc, sname, "State:/Network/Service/" + sname + "/DNS"),
361 dns(sc, sname, primary_dns(ipv4.dict, info.dict))
362 {
363 }
364
365 std::string to_string() const
366 {
367 std::ostringstream os;
368 os << ipv4.to_string();
369 os << info.to_string();
370 os << ovpn.to_string();
371 os << dns.to_string();
372 return os.str();
373 }
374
375 bool interface_change(Info *other) const
376 {
377 return other && dns.dskey != other->dns.dskey;
378 }
379
381 DSDict info; // we may modify
382 DSDict ovpn; // we may modify
383 DSDict dns; // we may modify
384
385 private:
386 static std::string primary_dns(const CF::Dict &ipv4, const CF::Dict &info)
387 {
388 std::string serv = CF::dict_get_str(ipv4, "PrimaryService");
389 if (serv.empty())
390 serv = CF::dict_get_str(info, "PrimaryService");
391 if (serv.empty())
392 throw macdns_error("no primary service");
393 return "Setup:/Network/Service/" + serv + "/DNS";
394 }
395 };
396
397 CF::DynamicStore ds_create() const
398 {
399 return DSDict::ds_create(sname);
400 }
401
402 const std::string sname;
405};
406} // namespace openvpn
407
408#endif
std::string to_string() const
Definition ver.hpp:42
int major() const
Definition ver.hpp:29
std::string to_string() const
Definition argv.hpp:30
static bool signal_network_reconfiguration(const std::string &sname)
Definition dsdict.hpp:151
bool push_to_store()
Definition dsdict.hpp:33
static CF::DynamicStore ds_create(const std::string &sname)
Definition dsdict.hpp:145
void will_modify()
Definition dsdict.hpp:66
std::string to_string() const
Definition dsdict.hpp:129
CF::MutableDict mod
Definition dsdict.hpp:164
const std::string dskey
Definition dsdict.hpp:162
void restore_orig()
Definition dsdict.hpp:97
bool remove_from_store()
Definition dsdict.hpp:49
void mod_reset()
Definition dsdict.hpp:72
void backup_orig(const std::string &key, const bool wipe_orig=true)
Definition dsdict.hpp:77
const CF::Dict dict
Definition dsdict.hpp:163
RCPtr< Config > Ptr
Definition macdns.hpp:45
Config(const TunBuilderCapture &settings)
Definition macdns.hpp:49
std::string to_string() const
Definition macdns.hpp:91
CF::Array resolve_domains
Definition macdns.hpp:105
static CF::Array get_resolve_domains(const DnsServer &server)
Definition macdns.hpp:119
static CF::Array get_search_domains(const TunBuilderCapture &settings)
Definition macdns.hpp:129
CF::Array server_addresses
Definition macdns.hpp:104
static CF::Array get_server_addresses(const DnsServer &server)
Definition macdns.hpp:109
Info(CF::DynamicStore &sc, const std::string &sname)
Definition macdns.hpp:357
static std::string primary_dns(const CF::Dict &ipv4, const CF::Dict &info)
Definition macdns.hpp:386
RCPtr< Info > Ptr
Definition macdns.hpp:355
bool interface_change(Info *other) const
Definition macdns.hpp:375
std::string to_string() const
Definition macdns.hpp:365
OPENVPN_EXCEPTION(macdns_error)
RCPtr< MacDNS > Ptr
Definition macdns.hpp:38
CF::Array dskey_array() const
Definition macdns.hpp:309
CF::DynamicStore ds_create() const
Definition macdns.hpp:397
bool setdns(const Config &config)
Definition macdns.hpp:177
std::string to_string() const
Definition macdns.hpp:302
void flush_cache()
Definition macdns.hpp:148
const std::string sname
Definition macdns.hpp:402
bool reset_primary_dns(Info *info)
Definition macdns.hpp:331
Info::Ptr prev
Definition macdns.hpp:404
bool signal_network_reconfiguration()
Definition macdns.hpp:172
void interface_change_cleanup(Info *info)
Definition macdns.hpp:322
bool resetdns()
Definition macdns.hpp:269
MacDNS(const std::string &sname_arg)
Definition macdns.hpp:143
Mac::Version ver
Definition macdns.hpp:403
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
#define OPENVPN_LOG(args)
CFIndex array_len(const ARRAY &array)
Definition cf.hpp:333
void dict_set_int(MutableDict &dict, const KEY &key, int value)
Definition cfhelper.hpp:173
MutableArray mutable_array(const CFIndex capacity=0)
Definition cf.hpp:306
std::string array_to_string(const ARRAY &array, const char delim=',')
Definition cf.hpp:426
Array const_array(MutableArray &marray)
Definition cf.hpp:291
CFIndex dict_len(const DICT &dict)
Definition cf.hpp:342
CFTypeRef dict_get_obj(const DICT &dict, const KEY &key)
Definition cfhelper.hpp:88
void dict_set_obj(MutableDict &dict, const KEY &key, CFTypeRef value)
Definition cfhelper.hpp:154
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
int system_cmd(const std::string &cmd, const Argv &argv, RedirectBase *redir, const Environ *env, const sigset_t *sigmask)
Definition process.hpp:90
A name server address and optional port.
std::map< int, DnsServer > servers
std::vector< DnsDomain > search_domains
DNS settings for a name server.
std::vector< DnsAddress > addresses
std::vector< DnsDomain > domains
std::string ret
std::ostringstream os
static const char config[]