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