Search code examples
uefignu-efi

Send TCP or UDP packets from efi application


I want to develop an application to be automatically executed from startup.nsh in EFI shell. This application should send raw bytes to an ip address and receive some back. I looked everywhere for explanation and example for the implementation of simple network protocol in my code but found nothin. Can someone explain and show a code example using gnu_efi libaries?


Solution

  • Here is a sample how to send and receive UDP packets with EDK2, porting it to gnu-efi should be an easy task, wrap all gBS->, gRT-> and protocolXY calls with the uefi_call_wrapper.

    Change the global values to match your client and server.

    #include <Uefi.h>
    #include <Library\UefiLib.h>
    #include <Protocol\ServiceBinding.h>
    #include <Protocol\Udp4.h>
    #include <Protocol\SimpleNetwork.h>
    #include <Protocol\ManagedNetwork.h>
    #include <Protocol\Ip4.h>
    
    #ifndef LOG
    #define LOG(fmt, ...) AsciiPrint(fmt, __VA_ARGS__)
    #endif
    
    #ifndef TRACE
    #define TRACE(status)   LOG("Status: '%r', Function: '%a', File: '%a', Line: '%d'\r\n", status, __FUNCTION__, __FILE__, __LINE__)
    #endif
    
    static EFI_GUID gEfiUdp4ServiceBindingProtocolGuid = EFI_UDP4_SERVICE_BINDING_PROTOCOL_GUID;
    static EFI_GUID gEfiUdp4ProtocolGuid = EFI_UDP4_PROTOCOL_GUID;
    
    extern EFI_BOOT_SERVICES    *gBS;
    extern EFI_RUNTIME_SERVICES *gRT;
    
    static BOOLEAN gTransmitCompleteFlag = FALSE;
    static BOOLEAN gReceiveCompleteFlag = FALSE;
    
    /*
    Configuration
    */
    static EFI_IPv4_ADDRESS gLocalAddress = { 10, 0, 2, 200 };
    static EFI_IPv4_ADDRESS gSubnetMask = { 255, 255, 255, 0 };
    static UINT16 gLocalPort = 0;
    
    static EFI_IPv4_ADDRESS gRemoteAddress = { 10, 0, 2, 180 };
    static UINT16 gRemotePort = 4444;
    
    
    static VOID 
    EFIAPI 
    TransmitEventCallback(
        IN  EFI_EVENT   Event,
        IN  void        *UserData)
    {
        gTransmitCompleteFlag = TRUE;
    }
    
    static VOID
    EFIAPI
    ReceiveEventCallback(
        IN  EFI_EVENT   Event,
        IN  void        *UserData)
    {
        gReceiveCompleteFlag = TRUE;
    }
    
    static EFI_STATUS
    EFIAPI
    WaitForFlag(
        IN  BOOLEAN             *Flag,
        IN  EFI_UDP4_PROTOCOL   *Udp4Protocol   OPTIONAL,
        IN  UINTN               Timeout)
    {
        EFI_STATUS  Status;
        UINT8       LastSecond = MAX_UINT8;
        UINT8       Timer = 0;
        EFI_TIME    CurrentTime;
    
        while (!*Flag && (Timeout == 0 || Timer < Timeout)) {
            if (Udp4Protocol) {
                Udp4Protocol->Poll(
                    Udp4Protocol);
            }
    
            // use gRT->GetTime to exit this loop
            Status = gRT->GetTime(&CurrentTime, NULL);
    
            if (EFI_ERROR(Status)) {
                TRACE(Status);
                // Error handling
                return Status;
            }
    
            if (LastSecond != CurrentTime.Second) {
                LastSecond = CurrentTime.Second;
                Timer++;
            }
        }
    
        return *Flag ? EFI_SUCCESS : EFI_TIMEOUT;
    }
    
    EFI_STATUS
    EFIAPI
    UefiMain(
        IN EFI_HANDLE        ImageHandle,
        IN EFI_SYSTEM_TABLE  *SystemTable)
    {
        EFI_STATUS                      Status;
        
        EFI_UDP4_CONFIG_DATA            Udp4ConfigData;
    
        EFI_UDP4_COMPLETION_TOKEN       Udp4ReceiveCompletionToken;
        EFI_UDP4_COMPLETION_TOKEN       Udp4TansmitCompletionToken;
        EFI_UDP4_TRANSMIT_DATA          Udp4TransmitData;
    
        EFI_HANDLE                      Udp4ChildHandle = NULL;
    
        EFI_UDP4_PROTOCOL               *Udp4Protocol = NULL;
        EFI_SERVICE_BINDING_PROTOCOL    *Udp4ServiceBindingProtocol = NULL;
    
        CHAR8                           TxBuffer[] = "Hello Server!";
    
    
        /*
        Step 1: Locate the corresponding Service Binding Protocol, if there is more then 1 network interface gBS->LocateHandleBuffer should be used
        */
    
        Status = gBS->LocateProtocol(
            &gEfiUdp4ServiceBindingProtocolGuid,
            NULL,
            &Udp4ServiceBindingProtocol);
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        /*
        Step 2: Create a new UDP4 instance
        */
    
        Status = Udp4ServiceBindingProtocol->CreateChild(
            Udp4ServiceBindingProtocol,
            &Udp4ChildHandle);
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        Status = gBS->HandleProtocol(
            Udp4ChildHandle,
            &gEfiUdp4ProtocolGuid,
            &Udp4Protocol);
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        /*
        Step 3: Prepare the UDP4 instance
        */
    
        Udp4ConfigData.AcceptBroadcast = FALSE;
        Udp4ConfigData.AcceptPromiscuous = FALSE;
        Udp4ConfigData.AcceptAnyPort = FALSE;
        Udp4ConfigData.AllowDuplicatePort = FALSE;
    
        Udp4ConfigData.TimeToLive = 16;
        Udp4ConfigData.TypeOfService = 0;
        Udp4ConfigData.DoNotFragment = TRUE;
        Udp4ConfigData.ReceiveTimeout = 0;
        Udp4ConfigData.TransmitTimeout = 0;
    
        // Change to TRUE and set the following fields to zero if DHCP is used
        Udp4ConfigData.UseDefaultAddress = FALSE;
        gBS->CopyMem(&Udp4ConfigData.StationAddress, &gLocalAddress, sizeof(Udp4ConfigData.StationAddress));
        gBS->CopyMem(&Udp4ConfigData.SubnetMask, &gSubnetMask, sizeof(Udp4ConfigData.SubnetMask));
        Udp4ConfigData.StationPort = gLocalPort;
        gBS->CopyMem(&Udp4ConfigData.RemoteAddress, &gRemoteAddress, sizeof(Udp4ConfigData.RemoteAddress));
        Udp4ConfigData.RemotePort = gRemotePort;
    
        Status = Udp4Protocol->Configure(
            Udp4Protocol,
            &Udp4ConfigData);
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        /*
        Step 4: Send data and wait for completion
        */
    
        Udp4TansmitCompletionToken.Status = EFI_SUCCESS;
        Udp4TansmitCompletionToken.Event = NULL;
    
        Status = gBS->CreateEvent(
                EVT_NOTIFY_SIGNAL,
                TPL_CALLBACK,
                TransmitEventCallback,
                NULL,
                &(Udp4TansmitCompletionToken.Event));
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
            
        Udp4TansmitCompletionToken.Packet.TxData = &Udp4TransmitData;
    
        Udp4TransmitData.UdpSessionData = NULL;
        gBS->SetMem(&Udp4TransmitData.GatewayAddress, sizeof(Udp4TransmitData.GatewayAddress), 0x00);
        Udp4TransmitData.DataLength = sizeof(TxBuffer);
        Udp4TransmitData.FragmentCount = 1;
        Udp4TransmitData.FragmentTable[0].FragmentLength = Udp4TransmitData.DataLength;
        Udp4TransmitData.FragmentTable[0].FragmentBuffer = TxBuffer;
    
        gTransmitCompleteFlag = FALSE;
    
        LOG("Sending data...\r\n");
    
        Status = Udp4Protocol->Transmit(
            Udp4Protocol,
            &Udp4TansmitCompletionToken);
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        Status = WaitForFlag(
            &gTransmitCompleteFlag,
            Udp4Protocol,
            10);
    
        if (EFI_ERROR(Status)) {
            TRACE(EFI_TIMEOUT);
            // Error handling
            return EFI_TIMEOUT;
        }
    
        if (EFI_ERROR(Udp4TansmitCompletionToken.Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        LOG("Data sent.\r\n");
        
        /*
        Step 5: Receive data
        */
        
        Udp4ReceiveCompletionToken.Status = EFI_SUCCESS;
        Udp4ReceiveCompletionToken.Event = NULL;
    
        Status = gBS->CreateEvent(
            EVT_NOTIFY_SIGNAL,
            TPL_CALLBACK,
            ReceiveEventCallback,
            NULL,
            &(Udp4ReceiveCompletionToken.Event));
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        Udp4ReceiveCompletionToken.Packet.RxData = NULL;
    
        gReceiveCompleteFlag = FALSE;
    
        LOG("Receiving data...\r\n");
    
        Status = Udp4Protocol->Receive(
            Udp4Protocol,
            &Udp4ReceiveCompletionToken);
    
        if (EFI_ERROR(Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        Status = WaitForFlag(
            &gReceiveCompleteFlag,
            Udp4Protocol,
            10);
    
        if (EFI_ERROR(Status)) {
            TRACE(EFI_TIMEOUT);
            // Error handling
            return EFI_TIMEOUT;
        }
    
        if (EFI_ERROR(Udp4ReceiveCompletionToken.Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
        
        /*
        Step 6: Process received data
        */
        
        if (
            Udp4ReceiveCompletionToken.Packet.RxData &&
            Udp4ReceiveCompletionToken.Packet.RxData->FragmentCount > 0 &&
            Udp4ReceiveCompletionToken.Packet.RxData->DataLength > 0) {
    
            LOG("Received '%a'.\r\n", 
                Udp4ReceiveCompletionToken.Packet.RxData->FragmentTable[0].FragmentBuffer);
        }
        else {
            LOG("Received an empty package.\r\n");
        }
        
        /*
        Step 7: Cleanup
        */
    
        if (
            Udp4ReceiveCompletionToken.Packet.RxData &&
            Udp4ReceiveCompletionToken.Packet.RxData->RecycleSignal) {
    
            Status = gBS->SignalEvent(Udp4ReceiveCompletionToken.Packet.RxData->RecycleSignal);
    
            if (EFI_ERROR(Udp4ReceiveCompletionToken.Status)) {
                TRACE(Status);
                // Error handling
                return Status;
            }
        }
    
        Status = Udp4ServiceBindingProtocol->DestroyChild(
            Udp4ServiceBindingProtocol,
            Udp4ChildHandle);
    
        if (EFI_ERROR(Udp4ReceiveCompletionToken.Status)) {
            TRACE(Status);
            // Error handling
            return Status;
        }
    
        return EFI_SUCCESS;
    }