Print this page
7388 Support DHCP Client FQDN. Allow IAID/DUID for all v4.


   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  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.

  23  */
  24 
  25 #include <unistd.h>
  26 #include <sys/types.h>
  27 #include <sys/stat.h>

  28 #include <stdlib.h>
  29 #include <netinet/in.h>           /* struct in_addr */
  30 #include <netinet/dhcp.h>
  31 #include <signal.h>
  32 #include <sys/socket.h>
  33 #include <net/route.h>
  34 #include <net/if_arp.h>
  35 #include <string.h>
  36 #include <dhcpmsg.h>
  37 #include <ctype.h>



  38 #include <netdb.h>
  39 #include <fcntl.h>
  40 #include <stdio.h>
  41 #include <dhcp_hostconf.h>



  42 
  43 #include "states.h"
  44 #include "agent.h"
  45 #include "interface.h"
  46 #include "util.h"
  47 #include "packet.h"

  48 
  49 /*
  50  * this file contains utility functions that have no real better home
  51  * of their own.  they can largely be broken into six categories:
  52  *
  53  *  o  conversion functions -- functions to turn integers into strings,
  54  *     or to convert between units of a similar measure.
  55  *
  56  *  o  time and timer functions -- functions to handle time measurement
  57  *     and events.
  58  *
  59  *  o  ipc-related functions -- functions to simplify the generation of
  60  *     ipc messages to the agent's clients.
  61  *
  62  *  o  signal-related functions -- functions to clean up the agent when
  63  *     it receives a signal.
  64  *
  65  *  o  routing table manipulation functions
  66  *
  67  *  o  true miscellany -- anything else
  68  */
  69 






  70 /*
  71  * pkt_type_to_string(): stringifies a packet type
  72  *
  73  *   input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
  74  *          boolean_t: B_TRUE if IPv6
  75  *  output: const char *: the stringified packet type
  76  */
  77 
  78 const char *
  79 pkt_type_to_string(uchar_t type, boolean_t isv6)
  80 {
  81         /*
  82          * note: the ordering in these arrays allows direct indexing of the
  83          *       table based on the RFC packet type value passed in.
  84          */
  85 
  86         static const char *v4types[] = {
  87                 "BOOTP",  "DISCOVER", "OFFER",   "REQUEST", "DECLINE",
  88                 "ACK",    "NAK",      "RELEASE", "INFORM"
  89         };


 449 boolean_t
 450 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
 451 {
 452         struct sockaddr_in6     sin6;
 453         int                     on = 1;
 454 
 455         (void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
 456         sin6.sin6_family = AF_INET6;
 457         sin6.sin6_port   = htons(port_hbo);
 458         if (addr_nbo != NULL) {
 459                 (void) memcpy(&sin6.sin6_addr, addr_nbo,
 460                     sizeof (sin6.sin6_addr));
 461         }
 462 
 463         (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
 464 
 465         return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
 466 }
 467 
 468 /*
 469  * valid_hostname(): check whether a string is a valid hostname
 470  *
 471  *   input: const char *: the string to verify as a hostname
 472  *  output: boolean_t: B_TRUE if the string is a valid hostname
 473  *
 474  * Note that we accept both host names beginning with a digit and
 475  * those containing hyphens.  Neither is strictly legal according
 476  * to the RFCs, but both are in common practice, so we endeavour
 477  * to not break what customers are using.
 478  */
 479 
 480 static boolean_t
 481 valid_hostname(const char *hostname)
 482 {
 483         unsigned int i;
 484 
 485         for (i = 0; hostname[i] != '\0'; i++) {
 486 
 487                 if (isalpha(hostname[i]) || isdigit(hostname[i]) ||
 488                     (((hostname[i] == '-') || (hostname[i] == '.')) && (i > 0)))
 489                         continue;
 490 
 491                 return (B_FALSE);
 492         }
 493 
 494         return (i > 0);
 495 }
 496 
 497 /*
 498  * iffile_to_hostname(): return the hostname contained on a line of the form
 499  *
 500  * [ ^I]*inet[ ^I]+hostname[\n]*\0
 501  *
 502  * in the file located at the specified path
 503  *
 504  *   input: const char *: the path of the file to look in for the hostname
 505  *  output: const char *: the hostname at that path, or NULL on failure
 506  */
 507 
 508 #define IFLINE_MAX      1024    /* maximum length of a hostname.<if> line */
 509 
 510 const char *
 511 iffile_to_hostname(const char *path)
 512 {
 513         FILE            *fp;
 514         static char     ifline[IFLINE_MAX];
 515 
 516         fp = fopen(path, "r");
 517         if (fp == NULL)


 539                         if ((*p == '\n') || (*p == '\0')) {
 540                                 (void) fclose(fp);
 541                                 return (NULL);
 542                         }
 543                         if (isspace(*p)) {
 544                                 char *nlptr;
 545 
 546                                 /* no need to read more of the file */
 547                                 (void) fclose(fp);
 548 
 549                                 while (isspace(*p))
 550                                         p++;
 551                                 if ((nlptr = strrchr(p, '\n')) != NULL)
 552                                         *nlptr = '\0';
 553                                 if (strlen(p) > MAXHOSTNAMELEN) {
 554                                         dhcpmsg(MSG_WARNING,
 555                                             "iffile_to_hostname:"
 556                                             " host name too long");
 557                                         return (NULL);
 558                                 }
 559                                 if (valid_hostname(p)) {
 560                                         return (p);
 561                                 } else {
 562                                         dhcpmsg(MSG_WARNING,
 563                                             "iffile_to_hostname:"
 564                                             " host name not valid");
 565                                         return (NULL);
 566                                 }
 567                         } else {
 568                                 (void) fclose(fp);
 569                                 return (NULL);
 570                         }
 571                 }
 572         }
 573 
 574         (void) fclose(fp);
 575         return (NULL);
 576 }
 577 
 578 /*
 579  * init_timer(): set up a DHCP timer


 687 void
 688 write_lease_to_hostconf(dhcp_smach_t *dsmp)
 689 {
 690         PKT_LIST *plp[2];
 691         const char *hcfile;
 692 
 693         hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
 694         plp[0] = dsmp->dsm_ack;
 695         plp[1] = dsmp->dsm_orig_ack;
 696         if (write_hostconf(dsmp->dsm_name, plp, 2,
 697             monosec_to_time(dsmp->dsm_curstart_monosec),
 698             dsmp->dsm_isv6) != -1) {
 699                 dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
 700         } else if (errno == EROFS) {
 701                 dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
 702                     "system; not saving lease", hcfile);
 703         } else {
 704                 dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
 705                     "not use cached configuration)", hcfile);
 706         }




























































































































































































































































































































































































































 707 }


   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  * Copyright (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
  23  * Copyright (c) 2016, Chris Fraire <cfraire@me.com>.
  24  */
  25 
  26 #include <unistd.h>
  27 #include <sys/types.h>
  28 #include <sys/stat.h>
  29 #include <sys/utsname.h>
  30 #include <stdlib.h>
  31 #include <netinet/in.h>           /* struct in_addr */
  32 #include <netinet/dhcp.h>
  33 #include <signal.h>
  34 #include <sys/socket.h>
  35 #include <net/route.h>
  36 #include <net/if_arp.h>
  37 #include <string.h>
  38 #include <dhcpmsg.h>
  39 #include <ctype.h>
  40 #include <arpa/inet.h>
  41 #include <arpa/nameser.h>
  42 #include <resolv.h>
  43 #include <netdb.h>
  44 #include <fcntl.h>
  45 #include <stdio.h>
  46 #include <dhcp_hostconf.h>
  47 #include <limits.h>
  48 #include <strings.h>
  49 #include <libipadm.h>
  50 
  51 #include "states.h"
  52 #include "agent.h"
  53 #include "interface.h"
  54 #include "util.h"
  55 #include "packet.h"
  56 #include "defaults.h"
  57 
  58 /*
  59  * this file contains utility functions that have no real better home
  60  * of their own.  they can largely be broken into six categories:
  61  *
  62  *  o  conversion functions -- functions to turn integers into strings,
  63  *     or to convert between units of a similar measure.
  64  *
  65  *  o  time and timer functions -- functions to handle time measurement
  66  *     and events.
  67  *
  68  *  o  ipc-related functions -- functions to simplify the generation of
  69  *     ipc messages to the agent's clients.
  70  *
  71  *  o  signal-related functions -- functions to clean up the agent when
  72  *     it receives a signal.
  73  *
  74  *  o  routing table manipulation functions
  75  *
  76  *  o  true miscellany -- anything else
  77  */
  78 
  79 #define ETCNODENAME             "/etc/nodename"
  80 #define ETCDEFAULTDOMAIN        "/etc/defaultdomain"
  81 
  82 static  boolean_t       is_fqdn(const char *);
  83 static int                      dhcp_assemble_fqdn(dhcp_smach_t *dsmp);
  84 
  85 /*
  86  * pkt_type_to_string(): stringifies a packet type
  87  *
  88  *   input: uchar_t: a DHCP packet type value, RFC 2131 or 3315
  89  *          boolean_t: B_TRUE if IPv6
  90  *  output: const char *: the stringified packet type
  91  */
  92 
  93 const char *
  94 pkt_type_to_string(uchar_t type, boolean_t isv6)
  95 {
  96         /*
  97          * note: the ordering in these arrays allows direct indexing of the
  98          *       table based on the RFC packet type value passed in.
  99          */
 100 
 101         static const char *v4types[] = {
 102                 "BOOTP",  "DISCOVER", "OFFER",   "REQUEST", "DECLINE",
 103                 "ACK",    "NAK",      "RELEASE", "INFORM"
 104         };


 464 boolean_t
 465 bind_sock_v6(int fd, in_port_t port_hbo, const in6_addr_t *addr_nbo)
 466 {
 467         struct sockaddr_in6     sin6;
 468         int                     on = 1;
 469 
 470         (void) memset(&sin6, 0, sizeof (struct sockaddr_in6));
 471         sin6.sin6_family = AF_INET6;
 472         sin6.sin6_port   = htons(port_hbo);
 473         if (addr_nbo != NULL) {
 474                 (void) memcpy(&sin6.sin6_addr, addr_nbo,
 475                     sizeof (sin6.sin6_addr));
 476         }
 477 
 478         (void) setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (int));
 479 
 480         return (bind(fd, (struct sockaddr *)&sin6, sizeof (sin6)) == 0);
 481 }
 482 
 483 /*





























 484  * iffile_to_hostname(): return the hostname contained on a line of the form
 485  *
 486  * [ ^I]*inet[ ^I]+hostname[\n]*\0
 487  *
 488  * in the file located at the specified path
 489  *
 490  *   input: const char *: the path of the file to look in for the hostname
 491  *  output: const char *: the hostname at that path, or NULL on failure
 492  */
 493 
 494 #define IFLINE_MAX      1024    /* maximum length of a hostname.<if> line */
 495 
 496 const char *
 497 iffile_to_hostname(const char *path)
 498 {
 499         FILE            *fp;
 500         static char     ifline[IFLINE_MAX];
 501 
 502         fp = fopen(path, "r");
 503         if (fp == NULL)


 525                         if ((*p == '\n') || (*p == '\0')) {
 526                                 (void) fclose(fp);
 527                                 return (NULL);
 528                         }
 529                         if (isspace(*p)) {
 530                                 char *nlptr;
 531 
 532                                 /* no need to read more of the file */
 533                                 (void) fclose(fp);
 534 
 535                                 while (isspace(*p))
 536                                         p++;
 537                                 if ((nlptr = strrchr(p, '\n')) != NULL)
 538                                         *nlptr = '\0';
 539                                 if (strlen(p) > MAXHOSTNAMELEN) {
 540                                         dhcpmsg(MSG_WARNING,
 541                                             "iffile_to_hostname:"
 542                                             " host name too long");
 543                                         return (NULL);
 544                                 }
 545                                 if (ipadm_is_valid_hostname(p)) {
 546                                         return (p);
 547                                 } else {
 548                                         dhcpmsg(MSG_WARNING,
 549                                             "iffile_to_hostname:"
 550                                             " host name not valid");
 551                                         return (NULL);
 552                                 }
 553                         } else {
 554                                 (void) fclose(fp);
 555                                 return (NULL);
 556                         }
 557                 }
 558         }
 559 
 560         (void) fclose(fp);
 561         return (NULL);
 562 }
 563 
 564 /*
 565  * init_timer(): set up a DHCP timer


 673 void
 674 write_lease_to_hostconf(dhcp_smach_t *dsmp)
 675 {
 676         PKT_LIST *plp[2];
 677         const char *hcfile;
 678 
 679         hcfile = ifname_to_hostconf(dsmp->dsm_name, dsmp->dsm_isv6);
 680         plp[0] = dsmp->dsm_ack;
 681         plp[1] = dsmp->dsm_orig_ack;
 682         if (write_hostconf(dsmp->dsm_name, plp, 2,
 683             monosec_to_time(dsmp->dsm_curstart_monosec),
 684             dsmp->dsm_isv6) != -1) {
 685                 dhcpmsg(MSG_DEBUG, "wrote lease to %s", hcfile);
 686         } else if (errno == EROFS) {
 687                 dhcpmsg(MSG_DEBUG, "%s is on a read-only file "
 688                     "system; not saving lease", hcfile);
 689         } else {
 690                 dhcpmsg(MSG_ERR, "cannot write %s (reboot will "
 691                     "not use cached configuration)", hcfile);
 692         }
 693 }
 694 
 695 /*
 696  * Try to get a string from the first line of a file, up to but not
 697  * including any space (0x20) or newline.
 698  */
 699 
 700 static int
 701 dhcp_get_oneline(const char *filename, char *buf, size_t buflen)
 702 {
 703         char    value[SYS_NMLN], *c;
 704         int             fd, i;
 705 
 706         if ((fd = open(filename, O_RDONLY)) <= 0) {
 707                 dhcpmsg(MSG_DEBUG, "dhcp_get_oneline: could not open %s",
 708                     filename);
 709                 *buf = '\0';
 710         } else {
 711                 if ((i = read(fd, value, SYS_NMLN - 1)) <= 0) {
 712                         dhcpmsg(MSG_WARNING, "dhcp_get_oneline: no line in %s",
 713                             filename);
 714                         *buf = '\0';
 715                 } else {
 716                         value[i] = '\0';
 717                         if ((c = strchr(value, '\n')) != NULL)
 718                                 *c = '\0';
 719                         if ((c = strchr(value, ' ')) != NULL)
 720                                 *c = '\0';
 721 
 722                         if (strlcpy(buf, value, buflen) >= buflen) {
 723                                 dhcpmsg(MSG_WARNING, "dhcp_get_oneline: too long value, %s",
 724                                     value);
 725                                 *buf = '\0';
 726                         }
 727                 }
 728                 (void) close(fd);
 729         }
 730 
 731         return (*buf != '\0' ? 0 : -1);
 732 }
 733 
 734 /*
 735  * Try to get the hostname from the /etc/nodename file. uname(2) cannot
 736  * be used, because that is initialized after DHCP has solicited, in order
 737  * to allow for the possibility that utsname.nodename can be set from
 738  * DHCP Hostname. Here, though, we want to send a value specified
 739  * advance of DHCP, so read /etc/nodename directly.
 740  */
 741 
 742 static int
 743 dhcp_get_nodename(char *buf, size_t buflen)
 744 {
 745         return dhcp_get_oneline(ETCNODENAME, buf, buflen);
 746 }
 747 
 748 /*
 749  * Try to get the value from the /etc/defaultdomain file. libnsl's
 750  * domainname() cannot be used, because that is initialized after DHCP has
 751  * solicited. Here, though, we want to send a value specified in advance
 752  * of DHCP, so read /etc/defaultdomain directly.
 753  */
 754 
 755 static int
 756 dhcp_get_defaultdomain(char *buf, size_t buflen)
 757 {
 758         return dhcp_get_oneline(ETCDEFAULTDOMAIN, buf, buflen);
 759 }
 760 
 761 /*
 762  * dhcp_add_hostname_opt(): Set CD_HOSTNAME option if REQUEST_HOSTNAME is
 763  *                       affirmative and if 1) dsm_msg_reqhost is available; or
 764  *                       2) hostname is read from an extant /etc/hostname.<ifname>
 765  *                       file; or 3) interface is primary and nodename(4) is defined.
 766  *
 767  *   input: dhcp_pkt_t *: pointer to DHCP message being constructed;
 768  *          dhcp_smach_t *: pointer to interface DHCP state machine;
 769  */
 770 
 771 int
 772 dhcp_add_hostname_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
 773 {
 774         const char      *reqhost;
 775         char    nodename[MAXNAMELEN];
 776 
 777         if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_HOSTNAME))
 778                 return (0);
 779 
 780         dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: DF_REQUEST_HOSTNAME");
 781 
 782         if (dsmp->dsm_msg_reqhost != NULL
 783             && ipadm_is_valid_hostname(dsmp->dsm_msg_reqhost)) {
 784                 reqhost = dsmp->dsm_msg_reqhost;
 785         } else {
 786                 char            hostfile[PATH_MAX + 1];
 787 
 788                 (void) snprintf(hostfile, sizeof (hostfile),
 789                     "/etc/hostname.%s", dsmp->dsm_name);
 790                 reqhost = iffile_to_hostname(hostfile);
 791         }
 792 
 793         if (reqhost == NULL && (dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
 794             dhcp_get_nodename(nodename, sizeof (nodename)) == 0) {
 795                 reqhost = nodename;
 796         }
 797 
 798         if (reqhost != NULL) {
 799                 free(dsmp->dsm_reqhost);
 800                 if ((dsmp->dsm_reqhost = strdup(reqhost)) == NULL)
 801                         dhcpmsg(MSG_WARNING, "dhcp_add_hostname_opt: cannot allocate "
 802                             "memory for host name option");
 803         }
 804 
 805         if (dsmp->dsm_reqhost != NULL) {
 806                 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: host %s for %s",
 807                     dsmp->dsm_reqhost, dsmp->dsm_name);
 808                 (void) add_pkt_opt(dpkt, CD_HOSTNAME, dsmp->dsm_reqhost,
 809                     strlen(dsmp->dsm_reqhost));
 810                 return 1;
 811         }
 812         else {
 813                 dhcpmsg(MSG_DEBUG, "dhcp_add_hostname_opt: no hostname for %s",
 814                     dsmp->dsm_name);
 815         }
 816 
 817         return (0);
 818 }
 819 
 820 /*
 821  * dhcp_add_fqdn_opt(): Set CD_CLIENTFQDN option if dsm_reqfqdn is not NULL
 822  *                       or if dhcp_assemble_fqdn() initializes it. If dsm_reqfqdn
 823  *                       cannot be set, no option will be added.
 824  *
 825  *   input: dhcp_pkt_t *: pointer to DHCP message being constructed;
 826  *          dhcp_smach_t *: pointer to interface DHCP state machine;
 827  *  output: 0 if a CLIENT_FQDN was added; non-zero if otherwise;
 828  */
 829 
 830 int
 831 dhcp_add_fqdn_opt(dhcp_pkt_t *dpkt, dhcp_smach_t *dsmp)
 832 {
 833         /*
 834          * RFC 4702 section 2:
 835          *
 836          * The format of the Client FQDN option is:
 837          *
 838          *  Code   Len    Flags  RCODE1 RCODE2   Domain Name
 839          * +------+------+------+------+------+------+--
 840          * |  81  |   n  |      |      |      |       ...
 841          * +------+------+------+------+------+------+--
 842          *
 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.
 846          *
 847          * The format of the Flags field is:
 848          *
 849          *  0 1 2 3 4 5 6 7
 850          * +-+-+-+-+-+-+-+-+
 851          * |  MBZ  |N|E|O|S|
 852          * +-+-+-+-+-+-+-+-+
 853          *
 854          * where MBZ is ignored and NEOS are:
 855          *
 856          * S = 1 to request that "the server SHOULD perform the A RR (FQDN-to-
 857          * address) DNS updates;
 858          *
 859          * O = 0, for a server-only response bit;
 860          *
 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 ....";
 864          *
 865          * N = 0 to request that "the server SHALL perform DNS updates [of the
 866          * PTR RR]." (1 would request SHALL NOT update).
 867          *
 868          * The format of the DHCPv6 Client FQDN option is shown below:
 869          *
 870          *     0                   1                   2                   3
 871          *     0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 872          *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 873          *    |          OPTION_FQDN          |         option-len            |
 874          *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 875          *    |   flags       |                                               |
 876          *    +-+-+-+-+-+-+-+-+                                               |
 877          *    .                                                               .
 878          *    .                          domain-name                          .
 879          *    .                                                               .
 880          *    +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 881          *
 882          *      option-code      OPTION_CLIENT_FQDN (39)
 883          *
 884          *      option-len       1 + length of domain name
 885          *
 886          *      flags            flag bits used between client and server to
 887          *                       negotiate who performs which updates
 888          *
 889          *      domain-name      the partial or fully qualified domain name
 890          *                       (with length option-len - 1)
 891          *
 892          *    The DHCPv6 format of the Flags field is:
 893          *
 894          *         0 1 2 3 4 5 6 7
 895          *        +-+-+-+-+-+-+-+-+
 896          *        |  MBZ    |N|O|S|
 897          *        +-+-+-+-+-+-+-+-+
 898          */
 899 
 900         const uint8_t   S_BITPOS = 7 - 7;
 901         const uint8_t   E_BITPOS = 7 - 5;
 902         const size_t    OPT_FQDN_METALEN = 3, OPT_V6_FQDN_METALEN = 1;
 903         uint_t          fqdncode;
 904         u_char          enc_fqdnbuf[MAXNAMELEN];
 905         uint8_t         fqdnopt[MAXNAMELEN + OPT_FQDN_METALEN];
 906         size_t          len, metalen;
 907 
 908         if (dsmp->dsm_reqfqdn == NULL && dhcp_assemble_fqdn(dsmp) != 0)
 909                 return (-1);
 910 
 911         /* encode the FQDN in canonical wire format */
 912 
 913         if (ns_name_pton2(dsmp->dsm_reqfqdn, enc_fqdnbuf, sizeof (enc_fqdnbuf),
 914             &len) < 0) {
 915                 dhcpmsg(MSG_WARNING, "dhcp_add_fqdn_opt: error encoding domain"
 916                     " name %s", dsmp->dsm_reqfqdn);
 917                 return (-1);
 918         }
 919 
 920         dhcpmsg(MSG_DEBUG, "dhcp_add_fqdn_opt: interface FQDN is %s"
 921             " for %s", dsmp->dsm_reqfqdn, dsmp->dsm_name);
 922 
 923         bzero(fqdnopt, sizeof (fqdnopt));
 924         if (dsmp->dsm_isv6) {
 925                 fqdncode = DHCPV6_OPT_CLIENT_FQDN;
 926                 metalen = OPT_V6_FQDN_METALEN;
 927                 *fqdnopt = (uint8_t)(1 << S_BITPOS);
 928         } else {
 929                 fqdncode = CD_CLIENTFQDN;
 930                 metalen = OPT_FQDN_METALEN;
 931                 *fqdnopt = (uint8_t)((1 << S_BITPOS) | (1 << E_BITPOS));
 932         }
 933         (void) memcpy(fqdnopt + metalen, enc_fqdnbuf, len);
 934         (void) add_pkt_opt(dpkt, fqdncode, fqdnopt, metalen + len);
 935 
 936         return (0);
 937 }
 938 
 939 /*
 940  * dhcp_assemble_fqdn(): Set dsm_reqfqdn if REQUEST_FQDN is set and
 941  *                       either a host name was sent in the IPC message (e.g., from
 942  *                       ipadm(1M) -h,--reqhost) or the interface is primary and a
 943  *                       nodename(4) is defined. If the host name is not already fully
 944  *                       qualified per is_fqdn(), then a value from
 945  *                       dhcp_get_defaultdomain() or from resolv.conf(4)--if defined--
 946  *                       is used to construct an FQDN.
 947  *
 948  *                       If no FQDN can be determined, dsm_reqfqdn will be NULL.
 949  *
 950  *   input: dhcp_smach_t *: pointer to interface DHCP state machine;
 951  *  output: 0 if dsm_reqfqdn was assigned; non-zero if otherwise;
 952  */
 953 
 954 static int
 955 dhcp_assemble_fqdn(dhcp_smach_t *dsmp)
 956 {
 957         char            fqdnbuf[MAXNAMELEN], nodename[MAXNAMELEN], *reqhost;
 958         size_t          pos, len;
 959 
 960         if (dsmp->dsm_reqfqdn != NULL) {
 961                 free(dsmp->dsm_reqfqdn);
 962                 dsmp->dsm_reqfqdn = NULL;
 963         }
 964 
 965         if (!df_get_bool(dsmp->dsm_name, dsmp->dsm_isv6, DF_REQUEST_FQDN))
 966                 return (-1);
 967 
 968         dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: DF_REQUEST_FQDN");
 969 
 970         bzero(fqdnbuf, sizeof (fqdnbuf));
 971 
 972         reqhost = dsmp->dsm_msg_reqhost;
 973         if (ipadm_is_nil_hostname(reqhost) &&
 974             (dsmp->dsm_dflags & DHCP_IF_PRIMARY) &&
 975             dhcp_get_nodename(nodename, sizeof (nodename)) == 0) {
 976                 reqhost = nodename;
 977         }
 978 
 979         if (ipadm_is_nil_hostname(reqhost)) {
 980                 dhcpmsg(MSG_DEBUG,
 981                     "dhcp_assemble_fqdn: no interface reqhost for %s",
 982                     dsmp->dsm_name);
 983                 return (-1);
 984         }
 985 
 986         if ((pos = strlcpy(fqdnbuf, reqhost, sizeof (fqdnbuf))) >=
 987             sizeof (fqdnbuf)) {
 988                 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long reqhost %s"
 989                     " for %s", reqhost, dsmp->dsm_name);
 990                 return (-1);
 991         }
 992 
 993         if (!is_fqdn(reqhost)) {
 994                 char            dnamebuf[MAXHOSTNAMELEN];
 995                 size_t          needdots;
 996                 int                     lasterrno;
 997                 char            *domainname = NULL;
 998 
 999                 /*
1000                  * determine FQDN domain name, if possible
1001                  */
1002 
1003                 if (dhcp_get_defaultdomain(dnamebuf, sizeof (dnamebuf)) == 0 &&
1004                     !ipadm_is_nil_hostname(dnamebuf)) {
1005                         domainname = dnamebuf;
1006                 } else {
1007                         /*
1008                          * fall back to resolv's "default domain (deprecated)"
1009                          */
1010 
1011                         struct __res_state      res_state;
1012                         bzero(&res_state, sizeof (struct __res_state));
1013 
1014                         /* initialize resolver or warn */
1015                         if ((lasterrno = res_ninit(&res_state)) != 0) {
1016                                 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: error %d"
1017                                     " initializing resolver", lasterrno);
1018                         }
1019                         else {
1020                                 if (*res_state.defdname != '\0') {
1021                                         (void) strlcpy(dnamebuf, res_state.defdname,
1022                                             sizeof (dnamebuf));
1023                                         domainname = dnamebuf;
1024                                 }
1025                                 res_ndestroy(&res_state);
1026                         }
1027                 }
1028 
1029                 if (ipadm_is_nil_hostname(domainname)) {
1030                         dhcpmsg(MSG_DEBUG, "dhcp_assemble_fqdn: no domain name for %s",
1031                             dsmp->dsm_name);
1032                         return (-1);
1033                 }
1034 
1035                 /*
1036                  * Finish constructing FQDN. Account for space needed to hold a
1037                  * separator '.' and a terminating '.'.
1038                  */
1039                 len = strlen(domainname);
1040                 needdots = 1 + (domainname[len - 1] != '.');
1041 
1042                 if (pos + len + needdots >= sizeof (fqdnbuf)) {
1043                         dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: too long FQDN %s.%s"
1044                             " for %s", fqdnbuf, domainname, dsmp->dsm_name);
1045                         return (-1);
1046                 }
1047 
1048                 /* add separator and then domain name */
1049                 fqdnbuf[pos++] = '.';
1050                 (void) strlcpy(fqdnbuf + pos, domainname, sizeof (fqdnbuf) - pos);
1051                 pos += len;
1052 
1053                 /* ensure the final character is '.' */
1054                 if (needdots > 1)
1055                         fqdnbuf[pos++] = '.'; /* following is already zeroed */
1056         }
1057 
1058         if (!ipadm_is_valid_hostname(fqdnbuf)) {
1059                 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: invalid FQDN %s for %s",
1060                     fqdnbuf, dsmp->dsm_name);
1061                 return (-1);
1062         }
1063 
1064         free(dsmp->dsm_reqfqdn);
1065         if ((dsmp->dsm_reqfqdn = strdup(fqdnbuf)) == NULL) {
1066                 dhcpmsg(MSG_WARNING, "dhcp_assemble_fqdn: cannot allocate memory");
1067                 return (-1);
1068         }
1069         return (0);
1070 }
1071 
1072 /*
1073  * is_fqdn() : Determine if the `hostname' can be considered as a Fully
1074  *                      Qualified Domain Name by being "rooted" (i.e., ending in '.')
1075  *                      or by containing at least three DNS labels (e.g.,
1076  *                      srv.example.com).
1077  *
1078  *   input: const char *: the hostname to inspect;
1079  *  output: boolean_t: B_TRUE if `hostname' is not NULL satisfies the
1080  *                      criteria above; otherwise, B_FALSE;
1081  */
1082 
1083 boolean_t
1084 is_fqdn(const char *hostname)
1085 {
1086         const char *c;
1087         size_t i;
1088 
1089         if (hostname == NULL)
1090                 return (B_FALSE);
1091 
1092         i = strlen(hostname);
1093         if (i > 0 && hostname[i - 1] == '.')
1094                 return (B_TRUE);
1095 
1096         c = hostname;
1097         i = 0;
1098         while ((c = strchr(c, '.')) != NULL) {
1099                 ++i;
1100                 ++c;
1101         }
1102 
1103         /* at least two separators is inferred to be fully-qualified */
1104         return (i >= 2);
1105 }