diff options
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/NetworkPkg/Mtftp4Dxe/Mtftp4Rrq.c')
-rw-r--r-- | src/VBox/Devices/EFI/Firmware/NetworkPkg/Mtftp4Dxe/Mtftp4Rrq.c | 818 |
1 files changed, 818 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/NetworkPkg/Mtftp4Dxe/Mtftp4Rrq.c b/src/VBox/Devices/EFI/Firmware/NetworkPkg/Mtftp4Dxe/Mtftp4Rrq.c new file mode 100644 index 00000000..054070c8 --- /dev/null +++ b/src/VBox/Devices/EFI/Firmware/NetworkPkg/Mtftp4Dxe/Mtftp4Rrq.c @@ -0,0 +1,818 @@ +/** @file + Routines to process Rrq (download). + +(C) Copyright 2014 Hewlett-Packard Development Company, L.P.<BR> +Copyright (c) 2006 - 2018, Intel Corporation. All rights reserved.<BR> +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + + +#include "Mtftp4Impl.h" + + +/** + The packet process callback for MTFTP download. + + @param UdpPacket The packet received + @param EndPoint The local/remote access point of the packet + @param IoStatus The status of the receiving + @param Context Opaque parameter, which is the MTFTP session + +**/ +VOID +EFIAPI +Mtftp4RrqInput ( + IN NET_BUF *UdpPacket, + IN UDP_END_POINT *EndPoint, + IN EFI_STATUS IoStatus, + IN VOID *Context + ); + + +/** + Start the MTFTP session to download. + + It will first initialize some of the internal states then build and send a RRQ + request packet, at last, it will start receive for the downloading. + + @param Instance The Mtftp session + @param Operation The MTFTP opcode, it may be a EFI_MTFTP4_OPCODE_RRQ + or EFI_MTFTP4_OPCODE_DIR. + + @retval EFI_SUCCESS The mtftp download session is started. + @retval Others Failed to start downloading. + +**/ +EFI_STATUS +Mtftp4RrqStart ( + IN MTFTP4_PROTOCOL *Instance, + IN UINT16 Operation + ) +{ + EFI_STATUS Status; + + // + // The valid block number range are [1, 0xffff]. For example: + // the client sends an RRQ request to the server, the server + // transfers the DATA1 block. If option negotiation is ongoing, + // the server will send back an OACK, then client will send ACK0. + // + Status = Mtftp4InitBlockRange (&Instance->Blocks, 1, 0xffff); + + if (EFI_ERROR (Status)) { + return Status; + } + + Status = Mtftp4SendRequest (Instance); + + if (EFI_ERROR (Status)) { + return Status; + } + + return UdpIoRecvDatagram (Instance->UnicastPort, Mtftp4RrqInput, Instance, 0); +} + + +/** + Build and send a ACK packet for the download session. + + @param Instance The Mtftp session + @param BlkNo The BlkNo to ack. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate memory for the packet + @retval EFI_SUCCESS The ACK has been sent + @retval Others Failed to send the ACK. + +**/ +EFI_STATUS +Mtftp4RrqSendAck ( + IN MTFTP4_PROTOCOL *Instance, + IN UINT16 BlkNo + ) +{ + EFI_MTFTP4_PACKET *Ack; + NET_BUF *Packet; + EFI_STATUS Status; + + Status = EFI_SUCCESS; + + Packet = NetbufAlloc (sizeof (EFI_MTFTP4_ACK_HEADER)); + if (Packet == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Ack = (EFI_MTFTP4_PACKET *) NetbufAllocSpace ( + Packet, + sizeof (EFI_MTFTP4_ACK_HEADER), + FALSE + ); + ASSERT (Ack != NULL); + + Ack->Ack.OpCode = HTONS (EFI_MTFTP4_OPCODE_ACK); + Ack->Ack.Block[0] = HTONS (BlkNo); + + Status = Mtftp4SendPacket (Instance, Packet); + if (!EFI_ERROR (Status)) { + Instance->AckedBlock = Instance->TotalBlock; + } + + return Status; +} + + +/** + Deliver the received data block to the user, which can be saved + in the user provide buffer or through the CheckPacket callback. + + @param Instance The Mtftp session + @param Packet The received data packet + @param Len The packet length + + @retval EFI_SUCCESS The data is saved successfully + @retval EFI_ABORTED The user tells to abort by return an error through + CheckPacket + @retval EFI_BUFFER_TOO_SMALL The user's buffer is too small and buffer length is + updated to the actual buffer size needed. + +**/ +EFI_STATUS +Mtftp4RrqSaveBlock ( + IN OUT MTFTP4_PROTOCOL *Instance, + IN EFI_MTFTP4_PACKET *Packet, + IN UINT32 Len + ) +{ + EFI_MTFTP4_TOKEN *Token; + EFI_STATUS Status; + UINT16 Block; + UINT64 Start; + UINT32 DataLen; + UINT64 BlockCounter; + BOOLEAN Completed; + + Completed = FALSE; + Token = Instance->Token; + Block = NTOHS (Packet->Data.Block); + DataLen = Len - MTFTP4_DATA_HEAD_LEN; + + // + // This is the last block, save the block no + // + if (DataLen < Instance->BlkSize) { + Completed = TRUE; + Instance->LastBlock = Block; + Mtftp4SetLastBlockNum (&Instance->Blocks, Block); + } + + // + // Remove this block number from the file hole. If Mtftp4RemoveBlockNum + // returns EFI_NOT_FOUND, the block has been saved, don't save it again. + // Note that : For bigger files, allowing the block counter to roll over + // to accept transfers of unlimited size. So BlockCounter is memorised as + // continuous block counter. + // + Status = Mtftp4RemoveBlockNum (&Instance->Blocks, Block, Completed, &BlockCounter); + + if (Status == EFI_NOT_FOUND) { + return EFI_SUCCESS; + } else if (EFI_ERROR (Status)) { + return Status; + } + + if (Token->CheckPacket != NULL) { + Status = Token->CheckPacket (&Instance->Mtftp4, Token, (UINT16) Len, Packet); + + if (EFI_ERROR (Status)) { + Mtftp4SendError ( + Instance, + EFI_MTFTP4_ERRORCODE_ILLEGAL_OPERATION, + (UINT8 *) "User aborted download" + ); + + return EFI_ABORTED; + } + } + + if (Token->Buffer != NULL) { + Start = MultU64x32 (BlockCounter - 1, Instance->BlkSize); + + if (Start + DataLen <= Token->BufferSize) { + CopyMem ((UINT8 *) Token->Buffer + Start, Packet->Data.Data, DataLen); + + // + // Update the file size when received the last block + // + if ((Instance->LastBlock == Block) && Completed) { + Token->BufferSize = Start + DataLen; + } + + } else if (Instance->LastBlock != 0) { + // + // Don't save the data if the buffer is too small, return + // EFI_BUFFER_TOO_SMALL if received the last packet. This + // will give a accurate file length. + // + Token->BufferSize = Start + DataLen; + + Mtftp4SendError ( + Instance, + EFI_MTFTP4_ERRORCODE_DISK_FULL, + (UINT8 *) "User provided memory block is too small" + ); + + return EFI_BUFFER_TOO_SMALL; + } + } + + return EFI_SUCCESS; +} + + +/** + Function to process the received data packets. + + It will save the block then send back an ACK if it is active. + + @param Instance The downloading MTFTP session + @param Packet The packet received + @param Len The length of the packet + @param Multicast Whether this packet is multicast or unicast + @param Completed Return whether the download has completed + + @retval EFI_SUCCESS The data packet is successfully processed + @retval EFI_ABORTED The download is aborted by the user + @retval EFI_BUFFER_TOO_SMALL The user provided buffer is too small + +**/ +EFI_STATUS +Mtftp4RrqHandleData ( + IN MTFTP4_PROTOCOL *Instance, + IN EFI_MTFTP4_PACKET *Packet, + IN UINT32 Len, + IN BOOLEAN Multicast, + OUT BOOLEAN *Completed + ) +{ + EFI_STATUS Status; + UINT16 BlockNum; + INTN Expected; + + *Completed = FALSE; + Status = EFI_SUCCESS; + BlockNum = NTOHS (Packet->Data.Block); + Expected = Mtftp4GetNextBlockNum (&Instance->Blocks); + + ASSERT (Expected >= 0); + + // + // If we are active (Master) and received an unexpected packet, transmit + // the ACK for the block we received, then restart receiving the + // expected one. If we are passive (Slave), save the block. + // + if (Instance->Master && (Expected != BlockNum)) { + // + // If Expected is 0, (UINT16) (Expected - 1) is also the expected Ack number (65535). + // + return Mtftp4RrqSendAck (Instance, (UINT16) (Expected - 1)); + } + + Status = Mtftp4RrqSaveBlock (Instance, Packet, Len); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Record the total received and saved block number. + // + Instance->TotalBlock ++; + + // + // Reset the passive client's timer whenever it received a + // valid data packet. + // + if (!Instance->Master) { + Mtftp4SetTimeout (Instance); + } + + // + // Check whether we have received all the blocks. Send the ACK if we + // are active (unicast client or master client for multicast download). + // If we have received all the blocks, send an ACK even if we are passive + // to tell the server that we are done. + // + Expected = Mtftp4GetNextBlockNum (&Instance->Blocks); + + if (Instance->Master || (Expected < 0)) { + if (Expected < 0) { + // + // If we are passive client, then the just received Block maybe + // isn't the last block. We need to send an ACK to the last block + // to inform the server that we are done. If we are active client, + // the Block == Instance->LastBlock. + // + BlockNum = Instance->LastBlock; + *Completed = TRUE; + + } else { + BlockNum = (UINT16) (Expected - 1); + } + + if (Instance->WindowSize == (Instance->TotalBlock - Instance->AckedBlock) || Expected < 0) { + Status = Mtftp4RrqSendAck (Instance, BlockNum); + } + + } + + return Status; +} + + +/** + Validate whether the options received in the server's OACK packet is valid. + + The options are valid only if: + 1. The server doesn't include options not requested by us + 2. The server can only use smaller blksize than that is requested + 3. The server can only use the same timeout as requested + 4. The server doesn't change its multicast channel. + + @param This The downloading Mtftp session + @param Reply The options in the OACK packet + @param Request The requested options + + @retval TRUE The options in the OACK is OK. + @retval FALSE The options in the OACK is invalid. + +**/ +BOOLEAN +Mtftp4RrqOackValid ( + IN MTFTP4_PROTOCOL *This, + IN MTFTP4_OPTION *Reply, + IN MTFTP4_OPTION *Request + ) +{ + + // + // It is invalid for server to return options we don't request + // + if ((Reply->Exist &~Request->Exist) != 0) { + return FALSE; + } + + // + // Server can only specify a smaller block size and window size to be used and + // return the timeout matches that requested. + // + if ((((Reply->Exist & MTFTP4_BLKSIZE_EXIST) != 0)&& (Reply->BlkSize > Request->BlkSize)) || + (((Reply->Exist & MTFTP4_WINDOWSIZE_EXIST) != 0)&& (Reply->WindowSize > Request->WindowSize)) || + (((Reply->Exist & MTFTP4_TIMEOUT_EXIST) != 0) && (Reply->Timeout != Request->Timeout)) + ) { + return FALSE; + } + + // + // The server can send ",,master" to client to change its master + // setting. But if it use the specific multicast channel, it can't + // change the setting. + // + if (((Reply->Exist & MTFTP4_MCAST_EXIST) != 0) && (This->McastIp != 0)) { + if ((Reply->McastIp != 0) && (Reply->McastIp != This->McastIp)) { + return FALSE; + } + + if ((Reply->McastPort != 0) && (Reply->McastPort != This->McastPort)) { + return FALSE; + } + } + + return TRUE; +} + + +/** + Configure a UDP IO port to receive the multicast. + + @param McastIo The UDP IO to configure + @param Context The opaque parameter to the function which is the + MTFTP session. + + @retval EFI_SUCCESS The UDP child is successfully configured. + @retval Others Failed to configure the UDP child. + +**/ +EFI_STATUS +EFIAPI +Mtftp4RrqConfigMcastPort ( + IN UDP_IO *McastIo, + IN VOID *Context + ) +{ + MTFTP4_PROTOCOL *Instance; + EFI_MTFTP4_CONFIG_DATA *Config; + EFI_UDP4_CONFIG_DATA UdpConfig; + EFI_IPv4_ADDRESS Group; + EFI_STATUS Status; + IP4_ADDR Ip; + + Instance = (MTFTP4_PROTOCOL *) Context; + Config = &Instance->Config; + + UdpConfig.AcceptBroadcast = FALSE; + UdpConfig.AcceptPromiscuous = FALSE; + UdpConfig.AcceptAnyPort = FALSE; + UdpConfig.AllowDuplicatePort = FALSE; + UdpConfig.TypeOfService = 0; + UdpConfig.TimeToLive = 64; + UdpConfig.DoNotFragment = FALSE; + UdpConfig.ReceiveTimeout = 0; + UdpConfig.TransmitTimeout = 0; + UdpConfig.UseDefaultAddress = Config->UseDefaultSetting; + IP4_COPY_ADDRESS (&UdpConfig.StationAddress, &Config->StationIp); + IP4_COPY_ADDRESS (&UdpConfig.SubnetMask, &Config->SubnetMask); + UdpConfig.StationPort = Instance->McastPort; + UdpConfig.RemotePort = 0; + + Ip = HTONL (Instance->ServerIp); + IP4_COPY_ADDRESS (&UdpConfig.RemoteAddress, &Ip); + + Status = McastIo->Protocol.Udp4->Configure (McastIo->Protocol.Udp4, &UdpConfig); + + if (EFI_ERROR (Status)) { + return Status; + } + + if (!Config->UseDefaultSetting && + !EFI_IP4_EQUAL (&mZeroIp4Addr, &Config->GatewayIp)) { + // + // The station IP address is manually configured and the Gateway IP is not 0. + // Add the default route for this UDP instance. + // + Status = McastIo->Protocol.Udp4->Routes ( + McastIo->Protocol.Udp4, + FALSE, + &mZeroIp4Addr, + &mZeroIp4Addr, + &Config->GatewayIp + ); + + if (EFI_ERROR (Status)) { + McastIo->Protocol.Udp4->Configure (McastIo->Protocol.Udp4, NULL); + return Status; + } + } + + // + // join the multicast group + // + Ip = HTONL (Instance->McastIp); + IP4_COPY_ADDRESS (&Group, &Ip); + + return McastIo->Protocol.Udp4->Groups (McastIo->Protocol.Udp4, TRUE, &Group); +} + + +/** + Function to process the OACK. + + It will first validate the OACK packet, then update the various negotiated parameters. + + @param Instance The download MTFTP session + @param Packet The packet received + @param Len The packet length + @param Multicast Whether this packet is received as a multicast + @param Completed Returns whether the download has completed. NOT + used by this function. + + @retval EFI_DEVICE_ERROR Failed to create/start a multicast UDP child + @retval EFI_TFTP_ERROR Some error happened during the process + @retval EFI_SUCCESS The OACK is successfully processed. + +**/ +EFI_STATUS +Mtftp4RrqHandleOack ( + IN OUT MTFTP4_PROTOCOL *Instance, + IN EFI_MTFTP4_PACKET *Packet, + IN UINT32 Len, + IN BOOLEAN Multicast, + OUT BOOLEAN *Completed + ) +{ + MTFTP4_OPTION Reply; + EFI_STATUS Status; + INTN Expected; + EFI_UDP4_PROTOCOL *Udp4; + + *Completed = FALSE; + + // + // If already started the master download, don't change the + // setting. Master download always succeeds. + // + Expected = Mtftp4GetNextBlockNum (&Instance->Blocks); + ASSERT (Expected != -1); + + if (Instance->Master && (Expected != 1)) { + return EFI_SUCCESS; + } + + // + // Parse and validate the options from server + // + ZeroMem (&Reply, sizeof (MTFTP4_OPTION)); + + Status = Mtftp4ParseOptionOack (Packet, Len, Instance->Operation, &Reply); + + if (EFI_ERROR (Status) || + !Mtftp4RrqOackValid (Instance, &Reply, &Instance->RequestOption)) { + // + // Don't send an ERROR packet if the error is EFI_OUT_OF_RESOURCES. + // + if (Status != EFI_OUT_OF_RESOURCES) { + Mtftp4SendError ( + Instance, + EFI_MTFTP4_ERRORCODE_ILLEGAL_OPERATION, + (UINT8 *) "Malformatted OACK packet" + ); + } + + return EFI_TFTP_ERROR; + } + + if ((Reply.Exist & MTFTP4_MCAST_EXIST) != 0) { + + // + // Save the multicast info. Always update the Master, only update the + // multicast IP address, block size, window size, timeout at the first time. + // If IP address is updated, create a UDP child to receive the multicast. + // + Instance->Master = Reply.Master; + + if (Instance->McastIp == 0) { + if ((Reply.McastIp == 0) || (Reply.McastPort == 0)) { + Mtftp4SendError ( + Instance, + EFI_MTFTP4_ERRORCODE_ILLEGAL_OPERATION, + (UINT8 *) "Illegal multicast setting" + ); + + return EFI_TFTP_ERROR; + } + + // + // Create a UDP child then start receive the multicast from it. + // + Instance->McastIp = Reply.McastIp; + Instance->McastPort = Reply.McastPort; + if (Instance->McastUdpPort == NULL) { + Instance->McastUdpPort = UdpIoCreateIo ( + Instance->Service->Controller, + Instance->Service->Image, + Mtftp4RrqConfigMcastPort, + UDP_IO_UDP4_VERSION, + Instance + ); + if (Instance->McastUdpPort != NULL) { + Status = gBS->OpenProtocol ( + Instance->McastUdpPort->UdpHandle, + &gEfiUdp4ProtocolGuid, + (VOID **) &Udp4, + Instance->Service->Image, + Instance->Handle, + EFI_OPEN_PROTOCOL_BY_CHILD_CONTROLLER + ); + if (EFI_ERROR (Status)) { + UdpIoFreeIo (Instance->McastUdpPort); + Instance->McastUdpPort = NULL; + return EFI_DEVICE_ERROR; + } + } + } + + + if (Instance->McastUdpPort == NULL) { + return EFI_DEVICE_ERROR; + } + + Status = UdpIoRecvDatagram (Instance->McastUdpPort, Mtftp4RrqInput, Instance, 0); + + if (EFI_ERROR (Status)) { + Mtftp4SendError ( + Instance, + EFI_MTFTP4_ERRORCODE_ACCESS_VIOLATION, + (UINT8 *) "Failed to create socket to receive multicast packet" + ); + + return Status; + } + + // + // Update the parameters used. + // + if (Reply.BlkSize != 0) { + Instance->BlkSize = Reply.BlkSize; + } + + if (Reply.WindowSize != 0) { + Instance->WindowSize = Reply.WindowSize; + } + + if (Reply.Timeout != 0) { + Instance->Timeout = Reply.Timeout; + } + } + + } else { + Instance->Master = TRUE; + + if (Reply.BlkSize != 0) { + Instance->BlkSize = Reply.BlkSize; + } + + if (Reply.WindowSize != 0) { + Instance->WindowSize = Reply.WindowSize; + } + + if (Reply.Timeout != 0) { + Instance->Timeout = Reply.Timeout; + } + } + + // + // Send an ACK to (Expected - 1) which is 0 for unicast download, + // or tell the server we want to receive the Expected block. + // + return Mtftp4RrqSendAck (Instance, (UINT16) (Expected - 1)); +} + + +/** + The packet process callback for MTFTP download. + + @param UdpPacket The packet received + @param EndPoint The local/remote access point of the packet + @param IoStatus The status of the receiving + @param Context Opaque parameter, which is the MTFTP session + +**/ +VOID +EFIAPI +Mtftp4RrqInput ( + IN NET_BUF *UdpPacket, + IN UDP_END_POINT *EndPoint, + IN EFI_STATUS IoStatus, + IN VOID *Context + ) +{ + MTFTP4_PROTOCOL *Instance; + EFI_MTFTP4_PACKET *Packet; + BOOLEAN Completed; + BOOLEAN Multicast; + EFI_STATUS Status; + UINT16 Opcode; + UINT32 Len; + + Instance = (MTFTP4_PROTOCOL *) Context; + NET_CHECK_SIGNATURE (Instance, MTFTP4_PROTOCOL_SIGNATURE); + + Status = EFI_SUCCESS; + Packet = NULL; + Completed = FALSE; + Multicast = FALSE; + + if (EFI_ERROR (IoStatus)) { + Status = IoStatus; + goto ON_EXIT; + } + + ASSERT (UdpPacket != NULL); + + // + // Find the port this packet is from to restart receive correctly. + // + Multicast = (BOOLEAN) (EndPoint->LocalAddr.Addr[0] == Instance->McastIp); + + if (UdpPacket->TotalSize < MTFTP4_OPCODE_LEN) { + goto ON_EXIT; + } + + // + // Client send initial request to server's listening port. Server + // will select a UDP port to communicate with the client. The server + // is required to use the same port as RemotePort to multicast the + // data. + // + if (EndPoint->RemotePort != Instance->ConnectedPort) { + if (Instance->ConnectedPort != 0) { + goto ON_EXIT; + } else { + Instance->ConnectedPort = EndPoint->RemotePort; + } + } + + // + // Copy the MTFTP packet to a continuous buffer if it isn't already so. + // + Len = UdpPacket->TotalSize; + + if (UdpPacket->BlockOpNum > 1) { + Packet = AllocatePool (Len); + + if (Packet == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto ON_EXIT; + } + + NetbufCopy (UdpPacket, 0, Len, (UINT8 *) Packet); + + } else { + Packet = (EFI_MTFTP4_PACKET *) NetbufGetByte (UdpPacket, 0, NULL); + ASSERT (Packet != NULL); + } + + Opcode = NTOHS (Packet->OpCode); + + // + // Call the user's CheckPacket if provided. Abort the transmission + // if CheckPacket returns an EFI_ERROR code. + // + if ((Instance->Token->CheckPacket != NULL) && + ((Opcode == EFI_MTFTP4_OPCODE_OACK) || (Opcode == EFI_MTFTP4_OPCODE_ERROR))) { + + Status = Instance->Token->CheckPacket ( + &Instance->Mtftp4, + Instance->Token, + (UINT16) Len, + Packet + ); + + if (EFI_ERROR (Status)) { + // + // Send an error message to the server to inform it + // + if (Opcode != EFI_MTFTP4_OPCODE_ERROR) { + Mtftp4SendError ( + Instance, + EFI_MTFTP4_ERRORCODE_REQUEST_DENIED, + (UINT8 *) "User aborted the transfer" + ); + } + + Status = EFI_ABORTED; + goto ON_EXIT; + } + } + + switch (Opcode) { + case EFI_MTFTP4_OPCODE_DATA: + if ((Len > (UINT32) (MTFTP4_DATA_HEAD_LEN + Instance->BlkSize)) || + (Len < (UINT32) MTFTP4_DATA_HEAD_LEN)) { + goto ON_EXIT; + } + + Status = Mtftp4RrqHandleData (Instance, Packet, Len, Multicast, &Completed); + break; + + case EFI_MTFTP4_OPCODE_OACK: + if (Multicast || (Len <= MTFTP4_OPCODE_LEN)) { + goto ON_EXIT; + } + + Status = Mtftp4RrqHandleOack (Instance, Packet, Len, Multicast, &Completed); + break; + + case EFI_MTFTP4_OPCODE_ERROR: + Status = EFI_TFTP_ERROR; + break; + + default: + break; + } + +ON_EXIT: + + // + // Free the resources, then if !EFI_ERROR (Status), restart the + // receive, otherwise end the session. + // + if ((Packet != NULL) && (UdpPacket->BlockOpNum > 1)) { + FreePool (Packet); + } + + if (UdpPacket != NULL) { + NetbufFree (UdpPacket); + } + + if (!EFI_ERROR (Status) && !Completed) { + if (Multicast) { + Status = UdpIoRecvDatagram (Instance->McastUdpPort, Mtftp4RrqInput, Instance, 0); + } else { + Status = UdpIoRecvDatagram (Instance->UnicastPort, Mtftp4RrqInput, Instance, 0); + } + } + + if (EFI_ERROR (Status) || Completed) { + Mtftp4CleanOperation (Instance, Status); + } +} |