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 /*****************************************************************************
25  *
26  *  ParentSelection.h - Interface to Parent Selection System
27  *
28  *
29  ****************************************************************************/
30 
31 #pragma once
32 
33 #include "ProxyConfig.h"
34 #include "ControlBase.h"
35 #include "ControlMatcher.h"
36 #include "records/P_RecProcess.h"
37 #include "tscore/ConsistentHash.h"
38 #include "tscore/Tokenizer.h"
39 #include "tscore/ink_apidefs.h"
40 #include "HostStatus.h"
41 
42 #include <algorithm>
43 #include <vector>
44 
45 #define MAX_PARENTS 64
46 
47 struct RequestData;
48 struct matcher_line;
49 struct ParentResult;
50 struct OverridableHttpConfigParams;
51 class ParentRecord;
52 class ParentSelectionStrategy;
53 
54 enum ParentResultType {
55   PARENT_UNDEFINED,
56   PARENT_DIRECT,
57   PARENT_SPECIFIED,
58   PARENT_AGENT,
59   PARENT_FAIL,
60 };
61 
62 static const char *ParentResultStr[] = {"PARENT_UNDEFINED", "PARENT_DIRECT", "PARENT_SPECIFIED", "PARENT_AGENT", "PARENT_FAIL"};
63 
64 enum ParentRR_t {
65   P_NO_ROUND_ROBIN = 0,
66   P_STRICT_ROUND_ROBIN,
67   P_HASH_ROUND_ROBIN,
68   P_CONSISTENT_HASH,
69   P_LATCHED_ROUND_ROBIN,
70   P_UNDEFINED
71 };
72 
73 enum ParentRetry_t {
74   PARENT_RETRY_NONE               = 0,
75   PARENT_RETRY_SIMPLE             = 1,
76   PARENT_RETRY_UNAVAILABLE_SERVER = 2,
77   // both simple and unavailable server retry
78   PARENT_RETRY_BOTH = 3
79 };
80 
81 struct UnavailableServerResponseCodes {
82   UnavailableServerResponseCodes(char *val);
~UnavailableServerResponseCodesUnavailableServerResponseCodes83   ~UnavailableServerResponseCodes(){};
84 
85   bool
containsUnavailableServerResponseCodes86   contains(int code)
87   {
88     return binary_search(codes.begin(), codes.end(), code);
89   }
90 
91 private:
92   std::vector<int> codes;
93 };
94 
95 struct SimpleRetryResponseCodes {
96   SimpleRetryResponseCodes(char *val);
~SimpleRetryResponseCodesSimpleRetryResponseCodes97   ~SimpleRetryResponseCodes(){};
98 
99   bool
containsSimpleRetryResponseCodes100   contains(int code)
101   {
102     return binary_search(codes.begin(), codes.end(), code);
103   }
104 
105 private:
106   std::vector<int> codes;
107 };
108 // struct pRecord
109 //
110 //    A record for an individual parent
111 //
112 struct pRecord : ATSConsistentHashNode {
113   char hostname[MAXDNAME + 1];
114   int port;
115   std::atomic<time_t> failedAt = 0;
116   std::atomic<int> failCount   = 0;
117   int32_t upAt;
118   const char *scheme; // for which parent matches (if any)
119   int idx;
120   float weight;
121   char hash_string[MAXDNAME + 1];
122   std::atomic<int> retriers = 0;
123 };
124 
125 typedef ControlMatcher<ParentRecord, ParentResult> P_table;
126 
127 // class ParentRecord : public ControlBase
128 //
129 //   A record for a configuration line in the parent.config
130 //    file
131 //
132 class ParentRecord : public ControlBase
133 {
134 public:
135   ~ParentRecord();
136 
137   Result Init(matcher_line *line_info);
138   bool DefaultInit(char *val);
139   void UpdateMatch(ParentResult *result, RequestData *rdata);
140 
141   void Print() const;
142 
143   pRecord *parents           = nullptr;
144   pRecord *secondary_parents = nullptr;
145   int num_parents            = 0;
146   int num_secondary_parents  = 0;
147 
148   bool
bypass_ok()149   bypass_ok() const
150   {
151     return go_direct;
152   }
153 
154   const char *scheme = nullptr;
155   // private:
156   void PreProcessParents(const char *val, const int line_num, char *buf, size_t len);
157   const char *ProcessParents(char *val, bool isPrimary);
158   bool ignore_query                                                  = false;
159   uint32_t rr_next                                                   = 0;
160   bool go_direct                                                     = true;
161   bool parent_is_proxy                                               = true;
162   ParentSelectionStrategy *selection_strategy                        = nullptr;
163   UnavailableServerResponseCodes *unavailable_server_retry_responses = nullptr;
164   SimpleRetryResponseCodes *simple_server_retry_responses            = nullptr;
165   ParentRetry_t parent_retry                                         = PARENT_RETRY_NONE;
166   int max_simple_retries                                             = 1;
167   int max_unavailable_server_retries                                 = 1;
168   int secondary_mode                                                 = 1;
169   bool ignore_self_detect                                            = false;
170 };
171 
172 // If the parent was set by the external customer api,
173 //   our HttpRequestData structure told us what parent to
174 //   use and we are only called to preserve clean interface
175 //   between HttpTransact & the parent selection code.  The following
176 ParentRecord *const extApiRecord = (ParentRecord *)0xeeeeffff;
177 
178 // used here to to set the number of ATSConsistentHashIter's
179 // used in NextHopSelectionStrategy to limit the host group
180 // size as well, group size is one to one with the number of rings
181 constexpr const uint32_t MAX_GROUP_RINGS = 5;
182 
183 struct ParentResult {
ParentResultParentResult184   ParentResult() { reset(); }
185   // For outside consumption
186   ParentResultType result;
187   const char *hostname;
188   int port;
189   bool retry;
190   bool chash_init[MAX_GROUP_RINGS] = {false};
191   HostStatus_t first_choice_status = HostStatus_t::HOST_STATUS_INIT;
192 
193   void
resetParentResult194   reset()
195   {
196     ink_zero(*this);
197     line_number   = -1;
198     result        = PARENT_UNDEFINED;
199     mapWrapped[0] = false;
200     mapWrapped[1] = false;
201   }
202 
203   bool
is_api_resultParentResult204   is_api_result() const
205   {
206     return rec == extApiRecord;
207   }
208 
209   // Do we have some result?
210   bool
is_someParentResult211   is_some() const
212   {
213     if (rec == nullptr) {
214       // If we don't have a result, we either haven't done a parent
215       // lookup yet (PARENT_UNDEFINED), or the lookup didn't match
216       // anything (PARENT_DIRECT).
217       ink_assert(result == PARENT_UNDEFINED || result == PARENT_DIRECT);
218       return false;
219     }
220 
221     return true;
222   }
223 
224   bool
parent_is_proxyParentResult225   parent_is_proxy() const
226   {
227     // Parents set by the TSHttpTxnParentProxySet API are always considered proxies rather than origins.
228     return is_api_result() ? true : rec->parent_is_proxy;
229   }
230 
231   unsigned
retry_typeParentResult232   retry_type() const
233   {
234     return is_api_result() ? PARENT_RETRY_NONE : rec->parent_retry;
235   }
236 
237   unsigned
max_retriesParentResult238   max_retries(ParentRetry_t method) const
239   {
240     // There's no API for specifying the retries, so you get 0.
241     if (is_api_result()) {
242       return 0;
243     }
244 
245     switch (method) {
246     case PARENT_RETRY_NONE:
247       return 0;
248     case PARENT_RETRY_SIMPLE:
249       return rec->max_simple_retries;
250     case PARENT_RETRY_UNAVAILABLE_SERVER:
251       return rec->max_unavailable_server_retries;
252     case PARENT_RETRY_BOTH:
253       return std::max(rec->max_unavailable_server_retries, rec->max_simple_retries);
254     }
255 
256     return 0;
257   }
258 
259   bool
response_is_retryableParentResult260   response_is_retryable(HTTPStatus response_code) const
261   {
262     Debug("parent_select", "In response_is_retryable, code: %d", response_code);
263     if (retry_type() == PARENT_RETRY_BOTH) {
264       Debug("parent_select", "Saw retry both");
265       return (rec->unavailable_server_retry_responses->contains(response_code) ||
266               rec->simple_server_retry_responses->contains(response_code));
267     } else if (retry_type() == PARENT_RETRY_UNAVAILABLE_SERVER) {
268       Debug("parent_select", "Saw retry unavailable server");
269       return rec->unavailable_server_retry_responses->contains(response_code);
270     } else if (retry_type() == PARENT_RETRY_SIMPLE) {
271       Debug("parent_select", "Saw retry simple retry");
272       return rec->simple_server_retry_responses->contains(response_code);
273     } else {
274       return false;
275     }
276   }
277 
278   bool
bypass_okParentResult279   bypass_ok() const
280   {
281     if (is_api_result()) {
282       return false;
283     } else {
284       // Caller should check for a valid result beforehand.
285       ink_assert(result != PARENT_UNDEFINED);
286       ink_assert(is_some());
287       return rec->bypass_ok();
288     }
289   }
290 
291   void
printParentResult292   print()
293   {
294     printf("ParentResult - hostname: %s, port: %d, retry: %s, line_number: %d, last_parent: %d, start_parent: %d, wrap_around: %s, "
295            "last_lookup: %d, result: %s\n",
296            hostname, port, (retry) ? "true" : "false", line_number, last_parent, start_parent, (wrap_around) ? "true" : "false",
297            last_lookup, ParentResultStr[result]);
298   }
299 
300 private:
301   // Internal use only
302   //   Not to be modified by HTTP
303   int line_number;
304   ParentRecord *rec;
305   uint32_t last_parent;
306   uint32_t start_parent;
307   uint32_t last_group;
308   bool wrap_around;
309   bool mapWrapped[2];
310   // state for consistent hash.
311   int last_lookup;
312   ATSConsistentHashIter chashIter[MAX_GROUP_RINGS];
313 
314   friend class NextHopSelectionStrategy;
315   friend class NextHopRoundRobin;
316   friend class NextHopConsistentHash;
317   friend class ParentConsistentHash;
318   friend class ParentRoundRobin;
319   friend class ParentConfigParams;
320   friend class ParentRecord;
321   friend class ParentSelectionStrategy;
322 };
323 
324 struct ParentSelectionPolicy {
325   int32_t ParentRetryTime;
326   int32_t ParentEnable;
327   int32_t FailThreshold;
328   ParentSelectionPolicy();
329 };
330 
331 //
332 // API definition.
333 class ParentSelectionStrategy
334 {
335 public:
336   int max_retriers = 0;
337 
ParentSelectionStrategy()338   ParentSelectionStrategy() { REC_ReadConfigInteger(max_retriers, "proxy.config.http.parent_proxy.max_trans_retries"); }
339   //
340   // Return the pRecord.
341   virtual pRecord *getParents(ParentResult *result) = 0;
342   // void selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold, unsigned int
343   // retry_time)
344   //
345   // The implementation parent lookup.
346   //
347   virtual void selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold,
348                             unsigned int retry_time) = 0;
349 
350   // uint32_t numParents(ParentResult *result);
351   //
352   // Returns the number of parent records in a strategy.
353   //
354   virtual uint32_t numParents(ParentResult *result) const = 0;
355   void markParentDown(ParentResult *result, unsigned int fail_threshold, unsigned int retry_time);
356   void markParentUp(ParentResult *result);
357 
358   // virtual destructor.
~ParentSelectionStrategy()359   virtual ~ParentSelectionStrategy(){};
360 };
361 
362 class ParentConfigParams : public ConfigInfo
363 {
364 public:
365   explicit ParentConfigParams(P_table *_parent_table);
366   ~ParentConfigParams() override;
367 
368   bool apiParentExists(HttpRequestData *rdata);
369   void findParent(HttpRequestData *rdata, ParentResult *result, unsigned int fail_threshold, unsigned int retry_time);
370   void nextParent(HttpRequestData *rdata, ParentResult *result, unsigned int fail_threshold, unsigned int retry_time);
371   bool parentExists(HttpRequestData *rdata);
372 
373   // implementation of functions from ParentSelectionStrategy.
374   void
selectParent(bool firstCall,ParentResult * result,RequestData * rdata,unsigned int fail_threshold,unsigned int retry_time)375   selectParent(bool firstCall, ParentResult *result, RequestData *rdata, unsigned int fail_threshold, unsigned int retry_time)
376   {
377     if (!result->is_api_result()) {
378       ink_release_assert(result->rec->selection_strategy != nullptr);
379       return result->rec->selection_strategy->selectParent(firstCall, result, rdata, fail_threshold, retry_time);
380     }
381   }
382 
383   void
markParentDown(ParentResult * result,unsigned int fail_threshold,unsigned int retry_time)384   markParentDown(ParentResult *result, unsigned int fail_threshold, unsigned int retry_time)
385   {
386     if (!result->is_api_result()) {
387       ink_release_assert(result->rec->selection_strategy != nullptr);
388       result->rec->selection_strategy->markParentDown(result, fail_threshold, retry_time);
389     }
390   }
391 
392   void
markParentUp(ParentResult * result)393   markParentUp(ParentResult *result)
394   {
395     if (!result->is_api_result()) {
396       ink_release_assert(result != nullptr);
397       result->rec->selection_strategy->markParentUp(result);
398     }
399   }
400 
401   uint32_t
numParents(ParentResult * result)402   numParents(ParentResult *result)
403   {
404     if (result->is_api_result()) {
405       return 1;
406     } else {
407       ink_release_assert(result->rec->selection_strategy != nullptr);
408       return result->rec->selection_strategy->numParents(result);
409     }
410   }
411 
412   P_table *parent_table;
413   ParentRecord *DefaultParent;
414   ParentSelectionPolicy policy;
415 };
416 
417 class HttpRequestData;
418 
419 struct ParentConfig {
420 public:
421   static void startup();
422   static void reconfigure();
423   static void print();
424   static void set_parent_table(P_table *pTable, ParentRecord *rec, int num_elements);
425 
426   static ParentConfigParams *
acquireParentConfig427   acquire()
428   {
429     return (ParentConfigParams *)configProcessor.get(ParentConfig::m_id);
430   }
431 
432   static void
releaseParentConfig433   release(ParentConfigParams *strategy)
434   {
435     configProcessor.release(ParentConfig::m_id, strategy);
436   }
437 
438   static int m_id;
439 };
440 
441 // Helper Functions
442 ParentRecord *createDefaultParent(char *val);
443 
444 // Unit Test Functions
445 void show_result(ParentResult *aParentResult);
446 void br(HttpRequestData *h, const char *os_hostname, sockaddr const *dest_ip = nullptr); // short for build request
447 int verify(ParentResult *r, ParentResultType e, const char *h, int p);
448 
449 /*
450   For supporting multiple Socks servers, we essentially use the
451   ParentSelection infrastructure. Only the initialization is different.
452   If needed, we will have to implement most of the functions in
453   ParentSection.cc for Socks as well. For right now we will just use
454   ParentSelection
455 
456   All the members in ParentConfig are static. Right now
457   we will duplicate the code for these static functions.
458 */
459 struct SocksServerConfig {
460   static void startup();
461   static void reconfigure();
462   static void print();
463 
464   static ParentConfigParams *
acquireSocksServerConfig465   acquire()
466   {
467     return (ParentConfigParams *)configProcessor.get(SocksServerConfig::m_id);
468   }
469   static void
releaseSocksServerConfig470   release(ParentConfigParams *params)
471   {
472     configProcessor.release(SocksServerConfig::m_id, params);
473   }
474 
475   static int m_id;
476 };
477