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