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
78 {
79 if (!settings.dns_options.from_dhcp_options)
80 {
81 // With --dns options we redirect when there are no split domains
83 }
84 else
85 {
86 // We redirect DNS if either of the following is true:
87 // 1. redirect-gateway (IPv4) is pushed, or
88 // 2. DNS servers are pushed but no search domains are pushed
90 }
91 }
92 }
93
94 std::string to_string() const
95 {
96 std::ostringstream os;
97 os << "RD=" << redirect_dns;
98 os << " SO=" << search_order;
99 os << " DNS=" << CF::array_to_string(server_addresses);
100 os << " RSD=" << CF::array_to_string(resolve_domains);
101 os << " DOM=" << CF::array_to_string(search_domains);
102 return os.str();
103 }
104
105 bool redirect_dns = false;
106 int search_order = 5000;
109 CF::Array search_domains;
110
111 private:
112 static CF::Array get_server_addresses(const DnsServer &server)
113 {
114 CF::MutableArray ret(CF::mutable_array());
115 for (const auto &ip : server.addresses)
116 {
117 CF::array_append_str(ret, ip.address);
118 }
119 return CF::const_array(ret);
120 }
121
122 static CF::Array get_resolve_domains(const DnsServer &server)
123 {
124 CF::MutableArray ret(CF::mutable_array());
125 for (const auto &rd : server.domains)
126 {
127 CF::array_append_str(ret, rd.domain);
128 }
129 return CF::const_array(ret);
130 }
131
132 static CF::Array get_search_domains(const TunBuilderCapture &settings)
133 {
134 CF::MutableArray ret(CF::mutable_array());
135 if (!settings.dns_options.servers.empty())
136 {
137 for (const auto &sd : settings.dns_options.search_domains)
138 {
139 CF::array_append_str(ret, sd.domain);
140 }
141 }
142 return CF::const_array(ret);
143 }
144 };
145
146 MacDNS(const std::string &sname_arg)
147 : sname(sname_arg)
148 {
149 }
150
152 {
153 const int v = ver.major();
155 OPENVPN_LOG("MacDNS: Error: No support for Mac OS X versions earlier than 10.6");
157 {
158 Argv args;
159 args.push_back("/usr/bin/dscacheutil");
160 args.push_back("-flushcache");
161 OPENVPN_LOG(args.to_string());
162 system_cmd(args);
163 }
164 if (v >= Mac::Version::OSX_10_7)
165 {
166 Argv args;
167 args.push_back("/usr/bin/killall");
168 args.push_back("-HUP");
169 args.push_back("mDNSResponder");
170 OPENVPN_LOG(args.to_string());
171 system_cmd(args);
172 }
173 }
174
179
180 bool setdns(const Config &config)
181 {
182 bool mod = false;
183
184 try
185 {
186 CF::DynamicStore sc = ds_create();
187 Info::Ptr info(new Info(sc, sname));
188
189 // cleanup settings applied to previous interface
191
192 if (config.redirect_dns)
193 {
194 // redirect all DNS
195 info->dns.will_modify();
196
197 // set DNS servers
198 if (CF::array_len(config.server_addresses))
199 {
200 info->dns.backup_orig("ServerAddresses");
201 CF::dict_set_obj(info->dns.mod, "ServerAddresses", config.server_addresses());
202 }
203
204 // set search domains
205 info->dns.backup_orig("SearchDomains");
206
207 if (CF::array_len(config.search_domains))
208 CF::dict_set_obj(info->dns.mod, "SearchDomains", config.search_domains());
209
210 // set search order
211 info->dns.backup_orig("SearchOrder");
212 CF::dict_set_int(info->dns.mod, "SearchOrder", config.search_order);
213
214 // push it
215 mod |= info->dns.push_to_store();
216 }
217 else
218 {
219 // split-DNS - resolve only specific domains
220 info->ovpn.mod_reset();
221
222 // set DNS servers
223 CF::dict_set_obj(info->ovpn.mod, "ServerAddresses", config.server_addresses());
224
225 // DNS will be used only for those domains
226 CF::dict_set_obj(info->ovpn.mod, "SupplementalMatchDomains", config.resolve_domains());
227
228 // do not use those domains in autocompletion
229 CF::dict_set_int(info->ovpn.mod, "SupplementalMatchDomainsNoSearch", 1);
230
231 // set search domains if there are any
232 if (CF::array_len(config.search_domains))
233 {
234 info->dns.backup_orig("SearchDomains");
235 CF::dict_set_obj(info->dns.mod, "SearchDomains", config.search_domains());
236 mod |= info->dns.push_to_store();
237 }
238
239 // in case of split-DNS macOS uses domain suffix of network adapter,
240 // not the one provided by VPN (which we put to SearchDomains)
241
242 // push it
243 mod |= info->ovpn.push_to_store();
244 }
245
246 if (mod)
247 {
248 // As a backup, save PrimaryService in private dict (if network goes down while
249 // we are set, we can lose info about PrimaryService in State:/Network/Global/IPv4
250 // and be unable to reset ourselves).
251 const CFTypeRef ps = CF::dict_get_obj(info->ipv4.dict, "PrimaryService");
252 if (ps)
253 {
254 info->info.mod_reset();
255 CF::dict_set_obj(info->info.mod, "PrimaryService", ps);
256 info->info.push_to_store();
257 }
258 }
259
260 prev = info;
261 if (mod)
262 OPENVPN_LOG("MacDNS: SETDNS " << ver.to_string() << "\n"
263 << info->to_string());
264 }
265 catch (const std::exception &e)
266 {
267 OPENVPN_LOG("MacDNS: setdns exception: " << e.what());
268 }
269 return mod;
270 }
271
272 bool resetdns()
273 {
274 bool mod = false;
275 try
276 {
277 CF::DynamicStore sc = ds_create();
278 Info::Ptr info(new Info(sc, sname));
279
280 // cleanup settings applied to previous interface
282
283 // undo primary dns changes
284 mod |= reset_primary_dns(info.get());
285
286 // undo non-redirect-gateway changes
287 if (CF::dict_len(info->ovpn.dict))
288 mod |= info->ovpn.remove_from_store();
289
290 // remove private info dict
291 if (CF::dict_len(info->info.dict))
292 mod |= info->info.remove_from_store();
293
294 if (mod)
295 OPENVPN_LOG("MacDNS: RESETDNS " << ver.to_string() << "\n"
296 << info->to_string());
297 }
298 catch (const std::exception &e)
299 {
300 OPENVPN_LOG("MacDNS: resetdns exception: " << e.what());
301 }
302 return mod;
303 }
304
305 std::string to_string() const
306 {
307 CF::DynamicStore sc = ds_create();
308 Info::Ptr info(new Info(sc, sname));
309 return info->to_string();
310 }
311
312 CF::Array dskey_array() const
313 {
314 CF::DynamicStore sc = ds_create();
315 Info::Ptr info(new Info(sc, sname));
316 CF::MutableArray ret(CF::mutable_array());
317 CF::array_append_str(ret, info->ipv4.dskey);
318 CF::array_append_str(ret, info->info.dskey);
319 CF::array_append_str(ret, info->ovpn.dskey);
320 CF::array_append_str(ret, info->dns.dskey);
321 return CF::const_array(ret);
322 }
323
324 private:
326 {
327 if (info->interface_change(prev.get()))
328 {
330 prev.reset();
331 }
332 }
333
335 {
336 bool mod = false;
337 if (info)
338 {
339#if 1
340 // Restore previous DNS settings.
341 // Recommended for production.
342 info->dns.will_modify();
343 info->dns.restore_orig();
344 mod |= info->dns.push_to_store();
345#else
346 // Wipe DNS settings without restore.
347 // This can potentially wipe static IP/DNS settings.
348 info->dns.mod_reset();
349 mod |= info->dns.push_to_store();
350#endif
351 }
352 return mod;
353 }
354
355 class Info : public RC<thread_unsafe_refcount>
356 {
357 public:
359
360 Info(CF::DynamicStore &sc, const std::string &sname)
361 : ipv4(sc, sname, "State:/Network/Global/IPv4"),
362 info(sc, sname, "State:/Network/Service/" + sname + "/Info"),
363 ovpn(sc, sname, "State:/Network/Service/" + sname + "/DNS"),
364 dns(sc, sname, primary_dns(ipv4.dict, info.dict))
365 {
366 }
367
368 std::string to_string() const
369 {
370 std::ostringstream os;
371 os << ipv4.to_string();
372 os << info.to_string();
373 os << ovpn.to_string();
374 os << dns.to_string();
375 return os.str();
376 }
377
378 bool interface_change(Info *other) const
379 {
380 return other && dns.dskey != other->dns.dskey;
381 }
382
384 DSDict info; // we may modify
385 DSDict ovpn; // we may modify
386 DSDict dns; // we may modify
387
388 private:
389 static std::string primary_dns(const CF::Dict &ipv4, const CF::Dict &info)
390 {
391 std::string serv = CF::dict_get_str(ipv4, "PrimaryService");
392 if (serv.empty())
393 serv = CF::dict_get_str(info, "PrimaryService");
394 if (serv.empty())
395 throw macdns_error("no primary service");
396 return "Setup:/Network/Service/" + serv + "/DNS";
397 }
398 };
399
400 CF::DynamicStore ds_create() const
401 {
402 return DSDict::ds_create(sname);
403 }
404
405 const std::string sname;
408};
409} // namespace openvpn
410
411#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:94
CF::Array resolve_domains
Definition macdns.hpp:108
static CF::Array get_resolve_domains(const DnsServer &server)
Definition macdns.hpp:122
static CF::Array get_search_domains(const TunBuilderCapture &settings)
Definition macdns.hpp:132
CF::Array server_addresses
Definition macdns.hpp:107
static CF::Array get_server_addresses(const DnsServer &server)
Definition macdns.hpp:112
Info(CF::DynamicStore &sc, const std::string &sname)
Definition macdns.hpp:360
static std::string primary_dns(const CF::Dict &ipv4, const CF::Dict &info)
Definition macdns.hpp:389
RCPtr< Info > Ptr
Definition macdns.hpp:358
bool interface_change(Info *other) const
Definition macdns.hpp:378
std::string to_string() const
Definition macdns.hpp:368
OPENVPN_EXCEPTION(macdns_error)
RCPtr< MacDNS > Ptr
Definition macdns.hpp:38
CF::Array dskey_array() const
Definition macdns.hpp:312
CF::DynamicStore ds_create() const
Definition macdns.hpp:400
bool setdns(const Config &config)
Definition macdns.hpp:180
std::string to_string() const
Definition macdns.hpp:305
void flush_cache()
Definition macdns.hpp:151
const std::string sname
Definition macdns.hpp:405
bool reset_primary_dns(Info *info)
Definition macdns.hpp:334
Info::Ptr prev
Definition macdns.hpp:407
bool signal_network_reconfiguration()
Definition macdns.hpp:175
void interface_change_cleanup(Info *info)
Definition macdns.hpp:325
bool resetdns()
Definition macdns.hpp:272
MacDNS(const std::string &sname_arg)
Definition macdns.hpp:146
Mac::Version ver
Definition macdns.hpp:406
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:908
#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
List of DNS servers to use, according to the list of priority.
std::vector< DnsDomain > search_domains
List of global DNS search domains to use.
bool from_dhcp_options
Set to true if the DNS options comes from –dhcp-option options.
DNS settings for a name server.
std::vector< DnsAddress > addresses
Parsed from –dns server n address ADDRESS[:PORT] [...] or –dhcp-option DNS/DNS6.
std::vector< DnsDomain > domains
Parsed from –dns server n resolve-domains DOMAIN [...] or –dhcp-option DOMAIN/DOMAIN-SEARCH if use_se...
@ Yes
Enforce using DNSSEC.
@ TLS
Use DNS-over-TLS (DoT)
@ HTTPS
Use DNS-over-HTTPS (DoH)
static const char config[]