xref: /PHP-8.1/sapi/fpm/fpm/fpm_sockets.c (revision e2a5428c)
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_permissions(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_permissions(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