]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/dnsdist-idstate.hh
Merge pull request #12401 from fredmorcos/openssl-3-prep
[thirdparty/pdns.git] / pdns / dnsdist-idstate.hh
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #pragma once
23
24 #include "config.h"
25 #include "dnsname.hh"
26 #include "dnsdist-protocols.hh"
27 #include "gettime.hh"
28 #include "iputils.hh"
29 #include "uuid-utils.hh"
30
31 struct ClientState;
32 struct DOHUnit;
33 class DNSCryptQuery;
34 class DNSDistPacketCache;
35
36 using QTag = std::unordered_map<string, string>;
37
38 struct StopWatch
39 {
40 StopWatch(bool realTime = false) :
41 d_needRealTime(realTime)
42 {
43 }
44
45 void start()
46 {
47 d_start = getCurrentTime();
48 }
49
50 void set(const struct timespec& from)
51 {
52 d_start = from;
53 }
54
55 double udiff() const
56 {
57 struct timespec now = getCurrentTime();
58 return 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
59 }
60
61 double udiffAndSet()
62 {
63 struct timespec now = getCurrentTime();
64 auto ret = 1000000.0 * (now.tv_sec - d_start.tv_sec) + (now.tv_nsec - d_start.tv_nsec) / 1000.0;
65 d_start = now;
66 return ret;
67 }
68
69 struct timespec getStartTime() const
70 {
71 return d_start;
72 }
73
74 struct timespec d_start
75 {
76 0, 0
77 };
78
79 private:
80 struct timespec getCurrentTime() const
81 {
82 struct timespec now;
83 if (gettime(&now, d_needRealTime) < 0) {
84 unixDie("Getting timestamp");
85 }
86 return now;
87 }
88
89 bool d_needRealTime;
90 };
91
92 /* g++ defines __SANITIZE_THREAD__
93 clang++ supports the nice __has_feature(thread_sanitizer),
94 let's merge them */
95 #if defined(__has_feature)
96 #if __has_feature(thread_sanitizer)
97 #define __SANITIZE_THREAD__ 1
98 #endif
99 #endif
100
101 struct InternalQueryState
102 {
103 static void DeleterPlaceHolder(DOHUnit*)
104 {
105 }
106
107 InternalQueryState() :
108 du(std::unique_ptr<DOHUnit, void (*)(DOHUnit*)>(nullptr, DeleterPlaceHolder))
109 {
110 origDest.sin4.sin_family = 0;
111 }
112
113 InternalQueryState(InternalQueryState&& rhs) = default;
114 InternalQueryState& operator=(InternalQueryState&& rhs) = default;
115
116 InternalQueryState(const InternalQueryState& orig) = delete;
117 InternalQueryState& operator=(const InternalQueryState& orig) = delete;
118
119 boost::optional<Netmask> subnet{boost::none}; // 40
120 ComboAddress origRemote; // 28
121 ComboAddress origDest; // 28
122 ComboAddress hopRemote;
123 ComboAddress hopLocal;
124 DNSName qname; // 24
125 std::string poolName; // 24
126 StopWatch queryRealTime{true}; // 16
127 std::shared_ptr<DNSDistPacketCache> packetCache{nullptr}; // 16
128 std::unique_ptr<DNSCryptQuery> dnsCryptQuery{nullptr}; // 8
129 std::unique_ptr<QTag> qTag{nullptr}; // 8
130 boost::optional<uint32_t> tempFailureTTL{boost::none}; // 8
131 ClientState* cs{nullptr}; // 8
132 std::unique_ptr<DOHUnit, void (*)(DOHUnit*)> du; // 8
133 uint32_t cacheKey{0}; // 4
134 uint32_t cacheKeyNoECS{0}; // 4
135 // DoH-only */
136 uint32_t cacheKeyUDP{0}; // 4
137 int backendFD{-1}; // 4
138 int delayMsec{0};
139 uint16_t qtype{0}; // 2
140 uint16_t qclass{0}; // 2
141 // origID is in network-byte order
142 uint16_t origID{0}; // 2
143 uint16_t origFlags{0}; // 2
144 uint16_t cacheFlags{0}; // DNS flags as sent to the backend // 2
145 uint16_t udpPayloadSize{0}; // Max UDP payload size from the query // 2
146 dnsdist::Protocol protocol; // 1
147 boost::optional<boost::uuids::uuid> uniqueId{boost::none}; // 17 (placed here to reduce the space lost to padding)
148 bool ednsAdded{false};
149 bool ecsAdded{false};
150 bool skipCache{false};
151 bool dnssecOK{false};
152 bool useZeroScope{false};
153 bool forwardedOverUDP{false};
154 };
155
156 struct IDState
157 {
158 IDState()
159 {
160 }
161
162 IDState(const IDState& orig) = delete;
163 IDState(IDState&& rhs)
164 {
165 if (rhs.isInUse()) {
166 throw std::runtime_error("Trying to move an in-use IDState");
167 }
168
169 #ifdef __SANITIZE_THREAD__
170 age.store(rhs.age.load());
171 #else
172 age = rhs.age;
173 #endif
174 internal = std::move(rhs.internal);
175 }
176
177 IDState& operator=(IDState&& rhs)
178 {
179 if (isInUse()) {
180 throw std::runtime_error("Trying to overwrite an in-use IDState");
181 }
182
183 if (rhs.isInUse()) {
184 throw std::runtime_error("Trying to move an in-use IDState");
185 }
186 #ifdef __SANITIZE_THREAD__
187 age.store(rhs.age.load());
188 #else
189 age = rhs.age;
190 #endif
191
192 internal = std::move(rhs.internal);
193
194 return *this;
195 }
196
197 static const int64_t unusedIndicator = -1;
198
199 static bool isInUse(int64_t usageIndicator)
200 {
201 return usageIndicator != unusedIndicator;
202 }
203
204 bool isInUse() const
205 {
206 return usageIndicator != unusedIndicator;
207 }
208
209 /* return true if the value has been successfully replaced meaning that
210 no-one updated the usage indicator in the meantime */
211 bool tryMarkUnused(int64_t expectedUsageIndicator)
212 {
213 return usageIndicator.compare_exchange_strong(expectedUsageIndicator, unusedIndicator);
214 }
215
216 /* mark as used no matter what, return true if the state was in use before */
217 bool markAsUsed()
218 {
219 auto currentGeneration = generation++;
220 return markAsUsed(currentGeneration);
221 }
222
223 /* mark as used no matter what, return true if the state was in use before */
224 bool markAsUsed(int64_t currentGeneration)
225 {
226 int64_t oldUsage = usageIndicator.exchange(currentGeneration);
227 return oldUsage != unusedIndicator;
228 }
229
230 /* We use this value to detect whether this state is in use.
231 For performance reasons we don't want to use a lock here, but that means
232 we need to be very careful when modifying this value. Modifications happen
233 from:
234 - one of the UDP or DoH 'client' threads receiving a query, selecting a backend
235 then picking one of the states associated to this backend (via the idOffset).
236 Most of the time this state should not be in use and usageIndicator is -1, but we
237 might not yet have received a response for the query previously associated to this
238 state, meaning that we will 'reuse' this state and erase the existing state.
239 If we ever receive a response for this state, it will be discarded. This is
240 mostly fine for UDP except that we still need to be careful in order to miss
241 the 'outstanding' counters, which should only be increased when we are picking
242 an empty state, and not when reusing ;
243 For DoH, though, we have dynamically allocated a DOHUnit object that needs to
244 be freed, as well as internal objects internals to libh2o.
245 - one of the UDP receiver threads receiving a response from a backend, picking
246 the corresponding state and sending the response to the client ;
247 - the 'healthcheck' thread scanning the states to actively discover timeouts,
248 mostly to keep some counters like the 'outstanding' one sane.
249 We previously based that logic on the origFD (FD on which the query was received,
250 and therefore from where the response should be sent) but this suffered from an
251 ABA problem since it was quite likely that a UDP 'client thread' would reset it to the
252 same value since we only have so much incoming sockets:
253 - 1/ 'client' thread gets a query and set origFD to its FD, say 5 ;
254 - 2/ 'receiver' thread gets a response, read the value of origFD to 5, check that the qname,
255 qtype and qclass match
256 - 3/ during that time the 'client' thread reuses the state, setting again origFD to 5 ;
257 - 4/ the 'receiver' thread uses compare_exchange_strong() to only replace the value if it's still
258 5, except it's not the same 5 anymore and it overrides a fresh state.
259 We now use a 32-bit unsigned counter instead, which is incremented every time the state is set,
260 wrapping around if necessary, and we set an atomic signed 64-bit value, so that we still have -1
261 when the state is unused and the value of our counter otherwise.
262 */
263 InternalQueryState internal;
264 std::atomic<int64_t> usageIndicator{unusedIndicator}; // set to unusedIndicator to indicate this state is empty // 8
265 std::atomic<uint32_t> generation{0}; // increased every time a state is used, to be able to detect an ABA issue // 4
266 #ifdef __SANITIZE_THREAD__
267 std::atomic<uint16_t> age{0};
268 #else
269 uint16_t age{0}; // 2
270 #endif
271 };