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