OpenVPN 3 Core Library
Loading...
Searching...
No Matches
continuation.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// Client-side code to handle pushed option list "continuations".
13// This is where multiple option lists are pushed by the server,
14// if an option list doesn't fit into the standard 1024 byte buffer.
15// This class will aggregate the options.
16
17#pragma once
18
19#include <unordered_set>
20
23
24namespace openvpn {
25
26struct PushOptionsBase : public RC<thread_unsafe_refcount>
27{
29
33};
34
35// Used by OptionListContinuation::finalize() to merge static and pushed options
36struct PushOptionsMerger : public RC<thread_unsafe_refcount>
37{
39 virtual void merge(OptionList &pushed, const OptionList &config) const = 0;
40};
41
42// Aggregate pushed option continuations into a singular option list.
43// Note that map is not updated until list is complete.
45{
46 public:
47 OPENVPN_SIMPLE_EXCEPTION(olc_complete); // add called when object is already complete
48 OPENVPN_EXCEPTION(push_update_unsupported_option);
49
51 : push_base(push_base_arg)
52 {
53 // Prepend from base where multiple options of the same type can aggregate,
54 // so that server-pushed options will be at the end of list.
55 if (push_base)
56 extend(push_base->multi, nullptr);
57 }
58
60
75 void add(const OptionList &other, OptionList::FilterBase *filt, bool push_update = false)
76 {
77 if (complete_)
78 {
79 throw olc_complete();
80 }
81
82 OptionList opts{other};
83 if (push_update)
84 {
85 update(opts);
86 }
87
88 partial_ = true;
89 try
90 {
91 // throws if pull-filter rejects
92 extend(opts, filt);
93 }
94 catch (const Option::RejectedException &)
95 {
96 // remove all server pushed options on reject
97 clear();
98 if (push_base)
99 extend(push_base->multi, nullptr);
100 throw;
101 }
102 if (!continuation(opts))
103 {
104 if (push_base)
105 {
106 // Append from base where only a single instance of each option makes sense,
107 // provided that option wasn't already pushed by server.
108 update_map();
110 }
111 update_map();
112 complete_ = true;
113 }
114 }
115
117 {
118 if (merger)
119 {
120 merger->merge(*this, push_base->merge);
121 update_map();
122 }
123
124 update_list.clear();
125 }
126
127 // returns true if add() was called at least once
128 bool partial() const
129 {
130 return partial_;
131 }
132
133 // returns true if option list is complete
134 bool complete() const
135 {
136 return complete_;
137 }
138
144 {
145 complete_ = false;
146 }
147
148 private:
161 void update(OptionList &opts)
162 {
163 std::unordered_set<std::string> opts_to_remove;
164 std::unordered_set<std::string> unsupported_mandatory_options;
165 std::unordered_set<std::string> unsupported_optional_options;
166
167 for (auto it = opts.begin(); it != opts.end();)
168 {
169 std::string &name = it->ref(0);
170
171 // option prefixed with "-" should be removed
172 bool remove = string::starts_with(name, "-");
173 if (remove)
174 {
175 name.erase(name.begin());
176 }
177
178 // option prefixed with "?" is considered "optional"
179 bool optional = string::starts_with(name, "?");
180 if (optional)
181 {
182 name.erase(name.begin());
183 }
184
185 if (!updatable_options.contains(name))
186 {
187 if (optional)
188 {
189 unsupported_optional_options.insert(name);
190 }
191 else
192 {
193 unsupported_mandatory_options.insert(name);
194 }
195 }
196
197 if (remove)
198 {
199 // remove current option if it is prefixed with "-" in update list
200 opts_to_remove.insert(name);
201 it = opts.erase(it);
202 }
203 else
204 {
205 // if upcoming updated option is not in update list, it should be removed from current options
206 if (!update_list.contains(name))
207 {
208 opts_to_remove.insert(name);
209 }
210
211 ++it;
212 }
213 }
214 opts.update_map();
215
216 erase(std::remove_if(begin(), end(), [&opts_to_remove](const Option &o)
217 {
218 const std::string &name = o.ref(0);
219 return opts_to_remove.contains(name); }),
220 end());
221
222 // we need to remove only original options, not the ones from ongoing PUSH_UPDATE
223 // make sure that options are considered for removal only once
224 update_list.insert(opts_to_remove.begin(), opts_to_remove.end());
225
226 if (!unsupported_mandatory_options.empty())
227 {
228 throw push_update_unsupported_option(string::join(unsupported_mandatory_options, ","));
229 }
230
231 if (!unsupported_optional_options.empty())
232 {
233 OPENVPN_LOG("Unsupported optional options: " << string::join(unsupported_optional_options, ","));
234 }
235 }
236
237 static bool continuation(const OptionList &opt)
238 {
239 const Option *o = opt.get_ptr("push-continuation");
240 return o && o->size() >= 2 && o->ref(1) == "2";
241 }
242
243 bool partial_ = false;
244 bool complete_ = false;
245
247
253 std::unordered_set<std::string> update_list;
254
255 inline static std::unordered_set<std::string> updatable_options = {
256 "block-ipv4",
257 "block-ipv6",
258 "block-outside-dns",
259 "dhcp-options",
260 "dns",
261 "ifconfig",
262 "ifconfig-ipv6",
263 "push-continuation",
264 "redirect-gateway",
265 "redirect-private",
266 "route",
267 "route-gateway",
268 "route-ipv6",
269 "route-metric",
270 "topology",
271 "tun-mtu"};
272};
273
274} // namespace openvpn
static std::unordered_set< std::string > updatable_options
OptionListContinuation(const PushOptionsBase::Ptr &push_base_arg)
std::unordered_set< std::string > update_list
A list of options to be updated or deleted during the update process. Existing options with the same ...
static bool continuation(const OptionList &opt)
void update(OptionList &opts)
OPENVPN_EXCEPTION(push_update_unsupported_option)
void reset_completion()
Resets completion flag. Intended to use by PUSH_UPDATE.
void finalize(const PushOptionsMerger::Ptr merger)
void add(const OptionList &other, OptionList::FilterBase *filt, bool push_update=false)
OPENVPN_SIMPLE_EXCEPTION(olc_complete)
void extend_nonexistent(const OptionList &other)
Definition options.hpp:1171
void extend(const OptionList &other, FilterBase *filt=nullptr)
Definition options.hpp:1123
const Option * get_ptr(const std::string &name) const
Definition options.hpp:1186
size_t size() const
Definition options.hpp:327
const std::string & ref(const size_t i) const
Definition options.hpp:353
The smart pointer class.
Definition rc.hpp:119
Reference count base class for objects tracked by RCPtr. Disallows copying and assignment.
Definition rc.hpp:912
#define OPENVPN_LOG(args)
auto join(const T &strings, const typename T::value_type &delim, const bool tail=false)
Definition string.hpp:521
bool starts_with(const STRING &str, const std::string &prefix)
Definition string.hpp:79
RCPtr< PushOptionsBase > Ptr
virtual void merge(OptionList &pushed, const OptionList &config) const =0
RCPtr< PushOptionsMerger > Ptr
static const char config[]