OpenVPN 3 Core Library
Loading...
Searching...
No Matches
dns.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) 2022- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12
13#pragma once
14
15#include <vector>
16#include <cstdint>
17#include <algorithm>
18#include <sstream>
19
22
23#ifdef HAVE_JSON
25#endif
26
27namespace openvpn {
28
34{
35 DnsOptionsParser(const OptionList &opt, bool use_dhcp_search_domains_as_split_domains)
36 {
38 parse_dhcp_options(opt, use_dhcp_search_domains_as_split_domains, !servers.empty());
39 if (!parse_errors.empty())
40 {
41 OPENVPN_THROW_ARG1(option_error, ERR_INVALID_OPTION_DNS, parse_errors);
42 }
43 }
44
45 static int parse_priority(const std::string &prio_str)
46 {
47 const auto min_prio = std::numeric_limits<std::int8_t>::min();
48 const auto max_prio = std::numeric_limits<std::int8_t>::max();
49
50 int priority;
51 if (!parse_number_validate<int>(prio_str, 4, min_prio, max_prio, &priority))
52 OPENVPN_THROW_ARG1(option_error, ERR_INVALID_OPTION_DNS, "dns server priority '" << prio_str << "' invalid");
53 return priority;
54 }
55
56 protected:
57 std::string parse_errors;
58
60 {
61 auto indices = opt.get_index_ptr("dns");
62 if (indices == nullptr)
63 {
64 return;
65 }
66
67 for (const auto i : *indices)
68 {
69 try
70 {
71 const auto &o = opt[i];
72 if (o.size() >= 3 && o.ref(1) == "search-domains")
73 {
74 for (std::size_t j = 2; j < o.size(); j++)
75 {
76 search_domains.emplace_back(o.ref(j));
77 }
78 }
79 else if (o.size() >= 5 && o.ref(1) == "server")
80 {
81 auto priority = parse_priority(o.ref(2));
82 auto &server = get_server(priority);
83
84 const auto &server_suboption = o.ref(3);
85 if (server_suboption == "address" && o.size() <= 12)
86 {
87 for (std::size_t j = 4; j < o.size(); j++)
88 {
89 try
90 {
91 server.addresses.emplace_back(DnsAddress(o.ref(j)));
92 }
93 catch (const openvpn::Exception &error)
94 {
95 OPENVPN_THROW_ARG1(option_error,
96 ERR_INVALID_OPTION_DNS,
97 "dns server " << priority << " error: " << error.what());
98 }
99 }
100 }
101 else if (server_suboption == "resolve-domains")
102 {
103 for (std::size_t j = 4; j < o.size(); j++)
104 {
105 server.domains.emplace_back(o.ref(j));
106 }
107 }
108 else if (server_suboption == "dnssec" && o.size() == 5)
109 {
110 try
111 {
112 server.parse_dnssec_value(o.ref(4));
113 }
114 catch (const openvpn::Exception &error)
115 {
116 OPENVPN_THROW_ARG1(option_error,
117 ERR_INVALID_OPTION_DNS,
118 "dns server " << priority << " error: " << error.what());
119 }
120 }
121 else if (server_suboption == "transport" && o.size() == 5)
122 {
123 try
124 {
125 server.parse_transport_value(o.ref(4));
126 }
127 catch (const openvpn::Exception &error)
128 {
129 OPENVPN_THROW_ARG1(option_error, ERR_INVALID_OPTION_DNS, "dns server " << priority << " error: " << error.what());
130 }
131 }
132 else if (server_suboption == "sni" && o.size() == 5)
133 {
134 server.sni = o.ref(4);
135 }
136 else
137 {
138 OPENVPN_THROW_ARG1(option_error, ERR_INVALID_OPTION_DNS, "dns server " << priority << " option '" << server_suboption << "' unknown or too many parameters");
139 }
140 }
141 else
142 {
143 OPENVPN_THROW_ARG1(option_error, ERR_INVALID_OPTION_DNS, "dns option unknown or invalid number of parameters " << o.render(Option::RENDER_TRUNC_64 | Option::RENDER_BRACKET));
144 }
145 }
146 catch (const std::exception &e)
147 {
148 parse_errors += "\n";
149 parse_errors += e.what();
150 }
151 }
152
153 // Check and remove servers without an address
154 std::vector<int> remove_servers;
155 for (const auto &[priority, server] : servers)
156 {
157 if (server.addresses.empty())
158 {
159 parse_errors += "\n";
160 parse_errors += "dns server " + std::to_string(priority) + " does not have an address assigned";
161 remove_servers.push_back(priority);
162 }
163 }
164 for (const auto &prio : remove_servers)
165 {
166 servers.erase(prio);
167 }
168
169 // Clear search domains when no servers were configured
170 if (servers.empty())
171 {
172 search_domains.clear();
173 }
174 }
175
176 void parse_dhcp_options(const OptionList &opt, bool use_search_as_split_domains, bool ignore_values)
177 {
178 auto dhcp_map = opt.map().find("dhcp-option");
179 if (dhcp_map == opt.map().end())
180 {
181 return;
182 }
183
184 // Example:
185 // [dhcp-option] [DNS] [172.16.0.23]
186 // [dhcp-option] [DOMAIN] [openvpn.net]
187 // [dhcp-option] [DOMAIN] [example.com]
188 // [dhcp-option] [DOMAIN] [foo1.com foo2.com foo3.com ...]
189 // [dhcp-option] [DOMAIN] [bar1.com] [bar2.com] [bar3.com] ...
190 // [dhcp-option] [ADAPTER_DOMAIN_SUFFIX] [mycompany.com]
191 std::string adapter_domain_suffix;
192 for (const auto &i : dhcp_map->second)
193 {
194 try
195 {
196 const Option &o = opt[i];
197 const std::string &type = o.get(1, 64);
198 if (type == "DNS" || type == "DNS6")
199 {
200 o.exact_args(3);
201 try
202 {
203 if (!ignore_values)
204 {
205 auto &server = get_server(0);
206 server.addresses.emplace_back(DnsAddress(o.get(2, 256)));
207 from_dhcp_options = true;
208 }
209 }
210 catch (const IP::ip_exception &)
211 {
212 OPENVPN_THROW_ARG1(option_error, ERR_INVALID_OPTION_DNS, o.render(Option::RENDER_BRACKET) << " invalid address");
213 }
214 catch (const openvpn::Exception &error)
215 {
216 OPENVPN_THROW_ARG1(option_error,
217 ERR_INVALID_OPTION_DNS,
218 o.render(Option::RENDER_BRACKET) << "dhcp-option DNS error: " << error.what());
219 }
220 }
221 else if (type == "DOMAIN" || type == "DOMAIN-SEARCH")
222 {
223 o.min_args(3);
224 for (size_t i = 2; i < o.size(); ++i)
225 {
226 using StrVec = std::vector<std::string>;
227 const StrVec domains = Split::by_space<StrVec, StandardLex, SpaceMatch, Split::NullLimit>(o.get(i, 256));
228 if (ignore_values)
229 {
230 continue;
231 }
232 for (const auto &domain : domains)
233 {
234 from_dhcp_options = true;
235 if (use_search_as_split_domains)
236 {
237 auto &server = get_server(0);
238 server.domains.emplace_back(domain);
239 }
240 else
241 {
242 search_domains.emplace_back(domain);
243 }
244 }
245 }
246 }
247 else if (type == "ADAPTER_DOMAIN_SUFFIX")
248 {
249 o.exact_args(3);
250 if (!ignore_values)
251 {
252 adapter_domain_suffix = o.ref(2);
253 from_dhcp_options = true;
254 }
255 }
256 }
257 catch (const std::exception &e)
258 {
259 parse_errors += "\n";
260 parse_errors += e.what();
261 }
262 }
263
264 if (!adapter_domain_suffix.empty())
265 {
266 search_domains.insert(search_domains.begin(), DnsDomain(std::move(adapter_domain_suffix)));
267 }
268
269 if (!ignore_values && !servers.empty() && servers[0].addresses.empty())
270 {
271 parse_errors += "\n";
272 parse_errors += "dns server does not have an address assigned";
273 servers.clear();
274 }
275 }
276};
277
279{
280 using PriorityList = std::vector<std::int8_t>;
281
283 {
284 DnsFilter(PriorityList &&pushed_prios)
285 : pushed_prios_(std::forward<PriorityList>(pushed_prios))
286 {
287 }
288
289 bool filter(const Option &opt) override
290 {
291 if (opt.empty()
292 || opt.size() < 3
293 || opt.ref(0) != "dns"
294 || opt.ref(1) != "server")
295 {
296 return true;
297 }
298 const auto priority = DnsOptionsParser::parse_priority(opt.ref(2));
299 const auto it = std::find(pushed_prios_.begin(), pushed_prios_.end(), priority);
300
301 // Filter out server option if an option with this priority was pushed
302 return it == pushed_prios_.end() ? true : false;
303 }
304
305 protected:
307 };
308
309 void merge(OptionList &pushed, const OptionList &config) const override
310 {
311 PriorityList pushed_prios;
312
313 auto indices = pushed.get_index_ptr("dns");
314 if (indices)
315 {
316 for (const auto i : *indices)
317 {
318 if (pushed[i].size() < 3 || pushed[i].ref(1) != "server")
319 continue;
320 const auto priority = DnsOptionsParser::parse_priority(pushed[i].ref(2));
321 pushed_prios.emplace_back(priority);
322 }
323 }
324
325 DnsFilter filter(std::move(pushed_prios));
326 pushed.extend(config, &filter);
327 }
328};
329
330} // namespace openvpn
const IndexMap & map() const
Definition options.hpp:1541
void extend(const OptionList &other, FilterBase *filt=nullptr)
Definition options.hpp:1111
const IndexList * get_index_ptr(const std::string &name) const
Definition options.hpp:1260
void exact_args(const size_t n) const
Definition options.hpp:135
const std::string & get(const size_t index, const size_t max_len) const
Definition options.hpp:184
size_t size() const
Definition options.hpp:320
void min_args(const size_t n) const
Definition options.hpp:128
bool empty() const
Definition options.hpp:324
const std::string & ref(const size_t i) const
Definition options.hpp:346
std::string render(const unsigned int flags) const
Definition options.hpp:257
#define OPENVPN_THROW_ARG1(exc, arg, stuff)
A name server address and optional port.
A DNS domain name.
DnsFilter(PriorityList &&pushed_prios)
Definition dns.hpp:284
const PriorityList pushed_prios_
Definition dns.hpp:306
bool filter(const Option &opt) override
Definition dns.hpp:289
std::vector< std::int8_t > PriorityList
Definition dns.hpp:280
void merge(OptionList &pushed, const OptionList &config) const override
Definition dns.hpp:309
void parse_dns_options(const OptionList &opt)
Definition dns.hpp:59
static int parse_priority(const std::string &prio_str)
Definition dns.hpp:45
std::string parse_errors
Definition dns.hpp:57
void parse_dhcp_options(const OptionList &opt, bool use_search_as_split_domains, bool ignore_values)
Definition dns.hpp:176
DnsOptionsParser(const OptionList &opt, bool use_dhcp_search_domains_as_split_domains)
Definition dns.hpp:35
All DNS options set with the –dns or –dhcp-option directive.
std::map< int, DnsServer > servers
List of DNS servers to use, according to the list of priority.
DnsServer & get_server(const int 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.
static const char config[]