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