Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

856 lines
21 KiB

  1. /*++
  2. Copyright (c) 1998, Microsoft Corporation
  3. Module Name:
  4. dhcpauto.c
  5. Abstract:
  6. This module contains code for automatic selection of a client address
  7. from a given scope of addresses.
  8. It makes use of a hashing function which accounts for the client's
  9. hardware address.
  10. Author:
  11. Abolade Gbadegesin (aboladeg) 9-Mar-1998
  12. Revision History:
  13. Raghu Gatta (rgatta) 5-Jul-2001
  14. +Changed DhcpIsReservedAddress & DhcpQueryReservedAddress to
  15. handle variable length name strings.
  16. +Added DhcpConvertHostNametoUnicode (mimics DhcpServer effect)
  17. Raghu Gatta (rgatta) 17-Jul-2001
  18. +Added DhcpGetLocalMacAddr
  19. --*/
  20. #include "precomp.h"
  21. #pragma hdrstop
  22. ULONG
  23. DhcpAcquireUniqueAddress(
  24. PCHAR Name,
  25. ULONG NameLength,
  26. PUCHAR HardwareAddress,
  27. ULONG HardwareAddressLength
  28. )
  29. /*++
  30. Routine Description:
  31. This routine is invoked to acquire a unique address for a client
  32. using the given hardware address to decrease the likelihood of collision.
  33. Arguments:
  34. Name - the name of the host for whom the address is being requested.
  35. If this matches the name of a server in the shared-access server-list,
  36. the address reserved for the server is returned.
  37. NameLength - length of 'Name', excluding any terminating 'nul'.
  38. HardwareAddress - the hardware address to be used
  39. HardwareAddressLength - the length of the hardware address
  40. Return Value:
  41. ULONG - the generated IP address
  42. Environment:
  43. Invoked from an arbitrary context.
  44. --*/
  45. {
  46. ULONG AssignedAddress;
  47. ULONG i = 0;
  48. PLIST_ENTRY Link;
  49. ULONG ScopeMask;
  50. ULONG ScopeNetwork;
  51. ULONG Seed = GetTickCount();
  52. BOOLEAN bUnused;
  53. PROFILE("DhcpAcquireUniqueAddress");
  54. EnterCriticalSection(&DhcpGlobalInfoLock);
  55. if (Name &&
  56. (AssignedAddress = DhcpQueryReservedAddress(Name, NameLength))) {
  57. LeaveCriticalSection(&DhcpGlobalInfoLock);
  58. NhTrace(
  59. TRACE_FLAG_DHCP,
  60. "DhcpAcquireUniqueAddress: returning mapping to %s",
  61. INET_NTOA(AssignedAddress)
  62. );
  63. return AssignedAddress;
  64. }
  65. ScopeNetwork = DhcpGlobalInfo->ScopeNetwork;
  66. ScopeMask = DhcpGlobalInfo->ScopeMask;
  67. LeaveCriticalSection(&DhcpGlobalInfoLock);
  68. do {
  69. if (++i > 4) { AssignedAddress = 0; break; }
  70. //
  71. // Generate an address
  72. //
  73. do {
  74. AssignedAddress =
  75. DhcpGenerateAddress(
  76. &Seed,
  77. HardwareAddress,
  78. HardwareAddressLength,
  79. ScopeNetwork,
  80. ScopeMask
  81. );
  82. } while(
  83. (AssignedAddress & ~ScopeMask) == 0 ||
  84. (AssignedAddress & ~ScopeMask) == ~ScopeMask
  85. );
  86. } while(!DhcpIsUniqueAddress(AssignedAddress, &bUnused, NULL, NULL));
  87. return AssignedAddress;
  88. } // DhcpAcquireUniqueAddress
  89. ULONG
  90. DhcpGenerateAddress(
  91. PULONG Seed,
  92. PUCHAR HardwareAddress,
  93. ULONG HardwareAddressLength,
  94. ULONG ScopeNetwork,
  95. ULONG ScopeMask
  96. )
  97. /*++
  98. Routine Description:
  99. This routine is invoked to compute a randomized hash value
  100. for a client IP address using a hardware-address.
  101. Arguments:
  102. Seed - contains (and receives) the seed to 'RtlRandom'
  103. HardwareAddress - the hardware address to be used
  104. HardwareAddressLength - the length of the hardware address
  105. ScopeNetwork - the network into which the generated address
  106. will be constrained
  107. ScopeMask - the mask for the scope network
  108. Return Value:
  109. ULONG - the generated IP address
  110. Environment:
  111. Invoked from an arbitrary context.
  112. Revision History:
  113. Based on 'GrandHashing' from net\sockets\tcpcmd\dhcpm\client\dhcp
  114. by RameshV.
  115. --*/
  116. {
  117. ULONG Hash;
  118. ULONG Shift;
  119. #if 1
  120. Hash = RtlRandom(Seed) & 0xffff0000;
  121. Hash |= RtlRandom(Seed) >> 16;
  122. #else
  123. Seed = GetTickCount();
  124. Seed = Seed * 1103515245 + 12345;
  125. Hash = (Seed) >> 16;
  126. Hash <<= 16;
  127. Seed = Seed * 1103515245 + 12345;
  128. Hash += Seed >> 16;
  129. #endif
  130. Shift = Hash % sizeof(ULONG);
  131. while(HardwareAddressLength--) {
  132. Hash += (*HardwareAddress++) << (8 * Shift);
  133. Shift = (Shift + 1) % sizeof(ULONG);
  134. }
  135. return (Hash & ~ScopeMask) | ScopeNetwork;
  136. } // DnsGenerateAddress
  137. BOOLEAN
  138. DhcpIsReservedAddress(
  139. ULONG Address,
  140. PCHAR Name OPTIONAL,
  141. ULONG NameLength OPTIONAL
  142. )
  143. /*++
  144. Routine Description:
  145. This routine is invoked to determine whether the given IP address
  146. is reserved for another client.
  147. Arguments:
  148. Address - the IP address to be determined
  149. Name - optionally specifies the client on whose behalf the call is made
  150. NameLength - specifies the length of 'Name' excluding the terminating nul
  151. Return Value:
  152. BOOLEAN - TRUE if the address is reserved for another client,
  153. FALSE otherwise.
  154. Environment:
  155. Invoked with 'DhcpGlobalInfoLock' held by the caller.
  156. --*/
  157. {
  158. ULONG Error = NO_ERROR;
  159. PLIST_ENTRY Link;
  160. PNAT_DHCP_RESERVATION Reservation;
  161. PWCHAR pszUnicodeHostName = NULL;
  162. EnterCriticalSection(&NhLock);
  163. if (IsListEmpty(&NhDhcpReservationList)) {
  164. LeaveCriticalSection(&NhLock);
  165. return FALSE;
  166. }
  167. if (Name) {
  168. Error = DhcpConvertHostNametoUnicode(
  169. CP_OEMCP, // atleast Windows clients send it this way
  170. Name,
  171. NameLength,
  172. &pszUnicodeHostName
  173. );
  174. if (NO_ERROR != Error) {
  175. LeaveCriticalSection(&NhLock);
  176. if (pszUnicodeHostName) {
  177. NH_FREE(pszUnicodeHostName);
  178. }
  179. //
  180. // we can return true or false on failure
  181. // better we return false - otherwise the client will be in a continuous
  182. // loop trying to get another address when we NACK its request
  183. //
  184. return FALSE;
  185. }
  186. }
  187. for (Link = NhDhcpReservationList.Flink;
  188. Link != &NhDhcpReservationList; Link = Link->Flink) {
  189. Reservation = CONTAINING_RECORD(Link, NAT_DHCP_RESERVATION, Link);
  190. if (Address == Reservation->Address) {
  191. if (lstrcmpiW(pszUnicodeHostName, Reservation->Name)) {
  192. LeaveCriticalSection(&NhLock);
  193. if (pszUnicodeHostName) {
  194. NH_FREE(pszUnicodeHostName);
  195. }
  196. return TRUE;
  197. } else {
  198. LeaveCriticalSection(&NhLock);
  199. if (pszUnicodeHostName) {
  200. NH_FREE(pszUnicodeHostName);
  201. }
  202. return FALSE;
  203. }
  204. } else if (lstrcmpiW(pszUnicodeHostName, Reservation->Name) == 0 &&
  205. Address != Reservation->Address) {
  206. LeaveCriticalSection(&NhLock);
  207. if (pszUnicodeHostName) {
  208. NH_FREE(pszUnicodeHostName);
  209. }
  210. return TRUE;
  211. }
  212. }
  213. LeaveCriticalSection(&NhLock);
  214. if (pszUnicodeHostName) {
  215. NH_FREE(pszUnicodeHostName);
  216. }
  217. return FALSE;
  218. } // DhcpIsReservedAddress
  219. BOOLEAN
  220. DhcpIsUniqueAddress(
  221. ULONG Address,
  222. PBOOLEAN IsLocal,
  223. PUCHAR ConflictAddress OPTIONAL,
  224. PULONG ConflictAddressLength OPTIONAL
  225. )
  226. /*++
  227. Routine Description:
  228. This routine is invoked to determine whether the given address
  229. is unique on the directly connected subnetworks.
  230. The determination accounts for any configured static addresses
  231. included in the global information.
  232. Arguments:
  233. Address - the address whose uniqueness is to be determined
  234. IsLocal - pointer to BOOLEAN which receives info about whether
  235. the requested address is one of the local interfaces' address
  236. ConflictAddress - optionally receives a copy of the conflicting
  237. hardware address if a conflict is found
  238. ConflictAddressLength - if 'ConflictAddress' is set, receives
  239. the length of the conflicting address.
  240. Return Value:
  241. BOOLEAN - TRUE if unique, FALSE otherwise.
  242. --*/
  243. {
  244. BOOLEAN ConflictFound = FALSE;
  245. ULONG Error;
  246. UCHAR ExistingAddress[MAX_HARDWARE_ADDRESS_LENGTH];
  247. ULONG ExistingAddressLength;
  248. ULONG i;
  249. PDHCP_INTERFACE Interfacep;
  250. BOOLEAN IsNatInterface;
  251. PLIST_ENTRY Link;
  252. ULONG SourceAddress;
  253. PROFILE("DhcpIsUniqueAddress");
  254. *IsLocal = FALSE;
  255. //
  256. // See if this is a static address
  257. //
  258. EnterCriticalSection(&DhcpGlobalInfoLock);
  259. if (DhcpGlobalInfo && DhcpGlobalInfo->ExclusionCount) {
  260. for (i = 0; i < DhcpGlobalInfo->ExclusionCount; i++) {
  261. if (Address == DhcpGlobalInfo->ExclusionArray[i]) {
  262. LeaveCriticalSection(&DhcpGlobalInfoLock);
  263. if (ConflictAddressLength) { *ConflictAddressLength = 0; }
  264. return FALSE;
  265. }
  266. }
  267. }
  268. LeaveCriticalSection(&DhcpGlobalInfoLock);
  269. //
  270. // Try to detect collisions
  271. //
  272. EnterCriticalSection(&DhcpInterfaceLock);
  273. for (Link = DhcpInterfaceList.Flink;
  274. Link != &DhcpInterfaceList;
  275. Link = Link->Flink
  276. ) {
  277. Interfacep = CONTAINING_RECORD(Link, DHCP_INTERFACE, Link);
  278. if (DHCP_INTERFACE_DELETED(Interfacep)) { continue; }
  279. ACQUIRE_LOCK(Interfacep);
  280. //
  281. // We send out an ARP request unless
  282. // (a) the interface is a boundary interface
  283. // (b) the interface is not NAT-enabled
  284. // (c) the allocator is not active on the interface
  285. // (d) the interface is not a LAN adapter
  286. // (e) the interface has no bindings.
  287. //
  288. if (!DHCP_INTERFACE_NAT_NONBOUNDARY(Interfacep) ||
  289. !DHCP_INTERFACE_ACTIVE(Interfacep) ||
  290. (Interfacep->Type != PERMANENT) ||
  291. !Interfacep->BindingCount) {
  292. RELEASE_LOCK(Interfacep);
  293. continue;
  294. }
  295. for (i = 0; i < Interfacep->BindingCount; i++) {
  296. SourceAddress = Interfacep->BindingArray[i].Address;
  297. ExistingAddressLength = sizeof(ExistingAddress);
  298. if (SourceAddress == Address)
  299. {
  300. //
  301. // check to see that requested address is not same as
  302. // one of the local addresses on the NAT box
  303. //
  304. NhTrace(
  305. TRACE_FLAG_DHCP,
  306. "DhcpIsUniqueAddress: %s is in use locally",
  307. INET_NTOA(Address)
  308. );
  309. if (ConflictAddress) {
  310. if (DhcpGetLocalMacAddr(
  311. Address,
  312. ExistingAddress,
  313. &ExistingAddressLength
  314. ))
  315. {
  316. if (ExistingAddressLength > MAX_HARDWARE_ADDRESS_LENGTH) {
  317. ExistingAddressLength = MAX_HARDWARE_ADDRESS_LENGTH;
  318. }
  319. CopyMemory(
  320. ConflictAddress,
  321. ExistingAddress,
  322. ExistingAddressLength
  323. );
  324. *ConflictAddressLength = ExistingAddressLength;
  325. }
  326. else
  327. {
  328. *ConflictAddressLength = 0;
  329. }
  330. }
  331. *IsLocal = TRUE;
  332. ConflictFound = TRUE;
  333. break;
  334. }
  335. RELEASE_LOCK(Interfacep);
  336. Error =
  337. SendARP(
  338. Address,
  339. SourceAddress,
  340. (PULONG)ExistingAddress,
  341. &ExistingAddressLength
  342. );
  343. ACQUIRE_LOCK(Interfacep);
  344. if (Error) {
  345. NhWarningLog(
  346. IP_AUTO_DHCP_LOG_SENDARP_FAILED,
  347. Error,
  348. "%I%I",
  349. Address,
  350. SourceAddress
  351. );
  352. } else if (ExistingAddressLength &&
  353. ExistingAddressLength <= sizeof(ExistingAddress)) {
  354. NhTrace(
  355. TRACE_FLAG_DHCP,
  356. "DhcpIsUniqueAddress: %s is in use",
  357. INET_NTOA(Address)
  358. );
  359. #if DBG
  360. NhDump(
  361. TRACE_FLAG_DHCP,
  362. ExistingAddress,
  363. ExistingAddressLength,
  364. 1
  365. );
  366. #endif
  367. if (ConflictAddress) {
  368. if (ExistingAddressLength > MAX_HARDWARE_ADDRESS_LENGTH) {
  369. ExistingAddressLength = MAX_HARDWARE_ADDRESS_LENGTH;
  370. }
  371. CopyMemory(
  372. ConflictAddress,
  373. ExistingAddress,
  374. ExistingAddressLength
  375. );
  376. *ConflictAddressLength = ExistingAddressLength;
  377. }
  378. ConflictFound = TRUE;
  379. break;
  380. }
  381. }
  382. RELEASE_LOCK(Interfacep);
  383. if (ConflictFound) { break; }
  384. }
  385. LeaveCriticalSection(&DhcpInterfaceLock);
  386. return ConflictFound ? FALSE : TRUE;
  387. } // DhcpIsUniqueAddress
  388. ULONG
  389. DhcpQueryReservedAddress(
  390. PCHAR Name,
  391. ULONG NameLength
  392. )
  393. /*++
  394. Routine Description:
  395. This routine is called to determine whether the given machine name
  396. corresponds to an entry in the list of reserved addresses.
  397. Arguments:
  398. Name - specifies the machine name, which might not be nul-terminated.
  399. NameLength - specifies the length of the given machine name,
  400. not including any terminating nul character.
  401. Return Value:
  402. ULONG - the IP address of the machine, if any.
  403. Environment:
  404. Invoked with 'DhcpGlobalInfoLock' held by the caller.
  405. --*/
  406. {
  407. ULONG Error = NO_ERROR;
  408. PLIST_ENTRY Link;
  409. ULONG ReservedAddress;
  410. PNAT_DHCP_RESERVATION Reservation;
  411. PWCHAR pszUnicodeHostName = NULL;
  412. EnterCriticalSection(&NhLock);
  413. if (IsListEmpty(&NhDhcpReservationList))
  414. {
  415. LeaveCriticalSection(&NhLock);
  416. return FALSE;
  417. }
  418. if (Name) {
  419. Error = DhcpConvertHostNametoUnicode(
  420. CP_OEMCP, // atleast Windows clients send it this way
  421. Name,
  422. NameLength,
  423. &pszUnicodeHostName
  424. );
  425. if (NO_ERROR != Error) {
  426. LeaveCriticalSection(&NhLock);
  427. if (pszUnicodeHostName) {
  428. NH_FREE(pszUnicodeHostName);
  429. }
  430. return FALSE;
  431. }
  432. }
  433. for (Link = NhDhcpReservationList.Flink;
  434. Link != &NhDhcpReservationList; Link = Link->Flink)
  435. {
  436. Reservation = CONTAINING_RECORD(Link, NAT_DHCP_RESERVATION, Link);
  437. if (lstrcmpiW(pszUnicodeHostName, Reservation->Name)) { continue; }
  438. ReservedAddress = Reservation->Address;
  439. LeaveCriticalSection(&NhLock);
  440. if (pszUnicodeHostName) {
  441. NH_FREE(pszUnicodeHostName);
  442. }
  443. return ReservedAddress;
  444. }
  445. LeaveCriticalSection(&NhLock);
  446. if (pszUnicodeHostName) {
  447. NH_FREE(pszUnicodeHostName);
  448. }
  449. return 0;
  450. } // DhcpQueryReservedAddress
  451. //
  452. // Utility routines
  453. //
  454. ULONG
  455. DhcpConvertHostNametoUnicode(
  456. UINT CodePage,
  457. CHAR *pHostName,
  458. ULONG HostNameLength,
  459. PWCHAR *ppszUnicode
  460. )
  461. {
  462. //
  463. // make sure to free the returned Unicode hostname
  464. //
  465. DWORD dwSize = 0;
  466. ULONG Error = NO_ERROR;
  467. PCHAR pszHostName = NULL;
  468. LPBYTE pszUtf8HostName = NULL; // copy of pszHostName in Utf8 format
  469. PWCHAR pszUnicodeHostName = NULL;
  470. if (ppszUnicode)
  471. {
  472. *ppszUnicode = NULL;
  473. }
  474. else
  475. {
  476. return ERROR_INVALID_PARAMETER;
  477. }
  478. do
  479. {
  480. //
  481. // create a null terminated copy
  482. //
  483. dwSize = HostNameLength + 4;
  484. pszHostName = reinterpret_cast<PCHAR>(NH_ALLOCATE(dwSize));
  485. if (!pszHostName)
  486. {
  487. NhTrace(
  488. TRACE_FLAG_DNS,
  489. "DhcpConvertHostNametoUnicode: allocation failed for "
  490. "hostname copy buffer"
  491. );
  492. break;
  493. }
  494. ZeroMemory(pszHostName, dwSize);
  495. memcpy(pszHostName, pHostName, HostNameLength);
  496. pszHostName[HostNameLength] = '\0';
  497. //
  498. // convert the given hostname to a Unicode string
  499. //
  500. if (CP_UTF8 == CodePage)
  501. {
  502. pszUtf8HostName = (LPBYTE)pszHostName;
  503. }
  504. else
  505. {
  506. //
  507. // now convert this into UTF8 format
  508. //
  509. if (!ConvertToUtf8(
  510. CodePage,
  511. (LPSTR)pszHostName,
  512. (PCHAR *)&pszUtf8HostName,
  513. &dwSize))
  514. {
  515. Error = GetLastError();
  516. NhTrace(
  517. TRACE_FLAG_DNS,
  518. "DhcpConvertHostNametoUnicode: conversion from "
  519. "CodePage %d to UTF8 for hostname failed "
  520. "with error %ld (0x%08x)",
  521. CodePage,
  522. Error,
  523. Error
  524. );
  525. break;
  526. }
  527. }
  528. //
  529. // now convert UTF8 string into Unicode format
  530. //
  531. if (!ConvertUTF8ToUnicode(
  532. pszUtf8HostName,
  533. (LPWSTR *)&pszUnicodeHostName,
  534. &dwSize))
  535. {
  536. Error = GetLastError();
  537. NhTrace(
  538. TRACE_FLAG_DNS,
  539. "DhcpConvertHostNametoUnicode: conversion from "
  540. "UTF8 to Unicode for hostname failed "
  541. "with error %ld (0x%08x)",
  542. Error,
  543. Error
  544. );
  545. if (pszUnicodeHostName)
  546. {
  547. NH_FREE(pszUnicodeHostName);
  548. }
  549. break;
  550. }
  551. *ppszUnicode = pszUnicodeHostName;
  552. NhTrace(
  553. TRACE_FLAG_DNS,
  554. "DhcpConvertHostNametoUnicode: succeeded! %S",
  555. pszUnicodeHostName
  556. );
  557. } while (FALSE);
  558. if (pszHostName)
  559. {
  560. NH_FREE(pszHostName);
  561. }
  562. if ((CP_UTF8 != CodePage) && pszUtf8HostName)
  563. {
  564. NH_FREE(pszUtf8HostName);
  565. }
  566. return Error;
  567. } // DhcpConvertHostNametoUnicode
  568. BOOL
  569. DhcpGetLocalMacAddr(
  570. ULONG Address,
  571. PUCHAR MacAddr,
  572. PULONG MacAddrLength
  573. )
  574. /*++
  575. Routine Description:
  576. This routine is invoked to determine the local physical MAC address
  577. for the given local IP address.
  578. Arguments:
  579. Address - the local IP address
  580. MacAddr - buffer for holding the MAC addr (upto MAX_HARDWARE_ADDRESS_LENGTH)
  581. MacAddrLength - specifies the length of 'MacAddr'
  582. Return Value:
  583. BOOLEAN - TRUE if we are able to get the MAC address,
  584. FALSE otherwise.
  585. Environment:
  586. Invoked from DhcpIsUniqueAddress().
  587. --*/
  588. {
  589. BOOL bRet = FALSE;
  590. DWORD Error = NO_ERROR;
  591. PMIB_IPNETTABLE IpNetTable = NULL;
  592. PMIB_IPNETROW IpNetRow = NULL;
  593. DWORD dwPhysAddrLen = 0, i;
  594. ULONG dwSize = 0;
  595. do
  596. {
  597. //
  598. // retrieve size of address mapping table
  599. //
  600. Error = GetIpNetTable(
  601. IpNetTable,
  602. &dwSize,
  603. FALSE
  604. );
  605. if (!Error)
  606. {
  607. NhTrace(
  608. TRACE_FLAG_DHCP,
  609. "DhcpGetLocalMacAddr: should NOT have returned %d",
  610. Error
  611. );
  612. break;
  613. }
  614. else
  615. if (ERROR_INSUFFICIENT_BUFFER != Error)
  616. {
  617. NhTrace(
  618. TRACE_FLAG_DHCP,
  619. "DhcpGetLocalMacAddr: GetIpNetTable=%d",
  620. Error
  621. );
  622. break;
  623. }
  624. //
  625. // allocate a buffer
  626. //
  627. IpNetTable = (PMIB_IPNETTABLE)NH_ALLOCATE(dwSize);
  628. if (!IpNetTable)
  629. {
  630. NhTrace(
  631. TRACE_FLAG_DHCP,
  632. "DhcpGetLocalMacAddr: error allocating %d bytes",
  633. dwSize
  634. );
  635. break;
  636. }
  637. //
  638. // retrieve the address mapping table
  639. //
  640. Error = GetIpNetTable(
  641. IpNetTable,
  642. &dwSize,
  643. FALSE
  644. );
  645. if (NO_ERROR != Error)
  646. {
  647. NhTrace(
  648. TRACE_FLAG_DHCP,
  649. "DhcpGetLocalMacAddr: GetIpNetTable=%d size=%d",
  650. Error,
  651. dwSize
  652. );
  653. break;
  654. }
  655. for (i = 0; i < IpNetTable->dwNumEntries; i++)
  656. {
  657. IpNetRow = &IpNetTable->table[i];
  658. if (IpNetRow->dwAddr == Address)
  659. {
  660. dwPhysAddrLen = IpNetRow->dwPhysAddrLen;
  661. if (dwPhysAddrLen > MAX_HARDWARE_ADDRESS_LENGTH)
  662. {
  663. dwPhysAddrLen = MAX_HARDWARE_ADDRESS_LENGTH;
  664. }
  665. CopyMemory(
  666. MacAddr,
  667. IpNetRow->bPhysAddr,
  668. dwPhysAddrLen
  669. );
  670. *MacAddrLength = dwPhysAddrLen;
  671. bRet = TRUE;
  672. break;
  673. }
  674. }
  675. } while (FALSE);
  676. if (IpNetTable)
  677. {
  678. NH_FREE(IpNetTable);
  679. }
  680. return bRet;
  681. } // DhcpGetLocalMacAddr