| /** @file | |
| EFI DHCP protocol implementation. | |
| Copyright (c) 2006 - 2010, Intel Corporation. All rights reserved.<BR> | |
| This program and the accompanying materials | |
| are licensed and made available under the terms and conditions of the BSD License | |
| which accompanies this distribution. The full text of the license may be found at | |
| http://opensource.org/licenses/bsd-license.php | |
| THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
| **/ | |
| #include "Dhcp4Impl.h" | |
| UINT32 mDhcp4DefaultTimeout[4] = { 4, 8, 16, 32 }; | |
| /** | |
| Send an initial DISCOVER or REQUEST message according to the | |
| DHCP service's current state. | |
| @param[in] DhcpSb The DHCP service instance | |
| @retval EFI_SUCCESS The request has been sent | |
| @retval other Some error occurs when sending the request. | |
| **/ | |
| EFI_STATUS | |
| DhcpInitRequest ( | |
| IN DHCP_SERVICE *DhcpSb | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| ASSERT ((DhcpSb->DhcpState == Dhcp4Init) || (DhcpSb->DhcpState == Dhcp4InitReboot)); | |
| if (DhcpSb->DhcpState == Dhcp4Init) { | |
| DhcpSetState (DhcpSb, Dhcp4Selecting, FALSE); | |
| Status = DhcpSendMessage (DhcpSb, NULL, NULL, DHCP_MSG_DISCOVER, NULL); | |
| if (EFI_ERROR (Status)) { | |
| DhcpSb->DhcpState = Dhcp4Init; | |
| return Status; | |
| } | |
| } else { | |
| DhcpSetState (DhcpSb, Dhcp4Rebooting, FALSE); | |
| Status = DhcpSendMessage (DhcpSb, NULL, NULL, DHCP_MSG_REQUEST, NULL); | |
| if (EFI_ERROR (Status)) { | |
| DhcpSb->DhcpState = Dhcp4InitReboot; | |
| return Status; | |
| } | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Call user provided callback function, and return the value the | |
| function returns. If the user doesn't provide a callback, a | |
| proper return value is selected to let the caller continue the | |
| normal process. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Event The event as defined in the spec | |
| @param[in] Packet The current packet trigger the event | |
| @param[out] NewPacket The user's return new packet | |
| @retval EFI_NOT_READY Direct the caller to continue collecting the offer. | |
| @retval EFI_SUCCESS The user function returns success. | |
| @retval EFI_ABORTED The user function ask it to abort. | |
| **/ | |
| EFI_STATUS | |
| DhcpCallUser ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_DHCP4_EVENT Event, | |
| IN EFI_DHCP4_PACKET *Packet, OPTIONAL | |
| OUT EFI_DHCP4_PACKET **NewPacket OPTIONAL | |
| ) | |
| { | |
| EFI_DHCP4_CONFIG_DATA *Config; | |
| EFI_STATUS Status; | |
| if (NewPacket != NULL) { | |
| *NewPacket = NULL; | |
| } | |
| // | |
| // If user doesn't provide the call back function, return the value | |
| // that directs the client to continue the normal process. | |
| // In Dhcp4Selecting EFI_SUCCESS tells the client to stop collecting | |
| // the offers and select a offer, EFI_NOT_READY tells the client to | |
| // collect more offers. | |
| // | |
| Config = &DhcpSb->ActiveConfig; | |
| if (Config->Dhcp4Callback == NULL) { | |
| if (Event == Dhcp4RcvdOffer) { | |
| return EFI_NOT_READY; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| Status = Config->Dhcp4Callback ( | |
| &DhcpSb->ActiveChild->Dhcp4Protocol, | |
| Config->CallbackContext, | |
| (EFI_DHCP4_STATE) DhcpSb->DhcpState, | |
| Event, | |
| Packet, | |
| NewPacket | |
| ); | |
| // | |
| // User's callback should only return EFI_SUCCESS, EFI_NOT_READY, | |
| // and EFI_ABORTED. If it returns values other than those, assume | |
| // it to be EFI_ABORTED. | |
| // | |
| if ((Status == EFI_SUCCESS) || (Status == EFI_NOT_READY)) { | |
| return Status; | |
| } | |
| return EFI_ABORTED; | |
| } | |
| /** | |
| Notify the user about the operation result. | |
| @param DhcpSb DHCP service instance | |
| @param Which Which notify function to signal | |
| **/ | |
| VOID | |
| DhcpNotifyUser ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN INTN Which | |
| ) | |
| { | |
| DHCP_PROTOCOL *Child; | |
| if ((Child = DhcpSb->ActiveChild) == NULL) { | |
| return ; | |
| } | |
| if ((Child->CompletionEvent != NULL) && | |
| ((Which == DHCP_NOTIFY_COMPLETION) || (Which == DHCP_NOTIFY_ALL)) | |
| ) { | |
| gBS->SignalEvent (Child->CompletionEvent); | |
| Child->CompletionEvent = NULL; | |
| } | |
| if ((Child->RenewRebindEvent != NULL) && | |
| ((Which == DHCP_NOTIFY_RENEWREBIND) || (Which == DHCP_NOTIFY_ALL)) | |
| ) { | |
| gBS->SignalEvent (Child->RenewRebindEvent); | |
| Child->RenewRebindEvent = NULL; | |
| } | |
| } | |
| /** | |
| Set the DHCP state. If CallUser is true, it will try to notify | |
| the user before change the state by DhcpNotifyUser. It returns | |
| EFI_ABORTED if the user return EFI_ABORTED, otherwise, it returns | |
| EFI_SUCCESS. If CallUser is FALSE, it isn't necessary to test | |
| the return value of this function. | |
| @param DhcpSb The DHCP service instance | |
| @param State The new DHCP state to change to | |
| @param CallUser Whether we need to call user | |
| @retval EFI_SUCCESS The state is changed | |
| @retval EFI_ABORTED The user asks to abort the DHCP process. | |
| **/ | |
| EFI_STATUS | |
| DhcpSetState ( | |
| IN OUT DHCP_SERVICE *DhcpSb, | |
| IN INTN State, | |
| IN BOOLEAN CallUser | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| if (CallUser) { | |
| Status = EFI_SUCCESS; | |
| if (State == Dhcp4Renewing) { | |
| Status = DhcpCallUser (DhcpSb, Dhcp4EnterRenewing, NULL, NULL); | |
| } else if (State == Dhcp4Rebinding) { | |
| Status = DhcpCallUser (DhcpSb, Dhcp4EnterRebinding, NULL, NULL); | |
| } else if (State == Dhcp4Bound) { | |
| Status = DhcpCallUser (DhcpSb, Dhcp4BoundCompleted, NULL, NULL); | |
| } | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| } | |
| // | |
| // Update the retransmission timer during the state transition. | |
| // This will clear the retry count. This is also why the rule | |
| // first transit the state, then send packets. | |
| // | |
| if (State == Dhcp4Selecting) { | |
| DhcpSb->MaxRetries = DhcpSb->ActiveConfig.DiscoverTryCount; | |
| } else { | |
| DhcpSb->MaxRetries = DhcpSb->ActiveConfig.RequestTryCount; | |
| } | |
| if (DhcpSb->MaxRetries == 0) { | |
| DhcpSb->MaxRetries = 4; | |
| } | |
| DhcpSb->CurRetry = 0; | |
| DhcpSb->PacketToLive = 0; | |
| DhcpSb->LastTimeout = 0; | |
| DhcpSb->DhcpState = State; | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Set the retransmit timer for the packet. It will select from either | |
| the discover timeouts/request timeouts or the default timeout values. | |
| @param DhcpSb The DHCP service instance. | |
| **/ | |
| VOID | |
| DhcpSetTransmitTimer ( | |
| IN OUT DHCP_SERVICE *DhcpSb | |
| ) | |
| { | |
| UINT32 *Times; | |
| ASSERT (DhcpSb->MaxRetries > DhcpSb->CurRetry); | |
| if (DhcpSb->DhcpState == Dhcp4Selecting) { | |
| Times = DhcpSb->ActiveConfig.DiscoverTimeout; | |
| } else { | |
| Times = DhcpSb->ActiveConfig.RequestTimeout; | |
| } | |
| if (Times == NULL) { | |
| Times = mDhcp4DefaultTimeout; | |
| } | |
| DhcpSb->PacketToLive = Times[DhcpSb->CurRetry]; | |
| DhcpSb->LastTimeout = DhcpSb->PacketToLive; | |
| return; | |
| } | |
| /** | |
| Compute the lease. If the server grants a permanent lease, just | |
| process it as a normal timeout value since the lease will last | |
| more than 100 years. | |
| @param DhcpSb The DHCP service instance | |
| @param Para The DHCP parameter extracted from the server's | |
| response. | |
| **/ | |
| VOID | |
| DhcpComputeLease ( | |
| IN OUT DHCP_SERVICE *DhcpSb, | |
| IN DHCP_PARAMETER *Para | |
| ) | |
| { | |
| ASSERT (Para != NULL); | |
| DhcpSb->Lease = Para->Lease; | |
| DhcpSb->T2 = Para->T2; | |
| DhcpSb->T1 = Para->T1; | |
| if (DhcpSb->Lease == 0) { | |
| DhcpSb->Lease = DHCP_DEFAULT_LEASE; | |
| } | |
| if ((DhcpSb->T2 == 0) || (DhcpSb->T2 >= Para->Lease)) { | |
| DhcpSb->T2 = Para->Lease - (Para->Lease >> 3); | |
| } | |
| if ((DhcpSb->T1 == 0) || (DhcpSb->T1 >= Para->T2)) { | |
| DhcpSb->T1 = DhcpSb->Lease >> 1; | |
| } | |
| } | |
| /** | |
| Configure a UDP IO port to use the acquired lease address. | |
| DHCP driver needs this port to unicast packet to the server | |
| such as DHCP release. | |
| @param[in] UdpIo The UDP IO to configure | |
| @param[in] Context Dhcp service instance. | |
| @retval EFI_SUCCESS The UDP IO port is successfully configured. | |
| @retval Others It failed to configure the port. | |
| **/ | |
| EFI_STATUS | |
| EFIAPI | |
| DhcpConfigLeaseIoPort ( | |
| IN UDP_IO *UdpIo, | |
| IN VOID *Context | |
| ) | |
| { | |
| EFI_UDP4_CONFIG_DATA UdpConfigData; | |
| EFI_IPv4_ADDRESS Subnet; | |
| EFI_IPv4_ADDRESS Gateway; | |
| DHCP_SERVICE *DhcpSb; | |
| EFI_STATUS Status; | |
| IP4_ADDR Ip; | |
| DhcpSb = (DHCP_SERVICE *) Context; | |
| UdpConfigData.AcceptBroadcast = FALSE; | |
| UdpConfigData.AcceptPromiscuous = FALSE; | |
| UdpConfigData.AcceptAnyPort = FALSE; | |
| UdpConfigData.AllowDuplicatePort = TRUE; | |
| UdpConfigData.TypeOfService = 0; | |
| UdpConfigData.TimeToLive = 64; | |
| UdpConfigData.DoNotFragment = FALSE; | |
| UdpConfigData.ReceiveTimeout = 1; | |
| UdpConfigData.TransmitTimeout = 0; | |
| UdpConfigData.UseDefaultAddress = FALSE; | |
| UdpConfigData.StationPort = DHCP_CLIENT_PORT; | |
| UdpConfigData.RemotePort = DHCP_SERVER_PORT; | |
| Ip = HTONL (DhcpSb->ClientAddr); | |
| CopyMem (&UdpConfigData.StationAddress, &Ip, sizeof (EFI_IPv4_ADDRESS)); | |
| Ip = HTONL (DhcpSb->Netmask); | |
| CopyMem (&UdpConfigData.SubnetMask, &Ip, sizeof (EFI_IPv4_ADDRESS)); | |
| ZeroMem (&UdpConfigData.RemoteAddress, sizeof (EFI_IPv4_ADDRESS)); | |
| Status = UdpIo->Protocol.Udp4->Configure (UdpIo->Protocol.Udp4, &UdpConfigData); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| // | |
| // Add a default route if received from the server. | |
| // | |
| if ((DhcpSb->Para != NULL) && (DhcpSb->Para->Router != 0)) { | |
| ZeroMem (&Subnet, sizeof (EFI_IPv4_ADDRESS)); | |
| Ip = HTONL (DhcpSb->Para->Router); | |
| CopyMem (&Gateway, &Ip, sizeof (EFI_IPv4_ADDRESS)); | |
| UdpIo->Protocol.Udp4->Routes (UdpIo->Protocol.Udp4, FALSE, &Subnet, &Subnet, &Gateway); | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Update the lease states when a new lease is acquired. It will not only | |
| save the acquired the address and lease time, it will also create a UDP | |
| child to provide address resolution for the address. | |
| @param DhcpSb The DHCP service instance | |
| @retval EFI_OUT_OF_RESOURCES Failed to allocate resources. | |
| @retval EFI_SUCCESS The lease is recorded. | |
| **/ | |
| EFI_STATUS | |
| DhcpLeaseAcquired ( | |
| IN OUT DHCP_SERVICE *DhcpSb | |
| ) | |
| { | |
| INTN Class; | |
| DhcpSb->ClientAddr = EFI_NTOHL (DhcpSb->Selected->Dhcp4.Header.YourAddr); | |
| if (DhcpSb->Para != NULL) { | |
| DhcpSb->Netmask = DhcpSb->Para->NetMask; | |
| DhcpSb->ServerAddr = DhcpSb->Para->ServerId; | |
| } | |
| if (DhcpSb->Netmask == 0) { | |
| Class = NetGetIpClass (DhcpSb->ClientAddr); | |
| DhcpSb->Netmask = gIp4AllMasks[Class << 3]; | |
| } | |
| if (DhcpSb->LeaseIoPort != NULL) { | |
| UdpIoFreeIo (DhcpSb->LeaseIoPort); | |
| } | |
| // | |
| // Create a UDP/IP child to provide ARP service for the Leased IP, | |
| // and transmit unicast packet with it as source address. Don't | |
| // start receive on this port, the queued packet will be timeout. | |
| // | |
| DhcpSb->LeaseIoPort = UdpIoCreateIo ( | |
| DhcpSb->Controller, | |
| DhcpSb->Image, | |
| DhcpConfigLeaseIoPort, | |
| UDP_IO_UDP4_VERSION, | |
| DhcpSb | |
| ); | |
| if (DhcpSb->LeaseIoPort == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| if (!DHCP_IS_BOOTP (DhcpSb->Para)) { | |
| DhcpComputeLease (DhcpSb, DhcpSb->Para); | |
| } | |
| return DhcpSetState (DhcpSb, Dhcp4Bound, TRUE); | |
| } | |
| /** | |
| Clean up the DHCP related states, IoStatus isn't reset. | |
| @param DhcpSb The DHCP instance service. | |
| **/ | |
| VOID | |
| DhcpCleanLease ( | |
| IN DHCP_SERVICE *DhcpSb | |
| ) | |
| { | |
| DhcpSb->DhcpState = Dhcp4Init; | |
| DhcpSb->Xid = DhcpSb->Xid + 1; | |
| DhcpSb->ClientAddr = 0; | |
| DhcpSb->Netmask = 0; | |
| DhcpSb->ServerAddr = 0; | |
| if (DhcpSb->LastOffer != NULL) { | |
| FreePool (DhcpSb->LastOffer); | |
| DhcpSb->LastOffer = NULL; | |
| } | |
| if (DhcpSb->Selected != NULL) { | |
| FreePool (DhcpSb->Selected); | |
| DhcpSb->Selected = NULL; | |
| } | |
| if (DhcpSb->Para != NULL) { | |
| FreePool (DhcpSb->Para); | |
| DhcpSb->Para = NULL; | |
| } | |
| DhcpSb->Lease = 0; | |
| DhcpSb->T1 = 0; | |
| DhcpSb->T2 = 0; | |
| DhcpSb->ExtraRefresh = FALSE; | |
| if (DhcpSb->LeaseIoPort != NULL) { | |
| UdpIoFreeIo (DhcpSb->LeaseIoPort); | |
| DhcpSb->LeaseIoPort = NULL; | |
| } | |
| if (DhcpSb->LastPacket != NULL) { | |
| FreePool (DhcpSb->LastPacket); | |
| DhcpSb->LastPacket = NULL; | |
| } | |
| DhcpSb->PacketToLive = 0; | |
| DhcpSb->LastTimeout = 0; | |
| DhcpSb->CurRetry = 0; | |
| DhcpSb->MaxRetries = 0; | |
| DhcpSb->LeaseLife = 0; | |
| // | |
| // Clean active config data. | |
| // | |
| DhcpCleanConfigure (&DhcpSb->ActiveConfig); | |
| } | |
| /** | |
| Select a offer among all the offers collected. If the offer selected is | |
| of BOOTP, the lease is recorded and user notified. If the offer is of | |
| DHCP, it will request the offer from the server. | |
| @param[in] DhcpSb The DHCP service instance. | |
| @retval EFI_SUCCESS One of the offer is selected. | |
| **/ | |
| EFI_STATUS | |
| DhcpChooseOffer ( | |
| IN DHCP_SERVICE *DhcpSb | |
| ) | |
| { | |
| EFI_DHCP4_PACKET *Selected; | |
| EFI_DHCP4_PACKET *NewPacket; | |
| EFI_DHCP4_PACKET *TempPacket; | |
| EFI_STATUS Status; | |
| ASSERT (DhcpSb->LastOffer != NULL); | |
| // | |
| // User will cache previous offers if he wants to select | |
| // from multiple offers. If user provides an invalid packet, | |
| // use the last offer, otherwise use the provided packet. | |
| // | |
| NewPacket = NULL; | |
| Status = DhcpCallUser (DhcpSb, Dhcp4SelectOffer, DhcpSb->LastOffer, &NewPacket); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| Selected = DhcpSb->LastOffer; | |
| if ((NewPacket != NULL) && !EFI_ERROR (DhcpValidateOptions (NewPacket, NULL))) { | |
| TempPacket = (EFI_DHCP4_PACKET *) AllocatePool (NewPacket->Size); | |
| if (TempPacket != NULL) { | |
| CopyMem (TempPacket, NewPacket, NewPacket->Size); | |
| FreePool (Selected); | |
| Selected = TempPacket; | |
| } | |
| } | |
| DhcpSb->Selected = Selected; | |
| DhcpSb->LastOffer = NULL; | |
| DhcpSb->Para = NULL; | |
| DhcpValidateOptions (Selected, &DhcpSb->Para); | |
| // | |
| // A bootp offer has been selected, save the lease status, | |
| // enter bound state then notify the user. | |
| // | |
| if (DHCP_IS_BOOTP (DhcpSb->Para)) { | |
| Status = DhcpLeaseAcquired (DhcpSb); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| DhcpSb->IoStatus = EFI_SUCCESS; | |
| DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_ALL); | |
| return EFI_SUCCESS; | |
| } | |
| // | |
| // Send a DHCP requests | |
| // | |
| Status = DhcpSetState (DhcpSb, Dhcp4Requesting, TRUE); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| return DhcpSendMessage (DhcpSb, Selected, DhcpSb->Para, DHCP_MSG_REQUEST, NULL); | |
| } | |
| /** | |
| Terminate the current address acquire. All the allocated resources | |
| are released. Be careful when calling this function. A rule related | |
| to this is: only call DhcpEndSession at the highest level, such as | |
| DhcpInput, DhcpOnTimerTick...At the other level, just return error. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Status The result of the DHCP process. | |
| **/ | |
| VOID | |
| DhcpEndSession ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_STATUS Status | |
| ) | |
| { | |
| if (DHCP_CONNECTED (DhcpSb->DhcpState)) { | |
| DhcpCallUser (DhcpSb, Dhcp4AddressLost, NULL, NULL); | |
| } else { | |
| DhcpCallUser (DhcpSb, Dhcp4Fail, NULL, NULL); | |
| } | |
| DhcpCleanLease (DhcpSb); | |
| DhcpSb->IoStatus = Status; | |
| DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_ALL); | |
| } | |
| /** | |
| Handle packets in DHCP select state. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Packet The DHCP packet received | |
| @param[in] Para The DHCP parameter extracted from the packet. That | |
| is, all the option value that we care. | |
| @retval EFI_SUCCESS The packet is successfully processed. | |
| @retval Others Some error occured. | |
| **/ | |
| EFI_STATUS | |
| DhcpHandleSelect ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_DHCP4_PACKET *Packet, | |
| IN DHCP_PARAMETER *Para | |
| ) | |
| { | |
| EFI_STATUS Status; | |
| Status = EFI_SUCCESS; | |
| // | |
| // First validate the message: | |
| // 1. the offer is a unicast | |
| // 2. if it is a DHCP message, it must contains a server ID. | |
| // Don't return a error for these two case otherwise the session is ended. | |
| // | |
| if (!DHCP_IS_BOOTP (Para) && | |
| ((Para->DhcpType != DHCP_MSG_OFFER) || (Para->ServerId == 0)) | |
| ) { | |
| goto ON_EXIT; | |
| } | |
| // | |
| // Call the user's callback. The action according to the return is as: | |
| // 1. EFI_SUCESS: stop waiting for more offers, select the offer now | |
| // 2. EFI_NOT_READY: wait for more offers | |
| // 3. EFI_ABORTED: abort the address acquiring. | |
| // | |
| Status = DhcpCallUser (DhcpSb, Dhcp4RcvdOffer, Packet, NULL); | |
| if (Status == EFI_SUCCESS) { | |
| if (DhcpSb->LastOffer != NULL) { | |
| FreePool (DhcpSb->LastOffer); | |
| } | |
| DhcpSb->LastOffer = Packet; | |
| return DhcpChooseOffer (DhcpSb); | |
| } else if (Status == EFI_NOT_READY) { | |
| if (DhcpSb->LastOffer != NULL) { | |
| FreePool (DhcpSb->LastOffer); | |
| } | |
| DhcpSb->LastOffer = Packet; | |
| } else if (Status == EFI_ABORTED) { | |
| // | |
| // DhcpInput will end the session upon error return. Remember | |
| // only to call DhcpEndSession at the top level call. | |
| // | |
| goto ON_EXIT; | |
| } | |
| return EFI_SUCCESS; | |
| ON_EXIT: | |
| FreePool (Packet); | |
| return Status; | |
| } | |
| /** | |
| Handle packets in DHCP request state. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Packet The DHCP packet received | |
| @param[in] Para The DHCP parameter extracted from the packet. That | |
| is, all the option value that we care. | |
| @retval EFI_SUCCESS The packet is successfully processed. | |
| @retval Others Some error occured. | |
| **/ | |
| EFI_STATUS | |
| DhcpHandleRequest ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_DHCP4_PACKET *Packet, | |
| IN DHCP_PARAMETER *Para | |
| ) | |
| { | |
| EFI_DHCP4_HEADER *Head; | |
| EFI_DHCP4_HEADER *Selected; | |
| EFI_STATUS Status; | |
| UINT8 *Message; | |
| ASSERT (!DHCP_IS_BOOTP (DhcpSb->Para)); | |
| Head = &Packet->Dhcp4.Header; | |
| Selected = &DhcpSb->Selected->Dhcp4.Header; | |
| // | |
| // Ignore the BOOTP message and DHCP messages other than DHCP ACK/NACK. | |
| // | |
| if (DHCP_IS_BOOTP (Para) || | |
| (Para->ServerId != DhcpSb->Para->ServerId) || | |
| ((Para->DhcpType != DHCP_MSG_ACK) && (Para->DhcpType != DHCP_MSG_NAK)) | |
| ) { | |
| Status = EFI_SUCCESS; | |
| goto ON_EXIT; | |
| } | |
| // | |
| // Received a NAK, end the session no matter what the user returns | |
| // | |
| Status = EFI_DEVICE_ERROR; | |
| if (Para->DhcpType == DHCP_MSG_NAK) { | |
| DhcpCallUser (DhcpSb, Dhcp4RcvdNak, Packet, NULL); | |
| goto ON_EXIT; | |
| } | |
| // | |
| // Check whether the ACK matches the selected offer | |
| // | |
| Message = NULL; | |
| if (!EFI_IP4_EQUAL (&Head->YourAddr, &Selected->YourAddr)) { | |
| Message = (UINT8 *) "Lease confirmed isn't the same as that in the offer"; | |
| goto REJECT; | |
| } | |
| Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL); | |
| if (EFI_ERROR (Status)) { | |
| Message = (UINT8 *) "Lease is denied upon received ACK"; | |
| goto REJECT; | |
| } | |
| // | |
| // Record the lease, transit to BOUND state, then notify the user | |
| // | |
| Status = DhcpLeaseAcquired (DhcpSb); | |
| if (EFI_ERROR (Status)) { | |
| Message = (UINT8 *) "Lease is denied upon entering bound"; | |
| goto REJECT; | |
| } | |
| DhcpSb->IoStatus = EFI_SUCCESS; | |
| DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_COMPLETION); | |
| FreePool (Packet); | |
| return EFI_SUCCESS; | |
| REJECT: | |
| DhcpSendMessage (DhcpSb, DhcpSb->Selected, DhcpSb->Para, DHCP_MSG_DECLINE, Message); | |
| ON_EXIT: | |
| FreePool (Packet); | |
| return Status; | |
| } | |
| /** | |
| Handle packets in DHCP renew/rebound state. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Packet The DHCP packet received | |
| @param[in] Para The DHCP parameter extracted from the packet. That | |
| is, all the option value that we care. | |
| @retval EFI_SUCCESS The packet is successfully processed. | |
| @retval Others Some error occured. | |
| **/ | |
| EFI_STATUS | |
| DhcpHandleRenewRebind ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_DHCP4_PACKET *Packet, | |
| IN DHCP_PARAMETER *Para | |
| ) | |
| { | |
| EFI_DHCP4_HEADER *Head; | |
| EFI_DHCP4_HEADER *Selected; | |
| EFI_STATUS Status; | |
| ASSERT (!DHCP_IS_BOOTP (DhcpSb->Para)); | |
| Head = &Packet->Dhcp4.Header; | |
| Selected = &DhcpSb->Selected->Dhcp4.Header; | |
| // | |
| // Ignore the BOOTP message and DHCP messages other than DHCP ACK/NACK | |
| // | |
| if (DHCP_IS_BOOTP (Para) || | |
| (Para->ServerId != DhcpSb->Para->ServerId) || | |
| ((Para->DhcpType != DHCP_MSG_ACK) && (Para->DhcpType != DHCP_MSG_NAK)) | |
| ) { | |
| Status = EFI_SUCCESS; | |
| goto ON_EXIT; | |
| } | |
| // | |
| // Received a NAK, ignore the user's return then terminate the process | |
| // | |
| Status = EFI_DEVICE_ERROR; | |
| if (Para->DhcpType == DHCP_MSG_NAK) { | |
| DhcpCallUser (DhcpSb, Dhcp4RcvdNak, Packet, NULL); | |
| goto ON_EXIT; | |
| } | |
| // | |
| // The lease is different from the selected. Don't send a DECLINE | |
| // since it isn't existed in the client's FSM. | |
| // | |
| if (!EFI_IP4_EQUAL (&Head->YourAddr, &Selected->YourAddr)) { | |
| goto ON_EXIT; | |
| } | |
| Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL); | |
| if (EFI_ERROR (Status)) { | |
| goto ON_EXIT; | |
| } | |
| // | |
| // Record the lease, start timer for T1 and T2, | |
| // | |
| DhcpComputeLease (DhcpSb, Para); | |
| DhcpSb->LeaseLife = 0; | |
| DhcpSetState (DhcpSb, Dhcp4Bound, TRUE); | |
| if (DhcpSb->ExtraRefresh != 0) { | |
| DhcpSb->ExtraRefresh = FALSE; | |
| DhcpSb->IoStatus = EFI_SUCCESS; | |
| DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_RENEWREBIND); | |
| } | |
| ON_EXIT: | |
| FreePool (Packet); | |
| return Status; | |
| } | |
| /** | |
| Handle packets in DHCP reboot state. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Packet The DHCP packet received | |
| @param[in] Para The DHCP parameter extracted from the packet. That | |
| is, all the option value that we care. | |
| @retval EFI_SUCCESS The packet is successfully processed. | |
| @retval Others Some error occured. | |
| **/ | |
| EFI_STATUS | |
| DhcpHandleReboot ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_DHCP4_PACKET *Packet, | |
| IN DHCP_PARAMETER *Para | |
| ) | |
| { | |
| EFI_DHCP4_HEADER *Head; | |
| EFI_STATUS Status; | |
| Head = &Packet->Dhcp4.Header; | |
| // | |
| // Ignore the BOOTP message and DHCP messages other than DHCP ACK/NACK | |
| // | |
| if (DHCP_IS_BOOTP (Para) || | |
| ((Para->DhcpType != DHCP_MSG_ACK) && (Para->DhcpType != DHCP_MSG_NAK)) | |
| ) { | |
| Status = EFI_SUCCESS; | |
| goto ON_EXIT; | |
| } | |
| // | |
| // If a NAK is received, transit to INIT and try again. | |
| // | |
| if (Para->DhcpType == DHCP_MSG_NAK) { | |
| DhcpCallUser (DhcpSb, Dhcp4RcvdNak, Packet, NULL); | |
| DhcpSb->ClientAddr = 0; | |
| DhcpSb->DhcpState = Dhcp4Init; | |
| Status = DhcpInitRequest (DhcpSb); | |
| goto ON_EXIT; | |
| } | |
| // | |
| // Check whether the ACK matches the selected offer | |
| // | |
| if (EFI_NTOHL (Head->YourAddr) != DhcpSb->ClientAddr) { | |
| Status = EFI_DEVICE_ERROR; | |
| goto ON_EXIT; | |
| } | |
| Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL); | |
| if (EFI_ERROR (Status)) { | |
| goto ON_EXIT; | |
| } | |
| // | |
| // OK, get the parameter from server, record the lease | |
| // | |
| DhcpSb->Para = AllocateCopyPool (sizeof (DHCP_PARAMETER), Para); | |
| if (DhcpSb->Para == NULL) { | |
| Status = EFI_OUT_OF_RESOURCES; | |
| goto ON_EXIT; | |
| } | |
| DhcpSb->Selected = Packet; | |
| Status = DhcpLeaseAcquired (DhcpSb); | |
| if (EFI_ERROR (Status)) { | |
| return Status; | |
| } | |
| DhcpSb->IoStatus = EFI_SUCCESS; | |
| DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_COMPLETION); | |
| return EFI_SUCCESS; | |
| ON_EXIT: | |
| FreePool (Packet); | |
| return Status; | |
| } | |
| /** | |
| Handle the received DHCP packets. This function drives the DHCP | |
| state machine. | |
| @param UdpPacket The UDP packets received. | |
| @param EndPoint The local/remote UDP access point | |
| @param IoStatus The status of the UDP receive | |
| @param Context The opaque parameter to the function. | |
| **/ | |
| VOID | |
| EFIAPI | |
| DhcpInput ( | |
| NET_BUF *UdpPacket, | |
| UDP_END_POINT *EndPoint, | |
| EFI_STATUS IoStatus, | |
| VOID *Context | |
| ) | |
| { | |
| DHCP_SERVICE *DhcpSb; | |
| EFI_DHCP4_HEADER *Head; | |
| EFI_DHCP4_PACKET *Packet; | |
| DHCP_PARAMETER *Para; | |
| EFI_STATUS Status; | |
| UINT32 Len; | |
| Packet = NULL; | |
| DhcpSb = (DHCP_SERVICE *) Context; | |
| // | |
| // Don't restart receive if error occurs or DHCP is destoried. | |
| // | |
| if (EFI_ERROR (IoStatus)) { | |
| return ; | |
| } else if (DhcpSb->ServiceState == DHCP_DESTORY) { | |
| NetbufFree (UdpPacket); | |
| return ; | |
| } | |
| ASSERT (UdpPacket != NULL); | |
| if (DhcpSb->DhcpState == Dhcp4Stopped) { | |
| goto RESTART; | |
| } | |
| // | |
| // Validate the packet received | |
| // | |
| if (UdpPacket->TotalSize < sizeof (EFI_DHCP4_HEADER)) { | |
| goto RESTART; | |
| } | |
| // | |
| // Copy the DHCP message to a continuous memory block | |
| // | |
| Len = sizeof (EFI_DHCP4_PACKET) + UdpPacket->TotalSize - sizeof (EFI_DHCP4_HEADER); | |
| Packet = (EFI_DHCP4_PACKET *) AllocatePool (Len); | |
| if (Packet == NULL) { | |
| goto RESTART; | |
| } | |
| Packet->Size = Len; | |
| Head = &Packet->Dhcp4.Header; | |
| Packet->Length = NetbufCopy (UdpPacket, 0, UdpPacket->TotalSize, (UINT8 *) Head); | |
| if (Packet->Length != UdpPacket->TotalSize) { | |
| goto RESTART; | |
| } | |
| // | |
| // Is this packet the answer to our packet? | |
| // | |
| if ((Head->OpCode != BOOTP_REPLY) || | |
| (NTOHL (Head->Xid) != DhcpSb->Xid) || | |
| (CompareMem (DhcpSb->ClientAddressSendOut, Head->ClientHwAddr, Head->HwAddrLen) != 0)) { | |
| goto RESTART; | |
| } | |
| // | |
| // Validate the options and retrieve the interested options | |
| // | |
| Para = NULL; | |
| if ((Packet->Length > sizeof (EFI_DHCP4_HEADER) + sizeof (UINT32)) && | |
| (Packet->Dhcp4.Magik == DHCP_OPTION_MAGIC) && | |
| EFI_ERROR (DhcpValidateOptions (Packet, &Para))) { | |
| goto RESTART; | |
| } | |
| // | |
| // Call the handler for each state. The handler should return | |
| // EFI_SUCCESS if the process can go on no matter whether the | |
| // packet is ignored or not. If the return is EFI_ERROR, the | |
| // session will be terminated. Packet's ownership is handled | |
| // over to the handlers. If operation succeeds, the handler | |
| // must notify the user. It isn't necessary to do if EFI_ERROR | |
| // is returned because the DhcpEndSession will notify the user. | |
| // | |
| Status = EFI_SUCCESS; | |
| switch (DhcpSb->DhcpState) { | |
| case Dhcp4Selecting: | |
| Status = DhcpHandleSelect (DhcpSb, Packet, Para); | |
| break; | |
| case Dhcp4Requesting: | |
| Status = DhcpHandleRequest (DhcpSb, Packet, Para); | |
| break; | |
| case Dhcp4InitReboot: | |
| case Dhcp4Init: | |
| case Dhcp4Bound: | |
| // | |
| // Ignore the packet in INITREBOOT, INIT and BOUND states | |
| // | |
| FreePool (Packet); | |
| Status = EFI_SUCCESS; | |
| break; | |
| case Dhcp4Renewing: | |
| case Dhcp4Rebinding: | |
| Status = DhcpHandleRenewRebind (DhcpSb, Packet, Para); | |
| break; | |
| case Dhcp4Rebooting: | |
| Status = DhcpHandleReboot (DhcpSb, Packet, Para); | |
| break; | |
| } | |
| if (Para != NULL) { | |
| FreePool (Para); | |
| } | |
| Packet = NULL; | |
| if (EFI_ERROR (Status)) { | |
| NetbufFree (UdpPacket); | |
| UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0); | |
| DhcpEndSession (DhcpSb, Status); | |
| return ; | |
| } | |
| RESTART: | |
| NetbufFree (UdpPacket); | |
| if (Packet != NULL) { | |
| FreePool (Packet); | |
| } | |
| Status = UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0); | |
| if (EFI_ERROR (Status)) { | |
| DhcpEndSession (DhcpSb, EFI_DEVICE_ERROR); | |
| } | |
| } | |
| /** | |
| Release the packet. | |
| @param[in] Arg The packet to release | |
| **/ | |
| VOID | |
| EFIAPI | |
| DhcpReleasePacket ( | |
| IN VOID *Arg | |
| ) | |
| { | |
| FreePool (Arg); | |
| } | |
| /** | |
| Release the net buffer when packet is sent. | |
| @param UdpPacket The UDP packets received. | |
| @param EndPoint The local/remote UDP access point | |
| @param IoStatus The status of the UDP receive | |
| @param Context The opaque parameter to the function. | |
| **/ | |
| VOID | |
| EFIAPI | |
| DhcpOnPacketSent ( | |
| NET_BUF *Packet, | |
| UDP_END_POINT *EndPoint, | |
| EFI_STATUS IoStatus, | |
| VOID *Context | |
| ) | |
| { | |
| NetbufFree (Packet); | |
| } | |
| /** | |
| Build and transmit a DHCP message according to the current states. | |
| This function implement the Table 5. of RFC 2131. Always transits | |
| the state (as defined in Figure 5. of the same RFC) before sending | |
| a DHCP message. The table is adjusted accordingly. | |
| @param[in] DhcpSb The DHCP service instance | |
| @param[in] Seed The seed packet which the new packet is based on | |
| @param[in] Para The DHCP parameter of the Seed packet | |
| @param[in] Type The message type to send | |
| @param[in] Msg The human readable message to include in the packet | |
| sent. | |
| @retval EFI_OUT_OF_RESOURCES Failed to allocate resources for the packet | |
| @retval EFI_ACCESS_DENIED Failed to transmit the packet through UDP | |
| @retval EFI_SUCCESS The message is sent | |
| @retval other Other error occurs | |
| **/ | |
| EFI_STATUS | |
| DhcpSendMessage ( | |
| IN DHCP_SERVICE *DhcpSb, | |
| IN EFI_DHCP4_PACKET *Seed, | |
| IN DHCP_PARAMETER *Para, | |
| IN UINT8 Type, | |
| IN UINT8 *Msg | |
| ) | |
| { | |
| EFI_DHCP4_CONFIG_DATA *Config; | |
| EFI_DHCP4_PACKET *Packet; | |
| EFI_DHCP4_PACKET *NewPacket; | |
| EFI_DHCP4_HEADER *Head; | |
| EFI_DHCP4_HEADER *SeedHead; | |
| UDP_IO *UdpIo; | |
| UDP_END_POINT EndPoint; | |
| NET_BUF *Wrap; | |
| NET_FRAGMENT Frag; | |
| EFI_STATUS Status; | |
| IP4_ADDR IpAddr; | |
| UINT8 *Buf; | |
| UINT16 MaxMsg; | |
| UINT32 Len; | |
| UINT32 Index; | |
| // | |
| // Allocate a big enough memory block to hold the DHCP packet | |
| // | |
| Len = sizeof (EFI_DHCP4_PACKET) + 128 + DhcpSb->UserOptionLen; | |
| if (Msg != NULL) { | |
| Len += (UINT32)AsciiStrLen ((CHAR8 *) Msg); | |
| } | |
| Packet = AllocatePool (Len); | |
| if (Packet == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| Packet->Size = Len; | |
| Packet->Length = sizeof (EFI_DHCP4_HEADER) + sizeof (UINT32); | |
| // | |
| // Fill in the DHCP header fields | |
| // | |
| Config = &DhcpSb->ActiveConfig; | |
| SeedHead = NULL; | |
| if (Seed != NULL) { | |
| SeedHead = &Seed->Dhcp4.Header; | |
| } | |
| Head = &Packet->Dhcp4.Header; | |
| ZeroMem (Head, sizeof (EFI_DHCP4_HEADER)); | |
| Head->OpCode = BOOTP_REQUEST; | |
| Head->HwType = DhcpSb->HwType; | |
| Head->HwAddrLen = DhcpSb->HwLen; | |
| Head->Xid = HTONL (DhcpSb->Xid); | |
| Head->Reserved = HTONS (0x8000); //Server, broadcast the message please. | |
| EFI_IP4 (Head->ClientAddr) = HTONL (DhcpSb->ClientAddr); | |
| CopyMem (Head->ClientHwAddr, DhcpSb->Mac.Addr, DhcpSb->HwLen); | |
| // | |
| // Append the DHCP message type | |
| // | |
| Packet->Dhcp4.Magik = DHCP_OPTION_MAGIC; | |
| Buf = Packet->Dhcp4.Option; | |
| Buf = DhcpAppendOption (Buf, DHCP_TAG_TYPE, 1, &Type); | |
| // | |
| // Append the serverid option if necessary: | |
| // 1. DHCP decline message | |
| // 2. DHCP release message | |
| // 3. DHCP request to confirm one lease. | |
| // | |
| if ((Type == DHCP_MSG_DECLINE) || (Type == DHCP_MSG_RELEASE) || | |
| ((Type == DHCP_MSG_REQUEST) && (DhcpSb->DhcpState == Dhcp4Requesting)) | |
| ) { | |
| ASSERT ((Para != NULL) && (Para->ServerId != 0)); | |
| IpAddr = HTONL (Para->ServerId); | |
| Buf = DhcpAppendOption (Buf, DHCP_TAG_SERVER_ID, 4, (UINT8 *) &IpAddr); | |
| } | |
| // | |
| // Append the requested IP option if necessary: | |
| // 1. DHCP request to use the previously allocated address | |
| // 2. DHCP request to confirm one lease | |
| // 3. DHCP decline to decline one lease | |
| // | |
| IpAddr = 0; | |
| if (Type == DHCP_MSG_REQUEST) { | |
| if (DhcpSb->DhcpState == Dhcp4Rebooting) { | |
| IpAddr = EFI_IP4 (Config->ClientAddress); | |
| } else if (DhcpSb->DhcpState == Dhcp4Requesting) { | |
| ASSERT (SeedHead != NULL); | |
| IpAddr = EFI_IP4 (SeedHead->YourAddr); | |
| } | |
| } else if (Type == DHCP_MSG_DECLINE) { | |
| ASSERT (SeedHead != NULL); | |
| IpAddr = EFI_IP4 (SeedHead->YourAddr); | |
| } | |
| if (IpAddr != 0) { | |
| Buf = DhcpAppendOption (Buf, DHCP_TAG_REQUEST_IP, 4, (UINT8 *) &IpAddr); | |
| } | |
| // | |
| // Append the Max Message Length option if it isn't a DECLINE | |
| // or RELEASE to direct the server use large messages instead of | |
| // override the BOOTFILE and SERVER fields in the message head. | |
| // | |
| if ((Type != DHCP_MSG_DECLINE) && (Type != DHCP_MSG_RELEASE)) { | |
| MaxMsg = HTONS (0xFF00); | |
| Buf = DhcpAppendOption (Buf, DHCP_TAG_MAXMSG, 2, (UINT8 *) &MaxMsg); | |
| } | |
| // | |
| // Append the user's message if it isn't NULL | |
| // | |
| if (Msg != NULL) { | |
| Len = MIN ((UINT32) AsciiStrLen ((CHAR8 *) Msg), 255); | |
| Buf = DhcpAppendOption (Buf, DHCP_TAG_MESSAGE, (UINT16) Len, Msg); | |
| } | |
| // | |
| // Append the user configured options | |
| // | |
| if (DhcpSb->UserOptionLen != 0) { | |
| for (Index = 0; Index < Config->OptionCount; Index++) { | |
| // | |
| // We can't use any option other than the client ID from user | |
| // if it is a DHCP decline or DHCP release . | |
| // | |
| if (((Type == DHCP_MSG_DECLINE) || (Type == DHCP_MSG_RELEASE)) && | |
| (Config->OptionList[Index]->OpCode != DHCP_TAG_CLIENT_ID)) { | |
| continue; | |
| } | |
| Buf = DhcpAppendOption ( | |
| Buf, | |
| Config->OptionList[Index]->OpCode, | |
| Config->OptionList[Index]->Length, | |
| Config->OptionList[Index]->Data | |
| ); | |
| } | |
| } | |
| *(Buf++) = DHCP_TAG_EOP; | |
| Packet->Length += (UINT32) (Buf - Packet->Dhcp4.Option); | |
| // | |
| // OK, the message is built, call the user to override it. | |
| // | |
| Status = EFI_SUCCESS; | |
| NewPacket = NULL; | |
| if (Type == DHCP_MSG_DISCOVER) { | |
| Status = DhcpCallUser (DhcpSb, Dhcp4SendDiscover, Packet, &NewPacket); | |
| } else if (Type == DHCP_MSG_REQUEST) { | |
| Status = DhcpCallUser (DhcpSb, Dhcp4SendRequest, Packet, &NewPacket); | |
| } else if (Type == DHCP_MSG_DECLINE) { | |
| Status = DhcpCallUser (DhcpSb, Dhcp4SendDecline, Packet, &NewPacket); | |
| } | |
| if (EFI_ERROR (Status)) { | |
| FreePool (Packet); | |
| return Status; | |
| } | |
| if (NewPacket != NULL) { | |
| FreePool (Packet); | |
| Packet = NewPacket; | |
| } | |
| // | |
| // Save the Client Address will be sent out | |
| // | |
| CopyMem ( | |
| &DhcpSb->ClientAddressSendOut[0], | |
| &Packet->Dhcp4.Header.ClientHwAddr[0], | |
| Packet->Dhcp4.Header.HwAddrLen | |
| ); | |
| // | |
| // Wrap it into a netbuf then send it. | |
| // | |
| Frag.Bulk = (UINT8 *) &Packet->Dhcp4.Header; | |
| Frag.Len = Packet->Length; | |
| Wrap = NetbufFromExt (&Frag, 1, 0, 0, DhcpReleasePacket, Packet); | |
| if (Wrap == NULL) { | |
| FreePool (Packet); | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Save it as the last sent packet for retransmission | |
| // | |
| if (DhcpSb->LastPacket != NULL) { | |
| FreePool (DhcpSb->LastPacket); | |
| } | |
| DhcpSb->LastPacket = Packet; | |
| DhcpSetTransmitTimer (DhcpSb); | |
| // | |
| // Broadcast the message, unless we know the server address. | |
| // Use the lease UdpIo port to send the unicast packet. | |
| // | |
| EndPoint.RemoteAddr.Addr[0] = 0xffffffff; | |
| EndPoint.LocalAddr.Addr[0] = 0; | |
| EndPoint.RemotePort = DHCP_SERVER_PORT; | |
| EndPoint.LocalPort = DHCP_CLIENT_PORT; | |
| UdpIo = DhcpSb->UdpIo; | |
| if ((DhcpSb->DhcpState == Dhcp4Renewing) || (Type == DHCP_MSG_RELEASE)) { | |
| EndPoint.RemoteAddr.Addr[0] = DhcpSb->ServerAddr; | |
| EndPoint.LocalAddr.Addr[0] = DhcpSb->ClientAddr; | |
| UdpIo = DhcpSb->LeaseIoPort; | |
| } | |
| ASSERT (UdpIo != NULL); | |
| NET_GET_REF (Wrap); | |
| Status = UdpIoSendDatagram ( | |
| UdpIo, | |
| Wrap, | |
| &EndPoint, | |
| NULL, | |
| DhcpOnPacketSent, | |
| DhcpSb | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| NET_PUT_REF (Wrap); | |
| return EFI_ACCESS_DENIED; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Retransmit a saved packet. Only DISCOVER and REQUEST messages | |
| will be retransmitted. | |
| @param[in] DhcpSb The DHCP service instance | |
| @retval EFI_ACCESS_DENIED Failed to transmit packet through UDP port | |
| @retval EFI_SUCCESS The packet is retransmitted. | |
| **/ | |
| EFI_STATUS | |
| DhcpRetransmit ( | |
| IN DHCP_SERVICE *DhcpSb | |
| ) | |
| { | |
| UDP_IO *UdpIo; | |
| UDP_END_POINT EndPoint; | |
| NET_BUF *Wrap; | |
| NET_FRAGMENT Frag; | |
| EFI_STATUS Status; | |
| ASSERT (DhcpSb->LastPacket != NULL); | |
| DhcpSb->LastPacket->Dhcp4.Header.Seconds = HTONS (*(UINT16 *)(&DhcpSb->LastTimeout)); | |
| // | |
| // Wrap it into a netbuf then send it. | |
| // | |
| Frag.Bulk = (UINT8 *) &DhcpSb->LastPacket->Dhcp4.Header; | |
| Frag.Len = DhcpSb->LastPacket->Length; | |
| Wrap = NetbufFromExt (&Frag, 1, 0, 0, DhcpReleasePacket, DhcpSb->LastPacket); | |
| if (Wrap == NULL) { | |
| return EFI_OUT_OF_RESOURCES; | |
| } | |
| // | |
| // Broadcast the message, unless we know the server address. | |
| // | |
| EndPoint.RemotePort = DHCP_SERVER_PORT; | |
| EndPoint.LocalPort = DHCP_CLIENT_PORT; | |
| EndPoint.RemoteAddr.Addr[0] = 0xffffffff; | |
| EndPoint.LocalAddr.Addr[0] = 0; | |
| UdpIo = DhcpSb->UdpIo; | |
| if (DhcpSb->DhcpState == Dhcp4Renewing) { | |
| EndPoint.RemoteAddr.Addr[0] = DhcpSb->ServerAddr; | |
| EndPoint.LocalAddr.Addr[0] = DhcpSb->ClientAddr; | |
| UdpIo = DhcpSb->LeaseIoPort; | |
| } | |
| ASSERT (UdpIo != NULL); | |
| NET_GET_REF (Wrap); | |
| Status = UdpIoSendDatagram ( | |
| UdpIo, | |
| Wrap, | |
| &EndPoint, | |
| NULL, | |
| DhcpOnPacketSent, | |
| DhcpSb | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| NET_PUT_REF (Wrap); | |
| return EFI_ACCESS_DENIED; | |
| } | |
| return EFI_SUCCESS; | |
| } | |
| /** | |
| Each DHCP service has three timer. Two of them are count down timer. | |
| One for the packet retransmission. The other is to collect the offers. | |
| The third timer increaments the lease life which is compared to T1, T2, | |
| and lease to determine the time to renew and rebind the lease. | |
| DhcpOnTimerTick will be called once every second. | |
| @param[in] Event The timer event | |
| @param[in] Context The context, which is the DHCP service instance. | |
| **/ | |
| VOID | |
| EFIAPI | |
| DhcpOnTimerTick ( | |
| IN EFI_EVENT Event, | |
| IN VOID *Context | |
| ) | |
| { | |
| DHCP_SERVICE *DhcpSb; | |
| DHCP_PROTOCOL *Instance; | |
| EFI_STATUS Status; | |
| DhcpSb = (DHCP_SERVICE *) Context; | |
| Instance = DhcpSb->ActiveChild; | |
| // | |
| // Check the retransmit timer | |
| // | |
| if ((DhcpSb->PacketToLive > 0) && (--DhcpSb->PacketToLive == 0)) { | |
| // | |
| // Select offer at each timeout if any offer received. | |
| // | |
| if (DhcpSb->DhcpState == Dhcp4Selecting && DhcpSb->LastOffer != NULL) { | |
| Status = DhcpChooseOffer (DhcpSb); | |
| if (EFI_ERROR(Status)) { | |
| if (DhcpSb->LastOffer != NULL) { | |
| FreePool (DhcpSb->LastOffer); | |
| DhcpSb->LastOffer = NULL; | |
| } | |
| } else { | |
| goto ON_EXIT; | |
| } | |
| } | |
| if (++DhcpSb->CurRetry < DhcpSb->MaxRetries) { | |
| // | |
| // Still has another try | |
| // | |
| DhcpRetransmit (DhcpSb); | |
| DhcpSetTransmitTimer (DhcpSb); | |
| } else if (DHCP_CONNECTED (DhcpSb->DhcpState)) { | |
| // | |
| // Retransmission failed, if the DHCP request is initiated by | |
| // user, adjust the current state according to the lease life. | |
| // Otherwise do nothing to wait the lease to timeout | |
| // | |
| if (DhcpSb->ExtraRefresh != 0) { | |
| Status = EFI_SUCCESS; | |
| if (DhcpSb->LeaseLife < DhcpSb->T1) { | |
| Status = DhcpSetState (DhcpSb, Dhcp4Bound, FALSE); | |
| } else if (DhcpSb->LeaseLife < DhcpSb->T2) { | |
| Status = DhcpSetState (DhcpSb, Dhcp4Renewing, FALSE); | |
| } else if (DhcpSb->LeaseLife < DhcpSb->Lease) { | |
| Status = DhcpSetState (DhcpSb, Dhcp4Rebinding, FALSE); | |
| } else { | |
| goto END_SESSION; | |
| } | |
| DhcpSb->IoStatus = EFI_TIMEOUT; | |
| DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_RENEWREBIND); | |
| } | |
| } else { | |
| goto END_SESSION; | |
| } | |
| } | |
| // | |
| // If an address has been acquired, check whether need to | |
| // refresh or whether it has expired. | |
| // | |
| if (DHCP_CONNECTED (DhcpSb->DhcpState)) { | |
| DhcpSb->LeaseLife++; | |
| // | |
| // Don't timeout the lease, only count the life if user is | |
| // requesting extra renew/rebind. Adjust the state after that. | |
| // | |
| if (DhcpSb->ExtraRefresh != 0) { | |
| return ; | |
| } | |
| if (DhcpSb->LeaseLife == DhcpSb->Lease) { | |
| // | |
| // Lease expires, end the session | |
| // | |
| goto END_SESSION; | |
| } else if (DhcpSb->LeaseLife == DhcpSb->T2) { | |
| // | |
| // T2 expires, transit to rebinding then send a REQUEST to any server | |
| // | |
| if (EFI_ERROR (DhcpSetState (DhcpSb, Dhcp4Rebinding, TRUE))) { | |
| goto END_SESSION; | |
| } | |
| Status = DhcpSendMessage ( | |
| DhcpSb, | |
| DhcpSb->Selected, | |
| DhcpSb->Para, | |
| DHCP_MSG_REQUEST, | |
| NULL | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto END_SESSION; | |
| } | |
| } else if (DhcpSb->LeaseLife == DhcpSb->T1) { | |
| // | |
| // T1 expires, transit to renewing, then send a REQUEST to the server | |
| // | |
| if (EFI_ERROR (DhcpSetState (DhcpSb, Dhcp4Renewing, TRUE))) { | |
| goto END_SESSION; | |
| } | |
| Status = DhcpSendMessage ( | |
| DhcpSb, | |
| DhcpSb->Selected, | |
| DhcpSb->Para, | |
| DHCP_MSG_REQUEST, | |
| NULL | |
| ); | |
| if (EFI_ERROR (Status)) { | |
| goto END_SESSION; | |
| } | |
| } | |
| } | |
| ON_EXIT: | |
| if ((Instance != NULL) && (Instance->Token != NULL)) { | |
| Instance->Timeout--; | |
| if (Instance->Timeout == 0) { | |
| PxeDhcpDone (Instance); | |
| } | |
| } | |
| return ; | |
| END_SESSION: | |
| DhcpEndSession (DhcpSb, EFI_TIMEOUT); | |
| return ; | |
| } |