]> git.proxmox.com Git - mirror_edk2.git/blobdiff - NetworkPkg/UefiPxeBcDxe/PxeBcBoot.c
NetworkPkg/UefiPxeBcDxe: handle competing DHCP servers (more) gracefully
[mirror_edk2.git] / NetworkPkg / UefiPxeBcDxe / PxeBcBoot.c
index dd1d76d8206171f1d195d443bdc82fd0894ec288..d062a526077b270d35867d22012578e58445bd29 100644 (file)
@@ -1,15 +1,10 @@
 /** @file\r
   Boot functions implementation for UefiPxeBc Driver.\r
 \r
-  Copyright (c) 2009 - 2010, Intel Corporation. All rights reserved.<BR>\r
+  Copyright (c) 2009 - 2018, Intel Corporation. All rights reserved.<BR>\r
+  (C) Copyright 2016 Hewlett Packard Enterprise Development LP<BR>\r
 \r
-  This program and the accompanying materials\r
-  are licensed and made available under the terms and conditions of the BSD License\r
-  which accompanies this distribution.  The full text of the license may be found at\r
-  http://opensource.org/licenses/bsd-license.php.\r
-\r
-  THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,\r
-  WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.\r
+  SPDX-License-Identifier: BSD-2-Clause-Patent\r
 \r
 **/\r
 \r
@@ -86,9 +81,9 @@ PxeBcSelectBootPrompt (
   OfferType    = Mode->UsingIpv6 ? Cache->Dhcp6.OfferType : Cache->Dhcp4.OfferType;\r
 \r
   //\r
-  // Only ProxyPxe10 offer needs boot prompt.\r
+  // Only DhcpPxe10 and ProxyPxe10 offer needs boot prompt.\r
   //\r
-  if (OfferType != PxeOfferTypeProxyPxe10) {\r
+  if (OfferType != PxeOfferTypeProxyPxe10 && OfferType != PxeOfferTypeDhcpPxe10) {\r
     return EFI_NOT_FOUND;\r
   }\r
 \r
@@ -98,8 +93,19 @@ PxeBcSelectBootPrompt (
   ASSERT (!Mode->UsingIpv6);\r
 \r
   VendorOpt = &Cache->Dhcp4.VendorOpt;\r
+  //\r
+  // According to the PXE specification 2.1, Table 2-1 PXE DHCP Options,\r
+  // we must not consider a boot prompt or boot menu if all of the following hold:\r
+  //   - the PXE_DISCOVERY_CONTROL tag(6) is present inside the Vendor Options(43), and has bit 3 set\r
+  //   - a boot file name has been presented in the initial DHCP or ProxyDHCP offer packet.\r
+  //\r
+  if (IS_DISABLE_PROMPT_MENU (VendorOpt->DiscoverCtrl) &&\r
+      Cache->Dhcp4.OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] != NULL) {\r
+    return EFI_ABORTED;\r
+  }\r
+\r
   if (!IS_VALID_BOOT_PROMPT (VendorOpt->BitMap)) {\r
-    return EFI_SUCCESS;\r
+    return EFI_TIMEOUT;\r
   }\r
 \r
   Timeout   = VendorOpt->MenuPrompt->Timeout;\r
@@ -110,10 +116,10 @@ PxeBcSelectBootPrompt (
   // The valid scope of Timeout refers to PXE2.1 spec.\r
   //\r
   if (Timeout == 0) {\r
-    return EFI_SUCCESS;\r
+    return EFI_TIMEOUT;\r
   }\r
   if (Timeout == 255) {\r
-    return EFI_TIMEOUT;\r
+    return EFI_SUCCESS;\r
   }\r
 \r
   //\r
@@ -133,7 +139,7 @@ PxeBcSelectBootPrompt (
   Status = gBS->SetTimer (\r
                   TimeoutEvent,\r
                   TimerRelative,\r
-                  Timeout * TICKS_PER_SECOND\r
+                  MultU64x32 (Timeout, TICKS_PER_SECOND)\r
                   );\r
   if (EFI_ERROR (Status)) {\r
     goto ON_EXIT;\r
@@ -173,6 +179,7 @@ PxeBcSelectBootPrompt (
   gST->ConOut->SetCursorPosition (gST->ConOut, SecCol + PromptLen, SecRow);\r
   AsciiPrint ("(%d) ", Timeout--);\r
 \r
+  Status = EFI_TIMEOUT;\r
   while (EFI_ERROR (gBS->CheckEvent (TimeoutEvent))) {\r
     if (!EFI_ERROR (gBS->CheckEvent (DescendEvent))) {\r
       gST->ConOut->SetCursorPosition (gST->ConOut, SecCol + PromptLen, SecRow);\r
@@ -184,6 +191,7 @@ PxeBcSelectBootPrompt (
     }\r
     //\r
     // Parse the input key by user.\r
+    // If <F8> or <Ctrl> + <M> is pressed, return success to display the boot menu.\r
     //\r
     if (InputKey.ScanCode == 0) {\r
 \r
@@ -196,7 +204,7 @@ PxeBcSelectBootPrompt (
       case CTRL ('m'):\r
       case 'm':\r
       case 'M':\r
-        Status = EFI_TIMEOUT;\r
+        Status = EFI_SUCCESS;\r
         break;\r
 \r
       default:\r
@@ -208,7 +216,7 @@ PxeBcSelectBootPrompt (
       switch (InputKey.ScanCode) {\r
 \r
       case SCAN_F8:\r
-        Status = EFI_TIMEOUT;\r
+        Status = EFI_SUCCESS;\r
         break;\r
 \r
       case SCAN_ESC:\r
@@ -249,7 +257,7 @@ ON_EXIT:
 \r
   @retval EFI_ABORTED     User cancel operation.\r
   @retval EFI_SUCCESS     Select the boot menu success.\r
-  @retval EFI_NOT_READY   Read the input key from the keybroad has not finish.\r
+  @retval EFI_NOT_READY   Read the input key from the keyboard has not finish.\r
 \r
 **/\r
 EFI_STATUS\r
@@ -276,7 +284,7 @@ PxeBcSelectBootMenu (
   PXEBC_BOOT_MENU_ENTRY      *MenuArray[PXEBC_MENU_MAX_NUM];\r
 \r
   Finish    = FALSE;\r
-  Select    = 1;\r
+  Select    = 0;\r
   Index     = 0;\r
   *Type     = 0;\r
   Mode      = Private->PxeBc.Mode;\r
@@ -284,10 +292,10 @@ PxeBcSelectBootMenu (
   OfferType = Mode->UsingIpv6 ? Cache->Dhcp6.OfferType : Cache->Dhcp4.OfferType;\r
 \r
   //\r
-  // There is no specified ProxyPxe10 for IPv6 in PXE and UEFI spec.\r
+  // There is no specified DhcpPxe10/ProxyPxe10 for IPv6 in PXE and UEFI spec.\r
   //\r
   ASSERT (!Mode->UsingIpv6);\r
-  ASSERT (OfferType == PxeOfferTypeProxyPxe10);\r
+  ASSERT (OfferType == PxeOfferTypeProxyPxe10 || OfferType == PxeOfferTypeDhcpPxe10);\r
 \r
   VendorOpt = &Cache->Dhcp4.VendorOpt;\r
   if (!IS_VALID_BOOT_MENU (VendorOpt->BitMap)) {\r
@@ -351,7 +359,7 @@ PxeBcSelectBootMenu (
       gBS->Stall (10 * TICKS_PER_MS);\r
     }\r
 \r
-    if (InputKey.ScanCode != 0) {\r
+    if (InputKey.ScanCode == 0) {\r
       switch (InputKey.UnicodeChar) {\r
       case CTRL ('c'):\r
         InputKey.ScanCode = SCAN_ESC;\r
@@ -455,6 +463,8 @@ PxeBcDhcp4BootInfo (
   EFI_STATUS                  Status;\r
   PXEBC_DHCP4_PACKET_CACHE    *Cache4;\r
   UINT16                      Value;\r
+  PXEBC_VENDOR_OPTION         *VendorOpt;\r
+  PXEBC_BOOT_SVR_ENTRY        *Entry;\r
 \r
   PxeBc       = &Private->PxeBc;\r
   Mode        = PxeBc->Mode;\r
@@ -472,17 +482,53 @@ PxeBcDhcp4BootInfo (
     Cache4 = &Private->DhcpAck.Dhcp4;\r
   }\r
 \r
+  if (Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] == NULL) {\r
+    //\r
+    // This should never happen in a correctly configured DHCP / PXE\r
+    // environment. One misconfiguration that can cause it is two DHCP servers\r
+    // mistakenly running on the same network segment at the same time, and\r
+    // racing each other in answering DHCP requests. Thus, the DHCP packets\r
+    // that the edk2 PXE client considers "belonging together" may actually be\r
+    // entirely independent, coming from two (competing) DHCP servers.\r
+    //\r
+    // Try to deal with this gracefully. Note that this check is not\r
+    // comprehensive, as we don't try to identify all such errors.\r
+    //\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
+\r
   //\r
-  // Parse the boot server Ipv4 address by next server address.\r
-  // If this field isn't available, use option 54 instead.\r
+  // Parse the boot server address.\r
+  // If prompt/discover is disabled, get the first boot server from the boot servers list.\r
+  // Otherwise, parse the boot server Ipv4 address from next server address field in DHCP header.\r
+  // If all these fields are not available, use option 54 instead.\r
   //\r
-  CopyMem (\r
-    &Private->ServerIp,\r
-    &Cache4->Packet.Offer.Dhcp4.Header.ServerAddr,\r
-    sizeof (EFI_IPv4_ADDRESS)\r
-    );\r
-\r
+  VendorOpt = &Cache4->VendorOpt;\r
+  if (IS_DISABLE_PROMPT_MENU (VendorOpt->DiscoverCtrl) && IS_VALID_BOOT_SERVERS (VendorOpt->BitMap)) {\r
+    Entry = VendorOpt->BootSvr;\r
+    if (VendorOpt->BootSvrLen >= sizeof (PXEBC_BOOT_SVR_ENTRY) && Entry->IpCnt > 0) {\r
+      CopyMem (\r
+        &Private->ServerIp,\r
+        &Entry->IpAddr[0],\r
+        sizeof (EFI_IPv4_ADDRESS)\r
+        );\r
+    }\r
+  }\r
   if (Private->ServerIp.Addr[0] == 0) {\r
+    //\r
+    // ServerIp.Addr[0] equals zero means we failed to get IP address from boot server list.\r
+    // Try to use next server address field.\r
+    //\r
+    CopyMem (\r
+      &Private->ServerIp,\r
+      &Cache4->Packet.Offer.Dhcp4.Header.ServerAddr,\r
+      sizeof (EFI_IPv4_ADDRESS)\r
+      );\r
+  }\r
+  if (Private->ServerIp.Addr[0] == 0) {\r
+    //\r
+    // Still failed , use the IP address from option 54.\r
+    //\r
     CopyMem (\r
       &Private->ServerIp,\r
       Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_SERVER_ID]->Data,\r
@@ -493,7 +539,6 @@ PxeBcDhcp4BootInfo (
   //\r
   // Parse the boot file name by option.\r
   //\r
-  ASSERT (Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE] != NULL);\r
   Private->BootFileName = Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE]->Data;\r
 \r
   if (Cache4->OptList[PXEBC_DHCP4_TAG_INDEX_BOOTFILE_LEN] != NULL) {\r
@@ -580,12 +625,35 @@ PxeBcDhcp6BootInfo (
     Cache6 = &Private->DhcpAck.Dhcp6;\r
   }\r
 \r
-  ASSERT (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] != NULL);\r
+  if (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL] == NULL) {\r
+    //\r
+    // This should never happen in a correctly configured DHCP / PXE\r
+    // environment. One misconfiguration that can cause it is two DHCP servers\r
+    // mistakenly running on the same network segment at the same time, and\r
+    // racing each other in answering DHCP requests. Thus, the DHCP packets\r
+    // that the edk2 PXE client considers "belonging together" may actually be\r
+    // entirely independent, coming from two (competing) DHCP servers.\r
+    //\r
+    // Try to deal with this gracefully. Note that this check is not\r
+    // comprehensive, as we don't try to identify all such errors.\r
+    //\r
+    return EFI_PROTOCOL_ERROR;\r
+  }\r
+\r
+  //\r
+  // Set the station address to IP layer.\r
+  //\r
+  Status = PxeBcSetIp6Address (Private);\r
+  if (EFI_ERROR (Status)) {\r
+    return Status;\r
+  }\r
+\r
 \r
   //\r
   // Parse (m)tftp server ip address and bootfile name.\r
   //\r
   Status = PxeBcExtractBootFileUrl (\r
+             Private,\r
              &Private->BootFileName,\r
              &Private->ServerIp.v6,\r
              (CHAR8 *) (Cache6->OptList[PXEBC_DHCP6_IDX_BOOT_FILE_URL]->Data),\r
@@ -651,7 +719,7 @@ PxeBcDhcp6BootInfo (
 \r
   @param[in]      Private      Pointer to PxeBc private data.\r
   @param[in]      Type         The type of bootstrap to perform.\r
-  @param[in, out] Info         Pointer to EFI_PXE_BASE_CODE_DISCOVER_INFO.\r
+  @param[in, out] DiscoverInfo Pointer to EFI_PXE_BASE_CODE_DISCOVER_INFO.\r
   @param[out]     BootEntry    Pointer to PXEBC_BOOT_SVR_ENTRY.\r
   @param[out]     SrvList      Pointer to EFI_PXE_BASE_CODE_SRVLIST.\r
 \r
@@ -663,7 +731,7 @@ EFI_STATUS
 PxeBcExtractDiscoverInfo (\r
   IN     PXEBC_PRIVATE_DATA               *Private,\r
   IN     UINT16                           Type,\r
-  IN OUT EFI_PXE_BASE_CODE_DISCOVER_INFO  *Info,\r
+  IN OUT EFI_PXE_BASE_CODE_DISCOVER_INFO  **DiscoverInfo,\r
      OUT PXEBC_BOOT_SVR_ENTRY             **BootEntry,\r
      OUT EFI_PXE_BASE_CODE_SRVLIST        **SrvList\r
   )\r
@@ -673,8 +741,11 @@ PxeBcExtractDiscoverInfo (
   PXEBC_VENDOR_OPTION             *VendorOpt;\r
   PXEBC_BOOT_SVR_ENTRY            *Entry;\r
   BOOLEAN                         IsFound;\r
+  EFI_PXE_BASE_CODE_DISCOVER_INFO *Info;\r
+  UINT16                          Index;\r
 \r
   Mode = Private->PxeBc.Mode;\r
+  Info = *DiscoverInfo;\r
 \r
   if (Mode->UsingIpv6) {\r
     Info->IpCnt    = 1;\r
@@ -708,7 +779,7 @@ PxeBcExtractDiscoverInfo (
     Info->UseMCast    = (BOOLEAN) !IS_DISABLE_MCAST_DISCOVER (VendorOpt->DiscoverCtrl);\r
     Info->UseBCast    = (BOOLEAN) !IS_DISABLE_BCAST_DISCOVER (VendorOpt->DiscoverCtrl);\r
     Info->MustUseList = (BOOLEAN) IS_ENABLE_USE_SERVER_LIST (VendorOpt->DiscoverCtrl);\r
-    Info->UseUCast    = Info->MustUseList;\r
+    Info->UseUCast    = (BOOLEAN) IS_VALID_BOOT_SERVERS (VendorOpt->BitMap);\r
 \r
     if (Info->UseMCast) {\r
       //\r
@@ -719,7 +790,7 @@ PxeBcExtractDiscoverInfo (
 \r
     Info->IpCnt = 0;\r
 \r
-    if (Info->MustUseList) {\r
+    if (Info->UseUCast) {\r
       Entry = VendorOpt->BootSvr;\r
 \r
       while (((UINT8) (Entry - VendorOpt->BootSvr)) < VendorOpt->BootSvrLen) {\r
@@ -735,9 +806,24 @@ PxeBcExtractDiscoverInfo (
       }\r
 \r
       Info->IpCnt = Entry->IpCnt;\r
+      if (Info->IpCnt >= 1) {\r
+        *DiscoverInfo = AllocatePool (sizeof (*Info) + (Info->IpCnt - 1) * sizeof (**SrvList));\r
+        if (*DiscoverInfo == NULL) {\r
+          return EFI_OUT_OF_RESOURCES;\r
+        }\r
+        CopyMem (*DiscoverInfo, Info, sizeof (*Info));\r
+        Info = *DiscoverInfo;\r
+      }\r
+\r
+      for (Index = 0; Index < Info->IpCnt; Index++) {\r
+        CopyMem (&Info->SrvList[Index].IpAddr, &Entry->IpAddr[Index], sizeof (EFI_IPv4_ADDRESS));\r
+        Info->SrvList[Index].AcceptAnyResponse = !Info->MustUseList;\r
+        Info->SrvList[Index].Type = NTOHS (Entry->Type);\r
+      }\r
     }\r
 \r
     *BootEntry = Entry;\r
+    *SrvList   = Info->SrvList;\r
   }\r
 \r
   return EFI_SUCCESS;\r
@@ -842,12 +928,12 @@ PxeBcDiscoverBootFile (
     //\r
     // Choose by user's input.\r
     //\r
-    Status = PxeBcSelectBootMenu (Private, &Type, TRUE);\r
+    Status = PxeBcSelectBootMenu (Private, &Type, FALSE);\r
   } else if (Status == EFI_TIMEOUT) {\r
     //\r
     // Choose by default item.\r
     //\r
-    Status = PxeBcSelectBootMenu (Private, &Type, FALSE);\r
+    Status = PxeBcSelectBootMenu (Private, &Type, TRUE);\r
   }\r
 \r
   if (!EFI_ERROR (Status)) {\r
@@ -868,6 +954,27 @@ PxeBcDiscoverBootFile (
     if (EFI_ERROR (Status)) {\r
       return Status;\r
     }\r
+\r
+    if (Mode->PxeReplyReceived && !Mode->ProxyOfferReceived) {\r
+      //\r
+      // Some network boot loader only search the packet in Mode.ProxyOffer to get its server\r
+      // IP address, so we need to store a copy of Mode.PxeReply packet into Mode.ProxyOffer.\r
+      //\r
+      if (Mode->UsingIpv6) {\r
+        CopyMem (\r
+          &Mode->ProxyOffer.Dhcpv6,\r
+          &Mode->PxeReply.Dhcpv6,\r
+          Private->PxeReply.Dhcp6.Packet.Ack.Length\r
+          );\r
+      } else {\r
+        CopyMem (\r
+          &Mode->ProxyOffer.Dhcpv4,\r
+          &Mode->PxeReply.Dhcpv4,\r
+          Private->PxeReply.Dhcp4.Packet.Ack.Length\r
+          );\r
+      }\r
+      Mode->ProxyOfferReceived = TRUE;\r
+    }\r
   }\r
 \r
   //\r
@@ -889,7 +996,7 @@ PxeBcDiscoverBootFile (
   @param[in, out] Private           Pointer to PxeBc private data.\r
   @param[out]     NewMakeCallback   If TRUE, it is a new callback.\r
                                     Otherwise, it is not new callback.\r
-  @retval EFI_SUCCESS          PxeBaseCodeCallbackProtocol installed succesfully.\r
+  @retval EFI_SUCCESS          PxeBaseCodeCallbackProtocol installed successfully.\r
   @retval Others               Failed to install PxeBaseCodeCallbackProtocol.\r
 \r
 **/\r
@@ -907,7 +1014,7 @@ PxeBcInstallCallback (
   //\r
   PxeBc  = &Private->PxeBc;\r
   Status = gBS->HandleProtocol (\r
-                  Private->Controller,\r
+                  Private->Mode.UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,\r
                   &gEfiPxeBaseCodeCallbackProtocolGuid,\r
                   (VOID **) &Private->PxeBcCallback\r
                   );\r
@@ -923,7 +1030,7 @@ PxeBcInstallCallback (
     // Install a default callback if user didn't offer one.\r
     //\r
     Status = gBS->InstallProtocolInterface (\r
-                    &Private->Controller,\r
+                    Private->Mode.UsingIpv6 ? &Private->Ip6Nic->Controller : &Private->Ip4Nic->Controller,\r
                     &gEfiPxeBaseCodeCallbackProtocolGuid,\r
                     EFI_NATIVE_INTERFACE,\r
                     &Private->LoadFileCallback\r
@@ -967,7 +1074,7 @@ PxeBcUninstallCallback (
     PxeBc->SetParameters (PxeBc, NULL, NULL, NULL, NULL, &NewMakeCallback);\r
 \r
     gBS->UninstallProtocolInterface (\r
-          Private->Controller,\r
+          Private->Mode.UsingIpv6 ? Private->Ip6Nic->Controller : Private->Ip4Nic->Controller,\r
           &gEfiPxeBaseCodeCallbackProtocolGuid,\r
           &Private->LoadFileCallback\r
           );\r
@@ -1074,6 +1181,9 @@ PxeBcLoadBootFile (
     // Discover the boot information about the bootfile if hasn't.\r
     //\r
     Status = PxeBcDiscoverBootFile (Private, &RequiredSize);\r
+    if (EFI_ERROR (Status)) {\r
+      goto ON_EXIT;\r
+    }\r
 \r
     if (PXEBC_IS_SIZE_OVERFLOWED (RequiredSize)) {\r
       //\r
@@ -1141,7 +1251,7 @@ ON_EXIT:
   PxeBcUninstallCallback(Private, NewMakeCallback);\r
 \r
   if (Status == EFI_SUCCESS) {\r
-    AsciiPrint ("\n  Succeed to download NBP file.\n");\r
+    AsciiPrint ("\n  NBP file downloaded successfully.\n");\r
     return EFI_SUCCESS;\r
   } else if (Status == EFI_BUFFER_TOO_SMALL && Buffer != NULL) {\r
     AsciiPrint ("\n  PXE-E05: Buffer size is smaller than the requested file.\n");\r
@@ -1152,7 +1262,7 @@ ON_EXIT:
   } else if (Status == EFI_NO_MEDIA) {\r
     AsciiPrint ("\n  PXE-E12: Could not detect network connection.\n");\r
   } else if (Status == EFI_NO_RESPONSE) {\r
-    AsciiPrint ("\n  PXE-E16: No offer received.\n");\r
+    AsciiPrint ("\n  PXE-E16: No valid offer received.\n");\r
   } else if (Status == EFI_TIMEOUT) {\r
     AsciiPrint ("\n  PXE-E18: Server response timeout.\n");\r
   } else if (Status == EFI_ABORTED) {\r
@@ -1161,6 +1271,8 @@ ON_EXIT:
     AsciiPrint ("\n  PXE-E22: Client received ICMP error from server.\n");\r
   } else if (Status == EFI_TFTP_ERROR) {\r
     AsciiPrint ("\n  PXE-E23: Client received TFTP error from server.\n");\r
+  } else if (Status == EFI_NOT_FOUND) {\r
+    AsciiPrint ("\n  PXE-E53: No boot filename received.\n");\r
   } else if (Status != EFI_BUFFER_TOO_SMALL) {\r
     AsciiPrint ("\n  PXE-E99: Unexpected network error.\n");\r
   }\r