diff options
author | Michael Brown | 2015-09-01 22:23:34 +0200 |
---|---|---|
committer | Michael Brown | 2015-09-02 14:45:12 +0200 |
commit | 3376fa520b0ce4ba3f0d9bad626617acf88908df (patch) | |
tree | c1887ddd09315c71eaf92efacf62976e24ed3359 | |
parent | [uri] Generalise tftp_uri() to pxe_uri() (diff) | |
download | ipxe-3376fa520b0ce4ba3f0d9bad626617acf88908df.tar.gz ipxe-3376fa520b0ce4ba3f0d9bad626617acf88908df.tar.xz ipxe-3376fa520b0ce4ba3f0d9bad626617acf88908df.zip |
[efi] Implement the EFI_PXE_BASE_CODE_PROTOCOL
Many UEFI NBPs expect to find an EFI_PXE_BASE_CODE_PROTOCOL installed
in addition to the EFI_SIMPLE_NETWORK_PROTOCOL. Most NBPs use the
EFI_PXE_BASE_CODE_PROTOCOL only to retrieve the cached DHCP packets.
This implementation has been tested with grub.efi, shim.efi,
syslinux.efi, and wdsmgfw.efi. Some methods (such as Discover() and
Arp()) are not used by any known NBP and so have not (yet) been
implemented.
Usage notes for the tested bootstraps are:
- grub.efi uses EFI_PXE_BASE_CODE_PROTOCOL only to retrieve the
cached DHCP packet, and uses no other methods.
- shim.efi uses EFI_PXE_BASE_CODE_PROTOCOL to retrieve the cached
DHCP packet and to retrieve the next NBP via the Mtftp() method.
If shim.efi was downloaded via HTTP (or other non-TFTP protocol)
then shim.efi will blindly call Mtftp() with an HTTP URI as the
filename: this allows the next NBP (e.g. grubx64.efi) to also be
transparently retrieved by HTTP.
shim.efi can also use the EFI_SIMPLE_FILE_SYSTEM_PROTOCOL to
retrieve files previously loaded by "imgfetch" or similar commands
in iPXE. The current implementation of shim.efi will use the
EFI_SIMPLE_FILE_SYSTEM_PROTOCOL only if it does not find an
EFI_PXE_BASE_CODE_PROTOCOL; this patch therefore prevents this
usage of our EFI_SIMPLE_FILE_SYSTEM_PROTOCOL. This logic could be
trivially reversed in shim.efi if needed.
- syslinux.efi uses EFI_PXE_BASE_CODE_PROTOCOL only to retrieve the
cached DHCP packet. Versions 6.03 and earlier have a bug which
may cause syslinux.efi to attach to the wrong NIC if there are
multiple NICs in the system (or if the UEFI firmware supports
IPv6).
- wdsmgfw.efi (ab)uses EFI_PXE_BASE_CODE_PROTOCOL to retrieve the
cached DHCP packets, and to send and retrieve UDP packets via the
UdpWrite() and UdpRead() methods. (This was presumably done in
order to minimise the amount of benefit obtainable by switching to
UEFI, by replicating all of the design mistakes present in the
original PXE specification.)
The EFI_DOWNGRADE_UX configuration option remains available for now,
until this implementation has received more widespread testing.
Signed-off-by: Michael Brown <mcb30@ipxe.org>
-rw-r--r-- | src/image/efi_image.c | 10 | ||||
-rw-r--r-- | src/include/ipxe/efi/efi_pxe.h | 17 | ||||
-rw-r--r-- | src/include/ipxe/errfile.h | 1 | ||||
-rw-r--r-- | src/interface/efi/efi_pxe.c | 1599 |
4 files changed, 1627 insertions, 0 deletions
diff --git a/src/image/efi_image.c b/src/image/efi_image.c index b7d8f9c6..89d57bbd 100644 --- a/src/image/efi_image.c +++ b/src/image/efi_image.c @@ -29,6 +29,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/efi/efi_utils.h> #include <ipxe/efi/efi_strings.h> #include <ipxe/efi/efi_wrap.h> +#include <ipxe/efi/efi_pxe.h> #include <ipxe/image.h> #include <ipxe/init.h> #include <ipxe/features.h> @@ -159,6 +160,13 @@ static int efi_image_exec ( struct image *image ) { goto err_file_install; } + /* Install PXE base code protocol */ + if ( ( rc = efi_pxe_install ( snpdev->handle, snpdev->netdev ) ) != 0 ){ + DBGC ( image, "EFIIMAGE %p could not install PXE protocol: " + "%s\n", image, strerror ( rc ) ); + goto err_pxe_install; + } + /* Install iPXE download protocol */ if ( ( rc = efi_download_install ( snpdev->handle ) ) != 0 ) { DBGC ( image, "EFIIMAGE %p could not install iPXE download " @@ -266,6 +274,8 @@ static int efi_image_exec ( struct image *image ) { err_image_path: efi_download_uninstall ( snpdev->handle ); err_download_install: + efi_pxe_uninstall ( snpdev->handle ); + err_pxe_install: efi_file_uninstall ( snpdev->handle ); err_file_install: err_no_snpdev: diff --git a/src/include/ipxe/efi/efi_pxe.h b/src/include/ipxe/efi/efi_pxe.h new file mode 100644 index 00000000..b356f378 --- /dev/null +++ b/src/include/ipxe/efi/efi_pxe.h @@ -0,0 +1,17 @@ +#ifndef _IPXE_EFI_PXE_H +#define _IPXE_EFI_PXE_H + +/** @file + * + * EFI PXE base code protocol + */ + +#include <ipxe/efi/efi.h> +#include <ipxe/netdevice.h> + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +extern int efi_pxe_install ( EFI_HANDLE handle, struct net_device *netdev ); +extern void efi_pxe_uninstall ( EFI_HANDLE handle ); + +#endif /* _IPXE_EFI_PXE_H */ diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index e21c9593..00f8f981 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -339,6 +339,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_vmbus ( ERRFILE_OTHER | 0x00470000 ) #define ERRFILE_efi_time ( ERRFILE_OTHER | 0x00480000 ) #define ERRFILE_efi_watchdog ( ERRFILE_OTHER | 0x00490000 ) +#define ERRFILE_efi_pxe ( ERRFILE_OTHER | 0x004a0000 ) /** @} */ diff --git a/src/interface/efi/efi_pxe.c b/src/interface/efi/efi_pxe.c new file mode 100644 index 00000000..1847e3fd --- /dev/null +++ b/src/interface/efi/efi_pxe.c @@ -0,0 +1,1599 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <string.h> +#include <errno.h> +#include <ipxe/refcnt.h> +#include <ipxe/list.h> +#include <ipxe/netdevice.h> +#include <ipxe/fakedhcp.h> +#include <ipxe/process.h> +#include <ipxe/uri.h> +#include <ipxe/in.h> +#include <ipxe/socket.h> +#include <ipxe/tcpip.h> +#include <ipxe/xferbuf.h> +#include <ipxe/open.h> +#include <ipxe/dhcppkt.h> +#include <ipxe/udp.h> +#include <ipxe/efi/efi.h> +#include <ipxe/efi/efi_snp.h> +#include <ipxe/efi/efi_pxe.h> +#include <ipxe/efi/Protocol/PxeBaseCode.h> +#include <usr/ifmgmt.h> +#include <config/general.h> + +/** @file + * + * EFI PXE base code protocol + * + */ + +/* Downgrade user experience if configured to do so + * + * See comments in efi_snp.c + */ +#ifdef EFI_DOWNGRADE_UX +static EFI_GUID dummy_pxe_base_code_protocol_guid = { + 0x70647523, 0x2320, 0x7477, + { 0x66, 0x20, 0x23, 0x6d, 0x6f, 0x72, 0x6f, 0x6e } +}; +#define efi_pxe_base_code_protocol_guid dummy_pxe_base_code_protocol_guid +#endif + +/** A PXE base code */ +struct efi_pxe { + /** Reference count */ + struct refcnt refcnt; + /** Underlying network device */ + struct net_device *netdev; + /** Name */ + const char *name; + /** List of PXE base codes */ + struct list_head list; + + /** Installed handle */ + EFI_HANDLE handle; + /** PXE base code protocol */ + EFI_PXE_BASE_CODE_PROTOCOL base; + /** PXE base code mode */ + EFI_PXE_BASE_CODE_MODE mode; + + /** TCP/IP network-layer protocol */ + struct tcpip_net_protocol *tcpip; + /** Network-layer protocol */ + struct net_protocol *net; + + /** Data transfer buffer */ + struct xfer_buffer buf; + + /** (M)TFTP download interface */ + struct interface tftp; + /** Block size (for TFTP) */ + size_t blksize; + /** Overall return status */ + int rc; + + /** UDP interface */ + struct interface udp; + /** List of received UDP packets */ + struct list_head queue; + /** UDP interface closer process */ + struct process process; +}; + +/** + * Free PXE base code + * + * @v refcnt Reference count + */ +static void efi_pxe_free ( struct refcnt *refcnt ) { + struct efi_pxe *pxe = container_of ( refcnt, struct efi_pxe, refcnt ); + + netdev_put ( pxe->netdev ); + free ( pxe ); +} + +/** List of PXE base codes */ +static LIST_HEAD ( efi_pxes ); + +/** + * Locate PXE base code + * + * @v handle EFI handle + * @ret pxe PXE base code, or NULL + */ +static struct efi_pxe * efi_pxe_find ( EFI_HANDLE handle ) { + struct efi_pxe *pxe; + + /* Locate base code */ + list_for_each_entry ( pxe, &efi_pxes, list ) { + if ( pxe->handle == handle ) + return pxe; + } + + return NULL; +} + +/****************************************************************************** + * + * IP addresses + * + ****************************************************************************** + */ + +/** + * An EFI socket address + * + */ +struct sockaddr_efi { + /** Socket address family (part of struct @c sockaddr) */ + sa_family_t se_family; + /** Flags (part of struct @c sockaddr_tcpip) */ + uint16_t se_flags; + /** TCP/IP port (part of struct @c sockaddr_tcpip) */ + uint16_t se_port; + /** Scope ID (part of struct @c sockaddr_tcpip) + * + * For link-local or multicast addresses, this is the network + * device index. + */ + uint16_t se_scope_id; + /** IP address */ + EFI_IP_ADDRESS se_addr; + /** Padding + * + * This ensures that a struct @c sockaddr_tcpip is large + * enough to hold a socket address for any TCP/IP address + * family. + */ + char pad[ sizeof ( struct sockaddr ) - + ( sizeof ( sa_family_t ) /* se_family */ + + sizeof ( uint16_t ) /* se_flags */ + + sizeof ( uint16_t ) /* se_port */ + + sizeof ( uint16_t ) /* se_scope_id */ + + sizeof ( EFI_IP_ADDRESS ) /* se_addr */ ) ]; +} __attribute__ (( packed, may_alias )); + +/** + * Populate socket address from EFI IP address + * + * @v pxe PXE base code + * @v ip EFI IP address + * @v sa Socket address to fill in + */ +static void efi_pxe_ip_sockaddr ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip, + struct sockaddr *sa ) { + union { + struct sockaddr sa; + struct sockaddr_efi se; + } *sockaddr = container_of ( sa, typeof ( *sockaddr ), sa ); + + /* Initialise socket address */ + memset ( sockaddr, 0, sizeof ( *sockaddr ) ); + sockaddr->sa.sa_family = pxe->tcpip->sa_family; + memcpy ( &sockaddr->se.se_addr, ip, pxe->net->net_addr_len ); + sockaddr->se.se_scope_id = pxe->netdev->index; +} + +/** + * Transcribe EFI IP address (for debugging) + * + * @v pxe PXE base code + * @v ip EFI IP address + * @ret text Transcribed IP address + */ +static const char * efi_pxe_ip_ntoa ( struct efi_pxe *pxe, + EFI_IP_ADDRESS *ip ) { + + return pxe->net->ntoa ( ip ); +} + +/** + * Populate local IP address + * + * @v pxe PXE base code + * @ret rc Return status code + */ +static int efi_pxe_ip ( struct efi_pxe *pxe ) { + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + struct in_addr address; + struct in_addr netmask; + + /* It's unclear which of the potentially many IPv6 addresses + * is supposed to be used. + */ + if ( mode->UsingIpv6 ) + return -ENOTSUP; + + /* Fetch IP address and subnet mask */ + fetch_ipv4_setting ( netdev_settings ( pxe->netdev ), &ip_setting, + &address ); + fetch_ipv4_setting ( netdev_settings ( pxe->netdev ), &netmask_setting, + &netmask ); + + /* Populate IP address and subnet mask */ + memset ( &mode->StationIp, 0, sizeof ( mode->StationIp ) ); + memcpy ( &mode->StationIp, &address, sizeof ( address ) ); + memset ( &mode->SubnetMask, 0, sizeof ( mode->SubnetMask ) ); + memcpy ( &mode->SubnetMask, &netmask, sizeof ( netmask ) ); + + return 0; +} + +/** + * Check if IP address matches filter + * + * @v pxe PXE base code + * @v ip EFI IP address + * @ret is_match IP address matches filter + */ +static int efi_pxe_ip_filter ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip ) { + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + EFI_PXE_BASE_CODE_IP_FILTER *filter = &mode->IpFilter; + uint8_t filters = filter->Filters; + union { + EFI_IP_ADDRESS ip; + struct in_addr in; + struct in6_addr in6; + } *u = container_of ( ip, typeof ( *u ), ip ); + size_t addr_len = pxe->net->net_addr_len; + unsigned int i; + + /* Match everything, if applicable */ + if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS ) + return 1; + + /* Match all multicasts, if applicable */ + if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST ) { + if ( mode->UsingIpv6 ) { + if ( IN6_IS_ADDR_MULTICAST ( &u->in6 ) ) + return 1; + } else { + if ( IN_IS_MULTICAST ( u->in.s_addr ) ) + return 1; + } + } + + /* Match IPv4 broadcasts, if applicable */ + if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST ) { + if ( ( ! mode->UsingIpv6 ) && + ( u->in.s_addr == INADDR_BROADCAST ) ) + return 1; + } + + /* Match station address, if applicable */ + if ( filters & EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP ) { + if ( memcmp ( ip, &mode->StationIp, addr_len ) == 0 ) + return 1; + } + + /* Match explicit addresses, if applicable */ + for ( i = 0 ; i < filter->IpCnt ; i++ ) { + if ( memcmp ( ip, &filter->IpList[i], addr_len ) == 0 ) + return 1; + } + + return 0; +} + +/****************************************************************************** + * + * Data transfer buffer + * + ****************************************************************************** + */ + +/** + * Reallocate PXE data transfer buffer + * + * @v xferbuf Data transfer buffer + * @v len New length (or zero to free buffer) + * @ret rc Return status code + */ +static int efi_pxe_buf_realloc ( struct xfer_buffer *xferbuf __unused, + size_t len __unused ) { + + /* Can never reallocate: return EFI_BUFFER_TOO_SMALL */ + return -ERANGE; +} + +/** + * Write data to PXE data transfer buffer + * + * @v xferbuf Data transfer buffer + * @v offset Starting offset + * @v data Data to copy + * @v len Length of data + */ +static void efi_pxe_buf_write ( struct xfer_buffer *xferbuf, size_t offset, + const void *data, size_t len ) { + + /* Copy data to buffer */ + memcpy ( ( xferbuf->data + offset ), data, len ); +} + +/** PXE data transfer buffer operations */ +static struct xfer_buffer_operations efi_pxe_buf_operations = { + .realloc = efi_pxe_buf_realloc, + .write = efi_pxe_buf_write, +}; + +/****************************************************************************** + * + * (M)TFTP download interface + * + ****************************************************************************** + */ + +/** + * Close PXE (M)TFTP download interface + * + * @v pxe PXE base code + * @v rc Reason for close + */ +static void efi_pxe_tftp_close ( struct efi_pxe *pxe, int rc ) { + + /* Restart interface */ + intf_restart ( &pxe->tftp, rc ); + + /* Record overall status */ + pxe->rc = rc; +} + +/** + * Check PXE (M)TFTP download flow control window + * + * @v pxe PXE base code + * @ret len Length of window + */ +static size_t efi_pxe_tftp_window ( struct efi_pxe *pxe ) { + + /* Return requested blocksize */ + return pxe->blksize; +} + +/** + * Receive new PXE (M)TFTP download data + * + * @v pxe PXE base code + * @v iobuf I/O buffer + * @v meta Transfer metadata + * @ret rc Return status code + */ +static int efi_pxe_tftp_deliver ( struct efi_pxe *pxe, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + int rc; + + /* Deliver to data transfer buffer */ + if ( ( rc = xferbuf_deliver ( &pxe->buf, iob_disown ( iobuf ), + meta ) ) != 0 ) + goto err_deliver; + + return 0; + + err_deliver: + efi_pxe_tftp_close ( pxe, rc ); + return rc; +} + +/** PXE file data transfer interface operations */ +static struct interface_operation efi_pxe_tftp_operations[] = { + INTF_OP ( xfer_deliver, struct efi_pxe *, efi_pxe_tftp_deliver ), + INTF_OP ( xfer_window, struct efi_pxe *, efi_pxe_tftp_window ), + INTF_OP ( intf_close, struct efi_pxe *, efi_pxe_tftp_close ), +}; + +/** PXE file data transfer interface descriptor */ +static struct interface_descriptor efi_pxe_tftp_desc = + INTF_DESC ( struct efi_pxe, tftp, efi_pxe_tftp_operations ); + +/** + * Open (M)TFTP download interface + * + * @v pxe PXE base code + * @v ip EFI IP address + * @v filename Filename + * @ret rc Return status code + */ +static int efi_pxe_tftp_open ( struct efi_pxe *pxe, EFI_IP_ADDRESS *ip, + const char *filename ) { + struct sockaddr server; + struct uri *uri; + int rc; + + /* Parse server address and filename */ + efi_pxe_ip_sockaddr ( pxe, ip, &server ); + uri = pxe_uri ( &server, filename ); + if ( ! uri ) { + DBGC ( pxe, "PXE %s could not parse %s:%s\n", pxe->name, + efi_pxe_ip_ntoa ( pxe, ip ), filename ); + rc = -ENOTSUP; + goto err_parse; + } + + /* Open URI */ + if ( ( rc = xfer_open_uri ( &pxe->tftp, uri ) ) != 0 ) { + DBGC ( pxe, "PXE %s could not open: %s\n", + pxe->name, strerror ( rc ) ); + goto err_open; + } + + err_open: + uri_put ( uri ); + err_parse: + return rc; +} + +/****************************************************************************** + * + * UDP interface + * + ****************************************************************************** + */ + +/** EFI UDP pseudo-header */ +struct efi_pxe_udp_pseudo_header { + /** Network-layer protocol */ + struct net_protocol *net; + /** Destination port */ + uint16_t dest_port; + /** Source port */ + uint16_t src_port; +} __attribute__ (( packed )); + +/** + * Close UDP interface + * + * @v pxe PXE base code + * @v rc Reason for close + */ +static void efi_pxe_udp_close ( struct efi_pxe *pxe, int rc ) { + struct io_buffer *iobuf; + struct io_buffer *tmp; + + /* Release our claim on SNP devices, if applicable */ + if ( process_running ( &pxe->process ) ) + efi_snp_release(); + + /* Stop process */ + process_del ( &pxe->process ); + + /* Restart UDP interface */ + intf_restart ( &pxe->udp, rc ); + + /* Flush any received UDP packets */ + list_for_each_entry_safe ( iobuf, tmp, &pxe->queue, list ) { + list_del ( &iobuf->list ); + free_iob ( iobuf ); + } +} + +/** + * Receive UDP packet + * + * @v pxe PXE base code + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int efi_pxe_udp_deliver ( struct efi_pxe *pxe, struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct sockaddr_efi *se_src; + struct sockaddr_efi *se_dest; + struct tcpip_net_protocol *tcpip; + struct net_protocol *net; + struct efi_pxe_udp_pseudo_header *pshdr; + size_t addr_len; + size_t pshdr_len; + int rc; + + /* Sanity checks */ + assert ( meta != NULL ); + se_src = ( ( struct sockaddr_efi * ) meta->src ); + assert ( se_src != NULL ); + se_dest = ( ( struct sockaddr_efi * ) meta->dest ); + assert ( se_dest != NULL ); + assert ( se_src->se_family == se_dest->se_family ); + + /* Determine protocol */ + tcpip = tcpip_net_protocol ( se_src->se_family ); + if ( ! tcpip ) { + rc = -ENOTSUP; + goto err_unsupported; + } + net = tcpip->net_protocol; + addr_len = net->net_addr_len; + + /* Construct pseudo-header */ + pshdr_len = ( sizeof ( *pshdr ) + ( 2 * addr_len ) ); + if ( ( rc = iob_ensure_headroom ( iobuf, pshdr_len ) ) != 0 ) + goto err_headroom; + memcpy ( iob_push ( iobuf, addr_len ), &se_src->se_addr, addr_len ); + memcpy ( iob_push ( iobuf, addr_len ), &se_dest->se_addr, addr_len ); + pshdr = iob_push ( iobuf, sizeof ( *pshdr ) ); + pshdr->net = net; + pshdr->dest_port = ntohs ( se_dest->se_port ); + pshdr->src_port = ntohs ( se_src->se_port ); + + /* Add to queue */ + list_add_tail ( &iobuf->list, &pxe->queue ); + + return 0; + + err_unsupported: + err_headroom: + free_iob ( iobuf ); + return rc; +} + +/** PXE UDP interface operations */ +static struct interface_operation efi_pxe_udp_operations[] = { + INTF_OP ( xfer_deliver, struct efi_pxe *, efi_pxe_udp_deliver ), + INTF_OP ( intf_close, struct efi_pxe *, efi_pxe_udp_close ), +}; + +/** PXE UDP interface descriptor */ +static struct interface_descriptor efi_pxe_udp_desc = + INTF_DESC ( struct efi_pxe, udp, efi_pxe_udp_operations ); + +/** + * Open UDP interface + * + * @v pxe PXE base code + * @ret rc Return status code + */ +static int efi_pxe_udp_open ( struct efi_pxe *pxe ) { + int rc; + + /* If interface is already open, then cancel the scheduled close */ + if ( process_running ( &pxe->process ) ) { + process_del ( &pxe->process ); + return 0; + } + + /* Open promiscuous UDP interface */ + if ( ( rc = udp_open_promisc ( &pxe->udp ) ) != 0 ) { + DBGC ( pxe, "PXE %s could not open UDP connection: %s\n", + pxe->name, strerror ( rc ) ); + return rc; + } + + /* Claim network devices */ + efi_snp_claim(); + + return 0; +} + +/** + * Schedule close of UDP interface + * + * @v pxe PXE base code + */ +static void efi_pxe_udp_schedule_close ( struct efi_pxe *pxe ) { + + /* The EFI PXE base code protocol does not provide any + * explicit UDP open/close methods. To avoid the overhead of + * reopening a socket for each read/write operation, we start + * a process which will close the socket immediately if the + * next call into iPXE is anything other than a UDP + * read/write. + */ + process_add ( &pxe->process ); +} + +/** + * Scheduled close of UDP interface + * + * @v pxe PXE base code + */ +static void efi_pxe_udp_scheduled_close ( struct efi_pxe *pxe ) { + + /* Close UDP interface */ + efi_pxe_udp_close ( pxe, 0 ); +} + +/** UDP close process descriptor */ +static struct process_descriptor efi_pxe_process_desc = + PROC_DESC_ONCE ( struct efi_pxe, process, efi_pxe_udp_scheduled_close ); + +/****************************************************************************** + * + * Fake DHCP packets + * + ****************************************************************************** + */ + +/** + * Name fake DHCP packet + * + * @v pxe PXE base code + * @v packet Packet + * @ret name Name of packet + */ +static const char * efi_pxe_fake_name ( struct efi_pxe *pxe, + EFI_PXE_BASE_CODE_PACKET *packet ) { + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + + if ( packet == &mode->DhcpDiscover ) { + return "DhcpDiscover"; + } else if ( packet == &mode->DhcpAck ) { + return "DhcpAck"; + } else if ( packet == &mode->ProxyOffer ) { + return "ProxyOffer"; + } else if ( packet == &mode->PxeDiscover ) { + return "PxeDiscover"; + } else if ( packet == &mode->PxeReply ) { + return "PxeReply"; + } else if ( packet == &mode->PxeBisReply ) { + return "PxeBisReply"; + } else { + return "<UNKNOWN>"; + } +} + +/** + * Construct fake DHCP packet and flag + * + * @v pxe PXE base code + * @v fake Fake packet constructor + * @v packet Packet to fill in + * @ret exists Packet existence flag + */ +static BOOLEAN efi_pxe_fake ( struct efi_pxe *pxe, + int ( * fake ) ( struct net_device *netdev, + void *data, size_t len ), + EFI_PXE_BASE_CODE_PACKET *packet ) { + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + struct dhcp_packet dhcppkt; + struct dhcphdr *dhcphdr; + unsigned int len; + int rc; + + /* The fake packet constructors do not support IPv6 */ + if ( mode->UsingIpv6 ) + return FALSE; + + /* Attempt to construct packet */ + if ( ( rc = fake ( pxe->netdev, packet, sizeof ( *packet ) ) != 0 ) ) { + DBGC ( pxe, "PXE %s could not fake %s: %s\n", pxe->name, + efi_pxe_fake_name ( pxe, packet ), strerror ( rc ) ); + return FALSE; + } + + /* The WDS bootstrap wdsmgfw.efi has a buggy DHCPv4 packet + * parser which does not correctly handle DHCP padding bytes. + * Specifically, if a padding byte (i.e. a zero) is + * encountered, the parse will first increment the pointer by + * one to skip over the padding byte but will then drop into + * the code path for handling normal options, which increments + * the pointer by two to skip over the (already-skipped) type + * field and the (non-existent) length field. + * + * The upshot of this bug in WDS is that the parser will fail + * with an error 0xc0000023 if the number of spare bytes after + * the end of the options is not an exact multiple of three. + * + * Work around this buggy parser by adding an explicit + * DHCP_END tag. + */ + dhcphdr = container_of ( &packet->Dhcpv4.BootpOpcode, + struct dhcphdr, op ); + dhcppkt_init ( &dhcppkt, dhcphdr, sizeof ( *packet ) ); + len = dhcppkt_len ( &dhcppkt ); + if ( len < sizeof ( *packet ) ) + packet->Raw[len] = DHCP_END; + + return TRUE; +} + +/** + * Construct fake DHCP packets + * + * @v pxe PXE base code + */ +static void efi_pxe_fake_all ( struct efi_pxe *pxe ) { + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + + /* Construct fake packets */ + mode->DhcpDiscoverValid = + efi_pxe_fake ( pxe, create_fakedhcpdiscover, + &mode->DhcpDiscover ); + mode->DhcpAckReceived = + efi_pxe_fake ( pxe, create_fakedhcpack, + &mode->DhcpAck ); + mode->PxeReplyReceived = + efi_pxe_fake ( pxe, create_fakepxebsack, + &mode->PxeReply ); +} + +/****************************************************************************** + * + * Base code protocol + * + ****************************************************************************** + */ + +/** + * Start PXE base code + * + * @v base PXE base code protocol + * @v use_ipv6 Use IPv6 + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_pxe_start ( EFI_PXE_BASE_CODE_PROTOCOL *base, + BOOLEAN use_ipv6 ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + struct tcpip_net_protocol *ipv6 = tcpip_net_protocol ( AF_INET6 ); + sa_family_t family = ( use_ipv6 ? AF_INET6 : AF_INET ); + int rc; + + DBGC ( pxe, "PXE %s START %s\n", pxe->name, ( ipv6 ? "IPv6" : "IPv4" )); + + /* Initialise mode structure */ + memset ( mode, 0, sizeof ( *mode ) ); + mode->AutoArp = TRUE; + mode->TTL = DEFAULT_TTL; + mode->ToS = DEFAULT_ToS; + mode->IpFilter.Filters = + ( EFI_PXE_BASE_CODE_IP_FILTER_STATION_IP | + EFI_PXE_BASE_CODE_IP_FILTER_BROADCAST | + EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS | + EFI_PXE_BASE_CODE_IP_FILTER_PROMISCUOUS_MULTICAST ); + + /* Check for IPv4/IPv6 support */ + mode->Ipv6Supported = ( ipv6 != NULL ); + mode->Ipv6Available = ( ipv6 != NULL ); + pxe->tcpip = tcpip_net_protocol ( family ); + if ( ! pxe->tcpip ) { + DBGC ( pxe, "PXE %s has no support for %s\n", + pxe->name, socket_family_name ( family ) ); + return EFI_UNSUPPORTED; + } + pxe->net = pxe->tcpip->net_protocol; + mode->UsingIpv6 = use_ipv6; + + /* Populate station IP address */ + if ( ( rc = efi_pxe_ip ( pxe ) ) != 0 ) + return rc; + + /* Construct fake DHCP packets */ + efi_pxe_fake_all ( pxe ); + + /* Record that base code is started */ + mode->Started = TRUE; + DBGC ( pxe, "PXE %s using %s\n", + pxe->name, pxe->net->ntoa ( &mode->StationIp ) ); + + return 0; +} + +/** + * Stop PXE base code + * + * @v base PXE base code protocol + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_pxe_stop ( EFI_PXE_BASE_CODE_PROTOCOL *base ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + + DBGC ( pxe, "PXE %s STOP\n", pxe->name ); + + /* Record that base code is stopped */ + mode->Started = FALSE; + + /* Close TFTP */ + efi_pxe_tftp_close ( pxe, 0 ); + + /* Close UDP */ + efi_pxe_udp_close ( pxe, 0 ); + + return 0; +} + +/** + * Perform DHCP + * + * @v base PXE base code protocol + * @v sort Offers should be sorted + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_pxe_dhcp ( EFI_PXE_BASE_CODE_PROTOCOL *base, + BOOLEAN sort ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + struct net_device *netdev = pxe->netdev; + int rc; + + DBGC ( pxe, "PXE %s DHCP %s\n", + pxe->name, ( sort ? "sorted" : "unsorted" ) ); + + /* Claim network devices */ + efi_snp_claim(); + + /* Initiate configuration */ + if ( ( rc = netdev_configure_all ( netdev ) ) != 0 ) { + DBGC ( pxe, "PXE %s could not initiate configuration: %s\n", + pxe->name, strerror ( rc ) ); + goto err_configure; + } + + /* Wait for configuration to complete (or time out) */ + while ( netdev_configuration_in_progress ( netdev ) ) + step(); + + /* Report timeout if configuration failed */ + if ( ! netdev_configuration_ok ( netdev ) ) { + rc = -ETIMEDOUT; + goto err_timeout; + } + + /* Update station IP address */ + if ( ( rc = efi_pxe_ip ( pxe ) ) != 0 ) + goto err_ip; + + /* Update faked DHCP packets */ + efi_pxe_fake_all ( pxe ); + + err_ip: + err_timeout: + err_configure: + efi_snp_release(); + return EFIRC ( rc ); +} + +/** + * Perform boot server discovery + * + * @v base PXE base code protocol + * @v type Boot server type + * @v layer Boot server layer + * @v bis Use boot integrity services + * @v info Additional information + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_discover ( EFI_PXE_BASE_CODE_PROTOCOL *base, UINT16 type, UINT16 *layer, + BOOLEAN bis, EFI_PXE_BASE_CODE_DISCOVER_INFO *info ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_IP_ADDRESS *ip; + unsigned int i; + + DBGC ( pxe, "PXE %s DISCOVER type %d layer %d%s\n", + pxe->name, type, *layer, ( bis ? " bis" : "" ) ); + if ( info ) { + DBGC ( pxe, "%s%s%s%s %s", + ( info->UseMCast ? " mcast" : "" ), + ( info->UseBCast ? " bcast" : "" ), + ( info->UseUCast ? " ucast" : "" ), + ( info->MustUseList ? " list" : "" ), + efi_pxe_ip_ntoa ( pxe, &info->ServerMCastIp ) ); + for ( i = 0 ; i < info->IpCnt ; i++ ) { + ip = &info->SrvList[i].IpAddr; + DBGC ( pxe, " %d%s:%s", info->SrvList[i].Type, + ( info->SrvList[i].AcceptAnyResponse ? + ":any" : "" ), efi_pxe_ip_ntoa ( pxe, ip ) ); + } + } + DBGC ( pxe, "\n" ); + + /* Not used by any bootstrap I can find to test with */ + return EFI_UNSUPPORTED; +} + +/** + * Perform (M)TFTP + * + * @v base PXE base code protocol + * @v opcode TFTP opcode + * @v data Data buffer + * @v overwrite Overwrite file + * @v len Length of data buffer + * @v blksize Block size + * @v ip Server address + * @v filename Filename + * @v info Additional information + * @v callback Pass packets to callback instead of data buffer + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_mtftp ( EFI_PXE_BASE_CODE_PROTOCOL *base, + EFI_PXE_BASE_CODE_TFTP_OPCODE opcode, VOID *data, + BOOLEAN overwrite, UINT64 *len, UINTN *blksize, + EFI_IP_ADDRESS *ip, UINT8 *filename, + EFI_PXE_BASE_CODE_MTFTP_INFO *info, BOOLEAN callback ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + int rc; + + DBGC ( pxe, "PXE %s MTFTP %d%s %p+%llx", pxe->name, opcode, + ( overwrite ? " overwrite" : "" ), data, *len ); + if ( blksize ) + DBGC ( pxe, " blksize %zd", ( ( size_t ) *blksize ) ); + DBGC ( pxe, " %s:%s", efi_pxe_ip_ntoa ( pxe, ip ), filename ); + if ( info ) { + DBGC ( pxe, " %s:%d:%d:%d:%d", + efi_pxe_ip_ntoa ( pxe, &info->MCastIp ), + info->CPort, info->SPort, info->ListenTimeout, + info->TransmitTimeout ); + } + DBGC ( pxe, "%s\n", ( callback ? " callback" : "" ) ); + + /* Fail unless operation is supported */ + if ( ! ( ( opcode == EFI_PXE_BASE_CODE_TFTP_READ_FILE ) || + ( opcode == EFI_PXE_BASE_CODE_MTFTP_READ_FILE ) ) ) { + DBGC ( pxe, "PXE %s unsupported MTFTP opcode %d\n", + pxe->name, opcode ); + rc = -ENOTSUP; + goto err_opcode; + } + + /* Claim network devices */ + efi_snp_claim(); + + /* Determine block size. Ignore the requested block size + * unless we are using callbacks, since limiting HTTP to a + * 512-byte TCP window is not sensible. + */ + pxe->blksize = ( ( callback && blksize ) ? *blksize : -1UL ); + + /* Initialise data transfer buffer */ + pxe->buf.data = data; + pxe->buf.len = *len; + + /* Open download */ + if ( ( rc = efi_pxe_tftp_open ( pxe, ip, + ( ( const char * ) filename ) ) ) != 0 ) + goto err_open; + + /* Wait for download to complete */ + pxe->rc = -EINPROGRESS; + while ( pxe->rc == -EINPROGRESS ) + step(); + if ( ( rc = pxe->rc ) != 0 ) { + DBGC ( pxe, "PXE %s download failed: %s\n", + pxe->name, strerror ( rc ) ); + goto err_download; + } + + err_download: + efi_pxe_tftp_close ( pxe, rc ); + err_open: + efi_snp_release(); + err_opcode: + return EFIRC ( rc ); +} + +/** + * Transmit UDP packet + * + * @v base PXE base code protocol + * @v flags Operation flags + * @v dest_ip Destination address + * @v dest_port Destination port + * @v gateway Gateway address + * @v src_ip Source address + * @v src_port Source port + * @v hdr_len Header length + * @v hdr Header data + * @v len Length + * @v data Data + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_udp_write ( EFI_PXE_BASE_CODE_PROTOCOL *base, UINT16 flags, + EFI_IP_ADDRESS *dest_ip, + EFI_PXE_BASE_CODE_UDP_PORT *dest_port, + EFI_IP_ADDRESS *gateway, EFI_IP_ADDRESS *src_ip, + EFI_PXE_BASE_CODE_UDP_PORT *src_port, + UINTN *hdr_len, VOID *hdr, UINTN *len, VOID *data ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + struct io_buffer *iobuf; + struct xfer_metadata meta; + union { + struct sockaddr_tcpip st; + struct sockaddr sa; + } dest; + union { + struct sockaddr_tcpip st; + struct sockaddr sa; + } src; + int rc; + + DBGC2 ( pxe, "PXE %s UDP WRITE ", pxe->name ); + if ( src_ip ) + DBGC2 ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, src_ip ) ); + DBGC2 ( pxe, ":" ); + if ( src_port && + ( ! ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) ) ) { + DBGC2 ( pxe, "%d", *src_port ); + } else { + DBGC2 ( pxe, "*" ); + } + DBGC2 ( pxe, "->%s:%d", efi_pxe_ip_ntoa ( pxe, dest_ip ), *dest_port ); + if ( gateway ) + DBGC2 ( pxe, " via %s", efi_pxe_ip_ntoa ( pxe, gateway ) ); + if ( hdr_len ) + DBGC2 ( pxe, " %p+%zx", hdr, ( ( size_t ) *hdr_len ) ); + DBGC2 ( pxe, " %p+%zx", data, ( ( size_t ) *len ) ); + if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_MAY_FRAGMENT ) + DBGC2 ( pxe, " frag" ); + DBGC2 ( pxe, "\n" ); + + /* Open UDP connection (if applicable) */ + if ( ( rc = efi_pxe_udp_open ( pxe ) ) != 0 ) + goto err_open; + + /* Construct destination address */ + efi_pxe_ip_sockaddr ( pxe, dest_ip, &dest.sa ); + dest.st.st_port = htons ( *dest_port ); + + /* Construct source address */ + efi_pxe_ip_sockaddr ( pxe, ( src_ip ? src_ip : &mode->StationIp ), + &src.sa ); + if ( src_port && + ( ! ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) ) ) { + src.st.st_port = htons ( *src_port ); + } else { + /* The API does not allow for a sensible concept of + * binding to a local port, so just use a random value. + */ + src.st.st_port = ( random() | htons ( 1024 ) ); + if ( src_port ) + *src_port = ntohs ( src.st.st_port ); + } + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &pxe->udp, + ( *len + ( hdr_len ? *hdr_len : 0 ) ) ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Populate I/O buffer */ + if ( hdr_len ) + memcpy ( iob_put ( iobuf, *hdr_len ), hdr, *hdr_len ); + memcpy ( iob_put ( iobuf, *len ), data, *len ); + + /* Construct metadata */ + memset ( &meta, 0, sizeof ( meta ) ); + meta.src = &src.sa; + meta.dest = &dest.sa; + meta.netdev = pxe->netdev; + + /* Deliver I/O buffer */ + if ( ( rc = xfer_deliver ( &pxe->udp, iob_disown ( iobuf ), + &meta ) ) != 0 ) { + DBGC ( pxe, "PXE %s could not transmit: %s\n", + pxe->name, strerror ( rc ) ); + goto err_deliver; + } + + err_deliver: + free_iob ( iobuf ); + err_alloc: + efi_pxe_udp_schedule_close ( pxe ); + err_open: + return EFIRC ( rc ); +} + +/** + * Receive UDP packet + * + * @v base PXE base code protocol + * @v flags Operation flags + * @v dest_ip Destination address + * @v dest_port Destination port + * @v src_ip Source address + * @v src_port Source port + * @v hdr_len Header length + * @v hdr Header data + * @v len Length + * @v data Data + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_udp_read ( EFI_PXE_BASE_CODE_PROTOCOL *base, UINT16 flags, + EFI_IP_ADDRESS *dest_ip, + EFI_PXE_BASE_CODE_UDP_PORT *dest_port, + EFI_IP_ADDRESS *src_ip, + EFI_PXE_BASE_CODE_UDP_PORT *src_port, + UINTN *hdr_len, VOID *hdr, UINTN *len, VOID *data ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + struct io_buffer *iobuf; + struct efi_pxe_udp_pseudo_header *pshdr; + EFI_IP_ADDRESS *actual_dest_ip; + EFI_IP_ADDRESS *actual_src_ip; + size_t addr_len; + size_t frag_len; + int rc; + + DBGC2 ( pxe, "PXE %s UDP READ ", pxe->name ); + if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER ) { + DBGC2 ( pxe, "(filter)" ); + } else if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP ) { + DBGC2 ( pxe, "*" ); + } else if ( dest_ip ) { + DBGC2 ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, dest_ip ) ); + } + DBGC2 ( pxe, ":" ); + if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT ) { + DBGC2 ( pxe, "*" ); + } else if ( dest_port ) { + DBGC2 ( pxe, "%d", *dest_port ); + } else { + DBGC2 ( pxe, "<NULL>" ); + } + DBGC2 ( pxe, "<-" ); + if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP ) { + DBGC2 ( pxe, "*" ); + } else if ( src_ip ) { + DBGC2 ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, src_ip ) ); + } else { + DBGC2 ( pxe, "<NULL>" ); + } + DBGC2 ( pxe, ":" ); + if ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) { + DBGC2 ( pxe, "*" ); + } else if ( src_port ) { + DBGC2 ( pxe, "%d", *src_port ); + } else { + DBGC2 ( pxe, "<NULL>" ); + } + if ( hdr_len ) + DBGC2 ( pxe, " %p+%zx", hdr, ( ( size_t ) *hdr_len ) ); + DBGC2 ( pxe, " %p+%zx\n", data, ( ( size_t ) *len ) ); + + /* Open UDP connection (if applicable) */ + if ( ( rc = efi_pxe_udp_open ( pxe ) ) != 0 ) + goto err_open; + + /* Try receiving a packet, if the queue is empty */ + if ( list_empty ( &pxe->queue ) ) + step(); + + /* Remove first packet from the queue */ + iobuf = list_first_entry ( &pxe->queue, struct io_buffer, list ); + if ( ! iobuf ) { + rc = -ETIMEDOUT; /* "no packet" */ + goto err_empty; + } + list_del ( &iobuf->list ); + + /* Strip pseudo-header */ + pshdr = iobuf->data; + addr_len = ( pshdr->net->net_addr_len ); + iob_pull ( iobuf, sizeof ( *pshdr ) ); + actual_dest_ip = iobuf->data; + iob_pull ( iobuf, addr_len ); + actual_src_ip = iobuf->data; + iob_pull ( iobuf, addr_len ); + DBGC2 ( pxe, "PXE %s UDP RX %s:%d", pxe->name, + pshdr->net->ntoa ( actual_dest_ip ), pshdr->dest_port ); + DBGC2 ( pxe, "<-%s:%d len %#zx\n", pshdr->net->ntoa ( actual_src_ip ), + pshdr->src_port, iob_len ( iobuf ) ); + + /* Filter based on network-layer protocol */ + if ( pshdr->net != pxe->net ) { + DBGC2 ( pxe, "PXE %s filtered out %s packet\n", + pxe->name, pshdr->net->name ); + rc = -ETIMEDOUT; /* "no packet" */ + goto err_filter; + } + + /* Filter based on port numbers */ + if ( ! ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_PORT ) || + ( dest_port && ( *dest_port == pshdr->dest_port ) ) ) ) { + DBGC2 ( pxe, "PXE %s filtered out destination port %d\n", + pxe->name, pshdr->dest_port ); + rc = -ETIMEDOUT; /* "no packet" */ + goto err_filter; + } + if ( ! ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_PORT ) || + ( src_port && ( *src_port == pshdr->src_port ) ) ) ) { + DBGC2 ( pxe, "PXE %s filtered out source port %d\n", + pxe->name, pshdr->src_port ); + rc = -ETIMEDOUT; /* "no packet" */ + goto err_filter; + } + + /* Filter based on source IP address */ + if ( ! ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_SRC_IP ) || + ( src_ip && + ( memcmp ( src_ip, actual_src_ip, addr_len ) == 0 ) ) ) ) { + DBGC2 ( pxe, "PXE %s filtered out source IP %s\n", + pxe->name, pshdr->net->ntoa ( actual_src_ip ) ); + rc = -ETIMEDOUT; /* "no packet" */ + goto err_filter; + } + + /* Filter based on destination IP address */ + if ( ! ( ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER ) && + efi_pxe_ip_filter ( pxe, actual_dest_ip ) ) || + ( ( ! ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_USE_FILTER ) ) && + ( ( flags & EFI_PXE_BASE_CODE_UDP_OPFLAGS_ANY_DEST_IP ) || + ( dest_ip && ( memcmp ( dest_ip, actual_dest_ip, + addr_len ) == 0 ) ) ) ) ) ) { + DBGC2 ( pxe, "PXE %s filtered out destination IP %s\n", + pxe->name, pshdr->net->ntoa ( actual_dest_ip ) ); + rc = -ETIMEDOUT; /* "no packet" */ + goto err_filter; + } + + /* Fill in addresses and port numbers */ + if ( dest_ip ) + memcpy ( dest_ip, actual_dest_ip, addr_len ); + if ( dest_port ) + *dest_port = pshdr->dest_port; + if ( src_ip ) + memcpy ( src_ip, actual_src_ip, addr_len ); + if ( src_port ) + *src_port = pshdr->src_port; + + /* Fill in header, if applicable */ + if ( hdr_len ) { + frag_len = iob_len ( iobuf ); + if ( frag_len > *hdr_len ) + frag_len = *hdr_len; + memcpy ( hdr, iobuf->data, frag_len ); + iob_pull ( iobuf, frag_len ); + *hdr_len = frag_len; + } + + /* Fill in data buffer */ + frag_len = iob_len ( iobuf ); + if ( frag_len > *len ) + frag_len = *len; + memcpy ( data, iobuf->data, frag_len ); + iob_pull ( iobuf, frag_len ); + *len = frag_len; + + /* Check for overflow */ + if ( iob_len ( iobuf ) ) { + rc = -ERANGE; + goto err_too_short; + } + + /* Success */ + rc = 0; + + err_too_short: + err_filter: + free_iob ( iobuf ); + err_empty: + efi_pxe_udp_schedule_close ( pxe ); + err_open: + return EFIRC ( rc ); +} + +/** + * Set receive filter + * + * @v base PXE base code protocol + * @v filter Receive filter + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_set_ip_filter ( EFI_PXE_BASE_CODE_PROTOCOL *base, + EFI_PXE_BASE_CODE_IP_FILTER *filter ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + unsigned int i; + + DBGC ( pxe, "PXE %s SET IP FILTER %02x", + pxe->name, filter->Filters ); + for ( i = 0 ; i < filter->IpCnt ; i++ ) { + DBGC ( pxe, " %s", + efi_pxe_ip_ntoa ( pxe, &filter->IpList[i] ) ); + } + DBGC ( pxe, "\n" ); + + /* Update filter */ + memcpy ( &mode->IpFilter, filter, sizeof ( mode->IpFilter ) ); + + return 0; +} + +/** + * Resolve MAC address + * + * @v base PXE base code protocol + * @v ip IP address + * @v mac MAC address to fill in + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI efi_pxe_arp ( EFI_PXE_BASE_CODE_PROTOCOL *base, + EFI_IP_ADDRESS *ip, + EFI_MAC_ADDRESS *mac ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + + DBGC ( pxe, "PXE %s ARP %s %p\n", + pxe->name, efi_pxe_ip_ntoa ( pxe, ip ), mac ); + + /* Not used by any bootstrap I can find to test with */ + return EFI_UNSUPPORTED; +} + +/** + * Set parameters + * + * @v base PXE base code protocol + * @v autoarp Automatic ARP packet generation + * @v sendguid Send GUID as client hardware address + * @v ttl IP time to live + * @v tos IP type of service + * @v callback Make callbacks + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_set_parameters ( EFI_PXE_BASE_CODE_PROTOCOL *base, + BOOLEAN *autoarp, BOOLEAN *sendguid, UINT8 *ttl, + UINT8 *tos, BOOLEAN *callback ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + + DBGC ( pxe, "PXE %s SET PARAMETERS", pxe->name ); + if ( autoarp ) + DBGC ( pxe, " %s", ( *autoarp ? "autoarp" : "noautoarp" ) ); + if ( sendguid ) + DBGC ( pxe, " %s", ( *sendguid ? "sendguid" : "sendmac" ) ); + if ( ttl ) + DBGC ( pxe, " ttl %d", *ttl ); + if ( tos ) + DBGC ( pxe, " tos %d", *tos ); + if ( callback ) { + DBGC ( pxe, " %s", + ( *callback ? "callback" : "nocallback" ) ); + } + DBGC ( pxe, "\n" ); + + /* Update parameters */ + if ( autoarp ) + mode->AutoArp = *autoarp; + if ( sendguid ) + mode->SendGUID = *sendguid; + if ( ttl ) + mode->TTL = *ttl; + if ( tos ) + mode->ToS = *tos; + if ( callback ) + mode->MakeCallbacks = *callback; + + return 0; +} + +/** + * Set IP address + * + * @v base PXE base code protocol + * @v ip IP address + * @v netmask Subnet mask + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_set_station_ip ( EFI_PXE_BASE_CODE_PROTOCOL *base, + EFI_IP_ADDRESS *ip, EFI_IP_ADDRESS *netmask ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + + DBGC ( pxe, "PXE %s SET STATION IP ", pxe->name ); + if ( ip ) + DBGC ( pxe, "%s", efi_pxe_ip_ntoa ( pxe, ip ) ); + if ( netmask ) + DBGC ( pxe, "/%s", efi_pxe_ip_ntoa ( pxe, netmask ) ); + DBGC ( pxe, "\n" ); + + /* Update IP address and netmask */ + if ( ip ) + memcpy ( &mode->StationIp, ip, sizeof ( mode->StationIp ) ); + if ( netmask ) + memcpy ( &mode->SubnetMask, netmask, sizeof (mode->SubnetMask)); + + return 0; +} + +/** + * Update cached DHCP packets + * + * @v base PXE base code protocol + * @v dhcpdisc_ok DHCPDISCOVER is valid + * @v dhcpack_ok DHCPACK received + * @v proxyoffer_ok ProxyDHCPOFFER received + * @v pxebsdisc_ok PxeBsDISCOVER valid + * @v pxebsack_ok PxeBsACK received + * @v pxebsbis_ok PxeBsBIS received + * @v dhcpdisc DHCPDISCOVER packet + * @v dhcpack DHCPACK packet + * @v proxyoffer ProxyDHCPOFFER packet + * @v pxebsdisc PxeBsDISCOVER packet + * @v pxebsack PxeBsACK packet + * @v pxebsbis PxeBsBIS packet + * @ret efirc EFI status code + */ +static EFI_STATUS EFIAPI +efi_pxe_set_packets ( EFI_PXE_BASE_CODE_PROTOCOL *base, BOOLEAN *dhcpdisc_ok, + BOOLEAN *dhcpack_ok, BOOLEAN *proxyoffer_ok, + BOOLEAN *pxebsdisc_ok, BOOLEAN *pxebsack_ok, + BOOLEAN *pxebsbis_ok, EFI_PXE_BASE_CODE_PACKET *dhcpdisc, + EFI_PXE_BASE_CODE_PACKET *dhcpack, + EFI_PXE_BASE_CODE_PACKET *proxyoffer, + EFI_PXE_BASE_CODE_PACKET *pxebsdisc, + EFI_PXE_BASE_CODE_PACKET *pxebsack, + EFI_PXE_BASE_CODE_PACKET *pxebsbis ) { + struct efi_pxe *pxe = container_of ( base, struct efi_pxe, base ); + EFI_PXE_BASE_CODE_MODE *mode = &pxe->mode; + + DBGC ( pxe, "PXE %s SET PACKETS\n", pxe->name ); + + /* Update fake packet flags */ + if ( dhcpdisc_ok ) + mode->DhcpDiscoverValid = *dhcpdisc_ok; + if ( dhcpack_ok ) + mode->DhcpAckReceived = *dhcpack_ok; + if ( proxyoffer_ok ) + mode->ProxyOfferReceived = *proxyoffer_ok; + if ( pxebsdisc_ok ) + mode->PxeDiscoverValid = *pxebsdisc_ok; + if ( pxebsack_ok ) + mode->PxeReplyReceived = *pxebsack_ok; + if ( pxebsbis_ok ) + mode->PxeBisReplyReceived = *pxebsbis_ok; + + /* Update fake packet contents */ + if ( dhcpdisc ) + memcpy ( &mode->DhcpDiscover, dhcpdisc, sizeof ( *dhcpdisc ) ); + if ( dhcpack ) + memcpy ( &mode->DhcpAck, dhcpack, sizeof ( *dhcpack ) ); + if ( proxyoffer ) + memcpy ( &mode->ProxyOffer, proxyoffer, sizeof ( *proxyoffer )); + if ( pxebsdisc ) + memcpy ( &mode->PxeDiscover, pxebsdisc, sizeof ( *pxebsdisc ) ); + if ( pxebsack ) + memcpy ( &mode->PxeReply, pxebsack, sizeof ( *pxebsack ) ); + if ( pxebsbis ) + memcpy ( &mode->PxeBisReply, pxebsbis, sizeof ( *pxebsbis ) ); + + return 0; +} + +/** PXE base code protocol */ +static EFI_PXE_BASE_CODE_PROTOCOL efi_pxe_base_code_protocol = { + .Revision = EFI_PXE_BASE_CODE_PROTOCOL_REVISION, + .Start = efi_pxe_start, + .Stop = efi_pxe_stop, + .Dhcp = efi_pxe_dhcp, + .Discover = efi_pxe_discover, + .Mtftp = efi_pxe_mtftp, + .UdpWrite = efi_pxe_udp_write, + .UdpRead = efi_pxe_udp_read, + .SetIpFilter = efi_pxe_set_ip_filter, + .Arp = efi_pxe_arp, + .SetParameters = efi_pxe_set_parameters, + .SetStationIp = efi_pxe_set_station_ip, + .SetPackets = efi_pxe_set_packets, +}; + +/** + * Install PXE base code protocol + * + * @v handle EFI handle + * @v netdev Underlying network device + * @ret rc Return status code + */ +int efi_pxe_install ( EFI_HANDLE handle, struct net_device *netdev ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct tcpip_net_protocol *ipv6 = tcpip_net_protocol ( AF_INET6 ); + struct efi_pxe *pxe; + struct in_addr ip; + BOOLEAN use_ipv6; + EFI_STATUS efirc; + int rc; + + /* Allocate and initialise structure */ + pxe = zalloc ( sizeof ( *pxe ) ); + if ( ! pxe ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &pxe->refcnt, efi_pxe_free ); + pxe->netdev = netdev_get ( netdev ); + pxe->name = netdev->name; + pxe->handle = handle; + memcpy ( &pxe->base, &efi_pxe_base_code_protocol, sizeof ( pxe->base )); + pxe->base.Mode = &pxe->mode; + pxe->buf.op = &efi_pxe_buf_operations; + intf_init ( &pxe->tftp, &efi_pxe_tftp_desc, &pxe->refcnt ); + intf_init ( &pxe->udp, &efi_pxe_udp_desc, &pxe->refcnt ); + INIT_LIST_HEAD ( &pxe->queue ); + process_init_stopped ( &pxe->process, &efi_pxe_process_desc, + &pxe->refcnt ); + + /* Crude heuristic: assume that we prefer to use IPv4 if we + * have an IPv4 address for the network device, otherwise + * prefer IPv6 (if available). + */ + fetch_ipv4_setting ( netdev_settings ( netdev ), &ip_setting, &ip ); + use_ipv6 = ( ip.s_addr ? FALSE : ( ipv6 != NULL ) ); + + /* Start base code */ + efi_pxe_start ( &pxe->base, use_ipv6 ); + + /* Install PXE base code protocol */ + if ( ( efirc = bs->InstallMultipleProtocolInterfaces ( + &handle, &efi_pxe_base_code_protocol_guid, &pxe->base, + NULL ) ) != 0 ) { + rc = -EEFI ( efirc ); + DBGC ( pxe, "PXE %s could not install base code protocol: %s\n", + pxe->name, strerror ( rc ) ); + goto err_install_protocol; + } + + /* Transfer reference to list and return */ + list_add_tail ( &pxe->list, &efi_pxes ); + DBGC ( pxe, "PXE %s installed for %s\n", + pxe->name, efi_handle_name ( handle ) ); + return 0; + + bs->UninstallMultipleProtocolInterfaces ( + handle, &efi_pxe_base_code_protocol_guid, &pxe->base, + NULL ); + err_install_protocol: + ref_put ( &pxe->refcnt ); + err_alloc: + return rc; +} + +/** + * Uninstall PXE base code protocol + * + * @v handle EFI handle + */ +void efi_pxe_uninstall ( EFI_HANDLE handle ) { + EFI_BOOT_SERVICES *bs = efi_systab->BootServices; + struct efi_pxe *pxe; + + /* Locate PXE base code */ + pxe = efi_pxe_find ( handle ); + if ( ! handle ) { + DBG ( "PXE could not find base code for %s\n", + efi_handle_name ( handle ) ); + return; + } + + /* Stop base code */ + efi_pxe_stop ( &pxe->base ); + + /* Uninstall PXE base code protocol */ + bs->UninstallMultipleProtocolInterfaces ( + handle, &efi_pxe_base_code_protocol_guid, &pxe->base, + NULL ); + + /* Remove from list and drop list's reference */ + list_del ( &pxe->list ); + ref_put ( &pxe->refcnt ); +} |