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