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.
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.
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]
22 * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
23 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
27 #include <sys/types.h>
29 #include <sys/utsname.h>
31 #include <netinet/in.h> /* struct in_addr */
32 #include <netinet/dhcp.h>
34 #include <sys/socket.h>
35 #include <net/route.h>
36 #include <net/if_arp.h>
40 #include <arpa/inet.h>
41 #include <arpa/nameser.h>
46 #include <dhcp_hostconf.h>
47 #include <dhcp_inittab.h>
48 #include <dhcp_symbol.h>
55 #include "interface.h"
61 * this file contains utility functions that have no real better home
62 * of their own. they can largely be broken into six categories:
64 * o conversion functions -- functions to turn integers into strings,
65 * or to convert between units of a similar measure.
67 * o time and timer functions -- functions to handle time measurement
70 * o ipc-related functions -- functions to simplify the generation of
71 * ipc messages to the agent's clients.
73 * o signal-related functions -- functions to clean up the agent when
74 * it receives a signal.
76 * o routing table manipulation functions
78 * o true miscellany -- anything else
81 #define ETCNODENAME "/etc/nodename"
83 static boolean_t
is_fqdn(const char *);
84 static boolean_t
dhcp_assemble_fqdn(char *fqdnbuf
, size_t buflen
,
88 * pkt_type_to_string(): stringifies a packet type
90 * input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
91 * boolean_t: B_TRUE if IPv6
92 * output: const char *: the stringified packet type
96 pkt_type_to_string(uchar_t type
, boolean_t isv6
)
99 * note: the ordering in these arrays allows direct indexing of the
100 * table based on the RFC packet type value passed in.
103 static const char *v4types
[] = {
104 "BOOTP", "DISCOVER", "OFFER", "REQUEST", "DECLINE",
105 "ACK", "NAK", "RELEASE", "INFORM"
107 static const char *v6types
[] = {
108 NULL
, "SOLICIT", "ADVERTISE", "REQUEST",
109 "CONFIRM", "RENEW", "REBIND", "REPLY",
110 "RELEASE", "DECLINE", "RECONFIGURE", "INFORMATION-REQUEST",
111 "RELAY-FORW", "RELAY-REPL"
115 if (type
>= sizeof (v6types
) / sizeof (*v6types
) ||
116 v6types
[type
] == NULL
)
117 return ("<unknown>");
119 return (v6types
[type
]);
121 if (type
>= sizeof (v4types
) / sizeof (*v4types
) ||
122 v4types
[type
] == NULL
)
123 return ("<unknown>");
125 return (v4types
[type
]);
130 * monosec_to_string(): converts a monosec_t into a date string
132 * input: monosec_t: the monosec_t to convert
133 * output: const char *: the corresponding date string
137 monosec_to_string(monosec_t monosec
)
139 time_t time
= monosec_to_time(monosec
);
140 char *time_string
= ctime(&time
);
142 /* strip off the newline -- ugh, why, why, why.. */
143 time_string
[strlen(time_string
) - 1] = '\0';
144 return (time_string
);
148 * monosec(): returns a monotonically increasing time in seconds that
149 * is not affected by stime(2) or adjtime(2).
152 * output: monosec_t: the number of seconds since some time in the past
158 return (gethrtime() / NANOSEC
);
162 * monosec_to_time(): converts a monosec_t into real wall time
164 * input: monosec_t: the absolute monosec_t to convert
165 * output: time_t: the absolute time that monosec_t represents in wall time
169 monosec_to_time(monosec_t abs_monosec
)
171 return (abs_monosec
- monosec()) + time(NULL
);
175 * hrtime_to_monosec(): converts a hrtime_t to monosec_t
177 * input: hrtime_t: the time to convert
178 * output: monosec_t: the time in monosec_t
182 hrtime_to_monosec(hrtime_t hrtime
)
184 return (hrtime
/ NANOSEC
);
188 * print_server_msg(): prints a message from a DHCP server
190 * input: dhcp_smach_t *: the state machine the message is associated with
191 * const char *: the string to display
192 * uint_t: length of string
197 print_server_msg(dhcp_smach_t
*dsmp
, const char *msg
, uint_t msglen
)
200 dhcpmsg(MSG_INFO
, "%s: message from server: %.*s",
201 dsmp
->dsm_name
, msglen
, msg
);
206 * alrm_exit(): Signal handler for SIGARLM. terminates grandparent.
208 * input: int: signal the handler was called with.
218 if (sig
== SIGALRM
&& grandparent
!= 0)
219 exitval
= EXIT_SUCCESS
;
221 exitval
= EXIT_FAILURE
;
227 * daemonize(): daemonizes the process
230 * output: int: 1 on success, 0 on failure
237 * We've found that adoption takes sufficiently long that
238 * a dhcpinfo run after dhcpagent -a is started may occur
239 * before the agent is ready to process the request.
240 * The result is an error message and an unhappy user.
242 * The initial process now sleeps for DHCP_ADOPT_SLEEP,
243 * unless interrupted by a SIGALRM, in which case it
244 * exits immediately. This has the effect that the
245 * grandparent doesn't exit until the dhcpagent is ready
246 * to process requests. This defers the the balance of
247 * the system start-up script processing until the
248 * dhcpagent is ready to field requests.
250 * grandparent is only set for the adopt case; other
251 * cases do not require the wait.
254 if (grandparent
!= 0)
255 (void) signal(SIGALRM
, alrm_exit
);
263 if (grandparent
!= 0)
264 (void) signal(SIGALRM
, SIG_DFL
);
267 * setsid() makes us lose our controlling terminal,
268 * and become both a session leader and a process
275 * under POSIX, a session leader can accidentally
276 * (through open(2)) acquire a controlling terminal if
277 * it does not have one. just to be safe, fork again
278 * so we are not a session leader.
287 (void) signal(SIGHUP
, SIG_IGN
);
299 if (grandparent
!= 0) {
300 (void) signal(SIGCHLD
, SIG_IGN
);
302 * Note that we're not the agent here, so the DHCP
303 * logging subsystem hasn't been configured yet.
305 syslog(LOG_DEBUG
| LOG_DAEMON
, "dhcpagent: daemonize: "
306 "waiting for adoption to complete.");
307 if (sleep(DHCP_ADOPT_SLEEP
) == 0) {
308 syslog(LOG_WARNING
| LOG_DAEMON
,
309 "dhcpagent: daemonize: timed out awaiting "
312 syslog(LOG_DEBUG
| LOG_DAEMON
, "dhcpagent: daemonize: "
322 * update_default_route(): update the interface's default route
324 * input: int: the type of message; either RTM_ADD or RTM_DELETE
325 * struct in_addr: the default gateway to use
326 * const char *: the interface associated with the route
327 * int: any additional flags (besides RTF_STATIC and RTF_GATEWAY)
328 * output: boolean_t: B_TRUE on success, B_FALSE on failure
332 update_default_route(uint32_t ifindex
, int type
, struct in_addr
*gateway_nbo
,
336 struct rt_msghdr rm_mh
;
337 struct sockaddr_in rm_dst
;
338 struct sockaddr_in rm_gw
;
339 struct sockaddr_in rm_mask
;
340 struct sockaddr_dl rm_ifp
;
343 (void) memset(&rtmsg
, 0, sizeof (rtmsg
));
344 rtmsg
.rm_mh
.rtm_version
= RTM_VERSION
;
345 rtmsg
.rm_mh
.rtm_msglen
= sizeof (rtmsg
);
346 rtmsg
.rm_mh
.rtm_type
= type
;
347 rtmsg
.rm_mh
.rtm_pid
= getpid();
348 rtmsg
.rm_mh
.rtm_flags
= RTF_GATEWAY
| RTF_STATIC
| flags
;
349 rtmsg
.rm_mh
.rtm_addrs
= RTA_GATEWAY
| RTA_DST
| RTA_NETMASK
| RTA_IFP
;
351 rtmsg
.rm_gw
.sin_family
= AF_INET
;
352 rtmsg
.rm_gw
.sin_addr
= *gateway_nbo
;
354 rtmsg
.rm_dst
.sin_family
= AF_INET
;
355 rtmsg
.rm_dst
.sin_addr
.s_addr
= htonl(INADDR_ANY
);
357 rtmsg
.rm_mask
.sin_family
= AF_INET
;
358 rtmsg
.rm_mask
.sin_addr
.s_addr
= htonl(0);
360 rtmsg
.rm_ifp
.sdl_family
= AF_LINK
;
361 rtmsg
.rm_ifp
.sdl_index
= ifindex
;
363 return (write(rtsock_fd
, &rtmsg
, sizeof (rtmsg
)) == sizeof (rtmsg
));
367 * add_default_route(): add the default route to the given gateway
369 * input: const char *: the name of the interface associated with the route
370 * struct in_addr: the default gateway to add
371 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
375 add_default_route(uint32_t ifindex
, struct in_addr
*gateway_nbo
)
377 return (update_default_route(ifindex
, RTM_ADD
, gateway_nbo
, RTF_UP
));
381 * del_default_route(): deletes the default route to the given gateway
383 * input: const char *: the name of the interface associated with the route
384 * struct in_addr: if not INADDR_ANY, the default gateway to remove
385 * output: boolean_t: B_TRUE on success, B_FALSE on failure
389 del_default_route(uint32_t ifindex
, struct in_addr
*gateway_nbo
)
391 if (gateway_nbo
->s_addr
== htonl(INADDR_ANY
)) /* no router */
394 return (update_default_route(ifindex
, RTM_DELETE
, gateway_nbo
, 0));
398 * inactivity_shutdown(): shuts down agent if there are no state machines left
401 * input: iu_tq_t *: unused
408 inactivity_shutdown(iu_tq_t
*tqp
, void *arg
)
410 if (smach_count() > 0) /* shouldn't happen, but... */
413 dhcpmsg(MSG_VERBOSE
, "inactivity_shutdown: timed out");
415 iu_stop_handling_events(eh
, DHCP_REASON_INACTIVITY
, NULL
, NULL
);
419 * graceful_shutdown(): shuts down the agent gracefully
421 * input: int: the signal that caused graceful_shutdown to be called
426 graceful_shutdown(int sig
)
428 iu_stop_handling_events(eh
, (sig
== SIGTERM
? DHCP_REASON_TERMINATE
:
429 DHCP_REASON_SIGNAL
), drain_script
, NULL
);
433 * bind_sock(): binds a socket to a given IP address and port number
435 * input: int: the socket to bind
436 * in_port_t: the port number to bind to, host byte order
437 * in_addr_t: the address to bind to, host byte order
438 * output: boolean_t: B_TRUE on success, B_FALSE on failure
442 bind_sock(int fd
, in_port_t port_hbo
, in_addr_t addr_hbo
)
444 struct sockaddr_in sin
;
447 (void) memset(&sin
, 0, sizeof (struct sockaddr_in
));
448 sin
.sin_family
= AF_INET
;
449 sin
.sin_port
= htons(port_hbo
);
450 sin
.sin_addr
.s_addr
= htonl(addr_hbo
);
452 (void) setsockopt(fd
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof (int));
454 return (bind(fd
, (struct sockaddr
*)&sin
, sizeof (sin
)) == 0);
458 * bind_sock_v6(): binds a socket to a given IP address and port number
460 * input: int: the socket to bind
461 * in_port_t: the port number to bind to, host byte order
462 * in6_addr_t: the address to bind to, network byte order
463 * output: boolean_t: B_TRUE on success, B_FALSE on failure
467 bind_sock_v6(int fd
, in_port_t port_hbo
, const in6_addr_t
*addr_nbo
)
469 struct sockaddr_in6 sin6
;
472 (void) memset(&sin6
, 0, sizeof (struct sockaddr_in6
));
473 sin6
.sin6_family
= AF_INET6
;
474 sin6
.sin6_port
= htons(port_hbo
);
475 if (addr_nbo
!= NULL
) {
476 (void) memcpy(&sin6
.sin6_addr
, addr_nbo
,
477 sizeof (sin6
.sin6_addr
));
480 (void) setsockopt(fd
, SOL_SOCKET
, SO_REUSEADDR
, &on
, sizeof (int));
482 return (bind(fd
, (struct sockaddr
*)&sin6
, sizeof (sin6
)) == 0);
486 * iffile_to_hostname(): return the hostname contained on a line of the form
488 * [ ^I]*inet[ ^I]+hostname[\n]*\0
490 * in the file located at the specified path
492 * input: const char *: the path of the file to look in for the hostname
493 * output: const char *: the hostname at that path, or NULL on failure
496 #define IFLINE_MAX 1024 /* maximum length of a hostname.<if> line */
499 iffile_to_hostname(const char *path
)
502 static char ifline
[IFLINE_MAX
];
504 fp
= fopen(path
, "r");
509 * /etc/hostname.<if> may contain multiple ifconfig commands, but each
510 * such command is on a separate line (see the "while read ifcmds" code
511 * in /etc/init.d/inetinit). Thus we will read the file a line at a
512 * time, searching for a line of the form
514 * [ ^I]*inet[ ^I]+hostname[\n]*\0
516 * extract the host name from it, and check it for validity.
518 while (fgets(ifline
, sizeof (ifline
), fp
) != NULL
) {
521 if ((p
= strstr(ifline
, "inet")) != NULL
) {
522 if ((p
!= ifline
) && !isspace(p
[-1])) {
526 p
+= 4; /* skip over "inet" and expect spaces or tabs */
527 if ((*p
== '\n') || (*p
== '\0')) {
534 /* no need to read more of the file */
539 if ((nlptr
= strrchr(p
, '\n')) != NULL
)
541 if (strlen(p
) > MAXHOSTNAMELEN
) {
543 "iffile_to_hostname:"
544 " host name too long");
547 if (ipadm_is_valid_hostname(p
)) {
551 "iffile_to_hostname:"
552 " host name not valid");
567 * init_timer(): set up a DHCP timer
569 * input: dhcp_timer_t *: the timer to set up
574 init_timer(dhcp_timer_t
*dt
, lease_t startval
)
577 dt
->dt_start
= startval
;
581 * cancel_timer(): cancel a DHCP timer
583 * input: dhcp_timer_t *: the timer to cancel
584 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
588 cancel_timer(dhcp_timer_t
*dt
)
593 if (iu_cancel_timer(tq
, dt
->dt_id
, NULL
) == 1) {
602 * schedule_timer(): schedule a DHCP timer. Note that it must not be already
603 * running, and that we can't cancel here. If it were, and
604 * we did, we'd leak a reference to the callback argument.
606 * input: dhcp_timer_t *: the timer to schedule
607 * output: boolean_t: B_TRUE on success, B_FALSE otherwise
611 schedule_timer(dhcp_timer_t
*dt
, iu_tq_callback_t
*cbfunc
, void *arg
)
615 dt
->dt_id
= iu_schedule_timer(tq
, dt
->dt_start
, cbfunc
, arg
);
616 return (dt
->dt_id
!= -1);
620 * dhcpv6_status_code(): report on a DHCPv6 status code found in an option
623 * input: const dhcpv6_option_t *: pointer to option
624 * uint_t: option length
625 * const char **: error string (nul-terminated)
626 * const char **: message from server (unterminated)
627 * uint_t *: length of server message
628 * output: int: -1 on error, or >= 0 for a DHCPv6 status code
632 dhcpv6_status_code(const dhcpv6_option_t
*d6o
, uint_t olen
, const char **estr
,
633 const char **msg
, uint_t
*msglenp
)
636 static const char *v6_status
[] = {
639 "Server has no addresses available",
640 "Client record unavailable",
641 "Prefix inappropriate for link",
642 "Client must use multicast",
643 "No prefix available"
645 static char sbuf
[32];
652 olen
-= sizeof (*d6o
);
654 *estr
= "garbled status code";
658 *msg
= (const char *)(d6o
+ 1) + 2;
661 (void) memcpy(&status
, d6o
+ 1, sizeof (status
));
662 status
= ntohs(status
);
664 if (status
> DHCPV6_STAT_NOPREFIX
) {
665 (void) snprintf(sbuf
, sizeof (sbuf
), "status %u",
669 *estr
= v6_status
[status
];
676 write_lease_to_hostconf(dhcp_smach_t
*dsmp
)
681 hcfile
= ifname_to_hostconf(dsmp
->dsm_name
, dsmp
->dsm_isv6
);
682 plp
[0] = dsmp
->dsm_ack
;
683 plp
[1] = dsmp
->dsm_orig_ack
;
684 if (write_hostconf(dsmp
->dsm_name
, plp
, 2,
685 monosec_to_time(dsmp
->dsm_curstart_monosec
),
686 dsmp
->dsm_isv6
) != -1) {
687 dhcpmsg(MSG_DEBUG
, "wrote lease to %s", hcfile
);
688 } else if (errno
== EROFS
) {
689 dhcpmsg(MSG_DEBUG
, "%s is on a read-only file "
690 "system; not saving lease", hcfile
);
692 dhcpmsg(MSG_ERR
, "cannot write %s (reboot will "
693 "not use cached configuration)", hcfile
);
698 * Try to get a string from the first line of a file, up to but not
699 * including any space (0x20) or newline.
701 * input: const char *: file name;
702 * char *: allocated buffer space;
703 * size_t: space available in buf;
704 * output: boolean_t: B_TRUE if a non-empty string was written to buf;
709 dhcp_get_oneline(const char *filename
, char *buf
, size_t buflen
)
711 char value
[SYS_NMLN
], *c
;
714 if ((fd
= open(filename
, O_RDONLY
)) <= 0) {
715 dhcpmsg(MSG_DEBUG
, "dhcp_get_oneline: could not open %s",
719 if ((i
= read(fd
, value
, SYS_NMLN
- 1)) <= 0) {
720 dhcpmsg(MSG_WARNING
, "dhcp_get_oneline: no line in %s",
725 if ((c
= strchr(value
, '\n')) != NULL
)
727 if ((c
= strchr(value
, ' ')) != NULL
)
730 if (strlcpy(buf
, value
, buflen
) >= buflen
) {
731 dhcpmsg(MSG_WARNING
, "dhcp_get_oneline: too"
732 " long value, %s", value
);
739 return (*buf
!= '\0');
743 * Try to get the hostname from the /etc/nodename file. uname(2) cannot
744 * be used, because that is initialized after DHCP has solicited, in order
745 * to allow for the possibility that utsname.nodename can be set from
746 * DHCP Hostname. Here, though, we want to send a value specified
747 * advance of DHCP, so read /etc/nodename directly.
749 * input: char *: allocated buffer space;
750 * size_t: space available in buf;
751 * output: boolean_t: B_TRUE if a non-empty string was written to buf;
756 dhcp_get_nodename(char *buf
, size_t buflen
)
758 return (dhcp_get_oneline(ETCNODENAME
, buf
, buflen
));
762 * dhcp_add_hostname_opt(): Set CD_HOSTNAME option if REQUEST_HOSTNAME is
763 * affirmative and if 1) dsm_msg_reqhost is available;
764 * or 2) hostname is read from an extant
765 * /etc/hostname.<ifname> file; or 3) interface is
766 * primary and nodename(4) is defined.
768 * input: dhcp_pkt_t *: pointer to DHCP message being constructed;
769 * dhcp_smach_t *: pointer to interface DHCP state machine;
770 * output: B_TRUE if a client hostname was added; B_FALSE otherwise.
774 dhcp_add_hostname_opt(dhcp_pkt_t
*dpkt
, dhcp_smach_t
*dsmp
)
777 char nodename
[MAXNAMELEN
];
779 if (!df_get_bool(dsmp
->dsm_name
, dsmp
->dsm_isv6
, DF_REQUEST_HOSTNAME
))
782 dhcpmsg(MSG_DEBUG
, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME");
784 if (dsmp
->dsm_msg_reqhost
!= NULL
&&
785 ipadm_is_valid_hostname(dsmp
->dsm_msg_reqhost
)) {
786 reqhost
= dsmp
->dsm_msg_reqhost
;
788 char hostfile
[PATH_MAX
+ 1];
790 (void) snprintf(hostfile
, sizeof (hostfile
),
791 "/etc/hostname.%s", dsmp
->dsm_name
);
792 reqhost
= iffile_to_hostname(hostfile
);
795 if (reqhost
== NULL
&& (dsmp
->dsm_dflags
& DHCP_IF_PRIMARY
) &&
796 dhcp_get_nodename(nodename
, sizeof (nodename
))) {
800 if (reqhost
!= NULL
) {
801 free(dsmp
->dsm_reqhost
);
802 if ((dsmp
->dsm_reqhost
= strdup(reqhost
)) == NULL
)
803 dhcpmsg(MSG_WARNING
, "dhcp_add_hostname_opt: cannot"
804 " allocate memory for host name option");
807 if (dsmp
->dsm_reqhost
!= NULL
) {
808 dhcpmsg(MSG_DEBUG
, "dhcp_add_hostname_opt: host %s for %s",
809 dsmp
->dsm_reqhost
, dsmp
->dsm_name
);
810 (void) add_pkt_opt(dpkt
, CD_HOSTNAME
, dsmp
->dsm_reqhost
,
811 strlen(dsmp
->dsm_reqhost
));
814 dhcpmsg(MSG_DEBUG
, "dhcp_add_hostname_opt: no hostname for %s",
822 * dhcp_add_fqdn_opt(): Set client FQDN option if dhcp_assemble_fqdn()
823 * initializes an FQDN, or else do nothing.
825 * input: dhcp_pkt_t *: pointer to DHCP message being constructed;
826 * dhcp_smach_t *: pointer to interface DHCP state machine;
827 * output: B_TRUE if a client FQDN was added; B_FALSE otherwise.
831 dhcp_add_fqdn_opt(dhcp_pkt_t
*dpkt
, dhcp_smach_t
*dsmp
)
834 * RFC 4702 section 2:
836 * The format of the Client FQDN option is:
838 * Code Len Flags RCODE1 RCODE2 Domain Name
839 * +------+------+------+------+------+------+--
840 * | 81 | n | | | | ...
841 * +------+------+------+------+------+------+--
843 * Code and Len are distinct, and the remainder is in a single buffer,
844 * opt81, for Flags + (unused) RCODE1 and RCODE2 (all octets) and a
845 * potentially maximum-length domain name.
847 * The format of the Flags field is:
854 * where MBZ is ignored and NEOS are:
856 * S = 1 to request that "the server SHOULD perform the A RR (FQDN-to-
857 * address) DNS updates;
859 * O = 0, for a server-only response bit;
861 * E = 1 to indicate the domain name is in "canonical wire format,
862 * without compression (i.e., ns_name_pton2) .... This encoding SHOULD
863 * be used by clients ....";
865 * N = 0 to request that "the server SHALL perform DNS updates [of the
866 * PTR RR]." (1 would request SHALL NOT update).
869 const uint8_t S_BIT_POS
= 7;
870 const uint8_t E_BIT_POS
= 5;
871 const uint8_t S_BIT
= 1 << (7 - S_BIT_POS
);
872 const uint8_t E_BIT
= 1 << (7 - E_BIT_POS
);
873 const size_t OPT_FQDN_METALEN
= 3;
874 char fqdnbuf
[MAXNAMELEN
];
875 uchar_t enc_fqdnbuf
[MAXNAMELEN
];
876 uint8_t fqdnopt
[MAXNAMELEN
+ OPT_FQDN_METALEN
];
883 if (!dhcp_assemble_fqdn(fqdnbuf
, sizeof (fqdnbuf
), dsmp
))
886 /* encode the FQDN in canonical wire format */
888 if (ns_name_pton2(fqdnbuf
, enc_fqdnbuf
, sizeof (enc_fqdnbuf
),
890 dhcpmsg(MSG_WARNING
, "dhcp_add_fqdn_opt: error encoding domain"
891 " name %s", fqdnbuf
);
895 dhcpmsg(MSG_DEBUG
, "dhcp_add_fqdn_opt: interface FQDN is %s"
896 " for %s", fqdnbuf
, dsmp
->dsm_name
);
898 bzero(fqdnopt
, sizeof (fqdnopt
));
899 fqdncode
= CD_CLIENTFQDN
;
900 metalen
= OPT_FQDN_METALEN
;
901 *fqdnopt
= S_BIT
| E_BIT
;
902 (void) memcpy(fqdnopt
+ metalen
, enc_fqdnbuf
, len
);
903 (void) add_pkt_opt(dpkt
, fqdncode
, fqdnopt
, metalen
+ len
);
909 * dhcp_adopt_domainname(): Set namebuf if either dsm_dhcp_domainname or
910 * resolv's "default domain (deprecated)" is defined.
912 * input: char *: pointer to buffer to which domain name will be written;
913 * size_t length of buffer;
914 * dhcp_smach_t *: pointer to interface DHCP state machine;
915 * output: B_TRUE if namebuf was set to a valid domain name; B_FALSE
920 dhcp_adopt_domainname(char *namebuf
, size_t buflen
, dhcp_smach_t
*dsmp
)
922 const char *domainname
;
923 struct __res_state res_state
;
926 domainname
= dsmp
->dsm_dhcp_domainname
;
928 if (ipadm_is_nil_hostname(domainname
)) {
930 * fall back to resolv's "default domain (deprecated)"
932 bzero(&res_state
, sizeof (struct __res_state
));
934 if ((lasterrno
= res_ninit(&res_state
)) != 0) {
935 dhcpmsg(MSG_WARNING
, "dhcp_adopt_domainname: error %d"
936 " initializing resolver", lasterrno
);
941 if (!ipadm_is_nil_hostname(res_state
.defdname
))
942 domainname
= res_state
.defdname
;
944 /* N.b. res_state.defdname survives the following call */
945 res_ndestroy(&res_state
);
948 if (domainname
== NULL
)
951 if (strlcpy(namebuf
, domainname
, buflen
) >= buflen
) {
953 "dhcp_adopt_domainname: too long adopted domain"
954 " name %s for %s", domainname
, dsmp
->dsm_name
);
962 * dhcp_pick_domainname(): Set namebuf if DNS_DOMAINNAME is defined in
963 * /etc/default/dhcpagent or if dhcp_adopt_domainname()
966 * input: char *: pointer to buffer to which domain name will be written;
967 * size_t length of buffer;
968 * dhcp_smach_t *: pointer to interface DHCP state machine;
969 * output: B_TRUE if namebuf was set to a valid domain name; B_FALSE
974 dhcp_pick_domainname(char *namebuf
, size_t buflen
, dhcp_smach_t
*dsmp
)
976 const char *domainname
;
979 * Try to use a static DNS_DOMAINNAME if defined in
980 * /etc/default/dhcpagent.
982 domainname
= df_get_string(dsmp
->dsm_name
, dsmp
->dsm_isv6
,
984 if (!ipadm_is_nil_hostname(domainname
)) {
985 if (strlcpy(namebuf
, domainname
, buflen
) >= buflen
) {
986 dhcpmsg(MSG_WARNING
, "dhcp_pick_domainname: too long"
987 " DNS_DOMAINNAME %s for %s", domainname
,
992 } else if (df_get_bool(dsmp
->dsm_name
, dsmp
->dsm_isv6
,
993 DF_ADOPT_DOMAINNAME
)) {
994 return (dhcp_adopt_domainname(namebuf
, buflen
, dsmp
));
1001 * dhcp_assemble_fqdn(): Set fqdnbuf if REQUEST_FQDN is set and
1002 * either a host name was sent in the IPC message (e.g.,
1003 * from ipadm(1M) -h,--reqhost) or the interface is
1004 * primary and a nodename(4) is defined. If the host
1005 * name is not already fully qualified per is_fqdn(),
1006 * then dhcp_pick_domainname() is tried to select a
1007 * domain to be used to construct an FQDN.
1009 * input: char *: pointer to buffer to which FQDN will be written;
1010 * size_t length of buffer;
1011 * dhcp_smach_t *: pointer to interface DHCP state machine;
1012 * output: B_TRUE if fqdnbuf was assigned a valid FQDN; B_FALSE otherwise.
1016 dhcp_assemble_fqdn(char *fqdnbuf
, size_t buflen
, dhcp_smach_t
*dsmp
)
1018 char nodename
[MAXNAMELEN
], *reqhost
;
1022 if (!df_get_bool(dsmp
->dsm_name
, dsmp
->dsm_isv6
, DF_REQUEST_FQDN
))
1025 dhcpmsg(MSG_DEBUG
, "dhcp_assemble_fqdn: DF_REQUEST_FQDN");
1027 /* It's convenient to ensure fqdnbuf is always null-terminated */
1028 bzero(fqdnbuf
, buflen
);
1030 reqhost
= dsmp
->dsm_msg_reqhost
;
1031 if (ipadm_is_nil_hostname(reqhost
) &&
1032 (dsmp
->dsm_dflags
& DHCP_IF_PRIMARY
) &&
1033 dhcp_get_nodename(nodename
, sizeof (nodename
))) {
1037 if (ipadm_is_nil_hostname(reqhost
)) {
1039 "dhcp_assemble_fqdn: no interface reqhost for %s",
1044 if ((pos
= strlcpy(fqdnbuf
, reqhost
, buflen
)) >= buflen
) {
1045 dhcpmsg(MSG_WARNING
, "dhcp_assemble_fqdn: too long reqhost %s"
1046 " for %s", reqhost
, dsmp
->dsm_name
);
1051 * If not yet FQDN, construct if possible
1053 if (!is_fqdn(reqhost
)) {
1054 char domainname
[MAXNAMELEN
];
1057 if (!dhcp_pick_domainname(domainname
, sizeof (domainname
),
1060 "dhcp_assemble_fqdn: no domain name for %s",
1066 * Finish constructing FQDN. Account for space needed to hold a
1067 * separator '.' and a terminating '.'.
1069 len
= strlen(domainname
);
1070 needdots
= 1 + (domainname
[len
- 1] != '.');
1072 if (pos
+ len
+ needdots
>= buflen
) {
1073 dhcpmsg(MSG_WARNING
, "dhcp_assemble_fqdn: too long"
1074 " FQDN %s.%s for %s", fqdnbuf
, domainname
,
1079 /* add separator and then domain name */
1080 fqdnbuf
[pos
++] = '.';
1081 if (strlcpy(fqdnbuf
+ pos
, domainname
, buflen
- pos
) >=
1083 /* shouldn't get here as we checked above */
1088 /* ensure the final character is '.' */
1090 fqdnbuf
[pos
++] = '.'; /* following is already zeroed */
1093 if (!ipadm_is_valid_hostname(fqdnbuf
)) {
1094 dhcpmsg(MSG_WARNING
, "dhcp_assemble_fqdn: invalid FQDN %s"
1095 " for %s", fqdnbuf
, dsmp
->dsm_name
);
1103 * is_fqdn() : Determine if the `hostname' can be considered as a Fully
1104 * Qualified Domain Name by being "rooted" (i.e., ending in '.')
1105 * or by containing at least three DNS labels (e.g.,
1108 * input: const char *: the hostname to inspect;
1109 * output: boolean_t: B_TRUE if `hostname' is not NULL satisfies the
1110 * criteria above; otherwise, B_FALSE;
1114 is_fqdn(const char *hostname
)
1119 if (hostname
== NULL
)
1122 i
= strlen(hostname
);
1123 if (i
> 0 && hostname
[i
- 1] == '.')
1128 while ((c
= strchr(c
, '.')) != NULL
) {
1133 /* at least two separators is inferred to be fully-qualified */
1138 * terminate_at_space(): Reset the first space, 0x20, to 0x0 in the
1141 * input: char *: NULL or a null-terminated string;
1146 terminate_at_space(char *value
)
1148 if (value
!= NULL
) {
1151 sp
= strchr(value
, ' ');
1158 * get_offered_domainname_v4(): decode a defined v4 DNSdmain value if it
1159 * exists to return a copy of the domain
1162 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from;
1163 * PKT_LIST *: the best packet to be used to construct a REQUEST;
1164 * output: char *: NULL or a copy of the domain name ('\0' terminated);
1168 get_offered_domainname_v4(PKT_LIST
*offer
)
1170 char *domainname
= NULL
;
1173 if ((opt
= offer
->opts
[CD_DNSDOMAIN
]) != NULL
) {
1175 dhcp_symbol_t
*symp
;
1177 valptr
= (uchar_t
*)opt
+ DHCP_OPT_META_LEN
;
1179 symp
= inittab_getbycode(
1180 ITAB_CAT_STANDARD
, ITAB_CONS_INFO
, opt
->code
);
1182 domainname
= inittab_decode(symp
, valptr
,
1184 terminate_at_space(domainname
);
1189 return (domainname
);
1193 * save_domainname(): assign dsm_dhcp_domainname from
1194 * get_offered_domainname_v4 or leave the field NULL if no
1195 * option is present.
1197 * input: dhcp_smach_t *: the state machine REQUESTs are being sent from;
1198 * PKT_LIST *: the best packet to be used to construct a REQUEST;
1203 save_domainname(dhcp_smach_t
*dsmp
, PKT_LIST
*offer
)
1205 char *domainname
= NULL
;
1207 free(dsmp
->dsm_dhcp_domainname
);
1208 dsmp
->dsm_dhcp_domainname
= NULL
;
1210 if (!dsmp
->dsm_isv6
) {
1211 domainname
= get_offered_domainname_v4(offer
);
1214 dsmp
->dsm_dhcp_domainname
= domainname
;