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