1 /** @file
2
3 A brief file description
4
5 @section license License
6
7 Licensed to the Apache Software Foundation (ASF) under one
8 or more contributor license agreements. See the NOTICE file
9 distributed with this work for additional information
10 regarding copyright ownership. The ASF licenses this file
11 to you under the Apache License, Version 2.0 (the
12 "License"); you may not use this file except in compliance
13 with the License. You may obtain a copy of the License at
14
15 http://www.apache.org/licenses/LICENSE-2.0
16
17 Unless required by applicable law or agreed to in writing, software
18 distributed under the License is distributed on an "AS IS" BASIS,
19 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 See the License for the specific language governing permissions and
21 limitations under the License.
22 */
23
24 #pragma once
25
26 #include <string_view>
27 #include <chrono>
28 #include <atomic>
29 #include <sstream>
30 #include <tuple>
31 #include <mutex>
32 #include "tscore/ink_platform.h"
33 #include "tscore/ink_config.h"
34 #include "tscore/ink_mutex.h"
35 #include "tscore/ink_inet.h"
36 #include "tscore/IntrusiveHashMap.h"
37 #include "tscore/Diags.h"
38 #include "tscore/CryptoHash.h"
39 #include "tscore/BufferWriterForward.h"
40 #include "tscpp/util/TextView.h"
41 #include <MgmtDefs.h>
42 #include "HttpProxyAPIEnums.h"
43 #include "Show.h"
44
45 /**
46 * Singleton class to keep track of the number of outbound connections.
47 *
48 * Outbound connections are divided in to equivalence classes (called "groups" here) based on the
49 * session matching setting. Tracking data is stored for each group.
50 */
51 class OutboundConnTrack
52 {
53 using self_type = OutboundConnTrack; ///< Self reference type.
54
55 public:
56 // Non-copyable.
57 OutboundConnTrack(const self_type &) = delete;
58 self_type &operator=(const self_type &) = delete;
59
60 /// Definition of an upstream server group equivalence class.
61 enum MatchType {
62 MATCH_IP = TS_SERVER_OUTBOUND_MATCH_IP, ///< Match by IP address.
63 MATCH_PORT = TS_SERVER_OUTBOUND_MATCH_PORT, ///< Match by IP address and port.
64 MATCH_HOST = TS_SERVER_OUTBOUND_MATCH_HOST, ///< Match by hostname (FQDN).
65 MATCH_BOTH = TS_SERVER_OUTBOUND_MATCH_BOTH, ///< Hostname, IP Address and port.
66 };
67
68 /// String equivalents for @c MatchType.
69 static const std::array<std::string_view, static_cast<int>(MATCH_BOTH) + 1> MATCH_TYPE_NAME;
70
71 /// Per transaction configuration values.
72 struct TxnConfig {
73 int max{0}; ///< Maximum concurrent connections.
74 int min{0}; ///< Minimum keepalive connections.
75 MatchType match{MATCH_IP}; ///< Match type.
76 };
77
78 /** Static configuration values. */
79 struct GlobalConfig {
80 int queue_size{0}; ///< Maximum delayed transactions.
81 std::chrono::milliseconds queue_delay{100}; ///< Reschedule / queue delay in ms.
82 std::chrono::seconds alert_delay{60}; ///< Alert delay in seconds.
83 };
84
85 // The names of the configuration values.
86 // Unfortunately these are not used in RecordsConfig.cc so that must be made consistent by hand.
87 // Note: These need to be @c constexpr or there are static initialization ordering risks.
88 static constexpr std::string_view CONFIG_VAR_MAX{"proxy.config.http.per_server.connection.max"_sv};
89 static constexpr std::string_view CONFIG_VAR_MIN{"proxy.config.http.per_server.connection.min"_sv};
90 static constexpr std::string_view CONFIG_VAR_MATCH{"proxy.config.http.per_server.connection.match"_sv};
91 static constexpr std::string_view CONFIG_VAR_QUEUE_SIZE{"proxy.config.http.per_server.connection.queue_size"_sv};
92 static constexpr std::string_view CONFIG_VAR_QUEUE_DELAY{"proxy.config.http.per_server.connection.queue_delay"_sv};
93 static constexpr std::string_view CONFIG_VAR_ALERT_DELAY{"proxy.config.http.per_server.connection.alert_delay"_sv};
94
95 /// A record for the outbound connection count.
96 /// These are stored per outbound session equivalence class, as determined by the session matching.
97 struct Group {
98 /// Base clock.
99 using Clock = std::chrono::system_clock;
100 /// Time point type, based on the clock to be used.
101 using TimePoint = Clock::time_point;
102 /// Raw type for clock / time point counts.
103 using Ticker = TimePoint::rep;
104 /// Length of time to suppress alerts for a group.
105 static const std::chrono::seconds ALERT_DELAY;
106
107 /// Equivalence key - two groups are equivalent if their keys are equal.
108 struct Key {
109 IpEndpoint const &_addr; ///< Remote IP address.
110 CryptoHash const &_hash; ///< Hash of the FQDN.
111 MatchType const &_match_type; ///< Type of matching.
112 };
113
114 IpEndpoint _addr; ///< Remote IP address.
115 CryptoHash _hash; ///< Hash of the FQDN.
116 MatchType _match_type; ///< Type of matching.
117 std::string _fqdn; ///< Expanded FQDN, set if matching on FQDN.
118 int min_keep_alive_conns; /// < Min keep alive conns on this server group
119 Key _key; ///< Pre-assembled key which references the following members.
120
121 // Counting data.
122 std::atomic<int> _count{0}; ///< Number of outbound connections.
123 std::atomic<int> _count_max{0}; ///< largest observed @a count value.
124 std::atomic<int> _blocked{0}; ///< Number of outbound connections blocked since last alert.
125 std::atomic<int> _rescheduled{0}; ///< # of connection reschedules.
126 std::atomic<int> _in_queue{0}; ///< # of connections queued, waiting for a connection.
127 std::atomic<Ticker> _last_alert{0}; ///< Absolute time of the last alert.
128
129 // Links for intrusive container.
130 Group *_next{nullptr};
131 Group *_prev{nullptr};
132
133 /** Constructor.
134 * Construct from @c Key because the use cases do a table lookup first so the @c Key is already constructed.
135 * @param key A populated @c Key structure - values are copied to the @c Group.
136 * @param fqdn The full FQDN.
137 */
138 Group(Key const &key, std::string_view fqdn, int min_keep_alive);
139 /// Key equality checker.
140 static bool equal(Key const &lhs, Key const &rhs);
141 /// Hashing function.
142 static uint64_t hash(Key const &);
143 /// Check and clear alert enable.
144 /// This is a modifying call - internal state will be updated to prevent too frequent alerts.
145 /// @param lat The last alert time, in epoch seconds, if the method returns @c true.
146 /// @return @c true if an alert should be generated, @c false otherwise.
147 bool should_alert(std::time_t *lat = nullptr);
148 /// Time of the last alert in epoch seconds.
149 std::time_t get_last_alert_epoch_time() const;
150 };
151
152 /// Container for per transaction state and operations.
153 struct TxnState {
154 Group *_g{nullptr}; ///< Active group for this transaction.
155 bool _reserved_p{false}; ///< Set if a connection slot has been reserved.
156 bool _queued_p{false}; ///< Set if the connection is delayed / queued.
157
158 /// Check if tracking is active.
159 bool is_active();
160
161 /// Reserve a connection.
162 int reserve();
163 /// Release a connection reservation.
164 void release();
165 /// Reserve a queue / retry slot.
166 int enqueue();
167 /// Release a block
168 void dequeue();
169 /// Note blocking a transaction.
170 void blocked();
171 /// Note a rescheduling
172 void rescheduled();
173 /// Clear all reservations.
174 void clear();
175 /// Drop the reservation - assume it will be cleaned up elsewhere.
176 /// @return The group for this reservation.
177 Group *drop();
178 /// Update the maximum observed count if needed against @a count.
179 void update_max_count(int count);
180
181 /** Generate a Notice that the group has become unblocked.
182 *
183 * @param config Transaction local configuration.
184 * @param count Current connection count for display in message.
185 * @param addr IP address of the upstream.
186 */
187 void Note_Unblocked(const TxnConfig *config, int count, const sockaddr *addr);
188
189 /** Generate a Warning that a connection was blocked.
190 *
191 * @param config Transaction local configuration.
192 * @param sm_id State machine ID to display in Warning.
193 * @param count Count value to display in Warning.
194 * @param addr IP address of the upstream.
195 * @param debug_tag Tag to use for the debug message. If no debug message should be generated set this to @c nullptr.
196 */
197 void Warn_Blocked(const TxnConfig *config, int64_t sm_id, int count, const sockaddr *addr, const char *debug_tag = nullptr);
198 };
199
200 /** Get or create the @c Group for the specified session properties.
201 * @param txn_cnf The transaction local configuration.
202 * @param fqdn The fully qualified domain name of the upstream.
203 * @param addr The IP address of the upstream.
204 * @return A @c Group for the arguments, existing if possible and created if not.
205 */
206 static TxnState obtain(TxnConfig const &txn_cnf, std::string_view fqdn, const IpEndpoint &addr);
207
208 /** Get the currently existing groups.
209 * @param [out] groups parameter - pointers to the groups are pushed in to this container.
210 *
211 * The groups are loaded in to @a groups, which is cleared before loading. Note the groups returned will remain valid
212 * although data inside the groups is volatile.
213 */
214 static void get(std::vector<Group const *> &groups);
215 /** Write the connection tracking data to JSON.
216 * @return string containing a JSON encoding of the table.
217 */
218 static std::string to_json_string();
219 /** Write the groups to @a f.
220 * @param f Output file.
221 */
222 static void dump(FILE *f);
223 /** Do global initialization.
224 *
225 * This sets up the global configuration and any configuration update callbacks needed. It is presumed
226 * the caller has set up the actual storage where the global configuration data is stored.
227 *
228 * @param config The storage for the global configuration data.
229 * @param txn The storage for the default per transaction data.
230 */
231 static void config_init(GlobalConfig *global, TxnConfig *txn);
232
233 /// Tag used for debugging output.
234 static constexpr char const *const DEBUG_TAG{"conn_track"};
235
236 /** Convert a string to a match type.
237 *
238 * @a type is updated only if this method returns @c true.
239 *
240 * @param [in] tag Tag to look up.
241 * @param [out] type Resulting type.
242 * @return @c true if @a tag was valid and @a type was updated, otherwise @c false.
243 */
244 static bool lookup_match_type(std::string_view tag, MatchType &type);
245
246 /** Generate a warning message for a bad @c MatchType tag.
247 *
248 * @param tag The invalid tag.
249 */
250 static void Warning_Bad_Match_Type(std::string_view tag);
251
252 // Converters for overridable values for use in the TS API.
253 static const MgmtConverter MIN_CONV;
254 static const MgmtConverter MAX_CONV;
255 static const MgmtConverter MATCH_CONV;
256
257 protected:
258 static GlobalConfig *_global_config; ///< Global configuration data.
259
260 /// Types and methods for the hash table.
261 struct Linkage {
262 using key_type = Group::Key const &;
263 using value_type = Group;
264
265 static value_type *&next_ptr(value_type *value);
266 static value_type *&prev_ptr(value_type *value);
267
268 static uint64_t hash_of(key_type key);
269
270 static key_type key_of(value_type *v);
271
272 static bool equal(key_type lhs, key_type rhs);
273 };
274
275 /// Internal implementation class instance.
276 struct Imp {
277 IntrusiveHashMap<Linkage> _table; ///< Hash table of upstream groups.
278 std::mutex _mutex; ///< Lock for insert & find.
279 };
280 static Imp _imp;
281
282 /// Get the implementation instance.
283 /// @note This is done purely to allow subclasses to reuse methods in this class.
284 Imp &instance();
285 };
286
287 inline OutboundConnTrack::Imp &
instance()288 OutboundConnTrack::instance()
289 {
290 return _imp;
291 }
292
Group(Key const & key,std::string_view fqdn,int min_keep_alive)293 inline OutboundConnTrack::Group::Group(Key const &key, std::string_view fqdn, int min_keep_alive)
294 : _hash(key._hash), _match_type(key._match_type), min_keep_alive_conns(min_keep_alive), _key{_addr, _hash, _match_type}
295 {
296 // store the host name if relevant.
297 if (MATCH_HOST == _match_type || MATCH_BOTH == _match_type) {
298 _fqdn.assign(fqdn);
299 }
300 // store the IP address if relevant.
301 if (MATCH_HOST == _match_type) {
302 _addr.setToAnyAddr(AF_INET);
303 } else {
304 ats_ip_copy(_addr, key._addr);
305 }
306 }
307
308 inline uint64_t
hash(const Key & key)309 OutboundConnTrack::Group::hash(const Key &key)
310 {
311 switch (key._match_type) {
312 case MATCH_IP:
313 return ats_ip_hash(&key._addr.sa);
314 case MATCH_PORT:
315 return ats_ip_port_hash(&key._addr.sa);
316 case MATCH_HOST:
317 return key._hash.fold();
318 case MATCH_BOTH:
319 return ats_ip_port_hash(&key._addr.sa) ^ key._hash.fold();
320 default:
321 return 0;
322 }
323 }
324
325 inline bool
is_active()326 OutboundConnTrack::TxnState::is_active()
327 {
328 return nullptr != _g;
329 }
330
331 inline int
reserve()332 OutboundConnTrack::TxnState::reserve()
333 {
334 _reserved_p = true;
335 return ++_g->_count;
336 }
337
338 inline void
release()339 OutboundConnTrack::TxnState::release()
340 {
341 if (_reserved_p) {
342 _reserved_p = false;
343 --_g->_count;
344 }
345 }
346
347 inline OutboundConnTrack::Group *
drop()348 OutboundConnTrack::TxnState::drop()
349 {
350 _reserved_p = false;
351 return _g;
352 }
353
354 inline int
enqueue()355 OutboundConnTrack::TxnState::enqueue()
356 {
357 _queued_p = true;
358 return ++_g->_in_queue;
359 }
360
361 inline void
dequeue()362 OutboundConnTrack::TxnState::dequeue()
363 {
364 if (_queued_p) {
365 _queued_p = false;
366 --_g->_in_queue;
367 }
368 }
369
370 inline void
clear()371 OutboundConnTrack::TxnState::clear()
372 {
373 if (_g) {
374 this->dequeue();
375 this->release();
376 _g = nullptr;
377 }
378 }
379
380 inline void
update_max_count(int count)381 OutboundConnTrack::TxnState::update_max_count(int count)
382 {
383 auto cmax = _g->_count_max.load();
384 if (count > cmax) {
385 _g->_count_max.compare_exchange_weak(cmax, count);
386 }
387 }
388
389 inline void
blocked()390 OutboundConnTrack::TxnState::blocked()
391 {
392 ++_g->_blocked;
393 }
394
395 inline void
rescheduled()396 OutboundConnTrack::TxnState::rescheduled()
397 {
398 ++_g->_rescheduled;
399 }
400
401 /* === Linkage === */
402 inline auto
403 OutboundConnTrack::Linkage::next_ptr(value_type *value) -> value_type *&
404 {
405 return value->_next;
406 }
407
408 inline auto
409 OutboundConnTrack::Linkage::prev_ptr(value_type *value) -> value_type *&
410 {
411 return value->_prev;
412 }
413
414 inline uint64_t
hash_of(key_type key)415 OutboundConnTrack::Linkage::hash_of(key_type key)
416 {
417 return Group::hash(key);
418 }
419
420 inline auto
421 OutboundConnTrack::Linkage::key_of(value_type *value) -> key_type
422 {
423 return value->_key;
424 }
425
426 inline bool
equal(key_type lhs,key_type rhs)427 OutboundConnTrack::Linkage::equal(key_type lhs, key_type rhs)
428 {
429 return Group::equal(lhs, rhs);
430 }
431 /* === */
432
433 Action *register_ShowConnectionCount(Continuation *, HTTPHdr *);
434
435 namespace ts
436 {
437 BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::MatchType type);
438 BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group::Key const &key);
439 BufferWriter &bwformat(BufferWriter &w, BWFSpec const &spec, OutboundConnTrack::Group const &g);
440 } // namespace ts
441