1 /*-
2 * SSLsplit - transparent SSL/TLS interception
3 * https://www.roe.ch/SSLsplit
4 *
5 * Copyright (c) 2009-2019, Daniel Roethlisberger <daniel@roe.ch>.
6 * Copyright (c) 2017-2021, Soner Tari <sonertari@gmail.com>.
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions are met:
11 * 1. Redistributions of source code must retain the above copyright notice,
12 * this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS ``AS IS''
18 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 * POSSIBILITY OF SUCH DAMAGE.
28 */
29
30 #include "pxythrmgr.h"
31
32 #include "sys.h"
33 #include "log.h"
34 #include "pxyconn.h"
35
36 #include <string.h>
37 #include <event2/bufferevent.h>
38
39 /*
40 * Proxy thread manager: manages the connection handling worker threads
41 * and the per-thread resources (i.e. event bases). The load is shared
42 * across num_cpu * 2 connection handling threads, using the number of
43 * currently assigned connections as the sole metric.
44 */
45
46 /*
47 * Create new thread manager but do not start any threads yet.
48 * This gets called before forking to background.
49 */
50 pxy_thrmgr_ctx_t *
pxy_thrmgr_new(global_t * global)51 pxy_thrmgr_new(global_t *global)
52 {
53 pxy_thrmgr_ctx_t *ctx;
54
55 if (!(ctx = malloc(sizeof(pxy_thrmgr_ctx_t))))
56 return NULL;
57 memset(ctx, 0, sizeof(pxy_thrmgr_ctx_t));
58
59 ctx->global = global;
60 ctx->num_thr = 2 * sys_get_cpu_cores();
61 return ctx;
62 }
63
64 /*
65 * Start the thread manager and associated threads.
66 * This must be called after forking.
67 *
68 * Returns -1 on failure, 0 on success.
69 */
70 int
pxy_thrmgr_run(pxy_thrmgr_ctx_t * ctx)71 pxy_thrmgr_run(pxy_thrmgr_ctx_t *ctx)
72 {
73 int i = -1, dns = 0;
74
75 dns = global_has_dns_spec(ctx->global);
76
77 if (!(ctx->thr = malloc(ctx->num_thr * sizeof(pxy_thr_ctx_t*)))) {
78 log_dbg_printf("Failed to allocate memory\n");
79 goto leave;
80 }
81 memset(ctx->thr, 0, ctx->num_thr * sizeof(pxy_thr_ctx_t*));
82
83 for (i = 0; i < ctx->num_thr; i++) {
84 if (!(ctx->thr[i] = malloc(sizeof(pxy_thr_ctx_t)))) {
85 log_dbg_printf("Failed to allocate memory\n");
86 goto leave;
87 }
88 memset(ctx->thr[i], 0, sizeof(pxy_thr_ctx_t));
89 ctx->thr[i]->evbase = event_base_new();
90 if (!ctx->thr[i]->evbase) {
91 log_dbg_printf("Failed to create evbase %d\n", i);
92 goto leave;
93 }
94 if (dns) {
95 /* only create dns base if we actually need it later */
96 ctx->thr[i]->dnsbase = evdns_base_new(ctx->thr[i]->evbase, 1);
97 if (!ctx->thr[i]->dnsbase) {
98 log_dbg_printf("Failed to create dnsbase %d\n", i);
99 goto leave;
100 }
101 }
102 ctx->thr[i]->load = 0;
103 ctx->thr[i]->running = 0;
104 ctx->thr[i]->conns = NULL;
105 ctx->thr[i]->id = i;
106 ctx->thr[i]->timeout_count = 0;
107 ctx->thr[i]->thrmgr = ctx;
108
109 #ifndef WITHOUT_USERAUTH
110 if ((ctx->global->conn_opts->user_auth || global_has_userauth_spec(ctx->global)) &&
111 sqlite3_prepare_v2(ctx->global->userdb, "SELECT user,ether,atime,desc FROM users WHERE ip = ?1", 100, &ctx->thr[i]->get_user, NULL)) {
112 log_err_level_printf(LOG_CRIT, "Error preparing get_user sql stmt: %s\n", sqlite3_errmsg(ctx->global->userdb));
113 goto leave;
114 }
115 #endif /* !WITHOUT_USERAUTH */
116 }
117
118 log_dbg_printf("Initialized %d connection handling threads\n", ctx->num_thr);
119
120 for (i = 0; i < ctx->num_thr; i++) {
121 if (pthread_create(&ctx->thr[i]->thr, NULL, pxy_thr, ctx->thr[i]))
122 goto leave_thr;
123 while (!ctx->thr[i]->running) {
124 sched_yield();
125 }
126 }
127
128 log_dbg_printf("Started %d connection handling threads\n", ctx->num_thr);
129 return 0;
130
131 leave_thr:
132 i--;
133 while (i >= 0) {
134 pthread_cancel(ctx->thr[i]->thr);
135 pthread_join(ctx->thr[i]->thr, NULL);
136 i--;
137 }
138 i = ctx->num_thr - 1;
139
140 leave:
141 while (i >= 0) {
142 if (ctx->thr[i]) {
143 if (ctx->thr[i]->dnsbase) {
144 evdns_base_free(ctx->thr[i]->dnsbase, 0);
145 }
146 if (ctx->thr[i]->evbase) {
147 event_base_free(ctx->thr[i]->evbase);
148 }
149 #ifndef WITHOUT_USERAUTH
150 if (ctx->global->userdb) {
151 // sqlite3.h: "Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op."
152 sqlite3_finalize(ctx->thr[i]->get_user);
153 }
154 #endif /* !WITHOUT_USERAUTH */
155 free(ctx->thr[i]);
156 }
157 i--;
158 }
159 if (ctx->thr) {
160 free(ctx->thr);
161 ctx->thr = NULL;
162 }
163 return -1;
164 }
165
166 /*
167 * Destroy the event manager and stop all threads.
168 */
169 void
pxy_thrmgr_free(pxy_thrmgr_ctx_t * ctx)170 pxy_thrmgr_free(pxy_thrmgr_ctx_t *ctx)
171 {
172 if (ctx->thr) {
173 for (int i = 0; i < ctx->num_thr; i++) {
174 event_base_loopbreak(ctx->thr[i]->evbase);
175 sched_yield();
176 }
177 for (int i = 0; i < ctx->num_thr; i++) {
178 pthread_join(ctx->thr[i]->thr, NULL);
179 }
180 for (int i = 0; i < ctx->num_thr; i++) {
181 if (ctx->thr[i]->dnsbase) {
182 evdns_base_free(ctx->thr[i]->dnsbase, 0);
183 }
184 if (ctx->thr[i]->evbase) {
185 event_base_free(ctx->thr[i]->evbase);
186 }
187 #ifndef WITHOUT_USERAUTH
188 if (ctx->global->userdb) {
189 // sqlite3.h: "Invoking sqlite3_finalize() on a NULL pointer is a harmless no-op."
190 sqlite3_finalize(ctx->thr[i]->get_user);
191 }
192 #endif /* !WITHOUT_USERAUTH */
193 free(ctx->thr[i]);
194 }
195 free(ctx->thr);
196 }
197 free(ctx);
198 }
199
200 /*
201 * Assign a new connection to a thread. Chooses the thread with the fewest
202 * currently active connections, returns the appropriate event bases.
203 * No need to be so accurate about balancing thread loads,
204 * so does not use mutexes, thread or thrmgr level.
205 * @todo Check if read accesses to thr load can cause any multithreading issues.
206 * Returns the index of the chosen thread.
207 * This function cannot fail.
208 */
209 void
pxy_thrmgr_assign_thr(pxy_conn_ctx_t * ctx)210 pxy_thrmgr_assign_thr(pxy_conn_ctx_t *ctx)
211 {
212 log_finest("ENTER");
213
214 pxy_thrmgr_ctx_t *tmctx = ctx->thrmgr;
215 size_t minload = tmctx->thr[0]->load;
216
217 #ifdef DEBUG_THREAD
218 log_dbg_printf("===> Proxy connection handler thread status:\nthr[0]: %zu\n", minload);
219 #endif /* DEBUG_THREAD */
220
221 int thrid = 0;
222 for (int i = 1; i < tmctx->num_thr; i++) {
223 size_t thrload = tmctx->thr[i]->load;
224 if (minload > thrload) {
225 minload = thrload;
226 thrid = i;
227 }
228
229 #ifdef DEBUG_THREAD
230 log_dbg_printf("thr[%d]: %zu\n", i, thrload);
231 #endif /* DEBUG_THREAD */
232 }
233
234 ctx->thr = tmctx->thr[thrid];
235
236 #ifdef DEBUG_THREAD
237 log_dbg_printf("thrid: %d\n", thrid);
238 #endif /* DEBUG_THREAD */
239 }
240
241 /* vim: set noet ft=c: */
242