diff options
Diffstat (limited to 'qemu/roms/ipxe/src/net/udp')
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/dhcp.c | 1446 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/dhcpv6.c | 989 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/dns.c | 1152 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/slam.c | 757 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/syslog.c | 298 | ||||
-rw-r--r-- | qemu/roms/ipxe/src/net/udp/tftp.c | 1236 |
6 files changed, 5878 insertions, 0 deletions
diff --git a/qemu/roms/ipxe/src/net/udp/dhcp.c b/qemu/roms/ipxe/src/net/udp/dhcp.c new file mode 100644 index 000000000..04fad04c2 --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/dhcp.c @@ -0,0 +1,1446 @@ +/* + * Copyright (C) 2006 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 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <string.h> +#include <stdlib.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/if_ether.h> +#include <ipxe/iobuf.h> +#include <ipxe/netdevice.h> +#include <ipxe/device.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/job.h> +#include <ipxe/retry.h> +#include <ipxe/tcpip.h> +#include <ipxe/ip.h> +#include <ipxe/uuid.h> +#include <ipxe/timer.h> +#include <ipxe/settings.h> +#include <ipxe/dhcp.h> +#include <ipxe/dhcpopts.h> +#include <ipxe/dhcppkt.h> +#include <ipxe/dhcp_arch.h> +#include <ipxe/features.h> + +/** @file + * + * Dynamic Host Configuration Protocol + * + */ + +struct dhcp_session; +static int dhcp_tx ( struct dhcp_session *dhcp ); + +/** + * DHCP operation types + * + * This table maps from DHCP message types (i.e. values of the @c + * DHCP_MESSAGE_TYPE option) to values of the "op" field within a DHCP + * packet. + */ +static const uint8_t dhcp_op[] = { + [DHCPDISCOVER] = BOOTP_REQUEST, + [DHCPOFFER] = BOOTP_REPLY, + [DHCPREQUEST] = BOOTP_REQUEST, + [DHCPDECLINE] = BOOTP_REQUEST, + [DHCPACK] = BOOTP_REPLY, + [DHCPNAK] = BOOTP_REPLY, + [DHCPRELEASE] = BOOTP_REQUEST, + [DHCPINFORM] = BOOTP_REQUEST, +}; + +/** Raw option data for options common to all DHCP requests */ +static uint8_t dhcp_request_options_data[] = { + DHCP_MESSAGE_TYPE, DHCP_BYTE ( 0 ), + DHCP_MAX_MESSAGE_SIZE, + DHCP_WORD ( ETH_MAX_MTU - 20 /* IP header */ - 8 /* UDP header */ ), + DHCP_CLIENT_ARCHITECTURE, DHCP_ARCH_CLIENT_ARCHITECTURE, + DHCP_CLIENT_NDI, DHCP_ARCH_CLIENT_NDI, + DHCP_VENDOR_CLASS_ID, DHCP_ARCH_VENDOR_CLASS_ID, + DHCP_USER_CLASS_ID, DHCP_STRING ( 'i', 'P', 'X', 'E' ), + DHCP_PARAMETER_REQUEST_LIST, + DHCP_OPTION ( DHCP_SUBNET_MASK, DHCP_ROUTERS, DHCP_DNS_SERVERS, + DHCP_LOG_SERVERS, DHCP_HOST_NAME, DHCP_DOMAIN_NAME, + DHCP_ROOT_PATH, DHCP_VENDOR_ENCAP, DHCP_VENDOR_CLASS_ID, + DHCP_TFTP_SERVER_NAME, DHCP_BOOTFILE_NAME, + DHCP_DOMAIN_SEARCH, + 128, 129, 130, 131, 132, 133, 134, 135, /* for PXE */ + DHCP_EB_ENCAP, DHCP_ISCSI_INITIATOR_IQN ), + DHCP_END +}; + +/** DHCP server address setting */ +const struct setting dhcp_server_setting __setting ( SETTING_MISC, + dhcp-server ) = { + .name = "dhcp-server", + .description = "DHCP server", + .tag = DHCP_SERVER_IDENTIFIER, + .type = &setting_type_ipv4, +}; + +/** + * Most recent DHCP transaction ID + * + * This is exposed for use by the fakedhcp code when reconstructing + * DHCP packets for PXE NBPs. + */ +uint32_t dhcp_last_xid; + +/** + * Name a DHCP packet type + * + * @v msgtype DHCP message type + * @ret string DHCP mesasge type name + */ +static inline const char * dhcp_msgtype_name ( unsigned int msgtype ) { + switch ( msgtype ) { + case DHCPNONE: return "BOOTP"; /* Non-DHCP packet */ + case DHCPDISCOVER: return "DHCPDISCOVER"; + case DHCPOFFER: return "DHCPOFFER"; + case DHCPREQUEST: return "DHCPREQUEST"; + case DHCPDECLINE: return "DHCPDECLINE"; + case DHCPACK: return "DHCPACK"; + case DHCPNAK: return "DHCPNAK"; + case DHCPRELEASE: return "DHCPRELEASE"; + case DHCPINFORM: return "DHCPINFORM"; + default: return "DHCP<invalid>"; + } +} + +/**************************************************************************** + * + * DHCP session + * + */ + +struct dhcp_session; + +/** DHCP session state operations */ +struct dhcp_session_state { + /** State name */ + const char *name; + /** + * Construct transmitted packet + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ + int ( * tx ) ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ); + /** Handle received packet + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + * @v server_id DHCP server ID + */ + void ( * rx ) ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, + uint8_t msgtype, struct in_addr server_id ); + /** Handle timer expiry + * + * @v dhcp DHCP session + */ + void ( * expired ) ( struct dhcp_session *dhcp ); + /** Transmitted message type */ + uint8_t tx_msgtype; + /** Apply minimum timeout */ + uint8_t apply_min_timeout; +}; + +static struct dhcp_session_state dhcp_state_discover; +static struct dhcp_session_state dhcp_state_request; +static struct dhcp_session_state dhcp_state_proxy; +static struct dhcp_session_state dhcp_state_pxebs; + +/** A DHCP session */ +struct dhcp_session { + /** Reference counter */ + struct refcnt refcnt; + /** Job control interface */ + struct interface job; + /** Data transfer interface */ + struct interface xfer; + + /** Network device being configured */ + struct net_device *netdev; + /** Local socket address */ + struct sockaddr_in local; + /** State of the session */ + struct dhcp_session_state *state; + /** Transaction ID (in network-endian order) */ + uint32_t xid; + + /** Offered IP address */ + struct in_addr offer; + /** DHCP server */ + struct in_addr server; + /** DHCP offer priority */ + int priority; + + /** ProxyDHCP protocol extensions should be ignored */ + int no_pxedhcp; + /** ProxyDHCP server */ + struct in_addr proxy_server; + /** ProxyDHCP offer */ + struct dhcp_packet *proxy_offer; + /** ProxyDHCP offer priority */ + int proxy_priority; + + /** PXE Boot Server type */ + uint16_t pxe_type; + /** List of PXE Boot Servers to attempt */ + struct in_addr *pxe_attempt; + /** List of PXE Boot Servers to accept */ + struct in_addr *pxe_accept; + + /** Retransmission timer */ + struct retry_timer timer; + /** Transmission counter */ + unsigned int count; + /** Start time of the current state (in ticks) */ + unsigned long start; +}; + +/** + * Free DHCP session + * + * @v refcnt Reference counter + */ +static void dhcp_free ( struct refcnt *refcnt ) { + struct dhcp_session *dhcp = + container_of ( refcnt, struct dhcp_session, refcnt ); + + netdev_put ( dhcp->netdev ); + dhcppkt_put ( dhcp->proxy_offer ); + free ( dhcp ); +} + +/** + * Mark DHCP session as complete + * + * @v dhcp DHCP session + * @v rc Return status code + */ +static void dhcp_finished ( struct dhcp_session *dhcp, int rc ) { + + /* Stop retry timer */ + stop_timer ( &dhcp->timer ); + + /* Shut down interfaces */ + intf_shutdown ( &dhcp->xfer, rc ); + intf_shutdown ( &dhcp->job, rc ); +} + +/** + * Transition to new DHCP session state + * + * @v dhcp DHCP session + * @v state New session state + */ +static void dhcp_set_state ( struct dhcp_session *dhcp, + struct dhcp_session_state *state ) { + + DBGC ( dhcp, "DHCP %p entering %s state\n", dhcp, state->name ); + dhcp->state = state; + dhcp->start = currticks(); + stop_timer ( &dhcp->timer ); + dhcp->timer.min_timeout = + ( state->apply_min_timeout ? DHCP_MIN_TIMEOUT : 0 ); + dhcp->timer.max_timeout = DHCP_MAX_TIMEOUT; + start_timer_nodelay ( &dhcp->timer ); +} + +/** + * Check if DHCP packet contains PXE options + * + * @v dhcppkt DHCP packet + * @ret has_pxeopts DHCP packet contains PXE options + * + * It is assumed that the packet is already known to contain option 60 + * set to "PXEClient". + */ +static int dhcp_has_pxeopts ( struct dhcp_packet *dhcppkt ) { + + /* Check for a boot filename */ + if ( dhcppkt_fetch ( dhcppkt, DHCP_BOOTFILE_NAME, NULL, 0 ) > 0 ) + return 1; + + /* Check for a PXE boot menu */ + if ( dhcppkt_fetch ( dhcppkt, DHCP_PXE_BOOT_MENU, NULL, 0 ) > 0 ) + return 1; + + return 0; +} + +/**************************************************************************** + * + * DHCP state machine + * + */ + +/** + * Construct transmitted packet for DHCP discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_discovery_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt __unused, + struct sockaddr_in *peer ) { + + DBGC ( dhcp, "DHCP %p DHCPDISCOVER\n", dhcp ); + + /* Set server address */ + peer->sin_addr.s_addr = INADDR_BROADCAST; + peer->sin_port = htons ( BOOTPS_PORT ); + + return 0; +} + +/** + * Handle received packet during DHCP discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + * @v server_id DHCP server ID + */ +static void dhcp_discovery_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { + struct in_addr ip; + char vci[9]; /* "PXEClient" */ + int vci_len; + int has_pxeclient; + int8_t priority = 0; + uint8_t no_pxedhcp = 0; + unsigned long elapsed; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + + /* Identify offered IP address */ + ip = dhcppkt->dhcphdr->yiaddr; + if ( ip.s_addr ) + DBGC ( dhcp, " for %s", inet_ntoa ( ip ) ); + + /* Identify "PXEClient" vendor class */ + vci_len = dhcppkt_fetch ( dhcppkt, DHCP_VENDOR_CLASS_ID, + vci, sizeof ( vci ) ); + has_pxeclient = ( ( vci_len >= ( int ) sizeof ( vci ) ) && + ( strncmp ( "PXEClient", vci, sizeof (vci) ) == 0 )); + if ( has_pxeclient ) { + DBGC ( dhcp, "%s", + ( dhcp_has_pxeopts ( dhcppkt ) ? " pxe" : " proxy" ) ); + } + + /* Identify priority */ + dhcppkt_fetch ( dhcppkt, DHCP_EB_PRIORITY, &priority, + sizeof ( priority ) ); + if ( priority ) + DBGC ( dhcp, " pri %d", priority ); + + /* Identify ignore-PXE flag */ + dhcppkt_fetch ( dhcppkt, DHCP_EB_NO_PXEDHCP, &no_pxedhcp, + sizeof ( no_pxedhcp ) ); + if ( no_pxedhcp ) + DBGC ( dhcp, " nopxe" ); + DBGC ( dhcp, "\n" ); + + /* Select as DHCP offer, if applicable */ + if ( ip.s_addr && ( peer->sin_port == htons ( BOOTPS_PORT ) ) && + ( ( msgtype == DHCPOFFER ) || ( ! msgtype /* BOOTP */ ) ) && + ( priority >= dhcp->priority ) ) { + dhcp->offer = ip; + dhcp->server = server_id; + dhcp->priority = priority; + dhcp->no_pxedhcp = no_pxedhcp; + } + + /* Select as ProxyDHCP offer, if applicable */ + if ( server_id.s_addr && has_pxeclient && + ( priority >= dhcp->proxy_priority ) ) { + dhcppkt_put ( dhcp->proxy_offer ); + dhcp->proxy_server = server_id; + dhcp->proxy_offer = dhcppkt_get ( dhcppkt ); + dhcp->proxy_priority = priority; + } + + /* We can exit the discovery state when we have a valid + * DHCPOFFER, and either: + * + * o The DHCPOFFER instructs us to ignore ProxyDHCPOFFERs, or + * o We have a valid ProxyDHCPOFFER, or + * o We have allowed sufficient time for ProxyDHCPOFFERs. + */ + + /* If we don't yet have a DHCPOFFER, do nothing */ + if ( ! dhcp->offer.s_addr ) + return; + + /* If we can't yet transition to DHCPREQUEST, do nothing */ + elapsed = ( currticks() - dhcp->start ); + if ( ! ( dhcp->no_pxedhcp || dhcp->proxy_offer || + ( elapsed > PROXYDHCP_MAX_TIMEOUT ) ) ) + return; + + /* Transition to DHCPREQUEST */ + dhcp_set_state ( dhcp, &dhcp_state_request ); +} + +/** + * Handle timer expiry during DHCP discovery + * + * @v dhcp DHCP session + */ +static void dhcp_discovery_expired ( struct dhcp_session *dhcp ) { + unsigned long elapsed = ( currticks() - dhcp->start ); + + /* Give up waiting for ProxyDHCP before we reach the failure point */ + if ( dhcp->offer.s_addr && ( elapsed > PROXYDHCP_MAX_TIMEOUT ) ) { + dhcp_set_state ( dhcp, &dhcp_state_request ); + return; + } + + /* Otherwise, retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** DHCP discovery state operations */ +static struct dhcp_session_state dhcp_state_discover = { + .name = "discovery", + .tx = dhcp_discovery_tx, + .rx = dhcp_discovery_rx, + .expired = dhcp_discovery_expired, + .tx_msgtype = DHCPDISCOVER, + .apply_min_timeout = 1, +}; + +/** + * Construct transmitted packet for DHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_request_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ) { + int rc; + + DBGC ( dhcp, "DHCP %p DHCPREQUEST to %s:%d", + dhcp, inet_ntoa ( dhcp->server ), BOOTPS_PORT ); + DBGC ( dhcp, " for %s\n", inet_ntoa ( dhcp->offer ) ); + + /* Set server ID */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &dhcp->server, + sizeof ( dhcp->server ) ) ) != 0 ) + return rc; + + /* Set requested IP address */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_REQUESTED_ADDRESS, + &dhcp->offer, + sizeof ( dhcp->offer ) ) ) != 0 ) + return rc; + + /* Set server address */ + peer->sin_addr.s_addr = INADDR_BROADCAST; + peer->sin_port = htons ( BOOTPS_PORT ); + + return 0; +} + +/** + * Handle received packet during DHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + * @v server_id DHCP server ID + */ +static void dhcp_request_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { + struct in_addr ip; + struct settings *parent; + struct settings *settings; + int rc; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + + /* Identify leased IP address */ + ip = dhcppkt->dhcphdr->yiaddr; + if ( ip.s_addr ) + DBGC ( dhcp, " for %s", inet_ntoa ( ip ) ); + DBGC ( dhcp, "\n" ); + + /* Filter out unacceptable responses */ + if ( peer->sin_port != htons ( BOOTPS_PORT ) ) + return; + if ( msgtype /* BOOTP */ && ( msgtype != DHCPACK ) ) + return; + if ( server_id.s_addr != dhcp->server.s_addr ) + return; + if ( ip.s_addr != dhcp->offer.s_addr ) + return; + + /* Record assigned address */ + dhcp->local.sin_addr = ip; + + /* Register settings */ + parent = netdev_settings ( dhcp->netdev ); + settings = &dhcppkt->settings; + if ( ( rc = register_settings ( settings, parent, + DHCP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not register settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + + /* Perform ProxyDHCP if applicable */ + if ( dhcp->proxy_offer /* Have ProxyDHCP offer */ && + ( ! dhcp->no_pxedhcp ) /* ProxyDHCP not disabled */ ) { + if ( dhcp_has_pxeopts ( dhcp->proxy_offer ) ) { + /* PXE options already present; register settings + * without performing a ProxyDHCPREQUEST + */ + settings = &dhcp->proxy_offer->settings; + if ( ( rc = register_settings ( settings, NULL, + PROXYDHCP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not register " + "proxy settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + } else { + /* PXE options not present; use a ProxyDHCPREQUEST */ + dhcp_set_state ( dhcp, &dhcp_state_proxy ); + return; + } + } + + /* Terminate DHCP */ + dhcp_finished ( dhcp, 0 ); +} + +/** + * Handle timer expiry during DHCP discovery + * + * @v dhcp DHCP session + */ +static void dhcp_request_expired ( struct dhcp_session *dhcp ) { + + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** DHCP request state operations */ +static struct dhcp_session_state dhcp_state_request = { + .name = "request", + .tx = dhcp_request_tx, + .rx = dhcp_request_rx, + .expired = dhcp_request_expired, + .tx_msgtype = DHCPREQUEST, + .apply_min_timeout = 0, +}; + +/** + * Construct transmitted packet for ProxyDHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_proxy_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ) { + int rc; + + DBGC ( dhcp, "DHCP %p ProxyDHCP REQUEST to %s\n", dhcp, + inet_ntoa ( dhcp->proxy_server ) ); + + /* Set server ID */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &dhcp->proxy_server, + sizeof ( dhcp->proxy_server ) ) ) != 0 ) + return rc; + + /* Set server address */ + peer->sin_addr = dhcp->proxy_server; + peer->sin_port = htons ( PXE_PORT ); + + return 0; +} + +/** + * Handle received packet during ProxyDHCP request + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + * @v server_id DHCP server ID + */ +static void dhcp_proxy_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { + struct settings *settings = &dhcppkt->settings; + int rc; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + DBGC ( dhcp, "\n" ); + + /* Filter out unacceptable responses */ + if ( peer->sin_port != ntohs ( PXE_PORT ) ) + return; + if ( ( msgtype != DHCPOFFER ) && ( msgtype != DHCPACK ) ) + return; + if ( server_id.s_addr /* Linux PXE server omits server ID */ && + ( server_id.s_addr != dhcp->proxy_server.s_addr ) ) + return; + + /* Register settings */ + if ( ( rc = register_settings ( settings, NULL, + PROXYDHCP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not register proxy settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + + /* Terminate DHCP */ + dhcp_finished ( dhcp, 0 ); +} + +/** + * Handle timer expiry during ProxyDHCP request + * + * @v dhcp DHCP session + */ +static void dhcp_proxy_expired ( struct dhcp_session *dhcp ) { + unsigned long elapsed = ( currticks() - dhcp->start ); + + /* Give up waiting for ProxyDHCP before we reach the failure point */ + if ( elapsed > PROXYDHCP_MAX_TIMEOUT ) { + dhcp_finished ( dhcp, 0 ); + return; + } + + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** ProxyDHCP request state operations */ +static struct dhcp_session_state dhcp_state_proxy = { + .name = "ProxyDHCP", + .tx = dhcp_proxy_tx, + .rx = dhcp_proxy_rx, + .expired = dhcp_proxy_expired, + .tx_msgtype = DHCPREQUEST, + .apply_min_timeout = 0, +}; + +/** + * Construct transmitted packet for PXE Boot Server Discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer Destination address + */ +static int dhcp_pxebs_tx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer ) { + struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 }; + int rc; + + /* Set server address */ + peer->sin_addr = *(dhcp->pxe_attempt); + peer->sin_port = ( ( peer->sin_addr.s_addr == INADDR_BROADCAST ) ? + htons ( BOOTPS_PORT ) : htons ( PXE_PORT ) ); + + DBGC ( dhcp, "DHCP %p PXEBS REQUEST to %s:%d for type %d\n", + dhcp, inet_ntoa ( peer->sin_addr ), ntohs ( peer->sin_port ), + le16_to_cpu ( dhcp->pxe_type ) ); + + /* Set boot menu item */ + menu_item.type = dhcp->pxe_type; + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, + &menu_item, sizeof ( menu_item ) ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Check to see if PXE Boot Server address is acceptable + * + * @v dhcp DHCP session + * @v bs Boot Server address + * @ret accept Boot Server is acceptable + */ +static int dhcp_pxebs_accept ( struct dhcp_session *dhcp, + struct in_addr bs ) { + struct in_addr *accept; + + /* Accept if we have no acceptance filter */ + if ( ! dhcp->pxe_accept ) + return 1; + + /* Scan through acceptance list */ + for ( accept = dhcp->pxe_accept ; accept->s_addr ; accept++ ) { + if ( accept->s_addr == bs.s_addr ) + return 1; + } + + DBGC ( dhcp, "DHCP %p rejecting server %s\n", + dhcp, inet_ntoa ( bs ) ); + return 0; +} + +/** + * Handle received packet during PXE Boot Server Discovery + * + * @v dhcp DHCP session + * @v dhcppkt DHCP packet + * @v peer DHCP server address + * @v msgtype DHCP message type + * @v server_id DHCP server ID + */ +static void dhcp_pxebs_rx ( struct dhcp_session *dhcp, + struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id ) { + struct dhcp_pxe_boot_menu_item menu_item = { 0, 0 }; + int rc; + + DBGC ( dhcp, "DHCP %p %s from %s:%d", dhcp, + dhcp_msgtype_name ( msgtype ), inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + if ( server_id.s_addr != peer->sin_addr.s_addr ) + DBGC ( dhcp, " (%s)", inet_ntoa ( server_id ) ); + + /* Identify boot menu item */ + dhcppkt_fetch ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, + &menu_item, sizeof ( menu_item ) ); + if ( menu_item.type ) + DBGC ( dhcp, " for type %d", ntohs ( menu_item.type ) ); + DBGC ( dhcp, "\n" ); + + /* Filter out unacceptable responses */ + if ( ( peer->sin_port != htons ( BOOTPS_PORT ) ) && + ( peer->sin_port != htons ( PXE_PORT ) ) ) + return; + if ( msgtype != DHCPACK ) + return; + if ( menu_item.type != dhcp->pxe_type ) + return; + if ( ! dhcp_pxebs_accept ( dhcp, ( server_id.s_addr ? + server_id : peer->sin_addr ) ) ) + return; + + /* Register settings */ + if ( ( rc = register_settings ( &dhcppkt->settings, NULL, + PXEBS_SETTINGS_NAME ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not register settings: %s\n", + dhcp, strerror ( rc ) ); + dhcp_finished ( dhcp, rc ); + return; + } + + /* Terminate DHCP */ + dhcp_finished ( dhcp, 0 ); +} + +/** + * Handle timer expiry during PXE Boot Server Discovery + * + * @v dhcp DHCP session + */ +static void dhcp_pxebs_expired ( struct dhcp_session *dhcp ) { + unsigned long elapsed = ( currticks() - dhcp->start ); + + /* Give up waiting before we reach the failure point, and fail + * over to the next server in the attempt list + */ + if ( elapsed > PXEBS_MAX_TIMEOUT ) { + dhcp->pxe_attempt++; + if ( dhcp->pxe_attempt->s_addr ) { + dhcp_set_state ( dhcp, &dhcp_state_pxebs ); + return; + } else { + dhcp_finished ( dhcp, -ETIMEDOUT ); + return; + } + } + + /* Retransmit current packet */ + dhcp_tx ( dhcp ); +} + +/** PXE Boot Server Discovery state operations */ +static struct dhcp_session_state dhcp_state_pxebs = { + .name = "PXEBS", + .tx = dhcp_pxebs_tx, + .rx = dhcp_pxebs_rx, + .expired = dhcp_pxebs_expired, + .tx_msgtype = DHCPREQUEST, + .apply_min_timeout = 1, +}; + +/**************************************************************************** + * + * Packet construction + * + */ + +/** + * Create a DHCP packet + * + * @v dhcppkt DHCP packet structure to fill in + * @v netdev Network device + * @v msgtype DHCP message type + * @v xid Transaction ID (in network-endian order) + * @v options Initial options to include (or NULL) + * @v options_len Length of initial options + * @v data Buffer for DHCP packet + * @v max_len Size of DHCP packet buffer + * @ret rc Return status code + * + * Creates a DHCP packet in the specified buffer, and initialise a + * DHCP packet structure. + */ +int dhcp_create_packet ( struct dhcp_packet *dhcppkt, + struct net_device *netdev, uint8_t msgtype, + uint32_t xid, const void *options, size_t options_len, + void *data, size_t max_len ) { + struct dhcphdr *dhcphdr = data; + int rc; + + /* Sanity check */ + if ( max_len < ( sizeof ( *dhcphdr ) + options_len ) ) + return -ENOSPC; + + /* Initialise DHCP packet content */ + memset ( dhcphdr, 0, max_len ); + dhcphdr->xid = xid; + dhcphdr->magic = htonl ( DHCP_MAGIC_COOKIE ); + dhcphdr->htype = ntohs ( netdev->ll_protocol->ll_proto ); + dhcphdr->op = dhcp_op[msgtype]; + dhcphdr->hlen = netdev->ll_protocol->ll_addr_len; + memcpy ( dhcphdr->chaddr, netdev->ll_addr, + netdev->ll_protocol->ll_addr_len ); + memcpy ( dhcphdr->options, options, options_len ); + + /* If the local link-layer address functions only as a name + * (i.e. cannot be used as a destination address), then + * request broadcast responses. + */ + if ( netdev->ll_protocol->flags & LL_NAME_ONLY ) + dhcphdr->flags |= htons ( BOOTP_FL_BROADCAST ); + + /* If the network device already has an IPv4 address then + * unicast responses from the DHCP server may be rejected, so + * request broadcast responses. + */ + if ( ipv4_has_any_addr ( netdev ) ) + dhcphdr->flags |= htons ( BOOTP_FL_BROADCAST ); + + /* Initialise DHCP packet structure */ + memset ( dhcppkt, 0, sizeof ( *dhcppkt ) ); + dhcppkt_init ( dhcppkt, data, max_len ); + + /* Set DHCP_MESSAGE_TYPE option */ + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_MESSAGE_TYPE, + &msgtype, sizeof ( msgtype ) ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Create DHCP request packet + * + * @v dhcppkt DHCP packet structure to fill in + * @v netdev Network device + * @v msgtype DHCP message type + * @v xid Transaction ID (in network-endian order) + * @v ciaddr Client IP address + * @v data Buffer for DHCP packet + * @v max_len Size of DHCP packet buffer + * @ret rc Return status code + * + * Creates a DHCP request packet in the specified buffer, and + * initialise a DHCP packet structure. + */ +int dhcp_create_request ( struct dhcp_packet *dhcppkt, + struct net_device *netdev, unsigned int msgtype, + uint32_t xid, struct in_addr ciaddr, + void *data, size_t max_len ) { + struct dhcp_netdev_desc dhcp_desc; + struct dhcp_client_id client_id; + struct dhcp_client_uuid client_uuid; + uint8_t *dhcp_features; + size_t dhcp_features_len; + size_t ll_addr_len; + void *user_class; + ssize_t len; + int rc; + + /* Create DHCP packet */ + if ( ( rc = dhcp_create_packet ( dhcppkt, netdev, msgtype, xid, + dhcp_request_options_data, + sizeof ( dhcp_request_options_data ), + data, max_len ) ) != 0 ) { + DBG ( "DHCP could not create DHCP packet: %s\n", + strerror ( rc ) ); + goto err_create_packet; + } + + /* Set client IP address */ + dhcppkt->dhcphdr->ciaddr = ciaddr; + + /* Add options to identify the feature list */ + dhcp_features = table_start ( DHCP_FEATURES ); + dhcp_features_len = table_num_entries ( DHCP_FEATURES ); + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_EB_ENCAP, dhcp_features, + dhcp_features_len ) ) != 0 ) { + DBG ( "DHCP could not set features list option: %s\n", + strerror ( rc ) ); + goto err_store_features; + } + + /* Add options to identify the network device */ + fetch_raw_setting ( netdev_settings ( netdev ), &busid_setting, + &dhcp_desc, sizeof ( dhcp_desc ) ); + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_EB_BUS_ID, &dhcp_desc, + sizeof ( dhcp_desc ) ) ) != 0 ) { + DBG ( "DHCP could not set bus ID option: %s\n", + strerror ( rc ) ); + goto err_store_busid; + } + + /* Add DHCP client identifier. Required for Infiniband, and + * doesn't hurt other link layers. + */ + client_id.ll_proto = ntohs ( netdev->ll_protocol->ll_proto ); + ll_addr_len = netdev->ll_protocol->ll_addr_len; + assert ( ll_addr_len <= sizeof ( client_id.ll_addr ) ); + memcpy ( client_id.ll_addr, netdev->ll_addr, ll_addr_len ); + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_CLIENT_ID, &client_id, + ( ll_addr_len + 1 ) ) ) != 0 ) { + DBG ( "DHCP could not set client ID: %s\n", + strerror ( rc ) ); + goto err_store_client_id; + } + + /* Add client UUID, if we have one. Required for PXE. The + * PXE spec does not specify a byte ordering for UUIDs, but + * RFC4578 suggests that it follows the EFI spec, in which the + * first three fields are little-endian. + */ + client_uuid.type = DHCP_CLIENT_UUID_TYPE; + if ( ( len = fetch_uuid_setting ( NULL, &uuid_setting, + &client_uuid.uuid ) ) >= 0 ) { + uuid_mangle ( &client_uuid.uuid ); + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_CLIENT_UUID, + &client_uuid, + sizeof ( client_uuid ) ) ) != 0 ) { + DBG ( "DHCP could not set client UUID: %s\n", + strerror ( rc ) ); + goto err_store_client_uuid; + } + } + + /* Add user class, if we have one. */ + if ( ( len = fetch_raw_setting_copy ( NULL, &user_class_setting, + &user_class ) ) >= 0 ) { + if ( ( rc = dhcppkt_store ( dhcppkt, DHCP_USER_CLASS_ID, + user_class, len ) ) != 0 ) { + DBG ( "DHCP could not set user class: %s\n", + strerror ( rc ) ); + goto err_store_user_class; + } + } + + err_store_user_class: + free ( user_class ); + err_store_client_uuid: + err_store_client_id: + err_store_busid: + err_store_features: + err_create_packet: + return rc; +} + +/**************************************************************************** + * + * Data transfer interface + * + */ + +/** + * Transmit DHCP request + * + * @v dhcp DHCP session + * @ret rc Return status code + */ +static int dhcp_tx ( struct dhcp_session *dhcp ) { + static struct sockaddr_in peer = { + .sin_family = AF_INET, + }; + struct xfer_metadata meta = { + .netdev = dhcp->netdev, + .src = ( struct sockaddr * ) &dhcp->local, + .dest = ( struct sockaddr * ) &peer, + }; + struct io_buffer *iobuf; + uint8_t msgtype = dhcp->state->tx_msgtype; + struct dhcp_packet dhcppkt; + int rc; + + /* Start retry timer. Do this first so that failures to + * transmit will be retried. + */ + start_timer ( &dhcp->timer ); + + /* Allocate buffer for packet */ + iobuf = xfer_alloc_iob ( &dhcp->xfer, DHCP_MIN_LEN ); + if ( ! iobuf ) + return -ENOMEM; + + /* Create basic DHCP packet in temporary buffer */ + if ( ( rc = dhcp_create_request ( &dhcppkt, dhcp->netdev, msgtype, + dhcp->xid, dhcp->local.sin_addr, + iobuf->data, + iob_tailroom ( iobuf ) ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n", + dhcp, strerror ( rc ) ); + goto done; + } + + /* (Ab)use the "secs" field to convey metadata about the DHCP + * session state into packet traces. Useful for extracting + * debug information from non-debug builds. + */ + dhcppkt.dhcphdr->secs = htons ( ( ++(dhcp->count) << 2 ) | + ( dhcp->offer.s_addr ? 0x02 : 0 ) | + ( dhcp->proxy_offer ? 0x01 : 0 ) ); + + /* Fill in packet based on current state */ + if ( ( rc = dhcp->state->tx ( dhcp, &dhcppkt, &peer ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not fill DHCP request: %s\n", + dhcp, strerror ( rc ) ); + goto done; + } + + /* Transmit the packet */ + iob_put ( iobuf, dhcppkt_len ( &dhcppkt ) ); + if ( ( rc = xfer_deliver ( &dhcp->xfer, iob_disown ( iobuf ), + &meta ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not transmit UDP packet: %s\n", + dhcp, strerror ( rc ) ); + goto done; + } + + done: + free_iob ( iobuf ); + return rc; +} + +/** + * Receive new data + * + * @v dhcp DHCP session + * @v iobuf I/O buffer + * @v meta Transfer metadata + * @ret rc Return status code + */ +static int dhcp_deliver ( struct dhcp_session *dhcp, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct net_device *netdev = dhcp->netdev; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + struct sockaddr_in *peer; + size_t data_len; + struct dhcp_packet *dhcppkt; + struct dhcphdr *dhcphdr; + uint8_t msgtype = 0; + struct in_addr server_id = { 0 }; + int rc = 0; + + /* Sanity checks */ + if ( ! meta->src ) { + DBGC ( dhcp, "DHCP %p received packet without source port\n", + dhcp ); + rc = -EINVAL; + goto err_no_src; + } + peer = ( struct sockaddr_in * ) meta->src; + + /* Create a DHCP packet containing the I/O buffer contents. + * Whilst we could just use the original buffer in situ, that + * would waste the unused space in the packet buffer, and also + * waste a relatively scarce fully-aligned I/O buffer. + */ + data_len = iob_len ( iobuf ); + dhcppkt = zalloc ( sizeof ( *dhcppkt ) + data_len ); + if ( ! dhcppkt ) { + rc = -ENOMEM; + goto err_alloc_dhcppkt; + } + dhcphdr = ( ( ( void * ) dhcppkt ) + sizeof ( *dhcppkt ) ); + memcpy ( dhcphdr, iobuf->data, data_len ); + dhcppkt_init ( dhcppkt, dhcphdr, data_len ); + + /* Identify message type */ + dhcppkt_fetch ( dhcppkt, DHCP_MESSAGE_TYPE, &msgtype, + sizeof ( msgtype ) ); + + /* Identify server ID */ + dhcppkt_fetch ( dhcppkt, DHCP_SERVER_IDENTIFIER, + &server_id, sizeof ( server_id ) ); + + /* Check for matching transaction ID */ + if ( dhcphdr->xid != dhcp->xid ) { + DBGC ( dhcp, "DHCP %p %s from %s:%d has bad transaction " + "ID\n", dhcp, dhcp_msgtype_name ( msgtype ), + inet_ntoa ( peer->sin_addr ), + ntohs ( peer->sin_port ) ); + rc = -EINVAL; + goto err_xid; + }; + + /* Check for matching client hardware address */ + if ( memcmp ( dhcphdr->chaddr, netdev->ll_addr, + ll_protocol->ll_addr_len ) != 0 ) { + DBGC ( dhcp, "DHCP %p %s from %s:%d has bad chaddr %s\n", + dhcp, dhcp_msgtype_name ( msgtype ), + inet_ntoa ( peer->sin_addr ), ntohs ( peer->sin_port ), + ll_protocol->ntoa ( dhcphdr->chaddr ) ); + rc = -EINVAL; + goto err_chaddr; + } + + /* Handle packet based on current state */ + dhcp->state->rx ( dhcp, dhcppkt, peer, msgtype, server_id ); + + err_chaddr: + err_xid: + dhcppkt_put ( dhcppkt ); + err_alloc_dhcppkt: + err_no_src: + free_iob ( iobuf ); + return rc; +} + +/** DHCP data transfer interface operations */ +static struct interface_operation dhcp_xfer_operations[] = { + INTF_OP ( xfer_deliver, struct dhcp_session *, dhcp_deliver ), +}; + +/** DHCP data transfer interface descriptor */ +static struct interface_descriptor dhcp_xfer_desc = + INTF_DESC ( struct dhcp_session, xfer, dhcp_xfer_operations ); + +/** + * Handle DHCP retry timer expiry + * + * @v timer DHCP retry timer + * @v fail Failure indicator + */ +static void dhcp_timer_expired ( struct retry_timer *timer, int fail ) { + struct dhcp_session *dhcp = + container_of ( timer, struct dhcp_session, timer ); + + /* If we have failed, terminate DHCP */ + if ( fail ) { + dhcp_finished ( dhcp, -ETIMEDOUT ); + return; + } + + /* Handle timer expiry based on current state */ + dhcp->state->expired ( dhcp ); +} + +/**************************************************************************** + * + * Job control interface + * + */ + +/** DHCP job control interface operations */ +static struct interface_operation dhcp_job_op[] = { + INTF_OP ( intf_close, struct dhcp_session *, dhcp_finished ), +}; + +/** DHCP job control interface descriptor */ +static struct interface_descriptor dhcp_job_desc = + INTF_DESC ( struct dhcp_session, job, dhcp_job_op ); + +/**************************************************************************** + * + * Instantiators + * + */ + +/** + * DHCP peer address for socket opening + * + * This is a dummy address; the only useful portion is the socket + * family (so that we get a UDP connection). The DHCP client will set + * the IP address and source port explicitly on each transmission. + */ +static struct sockaddr dhcp_peer = { + .sa_family = AF_INET, +}; + +/** + * Start DHCP state machine on a network device + * + * @v job Job control interface + * @v netdev Network device + * @ret rc Return status code + * + * Starts DHCP on the specified network device. If successful, the + * DHCPACK (and ProxyDHCPACK, if applicable) will be registered as + * option sources. + */ +int start_dhcp ( struct interface *job, struct net_device *netdev ) { + struct dhcp_session *dhcp; + int rc; + + /* Allocate and initialise structure */ + dhcp = zalloc ( sizeof ( *dhcp ) ); + if ( ! dhcp ) + return -ENOMEM; + ref_init ( &dhcp->refcnt, dhcp_free ); + intf_init ( &dhcp->job, &dhcp_job_desc, &dhcp->refcnt ); + intf_init ( &dhcp->xfer, &dhcp_xfer_desc, &dhcp->refcnt ); + timer_init ( &dhcp->timer, dhcp_timer_expired, &dhcp->refcnt ); + dhcp->netdev = netdev_get ( netdev ); + dhcp->local.sin_family = AF_INET; + dhcp->local.sin_port = htons ( BOOTPC_PORT ); + dhcp->xid = random(); + + /* Store DHCP transaction ID for fakedhcp code */ + dhcp_last_xid = dhcp->xid; + + /* Instantiate child objects and attach to our interfaces */ + if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, &dhcp_peer, + ( struct sockaddr * ) &dhcp->local ) ) != 0 ) + goto err; + + /* Enter DHCPDISCOVER state */ + dhcp_set_state ( dhcp, &dhcp_state_discover ); + + /* Attach parent interface, mortalise self, and return */ + intf_plug_plug ( &dhcp->job, job ); + ref_put ( &dhcp->refcnt ); + return 0; + + err: + dhcp_finished ( dhcp, rc ); + ref_put ( &dhcp->refcnt ); + return rc; +} + +/** + * Retrieve list of PXE boot servers for a given server type + * + * @v dhcp DHCP session + * @v raw DHCP PXE boot server list + * @v raw_len Length of DHCP PXE boot server list + * @v ip IP address list to fill in + * + * The caller must ensure that the IP address list has sufficient + * space. + */ +static void pxebs_list ( struct dhcp_session *dhcp, void *raw, + size_t raw_len, struct in_addr *ip ) { + struct dhcp_pxe_boot_server *server = raw; + size_t server_len; + unsigned int i; + + while ( raw_len ) { + if ( raw_len < sizeof ( *server ) ) { + DBGC ( dhcp, "DHCP %p malformed PXE server list\n", + dhcp ); + break; + } + server_len = offsetof ( typeof ( *server ), + ip[ server->num_ip ] ); + if ( raw_len < server_len ) { + DBGC ( dhcp, "DHCP %p malformed PXE server list\n", + dhcp ); + break; + } + if ( server->type == dhcp->pxe_type ) { + for ( i = 0 ; i < server->num_ip ; i++ ) + *(ip++) = server->ip[i]; + } + server = ( ( ( void * ) server ) + server_len ); + raw_len -= server_len; + } +} + +/** + * Start PXE Boot Server Discovery on a network device + * + * @v job Job control interface + * @v netdev Network device + * @v pxe_type PXE server type + * @ret rc Return status code + * + * Starts PXE Boot Server Discovery on the specified network device. + * If successful, the Boot Server ACK will be registered as an option + * source. + */ +int start_pxebs ( struct interface *job, struct net_device *netdev, + unsigned int pxe_type ) { + struct setting pxe_discovery_control_setting = + { .tag = DHCP_PXE_DISCOVERY_CONTROL }; + struct setting pxe_boot_servers_setting = + { .tag = DHCP_PXE_BOOT_SERVERS }; + struct setting pxe_boot_server_mcast_setting = + { .tag = DHCP_PXE_BOOT_SERVER_MCAST }; + ssize_t pxebs_list_len; + struct dhcp_session *dhcp; + struct in_addr *ip; + unsigned int pxe_discovery_control; + int rc; + + /* Get upper bound for PXE boot server IP address list */ + pxebs_list_len = fetch_raw_setting ( NULL, &pxe_boot_servers_setting, + NULL, 0 ); + if ( pxebs_list_len < 0 ) + pxebs_list_len = 0; + + /* Allocate and initialise structure */ + dhcp = zalloc ( sizeof ( *dhcp ) + sizeof ( *ip ) /* mcast */ + + sizeof ( *ip ) /* bcast */ + pxebs_list_len + + sizeof ( *ip ) /* terminator */ ); + if ( ! dhcp ) + return -ENOMEM; + ref_init ( &dhcp->refcnt, dhcp_free ); + intf_init ( &dhcp->job, &dhcp_job_desc, &dhcp->refcnt ); + intf_init ( &dhcp->xfer, &dhcp_xfer_desc, &dhcp->refcnt ); + timer_init ( &dhcp->timer, dhcp_timer_expired, &dhcp->refcnt ); + dhcp->netdev = netdev_get ( netdev ); + dhcp->local.sin_family = AF_INET; + fetch_ipv4_setting ( netdev_settings ( netdev ), &ip_setting, + &dhcp->local.sin_addr ); + dhcp->local.sin_port = htons ( BOOTPC_PORT ); + dhcp->pxe_type = cpu_to_le16 ( pxe_type ); + + /* Construct PXE boot server IP address lists */ + pxe_discovery_control = + fetch_uintz_setting ( NULL, &pxe_discovery_control_setting ); + ip = ( ( ( void * ) dhcp ) + sizeof ( *dhcp ) ); + dhcp->pxe_attempt = ip; + if ( ! ( pxe_discovery_control & PXEBS_NO_MULTICAST ) ) { + fetch_ipv4_setting ( NULL, &pxe_boot_server_mcast_setting, ip); + if ( ip->s_addr ) + ip++; + } + if ( ! ( pxe_discovery_control & PXEBS_NO_BROADCAST ) ) + (ip++)->s_addr = INADDR_BROADCAST; + if ( pxe_discovery_control & PXEBS_NO_UNKNOWN_SERVERS ) + dhcp->pxe_accept = ip; + if ( pxebs_list_len ) { + uint8_t buf[pxebs_list_len]; + + fetch_raw_setting ( NULL, &pxe_boot_servers_setting, + buf, sizeof ( buf ) ); + pxebs_list ( dhcp, buf, sizeof ( buf ), ip ); + } + if ( ! dhcp->pxe_attempt->s_addr ) { + DBGC ( dhcp, "DHCP %p has no PXE boot servers for type %04x\n", + dhcp, pxe_type ); + rc = -EINVAL; + goto err; + } + + /* Dump out PXE server lists */ + DBGC ( dhcp, "DHCP %p attempting", dhcp ); + for ( ip = dhcp->pxe_attempt ; ip->s_addr ; ip++ ) + DBGC ( dhcp, " %s", inet_ntoa ( *ip ) ); + DBGC ( dhcp, "\n" ); + if ( dhcp->pxe_accept ) { + DBGC ( dhcp, "DHCP %p accepting", dhcp ); + for ( ip = dhcp->pxe_accept ; ip->s_addr ; ip++ ) + DBGC ( dhcp, " %s", inet_ntoa ( *ip ) ); + DBGC ( dhcp, "\n" ); + } + + /* Instantiate child objects and attach to our interfaces */ + if ( ( rc = xfer_open_socket ( &dhcp->xfer, SOCK_DGRAM, &dhcp_peer, + ( struct sockaddr * ) &dhcp->local ) ) != 0 ) + goto err; + + /* Enter PXEBS state */ + dhcp_set_state ( dhcp, &dhcp_state_pxebs ); + + /* Attach parent interface, mortalise self, and return */ + intf_plug_plug ( &dhcp->job, job ); + ref_put ( &dhcp->refcnt ); + return 0; + + err: + dhcp_finished ( dhcp, rc ); + ref_put ( &dhcp->refcnt ); + return rc; +} + +/** DHCP network device configurator */ +struct net_device_configurator dhcp_configurator __net_device_configurator = { + .name = "dhcp", + .start = start_dhcp, +}; diff --git a/qemu/roms/ipxe/src/net/udp/dhcpv6.c b/qemu/roms/ipxe/src/net/udp/dhcpv6.c new file mode 100644 index 000000000..f7736d08e --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/dhcpv6.c @@ -0,0 +1,989 @@ +/* + * Copyright (C) 2013 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 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/interface.h> +#include <ipxe/xfer.h> +#include <ipxe/iobuf.h> +#include <ipxe/open.h> +#include <ipxe/netdevice.h> +#include <ipxe/settings.h> +#include <ipxe/retry.h> +#include <ipxe/timer.h> +#include <ipxe/in.h> +#include <ipxe/crc32.h> +#include <ipxe/errortab.h> +#include <ipxe/ipv6.h> +#include <ipxe/dhcpv6.h> + +/** @file + * + * Dynamic Host Configuration Protocol for IPv6 + * + */ + +/* Disambiguate the various error causes */ +#define EPROTO_UNSPECFAIL __einfo_error ( EINFO_EPROTO_UNSPECFAIL ) +#define EINFO_EPROTO_UNSPECFAIL \ + __einfo_uniqify ( EINFO_EPROTO, 1, "Unspecified server failure" ) +#define EPROTO_NOADDRSAVAIL __einfo_error ( EINFO_EPROTO_NOADDRSAVAIL ) +#define EINFO_EPROTO_NOADDRSAVAIL \ + __einfo_uniqify ( EINFO_EPROTO, 2, "No addresses available" ) +#define EPROTO_NOBINDING __einfo_error ( EINFO_EPROTO_NOBINDING ) +#define EINFO_EPROTO_NOBINDING \ + __einfo_uniqify ( EINFO_EPROTO, 3, "Client record unavailable" ) +#define EPROTO_NOTONLINK __einfo_error ( EINFO_EPROTO_NOTONLINK ) +#define EINFO_EPROTO_NOTONLINK \ + __einfo_uniqify ( EINFO_EPROTO, 4, "Prefix not on link" ) +#define EPROTO_USEMULTICAST __einfo_error ( EINFO_EPROTO_USEMULTICAST ) +#define EINFO_EPROTO_USEMULTICAST \ + __einfo_uniqify ( EINFO_EPROTO, 5, "Use multicast address" ) +#define EPROTO_STATUS( status ) \ + EUNIQ ( EINFO_EPROTO, ( (status) & 0x0f ), EPROTO_UNSPECFAIL, \ + EPROTO_NOADDRSAVAIL, EPROTO_NOBINDING, \ + EPROTO_NOTONLINK, EPROTO_USEMULTICAST ) + +/** Human-readable error messages */ +struct errortab dhcpv6_errors[] __errortab = { + __einfo_errortab ( EINFO_EPROTO_NOADDRSAVAIL ), +}; + +/**************************************************************************** + * + * DHCPv6 option lists + * + */ + +/** A DHCPv6 option list */ +struct dhcpv6_option_list { + /** Data buffer */ + const void *data; + /** Length of data buffer */ + size_t len; +}; + +/** + * Find DHCPv6 option + * + * @v options DHCPv6 option list + * @v code Option code + * @ret option DHCPv6 option, or NULL if not found + */ +static const union dhcpv6_any_option * +dhcpv6_option ( struct dhcpv6_option_list *options, unsigned int code ) { + const union dhcpv6_any_option *option = options->data; + size_t remaining = options->len; + size_t data_len; + + /* Scan through list of options */ + while ( remaining >= sizeof ( option->header ) ) { + + /* Calculate and validate option length */ + remaining -= sizeof ( option->header ); + data_len = ntohs ( option->header.len ); + if ( data_len > remaining ) { + /* Malformed option list */ + return NULL; + } + + /* Return if we have found the specified option */ + if ( option->header.code == htons ( code ) ) + return option; + + /* Otherwise, move to the next option */ + option = ( ( ( void * ) option->header.data ) + data_len ); + remaining -= data_len; + } + + return NULL; +} + +/** + * Check DHCPv6 client or server identifier + * + * @v options DHCPv6 option list + * @v code Option code + * @v expected Expected value + * @v len Length of expected value + * @ret rc Return status code + */ +static int dhcpv6_check_duid ( struct dhcpv6_option_list *options, + unsigned int code, const void *expected, + size_t len ) { + const union dhcpv6_any_option *option; + const struct dhcpv6_duid_option *duid; + + /* Find option */ + option = dhcpv6_option ( options, code ); + if ( ! option ) + return -ENOENT; + duid = &option->duid; + + /* Check option length */ + if ( ntohs ( duid->header.len ) != len ) + return -EINVAL; + + /* Compare option value */ + if ( memcmp ( duid->duid, expected, len ) != 0 ) + return -EINVAL; + + return 0; +} + +/** + * Get DHCPv6 status code + * + * @v options DHCPv6 option list + * @ret rc Return status code + */ +static int dhcpv6_status_code ( struct dhcpv6_option_list *options ) { + const union dhcpv6_any_option *option; + const struct dhcpv6_status_code_option *status_code; + unsigned int status; + + /* Find status code option, if present */ + option = dhcpv6_option ( options, DHCPV6_STATUS_CODE ); + if ( ! option ) { + /* Omitted status code should be treated as "success" */ + return 0; + } + status_code = &option->status_code; + + /* Sanity check */ + if ( ntohs ( status_code->header.len ) < + ( sizeof ( *status_code ) - sizeof ( status_code->header ) ) ) { + return -EINVAL; + } + + /* Calculate iPXE error code from DHCPv6 status code */ + status = ntohs ( status_code->status ); + return ( status ? -EPROTO_STATUS ( status ) : 0 ); +} + +/** + * Get DHCPv6 identity association address + * + * @v options DHCPv6 option list + * @v iaid Identity association ID + * @v address IPv6 address to fill in + * @ret rc Return status code + */ +static int dhcpv6_iaaddr ( struct dhcpv6_option_list *options, uint32_t iaid, + struct in6_addr *address ) { + const union dhcpv6_any_option *option; + const struct dhcpv6_ia_na_option *ia_na; + const struct dhcpv6_iaaddr_option *iaaddr; + struct dhcpv6_option_list suboptions; + size_t len; + int rc; + + /* Find identity association option, if present */ + option = dhcpv6_option ( options, DHCPV6_IA_NA ); + if ( ! option ) + return -ENOENT; + ia_na = &option->ia_na; + + /* Sanity check */ + len = ntohs ( ia_na->header.len ); + if ( len < ( sizeof ( *ia_na ) - sizeof ( ia_na->header ) ) ) + return -EINVAL; + + /* Check identity association ID */ + if ( ia_na->iaid != htonl ( iaid ) ) + return -EINVAL; + + /* Construct IA_NA sub-options list */ + suboptions.data = ia_na->options; + suboptions.len = ( len + sizeof ( ia_na->header ) - + offsetof ( typeof ( *ia_na ), options ) ); + + /* Check IA_NA status code */ + if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 ) + return rc; + + /* Find identity association address, if present */ + option = dhcpv6_option ( &suboptions, DHCPV6_IAADDR ); + if ( ! option ) + return -ENOENT; + iaaddr = &option->iaaddr; + + /* Sanity check */ + len = ntohs ( iaaddr->header.len ); + if ( len < ( sizeof ( *iaaddr ) - sizeof ( iaaddr->header ) ) ) + return -EINVAL; + + /* Construct IAADDR sub-options list */ + suboptions.data = iaaddr->options; + suboptions.len = ( len + sizeof ( iaaddr->header ) - + offsetof ( typeof ( *iaaddr ), options ) ); + + /* Check IAADDR status code */ + if ( ( rc = dhcpv6_status_code ( &suboptions ) ) != 0 ) + return rc; + + /* Extract IPv6 address */ + memcpy ( address, &iaaddr->address, sizeof ( *address ) ); + + return 0; +} + +/**************************************************************************** + * + * DHCPv6 settings blocks + * + */ + +/** A DHCPv6 settings block */ +struct dhcpv6_settings { + /** Reference count */ + struct refcnt refcnt; + /** Settings block */ + struct settings settings; + /** Option list */ + struct dhcpv6_option_list options; +}; + +/** + * Check applicability of DHCPv6 setting + * + * @v settings Settings block + * @v setting Setting + * @ret applies Setting applies within this settings block + */ +static int dhcpv6_applies ( struct settings *settings __unused, + const struct setting *setting ) { + + return ( setting->scope == &ipv6_scope ); +} + +/** + * Fetch value of DHCPv6 setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v data Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int dhcpv6_fetch ( struct settings *settings, + struct setting *setting, + void *data, size_t len ) { + struct dhcpv6_settings *dhcpv6set = + container_of ( settings, struct dhcpv6_settings, settings ); + const union dhcpv6_any_option *option; + size_t option_len; + + /* Find option */ + option = dhcpv6_option ( &dhcpv6set->options, setting->tag ); + if ( ! option ) + return -ENOENT; + + /* Copy option to data buffer */ + option_len = ntohs ( option->header.len ); + if ( len > option_len ) + len = option_len; + memcpy ( data, option->header.data, len ); + return option_len; +} + +/** DHCPv6 settings operations */ +static struct settings_operations dhcpv6_settings_operations = { + .applies = dhcpv6_applies, + .fetch = dhcpv6_fetch, +}; + +/** + * Register DHCPv6 options as network device settings + * + * @v options DHCPv6 option list + * @v parent Parent settings block + * @ret rc Return status code + */ +static int dhcpv6_register ( struct dhcpv6_option_list *options, + struct settings *parent ) { + struct dhcpv6_settings *dhcpv6set; + void *data; + size_t len; + int rc; + + /* Allocate and initialise structure */ + dhcpv6set = zalloc ( sizeof ( *dhcpv6set ) + options->len ); + if ( ! dhcpv6set ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &dhcpv6set->refcnt, NULL ); + settings_init ( &dhcpv6set->settings, &dhcpv6_settings_operations, + &dhcpv6set->refcnt, &ipv6_scope ); + data = ( ( ( void * ) dhcpv6set ) + sizeof ( *dhcpv6set ) ); + len = options->len; + memcpy ( data, options->data, len ); + dhcpv6set->options.data = data; + dhcpv6set->options.len = len; + + /* Register settings */ + if ( ( rc = register_settings ( &dhcpv6set->settings, parent, + DHCPV6_SETTINGS_NAME ) ) != 0 ) + goto err_register; + + err_register: + ref_put ( &dhcpv6set->refcnt ); + err_alloc: + return rc; +} + +/**************************************************************************** + * + * DHCPv6 protocol + * + */ + +/** Options to be requested */ +static uint16_t dhcpv6_requested_options[] = { + htons ( DHCPV6_DNS_SERVERS ), htons ( DHCPV6_DOMAIN_LIST ), + htons ( DHCPV6_BOOTFILE_URL ), htons ( DHCPV6_BOOTFILE_PARAM ), +}; + +/** + * Name a DHCPv6 packet type + * + * @v type DHCPv6 packet type + * @ret name DHCPv6 packet type name + */ +static __attribute__ (( unused )) const char * +dhcpv6_type_name ( unsigned int type ) { + static char buf[ 12 /* "UNKNOWN-xxx" + NUL */ ]; + + switch ( type ) { + case DHCPV6_SOLICIT: return "SOLICIT"; + case DHCPV6_ADVERTISE: return "ADVERTISE"; + case DHCPV6_REQUEST: return "REQUEST"; + case DHCPV6_REPLY: return "REPLY"; + case DHCPV6_INFORMATION_REQUEST: return "INFORMATION-REQUEST"; + default: + snprintf ( buf, sizeof ( buf ), "UNKNOWN-%d", type ); + return buf; + } +} + +/** A DHCPv6 session state */ +struct dhcpv6_session_state { + /** Current transmitted packet type */ + uint8_t tx_type; + /** Current expected received packet type */ + uint8_t rx_type; + /** Flags */ + uint8_t flags; + /** Next state (or NULL to terminate) */ + struct dhcpv6_session_state *next; +}; + +/** DHCPv6 session state flags */ +enum dhcpv6_session_state_flags { + /** Include identity association within request */ + DHCPV6_TX_IA_NA = 0x01, + /** Include leased IPv6 address within request */ + DHCPV6_TX_IAADDR = 0x02, + /** Record received server ID */ + DHCPV6_RX_RECORD_SERVER_ID = 0x04, + /** Record received IPv6 address */ + DHCPV6_RX_RECORD_IAADDR = 0x08, + /** Apply received IPv6 address */ + DHCPV6_RX_APPLY_IAADDR = 0x10, +}; + +/** DHCPv6 request state */ +static struct dhcpv6_session_state dhcpv6_request = { + .tx_type = DHCPV6_REQUEST, + .rx_type = DHCPV6_REPLY, + .flags = ( DHCPV6_TX_IA_NA | DHCPV6_TX_IAADDR | + DHCPV6_RX_RECORD_IAADDR | DHCPV6_RX_APPLY_IAADDR ), + .next = NULL, +}; + +/** DHCPv6 solicitation state */ +static struct dhcpv6_session_state dhcpv6_solicit = { + .tx_type = DHCPV6_SOLICIT, + .rx_type = DHCPV6_ADVERTISE, + .flags = ( DHCPV6_TX_IA_NA | DHCPV6_RX_RECORD_SERVER_ID | + DHCPV6_RX_RECORD_IAADDR ), + .next = &dhcpv6_request, +}; + +/** DHCPv6 information request state */ +static struct dhcpv6_session_state dhcpv6_information_request = { + .tx_type = DHCPV6_INFORMATION_REQUEST, + .rx_type = DHCPV6_REPLY, + .flags = 0, + .next = NULL, +}; + +/** A DHCPv6 session */ +struct dhcpv6_session { + /** Reference counter */ + struct refcnt refcnt; + /** Job control interface */ + struct interface job; + /** Data transfer interface */ + struct interface xfer; + + /** Network device being configured */ + struct net_device *netdev; + /** Transaction ID */ + uint8_t xid[3]; + /** Identity association ID */ + uint32_t iaid; + /** Start time (in ticks) */ + unsigned long start; + /** Client DUID */ + struct dhcpv6_duid_uuid client_duid; + /** Server DUID, if known */ + void *server_duid; + /** Server DUID length */ + size_t server_duid_len; + /** Leased IPv6 address */ + struct in6_addr lease; + + /** Retransmission timer */ + struct retry_timer timer; + + /** Current session state */ + struct dhcpv6_session_state *state; + /** Current timeout status code */ + int rc; +}; + +/** + * Free DHCPv6 session + * + * @v refcnt Reference count + */ +static void dhcpv6_free ( struct refcnt *refcnt ) { + struct dhcpv6_session *dhcpv6 = + container_of ( refcnt, struct dhcpv6_session, refcnt ); + + netdev_put ( dhcpv6->netdev ); + free ( dhcpv6->server_duid ); + free ( dhcpv6 ); +} + +/** + * Terminate DHCPv6 session + * + * @v dhcpv6 DHCPv6 session + * @v rc Reason for close + */ +static void dhcpv6_finished ( struct dhcpv6_session *dhcpv6, int rc ) { + + /* Stop timer */ + stop_timer ( &dhcpv6->timer ); + + /* Shut down interfaces */ + intf_shutdown ( &dhcpv6->xfer, rc ); + intf_shutdown ( &dhcpv6->job, rc ); +} + +/** + * Transition to new DHCPv6 session state + * + * @v dhcpv6 DHCPv6 session + * @v state New session state + */ +static void dhcpv6_set_state ( struct dhcpv6_session *dhcpv6, + struct dhcpv6_session_state *state ) { + + DBGC ( dhcpv6, "DHCPv6 %s entering %s state\n", dhcpv6->netdev->name, + dhcpv6_type_name ( state->tx_type ) ); + + /* Record state */ + dhcpv6->state = state; + + /* Default to -ETIMEDOUT if no more specific error is recorded */ + dhcpv6->rc = -ETIMEDOUT; + + /* Start timer to trigger transmission */ + start_timer_nodelay ( &dhcpv6->timer ); +} + +/** + * Get DHCPv6 user class + * + * @v data Data buffer + * @v len Length of data buffer + * @ret len Length of user class + */ +static size_t dhcpv6_user_class ( void *data, size_t len ) { + static const char default_user_class[4] = { 'i', 'P', 'X', 'E' }; + int actual_len; + + /* Fetch user-class setting, if defined */ + actual_len = fetch_raw_setting ( NULL, &user_class_setting, data, len ); + if ( actual_len >= 0 ) + return actual_len; + + /* Otherwise, use the default user class ("iPXE") */ + if ( len > sizeof ( default_user_class ) ) + len = sizeof ( default_user_class ); + memcpy ( data, default_user_class, len ); + return sizeof ( default_user_class ); +} + +/** + * Transmit current request + * + * @v dhcpv6 DHCPv6 session + * @ret rc Return status code + */ +static int dhcpv6_tx ( struct dhcpv6_session *dhcpv6 ) { + struct dhcpv6_duid_option *client_id; + struct dhcpv6_duid_option *server_id; + struct dhcpv6_ia_na_option *ia_na; + struct dhcpv6_iaaddr_option *iaaddr; + struct dhcpv6_option_request_option *option_request; + struct dhcpv6_user_class_option *user_class; + struct dhcpv6_elapsed_time_option *elapsed; + struct dhcpv6_header *dhcphdr; + struct io_buffer *iobuf; + size_t client_id_len; + size_t server_id_len; + size_t ia_na_len; + size_t option_request_len; + size_t user_class_string_len; + size_t user_class_len; + size_t elapsed_len; + size_t total_len; + int rc; + + /* Calculate lengths */ + client_id_len = ( sizeof ( *client_id ) + + sizeof ( dhcpv6->client_duid ) ); + server_id_len = ( dhcpv6->server_duid ? ( sizeof ( *server_id ) + + dhcpv6->server_duid_len ) :0); + if ( dhcpv6->state->flags & DHCPV6_TX_IA_NA ) { + ia_na_len = sizeof ( *ia_na ); + if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) + ia_na_len += sizeof ( *iaaddr ); + } else { + ia_na_len = 0; + } + option_request_len = ( sizeof ( *option_request ) + + sizeof ( dhcpv6_requested_options ) ); + user_class_string_len = dhcpv6_user_class ( NULL, 0 ); + user_class_len = ( sizeof ( *user_class ) + + sizeof ( user_class->user_class[0] ) + + user_class_string_len ); + elapsed_len = sizeof ( *elapsed ); + total_len = ( sizeof ( *dhcphdr ) + client_id_len + server_id_len + + ia_na_len + option_request_len + user_class_len + + elapsed_len ); + + /* Allocate packet */ + iobuf = xfer_alloc_iob ( &dhcpv6->xfer, total_len ); + if ( ! iobuf ) + return -ENOMEM; + + /* Construct header */ + dhcphdr = iob_put ( iobuf, sizeof ( *dhcphdr ) ); + dhcphdr->type = dhcpv6->state->tx_type; + memcpy ( dhcphdr->xid, dhcpv6->xid, sizeof ( dhcphdr->xid ) ); + + /* Construct client identifier */ + client_id = iob_put ( iobuf, client_id_len ); + client_id->header.code = htons ( DHCPV6_CLIENT_ID ); + client_id->header.len = htons ( client_id_len - + sizeof ( client_id->header ) ); + memcpy ( client_id->duid, &dhcpv6->client_duid, + sizeof ( dhcpv6->client_duid ) ); + + /* Construct server identifier, if applicable */ + if ( server_id_len ) { + server_id = iob_put ( iobuf, server_id_len ); + server_id->header.code = htons ( DHCPV6_SERVER_ID ); + server_id->header.len = htons ( server_id_len - + sizeof ( server_id->header ) ); + memcpy ( server_id->duid, dhcpv6->server_duid, + dhcpv6->server_duid_len ); + } + + /* Construct identity association, if applicable */ + if ( ia_na_len ) { + ia_na = iob_put ( iobuf, ia_na_len ); + ia_na->header.code = htons ( DHCPV6_IA_NA ); + ia_na->header.len = htons ( ia_na_len - + sizeof ( ia_na->header ) ); + ia_na->iaid = htonl ( dhcpv6->iaid ); + ia_na->renew = htonl ( 0 ); + ia_na->rebind = htonl ( 0 ); + if ( dhcpv6->state->flags & DHCPV6_TX_IAADDR ) { + iaaddr = ( ( void * ) ia_na->options ); + iaaddr->header.code = htons ( DHCPV6_IAADDR ); + iaaddr->header.len = htons ( sizeof ( *iaaddr ) - + sizeof ( iaaddr->header )); + memcpy ( &iaaddr->address, &dhcpv6->lease, + sizeof ( iaaddr->address ) ); + iaaddr->preferred = htonl ( 0 ); + iaaddr->valid = htonl ( 0 ); + } + } + + /* Construct option request */ + option_request = iob_put ( iobuf, option_request_len ); + option_request->header.code = htons ( DHCPV6_OPTION_REQUEST ); + option_request->header.len = htons ( option_request_len - + sizeof ( option_request->header )); + memcpy ( option_request->requested, dhcpv6_requested_options, + sizeof ( dhcpv6_requested_options ) ); + + /* Construct user class */ + user_class = iob_put ( iobuf, user_class_len ); + user_class->header.code = htons ( DHCPV6_USER_CLASS ); + user_class->header.len = htons ( user_class_len - + sizeof ( user_class->header ) ); + user_class->user_class[0].len = htons ( user_class_string_len ); + dhcpv6_user_class ( user_class->user_class[0].string, + user_class_string_len ); + + /* Construct elapsed time */ + elapsed = iob_put ( iobuf, elapsed_len ); + elapsed->header.code = htons ( DHCPV6_ELAPSED_TIME ); + elapsed->header.len = htons ( elapsed_len - + sizeof ( elapsed->header ) ); + elapsed->elapsed = htons ( ( ( currticks() - dhcpv6->start ) * 100 ) / + TICKS_PER_SEC ); + + /* Sanity check */ + assert ( iob_len ( iobuf ) == total_len ); + + /* Transmit packet */ + if ( ( rc = xfer_deliver_iob ( &dhcpv6->xfer, iobuf ) ) != 0 ) { + DBGC ( dhcpv6, "DHCPv6 %s could not transmit: %s\n", + dhcpv6->netdev->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Handle timer expiry + * + * @v timer Retransmission timer + * @v fail Failure indicator + */ +static void dhcpv6_timer_expired ( struct retry_timer *timer, int fail ) { + struct dhcpv6_session *dhcpv6 = + container_of ( timer, struct dhcpv6_session, timer ); + + /* If we have failed, terminate DHCPv6 */ + if ( fail ) { + dhcpv6_finished ( dhcpv6, dhcpv6->rc ); + return; + } + + /* Restart timer */ + start_timer ( &dhcpv6->timer ); + + /* (Re)transmit current request */ + dhcpv6_tx ( dhcpv6 ); +} + +/** + * Receive new data + * + * @v dhcpv6 DHCPv6 session + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int dhcpv6_rx ( struct dhcpv6_session *dhcpv6, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct settings *parent = netdev_settings ( dhcpv6->netdev ); + struct sockaddr_in6 *src = ( ( struct sockaddr_in6 * ) meta->src ); + struct dhcpv6_header *dhcphdr = iobuf->data; + struct dhcpv6_option_list options; + const union dhcpv6_any_option *option; + int rc; + + /* Sanity checks */ + if ( iob_len ( iobuf ) < sizeof ( *dhcphdr ) ) { + DBGC ( dhcpv6, "DHCPv6 %s received packet too short (%zd " + "bytes, min %zd bytes)\n", dhcpv6->netdev->name, + iob_len ( iobuf ), sizeof ( *dhcphdr ) ); + rc = -EINVAL; + goto done; + } + assert ( src != NULL ); + assert ( src->sin6_family == AF_INET6 ); + DBGC ( dhcpv6, "DHCPv6 %s received %s from %s\n", + dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), + inet6_ntoa ( &src->sin6_addr ) ); + + /* Construct option list */ + options.data = dhcphdr->options; + options.len = ( iob_len ( iobuf ) - + offsetof ( typeof ( *dhcphdr ), options ) ); + + /* Verify client identifier */ + if ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_CLIENT_ID, + &dhcpv6->client_duid, + sizeof ( dhcpv6->client_duid ) ) ) !=0){ + DBGC ( dhcpv6, "DHCPv6 %s received %s without correct client " + "ID: %s\n", dhcpv6->netdev->name, + dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) ); + goto done; + } + + /* Verify server identifier, if applicable */ + if ( dhcpv6->server_duid && + ( ( rc = dhcpv6_check_duid ( &options, DHCPV6_SERVER_ID, + dhcpv6->server_duid, + dhcpv6->server_duid_len ) ) != 0 ) ) { + DBGC ( dhcpv6, "DHCPv6 %s received %s without correct server " + "ID: %s\n", dhcpv6->netdev->name, + dhcpv6_type_name ( dhcphdr->type ), strerror ( rc ) ); + goto done; + } + + /* Check message type */ + if ( dhcphdr->type != dhcpv6->state->rx_type ) { + DBGC ( dhcpv6, "DHCPv6 %s received %s while expecting %s\n", + dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), + dhcpv6_type_name ( dhcpv6->state->rx_type ) ); + rc = -ENOTTY; + goto done; + } + + /* Fetch status code, if present */ + if ( ( rc = dhcpv6_status_code ( &options ) ) != 0 ) { + DBGC ( dhcpv6, "DHCPv6 %s received %s with error status: %s\n", + dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), + strerror ( rc ) ); + /* This is plausibly the error we want to return */ + dhcpv6->rc = rc; + goto done; + } + + /* Record identity association address, if applicable */ + if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_IAADDR ) { + if ( ( rc = dhcpv6_iaaddr ( &options, dhcpv6->iaid, + &dhcpv6->lease ) ) != 0 ) { + DBGC ( dhcpv6, "DHCPv6 %s received %s with unusable " + "IAADDR: %s\n", dhcpv6->netdev->name, + dhcpv6_type_name ( dhcphdr->type ), + strerror ( rc ) ); + /* This is plausibly the error we want to return */ + dhcpv6->rc = rc; + goto done; + } + DBGC ( dhcpv6, "DHCPv6 %s received %s is for %s\n", + dhcpv6->netdev->name, dhcpv6_type_name ( dhcphdr->type ), + inet6_ntoa ( &dhcpv6->lease ) ); + } + + /* Record server ID, if applicable */ + if ( dhcpv6->state->flags & DHCPV6_RX_RECORD_SERVER_ID ) { + assert ( dhcpv6->server_duid == NULL ); + option = dhcpv6_option ( &options, DHCPV6_SERVER_ID ); + if ( ! option ) { + DBGC ( dhcpv6, "DHCPv6 %s received %s missing server " + "ID\n", dhcpv6->netdev->name, + dhcpv6_type_name ( dhcphdr->type ) ); + rc = -EINVAL; + goto done; + } + dhcpv6->server_duid_len = ntohs ( option->duid.header.len ); + dhcpv6->server_duid = malloc ( dhcpv6->server_duid_len ); + if ( ! dhcpv6->server_duid ) { + rc = -ENOMEM; + goto done; + } + memcpy ( dhcpv6->server_duid, option->duid.duid, + dhcpv6->server_duid_len ); + } + + /* Apply identity association address, if applicable */ + if ( dhcpv6->state->flags & DHCPV6_RX_APPLY_IAADDR ) { + if ( ( rc = ipv6_set_address ( dhcpv6->netdev, + &dhcpv6->lease ) ) != 0 ) { + DBGC ( dhcpv6, "DHCPv6 %s could not apply %s: %s\n", + dhcpv6->netdev->name, + inet6_ntoa ( &dhcpv6->lease ), strerror ( rc ) ); + /* This is plausibly the error we want to return */ + dhcpv6->rc = rc; + goto done; + } + } + + /* Transition to next state or complete DHCPv6, as applicable */ + if ( dhcpv6->state->next ) { + + /* Transition to next state */ + dhcpv6_set_state ( dhcpv6, dhcpv6->state->next ); + rc = 0; + + } else { + + /* Register settings */ + if ( ( rc = dhcpv6_register ( &options, parent ) ) != 0 ) { + DBGC ( dhcpv6, "DHCPv6 %s could not register " + "settings: %s\n", dhcpv6->netdev->name, + strerror ( rc ) ); + goto done; + } + + /* Mark as complete */ + dhcpv6_finished ( dhcpv6, 0 ); + DBGC ( dhcpv6, "DHCPv6 %s complete\n", dhcpv6->netdev->name ); + } + + done: + free_iob ( iobuf ); + return rc; +} + +/** DHCPv6 job control interface operations */ +static struct interface_operation dhcpv6_job_op[] = { + INTF_OP ( intf_close, struct dhcpv6_session *, dhcpv6_finished ), +}; + +/** DHCPv6 job control interface descriptor */ +static struct interface_descriptor dhcpv6_job_desc = + INTF_DESC ( struct dhcpv6_session, job, dhcpv6_job_op ); + +/** DHCPv6 data transfer interface operations */ +static struct interface_operation dhcpv6_xfer_op[] = { + INTF_OP ( xfer_deliver, struct dhcpv6_session *, dhcpv6_rx ), +}; + +/** DHCPv6 data transfer interface descriptor */ +static struct interface_descriptor dhcpv6_xfer_desc = + INTF_DESC ( struct dhcpv6_session, xfer, dhcpv6_xfer_op ); + +/** + * Start DHCPv6 + * + * @v job Job control interface + * @v netdev Network device + * @v stateful Perform stateful address autoconfiguration + * @ret rc Return status code + */ +int start_dhcpv6 ( struct interface *job, struct net_device *netdev, + int stateful ) { + struct ll_protocol *ll_protocol = netdev->ll_protocol; + struct dhcpv6_session *dhcpv6; + struct { + union { + struct sockaddr_in6 sin6; + struct sockaddr sa; + } client; + union { + struct sockaddr_in6 sin6; + struct sockaddr sa; + } server; + } addresses; + uint32_t xid; + int len; + int rc; + + /* Allocate and initialise structure */ + dhcpv6 = zalloc ( sizeof ( *dhcpv6 ) ); + if ( ! dhcpv6 ) + return -ENOMEM; + ref_init ( &dhcpv6->refcnt, dhcpv6_free ); + intf_init ( &dhcpv6->job, &dhcpv6_job_desc, &dhcpv6->refcnt ); + intf_init ( &dhcpv6->xfer, &dhcpv6_xfer_desc, &dhcpv6->refcnt ); + dhcpv6->netdev = netdev_get ( netdev ); + xid = random(); + memcpy ( dhcpv6->xid, &xid, sizeof ( dhcpv6->xid ) ); + dhcpv6->start = currticks(); + timer_init ( &dhcpv6->timer, dhcpv6_timer_expired, &dhcpv6->refcnt ); + + /* Construct client and server addresses */ + memset ( &addresses, 0, sizeof ( addresses ) ); + addresses.client.sin6.sin6_family = AF_INET6; + addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT ); + addresses.server.sin6.sin6_family = AF_INET6; + ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr ); + addresses.server.sin6.sin6_scope_id = netdev->index; + addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT ); + + /* Construct client DUID from system UUID */ + dhcpv6->client_duid.type = htons ( DHCPV6_DUID_UUID ); + if ( ( len = fetch_uuid_setting ( NULL, &uuid_setting, + &dhcpv6->client_duid.uuid ) ) < 0 ) { + rc = len; + DBGC ( dhcpv6, "DHCPv6 %s could not create DUID-UUID: %s\n", + dhcpv6->netdev->name, strerror ( rc ) ); + goto err_client_duid; + } + + /* Construct IAID from link-layer address */ + dhcpv6->iaid = crc32_le ( 0, netdev->ll_addr, ll_protocol->ll_addr_len); + DBGC ( dhcpv6, "DHCPv6 %s has XID %02x%02x%02x\n", dhcpv6->netdev->name, + dhcpv6->xid[0], dhcpv6->xid[1], dhcpv6->xid[2] ); + + /* Enter initial state */ + dhcpv6_set_state ( dhcpv6, ( stateful ? &dhcpv6_solicit : + &dhcpv6_information_request ) ); + + /* Open socket */ + if ( ( rc = xfer_open_socket ( &dhcpv6->xfer, SOCK_DGRAM, + &addresses.server.sa, + &addresses.client.sa ) ) != 0 ) { + DBGC ( dhcpv6, "DHCPv6 %s could not open socket: %s\n", + dhcpv6->netdev->name, strerror ( rc ) ); + goto err_open_socket; + } + + /* Attach parent interface, mortalise self, and return */ + intf_plug_plug ( &dhcpv6->job, job ); + ref_put ( &dhcpv6->refcnt ); + return 0; + + err_open_socket: + dhcpv6_finished ( dhcpv6, rc ); + err_client_duid: + ref_put ( &dhcpv6->refcnt ); + return rc; +} + +/** Boot filename setting */ +const struct setting filename6_setting __setting ( SETTING_BOOT, filename ) = { + .name = "filename", + .description = "Boot filename", + .tag = DHCPV6_BOOTFILE_URL, + .type = &setting_type_string, + .scope = &ipv6_scope, +}; + +/** DNS search list setting */ +const struct setting dnssl6_setting __setting ( SETTING_IP_EXTRA, dnssl ) = { + .name = "dnssl", + .description = "DNS search list", + .tag = DHCPV6_DOMAIN_LIST, + .type = &setting_type_dnssl, + .scope = &ipv6_scope, +}; diff --git a/qemu/roms/ipxe/src/net/udp/dns.c b/qemu/roms/ipxe/src/net/udp/dns.c new file mode 100644 index 000000000..fffe6e697 --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/dns.c @@ -0,0 +1,1152 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * + * Portions copyright (C) 2004 Anselm M. Hoffmeister + * <stockholm@users.sourceforge.net>. + * + * 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 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/refcnt.h> +#include <ipxe/iobuf.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/resolv.h> +#include <ipxe/retry.h> +#include <ipxe/tcpip.h> +#include <ipxe/settings.h> +#include <ipxe/features.h> +#include <ipxe/dhcp.h> +#include <ipxe/dhcpv6.h> +#include <ipxe/dns.h> + +/** @file + * + * DNS protocol + * + */ + +FEATURE ( FEATURE_PROTOCOL, "DNS", DHCP_EB_FEATURE_DNS, 1 ); + +/* Disambiguate the various error causes */ +#define ENXIO_NO_RECORD __einfo_error ( EINFO_ENXIO_NO_RECORD ) +#define EINFO_ENXIO_NO_RECORD \ + __einfo_uniqify ( EINFO_ENXIO, 0x01, "DNS name does not exist" ) +#define ENXIO_NO_NAMESERVER __einfo_error ( EINFO_ENXIO_NO_NAMESERVER ) +#define EINFO_ENXIO_NO_NAMESERVER \ + __einfo_uniqify ( EINFO_ENXIO, 0x02, "No DNS servers available" ) + +/** The DNS server */ +static union { + struct sockaddr sa; + struct sockaddr_tcpip st; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +} nameserver = { + .st = { + .st_port = htons ( DNS_PORT ), + }, +}; + +/** The DNS search list */ +static struct dns_name dns_search; + +/** + * Encode a DNS name using RFC1035 encoding + * + * @v string DNS name as a string + * @v name DNS name to fill in + * @ret len Length of DNS name, or negative error + */ +int dns_encode ( const char *string, struct dns_name *name ) { + uint8_t *start = ( name->data + name->offset ); + uint8_t *end = ( name->data + name->len ); + uint8_t *dst = start; + size_t len = 0; + char c; + + /* Encode name */ + while ( ( c = *(string++) ) ) { + + /* Handle '.' separators */ + if ( c == '.' ) { + + /* Reject consecutive '.' */ + if ( ( len == 0 ) && ( dst != start ) ) + return -EINVAL; + + /* Terminate if this is the trailing '.' */ + if ( *string == '\0' ) + break; + + /* Reject initial non-terminating '.' */ + if ( len == 0 ) + return -EINVAL; + + /* Reset length */ + len = 0; + + } else { + + /* Increment length */ + len++; + + /* Check for overflow */ + if ( len > DNS_MAX_LABEL_LEN ) + return -EINVAL; + } + + /* Copy byte, update length */ + if ( ++dst < end ) { + *dst = c; + dst[-len] = len; + } + } + + /* Add terminating root marker */ + if ( len ) + dst++; + if ( dst < end ) + *dst = '\0'; + dst++; + + return ( dst - start ); +} + +/** + * Find start of valid label within an RFC1035-encoded DNS name + * + * @v name DNS name + * @v offset Current offset + * @ret offset Offset of label, or negative error + */ +static int dns_label ( struct dns_name *name, size_t offset ) { + const uint8_t *byte; + const uint16_t *word; + size_t len; + size_t ptr; + + while ( 1 ) { + + /* Fail if we have overrun the DNS name */ + if ( ( offset + sizeof ( *byte) ) > name->len ) + return -EINVAL; + byte = ( name->data + offset ); + + /* Follow compression pointer, if applicable */ + if ( DNS_IS_COMPRESSED ( *byte ) ) { + + /* Fail if we have overrun the DNS name */ + if ( ( offset + sizeof ( *word ) ) > name->len ) + return -EINVAL; + word = ( name->data + offset ); + + /* Extract pointer to new offset */ + ptr = DNS_COMPRESSED_OFFSET ( ntohs ( *word ) ); + + /* Fail if pointer does not point backwards. + * (This guarantees termination of the + * function.) + */ + if ( ptr >= offset ) + return -EINVAL; + + /* Continue from new offset */ + offset = ptr; + continue; + } + + /* Fail if we have overrun the DNS name */ + len = *byte; + if ( ( offset + sizeof ( *byte ) + len ) > name->len ) + return -EINVAL; + + /* We have a valid label */ + return offset; + } +} + +/** + * Decode RFC1035-encoded DNS name + * + * @v name DNS name + * @v data Output buffer + * @v len Length of output buffer + * @ret len Length of decoded DNS name, or negative error + */ +int dns_decode ( struct dns_name *name, char *data, size_t len ) { + unsigned int recursion_limit = name->len; /* Generous upper bound */ + int offset = name->offset; + const uint8_t *label; + size_t decoded_len = 0; + size_t label_len; + size_t copy_len; + + while ( recursion_limit-- ) { + + /* Find valid DNS label */ + offset = dns_label ( name, offset ); + if ( offset < 0 ) + return offset; + + /* Terminate if we have reached the root */ + label = ( name->data + offset ); + label_len = *(label++); + if ( label_len == 0 ) { + if ( decoded_len < len ) + *data = '\0'; + return decoded_len; + } + + /* Prepend '.' if applicable */ + if ( decoded_len && ( decoded_len++ < len ) ) + *(data++) = '.'; + + /* Copy label to output buffer */ + copy_len = ( ( decoded_len < len ) ? ( len - decoded_len ) : 0); + if ( copy_len > label_len ) + copy_len = label_len; + memcpy ( data, label, copy_len ); + data += copy_len; + decoded_len += label_len; + + /* Move to next label */ + offset += ( sizeof ( *label ) + label_len ); + } + + /* Recursion limit exceeded */ + return -EINVAL; +} + +/** + * Compare DNS names for equality + * + * @v first First DNS name + * @v second Second DNS name + * @ret rc Return status code + */ +int dns_compare ( struct dns_name *first, struct dns_name *second ) { + unsigned int recursion_limit = first->len; /* Generous upper bound */ + int first_offset = first->offset; + int second_offset = second->offset; + const uint8_t *first_label; + const uint8_t *second_label; + size_t label_len; + size_t len; + + while ( recursion_limit-- ) { + + /* Find valid DNS labels */ + first_offset = dns_label ( first, first_offset ); + if ( first_offset < 0 ) + return first_offset; + second_offset = dns_label ( second, second_offset ); + if ( second_offset < 0 ) + return second_offset; + + /* Compare label lengths */ + first_label = ( first->data + first_offset ); + second_label = ( second->data + second_offset ); + label_len = *(first_label++); + if ( label_len != *(second_label++) ) + return -ENOENT; + len = ( sizeof ( *first_label ) + label_len ); + + /* Terminate if we have reached the root */ + if ( label_len == 0 ) + return 0; + + /* Compare label contents (case-insensitively) */ + while ( label_len-- ) { + if ( tolower ( *(first_label++) ) != + tolower ( *(second_label++) ) ) + return -ENOENT; + } + + /* Move to next labels */ + first_offset += len; + second_offset += len; + } + + /* Recursion limit exceeded */ + return -EINVAL; +} + +/** + * Copy a DNS name + * + * @v src Source DNS name + * @v dst Destination DNS name + * @ret len Length of copied DNS name, or negative error + */ +int dns_copy ( struct dns_name *src, struct dns_name *dst ) { + unsigned int recursion_limit = src->len; /* Generous upper bound */ + int src_offset = src->offset; + size_t dst_offset = dst->offset; + const uint8_t *label; + size_t label_len; + size_t copy_len; + size_t len; + + while ( recursion_limit-- ) { + + /* Find valid DNS label */ + src_offset = dns_label ( src, src_offset ); + if ( src_offset < 0 ) + return src_offset; + + /* Copy as an uncompressed label */ + label = ( src->data + src_offset ); + label_len = *label; + len = ( sizeof ( *label ) + label_len ); + copy_len = ( ( dst_offset < dst->len ) ? + ( dst->len - dst_offset ) : 0 ); + if ( copy_len > len ) + copy_len = len; + memcpy ( ( dst->data + dst_offset ), label, copy_len ); + src_offset += len; + dst_offset += len; + + /* Terminate if we have reached the root */ + if ( label_len == 0 ) + return ( dst_offset - dst->offset ); + } + + /* Recursion limit exceeded */ + return -EINVAL; +} + +/** + * Skip RFC1035-encoded DNS name + * + * @v name DNS name + * @ret offset Offset to next name, or negative error + */ +int dns_skip ( struct dns_name *name ) { + unsigned int recursion_limit = name->len; /* Generous upper bound */ + int offset = name->offset; + int prev_offset; + const uint8_t *label; + size_t label_len; + + while ( recursion_limit-- ) { + + /* Find valid DNS label */ + prev_offset = offset; + offset = dns_label ( name, prev_offset ); + if ( offset < 0 ) + return offset; + + /* Terminate if we have reached a compression pointer */ + if ( offset != prev_offset ) + return ( prev_offset + sizeof ( uint16_t ) ); + + /* Skip this label */ + label = ( name->data + offset ); + label_len = *label; + offset += ( sizeof ( *label ) + label_len ); + + /* Terminate if we have reached the root */ + if ( label_len == 0 ) + return offset; + } + + /* Recursion limit exceeded */ + return -EINVAL; +} + +/** + * Skip RFC1035-encoded DNS name in search list + * + * @v name DNS name + * @ret offset Offset to next non-empty name, or negative error + */ +static int dns_skip_search ( struct dns_name *name ) { + int offset; + + /* Find next name */ + offset = dns_skip ( name ); + if ( offset < 0 ) + return offset; + + /* Skip over any subsequent empty names (e.g. due to padding + * bytes used in the NDP DNSSL option). + */ + while ( ( offset < ( ( int ) name->len ) ) && + ( *( ( uint8_t * ) ( name->data + offset ) ) == 0 ) ) { + offset++; + } + + return offset; +} + +/** + * Transcribe DNS name (for debugging) + * + * @v name DNS name + * @ret string Transcribed DNS name + */ +static const char * dns_name ( struct dns_name *name ) { + static char buf[256]; + int len; + + len = dns_decode ( name, buf, sizeof ( buf ) ); + return ( ( len < 0 ) ? "<INVALID>" : buf ); +} + +/** + * Name a DNS query type (for debugging) + * + * @v type Query type (in network byte order) + * @ret name Type name + */ +static const char * dns_type ( uint16_t type ) { + switch ( type ) { + case htons ( DNS_TYPE_A ): return "A"; + case htons ( DNS_TYPE_AAAA ): return "AAAA"; + case htons ( DNS_TYPE_CNAME ): return "CNAME"; + default: return "<UNKNOWN>"; + } +} + +/** A DNS request */ +struct dns_request { + /** Reference counter */ + struct refcnt refcnt; + /** Name resolution interface */ + struct interface resolv; + /** Data transfer interface */ + struct interface socket; + /** Retry timer */ + struct retry_timer timer; + + /** Socket address to fill in with resolved address */ + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } address; + /** Initial query type */ + uint16_t qtype; + /** Buffer for current query */ + struct { + /** Query header */ + struct dns_header query; + /** Name buffer */ + char name[DNS_MAX_NAME_LEN]; + /** Space for question */ + struct dns_question padding; + } __attribute__ (( packed )) buf; + /** Current query name */ + struct dns_name name; + /** Question within current query */ + struct dns_question *question; + /** Length of current query */ + size_t len; + /** Offset of search suffix within current query */ + size_t offset; + /** Search list */ + struct dns_name search; + /** Recursion counter */ + unsigned int recursion; +}; + +/** + * Mark DNS request as complete + * + * @v dns DNS request + * @v rc Return status code + */ +static void dns_done ( struct dns_request *dns, int rc ) { + + /* Stop the retry timer */ + stop_timer ( &dns->timer ); + + /* Shut down interfaces */ + intf_shutdown ( &dns->socket, rc ); + intf_shutdown ( &dns->resolv, rc ); +} + +/** + * Mark DNS request as resolved and complete + * + * @v dns DNS request + * @v rc Return status code + */ +static void dns_resolved ( struct dns_request *dns ) { + + DBGC ( dns, "DNS %p found address %s\n", + dns, sock_ntoa ( &dns->address.sa ) ); + + /* Return resolved address */ + resolv_done ( &dns->resolv, &dns->address.sa ); + + /* Mark operation as complete */ + dns_done ( dns, 0 ); +} + +/** + * Construct DNS question + * + * @v dns DNS request + * @ret rc Return status code + */ +static int dns_question ( struct dns_request *dns ) { + static struct dns_name search_root = { + .data = "", + .len = 1, + }; + struct dns_name *search = &dns->search; + int len; + size_t offset; + + /* Use root suffix if search list is empty */ + if ( search->offset == search->len ) + search = &search_root; + + /* Overwrite current suffix */ + dns->name.offset = dns->offset; + len = dns_copy ( search, &dns->name ); + if ( len < 0 ) + return len; + + /* Sanity check */ + offset = ( dns->name.offset + len ); + if ( offset > dns->name.len ) { + DBGC ( dns, "DNS %p name is too long\n", dns ); + return -EINVAL; + } + + /* Construct question */ + dns->question = ( ( ( void * ) &dns->buf ) + offset ); + dns->question->qtype = dns->qtype; + dns->question->qclass = htons ( DNS_CLASS_IN ); + + /* Store length */ + dns->len = ( offset + sizeof ( *(dns->question) ) ); + + /* Restore name */ + dns->name.offset = offsetof ( typeof ( dns->buf ), name ); + + DBGC2 ( dns, "DNS %p question is %s type %s\n", dns, + dns_name ( &dns->name ), dns_type ( dns->question->qtype ) ); + + return 0; +} + +/** + * Send DNS query + * + * @v dns DNS request + * @ret rc Return status code + */ +static int dns_send_packet ( struct dns_request *dns ) { + struct dns_header *query = &dns->buf.query; + + /* Start retransmission timer */ + start_timer ( &dns->timer ); + + /* Generate query identifier */ + query->id = random(); + + /* Send query */ + DBGC ( dns, "DNS %p sending query ID %#04x for %s type %s\n", dns, + ntohs ( query->id ), dns_name ( &dns->name ), + dns_type ( dns->question->qtype ) ); + + /* Send the data */ + return xfer_deliver_raw ( &dns->socket, query, dns->len ); +} + +/** + * Handle DNS retransmission timer expiry + * + * @v timer Retry timer + * @v fail Failure indicator + */ +static void dns_timer_expired ( struct retry_timer *timer, int fail ) { + struct dns_request *dns = + container_of ( timer, struct dns_request, timer ); + + if ( fail ) { + dns_done ( dns, -ETIMEDOUT ); + } else { + dns_send_packet ( dns ); + } +} + +/** + * Receive new data + * + * @v dns DNS request + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int dns_xfer_deliver ( struct dns_request *dns, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + struct dns_header *response = iobuf->data; + struct dns_header *query = &dns->buf.query; + unsigned int qtype = dns->question->qtype; + struct dns_name buf; + union dns_rr *rr; + int offset; + size_t answer_offset; + size_t next_offset; + size_t rdlength; + size_t name_len; + int rc; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *response ) ) { + DBGC ( dns, "DNS %p received underlength packet length %zd\n", + dns, iob_len ( iobuf ) ); + rc = -EINVAL; + goto done; + } + + /* Check response ID matches query ID */ + if ( response->id != query->id ) { + DBGC ( dns, "DNS %p received unexpected response ID %#04x " + "(wanted %d)\n", dns, ntohs ( response->id ), + ntohs ( query->id ) ); + rc = -EINVAL; + goto done; + } + DBGC ( dns, "DNS %p received response ID %#04x\n", + dns, ntohs ( response->id ) ); + + /* Check that we have exactly one question */ + if ( response->qdcount != htons ( 1 ) ) { + DBGC ( dns, "DNS %p received response with %d questions\n", + dns, ntohs ( response->qdcount ) ); + rc = -EINVAL; + goto done; + } + + /* Skip question section */ + buf.data = iobuf->data; + buf.offset = sizeof ( *response ); + buf.len = iob_len ( iobuf ); + offset = dns_skip ( &buf ); + if ( offset < 0 ) { + rc = offset; + DBGC ( dns, "DNS %p received response with malformed " + "question: %s\n", dns, strerror ( rc ) ); + goto done; + } + answer_offset = ( offset + sizeof ( struct dns_question ) ); + + /* Search through response for useful answers. Do this + * multiple times, to take advantage of useful nameservers + * which send us e.g. the CNAME *and* the A record for the + * pointed-to name. + */ + for ( buf.offset = answer_offset ; buf.offset != buf.len ; + buf.offset = next_offset ) { + + /* Check for valid name */ + offset = dns_skip ( &buf ); + if ( offset < 0 ) { + rc = offset; + DBGC ( dns, "DNS %p received response with malformed " + "answer: %s\n", dns, strerror ( rc ) ); + goto done; + } + + /* Check for sufficient space for resource record */ + rr = ( buf.data + offset ); + if ( ( offset + sizeof ( rr->common ) ) > buf.len ) { + DBGC ( dns, "DNS %p received response with underlength " + "RR\n", dns ); + rc = -EINVAL; + goto done; + } + rdlength = ntohs ( rr->common.rdlength ); + next_offset = ( offset + sizeof ( rr->common ) + rdlength ); + if ( next_offset > buf.len ) { + DBGC ( dns, "DNS %p received response with underlength " + "RR\n", dns ); + rc = -EINVAL; + goto done; + } + + /* Skip non-matching names */ + if ( dns_compare ( &buf, &dns->name ) != 0 ) { + DBGC2 ( dns, "DNS %p ignoring response for %s type " + "%s\n", dns, dns_name ( &buf ), + dns_type ( rr->common.type ) ); + continue; + } + + /* Handle answer */ + switch ( rr->common.type ) { + + case htons ( DNS_TYPE_AAAA ): + + /* Found the target AAAA record */ + if ( rdlength < sizeof ( dns->address.sin6.sin6_addr )){ + DBGC ( dns, "DNS %p received response with " + "underlength AAAA\n", dns ); + rc = -EINVAL; + goto done; + } + dns->address.sin6.sin6_family = AF_INET6; + memcpy ( &dns->address.sin6.sin6_addr, + &rr->aaaa.in6_addr, + sizeof ( dns->address.sin6.sin6_addr ) ); + dns_resolved ( dns ); + rc = 0; + goto done; + + case htons ( DNS_TYPE_A ): + + /* Found the target A record */ + if ( rdlength < sizeof ( dns->address.sin.sin_addr ) ) { + DBGC ( dns, "DNS %p received response with " + "underlength A\n", dns ); + rc = -EINVAL; + goto done; + } + dns->address.sin.sin_family = AF_INET; + dns->address.sin.sin_addr = rr->a.in_addr; + dns_resolved ( dns ); + rc = 0; + goto done; + + case htons ( DNS_TYPE_CNAME ): + + /* Terminate the operation if we recurse too far */ + if ( ++dns->recursion > DNS_MAX_CNAME_RECURSION ) { + DBGC ( dns, "DNS %p recursion exceeded\n", + dns ); + rc = -ELOOP; + dns_done ( dns, rc ); + goto done; + } + + /* Found a CNAME record; update query and recurse */ + buf.offset = ( offset + sizeof ( rr->cname ) ); + DBGC ( dns, "DNS %p found CNAME %s\n", + dns, dns_name ( &buf ) ); + dns->search.offset = dns->search.len; + name_len = dns_copy ( &buf, &dns->name ); + dns->offset = ( offsetof ( typeof ( dns->buf ), name ) + + name_len - 1 /* Strip root label */ ); + if ( ( rc = dns_question ( dns ) ) != 0 ) { + dns_done ( dns, rc ); + goto done; + } + next_offset = answer_offset; + break; + + default: + DBGC ( dns, "DNS %p got unknown record type %d\n", + dns, ntohs ( rr->common.type ) ); + break; + } + } + + /* Stop the retry timer. After this point, each code path + * must either restart the timer by calling dns_send_packet(), + * or mark the DNS operation as complete by calling + * dns_done() + */ + stop_timer ( &dns->timer ); + + /* Determine what to do next based on the type of query we + * issued and the response we received + */ + switch ( qtype ) { + + case htons ( DNS_TYPE_AAAA ): + /* We asked for an AAAA record and got nothing; try + * the A. + */ + DBGC ( dns, "DNS %p found no AAAA record; trying A\n", dns ); + dns->question->qtype = htons ( DNS_TYPE_A ); + dns_send_packet ( dns ); + rc = 0; + goto done; + + case htons ( DNS_TYPE_A ): + /* We asked for an A record and got nothing; + * try the CNAME. + */ + DBGC ( dns, "DNS %p found no A record; trying CNAME\n", dns ); + dns->question->qtype = htons ( DNS_TYPE_CNAME ); + dns_send_packet ( dns ); + rc = 0; + goto done; + + case htons ( DNS_TYPE_CNAME ): + /* We asked for a CNAME record. If we got a response + * (i.e. if the next AAAA/A query is already set up), + * then issue it. + */ + if ( qtype == dns->qtype ) { + dns_send_packet ( dns ); + rc = 0; + goto done; + } + + /* If we have already reached the end of the search list, + * then terminate lookup. + */ + if ( dns->search.offset == dns->search.len ) { + DBGC ( dns, "DNS %p found no CNAME record\n", dns ); + rc = -ENXIO_NO_RECORD; + dns_done ( dns, rc ); + goto done; + } + + /* Move to next entry in search list. This can never fail, + * since we have already used this entry. + */ + DBGC ( dns, "DNS %p found no CNAME record; trying next " + "suffix\n", dns ); + dns->search.offset = dns_skip_search ( &dns->search ); + if ( ( rc = dns_question ( dns ) ) != 0 ) { + dns_done ( dns, rc ); + goto done; + } + dns_send_packet ( dns ); + goto done; + + default: + assert ( 0 ); + rc = -EINVAL; + dns_done ( dns, rc ); + goto done; + } + + done: + /* Free I/O buffer */ + free_iob ( iobuf ); + return rc; +} + +/** + * Receive new data + * + * @v dns DNS request + * @v rc Reason for close + */ +static void dns_xfer_close ( struct dns_request *dns, int rc ) { + + if ( ! rc ) + rc = -ECONNABORTED; + + dns_done ( dns, rc ); +} + +/** DNS socket interface operations */ +static struct interface_operation dns_socket_operations[] = { + INTF_OP ( xfer_deliver, struct dns_request *, dns_xfer_deliver ), + INTF_OP ( intf_close, struct dns_request *, dns_xfer_close ), +}; + +/** DNS socket interface descriptor */ +static struct interface_descriptor dns_socket_desc = + INTF_DESC ( struct dns_request, socket, dns_socket_operations ); + +/** DNS resolver interface operations */ +static struct interface_operation dns_resolv_op[] = { + INTF_OP ( intf_close, struct dns_request *, dns_done ), +}; + +/** DNS resolver interface descriptor */ +static struct interface_descriptor dns_resolv_desc = + INTF_DESC ( struct dns_request, resolv, dns_resolv_op ); + +/** + * Resolve name using DNS + * + * @v resolv Name resolution interface + * @v name Name to resolve + * @v sa Socket address to fill in + * @ret rc Return status code + */ +static int dns_resolv ( struct interface *resolv, + const char *name, struct sockaddr *sa ) { + struct dns_request *dns; + struct dns_header *query; + size_t search_len; + int name_len; + int rc; + + /* Fail immediately if no DNS servers */ + if ( ! nameserver.sa.sa_family ) { + DBG ( "DNS not attempting to resolve \"%s\": " + "no DNS servers\n", name ); + rc = -ENXIO_NO_NAMESERVER; + goto err_no_nameserver; + } + + /* Determine whether or not to use search list */ + search_len = ( strchr ( name, '.' ) ? 0 : dns_search.len ); + + /* Allocate DNS structure */ + dns = zalloc ( sizeof ( *dns ) + search_len ); + if ( ! dns ) { + rc = -ENOMEM; + goto err_alloc_dns; + } + ref_init ( &dns->refcnt, NULL ); + intf_init ( &dns->resolv, &dns_resolv_desc, &dns->refcnt ); + intf_init ( &dns->socket, &dns_socket_desc, &dns->refcnt ); + timer_init ( &dns->timer, dns_timer_expired, &dns->refcnt ); + memcpy ( &dns->address.sa, sa, sizeof ( dns->address.sa ) ); + dns->search.data = ( ( ( void * ) dns ) + sizeof ( *dns ) ); + dns->search.len = search_len; + memcpy ( dns->search.data, dns_search.data, search_len ); + + /* Determine initial query type */ + switch ( nameserver.sa.sa_family ) { + case AF_INET: + dns->qtype = htons ( DNS_TYPE_A ); + break; + case AF_INET6: + dns->qtype = htons ( DNS_TYPE_AAAA ); + break; + default: + rc = -ENOTSUP; + goto err_type; + } + + /* Construct query */ + query = &dns->buf.query; + query->flags = htons ( DNS_FLAG_RD ); + query->qdcount = htons ( 1 ); + dns->name.data = &dns->buf; + dns->name.offset = offsetof ( typeof ( dns->buf ), name ); + dns->name.len = offsetof ( typeof ( dns->buf ), padding ); + name_len = dns_encode ( name, &dns->name ); + if ( name_len < 0 ) { + rc = name_len; + goto err_encode; + } + dns->offset = ( offsetof ( typeof ( dns->buf ), name ) + + name_len - 1 /* Strip root label */ ); + if ( ( rc = dns_question ( dns ) ) != 0 ) + goto err_question; + + /* Open UDP connection */ + if ( ( rc = xfer_open_socket ( &dns->socket, SOCK_DGRAM, + &nameserver.sa, NULL ) ) != 0 ) { + DBGC ( dns, "DNS %p could not open socket: %s\n", + dns, strerror ( rc ) ); + goto err_open_socket; + } + + /* Start timer to trigger first packet */ + start_timer_nodelay ( &dns->timer ); + + /* Attach parent interface, mortalise self, and return */ + intf_plug_plug ( &dns->resolv, resolv ); + ref_put ( &dns->refcnt ); + return 0; + + err_open_socket: + err_question: + err_encode: + err_type: + ref_put ( &dns->refcnt ); + err_alloc_dns: + err_no_nameserver: + return rc; +} + +/** DNS name resolver */ +struct resolver dns_resolver __resolver ( RESOLV_NORMAL ) = { + .name = "DNS", + .resolv = dns_resolv, +}; + +/****************************************************************************** + * + * Settings + * + ****************************************************************************** + */ + +/** + * Format DNS search list setting + * + * @v type Setting type + * @v raw Raw setting value + * @v raw_len Length of raw setting value + * @v buf Buffer to contain formatted value + * @v len Length of buffer + * @ret len Length of formatted value, or negative error + */ +static int format_dnssl_setting ( const struct setting_type *type __unused, + const void *raw, size_t raw_len, + char *buf, size_t len ) { + struct dns_name name = { + .data = ( ( void * ) raw ), + .len = raw_len, + }; + size_t remaining = len; + size_t total = 0; + int name_len; + + while ( name.offset < raw_len ) { + + /* Decode name */ + remaining = ( ( total < len ) ? ( len - total ) : 0 ); + name_len = dns_decode ( &name, ( buf + total ), remaining ); + if ( name_len < 0 ) + return name_len; + total += name_len; + + /* Move to next name */ + name.offset = dns_skip_search ( &name ); + + /* Add separator if applicable */ + if ( name.offset != raw_len ) { + if ( total < len ) + buf[total] = ' '; + total++; + } + } + + return total; +} + +/** A DNS search list setting type */ +const struct setting_type setting_type_dnssl __setting_type = { + .name = "dnssl", + .format = format_dnssl_setting, +}; + +/** IPv4 DNS server setting */ +const struct setting dns_setting __setting ( SETTING_IP_EXTRA, dns ) = { + .name = "dns", + .description = "DNS server", + .tag = DHCP_DNS_SERVERS, + .type = &setting_type_ipv4, +}; + +/** IPv6 DNS server setting */ +const struct setting dns6_setting __setting ( SETTING_IP_EXTRA, dns6 ) = { + .name = "dns6", + .description = "DNS server", + .tag = DHCPV6_DNS_SERVERS, + .type = &setting_type_ipv6, + .scope = &ipv6_scope, +}; + +/** DNS search list */ +const struct setting dnssl_setting __setting ( SETTING_IP_EXTRA, dnssl ) = { + .name = "dnssl", + .description = "DNS search list", + .tag = DHCP_DOMAIN_SEARCH, + .type = &setting_type_dnssl, +}; + +/** + * Apply DNS search list + * + */ +static void apply_dns_search ( void ) { + char *localdomain; + int len; + + /* Free existing search list */ + free ( dns_search.data ); + memset ( &dns_search, 0, sizeof ( dns_search ) ); + + /* Fetch DNS search list */ + len = fetch_setting_copy ( NULL, &dnssl_setting, NULL, NULL, + &dns_search.data ); + if ( len >= 0 ) { + dns_search.len = len; + return; + } + + /* If no DNS search list exists, try to fetch the local domain */ + fetch_string_setting_copy ( NULL, &domain_setting, &localdomain ); + if ( localdomain ) { + len = dns_encode ( localdomain, &dns_search ); + if ( len >= 0 ) { + dns_search.data = malloc ( len ); + if ( dns_search.data ) { + dns_search.len = len; + dns_encode ( localdomain, &dns_search ); + } + } + free ( localdomain ); + return; + } +} + +/** + * Apply DNS settings + * + * @ret rc Return status code + */ +static int apply_dns_settings ( void ) { + + /* Fetch DNS server address */ + nameserver.sa.sa_family = 0; + if ( fetch_ipv6_setting ( NULL, &dns6_setting, + &nameserver.sin6.sin6_addr ) >= 0 ) { + nameserver.sin6.sin6_family = AF_INET6; + } else if ( fetch_ipv4_setting ( NULL, &dns_setting, + &nameserver.sin.sin_addr ) >= 0 ) { + nameserver.sin.sin_family = AF_INET; + } + if ( nameserver.sa.sa_family ) { + DBG ( "DNS using nameserver %s\n", + sock_ntoa ( &nameserver.sa ) ); + } + + /* Fetch DNS search list */ + apply_dns_search(); + if ( DBG_LOG && ( dns_search.len != 0 ) ) { + struct dns_name name; + int offset; + + DBG ( "DNS search list:" ); + memcpy ( &name, &dns_search, sizeof ( name ) ); + while ( name.offset != name.len ) { + DBG ( " %s", dns_name ( &name ) ); + offset = dns_skip_search ( &name ); + if ( offset < 0 ) + break; + name.offset = offset; + } + DBG ( "\n" ); + } + + return 0; +} + +/** DNS settings applicator */ +struct settings_applicator dns_applicator __settings_applicator = { + .apply = apply_dns_settings, +}; diff --git a/qemu/roms/ipxe/src/net/udp/slam.c b/qemu/roms/ipxe/src/net/udp/slam.c new file mode 100644 index 000000000..3cb492d73 --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/slam.c @@ -0,0 +1,757 @@ +/* + * Copyright (C) 2008 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 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <assert.h> +#include <byteswap.h> +#include <ipxe/features.h> +#include <ipxe/iobuf.h> +#include <ipxe/bitmap.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/uri.h> +#include <ipxe/tcpip.h> +#include <ipxe/timer.h> +#include <ipxe/retry.h> + +/** @file + * + * Scalable Local Area Multicast protocol + * + * The SLAM protocol is supported only by Etherboot; it was designed + * and implemented by Eric Biederman. A server implementation is + * available in contrib/mini-slamd. There does not appear to be any + * documentation beyond a few sparse comments in Etherboot's + * proto_slam.c. + * + * SLAM packets use three types of data field: + * + * Nul : A single NUL (0) byte, used as a list terminator + * + * Raw : A block of raw data + * + * Int : A variable-length integer, in big-endian order. The length + * of the integer is encoded in the most significant three bits. + * + * Packets received by the client have the following layout: + * + * Int : Transaction identifier. This is an opaque value. + * + * Int : Total number of bytes in the transfer. + * + * Int : Block size, in bytes. + * + * Int : Packet sequence number within the transfer (if this packet + * contains data). + * + * Raw : Packet data (if this packet contains data). + * + * Packets transmitted by the client consist of a run-length-encoded + * representation of the received-blocks bitmap, looking something + * like: + * + * Int : Number of consecutive successfully-received packets + * Int : Number of consecutive missing packets + * Int : Number of consecutive successfully-received packets + * Int : Number of consecutive missing packets + * .... + * Nul + * + */ + +FEATURE ( FEATURE_PROTOCOL, "SLAM", DHCP_EB_FEATURE_SLAM, 1 ); + +/** Default SLAM server port */ +#define SLAM_DEFAULT_PORT 10000 + +/** Default SLAM multicast IP address */ +#define SLAM_DEFAULT_MULTICAST_IP \ + ( ( 239 << 24 ) | ( 255 << 16 ) | ( 1 << 8 ) | ( 1 << 0 ) ) + +/** Default SLAM multicast port */ +#define SLAM_DEFAULT_MULTICAST_PORT 10000 + +/** Maximum SLAM header length */ +#define SLAM_MAX_HEADER_LEN ( 7 /* transaction id */ + 7 /* total_bytes */ + \ + 7 /* block_size */ ) + +/** Maximum number of blocks to request per NACK + * + * This is a policy decision equivalent to selecting a TCP window + * size. + */ +#define SLAM_MAX_BLOCKS_PER_NACK 4 + +/** Maximum SLAM NACK length + * + * We only ever send a NACK for a single range of up to @c + * SLAM_MAX_BLOCKS_PER_NACK blocks. + */ +#define SLAM_MAX_NACK_LEN ( 7 /* block */ + 7 /* #blocks */ + 1 /* NUL */ ) + +/** SLAM slave timeout */ +#define SLAM_SLAVE_TIMEOUT ( 1 * TICKS_PER_SEC ) + +/** A SLAM request */ +struct slam_request { + /** Reference counter */ + struct refcnt refcnt; + + /** Data transfer interface */ + struct interface xfer; + /** Unicast socket */ + struct interface socket; + /** Multicast socket */ + struct interface mc_socket; + + /** Master client retry timer */ + struct retry_timer master_timer; + /** Slave client retry timer */ + struct retry_timer slave_timer; + + /** Cached header */ + uint8_t header[SLAM_MAX_HEADER_LEN]; + /** Size of cached header */ + size_t header_len; + /** Total number of bytes in transfer */ + unsigned long total_bytes; + /** Transfer block size */ + unsigned long block_size; + /** Number of blocks in transfer */ + unsigned long num_blocks; + /** Block bitmap */ + struct bitmap bitmap; + /** NACK sent flag */ + int nack_sent; +}; + +/** + * Free a SLAM request + * + * @v refcnt Reference counter + */ +static void slam_free ( struct refcnt *refcnt ) { + struct slam_request *slam = + container_of ( refcnt, struct slam_request, refcnt ); + + bitmap_free ( &slam->bitmap ); + free ( slam ); +} + +/** + * Mark SLAM request as complete + * + * @v slam SLAM request + * @v rc Return status code + */ +static void slam_finished ( struct slam_request *slam, int rc ) { + static const uint8_t slam_disconnect[] = { 0 }; + + DBGC ( slam, "SLAM %p finished with status code %d (%s)\n", + slam, rc, strerror ( rc ) ); + + /* Send a disconnect message if we ever sent anything to the + * server. + */ + if ( slam->nack_sent ) { + xfer_deliver_raw ( &slam->socket, slam_disconnect, + sizeof ( slam_disconnect ) ); + } + + /* Stop the retry timers */ + stop_timer ( &slam->master_timer ); + stop_timer ( &slam->slave_timer ); + + /* Close all data transfer interfaces */ + intf_shutdown ( &slam->socket, rc ); + intf_shutdown ( &slam->mc_socket, rc ); + intf_shutdown ( &slam->xfer, rc ); +} + +/**************************************************************************** + * + * TX datapath + * + */ + +/** + * Add a variable-length value to a SLAM packet + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @v value Value to add + * @ret rc Return status code + * + * Adds a variable-length value to the end of an I/O buffer. Will + * always leave at least one byte of tailroom in the I/O buffer (to + * allow space for the terminating NUL). + */ +static int slam_put_value ( struct slam_request *slam, + struct io_buffer *iobuf, unsigned long value ) { + uint8_t *data; + size_t len; + unsigned int i; + + /* Calculate variable length required to store value. Always + * leave at least one byte in the I/O buffer. + */ + len = ( ( flsl ( value ) + 10 ) / 8 ); + if ( len >= iob_tailroom ( iobuf ) ) { + DBGC2 ( slam, "SLAM %p cannot add %zd-byte value\n", + slam, len ); + return -ENOBUFS; + } + /* There is no valid way within the protocol that we can end + * up trying to push a full-sized long (i.e. without space for + * the length encoding). + */ + assert ( len <= sizeof ( value ) ); + + /* Add value */ + data = iob_put ( iobuf, len ); + for ( i = len ; i-- ; ) { + data[i] = value; + value >>= 8; + } + *data |= ( len << 5 ); + assert ( value == 0 ); + + return 0; +} + +/** + * Send SLAM NACK packet + * + * @v slam SLAM request + * @ret rc Return status code + */ +static int slam_tx_nack ( struct slam_request *slam ) { + struct io_buffer *iobuf; + unsigned long first_block; + unsigned long num_blocks; + uint8_t *nul; + int rc; + + /* Mark NACK as sent, so that we know we have to disconnect later */ + slam->nack_sent = 1; + + /* Allocate I/O buffer */ + iobuf = xfer_alloc_iob ( &slam->socket, SLAM_MAX_NACK_LEN ); + if ( ! iobuf ) { + DBGC ( slam, "SLAM %p could not allocate I/O buffer\n", + slam ); + return -ENOMEM; + } + + /* Construct NACK. We always request only a single packet; + * this allows us to force multicast-TFTP-style flow control + * on the SLAM server, which will otherwise just blast the + * data out as fast as it can. On a gigabit network, without + * RX checksumming, this would inevitably cause packet drops. + */ + first_block = bitmap_first_gap ( &slam->bitmap ); + for ( num_blocks = 1 ; ; num_blocks++ ) { + if ( num_blocks >= SLAM_MAX_BLOCKS_PER_NACK ) + break; + if ( ( first_block + num_blocks ) >= slam->num_blocks ) + break; + if ( bitmap_test ( &slam->bitmap, + ( first_block + num_blocks ) ) ) + break; + } + if ( first_block ) { + DBGCP ( slam, "SLAM %p transmitting NACK for blocks " + "%ld-%ld\n", slam, first_block, + ( first_block + num_blocks - 1 ) ); + } else { + DBGC ( slam, "SLAM %p transmitting initial NACK for blocks " + "0-%ld\n", slam, ( num_blocks - 1 ) ); + } + if ( ( rc = slam_put_value ( slam, iobuf, first_block ) ) != 0 ) + return rc; + if ( ( rc = slam_put_value ( slam, iobuf, num_blocks ) ) != 0 ) + return rc; + nul = iob_put ( iobuf, 1 ); + *nul = 0; + + /* Transmit packet */ + return xfer_deliver_iob ( &slam->socket, iobuf ); +} + +/** + * Handle SLAM master client retry timer expiry + * + * @v timer Master retry timer + * @v fail Failure indicator + */ +static void slam_master_timer_expired ( struct retry_timer *timer, + int fail ) { + struct slam_request *slam = + container_of ( timer, struct slam_request, master_timer ); + + if ( fail ) { + /* Allow timer to stop running. We will terminate the + * connection only if the slave timer times out. + */ + DBGC ( slam, "SLAM %p giving up acting as master client\n", + slam ); + } else { + /* Retransmit NACK */ + start_timer ( timer ); + slam_tx_nack ( slam ); + } +} + +/** + * Handle SLAM slave client retry timer expiry + * + * @v timer Master retry timer + * @v fail Failure indicator + */ +static void slam_slave_timer_expired ( struct retry_timer *timer, + int fail ) { + struct slam_request *slam = + container_of ( timer, struct slam_request, slave_timer ); + + if ( fail ) { + /* Terminate connection */ + slam_finished ( slam, -ETIMEDOUT ); + } else { + /* Try sending a NACK */ + DBGC ( slam, "SLAM %p trying to become master client\n", + slam ); + start_timer ( timer ); + slam_tx_nack ( slam ); + } +} + +/**************************************************************************** + * + * RX datapath + * + */ + +/** + * Read and strip a variable-length value from a SLAM packet + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @v value Value to fill in, or NULL to ignore value + * @ret rc Return status code + * + * Reads a variable-length value from the start of the I/O buffer. + */ +static int slam_pull_value ( struct slam_request *slam, + struct io_buffer *iobuf, + unsigned long *value ) { + uint8_t *data; + size_t len; + + /* Sanity check */ + if ( iob_len ( iobuf ) == 0 ) { + DBGC ( slam, "SLAM %p empty value\n", slam ); + return -EINVAL; + } + + /* Read and verify length of value */ + data = iobuf->data; + len = ( *data >> 5 ); + if ( ( len == 0 ) || + ( value && ( len > sizeof ( *value ) ) ) ) { + DBGC ( slam, "SLAM %p invalid value length %zd bytes\n", + slam, len ); + return -EINVAL; + } + if ( len > iob_len ( iobuf ) ) { + DBGC ( slam, "SLAM %p value extends beyond I/O buffer\n", + slam ); + return -EINVAL; + } + + /* Read value */ + iob_pull ( iobuf, len ); + *value = ( *data & 0x1f ); + while ( --len ) { + *value <<= 8; + *value |= *(++data); + } + + return 0; +} + +/** + * Read and strip SLAM header + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slam_pull_header ( struct slam_request *slam, + struct io_buffer *iobuf ) { + void *header = iobuf->data; + int rc; + + /* If header matches cached header, just pull it and return */ + if ( ( slam->header_len <= iob_len ( iobuf ) ) && + ( memcmp ( slam->header, iobuf->data, slam->header_len ) == 0 )){ + iob_pull ( iobuf, slam->header_len ); + return 0; + } + + DBGC ( slam, "SLAM %p detected changed header; resetting\n", slam ); + + /* Read and strip transaction ID, total number of bytes, and + * block size. + */ + if ( ( rc = slam_pull_value ( slam, iobuf, NULL ) ) != 0 ) + return rc; + if ( ( rc = slam_pull_value ( slam, iobuf, + &slam->total_bytes ) ) != 0 ) + return rc; + if ( ( rc = slam_pull_value ( slam, iobuf, + &slam->block_size ) ) != 0 ) + return rc; + + /* Update the cached header */ + slam->header_len = ( iobuf->data - header ); + assert ( slam->header_len <= sizeof ( slam->header ) ); + memcpy ( slam->header, header, slam->header_len ); + + /* Calculate number of blocks */ + slam->num_blocks = ( ( slam->total_bytes + slam->block_size - 1 ) / + slam->block_size ); + + DBGC ( slam, "SLAM %p has total bytes %ld, block size %ld, num " + "blocks %ld\n", slam, slam->total_bytes, slam->block_size, + slam->num_blocks ); + + /* Discard and reset the bitmap */ + bitmap_free ( &slam->bitmap ); + memset ( &slam->bitmap, 0, sizeof ( slam->bitmap ) ); + + /* Allocate a new bitmap */ + if ( ( rc = bitmap_resize ( &slam->bitmap, + slam->num_blocks ) ) != 0 ) { + /* Failure to allocate a bitmap is fatal */ + DBGC ( slam, "SLAM %p could not allocate bitmap for %ld " + "blocks: %s\n", slam, slam->num_blocks, + strerror ( rc ) ); + slam_finished ( slam, rc ); + return rc; + } + + /* Notify recipient of file size */ + xfer_seek ( &slam->xfer, slam->total_bytes ); + + return 0; +} + +/** + * Receive SLAM data packet + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slam_mc_socket_deliver ( struct slam_request *slam, + struct io_buffer *iobuf, + struct xfer_metadata *rx_meta __unused ) { + struct xfer_metadata meta; + unsigned long packet; + size_t len; + int rc; + + /* Stop the master client timer. Restart the slave client timer. */ + stop_timer ( &slam->master_timer ); + stop_timer ( &slam->slave_timer ); + start_timer_fixed ( &slam->slave_timer, SLAM_SLAVE_TIMEOUT ); + + /* Read and strip packet header */ + if ( ( rc = slam_pull_header ( slam, iobuf ) ) != 0 ) + goto err_discard; + + /* Read and strip packet number */ + if ( ( rc = slam_pull_value ( slam, iobuf, &packet ) ) != 0 ) + goto err_discard; + + /* Sanity check packet number */ + if ( packet >= slam->num_blocks ) { + DBGC ( slam, "SLAM %p received out-of-range packet %ld " + "(num_blocks=%ld)\n", slam, packet, slam->num_blocks ); + rc = -EINVAL; + goto err_discard; + } + + /* Sanity check length */ + len = iob_len ( iobuf ); + if ( len > slam->block_size ) { + DBGC ( slam, "SLAM %p received oversize packet of %zd bytes " + "(block_size=%ld)\n", slam, len, slam->block_size ); + rc = -EINVAL; + goto err_discard; + } + if ( ( packet != ( slam->num_blocks - 1 ) ) && + ( len < slam->block_size ) ) { + DBGC ( slam, "SLAM %p received short packet of %zd bytes " + "(block_size=%ld)\n", slam, len, slam->block_size ); + rc = -EINVAL; + goto err_discard; + } + + /* If we have already seen this packet, discard it */ + if ( bitmap_test ( &slam->bitmap, packet ) ) { + goto discard; + } + + /* Pass to recipient */ + memset ( &meta, 0, sizeof ( meta ) ); + meta.flags = XFER_FL_ABS_OFFSET; + meta.offset = ( packet * slam->block_size ); + if ( ( rc = xfer_deliver ( &slam->xfer, iobuf, &meta ) ) != 0 ) + goto err; + + /* Mark block as received */ + bitmap_set ( &slam->bitmap, packet ); + + /* If we have received all blocks, terminate */ + if ( bitmap_full ( &slam->bitmap ) ) + slam_finished ( slam, 0 ); + + return 0; + + err_discard: + discard: + free_iob ( iobuf ); + err: + return rc; +} + +/** + * Receive SLAM non-data packet + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slam_socket_deliver ( struct slam_request *slam, + struct io_buffer *iobuf, + struct xfer_metadata *rx_meta __unused ) { + int rc; + + /* Restart the master client timer */ + stop_timer ( &slam->master_timer ); + start_timer ( &slam->master_timer ); + + /* Read and strip packet header */ + if ( ( rc = slam_pull_header ( slam, iobuf ) ) != 0 ) + goto discard; + + /* Sanity check */ + if ( iob_len ( iobuf ) != 0 ) { + DBGC ( slam, "SLAM %p received trailing garbage:\n", slam ); + DBGC_HD ( slam, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto discard; + } + + /* Discard packet */ + free_iob ( iobuf ); + + /* Send NACK in reply */ + slam_tx_nack ( slam ); + + return 0; + + discard: + free_iob ( iobuf ); + return rc; + +} + +/** SLAM unicast socket interface operations */ +static struct interface_operation slam_socket_operations[] = { + INTF_OP ( xfer_deliver, struct slam_request *, slam_socket_deliver ), + INTF_OP ( intf_close, struct slam_request *, slam_finished ), +}; + +/** SLAM unicast socket interface descriptor */ +static struct interface_descriptor slam_socket_desc = + INTF_DESC ( struct slam_request, socket, slam_socket_operations ); + +/** SLAM multicast socket interface operations */ +static struct interface_operation slam_mc_socket_operations[] = { + INTF_OP ( xfer_deliver, struct slam_request *, slam_mc_socket_deliver ), + INTF_OP ( intf_close, struct slam_request *, slam_finished ), +}; + +/** SLAM multicast socket interface descriptor */ +static struct interface_descriptor slam_mc_socket_desc = + INTF_DESC ( struct slam_request, mc_socket, slam_mc_socket_operations ); + +/**************************************************************************** + * + * Data transfer interface + * + */ + +/** SLAM data transfer interface operations */ +static struct interface_operation slam_xfer_operations[] = { + INTF_OP ( intf_close, struct slam_request *, slam_finished ), +}; + +/** SLAM data transfer interface descriptor */ +static struct interface_descriptor slam_xfer_desc = + INTF_DESC ( struct slam_request, xfer, slam_xfer_operations ); + +/** + * Parse SLAM URI multicast address + * + * @v slam SLAM request + * @v path Path portion of x-slam:// URI + * @v address Socket address to fill in + * @ret rc Return status code + */ +static int slam_parse_multicast_address ( struct slam_request *slam, + const char *path, + struct sockaddr_in *address ) { + char path_dup[ strlen ( path ) /* no +1 */ ]; + char *sep; + char *end; + + /* Create temporary copy of path, minus the leading '/' */ + assert ( *path == '/' ); + memcpy ( path_dup, ( path + 1 ) , sizeof ( path_dup ) ); + + /* Parse port, if present */ + sep = strchr ( path_dup, ':' ); + if ( sep ) { + *(sep++) = '\0'; + address->sin_port = htons ( strtoul ( sep, &end, 0 ) ); + if ( *end != '\0' ) { + DBGC ( slam, "SLAM %p invalid multicast port " + "\"%s\"\n", slam, sep ); + return -EINVAL; + } + } + + /* Parse address */ + if ( inet_aton ( path_dup, &address->sin_addr ) == 0 ) { + DBGC ( slam, "SLAM %p invalid multicast address \"%s\"\n", + slam, path_dup ); + return -EINVAL; + } + + return 0; +} + +/** + * Initiate a SLAM request + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int slam_open ( struct interface *xfer, struct uri *uri ) { + static const struct sockaddr_in default_multicast = { + .sin_family = AF_INET, + .sin_port = htons ( SLAM_DEFAULT_MULTICAST_PORT ), + .sin_addr = { htonl ( SLAM_DEFAULT_MULTICAST_IP ) }, + }; + struct slam_request *slam; + struct sockaddr_tcpip server; + struct sockaddr_in multicast; + int rc; + + /* Sanity checks */ + if ( ! uri->host ) + return -EINVAL; + + /* Allocate and populate structure */ + slam = zalloc ( sizeof ( *slam ) ); + if ( ! slam ) + return -ENOMEM; + ref_init ( &slam->refcnt, slam_free ); + intf_init ( &slam->xfer, &slam_xfer_desc, &slam->refcnt ); + intf_init ( &slam->socket, &slam_socket_desc, &slam->refcnt ); + intf_init ( &slam->mc_socket, &slam_mc_socket_desc, &slam->refcnt ); + timer_init ( &slam->master_timer, slam_master_timer_expired, + &slam->refcnt ); + timer_init ( &slam->slave_timer, slam_slave_timer_expired, + &slam->refcnt ); + /* Fake an invalid cached header of { 0x00, ... } */ + slam->header_len = 1; + /* Fake parameters for initial NACK */ + slam->num_blocks = 1; + if ( ( rc = bitmap_resize ( &slam->bitmap, 1 ) ) != 0 ) { + DBGC ( slam, "SLAM %p could not allocate initial bitmap: " + "%s\n", slam, strerror ( rc ) ); + goto err; + } + + /* Open unicast socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st_port = htons ( uri_port ( uri, SLAM_DEFAULT_PORT ) ); + if ( ( rc = xfer_open_named_socket ( &slam->socket, SOCK_DGRAM, + ( struct sockaddr * ) &server, + uri->host, NULL ) ) != 0 ) { + DBGC ( slam, "SLAM %p could not open unicast socket: %s\n", + slam, strerror ( rc ) ); + goto err; + } + + /* Open multicast socket */ + memcpy ( &multicast, &default_multicast, sizeof ( multicast ) ); + if ( uri->path && + ( ( rc = slam_parse_multicast_address ( slam, uri->path, + &multicast ) ) != 0 ) ) { + goto err; + } + if ( ( rc = xfer_open_socket ( &slam->mc_socket, SOCK_DGRAM, + ( struct sockaddr * ) &multicast, + ( struct sockaddr * ) &multicast ) ) != 0 ) { + DBGC ( slam, "SLAM %p could not open multicast socket: %s\n", + slam, strerror ( rc ) ); + goto err; + } + + /* Start slave retry timer */ + start_timer_fixed ( &slam->slave_timer, SLAM_SLAVE_TIMEOUT ); + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &slam->xfer, xfer ); + ref_put ( &slam->refcnt ); + return 0; + + err: + slam_finished ( slam, rc ); + ref_put ( &slam->refcnt ); + return rc; +} + +/** SLAM URI opener */ +struct uri_opener slam_uri_opener __uri_opener = { + .scheme = "x-slam", + .open = slam_open, +}; diff --git a/qemu/roms/ipxe/src/net/udp/syslog.c b/qemu/roms/ipxe/src/net/udp/syslog.c new file mode 100644 index 000000000..d65d19ab8 --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/syslog.c @@ -0,0 +1,298 @@ +/* + * Copyright (C) 2011 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 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +/** @file + * + * Syslog protocol + * + */ + +#include <stdint.h> +#include <stdlib.h> +#include <ctype.h> +#include <byteswap.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/tcpip.h> +#include <ipxe/dhcp.h> +#include <ipxe/dhcpv6.h> +#include <ipxe/settings.h> +#include <ipxe/console.h> +#include <ipxe/lineconsole.h> +#include <ipxe/syslog.h> +#include <config/console.h> + +/* Set default console usage if applicable */ +#if ! ( defined ( CONSOLE_SYSLOG ) && CONSOLE_EXPLICIT ( CONSOLE_SYSLOG ) ) +#undef CONSOLE_SYSLOG +#define CONSOLE_SYSLOG ( CONSOLE_USAGE_ALL & ~CONSOLE_USAGE_TUI ) +#endif + +/** The syslog server */ +static union { + struct sockaddr sa; + struct sockaddr_tcpip st; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; +} logserver = { + .st = { + .st_port = htons ( SYSLOG_PORT ), + }, +}; + +/** Syslog UDP interface operations */ +static struct interface_operation syslogger_operations[] = {}; + +/** Syslog UDP interface descriptor */ +static struct interface_descriptor syslogger_desc = + INTF_DESC_PURE ( syslogger_operations ); + +/** The syslog UDP interface */ +static struct interface syslogger = INTF_INIT ( syslogger_desc ); + +/****************************************************************************** + * + * Console driver + * + ****************************************************************************** + */ + +/** Host name (for log messages) */ +static char *syslog_hostname; + +/** Domain name (for log messages) */ +static char *syslog_domain; + +/** + * Transmit formatted syslog message + * + * @v xfer Data transfer interface + * @v severity Severity + * @v message Message + * @v terminator Message terminator + * @ret rc Return status code + */ +int syslog_send ( struct interface *xfer, unsigned int severity, + const char *message, const char *terminator ) { + const char *hostname = ( syslog_hostname ? syslog_hostname : "" ); + const char *domain = ( ( hostname[0] && syslog_domain ) ? + syslog_domain : "" ); + + return xfer_printf ( xfer, "<%d>%s%s%s%sipxe: %s%s", + SYSLOG_PRIORITY ( SYSLOG_DEFAULT_FACILITY, + severity ), hostname, + ( domain[0] ? "." : "" ), domain, + ( hostname[0] ? " " : "" ), message, terminator ); +} + +/****************************************************************************** + * + * Console driver + * + ****************************************************************************** + */ + +/** Syslog line buffer */ +static char syslog_buffer[SYSLOG_BUFSIZE]; + +/** Syslog severity */ +static unsigned int syslog_severity = SYSLOG_DEFAULT_SEVERITY; + +/** + * Handle ANSI set syslog priority (private sequence) + * + * @v ctx ANSI escape sequence context + * @v count Parameter count + * @v params List of graphic rendition aspects + */ +static void syslog_handle_priority ( struct ansiesc_context *ctx __unused, + unsigned int count __unused, + int params[] ) { + if ( params[0] >= 0 ) { + syslog_severity = params[0]; + } else { + syslog_severity = SYSLOG_DEFAULT_SEVERITY; + } +} + +/** Syslog ANSI escape sequence handlers */ +static struct ansiesc_handler syslog_handlers[] = { + { ANSIESC_LOG_PRIORITY, syslog_handle_priority }, + { 0, NULL } +}; + +/** Syslog line console */ +static struct line_console syslog_line = { + .buffer = syslog_buffer, + .len = sizeof ( syslog_buffer ), + .ctx = { + .handlers = syslog_handlers, + }, +}; + +/** Syslog recursion marker */ +static int syslog_entered; + +/** + * Print a character to syslog console + * + * @v character Character to be printed + */ +static void syslog_putchar ( int character ) { + int rc; + + /* Ignore if we are already mid-logging */ + if ( syslog_entered ) + return; + + /* Fill line buffer */ + if ( line_putchar ( &syslog_line, character ) == 0 ) + return; + + /* Guard against re-entry */ + syslog_entered = 1; + + /* Send log message */ + if ( ( rc = syslog_send ( &syslogger, syslog_severity, + syslog_buffer, "" ) ) != 0 ) { + DBG ( "SYSLOG could not send log message: %s\n", + strerror ( rc ) ); + } + + /* Clear re-entry flag */ + syslog_entered = 0; +} + +/** Syslog console driver */ +struct console_driver syslog_console __console_driver = { + .putchar = syslog_putchar, + .disabled = CONSOLE_DISABLED, + .usage = CONSOLE_SYSLOG, +}; + +/****************************************************************************** + * + * Settings + * + ****************************************************************************** + */ + +/** IPv4 syslog server setting */ +const struct setting syslog_setting __setting ( SETTING_MISC, syslog ) = { + .name = "syslog", + .description = "Syslog server", + .tag = DHCP_LOG_SERVERS, + .type = &setting_type_ipv4, +}; + +/** IPv6 syslog server setting */ +const struct setting syslog6_setting __setting ( SETTING_MISC, syslog6 ) = { + .name = "syslog6", + .description = "Syslog server", + .tag = DHCPV6_LOG_SERVERS, + .type = &setting_type_ipv6, + .scope = &ipv6_scope, +}; + +/** + * Strip invalid characters from host/domain name + * + * @v name Name to strip + */ +static void syslog_fix_name ( char *name ) { + char *fixed = name; + int c; + + /* Do nothing if name does not exist */ + if ( ! name ) + return; + + /* Strip any non-printable or whitespace characters from the name */ + do { + c = *(name++); + *fixed = c; + if ( isprint ( c ) && ! isspace ( c ) ) + fixed++; + } while ( c ); +} + +/** + * Apply syslog settings + * + * @ret rc Return status code + */ +static int apply_syslog_settings ( void ) { + struct sockaddr old_logserver; + int rc; + + /* Fetch hostname and domain name */ + free ( syslog_hostname ); + fetch_string_setting_copy ( NULL, &hostname_setting, &syslog_hostname ); + syslog_fix_name ( syslog_hostname ); + free ( syslog_domain ); + fetch_string_setting_copy ( NULL, &domain_setting, &syslog_domain ); + syslog_fix_name ( syslog_domain ); + + /* Fetch log server */ + syslog_console.disabled = CONSOLE_DISABLED; + memcpy ( &old_logserver, &logserver, sizeof ( old_logserver ) ); + logserver.sa.sa_family = 0; + if ( fetch_ipv6_setting ( NULL, &syslog6_setting, + &logserver.sin6.sin6_addr ) >= 0 ) { + logserver.sin6.sin6_family = AF_INET6; + } else if ( fetch_ipv4_setting ( NULL, &syslog_setting, + &logserver.sin.sin_addr ) >= 0 ) { + logserver.sin.sin_family = AF_INET; + } + if ( logserver.sa.sa_family ) { + syslog_console.disabled = 0; + DBG ( "SYSLOG using log server %s\n", + sock_ntoa ( &logserver.sa ) ); + } + + /* Do nothing unless log server has changed */ + if ( memcmp ( &logserver, &old_logserver, sizeof ( logserver ) ) == 0 ) + return 0; + + /* Reset syslog connection */ + intf_restart ( &syslogger, 0 ); + + /* Do nothing unless we have a log server */ + if ( syslog_console.disabled ) { + DBG ( "SYSLOG has no log server\n" ); + return 0; + } + + /* Connect to log server */ + if ( ( rc = xfer_open_socket ( &syslogger, SOCK_DGRAM, + &logserver.sa, NULL ) ) != 0 ) { + DBG ( "SYSLOG cannot connect to log server: %s\n", + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** Syslog settings applicator */ +struct settings_applicator syslog_applicator __settings_applicator = { + .apply = apply_syslog_settings, +}; diff --git a/qemu/roms/ipxe/src/net/udp/tftp.c b/qemu/roms/ipxe/src/net/udp/tftp.c new file mode 100644 index 000000000..ee827ae3d --- /dev/null +++ b/qemu/roms/ipxe/src/net/udp/tftp.c @@ -0,0 +1,1236 @@ +/* + * Copyright (C) 2006 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 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. + */ + +FILE_LICENCE ( GPL2_OR_LATER ); + +#include <stdint.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <byteswap.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/refcnt.h> +#include <ipxe/iobuf.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/uri.h> +#include <ipxe/tcpip.h> +#include <ipxe/retry.h> +#include <ipxe/features.h> +#include <ipxe/bitmap.h> +#include <ipxe/settings.h> +#include <ipxe/dhcp.h> +#include <ipxe/uri.h> +#include <ipxe/tftp.h> + +/** @file + * + * TFTP protocol + * + */ + +FEATURE ( FEATURE_PROTOCOL, "TFTP", DHCP_EB_FEATURE_TFTP, 1 ); + +/* TFTP-specific error codes */ +#define EINVAL_BLKSIZE __einfo_error ( EINFO_EINVAL_BLKSIZE ) +#define EINFO_EINVAL_BLKSIZE __einfo_uniqify \ + ( EINFO_EINVAL, 0x01, "Invalid blksize" ) +#define EINVAL_TSIZE __einfo_error ( EINFO_EINVAL_TSIZE ) +#define EINFO_EINVAL_TSIZE __einfo_uniqify \ + ( EINFO_EINVAL, 0x02, "Invalid tsize" ) +#define EINVAL_MC_NO_PORT __einfo_error ( EINFO_EINVAL_MC_NO_PORT ) +#define EINFO_EINVAL_MC_NO_PORT __einfo_uniqify \ + ( EINFO_EINVAL, 0x03, "Missing multicast port" ) +#define EINVAL_MC_NO_MC __einfo_error ( EINFO_EINVAL_MC_NO_MC ) +#define EINFO_EINVAL_MC_NO_MC __einfo_uniqify \ + ( EINFO_EINVAL, 0x04, "Missing multicast mc" ) +#define EINVAL_MC_INVALID_MC __einfo_error ( EINFO_EINVAL_MC_INVALID_MC ) +#define EINFO_EINVAL_MC_INVALID_MC __einfo_uniqify \ + ( EINFO_EINVAL, 0x05, "Missing multicast IP" ) +#define EINVAL_MC_INVALID_IP __einfo_error ( EINFO_EINVAL_MC_INVALID_IP ) +#define EINFO_EINVAL_MC_INVALID_IP __einfo_uniqify \ + ( EINFO_EINVAL, 0x06, "Invalid multicast IP" ) +#define EINVAL_MC_INVALID_PORT __einfo_error ( EINFO_EINVAL_MC_INVALID_PORT ) +#define EINFO_EINVAL_MC_INVALID_PORT __einfo_uniqify \ + ( EINFO_EINVAL, 0x07, "Invalid multicast port" ) + +/** + * A TFTP request + * + * This data structure holds the state for an ongoing TFTP transfer. + */ +struct tftp_request { + /** Reference count */ + struct refcnt refcnt; + /** Data transfer interface */ + struct interface xfer; + + /** URI being fetched */ + struct uri *uri; + /** Transport layer interface */ + struct interface socket; + /** Multicast transport layer interface */ + struct interface mc_socket; + + /** Data block size + * + * This is the "blksize" option negotiated with the TFTP + * server. (If the TFTP server does not support TFTP options, + * this will default to 512). + */ + unsigned int blksize; + /** File size + * + * This is the value returned in the "tsize" option from the + * TFTP server. If the TFTP server does not support the + * "tsize" option, this value will be zero. + */ + unsigned long tsize; + + /** Server port + * + * This is the port to which RRQ packets are sent. + */ + unsigned int port; + /** Peer address + * + * The peer address is determined by the first response + * received to the TFTP RRQ. + */ + struct sockaddr_tcpip peer; + /** Request flags */ + unsigned int flags; + /** MTFTP timeout count */ + unsigned int mtftp_timeouts; + + /** Block bitmap */ + struct bitmap bitmap; + /** Maximum known length + * + * We don't always know the file length in advance. In + * particular, if the TFTP server doesn't support the tsize + * option, or we are using MTFTP, then we don't know the file + * length until we see the end-of-file block (which, in the + * case of MTFTP, may not be the last block we see). + * + * This value is updated whenever we obtain information about + * the file length. + */ + size_t filesize; + /** Retransmission timer */ + struct retry_timer timer; +}; + +/** TFTP request flags */ +enum { + /** Send ACK packets */ + TFTP_FL_SEND_ACK = 0x0001, + /** Request blksize and tsize options */ + TFTP_FL_RRQ_SIZES = 0x0002, + /** Request multicast option */ + TFTP_FL_RRQ_MULTICAST = 0x0004, + /** Perform MTFTP recovery on timeout */ + TFTP_FL_MTFTP_RECOVERY = 0x0008, + /** Only get filesize and then abort the transfer */ + TFTP_FL_SIZEONLY = 0x0010, +}; + +/** Maximum number of MTFTP open requests before falling back to TFTP */ +#define MTFTP_MAX_TIMEOUTS 3 + +/** + * Free TFTP request + * + * @v refcnt Reference counter + */ +static void tftp_free ( struct refcnt *refcnt ) { + struct tftp_request *tftp = + container_of ( refcnt, struct tftp_request, refcnt ); + + uri_put ( tftp->uri ); + bitmap_free ( &tftp->bitmap ); + free ( tftp ); +} + +/** + * Mark TFTP request as complete + * + * @v tftp TFTP connection + * @v rc Return status code + */ +static void tftp_done ( struct tftp_request *tftp, int rc ) { + + DBGC ( tftp, "TFTP %p finished with status %d (%s)\n", + tftp, rc, strerror ( rc ) ); + + /* Stop the retry timer */ + stop_timer ( &tftp->timer ); + + /* Close all data transfer interfaces */ + intf_shutdown ( &tftp->socket, rc ); + intf_shutdown ( &tftp->mc_socket, rc ); + intf_shutdown ( &tftp->xfer, rc ); +} + +/** + * Reopen TFTP socket + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_reopen ( struct tftp_request *tftp ) { + struct sockaddr_tcpip server; + int rc; + + /* Close socket */ + intf_restart ( &tftp->socket, 0 ); + + /* Disable ACK sending. */ + tftp->flags &= ~TFTP_FL_SEND_ACK; + + /* Reset peer address */ + memset ( &tftp->peer, 0, sizeof ( tftp->peer ) ); + + /* Open socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st_port = htons ( tftp->port ); + if ( ( rc = xfer_open_named_socket ( &tftp->socket, SOCK_DGRAM, + ( struct sockaddr * ) &server, + tftp->uri->host, NULL ) ) != 0 ) { + DBGC ( tftp, "TFTP %p could not open socket: %s\n", + tftp, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Reopen TFTP multicast socket + * + * @v tftp TFTP connection + * @v local Local socket address + * @ret rc Return status code + */ +static int tftp_reopen_mc ( struct tftp_request *tftp, + struct sockaddr *local ) { + int rc; + + /* Close multicast socket */ + intf_restart ( &tftp->mc_socket, 0 ); + + /* Open multicast socket. We never send via this socket, so + * use the local address as the peer address (since the peer + * address cannot be NULL). + */ + if ( ( rc = xfer_open_socket ( &tftp->mc_socket, SOCK_DGRAM, + local, local ) ) != 0 ) { + DBGC ( tftp, "TFTP %p could not open multicast " + "socket: %s\n", tftp, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Presize TFTP receive buffers and block bitmap + * + * @v tftp TFTP connection + * @v filesize Known minimum file size + * @ret rc Return status code + */ +static int tftp_presize ( struct tftp_request *tftp, size_t filesize ) { + unsigned int num_blocks; + int rc; + + /* Do nothing if we are already large enough */ + if ( filesize <= tftp->filesize ) + return 0; + + /* Record filesize */ + tftp->filesize = filesize; + + /* Notify recipient of file size */ + xfer_seek ( &tftp->xfer, filesize ); + xfer_seek ( &tftp->xfer, 0 ); + + /* Calculate expected number of blocks. Note that files whose + * length is an exact multiple of the blocksize will have a + * trailing zero-length block, which must be included. + */ + num_blocks = ( ( filesize / tftp->blksize ) + 1 ); + if ( ( rc = bitmap_resize ( &tftp->bitmap, num_blocks ) ) != 0 ) { + DBGC ( tftp, "TFTP %p could not resize bitmap to %d blocks: " + "%s\n", tftp, num_blocks, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * MTFTP multicast receive address + * + * This is treated as a global configuration parameter. + */ +static struct sockaddr_in tftp_mtftp_socket = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl ( 0xefff0101 ), + .sin_port = htons ( 3001 ), +}; + +/** + * Set MTFTP multicast address + * + * @v address Multicast IPv4 address + */ +void tftp_set_mtftp_address ( struct in_addr address ) { + tftp_mtftp_socket.sin_addr = address; +} + +/** + * Set MTFTP multicast port + * + * @v port Multicast port + */ +void tftp_set_mtftp_port ( unsigned int port ) { + tftp_mtftp_socket.sin_port = htons ( port ); +} + +/** + * Transmit RRQ + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_send_rrq ( struct tftp_request *tftp ) { + const char *path = tftp->uri->path; + struct tftp_rrq *rrq; + size_t len; + struct io_buffer *iobuf; + size_t blksize; + + DBGC ( tftp, "TFTP %p requesting \"%s\"\n", tftp, path ); + + /* Allocate buffer */ + len = ( sizeof ( *rrq ) + strlen ( path ) + 1 /* NUL */ + + 5 + 1 /* "octet" + NUL */ + + 7 + 1 + 5 + 1 /* "blksize" + NUL + ddddd + NUL */ + + 5 + 1 + 1 + 1 /* "tsize" + NUL + "0" + NUL */ + + 9 + 1 + 1 /* "multicast" + NUL + NUL */ ); + iobuf = xfer_alloc_iob ( &tftp->socket, len ); + if ( ! iobuf ) + return -ENOMEM; + + /* Determine block size */ + blksize = xfer_window ( &tftp->xfer ); + if ( blksize > TFTP_MAX_BLKSIZE ) + blksize = TFTP_MAX_BLKSIZE; + + /* Build request */ + rrq = iob_put ( iobuf, sizeof ( *rrq ) ); + rrq->opcode = htons ( TFTP_RRQ ); + iob_put ( iobuf, snprintf ( iobuf->tail, iob_tailroom ( iobuf ), + "%s%coctet", path, 0 ) + 1 ); + if ( tftp->flags & TFTP_FL_RRQ_SIZES ) { + iob_put ( iobuf, snprintf ( iobuf->tail, + iob_tailroom ( iobuf ), + "blksize%c%zd%ctsize%c0", + 0, blksize, 0, 0 ) + 1 ); + } + if ( tftp->flags & TFTP_FL_RRQ_MULTICAST ) { + iob_put ( iobuf, snprintf ( iobuf->tail, + iob_tailroom ( iobuf ), + "multicast%c", 0 ) + 1 ); + } + + /* RRQ always goes to the address specified in the initial + * xfer_open() call + */ + return xfer_deliver_iob ( &tftp->socket, iobuf ); +} + +/** + * Transmit ACK + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_send_ack ( struct tftp_request *tftp ) { + struct tftp_ack *ack; + struct io_buffer *iobuf; + struct xfer_metadata meta = { + .dest = ( struct sockaddr * ) &tftp->peer, + }; + unsigned int block; + + /* Determine next required block number */ + block = bitmap_first_gap ( &tftp->bitmap ); + DBGC2 ( tftp, "TFTP %p sending ACK for block %d\n", tftp, block ); + + /* Allocate buffer */ + iobuf = xfer_alloc_iob ( &tftp->socket, sizeof ( *ack ) ); + if ( ! iobuf ) + return -ENOMEM; + + /* Build ACK */ + ack = iob_put ( iobuf, sizeof ( *ack ) ); + ack->opcode = htons ( TFTP_ACK ); + ack->block = htons ( block ); + + /* ACK always goes to the peer recorded from the RRQ response */ + return xfer_deliver ( &tftp->socket, iobuf, &meta ); +} + +/** + * Transmit ERROR (Abort) + * + * @v tftp TFTP connection + * @v errcode TFTP error code + * @v errmsg Error message string + * @ret rc Return status code + */ +static int tftp_send_error ( struct tftp_request *tftp, int errcode, + const char *errmsg ) { + struct tftp_error *err; + struct io_buffer *iobuf; + struct xfer_metadata meta = { + .dest = ( struct sockaddr * ) &tftp->peer, + }; + size_t msglen; + + DBGC2 ( tftp, "TFTP %p sending ERROR %d: %s\n", tftp, errcode, + errmsg ); + + /* Allocate buffer */ + msglen = sizeof ( *err ) + strlen ( errmsg ) + 1 /* NUL */; + iobuf = xfer_alloc_iob ( &tftp->socket, msglen ); + if ( ! iobuf ) + return -ENOMEM; + + /* Build ERROR */ + err = iob_put ( iobuf, msglen ); + err->opcode = htons ( TFTP_ERROR ); + err->errcode = htons ( errcode ); + strcpy ( err->errmsg, errmsg ); + + /* ERR always goes to the peer recorded from the RRQ response */ + return xfer_deliver ( &tftp->socket, iobuf, &meta ); +} + +/** + * Transmit next relevant packet + * + * @v tftp TFTP connection + * @ret rc Return status code + */ +static int tftp_send_packet ( struct tftp_request *tftp ) { + + /* Update retransmission timer. While name resolution takes place the + * window is zero. Avoid unnecessary delay after name resolution + * completes by retrying immediately. + */ + stop_timer ( &tftp->timer ); + if ( xfer_window ( &tftp->socket ) ) { + start_timer ( &tftp->timer ); + } else { + start_timer_nodelay ( &tftp->timer ); + } + + /* Send RRQ or ACK as appropriate */ + if ( ! tftp->peer.st_family ) { + return tftp_send_rrq ( tftp ); + } else { + if ( tftp->flags & TFTP_FL_SEND_ACK ) { + return tftp_send_ack ( tftp ); + } else { + return 0; + } + } +} + +/** + * Handle TFTP retransmission timer expiry + * + * @v timer Retry timer + * @v fail Failure indicator + */ +static void tftp_timer_expired ( struct retry_timer *timer, int fail ) { + struct tftp_request *tftp = + container_of ( timer, struct tftp_request, timer ); + int rc; + + /* If we are doing MTFTP, attempt the various recovery strategies */ + if ( tftp->flags & TFTP_FL_MTFTP_RECOVERY ) { + if ( tftp->peer.st_family ) { + /* If we have received any response from the server, + * try resending the RRQ to restart the download. + */ + DBGC ( tftp, "TFTP %p attempting reopen\n", tftp ); + if ( ( rc = tftp_reopen ( tftp ) ) != 0 ) + goto err; + } else { + /* Fall back to plain TFTP after several attempts */ + tftp->mtftp_timeouts++; + DBGC ( tftp, "TFTP %p timeout %d waiting for MTFTP " + "open\n", tftp, tftp->mtftp_timeouts ); + + if ( tftp->mtftp_timeouts > MTFTP_MAX_TIMEOUTS ) { + DBGC ( tftp, "TFTP %p falling back to plain " + "TFTP\n", tftp ); + tftp->flags = TFTP_FL_RRQ_SIZES; + + /* Close multicast socket */ + intf_restart ( &tftp->mc_socket, 0 ); + + /* Reset retry timer */ + start_timer_nodelay ( &tftp->timer ); + + /* The blocksize may change: discard + * the block bitmap + */ + bitmap_free ( &tftp->bitmap ); + memset ( &tftp->bitmap, 0, + sizeof ( tftp->bitmap ) ); + + /* Reopen on standard TFTP port */ + tftp->port = TFTP_PORT; + if ( ( rc = tftp_reopen ( tftp ) ) != 0 ) + goto err; + } + } + } else { + /* Not doing MTFTP (or have fallen back to plain + * TFTP); fail as per normal. + */ + if ( fail ) { + rc = -ETIMEDOUT; + goto err; + } + } + tftp_send_packet ( tftp ); + return; + + err: + tftp_done ( tftp, rc ); +} + +/** + * Process TFTP "blksize" option + * + * @v tftp TFTP connection + * @v value Option value + * @ret rc Return status code + */ +static int tftp_process_blksize ( struct tftp_request *tftp, + const char *value ) { + char *end; + + tftp->blksize = strtoul ( value, &end, 10 ); + if ( *end ) { + DBGC ( tftp, "TFTP %p got invalid blksize \"%s\"\n", + tftp, value ); + return -EINVAL_BLKSIZE; + } + DBGC ( tftp, "TFTP %p blksize=%d\n", tftp, tftp->blksize ); + + return 0; +} + +/** + * Process TFTP "tsize" option + * + * @v tftp TFTP connection + * @v value Option value + * @ret rc Return status code + */ +static int tftp_process_tsize ( struct tftp_request *tftp, + const char *value ) { + char *end; + + tftp->tsize = strtoul ( value, &end, 10 ); + if ( *end ) { + DBGC ( tftp, "TFTP %p got invalid tsize \"%s\"\n", + tftp, value ); + return -EINVAL_TSIZE; + } + DBGC ( tftp, "TFTP %p tsize=%ld\n", tftp, tftp->tsize ); + + return 0; +} + +/** + * Process TFTP "multicast" option + * + * @v tftp TFTP connection + * @v value Option value + * @ret rc Return status code + */ +static int tftp_process_multicast ( struct tftp_request *tftp, + const char *value ) { + union { + struct sockaddr sa; + struct sockaddr_in sin; + } socket; + char buf[ strlen ( value ) + 1 ]; + char *addr; + char *port; + char *port_end; + char *mc; + char *mc_end; + int rc; + + /* Split value into "addr,port,mc" fields */ + memcpy ( buf, value, sizeof ( buf ) ); + addr = buf; + port = strchr ( addr, ',' ); + if ( ! port ) { + DBGC ( tftp, "TFTP %p multicast missing port,mc\n", tftp ); + return -EINVAL_MC_NO_PORT; + } + *(port++) = '\0'; + mc = strchr ( port, ',' ); + if ( ! mc ) { + DBGC ( tftp, "TFTP %p multicast missing mc\n", tftp ); + return -EINVAL_MC_NO_MC; + } + *(mc++) = '\0'; + + /* Parse parameters */ + if ( strtoul ( mc, &mc_end, 0 ) == 0 ) + tftp->flags &= ~TFTP_FL_SEND_ACK; + if ( *mc_end ) { + DBGC ( tftp, "TFTP %p multicast invalid mc %s\n", tftp, mc ); + return -EINVAL_MC_INVALID_MC; + } + DBGC ( tftp, "TFTP %p is%s the master client\n", + tftp, ( ( tftp->flags & TFTP_FL_SEND_ACK ) ? "" : " not" ) ); + if ( *addr && *port ) { + socket.sin.sin_family = AF_INET; + if ( inet_aton ( addr, &socket.sin.sin_addr ) == 0 ) { + DBGC ( tftp, "TFTP %p multicast invalid IP address " + "%s\n", tftp, addr ); + return -EINVAL_MC_INVALID_IP; + } + DBGC ( tftp, "TFTP %p multicast IP address %s\n", + tftp, inet_ntoa ( socket.sin.sin_addr ) ); + socket.sin.sin_port = htons ( strtoul ( port, &port_end, 0 ) ); + if ( *port_end ) { + DBGC ( tftp, "TFTP %p multicast invalid port %s\n", + tftp, port ); + return -EINVAL_MC_INVALID_PORT; + } + DBGC ( tftp, "TFTP %p multicast port %d\n", + tftp, ntohs ( socket.sin.sin_port ) ); + if ( ( rc = tftp_reopen_mc ( tftp, &socket.sa ) ) != 0 ) + return rc; + } + + return 0; +} + +/** A TFTP option */ +struct tftp_option { + /** Option name */ + const char *name; + /** Option processor + * + * @v tftp TFTP connection + * @v value Option value + * @ret rc Return status code + */ + int ( * process ) ( struct tftp_request *tftp, const char *value ); +}; + +/** Recognised TFTP options */ +static struct tftp_option tftp_options[] = { + { "blksize", tftp_process_blksize }, + { "tsize", tftp_process_tsize }, + { "multicast", tftp_process_multicast }, + { NULL, NULL } +}; + +/** + * Process TFTP option + * + * @v tftp TFTP connection + * @v name Option name + * @v value Option value + * @ret rc Return status code + */ +static int tftp_process_option ( struct tftp_request *tftp, + const char *name, const char *value ) { + struct tftp_option *option; + + for ( option = tftp_options ; option->name ; option++ ) { + if ( strcasecmp ( name, option->name ) == 0 ) + return option->process ( tftp, value ); + } + + DBGC ( tftp, "TFTP %p received unknown option \"%s\" = \"%s\"\n", + tftp, name, value ); + + /* Unknown options should be silently ignored */ + return 0; +} + +/** + * Receive OACK + * + * @v tftp TFTP connection + * @v buf Temporary data buffer + * @v len Length of temporary data buffer + * @ret rc Return status code + */ +static int tftp_rx_oack ( struct tftp_request *tftp, void *buf, size_t len ) { + struct tftp_oack *oack = buf; + char *end = buf + len; + char *name; + char *value; + char *next; + int rc = 0; + + /* Sanity check */ + if ( len < sizeof ( *oack ) ) { + DBGC ( tftp, "TFTP %p received underlength OACK packet " + "length %zd\n", tftp, len ); + rc = -EINVAL; + goto done; + } + + /* Process each option in turn */ + for ( name = oack->data ; name < end ; name = next ) { + + /* Parse option name and value + * + * We treat parsing errors as non-fatal, because there + * exists at least one TFTP server (IBM Tivoli PXE + * Server 5.1.0.3) that has been observed to send + * malformed OACKs containing trailing garbage bytes. + */ + value = ( name + strnlen ( name, ( end - name ) ) + 1 ); + if ( value > end ) { + DBGC ( tftp, "TFTP %p received OACK with malformed " + "option name:\n", tftp ); + DBGC_HD ( tftp, oack, len ); + break; + } + if ( value == end ) { + DBGC ( tftp, "TFTP %p received OACK missing value " + "for option \"%s\"\n", tftp, name ); + DBGC_HD ( tftp, oack, len ); + break; + } + next = ( value + strnlen ( value, ( end - value ) ) + 1 ); + if ( next > end ) { + DBGC ( tftp, "TFTP %p received OACK with malformed " + "value for option \"%s\":\n", tftp, name ); + DBGC_HD ( tftp, oack, len ); + break; + } + + /* Process option */ + if ( ( rc = tftp_process_option ( tftp, name, value ) ) != 0 ) + goto done; + } + + /* Process tsize information, if available */ + if ( tftp->tsize ) { + if ( ( rc = tftp_presize ( tftp, tftp->tsize ) ) != 0 ) + goto done; + } + + /* Abort request if only trying to determine file size */ + if ( tftp->flags & TFTP_FL_SIZEONLY ) { + rc = 0; + tftp_send_error ( tftp, 0, "TFTP Aborted" ); + tftp_done ( tftp, rc ); + return rc; + } + + /* Request next data block */ + tftp_send_packet ( tftp ); + + done: + if ( rc ) + tftp_done ( tftp, rc ); + return rc; +} + +/** + * Receive DATA + * + * @v tftp TFTP connection + * @v iobuf I/O buffer + * @ret rc Return status code + * + * Takes ownership of I/O buffer. + */ +static int tftp_rx_data ( struct tftp_request *tftp, + struct io_buffer *iobuf ) { + struct tftp_data *data = iobuf->data; + struct xfer_metadata meta; + unsigned int block; + off_t offset; + size_t data_len; + int rc; + + if ( tftp->flags & TFTP_FL_SIZEONLY ) { + /* If we get here then server doesn't support SIZE option */ + rc = -ENOTSUP; + tftp_send_error ( tftp, 0, "TFTP Aborted" ); + goto done; + } + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *data ) ) { + DBGC ( tftp, "TFTP %p received underlength DATA packet " + "length %zd\n", tftp, iob_len ( iobuf ) ); + rc = -EINVAL; + goto done; + } + + /* Calculate block number */ + block = ( ( bitmap_first_gap ( &tftp->bitmap ) + 1 ) & ~0xffff ); + if ( data->block == 0 && block == 0 ) { + DBGC ( tftp, "TFTP %p received data block 0\n", tftp ); + rc = -EINVAL; + goto done; + } + block += ( ntohs ( data->block ) - 1 ); + + /* Extract data */ + offset = ( block * tftp->blksize ); + iob_pull ( iobuf, sizeof ( *data ) ); + data_len = iob_len ( iobuf ); + if ( data_len > tftp->blksize ) { + DBGC ( tftp, "TFTP %p received overlength DATA packet " + "length %zd\n", tftp, data_len ); + rc = -EINVAL; + goto done; + } + + /* Deliver data */ + memset ( &meta, 0, sizeof ( meta ) ); + meta.flags = XFER_FL_ABS_OFFSET; + meta.offset = offset; + if ( ( rc = xfer_deliver ( &tftp->xfer, iob_disown ( iobuf ), + &meta ) ) != 0 ) { + DBGC ( tftp, "TFTP %p could not deliver data: %s\n", + tftp, strerror ( rc ) ); + goto done; + } + + /* Ensure block bitmap is ready */ + if ( ( rc = tftp_presize ( tftp, ( offset + data_len ) ) ) != 0 ) + goto done; + + /* Mark block as received */ + bitmap_set ( &tftp->bitmap, block ); + + /* Acknowledge block */ + tftp_send_packet ( tftp ); + + /* If all blocks have been received, finish. */ + if ( bitmap_full ( &tftp->bitmap ) ) + tftp_done ( tftp, 0 ); + + done: + free_iob ( iobuf ); + if ( rc ) + tftp_done ( tftp, rc ); + return rc; +} + +/** + * Convert TFTP error code to return status code + * + * @v errcode TFTP error code + * @ret rc Return status code + */ +static int tftp_errcode_to_rc ( unsigned int errcode ) { + switch ( errcode ) { + case TFTP_ERR_FILE_NOT_FOUND: return -ENOENT; + case TFTP_ERR_ACCESS_DENIED: return -EACCES; + case TFTP_ERR_ILLEGAL_OP: return -ENOTTY; + default: return -ENOTSUP; + } +} + +/** + * Receive ERROR + * + * @v tftp TFTP connection + * @v buf Temporary data buffer + * @v len Length of temporary data buffer + * @ret rc Return status code + */ +static int tftp_rx_error ( struct tftp_request *tftp, void *buf, size_t len ) { + struct tftp_error *error = buf; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *error ) ) { + DBGC ( tftp, "TFTP %p received underlength ERROR packet " + "length %zd\n", tftp, len ); + return -EINVAL; + } + + DBGC ( tftp, "TFTP %p received ERROR packet with code %d, message " + "\"%s\"\n", tftp, ntohs ( error->errcode ), error->errmsg ); + + /* Determine final operation result */ + rc = tftp_errcode_to_rc ( ntohs ( error->errcode ) ); + + /* Close TFTP request */ + tftp_done ( tftp, rc ); + + return 0; +} + +/** + * Receive new data + * + * @v tftp TFTP connection + * @v iobuf I/O buffer + * @v meta Transfer metadata + * @ret rc Return status code + */ +static int tftp_rx ( struct tftp_request *tftp, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct sockaddr_tcpip *st_src; + struct tftp_common *common = iobuf->data; + size_t len = iob_len ( iobuf ); + int rc = -EINVAL; + + /* Sanity checks */ + if ( len < sizeof ( *common ) ) { + DBGC ( tftp, "TFTP %p received underlength packet length " + "%zd\n", tftp, len ); + goto done; + } + if ( ! meta->src ) { + DBGC ( tftp, "TFTP %p received packet without source port\n", + tftp ); + goto done; + } + + /* Filter by TID. Set TID on first response received */ + st_src = ( struct sockaddr_tcpip * ) meta->src; + if ( ! tftp->peer.st_family ) { + memcpy ( &tftp->peer, st_src, sizeof ( tftp->peer ) ); + DBGC ( tftp, "TFTP %p using remote port %d\n", tftp, + ntohs ( tftp->peer.st_port ) ); + } else if ( memcmp ( &tftp->peer, st_src, + sizeof ( tftp->peer ) ) != 0 ) { + DBGC ( tftp, "TFTP %p received packet from wrong source (got " + "%d, wanted %d)\n", tftp, ntohs ( st_src->st_port ), + ntohs ( tftp->peer.st_port ) ); + goto done; + } + + switch ( common->opcode ) { + case htons ( TFTP_OACK ): + rc = tftp_rx_oack ( tftp, iobuf->data, len ); + break; + case htons ( TFTP_DATA ): + rc = tftp_rx_data ( tftp, iob_disown ( iobuf ) ); + break; + case htons ( TFTP_ERROR ): + rc = tftp_rx_error ( tftp, iobuf->data, len ); + break; + default: + DBGC ( tftp, "TFTP %p received strange packet type %d\n", + tftp, ntohs ( common->opcode ) ); + break; + }; + + done: + free_iob ( iobuf ); + return rc; +} + +/** + * Receive new data via socket + * + * @v tftp TFTP connection + * @v iobuf I/O buffer + * @v meta Transfer metadata + * @ret rc Return status code + */ +static int tftp_socket_deliver ( struct tftp_request *tftp, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + + /* Enable sending ACKs when we receive a unicast packet. This + * covers three cases: + * + * 1. Standard TFTP; we should always send ACKs, and will + * always receive a unicast packet before we need to send the + * first ACK. + * + * 2. RFC2090 multicast TFTP; the only unicast packets we will + * receive are the OACKs; enable sending ACKs here (before + * processing the OACK) and disable it when processing the + * multicast option if we are not the master client. + * + * 3. MTFTP; receiving a unicast datagram indicates that we + * are the "master client" and should send ACKs. + */ + tftp->flags |= TFTP_FL_SEND_ACK; + + return tftp_rx ( tftp, iobuf, meta ); +} + +/** TFTP socket operations */ +static struct interface_operation tftp_socket_operations[] = { + INTF_OP ( xfer_deliver, struct tftp_request *, tftp_socket_deliver ), +}; + +/** TFTP socket interface descriptor */ +static struct interface_descriptor tftp_socket_desc = + INTF_DESC ( struct tftp_request, socket, tftp_socket_operations ); + +/** TFTP multicast socket operations */ +static struct interface_operation tftp_mc_socket_operations[] = { + INTF_OP ( xfer_deliver, struct tftp_request *, tftp_rx ), +}; + +/** TFTP multicast socket interface descriptor */ +static struct interface_descriptor tftp_mc_socket_desc = + INTF_DESC ( struct tftp_request, mc_socket, tftp_mc_socket_operations ); + +/** + * Check flow control window + * + * @v tftp TFTP connection + * @ret len Length of window + */ +static size_t tftp_xfer_window ( struct tftp_request *tftp ) { + + /* We abuse this data-xfer method to convey the blocksize to + * the caller. This really should be done using some kind of + * stat() method, but we don't yet have the facility to do + * that. + */ + return tftp->blksize; +} + +/** TFTP data transfer interface operations */ +static struct interface_operation tftp_xfer_operations[] = { + INTF_OP ( xfer_window, struct tftp_request *, tftp_xfer_window ), + INTF_OP ( intf_close, struct tftp_request *, tftp_done ), +}; + +/** TFTP data transfer interface descriptor */ +static struct interface_descriptor tftp_xfer_desc = + INTF_DESC ( struct tftp_request, xfer, tftp_xfer_operations ); + +/** + * Initiate TFTP/TFTM/MTFTP download + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int tftp_core_open ( struct interface *xfer, struct uri *uri, + unsigned int default_port, + struct sockaddr *multicast, + unsigned int flags ) { + struct tftp_request *tftp; + int rc; + + /* Sanity checks */ + if ( ! uri->host ) + return -EINVAL; + if ( ! uri->path ) + return -EINVAL; + + /* Allocate and populate TFTP structure */ + tftp = zalloc ( sizeof ( *tftp ) ); + if ( ! tftp ) + return -ENOMEM; + ref_init ( &tftp->refcnt, tftp_free ); + intf_init ( &tftp->xfer, &tftp_xfer_desc, &tftp->refcnt ); + intf_init ( &tftp->socket, &tftp_socket_desc, &tftp->refcnt ); + intf_init ( &tftp->mc_socket, &tftp_mc_socket_desc, &tftp->refcnt ); + timer_init ( &tftp->timer, tftp_timer_expired, &tftp->refcnt ); + tftp->uri = uri_get ( uri ); + tftp->blksize = TFTP_DEFAULT_BLKSIZE; + tftp->flags = flags; + + /* Open socket */ + tftp->port = uri_port ( tftp->uri, default_port ); + if ( ( rc = tftp_reopen ( tftp ) ) != 0 ) + goto err; + + /* Open multicast socket */ + if ( multicast ) { + if ( ( rc = tftp_reopen_mc ( tftp, multicast ) ) != 0 ) + goto err; + } + + /* Start timer to initiate RRQ */ + start_timer_nodelay ( &tftp->timer ); + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &tftp->xfer, xfer ); + ref_put ( &tftp->refcnt ); + return 0; + + err: + DBGC ( tftp, "TFTP %p could not create request: %s\n", + tftp, strerror ( rc ) ); + tftp_done ( tftp, rc ); + ref_put ( &tftp->refcnt ); + return rc; +} + +/** + * Initiate TFTP download + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int tftp_open ( struct interface *xfer, struct uri *uri ) { + return tftp_core_open ( xfer, uri, TFTP_PORT, NULL, + TFTP_FL_RRQ_SIZES ); + +} + +/** TFTP URI opener */ +struct uri_opener tftp_uri_opener __uri_opener = { + .scheme = "tftp", + .open = tftp_open, +}; + +/** + * Initiate TFTP-size request + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int tftpsize_open ( struct interface *xfer, struct uri *uri ) { + return tftp_core_open ( xfer, uri, TFTP_PORT, NULL, + ( TFTP_FL_RRQ_SIZES | + TFTP_FL_SIZEONLY ) ); + +} + +/** TFTP URI opener */ +struct uri_opener tftpsize_uri_opener __uri_opener = { + .scheme = "tftpsize", + .open = tftpsize_open, +}; + +/** + * Initiate TFTM download + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int tftm_open ( struct interface *xfer, struct uri *uri ) { + return tftp_core_open ( xfer, uri, TFTP_PORT, NULL, + ( TFTP_FL_RRQ_SIZES | + TFTP_FL_RRQ_MULTICAST ) ); + +} + +/** TFTM URI opener */ +struct uri_opener tftm_uri_opener __uri_opener = { + .scheme = "tftm", + .open = tftm_open, +}; + +/** + * Initiate MTFTP download + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int mtftp_open ( struct interface *xfer, struct uri *uri ) { + return tftp_core_open ( xfer, uri, MTFTP_PORT, + ( struct sockaddr * ) &tftp_mtftp_socket, + TFTP_FL_MTFTP_RECOVERY ); +} + +/** MTFTP URI opener */ +struct uri_opener mtftp_uri_opener __uri_opener = { + .scheme = "mtftp", + .open = mtftp_open, +}; + +/****************************************************************************** + * + * Settings + * + ****************************************************************************** + */ + +/** + * Apply TFTP configuration settings + * + * @ret rc Return status code + */ +static int tftp_apply_settings ( void ) { + static struct in_addr tftp_server = { 0 }; + struct in_addr last_tftp_server; + char uri_string[32]; + struct uri *uri; + + /* Retrieve TFTP server setting */ + last_tftp_server = tftp_server; + fetch_ipv4_setting ( NULL, &next_server_setting, &tftp_server ); + + /* If TFTP server setting has changed, set the current working + * URI to match. Do it only when the TFTP server has changed + * to try to minimise surprises to the user, who probably + * won't expect the CWURI to change just because they updated + * an unrelated setting and triggered all the settings + * applicators. + */ + if ( tftp_server.s_addr != last_tftp_server.s_addr ) { + if ( tftp_server.s_addr ) { + snprintf ( uri_string, sizeof ( uri_string ), + "tftp://%s/", inet_ntoa ( tftp_server ) ); + uri = parse_uri ( uri_string ); + if ( ! uri ) + return -ENOMEM; + } else { + uri = NULL; + } + churi ( uri ); + uri_put ( uri ); + } + + return 0; +} + +/** TFTP settings applicator */ +struct settings_applicator tftp_settings_applicator __settings_applicator = { + .apply = tftp_apply_settings, +}; |