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