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 "tscore/HashFNV.h"
27 #include "tscore/ink_time.h"
28 #include "tscore/CryptoHash.h"
29 #include "tscore/ink_align.h"
30 #include "tscore/ink_resolver.h"
31 #include "I_EventSystem.h"
32 #include "SRV.h"
33 #include "P_RefCountCache.h"
34 
35 // Event returned on a lookup
36 #define EVENT_HOST_DB_LOOKUP (HOSTDB_EVENT_EVENTS_START + 0)
37 #define EVENT_HOST_DB_IP_REMOVED (HOSTDB_EVENT_EVENTS_START + 1)
38 #define EVENT_HOST_DB_GET_RESPONSE (HOSTDB_EVENT_EVENTS_START + 2)
39 
40 #define EVENT_SRV_LOOKUP (SRV_EVENT_EVENTS_START + 0)
41 #define EVENT_SRV_IP_REMOVED (SRV_EVENT_EVENTS_START + 1)
42 #define EVENT_SRV_GET_RESPONSE (SRV_EVENT_EVENTS_START + 2)
43 
44 //
45 // Data
46 //
47 struct HostDBContinuation;
48 
49 //
50 // The host database stores host information, most notably the
51 // IP address.
52 //
53 // Since host information is relatively small, we can afford to have
54 // a reasonable size memory cache, and use a (relatively) sparse
55 // disk representation to decrease # of seeks.
56 //
57 extern int hostdb_enable;
58 extern ink_time_t hostdb_current_interval;
59 extern unsigned int hostdb_ip_stale_interval;
60 extern unsigned int hostdb_ip_timeout_interval;
61 extern unsigned int hostdb_ip_fail_timeout_interval;
62 extern unsigned int hostdb_serve_stale_but_revalidate;
63 extern unsigned int hostdb_round_robin_max_count;
64 
65 static inline unsigned int
makeHostHash(const char * string)66 makeHostHash(const char *string)
67 {
68   ink_assert(string && *string);
69 
70   if (string && *string) {
71     ATSHash32FNV1a fnv;
72     fnv.update(string, strlen(string), ATSHash::nocase());
73     fnv.final();
74     return fnv.get();
75   }
76 
77   return 0;
78 }
79 
80 //
81 // Types
82 //
83 
84 //
85 // This structure contains the host information required by
86 // the application.  Except for the initial fields it
87 // is treated as opaque by the database.
88 //
89 
90 union HostDBApplicationInfo {
91   struct application_data_allotment {
92     unsigned int application1;
93     unsigned int application2;
94   } allotment;
95 
96   //////////////////////////////////////////////////////////
97   // http server attributes in the host database          //
98   //                                                      //
99   // http_version       - one of HttpVersion_t            //
100   // keep_alive_timeout - in seconds. (up to 63 seconds). //
101   // last_failure       - UNIX time for the last time     //
102   //                      we tried the server & failed    //
103   // fail_count         - Number of times we tried and    //
104   //                       and failed to contact the host //
105   //////////////////////////////////////////////////////////
106   struct http_server_attr {
107     unsigned int http_version : 3;
108     unsigned int keepalive_timeout : 6;
109     unsigned int fail_count : 8;
110     unsigned int unused1 : 8;
111     unsigned int last_failure : 32;
112   } http_data;
113 
114   enum HttpVersion_t {
115     HTTP_VERSION_UNDEFINED = 0,
116     HTTP_VERSION_09        = 1,
117     HTTP_VERSION_10        = 2,
118     HTTP_VERSION_11        = 3,
119   };
120 
121   struct application_data_rr {
122     unsigned int offset;
123   } rr;
124 };
125 
126 struct HostDBRoundRobin;
127 
128 struct SRVInfo {
129   unsigned int srv_offset : 16;
130   unsigned int srv_weight : 16;
131   unsigned int srv_priority : 16;
132   unsigned int srv_port : 16;
133   unsigned int key;
134 };
135 
136 struct HostDBInfo : public RefCountObj {
137   /** Internal IP address data.
138       This is at least large enough to hold an IPv6 address.
139   */
140 
141   static HostDBInfo *
allocHostDBInfo142   alloc(int size = 0)
143   {
144     size += sizeof(HostDBInfo);
145     int iobuffer_index = iobuffer_size_to_index(size);
146     ink_release_assert(iobuffer_index >= 0);
147     void *ptr = ioBufAllocator[iobuffer_index].alloc_void();
148     memset(ptr, 0, size);
149     HostDBInfo *ret     = new (ptr) HostDBInfo();
150     ret->iobuffer_index = iobuffer_index;
151     return ret;
152   }
153 
154   void
freeHostDBInfo155   free() override
156   {
157     Debug("hostdb", "freeing %d bytes at [%p]", (1 << (7 + iobuffer_index)), this);
158     ioBufAllocator[iobuffer_index].free_void((void *)(this));
159   }
160 
161   // return a version number-- so we can manage compatibility with the marshal/unmarshal
162   static ts::VersionNumber
versionHostDBInfo163   version()
164   {
165     return ts::VersionNumber(1, 0);
166   }
167 
168   static HostDBInfo *
unmarshallHostDBInfo169   unmarshall(char *buf, unsigned int size)
170   {
171     if (size < sizeof(HostDBInfo)) {
172       return nullptr;
173     }
174     HostDBInfo *ret = HostDBInfo::alloc(size - sizeof(HostDBInfo));
175     int buf_index   = ret->iobuffer_index;
176     memcpy((void *)ret, buf, size);
177     // Reset the refcount back to 0, this is a bit ugly-- but I'm not sure we want to expose a method
178     // to mess with the refcount, since this is a fairly unique use case
179     ret                 = new (ret) HostDBInfo();
180     ret->iobuffer_index = buf_index;
181     return ret;
182   }
183 
184   // return expiry time (in seconds since epoch)
185   ink_time_t
expiry_timeHostDBInfo186   expiry_time() const
187   {
188     return ip_timestamp + ip_timeout_interval + hostdb_serve_stale_but_revalidate;
189   }
190 
191   sockaddr *
ipHostDBInfo192   ip()
193   {
194     return &data.ip.sa;
195   }
196 
197   sockaddr const *
ipHostDBInfo198   ip() const
199   {
200     return &data.ip.sa;
201   }
202 
203   char *hostname() const;
204   char *perm_hostname() const;
205   char *srvname(HostDBRoundRobin *rr) const;
206 
207   /// Check if this entry is an element of a round robin entry.
208   /// If @c true then this entry is part of and was obtained from a round robin root. This is useful if the
209   /// address doesn't work - a retry can probably get a new address by doing another lookup and resolving to
210   /// a different element of the round robin.
211   bool
is_rr_eltHostDBInfo212   is_rr_elt() const
213   {
214     return 0 != round_robin_elt;
215   }
216 
217   HostDBRoundRobin *rr();
218 
219   unsigned int
ip_intervalHostDBInfo220   ip_interval() const
221   {
222     return (hostdb_current_interval - ip_timestamp) & 0x7FFFFFFF;
223   }
224 
225   int
ip_time_remainingHostDBInfo226   ip_time_remaining() const
227   {
228     return static_cast<int>(ip_timeout_interval) - static_cast<int>(this->ip_interval());
229   }
230 
231   bool
is_ip_staleHostDBInfo232   is_ip_stale() const
233   {
234     return ip_timeout_interval >= 2 * hostdb_ip_stale_interval && ip_interval() >= hostdb_ip_stale_interval;
235   }
236 
237   bool
is_ip_timeoutHostDBInfo238   is_ip_timeout() const
239   {
240     return ip_interval() >= ip_timeout_interval;
241   }
242 
243   bool
is_ip_fail_timeoutHostDBInfo244   is_ip_fail_timeout() const
245   {
246     return ip_interval() >= hostdb_ip_fail_timeout_interval;
247   }
248 
249   void
refresh_ipHostDBInfo250   refresh_ip()
251   {
252     ip_timestamp = hostdb_current_interval;
253   }
254 
255   bool
serve_stale_but_revalidateHostDBInfo256   serve_stale_but_revalidate() const
257   {
258     // the option is disabled
259     if (hostdb_serve_stale_but_revalidate <= 0) {
260       return false;
261     }
262 
263     // ip_timeout_interval == DNS TTL
264     // hostdb_serve_stale_but_revalidate == number of seconds
265     // ip_interval() is the number of seconds between now() and when the entry was inserted
266     if ((ip_timeout_interval + hostdb_serve_stale_but_revalidate) > ip_interval()) {
267       Debug("hostdb", "serving stale entry %d | %d | %d as requested by config", ip_timeout_interval,
268             hostdb_serve_stale_but_revalidate, ip_interval());
269       return true;
270     }
271 
272     // otherwise, the entry is too old
273     return false;
274   }
275 
276   /*
277    * Given the current time `now` and the fail_window, determine if this real is alive
278    */
279   bool
is_aliveHostDBInfo280   is_alive(ink_time_t now, int32_t fail_window)
281   {
282     unsigned int last_failure = app.http_data.last_failure;
283 
284     if (last_failure == 0 || (unsigned int)(now - fail_window) > last_failure) {
285       return true;
286     } else {
287       // Entry is marked down.  Make sure some nasty clock skew
288       //  did not occur.  Use the retry time to set an upper bound
289       //  as to how far in the future we should tolerate bogus last
290       //  failure times.  This sets the upper bound that we would ever
291       //  consider a server down to 2*down_server_timeout
292       if ((unsigned int)(now + fail_window) < last_failure) {
293         app.http_data.last_failure = 0;
294         return false;
295       }
296       return false;
297     }
298   }
299 
300   bool
is_failedHostDBInfo301   is_failed() const
302   {
303     return !((is_srv && data.srv.srv_offset) || (reverse_dns && data.hostname_offset) || ats_is_ip(ip()));
304   }
305 
306   void
set_failedHostDBInfo307   set_failed()
308   {
309     if (is_srv) {
310       data.srv.srv_offset = 0;
311     } else if (reverse_dns) {
312       data.hostname_offset = 0;
313     } else {
314       ats_ip_invalidate(ip());
315     }
316   }
317 
318   int iobuffer_index;
319 
320   uint64_t key;
321 
322   // Application specific data. NOTE: We need an integral number of
323   // these per block. This structure is 32 bytes. (at 200k hosts =
324   // 8 Meg). Which gives us 7 bytes of application information.
325   HostDBApplicationInfo app;
326 
327   union {
328     IpEndpoint ip;                ///< IP address / port data.
329     unsigned int hostname_offset; ///< Some hostname thing.
330     SRVInfo srv;
331   } data;
332 
333   unsigned int hostname_offset; // always maintain a permanent copy of the hostname for non-rev dns records.
334 
335   unsigned int ip_timestamp;
336 
337   unsigned int ip_timeout_interval; // bounded between 1 and HOST_DB_MAX_TTL (0x1FFFFF, 24 days)
338 
339   unsigned int is_srv : 1;
340   unsigned int reverse_dns : 1;
341 
342   unsigned int round_robin : 1;     // This is the root of a round robin block
343   unsigned int round_robin_elt : 1; // This is an address in a round robin block
344 };
345 
346 struct HostDBRoundRobin {
347   /** Total number (to compute space used). */
348   short rrcount = 0;
349 
350   /** Number which have not failed a connect. */
351   short good = 0;
352 
353   unsigned short current    = 0;
354   ink_time_t timed_rr_ctime = 0;
355 
356   // This is the equivalent of a variable length array, we can't use a VLA because
357   // HostDBInfo is a non-POD type-- so this is the best we can do.
358   HostDBInfo &
infoHostDBRoundRobin359   info(short n)
360   {
361     ink_assert(n < rrcount && n >= 0);
362     return *((HostDBInfo *)((char *)this + sizeof(HostDBRoundRobin)) + n);
363   }
364 
365   // Return the allocation size of a HostDBRoundRobin struct suitable for storing
366   // "count" HostDBInfo records.
367   static unsigned
sizeHostDBRoundRobin368   size(unsigned count, unsigned srv_len = 0)
369   {
370     ink_assert(count > 0);
371     return INK_ALIGN((sizeof(HostDBRoundRobin) + (count * sizeof(HostDBInfo)) + srv_len), 8);
372   }
373 
374   /** Find the index of @a addr in member @a info.
375       @return The index if found, -1 if not found.
376   */
377   int index_of(sockaddr const *addr);
378   HostDBInfo *find_ip(sockaddr const *addr);
379   // Find the srv target
380   HostDBInfo *find_target(const char *target);
381   /** Select the next entry after @a addr.
382       @note If @a addr isn't an address in the round robin nothing is updated.
383       @return The selected entry or @c nullptr if @a addr wasn't present.
384    */
385   HostDBInfo *select_next(sockaddr const *addr);
386   HostDBInfo *select_best_http(sockaddr const *client_ip, ink_time_t now, int32_t fail_window);
387   HostDBInfo *select_best_srv(char *target, InkRand *rand, ink_time_t now, int32_t fail_window);
HostDBRoundRobinHostDBRoundRobin388   HostDBRoundRobin() {}
389 };
390 
391 struct HostDBCache;
392 struct HostDBHash;
393 
394 // Prototype for inline completion function or
395 //  getbyname_imm()
396 typedef void (Continuation::*cb_process_result_pfn)(HostDBInfo *r);
397 
398 Action *iterate(Continuation *cont);
399 
400 /** The Host Database access interface. */
401 struct HostDBProcessor : public Processor {
402   friend struct HostDBSync;
403   // Public Interface
404 
405   // Lookup Hostinfo by name
406   //    cont->handleEvent( EVENT_HOST_DB_LOOKUP, HostDBInfo * ); on success
407   //    cont->handleEVent( EVENT_HOST_DB_LOOKUP, 0); on failure
408   // Failure occurs when the host cannot be DNS-ed
409   // NOTE: Will call the continuation back before returning if data is in the
410   //       cache.  The HostDBInfo * becomes invalid when the callback returns.
411   //       The HostDBInfo may be changed during the callback.
412 
413   enum {
414     HOSTDB_DO_NOT_FORCE_DNS   = 0,
415     HOSTDB_ROUND_ROBIN        = 0,
416     HOSTDB_FORCE_DNS_RELOAD   = 1,
417     HOSTDB_FORCE_DNS_ALWAYS   = 2,
418     HOSTDB_DO_NOT_ROUND_ROBIN = 4
419   };
420 
421   /// Optional parameters for getby...
422   struct Options {
423     typedef Options self;                                  ///< Self reference type.
424     int port                    = 0;                       ///< Target service port (default 0 -> don't care)
425     int flags                   = HOSTDB_DO_NOT_FORCE_DNS; ///< Processing flags (default HOSTDB_DO_NOT_FORCE_DNS)
426     int timeout                 = 0;                       ///< Timeout value (default 0 -> default timeout)
427     HostResStyle host_res_style = HOST_RES_IPV4;           ///< How to query host (default HOST_RES_IPV4)
428 
OptionsHostDBProcessor::Options429     Options() {}
430     /// Set the flags.
431     self &
setFlagsHostDBProcessor::Options432     setFlags(int f)
433     {
434       flags = f;
435       return *this;
436     }
437   };
438 
439   /// Default options.
440   static Options const DEFAULT_OPTIONS;
441 
HostDBProcessorHostDBProcessor442   HostDBProcessor() {}
443   inkcoreapi Action *getbyname_re(Continuation *cont, const char *hostname, int len, Options const &opt = DEFAULT_OPTIONS);
444 
445   Action *getbynameport_re(Continuation *cont, const char *hostname, int len, Options const &opt = DEFAULT_OPTIONS);
446 
447   Action *getSRVbyname_imm(Continuation *cont, cb_process_result_pfn process_srv_info, const char *hostname, int len,
448                            Options const &opt = DEFAULT_OPTIONS);
449 
450   Action *getbyname_imm(Continuation *cont, cb_process_result_pfn process_hostdb_info, const char *hostname, int len,
451                         Options const &opt = DEFAULT_OPTIONS);
452 
453   Action *iterate(Continuation *cont);
454 
455   /** Lookup Hostinfo by addr */
456   Action *getbyaddr_re(Continuation *cont, sockaddr const *aip);
457 
458   /** Set the application information (fire-and-forget). */
459   void
setbyname_appinfoHostDBProcessor460   setbyname_appinfo(char *hostname, int len, int port, HostDBApplicationInfo *app)
461   {
462     sockaddr_in addr;
463     ats_ip4_set(&addr, INADDR_ANY, port);
464     setby(hostname, len, ats_ip_sa_cast(&addr), app);
465   }
466 
467   void
setbyaddr_appinfoHostDBProcessor468   setbyaddr_appinfo(sockaddr const *addr, HostDBApplicationInfo *app)
469   {
470     this->setby(nullptr, 0, addr, app);
471   }
472 
473   void
setbyaddr_appinfoHostDBProcessor474   setbyaddr_appinfo(in_addr_t ip, HostDBApplicationInfo *app)
475   {
476     sockaddr_in addr;
477     ats_ip4_set(&addr, ip);
478     this->setby(nullptr, 0, ats_ip_sa_cast(&addr), app);
479   }
480 
481   /** Configuration. */
482   static int hostdb_strict_round_robin;
483   static int hostdb_timed_round_robin;
484 
485   // Processor Interface
486   /* hostdb does not use any dedicated event threads
487    * currently. Dont pass any value to start
488    */
489   int start(int no_of_additional_event_threads = 0, size_t stacksize = DEFAULT_STACKSIZE) override;
490 
491   // Private
492   HostDBCache *cache();
493 
494 private:
495   Action *getby(Continuation *cont, cb_process_result_pfn cb_process_result, HostDBHash &hash, Options const &opt);
496 
497 public:
498   /** Set something.
499       @a aip can carry address and / or port information. If setting just
500       by a port value, the address should be set to INADDR_ANY which is of
501       type IPv4.
502    */
503   void setby(const char *hostname,      ///< Hostname.
504              int len,                   ///< Length of hostname.
505              sockaddr const *aip,       ///< Address and/or port.
506              HostDBApplicationInfo *app ///< I don't know.
507   );
508 
509   void setby_srv(const char *hostname, int len, const char *target, HostDBApplicationInfo *app);
510 };
511 
512 void run_HostDBTest();
513 
514 extern inkcoreapi HostDBProcessor hostDBProcessor;
515 
516 void ink_hostdb_init(ts::ModuleVersion version);
517