summaryrefslogtreecommitdiffstats
path: root/qemu/roms/ipxe/src/net
diff options
context:
space:
mode:
Diffstat (limited to 'qemu/roms/ipxe/src/net')
-rw-r--r--qemu/roms/ipxe/src/net/80211/net80211.c10
-rw-r--r--qemu/roms/ipxe/src/net/80211/wpa.c1
-rw-r--r--qemu/roms/ipxe/src/net/80211/wpa_ccmp.c2
-rw-r--r--qemu/roms/ipxe/src/net/80211/wpa_tkip.c2
-rw-r--r--qemu/roms/ipxe/src/net/aoe.c6
-rw-r--r--qemu/roms/ipxe/src/net/arp.c12
-rw-r--r--qemu/roms/ipxe/src/net/dhcpopts.c6
-rw-r--r--qemu/roms/ipxe/src/net/dhcppkt.c6
-rw-r--r--qemu/roms/ipxe/src/net/eth_slow.c6
-rw-r--r--qemu/roms/ipxe/src/net/ethernet.c50
-rw-r--r--qemu/roms/ipxe/src/net/fakedhcp.c6
-rw-r--r--qemu/roms/ipxe/src/net/fc.c12
-rw-r--r--qemu/roms/ipxe/src/net/fcels.c6
-rw-r--r--qemu/roms/ipxe/src/net/fcns.c6
-rw-r--r--qemu/roms/ipxe/src/net/fcoe.c6
-rw-r--r--qemu/roms/ipxe/src/net/fcp.c6
-rw-r--r--qemu/roms/ipxe/src/net/fragment.c6
-rw-r--r--qemu/roms/ipxe/src/net/icmp.c6
-rw-r--r--qemu/roms/ipxe/src/net/icmpv4.c6
-rw-r--r--qemu/roms/ipxe/src/net/icmpv6.c86
-rw-r--r--qemu/roms/ipxe/src/net/infiniband.c18
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_cm.c6
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_mcast.c12
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_mi.c6
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_packet.c6
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_pathrec.c6
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_sma.c6
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_smc.c6
-rw-r--r--qemu/roms/ipxe/src/net/infiniband/ib_srp.c2
-rw-r--r--qemu/roms/ipxe/src/net/iobpad.c6
-rw-r--r--qemu/roms/ipxe/src/net/ipv4.c130
-rw-r--r--qemu/roms/ipxe/src/net/ipv6.c22
-rw-r--r--qemu/roms/ipxe/src/net/neighbour.c12
-rw-r--r--qemu/roms/ipxe/src/net/netdev_settings.c10
-rw-r--r--qemu/roms/ipxe/src/net/netdevice.c74
-rw-r--r--qemu/roms/ipxe/src/net/nullnet.c6
-rw-r--r--qemu/roms/ipxe/src/net/pccrc.c818
-rw-r--r--qemu/roms/ipxe/src/net/pccrd.c286
-rw-r--r--qemu/roms/ipxe/src/net/peerblk.c1366
-rw-r--r--qemu/roms/ipxe/src/net/peerdisc.c551
-rw-r--r--qemu/roms/ipxe/src/net/peerdist.c145
-rw-r--r--qemu/roms/ipxe/src/net/peermux.c387
-rw-r--r--qemu/roms/ipxe/src/net/ping.c6
-rw-r--r--qemu/roms/ipxe/src/net/rarp.c6
-rw-r--r--qemu/roms/ipxe/src/net/retry.c90
-rw-r--r--qemu/roms/ipxe/src/net/rndis.c1052
-rw-r--r--qemu/roms/ipxe/src/net/socket.c6
-rw-r--r--qemu/roms/ipxe/src/net/stp.c152
-rw-r--r--qemu/roms/ipxe/src/net/tcp.c245
-rw-r--r--qemu/roms/ipxe/src/net/tcp/http.c26
-rw-r--r--qemu/roms/ipxe/src/net/tcp/httpauth.c190
-rw-r--r--qemu/roms/ipxe/src/net/tcp/httpbasic.c102
-rw-r--r--qemu/roms/ipxe/src/net/tcp/httpblock.c134
-rw-r--r--qemu/roms/ipxe/src/net/tcp/httpconn.c309
-rw-r--r--qemu/roms/ipxe/src/net/tcp/httpcore.c2676
-rw-r--r--qemu/roms/ipxe/src/net/tcp/httpdigest.c234
-rw-r--r--qemu/roms/ipxe/src/net/tcp/https.c27
-rw-r--r--qemu/roms/ipxe/src/net/tcp/iscsi.c76
-rw-r--r--qemu/roms/ipxe/src/net/tcp/syslogs.c6
-rw-r--r--qemu/roms/ipxe/src/net/tcpip.c4
-rw-r--r--qemu/roms/ipxe/src/net/tls.c146
-rw-r--r--qemu/roms/ipxe/src/net/udp.c2
-rw-r--r--qemu/roms/ipxe/src/net/udp/dhcp.c133
-rw-r--r--qemu/roms/ipxe/src/net/udp/dhcpv6.c6
-rw-r--r--qemu/roms/ipxe/src/net/udp/dns.c6
-rw-r--r--qemu/roms/ipxe/src/net/udp/slam.c6
-rw-r--r--qemu/roms/ipxe/src/net/udp/syslog.c6
-rw-r--r--qemu/roms/ipxe/src/net/udp/tftp.c60
-rw-r--r--qemu/roms/ipxe/src/net/validator.c14
-rw-r--r--qemu/roms/ipxe/src/net/vlan.c10
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 * ) &ethhdr->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 = ( &ethhdr->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",