1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 #include "tscore/ink_config.h"
19 
20 // Needs special OpenSSL APIs as a global plugin for early CLIENT_HELLO inspection
21 #if TS_USE_HELLO_CB
22 
23 #include <string.h>
24 
25 #include "sni_limiter.h"
26 #include "sni_selector.h"
27 
28 ///////////////////////////////////////////////////////////////////////////////
29 // This is the queue management continuation, which gets called periodically
30 //
31 static int
sni_queue_cont(TSCont cont,TSEvent event,void * edata)32 sni_queue_cont(TSCont cont, TSEvent event, void *edata)
33 {
34   SniSelector *selector = static_cast<SniSelector *>(TSContDataGet(cont));
35 
36   for (const auto &[key, limiter] : selector->limiters()) {
37     QueueTime now = std::chrono::system_clock::now(); // Only do this once per limiter
38 
39     // Try to enable some queued VCs (if any) if there are slots available
40     while (limiter->size() > 0 && limiter->reserve()) {
41       auto [vc, contp, start_time]    = limiter->pop();
42       std::chrono::milliseconds delay = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
43 
44       (void)contp; // Ugly, but silences some compilers.
45       TSDebug(PLUGIN_NAME, "SNI=%s: Enabling queued VC after %ldms", key.data(), static_cast<long>(delay.count()));
46       TSVConnReenable(vc);
47     }
48 
49     // Kill any queued VCs if they are too old
50     if (limiter->size() > 0 && limiter->max_age > std::chrono::milliseconds::zero()) {
51       now = std::chrono::system_clock::now(); // Update the "now", for some extra accuracy
52 
53       while (limiter->size() > 0 && limiter->hasOldEntity(now)) {
54         // The oldest object on the queue is too old on the queue, so "kill" it.
55         auto [vc, contp, start_time]  = limiter->pop();
56         std::chrono::milliseconds age = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
57 
58         (void)contp;
59         TSDebug(PLUGIN_NAME, "Queued VC is too old (%ldms), erroring out", static_cast<long>(age.count()));
60         TSVConnReenableEx(vc, TS_EVENT_ERROR);
61       }
62     }
63   }
64 
65   return TS_EVENT_NONE;
66 }
67 
68 ///////////////////////////////////////////////////////////////////////////////
69 // This is the queue management continuation, which gets called periodically
70 //
71 bool
insert(std::string_view sni,SniRateLimiter * limiter)72 SniSelector::insert(std::string_view sni, SniRateLimiter *limiter)
73 {
74   if (_limiters.find(sni) == _limiters.end()) {
75     _limiters[sni] = limiter;
76     TSDebug(PLUGIN_NAME, "Added global limiter for SNI=%s (limit=%u, queue=%u, max_age=%ldms)", sni.data(), limiter->limit,
77             limiter->max_queue, static_cast<long>(limiter->max_age.count()));
78 
79     return true;
80   }
81 
82   return false;
83 }
84 
85 SniRateLimiter *
find(std::string_view sni)86 SniSelector::find(std::string_view sni)
87 {
88   auto limiter = _limiters.find(sni);
89 
90   if (limiter != _limiters.end()) {
91     return limiter->second;
92   }
93   return nullptr;
94 }
95 
96 ///////////////////////////////////////////////////////////////////////////////
97 // This factory will create a number of SNI limiters based on the input string
98 // given. The list of SNI's is comma separated. ToDo: This should go away when
99 // we switch to a proper YAML parser, and we will only use the insert() above.
100 //
101 size_t
factory(const char * sni_list,int argc,const char * argv[])102 SniSelector::factory(const char *sni_list, int argc, const char *argv[])
103 {
104   char *saveptr;
105   char *sni   = strdup(sni_list); // We make a copy of the sni list, to not touch the original string
106   char *token = strtok_r(sni, ",", &saveptr);
107   SniRateLimiter def_limiter;
108 
109   def_limiter.initialize(argc, argv); // Creates the template limiter
110   _needs_queue_cont = (def_limiter.max_queue > 0);
111 
112   while (nullptr != token) {
113     SniRateLimiter *limiter = new SniRateLimiter(def_limiter); // Make a shallow copy
114     TSReleaseAssert(limiter);
115 
116     limiter->description = token;
117 
118     insert(std::string_view(limiter->description), limiter);
119     token = strtok_r(nullptr, ",", &saveptr);
120   }
121   free(sni);
122 
123   return _limiters.size();
124 }
125 
126 ///////////////////////////////////////////////////////////////////////////////
127 // If needed, create the queue continuation that needs to run for this selector.
128 //
129 void
setupQueueCont()130 SniSelector::setupQueueCont()
131 {
132   if (_needs_queue_cont) {
133     _queue_cont = TSContCreate(sni_queue_cont, TSMutexCreate());
134     TSReleaseAssert(_queue_cont);
135     TSContDataSet(_queue_cont, this);
136     _action = TSContScheduleEveryOnPool(_queue_cont, QUEUE_DELAY_TIME.count(), TS_THREAD_POOL_TASK);
137   }
138 }
139 
140 #endif
141