/src/haproxy/src/proto_rhttp.c
Line | Count | Source |
1 | | #include <stdio.h> |
2 | | #include <string.h> |
3 | | |
4 | | #include <haproxy/api.h> |
5 | | #include <haproxy/connection.h> |
6 | | #include <haproxy/errors.h> |
7 | | #include <haproxy/intops.h> |
8 | | #include <haproxy/list.h> |
9 | | #include <haproxy/listener.h> |
10 | | #include <haproxy/log.h> |
11 | | #include <haproxy/proto_tcp.h> |
12 | | #include <haproxy/protocol.h> |
13 | | #include <haproxy/proxy.h> |
14 | | #include <haproxy/sample.h> |
15 | | #include <haproxy/server.h> |
16 | | #include <haproxy/session.h> |
17 | | #include <haproxy/sock.h> |
18 | | #include <haproxy/ssl_sock.h> |
19 | | #include <haproxy/task.h> |
20 | | |
21 | | #include <haproxy/proto_rhttp.h> |
22 | | |
23 | | struct proto_fam proto_fam_rhttp = { |
24 | | .name = "rhttp", |
25 | | .sock_domain = AF_INET, |
26 | | .sock_family = AF_CUST_RHTTP_SRV, |
27 | | .real_family = AF_CUST_RHTTP_SRV, |
28 | | .bind = rhttp_bind_receiver, |
29 | | }; |
30 | | |
31 | | struct protocol proto_rhttp = { |
32 | | .name = "rev", |
33 | | |
34 | | /* connection layer (no outgoing connection) */ |
35 | | .listen = rhttp_bind_listener, |
36 | | .enable = rhttp_enable_listener, |
37 | | .disable = rhttp_disable_listener, |
38 | | .suspend = rhttp_suspend_listener, |
39 | | .add = default_add_listener, |
40 | | .unbind = rhttp_unbind_receiver, |
41 | | .resume = default_resume_listener, |
42 | | .accept_conn = rhttp_accept_conn, |
43 | | .bind_tid_prep = rhttp_bind_tid_prep, |
44 | | |
45 | | /* address family */ |
46 | | .fam = &proto_fam_rhttp, |
47 | | |
48 | | /* socket layer */ |
49 | | .proto_type = PROTO_TYPE_STREAM, |
50 | | .sock_type = SOCK_STREAM, |
51 | | .sock_prot = IPPROTO_TCP, |
52 | | .rx_listening = rhttp_accepting_conn, |
53 | | .receivers = LIST_HEAD_INIT(proto_rhttp.receivers), |
54 | | }; |
55 | | |
56 | | static struct connection *new_reverse_conn(struct listener *l, struct server *srv) |
57 | 0 | { |
58 | 0 | struct connection *conn = conn_new(srv); |
59 | 0 | struct sockaddr_storage *bind_addr = NULL; |
60 | 0 | struct session *sess = NULL; |
61 | 0 | if (!conn) |
62 | 0 | goto err; |
63 | | |
64 | 0 | HA_ATOMIC_INC(&th_ctx->nb_rhttp_conns); |
65 | | |
66 | | /* session origin is only set after reversal. This ensures fetches |
67 | | * will be functional only after reversal, in particular src/dst. |
68 | | */ |
69 | 0 | sess = session_new(l->bind_conf->frontend, l, NULL); |
70 | 0 | if (!sess) |
71 | 0 | goto err; |
72 | | |
73 | 0 | conn_set_owner(conn, sess, conn_session_free); |
74 | 0 | conn_set_reverse(conn, &l->obj_type); |
75 | |
|
76 | 0 | if (alloc_bind_address(&bind_addr, srv, srv->proxy, NULL) != SRV_STATUS_OK) |
77 | 0 | goto err; |
78 | 0 | conn->src = bind_addr; |
79 | |
|
80 | 0 | sockaddr_alloc(&conn->dst, 0, 0); |
81 | 0 | if (!conn->dst) |
82 | 0 | goto err; |
83 | 0 | *conn->dst = srv->addr; |
84 | 0 | set_host_port(conn->dst, srv->svc_port); |
85 | |
|
86 | 0 | conn->send_proxy_ofs = 0; |
87 | 0 | if (srv->pp_opts & SRV_PP_ENABLED) { |
88 | 0 | conn->flags |= CO_FL_SEND_PROXY; |
89 | 0 | conn->send_proxy_ofs = 1; /* must compute size */ |
90 | 0 | } |
91 | | |
92 | | /* TODO support SOCKS4 */ |
93 | |
|
94 | 0 | if (conn_prepare(conn, protocol_lookup(conn->dst->ss_family, PROTO_TYPE_STREAM, 0), srv->xprt)) |
95 | 0 | goto err; |
96 | | |
97 | 0 | if (conn->ctrl->connect(conn, 0) != SF_ERR_NONE) |
98 | 0 | goto err; |
99 | | |
100 | | #ifdef USE_OPENSSL |
101 | | if (conn_is_ssl(conn) && srv->ssl_ctx.sni) { |
102 | | struct sample *sni_smp = NULL; |
103 | | /* TODO remove NULL session which can cause crash depending on the SNI sample expr used. */ |
104 | | sni_smp = sample_fetch_as_type(srv->proxy, sess, NULL, |
105 | | SMP_OPT_DIR_REQ | SMP_OPT_FINAL, |
106 | | srv->ssl_ctx.sni, SMP_T_STR); |
107 | | if (smp_make_safe(sni_smp)) |
108 | | ssl_sock_set_servername(conn, sni_smp->data.u.str.area); |
109 | | } |
110 | | #endif /* USE_OPENSSL */ |
111 | | |
112 | | /* The CO_FL_SEND_PROXY flag may have been set by the connect method, |
113 | | * if so, add our handshake pseudo-XPRT now. |
114 | | */ |
115 | 0 | if (conn->flags & CO_FL_HANDSHAKE) { |
116 | 0 | if (xprt_add_hs(conn) < 0) |
117 | 0 | goto err; |
118 | 0 | } |
119 | | |
120 | 0 | if (conn_xprt_start(conn) < 0) |
121 | 0 | goto err; |
122 | | |
123 | 0 | if (!srv->use_ssl || |
124 | 0 | (!srv->ssl_ctx.alpn_str && !srv->ssl_ctx.npn_str) || |
125 | 0 | srv->mux_proto) { |
126 | 0 | if (conn_install_mux_be(conn, NULL, sess, NULL) < 0) |
127 | 0 | goto err; |
128 | 0 | } |
129 | | |
130 | 0 | return conn; |
131 | | |
132 | 0 | err: |
133 | 0 | if (l->rx.rhttp.state != LI_PRECONN_ST_ERR) { |
134 | 0 | send_log(l->bind_conf->frontend, LOG_ERR, |
135 | 0 | "preconnect %s::%s: Error on conn allocation.\n", |
136 | 0 | l->bind_conf->frontend->id, l->bind_conf->rhttp_srvname); |
137 | 0 | l->rx.rhttp.state = LI_PRECONN_ST_ERR; |
138 | 0 | } |
139 | | |
140 | | /* No need to free session as conn.destroy_cb will take care of it. */ |
141 | 0 | if (conn) { |
142 | 0 | conn_stop_tracking(conn); |
143 | 0 | conn_xprt_shutw(conn); |
144 | 0 | conn_xprt_close(conn); |
145 | 0 | conn_sock_shutw(conn, 0); |
146 | 0 | conn_ctrl_close(conn); |
147 | |
|
148 | 0 | if (conn->destroy_cb) |
149 | 0 | conn->destroy_cb(conn); |
150 | | |
151 | | /* Mark connection as non-reversable. This prevents conn_free() |
152 | | * to reschedule rhttp task on freeing a preconnect connection. |
153 | | */ |
154 | 0 | conn->reverse.target = NULL; |
155 | 0 | conn_free(conn); |
156 | 0 | } |
157 | |
|
158 | 0 | return NULL; |
159 | 0 | } |
160 | | |
161 | | /* Report that a connection used for preconnect on listener <l> is freed before |
162 | | * reversal is completed. This is used to cleanup any reference to the |
163 | | * connection and rearm a new preconnect attempt. |
164 | | */ |
165 | | void rhttp_notify_preconn_err(struct listener *l) |
166 | 0 | { |
167 | | /* Receiver must reference a reverse connection as pending. */ |
168 | 0 | BUG_ON(!l->rx.rhttp.pend_conn); |
169 | | |
170 | | /* Remove reference to the freed connection. */ |
171 | 0 | l->rx.rhttp.pend_conn = NULL; |
172 | |
|
173 | 0 | if (l->rx.rhttp.state != LI_PRECONN_ST_ERR) { |
174 | 0 | send_log(l->bind_conf->frontend, LOG_ERR, |
175 | 0 | "preconnect %s::%s: Error encountered.\n", |
176 | 0 | l->bind_conf->frontend->id, l->bind_conf->rhttp_srvname); |
177 | 0 | l->rx.rhttp.state = LI_PRECONN_ST_ERR; |
178 | 0 | } |
179 | | |
180 | | /* Rearm a new preconnect attempt. */ |
181 | 0 | l->rx.rhttp.task->expire = MS_TO_TICKS(now_ms + 1000); |
182 | 0 | task_queue(l->rx.rhttp.task); |
183 | 0 | } |
184 | | |
185 | | /* Lookup over listener <l> threads for their current count of active reverse |
186 | | * HTTP connections. Returns the less loaded thread ID. |
187 | | */ |
188 | | static unsigned int select_thread(struct listener *l) |
189 | 0 | { |
190 | 0 | unsigned long mask = l->rx.bind_thread & _HA_ATOMIC_LOAD(&tg->threads_enabled); |
191 | 0 | unsigned int load_min = HA_ATOMIC_LOAD(&th_ctx->nb_rhttp_conns); |
192 | 0 | unsigned int load_thr; |
193 | 0 | unsigned int ret = tid; |
194 | 0 | int i; |
195 | | |
196 | | /* Returns current tid if listener runs on one thread only. */ |
197 | 0 | if (!atleast2(mask)) |
198 | 0 | goto end; |
199 | | |
200 | | /* Loop over all threads and return the less loaded one. This needs to |
201 | | * be just an approximation so it's not important if the selected |
202 | | * thread load has varied since its selection. |
203 | | */ |
204 | | |
205 | 0 | for (i = tg->base; mask; mask >>= 1, i++) { |
206 | 0 | if (!(mask & 0x1)) |
207 | 0 | continue; |
208 | | |
209 | 0 | load_thr = HA_ATOMIC_LOAD(&ha_thread_ctx[i].nb_rhttp_conns); |
210 | 0 | if (load_min > load_thr) { |
211 | 0 | ret = i; |
212 | 0 | load_min = load_thr; |
213 | 0 | } |
214 | 0 | } |
215 | |
|
216 | 0 | end: |
217 | 0 | return ret; |
218 | 0 | } |
219 | | |
220 | | /* Detach <task> from its thread and assign it to <new_tid> thread. The task is |
221 | | * queued to be woken up on the new thread. |
222 | | */ |
223 | | static void task_migrate(struct task *task, uint new_tid) |
224 | 0 | { |
225 | 0 | task_unlink_wq(task); |
226 | 0 | task->expire = TICK_ETERNITY; |
227 | 0 | task_set_thread(task, new_tid); |
228 | 0 | task_wakeup(task, TASK_WOKEN_MSG); |
229 | 0 | } |
230 | | |
231 | | struct task *rhttp_process(struct task *task, void *ctx, unsigned int state) |
232 | 0 | { |
233 | 0 | struct listener *l = ctx; |
234 | 0 | struct connection *conn = l->rx.rhttp.pend_conn; |
235 | |
|
236 | 0 | if (conn) { |
237 | | /* Either connection is on error ot the connect timeout fired. */ |
238 | 0 | if (conn->flags & CO_FL_ERROR || tick_is_expired(task->expire, now_ms)) { |
239 | | /* If mux already instantiated, let it release the |
240 | | * connection along with its context. Else do cleanup |
241 | | * directly. |
242 | | */ |
243 | 0 | if (conn->mux && conn->mux->destroy) { |
244 | 0 | CALL_MUX_NO_RET(conn->mux, destroy(conn->ctx)); |
245 | 0 | } |
246 | 0 | else { |
247 | 0 | conn_stop_tracking(conn); |
248 | 0 | conn_xprt_shutw(conn); |
249 | 0 | conn_xprt_close(conn); |
250 | 0 | conn_sock_shutw(conn, 0); |
251 | 0 | conn_ctrl_close(conn); |
252 | |
|
253 | 0 | if (conn->destroy_cb) |
254 | 0 | conn->destroy_cb(conn); |
255 | 0 | conn_free(conn); |
256 | 0 | } |
257 | | |
258 | | /* conn_free() must report preconnect failure using rhttp_notify_preconn_err(). */ |
259 | 0 | BUG_ON(l->rx.rhttp.pend_conn); |
260 | |
|
261 | 0 | l->rx.rhttp.task->expire = TICKS_TO_MS(now_ms); |
262 | 0 | } |
263 | 0 | else { |
264 | | /* Spurious receiver task woken up despite pend_conn not ready/on error. */ |
265 | 0 | BUG_ON(!(conn->flags & CO_FL_ACT_REVERSING)); |
266 | | |
267 | | /* A connection is ready to be accepted. */ |
268 | 0 | listener_accept(l); |
269 | 0 | l->rx.rhttp.task->expire = TICK_ETERNITY; |
270 | 0 | } |
271 | 0 | } |
272 | 0 | else { |
273 | 0 | struct server *srv = l->rx.rhttp.srv; |
274 | |
|
275 | 0 | if ((state & TASK_WOKEN_ANY) != TASK_WOKEN_MSG) { |
276 | 0 | unsigned int new_tid = select_thread(l); |
277 | 0 | if (new_tid != tid) { |
278 | 0 | task_migrate(l->rx.rhttp.task, new_tid); |
279 | 0 | return task; |
280 | 0 | } |
281 | 0 | } |
282 | | |
283 | | /* No pending reverse connection, prepare a new one. Store it in the |
284 | | * listener and return NULL. Connection will be returned later after |
285 | | * reversal is completed. |
286 | | */ |
287 | 0 | conn = new_reverse_conn(l, srv); |
288 | 0 | l->rx.rhttp.pend_conn = conn; |
289 | | |
290 | | /* On success task will be woken up by H2 mux after reversal. */ |
291 | 0 | l->rx.rhttp.task->expire = conn && tick_isset(srv->proxy->timeout.connect) ? |
292 | 0 | tick_add_ifset(now_ms, srv->proxy->timeout.connect) : |
293 | 0 | MS_TO_TICKS(now_ms + 1000); |
294 | 0 | } |
295 | | |
296 | 0 | return task; |
297 | 0 | } |
298 | | |
299 | | int rhttp_bind_receiver(struct receiver *rx, char **errmsg) |
300 | 0 | { |
301 | 0 | rx->flags |= RX_F_BOUND; |
302 | 0 | return ERR_NONE; |
303 | 0 | } |
304 | | |
305 | | int rhttp_bind_listener(struct listener *listener, char *errmsg, int errlen) |
306 | 0 | { |
307 | 0 | struct task *task; |
308 | 0 | struct proxy *be; |
309 | 0 | struct server *srv; |
310 | 0 | struct ist be_name, sv_name; |
311 | 0 | char *name = NULL; |
312 | |
|
313 | 0 | unsigned long mask; |
314 | 0 | uint task_tid; |
315 | |
|
316 | 0 | if (listener->state != LI_ASSIGNED) |
317 | 0 | return ERR_NONE; /* already bound */ |
318 | | |
319 | | /* Retrieve the first thread usable for this listener. */ |
320 | 0 | mask = listener->rx.bind_thread & _HA_ATOMIC_LOAD(&tg->threads_enabled); |
321 | 0 | task_tid = my_ffsl(mask) - 1 + ha_tgroup_info[listener->rx.bind_tgroup].base; |
322 | 0 | if (!(task = task_new_on(task_tid))) { |
323 | 0 | snprintf(errmsg, errlen, "Out of memory."); |
324 | 0 | goto err; |
325 | 0 | } |
326 | | |
327 | 0 | task->process = rhttp_process; |
328 | 0 | task->context = listener; |
329 | 0 | listener->rx.rhttp.task = task; |
330 | 0 | listener->rx.rhttp.state = LI_PRECONN_ST_STOP; |
331 | | |
332 | | /* Set maxconn which is defined via the special kw nbconn for reverse |
333 | | * connect. Use a default value of 1 if not set. This guarantees that |
334 | | * listener will be automatically re-enable each time it fell back below |
335 | | * it due to a connection error. |
336 | | */ |
337 | 0 | listener->bind_conf->maxconn = listener->bind_conf->rhttp_nbconn; |
338 | 0 | if (!listener->bind_conf->maxconn) |
339 | 0 | listener->bind_conf->maxconn = 1; |
340 | |
|
341 | 0 | name = strdup(listener->bind_conf->rhttp_srvname); |
342 | 0 | if (!name) { |
343 | 0 | snprintf(errmsg, errlen, "Out of memory."); |
344 | 0 | goto err; |
345 | 0 | } |
346 | | |
347 | 0 | sv_name = ist(name); |
348 | 0 | be_name = istsplit(&sv_name, '/'); |
349 | 0 | if (!istlen(sv_name)) { |
350 | 0 | snprintf(errmsg, errlen, "Invalid server name: '%s'.", name); |
351 | 0 | goto err; |
352 | 0 | } |
353 | | |
354 | 0 | if (!(be = proxy_be_by_name(ist0(be_name)))) { |
355 | 0 | snprintf(errmsg, errlen, "No such backend: '%s'.", name); |
356 | 0 | goto err; |
357 | 0 | } |
358 | 0 | if (!(srv = server_find(be, ist0(sv_name)))) { |
359 | 0 | snprintf(errmsg, errlen, "No such server: '%s/%s'.", ist0(be_name), ist0(sv_name)); |
360 | 0 | goto err; |
361 | 0 | } |
362 | | |
363 | 0 | if (srv->flags & SRV_F_RHTTP) { |
364 | 0 | snprintf(errmsg, errlen, "Cannot use reverse HTTP server '%s/%s' as target to a reverse bind.", ist0(be_name), ist0(sv_name)); |
365 | 0 | goto err; |
366 | 0 | } |
367 | | |
368 | 0 | if (srv_is_transparent(srv)) { |
369 | 0 | snprintf(errmsg, errlen, "Cannot use transparent server '%s/%s' as target to a reverse bind.", ist0(be_name), ist0(sv_name)); |
370 | 0 | goto err; |
371 | 0 | } |
372 | | |
373 | | /* Check that server uses HTTP/2 either with proto or ALPN. */ |
374 | 0 | if ((!srv->mux_proto || !isteqi(srv->mux_proto->mux_proto, ist("h2"))) && |
375 | 0 | (!srv->use_ssl || !isteqi(ist(srv->ssl_ctx.alpn_str), ist("\x02h2")))) { |
376 | 0 | snprintf(errmsg, errlen, "Cannot reverse connect with server '%s/%s' unless HTTP/2 is activated on it with either proto or alpn keyword.", name, ist0(sv_name)); |
377 | 0 | goto err; |
378 | 0 | } |
379 | | |
380 | | /* Prevent dynamic source address settings. */ |
381 | 0 | if (((srv->conn_src.opts & CO_SRC_TPROXY_MASK) && |
382 | 0 | (srv->conn_src.opts & CO_SRC_TPROXY_MASK) != CO_SRC_TPROXY_ADDR) || |
383 | 0 | ((srv->proxy->conn_src.opts & CO_SRC_TPROXY_MASK) && |
384 | 0 | (srv->proxy->conn_src.opts & CO_SRC_TPROXY_MASK) != CO_SRC_TPROXY_ADDR)) { |
385 | 0 | snprintf(errmsg, errlen, "Cannot reverse connect with server '%s/%s' which uses dynamic source address setting.", name, ist0(sv_name)); |
386 | 0 | goto err; |
387 | 0 | } |
388 | | |
389 | 0 | ha_free(&name); |
390 | |
|
391 | 0 | listener->rx.rhttp.srv = srv; |
392 | 0 | listener_set_state(listener, LI_LISTEN); |
393 | |
|
394 | 0 | return ERR_NONE; |
395 | | |
396 | 0 | err: |
397 | 0 | ha_free(&name); |
398 | 0 | return ERR_ALERT | ERR_FATAL; |
399 | 0 | } |
400 | | |
401 | | /* Do not support "disable frontend" for rhttp protocol. */ |
402 | | int rhttp_suspend_listener(struct listener *l) |
403 | 0 | { |
404 | 0 | send_log(l->bind_conf->frontend, LOG_ERR, "cannot disable a reverse-HTTP listener.\n"); |
405 | 0 | return -1; |
406 | 0 | } |
407 | | |
408 | | void rhttp_enable_listener(struct listener *l) |
409 | 0 | { |
410 | 0 | if (l->rx.rhttp.state < LI_PRECONN_ST_INIT) { |
411 | 0 | send_log(l->bind_conf->frontend, LOG_INFO, |
412 | 0 | "preconnect %s::%s: Initiating.\n", |
413 | 0 | l->bind_conf->frontend->id, l->bind_conf->rhttp_srvname); |
414 | 0 | l->rx.rhttp.state = LI_PRECONN_ST_INIT; |
415 | 0 | } |
416 | |
|
417 | 0 | task_wakeup(l->rx.rhttp.task, TASK_WOKEN_INIT); |
418 | 0 | } |
419 | | |
420 | | void rhttp_disable_listener(struct listener *l) |
421 | 0 | { |
422 | 0 | if (l->rx.rhttp.state < LI_PRECONN_ST_FULL) { |
423 | 0 | send_log(l->bind_conf->frontend, LOG_INFO, |
424 | 0 | "preconnect %s::%s: Running with nbconn %d reached.\n", |
425 | 0 | l->bind_conf->frontend->id, l->bind_conf->rhttp_srvname, |
426 | 0 | l->bind_conf->maxconn); |
427 | 0 | l->rx.rhttp.state = LI_PRECONN_ST_FULL; |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | | struct connection *rhttp_accept_conn(struct listener *l, int *status) |
432 | 0 | { |
433 | 0 | struct connection *conn = l->rx.rhttp.pend_conn; |
434 | |
|
435 | 0 | if (!conn) { |
436 | | /* Reverse connect listener must have an explicit maxconn set |
437 | | * to ensure it is re-enabled on connection error. |
438 | | */ |
439 | 0 | BUG_ON(!l->bind_conf->maxconn); |
440 | | |
441 | | /* Instantiate a new conn if maxconn not yet exceeded. */ |
442 | 0 | if (l->nbconn <= l->bind_conf->maxconn) { |
443 | | /* Try first if a new thread should be used for the new connection. */ |
444 | 0 | unsigned int new_tid = select_thread(l); |
445 | 0 | if (new_tid != tid) { |
446 | 0 | task_migrate(l->rx.rhttp.task, new_tid); |
447 | 0 | *status = CO_AC_DONE; |
448 | 0 | return NULL; |
449 | 0 | } |
450 | | |
451 | | /* No need to use a new thread, use the opportunity to alloc the connection right now. */ |
452 | 0 | l->rx.rhttp.pend_conn = new_reverse_conn(l, l->rx.rhttp.srv); |
453 | 0 | if (!l->rx.rhttp.pend_conn) { |
454 | 0 | *status = CO_AC_PAUSE; |
455 | 0 | return NULL; |
456 | 0 | } |
457 | 0 | } |
458 | | |
459 | 0 | *status = CO_AC_DONE; |
460 | 0 | return NULL; |
461 | 0 | } |
462 | | |
463 | | /* listener_accept() must not be called if no pending connection is not yet reversed. */ |
464 | 0 | BUG_ON(!(conn->flags & CO_FL_ACT_REVERSING)); |
465 | 0 | conn->flags &= ~CO_FL_ACT_REVERSING; |
466 | 0 | conn->flags |= CO_FL_REVERSED; |
467 | 0 | CALL_MUX_NO_RET(conn->mux, ctl(conn, MUX_CTL_REVERSE_CONN, NULL)); |
468 | |
|
469 | 0 | l->rx.rhttp.pend_conn = NULL; |
470 | 0 | *status = CO_AC_NONE; |
471 | |
|
472 | 0 | return conn; |
473 | 0 | } |
474 | | |
475 | | void rhttp_unbind_receiver(struct listener *l) |
476 | 0 | { |
477 | 0 | l->rx.flags &= ~RX_F_BOUND; |
478 | 0 | } |
479 | | |
480 | | int rhttp_bind_tid_prep(struct connection *conn, int new_tid) |
481 | 0 | { |
482 | | /* Explicitly disable connection thread migration on accept. Indeed, |
483 | | * it's unsafe to move a connection with its FD to another thread. Note |
484 | | * that active reverse task thread migration should be sufficient to |
485 | | * ensure repartition of reversed connections across listener threads. |
486 | | */ |
487 | 0 | return -1; |
488 | 0 | } |
489 | | |
490 | | int rhttp_accepting_conn(const struct receiver *rx) |
491 | 0 | { |
492 | 0 | return 1; |
493 | 0 | } |
494 | | |
495 | | INITCALL1(STG_REGISTER, protocol_register, &proto_rhttp); |
496 | | |
497 | | /* perform minimal initializations */ |
498 | | static void init_rhttp() |
499 | 0 | { |
500 | 0 | int i; |
501 | |
|
502 | 0 | for (i = 0; i < MAX_THREADS; i++) |
503 | 0 | ha_thread_ctx[i].nb_rhttp_conns = 0; |
504 | 0 | } |
505 | | |
506 | | INITCALL0(STG_PREPARE, init_rhttp); |