xref: /illumos-gate/usr/src/cmd/cmd-inet/usr.lib/ilbd/ilbd_main.c (revision 93a18d6d401e844455263f926578e9d2aa6b47ec)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * The core of ilbd daemon is a single-threaded event loop using
29  * event completion framework; it receives requests from client using
30  * the libilb functions, handles timeouts, initiates health checks, and
31  * populates the kernel state.
32  *
33  * The daemon has the following privileges (in addition to the basic ones):
34  *
35  * 	PRIV_PROC_OWNER, PRIV_NET_ICMPACCESS,
36  *	PRIV_SYS_IP_CONFIG, PRIV_PROC_AUDIT
37  *
38  * The aforementioned  privileges will be specified in the SMF manifest.
39  *
40  * AF_UNIX socket is used for IPC between libilb and this daemon as
41  * both processes will run on the same machine.
42  *
43  * To do health check, the daemon will create a timer for every health
44  * check probe. Each of these timers will be  associated with the
45  * event port. When a timer goes off, the daemon will initiate a
46  * pipe to a separate process to execute the specific health check
47  * probe. This new process will run with the same user-id as that of
48  * ilbd daemon and will inherit all the privileges from the ilbd
49  * daemon parent process except the following:
50  *
51  * PRIV_PROC_OWNER, PRIV_PROC_AUDIT
52  *
53  * All health checks, will be implemented as external methods
54  * (binary or script). The following arguments will be passed
55  * to external methods:
56  *
57  *	$1	VIP (literal IPv4 or IPv6 address)
58  *	$2	Server IP (literal IPv4 or IPv6 address)
59  *	$3	Protocol (UDP, TCP as a string)
60  *	$4	The load balance mode, "DSR", "NAT", "HALF_NAT"
61  *	$5	Numeric port range
62  *	$6	maximum time (in seconds) the method
63  * should wait before returning failure. If the method runs for
64  * longer, it may be killed, and the test considered failed.
65  *
66  * Upon success, a health check method should print the RTT to the
67  * it finds to its STDOUT for ilbd to consume.  The implicit unit
68  * is microseconds but only the number needs to be printed.  If it
69  * cannot find the RTT, it should print 0.  If the method decides
70  * that the server is dead, it should print -1 to its STDOUT.
71  *
72  * By default, an user-supplied health check probe process will
73  * also run with the same set of privileges as ILB's built-in
74  * probes.  If the administrator has an user-supplied health check
75  * program that requires a larger privilege set, he/she will have
76  * to implement setuid program.
77  *
78  * Each health check will have a timeout, such that if the health
79  * check process is hung, it will be killed after the timeout interval
80  * and the daemon will notify the kernel ILB engine of the server's
81  * unresponsiveness, so that load distribution can be appropriately
82  * adjusted.  If on the other hand the health check is successful
83  * the timeout timer is cancelled.
84  */
85 
86 #include <stdio.h>
87 #include <stdlib.h>
88 #include <strings.h>
89 #include <libgen.h>
90 #include <fcntl.h>
91 #include <stddef.h>
92 #include <signal.h>
93 #include <port.h>
94 #include <ctype.h>
95 #include <sys/types.h>
96 #include <sys/wait.h>
97 #include <sys/stat.h>
98 #include <sys/note.h>
99 #include <sys/resource.h>
100 #include <unistd.h>
101 #include <sys/socket.h>
102 #include <errno.h>
103 #include <ucred.h>
104 #include <priv_utils.h>
105 #include <net/if.h>
106 #include <libilb.h>
107 #include <assert.h>
108 #include <inet/ilb.h>
109 #include <libintl.h>
110 #include <fcntl.h>
111 #include <rpcsvc/daemon_utils.h>
112 #include "libilb_impl.h"
113 #include "ilbd.h"
114 
115 /*
116  * NOTE: The following needs to be kept up to date.
117  */
118 #define	ILBD_VERSION	"1.0"
119 #define	ILBD_COPYRIGHT	\
120 	"Copyright 2009 Sun Microsystems, Inc.  All rights reserved.\n" \
121 	"Use is subject to license terms.\n"
122 
123 /*
124  * Global reply buffer to client request.  Note that ilbd is single threaded,
125  * so a global buffer is OK.  If ilbd becomes multi-threaded, this needs to
126  * be changed.
127  */
128 static uint32_t reply_buf[ILBD_MSG_SIZE / sizeof (uint32_t)];
129 
130 static void
131 ilbd_free_cli(ilbd_client_t *cli)
132 {
133 	(void) close(cli->cli_sd);
134 	if (cli->cli_cmd == ILBD_SHOW_NAT)
135 		ilbd_show_nat_cleanup();
136 	if (cli->cli_cmd == ILBD_SHOW_PERSIST)
137 		ilbd_show_sticky_cleanup();
138 	if (cli->cli_saved_reply != NULL)
139 		free(cli->cli_saved_reply);
140 	free(cli->cli_pw_buf);
141 	free(cli);
142 }
143 
144 static void
145 ilbd_reset_kernel_state(void)
146 {
147 	ilb_status_t	rc;
148 	ilb_name_cmd_t	kcmd;
149 
150 	kcmd.cmd = ILB_DESTROY_RULE;
151 	kcmd.flags = ILB_RULE_ALLRULES;
152 	kcmd.name[0] = '\0';
153 
154 	rc = do_ioctl(&kcmd, 0);
155 	if (rc != ILB_STATUS_OK)
156 		logdebug("ilbd_reset_kernel_state: do_ioctl failed: %s",
157 		    strerror(errno));
158 }
159 
160 /* Signal handler to do clean up. */
161 /* ARGSUSED */
162 static void
163 ilbd_cleanup(int sig)
164 {
165 	(void) remove(SOCKET_PATH);
166 	ilbd_reset_kernel_state();
167 	exit(0);
168 }
169 
170 /*
171  * Create a socket and return it to caller.  If there is a failure, this
172  * function calls exit(2).  Hence it always returns a valid listener socket.
173  *
174  * Note that this function is called before ilbd becomes a daemon.  So
175  * we call perror(3C) to print out error message directly so that SMF can
176  * catch them.
177  */
178 static int
179 ilbd_create_client_socket(void)
180 {
181 	int			s;
182 	mode_t			omask;
183 	struct sockaddr_un	sa;
184 	int			sobufsz;
185 
186 	s = socket(PF_UNIX, SOCK_SEQPACKET, 0);
187 	if (s == -1) {
188 		perror("ilbd_create_client_socket: socket to"
189 		    " client failed");
190 		exit(errno);
191 	}
192 	if (fcntl(s, F_SETFD, FD_CLOEXEC) == -1) {
193 		perror("ilbd_create_client_socket: fcntl(FD_CLOEXEC)");
194 		exit(errno);
195 	}
196 
197 	sobufsz = ILBD_MSG_SIZE;
198 	if (setsockopt(s, SOL_SOCKET, SO_SNDBUF, &sobufsz,
199 	    sizeof (sobufsz)) != 0) {
200 		perror("ilbd_creat_client_socket: setsockopt(SO_SNDBUF) "
201 		    "failed");
202 		exit(errno);
203 	}
204 	if (setsockopt(s, SOL_SOCKET, SO_RCVBUF, &sobufsz,
205 	    sizeof (sobufsz)) != 0) {
206 		perror("ilbd_creat_client_socket: setsockopt(SO_RCVBUF) "
207 		    "failed");
208 		exit(errno);
209 	}
210 
211 	/*
212 	 * since everybody can talk to us, we need to open up permissions
213 	 * we check peer privileges on a per-operation basis.
214 	 * This is no security issue as long as we're single-threaded.
215 	 */
216 	omask = umask(0);
217 
218 	/* just in case we didn't clean up properly after last exit */
219 	(void) remove(SOCKET_PATH);
220 
221 	bzero(&sa, sizeof (sa));
222 	sa.sun_family = AF_UNIX;
223 	(void) strlcpy(sa.sun_path, SOCKET_PATH, sizeof (sa.sun_path));
224 
225 	if (bind(s, (struct sockaddr *)&sa, sizeof (sa)) != 0) {
226 		perror("ilbd_create_client_socket(): bind to client"
227 		    " socket failed");
228 		exit(errno);
229 	}
230 
231 	/* re-instate old umask */
232 	(void) umask(omask);
233 
234 #define	QLEN	16
235 
236 	if (listen(s, QLEN) != 0) {
237 		perror("ilbd_create_client_socket: listen to client"
238 		    " socket failed");
239 		exit(errno);
240 	}
241 
242 	(void) signal(SIGHUP, SIG_IGN);
243 	(void) signal(SIGPIPE, SIG_IGN);
244 	(void) signal(SIGSTOP, SIG_IGN);
245 	(void) signal(SIGTSTP, SIG_IGN);
246 	(void) signal(SIGTTIN, SIG_IGN);
247 	(void) signal(SIGTTOU, SIG_IGN);
248 
249 	(void) signal(SIGINT, ilbd_cleanup);
250 	(void) signal(SIGTERM, ilbd_cleanup);
251 	(void) signal(SIGQUIT, ilbd_cleanup);
252 
253 	return (s);
254 }
255 
256 /*
257  * Return the minimum size of a given request.  The returned size does not
258  * include the variable part of a request.
259  */
260 static size_t
261 ilbd_cmd_size(const ilb_comm_t *ic)
262 {
263 	size_t cmd_sz;
264 
265 	cmd_sz = sizeof (*ic);
266 	switch (ic->ic_cmd) {
267 	case ILBD_RETRIEVE_SG_NAMES:
268 	case ILBD_RETRIEVE_RULE_NAMES:
269 	case ILBD_RETRIEVE_HC_NAMES:
270 	case ILBD_CMD_OK:
271 		break;
272 	case ILBD_CMD_ERROR:
273 		cmd_sz += sizeof (ilb_status_t);
274 		break;
275 	case ILBD_RETRIEVE_SG_HOSTS:
276 	case ILBD_CREATE_SERVERGROUP:
277 	case ILBD_DESTROY_SERVERGROUP:
278 	case ILBD_DESTROY_RULE:
279 	case ILBD_ENABLE_RULE:
280 	case ILBD_DISABLE_RULE:
281 	case ILBD_RETRIEVE_RULE:
282 	case ILBD_DESTROY_HC:
283 	case ILBD_GET_HC_INFO:
284 	case ILBD_GET_HC_SRVS:
285 		cmd_sz += sizeof (ilbd_name_t);
286 		break;
287 	case ILBD_ENABLE_SERVER:
288 	case ILBD_DISABLE_SERVER:
289 	case ILBD_ADD_SERVER_TO_GROUP:
290 	case ILBD_REM_SERVER_FROM_GROUP:
291 		cmd_sz += sizeof (ilb_sg_info_t);
292 		break;
293 	case ILBD_SRV_ADDR2ID:
294 	case ILBD_SRV_ID2ADDR:
295 		cmd_sz += sizeof (ilb_sg_info_t) + sizeof (ilb_sg_srv_t);
296 		break;
297 	case ILBD_CREATE_RULE:
298 		cmd_sz += sizeof (ilb_rule_info_t);
299 		break;
300 	case ILBD_CREATE_HC:
301 		cmd_sz += sizeof (ilb_hc_info_t);
302 		break;
303 	case ILBD_SHOW_NAT:
304 	case ILBD_SHOW_PERSIST:
305 		cmd_sz += sizeof (ilb_show_info_t);
306 		break;
307 	}
308 
309 	return (cmd_sz);
310 }
311 
312 /*
313  * Given a request and its size, check that the size is big enough to
314  * contain the variable part of a request.
315  */
316 static ilb_status_t
317 ilbd_check_req_size(ilb_comm_t *ic, size_t ic_sz)
318 {
319 	ilb_status_t rc = ILB_STATUS_OK;
320 	ilb_sg_info_t *sg_info;
321 	ilbd_namelist_t *nlist;
322 
323 	switch (ic->ic_cmd) {
324 	case ILBD_CREATE_SERVERGROUP:
325 	case ILBD_ENABLE_SERVER:
326 	case ILBD_DISABLE_SERVER:
327 	case ILBD_ADD_SERVER_TO_GROUP:
328 	case ILBD_REM_SERVER_FROM_GROUP:
329 		sg_info = (ilb_sg_info_t *)&ic->ic_data;
330 
331 		if (ic_sz < ilbd_cmd_size(ic) + sg_info->sg_srvcount *
332 		    sizeof (ilb_sg_srv_t)) {
333 			rc = ILB_STATUS_EINVAL;
334 		}
335 		break;
336 	case ILBD_ENABLE_RULE:
337 	case ILBD_DISABLE_RULE:
338 	case ILBD_DESTROY_RULE:
339 		nlist = (ilbd_namelist_t *)&ic->ic_data;
340 
341 		if (ic_sz < ilbd_cmd_size(ic) + nlist->ilbl_count *
342 		    sizeof (ilbd_name_t)) {
343 			rc = ILB_STATUS_EINVAL;
344 		}
345 		break;
346 	}
347 	return (rc);
348 }
349 
350 /*
351  * this function *relies* on a complete message/data struct
352  * being passed in (currently via the SOCK_SEQPACKET socket type).
353  *
354  * Note that the size of ip is at most ILBD_MSG_SIZE.
355  */
356 static ilb_status_t
357 consume_common_struct(ilb_comm_t *ic, size_t ic_sz, ilbd_client_t *cli,
358     int ev_port)
359 {
360 	ilb_status_t	rc;
361 	struct passwd	*ps;
362 	size_t		rbufsz;
363 	ssize_t		ret;
364 	boolean_t	standard_reply = B_TRUE;
365 	ilbd_name_t	name;
366 
367 	/*
368 	 * cli_ev must be overridden during handling of individual commands,
369 	 * if there's a special need; otherwise, leave this for
370 	 * the "default" case
371 	 */
372 	cli->cli_ev = ILBD_EVENT_REQ;
373 
374 	ps = &cli->cli_pw;
375 	rbufsz = ILBD_MSG_SIZE;
376 
377 	/* Sanity check on the size of the static part of a request. */
378 	if (ic_sz < ilbd_cmd_size(ic)) {
379 		rc = ILB_STATUS_EINVAL;
380 		goto out;
381 	}
382 
383 	switch (ic->ic_cmd) {
384 	case ILBD_CREATE_SERVERGROUP: {
385 		ilb_sg_info_t sg_info;
386 
387 		/*
388 		 * ilbd_create_sg() only needs the sg_name field.  But it
389 		 * takes in a ilb_sg_info_t because it is used as a callback
390 		 * in ilbd_walk_sg_pgs().
391 		 */
392 		(void) strlcpy(sg_info.sg_name, (char *)&(ic->ic_data),
393 		    sizeof (sg_info.sg_name));
394 		rc = ilbd_create_sg(&sg_info, ev_port, ps,
395 		    cli->cli_peer_ucredp);
396 		break;
397 	}
398 
399 	case ILBD_DESTROY_SERVERGROUP:
400 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
401 		rc = ilbd_destroy_sg(name, ps, cli->cli_peer_ucredp);
402 		break;
403 
404 	case ILBD_ADD_SERVER_TO_GROUP:
405 		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
406 			break;
407 		rc = ilbd_add_server_to_group((ilb_sg_info_t *)&ic->ic_data,
408 		    ev_port, ps, cli->cli_peer_ucredp);
409 		break;
410 
411 	case ILBD_REM_SERVER_FROM_GROUP:
412 		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
413 			break;
414 		rc = ilbd_rem_server_from_group((ilb_sg_info_t *)&ic->ic_data,
415 		    ev_port, ps, cli->cli_peer_ucredp);
416 		break;
417 
418 	case ILBD_ENABLE_SERVER:
419 		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
420 			break;
421 		rc = ilbd_enable_server((ilb_sg_info_t *)&ic->ic_data, ps,
422 		    cli->cli_peer_ucredp);
423 		break;
424 
425 	case ILBD_DISABLE_SERVER:
426 		if ((rc = ilbd_check_req_size(ic, ic_sz)) != ILB_STATUS_OK)
427 			break;
428 		rc = ilbd_disable_server((ilb_sg_info_t *)&ic->ic_data, ps,
429 		    cli->cli_peer_ucredp);
430 		break;
431 
432 	case ILBD_SRV_ADDR2ID:
433 		rc = ilbd_address_to_srvID((ilb_sg_info_t *)&ic->ic_data,
434 		    reply_buf, &rbufsz);
435 		if (rc == ILB_STATUS_OK)
436 			standard_reply = B_FALSE;
437 		break;
438 
439 	case ILBD_SRV_ID2ADDR:
440 		rc = ilbd_srvID_to_address((ilb_sg_info_t *)&ic->ic_data,
441 		    reply_buf, &rbufsz);
442 		if (rc == ILB_STATUS_OK)
443 			standard_reply = B_FALSE;
444 		break;
445 
446 	case ILBD_RETRIEVE_SG_HOSTS:
447 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
448 		rc = ilbd_retrieve_sg_hosts(name, reply_buf, &rbufsz);
449 		if (rc == ILB_STATUS_OK)
450 			standard_reply = B_FALSE;
451 		break;
452 
453 	case ILBD_RETRIEVE_SG_NAMES:
454 	case ILBD_RETRIEVE_RULE_NAMES:
455 	case ILBD_RETRIEVE_HC_NAMES:
456 		rc = ilbd_retrieve_names(ic->ic_cmd, reply_buf, &rbufsz);
457 		if (rc == ILB_STATUS_OK)
458 			standard_reply = B_FALSE;
459 		break;
460 
461 	case ILBD_CREATE_RULE:
462 		rc = ilbd_create_rule((ilb_rule_info_t *)&ic->ic_data, ev_port,
463 		    ps, cli->cli_peer_ucredp);
464 		break;
465 
466 	case ILBD_DESTROY_RULE:
467 		/* Copy the name to ensure that name is NULL terminated. */
468 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
469 		rc = ilbd_destroy_rule(name, ps, cli->cli_peer_ucredp);
470 		break;
471 
472 	case ILBD_ENABLE_RULE:
473 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
474 		rc = ilbd_enable_rule(name, ps, cli->cli_peer_ucredp);
475 		break;
476 
477 	case ILBD_DISABLE_RULE:
478 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
479 		rc = ilbd_disable_rule(name, ps, cli->cli_peer_ucredp);
480 		break;
481 
482 	case ILBD_RETRIEVE_RULE:
483 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
484 		rc = ilbd_retrieve_rule(name, reply_buf, &rbufsz);
485 		if (rc == ILB_STATUS_OK)
486 			standard_reply = B_FALSE;
487 		break;
488 
489 	case ILBD_CREATE_HC:
490 		rc = ilbd_create_hc((ilb_hc_info_t *)&ic->ic_data, ev_port, ps,
491 		    cli->cli_peer_ucredp);
492 		break;
493 
494 	case ILBD_DESTROY_HC:
495 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
496 		rc = ilbd_destroy_hc(name, ps, cli->cli_peer_ucredp);
497 		break;
498 
499 	case ILBD_GET_HC_INFO:
500 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
501 		rc = ilbd_get_hc_info(name, reply_buf, &rbufsz);
502 		if (rc == ILB_STATUS_OK)
503 			standard_reply = B_FALSE;
504 		break;
505 
506 	case ILBD_GET_HC_SRVS:
507 		(void) strlcpy(name, (char *)&(ic->ic_data), sizeof (name));
508 		rc = ilbd_get_hc_srvs(name, reply_buf, &rbufsz);
509 		if (rc == ILB_STATUS_OK)
510 			standard_reply = B_FALSE;
511 		break;
512 
513 	case ILBD_SHOW_NAT:
514 		rc = ilbd_show_nat(cli, ic, reply_buf, &rbufsz);
515 		if (rc == ILB_STATUS_OK)
516 			standard_reply = B_FALSE;
517 		break;
518 
519 	case ILBD_SHOW_PERSIST:
520 		rc = ilbd_show_sticky(cli, ic, reply_buf, &rbufsz);
521 		if (rc == ILB_STATUS_OK)
522 			standard_reply = B_FALSE;
523 		break;
524 
525 	default:
526 		logdebug("consume_common_struct: unknown command");
527 		rc = ILB_STATUS_INVAL_CMD;
528 		break;
529 	}
530 
531 out:
532 	/*
533 	 * The message exchange is always in pairs, request/response.  If
534 	 * a transaction requires multiple exchanges, the client will send
535 	 * in multiple requests to get multiple responses.  The show-nat and
536 	 * show-persist request are examples of this.  The end of transaction
537 	 * is marked with ic_flags set to ILB_COMM_END.
538 	 */
539 
540 	/* This is the standard reply. */
541 	if (standard_reply) {
542 		if (rc == ILB_STATUS_OK)
543 			ilbd_reply_ok(reply_buf, &rbufsz);
544 		else
545 			ilbd_reply_err(reply_buf, &rbufsz, rc);
546 	}
547 
548 	if ((ret = send(cli->cli_sd, reply_buf, rbufsz, 0)) != rbufsz) {
549 		if (ret == -1) {
550 			if (errno != EWOULDBLOCK) {
551 				logdebug("consume_common_struct: send: %s",
552 				    strerror(errno));
553 				rc = ILB_STATUS_SEND;
554 				goto err_out;
555 			}
556 			/*
557 			 * The reply is blocked, save the reply.  handle_req()
558 			 * will associate the event port for the re-send.
559 			 */
560 			assert(cli->cli_saved_reply == NULL);
561 			if ((cli->cli_saved_reply = malloc(rbufsz)) == NULL) {
562 				/*
563 				 * Set the error to ILB_STATUS_SEND so that
564 				 * handle_req() will free the client.
565 				 */
566 				logdebug("consume_common_struct: failure to "
567 				    "allocate memory to save reply");
568 				rc = ILB_STATUS_SEND;
569 				goto err_out;
570 			}
571 			bcopy(reply_buf, cli->cli_saved_reply, rbufsz);
572 			cli->cli_saved_size = rbufsz;
573 			return (ILB_STATUS_EWOULDBLOCK);
574 		}
575 	}
576 err_out:
577 	return (rc);
578 }
579 
580 /*
581  * Accept a new client request.  A struct ilbd_client_t is allocated to
582  * store the client info.  The accepted socket is port_associate() with
583  * the given port.  And the allocated ilbd_client_t struct is passed as
584  * the user pointer.
585  */
586 static void
587 new_req(int ev_port, int listener, void *ev_obj)
588 {
589 	struct sockaddr	sa;
590 	int		sa_len;
591 	int		new_sd;
592 	int		sflags;
593 	ilbd_client_t	*cli;
594 	int		res;
595 	uid_t		uid;
596 
597 	sa_len = sizeof (sa);
598 	if ((new_sd = accept(listener, &sa, &sa_len)) == -1) {
599 		/* don't log if we're out of file descriptors */
600 		if (errno != EINTR && errno != EMFILE)
601 			logperror("new_req: accept failed");
602 		goto done;
603 	}
604 
605 	/* Set the new socket to be non-blocking. */
606 	if ((sflags = fcntl(new_sd, F_GETFL, 0)) == -1) {
607 		logperror("new_req: fcntl(F_GETFL)");
608 		goto clean_up;
609 	}
610 	if (fcntl(new_sd, F_SETFL, sflags | O_NONBLOCK) == -1) {
611 		logperror("new_req: fcntl(F_SETFL)");
612 		goto clean_up;
613 	}
614 	if (fcntl(new_sd, F_SETFD, FD_CLOEXEC) == -1) {
615 		logperror("new_req: fcntl(FD_CLOEXEC)");
616 		goto clean_up;
617 	}
618 	if ((cli = calloc(1, sizeof (ilbd_client_t))) == NULL) {
619 		logerr("new_req: malloc(ilbd_client_t)");
620 		goto clean_up;
621 	}
622 	res = getpeerucred(new_sd, &cli->cli_peer_ucredp);
623 	if (res == -1) {
624 		logperror("new_req: getpeerucred failed");
625 		goto clean_up;
626 	}
627 	if ((uid = ucred_getruid(cli->cli_peer_ucredp)) == (uid_t)-1) {
628 		logperror("new_req: ucred_getruid failed");
629 		goto clean_up;
630 	}
631 	cli->cli_pw_bufsz = (size_t)sysconf(_SC_GETPW_R_SIZE_MAX);
632 	if ((cli->cli_pw_buf = malloc(cli->cli_pw_bufsz)) == NULL) {
633 		free(cli);
634 		logerr("new_req: malloc(cli_pw_buf)");
635 		goto clean_up;
636 	}
637 	if (getpwuid_r(uid, &cli->cli_pw, cli->cli_pw_buf,
638 	    cli->cli_pw_bufsz) == NULL) {
639 		free(cli->cli_pw_buf);
640 		free(cli);
641 		logperror("new_req: invalid user");
642 		goto clean_up;
643 	}
644 	cli->cli_ev = ILBD_EVENT_REQ;
645 	cli->cli_sd = new_sd;
646 	cli->cli_cmd = ILBD_BAD_CMD;
647 	cli->cli_saved_reply = NULL;
648 	cli->cli_saved_size = 0;
649 	if (port_associate(ev_port, PORT_SOURCE_FD, new_sd, POLLRDNORM,
650 	    cli) == -1) {
651 		logperror("new_req: port_associate(cli) failed");
652 		free(cli->cli_pw_buf);
653 		free(cli);
654 clean_up:
655 		(void) close(new_sd);
656 	}
657 
658 done:
659 	/* Re-associate the listener with the event port. */
660 	if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM,
661 	    ev_obj) == -1) {
662 		logperror("new_req: port_associate(listener) failed");
663 		exit(1);
664 	}
665 }
666 
667 static void
668 handle_req(int ev_port, ilbd_event_t event, ilbd_client_t *cli)
669 {
670 	/* All request should be smaller than ILBD_MSG_SIZE */
671 	union {
672 		ilb_comm_t	ic;
673 		uint32_t	buf[ILBD_MSG_SIZE / sizeof (uint32_t)];
674 	} ic_u;
675 	int	rc = ILB_STATUS_OK;
676 	ssize_t	r;
677 
678 	if (event == ILBD_EVENT_REQ) {
679 		/*
680 		 * Something is wrong with the client since there is a
681 		 * pending reply, the client should not send us another
682 		 * request.  Kill this client.
683 		 */
684 		if (cli->cli_saved_reply != NULL) {
685 			logerr("handle_req: misbehaving client, more than one "
686 			    "outstanding request");
687 			rc = ILB_STATUS_INTERNAL;
688 			goto err_out;
689 		}
690 
691 		/*
692 		 * Our socket is message based so we should be able
693 		 * to get the request in one single read.
694 		 */
695 		r = recv(cli->cli_sd, (void *)ic_u.buf, sizeof (ic_u.buf), 0);
696 		if (r < 0) {
697 			if (errno != EINTR) {
698 				logperror("handle_req: read failed");
699 				rc = ILB_STATUS_READ;
700 				goto err_out;
701 			}
702 			/*
703 			 * If interrupted, just re-associate the cli_sd
704 			 * with the port.
705 			 */
706 			goto done;
707 		}
708 		cli->cli_cmd = ic_u.ic.ic_cmd;
709 
710 		rc = consume_common_struct(&ic_u.ic, r, cli, ev_port);
711 		if (rc == ILB_STATUS_EWOULDBLOCK)
712 			goto blocked;
713 		/* Fatal error communicating with client, free it. */
714 		if (rc == ILB_STATUS_SEND)
715 			goto err_out;
716 	} else {
717 		assert(event == ILBD_EVENT_REP_OK);
718 		assert(cli->cli_saved_reply != NULL);
719 
720 		/*
721 		 * The reply to client was previously blocked, we will
722 		 * send again.
723 		 */
724 		if (send(cli->cli_sd, cli->cli_saved_reply,
725 		    cli->cli_saved_size, 0) != cli->cli_saved_size) {
726 			if (errno != EWOULDBLOCK) {
727 				logdebug("handle_req: send: %s",
728 				    strerror(errno));
729 				rc = ILB_STATUS_SEND;
730 				goto err_out;
731 			}
732 			goto blocked;
733 		}
734 		free(cli->cli_saved_reply);
735 		cli->cli_saved_reply = NULL;
736 		cli->cli_saved_size = 0;
737 	}
738 done:
739 	/* Re-associate with the event port for more requests. */
740 	cli->cli_ev = ILBD_EVENT_REQ;
741 	if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd,
742 	    POLLRDNORM, cli) == -1) {
743 		logperror("handle_req: port_associate(POLLRDNORM)");
744 		rc = ILB_STATUS_INTERNAL;
745 		goto err_out;
746 	}
747 	return;
748 
749 blocked:
750 	/* Re-associate with the event port. */
751 	cli->cli_ev = ILBD_EVENT_REP_OK;
752 	if (port_associate(ev_port, PORT_SOURCE_FD, cli->cli_sd, POLLWRNORM,
753 	    cli) == -1) {
754 		logperror("handle_req: port_associate(POLLWRNORM)");
755 		rc = ILB_STATUS_INTERNAL;
756 		goto err_out;
757 	}
758 	return;
759 
760 err_out:
761 	ilbd_free_cli(cli);
762 }
763 
764 static void
765 i_ilbd_read_config(int ev_port)
766 {
767 	logdebug("i_ilbd_read_config: port %d", ev_port);
768 	(void) ilbd_walk_sg_pgs(ilbd_create_sg, &ev_port, NULL);
769 	(void) ilbd_walk_hc_pgs(ilbd_create_hc, &ev_port, NULL);
770 	(void) ilbd_walk_rule_pgs(ilbd_create_rule, &ev_port, NULL);
771 }
772 
773 /*
774  * main event loop for ilbd
775  * asserts that argument 'listener' is a server socket ready to accept() on.
776  */
777 static void
778 main_loop(int listener)
779 {
780 	port_event_t		p_ev;
781 	int			ev_port, ev_port_obj;
782 	ilbd_event_obj_t	ev_obj;
783 	ilbd_timer_event_obj_t	timer_ev_obj;
784 
785 	ev_port = port_create();
786 	if (ev_port == -1) {
787 		logperror("main_loop: port_create failed");
788 		exit(-1);
789 	}
790 	ilbd_hc_timer_init(ev_port, &timer_ev_obj);
791 
792 	ev_obj.ev = ILBD_EVENT_NEW_REQ;
793 	if (port_associate(ev_port, PORT_SOURCE_FD, listener, POLLRDNORM,
794 	    &ev_obj) == -1) {
795 		logperror("main_loop: port_associate failed");
796 		exit(1);
797 	}
798 
799 	i_ilbd_read_config(ev_port);
800 	ilbd_hc_timer_update(&timer_ev_obj);
801 
802 	_NOTE(CONSTCOND)
803 	while (B_TRUE) {
804 		int r;
805 		ilbd_event_t event;
806 		ilbd_client_t *cli;
807 
808 		r = port_get(ev_port, &p_ev, NULL);
809 		if (r == -1) {
810 			if (errno == EINTR)
811 				continue;
812 			logperror("main_loop: port_get failed");
813 			break;
814 		}
815 
816 		ev_port_obj = p_ev.portev_object;
817 		event = ((ilbd_event_obj_t *)p_ev.portev_user)->ev;
818 
819 		switch (event) {
820 		case ILBD_EVENT_TIMER:
821 			ilbd_hc_timeout();
822 			break;
823 
824 		case ILBD_EVENT_PROBE:
825 			ilbd_hc_probe_return(ev_port, ev_port_obj,
826 			    p_ev.portev_events,
827 			    (ilbd_hc_probe_event_t *)p_ev.portev_user);
828 			break;
829 
830 		case ILBD_EVENT_NEW_REQ:
831 			assert(ev_port_obj == listener);
832 			/*
833 			 * An error happens in the listener.  Exit
834 			 * for now....
835 			 */
836 			if (p_ev.portev_events & (POLLHUP|POLLERR)) {
837 				logerr("main_loop: listener error");
838 				exit(1);
839 			}
840 			new_req(ev_port, ev_port_obj, &ev_obj);
841 			break;
842 
843 		case ILBD_EVENT_REP_OK:
844 		case ILBD_EVENT_REQ:
845 			cli = (ilbd_client_t *)p_ev.portev_user;
846 			assert(ev_port_obj == cli->cli_sd);
847 
848 			/*
849 			 * An error happens in the newly accepted
850 			 * client request.  Clean up the client.
851 			 * this also happens when client closes socket,
852 			 * so not necessarily a reason for alarm
853 			 */
854 			if (p_ev.portev_events & (POLLHUP|POLLERR)) {
855 				ilbd_free_cli(cli);
856 				break;
857 			}
858 
859 			handle_req(ev_port, event, cli);
860 			break;
861 
862 		default:
863 			logerr("main_loop: unknown event %d", event);
864 			exit(EXIT_FAILURE);
865 			break;
866 		}
867 
868 		ilbd_hc_timer_update(&timer_ev_obj);
869 	}
870 }
871 
872 static void
873 i_ilbd_setup_lists(void)
874 {
875 	i_setup_sg_hlist();
876 	i_setup_rule_hlist();
877 	i_ilbd_setup_hc_list();
878 }
879 
880 /*
881  * Usage message - call only during startup. it will print its
882  * message on stderr and exit
883  */
884 static void
885 Usage(char *name)
886 {
887 	(void) fprintf(stderr, gettext("Usage: %s [-d|--debug]\n"), name);
888 	exit(1);
889 }
890 
891 static void
892 print_version(char *name)
893 {
894 	(void) printf("%s %s\n", basename(name), ILBD_VERSION);
895 	(void) printf(gettext(ILBD_COPYRIGHT));
896 	exit(0);
897 }
898 
899 /*
900  * Increase the file descriptor limit for handling a lot of health check
901  * processes (each requires a pipe).
902  *
903  * Note that this function is called before ilbd becomes a daemon.  So
904  * we call perror(3C) to print out error message directly so that SMF
905  * can catch them.
906  */
907 static void
908 set_rlim(void)
909 {
910 	struct rlimit rlp;
911 
912 	if (getrlimit(RLIMIT_NOFILE, &rlp) == -1) {
913 		perror("ilbd: getrlimit");
914 		exit(errno);
915 	}
916 	rlp.rlim_cur = rlp.rlim_max;
917 	if (setrlimit(RLIMIT_NOFILE, &rlp) == -1) {
918 		perror("ilbd: setrlimit");
919 		exit(errno);
920 	}
921 }
922 
923 int
924 main(int argc, char **argv)
925 {
926 	int	s;
927 	int	c;
928 
929 	(void) setlocale(LC_ALL, "");
930 #if !defined(TEXT_DOMAIN)
931 #define	TEXT_DOMAIN "SYS_TEST"
932 #endif
933 	static const char daemon_dir[] = DAEMON_DIR;
934 
935 	(void) textdomain(TEXT_DOMAIN);
936 
937 	while ((c = getopt(argc, argv, ":V?d(debug)")) != -1) {
938 		switch ((char)c) {
939 		case '?': Usage(argv[0]);
940 			/* not reached */
941 			break;
942 		case 'V': print_version(argv[0]);
943 			/* not reached */
944 			break;
945 		case 'd': ilbd_enable_debug();
946 			break;
947 		default: Usage(argv[0]);
948 			/* not reached */
949 			break;
950 		}
951 	}
952 
953 	/*
954 	 * Whenever the daemon starts, it needs to start with a clean
955 	 * slate in the kernel. We need sys_ip_config privilege for
956 	 * this.
957 	 */
958 	ilbd_reset_kernel_state();
959 
960 	/* Increase the limit on the number of file descriptors. */
961 	set_rlim();
962 
963 	/*
964 	 * ilbd daemon starts off as root, just so it can create
965 	 * /var/run/daemon if one does not exist. After that is done
966 	 * the daemon switches to "daemon" uid. This is similar to what
967 	 * rpcbind does.
968 	 */
969 	if (mkdir(daemon_dir, DAEMON_DIR_MODE) == 0 || errno == EEXIST) {
970 		(void) chmod(daemon_dir, DAEMON_DIR_MODE);
971 		(void) chown(daemon_dir, DAEMON_UID, DAEMON_GID);
972 	} else {
973 		perror("main: mkdir failed");
974 		exit(errno);
975 	}
976 	/*
977 	 * Now lets switch ilbd as uid = daemon, gid = daemon with a
978 	 * trimmed down privilege set
979 	 */
980 	if (__init_daemon_priv(PU_RESETGROUPS | PU_LIMITPRIVS | PU_INHERITPRIVS,
981 	    DAEMON_UID, DAEMON_GID, PRIV_PROC_OWNER, PRIV_PROC_AUDIT,
982 	    PRIV_NET_ICMPACCESS, PRIV_SYS_IP_CONFIG, NULL) == -1) {
983 		(void) fprintf(stderr, "Insufficient privileges\n");
984 		exit(EXIT_FAILURE);
985 	}
986 
987 	/*
988 	 * Opens a PF_UNIX socket to the client. No privilege needed
989 	 * for this.
990 	 */
991 	s = ilbd_create_client_socket();
992 
993 	/*
994 	 * Daemonify if ilbd is not running with -d option
995 	 * Need proc_fork privilege for this
996 	 */
997 	if (!is_debugging_on()) {
998 		logdebug("daemonizing...");
999 		if (daemon(0, 0) != 0) {
1000 			logperror("daemon failed");
1001 			exit(EXIT_FAILURE);
1002 		}
1003 	}
1004 	(void) priv_set(PRIV_OFF, PRIV_INHERITABLE, PRIV_PROC_OWNER,
1005 	    PRIV_PROC_AUDIT, NULL);
1006 
1007 	/* if daemonified then set up syslog */
1008 	if (!is_debugging_on())
1009 		openlog("ilbd", LOG_PID, LOG_DAEMON);
1010 
1011 	i_ilbd_setup_lists();
1012 
1013 	main_loop(s);
1014 
1015 	/*
1016 	 * if we come here, then we experienced an error or a shutdown
1017 	 * indicator, so clean up after ourselves.
1018 	 */
1019 	logdebug("main(): terminating");
1020 
1021 	(void) remove(SOCKET_PATH);
1022 	ilbd_reset_kernel_state();
1023 
1024 	return (0);
1025 }
1026