| /* |
| * 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/efi_null.h> |
| #include <ipxe/efi/Protocol/PxeBaseCode.h> |
| #include <ipxe/efi/Protocol/AppleNetBoot.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; |
| /** Apple NetBoot protocol */ |
| EFI_APPLE_NET_BOOT_PROTOCOL apple; |
| |
| /** 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->scope_id; |
| } |
| |
| /** |
| * 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, ( use_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, |
| }; |
| |
| /****************************************************************************** |
| * |
| * Apple NetBoot protocol |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * Get DHCP/BSDP response |
| * |
| * @v packet Packet |
| * @v len Length of data buffer |
| * @v data Data buffer |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_apple_get_response ( EFI_PXE_BASE_CODE_PACKET *packet, UINTN *len, |
| VOID *data ) { |
| |
| /* Check length */ |
| if ( *len < sizeof ( *packet ) ) { |
| *len = sizeof ( *packet ); |
| return EFI_BUFFER_TOO_SMALL; |
| } |
| |
| /* Copy packet */ |
| memcpy ( data, packet, sizeof ( *packet ) ); |
| *len = sizeof ( *packet ); |
| |
| return EFI_SUCCESS; |
| } |
| |
| /** |
| * Get DHCP response |
| * |
| * @v apple Apple NetBoot protocol |
| * @v len Length of data buffer |
| * @v data Data buffer |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_apple_get_dhcp_response ( EFI_APPLE_NET_BOOT_PROTOCOL *apple, |
| UINTN *len, VOID *data ) { |
| struct efi_pxe *pxe = container_of ( apple, struct efi_pxe, apple ); |
| |
| return efi_apple_get_response ( &pxe->mode.DhcpAck, len, data ); |
| } |
| |
| /** |
| * Get BSDP response |
| * |
| * @v apple Apple NetBoot protocol |
| * @v len Length of data buffer |
| * @v data Data buffer |
| * @ret efirc EFI status code |
| */ |
| static EFI_STATUS EFIAPI |
| efi_apple_get_bsdp_response ( EFI_APPLE_NET_BOOT_PROTOCOL *apple, |
| UINTN *len, VOID *data ) { |
| struct efi_pxe *pxe = container_of ( apple, struct efi_pxe, apple ); |
| |
| return efi_apple_get_response ( &pxe->mode.PxeReply, len, data ); |
| } |
| |
| /** Apple NetBoot protocol */ |
| static EFI_APPLE_NET_BOOT_PROTOCOL efi_apple_net_boot_protocol = { |
| .GetDhcpResponse = efi_apple_get_dhcp_response, |
| .GetBsdpResponse = efi_apple_get_bsdp_response, |
| }; |
| |
| /****************************************************************************** |
| * |
| * Installer |
| * |
| ****************************************************************************** |
| */ |
| |
| /** |
| * 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; |
| int leak = 0; |
| 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; |
| memcpy ( &pxe->apple, &efi_apple_net_boot_protocol, |
| sizeof ( pxe->apple ) ); |
| 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, |
| &efi_apple_net_boot_protocol_guid, &pxe->apple, |
| 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; |
| |
| if ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| handle, |
| &efi_pxe_base_code_protocol_guid, &pxe->base, |
| &efi_apple_net_boot_protocol_guid, &pxe->apple, |
| NULL ) ) != 0 ) { |
| DBGC ( pxe, "PXE %s could not uninstall: %s\n", |
| pxe->name, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| efi_nullify_pxe ( &pxe->base ); |
| efi_nullify_apple ( &pxe->apple ); |
| err_install_protocol: |
| if ( ! leak ) |
| ref_put ( &pxe->refcnt ); |
| err_alloc: |
| if ( leak ) |
| DBGC ( pxe, "PXE %s nullified and leaked\n", pxe->name ); |
| 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; |
| int leak = efi_shutdown_in_progress; |
| EFI_STATUS efirc; |
| |
| /* 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 */ |
| if ( ( ! efi_shutdown_in_progress ) && |
| ( ( efirc = bs->UninstallMultipleProtocolInterfaces ( |
| handle, |
| &efi_pxe_base_code_protocol_guid, &pxe->base, |
| &efi_apple_net_boot_protocol_guid, &pxe->apple, |
| NULL ) ) != 0 ) ) { |
| DBGC ( pxe, "PXE %s could not uninstall: %s\n", |
| pxe->name, strerror ( -EEFI ( efirc ) ) ); |
| leak = 1; |
| } |
| efi_nullify_pxe ( &pxe->base ); |
| efi_nullify_apple ( &pxe->apple ); |
| |
| /* Remove from list and drop list's reference */ |
| list_del ( &pxe->list ); |
| if ( ! leak ) |
| ref_put ( &pxe->refcnt ); |
| |
| /* Report leakage, if applicable */ |
| if ( leak && ( ! efi_shutdown_in_progress ) ) |
| DBGC ( pxe, "PXE %s nullified and leaked\n", pxe->name ); |
| } |