1/*
2 * Copyright (c) 2009-2013 Apple Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24/*
25 * DHCPv6Client.c
26 * - API's to instantiate and interact with DHCPv6Client
27 */
28
29/*
30 * Modification History
31 *
32 * September 22, 2009		Dieter Siegmund (dieter@apple.com)
33 * - created
34 *
35 * May 14, 2010			Dieter Siegmund (dieter@apple.com)
36 * - implemented stateful support
37 */
38
39#include <stdio.h>
40#include <stdlib.h>
41#include <unistd.h>
42#include <stddef.h>
43#include <sys/types.h>
44#include <sys/socket.h>
45#include <net/if.h>
46#include <mach/boolean.h>
47#include <string.h>
48#include <errno.h>
49#include <arpa/inet.h>
50#include "DHCPv6.h"
51#include "DHCPv6Client.h"
52#include "DHCPv6Options.h"
53#include "DHCPv6Socket.h"
54#include "DHCPDUIDIAID.h"
55#include "timer.h"
56#include "ipconfigd_threads.h"
57#include "interfaces.h"
58#include "cfutil.h"
59#include "DNSNameList.h"
60#include "symbol_scope.h"
61#include "ipconfigd_threads.h"
62
63typedef void
64(DHCPv6ClientEventFunc)(DHCPv6ClientRef client, IFEventID_t event_id,
65			void * event_data);
66typedef DHCPv6ClientEventFunc * DHCPv6ClientEventFuncRef;
67
68typedef enum {
69    kDHCPv6ClientStateNone = 0,
70    kDHCPv6ClientStateSolicit,
71    kDHCPv6ClientStateRequest,
72    kDHCPv6ClientStateBound,
73    kDHCPv6ClientStateRenew,
74    kDHCPv6ClientStateRebind,
75    kDHCPv6ClientStateConfirm,
76    kDHCPv6ClientStateRelease,
77    kDHCPv6ClientStateUnbound,
78    kDHCPv6ClientStateDecline,
79    kDHCPv6ClientStateInform,
80    kDHCPv6ClientStateLast
81} DHCPv6ClientState;
82
83STATIC const char *
84DHCPv6ClientStateGetName(DHCPv6ClientState cstate)
85{
86    STATIC const char * names[] = {
87	"None",
88	"Solicit",
89	"Request",
90	"Bound",
91	"Renew",
92	"Rebind",
93	"Confirm",
94	"Release",
95	"Unbound",
96	"Decline",
97	"Inform",
98    };
99
100    if (cstate >= kDHCPv6ClientStateNone
101	&& cstate < kDHCPv6ClientStateLast) {
102	return (names[cstate]);
103    }
104    return ("Unknown");
105}
106
107typedef struct {
108    CFAbsoluteTime		start;
109
110    /* these times are all relative to start */
111    uint32_t			t1;
112    uint32_t			t2;
113    uint32_t			valid_lifetime;
114    uint32_t			preferred_lifetime;
115    bool			valid;
116} lease_info_t;
117
118struct DHCPv6Client {
119    CFRunLoopSourceRef			callback_rls;
120    DHCPv6ClientNotificationCallBack 	callback;
121    void * 				callback_arg;
122    struct in6_addr			our_ip;
123    int					our_prefix_length;
124    DHCPv6ClientState			cstate;
125    DHCPv6SocketRef			sock;
126    timer_callout_t *			timer;
127    DHCPv6ClientEventFuncRef		address_changed_func;
128    uint32_t				transaction_id;
129    int					try;
130    CFAbsoluteTime			start_time;
131    CFTimeInterval			retransmit_time;
132    dhcpv6_info_t			saved;
133    bool				saved_verified;
134    DHCPDUIDRef				server_id; /* points to saved */
135    DHCPv6OptionIA_NARef		ia_na;	   /* points to saved */
136    DHCPv6OptionIAADDRRef		ia_addr;   /* points to saved */
137    lease_info_t			lease;
138};
139
140STATIC DHCPv6ClientEventFunc	DHCPv6Client_Bound;
141STATIC DHCPv6ClientEventFunc	DHCPv6Client_Unbound;
142STATIC DHCPv6ClientEventFunc	DHCPv6Client_Solicit;
143STATIC DHCPv6ClientEventFunc	DHCPv6Client_Request;
144STATIC DHCPv6ClientEventFunc	DHCPv6Client_RenewRebind;
145
146INLINE void
147DHCPv6ClientSetAddressChangedFunc(DHCPv6ClientRef client,
148				  DHCPv6ClientEventFuncRef func)
149{
150    client->address_changed_func = func;
151    return;
152}
153
154INLINE interface_t *
155DHCPv6ClientGetInterface(DHCPv6ClientRef client)
156{
157    return (DHCPv6SocketGetInterface(client->sock));
158}
159
160STATIC const struct sockaddr_in6 dhcpv6_all_servers_and_relay_agents = {
161    sizeof(dhcpv6_all_servers_and_relay_agents),
162    AF_INET6, 0, 0,
163    All_DHCP_Relay_Agents_and_Servers_INIT, 0
164};
165
166STATIC const uint16_t	DHCPv6RequestedOptionsStatic[] = {
167    kDHCPv6OPTION_DNS_SERVERS,
168    kDHCPv6OPTION_DOMAIN_LIST
169};
170#define kDHCPv6RequestedOptionsStaticCount 	(sizeof(DHCPv6RequestedOptionsStatic) / sizeof(DHCPv6RequestedOptionsStatic[0]))
171
172STATIC uint16_t *	DHCPv6RequestedOptionsDefault = (uint16_t *)DHCPv6RequestedOptionsStatic;
173STATIC int		DHCPv6RequestedOptionsDefaultCount = kDHCPv6RequestedOptionsStaticCount;
174
175STATIC uint16_t *	DHCPv6RequestedOptions = (uint16_t *)DHCPv6RequestedOptionsStatic;
176STATIC int		DHCPv6RequestedOptionsCount =  kDHCPv6RequestedOptionsStaticCount;
177
178STATIC int
179S_get_prefix_length(const struct in6_addr * addr, int if_index)
180{
181    int	prefix_length;
182
183    prefix_length = inet6_get_prefix_length(addr, if_index);
184    if (prefix_length == 0) {
185#define DHCPV6_PREFIX_LENGTH		128
186	prefix_length = DHCPV6_PREFIX_LENGTH;
187    }
188    return (prefix_length);
189}
190
191PRIVATE_EXTERN void
192DHCPv6ClientSetRequestedOptions(uint16_t * requested_options,
193				int requested_options_count)
194{
195    if (requested_options != NULL && requested_options_count != 0) {
196	DHCPv6RequestedOptionsDefault = requested_options;
197	DHCPv6RequestedOptionsDefaultCount = requested_options_count;
198    }
199    else {
200	DHCPv6RequestedOptionsDefault
201	    = (uint16_t *)DHCPv6RequestedOptionsStatic;
202	DHCPv6RequestedOptionsDefaultCount
203	    = kDHCPv6RequestedOptionsStaticCount;
204    }
205    DHCPv6RequestedOptions = DHCPv6RequestedOptionsDefault;
206    DHCPv6RequestedOptionsCount = DHCPv6RequestedOptionsDefaultCount;
207    return;
208}
209
210bool
211DHCPv6ClientOptionIsOK(int option)
212{
213    int i;
214
215    switch (option) {
216    case kDHCPv6OPTION_CLIENTID:
217    case kDHCPv6OPTION_SERVERID:
218    case kDHCPv6OPTION_ORO:
219    case kDHCPv6OPTION_ELAPSED_TIME:
220    case kDHCPv6OPTION_UNICAST:
221    case kDHCPv6OPTION_RAPID_COMMIT:
222    case kDHCPv6OPTION_IA_NA:
223    case kDHCPv6OPTION_IAADDR:
224    case kDHCPv6OPTION_STATUS_CODE:
225    case kDHCPv6OPTION_IA_TA:
226    case kDHCPv6OPTION_PREFERENCE:
227    case kDHCPv6OPTION_RELAY_MSG:
228    case kDHCPv6OPTION_AUTH:
229    case kDHCPv6OPTION_USER_CLASS:
230    case kDHCPv6OPTION_VENDOR_CLASS:
231    case kDHCPv6OPTION_VENDOR_OPTS:
232    case kDHCPv6OPTION_INTERFACE_ID:
233    case kDHCPv6OPTION_RECONF_MSG:
234    case kDHCPv6OPTION_RECONF_ACCEPT:
235	return (TRUE);
236    default:
237	break;
238    }
239
240    for (i = 0; i < DHCPv6RequestedOptionsCount; i++) {
241	if (DHCPv6RequestedOptions[i] == option) {
242	    return (TRUE);
243	}
244    }
245    return (FALSE);
246}
247
248STATIC double
249random_double_in_range(double bottom, double top)
250{
251    double		r = (double)arc4random() / (double)UINT32_MAX;
252
253    return (bottom + (top - bottom) * r);
254}
255
256STATIC uint32_t
257get_new_transaction_id(void)
258{
259    uint32_t	r = arc4random();
260
261#define LOWER_24_BITS	((uint32_t)0x00ffffff)
262    /* return the lower 24 bits */
263    return (r & LOWER_24_BITS);
264}
265
266STATIC bool
267S_insert_duid(DHCPv6OptionAreaRef oa_p)
268{
269    CFDataRef			data;
270    DHCPv6OptionErrorString 	err;
271
272    data = DHCPDUIDGet(get_interface_list());
273    if (data == NULL) {
274	return (FALSE);
275    }
276    if (DHCPv6OptionAreaAddOption(oa_p, kDHCPv6OPTION_CLIENTID,
277				  CFDataGetLength(data), CFDataGetBytePtr(data),
278				  &err) == FALSE) {
279	my_log(LOG_NOTICE, "DHCPv6Client: failed to add CLIENTID, %s",
280	       err.str);
281	return (FALSE);
282    }
283    return (TRUE);
284}
285
286STATIC bool
287S_duid_matches(DHCPv6OptionListRef options)
288{
289    CFDataRef		data;
290    DHCPDUIDRef		duid;
291    int			option_len;
292
293    data = DHCPDUIDGet(get_interface_list());
294    duid = (DHCPDUIDRef)
295	DHCPv6OptionListGetOptionDataAndLength(options,
296					       kDHCPv6OPTION_CLIENTID,
297					       &option_len, NULL);
298    if (duid == NULL
299	|| CFDataGetLength(data) != option_len
300	|| bcmp(duid, CFDataGetBytePtr(data), option_len) != 0) {
301	return (FALSE);
302    }
303    return (TRUE);
304}
305
306STATIC DHCPv6OptionIA_NARef
307get_ia_na_addr(DHCPv6ClientRef client, int msg_type,
308	       DHCPv6OptionListRef options,
309	       DHCPv6OptionIAADDRRef * ret_ia_addr)
310{
311    DHCPv6OptionErrorString 	err;
312    DHCPv6OptionIA_NARef	ia_na;
313    DHCPv6OptionListRef		ia_na_options;
314    interface_t *		if_p = DHCPv6ClientGetInterface(client);
315    int				option_len;
316    uint32_t			preferred_lifetime;
317    int				start_index;
318    uint32_t			t1;
319    uint32_t			t2;
320    uint32_t			valid_lifetime;
321
322    *ret_ia_addr = NULL;
323    ia_na = (DHCPv6OptionIA_NARef)
324	DHCPv6OptionListGetOptionDataAndLength(options,
325					       kDHCPv6OPTION_IA_NA,
326					       &option_len, NULL);
327    if (ia_na == NULL
328	|| option_len <= DHCPv6OptionIA_NA_MIN_LENGTH) {
329	/* no IA_NA option */
330	return (NULL);
331    }
332    t1 = DHCPv6OptionIA_NAGetT1(ia_na);
333    t2 = DHCPv6OptionIA_NAGetT2(ia_na);
334    if (t1 != 0 && t2 != 0) {
335	if (t1 > t2) {
336	    /* server is confused */
337	    return (NULL);
338	}
339    }
340    option_len -= DHCPv6OptionIA_NA_MIN_LENGTH;
341    ia_na_options = DHCPv6OptionListCreate(ia_na->options, option_len, &err);
342    if (ia_na_options == NULL) {
343	my_log(LOG_DEBUG,
344	       "DHCPv6 %s: %s IA_NA contains no options",
345	       if_name(if_p), DHCPv6MessageName(msg_type));
346	/* no options */
347	return (NULL);
348    }
349    /* find the first ia_addr with non-zero lifetime */
350    start_index = 0;
351    for (start_index = 0; TRUE; start_index++) {
352	DHCPv6OptionIAADDRRef	ia_addr;
353
354	ia_addr = (DHCPv6OptionIAADDRRef)
355	    DHCPv6OptionListGetOptionDataAndLength(ia_na_options,
356						   kDHCPv6OPTION_IAADDR,
357						   &option_len, &start_index);
358	if (ia_addr == NULL
359	    || option_len < DHCPv6OptionIAADDR_MIN_LENGTH) {
360	    my_log(LOG_DEBUG,
361		   "DHCPv6 %s: %s IA_NA contains no valid IAADDR option",
362		   if_name(if_p), DHCPv6MessageName(msg_type));
363	    /* missing/invalid IAADDR option */
364	    break;
365	}
366	valid_lifetime = DHCPv6OptionIAADDRGetValidLifetime(ia_addr);
367	preferred_lifetime
368	    = DHCPv6OptionIAADDRGetPreferredLifetime(ia_addr);
369	if (valid_lifetime == 0) {
370	    my_log(LOG_DEBUG,
371		   "DHCP %s: %s IA_ADDR has valid/preferred lifetime is 0,"
372		   " skipping",
373		   if_name(if_p), DHCPv6MessageName(msg_type));
374	}
375	else if (preferred_lifetime > valid_lifetime) {
376	    /* server is confused */
377	    my_log(LOG_DEBUG,
378		   "DHCP %s: %s IA_ADDR preferred %d > valid lifetime",
379		   if_name(if_p), DHCPv6MessageName(msg_type),
380		   preferred_lifetime, valid_lifetime);
381	    break;
382	}
383	else {
384	    *ret_ia_addr = ia_addr;
385	    break;
386	}
387    }
388
389    DHCPv6OptionListRelease(&ia_na_options);
390
391    /* if we didn't find a suitable IAADDR, then ignore the IA_NA */
392    if (*ret_ia_addr == NULL) {
393	ia_na = NULL;
394    }
395    return (ia_na);
396}
397
398STATIC uint8_t
399get_preference_value_from_options(DHCPv6OptionListRef options)
400{
401    int				option_len;
402    DHCPv6OptionPREFERENCERef	pref;
403    uint8_t			value = kDHCPv6OptionPREFERENCEMinValue;
404
405    pref = (DHCPv6OptionPREFERENCERef)
406	DHCPv6OptionListGetOptionDataAndLength(options,
407					       kDHCPv6OPTION_PREFERENCE,
408					       &option_len, NULL);
409    if (pref != NULL
410	&& option_len >= DHCPv6OptionPREFERENCE_MIN_LENGTH) {
411	value = pref->value;
412    }
413    return (value);
414}
415
416#define OUR_IA_NA_SIZE	(DHCPv6OptionIA_NA_MIN_LENGTH + DHCPV6_OPTION_HEADER_SIZE + DHCPv6OptionIAADDR_MIN_LENGTH)
417
418STATIC bool
419add_ia_na_option(DHCPv6ClientRef client, DHCPv6OptionAreaRef oa_p,
420		 DHCPv6OptionErrorStringRef err_p)
421{
422    char				buf[OUR_IA_NA_SIZE];
423    DHCPv6OptionIA_NARef		ia_na_p;
424    DHCPv6OptionRef			option;
425    DHCPv6OptionIAADDRRef		ia_addr_p;
426    interface_t *			if_p = DHCPv6ClientGetInterface(client);
427
428    ia_na_p = (DHCPv6OptionIA_NARef)buf;
429    DHCPv6OptionIA_NASetIAID(ia_na_p, DHCPIAIDGet(if_name(if_p)));
430    DHCPv6OptionIA_NASetT1(ia_na_p, 0);
431    DHCPv6OptionIA_NASetT2(ia_na_p, 0);
432    option = (DHCPv6OptionRef)(buf + DHCPv6OptionIA_NA_MIN_LENGTH);
433    DHCPv6OptionSetCode(option, kDHCPv6OPTION_IAADDR);
434    DHCPv6OptionSetLength(option, DHCPv6OptionIAADDR_MIN_LENGTH);
435    ia_addr_p = (DHCPv6OptionIAADDRRef)
436	(((char *)option) + DHCPV6_OPTION_HEADER_SIZE);
437    DHCPv6OptionIAADDRSetAddress(ia_addr_p,
438				 DHCPv6OptionIAADDRGetAddress(client->ia_addr));
439    DHCPv6OptionIAADDRSetPreferredLifetime(ia_addr_p, 0);
440    DHCPv6OptionIAADDRSetValidLifetime(ia_addr_p, 0);
441    return (DHCPv6OptionAreaAddOption(oa_p, kDHCPv6OPTION_IA_NA,
442				      OUR_IA_NA_SIZE, ia_na_p, err_p));
443}
444
445
446/*
447 * Function: option_data_get_length
448 * Purpose:
449 *   Given a pointer to the option data, return its length, which is stored
450 *   in the previous 2 bytes.
451 */
452STATIC int
453option_data_get_length(const void * option_data)
454{
455    const uint16_t *	len_p;
456
457    len_p = (const uint16_t *)(option_data - sizeof(uint16_t));
458    return (ntohs(*len_p));
459}
460
461STATIC CFTimeInterval
462DHCPv6_RAND(void)
463{
464    return (random_double_in_range(-0.1, 0.1));
465}
466
467STATIC CFTimeInterval
468DHCPv6SubsequentTimeout(CFTimeInterval RTprev, CFTimeInterval MRT)
469{
470    CFTimeInterval	RT;
471
472    RT = 2 * RTprev + DHCPv6_RAND() * RTprev;
473    if (MRT != 0 && RT > MRT) {
474	RT = MRT + DHCPv6_RAND() * MRT;
475    }
476    return (RT);
477}
478
479STATIC CFTimeInterval
480DHCPv6InitialTimeout(CFTimeInterval IRT)
481{
482    return (IRT + DHCPv6_RAND() * IRT);
483}
484
485STATIC uint16_t
486get_elapsed_time(DHCPv6ClientRef client)
487{
488    uint16_t	elapsed_time;
489
490    if (client->try == 1) {
491	elapsed_time = 0;
492    }
493    else {
494	uint32_t	elapsed;
495
496	/* elapsed time is in 1/100ths of a second */
497	elapsed = (timer_get_current_time() - client->start_time) * 100;
498#define MAX_ELAPSED	0xffff
499	if (elapsed > MAX_ELAPSED) {
500	    elapsed_time = MAX_ELAPSED;
501	}
502	else {
503	    elapsed_time = htons(elapsed);
504	}
505    }
506    return (elapsed_time);
507}
508
509/**
510 ** DHCPv6Client routines
511 **/
512STATIC void
513DHCPv6ClientSetState(DHCPv6ClientRef client, DHCPv6ClientState cstate)
514{
515    interface_t *	if_p = DHCPv6ClientGetInterface(client);
516
517    client->cstate = cstate;
518    my_log(LOG_DEBUG, "DHCPv6 %s: %s", if_name(if_p),
519	   DHCPv6ClientStateGetName(cstate));
520}
521
522STATIC void
523DHCPv6ClientRemoveAddress(DHCPv6ClientRef client, const char * label)
524{
525    interface_t *	if_p = DHCPv6ClientGetInterface(client);
526    char 		ntopbuf[INET6_ADDRSTRLEN];
527    int			s;
528
529    if (IN6_IS_ADDR_UNSPECIFIED(&client->our_ip)) {
530	return;
531    }
532    if (G_IPConfiguration_verbose) {
533	my_log(LOG_DEBUG, "DHCPv6 %s: %s: removing %s",
534	       if_name(if_p), label,
535	       inet_ntop(AF_INET6, &client->our_ip,
536			 ntopbuf, sizeof(ntopbuf)));
537    }
538    s = inet6_dgram_socket();
539    if (s < 0) {
540	my_log(LOG_ERR,
541	       "DHCPv6ClientRemoveAddress(%s):socket() failed, %s (%d)",
542	       if_name(if_p), strerror(errno), errno);
543    }
544    else {
545	if (inet6_difaddr(s, if_name(if_p), &client->our_ip) < 0) {
546	    my_log(LOG_DEBUG,
547		   "DHCPv6ClientRemoveAddress(%s): remove %s failed, %s (%d)",
548		   if_name(if_p),
549		   inet_ntop(AF_INET6, &client->our_ip,
550			     ntopbuf, sizeof(ntopbuf)),
551		   strerror(errno), errno);
552	}
553	close(s);
554    }
555    bzero(&client->our_ip, sizeof(client->our_ip));
556    client->our_prefix_length = 0;
557    return;
558}
559
560STATIC void
561DHCPv6ClientClearRetransmit(DHCPv6ClientRef client)
562{
563    client->try = 0;
564    return;
565}
566
567STATIC CFTimeInterval
568DHCPv6ClientNextRetransmit(DHCPv6ClientRef client,
569			   CFTimeInterval IRT, CFTimeInterval MRT)
570{
571    client->try++;
572    if (client->try == 1) {
573	client->retransmit_time = DHCPv6InitialTimeout(IRT);
574    }
575    else {
576	client->retransmit_time
577	    = DHCPv6SubsequentTimeout(client->retransmit_time, MRT);
578    }
579    return (client->retransmit_time);
580}
581
582STATIC void
583DHCPv6ClientPostNotification(DHCPv6ClientRef client)
584{
585    if (client->callback_rls != NULL) {
586	CFRunLoopSourceSignal(client->callback_rls);
587    }
588    return;
589}
590
591STATIC void
592DHCPv6ClientCancelPendingEvents(DHCPv6ClientRef client)
593{
594    DHCPv6ClientSetAddressChangedFunc(client, NULL);
595    DHCPv6SocketDisableReceive(client->sock);
596    timer_cancel(client->timer);
597    return;
598}
599
600STATIC void
601DHCPv6ClientClearPacket(DHCPv6ClientRef client)
602{
603    if (client->saved.pkt_len == 0) {
604	return;
605    }
606    bzero(&client->lease, sizeof(client->lease));
607    client->saved.pkt_len = 0;
608    if (client->saved.pkt != NULL) {
609	free(client->saved.pkt);
610	client->saved.pkt = NULL;
611    }
612    DHCPv6OptionListRelease(&client->saved.options);
613    client->server_id = NULL;
614    client->ia_na = NULL;
615    client->ia_addr = NULL;
616    client->saved_verified = FALSE;
617    return;
618}
619
620STATIC void
621DHCPv6ClientInactive(DHCPv6ClientRef client)
622{
623    DHCPv6ClientCancelPendingEvents(client);
624    DHCPv6ClientClearPacket(client);
625    DHCPv6ClientRemoveAddress(client, "Inactive");
626    DHCPv6ClientPostNotification(client);
627    return;
628}
629
630STATIC bool
631DHCPv6ClientLeaseStillValid(DHCPv6ClientRef client)
632{
633    CFAbsoluteTime		current_time;
634    lease_info_t *		lease_p = &client->lease;
635
636    if (lease_p->valid == FALSE) {
637	goto done;
638    }
639    if (lease_p->valid_lifetime == DHCP_INFINITE_LEASE) {
640	goto done;
641    }
642    current_time = timer_get_current_time();
643    if (current_time < lease_p->start) {
644	/* time went backwards */
645	DHCPv6ClientClearPacket(client);
646	lease_p->valid = FALSE;
647	goto done;
648    }
649    if ((current_time - lease_p->start) >= lease_p->valid_lifetime) {
650	/* expired */
651	DHCPv6ClientClearPacket(client);
652	lease_p->valid = FALSE;
653    }
654
655 done:
656    return (lease_p->valid);
657}
658
659STATIC void
660DHCPv6ClientSavePacket(DHCPv6ClientRef client, DHCPv6SocketReceiveDataRef data)
661{
662    CFAbsoluteTime		current_time = timer_get_current_time();
663    DHCPv6OptionErrorString 	err;
664    lease_info_t *		lease_p = &client->lease;
665    int				option_len;
666    uint32_t			preferred_lifetime;
667    uint32_t			t1;
668    uint32_t			t2;
669    uint32_t			valid_lifetime;
670
671    DHCPv6ClientClearPacket(client);
672    client->saved.pkt_len = data->pkt_len;
673    client->saved.pkt = malloc(client->saved.pkt_len);
674    bcopy(data->pkt, client->saved.pkt, client->saved.pkt_len);
675    client->saved.options
676	= DHCPv6OptionListCreateWithPacket(client->saved.pkt,
677					   client->saved.pkt_len, &err);
678    client->server_id = (DHCPDUIDRef)
679	DHCPv6OptionListGetOptionDataAndLength(client->saved.options,
680					       kDHCPv6OPTION_SERVERID,
681					       &option_len, NULL);
682    client->ia_na = get_ia_na_addr(client, client->saved.pkt->msg_type,
683				   client->saved.options,  &client->ia_addr);
684    if (client->ia_na != NULL) {
685	t1 = DHCPv6OptionIA_NAGetT1(client->ia_na);
686	t2 = DHCPv6OptionIA_NAGetT2(client->ia_na);
687	valid_lifetime = DHCPv6OptionIAADDRGetValidLifetime(client->ia_addr);
688	preferred_lifetime
689	    = DHCPv6OptionIAADDRGetPreferredLifetime(client->ia_addr);
690	if (preferred_lifetime == 0) {
691	    preferred_lifetime = valid_lifetime;
692	}
693	if (t1 == 0 || t2 == 0) {
694	    if (preferred_lifetime == DHCP_INFINITE_LEASE) {
695		t1 = t2 = 0;
696	    }
697	    else {
698		t1 = preferred_lifetime * 0.5;
699		t2 = preferred_lifetime * 0.8;
700	    }
701	}
702	else if (t1 == DHCP_INFINITE_LEASE || t2 == DHCP_INFINITE_LEASE) {
703	    t1 = t2 = 0;
704	    preferred_lifetime = DHCP_INFINITE_LEASE;
705	    valid_lifetime = DHCP_INFINITE_LEASE;
706	}
707	lease_p->start = current_time;
708	if (valid_lifetime == DHCP_INFINITE_LEASE) {
709	    lease_p->t1 = lease_p->t2 = 0;
710	    preferred_lifetime = DHCP_INFINITE_LEASE;
711	}
712	else {
713	    lease_p->t1 = t1;
714	    lease_p->t2 = t2;
715	}
716	lease_p->preferred_lifetime = preferred_lifetime;
717	lease_p->valid_lifetime = valid_lifetime;
718    }
719    client->saved_verified = TRUE;
720    return;
721}
722
723STATIC DHCPv6PacketRef
724DHCPv6ClientMakePacket(DHCPv6ClientRef client, int message_type,
725		       char * buf, int buf_size,
726		       DHCPv6OptionAreaRef oa_p)
727{
728    uint16_t			elapsed_time;
729    DHCPv6OptionErrorString 	err;
730    DHCPv6PacketRef		pkt;
731
732    pkt = (DHCPv6PacketRef)buf;
733    DHCPv6PacketSetMessageType(pkt, message_type);
734    DHCPv6PacketSetTransactionID(pkt, client->transaction_id);
735    DHCPv6OptionAreaInit(oa_p, pkt->options,
736			 buf_size - DHCPV6_PACKET_HEADER_LENGTH);
737    if (S_insert_duid(oa_p) == FALSE) {
738	return (NULL);
739    }
740    if (DHCPv6OptionAreaAddOptionRequestOption(oa_p,
741					       DHCPv6RequestedOptions,
742					       DHCPv6RequestedOptionsCount,
743					       &err) == FALSE) {
744	my_log(LOG_NOTICE, "DHCPv6Client: failed to add ORO, %s",
745	       err.str);
746	return (NULL);
747    }
748    elapsed_time = get_elapsed_time(client);
749    if (DHCPv6OptionAreaAddOption(oa_p, kDHCPv6OPTION_ELAPSED_TIME,
750				  sizeof(elapsed_time), &elapsed_time,
751				  &err) == FALSE) {
752	my_log(LOG_NOTICE, "DHCPv6Client: failed to add ELAPSED_TIME, %s",
753	       err.str);
754	return (NULL);
755    }
756    return (pkt);
757}
758
759
760STATIC void
761DHCPv6ClientSendInform(DHCPv6ClientRef client)
762{
763    char			buf[1500];
764    int				error;
765    interface_t *		if_p = DHCPv6ClientGetInterface(client);
766    DHCPv6OptionArea		oa;
767    DHCPv6PacketRef		pkt;
768
769    pkt = DHCPv6ClientMakePacket(client,  kDHCPv6MessageINFORMATION_REQUEST,
770				 buf, sizeof(buf), &oa);
771    if (pkt == NULL) {
772	return;
773    }
774    error = DHCPv6SocketTransmit(client->sock, pkt,
775				 DHCPV6_PACKET_HEADER_LENGTH
776				 + DHCPv6OptionAreaGetUsedLength(&oa));
777    switch (error) {
778    case 0:
779    case ENXIO:
780    case ENETDOWN:
781	break;
782    default:
783	my_log(LOG_NOTICE, "DHCPv6 %s: SendInformRequest transmit failed, %s",
784	       if_name(if_p), strerror(error));
785	break;
786    }
787    return;
788}
789
790STATIC void
791DHCPv6ClientSendSolicit(DHCPv6ClientRef client)
792{
793    char			buf[1500];
794    DHCPv6OptionErrorString 	err;
795    int				error;
796    DHCPv6OptionIA_NA		ia_na;
797    interface_t *		if_p = DHCPv6ClientGetInterface(client);
798    DHCPv6OptionArea		oa;
799    DHCPv6PacketRef		pkt;
800
801    pkt = DHCPv6ClientMakePacket(client, kDHCPv6MessageSOLICIT,
802				 buf, sizeof(buf), &oa);
803    if (pkt == NULL) {
804	return;
805    }
806    DHCPv6OptionIA_NASetIAID(&ia_na, DHCPIAIDGet(if_name(if_p)));
807    DHCPv6OptionIA_NASetT1(&ia_na, 0);
808    DHCPv6OptionIA_NASetT2(&ia_na, 0);
809    if (DHCPv6OptionAreaAddOption(&oa, kDHCPv6OPTION_IA_NA,
810				  DHCPv6OptionIA_NA_MIN_LENGTH,
811				  &ia_na, &err) == FALSE) {
812	my_log(LOG_NOTICE, "DHCPv6Client: failed to add IA_NA, %s",
813	       err.str);
814	return;
815    }
816    error = DHCPv6SocketTransmit(client->sock, pkt,
817				 DHCPV6_PACKET_HEADER_LENGTH
818				 + DHCPv6OptionAreaGetUsedLength(&oa));
819    switch (error) {
820    case 0:
821    case ENXIO:
822    case ENETDOWN:
823	break;
824    default:
825	my_log(LOG_NOTICE, "DHCPv6 %s: SendSolicit transmit failed, %s",
826	       if_name(if_p), strerror(error));
827	break;
828    }
829    return;
830}
831
832STATIC void
833DHCPv6ClientSendPacket(DHCPv6ClientRef client)
834{
835    char			buf[1500];
836    DHCPv6OptionErrorString 	err;
837    int				error;
838    interface_t *		if_p = DHCPv6ClientGetInterface(client);
839    int				message_type;
840    DHCPv6OptionArea		oa;
841    DHCPv6PacketRef		pkt;
842
843    if (client->ia_na == NULL || client->server_id == NULL) {
844	my_log(LOG_NOTICE, "DHCPv6 %s: SendPacket given NULLs",
845	       if_name(if_p));
846	return;
847    }
848    switch (client->cstate) {
849    case kDHCPv6ClientStateRequest:
850	message_type = kDHCPv6MessageREQUEST;
851	break;
852    case kDHCPv6ClientStateRenew:
853	message_type = kDHCPv6MessageRENEW;
854	break;
855    case kDHCPv6ClientStateRebind:
856	message_type = kDHCPv6MessageREBIND;
857	break;
858    case kDHCPv6ClientStateRelease:
859	message_type = kDHCPv6MessageRELEASE;
860	break;
861    case kDHCPv6ClientStateConfirm:
862	message_type = kDHCPv6MessageCONFIRM;
863	break;
864    case kDHCPv6ClientStateDecline:
865	message_type = kDHCPv6MessageDECLINE;
866	break;
867    default:
868	my_log(LOG_NOTICE,
869	       "DHCP %s: SendPacket doesn't know %s",
870	       if_name(if_p), DHCPv6ClientStateGetName(client->cstate));
871	return;
872    }
873    pkt = DHCPv6ClientMakePacket(client, message_type,
874				 buf, sizeof(buf), &oa);
875    if (pkt == NULL) {
876	return;
877    }
878    switch (message_type) {
879    case kDHCPv6MessageREBIND:
880    case kDHCPv6MessageCONFIRM:
881	break;
882    default:
883	if (DHCPv6OptionAreaAddOption(&oa, kDHCPv6OPTION_SERVERID,
884				      option_data_get_length(client->server_id),
885				      client->server_id, &err) == FALSE) {
886	    my_log(LOG_NOTICE, "DHCPv6Client: %s failed to add SERVERID, %s",
887		   DHCPv6ClientStateGetName(client->cstate), err.str);
888	    return;
889	}
890	break;
891    }
892    if (add_ia_na_option(client, &oa, &err) == FALSE) {
893	my_log(LOG_NOTICE, "DHCPv6Client: failed to add IA_NA, %s",
894	       err.str);
895	return;
896    }
897    error = DHCPv6SocketTransmit(client->sock, pkt,
898				 DHCPV6_PACKET_HEADER_LENGTH
899				 + DHCPv6OptionAreaGetUsedLength(&oa));
900    switch (error) {
901    case 0:
902    case ENXIO:
903    case ENETDOWN:
904	break;
905    default:
906	my_log(LOG_NOTICE, "DHCPv6 %s: SendPacket transmit failed, %s",
907	       if_name(if_p), strerror(error));
908	break;
909    }
910    return;
911}
912
913STATIC void
914DHCPv6Client_Inform(DHCPv6ClientRef client, IFEventID_t event_id,
915		   void * event_data)
916{
917    interface_t *	if_p = DHCPv6ClientGetInterface(client);
918
919    switch (event_id) {
920    case IFEventID_start_e:
921	DHCPv6ClientSetState(client, kDHCPv6ClientStateInform);
922	DHCPv6ClientClearPacket(client);
923	DHCPv6ClientClearRetransmit(client);
924	client->transaction_id = get_new_transaction_id();
925	DHCPv6SocketEnableReceive(client->sock, (DHCPv6SocketReceiveFuncPtr)
926				  DHCPv6Client_Inform,
927				  client, (void *)IFEventID_data_e);
928	timer_callout_set(client->timer,
929			  random_double_in_range(0, DHCPv6_INF_MAX_DELAY),
930			  (timer_func_t *)DHCPv6Client_Inform, client,
931			  (void *)IFEventID_timeout_e, NULL);
932	break;
933    case IFEventID_timeout_e:
934	if (client->try == 0) {
935	    client->start_time = timer_get_current_time();
936	}
937	else {
938	    link_status_t	link_status;
939
940	    link_status = if_get_link_status(if_p);
941	    if (link_status.valid && !link_status.active) {
942		DHCPv6ClientInactive(client);
943		break;
944	    }
945	}
946	timer_callout_set(client->timer,
947			  DHCPv6ClientNextRetransmit(client,
948						     DHCPv6_INF_TIMEOUT,
949						     DHCPv6_INF_MAX_RT),
950			  (timer_func_t *)DHCPv6Client_Inform,
951			  client, (void *)IFEventID_timeout_e, NULL);
952	my_log(LOG_DEBUG, "DHCPv6 %s: Inform Transmit (try=%d)",
953	       if_name(if_p), client->try);
954	DHCPv6ClientSendInform(client);
955	break;
956    case IFEventID_data_e: {
957	DHCPv6SocketReceiveDataRef 	data;
958
959	data = (DHCPv6SocketReceiveDataRef)event_data;
960	if (data->pkt->msg_type != kDHCPv6MessageREPLY
961	    || (DHCPv6PacketGetTransactionID((const DHCPv6PacketRef)data->pkt)
962		!= client->transaction_id)
963	    || (S_duid_matches(data->options) == FALSE)) {
964	    /* not a match */
965	    break;
966	}
967	my_log(LOG_DEBUG, "DHCPv6 %s: Reply Received (try=%d)",
968	       if_name(if_p), client->try);
969	DHCPv6ClientSavePacket(client, data);
970	DHCPv6ClientPostNotification(client);
971	DHCPv6ClientCancelPendingEvents(client);
972	/* XXX don't necessarily take the first response */
973	break;
974    }
975    default:
976	break;
977    }
978    return;
979}
980
981STATIC void
982DHCPv6Client_Release(DHCPv6ClientRef client, IFEventID_t event_id,
983		     void * event_data)
984{
985    interface_t *		if_p = DHCPv6ClientGetInterface(client);
986
987    switch (event_id) {
988    case IFEventID_start_e:
989	DHCPv6ClientSetState(client, kDHCPv6ClientStateRelease);
990	DHCPv6ClientRemoveAddress(client, "Release");
991	DHCPv6ClientCancelPendingEvents(client);
992	DHCPv6ClientClearRetransmit(client);
993	client->transaction_id = get_new_transaction_id();
994	my_log(LOG_DEBUG, "DHCPv6 %s: Release Transmit",
995	       if_name(if_p));
996	DHCPv6ClientSendPacket(client);
997	/*
998	 * We're supposed to wait for a Reply.  Unfortunately, that's not
999	 * possible because the code that invokes us expects the Stop
1000	 * event to be synchronous.
1001	 */
1002	break;
1003    default:
1004	break;
1005    }
1006    return;
1007}
1008
1009STATIC void
1010DHCPv6Client_Decline(DHCPv6ClientRef client, IFEventID_t event_id,
1011		     void * event_data)
1012{
1013    interface_t *		if_p = DHCPv6ClientGetInterface(client);
1014
1015    switch (event_id) {
1016    case IFEventID_start_e:
1017	DHCPv6ClientSetState(client, kDHCPv6ClientStateDecline);
1018	DHCPv6ClientRemoveAddress(client, "Decline");
1019	DHCPv6ClientCancelPendingEvents(client);
1020	client->lease.valid = FALSE;
1021	client->saved_verified = FALSE;
1022	DHCPv6ClientPostNotification(client);
1023	DHCPv6ClientClearRetransmit(client);
1024	client->transaction_id = get_new_transaction_id();
1025	DHCPv6SocketEnableReceive(client->sock, (DHCPv6SocketReceiveFuncPtr)
1026				  DHCPv6Client_Decline,
1027				  client, (void *)IFEventID_data_e);
1028	/* FALL THROUGH */
1029    case IFEventID_timeout_e:
1030	if (client->try >= DHCPv6_DEC_MAX_RC) {
1031	    /* go back to Solicit */
1032	    DHCPv6Client_Solicit(client, IFEventID_start_e, NULL);
1033	    return;
1034	}
1035	timer_callout_set(client->timer,
1036			  DHCPv6ClientNextRetransmit(client,
1037						     DHCPv6_DEC_TIMEOUT,
1038						     0),
1039			  (timer_func_t *)DHCPv6Client_Decline,
1040			  client, (void *)IFEventID_timeout_e, NULL);
1041	my_log(LOG_DEBUG, "DHCPv6 %s: Decline Transmit (try=%d)",
1042	       if_name(if_p), client->try);
1043	DHCPv6ClientSendPacket(client);
1044	break;
1045
1046    case IFEventID_data_e: {
1047	DHCPv6SocketReceiveDataRef 	data;
1048	int				option_len;
1049	DHCPDUIDRef			server_id;
1050
1051	data = (DHCPv6SocketReceiveDataRef)event_data;
1052	if (data->pkt->msg_type != kDHCPv6MessageREPLY
1053	    || (DHCPv6PacketGetTransactionID((const DHCPv6PacketRef)data->pkt)
1054		!= client->transaction_id)
1055	    || (S_duid_matches(data->options) == FALSE)) {
1056	    /* not a match */
1057	    break;
1058	}
1059	server_id = (DHCPDUIDRef)
1060	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1061						   kDHCPv6OPTION_SERVERID,
1062						   &option_len, NULL);
1063	if (server_id == NULL
1064	    || DHCPDUIDIsValid(server_id, option_len) == FALSE) {
1065	    /* missing/invalid DUID */
1066	    break;
1067	}
1068	if (G_IPConfiguration_verbose) {
1069	    my_log(LOG_DEBUG, "DHCPv6 %s: Reply Received (try=%d)",
1070		   if_name(if_p), client->try);
1071	}
1072	/* back to Solicit */
1073	DHCPv6Client_Solicit(client, IFEventID_start_e, NULL);
1074	break;
1075    }
1076    default:
1077	break;
1078    }
1079    return;
1080}
1081
1082STATIC void
1083DHCPv6Client_RenewRebind(DHCPv6ClientRef client, IFEventID_t event_id,
1084			 void * event_data)
1085{
1086    CFAbsoluteTime		current_time = timer_get_current_time();
1087    interface_t *		if_p = DHCPv6ClientGetInterface(client);
1088
1089    switch (event_id) {
1090    case IFEventID_start_e:
1091	DHCPv6ClientSetState(client, kDHCPv6ClientStateRenew);
1092	DHCPv6ClientCancelPendingEvents(client);
1093	DHCPv6ClientClearRetransmit(client);
1094	client->start_time = current_time;
1095	client->transaction_id = get_new_transaction_id();
1096	DHCPv6SocketEnableReceive(client->sock, (DHCPv6SocketReceiveFuncPtr)
1097				  DHCPv6Client_RenewRebind,
1098				  client, (void *)IFEventID_data_e);
1099	/* FALL THROUGH */
1100    case IFEventID_timeout_e: {
1101	CFTimeInterval	time_since_start;
1102	CFTimeInterval	wait_time;
1103
1104	if (current_time < client->lease.start) {
1105	    /* time went backwards? */
1106	    DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1107	    return;
1108	}
1109	time_since_start = current_time - client->lease.start;
1110	if (time_since_start >= client->lease.valid_lifetime) {
1111	    /* expired */
1112	    DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1113	    return;
1114	}
1115
1116	if (time_since_start < client->lease.t2) {
1117	    CFTimeInterval	time_until_t2;
1118
1119	    /* Renew (before T2) */
1120	    wait_time = DHCPv6ClientNextRetransmit(client,
1121						   DHCPv6_REN_TIMEOUT,
1122						   DHCPv6_REN_MAX_RT);
1123	    time_until_t2 = client->lease.t2 - time_since_start;
1124	    if (wait_time > time_until_t2) {
1125		wait_time = time_until_t2;
1126	    }
1127	}
1128	else {
1129	    CFTimeInterval	time_until_expiration;
1130
1131	    /* Rebind (T2 or later) */
1132	    if (client->cstate != kDHCPv6ClientStateRebind) {
1133		/* switch to Rebind */
1134		DHCPv6ClientSetState(client, kDHCPv6ClientStateRebind);
1135		DHCPv6ClientClearRetransmit(client);
1136	    }
1137	    wait_time = DHCPv6ClientNextRetransmit(client,
1138						   DHCPv6_REB_TIMEOUT,
1139						   DHCPv6_REB_MAX_RT);
1140	    time_until_expiration
1141		= client->lease.valid_lifetime - time_since_start;
1142	    if (wait_time > time_until_expiration) {
1143		wait_time = time_until_expiration;
1144	    }
1145	}
1146	timer_callout_set(client->timer,
1147			  wait_time,
1148			  (timer_func_t *)DHCPv6Client_RenewRebind,
1149			  client, (void *)IFEventID_timeout_e, NULL);
1150	my_log(LOG_DEBUG, "DHCPv6 %s: %s Transmit (try=%d)",
1151	       if_name(if_p), DHCPv6ClientStateGetName(client->cstate),
1152	       client->try);
1153	DHCPv6ClientSendPacket(client);
1154	break;
1155    }
1156    case IFEventID_data_e: {
1157	DHCPv6SocketReceiveDataRef 	data;
1158	DHCPv6OptionIA_NARef		ia_na;
1159	DHCPv6OptionIAADDRRef		ia_addr;
1160	char 				ntopbuf[INET6_ADDRSTRLEN];
1161	int				option_len;
1162	DHCPDUIDRef			server_id;
1163	DHCPv6OptionSTATUS_CODERef	status_code;
1164
1165	data = (DHCPv6SocketReceiveDataRef)event_data;
1166	if (data->pkt->msg_type != kDHCPv6MessageREPLY
1167	    || (DHCPv6PacketGetTransactionID((const DHCPv6PacketRef)data->pkt)
1168		!= client->transaction_id)
1169	    || (S_duid_matches(data->options) == FALSE)) {
1170	    /* not a match */
1171	    break;
1172	}
1173	server_id = (DHCPDUIDRef)
1174	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1175						   kDHCPv6OPTION_SERVERID,
1176						   &option_len, NULL);
1177	if (server_id == NULL
1178	    || DHCPDUIDIsValid(server_id, option_len) == FALSE) {
1179	    /* missing/invalid DUID */
1180	    break;
1181	}
1182	status_code = (DHCPv6OptionSTATUS_CODERef)
1183	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1184						   kDHCPv6OPTION_STATUS_CODE,
1185						   &option_len, NULL);
1186	if (status_code != NULL) {
1187	    uint16_t	code;
1188
1189	    if (option_len < DHCPv6OptionSTATUS_CODE_MIN_LENGTH) {
1190		/* too short */
1191		break;
1192	    }
1193
1194	    code = DHCPv6OptionSTATUS_CODEGetCode(status_code);
1195	    if (code != kDHCPv6StatusCodeSuccess) {
1196		/* XXX check for a specific value maybe? */
1197		DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1198		return;
1199	    }
1200	}
1201	ia_na = get_ia_na_addr(client, data->pkt->msg_type,
1202			       data->options, &ia_addr);
1203	if (ia_na == NULL) {
1204	    DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1205	    break;
1206	}
1207	if (G_IPConfiguration_verbose) {
1208	    my_log(LOG_DEBUG, "DHCPv6 %s: %s Received Reply (try=%d) "
1209		   "IAADDR %s Preferred %d Valid=%d",
1210		   if_name(if_p),
1211		   DHCPv6ClientStateGetName(client->cstate),
1212		   client->try,
1213		   inet_ntop(AF_INET6,
1214			     DHCPv6OptionIAADDRGetAddress(ia_addr),
1215			     ntopbuf, sizeof(ntopbuf)),
1216		   DHCPv6OptionIAADDRGetPreferredLifetime(ia_addr),
1217		   DHCPv6OptionIAADDRGetValidLifetime(ia_addr));
1218	}
1219	DHCPv6ClientSavePacket(client, data);
1220	DHCPv6Client_Bound(client, IFEventID_start_e, NULL);
1221	break;
1222    }
1223    default:
1224	break;
1225    }
1226}
1227
1228STATIC void
1229DHCPv6Client_Confirm(DHCPv6ClientRef client, IFEventID_t event_id,
1230		     void * event_data)
1231{
1232    CFAbsoluteTime		current_time = timer_get_current_time();
1233    interface_t *		if_p = DHCPv6ClientGetInterface(client);
1234
1235    switch (event_id) {
1236    case IFEventID_start_e:
1237	DHCPv6ClientSetState(client, kDHCPv6ClientStateConfirm);
1238	DHCPv6ClientCancelPendingEvents(client);
1239	DHCPv6ClientClearRetransmit(client);
1240	client->saved_verified = FALSE;
1241	client->transaction_id = get_new_transaction_id();
1242	DHCPv6SocketEnableReceive(client->sock, (DHCPv6SocketReceiveFuncPtr)
1243				  DHCPv6Client_Confirm,
1244				  client, (void *)IFEventID_data_e);
1245	timer_callout_set(client->timer,
1246			  random_double_in_range(0, DHCPv6_CNF_MAX_DELAY),
1247			  (timer_func_t *)DHCPv6Client_Confirm, client,
1248			  (void *)IFEventID_timeout_e, NULL);
1249	break;
1250    case IFEventID_timeout_e:
1251	if (client->try == 0) {
1252	    client->start_time = current_time;
1253	}
1254	else {
1255	    bool		done = FALSE;
1256	    link_status_t	link_status;
1257
1258	    link_status = if_get_link_status(if_p);
1259	    if (link_status.valid && !link_status.active) {
1260		DHCPv6ClientInactive(client);
1261		break;
1262	    }
1263	    if (current_time > client->start_time) {
1264		if ((current_time - client->start_time) >= DHCPv6_CNF_MAX_RD) {
1265		    done = TRUE;
1266		}
1267	    }
1268	    else {
1269		done = TRUE;
1270	    }
1271	    if (done) {
1272		if (DHCPv6ClientLeaseStillValid(client)) {
1273		    DHCPv6Client_Bound(client, IFEventID_start_e, NULL);
1274		    return;
1275		}
1276		DHCPv6Client_Solicit(client, IFEventID_start_e, NULL);
1277		return;
1278	    }
1279	}
1280	timer_callout_set(client->timer,
1281			  DHCPv6ClientNextRetransmit(client,
1282						     DHCPv6_CNF_TIMEOUT,
1283						     DHCPv6_CNF_MAX_RT),
1284			  (timer_func_t *)DHCPv6Client_Confirm,
1285			  client, (void *)IFEventID_timeout_e, NULL);
1286	my_log(LOG_DEBUG, "DHCPv6 %s: Confirm Transmit (try=%d)",
1287	       if_name(if_p), client->try);
1288	DHCPv6ClientSendPacket(client);
1289	break;
1290    case IFEventID_data_e: {
1291	DHCPv6SocketReceiveDataRef 	data;
1292	int				option_len;
1293	DHCPDUIDRef			server_id;
1294	DHCPv6OptionSTATUS_CODERef	status_code;
1295
1296	data = (DHCPv6SocketReceiveDataRef)event_data;
1297	if (data->pkt->msg_type != kDHCPv6MessageREPLY
1298	    || (DHCPv6PacketGetTransactionID((const DHCPv6PacketRef)data->pkt)
1299		!= client->transaction_id)
1300	    || (S_duid_matches(data->options) == FALSE)) {
1301	    /* not a match */
1302	    break;
1303	}
1304	server_id = (DHCPDUIDRef)
1305	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1306						   kDHCPv6OPTION_SERVERID,
1307						   &option_len, NULL);
1308	if (server_id == NULL
1309	    || DHCPDUIDIsValid(server_id, option_len) == FALSE) {
1310	    /* missing/invalid DUID */
1311	    break;
1312	}
1313	status_code = (DHCPv6OptionSTATUS_CODERef)
1314	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1315						   kDHCPv6OPTION_STATUS_CODE,
1316						   &option_len, NULL);
1317	if (status_code == NULL
1318	    || option_len < DHCPv6OptionSTATUS_CODE_MIN_LENGTH) {
1319	    break;
1320	}
1321	if (DHCPv6OptionSTATUS_CODEGetCode(status_code)
1322	    != kDHCPv6StatusCodeSuccess) {
1323	    DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1324	    return;
1325	}
1326	if (G_IPConfiguration_verbose) {
1327	    my_log(LOG_DEBUG, "DHCPv6 %s: Reply Received (try=%d)",
1328		   if_name(if_p), client->try);
1329	}
1330	DHCPv6Client_Bound(client, IFEventID_start_e, NULL);
1331	break;
1332    }
1333    default:
1334	break;
1335    }
1336}
1337
1338STATIC void
1339DHCPv6ClientHandleAddressChanged(DHCPv6ClientRef client,
1340				 inet6_addrlist_t * addr_list_p)
1341{
1342    int				i;
1343    inet6_addrinfo_t *		scan;
1344
1345    if (addr_list_p == NULL || addr_list_p->count == 0) {
1346	/* no addresses configured, nothing to do */
1347	return;
1348    }
1349    for (i = 0, scan = addr_list_p->list;
1350	 i < addr_list_p->count; i++, scan++) {
1351	if (IN6_ARE_ADDR_EQUAL(&client->our_ip, &scan->addr)) {
1352	    /* someone else is using this address, decline it */
1353	    if ((scan->addr_flags & IN6_IFF_DUPLICATED) != 0) {
1354		DHCPv6Client_Decline(client, IFEventID_start_e, NULL);
1355		return;
1356	    }
1357	    if ((scan->addr_flags & IN6_IFF_TENTATIVE) != 0) {
1358		my_log(LOG_DEBUG, "address is still tentative");
1359		/* address is still tentative */
1360		break;
1361	    }
1362	    /* notify that we're ready */
1363	    DHCPv6ClientPostNotification(client);
1364	    DHCPv6ClientCancelPendingEvents(client);
1365
1366	    /* set a timer to start in Renew */
1367	    if (client->lease.valid_lifetime != DHCP_INFINITE_LEASE) {
1368		CFAbsoluteTime	current_time = timer_get_current_time();
1369		uint32_t		t1 = client->lease.t1;
1370		CFTimeInterval	time_since_start = 0;
1371
1372		if (current_time < client->lease.start) {
1373		    /* time went backwards? */
1374		    DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1375		    return;
1376		}
1377		time_since_start = current_time - client->lease.start;
1378		if (time_since_start < t1) {
1379		    t1 -= time_since_start;
1380		}
1381		else {
1382		    t1 = 10; /* wakeup in 10 seconds */
1383		}
1384		timer_callout_set(client->timer, t1,
1385				  (timer_func_t *)DHCPv6Client_RenewRebind,
1386				  client, (void *)IFEventID_start_e, NULL);
1387	    }
1388	    break;
1389	}
1390    }
1391    return;
1392}
1393
1394STATIC void
1395DHCPv6ClientSimulateAddressChanged(DHCPv6ClientRef client)
1396{
1397    inet6_addrlist_t	addr_list;
1398    interface_t *	if_p = DHCPv6ClientGetInterface(client);
1399
1400    inet6_addrlist_copy(&addr_list, if_link_index(if_p));
1401    DHCPv6ClientHandleAddressChanged(client, &addr_list);
1402    inet6_addrlist_free(&addr_list);
1403    return;
1404}
1405
1406STATIC void
1407DHCPv6Client_Bound(DHCPv6ClientRef client, IFEventID_t event_id,
1408		   void * event_data)
1409{
1410    inet6_addrlist_t * 		addr_list_p;
1411    interface_t *		if_p = DHCPv6ClientGetInterface(client);
1412    char 			ntopbuf[INET6_ADDRSTRLEN];
1413
1414    switch (event_id) {
1415    case IFEventID_start_e: {
1416	struct in6_addr *	our_ip;
1417	struct in6_addr         our_ip_aligned;
1418	uint32_t		preferred_lifetime;
1419	int			prefix_length;
1420	int			s;
1421	bool			same_address = FALSE;
1422	CFTimeInterval		time_since_start = 0;
1423	uint32_t		valid_lifetime;
1424
1425	our_ip = &our_ip_aligned;
1426	bcopy((void *)DHCPv6OptionIAADDRGetAddress(client->ia_addr),
1427	      our_ip, sizeof(our_ip_aligned));
1428
1429	DHCPv6ClientSetState(client, kDHCPv6ClientStateBound);
1430	client->lease.valid = TRUE;
1431	client->saved_verified = TRUE;
1432	DHCPv6ClientCancelPendingEvents(client);
1433
1434	valid_lifetime = client->lease.valid_lifetime;
1435	preferred_lifetime = client->lease.preferred_lifetime;
1436	if (valid_lifetime != DHCP_INFINITE_LEASE) {
1437	    CFAbsoluteTime		current_time = timer_get_current_time();
1438
1439	    if (current_time < client->lease.start) {
1440		/* time went backwards? */
1441		DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1442		return;
1443	    }
1444	    time_since_start = current_time - client->lease.start;
1445	    if (time_since_start >= client->lease.valid_lifetime) {
1446		/* expired */
1447		DHCPv6Client_Unbound(client, IFEventID_start_e, NULL);
1448		return;
1449	    }
1450	    /* reduce the time left by the amount that's elapsed already */
1451	    valid_lifetime -= time_since_start;
1452	    if (time_since_start < preferred_lifetime) {
1453		preferred_lifetime -= time_since_start;
1454	    }
1455	    else {
1456		preferred_lifetime = 0; /* XXX really? */
1457	    }
1458	}
1459	s = inet6_dgram_socket();
1460	if (s < 0) {
1461	    my_log(LOG_ERR,
1462		   "DHCPv6ClientBound(%s):"
1463		   " socket() failed, %s (%d)",
1464		   if_name(if_p), strerror(errno), errno);
1465	    break;
1466	}
1467	/* if the address has changed, remove the old first */
1468	if (IN6_IS_ADDR_UNSPECIFIED(&client->our_ip) == FALSE) {
1469	    if (IN6_ARE_ADDR_EQUAL(&client->our_ip, our_ip)) {
1470		same_address = TRUE;
1471	    }
1472	    else {
1473		if (G_IPConfiguration_verbose) {
1474		    my_log(LOG_DEBUG, "DHCPv6 %s: Bound: removing %s",
1475			   if_name(if_p),
1476			   inet_ntop(AF_INET6, &client->our_ip,
1477				     ntopbuf, sizeof(ntopbuf)));
1478		}
1479		if (inet6_difaddr(s, if_name(if_p), &client->our_ip) < 0) {
1480		    my_log(LOG_DEBUG,
1481			   "DHCPv6ClientBound(%s): remove %s failed, %s (%d)",
1482			   if_name(if_p),
1483			   inet_ntop(AF_INET6, &client->our_ip,
1484				     ntopbuf, sizeof(ntopbuf)),
1485			   strerror(errno), errno);
1486		}
1487	    }
1488	}
1489	prefix_length = S_get_prefix_length(our_ip, if_link_index(if_p));
1490	if (G_IPConfiguration_verbose) {
1491	    my_log(LOG_DEBUG,
1492		   "DHCPv6 %s: setting %s/%d valid %d preferred %d",
1493		   if_name(if_p),
1494		   inet_ntop(AF_INET6, our_ip, ntopbuf, sizeof(ntopbuf)),
1495		   prefix_length, valid_lifetime, preferred_lifetime);
1496	}
1497	if (inet6_aifaddr(s, if_name(if_p), our_ip, NULL,
1498			  prefix_length,
1499			  valid_lifetime, preferred_lifetime) < 0) {
1500	    my_log(LOG_DEBUG,
1501		   "DHCPv6ClientBound(%s): adding %s failed, %s (%d)",
1502		   if_name(if_p),
1503		   inet_ntop(AF_INET6, our_ip,
1504			     ntopbuf, sizeof(ntopbuf)),
1505		   strerror(errno), errno);
1506	}
1507	else if (same_address) {
1508	    /* notify that we're ready */
1509	    DHCPv6ClientPostNotification(client);
1510	    DHCPv6ClientCancelPendingEvents(client);
1511
1512	    /* set a timer to start in Renew */
1513	    if (client->lease.valid_lifetime != DHCP_INFINITE_LEASE) {
1514		uint32_t	t1 = client->lease.t1;
1515
1516		if (time_since_start < t1) {
1517		    t1 -= time_since_start;
1518		}
1519		else {
1520		    t1 = 10; /* wakeup in 10 seconds */
1521
1522		}
1523		timer_callout_set(client->timer, t1,
1524				  (timer_func_t *)DHCPv6Client_RenewRebind,
1525				  client, (void *)IFEventID_start_e, NULL);
1526	    }
1527	}
1528	else {
1529	    /* register to receive address changed notifications */
1530	    DHCPv6ClientSetAddressChangedFunc(client, DHCPv6Client_Bound);
1531	    client->our_ip = *our_ip;
1532	    client->our_prefix_length = prefix_length;
1533	    /* and see what addresses are there now */
1534	    DHCPv6ClientSimulateAddressChanged(client);
1535	}
1536	close(s);
1537	break;
1538    }
1539    case IFEventID_ipv6_address_changed_e:
1540	addr_list_p = (inet6_addrlist_t *)event_data;
1541	DHCPv6ClientHandleAddressChanged(client, addr_list_p);
1542	break;
1543    default:
1544	break;
1545    }
1546}
1547
1548STATIC void
1549DHCPv6Client_Unbound(DHCPv6ClientRef client, IFEventID_t event_id,
1550		     void * event_data)
1551{
1552    switch (event_id) {
1553    case IFEventID_start_e:
1554	DHCPv6ClientSetState(client, kDHCPv6ClientStateUnbound);
1555	DHCPv6ClientCancelPendingEvents(client);
1556	DHCPv6ClientRemoveAddress(client, "Unbound");
1557	DHCPv6ClientClearPacket(client);
1558	DHCPv6ClientPostNotification(client);
1559	DHCPv6Client_Solicit(client, IFEventID_start_e, NULL);
1560	break;
1561    default:
1562	break;
1563    }
1564}
1565
1566STATIC void
1567DHCPv6Client_Request(DHCPv6ClientRef client, IFEventID_t event_id,
1568		     void * event_data)
1569{
1570    interface_t *	if_p = DHCPv6ClientGetInterface(client);
1571
1572    switch (event_id) {
1573    case IFEventID_start_e:
1574	DHCPv6ClientSetState(client, kDHCPv6ClientStateRequest);
1575	DHCPv6ClientClearRetransmit(client);
1576	client->transaction_id = get_new_transaction_id();
1577	DHCPv6SocketEnableReceive(client->sock, (DHCPv6SocketReceiveFuncPtr)
1578				  DHCPv6Client_Request,
1579				  client, (void *)IFEventID_data_e);
1580	/* FALL THROUGH */
1581    case IFEventID_timeout_e: {
1582	if (client->try >= DHCPv6_REQ_MAX_RC) {
1583	    /* go back to Solicit */
1584	    DHCPv6Client_Solicit(client, IFEventID_start_e, NULL);
1585	    return;
1586	}
1587	timer_callout_set(client->timer,
1588			  DHCPv6ClientNextRetransmit(client,
1589						     DHCPv6_REQ_TIMEOUT,
1590						     DHCPv6_REQ_MAX_RT),
1591			  (timer_func_t *)DHCPv6Client_Request,
1592			  client, (void *)IFEventID_timeout_e, NULL);
1593	my_log(LOG_DEBUG, "DHCPv6 %s: Request Transmit (try=%d)",
1594	       if_name(if_p), client->try);
1595	DHCPv6ClientSendPacket(client);
1596	break;
1597    }
1598    case IFEventID_data_e: {
1599	DHCPv6SocketReceiveDataRef 	data;
1600	DHCPv6OptionIA_NARef		ia_na;
1601	DHCPv6OptionIAADDRRef		ia_addr;
1602	char 				ntopbuf[INET6_ADDRSTRLEN];
1603	int				option_len;
1604	DHCPDUIDRef			server_id;
1605	DHCPv6OptionSTATUS_CODERef	status_code;
1606
1607	data = (DHCPv6SocketReceiveDataRef)event_data;
1608	if (data->pkt->msg_type != kDHCPv6MessageREPLY
1609	    || (DHCPv6PacketGetTransactionID((const DHCPv6PacketRef)data->pkt)
1610		!= client->transaction_id)
1611	    || (S_duid_matches(data->options) == FALSE)) {
1612	    /* not a match */
1613	    break;
1614	}
1615	server_id = (DHCPDUIDRef)
1616	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1617						   kDHCPv6OPTION_SERVERID,
1618						   &option_len, NULL);
1619	if (server_id == NULL
1620	    || DHCPDUIDIsValid(server_id, option_len) == FALSE) {
1621	    /* missing/invalid DUID */
1622	    break;
1623	}
1624	status_code = (DHCPv6OptionSTATUS_CODERef)
1625	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1626						   kDHCPv6OPTION_STATUS_CODE,
1627						   &option_len, NULL);
1628	if (status_code != NULL) {
1629	    uint16_t	code;
1630
1631	    if (option_len < DHCPv6OptionSTATUS_CODE_MIN_LENGTH) {
1632		/* too short */
1633		break;
1634	    }
1635
1636	    code = DHCPv6OptionSTATUS_CODEGetCode(status_code);
1637	    if (code != kDHCPv6StatusCodeSuccess) {
1638		if (code == kDHCPv6StatusCodeNoAddrsAvail) {
1639		    /* must ignore it */
1640		    break;
1641		}
1642	    }
1643	}
1644	ia_na = get_ia_na_addr(client, data->pkt->msg_type,
1645			       data->options, &ia_addr);
1646	if (ia_na == NULL) {
1647	    break;
1648	}
1649	if (G_IPConfiguration_verbose) {
1650	    my_log(LOG_DEBUG, "DHCPv6 %s: Reply Received (try=%d) "
1651		   "IAADDR %s Preferred %d Valid=%d",
1652		   if_name(if_p),
1653		   client->try,
1654		   inet_ntop(AF_INET6,
1655			     DHCPv6OptionIAADDRGetAddress(ia_addr),
1656			     ntopbuf, sizeof(ntopbuf)),
1657		   DHCPv6OptionIAADDRGetPreferredLifetime(ia_addr),
1658		   DHCPv6OptionIAADDRGetValidLifetime(ia_addr));
1659	}
1660	DHCPv6ClientSavePacket(client, data);
1661	DHCPv6Client_Bound(client, IFEventID_start_e, NULL);
1662	break;
1663    }
1664    default:
1665	break;
1666    }
1667    return;
1668}
1669
1670STATIC void
1671DHCPv6Client_Solicit(DHCPv6ClientRef client, IFEventID_t event_id,
1672		     void * event_data)
1673{
1674    interface_t *	if_p = DHCPv6ClientGetInterface(client);
1675
1676    switch (event_id) {
1677    case IFEventID_start_e:
1678	DHCPv6ClientSetState(client, kDHCPv6ClientStateSolicit);
1679	DHCPv6ClientClearRetransmit(client);
1680	DHCPv6ClientClearPacket(client);
1681	client->transaction_id = get_new_transaction_id();
1682	DHCPv6SocketEnableReceive(client->sock, (DHCPv6SocketReceiveFuncPtr)
1683				  DHCPv6Client_Solicit,
1684				  client, (void *)IFEventID_data_e);
1685	timer_callout_set(client->timer,
1686			  random_double_in_range(0, DHCPv6_SOL_MAX_DELAY),
1687			  (timer_func_t *)DHCPv6Client_Solicit, client,
1688			  (void *)IFEventID_timeout_e, NULL);
1689	break;
1690    case IFEventID_timeout_e: {
1691	if (client->try == 0) {
1692	    client->start_time = timer_get_current_time();
1693	}
1694	else {
1695	    link_status_t	link_status;
1696
1697	    link_status = if_get_link_status(if_p);
1698	    if (link_status.valid && !link_status.active) {
1699		DHCPv6ClientInactive(client);
1700		break;
1701	    }
1702	}
1703	/* we've got a packet */
1704	if (client->saved.pkt_len != 0) {
1705	    DHCPv6Client_Request(client, IFEventID_start_e, NULL);
1706	    return;
1707	}
1708	timer_callout_set(client->timer,
1709			  DHCPv6ClientNextRetransmit(client,
1710						     DHCPv6_SOL_TIMEOUT,
1711						     DHCPv6_SOL_MAX_RT),
1712			  (timer_func_t *)DHCPv6Client_Solicit,
1713			  client, (void *)IFEventID_timeout_e, NULL);
1714	my_log(LOG_DEBUG, "DHCPv6 %s: Solicit Transmit (try=%d)",
1715	       if_name(if_p), client->try);
1716	DHCPv6ClientSendSolicit(client);
1717	break;
1718    }
1719    case IFEventID_data_e: {
1720	DHCPv6SocketReceiveDataRef 	data;
1721	DHCPv6OptionIA_NARef		ia_na;
1722	DHCPv6OptionIAADDRRef		ia_addr;
1723	char 				ntopbuf[INET6_ADDRSTRLEN];
1724	int				option_len;
1725	uint8_t				pref;
1726	DHCPDUIDRef			server_id;
1727	DHCPv6OptionSTATUS_CODERef	status_code;
1728
1729	data = (DHCPv6SocketReceiveDataRef)event_data;
1730	if (data->pkt->msg_type != kDHCPv6MessageADVERTISE
1731	    || (DHCPv6PacketGetTransactionID((const DHCPv6PacketRef)data->pkt)
1732		!= client->transaction_id)
1733	    || (S_duid_matches(data->options) == FALSE)) {
1734	    /* not a match */
1735	    break;
1736	}
1737	server_id = (DHCPDUIDRef)
1738	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1739						   kDHCPv6OPTION_SERVERID,
1740						   &option_len, NULL);
1741	if (server_id == NULL
1742	    || DHCPDUIDIsValid(server_id, option_len) == FALSE) {
1743	    /* missing/invalid DUID */
1744	    break;
1745	}
1746	status_code = (DHCPv6OptionSTATUS_CODERef)
1747	    DHCPv6OptionListGetOptionDataAndLength(data->options,
1748						   kDHCPv6OPTION_STATUS_CODE,
1749						   &option_len, NULL);
1750	if (status_code != NULL) {
1751	    uint16_t	code;
1752
1753	    if (option_len < DHCPv6OptionSTATUS_CODE_MIN_LENGTH) {
1754		/* too short */
1755		break;
1756	    }
1757
1758	    code = DHCPv6OptionSTATUS_CODEGetCode(status_code);
1759	    if (code != kDHCPv6StatusCodeSuccess) {
1760		if (code == kDHCPv6StatusCodeNoAddrsAvail) {
1761		    /* must ignore it */
1762		    break;
1763		}
1764	    }
1765	}
1766	ia_na = get_ia_na_addr(client, data->pkt->msg_type,
1767			       data->options, &ia_addr);
1768	if (ia_na == NULL) {
1769	    break;
1770	}
1771	if (G_IPConfiguration_verbose) {
1772	    my_log(LOG_DEBUG, "DHCPv6 %s: Advertise Received (try=%d) "
1773		   "IAADDR %s Preferred %d Valid=%d",
1774		   if_name(if_p),
1775		   client->try,
1776		   inet_ntop(AF_INET6,
1777			     DHCPv6OptionIAADDRGetAddress(ia_addr),
1778			     ntopbuf, sizeof(ntopbuf)),
1779		   DHCPv6OptionIAADDRGetPreferredLifetime(ia_addr),
1780		   DHCPv6OptionIAADDRGetValidLifetime(ia_addr));
1781	}
1782
1783	/* check for a server preference value */
1784	pref = get_preference_value_from_options(data->options);
1785
1786	/* if this response is "better" than one we saved, use it */
1787	if (client->saved.options != NULL) {
1788	    uint8_t		saved_pref;
1789
1790	    saved_pref
1791		= get_preference_value_from_options(client->saved.options);
1792	    if (saved_pref >= pref) {
1793		/* saved packet is still "better" */
1794		break;
1795	    }
1796	}
1797	if (G_IPConfiguration_verbose) {
1798	    CFMutableStringRef	str;
1799
1800	    str = CFStringCreateMutable(NULL, 0);
1801	    DHCPDUIDPrintToString(str, server_id,
1802				  option_data_get_length(server_id));
1803	    my_log(LOG_DEBUG, "DHCPv6 %s: Saving Advertise from %@",
1804		   if_name(if_p), str);
1805	    CFRelease(str);
1806	}
1807	DHCPv6ClientSavePacket(client, data);
1808	if (pref == kDHCPv6OptionPREFERENCEMaxValue) {
1809	    /* if preference is max, jump right to Request */
1810	    DHCPv6Client_Request(client, IFEventID_start_e, NULL);
1811	    break;
1812	}
1813	break;
1814    }
1815    default:
1816	break;
1817    }
1818    return;
1819}
1820
1821DHCPv6ClientRef
1822DHCPv6ClientCreate(interface_t * if_p)
1823{
1824    DHCPv6ClientRef		client;
1825
1826    client = (DHCPv6ClientRef)malloc(sizeof(*client));
1827    bzero(client, sizeof(*client));
1828    client->sock = DHCPv6SocketCreate(if_p);
1829    client->timer = timer_callout_init();
1830    return (client);
1831}
1832
1833void
1834DHCPv6ClientStart(DHCPv6ClientRef client, bool allocate_address)
1835{
1836    if (allocate_address) {
1837	/* start Stateful */
1838	if (DHCPv6ClientLeaseStillValid(client)) {
1839	    DHCPv6Client_Confirm(client, IFEventID_start_e, NULL);
1840	}
1841	else {
1842	    DHCPv6Client_Solicit(client, IFEventID_start_e, NULL);
1843	}
1844    }
1845    else {
1846	/* start Stateless */
1847	DHCPv6ClientRemoveAddress(client, "Start");
1848	DHCPv6Client_Inform(client, IFEventID_start_e, NULL);
1849    }
1850    return;
1851}
1852
1853void
1854DHCPv6ClientStop(DHCPv6ClientRef client, bool discard_information)
1855{
1856    /* remove the IP address */
1857    DHCPv6ClientRemoveAddress(client, "Stop");
1858    DHCPv6ClientCancelPendingEvents(client);
1859    if (discard_information) {
1860	DHCPv6ClientClearPacket(client);
1861    }
1862    else {
1863	client->saved_verified = FALSE;
1864    }
1865    DHCPv6ClientPostNotification(client);
1866    return;
1867}
1868
1869void
1870DHCPv6ClientRelease(DHCPv6ClientRef * client_p)
1871{
1872    DHCPv6ClientRef	client = *client_p;
1873
1874    if (client == NULL) {
1875	return;
1876    }
1877    *client_p = NULL;
1878    if (DHCPv6ClientLeaseStillValid(client)) {
1879	DHCPv6Client_Release(client, IFEventID_start_e, NULL);
1880    }
1881    if (client->timer != NULL) {
1882	timer_callout_free(&client->timer);
1883    }
1884    DHCPv6SocketRelease(&client->sock);
1885    if (client->saved.pkt != NULL) {
1886	free(client->saved.pkt);
1887	client->saved.pkt = NULL;
1888    }
1889    DHCPv6ClientSetNotificationCallBack(client, NULL, NULL);
1890    DHCPv6OptionListRelease(&client->saved.options);
1891    free(client);
1892    return;
1893}
1894
1895bool
1896DHCPv6ClientGetInfo(DHCPv6ClientRef client, dhcpv6_info_t * info_p)
1897{
1898    if (client->saved.options == NULL || client->saved_verified == FALSE) {
1899	info_p->pkt = NULL;
1900	info_p->pkt_len = 0;
1901	info_p->options = NULL;
1902	return (FALSE);
1903    }
1904    *info_p = client->saved;
1905    return (TRUE);
1906}
1907
1908void
1909DHCPv6ClientCopyAddresses(DHCPv6ClientRef client,
1910			  inet6_addrlist_t * addr_list_p)
1911{
1912    if (IN6_IS_ADDR_UNSPECIFIED(&client->our_ip)) {
1913	inet6_addrlist_init(addr_list_p);
1914	return;
1915    }
1916    addr_list_p->list = addr_list_p->list_static;
1917    addr_list_p->count = 1;
1918    addr_list_p->list[0].addr = client->our_ip;
1919    addr_list_p->list[0].prefix_length = client->our_prefix_length;
1920    addr_list_p->list[0].addr_flags = 0;
1921    return;
1922}
1923
1924STATIC void
1925DHCPv6ClientDeliverNotification(void * info)
1926{
1927    DHCPv6ClientRef	client = (DHCPv6ClientRef)info;
1928
1929    if (client->callback == NULL) {
1930	/* this can't really happen */
1931	my_log(LOG_NOTICE,
1932	       "DHCPv6Client: runloop source signaled but callback is NULL");
1933	return;
1934    }
1935    (*client->callback)(client->callback_arg, client);
1936    return;
1937}
1938
1939void
1940DHCPv6ClientSetNotificationCallBack(DHCPv6ClientRef client,
1941				    DHCPv6ClientNotificationCallBack callback,
1942				    void * callback_arg)
1943{
1944    client->callback = callback;
1945    client->callback_arg = callback_arg;
1946    if (callback == NULL) {
1947	if (client->callback_rls != NULL) {
1948	    CFRunLoopSourceInvalidate(client->callback_rls);
1949	    my_CFRelease(&client->callback_rls);
1950	}
1951    }
1952    else if (client->callback_rls == NULL) {
1953	CFRunLoopSourceContext 	context;
1954
1955	bzero(&context, sizeof(context));
1956	context.info = (void *)client;
1957	context.perform = DHCPv6ClientDeliverNotification;
1958	client->callback_rls = CFRunLoopSourceCreate(NULL, 0, &context);
1959	CFRunLoopAddSource(CFRunLoopGetCurrent(), client->callback_rls,
1960			   kCFRunLoopDefaultMode);
1961    }
1962    return;
1963}
1964
1965void
1966DHCPv6ClientAddressChanged(DHCPv6ClientRef client,
1967			   inet6_addrlist_t * addr_list_p)
1968{
1969    if (addr_list_p == NULL || addr_list_p->count == 0) {
1970	/* no addresses configured, nothing to do */
1971	return;
1972    }
1973    if (client->address_changed_func != NULL) {
1974	(*client->address_changed_func)(client,
1975					IFEventID_ipv6_address_changed_e,
1976					addr_list_p);
1977    }
1978    else {
1979	/* XXX check for an on-going conflict */
1980    }
1981    return;
1982}
1983
1984#if TEST_DHCPV6_CLIENT
1985#include "sysconfig.h"
1986
1987#include <SystemConfiguration/SCPrivate.h>
1988
1989boolean_t G_is_netboot;
1990int G_dhcp_duid_type;
1991Boolean G_IPConfiguration_verbose = TRUE;
1992
1993bool S_allocate_address;
1994
1995STATIC void
1996client_notification(void * callback_arg, DHCPv6ClientRef client)
1997{
1998    dhcpv6_info_t	info;
1999
2000    if (DHCPv6ClientGetInfo(client, &info) == FALSE) {
2001	printf("DHCPv6 updated: no info\n");
2002    }
2003    else {
2004	printf("DHCPv6 updated\n");
2005	DHCPv6OptionListFPrint(stdout, info.options);
2006    }
2007    return;
2008}
2009
2010interface_list_t *
2011get_interface_list(void)
2012{
2013    STATIC interface_list_t *	S_interfaces;
2014
2015    if (S_interfaces == NULL) {
2016	S_interfaces = ifl_init();
2017    }
2018    return (S_interfaces);
2019}
2020
2021STATIC void
2022handle_change(SCDynamicStoreRef session, CFArrayRef changes, void * arg)
2023{
2024    int			count;
2025    DHCPv6ClientRef	client = (DHCPv6ClientRef)arg;
2026    int			i;
2027    interface_t *	if_p = DHCPv6ClientGetInterface(client);
2028
2029
2030    if (changes == NULL || (count = CFArrayGetCount(changes)) == 0) {
2031	return;
2032    }
2033
2034    for (i = 0; i < count; i++) {
2035	CFStringRef	key = CFArrayGetValueAtIndex(changes, i);
2036
2037	if (CFStringHasSuffix(key, kSCEntNetLink)) {
2038	    CFBooleanRef	active = kCFBooleanTrue;
2039	    CFDictionaryRef	dict;
2040
2041	    my_log(LOG_NOTICE, "link changed");
2042	    dict = SCDynamicStoreCopyValue(session, key);
2043	    if (dict != NULL) {
2044		if (CFDictionaryGetValue(dict, kSCPropNetLinkDetaching)) {
2045		    my_log(LOG_NOTICE, "%s detaching - exiting",
2046			   if_name(if_p));
2047		    exit(0);
2048		}
2049		active = CFDictionaryGetValue(dict, kSCPropNetLinkActive);
2050	    }
2051	    if (CFEqual(active, kCFBooleanTrue)) {
2052		DHCPv6ClientStart(client, S_allocate_address);
2053	    }
2054	    else {
2055		DHCPv6ClientStop(client, FALSE);
2056	    }
2057	}
2058	else if (CFStringHasSuffix(key, kSCEntNetIPv6)) {
2059	    inet6_addrlist_t 	addr_list;
2060
2061	    my_log(LOG_NOTICE, "address changed");
2062	    /* get the addresses from the interface and deliver the event */
2063	    inet6_addrlist_copy(&addr_list, if_link_index(if_p));
2064	    DHCPv6ClientAddressChanged(client, &addr_list);
2065	    inet6_addrlist_free(&addr_list);
2066	}
2067    }
2068    return;
2069}
2070
2071STATIC void
2072notification_init(DHCPv6ClientRef client)
2073{
2074    CFArrayRef			array;
2075    SCDynamicStoreContext	context;
2076    CFStringRef			ifname_cf;
2077    const void *		keys[2];
2078    CFRunLoopSourceRef		rls;
2079    SCDynamicStoreRef		store;
2080
2081    bzero(&context, sizeof(context));
2082    context.info = client;
2083    store = SCDynamicStoreCreate(NULL, CFSTR("DHCPv6Client"),
2084				 handle_change, &context);
2085    if (store == NULL) {
2086	my_log(LOG_ERR, "SCDynamicStoreCreate failed: %s",
2087	       SCErrorString(SCError()));
2088	return;
2089    }
2090    ifname_cf
2091	= CFStringCreateWithCString(NULL,
2092				    if_name(DHCPv6ClientGetInterface(client)),
2093				    kCFStringEncodingASCII);
2094    keys[0] = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
2095							    kSCDynamicStoreDomainState,
2096							    ifname_cf,
2097							    kSCEntNetIPv6);
2098    keys[1] = SCDynamicStoreKeyCreateNetworkInterfaceEntity(NULL,
2099							    kSCDynamicStoreDomainState,
2100							    ifname_cf,
2101							    kSCEntNetLink);
2102    CFRelease(ifname_cf);
2103    array = CFArrayCreate(NULL, (const void **)keys, 2, &kCFTypeArrayCallBacks);
2104    SCDynamicStoreSetNotificationKeys(store, array, NULL);
2105    CFRelease(array);
2106    rls = SCDynamicStoreCreateRunLoopSource(NULL, store, 0);
2107    CFRunLoopAddSource(CFRunLoopGetCurrent(), rls, kCFRunLoopDefaultMode);
2108    CFRelease(rls);
2109    return;
2110}
2111
2112int
2113main(int argc, char * argv[])
2114{
2115    DHCPv6ClientRef		client;
2116    interface_t *		if_p;
2117    const char *		ifname;
2118    interface_list_t *		interfaces = NULL;
2119
2120    if (argc < 2) {
2121	fprintf(stderr, "%s <ifname>\n", argv[0]);
2122	exit(1);
2123    }
2124    interfaces = get_interface_list();
2125    if (interfaces == NULL) {
2126	fprintf(stderr, "failed to get interface list\n");
2127	exit(2);
2128    }
2129    ifname = argv[1];
2130    if_p = ifl_find_name(interfaces, ifname);
2131    if (if_p == NULL) {
2132	fprintf(stderr, "No such interface '%s'\n", ifname);
2133	exit(2);
2134    }
2135    (void) openlog("DHCPv6Client", LOG_PERROR | LOG_PID, LOG_DAEMON);
2136    DHCPv6SocketVerbose(TRUE);
2137    client = DHCPv6ClientCreate(if_p);
2138    if (client == NULL) {
2139	fprintf(stderr, "DHCPv6ClientCreate(%s) failed\n", ifname);
2140	exit(2);
2141    }
2142    notification_init(client);
2143    DHCPv6ClientSetNotificationCallBack(client, client_notification, NULL);
2144    S_allocate_address = (argc > 2);
2145    DHCPv6ClientStart(client, S_allocate_address);
2146    CFRunLoopRun();
2147    fprintf(stderr, "all done\n");
2148    exit(0);
2149    return (0);
2150}
2151#endif /* TEST_DHCPV6_CLIENT */
2152