1/* 2 * Copyright (c) 2010-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/* 26 * DHCPDUIDIAID.c 27 * - routines to set/access the DHCP client DUID and the IAIDs for particular 28 * interfaces 29 */ 30 31/* 32 * Modification History 33 * 34 * May 14, 2010 35 * - created 36 */ 37 38#include <CoreFoundation/CFString.h> 39#include <SystemConfiguration/SCValidation.h> 40#include <SystemConfiguration/SCPrivate.h> 41#include <unistd.h> 42#include "util.h" 43#include "globals.h" 44#include "ipconfigd_globals.h" 45#include "cfutil.h" 46#include "HostUUID.h" 47#include "DHCPDUID.h" 48#include "DHCPDUIDIAID.h" 49 50#define DUID_IA_FILE IPCONFIGURATION_PRIVATE_DIR "/DUID_IA.plist" 51 52#define kDUIDKey CFSTR("DUID") /* data */ 53#define kIAIDListKey CFSTR("IAIDList") /* array[string] */ 54#define kHostUUIDKey CFSTR("HostUUID") /* data */ 55 56STATIC CFDataRef S_DUID; 57STATIC CFMutableArrayRef S_IAIDList; 58 59/* 60 * Function: seconds_since_Jan_1_2000 61 * Purpose: 62 * Return the number of seconds since midnight (UTC), January 1, 2000, the 63 * epoch for the DHCP DUID LLT. 64 */ 65STATIC uint32_t 66S_seconds_since_Jan_1_2000(void) 67{ 68 time_t DHCPDUID_epoch; 69 uint32_t seconds; 70 struct tm tm; 71 72 bzero(&tm, sizeof(tm)); 73 tm.tm_year = 100; /* 2000 (100 years since 1900) */ 74 tm.tm_mon = 0; /* January (0 months since January) */ 75 tm.tm_mday = 1; /* 1st (day of the month) */ 76 DHCPDUID_epoch = timegm(&tm); 77 seconds = time(NULL) - DHCPDUID_epoch; 78 return (seconds); 79} 80 81STATIC void 82save_DUID_info(void) 83{ 84 CFMutableDictionaryRef duid_ia; 85 CFDataRef host_UUID; 86 87 if (S_DUID == NULL) { 88 return; 89 } 90 duid_ia = CFDictionaryCreateMutable(NULL, 0, 91 &kCFTypeDictionaryKeyCallBacks, 92 &kCFTypeDictionaryValueCallBacks); 93 CFDictionarySetValue(duid_ia, kDUIDKey, S_DUID); 94 if (S_IAIDList != NULL) { 95 CFDictionarySetValue(duid_ia, kIAIDListKey, S_IAIDList); 96 } 97 host_UUID = HostUUIDGet(); 98 if (host_UUID != NULL) { 99 CFDictionarySetValue(duid_ia, kHostUUIDKey, host_UUID); 100 } 101 if (my_CFPropertyListWriteFile(duid_ia, DUID_IA_FILE, 0644) < 0) { 102 /* 103 * An ENOENT error is expected on a read-only filesystem. All 104 * other errors should be reported. 105 */ 106 if (errno != ENOENT) { 107 my_log(LOG_ERR, 108 "DHCPDUID: failed to write %s, %s", DUID_IA_FILE, 109 strerror(errno)); 110 } 111 } 112 CFRelease(duid_ia); 113 return; 114} 115 116STATIC bool 117load_DUID_info(void) 118{ 119 CFDataRef duid; 120 CFDictionaryRef duid_ia; 121 CFDataRef host_uuid; 122 CFArrayRef ia_list; 123 124 duid_ia = my_CFPropertyListCreateFromFile(DUID_IA_FILE); 125 if (isA_CFDictionary(duid_ia) == NULL) { 126 goto done; 127 } 128 duid = CFDictionaryGetValue(duid_ia, kDUIDKey); 129 if (isA_CFData(duid) == NULL) { 130 goto done; 131 } 132 ia_list = CFDictionaryGetValue(duid_ia, kIAIDListKey); 133 ia_list = isA_CFArray(ia_list); 134 if (ia_list != NULL) { 135 int count; 136 int i; 137 138 count = CFArrayGetCount(ia_list); 139 for (i = 0; i < count; i++) { 140 CFStringRef name = CFArrayGetValueAtIndex(ia_list, i); 141 if (isA_CFString(name) == NULL) { 142 /* invalid property */ 143 ia_list = NULL; 144 break; 145 } 146 } 147 } 148 host_uuid = CFDictionaryGetValue(duid_ia, kHostUUIDKey); 149 if (isA_CFData(host_uuid) != NULL 150 && CFDataGetLength(host_uuid) == sizeof(uuid_t)) { 151 CFDataRef our_UUID; 152 153 our_UUID = HostUUIDGet(); 154 if (our_UUID != NULL && CFEqual(host_uuid, our_UUID) == FALSE) { 155 syslog(LOG_NOTICE, 156 "DHCPDUID: ignoring DUID - host UUID doesn't match"); 157 goto done; 158 } 159 } 160 S_DUID = CFRetain(duid); 161 if (ia_list != NULL) { 162 S_IAIDList = CFArrayCreateMutableCopy(NULL, 0, ia_list); 163 } 164 165 done: 166 my_CFRelease(&duid_ia); 167 return (S_DUID != NULL); 168} 169 170STATIC CFDataRef 171make_DUID_LL_data(interface_t * if_p) 172{ 173 CFMutableDataRef data; 174 int duid_len; 175 DHCPDUID_LLRef ll_p; 176 177 duid_len = offsetof(DHCPDUID_LL, linklayer_address) + if_link_length(if_p); 178 data = CFDataCreateMutable(NULL, duid_len); 179 CFDataSetLength(data, duid_len); 180 ll_p = (DHCPDUID_LLRef)CFDataGetMutableBytePtr(data); 181 DHCPDUIDSetType((DHCPDUIDRef)ll_p, kDHCPDUIDTypeLL); 182 DHCPDUID_LLSetHardwareType(ll_p, if_link_arptype(if_p)); 183 bcopy(if_link_address(if_p), ll_p->linklayer_address, 184 if_link_length(if_p)); 185 return (data); 186} 187 188STATIC CFDataRef 189make_DUID_LLT_data(interface_t * if_p) 190{ 191 CFMutableDataRef data; 192 int duid_len; 193 DHCPDUID_LLTRef llt_p; 194 195 duid_len = offsetof(DHCPDUID_LLT, linklayer_address) + if_link_length(if_p); 196 data = CFDataCreateMutable(NULL, duid_len); 197 CFDataSetLength(data, duid_len); 198 llt_p = (DHCPDUID_LLTRef)CFDataGetMutableBytePtr(data); 199 DHCPDUIDSetType((DHCPDUIDRef)llt_p, kDHCPDUIDTypeLLT); 200 DHCPDUID_LLTSetHardwareType(llt_p, if_link_arptype(if_p)); 201 bcopy(if_link_address(if_p), llt_p->linklayer_address, 202 if_link_length(if_p)); 203 DHCPDUID_LLTSetTime(llt_p, S_seconds_since_Jan_1_2000()); 204 return (data); 205} 206 207PRIVATE_EXTERN CFDataRef 208DHCPDUIDGet(interface_list_t * interfaces) 209{ 210 interface_t * if_p; 211 interface_t * if_with_linkaddr_p = NULL; 212 213 if (S_DUID != NULL) { 214 goto done; 215 } 216 /* try to load the DUID from filesystem */ 217 if (G_is_netboot == FALSE && load_DUID_info()) { 218 goto done; 219 } 220 if (interfaces == NULL) { 221 goto done; 222 } 223 if_p = ifl_find_name(interfaces, "en0"); 224 if (if_p == NULL) { 225 int count; 226 int i; 227 228 count = ifl_count(interfaces); 229 for (i = 0; i < count; i++) { 230 interface_t * scan = ifl_at_index(interfaces, i); 231 232 switch (if_ift_type(scan)) { 233 case IFT_ETHER: 234 case IFT_IEEE1394: 235 break; 236 default: 237 if (if_with_linkaddr_p == NULL 238 && if_link_length(scan) > 0) { 239 if_with_linkaddr_p = scan; 240 } 241 continue; 242 } 243 if (if_p == NULL) { 244 if_p = scan; 245 } 246 else if (strcmp(if_name(scan), if_name(if_p)) < 0) { 247 /* pick "lowest" named interface */ 248 if_p = scan; 249 } 250 } 251 } 252 if (if_p == NULL) { 253 if (G_dhcp_duid_type == kDHCPDUIDTypeLL || if_with_linkaddr_p == NULL) { 254 my_log(LOG_ERR, 255 "DHCPv6Client: can't determine interface for DUID"); 256 goto done; 257 } 258 if_p = if_with_linkaddr_p; 259 } 260 if (G_IPConfiguration_verbose) { 261 my_log(LOG_DEBUG, "DHCPv6Client: chose %s for DUID", 262 if_name(if_p)); 263 } 264 if (G_dhcp_duid_type == kDHCPDUIDTypeLL || G_is_netboot) { 265 S_DUID = make_DUID_LL_data(if_p); 266 } 267 else { 268 S_DUID = make_DUID_LLT_data(if_p); 269 } 270 save_DUID_info(); 271 272 done: 273 return (S_DUID); 274} 275 276PRIVATE_EXTERN DHCPIAID 277DHCPIAIDGet(const char * ifname) 278{ 279 int count; 280 DHCPIAID iaid; 281 CFStringRef ifname_cf; 282 CFIndex where = kCFNotFound; 283 284 ifname_cf = CFStringCreateWithCString(NULL, ifname, kCFStringEncodingASCII); 285 if (S_IAIDList == NULL) { 286 S_IAIDList = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks); 287 count = 0; 288 } 289 else { 290 CFRange range; 291 292 count = CFArrayGetCount(S_IAIDList); 293 range = CFRangeMake(0, count); 294 where = CFArrayGetFirstIndexOfValue(S_IAIDList, range, ifname_cf); 295 } 296 if (where != kCFNotFound) { 297 iaid = where; 298 } 299 else { 300 CFArrayAppendValue(S_IAIDList, ifname_cf); 301 iaid = count; 302 save_DUID_info(); 303 } 304 CFRelease(ifname_cf); 305 return (iaid); 306} 307 308#ifdef TEST_DHCPDUIDIAID 309 310Boolean G_IPConfiguration_verbose = 1; 311int G_dhcp_duid_type; 312boolean_t G_is_netboot; 313 314int 315main(int argc, char * argv[]) 316{ 317 CFDataRef duid; 318 int i; 319 interface_list_t * interfaces; 320 CFMutableStringRef str; 321 322 (void) openlog("DHCPDUIDIAID", LOG_PERROR | LOG_PID, LOG_DAEMON); 323 interfaces = ifl_init(); 324 325 ipconfigd_create_paths(); 326 duid = DHCPDUIDGet(interfaces); 327 if (duid == NULL) { 328 fprintf(stderr, "Couldn't determine DUID\n"); 329 exit(1); 330 } 331 332 str = CFStringCreateMutable(NULL, 0); 333 DHCPDUIDPrintToString(str, (const DHCPDUIDRef)CFDataGetBytePtr(duid), 334 CFDataGetLength(duid)); 335 SCPrint(TRUE, stdout, CFSTR("%@\n"), str); 336 CFRelease(str); 337 if (argc > 1) { 338 for (i = 1; i < argc; i++) { 339 DHCPIAID iaid; 340 341 iaid = DHCPIAIDGet(argv[i]); 342 printf("%s = %d\n", argv[i], iaid); 343 } 344 } 345 exit(0); 346 return (0); 347} 348 349#endif /* TEST_DHCPDUIDIAID */ 350