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