udhcp: add PXELINUX config file option (code 209) definition
[oweals/busybox.git] / networking / udhcp / d6_dhcpc.c
index d1baaae9b4c8d0912cdd83bfde5ac2649a18ea13..b0f0798e5e8104f5725d8479980c532ab2da5f11 100644 (file)
  */
 
 //config:config UDHCPC6
-//config:       bool "udhcp client for DHCPv6 (udhcpc6)"
-//config:       default n  # not yet ready
-//config:       help
-//config:         udhcpc6 is a DHCPv6 client
+//config:      bool "udhcp client for DHCPv6 (udhcpc6)"
+//config:      default n  # not yet ready
+//config:      depends on FEATURE_IPV6
+//config:      help
+//config:        udhcpc6 is a DHCPv6 client
 
 //applet:IF_UDHCPC6(APPLET(udhcpc6, BB_DIR_USR_BIN, BB_SUID_DROP))
 
-//kbuild:lib-$(CONFIG_UDHCPC6) += d6_dhcpc.o d6_packet.o d6_socket.o common.o
+//kbuild:lib-$(CONFIG_UDHCPC6) += d6_dhcpc.o d6_packet.o d6_socket.o common.o socket.o signalpipe.o
 
 
 #include <syslog.h>
@@ -38,7 +39,7 @@
 
 
 #if ENABLE_LONG_OPTS
-static const char udhcpc_longopts[] ALIGN1 =
+static const char udhcpc6_longopts[] ALIGN1 =
        "interface\0"      Required_argument "i"
        "now\0"            No_argument       "n"
        "pidfile\0"        Required_argument "p"
@@ -47,7 +48,6 @@ static const char udhcpc_longopts[] ALIGN1 =
        "request\0"        Required_argument "r"
        "script\0"         Required_argument "s"
        "timeout\0"        Required_argument "T"
-       "version\0"        No_argument       "v"
        "retries\0"        Required_argument "t"
        "tryagain\0"       Required_argument "A"
        "syslog\0"         No_argument       "S"
@@ -55,7 +55,6 @@ static const char udhcpc_longopts[] ALIGN1 =
        "no-default-options\0" No_argument   "o"
        "foreground\0"     No_argument       "f"
        "background\0"     No_argument       "b"
-       "broadcast\0"      No_argument       "B"
 ///    IF_FEATURE_UDHCPC_ARPING("arping\0"     No_argument       "a")
        IF_FEATURE_UDHCP_PORT("client-port\0"   Required_argument "P")
        ;
@@ -77,9 +76,8 @@ enum {
        OPT_o = 1 << 12,
        OPT_x = 1 << 13,
        OPT_f = 1 << 14,
-       OPT_B = 1 << 15,
 /* The rest has variable bit positions, need to be clever */
-       OPTBIT_B = 15,
+       OPTBIT_f = 14,
        USE_FOR_MMU(             OPTBIT_b,)
        ///IF_FEATURE_UDHCPC_ARPING(OPTBIT_a,)
        IF_FEATURE_UDHCP_PORT(   OPTBIT_P,)
@@ -97,20 +95,20 @@ static void *d6_find_option(uint8_t *option, uint8_t *option_end, unsigned code)
        int len_m4 = option_end - option - 4;
        while (len_m4 >= 0) {
                /* Next option's len is too big? */
-               if (option[2] > len_m4)
+               if (option[3] > len_m4)
                        return NULL; /* yes. bogus packet! */
                /* So far we treat any opts with code >255
                 * or len >255 as bogus, and stop at once.
                 * This simplifies big-endian handling.
                 */
-               if (option[1] != 0 || option[3] != 0)
+               if (option[0] != 0 || option[2] != 0)
                        return NULL;
                /* Option seems to be valid */
                /* Does its code match? */
-               if (option[0] == code)
+               if (option[1] == code)
                        return option; /* yes! */
-               option += option[2] + 4;
-               len_m4 -= option[2] + 4;
+               option += option[3] + 4;
+               len_m4 -= option[3] + 4;
        }
        return NULL;
 }
@@ -120,7 +118,7 @@ static void *d6_copy_option(uint8_t *option, uint8_t *option_end, unsigned code)
        uint8_t *opt = d6_find_option(option, option_end, code);
        if (!opt)
                return opt;
-       return memcpy(xmalloc(opt[2] + 4), opt, opt[2] + 4);
+       return memcpy(xmalloc(opt[3] + 4), opt, opt[3] + 4);
 }
 
 static void *d6_store_blob(void *dst, const void *src, unsigned len)
@@ -132,32 +130,114 @@ static void *d6_store_blob(void *dst, const void *src, unsigned len)
 
 /*** Script execution code ***/
 
+static char** new_env(void)
+{
+       client6_data.env_ptr = xrealloc_vector(client6_data.env_ptr, 3, client6_data.env_idx);
+       return &client6_data.env_ptr[client6_data.env_idx++];
+}
+
 /* put all the parameters into the environment */
-static char **fill_envp(struct d6_packet *packet
-    UNUSED_PARAM
-)
+static void option_to_env(uint8_t *option, uint8_t *option_end)
 {
-       int envc;
-       char **envp, **curr;
+       /* "length minus 4" */
+       int len_m4 = option_end - option - 4;
+       while (len_m4 >= 0) {
+               uint32_t v32;
+               char ipv6str[sizeof("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff")];
+
+               if (option[0] != 0 || option[2] != 0)
+                       break;
+
+               switch (option[1]) {
+               //case D6_OPT_CLIENTID:
+               //case D6_OPT_SERVERID:
+               case D6_OPT_IA_NA:
+               case D6_OPT_IA_PD:
+                       option_to_env(option + 16, option + 4 + option[3]);
+                       break;
+               //case D6_OPT_IA_TA:
+               case D6_OPT_IAADDR:
+/*   0                   1                   2                   3
+ *  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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |          OPTION_IAADDR        |          option-len           |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                                                               |
+ * |                         IPv6 address                          |
+ * |                                                               |
+ * |                                                               |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      preferred-lifetime                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        valid-lifetime                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ */
+                       sprint_nip6(ipv6str, option + 4);
+                       *new_env() = xasprintf("ipv6=%s", ipv6str);
+
+                       move_from_unaligned32(v32, option + 4 + 16 + 4);
+                       *new_env() = xasprintf("lease=%u", (unsigned)v32);
+                       break;
+
+               //case D6_OPT_ORO:
+               //case D6_OPT_PREFERENCE:
+               //case D6_OPT_ELAPSED_TIME:
+               //case D6_OPT_RELAY_MSG:
+               //case D6_OPT_AUTH:
+               //case D6_OPT_UNICAST:
+               //case D6_OPT_STATUS_CODE:
+               //case D6_OPT_RAPID_COMMIT:
+               //case D6_OPT_USER_CLASS:
+               //case D6_OPT_VENDOR_CLASS:
+               //case D6_OPT_VENDOR_OPTS:
+               //case D6_OPT_INTERFACE_ID:
+               //case D6_OPT_RECONF_MSG:
+               //case D6_OPT_RECONF_ACCEPT:
+
+               case D6_OPT_IAPREFIX:
+/*  0                   1                   2                   3
+ *  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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |        OPTION_IAPREFIX        |         option-length         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                      preferred-lifetime                       |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |                        valid-lifetime                         |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | prefix-length |                                               |
+ * +-+-+-+-+-+-+-+-+          IPv6 prefix                          |
+ * |                           (16 octets)                         |
+ * |                                                               |
+ * |               +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * |               |
+ * +-+-+-+-+-+-+-+-+
+ */
+                       //move_from_unaligned32(v32, option + 4 + 4);
+                       //*new_env() = xasprintf("lease=%u", (unsigned)v32);
+
+                       sprint_nip6(ipv6str, option + 4 + 4 + 1);
+                       *new_env() = xasprintf("ipv6prefix=%s/%u", ipv6str, (unsigned)(option[4 + 4]));
+               }
+               option += 4 + option[3];
+               len_m4 -= 4 + option[3];
+       }
+}
 
-#define BITMAP unsigned
-#define BBITS (sizeof(BITMAP) * 8)
-#define BMASK(i) (1 << (i & (sizeof(BITMAP) * 8 - 1)))
-#define FOUND_OPTS(i) (found_opts[(unsigned)i / BBITS])
-       ///BITMAP found_opts[256 / BBITS];
+static char **fill_envp(struct d6_packet *packet)
+{
+       char **envp, **curr;
 
-       ///memset(found_opts, 0, sizeof(found_opts));
+       client6_data.env_ptr = NULL;
+       client6_data.env_idx = 0;
 
-       /* We need 2 elements for:
-        * "interface=IFACE"
-        * terminating NULL
-        */
-       envc = 2;
+       *new_env() = xasprintf("interface=%s", client_config.interface);
 
-       curr = envp = xzalloc(sizeof(envp[0]) * envc);
+       if (packet)
+               option_to_env(packet->d6_options, packet->d6_options + sizeof(packet->d6_options));
 
-       *curr = xasprintf("interface=%s", client_config.interface);
-       putenv(*curr++);
+       envp = curr = client6_data.env_ptr;
+       while (*curr)
+               putenv(*curr++);
 
        return envp;
 }
@@ -231,8 +311,8 @@ static int d6_mcast_from_client_config_ifindex(struct d6_packet *packet, uint8_t
 
        return d6_send_raw_packet(
                packet, (end - (uint8_t*) packet),
-               /*src*/ NULL, CLIENT_PORT,
-               /*dst*/ (struct in6_addr*)FF02__1_2, SERVER_PORT, MAC_BCAST_ADDR,
+               /*src*/ NULL, CLIENT_PORT6,
+               /*dst*/ (struct in6_addr*)FF02__1_2, SERVER_PORT6, MAC_BCAST_ADDR,
                client_config.ifindex
        );
 }
@@ -474,8 +554,8 @@ static NOINLINE int send_d6_renew(uint32_t xid, struct in6_addr *server_ipv6, st
        if (server_ipv6)
                return d6_send_kernel_packet(
                        &packet, (opt_ptr - (uint8_t*) &packet),
-                       our_cur_ipv6, CLIENT_PORT,
-                       server_ipv6, SERVER_PORT
+                       our_cur_ipv6, CLIENT_PORT6,
+                       server_ipv6, SERVER_PORT6
                );
        return d6_mcast_from_client_config_ifindex(&packet, opt_ptr);
 }
@@ -496,8 +576,8 @@ static int send_d6_release(struct in6_addr *server_ipv6, struct in6_addr *our_cu
        bb_info_msg("Sending release...");
        return d6_send_kernel_packet(
                &packet, (opt_ptr - (uint8_t*) &packet),
-               our_cur_ipv6, CLIENT_PORT,
-               server_ipv6, SERVER_PORT
+               our_cur_ipv6, CLIENT_PORT6,
+               server_ipv6, SERVER_PORT6
        );
 }
 
@@ -534,7 +614,7 @@ static NOINLINE int d6_recv_raw_packet(struct in6_addr *peer_ipv6
        /* make sure its the right packet for us, and that it passes sanity checks */
        if (packet.ip6.ip6_nxt != IPPROTO_UDP
         || (packet.ip6.ip6_vfc >> 4) != 6
-        || packet.udp.dest != htons(CLIENT_PORT)
+        || packet.udp.dest != htons(CLIENT_PORT6)
        /* || bytes > (int) sizeof(packet) - can't happen */
         || packet.udp.len != packet.ip6.ip6_plen
        ) {
@@ -628,7 +708,7 @@ static int d6_raw_socket(int ifindex)
                BPF_STMT(BPF_LDX|BPF_B|BPF_MSH, 0),
                /* load udp destination port from halfword[header_len + 2] */
                BPF_STMT(BPF_LD|BPF_H|BPF_IND, 2),
-               /* jump to L3 if udp dport is CLIENT_PORT, else to L4 */
+               /* jump to L3 if udp dport is CLIENT_PORT6, else to L4 */
                BPF_JUMP(BPF_JMP|BPF_JEQ|BPF_K, 68, 0, 1),
                /* L3: accept packet */
                BPF_STMT(BPF_RET|BPF_K, 0xffffffff),
@@ -653,7 +733,7 @@ static int d6_raw_socket(int ifindex)
        xbind(fd, (struct sockaddr *) &sock, sizeof(sock));
 
 #if 0
-       if (CLIENT_PORT == 68) {
+       if (CLIENT_PORT6 == 546) {
                /* Use only if standard port is in use */
                /* Ignoring error (kernel may lack support for this) */
                if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &filter_prog,
@@ -681,7 +761,7 @@ static void change_listen_mode(int new_mode)
                sockfd = -1;
        }
        if (new_mode == LISTEN_KERNEL)
-               sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT, client_config.interface);
+               sockfd = udhcp_listen_socket(/*INADDR_ANY,*/ CLIENT_PORT6, client_config.interface);
        else if (new_mode != LISTEN_NONE)
                sockfd = d6_raw_socket(client_config.ifindex);
        /* else LISTEN_NONE: sockfd stays closed */
@@ -751,7 +831,7 @@ static void client_background(void)
 //usage:# define IF_UDHCP_VERBOSE(...)
 //usage:#endif
 //usage:#define udhcpc6_trivial_usage
-//usage:       "[-fbnq"IF_UDHCP_VERBOSE("v")"oRB] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n"
+//usage:       "[-fbnq"IF_UDHCP_VERBOSE("v")"oR] [-i IFACE] [-r IP] [-s PROG] [-p PIDFILE]\n"
 //usage:       "       [-x OPT:VAL]... [-O OPT]..." IF_FEATURE_UDHCP_PORT(" [-P N]")
 //usage:#define udhcpc6_full_usage "\n"
 //usage:       IF_LONG_OPTS(
@@ -771,7 +851,7 @@ static void client_background(void)
 //usage:     "\n       -R,--release            Release IP on exit"
 //usage:     "\n       -S,--syslog             Log to syslog too"
 //usage:       IF_FEATURE_UDHCP_PORT(
-//usage:     "\n       -P,--client-port N      Use port N (default 68)"
+//usage:     "\n       -P,--client-port N      Use port N (default 546)"
 //usage:       )
 ////usage:     IF_FEATURE_UDHCPC_ARPING(
 ////usage:     "\n     -a,--arping             Use arping to validate offered address"
@@ -805,7 +885,7 @@ static void client_background(void)
 //usage:     "\n       -R              Release IP on exit"
 //usage:     "\n       -S              Log to syslog too"
 //usage:       IF_FEATURE_UDHCP_PORT(
-//usage:     "\n       -P N            Use port N (default 68)"
+//usage:     "\n       -P N            Use port N (default 546)"
 //usage:       )
 ////usage:     IF_FEATURE_UDHCPC_ARPING(
 ////usage:     "\n     -a              Use arping to validate offered address"
@@ -851,16 +931,16 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
        fd_set rfds;
 
        /* Default options */
-       IF_FEATURE_UDHCP_PORT(SERVER_PORT = 547;)
-       IF_FEATURE_UDHCP_PORT(CLIENT_PORT = 546;)
+       IF_FEATURE_UDHCP_PORT(SERVER_PORT6 = 547;)
+       IF_FEATURE_UDHCP_PORT(CLIENT_PORT6 = 546;)
        client_config.interface = "eth0";
        client_config.script = CONFIG_UDHCPC_DEFAULT_SCRIPT;
 
        /* Parse command line */
        /* O,x: list; -T,-t,-A take numeric param */
        opt_complementary = "O::x::T+:t+:A+" IF_UDHCP_VERBOSE(":vv") ;
-       IF_LONG_OPTS(applet_long_options = udhcpc_longopts;)
-       opt = getopt32(argv, "i:np:qRr:s:T:t:SA:O:ox:fB"
+       IF_LONG_OPTS(applet_long_options = udhcpc6_longopts;)
+       opt = getopt32(argv, "i:np:qRr:s:T:t:SA:O:ox:f"
                USE_FOR_MMU("b")
                ///IF_FEATURE_UDHCPC_ARPING("a")
                IF_FEATURE_UDHCP_PORT("P:")
@@ -872,7 +952,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
                , &list_x
                IF_FEATURE_UDHCP_PORT(, &str_P)
                IF_UDHCP_VERBOSE(, &dhcp_verbose)
-               );
+       );
        requested_ipv6 = NULL;
        if (opt & OPT_r) {
                if (inet_pton(AF_INET6, str_r, &ipv6_buf) <= 0)
@@ -881,12 +961,10 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
        }
 #if ENABLE_FEATURE_UDHCP_PORT
        if (opt & OPT_P) {
-               CLIENT_PORT = xatou16(str_P);
-               SERVER_PORT = CLIENT_PORT - 1;
+               CLIENT_PORT6 = xatou16(str_P);
+               SERVER_PORT6 = CLIENT_PORT6 + 1;
        }
 #endif
-       if (opt & OPT_o)
-               client_config.no_default_options = 1;
        while (list_O) {
                char *optstr = llist_pop(&list_O);
                unsigned n = bb_strtou(optstr, NULL, 0);
@@ -896,6 +974,16 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
                }
                client_config.opt_mask[n >> 3] |= 1 << (n & 7);
        }
+       if (!(opt & OPT_o)) {
+               /*
+               unsigned i, n;
+               for (i = 0; (n = dhcp_optflags[i].code) != 0; i++) {
+                       if (dhcp_optflags[i].flags & OPTION_REQ) {
+                               client_config.opt_mask[n >> 3] |= 1 << (n & 7);
+                       }
+               }
+               */
+       }
        while (list_x) {
                char *optstr = llist_pop(&list_x);
                char *colon = strchr(optstr, ':');
@@ -920,8 +1008,8 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
                clientid = xzalloc(2+2+2+2+6);
                clientid->code = D6_OPT_CLIENTID;
                clientid->len = 2+2+6;
-               clientid->data[0] = 3; /* DUID-LL */
-               clientid->data[2] = 1; /* ethernet */
+               clientid->data[1] = 3; /* DUID-LL */
+               clientid->data[3] = 1; /* ethernet */
                clientid_mac_ptr = clientid->data + 2+2;
                memcpy(clientid_mac_ptr, client_config.client_mac, 6);
                client_config.clientid = (void*)clientid;
@@ -986,8 +1074,8 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
                retval = 0;
                /* If we already timed out, fall through with retval = 0, else... */
                if ((int)tv.tv_sec > 0) {
+                       log1("Waiting on select %u seconds", (int)tv.tv_sec);
                        timestamp_before_wait = (unsigned)monotonic_sec();
-                       log1("Waiting on select...");
                        retval = select(max_fd + 1, &rfds, NULL, NULL, &tv);
                        if (retval < 0) {
                                /* EINTR? A signal was caught, don't panic */
@@ -1023,7 +1111,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
 
                        switch (state) {
                        case INIT_SELECTING:
-                               if (packet_num < discover_retries) {
+                               if (!discover_retries || packet_num < discover_retries) {
                                        if (packet_num == 0)
                                                xid = random_xid();
                                        /* multicast */
@@ -1052,7 +1140,7 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
                                packet_num = 0;
                                continue;
                        case REQUESTING:
-                               if (packet_num < discover_retries) {
+                               if (!discover_retries || packet_num < discover_retries) {
                                        /* send multicast select packet */
                                        send_d6_select(xid);
                                        timeout = discover_timeout;
@@ -1332,19 +1420,19 @@ int udhcpc6_main(int argc UNUSED_PARAM, char **argv)
                                free(client6_data.ia_na);
                                client6_data.ia_na = d6_copy_option(packet.d6_options, packet_end, D6_OPT_IA_NA);
                                if (!client6_data.ia_na) {
-                                       bb_error_msg("no lease time, ignoring packet");
+                                       bb_error_msg("no %s option, ignoring packet", "IA_NA");
                                        continue;
                                }
                                if (client6_data.ia_na->len < (4 + 4 + 4) + (2 + 2 + 16 + 4 + 4)) {
                                        bb_error_msg("IA_NA option is too short:%d bytes", client6_data.ia_na->len);
                                        continue;
                                }
-                               iaaddr = d6_find_option(client6_data.ia_na->data,
+                               iaaddr = d6_find_option(client6_data.ia_na->data + 4 + 4 + 4,
                                                client6_data.ia_na->data + client6_data.ia_na->len,
                                                D6_OPT_IAADDR
                                );
                                if (!iaaddr) {
-                                       bb_error_msg("no lease time, ignoring packet");
+                                       bb_error_msg("no %s option, ignoring packet", "IAADDR");
                                        continue;
                                }
                                if (iaaddr->len < (16 + 4 + 4)) {