1 /* (c) 2007,2008 Andrei Nigmatulin */
2
3 #include "fpm_config.h"
4
5 #ifdef HAVE_ALLOCA_H
6 #include <alloca.h>
7 #endif
8 #include <sys/types.h>
9 #include <sys/stat.h> /* for chmod(2) */
10 #include <sys/socket.h>
11 #include <netinet/in.h>
12 #include <arpa/inet.h>
13 #include <sys/un.h>
14 #include <netdb.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <errno.h>
19 #include <unistd.h>
20
21 #include "zlog.h"
22 #include "fpm_arrays.h"
23 #include "fpm_sockets.h"
24 #include "fpm_worker_pool.h"
25 #include "fpm_unix.h"
26 #include "fpm_str.h"
27 #include "fpm_env.h"
28 #include "fpm_cleanup.h"
29 #include "fpm_scoreboard.h"
30
31 struct listening_socket_s {
32 int refcount;
33 int sock;
34 int type;
35 char *key;
36 };
37
38 static struct fpm_array_s sockets_list;
39
40 enum { FPM_GET_USE_SOCKET = 1, FPM_STORE_SOCKET = 2, FPM_STORE_USE_SOCKET = 3 };
41
42 #ifdef SO_SETFIB
43 static int routemax = -1;
44 #endif
45
fpm_sockets_get_env_name(char * envname,unsigned idx)46 static inline void fpm_sockets_get_env_name(char *envname, unsigned idx) /* {{{ */
47 {
48 if (!idx) {
49 strcpy(envname, "FPM_SOCKETS");
50 } else {
51 sprintf(envname, "FPM_SOCKETS_%d", idx);
52 }
53 }
54 /* }}} */
55
fpm_sockets_cleanup(int which,void * arg)56 static void fpm_sockets_cleanup(int which, void *arg) /* {{{ */
57 {
58 unsigned i;
59 unsigned socket_set_count = 0;
60 unsigned socket_set[FPM_ENV_SOCKET_SET_MAX];
61 unsigned socket_set_buf = 0;
62 char envname[32];
63 char *env_value = 0;
64 int p = 0;
65 struct listening_socket_s *ls = sockets_list.data;
66
67 for (i = 0; i < sockets_list.used; i++, ls++) {
68 if (which != FPM_CLEANUP_PARENT_EXEC) {
69 close(ls->sock);
70 } else { /* on PARENT EXEC we want socket fds to be inherited through environment variable */
71 char fd[32];
72 char *tmpenv_value;
73 sprintf(fd, "%d", ls->sock);
74
75 socket_set_buf = (i % FPM_ENV_SOCKET_SET_SIZE == 0 && i) ? 1 : 0;
76 tmpenv_value = realloc(env_value, p + (p ? 1 : 0) + strlen(ls->key) + 1 + strlen(fd) + socket_set_buf + 1);
77 if (!tmpenv_value) {
78 zlog(ZLOG_SYSERROR, "failure to inherit data on parent exec for socket `%s` due to memory allocation failure", ls->key);
79 free(ls->key);
80 break;
81 }
82
83 env_value = tmpenv_value;
84
85 if (i % FPM_ENV_SOCKET_SET_SIZE == 0) {
86 socket_set[socket_set_count] = p + socket_set_buf;
87 socket_set_count++;
88 if (i) {
89 *(env_value + p + 1) = 0;
90 }
91 }
92
93 p += sprintf(env_value + p + socket_set_buf, "%s%s=%s", (p && !socket_set_buf) ? "," : "", ls->key, fd);
94 p += socket_set_buf;
95 }
96
97 if (which == FPM_CLEANUP_PARENT_EXIT_MAIN) {
98 if (ls->type == FPM_AF_UNIX) {
99 unlink(ls->key);
100 }
101 }
102 free(ls->key);
103 }
104
105 if (env_value) {
106 for (i = 0; i < socket_set_count; i++) {
107 fpm_sockets_get_env_name(envname, i);
108 setenv(envname, env_value + socket_set[i], 1);
109 }
110 fpm_sockets_get_env_name(envname, socket_set_count);
111 unsetenv(envname);
112 free(env_value);
113 }
114
115 fpm_array_free(&sockets_list);
116 }
117 /* }}} */
118
fpm_get_in_addr(struct sockaddr * sa)119 static void *fpm_get_in_addr(struct sockaddr *sa) /* {{{ */
120 {
121 if (sa->sa_family == AF_INET) {
122 return &(((struct sockaddr_in*)sa)->sin_addr);
123 }
124
125 return &(((struct sockaddr_in6*)sa)->sin6_addr);
126 }
127 /* }}} */
128
fpm_get_in_port(struct sockaddr * sa)129 static int fpm_get_in_port(struct sockaddr *sa) /* {{{ */
130 {
131 if (sa->sa_family == AF_INET) {
132 return ntohs(((struct sockaddr_in*)sa)->sin_port);
133 }
134
135 return ntohs(((struct sockaddr_in6*)sa)->sin6_port);
136 }
137 /* }}} */
138
fpm_sockets_hash_op(int sock,struct sockaddr * sa,char * key,int type,int op)139 static int fpm_sockets_hash_op(int sock, struct sockaddr *sa, char *key, int type, int op) /* {{{ */
140 {
141 if (key == NULL) {
142 switch (type) {
143 case FPM_AF_INET : {
144 key = alloca(INET6_ADDRSTRLEN+10);
145 inet_ntop(sa->sa_family, fpm_get_in_addr(sa), key, INET6_ADDRSTRLEN);
146 sprintf(key+strlen(key), ":%d", fpm_get_in_port(sa));
147 break;
148 }
149
150 case FPM_AF_UNIX : {
151 struct sockaddr_un *sa_un = (struct sockaddr_un *) sa;
152 key = alloca(strlen(sa_un->sun_path) + 1);
153 strcpy(key, sa_un->sun_path);
154 break;
155 }
156
157 default :
158 return -1;
159 }
160 }
161
162 switch (op) {
163
164 case FPM_GET_USE_SOCKET :
165 {
166 unsigned i;
167 struct listening_socket_s *ls = sockets_list.data;
168
169 for (i = 0; i < sockets_list.used; i++, ls++) {
170 if (!strcmp(ls->key, key)) {
171 ++ls->refcount;
172 return ls->sock;
173 }
174 }
175 break;
176 }
177
178 case FPM_STORE_SOCKET : /* inherited socket */
179 case FPM_STORE_USE_SOCKET : /* just created */
180 {
181 struct listening_socket_s *ls;
182
183 ls = fpm_array_push(&sockets_list);
184 if (!ls) {
185 break;
186 }
187
188 if (op == FPM_STORE_SOCKET) {
189 ls->refcount = 0;
190 } else {
191 ls->refcount = 1;
192 }
193 ls->type = type;
194 ls->sock = sock;
195 ls->key = strdup(key);
196
197 return 0;
198 }
199 }
200 return -1;
201 }
202 /* }}} */
203
fpm_sockets_new_listening_socket(struct fpm_worker_pool_s * wp,struct sockaddr * sa,int socklen)204 static int fpm_sockets_new_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */
205 {
206 int flags = 1;
207 int sock;
208 mode_t saved_umask = 0;
209
210 sock = socket(sa->sa_family, SOCK_STREAM, 0);
211
212 if (0 > sock) {
213 zlog(ZLOG_SYSERROR, "failed to create new listening socket: socket()");
214 return -1;
215 }
216
217 if (0 > setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags))) {
218 zlog(ZLOG_WARNING, "failed to change socket attribute");
219 }
220
221 if (wp->listen_address_domain == FPM_AF_UNIX) {
222 if (fpm_socket_unix_test_connect((struct sockaddr_un *)sa, socklen) == 0) {
223 zlog(ZLOG_ERROR, "Another FPM instance seems to already listen on %s", ((struct sockaddr_un *) sa)->sun_path);
224 close(sock);
225 return -1;
226 }
227 unlink( ((struct sockaddr_un *) sa)->sun_path);
228 saved_umask = umask(0777 ^ wp->socket_mode);
229 }
230
231 if (0 > bind(sock, sa, socklen)) {
232 zlog(ZLOG_SYSERROR, "unable to bind listening socket for address '%s'", wp->config->listen_address);
233 if (wp->listen_address_domain == FPM_AF_UNIX) {
234 umask(saved_umask);
235 }
236 close(sock);
237 return -1;
238 }
239
240 if (wp->listen_address_domain == FPM_AF_UNIX) {
241 char *path = ((struct sockaddr_un *) sa)->sun_path;
242
243 umask(saved_umask);
244
245 if (0 > fpm_unix_set_socket_permissions(wp, path)) {
246 close(sock);
247 return -1;
248 }
249 }
250
251 if (0 > listen(sock, wp->config->listen_backlog)) {
252 zlog(ZLOG_SYSERROR, "failed to listen to address '%s'", wp->config->listen_address);
253 close(sock);
254 return -1;
255 }
256
257 #ifdef SO_SETFIB
258 if (-1 < wp->config->listen_setfib) {
259 if (routemax < wp->config->listen_setfib) {
260 zlog(ZLOG_ERROR, "Invalid routing table id %d, max is %d", wp->config->listen_setfib, routemax);
261 close(sock);
262 return -1;
263 }
264
265 if (0 > setsockopt(sock, SOL_SOCKET, SO_SETFIB, &wp->config->listen_setfib, sizeof(wp->config->listen_setfib))) {
266 zlog(ZLOG_WARNING, "failed to change socket SO_SETFIB attribute");
267 }
268 }
269 #endif
270
271 return sock;
272 }
273 /* }}} */
274
fpm_sockets_get_listening_socket(struct fpm_worker_pool_s * wp,struct sockaddr * sa,int socklen)275 static int fpm_sockets_get_listening_socket(struct fpm_worker_pool_s *wp, struct sockaddr *sa, int socklen) /* {{{ */
276 {
277 int sock;
278
279 sock = fpm_sockets_hash_op(0, sa, 0, wp->listen_address_domain, FPM_GET_USE_SOCKET);
280 if (sock >= 0) {
281 return sock;
282 }
283
284 sock = fpm_sockets_new_listening_socket(wp, sa, socklen);
285 fpm_sockets_hash_op(sock, sa, 0, wp->listen_address_domain, FPM_STORE_USE_SOCKET);
286
287 return sock;
288 }
289 /* }}} */
290
fpm_sockets_domain_from_address(char * address)291 enum fpm_address_domain fpm_sockets_domain_from_address(char *address) /* {{{ */
292 {
293 if (strchr(address, ':')) {
294 return FPM_AF_INET;
295 }
296
297 if (strlen(address) == strspn(address, "0123456789")) {
298 return FPM_AF_INET;
299 }
300 return FPM_AF_UNIX;
301 }
302 /* }}} */
303
fpm_socket_af_inet_socket_by_addr(struct fpm_worker_pool_s * wp,const char * addr,const char * port)304 static int fpm_socket_af_inet_socket_by_addr(struct fpm_worker_pool_s *wp, const char *addr, const char *port) /* {{{ */
305 {
306 struct addrinfo hints, *servinfo, *p;
307 char tmpbuf[INET6_ADDRSTRLEN];
308 int status;
309 int sock = -1;
310
311 memset(&hints, 0, sizeof hints);
312 hints.ai_family = AF_UNSPEC;
313 hints.ai_socktype = SOCK_STREAM;
314
315 if ((status = getaddrinfo(addr, port, &hints, &servinfo)) != 0) {
316 zlog(ZLOG_ERROR, "getaddrinfo: %s\n", gai_strerror(status));
317 return -1;
318 }
319
320 for (p = servinfo; p != NULL; p = p->ai_next) {
321 inet_ntop(p->ai_family, fpm_get_in_addr(p->ai_addr), tmpbuf, INET6_ADDRSTRLEN);
322 if (sock < 0) {
323 if ((sock = fpm_sockets_get_listening_socket(wp, p->ai_addr, p->ai_addrlen)) != -1) {
324 zlog(ZLOG_DEBUG, "Found address for %s, socket opened on %s", addr, tmpbuf);
325 }
326 } else {
327 zlog(ZLOG_WARNING, "Found multiple addresses for %s, %s ignored", addr, tmpbuf);
328 }
329 }
330
331 freeaddrinfo(servinfo);
332
333 return sock;
334 }
335 /* }}} */
336
fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s * wp)337 static int fpm_socket_af_inet_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
338 {
339 char *dup_address = strdup(wp->config->listen_address);
340 char *port_str = strrchr(dup_address, ':');
341 char *addr = NULL;
342 int addr_len;
343 int port = 0;
344 int sock = -1;
345
346 if (port_str) { /* this is host:port pair */
347 *port_str++ = '\0';
348 port = atoi(port_str);
349 addr = dup_address;
350
351 /* strip brackets from address for getaddrinfo */
352 addr_len = strlen(addr);
353 if (addr[0] == '[' && addr[addr_len - 1] == ']') {
354 addr[addr_len - 1] = '\0';
355 addr++;
356 }
357
358 } else if (strlen(dup_address) == strspn(dup_address, "0123456789")) { /* this is port */
359 port = atoi(dup_address);
360 port_str = dup_address;
361 }
362
363 if (port < 1 || port > 65535) {
364 zlog(ZLOG_ERROR, "invalid port value '%s'", port_str);
365 free(dup_address);
366 return -1;
367 }
368
369 if (addr) {
370 /* Bind a specific address */
371 sock = fpm_socket_af_inet_socket_by_addr(wp, addr, port_str);
372 } else {
373 /* Bind ANYADDR
374 *
375 * Try "::" first as that covers IPv6 ANYADDR and mapped IPv4 ANYADDR
376 * silencing warnings since failure is an option
377 *
378 * If that fails (because AF_INET6 is unsupported) retry with 0.0.0.0
379 */
380 int old_level = zlog_set_level(ZLOG_ALERT);
381 sock = fpm_socket_af_inet_socket_by_addr(wp, "::", port_str);
382 zlog_set_level(old_level);
383
384 if (sock < 0) {
385 zlog(ZLOG_NOTICE, "Failed implicitly binding to ::, retrying with 0.0.0.0");
386 sock = fpm_socket_af_inet_socket_by_addr(wp, "0.0.0.0", port_str);
387 }
388 }
389
390 free(dup_address);
391
392 return sock;
393 }
394 /* }}} */
395
fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s * wp)396 static int fpm_socket_af_unix_listening_socket(struct fpm_worker_pool_s *wp) /* {{{ */
397 {
398 struct sockaddr_un sa_un;
399
400 memset(&sa_un, 0, sizeof(sa_un));
401 strlcpy(sa_un.sun_path, wp->config->listen_address, sizeof(sa_un.sun_path));
402 sa_un.sun_family = AF_UNIX;
403 return fpm_sockets_get_listening_socket(wp, (struct sockaddr *) &sa_un, sizeof(struct sockaddr_un));
404 }
405 /* }}} */
406
407 #ifdef SO_SETFIB
fpm_socket_setfib_init(void)408 static zend_result fpm_socket_setfib_init(void)
409 {
410 /* potentially up to 65536 but needs to check the actual cap beforehand */
411 size_t len = sizeof(routemax);
412 if (sysctlbyname("net.fibs", &routemax, &len, NULL, 0) < 0) {
413 zlog(ZLOG_ERROR, "failed to get max routing table");
414 return FAILURE;
415 }
416
417 return SUCCESS;
418 }
419 #endif
420
fpm_sockets_init_main(void)421 int fpm_sockets_init_main(void)
422 {
423 unsigned i, lq_len;
424 struct fpm_worker_pool_s *wp;
425 char envname[32];
426 char sockpath[256];
427 char *inherited;
428 struct listening_socket_s *ls;
429
430 if (0 == fpm_array_init(&sockets_list, sizeof(struct listening_socket_s), 10)) {
431 return -1;
432 }
433
434 #ifdef SO_SETFIB
435 if (fpm_socket_setfib_init() == FAILURE) {
436 return -1;
437 }
438 #endif
439
440 /* import inherited sockets */
441 for (i = 0; i < FPM_ENV_SOCKET_SET_MAX; i++) {
442 fpm_sockets_get_env_name(envname, i);
443 inherited = getenv(envname);
444 if (!inherited) {
445 break;
446 }
447
448 while (inherited && *inherited) {
449 char *comma = strchr(inherited, ',');
450 int type, fd_no;
451 char *eq;
452
453 if (comma) {
454 *comma = '\0';
455 }
456
457 eq = strchr(inherited, '=');
458 if (eq) {
459 int sockpath_len = eq - inherited;
460 if (sockpath_len > 255) {
461 /* this should never happen as UDS limit is lower */
462 sockpath_len = 255;
463 }
464 memcpy(sockpath, inherited, sockpath_len);
465 sockpath[sockpath_len] = '\0';
466 fd_no = atoi(eq + 1);
467 type = fpm_sockets_domain_from_address(sockpath);
468 zlog(ZLOG_NOTICE, "using inherited socket fd=%d, \"%s\"", fd_no, sockpath);
469 fpm_sockets_hash_op(fd_no, 0, sockpath, type, FPM_STORE_SOCKET);
470 }
471
472 if (comma) {
473 inherited = comma + 1;
474 } else {
475 inherited = 0;
476 }
477 }
478 }
479
480 /* create all required sockets */
481 for (wp = fpm_worker_all_pools; wp; wp = wp->next) {
482 switch (wp->listen_address_domain) {
483 case FPM_AF_INET :
484 wp->listening_socket = fpm_socket_af_inet_listening_socket(wp);
485 break;
486
487 case FPM_AF_UNIX :
488 if (0 > fpm_unix_resolve_socket_permissions(wp)) {
489 return -1;
490 }
491 wp->listening_socket = fpm_socket_af_unix_listening_socket(wp);
492 break;
493 }
494
495 if (wp->listening_socket == -1) {
496 return -1;
497 }
498
499 if (wp->listen_address_domain == FPM_AF_INET && fpm_socket_get_listening_queue(wp->listening_socket, NULL, &lq_len) >= 0) {
500 fpm_scoreboard_update(-1, -1, -1, (int)lq_len, -1, -1, 0, FPM_SCOREBOARD_ACTION_SET, wp->scoreboard);
501 }
502 }
503
504 /* close unused sockets that was inherited */
505 ls = sockets_list.data;
506
507 for (i = 0; i < sockets_list.used; ) {
508 if (ls->refcount == 0) {
509 close(ls->sock);
510 if (ls->type == FPM_AF_UNIX) {
511 unlink(ls->key);
512 }
513 free(ls->key);
514 fpm_array_item_remove(&sockets_list, i);
515 } else {
516 ++i;
517 ++ls;
518 }
519 }
520
521 if (0 > fpm_cleanup_add(FPM_CLEANUP_ALL, fpm_sockets_cleanup, 0)) {
522 return -1;
523 }
524 return 0;
525 }
526
527 #if HAVE_FPM_LQ
528
529 #ifdef HAVE_LQ_TCP_INFO
530
531 #include <netinet/tcp.h>
532
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)533 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
534 {
535 struct tcp_info info;
536 socklen_t len = sizeof(info);
537
538 if (0 > getsockopt(sock, IPPROTO_TCP, TCP_INFO, &info, &len)) {
539 zlog(ZLOG_SYSERROR, "failed to retrieve TCP_INFO for socket");
540 return -1;
541 }
542 #if defined(__FreeBSD__) || defined(__NetBSD__)
543 if (info.__tcpi_sacked == 0) {
544 return -1;
545 }
546
547 if (cur_lq) {
548 *cur_lq = info.__tcpi_unacked;
549 }
550
551 if (max_lq) {
552 *max_lq = info.__tcpi_sacked;
553 }
554 #else
555 /* kernel >= 2.6.24 return non-zero here, that means operation is supported */
556 if (info.tcpi_sacked == 0) {
557 return -1;
558 }
559
560 if (cur_lq) {
561 *cur_lq = info.tcpi_unacked;
562 }
563
564 if (max_lq) {
565 *max_lq = info.tcpi_sacked;
566 }
567 #endif
568
569 return 0;
570 }
571
572 #elif defined(HAVE_LQ_TCP_CONNECTION_INFO)
573
574 #include <netinet/tcp.h>
575
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)576 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
577 {
578 struct tcp_connection_info info;
579 socklen_t len = sizeof(info);
580
581 if (0 > getsockopt(sock, IPPROTO_TCP, TCP_CONNECTION_INFO, &info, &len)) {
582 zlog(ZLOG_SYSERROR, "failed to retrieve TCP_CONNECTION_INFO for socket");
583 return -1;
584 }
585
586 if (cur_lq) {
587 *cur_lq = info.tcpi_tfo_syn_data_acked;
588 }
589
590 if (max_lq) {
591 *max_lq = 0;
592 }
593
594 return 0;
595 }
596 #endif
597
598 #ifdef HAVE_LQ_SO_LISTENQ
599
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)600 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
601 {
602 int val;
603 socklen_t len = sizeof(val);
604
605 if (cur_lq) {
606 if (0 > getsockopt(sock, SOL_SOCKET, SO_LISTENQLEN, &val, &len)) {
607 return -1;
608 }
609
610 *cur_lq = val;
611 }
612
613 if (max_lq) {
614 if (0 > getsockopt(sock, SOL_SOCKET, SO_LISTENQLIMIT, &val, &len)) {
615 return -1;
616 }
617
618 *max_lq = val;
619 }
620
621 return 0;
622 }
623
624 #endif
625
626 #else
627
fpm_socket_get_listening_queue(int sock,unsigned * cur_lq,unsigned * max_lq)628 int fpm_socket_get_listening_queue(int sock, unsigned *cur_lq, unsigned *max_lq)
629 {
630 return -1;
631 }
632
633 #endif
634
fpm_socket_unix_test_connect(struct sockaddr_un * sock,size_t socklen)635 int fpm_socket_unix_test_connect(struct sockaddr_un *sock, size_t socklen) /* {{{ */
636 {
637 int fd;
638
639 if (!sock || sock->sun_family != AF_UNIX) {
640 return -1;
641 }
642
643 if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
644 return -1;
645 }
646
647 if (connect(fd, (struct sockaddr *)sock, socklen) == -1) {
648 close(fd);
649 return -1;
650 }
651
652 close(fd);
653 return 0;
654 }
655 /* }}} */
656