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