diff options
Diffstat (limited to 'qemu/roms/ipxe/src/net')
70 files changed, 8304 insertions, 1554 deletions
diff --git a/qemu/roms/ipxe/src/net/80211/net80211.c b/qemu/roms/ipxe/src/net/80211/net80211.c index 434944523..d4970ad5c 100644 --- a/qemu/roms/ipxe/src/net/80211/net80211.c +++ b/qemu/roms/ipxe/src/net/80211/net80211.c @@ -805,6 +805,10 @@ int net80211_register ( struct net80211_device *dev, NET80211_MAX_CHANNELS * sizeof ( dev->channels[0] ) ); dev->channel = 0; + /* Mark device as not supporting interrupts, if applicable */ + if ( ! ops->irq ) + dev->netdev->state |= NETDEV_IRQ_UNSUPPORTED; + list_add_tail ( &dev->list, &net80211_devices ); return register_netdev ( dev->netdev ); } @@ -2826,3 +2830,9 @@ struct errortab common_wireless_errors[] __errortab = { __einfo_errortab ( EINFO_ECONNREFUSED_ASSOC_DENIED ), __einfo_errortab ( EINFO_ECONNREFUSED_AUTH_ALGO_UNSUPP ), }; + +/* Drag in objects via net80211_ll_protocol */ +REQUIRING_SYMBOL ( net80211_ll_protocol ); + +/* Drag in 802.11 configuration */ +REQUIRE_OBJECT ( config_net80211 ); diff --git a/qemu/roms/ipxe/src/net/80211/wpa.c b/qemu/roms/ipxe/src/net/80211/wpa.c index e2c4945f9..77f66d825 100644 --- a/qemu/roms/ipxe/src/net/80211/wpa.c +++ b/qemu/roms/ipxe/src/net/80211/wpa.c @@ -912,4 +912,5 @@ struct eapol_handler eapol_key_handler __eapol_handler = { }; /* WPA always needs EAPOL in order to be useful */ +REQUIRING_SYMBOL ( eapol_key_handler ); REQUIRE_OBJECT ( eapol ); diff --git a/qemu/roms/ipxe/src/net/80211/wpa_ccmp.c b/qemu/roms/ipxe/src/net/80211/wpa_ccmp.c index f98ebea26..a073c6a3c 100644 --- a/qemu/roms/ipxe/src/net/80211/wpa_ccmp.c +++ b/qemu/roms/ipxe/src/net/80211/wpa_ccmp.c @@ -480,7 +480,7 @@ static void ccmp_kie_mic ( const void *kck, const void *msg, size_t len, { u8 sha1_ctx[SHA1_CTX_SIZE]; u8 kckb[16]; - u8 hash[SHA1_SIZE]; + u8 hash[SHA1_DIGEST_SIZE]; size_t kck_len = 16; memcpy ( kckb, kck, kck_len ); diff --git a/qemu/roms/ipxe/src/net/80211/wpa_tkip.c b/qemu/roms/ipxe/src/net/80211/wpa_tkip.c index fa3e0763b..3b1934b59 100644 --- a/qemu/roms/ipxe/src/net/80211/wpa_tkip.c +++ b/qemu/roms/ipxe/src/net/80211/wpa_tkip.c @@ -136,7 +136,7 @@ static const u16 Sbox[256] = { */ static inline u16 S ( u16 v ) { - return Sbox[v & 0xFF] ^ swap16 ( Sbox[v >> 8] ); + return Sbox[v & 0xFF] ^ bswap_16 ( Sbox[v >> 8] ); } /** diff --git a/qemu/roms/ipxe/src/net/aoe.c b/qemu/roms/ipxe/src/net/aoe.c index a6d7b3e7b..2da8655b4 100644 --- a/qemu/roms/ipxe/src/net/aoe.c +++ b/qemu/roms/ipxe/src/net/aoe.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/net/arp.c b/qemu/roms/ipxe/src/net/arp.c index 261e681e1..1e27c44e7 100644 --- a/qemu/roms/ipxe/src/net/arp.c +++ b/qemu/roms/ipxe/src/net/arp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -52,9 +56,9 @@ struct net_protocol arp_protocol __net_protocol; * @v net_source Source network-layer address * @ret rc Return status code */ -static int arp_tx_request ( struct net_device *netdev, - struct net_protocol *net_protocol, - const void *net_dest, const void *net_source ) { +int arp_tx_request ( struct net_device *netdev, + struct net_protocol *net_protocol, + const void *net_dest, const void *net_source ) { struct ll_protocol *ll_protocol = netdev->ll_protocol; struct io_buffer *iobuf; struct arphdr *arphdr; diff --git a/qemu/roms/ipxe/src/net/dhcpopts.c b/qemu/roms/ipxe/src/net/dhcpopts.c index 8cd19cf80..cdb632b46 100644 --- a/qemu/roms/ipxe/src/net/dhcpopts.c +++ b/qemu/roms/ipxe/src/net/dhcpopts.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/dhcppkt.c b/qemu/roms/ipxe/src/net/dhcppkt.c index a9a6d3a94..4e64f85e4 100644 --- a/qemu/roms/ipxe/src/net/dhcppkt.c +++ b/qemu/roms/ipxe/src/net/dhcppkt.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/eth_slow.c b/qemu/roms/ipxe/src/net/eth_slow.c index db54b55a4..049c26cb3 100644 --- a/qemu/roms/ipxe/src/net/eth_slow.c +++ b/qemu/roms/ipxe/src/net/eth_slow.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdlib.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/net/ethernet.c b/qemu/roms/ipxe/src/net/ethernet.c index 03978c2a8..6ddf05344 100644 --- a/qemu/roms/ipxe/src/net/ethernet.c +++ b/qemu/roms/ipxe/src/net/ethernet.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -43,6 +47,24 @@ FILE_LICENCE ( GPL2_OR_LATER ); uint8_t eth_broadcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; /** + * Check if Ethernet packet has an 802.3 LLC header + * + * @v ethhdr Ethernet header + * @ret is_llc Packet has 802.3 LLC header + */ +static inline int eth_is_llc_packet ( struct ethhdr *ethhdr ) { + uint8_t len_msb; + + /* Check if the protocol field contains a value short enough + * to be a frame length. The slightly convoluted form of the + * comparison is designed to reduce to a single x86 + * instruction. + */ + len_msb = *( ( uint8_t * ) ðhdr->h_protocol ); + return ( len_msb < 0x06 ); +} + +/** * Add Ethernet link-layer header * * @v netdev Network device @@ -80,9 +102,14 @@ int eth_pull ( struct net_device *netdev __unused, struct io_buffer *iobuf, const void **ll_dest, const void **ll_source, uint16_t *net_proto, unsigned int *flags ) { struct ethhdr *ethhdr = iobuf->data; + uint16_t *llc_proto; - /* Sanity check */ - if ( iob_len ( iobuf ) < sizeof ( *ethhdr ) ) { + /* Sanity check. While in theory we could receive a one-byte + * packet, this will never happen in practice and performing + * the combined length check here avoids the need for an + * additional comparison if we detect an LLC frame. + */ + if ( iob_len ( iobuf ) < ( sizeof ( *ethhdr ) + sizeof ( *llc_proto ))){ DBG ( "Ethernet packet too short (%zd bytes)\n", iob_len ( iobuf ) ); return -EINVAL; @@ -100,6 +127,17 @@ int eth_pull ( struct net_device *netdev __unused, struct io_buffer *iobuf, ( is_broadcast_ether_addr ( ethhdr->h_dest ) ? LL_BROADCAST : 0 ) ); + /* If this is an LLC frame (with a length in place of the + * protocol field), then use the next two bytes (which happen + * to be the LLC DSAP and SSAP) as the protocol. This allows + * for minimal-overhead support for receiving (rare) LLC + * frames, without requiring a full LLC protocol layer. + */ + if ( eth_is_llc_packet ( ethhdr ) ) { + llc_proto = ( ðhdr->h_protocol + 1 ); + *net_proto = *llc_proto; + } + return 0; } @@ -235,5 +273,11 @@ struct net_device * alloc_etherdev ( size_t priv_size ) { return netdev; } +/* Drag in objects via ethernet_protocol */ +REQUIRING_SYMBOL ( ethernet_protocol ); + +/* Drag in Ethernet configuration */ +REQUIRE_OBJECT ( config_ethernet ); + /* Drag in Ethernet slow protocols */ REQUIRE_OBJECT ( eth_slow ); diff --git a/qemu/roms/ipxe/src/net/fakedhcp.c b/qemu/roms/ipxe/src/net/fakedhcp.c index 3dec88b11..b6c456a59 100644 --- a/qemu/roms/ipxe/src/net/fakedhcp.c +++ b/qemu/roms/ipxe/src/net/fakedhcp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/fc.c b/qemu/roms/ipxe/src/net/fc.c index 58008995c..2e8070272 100644 --- a/qemu/roms/ipxe/src/net/fc.c +++ b/qemu/roms/ipxe/src/net/fc.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdlib.h> @@ -1935,3 +1939,9 @@ struct fc_ulp * fc_ulp_get_port_id_type ( struct fc_port *port, err_peer_get_wwn: return NULL; } + +/* Drag in objects via fc_ports */ +REQUIRING_SYMBOL ( fc_ports ); + +/* Drag in Fibre Channel configuration */ +REQUIRE_OBJECT ( config_fc ); diff --git a/qemu/roms/ipxe/src/net/fcels.c b/qemu/roms/ipxe/src/net/fcels.c index 1cfe90727..5fc27cef4 100644 --- a/qemu/roms/ipxe/src/net/fcels.c +++ b/qemu/roms/ipxe/src/net/fcels.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/fcns.c b/qemu/roms/ipxe/src/net/fcns.c index 3ca4ad557..be4dfea24 100644 --- a/qemu/roms/ipxe/src/net/fcns.c +++ b/qemu/roms/ipxe/src/net/fcns.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/fcoe.c b/qemu/roms/ipxe/src/net/fcoe.c index e9e404ec3..c3258f15e 100644 --- a/qemu/roms/ipxe/src/net/fcoe.c +++ b/qemu/roms/ipxe/src/net/fcoe.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/fcp.c b/qemu/roms/ipxe/src/net/fcp.c index 9c36a4c72..930bf7dd4 100644 --- a/qemu/roms/ipxe/src/net/fcp.c +++ b/qemu/roms/ipxe/src/net/fcp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <stdint.h> diff --git a/qemu/roms/ipxe/src/net/fragment.c b/qemu/roms/ipxe/src/net/fragment.c index 410915b3b..781b9bc60 100644 --- a/qemu/roms/ipxe/src/net/fragment.c +++ b/qemu/roms/ipxe/src/net/fragment.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/icmp.c b/qemu/roms/ipxe/src/net/icmp.c index 1bbf8bd30..5371277e4 100644 --- a/qemu/roms/ipxe/src/net/icmp.c +++ b/qemu/roms/ipxe/src/net/icmp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <byteswap.h> diff --git a/qemu/roms/ipxe/src/net/icmpv4.c b/qemu/roms/ipxe/src/net/icmpv4.c index 996ba1490..0858ff37f 100644 --- a/qemu/roms/ipxe/src/net/icmpv4.c +++ b/qemu/roms/ipxe/src/net/icmpv4.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/net/icmpv6.c b/qemu/roms/ipxe/src/net/icmpv6.c index 479800e7d..8555aaf0b 100644 --- a/qemu/roms/ipxe/src/net/icmpv6.c +++ b/qemu/roms/ipxe/src/net/icmpv6.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <errno.h> @@ -34,6 +38,65 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ +/* Disambiguate the various error causes */ +#define EHOSTUNREACH_ROUTE \ + __einfo_error ( EINFO_EHOSTUNREACH_ROUTE ) +#define EINFO_EHOSTUNREACH_ROUTE \ + __einfo_uniqify ( EINFO_EHOSTUNREACH, 0, \ + "No route to destination" ) +#define EHOSTUNREACH_PROHIBITED \ + __einfo_error ( EINFO_EHOSTUNREACH_PROHIBITED ) +#define EINFO_EHOSTUNREACH_PROHIBITED \ + __einfo_uniqify ( EINFO_EHOSTUNREACH, 1, \ + "Communication administratively prohibited" ) +#define EHOSTUNREACH_ADDRESS \ + __einfo_error ( EINFO_EHOSTUNREACH_ADDRESS ) +#define EINFO_EHOSTUNREACH_ADDRESS \ + __einfo_uniqify ( EINFO_EHOSTUNREACH, 3, \ + "Address unreachable" ) +#define EHOSTUNREACH_PORT \ + __einfo_error ( EINFO_EHOSTUNREACH_PORT ) +#define EINFO_EHOSTUNREACH_PORT \ + __einfo_uniqify ( EINFO_EHOSTUNREACH, 4, \ + "Port unreachable" ) +#define EHOSTUNREACH_CODE( code ) \ + EUNIQ ( EINFO_EHOSTUNREACH, ( (code) & 0x1f ), \ + EHOSTUNREACH_ROUTE, EHOSTUNREACH_PROHIBITED, \ + EHOSTUNREACH_ADDRESS, EHOSTUNREACH_PORT ) + +#define ETIMEDOUT_HOP \ + __einfo_error ( EINFO_ETIMEDOUT_HOP ) +#define EINFO_ETIMEDOUT_HOP \ + __einfo_uniqify ( EINFO_ETIMEDOUT, 0, \ + "Hop limit exceeded in transit" ) +#define ETIMEDOUT_REASSEMBLY \ + __einfo_error ( EINFO_ETIMEDOUT_REASSEMBLY ) +#define EINFO_ETIMEDOUT_REASSEMBLY \ + __einfo_uniqify ( EINFO_ETIMEDOUT, 1, \ + "Fragment reassembly time exceeded" ) +#define ETIMEDOUT_CODE( code ) \ + EUNIQ ( EINFO_ETIMEDOUT, ( (code) & 0x1f ), \ + ETIMEDOUT_HOP, ETIMEDOUT_REASSEMBLY ) + +#define EPROTO_BAD_HEADER \ + __einfo_error ( EINFO_EPROTO_BAD_HEADER ) +#define EINFO_EPROTO_BAD_HEADER \ + __einfo_uniqify ( EINFO_EPROTO, 0, \ + "Erroneous header field" ) +#define EPROTO_NEXT_HEADER \ + __einfo_error ( EINFO_EPROTO_NEXT_HEADER ) +#define EINFO_EPROTO_NEXT_HEADER \ + __einfo_uniqify ( EINFO_EPROTO, 1, \ + "Unrecognised next header type" ) +#define EPROTO_OPTION \ + __einfo_error ( EINFO_EPROTO_OPTION ) +#define EINFO_EPROTO_OPTION \ + __einfo_uniqify ( EINFO_EPROTO, 2, \ + "Unrecognised IPv6 option" ) +#define EPROTO_CODE( code ) \ + EUNIQ ( EINFO_EPROTO, ( (code) & 0x1f ), \ + EPROTO_BAD_HEADER, EPROTO_NEXT_HEADER, EPROTO_OPTION ) + struct icmp_echo_protocol icmpv6_echo_protocol __icmp_echo_protocol; /** @@ -144,8 +207,25 @@ static int icmpv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, /* Identify handler */ handler = icmpv6_handler ( icmp->type ); if ( ! handler ) { - DBGC ( netdev, "ICMPv6 unrecognised type %d\n", icmp->type ); - rc = -ENOTSUP; + switch ( icmp->type ) { + case ICMPV6_DESTINATION_UNREACHABLE: + rc = -EHOSTUNREACH_CODE ( icmp->code ); + break; + case ICMPV6_PACKET_TOO_BIG: + rc = -ERANGE; + break; + case ICMPV6_TIME_EXCEEDED: + rc = -ETIMEDOUT_CODE ( icmp->code ); + break; + case ICMPV6_PARAMETER_PROBLEM: + rc = -EPROTO_CODE ( icmp->code ); + break; + default: + DBGC ( netdev, "ICMPv6 unrecognised type %d code %d\n", + icmp->type, icmp->code ); + rc = -ENOTSUP; + break; + }; goto done; } diff --git a/qemu/roms/ipxe/src/net/infiniband.c b/qemu/roms/ipxe/src/net/infiniband.c index 12d1d83ce..2e3d76d54 100644 --- a/qemu/roms/ipxe/src/net/infiniband.c +++ b/qemu/roms/ipxe/src/net/infiniband.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -714,6 +718,9 @@ int ib_mcast_attach ( struct ib_device *ibdev, struct ib_queue_pair *qp, struct ib_multicast_gid *mgid; int rc; + /* Sanity check */ + assert ( qp != NULL ); + /* Add to software multicast GID list */ mgid = zalloc ( sizeof ( *mgid ) ); if ( ! mgid ) { @@ -747,6 +754,9 @@ void ib_mcast_detach ( struct ib_device *ibdev, struct ib_queue_pair *qp, union ib_gid *gid ) { struct ib_multicast_gid *mgid; + /* Sanity check */ + assert ( qp != NULL ); + /* Remove from hardware multicast GID list */ ibdev->op->mcast_detach ( ibdev, qp, gid ); @@ -995,5 +1005,11 @@ struct ib_device * last_opened_ibdev ( void ) { return ibdev; } +/* Drag in objects via register_ibdev() */ +REQUIRING_SYMBOL ( register_ibdev ); + +/* Drag in Infiniband configuration */ +REQUIRE_OBJECT ( config_infiniband ); + /* Drag in IPoIB */ REQUIRE_OBJECT ( ipoib ); diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_cm.c b/qemu/roms/ipxe/src/net/infiniband/ib_cm.c index 797639bc8..85982f09d 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_cm.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_cm.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_mcast.c b/qemu/roms/ipxe/src/net/infiniband/ib_mcast.c index 0a5e72a37..fc4ff7f0a 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_mcast.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_mcast.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> @@ -146,6 +150,9 @@ int ib_mcast_join ( struct ib_device *ibdev, struct ib_queue_pair *qp, DBGC ( ibdev, "IBDEV %p QPN %lx joining " IB_GID_FMT "\n", ibdev, qp->qpn, IB_GID_ARGS ( gid ) ); + /* Sanity check */ + assert ( qp != NULL ); + /* Initialise structure */ membership->qp = qp; memcpy ( &membership->gid, gid, sizeof ( membership->gid ) ); @@ -195,6 +202,9 @@ void ib_mcast_leave ( struct ib_device *ibdev, struct ib_queue_pair *qp, DBGC ( ibdev, "IBDEV %p QPN %lx leaving " IB_GID_FMT "\n", ibdev, qp->qpn, IB_GID_ARGS ( gid ) ); + /* Sanity check */ + assert ( qp != NULL ); + /* Detach from multicast GID */ ib_mcast_detach ( ibdev, qp, &membership->gid ); diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_mi.c b/qemu/roms/ipxe/src/net/infiniband/ib_mi.c index ef6d539f1..b43212974 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_mi.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_mi.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_packet.c b/qemu/roms/ipxe/src/net/infiniband/ib_packet.c index 6c850e39b..d3a22d309 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_packet.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_packet.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_pathrec.c b/qemu/roms/ipxe/src/net/infiniband/ib_pathrec.c index 1b95cbfa8..f9cbab87f 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_pathrec.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_pathrec.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_sma.c b/qemu/roms/ipxe/src/net/infiniband/ib_sma.c index 86553732a..a05d7c924 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_sma.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_sma.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_smc.c b/qemu/roms/ipxe/src/net/infiniband/ib_smc.c index 4d947d568..c1741b26c 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_smc.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_smc.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/infiniband/ib_srp.c b/qemu/roms/ipxe/src/net/infiniband/ib_srp.c index 7b2b2b4ea..3700184c0 100644 --- a/qemu/roms/ipxe/src/net/infiniband/ib_srp.c +++ b/qemu/roms/ipxe/src/net/infiniband/ib_srp.c @@ -291,7 +291,7 @@ static int ib_srp_parse_byte_string ( const char *rp_comp, uint8_t *bytes, return -EINVAL_BYTE_STRING_LEN; /* Parse byte string */ - decoded_size = base16_decode ( rp_comp, bytes ); + decoded_size = base16_decode ( rp_comp, bytes, size ); if ( decoded_size < 0 ) return decoded_size; diff --git a/qemu/roms/ipxe/src/net/iobpad.c b/qemu/roms/ipxe/src/net/iobpad.c index 9cc8328e9..936b4bde4 100644 --- a/qemu/roms/ipxe/src/net/iobpad.c +++ b/qemu/roms/ipxe/src/net/iobpad.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file diff --git a/qemu/roms/ipxe/src/net/ipv4.c b/qemu/roms/ipxe/src/net/ipv4.c index 9c5cf2eb4..a54784049 100644 --- a/qemu/roms/ipxe/src/net/ipv4.c +++ b/qemu/roms/ipxe/src/net/ipv4.c @@ -1,3 +1,27 @@ +/* + * Copyright (C) 2006 Michael Brown <mbrown@fensystems.co.uk>. + * Copyright (C) 2006 Nikhil Chandru Rao + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + #include <string.h> #include <stdint.h> #include <stdlib.h> @@ -24,7 +48,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /* Unique IP datagram identification number (high byte) */ static uint8_t next_ident_high = 0; @@ -115,6 +139,7 @@ static void del_ipv4_miniroute ( struct ipv4_miniroute *miniroute ) { /** * Perform IPv4 routing * + * @v scope_id Destination address scope ID * @v dest Final destination address * @ret dest Next hop destination address * @ret miniroute Routing table entry to use, or NULL if no route @@ -122,22 +147,42 @@ static void del_ipv4_miniroute ( struct ipv4_miniroute *miniroute ) { * If the route requires use of a gateway, the next hop destination * address will be overwritten with the gateway address. */ -static struct ipv4_miniroute * ipv4_route ( struct in_addr *dest ) { +static struct ipv4_miniroute * ipv4_route ( unsigned int scope_id, + struct in_addr *dest ) { struct ipv4_miniroute *miniroute; - int local; - int has_gw; /* Find first usable route in routing table */ list_for_each_entry ( miniroute, &ipv4_miniroutes, list ) { + + /* Skip closed network devices */ if ( ! netdev_is_open ( miniroute->netdev ) ) continue; - local = ( ( ( dest->s_addr ^ miniroute->address.s_addr ) - & miniroute->netmask.s_addr ) == 0 ); - has_gw = ( miniroute->gateway.s_addr ); - if ( local || has_gw ) { - if ( ! local ) + + if ( IN_IS_MULTICAST ( dest->s_addr ) ) { + + /* If destination is non-global, and the scope ID + * matches this network device, then use this route. + */ + if ( miniroute->netdev->index == scope_id ) + return miniroute; + + } else { + + /* If destination is an on-link global + * address, then use this route. + */ + if ( ( ( dest->s_addr ^ miniroute->address.s_addr ) + & miniroute->netmask.s_addr ) == 0 ) + return miniroute; + + /* If destination is an off-link global + * address, and we have a default gateway, + * then use this route. + */ + if ( miniroute->gateway.s_addr ) { *dest = miniroute->gateway; - return miniroute; + return miniroute; + } } } @@ -156,7 +201,7 @@ static struct net_device * ipv4_netdev ( struct sockaddr_tcpip *st_dest ) { struct ipv4_miniroute *miniroute; /* Find routing table entry */ - miniroute = ipv4_route ( &dest ); + miniroute = ipv4_route ( sin_dest->sin_scope_id, &dest ); if ( ! miniroute ) return NULL; @@ -290,8 +335,8 @@ static int ipv4_tx ( struct io_buffer *iobuf, if ( sin_src ) iphdr->src = sin_src->sin_addr; if ( ( next_hop.s_addr != INADDR_BROADCAST ) && - ( ! IN_MULTICAST ( ntohl ( next_hop.s_addr ) ) ) && - ( ( miniroute = ipv4_route ( &next_hop ) ) != NULL ) ) { + ( ( miniroute = ipv4_route ( sin_dest->sin_scope_id, + &next_hop ) ) != NULL ) ) { iphdr->src = miniroute->address; netmask = miniroute->netmask; netdev = miniroute->netdev; @@ -329,7 +374,7 @@ static int ipv4_tx ( struct io_buffer *iobuf, /* Broadcast address */ ipv4_stats.out_bcast_pkts++; ll_dest = netdev->ll_broadcast; - } else if ( IN_MULTICAST ( ntohl ( next_hop.s_addr ) ) ) { + } else if ( IN_IS_MULTICAST ( next_hop.s_addr ) ) { /* Multicast address */ ipv4_stats.out_mcast_pkts++; if ( ( rc = netdev->ll_protocol->mc_hash ( AF_INET, &next_hop, @@ -569,10 +614,42 @@ static int ipv4_arp_check ( struct net_device *netdev, const void *net_addr ) { } /** + * Parse IPv4 address + * + * @v string IPv4 address string + * @ret in IPv4 address to fill in + * @ret ok IPv4 address is valid + * + * Note that this function returns nonzero iff the address is valid, + * to match the standard BSD API function of the same name. Unlike + * most other iPXE functions, a zero therefore indicates failure. + */ +int inet_aton ( const char *string, struct in_addr *in ) { + const char *separator = "..."; + uint8_t *byte = ( ( uint8_t * ) in ); + char *endp; + unsigned long value; + + while ( 1 ) { + value = strtoul ( string, &endp, 0 ); + if ( string == endp ) + return 0; + if ( value > 0xff ) + return 0; + *(byte++) = value; + if ( *endp != *separator ) + return 0; + if ( ! *(separator++) ) + return 1; + string = ( endp + 1 ); + } +} + +/** * Convert IPv4 address to dotted-quad notation * - * @v in IP address - * @ret string IP address in dotted-quad notation + * @v in IPv4 address + * @ret string IPv4 address in dotted-quad notation */ char * inet_ntoa ( struct in_addr in ) { static char buf[16]; /* "xxx.xxx.xxx.xxx" */ @@ -583,10 +660,10 @@ char * inet_ntoa ( struct in_addr in ) { } /** - * Transcribe IP address + * Transcribe IPv4 address * - * @v net_addr IP address - * @ret string IP address in dotted-quad notation + * @v net_addr IPv4 address + * @ret string IPv4 address in dotted-quad notation * */ static const char * ipv4_ntoa ( const void *net_addr ) { @@ -760,12 +837,12 @@ static int ipv4_create_routes ( void ) { fetch_ipv4_setting ( settings, &netmask_setting, &netmask ); /* Calculate default netmask, if necessary */ if ( ! netmask.s_addr ) { - if ( IN_CLASSA ( ntohl ( address.s_addr ) ) ) { - netmask.s_addr = htonl ( IN_CLASSA_NET ); - } else if ( IN_CLASSB ( ntohl ( address.s_addr ) ) ) { - netmask.s_addr = htonl ( IN_CLASSB_NET ); - } else if ( IN_CLASSC ( ntohl ( address.s_addr ) ) ) { - netmask.s_addr = htonl ( IN_CLASSC_NET ); + if ( IN_IS_CLASSA ( address.s_addr ) ) { + netmask.s_addr = INADDR_NET_CLASSA; + } else if ( IN_IS_CLASSB ( address.s_addr ) ) { + netmask.s_addr = INADDR_NET_CLASSB; + } else if ( IN_IS_CLASSC ( address.s_addr ) ) { + netmask.s_addr = INADDR_NET_CLASSC; } } /* Get default gateway, if present */ @@ -785,5 +862,8 @@ struct settings_applicator ipv4_settings_applicator __settings_applicator = { .apply = ipv4_create_routes, }; +/* Drag in objects via ipv4_protocol */ +REQUIRING_SYMBOL ( ipv4_protocol ); + /* Drag in ICMPv4 */ REQUIRE_OBJECT ( icmpv4 ); diff --git a/qemu/roms/ipxe/src/net/ipv6.c b/qemu/roms/ipxe/src/net/ipv6.c index 3c374168c..a75e72ddb 100644 --- a/qemu/roms/ipxe/src/net/ipv6.c +++ b/qemu/roms/ipxe/src/net/ipv6.c @@ -290,8 +290,7 @@ static struct ipv6_miniroute * ipv6_route ( unsigned int scope_id, if ( ! ( miniroute->flags & IPV6_HAS_ADDRESS ) ) continue; - if ( IN6_IS_ADDR_LINKLOCAL ( *dest ) || - IN6_IS_ADDR_MULTICAST ( *dest ) ) { + if ( IN6_IS_ADDR_NONGLOBAL ( *dest ) ) { /* If destination is non-global, and the scope ID * matches this network device, then use this route. @@ -901,7 +900,7 @@ static const char * ipv6_sock_ntoa ( struct sockaddr *sa ) { const char *netdev_name; /* Identify network device, if applicable */ - if ( IN6_IS_ADDR_LINKLOCAL ( in ) || IN6_IS_ADDR_MULTICAST ( in ) ) { + if ( IN6_IS_ADDR_NONGLOBAL ( in ) ) { netdev = find_netdev_by_index ( sin6->sin6_scope_id ); netdev_name = ( netdev ? netdev->name : "UNKNOWN" ); } else { @@ -956,14 +955,26 @@ static int ipv6_sock_aton ( const char *string, struct sockaddr *sa ) { if ( ( rc = inet6_aton ( in_string, &in ) ) != 0 ) goto err_inet6_aton; - /* Parse network device name, if present */ + /* Parse scope ID, if applicable */ if ( netdev_string ) { + + /* Parse explicit network device name, if present */ netdev = find_netdev ( netdev_string ); if ( ! netdev ) { rc = -ENODEV; goto err_find_netdev; } sin6->sin6_scope_id = netdev->index; + + } else if ( IN6_IS_ADDR_NONGLOBAL ( &in ) ) { + + /* If no network device is explicitly specified for a + * link-local or multicast address, default to using + * "netX" (if existent). + */ + netdev = last_opened_netdev(); + if ( netdev ) + sin6->sin6_scope_id = netdev->index; } /* Copy IPv6 address portion to socket address */ @@ -1104,6 +1115,9 @@ struct net_driver ipv6_driver __net_driver = { .remove = ipv6_remove, }; +/* Drag in objects via ipv6_protocol */ +REQUIRING_SYMBOL ( ipv6_protocol ); + /* Drag in ICMPv6 */ REQUIRE_OBJECT ( icmpv6 ); diff --git a/qemu/roms/ipxe/src/net/neighbour.c b/qemu/roms/ipxe/src/net/neighbour.c index e3026ce46..7f66d9992 100644 --- a/qemu/roms/ipxe/src/net/neighbour.c +++ b/qemu/roms/ipxe/src/net/neighbour.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -91,8 +95,8 @@ static struct neighbour * neighbour_create ( struct net_device *netdev, memcpy ( neighbour->net_dest, net_dest, net_protocol->net_addr_len ); timer_init ( &neighbour->timer, neighbour_expired, &neighbour->refcnt ); - neighbour->timer.min_timeout = NEIGHBOUR_MIN_TIMEOUT; - neighbour->timer.max_timeout = NEIGHBOUR_MAX_TIMEOUT; + set_timer_limits ( &neighbour->timer, NEIGHBOUR_MIN_TIMEOUT, + NEIGHBOUR_MAX_TIMEOUT ); INIT_LIST_HEAD ( &neighbour->tx_queue ); /* Transfer ownership to cache */ @@ -318,7 +322,7 @@ int neighbour_tx ( struct io_buffer *iobuf, struct net_device *netdev, netdev->name, net_protocol->name, net_protocol->ntoa ( net_dest ) ); list_add_tail ( &iobuf->list, &neighbour->tx_queue ); - return -EAGAIN; + return 0; } } diff --git a/qemu/roms/ipxe/src/net/netdev_settings.c b/qemu/roms/ipxe/src/net/netdev_settings.c index b3b2e68d8..edd4c4b9f 100644 --- a/qemu/roms/ipxe/src/net/netdev_settings.c +++ b/qemu/roms/ipxe/src/net/netdev_settings.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <errno.h> @@ -121,6 +125,10 @@ static int netdev_fetch_bustype ( struct net_device *netdev, void *data, [BUS_TYPE_MCA] = "MCA", [BUS_TYPE_ISA] = "ISA", [BUS_TYPE_TAP] = "TAP", + [BUS_TYPE_EFI] = "EFI", + [BUS_TYPE_XEN] = "XEN", + [BUS_TYPE_HV] = "HV", + [BUS_TYPE_USB] = "USB", }; struct device_description *desc = &netdev->dev->desc; const char *bustype; diff --git a/qemu/roms/ipxe/src/net/netdevice.c b/qemu/roms/ipxe/src/net/netdevice.c index a55e6b7d7..7c40a2ac8 100644 --- a/qemu/roms/ipxe/src/net/netdevice.c +++ b/qemu/roms/ipxe/src/net/netdevice.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -35,6 +39,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/device.h> #include <ipxe/errortab.h> #include <ipxe/profile.h> +#include <ipxe/fault.h> #include <ipxe/vlan.h> #include <ipxe/netdevice.h> @@ -157,6 +162,9 @@ void netdev_rx_unfreeze ( struct net_device *netdev ) { */ void netdev_link_err ( struct net_device *netdev, int rc ) { + /* Stop link block timer */ + stop_timer ( &netdev->link_block ); + /* Record link state */ netdev->link_rc = rc; if ( netdev->link_rc == 0 ) { @@ -187,6 +195,50 @@ void netdev_link_down ( struct net_device *netdev ) { } /** + * Mark network device link as being blocked + * + * @v netdev Network device + * @v timeout Timeout (in ticks) + */ +void netdev_link_block ( struct net_device *netdev, unsigned long timeout ) { + + /* Start link block timer */ + if ( ! netdev_link_blocked ( netdev ) ) { + DBGC ( netdev, "NETDEV %s link blocked for %ld ticks\n", + netdev->name, timeout ); + } + start_timer_fixed ( &netdev->link_block, timeout ); +} + +/** + * Mark network device link as being unblocked + * + * @v netdev Network device + */ +void netdev_link_unblock ( struct net_device *netdev ) { + + /* Stop link block timer */ + if ( netdev_link_blocked ( netdev ) ) + DBGC ( netdev, "NETDEV %s link unblocked\n", netdev->name ); + stop_timer ( &netdev->link_block ); +} + +/** + * Handle network device link block timer expiry + * + * @v timer Link block timer + * @v fail Failure indicator + */ +static void netdev_link_block_expired ( struct retry_timer *timer, + int fail __unused ) { + struct net_device *netdev = + container_of ( timer, struct net_device, link_block ); + + /* Assume link is no longer blocked */ + DBGC ( netdev, "NETDEV %s link block expired\n", netdev->name ); +} + +/** * Record network device statistic * * @v stats Network device statistics @@ -252,11 +304,8 @@ int netdev_tx ( struct net_device *netdev, struct io_buffer *iobuf ) { } /* Discard packet (for test purposes) if applicable */ - if ( ( NETDEV_DISCARD_RATE > 0 ) && - ( ( random() % NETDEV_DISCARD_RATE ) == 0 ) ) { - rc = -EAGAIN; + if ( ( rc = inject_fault ( NETDEV_DISCARD_RATE ) ) != 0 ) goto err; - } /* Transmit packet */ if ( ( rc = netdev->op->transmit ( netdev, iobuf ) ) != 0 ) @@ -406,14 +455,14 @@ static void netdev_tx_flush ( struct net_device *netdev ) { * function takes ownership of the I/O buffer. */ void netdev_rx ( struct net_device *netdev, struct io_buffer *iobuf ) { + int rc; DBGC2 ( netdev, "NETDEV %s received %p (%p+%zx)\n", netdev->name, iobuf, iobuf->data, iob_len ( iobuf ) ); /* Discard packet (for test purposes) if applicable */ - if ( ( NETDEV_DISCARD_RATE > 0 ) && - ( ( random() % NETDEV_DISCARD_RATE ) == 0 ) ) { - netdev_rx_err ( netdev, iobuf, -EAGAIN ); + if ( ( rc = inject_fault ( NETDEV_DISCARD_RATE ) ) != 0 ) { + netdev_rx_err ( netdev, iobuf, rc ); return; } @@ -541,7 +590,8 @@ static struct interface_descriptor netdev_config_desc = static void free_netdev ( struct refcnt *refcnt ) { struct net_device *netdev = container_of ( refcnt, struct net_device, refcnt ); - + + stop_timer ( &netdev->link_block ); netdev_tx_flush ( netdev ); netdev_rx_flush ( netdev ); clear_settings ( netdev_settings ( netdev ) ); @@ -571,6 +621,8 @@ struct net_device * alloc_netdev ( size_t priv_len ) { if ( netdev ) { ref_init ( &netdev->refcnt, free_netdev ); netdev->link_rc = -EUNKNOWN_LINK_STATUS; + timer_init ( &netdev->link_block, netdev_link_block_expired, + &netdev->refcnt ); INIT_LIST_HEAD ( &netdev->tx_queue ); INIT_LIST_HEAD ( &netdev->tx_deferred ); INIT_LIST_HEAD ( &netdev->rx_queue ); @@ -624,11 +676,11 @@ int register_netdev ( struct net_device *netdev ) { } /* Record device index and create device name */ - netdev->index = netdev_index++; if ( netdev->name[0] == '\0' ) { snprintf ( netdev->name, sizeof ( netdev->name ), "net%d", - netdev->index ); + netdev_index ); } + netdev->index = ++netdev_index; /* Use least significant bits of the link-layer address to * improve the randomness of the (non-cryptographic) random diff --git a/qemu/roms/ipxe/src/net/nullnet.c b/qemu/roms/ipxe/src/net/nullnet.c index 4ac50f64b..2948b38c0 100644 --- a/qemu/roms/ipxe/src/net/nullnet.c +++ b/qemu/roms/ipxe/src/net/nullnet.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/net/pccrc.c b/qemu/roms/ipxe/src/net/pccrc.c new file mode 100644 index 000000000..4cd82cd1c --- /dev/null +++ b/qemu/roms/ipxe/src/net/pccrc.c @@ -0,0 +1,818 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <errno.h> +#include <assert.h> +#include <ipxe/uaccess.h> +#include <ipxe/sha256.h> +#include <ipxe/sha512.h> +#include <ipxe/hmac.h> +#include <ipxe/base16.h> +#include <ipxe/pccrc.h> + +/** @file + * + * Peer Content Caching and Retrieval: Content Identification [MS-PCCRC] + * + */ + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + +/** + * Transcribe hash value (for debugging) + * + * @v info Content information + * @v hash Hash value + * @ret string Hash value string + */ +static inline const char * +peerdist_info_hash_ntoa ( const struct peerdist_info *info, const void *hash ) { + static char buf[ ( 2 * PEERDIST_DIGEST_MAX_SIZE ) + 1 /* NUL */ ]; + size_t digestsize = info->digestsize; + + /* Sanity check */ + assert ( info != NULL ); + assert ( digestsize != 0 ); + assert ( base16_encoded_len ( digestsize ) < sizeof ( buf ) ); + + /* Transcribe hash value */ + base16_encode ( hash, digestsize, buf, sizeof ( buf ) ); + return buf; +} + +/** + * Get raw data + * + * @v info Content information + * @v data Data buffer + * @v offset Starting offset + * @v len Length + * @ret rc Return status code + */ +static int peerdist_info_get ( const struct peerdist_info *info, void *data, + size_t offset, size_t len ) { + + /* Sanity check */ + if ( ( offset > info->raw.len ) || + ( len > ( info->raw.len - offset ) ) ) { + DBGC ( info, "PCCRC %p data underrun at [%zx,%zx) of %zx\n", + info, offset, ( offset + len ), info->raw.len ); + return -ERANGE; + } + + /* Copy data */ + copy_from_user ( data, info->raw.data, offset, len ); + + return 0; +} + +/** + * Populate segment hashes + * + * @v segment Content information segment to fill in + * @v hash Segment hash of data + * @v secret Segment secret + */ +static void peerdist_info_segment_hash ( struct peerdist_info_segment *segment, + const void *hash, const void *secret ){ + const struct peerdist_info *info = segment->info; + struct digest_algorithm *digest = info->digest; + uint8_t ctx[digest->ctxsize]; + size_t digestsize = info->digestsize; + size_t secretsize = digestsize; + static const uint16_t magic[] = PEERDIST_SEGMENT_ID_MAGIC; + + /* Sanity check */ + assert ( digestsize <= sizeof ( segment->hash ) ); + assert ( digestsize <= sizeof ( segment->secret ) ); + assert ( digestsize <= sizeof ( segment->id ) ); + + /* Get segment hash of data */ + memcpy ( segment->hash, hash, digestsize ); + + /* Get segment secret */ + memcpy ( segment->secret, secret, digestsize ); + + /* Calculate segment identifier */ + hmac_init ( digest, ctx, segment->secret, &secretsize ); + assert ( secretsize == digestsize ); + hmac_update ( digest, ctx, segment->hash, digestsize ); + hmac_update ( digest, ctx, magic, sizeof ( magic ) ); + hmac_final ( digest, ctx, segment->secret, &secretsize, segment->id ); + assert ( secretsize == digestsize ); +} + +/****************************************************************************** + * + * Content Information version 1 + * + ****************************************************************************** + */ + +/** + * Get number of blocks within a block description + * + * @v info Content information + * @v offset Block description offset + * @ret blocks Number of blocks, or negative error + */ +static int peerdist_info_v1_blocks ( const struct peerdist_info *info, + size_t offset ) { + struct peerdist_info_v1_block raw; + unsigned int blocks; + int rc; + + /* Get block description header */ + if ( ( rc = peerdist_info_get ( info, &raw, offset, + sizeof ( raw ) ) ) != 0 ) + return rc; + + /* Calculate number of blocks */ + blocks = le32_to_cpu ( raw.blocks ); + + return blocks; +} + +/** + * Locate block description + * + * @v info Content information + * @v index Segment index + * @ret offset Block description offset, or negative error + */ +static ssize_t peerdist_info_v1_block_offset ( const struct peerdist_info *info, + unsigned int index ) { + size_t digestsize = info->digestsize; + unsigned int i; + size_t offset; + int blocks; + int rc; + + /* Sanity check */ + assert ( index < info->segments ); + + /* Calculate offset of first block description */ + offset = ( sizeof ( struct peerdist_info_v1 ) + + ( info->segments * + sizeof ( peerdist_info_v1_segment_t ( digestsize ) ) ) ); + + /* Iterate over block descriptions until we find this segment */ + for ( i = 0 ; i < index ; i++ ) { + + /* Get number of blocks */ + blocks = peerdist_info_v1_blocks ( info, offset ); + if ( blocks < 0 ) { + rc = blocks; + DBGC ( info, "PCCRC %p segment %d could not get number " + "of blocks: %s\n", info, i, strerror ( rc ) ); + return rc; + } + + /* Move to next block description */ + offset += sizeof ( peerdist_info_v1_block_t ( digestsize, + blocks ) ); + } + + return offset; +} + +/** + * Populate content information + * + * @v info Content information to fill in + * @ret rc Return status code + */ +static int peerdist_info_v1 ( struct peerdist_info *info ) { + struct peerdist_info_v1 raw; + struct peerdist_info_segment first; + struct peerdist_info_segment last; + size_t first_skip; + size_t last_skip; + size_t last_read; + int rc; + + /* Get raw header */ + if ( ( rc = peerdist_info_get ( info, &raw, 0, sizeof ( raw ) ) ) != 0){ + DBGC ( info, "PCCRC %p could not get V1 content information: " + "%s\n", info, strerror ( rc ) ); + return rc; + } + assert ( raw.version.raw == cpu_to_le16 ( PEERDIST_INFO_V1 ) ); + + /* Determine hash algorithm */ + switch ( raw.hash ) { + case cpu_to_le32 ( PEERDIST_INFO_V1_HASH_SHA256 ) : + info->digest = &sha256_algorithm; + break; + case cpu_to_le32 ( PEERDIST_INFO_V1_HASH_SHA384 ) : + info->digest = &sha384_algorithm; + break; + case cpu_to_le32 ( PEERDIST_INFO_V1_HASH_SHA512 ) : + info->digest = &sha512_algorithm; + break; + default: + DBGC ( info, "PCCRC %p unsupported hash algorithm %#08x\n", + info, le32_to_cpu ( raw.hash ) ); + return -ENOTSUP; + } + info->digestsize = info->digest->digestsize; + assert ( info->digest != NULL ); + DBGC2 ( info, "PCCRC %p using %s[%zd]\n", + info, info->digest->name, ( info->digestsize * 8 ) ); + + /* Calculate number of segments */ + info->segments = le32_to_cpu ( raw.segments ); + + /* Get first segment */ + if ( ( rc = peerdist_info_segment ( info, &first, 0 ) ) != 0 ) + return rc; + + /* Calculate range start offset */ + info->range.start = first.range.start; + + /* Calculate trimmed range start offset */ + first_skip = le32_to_cpu ( raw.first ); + info->trim.start = ( first.range.start + first_skip ); + + /* Get last segment */ + if ( ( rc = peerdist_info_segment ( info, &last, + ( info->segments - 1 ) ) ) != 0 ) + return rc; + + /* Calculate range end offset */ + info->range.end = last.range.end; + + /* Calculate trimmed range end offset */ + if ( raw.last ) { + /* Explicit length to include from last segment is given */ + last_read = le32_to_cpu ( raw.last ); + last_skip = ( last.index ? 0 : first_skip ); + info->trim.end = ( last.range.start + last_skip + last_read ); + } else { + /* No explicit length given: range extends to end of segment */ + info->trim.end = last.range.end; + } + + return 0; +} + +/** + * Populate content information segment + * + * @v segment Content information segment to fill in + * @ret rc Return status code + */ +static int peerdist_info_v1_segment ( struct peerdist_info_segment *segment ) { + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + peerdist_info_v1_segment_t ( digestsize ) raw; + ssize_t raw_offset; + int blocks; + int rc; + + /* Sanity checks */ + assert ( segment->index < info->segments ); + + /* Get raw description */ + raw_offset = ( sizeof ( struct peerdist_info_v1 ) + + ( segment->index * sizeof ( raw ) ) ); + if ( ( rc = peerdist_info_get ( info, &raw, raw_offset, + sizeof ( raw ) ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not get segment " + "description: %s\n", info, segment->index, + strerror ( rc ) ); + return rc; + } + + /* Calculate start offset of this segment */ + segment->range.start = le64_to_cpu ( raw.segment.offset ); + + /* Calculate end offset of this segment */ + segment->range.end = ( segment->range.start + + le32_to_cpu ( raw.segment.len ) ); + + /* Calculate block size of this segment */ + segment->blksize = le32_to_cpu ( raw.segment.blksize ); + + /* Locate block description for this segment */ + raw_offset = peerdist_info_v1_block_offset ( info, segment->index ); + if ( raw_offset < 0 ) { + rc = raw_offset; + return rc; + } + + /* Get number of blocks */ + blocks = peerdist_info_v1_blocks ( info, raw_offset ); + if ( blocks < 0 ) { + rc = blocks; + DBGC ( info, "PCCRC %p segment %d could not get number of " + "blocks: %s\n", info, segment->index, strerror ( rc ) ); + return rc; + } + segment->blocks = blocks; + + /* Calculate segment hashes */ + peerdist_info_segment_hash ( segment, raw.hash, raw.secret ); + + return 0; +} + +/** + * Populate content information block + * + * @v block Content information block to fill in + * @ret rc Return status code + */ +static int peerdist_info_v1_block ( struct peerdist_info_block *block ) { + const struct peerdist_info_segment *segment = block->segment; + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + peerdist_info_v1_block_t ( digestsize, segment->blocks ) raw; + ssize_t raw_offset; + int rc; + + /* Sanity checks */ + assert ( block->index < segment->blocks ); + + /* Calculate start offset of this block */ + block->range.start = ( segment->range.start + + ( block->index * segment->blksize ) ); + + /* Calculate end offset of this block */ + block->range.end = ( block->range.start + segment->blksize ); + if ( block->range.end > segment->range.end ) + block->range.end = segment->range.end; + + /* Locate block description */ + raw_offset = peerdist_info_v1_block_offset ( info, segment->index ); + if ( raw_offset < 0 ) { + rc = raw_offset; + return rc; + } + + /* Get block hash */ + raw_offset += offsetof ( typeof ( raw ), hash[block->index] ); + if ( ( rc = peerdist_info_get ( info, block->hash, raw_offset, + digestsize ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d block %d could not get " + "hash: %s\n", info, segment->index, block->index, + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** Content information version 1 operations */ +static struct peerdist_info_operations peerdist_info_v1_operations = { + .info = peerdist_info_v1, + .segment = peerdist_info_v1_segment, + .block = peerdist_info_v1_block, +}; + +/****************************************************************************** + * + * Content Information version 2 + * + ****************************************************************************** + */ + +/** A segment cursor */ +struct peerdist_info_v2_cursor { + /** Raw data offset */ + size_t offset; + /** Number of segments remaining within this chunk */ + unsigned int remaining; + /** Accumulated segment length */ + size_t len; +}; + +/** + * Initialise segment cursor + * + * @v cursor Segment cursor + */ +static inline void +peerdist_info_v2_cursor_init ( struct peerdist_info_v2_cursor *cursor ) { + + /* Initialise cursor */ + cursor->offset = ( sizeof ( struct peerdist_info_v2 ) + + sizeof ( struct peerdist_info_v2_chunk ) ); + cursor->remaining = 0; + cursor->len = 0; +} + +/** + * Update segment cursor to next segment description + * + * @v info Content information + * @v offset Current offset + * @v remaining Number of segments remaining within this chunk + * @ret rc Return status code + */ +static int +peerdist_info_v2_cursor_next ( const struct peerdist_info *info, + struct peerdist_info_v2_cursor *cursor ) { + size_t digestsize = info->digestsize; + peerdist_info_v2_segment_t ( digestsize ) raw; + struct peerdist_info_v2_chunk chunk; + int rc; + + /* Get chunk description if applicable */ + if ( ! cursor->remaining ) { + + /* Get chunk description */ + if ( ( rc = peerdist_info_get ( info, &chunk, + ( cursor->offset - + sizeof ( chunk ) ), + sizeof ( chunk ) ) ) != 0 ) + return rc; + + /* Update number of segments remaining */ + cursor->remaining = ( be32_to_cpu ( chunk.len ) / + sizeof ( raw ) ); + } + + /* Get segment description header */ + if ( ( rc = peerdist_info_get ( info, &raw.segment, cursor->offset, + sizeof ( raw.segment ) ) ) != 0 ) + return rc; + + /* Update cursor */ + cursor->offset += sizeof ( raw ); + cursor->remaining--; + if ( ! cursor->remaining ) + cursor->offset += sizeof ( chunk ); + cursor->len += be32_to_cpu ( raw.segment.len ); + + return 0; +} + +/** + * Get number of segments and total length + * + * @v info Content information + * @v len Length to fill in + * @ret rc Number of segments, or negative error + */ +static int peerdist_info_v2_segments ( const struct peerdist_info *info, + size_t *len ) { + struct peerdist_info_v2_cursor cursor; + unsigned int segments; + int rc; + + /* Iterate over all segments */ + for ( peerdist_info_v2_cursor_init ( &cursor ), segments = 0 ; + cursor.offset < info->raw.len ; segments++ ) { + + /* Update segment cursor */ + if ( ( rc = peerdist_info_v2_cursor_next ( info, + &cursor ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not update " + "segment cursor: %s\n", + info, segments, strerror ( rc ) ); + return rc; + } + } + + /* Record accumulated length */ + *len = cursor.len; + + return segments; +} + +/** + * Populate content information + * + * @v info Content information to fill in + * @ret rc Return status code + */ +static int peerdist_info_v2 ( struct peerdist_info *info ) { + struct peerdist_info_v2 raw; + size_t len = 0; + int segments; + int rc; + + /* Get raw header */ + if ( ( rc = peerdist_info_get ( info, &raw, 0, sizeof ( raw ) ) ) != 0){ + DBGC ( info, "PCCRC %p could not get V2 content information: " + "%s\n", info, strerror ( rc ) ); + return rc; + } + assert ( raw.version.raw == cpu_to_le16 ( PEERDIST_INFO_V2 ) ); + + /* Determine hash algorithm */ + switch ( raw.hash ) { + case PEERDIST_INFO_V2_HASH_SHA512_TRUNC : + info->digest = &sha512_algorithm; + info->digestsize = ( 256 / 8 ); + break; + default: + DBGC ( info, "PCCRC %p unsupported hash algorithm %#02x\n", + info, raw.hash ); + return -ENOTSUP; + } + assert ( info->digest != NULL ); + DBGC2 ( info, "PCCRC %p using %s[%zd]\n", + info, info->digest->name, ( info->digestsize * 8 ) ); + + /* Calculate number of segments and total length */ + segments = peerdist_info_v2_segments ( info, &len ); + if ( segments < 0 ) { + rc = segments; + DBGC ( info, "PCCRC %p could not get segment count and length: " + "%s\n", info, strerror ( rc ) ); + return rc; + } + info->segments = segments; + + /* Calculate range start offset */ + info->range.start = be64_to_cpu ( raw.offset ); + + /* Calculate trimmed range start offset */ + info->trim.start = ( info->range.start + be32_to_cpu ( raw.first ) ); + + /* Calculate range end offset */ + info->range.end = ( info->range.start + len ); + + /* Calculate trimmed range end offset */ + info->trim.end = ( raw.len ? be64_to_cpu ( raw.len ) : + info->range.end ); + + return 0; +} + +/** + * Populate content information segment + * + * @v segment Content information segment to fill in + * @ret rc Return status code + */ +static int peerdist_info_v2_segment ( struct peerdist_info_segment *segment ) { + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + peerdist_info_v2_segment_t ( digestsize ) raw; + struct peerdist_info_v2_cursor cursor; + unsigned int index; + size_t len; + int rc; + + /* Sanity checks */ + assert ( segment->index < info->segments ); + + /* Iterate over all segments before the target segment */ + for ( peerdist_info_v2_cursor_init ( &cursor ), index = 0 ; + index < segment->index ; index++ ) { + + /* Update segment cursor */ + if ( ( rc = peerdist_info_v2_cursor_next ( info, + &cursor ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not update " + "segment cursor: %s\n", + info, index, strerror ( rc ) ); + return rc; + } + } + + /* Get raw description */ + if ( ( rc = peerdist_info_get ( info, &raw, cursor.offset, + sizeof ( raw ) ) ) != 0 ) { + DBGC ( info, "PCCRC %p segment %d could not get segment " + "description: %s\n", + info, segment->index, strerror ( rc ) ); + return rc; + } + + /* Calculate start offset of this segment */ + segment->range.start = ( info->range.start + cursor.len ); + + /* Calculate end offset of this segment */ + len = be32_to_cpu ( raw.segment.len ); + segment->range.end = ( segment->range.start + len ); + + /* Model as a segment containing a single block */ + segment->blocks = 1; + segment->blksize = len; + + /* Calculate segment hashes */ + peerdist_info_segment_hash ( segment, raw.hash, raw.secret ); + + return 0; +} + +/** + * Populate content information block + * + * @v block Content information block to fill in + * @ret rc Return status code + */ +static int peerdist_info_v2_block ( struct peerdist_info_block *block ) { + const struct peerdist_info_segment *segment = block->segment; + const struct peerdist_info *info = segment->info; + size_t digestsize = info->digestsize; + + /* Sanity checks */ + assert ( block->index < segment->blocks ); + + /* Model as a block covering the whole segment */ + memcpy ( &block->range, &segment->range, sizeof ( block->range ) ); + memcpy ( block->hash, segment->hash, digestsize ); + + return 0; +} + +/** Content information version 2 operations */ +static struct peerdist_info_operations peerdist_info_v2_operations = { + .block = peerdist_info_v2_block, + .segment = peerdist_info_v2_segment, + .info = peerdist_info_v2, +}; + +/****************************************************************************** + * + * Content Information + * + ****************************************************************************** + */ + +/** + * Populate content information + * + * @v data Raw data + * @v len Length of raw data + * @v info Content information to fill in + * @ret rc Return status code + */ +int peerdist_info ( userptr_t data, size_t len, struct peerdist_info *info ) { + union peerdist_info_version version; + int rc; + + /* Initialise structure */ + memset ( info, 0, sizeof ( *info ) ); + info->raw.data = data; + info->raw.len = len; + + /* Get version */ + if ( ( rc = peerdist_info_get ( info, &version, 0, + sizeof ( version ) ) ) != 0 ) { + DBGC ( info, "PCCRC %p could not get version: %s\n", + info, strerror ( rc ) ); + return rc; + } + DBGC2 ( info, "PCCRC %p version %d.%d\n", + info, version.major, version.minor ); + + /* Determine version */ + switch ( version.raw ) { + case cpu_to_le16 ( PEERDIST_INFO_V1 ) : + info->op = &peerdist_info_v1_operations; + break; + case cpu_to_le16 ( PEERDIST_INFO_V2 ) : + info->op = &peerdist_info_v2_operations; + break; + default: + DBGC ( info, "PCCRC %p unsupported version %d.%d\n", + info, version.major, version.minor ); + return -ENOTSUP; + } + assert ( info->op != NULL ); + assert ( info->op->info != NULL ); + + /* Populate content information */ + if ( ( rc = info->op->info ( info ) ) != 0 ) + return rc; + + DBGC2 ( info, "PCCRC %p range [%08zx,%08zx) covers [%08zx,%08zx) with " + "%d segments\n", info, info->range.start, info->range.end, + info->trim.start, info->trim.end, info->segments ); + return 0; +} + +/** + * Populate content information segment + * + * @v info Content information + * @v segment Content information segment to fill in + * @v index Segment index + * @ret rc Return status code + */ +int peerdist_info_segment ( const struct peerdist_info *info, + struct peerdist_info_segment *segment, + unsigned int index ) { + int rc; + + /* Sanity checks */ + assert ( info != NULL ); + assert ( info->op != NULL ); + assert ( info->op->segment != NULL ); + if ( index >= info->segments ) { + DBGC ( info, "PCCRC %p segment %d of [0,%d) out of range\n", + info, index, info->segments ); + return -ERANGE; + } + + /* Initialise structure */ + memset ( segment, 0, sizeof ( *segment ) ); + segment->info = info; + segment->index = index; + + /* Populate content information segment */ + if ( ( rc = info->op->segment ( segment ) ) != 0 ) + return rc; + + DBGC2 ( info, "PCCRC %p segment %d range [%08zx,%08zx) with %d " + "blocks\n", info, segment->index, segment->range.start, + segment->range.end, segment->blocks ); + DBGC2 ( info, "PCCRC %p segment %d digest %s\n", info, segment->index, + peerdist_info_hash_ntoa ( info, segment->hash ) ); + DBGC2 ( info, "PCCRC %p segment %d secret %s\n", info, segment->index, + peerdist_info_hash_ntoa ( info, segment->secret ) ); + DBGC2 ( info, "PCCRC %p segment %d identf %s\n", info, segment->index, + peerdist_info_hash_ntoa ( info, segment->id ) ); + return 0; +} + +/** + * Populate content information block + * + * @v segment Content information segment + * @v block Content information block to fill in + * @v index Block index + * @ret rc Return status code + */ +int peerdist_info_block ( const struct peerdist_info_segment *segment, + struct peerdist_info_block *block, + unsigned int index ) { + const struct peerdist_info *info = segment->info; + size_t start; + size_t end; + int rc; + + /* Sanity checks */ + assert ( segment != NULL ); + assert ( info != NULL ); + assert ( info->op != NULL ); + assert ( info->op->block != NULL ); + if ( index >= segment->blocks ) { + DBGC ( info, "PCCRC %p segment %d block %d of [0,%d) out of " + "range\n", info, segment->index, index, segment->blocks); + return -ERANGE; + } + + /* Initialise structure */ + memset ( block, 0, sizeof ( *block ) ); + block->segment = segment; + block->index = index; + + /* Populate content information block */ + if ( ( rc = info->op->block ( block ) ) != 0 ) + return rc; + + /* Calculate trimmed range */ + start = block->range.start; + if ( start < info->trim.start ) + start = info->trim.start; + end = block->range.end; + if ( end > info->trim.end ) + end = info->trim.end; + if ( end < start ) + end = start; + block->trim.start = start; + block->trim.end = end; + + DBGC2 ( info, "PCCRC %p segment %d block %d hash %s\n", + info, segment->index, block->index, + peerdist_info_hash_ntoa ( info, block->hash ) ); + DBGC2 ( info, "PCCRC %p segment %d block %d range [%08zx,%08zx) covers " + "[%08zx,%08zx)\n", info, segment->index, block->index, + block->range.start, block->range.end, block->trim.start, + block->trim.end ); + return 0; +} diff --git a/qemu/roms/ipxe/src/net/pccrd.c b/qemu/roms/ipxe/src/net/pccrd.c new file mode 100644 index 000000000..04b5dd86c --- /dev/null +++ b/qemu/roms/ipxe/src/net/pccrd.c @@ -0,0 +1,286 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stddef.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/pccrd.h> + +/** @file + * + * Peer Content Caching and Retrieval: Discovery Protocol [MS-PCCRD] + * + * This protocol manages to ingeniously combine the excessive + * verbosity of XML with a paucity of actual information. For + * example: even in version 2.0 of the protocol it is still not + * possible to discover which peers hold a specific block within a + * given segment. + * + * For added bonus points, version 1.0 of the protocol is specified to + * use a case-sensitive string comparison (for SHA2 digest values) but + * nothing specifies whether the strings in question should be in + * upper or lower case. There are example strings given in the + * specification, but the author skilfully manages to leave the issue + * unresolved by using the somewhat implausible digest value of + * "0200000000000000000000000000000000000000000000000000000000000000". + * + * Just in case you were thinking that the silver lining of the choice + * to use an XML-based protocol would be the ability to generate and + * process messages with standard tools, version 2.0 of the protocol + * places most of the critical information inside a Base64-encoded + * custom binary data structure. Within an XML element, naturally. + * + * I hereby announce this specification to be the 2015 winner of the + * prestigious "UEFI HII API" award for incompetent design. + */ + +/** Discovery request format */ +#define PEERDIST_DISCOVERY_REQUEST \ + "<?xml version=\"1.0\" encoding=\"utf-8\"?>" \ + "<soap:Envelope " \ + "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" " \ + "xmlns:wsa=\"http://schemas.xmlsoap.org/ws/2004/08/addressing\" " \ + "xmlns:wsd=\"http://schemas.xmlsoap.org/ws/2005/04/discovery\" " \ + "xmlns:PeerDist=\"http://schemas.microsoft.com/p2p/" \ + "2007/09/PeerDistributionDiscovery\">" \ + "<soap:Header>" \ + "<wsa:To>" \ + "urn:schemas-xmlsoap-org:ws:2005:04:discovery" \ + "</wsa:To>" \ + "<wsa:Action>" \ + "http://schemas.xmlsoap.org/ws/2005/04/discovery/Probe" \ + "</wsa:Action>" \ + "<wsa:MessageID>" \ + "urn:uuid:%s" \ + "</wsa:MessageID>" \ + "</soap:Header>" \ + "<soap:Body>" \ + "<wsd:Probe>" \ + "<wsd:Types>" \ + "PeerDist:PeerDistData" \ + "</wsd:Types>" \ + "<wsd:Scopes MatchBy=\"http://schemas.xmlsoap.org/ws/" \ + "2005/04/discovery/strcmp0\">" \ + "%s" \ + "</wsd:Scopes>" \ + "</wsd:Probe>" \ + "</soap:Body>" \ + "</soap:Envelope>" + +/** + * Construct discovery request + * + * @v uuid Message UUID string + * @v id Segment identifier string + * @ret request Discovery request, or NULL on failure + * + * The request is dynamically allocated; the caller must eventually + * free() the request. + */ +char * peerdist_discovery_request ( const char *uuid, const char *id ) { + char *request; + int len; + + /* Construct request */ + len = asprintf ( &request, PEERDIST_DISCOVERY_REQUEST, uuid, id ); + if ( len < 0 ) + return NULL; + + return request; +} + +/** + * Locate discovery reply tag + * + * @v data Reply data (not NUL-terminated) + * @v len Length of reply data + * @v tag XML tag + * @ret found Found tag (or NULL if not found) + */ +static char * peerdist_discovery_reply_tag ( char *data, size_t len, + const char *tag ) { + size_t tag_len = strlen ( tag ); + + /* Search, allowing for the fact that the reply data is not + * cleanly NUL-terminated and may contain embedded NULs due to + * earlier parsing. + */ + for ( ; len >= tag_len ; data++, len-- ) { + if ( strncmp ( data, tag, tag_len ) == 0 ) + return data; + } + return NULL; +} + +/** + * Locate discovery reply values + * + * @v data Reply data (not NUL-terminated, will be modified) + * @v len Length of reply data + * @v name XML tag name + * @ret values Tag values (or NULL if not found) + * + * The reply data is modified by adding NULs and moving characters as + * needed to produce a NUL-separated list of values, terminated with a + * zero-length string. + * + * This is not supposed to be a full XML parser; it's supposed to + * include just enough functionality to allow PeerDist discovery to + * work with existing implementations. + */ +static char * peerdist_discovery_reply_values ( char *data, size_t len, + const char *name ) { + char buf[ 2 /* "</" */ + strlen ( name ) + 1 /* ">" */ + 1 /* NUL */ ]; + char *open; + char *close; + char *start; + char *end; + char *in; + char *out; + char c; + + /* Locate opening tag */ + snprintf ( buf, sizeof ( buf ), "<%s>", name ); + open = peerdist_discovery_reply_tag ( data, len, buf ); + if ( ! open ) + return NULL; + start = ( open + strlen ( buf ) ); + len -= ( start - data ); + data = start; + + /* Locate closing tag */ + snprintf ( buf, sizeof ( buf ), "</%s>", name ); + close = peerdist_discovery_reply_tag ( data, len, buf ); + if ( ! close ) + return NULL; + assert ( close >= open ); + end = close; + + /* Strip initial whitespace, convert other whitespace + * sequences to single NULs, add terminating pair of NULs. + * This will probably overwrite part of the closing tag. + */ + for ( in = start, out = start ; in < end ; in++ ) { + c = *in; + if ( isspace ( c ) ) { + if ( ( out > start ) && ( out[-1] != '\0' ) ) + *(out++) = '\0'; + } else { + *(out++) = c; + } + } + *(out++) = '\0'; + *(out++) = '\0'; + assert ( out < ( close + strlen ( buf ) ) ); + + return start; +} + +/** + * Parse discovery reply + * + * @v data Reply data (not NUL-terminated, will be modified) + * @v len Length of reply data + * @v reply Discovery reply to fill in + * @ret rc Return status code + * + * The discovery reply includes pointers to strings within the + * modified reply data. + */ +int peerdist_discovery_reply ( char *data, size_t len, + struct peerdist_discovery_reply *reply ) { + static const struct peerdist_discovery_block_count zcount = { + .hex = "00000000", + }; + struct peerdist_discovery_block_count *count; + unsigned int max; + unsigned int i; + char *scopes; + char *xaddrs; + char *blockcount; + char *in; + char *out; + size_t skip; + + /* Find <wsd:Scopes> tag */ + scopes = peerdist_discovery_reply_values ( data, len, "wsd:Scopes" ); + if ( ! scopes ) { + DBGC ( reply, "PCCRD %p missing <wsd:Scopes> tag\n", reply ); + return -ENOENT; + } + + /* Find <wsd:XAddrs> tag */ + xaddrs = peerdist_discovery_reply_values ( data, len, "wsd:XAddrs" ); + if ( ! xaddrs ) { + DBGC ( reply, "PCCRD %p missing <wsd:XAddrs> tag\n", reply ); + return -ENOENT; + } + + /* Find <PeerDist:BlockCount> tag */ + blockcount = peerdist_discovery_reply_values ( data, len, + "PeerDist:BlockCount" ); + if ( ! blockcount ) { + DBGC ( reply, "PCCRD %p missing <PeerDist:BlockCount> tag\n", + reply ); + return -ENOENT; + } + + /* Determine maximum number of segments (according to number + * of entries in the block count list). + */ + max = ( strlen ( blockcount ) / sizeof ( *count ) ); + count = container_of ( blockcount, + struct peerdist_discovery_block_count, hex[0] ); + + /* Eliminate any segments with a zero block count */ + for ( i = 0, in = scopes, out = scopes ; *in ; i++, in += skip ) { + + /* Fail if we have overrun the maximum number of segments */ + if ( i >= max ) { + DBGC ( reply, "PCCRD %p too many segment IDs\n", + reply ); + return -EPROTO; + } + + /* Delete segment if block count is zero */ + skip = ( strlen ( in ) + 1 /* NUL */ ); + if ( memcmp ( count[i].hex, zcount.hex, + sizeof ( zcount.hex ) ) == 0 ) + continue; + strcpy ( out, in ); + out += skip; + } + out[0] = '\0'; /* Ensure list is terminated with a zero-length string */ + + /* Fill in discovery reply */ + reply->ids = scopes; + reply->locations = xaddrs; + + return 0; +} diff --git a/qemu/roms/ipxe/src/net/peerblk.c b/qemu/roms/ipxe/src/net/peerblk.c new file mode 100644 index 000000000..fd7ea0893 --- /dev/null +++ b/qemu/roms/ipxe/src/net/peerblk.c @@ -0,0 +1,1366 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdlib.h> +#include <stdio.h> +#include <errno.h> +#include <ipxe/http.h> +#include <ipxe/iobuf.h> +#include <ipxe/xfer.h> +#include <ipxe/uri.h> +#include <ipxe/timer.h> +#include <ipxe/profile.h> +#include <ipxe/fault.h> +#include <ipxe/pccrr.h> +#include <ipxe/peerblk.h> + +/** @file + * + * Peer Content Caching and Retrieval (PeerDist) protocol block downloads + * + */ + +/** PeerDist decryption chunksize + * + * This is a policy decision. + */ +#define PEERBLK_DECRYPT_CHUNKSIZE 2048 + +/** PeerDist raw block download attempt initial progress timeout + * + * This is a policy decision. + */ +#define PEERBLK_RAW_OPEN_TIMEOUT ( 10 * TICKS_PER_SEC ) + +/** PeerDist raw block download attempt ongoing progress timeout + * + * This is a policy decision. + */ +#define PEERBLK_RAW_RX_TIMEOUT ( 15 * TICKS_PER_SEC ) + +/** PeerDist retrieval protocol block download attempt initial progress timeout + * + * This is a policy decision. + */ +#define PEERBLK_RETRIEVAL_OPEN_TIMEOUT ( 3 * TICKS_PER_SEC ) + +/** PeerDist retrieval protocol block download attempt ongoing progress timeout + * + * This is a policy decision. + */ +#define PEERBLK_RETRIEVAL_RX_TIMEOUT ( 5 * TICKS_PER_SEC ) + +/** PeerDist maximum number of full download attempt cycles + * + * This is the maximum number of times that we will try a full cycle + * of download attempts (i.e. a retrieval protocol download attempt + * from each discovered peer plus a raw download attempt from the + * origin server). + * + * This is a policy decision. + */ +#define PEERBLK_MAX_ATTEMPT_CYCLES 4 + +/** PeerDist block download profiler */ +static struct profiler peerblk_download_profiler __profiler = + { .name = "peerblk.download" }; + +/** PeerDist block download attempt success profiler */ +static struct profiler peerblk_attempt_success_profiler __profiler = + { .name = "peerblk.attempt.success" }; + +/** PeerDist block download attempt failure profiler */ +static struct profiler peerblk_attempt_failure_profiler __profiler = + { .name = "peerblk.attempt.failure" }; + +/** PeerDist block download attempt timeout profiler */ +static struct profiler peerblk_attempt_timeout_profiler __profiler = + { .name = "peerblk.attempt.timeout" }; + +/** PeerDist block download discovery success profiler */ +static struct profiler peerblk_discovery_success_profiler __profiler = + { .name = "peerblk.discovery.success" }; + +/** PeerDist block download discovery timeout profiler */ +static struct profiler peerblk_discovery_timeout_profiler __profiler = + { .name = "peerblk.discovery.timeout" }; + +/** + * Get profiling timestamp + * + * @ret timestamp Timestamp + */ +static inline __attribute__ (( always_inline )) unsigned long +peerblk_timestamp ( void ) { + + if ( PROFILING ) { + return currticks(); + } else { + return 0; + } +} + +/** + * Free PeerDist block download + * + * @v refcnt Reference count + */ +static void peerblk_free ( struct refcnt *refcnt ) { + struct peerdist_block *peerblk = + container_of ( refcnt, struct peerdist_block, refcnt ); + + uri_put ( peerblk->uri ); + free ( peerblk->cipherctx ); + free ( peerblk ); +} + +/** + * Reset PeerDist block download attempt + * + * @v peerblk PeerDist block download + * @v rc Reason for reset + */ +static void peerblk_reset ( struct peerdist_block *peerblk, int rc ) { + + /* Stop decryption process */ + process_del ( &peerblk->process ); + + /* Stop timer */ + stop_timer ( &peerblk->timer ); + + /* Abort any current download attempt */ + intf_restart ( &peerblk->raw, rc ); + intf_restart ( &peerblk->retrieval, rc ); + + /* Empty received data buffer */ + xferbuf_free ( &peerblk->buffer ); + peerblk->pos = 0; + + /* Reset digest and free cipher context */ + digest_init ( peerblk->digest, peerblk->digestctx ); + free ( peerblk->cipherctx ); + peerblk->cipherctx = NULL; + peerblk->cipher = NULL; + + /* Reset trim thresholds */ + peerblk->start = ( peerblk->trim.start - peerblk->range.start ); + peerblk->end = ( peerblk->trim.end - peerblk->range.start ); + assert ( peerblk->start <= peerblk->end ); +} + +/** + * Close PeerDist block download + * + * @v peerblk PeerDist block download + * @v rc Reason for close + */ +static void peerblk_close ( struct peerdist_block *peerblk, int rc ) { + unsigned long now = peerblk_timestamp(); + + /* Profile overall block download */ + profile_custom ( &peerblk_download_profiler, + ( now - peerblk->started ) ); + + /* Reset download attempt */ + peerblk_reset ( peerblk, rc ); + + /* Close discovery */ + peerdisc_close ( &peerblk->discovery ); + + /* Shut down all interfaces */ + intf_shutdown ( &peerblk->retrieval, rc ); + intf_shutdown ( &peerblk->raw, rc ); + intf_shutdown ( &peerblk->xfer, rc ); +} + +/** + * Calculate offset within overall download + * + * @v peerblk PeerDist block download + * @v pos Position within incoming data stream + * @ret offset Offset within overall download + */ +static inline __attribute__ (( always_inline )) size_t +peerblk_offset ( struct peerdist_block *peerblk, size_t pos ) { + + return ( ( pos - peerblk->start ) + peerblk->offset ); +} + +/** + * Deliver download attempt data block + * + * @v peerblk PeerDist block download + * @v iobuf I/O buffer + * @v meta Original data transfer metadata + * @v pos Position within incoming data stream + * @ret rc Return status code + */ +static int peerblk_deliver ( struct peerdist_block *peerblk, + struct io_buffer *iobuf, + struct xfer_metadata *meta, size_t pos ) { + struct xfer_metadata xfer_meta; + size_t len = iob_len ( iobuf ); + size_t start = pos; + size_t end = ( pos + len ); + int rc; + + /* Discard zero-length packets and packets which lie entirely + * outside the trimmed range. + */ + if ( ( start >= peerblk->end ) || ( end <= peerblk->start ) || + ( len == 0 ) ) { + free_iob ( iobuf ); + return 0; + } + + /* Truncate data to within trimmed range */ + if ( start < peerblk->start ) { + iob_pull ( iobuf, ( peerblk->start - start ) ); + start = peerblk->start; + } + if ( end > peerblk->end ) { + iob_unput ( iobuf, ( end - peerblk->end ) ); + end = peerblk->end; + } + + /* Construct metadata */ + memcpy ( &xfer_meta, meta, sizeof ( xfer_meta ) ); + xfer_meta.flags |= XFER_FL_ABS_OFFSET; + xfer_meta.offset = peerblk_offset ( peerblk, start ); + + /* Deliver data */ + if ( ( rc = xfer_deliver ( &peerblk->xfer, iob_disown ( iobuf ), + &xfer_meta ) ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d could not deliver data: %s\n", + peerblk, peerblk->segment, peerblk->block, + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Finish PeerDist block download attempt + * + * @v peerblk PeerDist block download + * @v rc Reason for close + */ +static void peerblk_done ( struct peerdist_block *peerblk, int rc ) { + struct digest_algorithm *digest = peerblk->digest; + uint8_t hash[digest->digestsize]; + unsigned long now = peerblk_timestamp(); + + /* Check for errors on completion */ + if ( rc != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d attempt failed: %s\n", + peerblk, peerblk->segment, peerblk->block, + strerror ( rc ) ); + goto err; + } + + /* Check digest */ + digest_final ( digest, peerblk->digestctx, hash ); + if ( memcmp ( hash, peerblk->hash, peerblk->digestsize ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d digest mismatch:\n", + peerblk, peerblk->segment, peerblk->block ); + DBGC_HDA ( peerblk, 0, hash, peerblk->digestsize ); + DBGC_HDA ( peerblk, 0, peerblk->hash, peerblk->digestsize ); + rc = -EIO; + goto err; + } + + /* Profile successful attempt */ + profile_custom ( &peerblk_attempt_success_profiler, + ( now - peerblk->attempted ) ); + + /* Close download */ + peerblk_close ( peerblk, 0 ); + return; + + err: + /* Record failure reason and schedule a retry attempt */ + profile_custom ( &peerblk_attempt_failure_profiler, + ( now - peerblk->attempted ) ); + peerblk_reset ( peerblk, rc ); + peerblk->rc = rc; + start_timer_nodelay ( &peerblk->timer ); +} + +/****************************************************************************** + * + * Raw block download attempts (using an HTTP range request) + * + ****************************************************************************** + */ + +/** + * Open PeerDist raw block download attempt + * + * @v peerblk PeerDist block download + * @ret rc Return status code + */ +static int peerblk_raw_open ( struct peerdist_block *peerblk ) { + struct http_request_range range; + int rc; + + DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting raw range request\n", + peerblk, peerblk->segment, peerblk->block ); + + /* Construct HTTP range */ + memset ( &range, 0, sizeof ( range ) ); + range.start = peerblk->range.start; + range.len = ( peerblk->range.end - peerblk->range.start ); + + /* Initiate range request to retrieve block */ + if ( ( rc = http_open ( &peerblk->raw, &http_get, peerblk->uri, + &range, NULL ) ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d could not create range " + "request: %s\n", peerblk, peerblk->segment, + peerblk->block, strerror ( rc ) ); + return rc; + } + + /* Annul HTTP connection (for testing) if applicable. Do not + * report as an immediate error, in order to test our ability + * to recover from a totally unresponsive HTTP server. + */ + if ( inject_fault ( PEERBLK_ANNUL_RATE ) ) + intf_restart ( &peerblk->raw, 0 ); + + return 0; +} + +/** + * Receive PeerDist raw data + * + * @v peerblk PeerDist block download + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int peerblk_raw_rx ( struct peerdist_block *peerblk, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + size_t len = iob_len ( iobuf ); + size_t pos = peerblk->pos; + size_t mid = ( ( peerblk->range.end - peerblk->range.start ) / 2 ); + int rc; + + /* Corrupt received data (for testing) if applicable */ + inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len ); + + /* Fail if data is delivered out of order, since the streaming + * digest requires strict ordering. + */ + if ( ( rc = xfer_check_order ( meta, &peerblk->pos, len ) ) != 0 ) + goto err; + + /* Add data to digest */ + digest_update ( peerblk->digest, peerblk->digestctx, iobuf->data, len ); + + /* Deliver data */ + if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta, + pos ) ) != 0 ) + goto err; + + /* Extend download attempt timer */ + start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_RX_TIMEOUT ); + + /* Stall download attempt (for testing) if applicable */ + if ( ( pos < mid ) && ( ( pos + len ) >= mid ) && + ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) { + intf_restart ( &peerblk->raw, rc ); + } + + return 0; + + err: + free_iob ( iobuf ); + peerblk_done ( peerblk, rc ); + return rc; +} + +/** + * Close PeerDist raw block download attempt + * + * @v peerblk PeerDist block download + * @v rc Reason for close + */ +static void peerblk_raw_close ( struct peerdist_block *peerblk, int rc ) { + + /* Restart interface */ + intf_restart ( &peerblk->raw, rc ); + + /* Fail immediately if we have an error */ + if ( rc != 0 ) + goto done; + + /* Abort download attempt (for testing) if applicable */ + if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 ) + goto done; + + done: + /* Complete download attempt */ + peerblk_done ( peerblk, rc ); +} + +/****************************************************************************** + * + * Retrieval protocol block download attempts (using HTTP POST) + * + ****************************************************************************** + */ + +/** + * Construct PeerDist retrieval protocol URI + * + * @v location Peer location + * @ret uri Retrieval URI, or NULL on error + */ +static struct uri * peerblk_retrieval_uri ( const char *location ) { + char uri_string[ 7 /* "http://" */ + strlen ( location ) + + sizeof ( PEERDIST_MAGIC_PATH /* includes NUL */ ) ]; + + /* Construct URI string */ + snprintf ( uri_string, sizeof ( uri_string ), + ( "http://%s" PEERDIST_MAGIC_PATH ), location ); + + /* Parse URI string */ + return parse_uri ( uri_string ); +} + +/** + * Open PeerDist retrieval protocol block download attempt + * + * @v peerblk PeerDist block download + * @v location Peer location + * @ret rc Return status code + */ +static int peerblk_retrieval_open ( struct peerdist_block *peerblk, + const char *location ) { + size_t digestsize = peerblk->digestsize; + peerdist_msg_getblks_t ( digestsize, 1, 0 ) req; + peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *rsp; + struct http_request_content content; + struct uri *uri; + int rc; + + DBGC2 ( peerblk, "PEERBLK %p %d.%d attempting retrieval from %s\n", + peerblk, peerblk->segment, peerblk->block, location ); + + /* Construct block fetch request */ + memset ( &req, 0, sizeof ( req ) ); + req.getblks.hdr.version.raw = htonl ( PEERDIST_MSG_GETBLKS_VERSION ); + req.getblks.hdr.type = htonl ( PEERDIST_MSG_GETBLKS_TYPE ); + req.getblks.hdr.len = htonl ( sizeof ( req ) ); + req.getblks.hdr.algorithm = htonl ( PEERDIST_MSG_AES_128_CBC ); + req.segment.segment.digestsize = htonl ( digestsize ); + memcpy ( req.segment.id, peerblk->id, digestsize ); + req.ranges.ranges.count = htonl ( 1 ); + req.ranges.range[0].first = htonl ( peerblk->block ); + req.ranges.range[0].count = htonl ( 1 ); + + /* Construct POST request content */ + memset ( &content, 0, sizeof ( content ) ); + content.data = &req; + content.len = sizeof ( req ); + + /* Construct URI */ + if ( ( uri = peerblk_retrieval_uri ( location ) ) == NULL ) { + rc = -ENOMEM; + goto err_uri; + } + + /* Update trim thresholds */ + peerblk->start += offsetof ( typeof ( *rsp ), msg.vrf ); + peerblk->end += offsetof ( typeof ( *rsp ), msg.vrf ); + + /* Initiate HTTP POST to retrieve block */ + if ( ( rc = http_open ( &peerblk->retrieval, &http_post, uri, + NULL, &content ) ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d could not create retrieval " + "request: %s\n", peerblk, peerblk->segment, + peerblk->block, strerror ( rc ) ); + goto err_open; + } + + /* Annul HTTP connection (for testing) if applicable. Do not + * report as an immediate error, in order to test our ability + * to recover from a totally unresponsive HTTP server. + */ + if ( inject_fault ( PEERBLK_ANNUL_RATE ) ) + intf_restart ( &peerblk->retrieval, 0 ); + + err_open: + uri_put ( uri ); + err_uri: + return rc; +} + +/** + * Receive PeerDist retrieval protocol data + * + * @v peerblk PeerDist block download + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int peerblk_retrieval_rx ( struct peerdist_block *peerblk, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + size_t len = iob_len ( iobuf ); + size_t start; + size_t end; + size_t before; + size_t after; + size_t cut; + int rc; + + /* Some genius at Microsoft thought it would be a great idea + * to place the AES-CBC initialisation vector *after* the + * encrypted data, thereby making it logically impossible to + * decrypt each packet as it arrives. + * + * To work around this mindless stupidity, we deliver the + * ciphertext as-is and later use xfer_buffer() to obtain + * access to the underlying data transfer buffer in order to + * perform the decryption. + * + * There will be some data both before and after the bytes + * corresponding to the trimmed plaintext: a MSG_BLK + * header/footer, some block padding for the AES-CBC cipher, + * and a possibly large quantity of unwanted ciphertext which + * is excluded from the trimmed content range. We store this + * data in a local data transfer buffer. If the amount of + * data to be stored is too large, we will fail allocation and + * so eventually fall back to using a range request (which + * does not require this kind of temporary storage + * allocation). + */ + + /* Corrupt received data (for testing) if applicable */ + inject_corruption ( PEERBLK_CORRUPT_RATE, iobuf->data, len ); + + /* Calculate start and end positions of this buffer */ + start = peerblk->pos; + if ( meta->flags & XFER_FL_ABS_OFFSET ) + start = 0; + start += meta->offset; + end = ( start + len ); + + /* Buffer any data before the trimmed content */ + if ( ( start < peerblk->start ) && ( len > 0 ) ) { + + /* Calculate length of data before the trimmed content */ + before = ( peerblk->start - start ); + if ( before > len ) + before = len; + + /* Buffer data before the trimmed content */ + if ( ( rc = xferbuf_write ( &peerblk->buffer, start, + iobuf->data, before ) ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer " + "data: %s\n", peerblk, peerblk->segment, + peerblk->block, strerror ( rc ) ); + goto err; + } + } + + /* Buffer any data after the trimmed content */ + if ( ( end > peerblk->end ) && ( len > 0 ) ) { + + /* Calculate length of data after the trimmed content */ + after = ( end - peerblk->end ); + if ( after > len ) + after = len; + + /* Buffer data after the trimmed content */ + cut = ( peerblk->end - peerblk->start ); + if ( ( rc = xferbuf_write ( &peerblk->buffer, + ( end - after - cut ), + ( iobuf->data + len - after ), + after ) ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d could not buffer " + "data: %s\n", peerblk, peerblk->segment, + peerblk->block, strerror ( rc ) ); + goto err; + } + } + + /* Deliver any remaining data */ + if ( ( rc = peerblk_deliver ( peerblk, iob_disown ( iobuf ), meta, + start ) ) != 0 ) + goto err; + + /* Update position */ + peerblk->pos = end; + + /* Extend download attempt timer */ + start_timer_fixed ( &peerblk->timer, PEERBLK_RETRIEVAL_RX_TIMEOUT ); + + /* Stall download attempt (for testing) if applicable */ + if ( ( start < peerblk->end ) && ( end >= peerblk->end ) && + ( ( rc = inject_fault ( PEERBLK_STALL_RATE ) ) != 0 ) ) { + intf_restart ( &peerblk->retrieval, rc ); + } + + return 0; + + err: + free_iob ( iobuf ); + peerblk_done ( peerblk, rc ); + return rc; +} + +/** + * Parse retrieval protocol message header + * + * @v peerblk PeerDist block download + * @ret rc Return status code + */ +static int peerblk_parse_header ( struct peerdist_block *peerblk ) { + struct { + struct peerdist_msg_transport_header hdr; + struct peerdist_msg_header msg; + } __attribute__ (( packed )) *msg = peerblk->buffer.data; + struct cipher_algorithm *cipher; + size_t len = peerblk->buffer.len; + size_t keylen = 0; + int rc; + + /* Check message length */ + if ( len < sizeof ( *msg ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d message too short for header " + "(%zd bytes)\n", peerblk, peerblk->segment, + peerblk->block, len ); + return -ERANGE; + } + + /* Check message type */ + if ( msg->msg.type != htonl ( PEERDIST_MSG_BLK_TYPE ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d unexpected message type " + "%#08x\n", peerblk, peerblk->segment, peerblk->block, + ntohl ( msg->msg.type ) ); + return -EPROTO; + } + + /* Determine cipher algorithm and key length */ + cipher = &aes_cbc_algorithm; + switch ( msg->msg.algorithm ) { + case htonl ( PEERDIST_MSG_PLAINTEXT ) : + cipher = NULL; + break; + case htonl ( PEERDIST_MSG_AES_128_CBC ) : + keylen = ( 128 / 8 ); + break; + case htonl ( PEERDIST_MSG_AES_192_CBC ) : + keylen = ( 192 / 8 ); + break; + case htonl ( PEERDIST_MSG_AES_256_CBC ) : + keylen = ( 256 / 8 ); + break; + default: + DBGC ( peerblk, "PEERBLK %p %d.%d unrecognised algorithm " + "%#08x\n", peerblk, peerblk->segment, peerblk->block, + ntohl ( msg->msg.algorithm ) ); + return -ENOTSUP; + } + DBGC2 ( peerblk, "PEERBLK %p %d.%d using %s with %zd-bit key\n", + peerblk, peerblk->segment, peerblk->block, + ( cipher ? cipher->name : "plaintext" ), ( 8 * keylen ) ); + + /* Sanity check key length against maximum secret length */ + if ( keylen > peerblk->digestsize ) { + DBGC ( peerblk, "PEERBLK %p %d.%d %zd-byte secret too short " + "for %zd-bit key\n", peerblk, peerblk->segment, + peerblk->block, peerblk->digestsize, ( 8 * keylen ) ); + return -EPROTO; + } + + /* Allocate cipher context. Freeing the cipher context (on + * error or otherwise) is handled by peerblk_reset(). + */ + peerblk->cipher = cipher; + assert ( peerblk->cipherctx == NULL ); + peerblk->cipherctx = malloc ( cipher->ctxsize ); + if ( ! peerblk->cipherctx ) + return -ENOMEM; + + /* Initialise cipher */ + if ( ( rc = cipher_setkey ( cipher, peerblk->cipherctx, peerblk->secret, + keylen ) ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d could not set key: %s\n", + peerblk, peerblk->segment, peerblk->block, + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Parse retrieval protocol message segment and block details + * + * @v peerblk PeerDist block download + * @v buf_len Length of buffered data to fill in + * @ret rc Return status code + */ +static int peerblk_parse_block ( struct peerdist_block *peerblk, + size_t *buf_len ) { + size_t digestsize = peerblk->digestsize; + peerblk_msg_blk_t ( digestsize, 0, 0, 0 ) *msg = peerblk->buffer.data; + size_t len = peerblk->buffer.len; + size_t data_len; + size_t total; + + /* Check message length */ + if ( len < offsetof ( typeof ( *msg ), msg.block.data ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d message too short for " + "zero-length data (%zd bytes)\n", peerblk, + peerblk->segment, peerblk->block, len ); + return -ERANGE; + } + + /* Check digest size */ + if ( ntohl ( msg->msg.segment.segment.digestsize ) != digestsize ) { + DBGC ( peerblk, "PEERBLK %p %d.%d incorrect digest size %d\n", + peerblk, peerblk->segment, peerblk->block, + ntohl ( msg->msg.segment.segment.digestsize ) ); + return -EPROTO; + } + + /* Check segment ID */ + if ( memcmp ( msg->msg.segment.id, peerblk->id, digestsize ) != 0 ) { + DBGC ( peerblk, "PEERBLK %p %d.%d segment ID mismatch\n", + peerblk, peerblk->segment, peerblk->block ); + return -EPROTO; + } + + /* Check block ID */ + if ( ntohl ( msg->msg.index ) != peerblk->block ) { + DBGC ( peerblk, "PEERBLK %p %d.%d block ID mismatch (got %d)\n", + peerblk, peerblk->segment, peerblk->block, + ntohl ( msg->msg.index ) ); + return -EPROTO; + } + + /* Check for missing blocks */ + data_len = be32_to_cpu ( msg->msg.block.block.len ); + if ( ! data_len ) { + DBGC ( peerblk, "PEERBLK %p %d.%d block not found\n", + peerblk, peerblk->segment, peerblk->block ); + return -ENOENT; + } + + /* Check for underlength blocks */ + if ( data_len < ( peerblk->range.end - peerblk->range.start ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d underlength block (%zd " + "bytes)\n", peerblk, peerblk->segment, peerblk->block, + data_len ); + return -ERANGE; + } + + /* Calculate buffered data length (i.e. excluding data which + * was delivered to the final data transfer buffer). + */ + *buf_len = ( data_len - ( peerblk->end - peerblk->start ) ); + + /* Describe data before the trimmed content */ + peerblk->decrypt[PEERBLK_BEFORE].xferbuf = &peerblk->buffer; + peerblk->decrypt[PEERBLK_BEFORE].offset = + offsetof ( typeof ( *msg ), msg.block.data ); + peerblk->decrypt[PEERBLK_BEFORE].len = + ( peerblk->start - + offsetof ( typeof ( *msg ), msg.block.data ) ); + total = peerblk->decrypt[PEERBLK_BEFORE].len; + + /* Describe data within the trimmed content */ + peerblk->decrypt[PEERBLK_DURING].offset = + peerblk_offset ( peerblk, peerblk->start ); + peerblk->decrypt[PEERBLK_DURING].len = + ( peerblk->end - peerblk->start ); + total += peerblk->decrypt[PEERBLK_DURING].len; + + /* Describe data after the trimmed content */ + peerblk->decrypt[PEERBLK_AFTER].xferbuf = &peerblk->buffer; + peerblk->decrypt[PEERBLK_AFTER].offset = peerblk->start; + peerblk->decrypt[PEERBLK_AFTER].len = + ( offsetof ( typeof ( *msg ), msg.block.data ) + + *buf_len - peerblk->start ); + total += peerblk->decrypt[PEERBLK_AFTER].len; + + /* Sanity check */ + assert ( total == be32_to_cpu ( msg->msg.block.block.len ) ); + + /* Initialise cipher and digest lengths */ + peerblk->cipher_remaining = total; + peerblk->digest_remaining = + ( peerblk->range.end - peerblk->range.start ); + assert ( peerblk->cipher_remaining >= peerblk->digest_remaining ); + + return 0; +} + +/** + * Parse retrieval protocol message useless details + * + * @v peerblk PeerDist block download + * @v buf_len Length of buffered data + * @v vrf_len Length of uselessness to fill in + * @ret rc Return status code + */ +static int peerblk_parse_useless ( struct peerdist_block *peerblk, + size_t buf_len, size_t *vrf_len ) { + size_t digestsize = peerblk->digestsize; + peerblk_msg_blk_t ( digestsize, buf_len, 0, 0 ) *msg = + peerblk->buffer.data; + size_t len = peerblk->buffer.len; + + /* Check message length */ + if ( len < offsetof ( typeof ( *msg ), msg.vrf.data ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d message too short for " + "zero-length uselessness (%zd bytes)\n", peerblk, + peerblk->segment, peerblk->block, len ); + return -ERANGE; + } + + /* Extract length of uselessness */ + *vrf_len = be32_to_cpu ( msg->msg.vrf.vrf.len ); + + return 0; +} + +/** + * Parse retrieval protocol message initialisation vector details + * + * @v peerblk PeerDist block download + * @v buf_len Length of buffered data + * @v vrf_len Length of uselessness + * @ret rc Return status code + */ +static int peerblk_parse_iv ( struct peerdist_block *peerblk, size_t buf_len, + size_t vrf_len ) { + size_t digestsize = peerblk->digestsize; + size_t blksize = peerblk->cipher->blocksize; + peerblk_msg_blk_t ( digestsize, buf_len, vrf_len, blksize ) *msg = + peerblk->buffer.data; + size_t len = peerblk->buffer.len; + + /* Check message length */ + if ( len < sizeof ( *msg ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d message too short for " + "initialisation vector (%zd bytes)\n", peerblk, + peerblk->segment, peerblk->block, len ); + return -ERANGE; + } + + /* Check initialisation vector size */ + if ( ntohl ( msg->msg.iv.iv.blksize ) != blksize ) { + DBGC ( peerblk, "PEERBLK %p %d.%d incorrect IV size %d\n", + peerblk, peerblk->segment, peerblk->block, + ntohl ( msg->msg.iv.iv.blksize ) ); + return -EPROTO; + } + + /* Set initialisation vector */ + cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data ); + + return 0; +} + +/** + * Read from decryption buffers + * + * @v peerblk PeerDist block download + * @v data Data buffer + * @v len Length to read + * @ret rc Return status code + */ +static int peerblk_decrypt_read ( struct peerdist_block *peerblk, + void *data, size_t len ) { + struct peerdist_block_decrypt *decrypt = peerblk->decrypt; + size_t frag_len; + int rc; + + /* Read from each decryption buffer in turn */ + for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) { + + /* Calculate length to use from this buffer */ + frag_len = decrypt->len; + if ( frag_len > len ) + frag_len = len; + if ( ! frag_len ) + continue; + + /* Read from this buffer */ + if ( ( rc = xferbuf_read ( decrypt->xferbuf, decrypt->offset, + data, frag_len ) ) != 0 ) + return rc; + } + + return 0; +} + +/** + * Write to decryption buffers and update offsets and lengths + * + * @v peerblk PeerDist block download + * @v data Data buffer + * @v len Length to read + * @ret rc Return status code + */ +static int peerblk_decrypt_write ( struct peerdist_block *peerblk, + const void *data, size_t len ) { + struct peerdist_block_decrypt *decrypt = peerblk->decrypt; + size_t frag_len; + int rc; + + /* Write to each decryption buffer in turn */ + for ( ; len ; decrypt++, data += frag_len, len -= frag_len ) { + + /* Calculate length to use from this buffer */ + frag_len = decrypt->len; + if ( frag_len > len ) + frag_len = len; + if ( ! frag_len ) + continue; + + /* Write to this buffer */ + if ( ( rc = xferbuf_write ( decrypt->xferbuf, decrypt->offset, + data, frag_len ) ) != 0 ) + return rc; + + /* Update offset and length */ + decrypt->offset += frag_len; + decrypt->len -= frag_len; + } + + return 0; +} + +/** + * Decrypt one chunk of PeerDist retrieval protocol data + * + * @v peerblk PeerDist block download + */ +static void peerblk_decrypt ( struct peerdist_block *peerblk ) { + struct cipher_algorithm *cipher = peerblk->cipher; + struct digest_algorithm *digest = peerblk->digest; + struct xfer_buffer *xferbuf; + size_t cipher_len; + size_t digest_len; + void *data; + int rc; + + /* Sanity check */ + assert ( ( PEERBLK_DECRYPT_CHUNKSIZE % cipher->blocksize ) == 0 ); + + /* Get the underlying data transfer buffer */ + xferbuf = xfer_buffer ( &peerblk->xfer ); + if ( ! xferbuf ) { + DBGC ( peerblk, "PEERBLK %p %d.%d has no underlying data " + "transfer buffer\n", peerblk, peerblk->segment, + peerblk->block ); + rc = -ENOTSUP; + goto err_xfer_buffer; + } + peerblk->decrypt[PEERBLK_DURING].xferbuf = xferbuf; + + /* Calculate cipher and digest lengths */ + cipher_len = PEERBLK_DECRYPT_CHUNKSIZE; + if ( cipher_len > peerblk->cipher_remaining ) + cipher_len = peerblk->cipher_remaining; + digest_len = cipher_len; + if ( digest_len > peerblk->digest_remaining ) + digest_len = peerblk->digest_remaining; + assert ( ( cipher_len & ( cipher->blocksize - 1 ) ) == 0 ); + + /* Allocate temporary data buffer */ + data = malloc ( cipher_len ); + if ( ! data ) { + rc = -ENOMEM; + goto err_alloc_data; + } + + /* Read ciphertext */ + if ( ( rc = peerblk_decrypt_read ( peerblk, data, cipher_len ) ) != 0 ){ + DBGC ( peerblk, "PEERBLK %p %d.%d could not read ciphertext: " + "%s\n", peerblk, peerblk->segment, peerblk->block, + strerror ( rc ) ); + goto err_read; + } + + /* Decrypt data */ + cipher_decrypt ( cipher, peerblk->cipherctx, data, data, cipher_len ); + + /* Add data to digest */ + digest_update ( digest, peerblk->digestctx, data, digest_len ); + + /* Write plaintext */ + if ( ( rc = peerblk_decrypt_write ( peerblk, data, cipher_len ) ) != 0){ + DBGC ( peerblk, "PEERBLK %p %d.%d could not write plaintext: " + "%s\n", peerblk, peerblk->segment, peerblk->block, + strerror ( rc ) ); + goto err_write; + } + + /* Consume input */ + peerblk->cipher_remaining -= cipher_len; + peerblk->digest_remaining -= digest_len; + + /* Free temporary data buffer */ + free ( data ); + + /* Continue processing until all input is consumed */ + if ( peerblk->cipher_remaining ) + return; + + /* Complete download attempt */ + peerblk_done ( peerblk, 0 ); + return; + + err_write: + err_read: + free ( data ); + err_alloc_data: + err_xfer_buffer: + peerblk_done ( peerblk, rc ); +} + +/** + * Close PeerDist retrieval protocol block download attempt + * + * @v peerblk PeerDist block download + * @v rc Reason for close + */ +static void peerblk_retrieval_close ( struct peerdist_block *peerblk, int rc ) { + size_t buf_len; + size_t vrf_len; + + /* Restart interface */ + intf_restart ( &peerblk->retrieval, rc ); + + /* Fail immediately if we have an error */ + if ( rc != 0 ) + goto done; + + /* Abort download attempt (for testing) if applicable */ + if ( ( rc = inject_fault ( PEERBLK_ABORT_RATE ) ) != 0 ) + goto done; + + /* Parse message header */ + if ( ( rc = peerblk_parse_header ( peerblk ) ) != 0 ) + goto done; + + /* Parse message segment and block details */ + if ( ( rc = peerblk_parse_block ( peerblk, &buf_len ) ) != 0 ) + goto done; + + /* If the block was plaintext, then there is nothing more to do */ + if ( ! peerblk->cipher ) + goto done; + + /* Parse message useless details */ + if ( ( rc = peerblk_parse_useless ( peerblk, buf_len, &vrf_len ) ) != 0) + goto done; + + /* Parse message initialisation vector details */ + if ( ( rc = peerblk_parse_iv ( peerblk, buf_len, vrf_len ) ) != 0 ) + goto done; + + /* Fail if decryption length is not aligned to the cipher block size */ + if ( peerblk->cipher_remaining & ( peerblk->cipher->blocksize - 1 ) ) { + DBGC ( peerblk, "PEERBLK %p %d.%d unaligned data length %zd\n", + peerblk, peerblk->segment, peerblk->block, + peerblk->cipher_remaining ); + rc = -EPROTO; + goto done; + } + + /* Stop the download attempt timer: there is no point in + * timing out while decrypting. + */ + stop_timer ( &peerblk->timer ); + + /* Start decryption process */ + process_add ( &peerblk->process ); + return; + + done: + /* Complete download attempt */ + peerblk_done ( peerblk, rc ); +} + +/****************************************************************************** + * + * Retry policy + * + ****************************************************************************** + */ + +/** + * Handle PeerDist retry timer expiry + * + * @v timer Retry timer + * @v over Failure indicator + */ +static void peerblk_expired ( struct retry_timer *timer, int over __unused ) { + struct peerdist_block *peerblk = + container_of ( timer, struct peerdist_block, timer ); + struct peerdisc_segment *segment = peerblk->discovery.segment; + struct peerdisc_peer *head; + unsigned long now = peerblk_timestamp(); + const char *location; + int rc; + + /* Profile discovery timeout, if applicable */ + if ( ( peerblk->peer == NULL ) && ( timer->timeout != 0 ) ) { + profile_custom ( &peerblk_discovery_timeout_profiler, + ( now - peerblk->started ) ); + DBGC ( peerblk, "PEERBLK %p %d.%d discovery timed out after " + "%ld ticks\n", peerblk, peerblk->segment, + peerblk->block, timer->timeout ); + } + + /* Profile download timeout, if applicable */ + if ( ( peerblk->peer != NULL ) && ( timer->timeout != 0 ) ) { + profile_custom ( &peerblk_attempt_timeout_profiler, + ( now - peerblk->attempted ) ); + DBGC ( peerblk, "PEERBLK %p %d.%d timed out after %ld ticks\n", + peerblk, peerblk->segment, peerblk->block, + timer->timeout ); + } + + /* Abort any current download attempt */ + peerblk_reset ( peerblk, -ETIMEDOUT ); + + /* Record attempt start time */ + peerblk->attempted = now; + + /* If we have exceeded our maximum number of attempt cycles + * (each cycle comprising a retrieval protocol download from + * each peer in the list followed by a raw download from the + * origin server), then abort the overall download. + */ + head = list_entry ( &segment->peers, struct peerdisc_peer, list ); + if ( ( peerblk->peer == head ) && + ( ++peerblk->cycles >= PEERBLK_MAX_ATTEMPT_CYCLES ) ) { + rc = peerblk->rc; + assert ( rc != 0 ); + goto err; + } + + /* If we have not yet made any download attempts, then move to + * the start of the peer list. + */ + if ( peerblk->peer == NULL ) + peerblk->peer = head; + + /* Attempt retrieval protocol download from next usable peer */ + list_for_each_entry_continue ( peerblk->peer, &segment->peers, list ) { + + /* Attempt retrieval protocol download from this peer */ + location = peerblk->peer->location; + if ( ( rc = peerblk_retrieval_open ( peerblk, + location ) ) != 0 ) { + /* Non-fatal: continue to try next peer */ + continue; + } + + /* Start download attempt timer */ + peerblk->rc = -ETIMEDOUT; + start_timer_fixed ( &peerblk->timer, + PEERBLK_RETRIEVAL_OPEN_TIMEOUT ); + return; + } + + /* Attempt raw download */ + if ( ( rc = peerblk_raw_open ( peerblk ) ) != 0 ) + goto err; + + /* Start download attempt timer */ + peerblk->rc = -ETIMEDOUT; + start_timer_fixed ( &peerblk->timer, PEERBLK_RAW_OPEN_TIMEOUT ); + return; + + err: + peerblk_close ( peerblk, rc ); +} + +/** + * Handle PeerDist peer discovery + * + * @v discovery PeerDist discovery client + */ +static void peerblk_discovered ( struct peerdisc_client *discovery ) { + struct peerdist_block *peerblk = + container_of ( discovery, struct peerdist_block, discovery ); + unsigned long now = peerblk_timestamp(); + + /* Do nothing unless we are still waiting for the initial + * discovery timeout. + */ + if ( ( peerblk->peer != NULL ) || ( peerblk->timer.timeout == 0 ) ) + return; + + /* Schedule an immediate retry */ + start_timer_nodelay ( &peerblk->timer ); + + /* Profile discovery success */ + profile_custom ( &peerblk_discovery_success_profiler, + ( now - peerblk->started ) ); +} + +/****************************************************************************** + * + * Opener + * + ****************************************************************************** + */ + +/** PeerDist block download data transfer interface operations */ +static struct interface_operation peerblk_xfer_operations[] = { + INTF_OP ( intf_close, struct peerdist_block *, peerblk_close ), +}; + +/** PeerDist block download data transfer interface descriptor */ +static struct interface_descriptor peerblk_xfer_desc = + INTF_DESC ( struct peerdist_block, xfer, peerblk_xfer_operations ); + +/** PeerDist block download raw data interface operations */ +static struct interface_operation peerblk_raw_operations[] = { + INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_raw_rx ), + INTF_OP ( intf_close, struct peerdist_block *, peerblk_raw_close ), +}; + +/** PeerDist block download raw data interface descriptor */ +static struct interface_descriptor peerblk_raw_desc = + INTF_DESC ( struct peerdist_block, raw, peerblk_raw_operations ); + +/** PeerDist block download retrieval protocol interface operations */ +static struct interface_operation peerblk_retrieval_operations[] = { + INTF_OP ( xfer_deliver, struct peerdist_block *, peerblk_retrieval_rx ), + INTF_OP ( intf_close, struct peerdist_block *, peerblk_retrieval_close), +}; + +/** PeerDist block download retrieval protocol interface descriptor */ +static struct interface_descriptor peerblk_retrieval_desc = + INTF_DESC ( struct peerdist_block, retrieval, + peerblk_retrieval_operations ); + +/** PeerDist block download decryption process descriptor */ +static struct process_descriptor peerblk_process_desc = + PROC_DESC ( struct peerdist_block, process, peerblk_decrypt ); + +/** PeerDist block download discovery operations */ +static struct peerdisc_client_operations peerblk_discovery_operations = { + .discovered = peerblk_discovered, +}; + +/** + * Open PeerDist block download + * + * @v xfer Data transfer interface + * @v uri Original URI + * @v info Content information block + * @ret rc Return status code + */ +int peerblk_open ( struct interface *xfer, struct uri *uri, + struct peerdist_info_block *block ) { + const struct peerdist_info_segment *segment = block->segment; + const struct peerdist_info *info = segment->info; + struct digest_algorithm *digest = info->digest; + struct peerdist_block *peerblk; + unsigned long timeout; + size_t digestsize; + int rc; + + /* Allocate and initialise structure */ + peerblk = zalloc ( sizeof ( *peerblk ) + digest->ctxsize ); + if ( ! peerblk ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &peerblk->refcnt, peerblk_free ); + intf_init ( &peerblk->xfer, &peerblk_xfer_desc, &peerblk->refcnt ); + intf_init ( &peerblk->raw, &peerblk_raw_desc, &peerblk->refcnt ); + intf_init ( &peerblk->retrieval, &peerblk_retrieval_desc, + &peerblk->refcnt ); + peerblk->uri = uri_get ( uri ); + memcpy ( &peerblk->range, &block->range, sizeof ( peerblk->range ) ); + memcpy ( &peerblk->trim, &block->trim, sizeof ( peerblk->trim ) ); + peerblk->offset = ( block->trim.start - info->trim.start ); + peerblk->digest = info->digest; + peerblk->digestsize = digestsize = info->digestsize; + peerblk->digestctx = ( ( ( void * ) peerblk ) + sizeof ( *peerblk ) ); + peerblk->segment = segment->index; + memcpy ( peerblk->id, segment->id, sizeof ( peerblk->id ) ); + memcpy ( peerblk->secret, segment->secret, sizeof ( peerblk->secret ) ); + peerblk->block = block->index; + memcpy ( peerblk->hash, block->hash, sizeof ( peerblk->hash ) ); + xferbuf_malloc_init ( &peerblk->buffer ); + process_init_stopped ( &peerblk->process, &peerblk_process_desc, + &peerblk->refcnt ); + peerdisc_init ( &peerblk->discovery, &peerblk_discovery_operations ); + timer_init ( &peerblk->timer, peerblk_expired, &peerblk->refcnt ); + DBGC2 ( peerblk, "PEERBLK %p %d.%d id %02x%02x%02x%02x%02x..." + "%02x%02x%02x [%08zx,%08zx)", peerblk, peerblk->segment, + peerblk->block, peerblk->id[0], peerblk->id[1], peerblk->id[2], + peerblk->id[3], peerblk->id[4], peerblk->id[ digestsize - 3 ], + peerblk->id[ digestsize - 2 ], peerblk->id[ digestsize - 1 ], + peerblk->range.start, peerblk->range.end ); + if ( ( peerblk->trim.start != peerblk->range.start ) || + ( peerblk->trim.end != peerblk->range.end ) ) { + DBGC2 ( peerblk, " covers [%08zx,%08zx)", + peerblk->trim.start, peerblk->trim.end ); + } + DBGC2 ( peerblk, "\n" ); + + /* Open discovery */ + if ( ( rc = peerdisc_open ( &peerblk->discovery, peerblk->id, + peerblk->digestsize ) ) != 0 ) + goto err_open_discovery; + + /* Schedule a retry attempt either immediately (if we already + * have some peers) or after the discovery timeout. + */ + timeout = ( list_empty ( &peerblk->discovery.segment->peers ) ? + ( peerdisc_timeout_secs * TICKS_PER_SEC ) : 0 ); + start_timer_fixed ( &peerblk->timer, timeout ); + + /* Record start time */ + peerblk->started = peerblk_timestamp(); + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( xfer, &peerblk->xfer ); + ref_put ( &peerblk->refcnt ); + return 0; + + err_open_discovery: + peerblk_close ( peerblk, rc ); + err_alloc: + return rc; +} diff --git a/qemu/roms/ipxe/src/net/peerdisc.c b/qemu/roms/ipxe/src/net/peerdisc.c new file mode 100644 index 000000000..5b0e98911 --- /dev/null +++ b/qemu/roms/ipxe/src/net/peerdisc.c @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <assert.h> +#include <ipxe/xfer.h> +#include <ipxe/iobuf.h> +#include <ipxe/open.h> +#include <ipxe/tcpip.h> +#include <ipxe/uuid.h> +#include <ipxe/base16.h> +#include <ipxe/netdevice.h> +#include <ipxe/timer.h> +#include <ipxe/fault.h> +#include <ipxe/pccrd.h> +#include <ipxe/peerdisc.h> + +/** @file + * + * Peer Content Caching and Retrieval (PeerDist) protocol peer discovery + * + */ + +/** List of discovery segments */ +static LIST_HEAD ( peerdisc_segments ); + +/** Number of repeated discovery attempts */ +#define PEERDISC_REPEAT_COUNT 2 + +/** Time between repeated discovery attempts */ +#define PEERDISC_REPEAT_TIMEOUT ( 1 * TICKS_PER_SEC ) + +/** Default discovery timeout (in seconds) */ +#define PEERDISC_DEFAULT_TIMEOUT_SECS 2 + +/** Recommended discovery timeout (in seconds) + * + * We reduce the recommended discovery timeout whenever a segment + * fails to discover any peers, and restore the default value whenever + * a valid discovery reply is received. We continue to send discovery + * requests even if the recommended timeout is reduced to zero. + * + * This strategy is intended to minimise discovery delays when no + * peers are available on the network, while allowing downloads to + * quickly switch back to using PeerDist acceleration if new peers + * become available. + */ +unsigned int peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS; + +static struct peerdisc_segment * peerdisc_find ( const char *id ); +static int peerdisc_discovered ( struct peerdisc_segment *segment, + const char *location ); + +/****************************************************************************** + * + * Discovery sockets + * + ****************************************************************************** + */ + +/** + * Open all PeerDist discovery sockets + * + * @ret rc Return status code + */ +static int peerdisc_socket_open ( void ) { + struct peerdisc_socket *socket; + int rc; + + /* Open each socket */ + for_each_table_entry ( socket, PEERDISC_SOCKETS ) { + if ( ( rc = xfer_open_socket ( &socket->xfer, SOCK_DGRAM, + &socket->address.sa, + NULL ) ) != 0 ) { + DBGC ( socket, "PEERDISC %s could not open socket: " + "%s\n", socket->name, strerror ( rc ) ); + goto err; + } + } + + return 0; + + err: + for_each_table_entry_continue_reverse ( socket, PEERDISC_SOCKETS ) + intf_restart ( &socket->xfer, rc ); + return rc; +} + +/** + * Attempt to transmit PeerDist discovery requests on all sockets + * + * @v uuid Message UUID string + * @v id Segment identifier string + */ +static void peerdisc_socket_tx ( const char *uuid, const char *id ) { + struct peerdisc_socket *socket; + struct net_device *netdev; + struct xfer_metadata meta; + union { + struct sockaddr sa; + struct sockaddr_tcpip st; + } address; + char *request; + size_t len; + int rc; + + /* Construct discovery request */ + request = peerdist_discovery_request ( uuid, id ); + if ( ! request ) + goto err_request; + len = strlen ( request ); + + /* Initialise data transfer metadata */ + memset ( &meta, 0, sizeof ( meta ) ); + meta.dest = &address.sa; + + /* Send message on each socket */ + for_each_table_entry ( socket, PEERDISC_SOCKETS ) { + + /* Initialise socket address */ + memcpy ( &address.sa, &socket->address.sa, + sizeof ( address.sa ) ); + + /* Send message on each open network device */ + for_each_netdev ( netdev ) { + + /* Skip unopened network devices */ + if ( ! netdev_is_open ( netdev ) ) + continue; + address.st.st_scope_id = netdev->index; + + /* Discard request (for test purposes) if applicable */ + if ( inject_fault ( PEERDISC_DISCARD_RATE ) ) + continue; + + /* Transmit request */ + if ( ( rc = xfer_deliver_raw_meta ( &socket->xfer, + request, len, + &meta ) ) != 0 ) { + DBGC ( socket, "PEERDISC %s could not transmit " + "via %s: %s\n", socket->name, + netdev->name, strerror ( rc ) ); + /* Contine to try other net devices/sockets */ + continue; + } + } + } + + free ( request ); + err_request: + return; +} + +/** + * Handle received PeerDist discovery reply + * + * @v socket PeerDist discovery socket + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int peerdisc_socket_rx ( struct peerdisc_socket *socket, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + struct peerdist_discovery_reply reply; + struct peerdisc_segment *segment; + char *id; + char *location; + int rc; + + /* Discard reply (for test purposes) if applicable */ + if ( ( rc = inject_fault ( PEERDISC_DISCARD_RATE ) ) != 0 ) + goto err; + + /* Parse reply */ + if ( ( rc = peerdist_discovery_reply ( iobuf->data, iob_len ( iobuf ), + &reply ) ) != 0 ) { + DBGC ( socket, "PEERDISC %s could not parse reply: %s\n", + socket->name, strerror ( rc ) ); + DBGC_HDA ( socket, 0, iobuf->data, iob_len ( iobuf ) ); + goto err; + } + + /* Any kind of discovery reply indicates that there are active + * peers on a local network, so restore the recommended + * discovery timeout to its default value for future requests. + */ + if ( peerdisc_timeout_secs != PEERDISC_DEFAULT_TIMEOUT_SECS ) { + DBGC ( socket, "PEERDISC %s restoring timeout to %d seconds\n", + socket->name, PEERDISC_DEFAULT_TIMEOUT_SECS ); + } + peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS; + + /* Iterate over segment IDs */ + for ( id = reply.ids ; *id ; id += ( strlen ( id ) + 1 /* NUL */ ) ) { + + /* Find corresponding segment */ + segment = peerdisc_find ( id ); + if ( ! segment ) { + DBGC ( socket, "PEERDISC %s ignoring reply for %s\n", + socket->name, id ); + continue; + } + + /* Report all discovered peer locations */ + for ( location = reply.locations ; *location ; + location += ( strlen ( location ) + 1 /* NUL */ ) ) { + + /* Report discovered peer location */ + if ( ( rc = peerdisc_discovered ( segment, + location ) ) != 0 ) + goto err; + } + } + + err: + free_iob ( iobuf ); + return rc; +} + +/** + * Close all PeerDist discovery sockets + * + * @v rc Reason for close + */ +static void peerdisc_socket_close ( int rc ) { + struct peerdisc_socket *socket; + + /* Close all sockets */ + for_each_table_entry ( socket, PEERDISC_SOCKETS ) + intf_restart ( &socket->xfer, rc ); +} + +/** PeerDist discovery socket interface operations */ +static struct interface_operation peerdisc_socket_operations[] = { + INTF_OP ( xfer_deliver, struct peerdisc_socket *, peerdisc_socket_rx ), +}; + +/** PeerDist discovery socket interface descriptor */ +static struct interface_descriptor peerdisc_socket_desc = + INTF_DESC ( struct peerdisc_socket, xfer, peerdisc_socket_operations ); + +/** PeerDist discovery IPv4 socket */ +struct peerdisc_socket peerdisc_socket_ipv4 __peerdisc_socket = { + .name = "IPv4", + .address = { + .sin = { + .sin_family = AF_INET, + .sin_port = htons ( PEERDIST_DISCOVERY_PORT ), + .sin_addr.s_addr = htonl ( PEERDIST_DISCOVERY_IPV4 ), + }, + }, + .xfer = INTF_INIT ( peerdisc_socket_desc ), +}; + +/** PeerDist discovery IPv6 socket */ +struct peerdisc_socket peerdisc_socket_ipv6 __peerdisc_socket = { + .name = "IPv6", + .address = { + .sin6 = { + .sin6_family = AF_INET6, + .sin6_port = htons ( PEERDIST_DISCOVERY_PORT ), + .sin6_addr.s6_addr = PEERDIST_DISCOVERY_IPV6, + }, + }, + .xfer = INTF_INIT ( peerdisc_socket_desc ), +}; + +/****************************************************************************** + * + * Discovery segments + * + ****************************************************************************** + */ + +/** + * Free PeerDist discovery segment + * + * @v refcnt Reference count + */ +static void peerdisc_free ( struct refcnt *refcnt ) { + struct peerdisc_segment *segment = + container_of ( refcnt, struct peerdisc_segment, refcnt ); + struct peerdisc_peer *peer; + struct peerdisc_peer *tmp; + + /* Free all discovered peers */ + list_for_each_entry_safe ( peer, tmp, &segment->peers, list ) { + list_del ( &peer->list ); + free ( peer ); + } + + /* Free segment */ + free ( segment ); +} + +/** + * Find PeerDist discovery segment + * + * @v id Segment ID + * @ret segment PeerDist discovery segment, or NULL if not found + */ +static struct peerdisc_segment * peerdisc_find ( const char *id ) { + struct peerdisc_segment *segment; + + /* Look for a matching segment */ + list_for_each_entry ( segment, &peerdisc_segments, list ) { + if ( strcmp ( id, segment->id ) == 0 ) + return segment; + } + + return NULL; +} + +/** + * Add discovered PeerDist peer + * + * @v segment PeerDist discovery segment + * @v location Peer location + * @ret rc Return status code + */ +static int peerdisc_discovered ( struct peerdisc_segment *segment, + const char *location ) { + struct peerdisc_peer *peer; + struct peerdisc_client *peerdisc; + struct peerdisc_client *tmp; + + /* Ignore duplicate peers */ + list_for_each_entry ( peer, &segment->peers, list ) { + if ( strcmp ( peer->location, location ) == 0 ) { + DBGC2 ( segment, "PEERDISC %p duplicate %s\n", + segment, location ); + return 0; + } + } + DBGC2 ( segment, "PEERDISC %p discovered %s\n", segment, location ); + + /* Allocate and initialise structure */ + peer = zalloc ( sizeof ( *peer ) + strlen ( location ) + 1 /* NUL */ ); + if ( ! peer ) + return -ENOMEM; + strcpy ( peer->location, location ); + + /* Add to end of list of peers */ + list_add_tail ( &peer->list, &segment->peers ); + + /* Notify all clients */ + list_for_each_entry_safe ( peerdisc, tmp, &segment->clients, list ) + peerdisc->op->discovered ( peerdisc ); + + return 0; +} + +/** + * Handle discovery timer expiry + * + * @v timer Discovery timer + * @v over Failure indicator + */ +static void peerdisc_expired ( struct retry_timer *timer, int over __unused ) { + struct peerdisc_segment *segment = + container_of ( timer, struct peerdisc_segment, timer ); + + /* Attempt to transmit discovery requests */ + peerdisc_socket_tx ( segment->uuid, segment->id ); + + /* Schedule next transmission, if applicable */ + if ( timer->count < PEERDISC_REPEAT_COUNT ) + start_timer_fixed ( &segment->timer, PEERDISC_REPEAT_TIMEOUT ); +} + +/** + * Create PeerDist discovery segment + * + * @v id Segment ID + * @ret segment PeerDist discovery segment, or NULL on error + */ +static struct peerdisc_segment * peerdisc_create ( const char *id ) { + struct peerdisc_segment *segment; + union { + union uuid uuid; + uint32_t dword[ sizeof ( union uuid ) / sizeof ( uint32_t ) ]; + } random_uuid; + size_t uuid_len; + size_t id_len; + char *uuid; + char *uuid_copy; + char *id_copy; + unsigned int i; + + /* Generate a random message UUID. This does not require high + * quality randomness. + */ + for ( i = 0 ; i < ( sizeof ( random_uuid.dword ) / + sizeof ( random_uuid.dword[0] ) ) ; i++ ) + random_uuid.dword[i] = random(); + uuid = uuid_ntoa ( &random_uuid.uuid ); + + /* Calculate string lengths */ + id_len = ( strlen ( id ) + 1 /* NUL */ ); + uuid_len = ( strlen ( uuid ) + 1 /* NUL */ ); + + /* Allocate and initialise structure */ + segment = zalloc ( sizeof ( *segment ) + id_len + uuid_len ); + if ( ! segment ) + return NULL; + id_copy = ( ( ( void * ) segment ) + sizeof ( *segment ) ); + memcpy ( id_copy, id, id_len ); + uuid_copy = ( ( ( void * ) id_copy ) + id_len ); + memcpy ( uuid_copy, uuid, uuid_len ); + ref_init ( &segment->refcnt, peerdisc_free ); + segment->id = id_copy; + segment->uuid = uuid_copy; + INIT_LIST_HEAD ( &segment->peers ); + INIT_LIST_HEAD ( &segment->clients ); + timer_init ( &segment->timer, peerdisc_expired, &segment->refcnt ); + DBGC2 ( segment, "PEERDISC %p discovering %s\n", segment, segment->id ); + + /* Start discovery timer */ + start_timer_nodelay ( &segment->timer ); + + /* Add to list of segments, transfer reference to list, and return */ + list_add_tail ( &segment->list, &peerdisc_segments ); + return segment; +} + +/** + * Destroy PeerDist discovery segment + * + * @v segment PeerDist discovery segment + */ +static void peerdisc_destroy ( struct peerdisc_segment *segment ) { + + /* Sanity check */ + assert ( list_empty ( &segment->clients ) ); + + /* Stop timer */ + stop_timer ( &segment->timer ); + + /* Remove from list of segments and drop list's reference */ + list_del ( &segment->list ); + ref_put ( &segment->refcnt ); +} + +/****************************************************************************** + * + * Discovery clients + * + ****************************************************************************** + */ + +/** + * Open PeerDist discovery client + * + * @v peerdisc PeerDist discovery client + * @v id Segment ID + * @v len Length of segment ID + * @ret rc Return status code + */ +int peerdisc_open ( struct peerdisc_client *peerdisc, const void *id, + size_t len ) { + struct peerdisc_segment *segment; + char id_string[ base16_encoded_len ( len ) + 1 /* NUL */ ]; + char *id_chr; + int rc; + + /* Construct ID string */ + base16_encode ( id, len, id_string, sizeof ( id_string ) ); + for ( id_chr = id_string ; *id_chr ; id_chr++ ) + *id_chr = toupper ( *id_chr ); + + /* Sanity check */ + assert ( peerdisc->segment == NULL ); + + /* Open socket if this is the first segment */ + if ( list_empty ( &peerdisc_segments ) && + ( ( rc = peerdisc_socket_open() ) != 0 ) ) + return rc; + + /* Find or create segment */ + if ( ! ( ( segment = peerdisc_find ( id_string ) ) || + ( segment = peerdisc_create ( id_string ) ) ) ) + return -ENOMEM; + + /* Add to list of clients */ + ref_get ( &segment->refcnt ); + peerdisc->segment = segment; + list_add_tail ( &peerdisc->list, &segment->clients ); + + return 0; +} + +/** + * Close PeerDist discovery client + * + * @v peerdisc PeerDist discovery client + */ +void peerdisc_close ( struct peerdisc_client *peerdisc ) { + struct peerdisc_segment *segment = peerdisc->segment; + + /* Ignore if discovery is already closed */ + if ( ! segment ) + return; + + /* If no peers were discovered, reduce the recommended + * discovery timeout to minimise delays on future requests. + */ + if ( list_empty ( &segment->peers ) && peerdisc_timeout_secs ) { + peerdisc_timeout_secs--; + DBGC ( segment, "PEERDISC %p reducing timeout to %d " + "seconds\n", peerdisc, peerdisc_timeout_secs ); + } + + /* Remove from list of clients */ + peerdisc->segment = NULL; + list_del ( &peerdisc->list ); + ref_put ( &segment->refcnt ); + + /* If this was the last clients, destroy the segment */ + if ( list_empty ( &segment->clients ) ) + peerdisc_destroy ( segment ); + + /* If there are no more segments, close the socket */ + if ( list_empty ( &peerdisc_segments ) ) + peerdisc_socket_close ( 0 ); +} diff --git a/qemu/roms/ipxe/src/net/peerdist.c b/qemu/roms/ipxe/src/net/peerdist.c new file mode 100644 index 000000000..48933f951 --- /dev/null +++ b/qemu/roms/ipxe/src/net/peerdist.c @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdio.h> +#include <ipxe/http.h> +#include <ipxe/peermux.h> + +/** @file + * + * Peer Content Caching and Retrieval (PeerDist) protocol + * + * This is quite possibly the ugliest protocol I have ever had the + * misfortune to encounter, and I've encountered multicast TFTP. + */ + +/** + * Check whether or not to support PeerDist encoding for this request + * + * @v http HTTP transaction + * @ret supported PeerDist encoding is supported for this request + */ +static int http_peerdist_supported ( struct http_transaction *http ) { + + /* Support PeerDist encoding only if we can directly access an + * underlying data transfer buffer. Direct access is required + * in order to support decryption of data received via the + * retrieval protocol (which provides the AES initialisation + * vector only after all of the encrypted data has been + * received). + * + * This test simultaneously ensures that we do not attempt to + * use PeerDist encoding on a request which is itself a + * PeerDist individual block download, since the individual + * block downloads do not themselves provide direct access to + * an underlying data transfer buffer. + */ + return ( xfer_buffer ( &http->xfer ) != NULL ); +} + +/** + * Format HTTP "X-P2P-PeerDist" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_p2p_peerdist ( struct http_transaction *http, + char *buf, size_t len ) { + int supported = http_peerdist_supported ( http ); + int missing; + + /* PeerDist wants us to inform the server whenever we make a + * request for data that was missing from local peers + * (presumably for statistical purposes only). We use the + * heuristic of assuming that the combination of "this request + * may not itself use PeerDist content encoding" and "this is + * a range request" probably indicates that we are making a + * PeerDist block raw range request for missing data. + */ + missing = ( http->request.range.len && ( ! supported ) ); + + /* Omit header if PeerDist encoding is not supported and we + * are not reporting a missing data request. + */ + if ( ! ( supported || missing ) ) + return 0; + + /* Construct header */ + return snprintf ( buf, len, "Version=1.1%s", + ( missing ? ", MissingDataRequest=true" : "" ) ); +} + +/** HTTP "X-P2P-PeerDist" header */ +struct http_request_header http_request_p2p_peerdist __http_request_header = { + .name = "X-P2P-PeerDist", + .format = http_format_p2p_peerdist, +}; + +/** + * Format HTTP "X-P2P-PeerDistEx" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_p2p_peerdistex ( struct http_transaction *http, + char *buf, size_t len ) { + int supported = http_peerdist_supported ( http ); + + /* Omit header if PeerDist encoding is not supported */ + if ( ! supported ) + return 0; + + /* Construct header */ + return snprintf ( buf, len, ( "MinContentInformation=1.0, " + "MaxContentInformation=2.0" ) ); +} + +/** HTTP "X-P2P-PeerDist" header */ +struct http_request_header http_request_p2p_peerdistex __http_request_header = { + .name = "X-P2P-PeerDistEx", + .format = http_format_p2p_peerdistex, +}; + +/** + * Initialise PeerDist content encoding + * + * @v http HTTP transaction + * @ret rc Return status code + */ +static int http_peerdist_init ( struct http_transaction *http ) { + + return peermux_filter ( &http->content, &http->transfer, http->uri ); +} + +/** PeerDist HTTP content encoding */ +struct http_content_encoding peerdist_encoding __http_content_encoding = { + .name = "peerdist", + .supported = http_peerdist_supported, + .init = http_peerdist_init, +}; diff --git a/qemu/roms/ipxe/src/net/peermux.c b/qemu/roms/ipxe/src/net/peermux.c new file mode 100644 index 000000000..634c69992 --- /dev/null +++ b/qemu/roms/ipxe/src/net/peermux.c @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <stdlib.h> +#include <errno.h> +#include <ipxe/uri.h> +#include <ipxe/xferbuf.h> +#include <ipxe/peerblk.h> +#include <ipxe/peermux.h> + +/** @file + * + * Peer Content Caching and Retrieval (PeerDist) protocol multiplexer + * + */ + +/** + * Free PeerDist download multiplexer + * + * @v refcnt Reference count + */ +static void peermux_free ( struct refcnt *refcnt ) { + struct peerdist_multiplexer *peermux = + container_of ( refcnt, struct peerdist_multiplexer, refcnt ); + + uri_put ( peermux->uri ); + xferbuf_free ( &peermux->buffer ); + free ( peermux ); +} + +/** + * Close PeerDist download multiplexer + * + * @v peermux PeerDist download multiplexer + * @v rc Reason for close + */ +static void peermux_close ( struct peerdist_multiplexer *peermux, int rc ) { + unsigned int i; + + /* Stop block download initiation process */ + process_del ( &peermux->process ); + + /* Shut down all block downloads */ + for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ ) + intf_shutdown ( &peermux->block[i].xfer, rc ); + + /* Shut down all other interfaces (which may be connected to + * the same object). + */ + intf_nullify ( &peermux->info ); /* avoid potential loops */ + intf_shutdown ( &peermux->xfer, rc ); + intf_shutdown ( &peermux->info, rc ); +} + +/** + * Receive content information + * + * @v peermux PeerDist download multiplexer + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int peermux_info_deliver ( struct peerdist_multiplexer *peermux, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + int rc; + + /* Add data to buffer */ + if ( ( rc = xferbuf_deliver ( &peermux->buffer, iobuf, meta ) ) != 0 ) + goto err; + + return 0; + + err: + peermux_close ( peermux, rc ); + return rc; +} + +/** + * Close content information interface + * + * @v peermux PeerDist download multiplexer + * @v rc Reason for close + */ +static void peermux_info_close ( struct peerdist_multiplexer *peermux, int rc ){ + struct peerdist_info *info = &peermux->cache.info; + size_t len; + + /* Terminate download on error */ + if ( rc != 0 ) + goto err; + + /* Successfully closing the content information interface + * indicates that the content information has been fully + * received, and initiates the actual PeerDist download. + */ + + /* Shut down content information interface */ + intf_shutdown ( &peermux->info, rc ); + + /* Parse content information */ + if ( ( rc = peerdist_info ( info->raw.data, peermux->buffer.len, + info ) ) != 0 ) { + DBGC ( peermux, "PEERMUX %p could not parse content info: %s\n", + peermux, strerror ( rc ) ); + goto err; + } + + /* Notify recipient of total download size */ + len = ( info->trim.end - info->trim.start ); + if ( ( rc = xfer_seek ( &peermux->xfer, len ) ) != 0 ) { + DBGC ( peermux, "PEERMUX %p could not presize buffer: %s\n", + peermux, strerror ( rc ) ); + goto err; + } + xfer_seek ( &peermux->xfer, 0 ); + + /* Start block download process */ + process_add ( &peermux->process ); + + return; + + err: + peermux_close ( peermux, rc ); +} + +/** + * Initiate multiplexed block download + * + * @v peermux PeerDist download multiplexer + */ +static void peermux_step ( struct peerdist_multiplexer *peermux ) { + struct peerdist_info *info = &peermux->cache.info; + struct peerdist_info_segment *segment = &peermux->cache.segment; + struct peerdist_info_block *block = &peermux->cache.block; + struct peerdist_multiplexed_block *peermblk; + unsigned int next_segment; + unsigned int next_block; + int rc; + + /* Stop initiation process if all block downloads are busy */ + peermblk = list_first_entry ( &peermux->idle, + struct peerdist_multiplexed_block, list ); + if ( ! peermblk ) { + process_del ( &peermux->process ); + return; + } + + /* Increment block index */ + next_block = ( block->index + 1 ); + + /* Move to first/next segment, if applicable */ + if ( next_block >= segment->blocks ) { + + /* Reset block index */ + next_block = 0; + + /* Calculate segment index */ + next_segment = ( segment->info ? ( segment->index + 1 ) : 0 ); + + /* If we have finished all segments and have no + * remaining block downloads, then we are finished. + */ + if ( next_segment >= info->segments ) { + process_del ( &peermux->process ); + if ( list_empty ( &peermux->busy ) ) + peermux_close ( peermux, 0 ); + return; + } + + /* Get content information segment */ + if ( ( rc = peerdist_info_segment ( info, segment, + next_segment ) ) != 0 ) { + DBGC ( peermux, "PEERMUX %p could not get segment %d " + "information: %s\n", peermux, next_segment, + strerror ( rc ) ); + goto err; + } + } + + /* Get content information block */ + if ( ( rc = peerdist_info_block ( segment, block, next_block ) ) != 0 ){ + DBGC ( peermux, "PEERMUX %p could not get segment %d block " + "%d information: %s\n", peermux, segment->index, + next_block, strerror ( rc ) ); + goto err; + } + + /* Ignore block if it lies entirely outside the trimmed range */ + if ( block->trim.start == block->trim.end ) { + DBGC ( peermux, "PEERMUX %p skipping segment %d block %d\n", + peermux, segment->index, block->index ); + return; + } + + /* Start downloading this block */ + if ( ( rc = peerblk_open ( &peermblk->xfer, peermux->uri, + block ) ) != 0 ) { + DBGC ( peermux, "PEERMUX %p could not start download for " + "segment %d block %d: %s\n", peermux, segment->index, + block->index, strerror ( rc ) ); + goto err; + } + + /* Move to list of busy block downloads */ + list_del ( &peermblk->list ); + list_add_tail ( &peermblk->list, &peermux->busy ); + + return; + + err: + peermux_close ( peermux, rc ); +} + +/** + * Receive data from multiplexed block download + * + * @v peermblk PeerDist multiplexed block download + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int peermux_block_deliver ( struct peerdist_multiplexed_block *peermblk, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct peerdist_multiplexer *peermux = peermblk->peermux; + + /* Sanity check: all block downloads must use absolute + * positions for all deliveries, since they run concurrently. + */ + assert ( meta->flags & XFER_FL_ABS_OFFSET ); + + /* We can't use a simple passthrough interface descriptor, + * since there are multiple block download interfaces. + */ + return xfer_deliver ( &peermux->xfer, iob_disown ( iobuf ), meta ); +} + +/** + * Get multiplexed block download underlying data transfer buffer + * + * @v peermblk PeerDist multiplexed download block + * @ret xferbuf Data transfer buffer, or NULL on error + */ +static struct xfer_buffer * +peermux_block_buffer ( struct peerdist_multiplexed_block *peermblk ) { + struct peerdist_multiplexer *peermux = peermblk->peermux; + + /* We can't use a simple passthrough interface descriptor, + * since there are multiple block download interfaces. + */ + return xfer_buffer ( &peermux->xfer ); +} + +/** + * Close multiplexed block download + * + * @v peermblk PeerDist multiplexed block download + * @v rc Reason for close + */ +static void peermux_block_close ( struct peerdist_multiplexed_block *peermblk, + int rc ) { + struct peerdist_multiplexer *peermux = peermblk->peermux; + + /* Move to list of idle downloads */ + list_del ( &peermblk->list ); + list_add_tail ( &peermblk->list, &peermux->idle ); + + /* If any error occurred, terminate the whole multiplexer */ + if ( rc != 0 ) { + peermux_close ( peermux, rc ); + return; + } + + /* Restart data transfer interface */ + intf_restart ( &peermblk->xfer, rc ); + + /* Restart block download initiation process */ + process_add ( &peermux->process ); +} + +/** Data transfer interface operations */ +static struct interface_operation peermux_xfer_operations[] = { + INTF_OP ( intf_close, struct peerdist_multiplexer *, peermux_close ), +}; + +/** Data transfer interface descriptor */ +static struct interface_descriptor peermux_xfer_desc = + INTF_DESC_PASSTHRU ( struct peerdist_multiplexer, xfer, + peermux_xfer_operations, info ); + +/** Content information interface operations */ +static struct interface_operation peermux_info_operations[] = { + INTF_OP ( xfer_deliver, struct peerdist_multiplexer *, + peermux_info_deliver ), + INTF_OP ( intf_close, struct peerdist_multiplexer *, + peermux_info_close ), +}; + +/** Content information interface descriptor */ +static struct interface_descriptor peermux_info_desc = + INTF_DESC_PASSTHRU ( struct peerdist_multiplexer, info, + peermux_info_operations, xfer ); + +/** Block download data transfer interface operations */ +static struct interface_operation peermux_block_operations[] = { + INTF_OP ( xfer_deliver, struct peerdist_multiplexed_block *, + peermux_block_deliver ), + INTF_OP ( xfer_buffer, struct peerdist_multiplexed_block *, + peermux_block_buffer ), + INTF_OP ( intf_close, struct peerdist_multiplexed_block *, + peermux_block_close ), +}; + +/** Block download data transfer interface descriptor */ +static struct interface_descriptor peermux_block_desc = + INTF_DESC ( struct peerdist_multiplexed_block, xfer, + peermux_block_operations ); + +/** Block download initiation process descriptor */ +static struct process_descriptor peermux_process_desc = + PROC_DESC ( struct peerdist_multiplexer, process, peermux_step ); + +/** + * Add PeerDist content-encoding filter + * + * @v xfer Data transfer interface + * @v info Content information interface + * @v uri Original URI + * @ret rc Return status code + */ +int peermux_filter ( struct interface *xfer, struct interface *info, + struct uri *uri ) { + struct peerdist_multiplexer *peermux; + struct peerdist_multiplexed_block *peermblk; + unsigned int i; + + /* Allocate and initialise structure */ + peermux = zalloc ( sizeof ( *peermux ) ); + if ( ! peermux ) + return -ENOMEM; + ref_init ( &peermux->refcnt, peermux_free ); + intf_init ( &peermux->xfer, &peermux_xfer_desc, &peermux->refcnt ); + intf_init ( &peermux->info, &peermux_info_desc, &peermux->refcnt ); + peermux->uri = uri_get ( uri ); + xferbuf_umalloc_init ( &peermux->buffer, + &peermux->cache.info.raw.data ); + process_init_stopped ( &peermux->process, &peermux_process_desc, + &peermux->refcnt ); + INIT_LIST_HEAD ( &peermux->busy ); + INIT_LIST_HEAD ( &peermux->idle ); + for ( i = 0 ; i < PEERMUX_MAX_BLOCKS ; i++ ) { + peermblk = &peermux->block[i]; + peermblk->peermux = peermux; + list_add_tail ( &peermblk->list, &peermux->idle ); + intf_init ( &peermblk->xfer, &peermux_block_desc, + &peermux->refcnt ); + } + + /* Attach to parent interfaces, mortalise self, and return */ + intf_plug_plug ( &peermux->xfer, xfer ); + intf_plug_plug ( &peermux->info, info ); + ref_put ( &peermux->refcnt ); + return 0; +} diff --git a/qemu/roms/ipxe/src/net/ping.c b/qemu/roms/ipxe/src/net/ping.c index d9da87ade..3f4fa5c11 100644 --- a/qemu/roms/ipxe/src/net/ping.c +++ b/qemu/roms/ipxe/src/net/ping.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdlib.h> #include <string.h> diff --git a/qemu/roms/ipxe/src/net/rarp.c b/qemu/roms/ipxe/src/net/rarp.c index 371145015..c194a404f 100644 --- a/qemu/roms/ipxe/src/net/rarp.c +++ b/qemu/roms/ipxe/src/net/rarp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <byteswap.h> diff --git a/qemu/roms/ipxe/src/net/retry.c b/qemu/roms/ipxe/src/net/retry.c index 8f210bdcc..734567be5 100644 --- a/qemu/roms/ipxe/src/net/retry.c +++ b/qemu/roms/ipxe/src/net/retry.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <ipxe/timer.h> @@ -35,7 +39,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); * * This implementation of the timer is designed to satisfy RFC 2988 * and therefore be usable as a TCP retransmission timer. - * * */ @@ -49,47 +52,59 @@ FILE_LICENCE ( GPL2_OR_LATER ); static LIST_HEAD ( timers ); /** - * Start timer + * Start timer with a specified timeout * * @v timer Retry timer + * @v timeout Timeout, in ticks * - * This starts the timer running with the current timeout value. If + * This starts the timer running with the specified timeout value. If * stop_timer() is not called before the timer expires, the timer will * be stopped and the timer's callback function will be called. */ -void start_timer ( struct retry_timer *timer ) { +void start_timer_fixed ( struct retry_timer *timer, unsigned long timeout ) { + + /* Add to list of running timers (if applicable) */ if ( ! timer->running ) { list_add ( &timer->list, &timers ); ref_get ( timer->refcnt ); + timer->running = 1; } + + /* Record start time */ timer->start = currticks(); - timer->running = 1; - - /* 0 means "use default timeout" */ - if ( timer->min_timeout == 0 ) - timer->min_timeout = DEFAULT_MIN_TIMEOUT; - /* We must never be less than MIN_TIMEOUT under any circumstances */ - if ( timer->min_timeout < MIN_TIMEOUT ) - timer->min_timeout = MIN_TIMEOUT; - /* Honor user-specified minimum timeout */ - if ( timer->timeout < timer->min_timeout ) - timer->timeout = timer->min_timeout; - - DBG2 ( "Timer %p started at time %ld (expires at %ld)\n", - timer, timer->start, ( timer->start + timer->timeout ) ); + + /* Record timeout */ + timer->timeout = timeout; + + DBGC2 ( timer, "Timer %p started at time %ld (expires at %ld)\n", + timer, timer->start, ( timer->start + timer->timeout ) ); } /** - * Start timer with a specified fixed timeout + * Start timer * * @v timer Retry timer - * @v timeout Timeout, in ticks + * + * This starts the timer running with the current timeout value + * (rounded up to the minimum timeout value). If stop_timer() is not + * called before the timer expires, the timer will be stopped and the + * timer's callback function will be called. */ -void start_timer_fixed ( struct retry_timer *timer, unsigned long timeout ) { - start_timer ( timer ); - timer->timeout = timeout; - DBG2 ( "Timer %p expiry time changed to %ld\n", - timer, ( timer->start + timer->timeout ) ); +void start_timer ( struct retry_timer *timer ) { + unsigned long timeout = timer->timeout; + unsigned long min; + + /* Calculate minimum timeout */ + min = ( timer->min ? timer->min : DEFAULT_MIN_TIMEOUT ); + if ( min < MIN_TIMEOUT ) + min = MIN_TIMEOUT; + + /* Ensure timeout is at least the minimum */ + if ( timeout < min ) + timeout = min; + + /* Start timer with this timeout */ + start_timer_fixed ( timer, timeout ); } /** @@ -111,8 +126,8 @@ void stop_timer ( struct retry_timer *timer ) { list_del ( &timer->list ); runtime = ( now - timer->start ); timer->running = 0; - DBG2 ( "Timer %p stopped at time %ld (ran for %ld)\n", - timer, now, runtime ); + DBGC2 ( timer, "Timer %p stopped at time %ld (ran for %ld)\n", + timer, now, runtime ); /* Update timer. Variables are: * @@ -135,8 +150,8 @@ void stop_timer ( struct retry_timer *timer ) { timer->timeout -= ( timer->timeout >> 3 ); timer->timeout += ( runtime >> 1 ); if ( timer->timeout != old_timeout ) { - DBG ( "Timer %p timeout updated to %ld\n", - timer, timer->timeout ); + DBGC ( timer, "Timer %p timeout updated to %ld\n", + timer, timer->timeout ); } } @@ -150,11 +165,12 @@ void stop_timer ( struct retry_timer *timer ) { */ static void timer_expired ( struct retry_timer *timer ) { struct refcnt *refcnt = timer->refcnt; + unsigned long max = ( timer->max ? timer->max : DEFAULT_MAX_TIMEOUT ); int fail; /* Stop timer without performing RTT calculations */ - DBG2 ( "Timer %p stopped at time %ld on expiry\n", - timer, currticks() ); + DBGC2 ( timer, "Timer %p stopped at time %ld on expiry\n", + timer, currticks() ); assert ( timer->running ); list_del ( &timer->list ); timer->running = 0; @@ -162,12 +178,10 @@ static void timer_expired ( struct retry_timer *timer ) { /* Back off the timeout value */ timer->timeout <<= 1; - if ( timer->max_timeout == 0 ) /* 0 means "use default timeout" */ - timer->max_timeout = DEFAULT_MAX_TIMEOUT; - if ( ( fail = ( timer->timeout > timer->max_timeout ) ) ) - timer->timeout = timer->max_timeout; - DBG ( "Timer %p timeout backed off to %ld\n", - timer, timer->timeout ); + if ( ( fail = ( timer->timeout > max ) ) ) + timer->timeout = max; + DBGC ( timer, "Timer %p timeout backed off to %ld\n", + timer, timer->timeout ); /* Call expiry callback */ timer->expired ( timer, fail ); diff --git a/qemu/roms/ipxe/src/net/rndis.c b/qemu/roms/ipxe/src/net/rndis.c new file mode 100644 index 000000000..8c4fe8b30 --- /dev/null +++ b/qemu/roms/ipxe/src/net/rndis.c @@ -0,0 +1,1052 @@ +/* + * Copyright (C) 2014 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** @file + * + * Remote Network Driver Interface Specification + * + */ + +#include <unistd.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/iobuf.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/device.h> +#include <ipxe/rndis.h> + +/** + * Allocate I/O buffer + * + * @v len Length + * @ret iobuf I/O buffer, or NULL + */ +static struct io_buffer * rndis_alloc_iob ( size_t len ) { + struct rndis_header *header; + struct io_buffer *iobuf; + + /* Allocate I/O buffer and reserve space */ + iobuf = alloc_iob ( sizeof ( *header ) + len ); + if ( iobuf ) + iob_reserve ( iobuf, sizeof ( *header ) ); + + return iobuf; +} + +/** + * Wait for completion + * + * @v rndis RNDIS device + * @v wait_id Request ID + * @ret rc Return status code + */ +static int rndis_wait ( struct rndis_device *rndis, unsigned int wait_id ) { + unsigned int i; + + /* Record query ID */ + rndis->wait_id = wait_id; + + /* Wait for operation to complete */ + for ( i = 0 ; i < RNDIS_MAX_WAIT_MS ; i++ ) { + + /* Check for completion */ + if ( ! rndis->wait_id ) + return rndis->wait_rc; + + /* Poll RNDIS device */ + rndis->op->poll ( rndis ); + + /* Delay for 1ms */ + mdelay ( 1 ); + } + + DBGC ( rndis, "RNDIS %s timed out waiting for ID %#08x\n", + rndis->name, wait_id ); + return -ETIMEDOUT; +} + +/** + * Transmit message + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @v type Message type + * @ret rc Return status code + */ +static int rndis_tx_message ( struct rndis_device *rndis, + struct io_buffer *iobuf, unsigned int type ) { + struct rndis_header *header; + int rc; + + /* Prepend RNDIS header */ + header = iob_push ( iobuf, sizeof ( *header ) ); + header->type = cpu_to_le32 ( type ); + header->len = cpu_to_le32 ( iob_len ( iobuf ) ); + + /* Transmit message */ + if ( ( rc = rndis->op->transmit ( rndis, iobuf ) ) != 0 ) { + DBGC ( rndis, "RNDIS %s could not transmit: %s\n", + rndis->name, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Complete message transmission + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @v rc Packet status code + */ +void rndis_tx_complete_err ( struct rndis_device *rndis, + struct io_buffer *iobuf, int rc ) { + struct net_device *netdev = rndis->netdev; + struct rndis_header *header; + size_t len = iob_len ( iobuf ); + + /* Sanity check */ + if ( len < sizeof ( *header ) ) { + DBGC ( rndis, "RNDIS %s completed underlength transmission:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + netdev_tx_err ( netdev, NULL, -EINVAL ); + return; + } + header = iobuf->data; + + /* Complete buffer */ + if ( header->type == cpu_to_le32 ( RNDIS_PACKET_MSG ) ) { + netdev_tx_complete_err ( netdev, iobuf, rc ); + } else { + free_iob ( iobuf ); + } +} + +/** + * Transmit data packet + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int rndis_tx_data ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct rndis_packet_message *msg; + size_t len = iob_len ( iobuf ); + int rc; + + /* Prepend packet message header */ + msg = iob_push ( iobuf, sizeof ( *msg ) ); + memset ( msg, 0, sizeof ( *msg ) ); + msg->data.offset = cpu_to_le32 ( sizeof ( *msg ) ); + msg->data.len = cpu_to_le32 ( len ); + + /* Transmit message */ + if ( ( rc = rndis_tx_message ( rndis, iobuf, RNDIS_PACKET_MSG ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Defer transmitted packet + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @ret rc Return status code + * + * As with netdev_tx_defer(), the caller must ensure that space in the + * transmit descriptor ring is freed up before calling + * rndis_tx_complete(). + * + * Unlike netdev_tx_defer(), this call may fail. + */ +int rndis_tx_defer ( struct rndis_device *rndis, struct io_buffer *iobuf ) { + struct net_device *netdev = rndis->netdev; + struct rndis_header *header; + struct rndis_packet_message *msg; + + /* Fail unless this was a packet message. Only packet + * messages correspond to I/O buffers in the network device's + * TX queue; other messages cannot be deferred in this way. + */ + assert ( iob_len ( iobuf ) >= sizeof ( *header ) ); + header = iobuf->data; + if ( header->type != cpu_to_le32 ( RNDIS_PACKET_MSG ) ) + return -ENOTSUP; + + /* Strip RNDIS header and packet message header, to return + * this packet to the state in which we received it. + */ + iob_pull ( iobuf, ( sizeof ( *header ) + sizeof ( *msg ) ) ); + + /* Defer packet */ + netdev_tx_defer ( netdev, iobuf ); + + return 0; +} + +/** + * Receive data packet + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + */ +static void rndis_rx_data ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct net_device *netdev = rndis->netdev; + struct rndis_packet_message *msg; + size_t len = iob_len ( iobuf ); + size_t data_offset; + size_t data_len; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *msg ) ) { + DBGC ( rndis, "RNDIS %s received underlength data packet:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_len; + } + msg = iobuf->data; + + /* Locate and sanity check data buffer */ + data_offset = le32_to_cpu ( msg->data.offset ); + data_len = le32_to_cpu ( msg->data.len ); + if ( ( data_offset > len ) || ( data_len > ( len - data_offset ) ) ) { + DBGC ( rndis, "RNDIS %s data packet data exceeds packet:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_data; + } + + /* Strip non-data portions */ + iob_pull ( iobuf, data_offset ); + iob_unput ( iobuf, ( iob_len ( iobuf ) - data_len ) ); + + /* Hand off to network stack */ + netdev_rx ( netdev, iob_disown ( iobuf ) ); + + return; + + err_data: + err_len: + /* Report error to network stack */ + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); +} + +/** + * Transmit initialisation message + * + * @v rndis RNDIS device + * @v id Request ID + * @ret rc Return status code + */ +static int rndis_tx_initialise ( struct rndis_device *rndis, unsigned int id ) { + struct io_buffer *iobuf; + struct rndis_initialise_message *msg; + int rc; + + /* Allocate I/O buffer */ + iobuf = rndis_alloc_iob ( sizeof ( *msg ) ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Construct message */ + msg = iob_put ( iobuf, sizeof ( *msg ) ); + memset ( msg, 0, sizeof ( *msg ) ); + msg->id = id; /* Non-endian */ + msg->major = cpu_to_le32 ( RNDIS_VERSION_MAJOR ); + msg->minor = cpu_to_le32 ( RNDIS_VERSION_MINOR ); + msg->mtu = cpu_to_le32 ( RNDIS_MTU ); + + /* Transmit message */ + if ( ( rc = rndis_tx_message ( rndis, iobuf, + RNDIS_INITIALISE_MSG ) ) != 0 ) + goto err_tx; + + return 0; + + err_tx: + free_iob ( iobuf ); + err_alloc: + return rc; +} + +/** + * Receive initialisation completion + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + */ +static void rndis_rx_initialise ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct rndis_initialise_completion *cmplt; + size_t len = iob_len ( iobuf ); + unsigned int id; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *cmplt ) ) { + DBGC ( rndis, "RNDIS %s received underlength initialisation " + "completion:\n", rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_len; + } + cmplt = iobuf->data; + + /* Extract request ID */ + id = cmplt->id; /* Non-endian */ + + /* Check status */ + if ( cmplt->status ) { + DBGC ( rndis, "RNDIS %s received initialisation completion " + "failure %#08x\n", rndis->name, + le32_to_cpu ( cmplt->status ) ); + rc = -EIO; + goto err_status; + } + + /* Success */ + rc = 0; + + err_status: + /* Record completion result if applicable */ + if ( id == rndis->wait_id ) { + rndis->wait_id = 0; + rndis->wait_rc = rc; + } + err_len: + free_iob ( iobuf ); +} + +/** + * Initialise RNDIS + * + * @v rndis RNDIS device + * @ret rc Return status code + */ +static int rndis_initialise ( struct rndis_device *rndis ) { + int rc; + + /* Transmit initialisation message */ + if ( ( rc = rndis_tx_initialise ( rndis, RNDIS_INIT_ID ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = rndis_wait ( rndis, RNDIS_INIT_ID ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Transmit halt message + * + * @v rndis RNDIS device + * @ret rc Return status code + */ +static int rndis_tx_halt ( struct rndis_device *rndis ) { + struct io_buffer *iobuf; + struct rndis_halt_message *msg; + int rc; + + /* Allocate I/O buffer */ + iobuf = rndis_alloc_iob ( sizeof ( *msg ) ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Construct message */ + msg = iob_put ( iobuf, sizeof ( *msg ) ); + memset ( msg, 0, sizeof ( *msg ) ); + + /* Transmit message */ + if ( ( rc = rndis_tx_message ( rndis, iobuf, RNDIS_HALT_MSG ) ) != 0 ) + goto err_tx; + + return 0; + + err_tx: + free_iob ( iobuf ); + err_alloc: + return rc; +} + +/** + * Halt RNDIS + * + * @v rndis RNDIS device + * @ret rc Return status code + */ +static int rndis_halt ( struct rndis_device *rndis ) { + int rc; + + /* Transmit halt message */ + if ( ( rc = rndis_tx_halt ( rndis ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Transmit OID message + * + * @v rndis RNDIS device + * @v oid Object ID + * @v data New OID value (or NULL to query current value) + * @v len Length of new OID value + * @ret rc Return status code + */ +static int rndis_tx_oid ( struct rndis_device *rndis, unsigned int oid, + const void *data, size_t len ) { + struct io_buffer *iobuf; + struct rndis_oid_message *msg; + unsigned int type; + int rc; + + /* Allocate I/O buffer */ + iobuf = rndis_alloc_iob ( sizeof ( *msg ) + len ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Construct message. We use the OID as the request ID. */ + msg = iob_put ( iobuf, sizeof ( *msg ) ); + memset ( msg, 0, sizeof ( *msg ) ); + msg->id = oid; /* Non-endian */ + msg->oid = cpu_to_le32 ( oid ); + msg->offset = cpu_to_le32 ( sizeof ( *msg ) ); + msg->len = cpu_to_le32 ( len ); + memcpy ( iob_put ( iobuf, len ), data, len ); + + /* Transmit message */ + type = ( data ? RNDIS_SET_MSG : RNDIS_QUERY_MSG ); + if ( ( rc = rndis_tx_message ( rndis, iobuf, type ) ) != 0 ) + goto err_tx; + + return 0; + + err_tx: + free_iob ( iobuf ); + err_alloc: + return rc; +} + +/** + * Receive query OID completion + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + */ +static void rndis_rx_query_oid ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct net_device *netdev = rndis->netdev; + struct rndis_query_completion *cmplt; + size_t len = iob_len ( iobuf ); + size_t info_offset; + size_t info_len; + unsigned int id; + void *info; + uint32_t *link_status; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *cmplt ) ) { + DBGC ( rndis, "RNDIS %s received underlength query " + "completion:\n", rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_len; + } + cmplt = iobuf->data; + + /* Extract request ID */ + id = cmplt->id; /* Non-endian */ + + /* Check status */ + if ( cmplt->status ) { + DBGC ( rndis, "RNDIS %s received query completion failure " + "%#08x\n", rndis->name, le32_to_cpu ( cmplt->status ) ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EIO; + goto err_status; + } + + /* Locate and sanity check information buffer */ + info_offset = le32_to_cpu ( cmplt->offset ); + info_len = le32_to_cpu ( cmplt->len ); + if ( ( info_offset > len ) || ( info_len > ( len - info_offset ) ) ) { + DBGC ( rndis, "RNDIS %s query completion information exceeds " + "packet:\n", rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_info; + } + info = ( ( ( void * ) cmplt ) + info_offset ); + + /* Handle OID */ + switch ( id ) { + + case RNDIS_OID_802_3_PERMANENT_ADDRESS: + if ( info_len > sizeof ( netdev->hw_addr ) ) + info_len = sizeof ( netdev->hw_addr ); + memcpy ( netdev->hw_addr, info, info_len ); + break; + + case RNDIS_OID_802_3_CURRENT_ADDRESS: + if ( info_len > sizeof ( netdev->ll_addr ) ) + info_len = sizeof ( netdev->ll_addr ); + memcpy ( netdev->ll_addr, info, info_len ); + break; + + case RNDIS_OID_GEN_MEDIA_CONNECT_STATUS: + if ( info_len != sizeof ( *link_status ) ) { + DBGC ( rndis, "RNDIS %s invalid link status:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EPROTO; + goto err_link_status; + } + link_status = info; + if ( *link_status == 0 ) { + DBGC ( rndis, "RNDIS %s link is up\n", rndis->name ); + netdev_link_up ( netdev ); + } else { + DBGC ( rndis, "RNDIS %s link is down: %#08x\n", + rndis->name, le32_to_cpu ( *link_status ) ); + netdev_link_down ( netdev ); + } + break; + + default: + DBGC ( rndis, "RNDIS %s unexpected query completion ID %#08x\n", + rndis->name, id ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EPROTO; + goto err_id; + } + + /* Success */ + rc = 0; + + err_id: + err_link_status: + err_info: + err_status: + /* Record completion result if applicable */ + if ( id == rndis->wait_id ) { + rndis->wait_id = 0; + rndis->wait_rc = rc; + } + err_len: + /* Free I/O buffer */ + free_iob ( iobuf ); +} + +/** + * Receive set OID completion + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + */ +static void rndis_rx_set_oid ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct rndis_set_completion *cmplt; + size_t len = iob_len ( iobuf ); + unsigned int id; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *cmplt ) ) { + DBGC ( rndis, "RNDIS %s received underlength set completion:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_len; + } + cmplt = iobuf->data; + + /* Extract request ID */ + id = cmplt->id; /* Non-endian */ + + /* Check status */ + if ( cmplt->status ) { + DBGC ( rndis, "RNDIS %s received set completion failure " + "%#08x\n", rndis->name, le32_to_cpu ( cmplt->status ) ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EIO; + goto err_status; + } + + /* Success */ + rc = 0; + + err_status: + /* Record completion result if applicable */ + if ( id == rndis->wait_id ) { + rndis->wait_id = 0; + rndis->wait_rc = rc; + } + err_len: + /* Free I/O buffer */ + free_iob ( iobuf ); +} + +/** + * Query or set OID + * + * @v rndis RNDIS device + * @v oid Object ID + * @v data New OID value (or NULL to query current value) + * @v len Length of new OID value + * @ret rc Return status code + */ +static int rndis_oid ( struct rndis_device *rndis, unsigned int oid, + const void *data, size_t len ) { + int rc; + + /* Transmit query */ + if ( ( rc = rndis_tx_oid ( rndis, oid, data, len ) ) != 0 ) + return rc; + + /* Wait for response */ + if ( ( rc = rndis_wait ( rndis, oid ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Receive indicate status message + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + */ +static void rndis_rx_status ( struct rndis_device *rndis, + struct io_buffer *iobuf ) { + struct net_device *netdev = rndis->netdev; + struct rndis_indicate_status_message *msg; + size_t len = iob_len ( iobuf ); + unsigned int status; + int rc; + + /* Sanity check */ + if ( len < sizeof ( *msg ) ) { + DBGC ( rndis, "RNDIS %s received underlength status message:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -EINVAL; + goto err_len; + } + msg = iobuf->data; + + /* Extract status */ + status = le32_to_cpu ( msg->status ); + + /* Handle status */ + switch ( msg->status ) { + + case RNDIS_STATUS_MEDIA_CONNECT: + DBGC ( rndis, "RNDIS %s link is up\n", rndis->name ); + netdev_link_up ( netdev ); + break; + + case RNDIS_STATUS_MEDIA_DISCONNECT: + DBGC ( rndis, "RNDIS %s link is down\n", rndis->name ); + netdev_link_down ( netdev ); + break; + + case RNDIS_STATUS_WTF_WORLD: + /* Ignore */ + break; + + default: + DBGC ( rndis, "RNDIS %s unexpected status %#08x:\n", + rndis->name, status ); + DBGC_HDA ( rndis, 0, iobuf->data, len ); + rc = -ENOTSUP; + goto err_status; + } + + /* Free I/O buffer */ + free_iob ( iobuf ); + + return; + + err_status: + err_len: + /* Report error via network device statistics */ + netdev_rx_err ( netdev, iobuf, rc ); +} + +/** + * Receive RNDIS message + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @v type Message type + */ +static void rndis_rx_message ( struct rndis_device *rndis, + struct io_buffer *iobuf, unsigned int type ) { + struct net_device *netdev = rndis->netdev; + int rc; + + /* Handle packet */ + switch ( type ) { + + case RNDIS_PACKET_MSG: + rndis_rx_data ( rndis, iob_disown ( iobuf ) ); + break; + + case RNDIS_INITIALISE_CMPLT: + rndis_rx_initialise ( rndis, iob_disown ( iobuf ) ); + break; + + case RNDIS_QUERY_CMPLT: + rndis_rx_query_oid ( rndis, iob_disown ( iobuf ) ); + break; + + case RNDIS_SET_CMPLT: + rndis_rx_set_oid ( rndis, iob_disown ( iobuf ) ); + break; + + case RNDIS_INDICATE_STATUS_MSG: + rndis_rx_status ( rndis, iob_disown ( iobuf ) ); + break; + + default: + DBGC ( rndis, "RNDIS %s received unexpected type %#08x\n", + rndis->name, type ); + DBGC_HDA ( rndis, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EPROTO; + goto err_type; + } + + return; + + err_type: + /* Report error via network device statistics */ + netdev_rx_err ( netdev, iobuf, rc ); +} + +/** + * Receive packet from underlying transport layer + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + */ +void rndis_rx ( struct rndis_device *rndis, struct io_buffer *iobuf ) { + struct net_device *netdev = rndis->netdev; + struct rndis_header *header; + unsigned int type; + int rc; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *header ) ) { + DBGC ( rndis, "RNDIS %s received underlength packet:\n", + rndis->name ); + DBGC_HDA ( rndis, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto drop; + } + header = iobuf->data; + + /* Parse and strip header */ + type = le32_to_cpu ( header->type ); + iob_pull ( iobuf, sizeof ( *header ) ); + + /* Handle message */ + rndis_rx_message ( rndis, iob_disown ( iobuf ), type ); + + return; + + drop: + /* Record error */ + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); +} + +/** + * Discard packet from underlying transport layer + * + * @v rndis RNDIS device + * @v iobuf I/O buffer + * @v rc Packet status code + */ +void rndis_rx_err ( struct rndis_device *rndis, struct io_buffer *iobuf, + int rc ) { + struct net_device *netdev = rndis->netdev; + + /* Record error */ + netdev_rx_err ( netdev, iob_disown ( iobuf ), rc ); +} + +/** + * Set receive filter + * + * @v rndis RNDIS device + * @v filter Receive filter + * @ret rc Return status code + */ +static int rndis_filter ( struct rndis_device *rndis, unsigned int filter ) { + uint32_t value = cpu_to_le32 ( filter ); + int rc; + + /* Set receive filter */ + if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_CURRENT_PACKET_FILTER, + &value, sizeof ( value ) ) ) != 0 ) { + DBGC ( rndis, "RNDIS %s could not set receive filter to %#08x: " + "%s\n", rndis->name, filter, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Open network device + * + * @v netdev Network device + * @ret rc Return status code + */ +static int rndis_open ( struct net_device *netdev ) { + struct rndis_device *rndis = netdev->priv; + int rc; + + /* Open RNDIS device */ + if ( ( rc = rndis->op->open ( rndis ) ) != 0 ) { + DBGC ( rndis, "RNDIS %s could not open: %s\n", + rndis->name, strerror ( rc ) ); + goto err_open; + } + + /* Initialise RNDIS */ + if ( ( rc = rndis_initialise ( rndis ) ) != 0 ) + goto err_initialise; + + /* Set receive filter */ + if ( ( rc = rndis_filter ( rndis, ( RNDIS_FILTER_UNICAST | + RNDIS_FILTER_MULTICAST | + RNDIS_FILTER_ALL_MULTICAST | + RNDIS_FILTER_BROADCAST | + RNDIS_FILTER_PROMISCUOUS ) ) ) != 0) + goto err_set_filter; + + /* Update link status */ + if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_MEDIA_CONNECT_STATUS, + NULL, 0 ) ) != 0 ) + goto err_query_link; + + return 0; + + err_query_link: + err_set_filter: + rndis_halt ( rndis ); + err_initialise: + rndis->op->close ( rndis ); + err_open: + return rc; +} + +/** + * Close network device + * + * @v netdev Network device + */ +static void rndis_close ( struct net_device *netdev ) { + struct rndis_device *rndis = netdev->priv; + + /* Clear receive filter */ + rndis_filter ( rndis, 0 ); + + /* Halt RNDIS device */ + rndis_halt ( rndis ); + + /* Close RNDIS device */ + rndis->op->close ( rndis ); +} + +/** + * Transmit packet + * + * @v netdev Network device + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int rndis_transmit ( struct net_device *netdev, + struct io_buffer *iobuf ) { + struct rndis_device *rndis = netdev->priv; + + /* Transmit data packet */ + return rndis_tx_data ( rndis, iobuf ); +} + +/** + * Poll for completed and received packets + * + * @v netdev Network device + */ +static void rndis_poll ( struct net_device *netdev ) { + struct rndis_device *rndis = netdev->priv; + + /* Poll RNDIS device */ + rndis->op->poll ( rndis ); +} + +/** Network device operations */ +static struct net_device_operations rndis_operations = { + .open = rndis_open, + .close = rndis_close, + .transmit = rndis_transmit, + .poll = rndis_poll, +}; + +/** + * Allocate RNDIS device + * + * @v priv_len Length of private data + * @ret rndis RNDIS device, or NULL on allocation failure + */ +struct rndis_device * alloc_rndis ( size_t priv_len ) { + struct net_device *netdev; + struct rndis_device *rndis; + + /* Allocate and initialise structure */ + netdev = alloc_etherdev ( sizeof ( *rndis ) + priv_len ); + if ( ! netdev ) + return NULL; + netdev_init ( netdev, &rndis_operations ); + rndis = netdev->priv; + rndis->netdev = netdev; + rndis->priv = ( ( ( void * ) rndis ) + sizeof ( *rndis ) ); + + return rndis; +} + +/** + * Register RNDIS device + * + * @v rndis RNDIS device + * @ret rc Return status code + * + * Note that this routine will open and use the RNDIS device in order + * to query the MAC address. The device must be immediately ready for + * use prior to registration. + */ +int register_rndis ( struct rndis_device *rndis ) { + struct net_device *netdev = rndis->netdev; + int rc; + + /* Assign device name (for debugging) */ + rndis->name = netdev->dev->name; + + /* Register network device */ + if ( ( rc = register_netdev ( netdev ) ) != 0 ) { + DBGC ( rndis, "RNDIS %s could not register: %s\n", + rndis->name, strerror ( rc ) ); + goto err_register; + } + + /* Open RNDIS device to read MAC addresses */ + if ( ( rc = rndis->op->open ( rndis ) ) != 0 ) { + DBGC ( rndis, "RNDIS %s could not open: %s\n", + rndis->name, strerror ( rc ) ); + goto err_open; + } + + /* Initialise RNDIS */ + if ( ( rc = rndis_initialise ( rndis ) ) != 0 ) + goto err_initialise; + + /* Query permanent MAC address */ + if ( ( rc = rndis_oid ( rndis, RNDIS_OID_802_3_PERMANENT_ADDRESS, + NULL, 0 ) ) != 0 ) + goto err_query_permanent; + + /* Query current MAC address */ + if ( ( rc = rndis_oid ( rndis, RNDIS_OID_802_3_CURRENT_ADDRESS, + NULL, 0 ) ) != 0 ) + goto err_query_current; + + /* Get link status */ + if ( ( rc = rndis_oid ( rndis, RNDIS_OID_GEN_MEDIA_CONNECT_STATUS, + NULL, 0 ) ) != 0 ) + goto err_query_link; + + /* Halt RNDIS device */ + rndis_halt ( rndis ); + + /* Close RNDIS device */ + rndis->op->close ( rndis ); + + return 0; + + err_query_link: + err_query_current: + err_query_permanent: + rndis_halt ( rndis ); + err_initialise: + rndis->op->close ( rndis ); + err_open: + unregister_netdev ( netdev ); + err_register: + return rc; +} + +/** + * Unregister RNDIS device + * + * @v rndis RNDIS device + */ +void unregister_rndis ( struct rndis_device *rndis ) { + struct net_device *netdev = rndis->netdev; + + /* Unregister network device */ + unregister_netdev ( netdev ); +} + +/** + * Free RNDIS device + * + * @v rndis RNDIS device + */ +void free_rndis ( struct rndis_device *rndis ) { + struct net_device *netdev = rndis->netdev; + + /* Free network device */ + netdev_nullify ( netdev ); + netdev_put ( netdev ); +} diff --git a/qemu/roms/ipxe/src/net/socket.c b/qemu/roms/ipxe/src/net/socket.c index 24f6a0892..2009ab237 100644 --- a/qemu/roms/ipxe/src/net/socket.c +++ b/qemu/roms/ipxe/src/net/socket.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <errno.h> diff --git a/qemu/roms/ipxe/src/net/stp.c b/qemu/roms/ipxe/src/net/stp.c new file mode 100644 index 000000000..d4e65a1a2 --- /dev/null +++ b/qemu/roms/ipxe/src/net/stp.c @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include <errno.h> +#include <byteswap.h> +#include <ipxe/netdevice.h> +#include <ipxe/ethernet.h> +#include <ipxe/iobuf.h> +#include <ipxe/timer.h> +#include <ipxe/stp.h> + +/** @file + * + * Spanning Tree Protocol (STP) + * + */ + +/* Disambiguate the various error causes */ +#define ENOTSUP_PROTOCOL __einfo_error ( EINFO_ENOTSUP_PROTOCOL ) +#define EINFO_ENOTSUP_PROTOCOL \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \ + "Non-STP packet received" ) +#define ENOTSUP_VERSION __einfo_error ( EINFO_ENOTSUP_VERSION ) +#define EINFO_ENOTSUP_VERSION \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \ + "Legacy STP packet received" ) +#define ENOTSUP_TYPE __einfo_error ( EINFO_ENOTSUP_TYPE ) +#define EINFO_ENOTSUP_TYPE \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x01, \ + "Non-RSTP packet received" ) + +/** + * Process incoming STP packets + * + * @v iobuf I/O buffer + * @v netdev Network device + * @v ll_source Link-layer source address + * @v flags Packet flags + * @ret rc Return status code + */ +static int stp_rx ( struct io_buffer *iobuf, struct net_device *netdev, + const void *ll_dest __unused, + const void *ll_source __unused, + unsigned int flags __unused ) { + struct stp_bpdu *stp; + unsigned int hello; + int rc; + + /* Sanity check */ + if ( iob_len ( iobuf ) < sizeof ( *stp ) ) { + DBGC ( netdev, "STP %s received underlength packet (%zd " + "bytes):\n", netdev->name, iob_len ( iobuf ) ); + DBGC_HDA ( netdev, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto done; + } + stp = iobuf->data; + + /* Ignore non-RSTP packets */ + if ( stp->protocol != htons ( STP_PROTOCOL ) ) { + DBGC ( netdev, "STP %s ignoring non-STP packet (protocol " + "%#04x)\n", netdev->name, ntohs ( stp->protocol ) ); + rc = -ENOTSUP_PROTOCOL; + goto done; + } + if ( stp->version < STP_VERSION_RSTP ) { + DBGC ( netdev, "STP %s received legacy STP packet (version " + "%#02x)\n", netdev->name, stp->version ); + rc = -ENOTSUP_VERSION; + goto done; + } + if ( stp->type != STP_TYPE_RSTP ) { + DBGC ( netdev, "STP %s received non-RSTP packet (type %#02x)\n", + netdev->name, stp->type ); + rc = -ENOTSUP_TYPE; + goto done; + } + + /* Dump information */ + DBGC2 ( netdev, "STP %s %s port %#04x flags %#02x hello %d delay %d\n", + netdev->name, eth_ntoa ( stp->sender.mac ), ntohs ( stp->port ), + stp->flags, ntohs ( stp->hello ), ntohs ( stp->delay ) ); + + /* Check if port is forwarding */ + if ( ! ( stp->flags & STP_FL_FORWARDING ) ) { + /* Port is not forwarding: block link for two hello times */ + DBGC ( netdev, "STP %s %s port %#04x flags %#02x is not " + "forwarding\n", + netdev->name, eth_ntoa ( stp->sender.mac ), + ntohs ( stp->port ), stp->flags ); + hello = ( ( ntohs ( stp->hello ) * TICKS_PER_SEC ) / 256 ); + netdev_link_block ( netdev, ( hello * 2 ) ); + rc = -ENETUNREACH; + goto done; + } + + /* Success */ + if ( netdev_link_blocked ( netdev ) ) { + DBGC ( netdev, "STP %s %s port %#04x flags %#02x is " + "forwarding\n", + netdev->name, eth_ntoa ( stp->sender.mac ), + ntohs ( stp->port ), stp->flags ); + } + netdev_link_unblock ( netdev ); + rc = 0; + + done: + free_iob ( iobuf ); + return rc; +} + +/** + * Transcribe STP address + * + * @v net_addr STP address + * @ret string "<STP>" + * + * This operation is meaningless for the STP protocol. + */ +static const char * stp_ntoa ( const void *net_addr __unused ) { + return "<STP>"; +} + +/** STP network protocol */ +struct net_protocol stp_protocol __net_protocol = { + .name = "STP", + .net_proto = htons ( ETH_P_STP ), + .rx = stp_rx, + .ntoa = stp_ntoa, +}; diff --git a/qemu/roms/ipxe/src/net/tcp.c b/qemu/roms/ipxe/src/net/tcp.c index 987cb63e1..c69c83b85 100644 --- a/qemu/roms/ipxe/src/net/tcp.c +++ b/qemu/roms/ipxe/src/net/tcp.c @@ -26,7 +26,7 @@ * */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** A TCP connection */ struct tcp_connection { @@ -101,8 +101,9 @@ struct tcp_connection { * Equivalent to Rcv.Wind.Scale in RFC 1323 terminology */ uint8_t rcv_win_scale; - /** Maximum receive window */ - uint32_t max_rcv_win; + + /** Selective acknowledgement list (in host-endian order) */ + struct tcp_sack_block sack[TCP_SACK_MAX]; /** Transmit queue */ struct list_head tx_queue; @@ -129,6 +130,8 @@ enum tcp_flags { TCP_TS_ENABLED = 0x0002, /** TCP acknowledgement is pending */ TCP_ACK_PENDING = 0x0004, + /** TCP selective acknowledgement is enabled */ + TCP_SACK_ENABLED = 0x0008, }; /** TCP internal header @@ -143,6 +146,8 @@ struct tcp_rx_queued_header { * enqueued, and so excludes the SYN, if present. */ uint32_t seq; + /** Next SEQ value, in host-endian order */ + uint32_t nxt; /** Flags * * Only FIN is valid within this flags byte; all other flags @@ -284,7 +289,6 @@ static int tcp_open ( struct interface *xfer, struct sockaddr *peer, tcp->tcp_state = TCP_STATE_SENT ( TCP_SYN ); tcp_dump_state ( tcp ); tcp->snd_seq = random(); - tcp->max_rcv_win = TCP_MAX_WINDOW_SIZE; INIT_LIST_HEAD ( &tcp->tx_queue ); INIT_LIST_HEAD ( &tcp->rx_queue ); memcpy ( &tcp->peer, st_peer, sizeof ( tcp->peer ) ); @@ -396,6 +400,7 @@ static void tcp_close ( struct tcp_connection *tcp, int rc ) { tcp->tcp_state |= TCP_STATE_SENT ( TCP_FIN ); tcp_dump_state ( tcp ); + process_add ( &tcp->process ); /* Add a pending operation for the FIN */ pending_get ( &tcp->pending_flags ); @@ -450,6 +455,94 @@ static size_t tcp_xfer_window ( struct tcp_connection *tcp ) { } /** + * Find selective acknowledgement block + * + * @v tcp TCP connection + * @v seq SEQ value in SACK block (in host-endian order) + * @v sack SACK block to fill in (in host-endian order) + * @ret len Length of SACK block + */ +static uint32_t tcp_sack_block ( struct tcp_connection *tcp, uint32_t seq, + struct tcp_sack_block *sack ) { + struct io_buffer *iobuf; + struct tcp_rx_queued_header *tcpqhdr; + uint32_t left = tcp->rcv_ack; + uint32_t right = left; + + /* Find highest block which does not start after SEQ */ + list_for_each_entry ( iobuf, &tcp->rx_queue, list ) { + tcpqhdr = iobuf->data; + if ( tcp_cmp ( tcpqhdr->seq, right ) > 0 ) { + if ( tcp_cmp ( tcpqhdr->seq, seq ) > 0 ) + break; + left = tcpqhdr->seq; + } + if ( tcp_cmp ( tcpqhdr->nxt, right ) > 0 ) + right = tcpqhdr->nxt; + } + + /* Fail if this block does not contain SEQ */ + if ( tcp_cmp ( right, seq ) < 0 ) + return 0; + + /* Populate SACK block */ + sack->left = left; + sack->right = right; + return ( right - left ); +} + +/** + * Update TCP selective acknowledgement list + * + * @v tcp TCP connection + * @v seq SEQ value in first SACK block (in host-endian order) + * @ret count Number of SACK blocks + */ +static unsigned int tcp_sack ( struct tcp_connection *tcp, uint32_t seq ) { + struct tcp_sack_block sack[TCP_SACK_MAX]; + unsigned int old = 0; + unsigned int new = 0; + unsigned int i; + uint32_t len; + + /* Populate first new SACK block */ + len = tcp_sack_block ( tcp, seq, &sack[0] ); + if ( len ) + new++; + + /* Populate remaining new SACK blocks based on old SACK blocks */ + for ( old = 0 ; old < TCP_SACK_MAX ; old++ ) { + + /* Stop if we run out of space in the new list */ + if ( new == TCP_SACK_MAX ) + break; + + /* Skip empty old SACK blocks */ + if ( tcp->sack[old].left == tcp->sack[old].right ) + continue; + + /* Populate new SACK block */ + len = tcp_sack_block ( tcp, tcp->sack[old].left, &sack[new] ); + if ( len == 0 ) + continue; + + /* Eliminate duplicates */ + for ( i = 0 ; i < new ; i++ ) { + if ( sack[i].left == sack[new].left ) { + new--; + break; + } + } + new++; + } + + /* Update SACK list */ + memset ( tcp->sack, 0, sizeof ( tcp->sack ) ); + memcpy ( tcp->sack, sack, ( new * sizeof ( tcp->sack[0] ) ) ); + return new; +} + +/** * Process TCP transmit queue * * @v tcp TCP connection @@ -493,9 +586,10 @@ static size_t tcp_process_tx_queue ( struct tcp_connection *tcp, size_t max_len, } /** - * Transmit any outstanding data + * Transmit any outstanding data (with selective acknowledgement) * * @v tcp TCP connection + * @v sack_seq SEQ for first selective acknowledgement (if any) * * Transmits any outstanding data on the connection. * @@ -503,17 +597,22 @@ static size_t tcp_process_tx_queue ( struct tcp_connection *tcp, size_t max_len, * will have been started if necessary, and so the stack will * eventually attempt to retransmit the failed packet. */ -static void tcp_xmit ( struct tcp_connection *tcp ) { +static void tcp_xmit_sack ( struct tcp_connection *tcp, uint32_t sack_seq ) { struct io_buffer *iobuf; struct tcp_header *tcphdr; struct tcp_mss_option *mssopt; struct tcp_window_scale_padded_option *wsopt; struct tcp_timestamp_padded_option *tsopt; + struct tcp_sack_permitted_padded_option *spopt; + struct tcp_sack_padded_option *sackopt; + struct tcp_sack_block *sack; void *payload; unsigned int flags; + unsigned int sack_count; + unsigned int i; size_t len = 0; + size_t sack_len; uint32_t seq_len; - uint32_t app_win; uint32_t max_rcv_win; uint32_t max_representable_win; int rc; @@ -567,10 +666,9 @@ static void tcp_xmit ( struct tcp_connection *tcp ) { tcp_process_tx_queue ( tcp, len, iobuf, 0 ); /* Expand receive window if possible */ - max_rcv_win = tcp->max_rcv_win; - app_win = xfer_window ( &tcp->xfer ); - if ( max_rcv_win > app_win ) - max_rcv_win = app_win; + max_rcv_win = xfer_window ( &tcp->xfer ); + if ( max_rcv_win > TCP_MAX_WINDOW_SIZE ) + max_rcv_win = TCP_MAX_WINDOW_SIZE; max_representable_win = ( 0xffff << tcp->rcv_win_scale ); if ( max_rcv_win > max_representable_win ) max_rcv_win = max_representable_win; @@ -590,6 +688,10 @@ static void tcp_xmit ( struct tcp_connection *tcp ) { wsopt->wsopt.kind = TCP_OPTION_WS; wsopt->wsopt.length = sizeof ( wsopt->wsopt ); wsopt->wsopt.scale = TCP_RX_WINDOW_SCALE; + spopt = iob_push ( iobuf, sizeof ( *spopt ) ); + memset ( spopt->nop, TCP_OPTION_NOP, sizeof ( spopt ) ); + spopt->spopt.kind = TCP_OPTION_SACK_PERMITTED; + spopt->spopt.length = sizeof ( spopt->spopt ); } if ( ( flags & TCP_SYN ) || ( tcp->flags & TCP_TS_ENABLED ) ) { tsopt = iob_push ( iobuf, sizeof ( *tsopt ) ); @@ -599,6 +701,21 @@ static void tcp_xmit ( struct tcp_connection *tcp ) { tsopt->tsopt.tsval = htonl ( currticks() ); tsopt->tsopt.tsecr = htonl ( tcp->ts_recent ); } + if ( ( tcp->flags & TCP_SACK_ENABLED ) && + ( ! list_empty ( &tcp->rx_queue ) ) && + ( ( sack_count = tcp_sack ( tcp, sack_seq ) ) != 0 ) ) { + sack_len = ( sack_count * sizeof ( *sack ) ); + sackopt = iob_push ( iobuf, ( sizeof ( *sackopt ) + sack_len )); + memset ( sackopt->nop, TCP_OPTION_NOP, sizeof ( sackopt->nop )); + sackopt->sackopt.kind = TCP_OPTION_SACK; + sackopt->sackopt.length = + ( sizeof ( sackopt->sackopt ) + sack_len ); + sack = ( ( ( void * ) sackopt ) + sizeof ( *sackopt ) ); + for ( i = 0 ; i < sack_count ; i++, sack++ ) { + sack->left = htonl ( tcp->sack[i].left ); + sack->right = htonl ( tcp->sack[i].right ); + } + } if ( len != 0 ) flags |= TCP_PSH; tcphdr = iob_push ( iobuf, sizeof ( *tcphdr ) ); @@ -635,6 +752,17 @@ static void tcp_xmit ( struct tcp_connection *tcp ) { profile_stop ( &tcp_tx_profiler ); } +/** + * Transmit any outstanding data + * + * @v tcp TCP connection + */ +static void tcp_xmit ( struct tcp_connection *tcp ) { + + /* Transmit without an explicit first SACK */ + tcp_xmit_sack ( tcp, tcp->rcv_ack ); +} + /** TCP process descriptor */ static struct process_descriptor tcp_process_desc = PROC_DESC_ONCE ( struct tcp_connection, process, tcp_xmit ); @@ -804,6 +932,12 @@ static void tcp_rx_opts ( struct tcp_connection *tcp, const void *data, case TCP_OPTION_WS: options->wsopt = data; break; + case TCP_OPTION_SACK_PERMITTED: + options->spopt = data; + break; + case TCP_OPTION_SACK: + /* Ignore received SACKs */ + break; case TCP_OPTION_TS: options->tsopt = data; break; @@ -823,6 +957,7 @@ static void tcp_rx_opts ( struct tcp_connection *tcp, const void *data, * @v seq_len Sequence space length to consume */ static void tcp_rx_seq ( struct tcp_connection *tcp, uint32_t seq_len ) { + unsigned int sack; /* Sanity check */ assert ( seq_len > 0 ); @@ -840,6 +975,16 @@ static void tcp_rx_seq ( struct tcp_connection *tcp, uint32_t seq_len ) { /* Update timestamp */ tcp->ts_recent = tcp->ts_val; + /* Update SACK list */ + for ( sack = 0 ; sack < TCP_SACK_MAX ; sack++ ) { + if ( tcp->sack[sack].left == tcp->sack[sack].right ) + continue; + if ( tcp_cmp ( tcp->sack[sack].left, tcp->rcv_ack ) < 0 ) + tcp->sack[sack].left = tcp->rcv_ack; + if ( tcp_cmp ( tcp->sack[sack].right, tcp->rcv_ack ) < 0 ) + tcp->sack[sack].right = tcp->rcv_ack; + } + /* Mark ACK as pending */ tcp->flags |= TCP_ACK_PENDING; } @@ -860,6 +1005,8 @@ static int tcp_rx_syn ( struct tcp_connection *tcp, uint32_t seq, tcp->rcv_ack = seq; if ( options->tsopt ) tcp->flags |= TCP_TS_ENABLED; + if ( options->spopt ) + tcp->flags |= TCP_SACK_ENABLED; if ( options->wsopt ) { tcp->snd_win_scale = options->wsopt->scale; tcp->rcv_win_scale = TCP_RX_WINDOW_SCALE; @@ -1070,6 +1217,7 @@ static void tcp_rx_enqueue ( struct tcp_connection *tcp, uint32_t seq, struct io_buffer *queued; size_t len; uint32_t seq_len; + uint32_t nxt; /* Calculate remaining flags and sequence length. Note that * SYN, if present, has already been processed by this point. @@ -1077,6 +1225,7 @@ static void tcp_rx_enqueue ( struct tcp_connection *tcp, uint32_t seq, flags &= TCP_FIN; len = iob_len ( iobuf ); seq_len = ( len + ( flags ? 1 : 0 ) ); + nxt = ( seq + seq_len ); /* Discard immediately (to save memory) if: * @@ -1087,7 +1236,7 @@ static void tcp_rx_enqueue ( struct tcp_connection *tcp, uint32_t seq, */ if ( ( ! ( tcp->tcp_state & TCP_STATE_RCVD ( TCP_SYN ) ) ) || ( tcp_cmp ( seq, tcp->rcv_ack + tcp->rcv_win ) >= 0 ) || - ( tcp_cmp ( seq + seq_len, tcp->rcv_ack ) < 0 ) || + ( tcp_cmp ( nxt, tcp->rcv_ack ) < 0 ) || ( seq_len == 0 ) ) { free_iob ( iobuf ); return; @@ -1096,6 +1245,7 @@ static void tcp_rx_enqueue ( struct tcp_connection *tcp, uint32_t seq, /* Add internal header */ tcpqhdr = iob_push ( iobuf, sizeof ( *tcpqhdr ) ); tcpqhdr->seq = seq; + tcpqhdr->nxt = nxt; tcpqhdr->flags = flags; /* Add to RX queue */ @@ -1289,7 +1439,7 @@ static int tcp_rx ( struct io_buffer *iobuf, if ( list_empty ( &tcp->rx_queue ) ) { process_add ( &tcp->process ); } else { - tcp_xmit ( tcp ); + tcp_xmit_sack ( tcp, seq ); } /* If this packet was the last we expect to receive, set up @@ -1328,24 +1478,12 @@ struct tcpip_protocol tcp_protocol __tcpip_protocol = { static unsigned int tcp_discard ( void ) { struct tcp_connection *tcp; struct io_buffer *iobuf; - struct tcp_rx_queued_header *tcpqhdr; - uint32_t max_win; unsigned int discarded = 0; /* Try to drop one queued RX packet from each connection */ list_for_each_entry ( tcp, &tcp_conns, list ) { list_for_each_entry_reverse ( iobuf, &tcp->rx_queue, list ) { - /* Limit window to prevent future discards */ - tcpqhdr = iobuf->data; - max_win = ( tcpqhdr->seq - tcp->rcv_ack ); - if ( max_win < tcp->max_rcv_win ) { - DBGC ( tcp, "TCP %p reducing maximum window " - "from %d to %d\n", - tcp, tcp->max_rcv_win, max_win ); - tcp->max_rcv_win = max_win; - } - /* Remove packet from queue */ list_del ( &iobuf->list ); free_iob ( iobuf ); @@ -1365,12 +1503,67 @@ struct cache_discarder tcp_discarder __cache_discarder ( CACHE_NORMAL ) = { }; /** + * Find first TCP connection that has not yet been closed + * + * @ret tcp First unclosed connection, or NULL + */ +static struct tcp_connection * tcp_first_unclosed ( void ) { + struct tcp_connection *tcp; + + /* Find first connection which has not yet been closed */ + list_for_each_entry ( tcp, &tcp_conns, list ) { + if ( ! ( tcp->flags & TCP_XFER_CLOSED ) ) + return tcp; + } + return NULL; +} + +/** + * Find first TCP connection that has not yet finished all operations + * + * @ret tcp First unfinished connection, or NULL + */ +static struct tcp_connection * tcp_first_unfinished ( void ) { + struct tcp_connection *tcp; + + /* Find first connection which has not yet closed gracefully, + * or which still has a pending transmission (e.g. to ACK the + * received FIN). + */ + list_for_each_entry ( tcp, &tcp_conns, list ) { + if ( ( ! TCP_CLOSED_GRACEFULLY ( tcp->tcp_state ) ) || + process_running ( &tcp->process ) ) { + return tcp; + } + } + return NULL; +} + +/** * Shut down all TCP connections * */ static void tcp_shutdown ( int booting __unused ) { struct tcp_connection *tcp; + unsigned long start; + unsigned long elapsed; + + /* Initiate a graceful close of all connections, allowing for + * the fact that the connection list may change as we do so. + */ + while ( ( tcp = tcp_first_unclosed() ) ) { + DBGC ( tcp, "TCP %p closing for shutdown\n", tcp ); + tcp_close ( tcp, -ECANCELED ); + } + + /* Wait for all connections to finish closing gracefully */ + start = currticks(); + while ( ( tcp = tcp_first_unfinished() ) && + ( ( elapsed = ( currticks() - start ) ) < TCP_FINISH_TIMEOUT )){ + step(); + } + /* Forcibly close any remaining connections */ while ( ( tcp = list_first_entry ( &tcp_conns, struct tcp_connection, list ) ) != NULL ) { tcp->tcp_state = TCP_CLOSED; @@ -1380,7 +1573,7 @@ static void tcp_shutdown ( int booting __unused ) { } /** TCP shutdown function */ -struct startup_fn tcp_startup_fn __startup_fn ( STARTUP_EARLY ) = { +struct startup_fn tcp_startup_fn __startup_fn ( STARTUP_LATE ) = { .shutdown = tcp_shutdown, }; diff --git a/qemu/roms/ipxe/src/net/tcp/http.c b/qemu/roms/ipxe/src/net/tcp/http.c index 90bae9d7a..b000ed80f 100644 --- a/qemu/roms/ipxe/src/net/tcp/http.c +++ b/qemu/roms/ipxe/src/net/tcp/http.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file @@ -26,26 +30,20 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ -#include <stddef.h> #include <ipxe/open.h> #include <ipxe/http.h> #include <ipxe/features.h> FEATURE ( FEATURE_PROTOCOL, "HTTP", DHCP_EB_FEATURE_HTTP, 1 ); -/** - * Initiate an HTTP connection - * - * @v xfer Data transfer interface - * @v uri Uniform Resource Identifier - * @ret rc Return status code - */ -static int http_open ( struct interface *xfer, struct uri *uri ) { - return http_open_filter ( xfer, uri, HTTP_PORT, NULL ); -} - /** HTTP URI opener */ struct uri_opener http_uri_opener __uri_opener = { .scheme = "http", - .open = http_open, + .open = http_open_uri, +}; + +/** HTTP URI scheme */ +struct http_scheme http_scheme __http_scheme = { + .name = "http", + .port = HTTP_PORT, }; diff --git a/qemu/roms/ipxe/src/net/tcp/httpauth.c b/qemu/roms/ipxe/src/net/tcp/httpauth.c new file mode 100644 index 000000000..fb6dcd035 --- /dev/null +++ b/qemu/roms/ipxe/src/net/tcp/httpauth.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * Hyper Text Transfer Protocol (HTTP) authentication + * + */ + +#include <stdio.h> +#include <strings.h> +#include <errno.h> +#include <ipxe/http.h> + +/** + * Identify authentication scheme + * + * @v http HTTP transaction + * @v name Scheme name + * @ret auth Authentication scheme, or NULL + */ +static struct http_authentication * http_authentication ( const char *name ) { + struct http_authentication *auth; + + /* Identify authentication scheme */ + for_each_table_entry ( auth, HTTP_AUTHENTICATIONS ) { + if ( strcasecmp ( name, auth->name ) == 0 ) + return auth; + } + + return NULL; +} + +/** An HTTP "WWW-Authenticate" response field */ +struct http_www_authenticate_field { + /** Name */ + const char *name; + /** Offset */ + size_t offset; +}; + +/** Define an HTTP "WWW-Authenticate" response field */ +#define HTTP_WWW_AUTHENTICATE_FIELD( _name ) { \ + .name = #_name, \ + .offset = offsetof ( struct http_transaction, \ + response.auth._name ), \ + } + +/** + * Set HTTP "WWW-Authenticate" response field value + * + * @v http HTTP transaction + * @v field Response field + * @v value Field value + */ +static inline void +http_www_auth_field ( struct http_transaction *http, + struct http_www_authenticate_field *field, char *value ) { + char **ptr; + + ptr = ( ( ( void * ) http ) + field->offset ); + *ptr = value; +} + +/** HTTP "WWW-Authenticate" fields */ +static struct http_www_authenticate_field http_www_auth_fields[] = { + HTTP_WWW_AUTHENTICATE_FIELD ( realm ), + HTTP_WWW_AUTHENTICATE_FIELD ( qop ), + HTTP_WWW_AUTHENTICATE_FIELD ( algorithm ), + HTTP_WWW_AUTHENTICATE_FIELD ( nonce ), + HTTP_WWW_AUTHENTICATE_FIELD ( opaque ), +}; + +/** + * Parse HTTP "WWW-Authenticate" header + * + * @v http HTTP transaction + * @v line Remaining header line + * @ret rc Return status code + */ +static int http_parse_www_authenticate ( struct http_transaction *http, + char *line ) { + struct http_www_authenticate_field *field; + char *name; + char *key; + char *value; + unsigned int i; + + /* Get scheme name */ + name = http_token ( &line, NULL ); + if ( ! name ) { + DBGC ( http, "HTTP %p malformed WWW-Authenticate \"%s\"\n", + http, value ); + return -EPROTO; + } + + /* Identify scheme */ + http->response.auth.auth = http_authentication ( name ); + if ( ! http->response.auth.auth ) { + DBGC ( http, "HTTP %p unrecognised authentication scheme " + "\"%s\"\n", http, name ); + return -ENOTSUP; + } + + /* Process fields */ + while ( ( key = http_token ( &line, &value ) ) ) { + for ( i = 0 ; i < ( sizeof ( http_www_auth_fields ) / + sizeof ( http_www_auth_fields[0] ) ) ; i++){ + field = &http_www_auth_fields[i]; + if ( strcasecmp ( key, field->name ) == 0 ) + http_www_auth_field ( http, field, value ); + } + } + + /* Allow HTTP request to be retried if the request had not + * already tried authentication. + */ + if ( ! http->request.auth.auth ) + http->response.flags |= HTTP_RESPONSE_RETRY; + + return 0; +} + +/** HTTP "WWW-Authenticate" header */ +struct http_response_header +http_response_www_authenticate __http_response_header = { + .name = "WWW-Authenticate", + .parse = http_parse_www_authenticate, +}; + +/** + * Construct HTTP "Authorization" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_authorization ( struct http_transaction *http, + char *buf, size_t len ) { + struct http_authentication *auth = http->request.auth.auth; + size_t used; + int auth_len; + int rc; + + /* Do nothing unless we have an authentication scheme */ + if ( ! auth ) + return 0; + + /* Construct header */ + used = snprintf ( buf, len, "%s ", auth->name ); + auth_len = auth->format ( http, ( buf + used ), + ( ( used < len ) ? ( len - used ) : 0 ) ); + if ( auth_len < 0 ) { + rc = auth_len; + return rc; + } + used += auth_len; + + return used; +} + +/** HTTP "Authorization" header */ +struct http_request_header http_request_authorization __http_request_header = { + .name = "Authorization", + .format = http_format_authorization, +}; diff --git a/qemu/roms/ipxe/src/net/tcp/httpbasic.c b/qemu/roms/ipxe/src/net/tcp/httpbasic.c new file mode 100644 index 000000000..7ed7de9e7 --- /dev/null +++ b/qemu/roms/ipxe/src/net/tcp/httpbasic.c @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * Hyper Text Transfer Protocol (HTTP) Basic authentication + * + */ + +#include <stdio.h> +#include <errno.h> +#include <ipxe/uri.h> +#include <ipxe/base64.h> +#include <ipxe/http.h> + +/* Disambiguate the various error causes */ +#define EACCES_USERNAME __einfo_error ( EINFO_EACCES_USERNAME ) +#define EINFO_EACCES_USERNAME \ + __einfo_uniqify ( EINFO_EACCES, 0x01, \ + "No username available for Basic authentication" ) + +/** + * Perform HTTP Basic authentication + * + * @v http HTTP transaction + * @ret rc Return status code + */ +static int http_basic_authenticate ( struct http_transaction *http ) { + struct http_request_auth *req = &http->request.auth; + + /* Record username and password */ + if ( ! http->uri->user ) { + DBGC ( http, "HTTP %p has no username for Basic " + "authentication\n", http ); + return -EACCES_USERNAME; + } + req->username = http->uri->user; + req->password = ( http->uri->password ? http->uri->password : "" ); + + return 0; +} + +/** + * Construct HTTP "Authorization" header for Basic authentication + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_basic_auth ( struct http_transaction *http, + char *buf, size_t len ) { + struct http_request_auth *req = &http->request.auth; + size_t user_pw_len = ( strlen ( req->username ) + 1 /* ":" */ + + strlen ( req->password ) ); + char user_pw[ user_pw_len + 1 /* NUL */ ]; + + /* Sanity checks */ + assert ( req->username != NULL ); + assert ( req->password != NULL ); + + /* Construct "user:password" string */ + snprintf ( user_pw, sizeof ( user_pw ), "%s:%s", + req->username, req->password ); + + /* Construct response */ + return base64_encode ( user_pw, user_pw_len, buf, len ); +} + +/** HTTP Basic authentication scheme */ +struct http_authentication http_basic_auth __http_authentication = { + .name = "Basic", + .authenticate = http_basic_authenticate, + .format = http_format_basic_auth, +}; + +/* Drag in HTTP authentication support */ +REQUIRING_SYMBOL ( http_basic_auth ); +REQUIRE_OBJECT ( httpauth ); diff --git a/qemu/roms/ipxe/src/net/tcp/httpblock.c b/qemu/roms/ipxe/src/net/tcp/httpblock.c new file mode 100644 index 000000000..e124ad2d6 --- /dev/null +++ b/qemu/roms/ipxe/src/net/tcp/httpblock.c @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * Hyper Text Transfer Protocol (HTTP) block device + * + */ + +#include <stdint.h> +#include <ipxe/uaccess.h> +#include <ipxe/blocktrans.h> +#include <ipxe/blockdev.h> +#include <ipxe/acpi.h> +#include <ipxe/http.h> + +/** Block size used for HTTP block device requests */ +#define HTTP_BLKSIZE 512 + +/** + * Read from block device + * + * @v http HTTP transaction + * @v data Data interface + * @v lba Starting logical block address + * @v count Number of logical blocks + * @v buffer Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +int http_block_read ( struct http_transaction *http, struct interface *data, + uint64_t lba, unsigned int count, userptr_t buffer, + size_t len ) { + struct http_request_range range; + int rc; + + /* Sanity check */ + assert ( len == ( count * HTTP_BLKSIZE ) ); + + /* Construct request range descriptor */ + range.start = ( lba * HTTP_BLKSIZE ); + range.len = len; + + /* Start a range request to retrieve the block(s) */ + if ( ( rc = http_open ( data, &http_get, http->uri, &range, + NULL ) ) != 0 ) + goto err_open; + + /* Insert block device translator */ + if ( ( rc = block_translate ( data, buffer, len ) ) != 0 ) { + DBGC ( http, "HTTP %p could not insert block translator: %s\n", + http, strerror ( rc ) ); + goto err_translate; + } + + return 0; + + err_translate: + intf_restart ( data, rc ); + err_open: + return rc; +} + +/** + * Read block device capacity + * + * @v control Control interface + * @v data Data interface + * @ret rc Return status code + */ +int http_block_read_capacity ( struct http_transaction *http, + struct interface *data ) { + int rc; + + /* Start a HEAD request to retrieve the capacity */ + if ( ( rc = http_open ( data, &http_head, http->uri, NULL, + NULL ) ) != 0 ) + goto err_open; + + /* Insert block device translator */ + if ( ( rc = block_translate ( data, UNULL, HTTP_BLKSIZE ) ) != 0 ) { + DBGC ( http, "HTTP %p could not insert block translator: %s\n", + http, strerror ( rc ) ); + goto err_translate; + } + + return 0; + + err_translate: + intf_restart ( data, rc ); + err_open: + return rc; +} + +/** + * Describe device in ACPI table + * + * @v http HTTP transaction + * @v acpi ACPI table + * @v len Length of ACPI table + * @ret rc Return status code + */ +int http_acpi_describe ( struct http_transaction *http, + struct acpi_description_header *acpi, size_t len ) { + + DBGC ( http, "HTTP %p cannot yet describe device in an ACPI table\n", + http ); + ( void ) acpi; + ( void ) len; + return 0; +} diff --git a/qemu/roms/ipxe/src/net/tcp/httpconn.c b/qemu/roms/ipxe/src/net/tcp/httpconn.c new file mode 100644 index 000000000..7e4877b7b --- /dev/null +++ b/qemu/roms/ipxe/src/net/tcp/httpconn.c @@ -0,0 +1,309 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * Hyper Text Transfer Protocol (HTTP) connection management + * + */ + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/tcpip.h> +#include <ipxe/uri.h> +#include <ipxe/timer.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/pool.h> +#include <ipxe/http.h> + +/** HTTP pooled connection expiry time */ +#define HTTP_CONN_EXPIRY ( 10 * TICKS_PER_SEC ) + +/** HTTP connection pool */ +static LIST_HEAD ( http_connection_pool ); + +/** + * Identify HTTP scheme + * + * @v uri URI + * @ret scheme HTTP scheme, or NULL + */ +static struct http_scheme * http_scheme ( struct uri *uri ) { + struct http_scheme *scheme; + + /* Sanity check */ + if ( ! uri->scheme ) + return NULL; + + /* Identify scheme */ + for_each_table_entry ( scheme, HTTP_SCHEMES ) { + if ( strcmp ( uri->scheme, scheme->name ) == 0 ) + return scheme; + } + + return NULL; +} + +/** + * Free HTTP connection + * + * @v refcnt Reference count + */ +static void http_conn_free ( struct refcnt *refcnt ) { + struct http_connection *conn = + container_of ( refcnt, struct http_connection, refcnt ); + + /* Free connection */ + uri_put ( conn->uri ); + free ( conn ); +} + +/** + * Close HTTP connection + * + * @v conn HTTP connection + * @v rc Reason for close + */ +static void http_conn_close ( struct http_connection *conn, int rc ) { + + /* Remove from connection pool, if applicable */ + pool_del ( &conn->pool ); + + /* Shut down interfaces */ + intf_shutdown ( &conn->socket, rc ); + intf_shutdown ( &conn->xfer, rc ); + if ( rc == 0 ) { + DBGC2 ( conn, "HTTPCONN %p closed %s://%s\n", + conn, conn->scheme->name, conn->uri->host ); + } else { + DBGC ( conn, "HTTPCONN %p closed %s://%s: %s\n", + conn, conn->scheme->name, conn->uri->host, + strerror ( rc ) ); + } +} + +/** + * Disconnect idle HTTP connection + * + * @v pool Pooled connection + */ +static void http_conn_expired ( struct pooled_connection *pool ) { + struct http_connection *conn = + container_of ( pool, struct http_connection, pool ); + + /* Close connection */ + http_conn_close ( conn, 0 /* Not an error to close idle connection */ ); +} + +/** + * Receive data from transport layer interface + * + * @v http HTTP connection + * @v iobuf I/O buffer + * @v meta Transfer metadata + * @ret rc Return status code + */ +static int http_conn_socket_deliver ( struct http_connection *conn, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + + /* Mark connection as alive */ + pool_alive ( &conn->pool ); + + /* Pass on to data transfer interface */ + return xfer_deliver ( &conn->xfer, iobuf, meta ); +} + +/** + * Close HTTP connection transport layer interface + * + * @v http HTTP connection + * @v rc Reason for close + */ +static void http_conn_socket_close ( struct http_connection *conn, int rc ) { + + /* If we are reopenable (i.e. we are a recycled connection + * from the connection pool, and we have received no data from + * the underlying socket since we were pooled), then suggest + * that the client should reopen the connection. + */ + if ( pool_is_reopenable ( &conn->pool ) ) + pool_reopen ( &conn->xfer ); + + /* Close the connection */ + http_conn_close ( conn, rc ); +} + +/** + * Recycle this connection after closing + * + * @v http HTTP connection + */ +static void http_conn_xfer_recycle ( struct http_connection *conn ) { + + /* Mark connection as recyclable */ + pool_recyclable ( &conn->pool ); + DBGC2 ( conn, "HTTPCONN %p keepalive enabled\n", conn ); +} + +/** + * Close HTTP connection data transfer interface + * + * @v conn HTTP connection + * @v rc Reason for close + */ +static void http_conn_xfer_close ( struct http_connection *conn, int rc ) { + + /* Add to the connection pool if keepalive is enabled and no + * error occurred. + */ + if ( ( rc == 0 ) && pool_is_recyclable ( &conn->pool ) ) { + intf_restart ( &conn->xfer, rc ); + pool_add ( &conn->pool, &http_connection_pool, + HTTP_CONN_EXPIRY ); + DBGC2 ( conn, "HTTPCONN %p pooled %s://%s\n", + conn, conn->scheme->name, conn->uri->host ); + return; + } + + /* Otherwise, close the connection */ + http_conn_close ( conn, rc ); +} + +/** HTTP connection socket interface operations */ +static struct interface_operation http_conn_socket_operations[] = { + INTF_OP ( xfer_deliver, struct http_connection *, + http_conn_socket_deliver ), + INTF_OP ( intf_close, struct http_connection *, + http_conn_socket_close ), +}; + +/** HTTP connection socket interface descriptor */ +static struct interface_descriptor http_conn_socket_desc = + INTF_DESC_PASSTHRU ( struct http_connection, socket, + http_conn_socket_operations, xfer ); + +/** HTTP connection data transfer interface operations */ +static struct interface_operation http_conn_xfer_operations[] = { + INTF_OP ( pool_recycle, struct http_connection *, + http_conn_xfer_recycle ), + INTF_OP ( intf_close, struct http_connection *, + http_conn_xfer_close ), +}; + +/** HTTP connection data transfer interface descriptor */ +static struct interface_descriptor http_conn_xfer_desc = + INTF_DESC_PASSTHRU ( struct http_connection, xfer, + http_conn_xfer_operations, socket ); + +/** + * Connect to an HTTP server + * + * @v xfer Data transfer interface + * @v uri Connection URI + * @ret rc Return status code + * + * HTTP connections are pooled. The caller should be prepared to + * receive a pool_reopen() message. + */ +int http_connect ( struct interface *xfer, struct uri *uri ) { + struct http_connection *conn; + struct http_scheme *scheme; + struct sockaddr_tcpip server; + struct interface *socket; + int rc; + + /* Identify scheme */ + scheme = http_scheme ( uri ); + if ( ! scheme ) + return -ENOTSUP; + + /* Sanity check */ + if ( ! uri->host ) + return -EINVAL; + + /* Look for a reusable connection in the pool */ + list_for_each_entry ( conn, &http_connection_pool, pool.list ) { + + /* Sanity checks */ + assert ( conn->uri != NULL ); + assert ( conn->uri->host != NULL ); + + /* Reuse connection, if possible */ + if ( ( scheme == conn->scheme ) && + ( strcmp ( uri->host, conn->uri->host ) == 0 ) ) { + + /* Remove from connection pool, stop timer, + * attach to parent interface, and return. + */ + pool_del ( &conn->pool ); + intf_plug_plug ( &conn->xfer, xfer ); + DBGC2 ( conn, "HTTPCONN %p reused %s://%s\n", + conn, conn->scheme->name, conn->uri->host ); + return 0; + } + } + + /* Allocate and initialise structure */ + conn = zalloc ( sizeof ( *conn ) ); + ref_init ( &conn->refcnt, http_conn_free ); + conn->uri = uri_get ( uri ); + conn->scheme = scheme; + intf_init ( &conn->socket, &http_conn_socket_desc, &conn->refcnt ); + intf_init ( &conn->xfer, &http_conn_xfer_desc, &conn->refcnt ); + pool_init ( &conn->pool, http_conn_expired, &conn->refcnt ); + + /* Open socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st_port = htons ( uri_port ( uri, scheme->port ) ); + socket = &conn->socket; + if ( scheme->filter && + ( ( rc = scheme->filter ( socket, uri->host, &socket ) ) != 0 ) ) + goto err_filter; + if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM, + ( struct sockaddr * ) &server, + uri->host, NULL ) ) != 0 ) + goto err_open; + + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &conn->xfer, xfer ); + ref_put ( &conn->refcnt ); + + DBGC2 ( conn, "HTTPCONN %p created %s://%s:%d\n", conn, + conn->scheme->name, conn->uri->host, ntohs ( server.st_port ) ); + return 0; + + err_open: + err_filter: + DBGC2 ( conn, "HTTPCONN %p could not create %s://%s: %s\n", + conn, conn->scheme->name, conn->uri->host, strerror ( rc ) ); + http_conn_close ( conn, rc ); + ref_put ( &conn->refcnt ); + return rc; +} diff --git a/qemu/roms/ipxe/src/net/tcp/httpcore.c b/qemu/roms/ipxe/src/net/tcp/httpcore.c index 1d1953e61..af3ca9780 100644 --- a/qemu/roms/ipxe/src/net/tcp/httpcore.c +++ b/qemu/roms/ipxe/src/net/tcp/httpcore.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>. + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file @@ -40,35 +44,26 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/iobuf.h> #include <ipxe/xfer.h> #include <ipxe/open.h> -#include <ipxe/socket.h> -#include <ipxe/tcpip.h> #include <ipxe/process.h> #include <ipxe/retry.h> #include <ipxe/timer.h> #include <ipxe/linebuf.h> -#include <ipxe/base64.h> -#include <ipxe/base16.h> -#include <ipxe/md5.h> +#include <ipxe/xferbuf.h> #include <ipxe/blockdev.h> #include <ipxe/acpi.h> #include <ipxe/version.h> #include <ipxe/params.h> #include <ipxe/profile.h> +#include <ipxe/vsprintf.h> #include <ipxe/http.h> /* Disambiguate the various error causes */ #define EACCES_401 __einfo_error ( EINFO_EACCES_401 ) #define EINFO_EACCES_401 \ __einfo_uniqify ( EINFO_EACCES, 0x01, "HTTP 401 Unauthorized" ) -#define EIO_OTHER __einfo_error ( EINFO_EIO_OTHER ) -#define EINFO_EIO_OTHER \ - __einfo_uniqify ( EINFO_EIO, 0x01, "Unrecognised HTTP response code" ) -#define EIO_CONTENT_LENGTH __einfo_error ( EINFO_EIO_CONTENT_LENGTH ) -#define EINFO_EIO_CONTENT_LENGTH \ - __einfo_uniqify ( EINFO_EIO, 0x02, "Content length mismatch" ) -#define EINVAL_RESPONSE __einfo_error ( EINFO_EINVAL_RESPONSE ) -#define EINFO_EINVAL_RESPONSE \ - __einfo_uniqify ( EINFO_EINVAL, 0x01, "Invalid content length" ) +#define EINVAL_STATUS __einfo_error ( EINFO_EINVAL_STATUS ) +#define EINFO_EINVAL_STATUS \ + __einfo_uniqify ( EINFO_EINVAL, 0x01, "Invalid status line" ) #define EINVAL_HEADER __einfo_error ( EINFO_EINVAL_HEADER ) #define EINFO_EINVAL_HEADER \ __einfo_uniqify ( EINFO_EINVAL, 0x02, "Invalid header" ) @@ -78,9 +73,27 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINVAL_CHUNK_LENGTH __einfo_error ( EINFO_EINVAL_CHUNK_LENGTH ) #define EINFO_EINVAL_CHUNK_LENGTH \ __einfo_uniqify ( EINFO_EINVAL, 0x04, "Invalid chunk length" ) +#define EIO_OTHER __einfo_error ( EINFO_EIO_OTHER ) +#define EINFO_EIO_OTHER \ + __einfo_uniqify ( EINFO_EIO, 0x01, "Unrecognised HTTP response code" ) +#define EIO_CONTENT_LENGTH __einfo_error ( EINFO_EIO_CONTENT_LENGTH ) +#define EINFO_EIO_CONTENT_LENGTH \ + __einfo_uniqify ( EINFO_EIO, 0x02, "Content length mismatch" ) +#define EIO_4XX __einfo_error ( EINFO_EIO_4XX ) +#define EINFO_EIO_4XX \ + __einfo_uniqify ( EINFO_EIO, 0x04, "HTTP 4xx Client Error" ) +#define EIO_5XX __einfo_error ( EINFO_EIO_5XX ) +#define EINFO_EIO_5XX \ + __einfo_uniqify ( EINFO_EIO, 0x05, "HTTP 5xx Server Error" ) #define ENOENT_404 __einfo_error ( EINFO_ENOENT_404 ) #define EINFO_ENOENT_404 \ __einfo_uniqify ( EINFO_ENOENT, 0x01, "HTTP 404 Not Found" ) +#define ENOTSUP_CONNECTION __einfo_error ( EINFO_ENOTSUP_CONNECTION ) +#define EINFO_ENOTSUP_CONNECTION \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x01, "Unsupported connection header" ) +#define ENOTSUP_TRANSFER __einfo_error ( EINFO_ENOTSUP_TRANSFER ) +#define EINFO_ENOTSUP_TRANSFER \ + __einfo_uniqify ( EINFO_ENOTSUP, 0x02, "Unsupported transfer encoding" ) #define EPERM_403 __einfo_error ( EINFO_EPERM_403 ) #define EINFO_EPERM_403 \ __einfo_uniqify ( EINFO_EPERM, 0x01, "HTTP 403 Forbidden" ) @@ -88,9 +101,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINFO_EPROTO_UNSOLICITED \ __einfo_uniqify ( EINFO_EPROTO, 0x01, "Unsolicited data" ) -/** Block size used for HTTP block device request */ -#define HTTP_BLKSIZE 512 - /** Retry delay used when we cannot understand the Retry-After header */ #define HTTP_RETRY_SECONDS 5 @@ -100,1069 +110,1711 @@ static struct profiler http_rx_profiler __profiler = { .name = "http.rx" }; /** Data transfer profiler */ static struct profiler http_xfer_profiler __profiler = { .name = "http.xfer" }; -/** HTTP flags */ -enum http_flags { - /** Request is waiting to be transmitted */ - HTTP_TX_PENDING = 0x0001, - /** Fetch header only */ - HTTP_HEAD_ONLY = 0x0002, - /** Client would like to keep connection alive */ - HTTP_CLIENT_KEEPALIVE = 0x0004, - /** Server will keep connection alive */ - HTTP_SERVER_KEEPALIVE = 0x0008, - /** Discard the current request and try again */ - HTTP_TRY_AGAIN = 0x0010, - /** Provide Basic authentication details */ - HTTP_BASIC_AUTH = 0x0020, - /** Provide Digest authentication details */ - HTTP_DIGEST_AUTH = 0x0040, - /** Socket must be reopened */ - HTTP_REOPEN_SOCKET = 0x0080, +static struct http_state http_request; +static struct http_state http_headers; +static struct http_state http_trailers; +static struct http_transfer_encoding http_transfer_identity; + +/****************************************************************************** + * + * Methods + * + ****************************************************************************** + */ + +/** HTTP HEAD method */ +struct http_method http_head = { + .name = "HEAD", }; -/** HTTP receive state */ -enum http_rx_state { - HTTP_RX_RESPONSE = 0, - HTTP_RX_HEADER, - HTTP_RX_CHUNK_LEN, - /* In HTTP_RX_DATA, it is acceptable for the server to close - * the connection (unless we are in the middle of a chunked - * transfer). - */ - HTTP_RX_DATA, - /* In the following states, it is acceptable for the server to - * close the connection. - */ - HTTP_RX_TRAILER, - HTTP_RX_IDLE, - HTTP_RX_DEAD, +/** HTTP GET method */ +struct http_method http_get = { + .name = "GET", }; +/** HTTP POST method */ +struct http_method http_post = { + .name = "POST", +}; + +/****************************************************************************** + * + * Utility functions + * + ****************************************************************************** + */ + /** - * An HTTP request + * Handle received HTTP line-buffered data * + * @v http HTTP transaction + * @v iobuf I/O buffer + * @v linebuf Line buffer + * @ret rc Return status code */ -struct http_request { - /** Reference count */ - struct refcnt refcnt; - /** Data transfer interface */ - struct interface xfer; - /** Partial transfer interface */ - struct interface partial; - - /** URI being fetched */ - struct uri *uri; - /** Default port */ - unsigned int default_port; - /** Filter (if any) */ - int ( * filter ) ( struct interface *xfer, - const char *name, - struct interface **next ); - /** Transport layer interface */ - struct interface socket; - - /** Flags */ - unsigned int flags; - /** Starting offset of partial transfer (if applicable) */ - size_t partial_start; - /** Length of partial transfer (if applicable) */ - size_t partial_len; - - /** TX process */ - struct process process; - - /** RX state */ - enum http_rx_state rx_state; - /** Response code */ - unsigned int code; - /** Received length */ - size_t rx_len; - /** Length remaining (or 0 if unknown) */ - size_t remaining; - /** HTTP is using Transfer-Encoding: chunked */ - int chunked; - /** Current chunk length remaining (if applicable) */ - size_t chunk_remaining; - /** Line buffer for received header lines */ - struct line_buffer linebuf; - /** Receive data buffer (if applicable) */ - userptr_t rx_buffer; - - /** Authentication realm (if any) */ - char *auth_realm; - /** Authentication nonce (if any) */ - char *auth_nonce; - /** Authentication opaque string (if any) */ - char *auth_opaque; - - /** Request retry timer */ - struct retry_timer timer; - /** Retry delay (in timer ticks) */ - unsigned long retry_delay; -}; +static int http_rx_linebuf ( struct http_transaction *http, + struct io_buffer *iobuf, + struct line_buffer *linebuf ) { + int consumed; + int rc; + + /* Buffer received line */ + consumed = line_buffer ( linebuf, iobuf->data, iob_len ( iobuf ) ); + if ( consumed < 0 ) { + rc = consumed; + DBGC ( http, "HTTP %p could not buffer line: %s\n", + http, strerror ( rc ) ); + return rc; + } + + /* Consume line */ + iob_pull ( iobuf, consumed ); + + return 0; +} /** - * Free HTTP request + * Get HTTP response token * - * @v refcnt Reference counter + * @v line Line position + * @v value Token value to fill in (if any) + * @ret token Token, or NULL + */ +char * http_token ( char **line, char **value ) { + char *token; + char quote = '\0'; + char c; + + /* Avoid returning uninitialised data */ + if ( value ) + *value = NULL; + + /* Skip any initial whitespace */ + while ( isspace ( **line ) ) + (*line)++; + + /* Check for end of line and record token position */ + if ( ! **line ) + return NULL; + token = *line; + + /* Scan for end of token */ + while ( ( c = **line ) ) { + + /* Terminate if we hit an unquoted whitespace */ + if ( isspace ( c ) && ! quote ) + break; + + /* Terminate if we hit a closing quote */ + if ( c == quote ) + break; + + /* Check for value separator */ + if ( value && ( ! *value ) && ( c == '=' ) ) { + + /* Terminate key portion of token */ + *((*line)++) = '\0'; + + /* Check for quote character */ + c = **line; + if ( ( c == '"' ) || ( c == '\'' ) ) { + quote = c; + (*line)++; + } + + /* Record value portion of token */ + *value = *line; + + } else { + + /* Move to next character */ + (*line)++; + } + } + + /* Terminate token, if applicable */ + if ( c ) + *((*line)++) = '\0'; + + return token; +} + +/****************************************************************************** + * + * Transactions + * + ****************************************************************************** + */ + +/** + * Free HTTP transaction + * + * @v refcnt Reference count */ static void http_free ( struct refcnt *refcnt ) { - struct http_request *http = - container_of ( refcnt, struct http_request, refcnt ); + struct http_transaction *http = + container_of ( refcnt, struct http_transaction, refcnt ); - uri_put ( http->uri ); + empty_line_buffer ( &http->response.headers ); empty_line_buffer ( &http->linebuf ); - free ( http->auth_realm ); - free ( http->auth_nonce ); - free ( http->auth_opaque ); + uri_put ( http->uri ); free ( http ); -}; +} /** - * Close HTTP request + * Close HTTP transaction * - * @v http HTTP request - * @v rc Return status code + * @v http HTTP transaction + * @v rc Reason for close */ -static void http_close ( struct http_request *http, int rc ) { - - /* Prevent further processing of any current packet */ - http->rx_state = HTTP_RX_DEAD; +static void http_close ( struct http_transaction *http, int rc ) { - /* Prevent reconnection */ - http->flags &= ~HTTP_CLIENT_KEEPALIVE; - - /* Remove process */ + /* Stop process */ process_del ( &http->process ); - /* Close all data transfer interfaces */ - intf_shutdown ( &http->socket, rc ); - intf_shutdown ( &http->partial, rc ); + /* Stop timer */ + stop_timer ( &http->timer ); + + /* Close all interfaces, allowing for the fact that the + * content-decoded and transfer-decoded interfaces may be + * connected to the same object. + */ + intf_shutdown ( &http->conn, rc ); + intf_nullify ( &http->transfer ); + intf_shutdown ( &http->content, rc ); + intf_shutdown ( &http->transfer, rc ); intf_shutdown ( &http->xfer, rc ); } /** - * Open HTTP socket + * Close HTTP transaction with error (even if none specified) * - * @v http HTTP request - * @ret rc Return status code + * @v http HTTP transaction + * @v rc Reason for close */ -static int http_socket_open ( struct http_request *http ) { - struct uri *uri = http->uri; - struct sockaddr_tcpip server; - struct interface *socket; +static void http_close_error ( struct http_transaction *http, int rc ) { + + /* Treat any close as an error */ + http_close ( http, ( rc ? rc : -EPIPE ) ); +} + +/** + * Reopen stale HTTP connection + * + * @v http HTTP transaction + */ +static void http_reopen ( struct http_transaction *http ) { int rc; - /* Open socket */ - memset ( &server, 0, sizeof ( server ) ); - server.st_port = htons ( uri_port ( uri, http->default_port ) ); - socket = &http->socket; - if ( http->filter ) { - if ( ( rc = http->filter ( socket, uri->host, &socket ) ) != 0 ) - return rc; + /* Close existing connection */ + intf_restart ( &http->conn, -ECANCELED ); + + /* Reopen connection */ + if ( ( rc = http_connect ( &http->conn, http->uri ) ) != 0 ) { + DBGC ( http, "HTTP %p could not reconnect: %s\n", + http, strerror ( rc ) ); + goto err_connect; } - if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM, - ( struct sockaddr * ) &server, - uri->host, NULL ) ) != 0 ) - return rc; - return 0; + /* Reset state */ + http->state = &http_request; + + /* Reschedule transmission process */ + process_add ( &http->process ); + + return; + + err_connect: + http_close ( http, rc ); } /** - * Retry HTTP request + * Handle retry timer expiry * * @v timer Retry timer - * @v fail Failure indicator + * @v over Failure indicator + */ +static void http_expired ( struct retry_timer *timer, int over __unused ) { + struct http_transaction *http = + container_of ( timer, struct http_transaction, timer ); + + /* Reopen connection */ + http_reopen ( http ); +} + +/** + * HTTP transmit process + * + * @v http HTTP transaction */ -static void http_retry ( struct retry_timer *timer, int fail __unused ) { - struct http_request *http = - container_of ( timer, struct http_request, timer ); +static void http_step ( struct http_transaction *http ) { int rc; - /* Reopen socket if required */ - if ( http->flags & HTTP_REOPEN_SOCKET ) { - http->flags &= ~HTTP_REOPEN_SOCKET; - DBGC ( http, "HTTP %p reopening connection\n", http ); - if ( ( rc = http_socket_open ( http ) ) != 0 ) { - http_close ( http, rc ); - return; + /* Do nothing if we have nothing to transmit */ + if ( ! http->state->tx ) + return; + + /* Do nothing until connection is ready */ + if ( ! xfer_window ( &http->conn ) ) + return; + + /* Do nothing until data transfer interface is ready */ + if ( ! xfer_window ( &http->xfer ) ) + return; + + /* Transmit data */ + if ( ( rc = http->state->tx ( http ) ) != 0 ) + goto err; + + return; + + err: + http_close ( http, rc ); +} + +/** + * Handle received HTTP data + * + * @v http HTTP transaction + * @v iobuf I/O buffer + * @v meta Transfer metadata + * @ret rc Return status code + * + * This function takes ownership of the I/O buffer. + */ +static int http_conn_deliver ( struct http_transaction *http, + struct io_buffer *iobuf, + struct xfer_metadata *meta __unused ) { + int rc; + + /* Handle received data */ + profile_start ( &http_rx_profiler ); + while ( iobuf && iob_len ( iobuf ) ) { + + /* Sanity check */ + if ( ( ! http->state ) || ( ! http->state->rx ) ) { + DBGC ( http, "HTTP %p unexpected data\n", http ); + rc = -EPROTO_UNSOLICITED; + goto err; } - } - /* Retry the request if applicable */ - if ( http->flags & HTTP_TRY_AGAIN ) { - http->flags &= ~HTTP_TRY_AGAIN; - DBGC ( http, "HTTP %p retrying request\n", http ); - http->flags |= HTTP_TX_PENDING; - http->rx_state = HTTP_RX_RESPONSE; - process_add ( &http->process ); + /* Receive (some) data */ + if ( ( rc = http->state->rx ( http, &iobuf ) ) != 0 ) + goto err; } + + /* Free I/O buffer, if applicable */ + free_iob ( iobuf ); + + profile_stop ( &http_rx_profiler ); + return 0; + + err: + free_iob ( iobuf ); + http_close ( http, rc ); + return rc; } /** - * Mark HTTP request as completed successfully + * Handle server connection close * - * @v http HTTP request + * @v http HTTP transaction + * @v rc Reason for close */ -static void http_done ( struct http_request *http ) { +static void http_conn_close ( struct http_transaction *http, int rc ) { - /* If we are not at an appropriate stage of the protocol - * (including being in the middle of a chunked transfer), - * force an error. - */ - if ( ( http->rx_state < HTTP_RX_DATA ) || ( http->chunked != 0 ) ) { - DBGC ( http, "HTTP %p connection closed unexpectedly in state " - "%d\n", http, http->rx_state ); - http_close ( http, -ECONNRESET ); - return; - } + /* Sanity checks */ + assert ( http->state != NULL ); + assert ( http->state->close != NULL ); + + /* Restart server connection interface */ + intf_restart ( &http->conn, rc ); + + /* Hand off to state-specific method */ + http->state->close ( http, rc ); +} - /* If we had a Content-Length, and the received content length - * isn't correct, force an error +/** + * Handle received content-decoded data + * + * @v http HTTP transaction + * @v iobuf I/O buffer + * @v meta Data transfer metadata + */ +static int http_content_deliver ( struct http_transaction *http, + struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + int rc; + + /* Ignore content if this is anything other than a successful + * transfer. */ - if ( http->remaining != 0 ) { - DBGC ( http, "HTTP %p incorrect length %zd, should be %zd\n", - http, http->rx_len, ( http->rx_len + http->remaining ) ); - http_close ( http, -EIO_CONTENT_LENGTH ); - return; + if ( http->response.rc != 0 ) { + free_iob ( iobuf ); + return 0; } - /* Enter idle state */ - http->rx_state = HTTP_RX_IDLE; - http->rx_len = 0; - assert ( http->remaining == 0 ); - assert ( http->chunked == 0 ); - assert ( http->chunk_remaining == 0 ); + /* Deliver to data transfer interface */ + profile_start ( &http_xfer_profiler ); + if ( ( rc = xfer_deliver ( &http->xfer, iob_disown ( iobuf ), + meta ) ) != 0 ) + return rc; + profile_stop ( &http_xfer_profiler ); - /* Close partial transfer interface */ - if ( ! ( http->flags & HTTP_TRY_AGAIN ) ) - intf_restart ( &http->partial, 0 ); + return 0; +} - /* Close everything unless we want to keep the connection alive */ - if ( ! ( http->flags & ( HTTP_CLIENT_KEEPALIVE | HTTP_TRY_AGAIN ) ) ) { - http_close ( http, 0 ); - return; - } +/** + * Get underlying data transfer buffer + * + * @v http HTTP transaction + * @ret xferbuf Data transfer buffer, or NULL on error + */ +static struct xfer_buffer * +http_content_buffer ( struct http_transaction *http ) { - /* If the server is not intending to keep the connection - * alive, then close the socket and mark it as requiring - * reopening. + /* Deny access to the data transfer buffer if this is anything + * other than a successful transfer. */ - if ( ! ( http->flags & HTTP_SERVER_KEEPALIVE ) ) { - intf_restart ( &http->socket, 0 ); - http->flags &= ~HTTP_SERVER_KEEPALIVE; - http->flags |= HTTP_REOPEN_SOCKET; - } + if ( http->response.rc != 0 ) + return NULL; - /* Start request retry timer */ - start_timer_fixed ( &http->timer, http->retry_delay ); - http->retry_delay = 0; + /* Hand off to data transfer interface */ + return xfer_buffer ( &http->xfer ); } /** - * Convert HTTP response code to return status code + * Read from block device (when HTTP block device support is not present) * - * @v response HTTP response code + * @v http HTTP transaction + * @v data Data interface + * @v lba Starting logical block address + * @v count Number of logical blocks + * @v buffer Data buffer + * @v len Length of data buffer * @ret rc Return status code */ -static int http_response_to_rc ( unsigned int response ) { - switch ( response ) { - case 200: - case 206: - case 301: - case 302: - case 303: - return 0; - case 404: - return -ENOENT_404; - case 403: - return -EPERM_403; - case 401: - return -EACCES_401; - default: - return -EIO_OTHER; - } +__weak int http_block_read ( struct http_transaction *http __unused, + struct interface *data __unused, + uint64_t lba __unused, unsigned int count __unused, + userptr_t buffer __unused, size_t len __unused ) { + + return -ENOTSUP; } /** - * Handle HTTP response + * Read block device capacity (when HTTP block device support is not present) * - * @v http HTTP request - * @v response HTTP response + * @v control Control interface + * @v data Data interface * @ret rc Return status code */ -static int http_rx_response ( struct http_request *http, char *response ) { - char *spc; - - DBGC ( http, "HTTP %p response \"%s\"\n", http, response ); +__weak int http_block_read_capacity ( struct http_transaction *http __unused, + struct interface *data __unused ) { - /* Check response starts with "HTTP/" */ - if ( strncmp ( response, "HTTP/", 5 ) != 0 ) - return -EINVAL_RESPONSE; + return -ENOTSUP; +} - /* Locate and store response code */ - spc = strchr ( response, ' ' ); - if ( ! spc ) - return -EINVAL_RESPONSE; - http->code = strtoul ( spc, NULL, 10 ); +/** + * Describe device in ACPI table (when HTTP block device support is not present) + * + * @v http HTTP transaction + * @v acpi ACPI table + * @v len Length of ACPI table + * @ret rc Return status code + */ +__weak int http_acpi_describe ( struct http_transaction *http __unused, + struct acpi_description_header *acpi __unused, + size_t len __unused ) { - /* Move to receive headers */ - http->rx_state = ( ( http->flags & HTTP_HEAD_ONLY ) ? - HTTP_RX_TRAILER : HTTP_RX_HEADER ); - return 0; + return -ENOTSUP; } +/** HTTP data transfer interface operations */ +static struct interface_operation http_xfer_operations[] = { + INTF_OP ( block_read, struct http_transaction *, http_block_read ), + INTF_OP ( block_read_capacity, struct http_transaction *, + http_block_read_capacity ), + INTF_OP ( acpi_describe, struct http_transaction *, + http_acpi_describe ), + INTF_OP ( xfer_window_changed, struct http_transaction *, http_step ), + INTF_OP ( intf_close, struct http_transaction *, http_close ), +}; + +/** HTTP data transfer interface descriptor */ +static struct interface_descriptor http_xfer_desc = + INTF_DESC_PASSTHRU ( struct http_transaction, xfer, + http_xfer_operations, content ); + +/** HTTP content-decoded interface operations */ +static struct interface_operation http_content_operations[] = { + INTF_OP ( xfer_deliver, struct http_transaction *, + http_content_deliver ), + INTF_OP ( xfer_buffer, struct http_transaction *, http_content_buffer ), + INTF_OP ( intf_close, struct http_transaction *, http_close ), +}; + +/** HTTP content-decoded interface descriptor */ +static struct interface_descriptor http_content_desc = + INTF_DESC_PASSTHRU ( struct http_transaction, content, + http_content_operations, xfer ); + +/** HTTP transfer-decoded interface operations */ +static struct interface_operation http_transfer_operations[] = { + INTF_OP ( intf_close, struct http_transaction *, http_close ), +}; + +/** HTTP transfer-decoded interface descriptor */ +static struct interface_descriptor http_transfer_desc = + INTF_DESC_PASSTHRU ( struct http_transaction, transfer, + http_transfer_operations, conn ); + +/** HTTP server connection interface operations */ +static struct interface_operation http_conn_operations[] = { + INTF_OP ( xfer_deliver, struct http_transaction *, http_conn_deliver ), + INTF_OP ( xfer_window_changed, struct http_transaction *, http_step ), + INTF_OP ( pool_reopen, struct http_transaction *, http_reopen ), + INTF_OP ( intf_close, struct http_transaction *, http_conn_close ), +}; + +/** HTTP server connection interface descriptor */ +static struct interface_descriptor http_conn_desc = + INTF_DESC_PASSTHRU ( struct http_transaction, conn, + http_conn_operations, transfer ); + +/** HTTP process descriptor */ +static struct process_descriptor http_process_desc = + PROC_DESC_ONCE ( struct http_transaction, process, http_step ); + /** - * Handle HTTP Location header + * Open HTTP transaction * - * @v http HTTP request - * @v value HTTP header value + * @v xfer Data transfer interface + * @v method Request method + * @v uri Request URI + * @v range Content range (if any) + * @v content Request content (if any) * @ret rc Return status code */ -static int http_rx_location ( struct http_request *http, char *value ) { +int http_open ( struct interface *xfer, struct http_method *method, + struct uri *uri, struct http_request_range *range, + struct http_request_content *content ) { + struct http_transaction *http; + struct uri request_uri; + struct uri request_host; + size_t request_uri_len; + size_t request_host_len; + size_t content_len; + char *request_uri_string; + char *request_host_string; + void *content_data; int rc; - /* Redirect to new location */ - DBGC ( http, "HTTP %p redirecting to %s\n", http, value ); - if ( ( rc = xfer_redirect ( &http->xfer, LOCATION_URI_STRING, - value ) ) != 0 ) { - DBGC ( http, "HTTP %p could not redirect: %s\n", + /* Calculate request URI length */ + memset ( &request_uri, 0, sizeof ( request_uri ) ); + request_uri.path = ( uri->path ? uri->path : "/" ); + request_uri.query = uri->query; + request_uri_len = + ( format_uri ( &request_uri, NULL, 0 ) + 1 /* NUL */); + + /* Calculate host name length */ + memset ( &request_host, 0, sizeof ( request_host ) ); + request_host.host = uri->host; + request_host.port = uri->port; + request_host_len = + ( format_uri ( &request_host, NULL, 0 ) + 1 /* NUL */ ); + + /* Calculate request content length */ + content_len = ( content ? content->len : 0 ); + + /* Allocate and initialise structure */ + http = zalloc ( sizeof ( *http ) + request_uri_len + request_host_len + + content_len ); + if ( ! http ) { + rc = -ENOMEM; + goto err_alloc; + } + request_uri_string = ( ( ( void * ) http ) + sizeof ( *http ) ); + request_host_string = ( request_uri_string + request_uri_len ); + content_data = ( request_host_string + request_host_len ); + format_uri ( &request_uri, request_uri_string, request_uri_len ); + format_uri ( &request_host, request_host_string, request_host_len ); + ref_init ( &http->refcnt, http_free ); + intf_init ( &http->xfer, &http_xfer_desc, &http->refcnt ); + intf_init ( &http->content, &http_content_desc, &http->refcnt ); + intf_init ( &http->transfer, &http_transfer_desc, &http->refcnt ); + intf_init ( &http->conn, &http_conn_desc, &http->refcnt ); + intf_plug_plug ( &http->transfer, &http->content ); + process_init ( &http->process, &http_process_desc, &http->refcnt ); + timer_init ( &http->timer, http_expired, &http->refcnt ); + http->uri = uri_get ( uri ); + http->request.method = method; + http->request.uri = request_uri_string; + http->request.host = request_host_string; + if ( range ) { + memcpy ( &http->request.range, range, + sizeof ( http->request.range ) ); + } + if ( content ) { + http->request.content.type = content->type; + http->request.content.data = content_data; + http->request.content.len = content_len; + memcpy ( content_data, content->data, content_len ); + } + http->state = &http_request; + DBGC2 ( http, "HTTP %p %s://%s%s\n", http, http->uri->scheme, + http->request.host, http->request.uri ); + + /* Open connection */ + if ( ( rc = http_connect ( &http->conn, uri ) ) != 0 ) { + DBGC ( http, "HTTP %p could not connect: %s\n", http, strerror ( rc ) ); - return rc; + goto err_connect; } + /* Attach to parent interface, mortalise self, and return */ + intf_plug_plug ( &http->xfer, xfer ); + ref_put ( &http->refcnt ); return 0; + + err_connect: + http_close ( http, rc ); + ref_put ( &http->refcnt ); + err_alloc: + return rc; } /** - * Handle HTTP Content-Length header + * Handle successful transfer completion * - * @v http HTTP request - * @v value HTTP header value + * @v http HTTP transaction * @ret rc Return status code */ -static int http_rx_content_length ( struct http_request *http, char *value ) { - struct block_device_capacity capacity; - size_t content_len; - char *endp; +static int http_transfer_complete ( struct http_transaction *http ) { + struct http_authentication *auth; + const char *location; + int rc; - /* Parse content length */ - content_len = strtoul ( value, &endp, 10 ); - if ( ! ( ( *endp == '\0' ) || isspace ( *endp ) ) ) { - DBGC ( http, "HTTP %p invalid Content-Length \"%s\"\n", - http, value ); - return -EINVAL_CONTENT_LENGTH; + /* Keep connection alive if applicable */ + if ( http->response.flags & HTTP_RESPONSE_KEEPALIVE ) + pool_recycle ( &http->conn ); + + /* Restart server connection interface */ + intf_restart ( &http->conn, 0 ); + + /* No more data is expected */ + http->state = NULL; + + /* If transaction is successful, then close the + * transfer-decoded interface. The content encoding may + * choose whether or not to immediately terminate the + * transaction. + */ + if ( http->response.rc == 0 ) { + intf_shutdown ( &http->transfer, 0 ); + return 0; } - /* If we already have an expected content length, and this - * isn't it, then complain + /* Perform redirection, if applicable */ + if ( ( location = http->response.location ) ) { + DBGC2 ( http, "HTTP %p redirecting to \"%s\"\n", + http, location ); + if ( ( rc = xfer_redirect ( &http->xfer, LOCATION_URI_STRING, + location ) ) != 0 ) { + DBGC ( http, "HTTP %p could not redirect: %s\n", + http, strerror ( rc ) ); + return rc; + } + http_close ( http, 0 ); + return 0; + } + + /* Fail unless a retry is permitted */ + if ( ! ( http->response.flags & HTTP_RESPONSE_RETRY ) ) + return http->response.rc; + + /* Perform authentication, if applicable */ + if ( ( auth = http->response.auth.auth ) ) { + http->request.auth.auth = auth; + DBGC2 ( http, "HTTP %p performing %s authentication\n", + http, auth->name ); + if ( ( rc = auth->authenticate ( http ) ) != 0 ) { + DBGC ( http, "HTTP %p could not authenticate: %s\n", + http, strerror ( rc ) ); + return rc; + } + } + + /* Restart content decoding interfaces (which may be attached + * to the same object). */ - if ( http->remaining && ( http->remaining != content_len ) ) { - DBGC ( http, "HTTP %p incorrect Content-Length %zd (expected " - "%zd)\n", http, content_len, http->remaining ); - return -EIO_CONTENT_LENGTH; + intf_nullify ( &http->content ); + intf_nullify ( &http->transfer ); + intf_restart ( &http->content, http->response.rc ); + intf_restart ( &http->transfer, http->response.rc ); + http->content.desc = &http_content_desc; + http->transfer.desc = &http_transfer_desc; + intf_plug_plug ( &http->transfer, &http->content ); + http->len = 0; + assert ( http->remaining == 0 ); + + /* Start timer to initiate retry */ + DBGC2 ( http, "HTTP %p retrying after %d seconds\n", + http, http->response.retry_after ); + start_timer_fixed ( &http->timer, + ( http->response.retry_after * TICKS_PER_SEC ) ); + return 0; +} + +/****************************************************************************** + * + * Requests + * + ****************************************************************************** + */ + +/** + * Construct HTTP request headers + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length, or negative error + */ +static int http_format_headers ( struct http_transaction *http, char *buf, + size_t len ) { + struct http_request_header *header; + size_t used; + size_t remaining; + char *line; + int value_len; + int rc; + + /* Construct request line */ + used = ssnprintf ( buf, len, "%s %s HTTP/1.1", + http->request.method->name, http->request.uri ); + if ( used < len ) + DBGC2 ( http, "HTTP %p TX %s\n", http, buf ); + used += ssnprintf ( ( buf + used ), ( len - used ), "\r\n" ); + + /* Construct all headers */ + for_each_table_entry ( header, HTTP_REQUEST_HEADERS ) { + + /* Determine header value length */ + value_len = header->format ( http, NULL, 0 ); + if ( value_len < 0 ) { + rc = value_len; + return rc; + } + + /* Skip zero-length headers */ + if ( ! value_len ) + continue; + + /* Construct header */ + line = ( buf + used ); + used += ssnprintf ( ( buf + used ), ( len - used ), "%s: ", + header->name ); + remaining = ( ( used < len ) ? ( len - used ) : 0 ); + used += header->format ( http, ( buf + used ), remaining ); + if ( used < len ) + DBGC2 ( http, "HTTP %p TX %s\n", http, line ); + used += ssnprintf ( ( buf + used ), ( len - used ), "\r\n" ); } - if ( ! ( http->flags & HTTP_HEAD_ONLY ) ) - http->remaining = content_len; - /* Do nothing more if we are retrying the request */ - if ( http->flags & HTTP_TRY_AGAIN ) + /* Construct terminating newline */ + used += ssnprintf ( ( buf + used ), ( len - used ), "\r\n" ); + + return used; +} + +/** + * Construct HTTP "Host" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_host ( struct http_transaction *http, char *buf, + size_t len ) { + + /* Construct host URI */ + return snprintf ( buf, len, "%s", http->request.host ); +} + +/** HTTP "Host" header "*/ +struct http_request_header http_request_host __http_request_header = { + .name = "Host", + .format = http_format_host, +}; + +/** + * Construct HTTP "User-Agent" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_user_agent ( struct http_transaction *http __unused, + char *buf, size_t len ) { + + /* Construct user agent */ + return snprintf ( buf, len, "iPXE/%s", product_version ); +} + +/** HTTP "User-Agent" header */ +struct http_request_header http_request_user_agent __http_request_header = { + .name = "User-Agent", + .format = http_format_user_agent, +}; + +/** + * Construct HTTP "Connection" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_connection ( struct http_transaction *http __unused, + char *buf, size_t len ) { + + /* Always request keep-alive */ + return snprintf ( buf, len, "keep-alive" ); +} + +/** HTTP "Connection" header */ +struct http_request_header http_request_connection __http_request_header = { + .name = "Connection", + .format = http_format_connection, +}; + +/** + * Construct HTTP "Range" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_range ( struct http_transaction *http, + char *buf, size_t len ) { + + /* Construct range, if applicable */ + if ( http->request.range.len ) { + return snprintf ( buf, len, "bytes=%zd-%zd", + http->request.range.start, + ( http->request.range.start + + http->request.range.len - 1 ) ); + } else { return 0; + } +} + +/** HTTP "Range" header */ +struct http_request_header http_request_range __http_request_header = { + .name = "Range", + .format = http_format_range, +}; - /* Use seek() to notify recipient of filesize */ - xfer_seek ( &http->xfer, http->remaining ); - xfer_seek ( &http->xfer, 0 ); +/** + * Construct HTTP "Content-Type" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_content_type ( struct http_transaction *http, + char *buf, size_t len ) { - /* Report block device capacity if applicable */ - if ( http->flags & HTTP_HEAD_ONLY ) { - capacity.blocks = ( content_len / HTTP_BLKSIZE ); - capacity.blksize = HTTP_BLKSIZE; - capacity.max_count = -1U; - block_capacity ( &http->partial, &capacity ); + /* Construct content type, if applicable */ + if ( http->request.content.type ) { + return snprintf ( buf, len, "%s", http->request.content.type ); + } else { + return 0; } - return 0; } +/** HTTP "Content-Type" header */ +struct http_request_header http_request_content_type __http_request_header = { + .name = "Content-Type", + .format = http_format_content_type, +}; + /** - * Handle HTTP Transfer-Encoding header + * Construct HTTP "Content-Length" header * - * @v http HTTP request - * @v value HTTP header value - * @ret rc Return status code + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error */ -static int http_rx_transfer_encoding ( struct http_request *http, char *value ){ +static int http_format_content_length ( struct http_transaction *http, + char *buf, size_t len ) { - if ( strcasecmp ( value, "chunked" ) == 0 ) { - /* Mark connection as using chunked transfer encoding */ - http->chunked = 1; + /* Construct content length, if applicable */ + if ( http->request.content.len ) { + return snprintf ( buf, len, "%zd", http->request.content.len ); + } else { + return 0; } +} - return 0; +/** HTTP "Content-Length" header */ +struct http_request_header http_request_content_length __http_request_header = { + .name = "Content-Length", + .format = http_format_content_length, +}; + +/** + * Construct HTTP "Accept-Encoding" header + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_accept_encoding ( struct http_transaction *http, + char *buf, size_t len ) { + struct http_content_encoding *encoding; + const char *sep = ""; + size_t used = 0; + + /* Construct list of content encodings */ + for_each_table_entry ( encoding, HTTP_CONTENT_ENCODINGS ) { + if ( encoding->supported && ( ! encoding->supported ( http ) ) ) + continue; + used += ssnprintf ( ( buf + used ), ( len - used ), + "%s%s", sep, encoding->name ); + sep = ", "; + } + + return used; } +/** HTTP "Accept-Encoding" header */ +struct http_request_header http_request_accept_encoding __http_request_header ={ + .name = "Accept-Encoding", + .format = http_format_accept_encoding, +}; + /** - * Handle HTTP Connection header + * Transmit request * - * @v http HTTP request - * @v value HTTP header value + * @v http HTTP transaction * @ret rc Return status code */ -static int http_rx_connection ( struct http_request *http, char *value ) { +static int http_tx_request ( struct http_transaction *http ) { + struct io_buffer *iobuf; + int len; + int check_len; + int rc; + + /* Calculate request length */ + len = http_format_headers ( http, NULL, 0 ); + if ( len < 0 ) { + rc = len; + DBGC ( http, "HTTP %p could not construct request: %s\n", + http, strerror ( rc ) ); + goto err_len; + } + + /* Allocate I/O buffer */ + iobuf = alloc_iob ( len + 1 /* NUL */ + http->request.content.len ); + if ( ! iobuf ) { + rc = -ENOMEM; + goto err_alloc; + } - if ( strcasecmp ( value, "keep-alive" ) == 0 ) { - /* Mark connection as being kept alive by the server */ - http->flags |= HTTP_SERVER_KEEPALIVE; + /* Construct request */ + check_len = http_format_headers ( http, iob_put ( iobuf, len ), + ( len + 1 /* NUL */ ) ); + assert ( check_len == len ); + memcpy ( iob_put ( iobuf, http->request.content.len ), + http->request.content.data, http->request.content.len ); + + /* Deliver request */ + if ( ( rc = xfer_deliver_iob ( &http->conn, + iob_disown ( iobuf ) ) ) != 0 ) { + DBGC ( http, "HTTP %p could not deliver request: %s\n", + http, strerror ( rc ) ); + goto err_deliver; } + /* Clear any previous response */ + empty_line_buffer ( &http->response.headers ); + memset ( &http->response, 0, sizeof ( http->response ) ); + + /* Move to response headers state */ + http->state = &http_headers; + return 0; + + err_deliver: + free_iob ( iobuf ); + err_alloc: + err_len: + return rc; } +/** HTTP request state */ +static struct http_state http_request = { + .tx = http_tx_request, + .close = http_close_error, +}; + +/****************************************************************************** + * + * Response headers + * + ****************************************************************************** + */ + /** - * Handle WWW-Authenticate Basic header + * Parse HTTP status line * - * @v http HTTP request - * @v params Parameters + * @v http HTTP transaction + * @v line Status line * @ret rc Return status code */ -static int http_rx_basic_auth ( struct http_request *http, char *params ) { +static int http_parse_status ( struct http_transaction *http, char *line ) { + char *endp; + char *version; + char *vernum; + char *status; + int response_rc; + + DBGC2 ( http, "HTTP %p RX %s\n", http, line ); + + /* Parse HTTP version */ + version = http_token ( &line, NULL ); + if ( ( ! version ) || ( strncmp ( version, "HTTP/", 5 ) != 0 ) ) { + DBGC ( http, "HTTP %p malformed version \"%s\"\n", http, line ); + return -EINVAL_STATUS; + } - DBGC ( http, "HTTP %p Basic authentication required (%s)\n", - http, params ); + /* Keepalive is enabled by default for anything newer than HTTP/1.0 */ + vernum = ( version + 5 /* "HTTP/" (presence already checked) */ ); + if ( vernum[0] == '0' ) { + /* HTTP/0.x : keepalive not enabled by default */ + } else if ( strncmp ( vernum, "1.0", 3 ) == 0 ) { + /* HTTP/1.0 : keepalive not enabled by default */ + } else { + /* HTTP/1.1 or newer: keepalive enabled by default */ + http->response.flags |= HTTP_RESPONSE_KEEPALIVE; + } - /* If we received a 401 Unauthorized response, then retry - * using Basic authentication - */ - if ( ( http->code == 401 ) && - ( ! ( http->flags & HTTP_BASIC_AUTH ) ) && - ( http->uri->user != NULL ) ) { - http->flags |= ( HTTP_TRY_AGAIN | HTTP_BASIC_AUTH ); + /* Parse status code */ + status = line; + http->response.status = strtoul ( status, &endp, 10 ); + if ( *endp != ' ' ) { + DBGC ( http, "HTTP %p malformed status code \"%s\"\n", + http, status ); + return -EINVAL_STATUS; + } + + /* Convert HTTP status code to iPXE return status code */ + if ( status[0] == '2' ) { + /* 2xx Success */ + response_rc = 0; + } else if ( status[0] == '3' ) { + /* 3xx Redirection */ + response_rc = -EXDEV; + } else if ( http->response.status == 401 ) { + /* 401 Unauthorized */ + response_rc = -EACCES_401; + } else if ( http->response.status == 403 ) { + /* 403 Forbidden */ + response_rc = -EPERM_403; + } else if ( http->response.status == 404 ) { + /* 404 Not Found */ + response_rc = -ENOENT_404; + } else if ( status[0] == '4' ) { + /* 4xx Client Error (not already specified) */ + response_rc = -EIO_4XX; + } else if ( status[0] == '5' ) { + /* 5xx Server Error */ + response_rc = -EIO_5XX; + } else { + /* Unrecognised */ + response_rc = -EIO_OTHER; } + http->response.rc = response_rc; return 0; } /** - * Parse Digest authentication parameter + * Parse HTTP header * - * @v params Parameters - * @v name Parameter name (including trailing "=\"") - * @ret value Parameter value, or NULL + * @v http HTTP transaction + * @v line Header line + * @ret rc Return status code */ -static char * http_digest_param ( char *params, const char *name ) { - char *key; - char *value; - char *terminator; - - /* Locate parameter */ - key = strstr ( params, name ); - if ( ! key ) - return NULL; +static int http_parse_header ( struct http_transaction *http, char *line ) { + struct http_response_header *header; + char *name = line; + char *sep; - /* Extract value */ - value = ( key + strlen ( name ) ); - terminator = strchr ( value, '"' ); - if ( ! terminator ) - return NULL; - return strndup ( value, ( terminator - value ) ); + DBGC2 ( http, "HTTP %p RX %s\n", http, line ); + + /* Extract header name */ + sep = strstr ( line, ": " ); + if ( ! sep ) { + DBGC ( http, "HTTP %p malformed header \"%s\"\n", http, line ); + return -EINVAL_HEADER; + } + *sep = '\0'; + line = ( sep + 2 /* ": " */ ); + + /* Process header, if recognised */ + for_each_table_entry ( header, HTTP_RESPONSE_HEADERS ) { + if ( strcasecmp ( name, header->name ) == 0 ) + return header->parse ( http, line ); + } + + /* Unrecognised headers should be ignored */ + return 0; } /** - * Handle WWW-Authenticate Digest header + * Parse HTTP response headers * - * @v http HTTP request - * @v params Parameters + * @v http HTTP transaction * @ret rc Return status code */ -static int http_rx_digest_auth ( struct http_request *http, char *params ) { +static int http_parse_headers ( struct http_transaction *http ) { + char *line; + char *next; + int rc; - DBGC ( http, "HTTP %p Digest authentication required (%s)\n", - http, params ); + /* Get status line */ + line = http->response.headers.data; + assert ( line != NULL ); + next = ( line + strlen ( line ) + 1 /* NUL */ ); - /* If we received a 401 Unauthorized response, then retry - * using Digest authentication - */ - if ( ( http->code == 401 ) && - ( ! ( http->flags & HTTP_DIGEST_AUTH ) ) && - ( http->uri->user != NULL ) ) { - - /* Extract realm */ - free ( http->auth_realm ); - http->auth_realm = http_digest_param ( params, "realm=\"" ); - if ( ! http->auth_realm ) { - DBGC ( http, "HTTP %p Digest prompt missing realm\n", - http ); - return -EINVAL_HEADER; - } + /* Parse status line */ + if ( ( rc = http_parse_status ( http, line ) ) != 0 ) + return rc; - /* Extract nonce */ - free ( http->auth_nonce ); - http->auth_nonce = http_digest_param ( params, "nonce=\"" ); - if ( ! http->auth_nonce ) { - DBGC ( http, "HTTP %p Digest prompt missing nonce\n", - http ); - return -EINVAL_HEADER; - } + /* Process header lines */ + while ( 1 ) { - /* Extract opaque */ - free ( http->auth_opaque ); - http->auth_opaque = http_digest_param ( params, "opaque=\"" ); - if ( ! http->auth_opaque ) { - /* Not an error; "opaque" is optional */ - } + /* Move to next line */ + line = next; + next = ( line + strlen ( line ) + 1 /* NUL */ ); + + /* Stop on terminating blank line */ + if ( ! line[0] ) + return 0; - http->flags |= ( HTTP_TRY_AGAIN | HTTP_DIGEST_AUTH ); + /* Process header line */ + if ( ( rc = http_parse_header ( http, line ) ) != 0 ) + return rc; } +} +/** + * Parse HTTP "Location" header + * + * @v http HTTP transaction + * @v line Remaining header line + * @ret rc Return status code + */ +static int http_parse_location ( struct http_transaction *http, char *line ) { + + /* Store location */ + http->response.location = line; return 0; } -/** An HTTP WWW-Authenticate header handler */ -struct http_auth_header_handler { - /** Scheme (e.g. "Basic") */ - const char *scheme; - /** Handle received parameters - * - * @v http HTTP request - * @v params Parameters - * @ret rc Return status code - */ - int ( * rx ) ( struct http_request *http, char *params ); +/** HTTP "Location" header */ +struct http_response_header http_response_location __http_response_header = { + .name = "Location", + .parse = http_parse_location, }; -/** List of HTTP WWW-Authenticate header handlers */ -static struct http_auth_header_handler http_auth_header_handlers[] = { - { - .scheme = "Basic", - .rx = http_rx_basic_auth, - }, - { - .scheme = "Digest", - .rx = http_rx_digest_auth, - }, - { NULL, NULL }, +/** + * Parse HTTP "Transfer-Encoding" header + * + * @v http HTTP transaction + * @v line Remaining header line + * @ret rc Return status code + */ +static int http_parse_transfer_encoding ( struct http_transaction *http, + char *line ) { + struct http_transfer_encoding *encoding; + + /* Check for known transfer encodings */ + for_each_table_entry ( encoding, HTTP_TRANSFER_ENCODINGS ) { + if ( strcasecmp ( line, encoding->name ) == 0 ) { + http->response.transfer.encoding = encoding; + return 0; + } + } + + DBGC ( http, "HTTP %p unrecognised Transfer-Encoding \"%s\"\n", + http, line ); + return -ENOTSUP_TRANSFER; +} + +/** HTTP "Transfer-Encoding" header */ +struct http_response_header +http_response_transfer_encoding __http_response_header = { + .name = "Transfer-Encoding", + .parse = http_parse_transfer_encoding, }; /** - * Handle HTTP WWW-Authenticate header + * Parse HTTP "Connection" header * - * @v http HTTP request - * @v value HTTP header value + * @v http HTTP transaction + * @v line Remaining header line * @ret rc Return status code */ -static int http_rx_www_authenticate ( struct http_request *http, char *value ) { - struct http_auth_header_handler *handler; - char *separator; - char *scheme; - char *params; - int rc; +static int http_parse_connection ( struct http_transaction *http, char *line ) { - /* Extract scheme */ - separator = strchr ( value, ' ' ); - if ( ! separator ) { - DBGC ( http, "HTTP %p malformed WWW-Authenticate header\n", - http ); - return -EINVAL_HEADER; + /* Check for known connection intentions */ + if ( strcasecmp ( line, "keep-alive" ) == 0 ) { + http->response.flags |= HTTP_RESPONSE_KEEPALIVE; + return 0; } - *separator = '\0'; - scheme = value; - params = ( separator + 1 ); - - /* Hand off to header handler, if one exists */ - for ( handler = http_auth_header_handlers; handler->scheme; handler++ ){ - if ( strcasecmp ( scheme, handler->scheme ) == 0 ) { - if ( ( rc = handler->rx ( http, params ) ) != 0 ) - return rc; - break; - } + if ( strcasecmp ( line, "close" ) == 0 ) { + http->response.flags &= ~HTTP_RESPONSE_KEEPALIVE; + return 0; } - return 0; + + DBGC ( http, "HTTP %p unrecognised Connection \"%s\"\n", http, line ); + return -ENOTSUP_CONNECTION; } +/** HTTP "Connection" header */ +struct http_response_header http_response_connection __http_response_header = { + .name = "Connection", + .parse = http_parse_connection, +}; + /** - * Handle HTTP Retry-After header + * Parse HTTP "Content-Length" header * - * @v http HTTP request - * @v value HTTP header value + * @v http HTTP transaction + * @v line Remaining header line * @ret rc Return status code */ -static int http_rx_retry_after ( struct http_request *http, char *value ) { - unsigned long seconds; +static int http_parse_content_length ( struct http_transaction *http, + char *line ) { char *endp; - DBGC ( http, "HTTP %p retry requested (%s)\n", http, value ); + /* Parse length */ + http->response.content.len = strtoul ( line, &endp, 10 ); + if ( *endp != '\0' ) { + DBGC ( http, "HTTP %p invalid Content-Length \"%s\"\n", + http, line ); + return -EINVAL_CONTENT_LENGTH; + } - /* If we received a 503 Service Unavailable response, then - * retry after the specified number of seconds. If the value - * is not a simple number of seconds (e.g. a full HTTP date), - * then retry after a fixed delay, since we don't have code - * able to parse full HTTP dates. - */ - if ( http->code == 503 ) { - seconds = strtoul ( value, &endp, 10 ); - if ( *endp != '\0' ) { - seconds = HTTP_RETRY_SECONDS; - DBGC ( http, "HTTP %p cannot understand \"%s\"; " - "using %ld seconds\n", http, value, seconds ); + /* Record that we have a content length (since it may be zero) */ + http->response.flags |= HTTP_RESPONSE_CONTENT_LEN; + + return 0; +} + +/** HTTP "Content-Length" header */ +struct http_response_header +http_response_content_length __http_response_header = { + .name = "Content-Length", + .parse = http_parse_content_length, +}; + +/** + * Parse HTTP "Content-Encoding" header + * + * @v http HTTP transaction + * @v line Remaining header line + * @ret rc Return status code + */ +static int http_parse_content_encoding ( struct http_transaction *http, + char *line ) { + struct http_content_encoding *encoding; + + /* Check for known content encodings */ + for_each_table_entry ( encoding, HTTP_CONTENT_ENCODINGS ) { + if ( encoding->supported && ( ! encoding->supported ( http ) ) ) + continue; + if ( strcasecmp ( line, encoding->name ) == 0 ) { + http->response.content.encoding = encoding; + return 0; } - http->flags |= HTTP_TRY_AGAIN; - http->retry_delay = ( seconds * TICKS_PER_SEC ); } + /* Some servers (e.g. Apache) have a habit of specifying + * unwarranted content encodings. For example, if Apache + * detects (via /etc/httpd/conf/magic) that a file's contents + * are gzip-compressed, it will set "Content-Encoding: x-gzip" + * regardless of the client's Accept-Encoding header. The + * only viable way to handle such servers is to treat unknown + * content encodings as equivalent to "identity". + */ + DBGC ( http, "HTTP %p unrecognised Content-Encoding \"%s\"\n", + http, line ); return 0; } -/** An HTTP header handler */ -struct http_header_handler { - /** Name (e.g. "Content-Length") */ - const char *header; - /** Handle received header - * - * @v http HTTP request - * @v value HTTP header value - * @ret rc Return status code - * - * If an error is returned, the download will be aborted. - */ - int ( * rx ) ( struct http_request *http, char *value ); +/** HTTP "Content-Encoding" header */ +struct http_response_header +http_response_content_encoding __http_response_header = { + .name = "Content-Encoding", + .parse = http_parse_content_encoding, }; -/** List of HTTP header handlers */ -static struct http_header_handler http_header_handlers[] = { - { - .header = "Location", - .rx = http_rx_location, - }, - { - .header = "Content-Length", - .rx = http_rx_content_length, - }, - { - .header = "Transfer-Encoding", - .rx = http_rx_transfer_encoding, - }, - { - .header = "Connection", - .rx = http_rx_connection, - }, - { - .header = "WWW-Authenticate", - .rx = http_rx_www_authenticate, - }, - { - .header = "Retry-After", - .rx = http_rx_retry_after, - }, - { NULL, NULL } +/** + * Parse HTTP "Retry-After" header + * + * @v http HTTP transaction + * @v line Remaining header line + * @ret rc Return status code + */ +static int http_parse_retry_after ( struct http_transaction *http, + char *line ) { + char *endp; + + /* Try to parse value as a simple number of seconds */ + http->response.retry_after = strtoul ( line, &endp, 10 ); + if ( *endp != '\0' ) { + /* For any value which is not a simple number of + * seconds (e.g. a full HTTP date), just retry after a + * fixed delay, since we don't have code able to parse + * full HTTP dates. + */ + http->response.retry_after = HTTP_RETRY_SECONDS; + DBGC ( http, "HTTP %p cannot understand Retry-After \"%s\"; " + "using %d seconds\n", http, line, HTTP_RETRY_SECONDS ); + } + + /* Allow HTTP request to be retried after specified delay */ + http->response.flags |= HTTP_RESPONSE_RETRY; + + return 0; +} + +/** HTTP "Retry-After" header */ +struct http_response_header http_response_retry_after __http_response_header = { + .name = "Retry-After", + .parse = http_parse_retry_after, }; /** - * Handle HTTP header + * Handle received HTTP headers * - * @v http HTTP request - * @v header HTTP header + * @v http HTTP transaction + * @v iobuf I/O buffer (may be claimed) * @ret rc Return status code */ -static int http_rx_header ( struct http_request *http, char *header ) { - struct http_header_handler *handler; - char *separator; - char *value; +static int http_rx_headers ( struct http_transaction *http, + struct io_buffer **iobuf ) { + struct http_transfer_encoding *transfer; + struct http_content_encoding *content; + char *line; int rc; - /* An empty header line marks the end of this phase */ - if ( ! header[0] ) { - empty_line_buffer ( &http->linebuf ); + /* Buffer header line */ + if ( ( rc = http_rx_linebuf ( http, *iobuf, + &http->response.headers ) ) != 0 ) + return rc; - /* Handle response code */ - if ( ! ( http->flags & HTTP_TRY_AGAIN ) ) { - if ( ( rc = http_response_to_rc ( http->code ) ) != 0 ) - return rc; - } + /* Wait until we see the empty line marking end of headers */ + line = buffered_line ( &http->response.headers ); + if ( ( line == NULL ) || ( line[0] != '\0' ) ) + return 0; - /* Move to next state */ - if ( http->rx_state == HTTP_RX_HEADER ) { - DBGC ( http, "HTTP %p start of data\n", http ); - http->rx_state = ( http->chunked ? - HTTP_RX_CHUNK_LEN : HTTP_RX_DATA ); - if ( ( http->partial_len != 0 ) && - ( ! ( http->flags & HTTP_TRY_AGAIN ) ) ) { - http->remaining = http->partial_len; - } - return 0; - } else { - DBGC ( http, "HTTP %p end of trailer\n", http ); - http_done ( http ); - return 0; - } + /* Process headers */ + if ( ( rc = http_parse_headers ( http ) ) != 0 ) + return rc; + + /* Initialise content encoding, if applicable */ + if ( ( content = http->response.content.encoding ) && + ( ( rc = content->init ( http ) ) != 0 ) ) { + DBGC ( http, "HTTP %p could not initialise %s content " + "encoding: %s\n", http, content->name, strerror ( rc ) ); + return rc; } - DBGC ( http, "HTTP %p header \"%s\"\n", http, header ); + /* Presize receive buffer, if we have a content length */ + if ( http->response.content.len ) { + xfer_seek ( &http->transfer, http->response.content.len ); + xfer_seek ( &http->transfer, 0 ); + } - /* Split header at the ": " */ - separator = strstr ( header, ": " ); - if ( ! separator ) { - DBGC ( http, "HTTP %p malformed header\n", http ); - return -EINVAL_HEADER; + /* Complete transfer if this is a HEAD request */ + if ( http->request.method == &http_head ) { + if ( ( rc = http_transfer_complete ( http ) ) != 0 ) + return rc; + return 0; } - *separator = '\0'; - value = ( separator + 2 ); - - /* Hand off to header handler, if one exists */ - for ( handler = http_header_handlers ; handler->header ; handler++ ) { - if ( strcasecmp ( header, handler->header ) == 0 ) { - if ( ( rc = handler->rx ( http, value ) ) != 0 ) - return rc; - break; - } + + /* Default to identity transfer encoding, if none specified */ + if ( ! http->response.transfer.encoding ) + http->response.transfer.encoding = &http_transfer_identity; + + /* Move to transfer encoding-specific data state */ + transfer = http->response.transfer.encoding; + http->state = &transfer->state; + + /* Initialise transfer encoding */ + if ( ( rc = transfer->init ( http ) ) != 0 ) { + DBGC ( http, "HTTP %p could not initialise %s transfer " + "encoding: %s\n", http, transfer->name, strerror ( rc )); + return rc; } + return 0; } +/** HTTP response headers state */ +static struct http_state http_headers = { + .rx = http_rx_headers, + .close = http_close_error, +}; + +/****************************************************************************** + * + * Identity transfer encoding + * + ****************************************************************************** + */ + /** - * Handle HTTP chunk length + * Initialise transfer encoding * - * @v http HTTP request - * @v length HTTP chunk length + * @v http HTTP transaction * @ret rc Return status code */ -static int http_rx_chunk_len ( struct http_request *http, char *length ) { - char *endp; +static int http_init_transfer_identity ( struct http_transaction *http ) { + int rc; - /* Skip blank lines between chunks */ - if ( length[0] == '\0' ) - return 0; + /* Complete transfer immediately if we have a zero content length */ + if ( ( http->response.flags & HTTP_RESPONSE_CONTENT_LEN ) && + ( http->response.content.len == 0 ) && + ( ( rc = http_transfer_complete ( http ) ) != 0 ) ) + return rc; - /* Parse chunk length */ - http->chunk_remaining = strtoul ( length, &endp, 16 ); - if ( *endp != '\0' ) { - DBGC ( http, "HTTP %p invalid chunk length \"%s\"\n", - http, length ); - return -EINVAL_CHUNK_LENGTH; - } + return 0; +} - /* Terminate chunked encoding if applicable */ - if ( http->chunk_remaining == 0 ) { - DBGC ( http, "HTTP %p end of chunks\n", http ); - http->chunked = 0; - http->rx_state = HTTP_RX_TRAILER; - return 0; - } +/** + * Handle received data + * + * @v http HTTP transaction + * @v iobuf I/O buffer (may be claimed) + * @ret rc Return status code + */ +static int http_rx_transfer_identity ( struct http_transaction *http, + struct io_buffer **iobuf ) { + size_t len = iob_len ( *iobuf ); + int rc; + + /* Update lengths */ + http->len += len; - /* Use seek() to notify recipient of new filesize */ - DBGC ( http, "HTTP %p start of chunk of length %zd\n", - http, http->chunk_remaining ); - if ( ! ( http->flags & HTTP_TRY_AGAIN ) ) { - xfer_seek ( &http->xfer, - ( http->rx_len + http->chunk_remaining ) ); - xfer_seek ( &http->xfer, http->rx_len ); + /* Fail if this transfer would overrun the expected content + * length (if any). + */ + if ( ( http->response.flags & HTTP_RESPONSE_CONTENT_LEN ) && + ( http->len > http->response.content.len ) ) { + DBGC ( http, "HTTP %p content length overrun\n", http ); + return -EIO_CONTENT_LENGTH; } - /* Start receiving data */ - http->rx_state = HTTP_RX_DATA; + /* Hand off to content encoding */ + if ( ( rc = xfer_deliver_iob ( &http->transfer, + iob_disown ( *iobuf ) ) ) != 0 ) + return rc; + + /* Complete transfer if we have received the expected content + * length (if any). + */ + if ( ( http->response.flags & HTTP_RESPONSE_CONTENT_LEN ) && + ( http->len == http->response.content.len ) && + ( ( rc = http_transfer_complete ( http ) ) != 0 ) ) + return rc; return 0; } -/** An HTTP line-based data handler */ -struct http_line_handler { - /** Handle line - * - * @v http HTTP request - * @v line Line to handle - * @ret rc Return status code +/** + * Handle server connection close + * + * @v http HTTP transaction + * @v rc Reason for close + */ +static void http_close_transfer_identity ( struct http_transaction *http, + int rc ) { + + /* Fail if any error occurred */ + if ( rc != 0 ) + goto err; + + /* Fail if we have a content length (since we would have + * already closed the connection if we had received the + * correct content length). */ - int ( * rx ) ( struct http_request *http, char *line ); -}; + if ( http->response.flags & HTTP_RESPONSE_CONTENT_LEN ) { + DBGC ( http, "HTTP %p content length underrun\n", http ); + rc = EIO_CONTENT_LENGTH; + goto err; + } + + /* Indicate that transfer is complete */ + if ( ( rc = http_transfer_complete ( http ) ) != 0 ) + goto err; -/** List of HTTP line-based data handlers */ -static struct http_line_handler http_line_handlers[] = { - [HTTP_RX_RESPONSE] = { .rx = http_rx_response }, - [HTTP_RX_HEADER] = { .rx = http_rx_header }, - [HTTP_RX_CHUNK_LEN] = { .rx = http_rx_chunk_len }, - [HTTP_RX_TRAILER] = { .rx = http_rx_header }, + return; + + err: + http_close ( http, rc ); +} + +/** Identity transfer encoding */ +static struct http_transfer_encoding http_transfer_identity = { + .name = "identity", + .init = http_init_transfer_identity, + .state = { + .rx = http_rx_transfer_identity, + .close = http_close_transfer_identity, + }, }; +/****************************************************************************** + * + * Chunked transfer encoding + * + ****************************************************************************** + */ + /** - * Handle new data arriving via HTTP connection + * Initialise transfer encoding * - * @v http HTTP request - * @v iobuf I/O buffer - * @v meta Data transfer metadata + * @v http HTTP transaction * @ret rc Return status code */ -static int http_socket_deliver ( struct http_request *http, - struct io_buffer *iobuf, - struct xfer_metadata *meta __unused ) { - struct http_line_handler *lh; +static int http_init_transfer_chunked ( struct http_transaction *http ) { + + /* Sanity checks */ + assert ( http->remaining == 0 ); + assert ( http->linebuf.len == 0 ); + + return 0; +} + +/** + * Handle received chunk length + * + * @v http HTTP transaction + * @v iobuf I/O buffer (may be claimed) + * @ret rc Return status code + */ +static int http_rx_chunk_len ( struct http_transaction *http, + struct io_buffer **iobuf ) { char *line; - size_t data_len; - ssize_t line_len; - int rc = 0; + char *endp; + size_t len; + int rc; - profile_start ( &http_rx_profiler ); - while ( iobuf && iob_len ( iobuf ) ) { + /* Receive into temporary line buffer */ + if ( ( rc = http_rx_linebuf ( http, *iobuf, &http->linebuf ) ) != 0 ) + return rc; - switch ( http->rx_state ) { - case HTTP_RX_IDLE: - /* Receiving any data in this state is an error */ - DBGC ( http, "HTTP %p received %zd bytes while %s\n", - http, iob_len ( iobuf ), - ( ( http->rx_state == HTTP_RX_IDLE ) ? - "idle" : "dead" ) ); - rc = -EPROTO_UNSOLICITED; - goto done; - case HTTP_RX_DEAD: - /* Do no further processing */ - goto done; - case HTTP_RX_DATA: - /* Pass received data to caller */ - data_len = iob_len ( iobuf ); - if ( http->chunk_remaining && - ( http->chunk_remaining < data_len ) ) { - data_len = http->chunk_remaining; - } - if ( http->remaining && - ( http->remaining < data_len ) ) { - data_len = http->remaining; - } - if ( http->flags & HTTP_TRY_AGAIN ) { - /* Discard all received data */ - iob_pull ( iobuf, data_len ); - } else if ( http->rx_buffer != UNULL ) { - /* Copy to partial transfer buffer */ - copy_to_user ( http->rx_buffer, http->rx_len, - iobuf->data, data_len ); - iob_pull ( iobuf, data_len ); - } else if ( data_len < iob_len ( iobuf ) ) { - /* Deliver partial buffer as raw data */ - profile_start ( &http_xfer_profiler ); - rc = xfer_deliver_raw ( &http->xfer, - iobuf->data, data_len ); - iob_pull ( iobuf, data_len ); - if ( rc != 0 ) - goto done; - profile_stop ( &http_xfer_profiler ); - } else { - /* Deliver whole I/O buffer */ - profile_start ( &http_xfer_profiler ); - if ( ( rc = xfer_deliver_iob ( &http->xfer, - iob_disown ( iobuf ) ) ) != 0 ) - goto done; - profile_stop ( &http_xfer_profiler ); - } - http->rx_len += data_len; - if ( http->chunk_remaining ) { - http->chunk_remaining -= data_len; - if ( http->chunk_remaining == 0 ) - http->rx_state = HTTP_RX_CHUNK_LEN; - } - if ( http->remaining ) { - http->remaining -= data_len; - if ( ( http->remaining == 0 ) && - ( http->rx_state == HTTP_RX_DATA ) ) { - http_done ( http ); - } - } - break; - case HTTP_RX_RESPONSE: - case HTTP_RX_HEADER: - case HTTP_RX_CHUNK_LEN: - case HTTP_RX_TRAILER: - /* In the other phases, buffer and process a - * line at a time - */ - line_len = line_buffer ( &http->linebuf, iobuf->data, - iob_len ( iobuf ) ); - if ( line_len < 0 ) { - rc = line_len; - DBGC ( http, "HTTP %p could not buffer line: " - "%s\n", http, strerror ( rc ) ); - goto done; - } - iob_pull ( iobuf, line_len ); - line = buffered_line ( &http->linebuf ); - if ( line ) { - lh = &http_line_handlers[http->rx_state]; - if ( ( rc = lh->rx ( http, line ) ) != 0 ) - goto done; - } - break; - default: - assert ( 0 ); - break; - } + /* Wait until we receive a non-empty line */ + line = buffered_line ( &http->linebuf ); + if ( ( line == NULL ) || ( line[0] == '\0' ) ) + return 0; + + /* Parse chunk length */ + http->remaining = strtoul ( line, &endp, 16 ); + if ( *endp != '\0' ) { + DBGC ( http, "HTTP %p invalid chunk length \"%s\"\n", + http, line ); + return -EINVAL_CHUNK_LENGTH; } - done: - if ( rc ) - http_close ( http, rc ); - free_iob ( iobuf ); - profile_stop ( &http_rx_profiler ); - return rc; + /* Empty line buffer */ + empty_line_buffer ( &http->linebuf ); + + /* Update expected length */ + len = ( http->len + http->remaining ); + xfer_seek ( &http->transfer, len ); + xfer_seek ( &http->transfer, http->len ); + + /* If chunk length is zero, then move to response trailers state */ + if ( ! http->remaining ) + http->state = &http_trailers; + + return 0; } /** - * Check HTTP socket flow control window + * Handle received chunk data * - * @v http HTTP request - * @ret len Length of window + * @v http HTTP transaction + * @v iobuf I/O buffer (may be claimed) + * @ret rc Return status code */ -static size_t http_socket_window ( struct http_request *http __unused ) { +static int http_rx_chunk_data ( struct http_transaction *http, + struct io_buffer **iobuf ) { + struct io_buffer *payload; + uint8_t *crlf; + size_t len; + int rc; - /* Window is always open. This is to prevent TCP from - * stalling if our parent window is not currently open. + /* In the common case of a final chunk in a packet which also + * includes the terminating CRLF, strip the terminating CRLF + * (which we would ignore anyway) and hence avoid + * unnecessarily copying the data. */ - return ( ~( ( size_t ) 0 ) ); + if ( iob_len ( *iobuf ) == ( http->remaining + 2 /* CRLF */ ) ) { + crlf = ( (*iobuf)->data + http->remaining ); + if ( ( crlf[0] == '\r' ) && ( crlf[1] == '\n' ) ) + iob_unput ( (*iobuf), 2 /* CRLF */ ); + } + len = iob_len ( *iobuf ); + + /* Use whole/partial buffer as applicable */ + if ( len <= http->remaining ) { + + /* Whole buffer is to be consumed: decrease remaining + * length and use original I/O buffer as payload. + */ + payload = iob_disown ( *iobuf ); + http->len += len; + http->remaining -= len; + + } else { + + /* Partial buffer is to be consumed: copy data to a + * temporary I/O buffer. + */ + payload = alloc_iob ( http->remaining ); + if ( ! payload ) { + rc = -ENOMEM; + goto err; + } + memcpy ( iob_put ( payload, http->remaining ), (*iobuf)->data, + http->remaining ); + iob_pull ( *iobuf, http->remaining ); + http->len += http->remaining; + http->remaining = 0; + } + + /* Hand off to content encoding */ + if ( ( rc = xfer_deliver_iob ( &http->transfer, + iob_disown ( payload ) ) ) != 0 ) + goto err; + + return 0; + + err: + assert ( payload == NULL ); + return rc; } /** - * Close HTTP socket + * Handle received chunked data * - * @v http HTTP request - * @v rc Reason for close + * @v http HTTP transaction + * @v iobuf I/O buffer (may be claimed) + * @ret rc Return status code */ -static void http_socket_close ( struct http_request *http, int rc ) { +static int http_rx_transfer_chunked ( struct http_transaction *http, + struct io_buffer **iobuf ) { - /* If we have an error, terminate */ - if ( rc != 0 ) { - http_close ( http, rc ); - return; + /* Handle as chunk length or chunk data as appropriate */ + if ( http->remaining ) { + return http_rx_chunk_data ( http, iobuf ); + } else { + return http_rx_chunk_len ( http, iobuf ); } - - /* Mark HTTP request as complete */ - http_done ( http ); } -/** - * Generate HTTP Basic authorisation string +/** Chunked transfer encoding */ +struct http_transfer_encoding http_transfer_chunked __http_transfer_encoding = { + .name = "chunked", + .init = http_init_transfer_chunked, + .state = { + .rx = http_rx_transfer_chunked, + .close = http_close_error, + }, +}; + +/****************************************************************************** * - * @v http HTTP request - * @ret auth Authorisation string, or NULL on error + * Response trailers * - * The authorisation string is dynamically allocated, and must be - * freed by the caller. + ****************************************************************************** */ -static char * http_basic_auth ( struct http_request *http ) { - const char *user = http->uri->user; - const char *password = ( http->uri->password ? - http->uri->password : "" ); - size_t user_pw_len = - ( strlen ( user ) + 1 /* ":" */ + strlen ( password ) ); - char user_pw[ user_pw_len + 1 /* NUL */ ]; - size_t user_pw_base64_len = base64_encoded_len ( user_pw_len ); - char user_pw_base64[ user_pw_base64_len + 1 /* NUL */ ]; - char *auth; - int len; - /* Sanity check */ - assert ( user != NULL ); +/** + * Handle received HTTP trailer + * + * @v http HTTP transaction + * @v iobuf I/O buffer (may be claimed) + * @ret rc Return status code + */ +static int http_rx_trailers ( struct http_transaction *http, + struct io_buffer **iobuf ) { + char *line; + int rc; - /* Make "user:password" string from decoded fields */ - snprintf ( user_pw, sizeof ( user_pw ), "%s:%s", user, password ); + /* Buffer trailer line */ + if ( ( rc = http_rx_linebuf ( http, *iobuf, &http->linebuf ) ) != 0 ) + return rc; - /* Base64-encode the "user:password" string */ - base64_encode ( ( void * ) user_pw, user_pw_len, user_pw_base64 ); + /* Wait until we see the empty line marking end of trailers */ + line = buffered_line ( &http->linebuf ); + if ( ( line == NULL ) || ( line[0] != '\0' ) ) + return 0; - /* Generate the authorisation string */ - len = asprintf ( &auth, "Authorization: Basic %s\r\n", - user_pw_base64 ); - if ( len < 0 ) - return NULL; + /* Empty line buffer */ + empty_line_buffer ( &http->linebuf ); - return auth; + /* Transfer is complete */ + if ( ( rc = http_transfer_complete ( http ) ) != 0 ) + return rc; + + return 0; } -/** - * Generate HTTP Digest authorisation string +/** HTTP response trailers state */ +static struct http_state http_trailers = { + .rx = http_rx_trailers, + .close = http_close_error, +}; + +/****************************************************************************** * - * @v http HTTP request - * @v method HTTP method (e.g. "GET") - * @v uri HTTP request URI (e.g. "/index.html") - * @ret auth Authorisation string, or NULL on error + * Simple URI openers * - * The authorisation string is dynamically allocated, and must be - * freed by the caller. + ****************************************************************************** */ -static char * http_digest_auth ( struct http_request *http, - const char *method, const char *uri ) { - const char *user = http->uri->user; - const char *password = ( http->uri->password ? - http->uri->password : "" ); - const char *realm = http->auth_realm; - const char *nonce = http->auth_nonce; - const char *opaque = http->auth_opaque; - static const char colon = ':'; - uint8_t ctx[MD5_CTX_SIZE]; - uint8_t digest[MD5_DIGEST_SIZE]; - char ha1[ base16_encoded_len ( sizeof ( digest ) ) + 1 /* NUL */ ]; - char ha2[ base16_encoded_len ( sizeof ( digest ) ) + 1 /* NUL */ ]; - char response[ base16_encoded_len ( sizeof ( digest ) ) + 1 /* NUL */ ]; - char *auth; - int len; - - /* Sanity checks */ - assert ( user != NULL ); - assert ( realm != NULL ); - assert ( nonce != NULL ); - - /* Generate HA1 */ - digest_init ( &md5_algorithm, ctx ); - digest_update ( &md5_algorithm, ctx, user, strlen ( user ) ); - digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) ); - digest_update ( &md5_algorithm, ctx, realm, strlen ( realm ) ); - digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) ); - digest_update ( &md5_algorithm, ctx, password, strlen ( password ) ); - digest_final ( &md5_algorithm, ctx, digest ); - base16_encode ( digest, sizeof ( digest ), ha1 ); - - /* Generate HA2 */ - digest_init ( &md5_algorithm, ctx ); - digest_update ( &md5_algorithm, ctx, method, strlen ( method ) ); - digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) ); - digest_update ( &md5_algorithm, ctx, uri, strlen ( uri ) ); - digest_final ( &md5_algorithm, ctx, digest ); - base16_encode ( digest, sizeof ( digest ), ha2 ); - - /* Generate response */ - digest_init ( &md5_algorithm, ctx ); - digest_update ( &md5_algorithm, ctx, ha1, strlen ( ha1 ) ); - digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) ); - digest_update ( &md5_algorithm, ctx, nonce, strlen ( nonce ) ); - digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) ); - digest_update ( &md5_algorithm, ctx, ha2, strlen ( ha2 ) ); - digest_final ( &md5_algorithm, ctx, digest ); - base16_encode ( digest, sizeof ( digest ), response ); - - /* Generate the authorisation string */ - len = asprintf ( &auth, "Authorization: Digest username=\"%s\", " - "realm=\"%s\", nonce=\"%s\", uri=\"%s\", " - "%s%s%sresponse=\"%s\"\r\n", user, realm, nonce, uri, - ( opaque ? "opaque=\"" : "" ), - ( opaque ? opaque : "" ), - ( opaque ? "\", " : "" ), response ); - if ( len < 0 ) - return NULL; - - return auth; -} /** - * Generate HTTP POST parameter list + * Construct HTTP parameter list * - * @v http HTTP request + * @v params Parameter list * @v buf Buffer to contain HTTP POST parameters * @v len Length of buffer * @ret len Length of parameter list (excluding terminating NUL) */ -static size_t http_post_params ( struct http_request *http, - char *buf, size_t len ) { +static size_t http_params ( struct parameters *params, char *buf, size_t len ) { struct parameter *param; ssize_t remaining = len; size_t frag_len; /* Add each parameter in the form "key=value", joined with "&" */ len = 0; - for_each_param ( param, http->uri->params ) { + for_each_param ( param, params ) { /* Add the "&", if applicable */ if ( len ) { @@ -1201,374 +1853,78 @@ static size_t http_post_params ( struct http_request *http, } /** - * Generate HTTP POST body + * Open HTTP transaction for simple GET URI * - * @v http HTTP request - * @ret post I/O buffer containing POST body, or NULL on error + * @v xfer Data transfer interface + * @v uri Request URI + * @ret rc Return status code */ -static struct io_buffer * http_post ( struct http_request *http ) { - struct io_buffer *post; - size_t len; - size_t check_len; +static int http_open_get_uri ( struct interface *xfer, struct uri *uri ) { - /* Calculate length of parameter list */ - len = http_post_params ( http, NULL, 0 ); - - /* Allocate parameter list */ - post = alloc_iob ( len + 1 /* NUL */ ); - if ( ! post ) - return NULL; - - /* Fill parameter list */ - check_len = http_post_params ( http, iob_put ( post, len ), - ( len + 1 /* NUL */ ) ); - assert ( len == check_len ); - DBGC ( http, "HTTP %p POST %s\n", http, ( ( char * ) post->data ) ); - - return post; + return http_open ( xfer, &http_get, uri, NULL, NULL ); } /** - * HTTP process + * Open HTTP transaction for simple POST URI * - * @v http HTTP request + * @v xfer Data transfer interface + * @v uri Request URI + * @ret rc Return status code */ -static void http_step ( struct http_request *http ) { - struct io_buffer *post; - struct uri host_uri; - struct uri path_uri; - char *host_uri_string; - char *path_uri_string; - char *method; - char *range; - char *auth; - char *content; - int len; +static int http_open_post_uri ( struct interface *xfer, struct uri *uri ) { + struct parameters *params = uri->params; + struct http_request_content content; + void *data; + size_t len; + size_t check_len; int rc; - /* Do nothing if we have already transmitted the request */ - if ( ! ( http->flags & HTTP_TX_PENDING ) ) - return; - - /* Do nothing until socket is ready */ - if ( ! xfer_window ( &http->socket ) ) - return; - - /* Force a HEAD request if we have nowhere to send any received data */ - if ( ( xfer_window ( &http->xfer ) == 0 ) && - ( http->rx_buffer == UNULL ) ) { - http->flags |= ( HTTP_HEAD_ONLY | HTTP_CLIENT_KEEPALIVE ); - } - - /* Determine method */ - method = ( ( http->flags & HTTP_HEAD_ONLY ) ? "HEAD" : - ( http->uri->params ? "POST" : "GET" ) ); + /* Calculate length of parameter list */ + len = http_params ( params, NULL, 0 ); - /* Construct host URI */ - memset ( &host_uri, 0, sizeof ( host_uri ) ); - host_uri.host = http->uri->host; - host_uri.port = http->uri->port; - host_uri_string = format_uri_alloc ( &host_uri ); - if ( ! host_uri_string ) { + /* Allocate temporary parameter list */ + data = zalloc ( len + 1 /* NUL */ ); + if ( ! data ) { rc = -ENOMEM; - goto err_host_uri; + goto err_alloc; } - /* Construct path URI */ - memset ( &path_uri, 0, sizeof ( path_uri ) ); - path_uri.path = ( http->uri->path ? http->uri->path : "/" ); - path_uri.query = http->uri->query; - path_uri_string = format_uri_alloc ( &path_uri ); - if ( ! path_uri_string ) { - rc = -ENOMEM; - goto err_path_uri; - } + /* Construct temporary parameter list */ + check_len = http_params ( params, data, ( len + 1 /* NUL */ ) ); + assert ( check_len == len ); - /* Calculate range request parameters if applicable */ - if ( http->partial_len ) { - len = asprintf ( &range, "Range: bytes=%zd-%zd\r\n", - http->partial_start, - ( http->partial_start + http->partial_len - - 1 ) ); - if ( len < 0 ) { - rc = len; - goto err_range; - } - } else { - range = NULL; - } + /* Construct request content */ + content.type = "application/x-www-form-urlencoded"; + content.data = data; + content.len = len; - /* Construct authorisation, if applicable */ - if ( http->flags & HTTP_BASIC_AUTH ) { - auth = http_basic_auth ( http ); - if ( ! auth ) { - rc = -ENOMEM; - goto err_auth; - } - } else if ( http->flags & HTTP_DIGEST_AUTH ) { - auth = http_digest_auth ( http, method, path_uri_string ); - if ( ! auth ) { - rc = -ENOMEM; - goto err_auth; - } - } else { - auth = NULL; - } + /* Open HTTP transaction */ + if ( ( rc = http_open ( xfer, &http_post, uri, NULL, &content ) ) != 0 ) + goto err_open; - /* Construct POST content, if applicable */ - if ( http->uri->params ) { - post = http_post ( http ); - if ( ! post ) { - rc = -ENOMEM; - goto err_post; - } - len = asprintf ( &content, "Content-Type: " - "application/x-www-form-urlencoded\r\n" - "Content-Length: %zd\r\n", iob_len ( post ) ); - if ( len < 0 ) { - rc = len; - goto err_content; - } - } else { - post = NULL; - content = NULL; - } - - /* Mark request as transmitted */ - http->flags &= ~HTTP_TX_PENDING; - - /* Send request */ - if ( ( rc = xfer_printf ( &http->socket, - "%s %s HTTP/1.1\r\n" - "User-Agent: iPXE/%s\r\n" - "Host: %s\r\n" - "%s%s%s%s" - "\r\n", - method, path_uri_string, product_version, - host_uri_string, - ( ( http->flags & HTTP_CLIENT_KEEPALIVE ) ? - "Connection: keep-alive\r\n" : "" ), - ( range ? range : "" ), - ( auth ? auth : "" ), - ( content ? content : "" ) ) ) != 0 ) { - goto err_xfer; - } - - /* Send POST content, if applicable */ - if ( post ) { - if ( ( rc = xfer_deliver_iob ( &http->socket, - iob_disown ( post ) ) ) != 0 ) - goto err_xfer_post; - } - - err_xfer_post: - err_xfer: - free ( content ); - err_content: - free ( post ); - err_post: - free ( auth ); - err_auth: - free ( range ); - err_range: - free ( path_uri_string ); - err_path_uri: - free ( host_uri_string ); - err_host_uri: - if ( rc != 0 ) - http_close ( http, rc ); -} - -/** - * Check HTTP data transfer flow control window - * - * @v http HTTP request - * @ret len Length of window - */ -static size_t http_xfer_window ( struct http_request *http ) { - - /* New block commands may be issued only when we are idle */ - return ( ( http->rx_state == HTTP_RX_IDLE ) ? 1 : 0 ); -} - -/** - * Initiate HTTP partial read - * - * @v http HTTP request - * @v partial Partial transfer interface - * @v offset Starting offset - * @v buffer Data buffer - * @v len Length - * @ret rc Return status code - */ -static int http_partial_read ( struct http_request *http, - struct interface *partial, - size_t offset, userptr_t buffer, size_t len ) { - - /* Sanity check */ - if ( http_xfer_window ( http ) == 0 ) - return -EBUSY; - - /* Initialise partial transfer parameters */ - http->rx_buffer = buffer; - http->partial_start = offset; - http->partial_len = len; - - /* Schedule request */ - http->rx_state = HTTP_RX_RESPONSE; - http->flags = ( HTTP_TX_PENDING | HTTP_CLIENT_KEEPALIVE ); - if ( ! len ) - http->flags |= HTTP_HEAD_ONLY; - process_add ( &http->process ); - - /* Attach to parent interface and return */ - intf_plug_plug ( &http->partial, partial ); - - return 0; -} - -/** - * Issue HTTP block device read - * - * @v http HTTP request - * @v block Block data interface - * @v lba Starting logical block address - * @v count Number of blocks to transfer - * @v buffer Data buffer - * @v len Length of data buffer - * @ret rc Return status code - */ -static int http_block_read ( struct http_request *http, - struct interface *block, - uint64_t lba, unsigned int count, - userptr_t buffer, size_t len __unused ) { - - return http_partial_read ( http, block, ( lba * HTTP_BLKSIZE ), - buffer, ( count * HTTP_BLKSIZE ) ); -} - -/** - * Read HTTP block device capacity - * - * @v http HTTP request - * @v block Block data interface - * @ret rc Return status code - */ -static int http_block_read_capacity ( struct http_request *http, - struct interface *block ) { - - return http_partial_read ( http, block, 0, 0, 0 ); -} - -/** - * Describe HTTP device in an ACPI table - * - * @v http HTTP request - * @v acpi ACPI table - * @v len Length of ACPI table - * @ret rc Return status code - */ -static int http_acpi_describe ( struct http_request *http, - struct acpi_description_header *acpi, - size_t len ) { - - DBGC ( http, "HTTP %p cannot yet describe device in an ACPI table\n", - http ); - ( void ) acpi; - ( void ) len; - return 0; + err_open: + free ( data ); + err_alloc: + return rc; } -/** HTTP socket interface operations */ -static struct interface_operation http_socket_operations[] = { - INTF_OP ( xfer_window, struct http_request *, http_socket_window ), - INTF_OP ( xfer_deliver, struct http_request *, http_socket_deliver ), - INTF_OP ( xfer_window_changed, struct http_request *, http_step ), - INTF_OP ( intf_close, struct http_request *, http_socket_close ), -}; - -/** HTTP socket interface descriptor */ -static struct interface_descriptor http_socket_desc = - INTF_DESC_PASSTHRU ( struct http_request, socket, - http_socket_operations, xfer ); - -/** HTTP partial transfer interface operations */ -static struct interface_operation http_partial_operations[] = { - INTF_OP ( intf_close, struct http_request *, http_close ), -}; - -/** HTTP partial transfer interface descriptor */ -static struct interface_descriptor http_partial_desc = - INTF_DESC ( struct http_request, partial, http_partial_operations ); - -/** HTTP data transfer interface operations */ -static struct interface_operation http_xfer_operations[] = { - INTF_OP ( xfer_window, struct http_request *, http_xfer_window ), - INTF_OP ( block_read, struct http_request *, http_block_read ), - INTF_OP ( block_read_capacity, struct http_request *, - http_block_read_capacity ), - INTF_OP ( intf_close, struct http_request *, http_close ), - INTF_OP ( acpi_describe, struct http_request *, http_acpi_describe ), -}; - -/** HTTP data transfer interface descriptor */ -static struct interface_descriptor http_xfer_desc = - INTF_DESC_PASSTHRU ( struct http_request, xfer, - http_xfer_operations, socket ); - -/** HTTP process descriptor */ -static struct process_descriptor http_process_desc = - PROC_DESC_ONCE ( struct http_request, process, http_step ); - /** - * Initiate an HTTP connection, with optional filter + * Open HTTP transaction for simple URI * * @v xfer Data transfer interface - * @v uri Uniform Resource Identifier - * @v default_port Default port number - * @v filter Filter to apply to socket, or NULL + * @v uri Request URI * @ret rc Return status code */ -int http_open_filter ( struct interface *xfer, struct uri *uri, - unsigned int default_port, - int ( * filter ) ( struct interface *xfer, - const char *name, - struct interface **next ) ) { - struct http_request *http; - int rc; - - /* Sanity checks */ - if ( ! uri->host ) - return -EINVAL; - - /* Allocate and populate HTTP structure */ - http = zalloc ( sizeof ( *http ) ); - if ( ! http ) - return -ENOMEM; - ref_init ( &http->refcnt, http_free ); - intf_init ( &http->xfer, &http_xfer_desc, &http->refcnt ); - intf_init ( &http->partial, &http_partial_desc, &http->refcnt ); - http->uri = uri_get ( uri ); - http->default_port = default_port; - http->filter = filter; - intf_init ( &http->socket, &http_socket_desc, &http->refcnt ); - process_init ( &http->process, &http_process_desc, &http->refcnt ); - timer_init ( &http->timer, http_retry, &http->refcnt ); - http->flags = HTTP_TX_PENDING; +int http_open_uri ( struct interface *xfer, struct uri *uri ) { - /* Open socket */ - if ( ( rc = http_socket_open ( http ) ) != 0 ) - goto err; - - /* Attach to parent interface, mortalise self, and return */ - intf_plug_plug ( &http->xfer, xfer ); - ref_put ( &http->refcnt ); - return 0; - - err: - DBGC ( http, "HTTP %p could not create request: %s\n", - http, strerror ( rc ) ); - http_close ( http, rc ); - ref_put ( &http->refcnt ); - return rc; + /* Open GET/POST URI as applicable */ + if ( uri->params ) { + return http_open_post_uri ( xfer, uri ); + } else { + return http_open_get_uri ( xfer, uri ); + } } + +/* Drag in HTTP extensions */ +REQUIRING_SYMBOL ( http_open ); +REQUIRE_OBJECT ( config_http ); diff --git a/qemu/roms/ipxe/src/net/tcp/httpdigest.c b/qemu/roms/ipxe/src/net/tcp/httpdigest.c new file mode 100644 index 000000000..626dd7e9d --- /dev/null +++ b/qemu/roms/ipxe/src/net/tcp/httpdigest.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +/** + * @file + * + * Hyper Text Transfer Protocol (HTTP) Digest authentication + * + */ + +#include <stdio.h> +#include <errno.h> +#include <strings.h> +#include <ipxe/uri.h> +#include <ipxe/md5.h> +#include <ipxe/base16.h> +#include <ipxe/vsprintf.h> +#include <ipxe/http.h> + +/* Disambiguate the various error causes */ +#define EACCES_USERNAME __einfo_error ( EINFO_EACCES_USERNAME ) +#define EINFO_EACCES_USERNAME \ + __einfo_uniqify ( EINFO_EACCES, 0x01, \ + "No username available for Digest authentication" ) + +/** + * Initialise HTTP Digest + * + * @v ctx Digest context + * @v string Initial string + */ +static void http_digest_init ( struct md5_context *ctx ) { + + /* Initialise MD5 digest */ + digest_init ( &md5_algorithm, ctx ); +} + +/** + * Update HTTP Digest with new data + * + * @v ctx Digest context + * @v string String to append + */ +static void http_digest_update ( struct md5_context *ctx, const char *string ) { + static const char colon = ':'; + + /* Add (possibly colon-separated) field to MD5 digest */ + if ( ctx->len ) + digest_update ( &md5_algorithm, ctx, &colon, sizeof ( colon ) ); + digest_update ( &md5_algorithm, ctx, string, strlen ( string ) ); +} + +/** + * Finalise HTTP Digest + * + * @v ctx Digest context + * @v out Buffer for digest output + * @v len Buffer length + */ +static void http_digest_final ( struct md5_context *ctx, char *out, + size_t len ) { + uint8_t digest[MD5_DIGEST_SIZE]; + + /* Finalise and base16-encode MD5 digest */ + digest_final ( &md5_algorithm, ctx, digest ); + base16_encode ( digest, sizeof ( digest ), out, len ); +} + +/** + * Perform HTTP Digest authentication + * + * @v http HTTP transaction + * @ret rc Return status code + */ +static int http_digest_authenticate ( struct http_transaction *http ) { + struct http_request_auth *req = &http->request.auth; + struct http_response_auth *rsp = &http->response.auth; + char ha1[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ]; + char ha2[ base16_encoded_len ( MD5_DIGEST_SIZE ) + 1 /* NUL */ ]; + static const char md5sess[] = "MD5-sess"; + static const char md5[] = "MD5"; + struct md5_context ctx; + + /* Check for required response parameters */ + if ( ! rsp->realm ) { + DBGC ( http, "HTTP %p has no realm for Digest authentication\n", + http ); + return -EINVAL; + } + if ( ! rsp->nonce ) { + DBGC ( http, "HTTP %p has no nonce for Digest authentication\n", + http ); + return -EINVAL; + } + + /* Record username and password */ + if ( ! http->uri->user ) { + DBGC ( http, "HTTP %p has no username for Digest " + "authentication\n", http ); + return -EACCES_USERNAME; + } + req->username = http->uri->user; + req->password = ( http->uri->password ? http->uri->password : "" ); + + /* Handle quality of protection */ + if ( rsp->qop ) { + + /* Use "auth" in subsequent request */ + req->qop = "auth"; + + /* Generate a client nonce */ + snprintf ( req->cnonce, sizeof ( req->cnonce ), + "%08lx", random() ); + + /* Determine algorithm */ + req->algorithm = md5; + if ( rsp->algorithm && + ( strcasecmp ( rsp->algorithm, md5sess ) == 0 ) ) { + req->algorithm = md5sess; + } + } + + /* Generate HA1 */ + http_digest_init ( &ctx ); + http_digest_update ( &ctx, req->username ); + http_digest_update ( &ctx, rsp->realm ); + http_digest_update ( &ctx, req->password ); + http_digest_final ( &ctx, ha1, sizeof ( ha1 ) ); + if ( req->algorithm == md5sess ) { + http_digest_init ( &ctx ); + http_digest_update ( &ctx, ha1 ); + http_digest_update ( &ctx, rsp->nonce ); + http_digest_update ( &ctx, req->cnonce ); + http_digest_final ( &ctx, ha1, sizeof ( ha1 ) ); + } + + /* Generate HA2 */ + http_digest_init ( &ctx ); + http_digest_update ( &ctx, http->request.method->name ); + http_digest_update ( &ctx, http->request.uri ); + http_digest_final ( &ctx, ha2, sizeof ( ha2 ) ); + + /* Generate response */ + http_digest_init ( &ctx ); + http_digest_update ( &ctx, ha1 ); + http_digest_update ( &ctx, rsp->nonce ); + if ( req->qop ) { + http_digest_update ( &ctx, HTTP_DIGEST_NC ); + http_digest_update ( &ctx, req->cnonce ); + http_digest_update ( &ctx, req->qop ); + } + http_digest_update ( &ctx, ha2 ); + http_digest_final ( &ctx, req->response, sizeof ( req->response ) ); + + return 0; +} + +/** + * Construct HTTP "Authorization" header for Digest authentication + * + * @v http HTTP transaction + * @v buf Buffer + * @v len Length of buffer + * @ret len Length of header value, or negative error + */ +static int http_format_digest_auth ( struct http_transaction *http, + char *buf, size_t len ) { + struct http_request_auth *req = &http->request.auth; + struct http_response_auth *rsp = &http->response.auth; + size_t used = 0; + + /* Sanity checks */ + assert ( rsp->realm != NULL ); + assert ( rsp->nonce != NULL ); + assert ( req->username != NULL ); + if ( req->qop ) { + assert ( req->algorithm != NULL ); + assert ( req->cnonce[0] != '\0' ); + } + assert ( req->response[0] != '\0' ); + + /* Construct response */ + used += ssnprintf ( ( buf + used ), ( len - used ), + "realm=\"%s\", nonce=\"%s\", uri=\"%s\", " + "username=\"%s\"", rsp->realm, rsp->nonce, + http->request.uri, req->username ); + if ( rsp->opaque ) { + used += ssnprintf ( ( buf + used ), ( len - used ), + ", opaque=\"%s\"", rsp->opaque ); + } + if ( req->qop ) { + used += ssnprintf ( ( buf + used ), ( len - used ), + ", qop=%s, algorithm=%s, cnonce=\"%s\", " + "nc=" HTTP_DIGEST_NC, req->qop, + req->algorithm, req->cnonce ); + } + used += ssnprintf ( ( buf + used ), ( len - used ), + ", response=\"%s\"", req->response ); + + return used; +} + +/** HTTP Digest authentication scheme */ +struct http_authentication http_digest_auth __http_authentication = { + .name = "Digest", + .authenticate = http_digest_authenticate, + .format = http_format_digest_auth, +}; + +/* Drag in HTTP authentication support */ +REQUIRING_SYMBOL ( http_digest_auth ); +REQUIRE_OBJECT ( httpauth ); diff --git a/qemu/roms/ipxe/src/net/tcp/https.c b/qemu/roms/ipxe/src/net/tcp/https.c index 6112acdae..e91000322 100644 --- a/qemu/roms/ipxe/src/net/tcp/https.c +++ b/qemu/roms/ipxe/src/net/tcp/https.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * @file @@ -26,7 +30,6 @@ FILE_LICENCE ( GPL2_OR_LATER ); * */ -#include <stddef.h> #include <ipxe/open.h> #include <ipxe/tls.h> #include <ipxe/http.h> @@ -34,19 +37,15 @@ FILE_LICENCE ( GPL2_OR_LATER ); FEATURE ( FEATURE_PROTOCOL, "HTTPS", DHCP_EB_FEATURE_HTTPS, 1 ); -/** - * Initiate an HTTPS connection - * - * @v xfer Data transfer interface - * @v uri Uniform Resource Identifier - * @ret rc Return status code - */ -static int https_open ( struct interface *xfer, struct uri *uri ) { - return http_open_filter ( xfer, uri, HTTPS_PORT, add_tls ); -} - /** HTTPS URI opener */ struct uri_opener https_uri_opener __uri_opener = { .scheme = "https", - .open = https_open, + .open = http_open_uri, +}; + +/** HTTP URI scheme */ +struct http_scheme https_scheme __http_scheme = { + .name = "https", + .port = HTTPS_PORT, + .filter = add_tls, }; diff --git a/qemu/roms/ipxe/src/net/tcp/iscsi.c b/qemu/roms/ipxe/src/net/tcp/iscsi.c index 03c6d0f23..019a4c14e 100644 --- a/qemu/roms/ipxe/src/net/tcp/iscsi.c +++ b/qemu/roms/ipxe/src/net/tcp/iscsi.c @@ -15,14 +15,19 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stddef.h> #include <string.h> #include <stdlib.h> #include <stdio.h> +#include <ctype.h> #include <errno.h> #include <assert.h> #include <byteswap.h> @@ -127,7 +132,7 @@ FEATURE ( FEATURE_PROTOCOL, "iSCSI", DHCP_EB_FEATURE_ISCSI, 1 ); #define EPROTO_INVALID_LARGE_BINARY \ __einfo_error ( EINFO_EPROTO_INVALID_LARGE_BINARY ) #define EINFO_EPROTO_INVALID_LARGE_BINARY \ - __einfo_uniqify ( EINFO_EPROTO, 0x03, "Invalid large binary" ) + __einfo_uniqify ( EINFO_EPROTO, 0x03, "Invalid large binary value" ) #define EPROTO_INVALID_CHAP_RESPONSE \ __einfo_error ( EINFO_EPROTO_INVALID_CHAP_RESPONSE ) #define EINFO_EPROTO_INVALID_CHAP_RESPONSE \ @@ -704,7 +709,7 @@ static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi, char buf[ base16_encoded_len ( iscsi->chap.response_len ) + 1 ]; assert ( iscsi->initiator_username != NULL ); base16_encode ( iscsi->chap.response, iscsi->chap.response_len, - buf ); + buf, sizeof ( buf ) ); used += ssnprintf ( data + used, len - used, "CHAP_N=%s%cCHAP_R=0x%s%c", iscsi->initiator_username, 0, buf, 0 ); @@ -714,7 +719,7 @@ static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi, size_t challenge_len = ( sizeof ( iscsi->chap_challenge ) - 1 ); char buf[ base16_encoded_len ( challenge_len ) + 1 ]; base16_encode ( ( iscsi->chap_challenge + 1 ), challenge_len, - buf ); + buf, sizeof ( buf ) ); used += ssnprintf ( data + used, len - used, "CHAP_I=%d%cCHAP_C=0x%s%c", iscsi->chap_challenge[0], 0, buf, 0 ); @@ -824,38 +829,27 @@ static int iscsi_tx_login_request ( struct iscsi_session *iscsi ) { } /** - * Calculate maximum length of decoded large binary value - * - * @v encoded Encoded large binary value - * @v max_raw_len Maximum length of raw data - */ -static inline size_t -iscsi_large_binary_decoded_max_len ( const char *encoded ) { - return ( strlen ( encoded ) ); /* Decoding never expands data */ -} - -/** * Decode large binary value * * @v encoded Encoded large binary value * @v raw Raw data + * @v len Length of data buffer * @ret len Length of raw data, or negative error */ -static int iscsi_large_binary_decode ( const char *encoded, uint8_t *raw ) { - - if ( encoded[0] != '0' ) - return -EPROTO_INVALID_LARGE_BINARY; - - switch ( encoded[1] ) { - case 'x' : - case 'X' : - return base16_decode ( ( encoded + 2 ), raw ); - case 'b' : - case 'B' : - return base64_decode ( ( encoded + 2 ), raw ); - default: - return -EPROTO_INVALID_LARGE_BINARY; +static int iscsi_large_binary_decode ( const char *encoded, uint8_t *raw, + size_t len ) { + + /* Check for initial '0x' or '0b' and decode as appropriate */ + if ( *(encoded++) == '0' ) { + switch ( tolower ( *(encoded++) ) ) { + case 'x' : + return base16_decode ( encoded, raw, len ); + case 'b' : + return base64_decode ( encoded, raw, len ); + } } + + return -EPROTO_INVALID_LARGE_BINARY; } /** @@ -982,19 +976,19 @@ static int iscsi_handle_chap_i_value ( struct iscsi_session *iscsi, */ static int iscsi_handle_chap_c_value ( struct iscsi_session *iscsi, const char *value ) { - uint8_t buf[ iscsi_large_binary_decoded_max_len ( value ) ]; + uint8_t buf[ strlen ( value ) ]; /* Decoding never expands data */ unsigned int i; - size_t len; + int len; int rc; /* Process challenge */ - rc = iscsi_large_binary_decode ( value, buf ); - if ( rc < 0 ) { + len = iscsi_large_binary_decode ( value, buf, sizeof ( buf ) ); + if ( len < 0 ) { + rc = len; DBGC ( iscsi, "iSCSI %p invalid CHAP challenge \"%s\": %s\n", iscsi, value, strerror ( rc ) ); return rc; } - len = rc; chap_update ( &iscsi->chap, buf, len ); /* Build CHAP response */ @@ -1052,8 +1046,8 @@ static int iscsi_handle_chap_n_value ( struct iscsi_session *iscsi, */ static int iscsi_handle_chap_r_value ( struct iscsi_session *iscsi, const char *value ) { - uint8_t buf[ iscsi_large_binary_decoded_max_len ( value ) ]; - size_t len; + uint8_t buf[ strlen ( value ) ]; /* Decoding never expands data */ + int len; int rc; /* Generate CHAP response for verification */ @@ -1073,16 +1067,16 @@ static int iscsi_handle_chap_r_value ( struct iscsi_session *iscsi, chap_respond ( &iscsi->chap ); /* Process response */ - rc = iscsi_large_binary_decode ( value, buf ); - if ( rc < 0 ) { + len = iscsi_large_binary_decode ( value, buf, sizeof ( buf ) ); + if ( len < 0 ) { + rc = len; DBGC ( iscsi, "iSCSI %p invalid CHAP response \"%s\": %s\n", iscsi, value, strerror ( rc ) ); return rc; } - len = rc; /* Check CHAP response */ - if ( len != iscsi->chap.response_len ) { + if ( len != ( int ) iscsi->chap.response_len ) { DBGC ( iscsi, "iSCSI %p invalid CHAP response length\n", iscsi ); return -EPROTO_INVALID_CHAP_RESPONSE; @@ -1445,8 +1439,10 @@ static void iscsi_tx_done ( struct iscsi_session *iscsi ) { switch ( common->opcode & ISCSI_OPCODE_MASK ) { case ISCSI_OPCODE_DATA_OUT: iscsi_data_out_done ( iscsi ); + break; case ISCSI_OPCODE_LOGIN_REQUEST: iscsi_login_request_done ( iscsi ); + break; default: /* No action */ break; diff --git a/qemu/roms/ipxe/src/net/tcp/syslogs.c b/qemu/roms/ipxe/src/net/tcp/syslogs.c index 095afc543..0c07f86d5 100644 --- a/qemu/roms/ipxe/src/net/tcp/syslogs.c +++ b/qemu/roms/ipxe/src/net/tcp/syslogs.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * diff --git a/qemu/roms/ipxe/src/net/tcpip.c b/qemu/roms/ipxe/src/net/tcpip.c index 4bcbe64bb..5ad982fd1 100644 --- a/qemu/roms/ipxe/src/net/tcpip.c +++ b/qemu/roms/ipxe/src/net/tcpip.c @@ -17,7 +17,7 @@ * TCP/IP transport-network layer interface */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * Process a received TCP/IP packet @@ -235,7 +235,7 @@ int tcpip_bind ( struct sockaddr_tcpip *st_local, /* Otherwise, find an available port in the range [1,1023] or * [1025,65535] as appropriate. */ - min_port = ( ( ( ! flags ) & TCPIP_BIND_PRIVILEGED ) + 1 ); + min_port = ( ( ( ~flags ) & TCPIP_BIND_PRIVILEGED ) + 1 ); max_port = ( ( flags & TCPIP_BIND_PRIVILEGED ) - 1 ); offset = random(); for ( i = 0 ; i <= max_port ; i++ ) { diff --git a/qemu/roms/ipxe/src/net/tls.c b/qemu/roms/ipxe/src/net/tls.c index 30ccc932e..db01fb291 100644 --- a/qemu/roms/ipxe/src/net/tls.c +++ b/qemu/roms/ipxe/src/net/tls.c @@ -179,20 +179,29 @@ static void tls_clear_cipher ( struct tls_session *tls, ****************************************************************************** */ +/** A TLS 24-bit integer + * + * TLS uses 24-bit integers in several places, which are awkward to + * parse in C. + */ +typedef struct { + /** High byte */ + uint8_t high; + /** Low word */ + uint16_t low; +} __attribute__ (( packed )) tls24_t; + /** * Extract 24-bit field value * * @v field24 24-bit field * @ret value Field value * - * TLS uses 24-bit integers in several places, which are awkward to - * parse in C. */ static inline __attribute__ (( always_inline )) unsigned long -tls_uint24 ( const uint8_t field24[3] ) { - const uint32_t *field32 __attribute__ (( may_alias )) = - ( ( const void * ) field24 ); - return ( be32_to_cpu ( *field32 ) >> 8 ); +tls_uint24 ( const tls24_t *field24 ) { + + return ( ( field24->high << 16 ) | be16_to_cpu ( field24->low ) ); } /** @@ -200,13 +209,11 @@ tls_uint24 ( const uint8_t field24[3] ) { * * @v field24 24-bit field * @v value Field value - * - * The field must be pre-zeroed. */ -static void tls_set_uint24 ( uint8_t field24[3], unsigned long value ) { - uint32_t *field32 __attribute__ (( may_alias )) = - ( ( void * ) field24 ); - *field32 |= cpu_to_be32 ( value << 8 ); +static void tls_set_uint24 ( tls24_t *field24, unsigned long value ) { + + field24->high = ( value >> 16 ); + field24->low = cpu_to_be16 ( value ); } /** @@ -659,41 +666,8 @@ struct tls_cipher_suite tls_cipher_suite_null = { .digest = &digest_null, }; -/** Supported cipher suites, in order of preference */ -struct tls_cipher_suite tls_cipher_suites[] = { - { - .code = htons ( TLS_RSA_WITH_AES_256_CBC_SHA256 ), - .key_len = ( 256 / 8 ), - .pubkey = &rsa_algorithm, - .cipher = &aes_cbc_algorithm, - .digest = &sha256_algorithm, - }, - { - .code = htons ( TLS_RSA_WITH_AES_128_CBC_SHA256 ), - .key_len = ( 128 / 8 ), - .pubkey = &rsa_algorithm, - .cipher = &aes_cbc_algorithm, - .digest = &sha256_algorithm, - }, - { - .code = htons ( TLS_RSA_WITH_AES_256_CBC_SHA ), - .key_len = ( 256 / 8 ), - .pubkey = &rsa_algorithm, - .cipher = &aes_cbc_algorithm, - .digest = &sha1_algorithm, - }, - { - .code = htons ( TLS_RSA_WITH_AES_128_CBC_SHA ), - .key_len = ( 128 / 8 ), - .pubkey = &rsa_algorithm, - .cipher = &aes_cbc_algorithm, - .digest = &sha1_algorithm, - }, -}; - /** Number of supported cipher suites */ -#define TLS_NUM_CIPHER_SUITES \ - ( sizeof ( tls_cipher_suites ) / sizeof ( tls_cipher_suites[0] ) ) +#define TLS_NUM_CIPHER_SUITES table_num_entries ( TLS_CIPHER_SUITES ) /** * Identify cipher suite @@ -704,11 +678,9 @@ struct tls_cipher_suite tls_cipher_suites[] = { static struct tls_cipher_suite * tls_find_cipher_suite ( unsigned int cipher_suite ) { struct tls_cipher_suite *suite; - unsigned int i; /* Identify cipher suite */ - for ( i = 0 ; i < TLS_NUM_CIPHER_SUITES ; i++ ) { - suite = &tls_cipher_suites[i]; + for_each_table_entry ( suite, TLS_CIPHER_SUITES ) { if ( suite->code == cipher_suite ) return suite; } @@ -841,26 +813,9 @@ static int tls_change_cipher ( struct tls_session *tls, ****************************************************************************** */ -/** Supported signature and hash algorithms - * - * Note that the default (TLSv1.1 and earlier) algorithm using - * MD5+SHA1 is never explicitly specified. - */ -struct tls_signature_hash_algorithm tls_signature_hash_algorithms[] = { - { - .code = { - .signature = TLS_RSA_ALGORITHM, - .hash = TLS_SHA256_ALGORITHM, - }, - .pubkey = &rsa_algorithm, - .digest = &sha256_algorithm, - }, -}; - /** Number of supported signature and hash algorithms */ -#define TLS_NUM_SIG_HASH_ALGORITHMS \ - ( sizeof ( tls_signature_hash_algorithms ) / \ - sizeof ( tls_signature_hash_algorithms[0] ) ) +#define TLS_NUM_SIG_HASH_ALGORITHMS \ + table_num_entries ( TLS_SIG_HASH_ALGORITHMS ) /** * Find TLS signature and hash algorithm @@ -873,11 +828,9 @@ static struct tls_signature_hash_algorithm * tls_signature_hash_algorithm ( struct pubkey_algorithm *pubkey, struct digest_algorithm *digest ) { struct tls_signature_hash_algorithm *sig_hash; - unsigned int i; /* Identify signature and hash algorithm */ - for ( i = 0 ; i < TLS_NUM_SIG_HASH_ALGORITHMS ; i++ ) { - sig_hash = &tls_signature_hash_algorithms[i]; + for_each_table_entry ( sig_hash, TLS_SIG_HASH_ALGORITHMS ) { if ( ( sig_hash->pubkey == pubkey ) && ( sig_hash->digest == digest ) ) { return sig_hash; @@ -994,8 +947,17 @@ static int tls_send_client_hello ( struct tls_session *tls ) { struct { uint8_t max; } __attribute__ (( packed )) max_fragment_length; + uint16_t signature_algorithms_type; + uint16_t signature_algorithms_len; + struct { + uint16_t len; + struct tls_signature_hash_id + code[TLS_NUM_SIG_HASH_ALGORITHMS]; + } __attribute__ (( packed )) signature_algorithms; } __attribute__ (( packed )) extensions; } __attribute__ (( packed )) hello; + struct tls_cipher_suite *suite; + struct tls_signature_hash_algorithm *sighash; unsigned int i; memset ( &hello, 0, sizeof ( hello ) ); @@ -1005,8 +967,8 @@ static int tls_send_client_hello ( struct tls_session *tls ) { hello.version = htons ( tls->version ); memcpy ( &hello.random, &tls->client_random, sizeof ( hello.random ) ); hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) ); - for ( i = 0 ; i < TLS_NUM_CIPHER_SUITES ; i++ ) - hello.cipher_suites[i] = tls_cipher_suites[i].code; + i = 0 ; for_each_table_entry ( suite, TLS_CIPHER_SUITES ) + hello.cipher_suites[i++] = suite->code; hello.compression_methods_len = sizeof ( hello.compression_methods ); hello.extensions_len = htons ( sizeof ( hello.extensions ) ); hello.extensions.server_name_type = htons ( TLS_SERVER_NAME ); @@ -1025,6 +987,14 @@ static int tls_send_client_hello ( struct tls_session *tls ) { = htons ( sizeof ( hello.extensions.max_fragment_length ) ); hello.extensions.max_fragment_length.max = TLS_MAX_FRAGMENT_LENGTH_4096; + hello.extensions.signature_algorithms_type + = htons ( TLS_SIGNATURE_ALGORITHMS ); + hello.extensions.signature_algorithms_len + = htons ( sizeof ( hello.extensions.signature_algorithms ) ); + hello.extensions.signature_algorithms.len + = htons ( sizeof ( hello.extensions.signature_algorithms.code)); + i = 0 ; for_each_table_entry ( sighash, TLS_SIG_HASH_ALGORITHMS ) + hello.extensions.signature_algorithms.code[i++] = sighash->code; return tls_send_handshake ( tls, &hello, sizeof ( hello ) ); } @@ -1038,9 +1008,9 @@ static int tls_send_client_hello ( struct tls_session *tls ) { static int tls_send_certificate ( struct tls_session *tls ) { struct { uint32_t type_length; - uint8_t length[3]; + tls24_t length; struct { - uint8_t length[3]; + tls24_t length; uint8_t data[ tls->cert->raw.len ]; } __attribute__ (( packed )) certificates[1]; } __attribute__ (( packed )) *certificate; @@ -1058,9 +1028,9 @@ static int tls_send_certificate ( struct tls_session *tls ) { ( cpu_to_le32 ( TLS_CERTIFICATE ) | htonl ( sizeof ( *certificate ) - sizeof ( certificate->type_length ) ) ); - tls_set_uint24 ( certificate->length, + tls_set_uint24 ( &certificate->length, sizeof ( certificate->certificates ) ); - tls_set_uint24 ( certificate->certificates[0].length, + tls_set_uint24 ( &certificate->certificates[0].length, sizeof ( certificate->certificates[0].data ) ); memcpy ( certificate->certificates[0].data, tls->cert->raw.data, @@ -1412,7 +1382,7 @@ static int tls_parse_chain ( struct tls_session *tls, const void *data, size_t len ) { const void *end = ( data + len ); const struct { - uint8_t length[3]; + tls24_t length; uint8_t data[0]; } __attribute__ (( packed )) *certificate; size_t certificate_len; @@ -1436,7 +1406,7 @@ static int tls_parse_chain ( struct tls_session *tls, /* Extract raw certificate data */ certificate = data; - certificate_len = tls_uint24 ( certificate->length ); + certificate_len = tls_uint24 ( &certificate->length ); next = ( certificate->data + certificate_len ); if ( next > end ) { DBGC ( tls, "TLS %p overlength certificate:\n", tls ); @@ -1482,10 +1452,10 @@ static int tls_parse_chain ( struct tls_session *tls, static int tls_new_certificate ( struct tls_session *tls, const void *data, size_t len ) { const struct { - uint8_t length[3]; + tls24_t length; uint8_t certificates[0]; } __attribute__ (( packed )) *certificate = data; - size_t certificates_len = tls_uint24 ( certificate->length ); + size_t certificates_len = tls_uint24 ( &certificate->length ); const void *end = ( certificate->certificates + certificates_len ); int rc; @@ -1634,12 +1604,12 @@ static int tls_new_handshake ( struct tls_session *tls, while ( data != end ) { const struct { uint8_t type; - uint8_t length[3]; + tls24_t length; uint8_t payload[0]; } __attribute__ (( packed )) *handshake = data; - void *payload = &handshake->payload; - size_t payload_len = tls_uint24 ( handshake->length ); - void *next = ( payload + payload_len ); + const void *payload = &handshake->payload; + size_t payload_len = tls_uint24 ( &handshake->length ); + const void *next = ( payload + payload_len ); /* Sanity check */ if ( next > end ) { @@ -2637,3 +2607,9 @@ int add_tls ( struct interface *xfer, const char *name, err_alloc: return rc; } + +/* Drag in objects via add_tls() */ +REQUIRING_SYMBOL ( add_tls ); + +/* Drag in crypto configuration */ +REQUIRE_OBJECT ( config_crypto ); diff --git a/qemu/roms/ipxe/src/net/udp.c b/qemu/roms/ipxe/src/net/udp.c index 76da67ecf..0f7dfb24a 100644 --- a/qemu/roms/ipxe/src/net/udp.c +++ b/qemu/roms/ipxe/src/net/udp.c @@ -17,7 +17,7 @@ * UDP protocol */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** * A UDP connection diff --git a/qemu/roms/ipxe/src/net/udp/dhcp.c b/qemu/roms/ipxe/src/net/udp/dhcp.c index 04fad04c2..aed5ee360 100644 --- a/qemu/roms/ipxe/src/net/udp/dhcp.c +++ b/qemu/roms/ipxe/src/net/udp/dhcp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <stdlib.h> @@ -44,6 +48,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #include <ipxe/dhcppkt.h> #include <ipxe/dhcp_arch.h> #include <ipxe/features.h> +#include <config/dhcp.h> /** @file * @@ -149,30 +154,32 @@ struct dhcp_session_state { * @v dhcppkt DHCP packet * @v peer Destination address */ - int ( * tx ) ( struct dhcp_session *dhcp, - struct dhcp_packet *dhcppkt, + int ( * tx ) ( struct dhcp_session *dhcp, struct dhcp_packet *dhcppkt, struct sockaddr_in *peer ); - /** Handle received packet + /** + * 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 + * @v pseudo_id DHCP server pseudo-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 + void ( * rx ) ( struct dhcp_session *dhcp, struct dhcp_packet *dhcppkt, + struct sockaddr_in *peer, uint8_t msgtype, + struct in_addr server_id, struct in_addr pseudo_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; + /** Timeout parameters */ + uint8_t min_timeout_sec; + uint8_t max_timeout_sec; }; static struct dhcp_session_state dhcp_state_discover; @@ -272,9 +279,9 @@ static void dhcp_set_state ( struct dhcp_session *dhcp, 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; + set_timer_limits ( &dhcp->timer, + ( state->min_timeout_sec * TICKS_PER_SEC ), + ( state->max_timeout_sec * TICKS_PER_SEC ) ); start_timer_nodelay ( &dhcp->timer ); } @@ -334,11 +341,13 @@ static int dhcp_discovery_tx ( struct dhcp_session *dhcp, * @v peer DHCP server address * @v msgtype DHCP message type * @v server_id DHCP server ID + * @v pseudo_id DHCP server pseudo-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 server_id, + struct in_addr pseudo_id ) { struct in_addr ip; char vci[9]; /* "PXEClient" */ int vci_len; @@ -350,8 +359,11 @@ static void dhcp_discovery_rx ( struct dhcp_session *dhcp, 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 ) ); + if ( ( server_id.s_addr != peer->sin_addr.s_addr ) || + ( pseudo_id.s_addr != peer->sin_addr.s_addr ) ) { + DBGC ( dhcp, " (%s/", inet_ntoa ( server_id ) ); + DBGC ( dhcp, "%s)", inet_ntoa ( pseudo_id ) ); + } /* Identify offered IP address */ ip = dhcppkt->dhcphdr->yiaddr; @@ -392,10 +404,10 @@ static void dhcp_discovery_rx ( struct dhcp_session *dhcp, } /* Select as ProxyDHCP offer, if applicable */ - if ( server_id.s_addr && has_pxeclient && + if ( pseudo_id.s_addr && has_pxeclient && ( priority >= dhcp->proxy_priority ) ) { dhcppkt_put ( dhcp->proxy_offer ); - dhcp->proxy_server = server_id; + dhcp->proxy_server = pseudo_id; dhcp->proxy_offer = dhcppkt_get ( dhcppkt ); dhcp->proxy_priority = priority; } @@ -415,7 +427,7 @@ static void dhcp_discovery_rx ( struct dhcp_session *dhcp, /* 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 ) ) ) + ( elapsed > DHCP_DISC_PROXY_TIMEOUT_SEC * TICKS_PER_SEC ) ) ) return; /* Transition to DHCPREQUEST */ @@ -430,8 +442,18 @@ static void dhcp_discovery_rx ( struct dhcp_session *dhcp, static void dhcp_discovery_expired ( struct dhcp_session *dhcp ) { unsigned long elapsed = ( currticks() - dhcp->start ); + /* If link is blocked, defer DHCP discovery (and reset timeout) */ + if ( netdev_link_blocked ( dhcp->netdev ) ) { + DBGC ( dhcp, "DHCP %p deferring discovery\n", dhcp ); + start_timer_fixed ( &dhcp->timer, + ( DHCP_DISC_START_TIMEOUT_SEC * + TICKS_PER_SEC ) ); + return; + } + /* Give up waiting for ProxyDHCP before we reach the failure point */ - if ( dhcp->offer.s_addr && ( elapsed > PROXYDHCP_MAX_TIMEOUT ) ) { + if ( dhcp->offer.s_addr && + ( elapsed > DHCP_DISC_PROXY_TIMEOUT_SEC * TICKS_PER_SEC ) ) { dhcp_set_state ( dhcp, &dhcp_state_request ); return; } @@ -447,7 +469,8 @@ static struct dhcp_session_state dhcp_state_discover = { .rx = dhcp_discovery_rx, .expired = dhcp_discovery_expired, .tx_msgtype = DHCPDISCOVER, - .apply_min_timeout = 1, + .min_timeout_sec = DHCP_DISC_START_TIMEOUT_SEC, + .max_timeout_sec = DHCP_DISC_END_TIMEOUT_SEC, }; /** @@ -493,11 +516,13 @@ static int dhcp_request_tx ( struct dhcp_session *dhcp, * @v peer DHCP server address * @v msgtype DHCP message type * @v server_id DHCP server ID + * @v pseudo_id DHCP server pseudo-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 server_id, + struct in_addr pseudo_id ) { struct in_addr ip; struct settings *parent; struct settings *settings; @@ -506,8 +531,11 @@ static void dhcp_request_rx ( struct dhcp_session *dhcp, 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 ) ); + if ( ( server_id.s_addr != peer->sin_addr.s_addr ) || + ( pseudo_id.s_addr != peer->sin_addr.s_addr ) ) { + DBGC ( dhcp, " (%s/", inet_ntoa ( server_id ) ); + DBGC ( dhcp, "%s)", inet_ntoa ( pseudo_id ) ); + } /* Identify leased IP address */ ip = dhcppkt->dhcphdr->yiaddr; @@ -584,7 +612,8 @@ static struct dhcp_session_state dhcp_state_request = { .rx = dhcp_request_rx, .expired = dhcp_request_expired, .tx_msgtype = DHCPREQUEST, - .apply_min_timeout = 0, + .min_timeout_sec = DHCP_REQ_START_TIMEOUT_SEC, + .max_timeout_sec = DHCP_REQ_END_TIMEOUT_SEC, }; /** @@ -623,19 +652,26 @@ static int dhcp_proxy_tx ( struct dhcp_session *dhcp, * @v peer DHCP server address * @v msgtype DHCP message type * @v server_id DHCP server ID + * @v pseudo_id DHCP server pseudo-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 in_addr server_id, + struct in_addr pseudo_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 ) ); + if ( ( server_id.s_addr != peer->sin_addr.s_addr ) || + ( pseudo_id.s_addr != peer->sin_addr.s_addr ) ) { + DBGC ( dhcp, " (%s/", inet_ntoa ( server_id ) ); + DBGC ( dhcp, "%s)", inet_ntoa ( pseudo_id ) ); + } + if ( dhcp_has_pxeopts ( dhcppkt ) ) + DBGC ( dhcp, " pxe" ); DBGC ( dhcp, "\n" ); /* Filter out unacceptable responses */ @@ -643,8 +679,9 @@ static void dhcp_proxy_rx ( struct dhcp_session *dhcp, 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 ) ) + if ( ( pseudo_id.s_addr != dhcp->proxy_server.s_addr ) ) + return; + if ( ! dhcp_has_pxeopts ( dhcppkt ) ) return; /* Register settings */ @@ -669,7 +706,7 @@ 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 ) { + if ( elapsed > DHCP_REQ_PROXY_TIMEOUT_SEC * TICKS_PER_SEC ) { dhcp_finished ( dhcp, 0 ); return; } @@ -685,7 +722,8 @@ static struct dhcp_session_state dhcp_state_proxy = { .rx = dhcp_proxy_rx, .expired = dhcp_proxy_expired, .tx_msgtype = DHCPREQUEST, - .apply_min_timeout = 0, + .min_timeout_sec = DHCP_PROXY_START_TIMEOUT_SEC, + .max_timeout_sec = DHCP_PROXY_END_TIMEOUT_SEC, }; /** @@ -753,19 +791,24 @@ static int dhcp_pxebs_accept ( struct dhcp_session *dhcp, * @v peer DHCP server address * @v msgtype DHCP message type * @v server_id DHCP server ID + * @v pseudo_id DHCP server pseudo-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 in_addr server_id, + struct in_addr pseudo_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 ) ); + if ( ( server_id.s_addr != peer->sin_addr.s_addr ) || + ( pseudo_id.s_addr != peer->sin_addr.s_addr ) ) { + DBGC ( dhcp, " (%s/", inet_ntoa ( server_id ) ); + DBGC ( dhcp, "%s)", inet_ntoa ( pseudo_id ) ); + } /* Identify boot menu item */ dhcppkt_fetch ( dhcppkt, DHCP_PXE_BOOT_MENU_ITEM, @@ -782,8 +825,7 @@ static void dhcp_pxebs_rx ( struct dhcp_session *dhcp, return; if ( menu_item.type != dhcp->pxe_type ) return; - if ( ! dhcp_pxebs_accept ( dhcp, ( server_id.s_addr ? - server_id : peer->sin_addr ) ) ) + if ( ! dhcp_pxebs_accept ( dhcp, pseudo_id ) ) return; /* Register settings */ @@ -810,7 +852,7 @@ static void dhcp_pxebs_expired ( struct dhcp_session *dhcp ) { /* 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 ) { + if ( elapsed > PXEBS_MAX_TIMEOUT_SEC * TICKS_PER_SEC ) { dhcp->pxe_attempt++; if ( dhcp->pxe_attempt->s_addr ) { dhcp_set_state ( dhcp, &dhcp_state_pxebs ); @@ -832,7 +874,8 @@ static struct dhcp_session_state dhcp_state_pxebs = { .rx = dhcp_pxebs_rx, .expired = dhcp_pxebs_expired, .tx_msgtype = DHCPREQUEST, - .apply_min_timeout = 1, + .min_timeout_sec = PXEBS_START_TIMEOUT_SEC, + .max_timeout_sec = PXEBS_END_TIMEOUT_SEC, }; /**************************************************************************** @@ -1114,6 +1157,7 @@ static int dhcp_deliver ( struct dhcp_session *dhcp, struct dhcphdr *dhcphdr; uint8_t msgtype = 0; struct in_addr server_id = { 0 }; + struct in_addr pseudo_id; int rc = 0; /* Sanity checks */ @@ -1148,6 +1192,13 @@ static int dhcp_deliver ( struct dhcp_session *dhcp, dhcppkt_fetch ( dhcppkt, DHCP_SERVER_IDENTIFIER, &server_id, sizeof ( server_id ) ); + /* Identify server pseudo-ID */ + pseudo_id = server_id; + if ( ! pseudo_id.s_addr ) + pseudo_id = dhcppkt->dhcphdr->siaddr; + if ( ! pseudo_id.s_addr ) + pseudo_id = peer->sin_addr; + /* Check for matching transaction ID */ if ( dhcphdr->xid != dhcp->xid ) { DBGC ( dhcp, "DHCP %p %s from %s:%d has bad transaction " @@ -1170,7 +1221,7 @@ static int dhcp_deliver ( struct dhcp_session *dhcp, } /* Handle packet based on current state */ - dhcp->state->rx ( dhcp, dhcppkt, peer, msgtype, server_id ); + dhcp->state->rx ( dhcp, dhcppkt, peer, msgtype, server_id, pseudo_id ); err_chaddr: err_xid: diff --git a/qemu/roms/ipxe/src/net/udp/dhcpv6.c b/qemu/roms/ipxe/src/net/udp/dhcpv6.c index f7736d08e..a63543775 100644 --- a/qemu/roms/ipxe/src/net/udp/dhcpv6.c +++ b/qemu/roms/ipxe/src/net/udp/dhcpv6.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdlib.h> #include <stdio.h> diff --git a/qemu/roms/ipxe/src/net/udp/dns.c b/qemu/roms/ipxe/src/net/udp/dns.c index fffe6e697..2d77477f6 100644 --- a/qemu/roms/ipxe/src/net/udp/dns.c +++ b/qemu/roms/ipxe/src/net/udp/dns.c @@ -18,9 +18,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/udp/slam.c b/qemu/roms/ipxe/src/net/udp/slam.c index 3cb492d73..8b26bfb3c 100644 --- a/qemu/roms/ipxe/src/net/udp/slam.c +++ b/qemu/roms/ipxe/src/net/udp/slam.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> diff --git a/qemu/roms/ipxe/src/net/udp/syslog.c b/qemu/roms/ipxe/src/net/udp/syslog.c index d65d19ab8..b6eee6036 100644 --- a/qemu/roms/ipxe/src/net/udp/syslog.c +++ b/qemu/roms/ipxe/src/net/udp/syslog.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * diff --git a/qemu/roms/ipxe/src/net/udp/tftp.c b/qemu/roms/ipxe/src/net/udp/tftp.c index ee827ae3d..953bcb46a 100644 --- a/qemu/roms/ipxe/src/net/udp/tftp.c +++ b/qemu/roms/ipxe/src/net/udp/tftp.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <stdlib.h> @@ -149,8 +153,6 @@ enum { 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 */ @@ -759,14 +761,6 @@ static int tftp_rx_oack ( struct tftp_request *tftp, void *buf, size_t len ) { 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 ); @@ -794,13 +788,6 @@ static int tftp_rx_data ( struct tftp_request *tftp, 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 " @@ -1036,10 +1023,25 @@ static size_t tftp_xfer_window ( struct tftp_request *tftp ) { return tftp->blksize; } +/** + * Terminate download + * + * @v tftp TFTP connection + * @v rc Reason for close + */ +static void tftp_close ( struct tftp_request *tftp, int rc ) { + + /* Abort download */ + tftp_send_error ( tftp, 0, "TFTP Aborted" ); + + /* Close TFTP request */ + tftp_done ( tftp, rc ); +} + /** 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 ), + INTF_OP ( intf_close, struct tftp_request *, tftp_close ), }; /** TFTP data transfer interface descriptor */ @@ -1126,26 +1128,6 @@ struct uri_opener tftp_uri_opener __uri_opener = { }; /** - * 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 diff --git a/qemu/roms/ipxe/src/net/validator.c b/qemu/roms/ipxe/src/net/validator.c index 74d70e312..db968398a 100644 --- a/qemu/roms/ipxe/src/net/validator.c +++ b/qemu/roms/ipxe/src/net/validator.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <string.h> #include <stdio.h> @@ -79,7 +83,7 @@ static void validator_free ( struct refcnt *refcnt ) { DBGC2 ( validator, "VALIDATOR %p freed\n", validator ); x509_chain_put ( validator->chain ); ocsp_put ( validator->ocsp ); - xferbuf_done ( &validator->buffer ); + xferbuf_free ( &validator->buffer ); free ( validator ); } @@ -250,7 +254,8 @@ static int validator_start_download ( struct validator *validator, /* Generate URI string */ len = snprintf ( uri_string, uri_string_len, "%s/%08x.der?subject=", crosscert, crc ); - base64_encode ( issuer->data, issuer->len, ( uri_string + len ) ); + base64_encode ( issuer->data, issuer->len, ( uri_string + len ), + ( uri_string_len - len ) ); DBGC ( validator, "VALIDATOR %p downloading cross-signed certificate " "from %s\n", validator, uri_string ); @@ -387,7 +392,7 @@ static void validator_xfer_close ( struct validator *validator, int rc ) { goto err_append; /* Free downloaded data */ - xferbuf_done ( &validator->buffer ); + xferbuf_free ( &validator->buffer ); /* Resume validation process */ process_add ( &validator->process ); @@ -552,6 +557,7 @@ int create_validator ( struct interface *job, struct x509_chain *chain ) { process_init ( &validator->process, &validator_process_desc, &validator->refcnt ); validator->chain = x509_chain_get ( chain ); + xferbuf_malloc_init ( &validator->buffer ); /* Attach parent interface, mortalise self, and return */ intf_plug_plug ( &validator->job, job ); diff --git a/qemu/roms/ipxe/src/net/vlan.c b/qemu/roms/ipxe/src/net/vlan.c index b4ddde42d..f515c2dc9 100644 --- a/qemu/roms/ipxe/src/net/vlan.c +++ b/qemu/roms/ipxe/src/net/vlan.c @@ -15,9 +15,13 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. */ -FILE_LICENCE ( GPL2_OR_LATER ); +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <stdint.h> #include <string.h> @@ -385,6 +389,10 @@ int vlan_create ( struct net_device *trunk, unsigned int tag, snprintf ( netdev->name, sizeof ( netdev->name ), "%s-%d", trunk->name, vlan->tag ); + /* Mark device as not supporting interrupts, if applicable */ + if ( ! netdev_irq_supported ( trunk ) ) + netdev->state |= NETDEV_IRQ_UNSUPPORTED; + /* Register VLAN device */ if ( ( rc = register_netdev ( netdev ) ) != 0 ) { DBGC ( netdev, "VLAN %s could not register: %s\n", |