udhcpc: include client-id option in DECLINEs, even if it's a custom -x 61:HEX option

client_data.vendorclass, .hostname and .fqdn probably need the same treatment:
just insert them into the list of -x opts, get rid of

        if (client_data.vendorclass)
                udhcp_add_binary_option(packet, client_data.vendorclass);
        if (client_data.hostname)
                udhcp_add_binary_option(packet, client_data.hostname);
        if (client_data.fqdn)
                udhcp_add_binary_option(packet, client_data.fqdn);

function                                             old     new   delta
udhcp_insert_new_option                                -     166    +166
perform_release                                      171     207     +36
perform_d6_release                                   227     259     +32
udhcpc6_main                                        2558    2580     +22
init_d6_packet                                       103      84     -19
udhcpc_main                                         2585    2564     -21
attach_option                                        397     253    -144
------------------------------------------------------------------------------
(add/remove: 1/0 grow/shrink: 3/3 up/down: 256/-184)           Total: 72 bytes

Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
diff --git a/networking/udhcp/common.c b/networking/udhcp/common.c
index f2d6907..684d76b 100644
--- a/networking/udhcp/common.c
+++ b/networking/udhcp/common.c
@@ -420,6 +420,43 @@
 	return 1;
 }
 
+void* FAST_FUNC udhcp_insert_new_option(
+		struct option_set **opt_list,
+		unsigned code,
+		const void *buffer,
+		unsigned length,
+		bool dhcpv6)
+{
+	IF_NOT_UDHCPC6(bool dhcpv6 = 0;)
+	struct option_set *new, **curr;
+
+	log2("attaching option %02x to list", code);
+	new = xmalloc(sizeof(*new));
+	if (!dhcpv6) {
+		new->data = xmalloc(length + OPT_DATA);
+		new->data[OPT_CODE] = code;
+		new->data[OPT_LEN] = length;
+		memcpy(new->data + OPT_DATA, buffer, length);
+	} else {
+		new->data = xmalloc(length + D6_OPT_DATA);
+		new->data[D6_OPT_CODE] = code >> 8;
+		new->data[D6_OPT_CODE + 1] = code & 0xff;
+		new->data[D6_OPT_LEN] = length >> 8;
+		new->data[D6_OPT_LEN + 1] = length & 0xff;
+		memcpy(new->data + D6_OPT_DATA, buffer,	length);
+	}
+
+	curr = opt_list;
+//FIXME: DHCP6 codes > 255!!
+	while (*curr && (*curr)->data[OPT_CODE] < code)
+		curr = &(*curr)->next;
+
+	new->next = *curr;
+	*curr = new;
+
+	return new->data;
+}
+
 /* udhcp_str2optset:
  * Parse string option representation to binary form and add it to opt_list.
  * Called to parse "udhcpc -x OPTNAME:OPTVAL"
@@ -459,32 +496,8 @@
 
 	existing = udhcp_find_option(*opt_list, optflag->code);
 	if (!existing) {
-		struct option_set *new, **curr;
-
 		/* make a new option */
-		log2("attaching option %02x to list", optflag->code);
-		new = xmalloc(sizeof(*new));
-		if (!dhcpv6) {
-			new->data = xmalloc(length + OPT_DATA);
-			new->data[OPT_CODE] = optflag->code;
-			new->data[OPT_LEN] = length;
-			memcpy(new->data + OPT_DATA, buffer, length);
-		} else {
-			new->data = xmalloc(length + D6_OPT_DATA);
-			new->data[D6_OPT_CODE] = optflag->code >> 8;
-			new->data[D6_OPT_CODE + 1] = optflag->code & 0xff;
-			new->data[D6_OPT_LEN] = length >> 8;
-			new->data[D6_OPT_LEN + 1] = length & 0xff;
-			memcpy(new->data + D6_OPT_DATA, buffer,
-					length);
-		}
-
-		curr = opt_list;
-		while (*curr && (*curr)->data[OPT_CODE] < optflag->code)
-			curr = &(*curr)->next;
-
-		new->next = *curr;
-		*curr = new;
+		udhcp_insert_new_option(opt_list, optflag->code, buffer, length, dhcpv6);
 		goto ret;
 	}
 
diff --git a/networking/udhcp/common.h b/networking/udhcp/common.h
index cc0abd2..e5af628 100644
--- a/networking/udhcp/common.h
+++ b/networking/udhcp/common.h
@@ -319,6 +319,17 @@
 
 /* 2nd param is "uint32_t*" */
 int FAST_FUNC udhcp_str2nip(const char *str, void *arg);
+
+#if !ENABLE_UDHCPC6
+#define udhcp_insert_new_option(opt_list, code, buffer, length, dhcpv6) \
+	udhcp_insert_new_option(opt_list, code, buffer, length)
+#endif
+void* FAST_FUNC udhcp_insert_new_option(struct option_set **opt_list,
+		unsigned code,
+		const void *buffer,
+		unsigned length,
+		bool dhcpv6);
+
 /* 2nd param is "struct option_set**" */
 #if !ENABLE_UDHCPC6
 #define udhcp_str2optset(str, arg, optflags, option_strings, dhcpv6) \
diff --git a/networking/udhcp/d6_dhcpc.c b/networking/udhcp/d6_dhcpc.c
index 5bca4a8..c4bedb2 100644
--- a/networking/udhcp/d6_dhcpc.c
+++ b/networking/udhcp/d6_dhcpc.c
@@ -482,7 +482,6 @@
 static uint8_t *init_d6_packet(struct d6_packet *packet, char type, uint32_t xid)
 {
 	uint8_t *ptr;
-	struct d6_option *clientid;
 	unsigned secs;
 
 	memset(packet, 0, sizeof(*packet));
@@ -503,9 +502,7 @@
 	*((uint16_t*)ptr) = (secs < 0xffff) ? htons(secs) : 0xffff;
 	ptr += 2;
 
-	/* add CLIENTID option */
-	clientid = (void*)client_data.clientid;
-	return mempcpy(ptr, clientid, clientid->len + 2+2);
+	return ptr;
 }
 
 static uint8_t *add_d6_client_options(uint8_t *ptr)
@@ -593,10 +590,10 @@
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
 
-	/* Fill in: msg type, client id */
+	/* Fill in: msg type */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_INFORMATION_REQUEST, xid);
 
-	/* Add options:
+	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
 	opt_ptr = add_d6_client_options(opt_ptr);
@@ -693,7 +690,7 @@
 	uint8_t *opt_ptr;
 	unsigned len;
 
-	/* Fill in: msg type, client id */
+	/* Fill in: msg type */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_SOLICIT, xid);
 
 	/* Create new IA_NA, optionally with included IAADDR with requested IP */
@@ -726,7 +723,7 @@
 		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, len);
 	}
 
-	/* Add options:
+	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
 	opt_ptr = add_d6_client_options(opt_ptr);
@@ -771,7 +768,7 @@
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
 
-	/* Fill in: msg type, client id */
+	/* Fill in: msg type */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_REQUEST, xid);
 
 	/* server id */
@@ -783,7 +780,7 @@
 	if (client6_data.ia_pd)
 		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
 
-	/* Add options:
+	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
 	opt_ptr = add_d6_client_options(opt_ptr);
@@ -844,7 +841,7 @@
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
 
-	/* Fill in: msg type, client id */
+	/* Fill in: msg type */
 	opt_ptr = init_d6_packet(&packet, DHCPREQUEST, xid);
 
 	/* server id */
@@ -856,7 +853,7 @@
 	if (client6_data.ia_pd)
 		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
 
-	/* Add options:
+	/* Add options: client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
 	opt_ptr = add_d6_client_options(opt_ptr);
@@ -878,6 +875,7 @@
 {
 	struct d6_packet packet;
 	uint8_t *opt_ptr;
+	struct option_set *ci;
 
 	/* Fill in: msg type, client id */
 	opt_ptr = init_d6_packet(&packet, D6_MSG_RELEASE, random_xid());
@@ -889,6 +887,10 @@
 	/* IA PD */
 	if (client6_data.ia_pd)
 		opt_ptr = mempcpy(opt_ptr, client6_data.ia_pd, client6_data.ia_pd->len + 2+2);
+	/* Client-id */
+	ci = udhcp_find_option(client_data.options, D6_OPT_CLIENTID);
+	if (ci)
+		opt_ptr = mempcpy(opt_ptr, ci->data, D6_OPT_DATA + 2+2 + 6);
 
 	bb_info_msg("sending %s", "release");
 	return d6_send_kernel_packet_from_client_data_ifindex(
@@ -1184,7 +1186,7 @@
 {
 	const char *str_r;
 	IF_FEATURE_UDHCP_PORT(char *str_P;)
-	void *clientid_mac_ptr;
+	uint8_t *clientid_mac_ptr;
 	llist_t *list_O = NULL;
 	llist_t *list_x = NULL;
 	int tryagain_timeout = 20;
@@ -1284,22 +1286,19 @@
 	if (d6_read_interface(client_data.interface,
 			&client_data.ifindex,
 			&client6_data.ll_ip6,
-			client_data.client_mac)
+			client_data_client_mac)
 	) {
 		return 1;
 	}
 
-	/* Create client ID based on mac, set clientid_mac_ptr */
-	{
-		struct d6_option *clientid;
-		clientid = xzalloc(2+2+2+2+6);
-		clientid->code = D6_OPT_CLIENTID;
-		clientid->len = 2+2+6;
-		clientid->data[1] = 3; /* DUID-LL */
-		clientid->data[3] = 1; /* ethernet */
-		clientid_mac_ptr = clientid->data + 2+2;
-		memcpy(clientid_mac_ptr, client_data.client_mac, 6);
-		client_data.clientid = (void*)clientid;
+	clientid_mac_ptr = NULL;
+	if (!udhcp_find_option(client_data.options, D6_OPT_CLIENTID)) {
+		/* not set, set the default client ID */
+		client_data.clientid[1] = 3; /* DUID-LL */
+		client_data.clientid[3] = 1; /* ethernet */
+		clientid_mac_ptr = udhcp_insert_new_option(&client_data.options, D6_OPT_CLIENTID,
+				client_data.clientid, 2+2 + 6, /*dhcp6:*/ 1);
+		clientid_mac_ptr += 2+2 + 2+2; /* skip option code, len, DUID-LL, ethernet */
 	}
 
 #if !BB_MMU
@@ -1386,12 +1385,13 @@
 			if (d6_read_interface(client_data.interface,
 					&client_data.ifindex,
 					&client6_data.ll_ip6,
-					client_data.client_mac)
+					client_data_client_mac)
 			) {
 				goto ret0; /* iface is gone? */
 			}
 
-			memcpy(clientid_mac_ptr, client_data.client_mac, 6);
+			if (clientid_mac_ptr)
+				memcpy(clientid_mac_ptr, client_data_client_mac, 6);
 
 			switch (client_data.state) {
 			case INIT_SELECTING:
@@ -1505,7 +1505,9 @@
 				continue;
 			/* case RELEASED: */
 			}
-			/* yah, I know, *you* say it would never happen */
+			/* RELEASED state (when we got SIGUSR2) ends up here.
+			 * (wait for SIGUSR1 to re-init, or for TERM, etc)
+			 */
 			timeout = INT_MAX;
 			continue; /* back to main loop */
 		} /* if poll timed out */
diff --git a/networking/udhcp/dhcpc.c b/networking/udhcp/dhcpc.c
index ea06405..a06eeaa 100644
--- a/networking/udhcp/dhcpc.c
+++ b/networking/udhcp/dhcpc.c
@@ -612,9 +612,7 @@
 	secs = client_data.last_secs - client_data.first_secs;
 	packet->secs = (secs < 0xffff) ? htons(secs) : 0xffff;
 
-	memcpy(packet->chaddr, client_data.client_mac, 6);
-	if (client_data.clientid)
-		udhcp_add_binary_option(packet, client_data.clientid);
+	memcpy(packet->chaddr, client_data_client_mac, 6);
 }
 
 static void add_client_options(struct dhcp_packet *packet)
@@ -715,7 +713,7 @@
 
 	/* Fill in: op, htype, hlen, cookie, chaddr fields,
 	 * random xid field (we override it below),
-	 * client-id option (unless -C), message type option:
+	 * message type option:
 	 */
 	init_packet(&packet, DHCPDISCOVER);
 
@@ -724,7 +722,7 @@
 		udhcp_add_simple_option(&packet, DHCP_REQUESTED_IP, requested);
 
 	/* Add options: maxsize,
-	 * optionally: hostname, fqdn, vendorclass,
+	 * optionally: hostname, fqdn, vendorclass, client-id,
 	 * "param req" option according to -O, options specified with -x
 	 */
 	add_client_options(&packet);
@@ -758,7 +756,7 @@
  */
 	/* Fill in: op, htype, hlen, cookie, chaddr fields,
 	 * random xid field (we override it below),
-	 * client-id option (unless -C), message type option:
+	 * message type option:
 	 */
 	init_packet(&packet, DHCPREQUEST);
 
@@ -768,7 +766,7 @@
 	udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
 
 	/* Add options: maxsize,
-	 * optionally: hostname, fqdn, vendorclass,
+	 * optionally: hostname, fqdn, vendorclass, client-id,
 	 * "param req" option according to -O, and options specified with -x
 	 */
 	add_client_options(&packet);
@@ -805,7 +803,7 @@
  */
 	/* Fill in: op, htype, hlen, cookie, chaddr fields,
 	 * random xid field (we override it below),
-	 * client-id option (unless -C), message type option:
+	 * message type option:
 	 */
 	init_packet(&packet, DHCPREQUEST);
 
@@ -813,7 +811,7 @@
 	packet.ciaddr = ciaddr;
 
 	/* Add options: maxsize,
-	 * optionally: hostname, fqdn, vendorclass,
+	 * optionally: hostname, fqdn, vendorclass, client-id,
 	 * "param req" option according to -O, and options specified with -x
 	 */
 	add_client_options(&packet);
@@ -837,7 +835,7 @@
 	struct dhcp_packet packet;
 
 	/* Fill in: op, htype, hlen, cookie, chaddr, random xid fields,
-	 * client-id option (unless -C), message type option:
+	 * message type option:
 	 */
 	init_packet(&packet, DHCPDECLINE);
 
@@ -854,6 +852,8 @@
 
 	udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
 
+//TODO: add client-id opt?
+
 	bb_simple_info_msg("broadcasting decline");
 	return raw_bcast_from_client_data_ifindex(&packet, INADDR_ANY);
 }
@@ -865,9 +865,10 @@
 int send_release(uint32_t server, uint32_t ciaddr)
 {
 	struct dhcp_packet packet;
+	struct option_set *ci;
 
 	/* Fill in: op, htype, hlen, cookie, chaddr, random xid fields,
-	 * client-id option (unless -C), message type option:
+	 * message type option:
 	 */
 	init_packet(&packet, DHCPRELEASE);
 
@@ -876,6 +877,14 @@
 
 	udhcp_add_simple_option(&packet, DHCP_SERVER_ID, server);
 
+	/* RFC 2131 section 3.1.6:
+	 * If the client used a 'client identifier' when it obtained the lease,
+	 * it MUST use the same 'client identifier' in the DHCPRELEASE message.
+	 */
+	ci = udhcp_find_option(client_data.options, DHCP_CLIENT_ID);
+	if (ci)
+		udhcp_add_binary_option(&packet, ci->data);
+
 	bb_info_msg("sending %s", "release");
 	/* Note: normally we unicast here since "server" is not zero.
 	 * However, there _are_ people who run "address-less" DHCP servers,
@@ -1230,7 +1239,7 @@
 	const char *str_V, *str_h, *str_F, *str_r;
 	IF_FEATURE_UDHCPC_ARPING(const char *str_a = "2000";)
 	IF_FEATURE_UDHCP_PORT(char *str_P;)
-	void *clientid_mac_ptr;
+	uint8_t *clientid_mac_ptr;
 	llist_t *list_O = NULL;
 	llist_t *list_x = NULL;
 	int tryagain_timeout = 20;
@@ -1339,7 +1348,7 @@
 	if (udhcp_read_interface(client_data.interface,
 			&client_data.ifindex,
 			NULL,
-			client_data.client_mac)
+			client_data_client_mac)
 	) {
 		return 1;
 	}
@@ -1347,10 +1356,11 @@
 	clientid_mac_ptr = NULL;
 	if (!(opt & OPT_C) && !udhcp_find_option(client_data.options, DHCP_CLIENT_ID)) {
 		/* not suppressed and not set, set the default client ID */
-		client_data.clientid = alloc_dhcp_option(DHCP_CLIENT_ID, "", 7);
-		client_data.clientid[OPT_DATA] = 1; /* type: ethernet */
-		clientid_mac_ptr = client_data.clientid + OPT_DATA+1;
-		memcpy(clientid_mac_ptr, client_data.client_mac, 6);
+		client_data_client_mac[-1] = 1; /* type: ethernet */
+		clientid_mac_ptr = udhcp_insert_new_option(
+				&client_data.options, DHCP_CLIENT_ID,
+				client_data_client_mac - 1, 1 + 6, /*dhcp6:*/ 0);
+		clientid_mac_ptr += 3; /* skip option code, len, ethernet */
 	}
 	if (str_V[0] != '\0') {
 		// can drop -V, str_V, client_data.vendorclass,
@@ -1447,12 +1457,12 @@
 			if (udhcp_read_interface(client_data.interface,
 					&client_data.ifindex,
 					NULL,
-					client_data.client_mac)
+					client_data_client_mac)
 			) {
 				goto ret0; /* iface is gone? */
 			}
 			if (clientid_mac_ptr)
-				memcpy(clientid_mac_ptr, client_data.client_mac, 6);
+				memcpy(clientid_mac_ptr, client_data_client_mac, 6);
 
 			switch (client_data.state) {
 			case INIT_SELECTING:
@@ -1569,7 +1579,9 @@
 				continue;
 			/* case RELEASED: */
 			}
-			/* yah, I know, *you* say it would never happen */
+			/* RELEASED state (when we got SIGUSR2) ends up here.
+			 * (wait for SIGUSR1 to re-init, or for TERM, etc)
+			 */
 			timeout = INT_MAX;
 			continue; /* back to main loop */
 		} /* if poll timed out */
@@ -1645,7 +1657,7 @@
 
 		/* Ignore packets that aren't for us */
 		if (packet.hlen != 6
-		 || memcmp(packet.chaddr, client_data.client_mac, 6) != 0
+		 || memcmp(packet.chaddr, client_data_client_mac, 6) != 0
 		) {
 //FIXME: need to also check that last 10 bytes are zero
 			log1("chaddr does not match%s", ", ignoring packet"); // log2?
@@ -1757,7 +1769,7 @@
 					if (!arpping(requested_ip,
 							NULL,
 							(uint32_t) 0,
-							client_data.client_mac,
+							client_data_client_mac,
 							client_data.interface,
 							arpping_ms)
 					) {
diff --git a/networking/udhcp/dhcpc.h b/networking/udhcp/dhcpc.h
index 7ad01ea..a4cc188 100644
--- a/networking/udhcp/dhcpc.h
+++ b/networking/udhcp/dhcpc.h
@@ -8,7 +8,8 @@
 PUSH_AND_SET_FUNCTION_VISIBILITY_TO_HIDDEN
 
 struct client_data_t {
-	uint8_t client_mac[6];          /* Our mac address */
+	uint8_t clientid[2+2 + 6];      /* Our mac address (prefixed by padding used for client-id) */
+#define client_data_client_mac (client_data.clientid + 2+2)
 	IF_FEATURE_UDHCP_PORT(uint16_t port;)
 	int ifindex;                    /* Index number of the interface to use */
 	uint8_t opt_mask[256 / 8];      /* Bitmask of options to send (-O option) */
@@ -17,7 +18,6 @@
 	char *pidfile;                  /* Optionally store the process ID */
 	const char *script;             /* User script to run at dhcp events */
 	struct option_set *options;     /* list of DHCP options to send to server */
-	uint8_t *clientid;              /* Optional client id to use */
 	uint8_t *vendorclass;           /* Optional vendor class-id to use */
 	uint8_t *hostname;              /* Optional hostname to use */
 	uint8_t *fqdn;                  /* Optional fully qualified domain name to use */