From 772db4bb33ae66fa20e39f786b5f80d107d450a5 Mon Sep 17 00:00:00 2001 From: vanjeff Date: Mon, 30 Jul 2007 02:37:10 +0000 Subject: Import ArpDxe, Dhcp4Dxe, Ip4Dxe, Mtftp4Dxe, PxeBcDxe and PxeDhcp4Dxe. git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@3492 6f19259b-4bc3-4df7-8a09-765794883524 --- MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Io.c | 1631 +++++++++++++++++++++ 1 file changed, 1631 insertions(+) create mode 100644 MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Io.c (limited to 'MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Io.c') diff --git a/MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Io.c b/MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Io.c new file mode 100644 index 0000000000..e0fdcd382c --- /dev/null +++ b/MdeModulePkg/Universal/Network/Dhcp4Dxe/Dhcp4Io.c @@ -0,0 +1,1631 @@ +/** @file + +Copyright (c) 2006 - 2007, Intel Corporation +All rights reserved. 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. + +Module Name: + + Dhcp4Io.c + +Abstract: + + EFI DHCP protocol implementation + + +**/ + + +#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 DhcpSb The DHCP service instance + + @retval EFI_SUCCESS The request has been sent + +**/ +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; + } + + DhcpSb->WaitOffer = DHCP_WAIT_OFFER; + } 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 DhcpSb The DHCP service instance + @param Event The event as defined in the spec + @param Packet The current packet trigger the event + @param 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. + +**/ +STATIC +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, + 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 + + @return None + +**/ +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 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 == Dhcp4Init) { + 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->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. + + @return None + +**/ +STATIC +VOID +DhcpSetTransmitTimer ( + IN DHCP_SERVICE *DhcpSb + ) +{ + UINT32 *Times; + + ASSERT (DhcpSb->MaxRetries > DhcpSb->CurRetry); + + if (DhcpSb->DhcpState == Dhcp4Init) { + Times = DhcpSb->ActiveConfig.DiscoverTimeout; + } else { + Times = DhcpSb->ActiveConfig.RequestTimeout; + } + + if (Times == NULL) { + Times = mDhcp4DefaultTimeout; + } + + DhcpSb->PacketToLive = Times[DhcpSb->CurRetry]; +} + + +/** + 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. + + @return None + +**/ +STATIC +VOID +DhcpComputeLease ( + IN 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 UdpIo The UDP IO port to configure + @param Context The opaque parameter to the function. + + @retval EFI_SUCCESS The UDP IO port is successfully configured. + @retval Others It failed to configure the port. + +**/ +EFI_STATUS +DhcpConfigLeaseIoPort ( + IN UDP_IO_PORT *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); + NetCopyMem (&UdpConfigData.StationAddress, &Ip, sizeof (EFI_IPv4_ADDRESS)); + + Ip = HTONL (DhcpSb->Netmask); + NetCopyMem (&UdpConfigData.SubnetMask, &Ip, sizeof (EFI_IPv4_ADDRESS)); + + NetZeroMem (&UdpConfigData.RemoteAddress, sizeof (EFI_IPv4_ADDRESS)); + + Status = UdpIo->Udp->Configure (UdpIo->Udp, &UdpConfigData); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Add a default route if received from the server. + // + if ((DhcpSb->Para != NULL) && (DhcpSb->Para->Router != 0)) { + NetZeroMem (&Subnet, sizeof (EFI_IPv4_ADDRESS)); + + Ip = HTONL (DhcpSb->Para->Router); + NetCopyMem (&Gateway, &Ip, sizeof (EFI_IPv4_ADDRESS)); + + UdpIo->Udp->Routes (UdpIo->Udp, 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. + +**/ +STATIC +EFI_STATUS +DhcpLeaseAcquired ( + IN 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 = mIp4AllMasks[Class << 3]; + } + + if (DhcpSb->LeaseIoPort != NULL) { + UdpIoFreePort (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 = UdpIoCreatePort ( + DhcpSb->Controller, + DhcpSb->Image, + DhcpConfigLeaseIoPort, + 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. + + @return None + +**/ +VOID +DhcpCleanLease ( + IN DHCP_SERVICE *DhcpSb + ) +{ + DhcpSb->DhcpState = Dhcp4Init; + DhcpSb->Xid = DhcpSb->Xid + 1; + DhcpSb->ClientAddr = 0; + DhcpSb->ServerAddr = 0; + + if (DhcpSb->LastOffer != NULL) { + NetFreePool (DhcpSb->LastOffer); + DhcpSb->LastOffer = NULL; + } + + if (DhcpSb->Selected != NULL) { + NetFreePool (DhcpSb->Selected); + DhcpSb->Selected = NULL; + } + + if (DhcpSb->Para != NULL) { + NetFreePool (DhcpSb->Para); + DhcpSb->Para = NULL; + } + + DhcpSb->Lease = 0; + DhcpSb->T1 = 0; + DhcpSb->T2 = 0; + DhcpSb->ExtraRefresh = FALSE; + + if (DhcpSb->LeaseIoPort != NULL) { + UdpIoFreePort (DhcpSb->LeaseIoPort); + DhcpSb->LeaseIoPort = NULL; + } + + if (DhcpSb->LastPacket != NULL) { + NetbufFree (DhcpSb->LastPacket); + DhcpSb->LastPacket = NULL; + } + + DhcpSb->PacketToLive = 0; + DhcpSb->CurRetry = 0; + DhcpSb->MaxRetries = 0; + DhcpSb->WaitOffer = 0; + DhcpSb->LeaseLife = 0; +} + + +/** + 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 DhcpSb The DHCP service instance. + + @retval EFI_SUCCESS One of the offer is selected. + +**/ +STATIC +EFI_STATUS +DhcpChooseOffer ( + IN DHCP_SERVICE *DhcpSb + ) +{ + EFI_DHCP4_PACKET *Selected; + EFI_DHCP4_PACKET *NewPacket; + EFI_STATUS Status; + + ASSERT (DhcpSb->LastOffer != NULL); + + // + // Stop waiting more offers + // + DhcpSb->WaitOffer = 0; + + // + // 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) { + if (EFI_ERROR (DhcpValidateOptions (NewPacket, NULL))) { + NetFreePool (NewPacket); + } else { + NetFreePool (Selected); + Selected = NewPacket; + } + } + + 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 DhcpSb The DHCP service instance + @param Status The result of the DHCP process. + + @return None + +**/ +STATIC +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 DhcpSb The DHCP service instance + @param Packet The DHCP packet received + @param 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. + +**/ +STATIC +EFI_STATUS +DhcpHandleSelect ( + IN DHCP_SERVICE *DhcpSb, + IN EFI_DHCP4_PACKET *Packet, + IN DHCP_PARAMETER *Para + ) +{ + EFI_DHCP4_HEADER *Head; + 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. + // + Head = &Packet->Dhcp4.Header; + + if (!Ip4IsUnicast (EFI_NTOHL (Head->YourAddr), (Para == NULL ? 0 : Para->NetMask))) { + goto ON_EXIT; + } + + 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) { + NetFreePool (DhcpSb->LastOffer); + } + + DhcpSb->LastOffer = Packet; + + return DhcpChooseOffer (DhcpSb); + + } else if (Status == EFI_NOT_READY) { + if (DhcpSb->LastOffer != NULL) { + NetFreePool (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: + NetFreePool (Packet); + return Status; +} + + +/** + Handle packets in DHCP request state. + + @param DhcpSb The DHCP service instance + @param Packet The DHCP packet received + @param 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. + +**/ +STATIC +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 = "Lease confirmed isn't the same as that in the offer"; + goto REJECT; + } + + Status = DhcpCallUser (DhcpSb, Dhcp4RcvdAck, Packet, NULL); + + if (EFI_ERROR (Status)) { + Message = "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 = "Lease is denied upon entering bound"; + goto REJECT; + } + + DhcpSb->IoStatus = EFI_SUCCESS; + DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_COMPLETION); + + NetFreePool (Packet); + return EFI_SUCCESS; + +REJECT: + DhcpSendMessage (DhcpSb, DhcpSb->Selected, DhcpSb->Para, DHCP_MSG_DECLINE, Message); + +ON_EXIT: + NetFreePool (Packet); + return Status; +} + + +/** + Handle packets in DHCP renew/rebound state. + + @param DhcpSb The DHCP service instance + @param Packet The DHCP packet received + @param 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. + +**/ +STATIC +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) { + DhcpSb->ExtraRefresh = FALSE; + + DhcpSb->IoStatus = EFI_SUCCESS; + DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_RENEWREBIND); + } + +ON_EXIT: + NetFreePool (Packet); + return Status; +} + + +/** + Handle packets in DHCP reboot state. + + @param DhcpSb The DHCP service instance + @param Packet The DHCP packet received + @param 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. + +**/ +STATIC +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 = NetAllocatePool (sizeof (DHCP_PARAMETER)); + + if (DhcpSb->Para == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + DhcpSb->Selected = Packet; + CopyMem (DhcpSb->Para, Para, sizeof (DHCP_PARAMETER)); + + Status = DhcpLeaseAcquired (DhcpSb); + + if (EFI_ERROR (Status)) { + return Status; + } + + DhcpSb->IoStatus = EFI_SUCCESS; + DhcpNotifyUser (DhcpSb, DHCP_NOTIFY_COMPLETION); + return EFI_SUCCESS; + +ON_EXIT: + NetFreePool (Packet); + return Status; +} + + +/** + Handle the received DHCP packets. This function drivers the DHCP + state machine. + + @param UdpPacket The UDP packets received. + @param Points The local/remote UDP access points + @param IoStatus The status of the UDP receive + @param Context The opaque parameter to the function. + + @return None + +**/ +VOID +DhcpInput ( + NET_BUF *UdpPacket, + UDP_POINTS *Points, + 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 *) NetAllocatePool (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) || + !NET_MAC_EQUAL (&DhcpSb->Mac, Head->ClientHwAddr, DhcpSb->HwLen)) { + 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 + // + NetFreePool (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) { + NetFreePool (Para); + } + + Packet = NULL; + + if (EFI_ERROR (Status)) { + NetbufFree (UdpPacket); + DhcpEndSession (DhcpSb, Status); + return ; + } + +RESTART: + NetbufFree (UdpPacket); + + if (Packet != NULL) { + NetFreePool (Packet); + } + + Status = UdpIoRecvDatagram (DhcpSb->UdpIo, DhcpInput, DhcpSb, 0); + + if (EFI_ERROR (Status)) { + DhcpEndSession (DhcpSb, EFI_DEVICE_ERROR); + } +} + + +/** + Release the packet. + + @param Arg The packet to release + + @return None + +**/ +VOID +DhcpReleasePacket ( + IN VOID *Arg + ) +{ + NetFreePool (Arg); +} + + +/** + Release the net buffer when packet is sent. + + @param UdpPacket The UDP packets received. + @param Points The local/remote UDP access points + @param IoStatus The status of the UDP receive + @param Context The opaque parameter to the function. + + @return None + +**/ +VOID +DhcpOnPacketSent ( + NET_BUF *Packet, + UDP_POINTS *Points, + 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 DhcpSb The DHCP service instance + @param Seed The seed packet which the new packet is based on + @param Para The DHCP parameter of the Seed packet + @param Type The message type to send + @param 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 + +**/ +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_PORT *UdpIo; + UDP_POINTS 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 (Msg); + } + + Packet = NetAllocatePool (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; + NetZeroMem (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); + NetCopyMem (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 = NET_MIN ((UINT32) AsciiStrLen (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)) { + NetFreePool (Packet); + return Status; + } + + if (NewPacket != NULL) { + NetFreePool (Packet); + Packet = NewPacket; + } + + // + // 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) { + NetFreePool (Packet); + return EFI_OUT_OF_RESOURCES; + } + + // + // Save it as the last sent packet for retransmission + // + if (DhcpSb->LastPacket != NULL) { + NetbufFree (DhcpSb->LastPacket); + } + + NET_GET_REF (Wrap); + DhcpSb->LastPacket = Wrap; + DhcpSetTransmitTimer (DhcpSb); + + // + // Broadcast the message, unless we know the server address. + // Use the lease UdpIo port to send the unicast packet. + // + EndPoint.RemoteAddr = 0xffffffff; + EndPoint.LocalAddr = 0; + EndPoint.RemotePort = DHCP_SERVER_PORT; + EndPoint.LocalPort = DHCP_CLIENT_PORT; + UdpIo = DhcpSb->UdpIo; + + if ((DhcpSb->DhcpState == Dhcp4Renewing) || (Type == DHCP_MSG_RELEASE)) { + EndPoint.RemoteAddr = DhcpSb->ServerAddr; + EndPoint.LocalAddr = DhcpSb->ClientAddr; + UdpIo = DhcpSb->LeaseIoPort; + } + + ASSERT (UdpIo != NULL); + Status = UdpIoSendDatagram (UdpIo, Wrap, &EndPoint, 0, DhcpOnPacketSent, DhcpSb); + + if (EFI_ERROR (Status)) { + NetbufFree (Wrap); + return EFI_ACCESS_DENIED; + } + + return EFI_SUCCESS; +} + + +/** + Retransmit a saved packet. Only DISCOVER and REQUEST messages + will be retransmitted. + + @param 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_PORT *UdpIo; + UDP_POINTS EndPoint; + EFI_STATUS Status; + + ASSERT (DhcpSb->LastPacket != NULL); + + // + // Broadcast the message, unless we know the server address. + // + EndPoint.RemotePort = DHCP_SERVER_PORT; + EndPoint.LocalPort = DHCP_CLIENT_PORT; + EndPoint.RemoteAddr = 0xffffffff; + EndPoint.LocalAddr = 0; + UdpIo = DhcpSb->UdpIo; + + if (DhcpSb->DhcpState == Dhcp4Renewing) { + EndPoint.RemoteAddr = DhcpSb->ServerAddr; + EndPoint.LocalAddr = DhcpSb->ClientAddr; + UdpIo = DhcpSb->LeaseIoPort; + } + + ASSERT (UdpIo != NULL); + + NET_GET_REF (DhcpSb->LastPacket); + Status = UdpIoSendDatagram ( + UdpIo, + DhcpSb->LastPacket, + &EndPoint, + 0, + DhcpOnPacketSent, + DhcpSb + ); + + if (EFI_ERROR (Status)) { + NET_PUT_REF (DhcpSb->LastPacket); + 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 Event The timer event + @param Context The context, which is the DHCP service instance. + + @return None + +**/ +VOID +EFIAPI +DhcpOnTimerTick ( + IN EFI_EVENT Event, + IN VOID *Context + ) +{ + DHCP_SERVICE *DhcpSb; + EFI_STATUS Status; + + DhcpSb = (DHCP_SERVICE *) Context; + + // + // Check the retransmit timer first + // + if ((DhcpSb->PacketToLive > 0) && (--DhcpSb->PacketToLive == 0)) { + + if (++DhcpSb->CurRetry < DhcpSb->MaxRetries) { + // + // Still has another try + // + DhcpRetransmit (DhcpSb); + DhcpSetTransmitTimer (DhcpSb); + + } else { + if (!DHCP_CONNECTED (DhcpSb->DhcpState)) { + goto END_SESSION; + } + + // + // 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) { + 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); + } + } + } + + if ((DhcpSb->WaitOffer > 0) && (--DhcpSb->WaitOffer == 0)) { + // + // OK, offer collection finished, select a offer + // + ASSERT (DhcpSb->DhcpState == Dhcp4Selecting); + + if (DhcpSb->LastOffer == NULL) { + goto END_SESSION; + } + + if (EFI_ERROR (DhcpChooseOffer (DhcpSb))) { + 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) { + 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; + } + } + } + + return ; + +END_SESSION: + DhcpEndSession (DhcpSb, EFI_TIMEOUT); + + return ; +} -- cgit v1.2.3