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 #include "tscore/ink_config.h"
25 #include "P_Net.h"
26 #include "HttpConfig.h"
27 #include "HttpSessionAccept.h"
28 #include "ReverseProxy.h"
29 #include "HttpSessionManager.h"
30 #ifdef USE_HTTP_DEBUG_LISTS
31 #include "Http1ClientSession.h"
32 #endif
33 #include "HttpPages.h"
34 #include "HttpTunnel.h"
35 #include "tscore/Tokenizer.h"
36 #include "P_SSLNextProtocolAccept.h"
37 #include "ProtocolProbeSessionAccept.h"
38 #include "http2/Http2SessionAccept.h"
39 #include "HttpConnectionCount.h"
40 #include "HttpProxyServerMain.h"
41 #if TS_USE_QUIC == 1
42 #include "P_QUICNetProcessor.h"
43 #include "P_QUICNextProtocolAccept.h"
44 #include "http3/Http3SessionAccept.h"
45 #endif
46
47 #include <vector>
48
49 HttpSessionAccept *plugin_http_accept = nullptr;
50 HttpSessionAccept *plugin_http_transparent_accept = nullptr;
51
52 static SLL<SSLNextProtocolAccept> ssl_plugin_acceptors;
53 static Ptr<ProxyMutex> ssl_plugin_mutex;
54
55 std::mutex proxyServerMutex;
56 std::condition_variable proxyServerCheck;
57 bool et_net_threads_ready = false;
58
59 std::mutex etUdpMutex;
60 std::condition_variable etUdpCheck;
61 bool et_udp_threads_ready = false;
62
63 extern int num_of_net_threads;
64 extern int num_accept_threads;
65
66 /// Global BufferWriter format name functions.
67 namespace
68 {
69 void
TS_bwf_thread(ts::BufferWriter & w,ts::BWFSpec const & spec)70 TS_bwf_thread(ts::BufferWriter &w, ts::BWFSpec const &spec)
71 {
72 bwformat(w, spec, this_thread());
73 }
74 void
TS_bwf_ethread(ts::BufferWriter & w,ts::BWFSpec const & spec)75 TS_bwf_ethread(ts::BufferWriter &w, ts::BWFSpec const &spec)
76 {
77 bwformat(w, spec, this_ethread());
78 }
79 } // namespace
80
81 // File / process scope initializations
__anon5735c1be0202() 82 static bool HTTP_SERVER_INITIALIZED __attribute__((unused)) = []() -> bool {
83 ts::bwf_register_global("ts-thread", &TS_bwf_thread);
84 ts::bwf_register_global("ts-ethread", &TS_bwf_ethread);
85 return true;
86 }();
87
88 bool
ssl_register_protocol(const char * protocol,Continuation * contp)89 ssl_register_protocol(const char *protocol, Continuation *contp)
90 {
91 SCOPED_MUTEX_LOCK(lock, ssl_plugin_mutex, this_ethread());
92
93 for (SSLNextProtocolAccept *ssl = ssl_plugin_acceptors.head; ssl; ssl = ssl_plugin_acceptors.next(ssl)) {
94 if (!ssl->registerEndpoint(protocol, contp)) {
95 return false;
96 }
97 }
98
99 return true;
100 }
101
102 /////////////////////////////////////////////////////////////////
103 //
104 // main()
105 //
106 /////////////////////////////////////////////////////////////////
107
108 /** Data about an acceptor.
109
110 This is used to separate setting up the proxy ports and
111 starting to accept on them.
112
113 */
114 struct HttpProxyAcceptor {
115 /// Accept continuation.
116 Continuation *_accept = nullptr;
117 /// Options for @c NetProcessor.
118 NetProcessor::AcceptOptions _net_opt;
119
120 /// Default constructor.
HttpProxyAcceptorHttpProxyAcceptor121 HttpProxyAcceptor() {}
122 };
123
124 /** Global acceptors.
125
126 This is parallel to @c HttpProxyPort::global(), each generated
127 from the corresponding port descriptor.
128
129 @internal We use @c Continuation instead of @c HttpAccept because
130 @c SSLNextProtocolAccept is a subclass of @c Cont instead of @c
131 HttpAccept.
132 */
133 std::vector<HttpProxyAcceptor> HttpProxyAcceptors;
134
135 // Called from InkAPI.cc
136 NetProcessor::AcceptOptions
make_net_accept_options(const HttpProxyPort * port,unsigned nthreads)137 make_net_accept_options(const HttpProxyPort *port, unsigned nthreads)
138 {
139 NetProcessor::AcceptOptions net;
140
141 net.accept_threads = nthreads;
142
143 REC_ReadConfigInteger(net.packet_mark, "proxy.config.net.sock_packet_mark_in");
144 REC_ReadConfigInteger(net.packet_tos, "proxy.config.net.sock_packet_tos_in");
145 REC_ReadConfigInteger(net.recv_bufsize, "proxy.config.net.sock_recv_buffer_size_in");
146 REC_ReadConfigInteger(net.send_bufsize, "proxy.config.net.sock_send_buffer_size_in");
147 REC_ReadConfigInteger(net.sockopt_flags, "proxy.config.net.sock_option_flag_in");
148 REC_ReadConfigInteger(net.defer_accept, "proxy.config.net.defer_accept");
149
150 #ifdef TCP_FASTOPEN
151 REC_ReadConfigInteger(net.tfo_queue_length, "proxy.config.net.sock_option_tfo_queue_size_in");
152 #endif
153
154 if (port) {
155 net.f_inbound_transparent = port->m_inbound_transparent_p;
156 net.f_mptcp = port->m_mptcp;
157 net.ip_family = port->m_family;
158 net.local_port = port->m_port;
159 net.f_proxy_protocol = port->m_proxy_protocol;
160
161 if (port->m_inbound_ip.isValid()) {
162 net.local_ip = port->m_inbound_ip;
163 } else if (AF_INET6 == port->m_family && HttpConfig::m_master.inbound_ip6.isIp6()) {
164 net.local_ip = HttpConfig::m_master.inbound_ip6;
165 } else if (AF_INET == port->m_family && HttpConfig::m_master.inbound_ip4.isIp4()) {
166 net.local_ip = HttpConfig::m_master.inbound_ip4;
167 }
168 }
169 return net;
170 }
171
172 static void
MakeHttpProxyAcceptor(HttpProxyAcceptor & acceptor,HttpProxyPort & port,unsigned nthreads)173 MakeHttpProxyAcceptor(HttpProxyAcceptor &acceptor, HttpProxyPort &port, unsigned nthreads)
174 {
175 NetProcessor::AcceptOptions &net_opt = acceptor._net_opt;
176 HttpSessionAccept::Options accept_opt;
177
178 net_opt = make_net_accept_options(&port, nthreads);
179
180 accept_opt.f_outbound_transparent = port.m_outbound_transparent_p;
181 accept_opt.transport_type = port.m_type;
182 accept_opt.setHostResPreference(port.m_host_res_preference);
183 accept_opt.setTransparentPassthrough(port.m_transparent_passthrough);
184 accept_opt.setSessionProtocolPreference(port.m_session_protocol_preference);
185
186 if (port.m_outbound_ip4.isValid()) {
187 accept_opt.outbound_ip4 = port.m_outbound_ip4;
188 } else if (HttpConfig::m_master.outbound_ip4.isValid()) {
189 accept_opt.outbound_ip4 = HttpConfig::m_master.outbound_ip4;
190 }
191
192 if (port.m_outbound_ip6.isValid()) {
193 accept_opt.outbound_ip6 = port.m_outbound_ip6;
194 } else if (HttpConfig::m_master.outbound_ip6.isValid()) {
195 accept_opt.outbound_ip6 = HttpConfig::m_master.outbound_ip6;
196 }
197
198 // OK the way this works is that the fallback for each port is a protocol
199 // probe acceptor. For SSL ports, we can stack a NPN+ALPN acceptor in front
200 // of that, and these ports will fall back to the probe if no NPN+ALPN endpoint
201 // was negotiated.
202
203 // XXX the protocol probe should be a configuration option.
204
205 ProtocolProbeSessionAccept *probe = new ProtocolProbeSessionAccept();
206 HttpSessionAccept *http = nullptr; // don't allocate this unless it will be used.
207 probe->proxyPort = &port;
208 probe->proxy_protocol_ipmap = &HttpConfig::m_master.config_proxy_protocol_ipmap;
209
210 if (port.m_session_protocol_preference.intersects(HTTP_PROTOCOL_SET)) {
211 http = new HttpSessionAccept(accept_opt);
212 probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_HTTP, http);
213 }
214
215 if (port.m_session_protocol_preference.intersects(HTTP2_PROTOCOL_SET)) {
216 probe->registerEndpoint(ProtocolProbeSessionAccept::PROTO_HTTP2, new Http2SessionAccept(accept_opt));
217 }
218
219 if (port.isSSL()) {
220 SSLNextProtocolAccept *ssl = new SSLNextProtocolAccept(probe, port.m_transparent_passthrough);
221
222 // ALPN selects the first server-offered protocol,
223 // so make sure that we offer the newest protocol first.
224 // But since registerEndpoint prepends you want to
225 // register them backwards, so you'd want to register
226 // the least important protocol first:
227 // http/1.0, http/1.1, h2
228
229 ssl->enableProtocols(port.m_session_protocol_preference);
230 ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_1_0, http);
231 ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_1_1, http);
232 ssl->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_2_0, new Http2SessionAccept(accept_opt));
233
234 SCOPED_MUTEX_LOCK(lock, ssl_plugin_mutex, this_ethread());
235 ssl_plugin_acceptors.push(ssl);
236 ssl->proxyPort = &port;
237 acceptor._accept = ssl;
238 #if TS_USE_QUIC == 1
239 } else if (port.isQUIC()) {
240 QUICNextProtocolAccept *quic = new QUICNextProtocolAccept();
241
242 quic->enableProtocols(port.m_session_protocol_preference);
243
244 // HTTP/0.9 over QUIC draft-27 (for interop only, will be removed)
245 quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC_D27, new Http3SessionAccept(accept_opt));
246
247 // HTTP/3 draft-27
248 quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3_D27, new Http3SessionAccept(accept_opt));
249
250 // HTTP/0.9 over QUIC (for interop only, will be removed)
251 quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_QUIC, new Http3SessionAccept(accept_opt));
252
253 // HTTP/3
254 quic->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_3, new Http3SessionAccept(accept_opt));
255
256 quic->proxyPort = &port;
257 acceptor._accept = quic;
258 #endif
259 } else {
260 acceptor._accept = probe;
261 }
262 }
263
264 /// Do all pre-thread initialization / setup.
265 void
prep_HttpProxyServer()266 prep_HttpProxyServer()
267 {
268 httpSessionManager.init();
269 }
270
271 /** Set up all the accepts and sockets.
272 */
273 void
init_accept_HttpProxyServer(int n_accept_threads)274 init_accept_HttpProxyServer(int n_accept_threads)
275 {
276 HttpProxyPort::Group &proxy_ports = HttpProxyPort::global();
277
278 init_reverse_proxy();
279 http_pages_init();
280
281 #ifdef USE_HTTP_DEBUG_LISTS
282 ink_mutex_init(&debug_sm_list_mutex);
283 ink_mutex_init(&debug_cs_list_mutex);
284 #endif
285
286 // Used to give plugins the ability to create http requests
287 // The equivalent of the connecting to localhost on the proxy
288 // port but without going through the operating system
289 //
290 if (plugin_http_accept == nullptr) {
291 plugin_http_accept = new HttpSessionAccept();
292 }
293
294 // Same as plugin_http_accept except outbound transparent.
295 if (!plugin_http_transparent_accept) {
296 HttpSessionAccept::Options ha_opt;
297 ha_opt.setOutboundTransparent(true);
298 plugin_http_transparent_accept = new HttpSessionAccept(ha_opt);
299 }
300
301 if (!ssl_plugin_mutex) {
302 ssl_plugin_mutex = new_ProxyMutex();
303 }
304
305 // Do the configuration defined ports.
306 // Assign temporary empty objects of proxy ports size
307 HttpProxyAcceptors.assign(proxy_ports.size(), HttpProxyAcceptor());
308 for (int i = 0, n = proxy_ports.size(); i < n; ++i) {
309 MakeHttpProxyAcceptor(HttpProxyAcceptors.at(i), proxy_ports[i], n_accept_threads);
310 }
311 }
312
313 /** Increment the counter to keep track of how many et_net threads
314 * we have started. This function is scheduled at the start of each
315 * et_net thread using schedule_spawn(). We also check immediately
316 * after incrementing the counter to see whether all of the et_net
317 * threads have started such that we can notify main() to call
318 * start_HttpProxyServer().
319 */
320 void
init_HttpProxyServer()321 init_HttpProxyServer()
322 {
323 if (eventProcessor.has_tg_started(ET_NET)) {
324 std::unique_lock<std::mutex> lock(proxyServerMutex);
325 et_net_threads_ready = true;
326 lock.unlock();
327 proxyServerCheck.notify_one();
328 }
329
330 #if TS_USE_QUIC == 1
331 if (eventProcessor.has_tg_started(ET_UDP)) {
332 std::unique_lock<std::mutex> lock(etUdpMutex);
333 et_udp_threads_ready = true;
334 lock.unlock();
335 etUdpCheck.notify_one();
336 }
337 #endif
338 }
339
340 void
start_HttpProxyServer()341 start_HttpProxyServer()
342 {
343 static bool called_once = false;
344 HttpProxyPort::Group &proxy_ports = HttpProxyPort::global();
345
346 ///////////////////////////////////
347 // start accepting connections //
348 ///////////////////////////////////
349
350 ink_assert(!called_once);
351 ink_assert(proxy_ports.size() == HttpProxyAcceptors.size());
352
353 for (int i = 0, n = proxy_ports.size(); i < n; ++i) {
354 HttpProxyAcceptor &acceptor = HttpProxyAcceptors[i];
355 HttpProxyPort &port = proxy_ports[i];
356 if (port.isSSL()) {
357 if (nullptr == sslNetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) {
358 return;
359 }
360 #if TS_USE_QUIC == 1
361 } else if (port.isQUIC()) {
362 if (nullptr == quic_NetProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) {
363 return;
364 }
365 #endif
366 } else if (!port.isPlugin()) {
367 if (nullptr == netProcessor.main_accept(acceptor._accept, port.m_fd, acceptor._net_opt)) {
368 return;
369 }
370 }
371 // XXX although we make a good pretence here, I don't believe that NetProcessor::main_accept() ever actually returns
372 // NULL. It would be useful to be able to detect errors and spew them here though.
373 }
374
375 // Set up stat page for http connection count
376 statPagesManager.register_http("connection_count", register_ShowConnectionCount);
377
378 // Alert plugins that connections will be accepted.
379 APIHook *hook = lifecycle_hooks->get(TS_LIFECYCLE_PORTS_READY_HOOK);
380 while (hook) {
381 hook->invoke(TS_EVENT_LIFECYCLE_PORTS_READY, nullptr);
382 hook = hook->next();
383 }
384 }
385
386 void
stop_HttpProxyServer()387 stop_HttpProxyServer()
388 {
389 sslNetProcessor.stop_accept();
390 netProcessor.stop_accept();
391 }
392