1
2 /* $Id: fpm_sockets.c,v 1.20.2.1 2008/12/13 03:21:18 anight Exp $ */
3 /* (c) 2007,2008 Andrei Nigmatulin */
4
5 #include "fpm_config.h"
6
7 #ifdef HAVE_ALLOCA_H
8 #include <alloca.h>
9 #endif
10 #include <sys/types.h>
11 #include <sys/stat.h> /* for chmod(2) */
12 #include <sys/socket.h>
13 #include <netinet/in.h>
14 #include <arpa/inet.h>
15 #include <sys/un.h>
16 #include <netdb.h>
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <errno.h>
21 #include <unistd.h>
22
23 #include "zlog.h"
24 #include "fpm_arrays.h"
25 #include "fpm_sockets.h"
26 #include "fpm_worker_pool.h"
27 #include "fpm_unix.h"
28 #include "fpm_str.h"
29 #include "fpm_env.h"
30 #include "fpm_cleanup.h"
31 #include "fpm_scoreboard.h"
32
33 struct listening_socket_s {
34 int refcount;
35 int sock;
36 int type;
37 char *key;
38 };
39
40 static struct fpm_array_s sockets_list;
41
42 enum { FPM_GET_USE_SOCKET = 1, FPM_STORE_SOCKET = 2, FPM_STORE_USE_SOCKET = 3 };
43
fpm_sockets_cleanup(int which,void * arg)44 static void fpm_sockets_cleanup(int which, void *arg) /* {{{ */
45 {
46 unsigned i;
47 char *env_value = 0;
48 int p = 0;
49 struct listening_socket_s *ls = sockets_list.data;
50
51 for (i = 0; i < sockets_list.used; i++, ls++) {
52 if (which != FPM_CLEANUP_PARENT_EXEC) {
53 close(ls->sock);
54 } else { /* on PARENT EXEC we want socket fds to be inherited through environment variable */
55 char fd[32];
56 sprintf(fd, "%d", ls->sock);
57 env_value = realloc(env_value, p + (p ? 1 : 0) + strlen(ls->key) + 1 + strlen(fd) + 1);
58 p += sprintf(env_value + p, "%s%s=%s", p ? "," : "", ls->key, fd);
59 }
60
61 if (which == FPM_CLEANUP_PARENT_EXIT_MAIN) {
62 if (ls->type == FPM_AF_UNIX) {
63 unlink(ls->key);
64 }
65 }
66 free(ls->key);
67 }
68
69 if (env_value) {
70 setenv("FPM_SOCKETS", env_value, 1);
71 free(env_value);
72 }
73
74 fpm_array_free(&sockets_list);
75 }
76 /* }}} */
77
fpm_get_in_addr(struct sockaddr * sa)78 static void *fpm_get_in_addr(struct sockaddr *sa) /* {{{ */
79 {
80 if (sa->sa_family == AF_INET) {
81 return &(((struct sockaddr_in*)sa)->sin_addr);
82 }
83
84 return &(((struct sockaddr_in6*)sa)->sin6_addr);
85 }
86 /* }}} */
87
fpm_get_in_port(struct sockaddr * sa)88 static int fpm_get_in_port(struct sockaddr *sa) /* {{{ */
89 {
90 if (sa->sa_family == AF_INET) {
91 return ntohs(((struct sockaddr_in*)sa)->sin_port);
92 }
93
94 return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
95 }
96 /* }}} */
97
fpm_sockets_hash_op(int sock,struct sockaddr * sa,char * key,int type,int op)98 static int fpm_sockets_hash_op(int sock, struct sockaddr *sa, char *key, int type, int op) /* {{{ */
99 {
100 if (key == NULL) {
101 switch (type) {
102 case FPM_AF_INET : {
103 key = alloca(INET6_ADDRSTRLEN+10);
104 inet_ntop(sa->sa_family, fpm_get_in_addr(sa), key, INET6_ADDRSTRLEN);
105 sprintf(key+strlen(key), ":%d", fpm_get_in_port(sa));
106 break;
107 }
108
109 case FPM_AF_UNIX : {
110 struct sockaddr_un *sa_un = (struct sockaddr_un *) sa;
111 key = alloca(strlen(sa_un->sun_path) + 1);
112 strcpy(key, sa_un->sun_path);
113 break;
114 }
115
116 default :
117 return -1;
118 }
119 }
120
121 switch (op) {
122
123 case FPM_GET_USE_SOCKET :
124 {
125 unsigned i;
126 struct listening_socket_s *ls = sockets_list.data;
127
128 for (i = 0; i < sockets_list.used; i++, ls++) {
129 if (!strcmp(ls->key, key)) {
130 ++ls->refcount;
131 return ls->sock;
132 }
133 }
134 break;
135 }
136
137 case FPM_STORE_SOCKET : /* inherited socket */
138 case FPM_STORE_USE_SOCKET : /* just created */
139 {
140 struct listening_socket_s *ls;
141
142 ls = fpm_array_push(&sockets_list);
143 if (!ls) {
144 break;
145 }
146
147 if (op == FPM_STORE_SOCKET) {
148 ls->refcount = 0;
149 } else {
150 ls->refcount = 1;
151 }
152 ls->type = type;
153 ls->sock = sock;
154 ls->key = strdup(key);
155
156 return 0;
157 }
158 }
159 return -1;
160 }
161 /* }}} */
162
fpm_sockets_new_listening_socket(struct fpm_worker_pool_s * wp,struct sockaddr * sa,int socklen)163 static int fpm_sockets_new_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */
164 {
165 int flags = 1;
166 int sock;
167 mode_t saved_umask = 0;
168
169 sock = socket(sa->sa_family, SOCK_STREAM, 0);
170
171 if (0 > sock) {
172 zlog(ZLOG_SYSERROR, "failed to create new listening socket: socket()");
173 return -1;
174 }
175
176 if (0 > setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags))) {
177 zlog(ZLOG_WARNING, "failed to change socket attribute");
178 }
179
180 if (wp->listen_address_domain == FPM_AF_UNIX) {
181 if (fpm_socket_unix_test_connect((struct sockaddr_un *)sa, socklen) == 0) {
182 zlog(ZLOG_ERROR, "An another FPM instance seems to already listen on %s", ((struct sockaddr_un *) sa)->sun_path);
183 close(sock);
184 return -1;
185 }
186 unlink( ((struct sockaddr_un *) sa)->sun_path);
187 saved_umask = umask(0777 ^ wp->socket_mode);
188 }
189
190 if (0 > bind(sock, sa, socklen)) {
191 zlog(ZLOG_SYSERROR, "unable to bind listening socket for address '%s'", wp->config->listen_address);
192 if (wp->listen_address_domain == FPM_AF_UNIX) {
193 umask(saved_umask);
194 }
195 close(sock);
196 return -1;
197 }
198
199 if (wp->listen_address_domain == FPM_AF_UNIX) {
200 char *path = ((struct sockaddr_un *) sa)->sun_path;
201
202 umask(saved_umask);
203
204 if (0 > fpm_unix_set_socket_premissions(wp, path)) {
205 close(sock);
206 return -1;
207 }
208 }
209
210 if (0 > listen(sock, wp->config->listen_backlog)) {
211 zlog(ZLOG_SYSERROR, "failed to listen to address '%s'", wp->config->listen_address);
212 close(sock);
213 return -1;
214 }
215
216 return sock;
217 }
218 /* }}} */
219
fpm_sockets_get_listening_socket(struct fpm_worker_pool_s * wp,struct sockaddr * sa,int socklen)220 static int fpm_sockets_get_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */
221 {
222 int sock;
223
224 sock = fpm_sockets_hash_op(0, sa, 0, wp->listen_address_domain, FPM_GET_USE_SOCKET);
225 if (sock >= 0) {
226 return sock;
227 }
228
229 sock = fpm_sockets_new_listening_socket(wp, sa, socklen);
230 fpm_sockets_hash_op(sock, sa, 0, wp->listen_address_domain, FPM_STORE_USE_SOCKET);
231
232 return sock;
233 }
234 /* }}} */
235
fpm_sockets_domain_from_address(char * address)236 enum fpm_address_domain fpm_sockets_domain_from_address(char *address) /* {{{ */
237 {
238 if (strchr(address, ':')) {
239 return FPM_AF_INET;
240 }
241
242 if (strlen(address) == strspn(address, "0123456789")) {
243 return FPM_AF_INET;
244 }
245 return FPM_AF_UNIX;
246 }
247 /* }}} */
248
fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s * wp)249 static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
250 {
251 struct addrinfo hints, *servinfo, *p;
252 char *dup_address = strdup(wp->config->listen_address);
253 char *port_str = strrchr(dup_address, ':');
254 char *addr = NULL;
255 char tmpbuf[INET6_ADDRSTRLEN];
256 int addr_len;
257 int port = 0;
258 int sock = -1;
259 int status;
260
261 if (port_str) { /* this is host:port pair */
262 *port_str++ = '\0';
263 port = atoi(port_str);
264 addr = dup_address;
265 } else if (strlen(dup_address) == strspn(dup_address, "0123456789")) { /* this is port */
266 port = atoi(dup_address);
267 port_str = dup_address;
268 /* IPv6 catch-all + IPv4-mapped */
269 addr = "::";
270 }
271
272 if (port == 0) {
273 zlog(ZLOG_ERROR, "invalid port value '%s'", port_str);
274 return -1;
275 }
276
277 /* strip brackets from address for getaddrinfo */
278 addr_len = strlen(addr);
279 if (addr[0] == '[' && addr[addr_len - 1] == ']') {
280 addr[addr_len - 1] = '\0';
281 addr++;
282 }
283
284 memset(&hints, 0, sizeof hints);
285 hints.ai_family = AF_UNSPEC;
286 hints.ai_socktype = SOCK_STREAM;
287
288 if ((status = getaddrinfo(addr, port_str, &hints, &servinfo)) != 0) {
289 zlog(ZLOG_ERROR, "getaddrinfo: %s\n", gai_strerror(status));
290 free(dup_address);
291 return -1;
292 }
293
294 for (p = servinfo; p != NULL; p = p->ai_next) {
295 inet_ntop(p->ai_family, fpm_get_in_addr(p->ai_addr), tmpbuf, INET6_ADDRSTRLEN);
296 if (sock < 0) {
297 if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -1) {
298 zlog(ZLOG_DEBUG, "Found address for %s, socket opened on %s", addr, tmpbuf);
299 }
300 } else {
301 zlog(ZLOG_WARNING, "Found multiple addresses for %s, %s ignored", addr, tmpbuf);
302 }
303 }
304
305 free(dup_address);
306 freeaddrinfo(servinfo);
307
308 return sock;
309 }
310 /* }}} */
311
fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s * wp)312 static int fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
313 {
314 struct sockaddr_un sa_un;
315
316 memset(&sa_un, 0, sizeof(sa_un));
317 strlcpy(sa_un.sun_path, wp->config->listen_address, sizeof(sa_un.sun_path));
318 sa_un.sun_family = AF_UNIX;
319 return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_un, sizeof(struct sockaddr_un));
320 }
321 /* }}} */
322
fpm_sockets_init_main()323 int fpm_sockets_init_main() /* {{{ */
324 {
325 unsigned i, lq_len;
326 struct fpm_worker_pool_s *wp;
327 char *inherited = getenv("FPM_SOCKETS");
328 struct listening_socket_s *ls;
329
330 if (0 == fpm_array_init(&sockets_list, sizeof(struct listening_socket_s), 10)) {
331 return -1;
332 }
333
334 /* import inherited sockets */
335 while (inherited && *inherited) {
336 char *comma = strchr(inherited, ',');
337 int type, fd_no;
338 char *eq;
339
340 if (comma) {
341 *comma = '\0';
342 }
343
344 eq = strchr(inherited, '=');
345 if (eq) {
346 *eq = '\0';
347 fd_no = atoi(eq + 1);
348 type = fpm_sockets_domain_from_address(inherited);
349 zlog(ZLOG_NOTICE, "using inherited socket fd=%d, \"%s\"", fd_no, inherited);
350 fpm_sockets_hash_op(fd_no, 0, inherited, type, FPM_STORE_SOCKET);
351 }
352
353 if (comma) {
354 inherited = comma + 1;
355 } else {
356 inherited = 0;
357 }
358 }
359
360 /* create all required sockets */
361 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
362 switch (wp->listen_address_domain) {
363 case FPM_AF_INET :
364 wp->listening_socket = fpm_socket_af_inet_listening_socket(wp);
365 break;
366
367 case FPM_AF_UNIX :
368 if (0 > fpm_unix_resolve_socket_premissions(wp)) {
369 return -1;
370 }
371 wp->listening_socket = fpm_socket_af_unix_listening_socket(wp);
372 break;
373 }
374
375 if (wp->listening_socket == -1) {
376 return -1;
377 }
378
379 if (wp->listen_address_domain == FPM_AF_INET && fpm_socket_get_listening_queue(wp->listening_socket, NULL, &lq_len) >= 0) {
380 fpm_scoreboard_update(-1, -1, -1, (int)lq_len, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard);
381 }
382 }
383
384 /* close unused sockets that was inherited */
385 ls = sockets_list.data;
386
387 for (i = 0; i < sockets_list.used; ) {
388 if (ls->refcount == 0) {
389 close(ls->sock);
390 if (ls->type == FPM_AF_UNIX) {
391 unlink(ls->key);
392 }
393 free(ls->key);
394 fpm_array_item_remove(&sockets_list, i);
395 } else {
396 ++i;
397 ++ls;
398 }
399 }
400
401 if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_sockets_cleanup, 0)) {
402 return -1;
403 }
404 return 0;
405 }
406 /* }}} */
407
408 #if HAVE_FPM_LQ
409
410 #ifdef HAVE_LQ_TCP_INFO
411
412 #include <netinet/tcp.h>
413
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)414 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
415 {
416 struct tcp_info info;
417 socklen_t len = sizeof(info);
418
419 if (0 > getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &len)) {
420 zlog(ZLOG_SYSERROR, "failed to retrieve TCP_INFO for socket");
421 return -1;
422 }
423 #if defined(__FreeBSD__) || defined(__NetBSD__)
424 if (info.__tcpi_sacked == 0) {
425 return -1;
426 }
427
428 if (cur_lq) {
429 *cur_lq = info.__tcpi_unacked;
430 }
431
432 if (max_lq) {
433 *max_lq = info.__tcpi_sacked;
434 }
435 #else
436 /* kernel >= 2.6.24 return non-zero here, that means operation is supported */
437 if (info.tcpi_sacked == 0) {
438 return -1;
439 }
440
441 if (cur_lq) {
442 *cur_lq = info.tcpi_unacked;
443 }
444
445 if (max_lq) {
446 *max_lq = info.tcpi_sacked;
447 }
448 #endif
449
450 return 0;
451 }
452
453 #endif
454
455 #ifdef HAVE_LQ_SO_LISTENQ
456
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)457 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
458 {
459 int val;
460 socklen_t len = sizeof(val);
461
462 if (cur_lq) {
463 if (0 > getsockopt(sock, SOL_SOCKET, SO_LISTENQLEN, &val, &len)) {
464 return -1;
465 }
466
467 *cur_lq = val;
468 }
469
470 if (max_lq) {
471 if (0 > getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &val, &len)) {
472 return -1;
473 }
474
475 *max_lq = val;
476 }
477
478 return 0;
479 }
480
481 #endif
482
483 #else
484
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)485 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
486 {
487 return -1;
488 }
489
490 #endif
491
fpm_socket_unix_test_connect(struct sockaddr_un * sock,size_t socklen)492 int fpm_socket_unix_test_connect(struct sockaddr_un *sock, size_t socklen) /* {{{ */
493 {
494 int fd;
495
496 if (!sock || sock->sun_family != AF_UNIX) {
497 return -1;
498 }
499
500 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
501 return -1;
502 }
503
504 if (connect(fd, (struct sockaddr *)sock, socklen) == -1) {
505 close(fd);
506 return -1;
507 }
508
509 close(fd);
510 return 0;
511 }
512 /* }}} */
513