Files: 2705426f630f6e62f738ff35da68c91824fe6bbd / sslh-select.c
11703 bytesRaw
1 | /* |
2 | sslh-select: mono-processus server |
3 | |
4 | # Copyright (C) 2007-2010 Yves Rutschle |
5 | # |
6 | # This program is free software; you can redistribute it |
7 | # and/or modify it under the terms of the GNU General Public |
8 | # License as published by the Free Software Foundation; either |
9 | # version 2 of the License, or (at your option) any later |
10 | # version. |
11 | # |
12 | # This program is distributed in the hope that it will be |
13 | # useful, but WITHOUT ANY WARRANTY; without even the implied |
14 | # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
15 | # PURPOSE. See the GNU General Public License for more |
16 | # details. |
17 | # |
18 | # The full text for the General Public License is here: |
19 | # http://www.gnu.org/licenses/gpl.html |
20 | |
21 | */ |
22 | |
23 | |
24 | |
25 | |
26 | |
27 | |
28 | const char* server_type = "sslh-select"; |
29 | |
30 | /* cnx_num_alloc is the number of connection to allocate at once (at start-up, |
31 | * and then every time we get too many simultaneous connections: e.g. start |
32 | * with 100 slots, then if we get more than 100 connections allocate another |
33 | * 100 slots, and so on). We never free up connection structures. We try to |
34 | * allocate as many structures at once as will fit in one page (which is 102 |
35 | * in sslh 1.9 on Linux on x86) |
36 | */ |
37 | static long cnx_num_alloc; |
38 | |
39 | /* Make the file descriptor non-block */ |
40 | int set_nonblock(int fd) |
41 | { |
42 | int flags; |
43 | |
44 | flags = fcntl(fd, F_GETFL); |
45 | CHECK_RES_RETURN(flags, "fcntl"); |
46 | |
47 | flags |= O_NONBLOCK; |
48 | |
49 | flags = fcntl(fd, F_SETFL, flags); |
50 | CHECK_RES_RETURN(flags, "fcntl"); |
51 | |
52 | return flags; |
53 | } |
54 | |
55 | int tidy_connection(struct connection *cnx, fd_set *fds, fd_set *fds2) |
56 | { |
57 | int i; |
58 | |
59 | for (i = 0; i < 2; i++) { |
60 | if (cnx->q[i].fd != -1) { |
61 | if (verbose) |
62 | fprintf(stderr, "closing fd %d\n", cnx->q[i].fd); |
63 | |
64 | close(cnx->q[i].fd); |
65 | FD_CLR(cnx->q[i].fd, fds); |
66 | FD_CLR(cnx->q[i].fd, fds2); |
67 | if (cnx->q[i].deferred_data) |
68 | free(cnx->q[i].deferred_data); |
69 | } |
70 | } |
71 | init_cnx(cnx); |
72 | return 0; |
73 | } |
74 | |
75 | /* if fd becomes higher than FD_SETSIZE, things won't work so well with FD_SET |
76 | * and FD_CLR. Need to drop connections if we go above that limit */ |
77 | int fd_is_in_range(int fd) { |
78 | if (fd >= FD_SETSIZE) { |
79 | log_message(LOG_ERR, "too many open file descriptor to monitor them all -- dropping connection\n"); |
80 | return 0; |
81 | } |
82 | return 1; |
83 | } |
84 | |
85 | /* Accepts a connection from the main socket and assigns it to an empty slot. |
86 | * If no slots are available, allocate another few. If that fails, drop the |
87 | * connexion */ |
88 | int accept_new_connection(int listen_socket, struct connection *cnx[], int* cnx_size) |
89 | { |
90 | int in_socket, free, i, res; |
91 | struct connection *new; |
92 | |
93 | in_socket = accept(listen_socket, 0, 0); |
94 | CHECK_RES_RETURN(in_socket, "accept"); |
95 | |
96 | if (!fd_is_in_range(in_socket)) |
97 | return -1; |
98 | |
99 | res = set_nonblock(in_socket); |
100 | if (res == -1) return -1; |
101 | |
102 | /* Find an empty slot */ |
103 | for (free = 0; (free < *cnx_size) && ((*cnx)[free].q[0].fd != -1); free++) { |
104 | /* nothing */ |
105 | } |
106 | if (free >= *cnx_size) { |
107 | if (verbose) |
108 | fprintf(stderr, "buying more slots from the slot machine.\n"); |
109 | new = realloc(*cnx, (*cnx_size + cnx_num_alloc) * sizeof((*cnx)[0])); |
110 | if (!new) { |
111 | log_message(LOG_ERR, "unable to realloc -- dropping connection\n"); |
112 | return -1; |
113 | } |
114 | *cnx = new; |
115 | *cnx_size += cnx_num_alloc; |
116 | for (i = free; i < *cnx_size; i++) { |
117 | init_cnx(&(*cnx)[i]); |
118 | } |
119 | } |
120 | (*cnx)[free].q[0].fd = in_socket; |
121 | (*cnx)[free].state = ST_PROBING; |
122 | (*cnx)[free].probe_timeout = time(NULL) + probing_timeout; |
123 | |
124 | if (verbose) |
125 | fprintf(stderr, "accepted fd %d on slot %d\n", in_socket, free); |
126 | |
127 | return in_socket; |
128 | } |
129 | |
130 | |
131 | /* Connect queue 1 of connection to SSL; returns new file descriptor */ |
132 | int connect_queue(struct connection *cnx, fd_set *fds_r, fd_set *fds_w) |
133 | { |
134 | struct queue *q = &cnx->q[1]; |
135 | |
136 | q->fd = connect_addr(cnx, cnx->q[0].fd); |
137 | if ((q->fd != -1) && fd_is_in_range(q->fd)) { |
138 | log_connection(cnx); |
139 | set_nonblock(q->fd); |
140 | flush_deferred(q); |
141 | if (q->deferred_data) { |
142 | FD_SET(q->fd, fds_w); |
143 | } else { |
144 | FD_SET(q->fd, fds_r); |
145 | } |
146 | return q->fd; |
147 | } else { |
148 | tidy_connection(cnx, fds_r, fds_w); |
149 | return -1; |
150 | } |
151 | } |
152 | |
153 | /* shovels data from active fd to the other |
154 | returns after one socket closed or operation would block |
155 | */ |
156 | void shovel(struct connection *cnx, int active_fd, |
157 | fd_set *fds_r, fd_set *fds_w) |
158 | { |
159 | struct queue *read_q, *write_q; |
160 | |
161 | read_q = &cnx->q[active_fd]; |
162 | write_q = &cnx->q[1-active_fd]; |
163 | |
164 | if (verbose) |
165 | fprintf(stderr, "activity on fd%d\n", read_q->fd); |
166 | |
167 | switch(fd2fd(write_q, read_q)) { |
168 | case -1: |
169 | case FD_CNXCLOSED: |
170 | tidy_connection(cnx, fds_r, fds_w); |
171 | break; |
172 | |
173 | case FD_STALLED: |
174 | FD_SET(write_q->fd, fds_w); |
175 | FD_CLR(read_q->fd, fds_r); |
176 | break; |
177 | |
178 | default: /* Nothing */ |
179 | break; |
180 | } |
181 | } |
182 | |
183 | /* returns true if specified fd is initialised and present in fd_set */ |
184 | int is_fd_active(int fd, fd_set* set) |
185 | { |
186 | if (fd == -1) return 0; |
187 | return FD_ISSET(fd, set); |
188 | } |
189 | |
190 | /* Main loop: the idea is as follow: |
191 | * - fds_r and fds_w contain the file descriptors to monitor in read and write |
192 | * - When a file descriptor goes off, process it: read from it, write the data |
193 | * to its corresponding pair. |
194 | * - When a file descriptor blocks when writing, remove the read fd from fds_r, |
195 | * move the data to a deferred buffer, and add the write fd to fds_w. Defered |
196 | * buffer is allocated dynamically. |
197 | * - When we can write to a file descriptor that has deferred data, we try to |
198 | * write as much as we can. Once all data is written, remove the fd from fds_w |
199 | * and add its corresponding pair to fds_r, free the buffer. |
200 | * |
201 | * That way, each pair of file descriptor (read from one, write to the other) |
202 | * is monitored either for read or for write, but never for both. |
203 | */ |
204 | void main_loop(int listen_sockets[], int num_addr_listen) |
205 | { |
206 | fd_set fds_r, fds_w; /* reference fd sets (used to init the next 2) */ |
207 | fd_set readfds, writefds; /* working read and write fd sets */ |
208 | struct timeval tv; |
209 | int max_fd, in_socket, i, j, res; |
210 | struct connection *cnx; |
211 | int num_cnx; /* Number of connections in *cnx */ |
212 | int num_probing = 0; /* Number of connections currently probing |
213 | * We use this to know if we need to time out of |
214 | * select() */ |
215 | |
216 | FD_ZERO(&fds_r); |
217 | FD_ZERO(&fds_w); |
218 | |
219 | for (i = 0; i < num_addr_listen; i++) { |
220 | FD_SET(listen_sockets[i], &fds_r); |
221 | set_nonblock(listen_sockets[i]); |
222 | } |
223 | max_fd = listen_sockets[num_addr_listen-1] + 1; |
224 | |
225 | cnx_num_alloc = getpagesize() / sizeof(struct connection); |
226 | |
227 | num_cnx = cnx_num_alloc; /* Start with a set pool of slots */ |
228 | cnx = malloc(num_cnx * sizeof(struct connection)); |
229 | for (i = 0; i < num_cnx; i++) |
230 | init_cnx(&cnx[i]); |
231 | |
232 | while (1) |
233 | { |
234 | memset(&tv, 0, sizeof(tv)); |
235 | tv.tv_sec = probing_timeout; |
236 | |
237 | memcpy(&readfds, &fds_r, sizeof(readfds)); |
238 | memcpy(&writefds, &fds_w, sizeof(writefds)); |
239 | |
240 | if (verbose) |
241 | fprintf(stderr, "selecting... max_fd=%d num_probing=%d\n", max_fd, num_probing); |
242 | res = select(max_fd, &readfds, &writefds, NULL, num_probing ? &tv : NULL); |
243 | if (res < 0) |
244 | perror("select"); |
245 | |
246 | |
247 | /* Check main socket for new connections */ |
248 | for (i = 0; i < num_addr_listen; i++) { |
249 | if (FD_ISSET(listen_sockets[i], &readfds)) { |
250 | in_socket = accept_new_connection(listen_sockets[i], &cnx, &num_cnx); |
251 | if (in_socket != -1) |
252 | num_probing++; |
253 | |
254 | if (in_socket > 0) { |
255 | FD_SET(in_socket, &fds_r); |
256 | if (in_socket >= max_fd) |
257 | max_fd = in_socket + 1;; |
258 | } |
259 | FD_CLR(listen_sockets[i], &readfds); |
260 | } |
261 | } |
262 | |
263 | /* Check all sockets for write activity */ |
264 | for (i = 0; i < num_cnx; i++) { |
265 | if (cnx[i].q[0].fd != -1) { |
266 | for (j = 0; j < 2; j++) { |
267 | if (is_fd_active(cnx[i].q[j].fd, &writefds)) { |
268 | res = flush_deferred(&cnx[i].q[j]); |
269 | if ((res == -1) && ((errno == EPIPE) || (errno == ECONNRESET))) { |
270 | if (cnx[i].state == ST_PROBING) num_probing--; |
271 | tidy_connection(&cnx[i], &fds_r, &fds_w); |
272 | if (verbose) |
273 | fprintf(stderr, "closed slot %d\n", i); |
274 | } else { |
275 | /* If no deferred data is left, stop monitoring the fd |
276 | * for write, and restart monitoring the other one for reads*/ |
277 | if (!cnx[i].q[j].deferred_data_size) { |
278 | FD_CLR(cnx[i].q[j].fd, &fds_w); |
279 | FD_SET(cnx[i].q[1-j].fd, &fds_r); |
280 | } |
281 | } |
282 | } |
283 | } |
284 | } |
285 | } |
286 | |
287 | /* Check all sockets for read activity */ |
288 | for (i = 0; i < num_cnx; i++) { |
289 | for (j = 0; j < 2; j++) { |
290 | if (is_fd_active(cnx[i].q[j].fd, &readfds) || |
291 | ((cnx[i].state == ST_PROBING) && (cnx[i].probe_timeout < time(NULL)))) { |
292 | if (verbose) |
293 | fprintf(stderr, "processing fd%d slot %d\n", j, i); |
294 | |
295 | switch (cnx[i].state) { |
296 | |
297 | case ST_PROBING: |
298 | if (j == 1) { |
299 | fprintf(stderr, "Activity on fd2 while probing, impossible\n"); |
300 | dump_connection(&cnx[i]); |
301 | exit(1); |
302 | } |
303 | |
304 | /* If timed out it's SSH, otherwise the client sent |
305 | * data so probe the protocol */ |
306 | if ((cnx[i].probe_timeout < time(NULL))) { |
307 | cnx[i].proto = timeout_protocol(); |
308 | } else { |
309 | res = probe_client_protocol(&cnx[i]); |
310 | if (res == PROBE_AGAIN) |
311 | continue; |
312 | } |
313 | |
314 | num_probing--; |
315 | cnx[i].state = ST_SHOVELING; |
316 | |
317 | /* libwrap check if required for this protocol */ |
318 | if (cnx[i].proto->service && |
319 | check_access_rights(in_socket, cnx[i].proto->service)) { |
320 | tidy_connection(&cnx[i], &fds_r, &fds_w); |
321 | res = -1; |
322 | } else { |
323 | res = connect_queue(&cnx[i], &fds_r, &fds_w); |
324 | } |
325 | |
326 | if (res >= max_fd) |
327 | max_fd = res + 1;; |
328 | break; |
329 | |
330 | case ST_SHOVELING: |
331 | shovel(&cnx[i], j, &fds_r, &fds_w); |
332 | break; |
333 | |
334 | default: /* illegal */ |
335 | log_message(LOG_ERR, "Illegal connection state %d\n", cnx[i].state); |
336 | exit(1); |
337 | } |
338 | } |
339 | } |
340 | } |
341 | } |
342 | } |
343 | |
344 | |
345 | void start_shoveler(int listen_socket) { |
346 | fprintf(stderr, "inetd mode is not supported in select mode\n"); |
347 | exit(1); |
348 | } |
349 | |
350 | |
351 | /* The actual main is in common.c: it's the same for both version of |
352 | * the server |
353 | */ |
354 | |
355 | |
356 |
Built with git-ssb-web