diff options
| author | Simon Rettberg | 2023-04-04 15:12:41 +0200 |
|---|---|---|
| committer | Simon Rettberg | 2023-04-04 15:12:41 +0200 |
| commit | 5ba496dce11d10198a0eae0c8440dccb256fbf32 (patch) | |
| tree | 549903f1dab893870335a6e4767a4530444d2e83 /src/net | |
| parent | [vesafb] Map Unicode characters to CP437 if possible (diff) | |
| parent | [tls] Handle fragmented handshake records (diff) | |
| download | ipxe-5ba496dce11d10198a0eae0c8440dccb256fbf32.tar.gz ipxe-5ba496dce11d10198a0eae0c8440dccb256fbf32.tar.xz ipxe-5ba496dce11d10198a0eae0c8440dccb256fbf32.zip | |
Merge branch 'master' into openslx
Diffstat (limited to 'src/net')
| -rw-r--r-- | src/net/ipv4.c | 2 | ||||
| -rw-r--r-- | src/net/ipv6.c | 12 | ||||
| -rw-r--r-- | src/net/lldp.c | 340 | ||||
| -rw-r--r-- | src/net/ndp.c | 4 | ||||
| -rw-r--r-- | src/net/netdev_settings.c | 4 | ||||
| -rw-r--r-- | src/net/netdevice.c | 68 | ||||
| -rw-r--r-- | src/net/peerblk.c | 3 | ||||
| -rw-r--r-- | src/net/peerdisc.c | 2 | ||||
| -rw-r--r-- | src/net/tcp/httpcore.c | 107 | ||||
| -rw-r--r-- | src/net/tcp/iscsi.c | 65 | ||||
| -rw-r--r-- | src/net/tls.c | 839 | ||||
| -rw-r--r-- | src/net/udp/dhcp.c | 14 | ||||
| -rw-r--r-- | src/net/udp/dhcpv6.c | 4 | ||||
| -rw-r--r-- | src/net/vlan.c | 59 |
14 files changed, 1038 insertions, 485 deletions
diff --git a/src/net/ipv4.c b/src/net/ipv4.c index b9ce5e7f7..b91fa2ad0 100644 --- a/src/net/ipv4.c +++ b/src/net/ipv4.c @@ -163,7 +163,7 @@ static struct ipv4_miniroute * ipv4_route ( unsigned int scope_id, /* If destination is non-global, and the scope ID * matches this network device, then use this route. */ - if ( miniroute->netdev->index == scope_id ) + if ( miniroute->netdev->scope_id == scope_id ) return miniroute; } else { diff --git a/src/net/ipv6.c b/src/net/ipv6.c index 901203c40..ef5e51daa 100644 --- a/src/net/ipv6.c +++ b/src/net/ipv6.c @@ -330,7 +330,7 @@ struct ipv6_miniroute * ipv6_route ( unsigned int scope_id, /* Skip entries with a non-matching scope ID, if * destination specifies a scope ID. */ - if ( scope_id && ( miniroute->netdev->index != scope_id ) ) + if ( scope_id && ( miniroute->netdev->scope_id != scope_id ) ) continue; /* Skip entries that are out of scope */ @@ -789,12 +789,12 @@ static int ipv6_rx ( struct io_buffer *iobuf, struct net_device *netdev, src.sin6.sin6_family = AF_INET6; memcpy ( &src.sin6.sin6_addr, &iphdr->src, sizeof ( src.sin6.sin6_addr ) ); - src.sin6.sin6_scope_id = netdev->index; + src.sin6.sin6_scope_id = netdev->scope_id; memset ( &dest, 0, sizeof ( dest ) ); dest.sin6.sin6_family = AF_INET6; memcpy ( &dest.sin6.sin6_addr, &iphdr->dest, sizeof ( dest.sin6.sin6_addr ) ); - dest.sin6.sin6_scope_id = netdev->index; + dest.sin6.sin6_scope_id = netdev->scope_id; iob_pull ( iobuf, hdrlen ); pshdr_csum = ipv6_pshdr_chksum ( iphdr, iob_len ( iobuf ), next_header, TCPIP_EMPTY_CSUM ); @@ -957,7 +957,7 @@ static const char * ipv6_sock_ntoa ( struct sockaddr *sa ) { /* Identify network device, if applicable */ if ( IN6_IS_ADDR_LINKLOCAL ( in ) || IN6_IS_ADDR_MULTICAST ( in ) ) { - netdev = find_netdev_by_index ( sin6->sin6_scope_id ); + netdev = find_netdev_by_scope_id ( sin6->sin6_scope_id ); netdev_name = ( netdev ? netdev->name : "UNKNOWN" ); } else { netdev_name = NULL; @@ -1020,7 +1020,7 @@ static int ipv6_sock_aton ( const char *string, struct sockaddr *sa ) { rc = -ENODEV; goto err_find_netdev; } - sin6->sin6_scope_id = netdev->index; + sin6->sin6_scope_id = netdev->scope_id; } else if ( IN6_IS_ADDR_LINKLOCAL ( &in ) || IN6_IS_ADDR_MULTICAST ( &in ) ) { @@ -1031,7 +1031,7 @@ static int ipv6_sock_aton ( const char *string, struct sockaddr *sa ) { */ netdev = last_opened_netdev(); if ( netdev ) - sin6->sin6_scope_id = netdev->index; + sin6->sin6_scope_id = netdev->scope_id; } /* Copy IPv6 address portion to socket address */ diff --git a/src/net/lldp.c b/src/net/lldp.c new file mode 100644 index 000000000..72e3ecdf6 --- /dev/null +++ b/src/net/lldp.c @@ -0,0 +1,340 @@ +/* + * Copyright (C) 2023 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 + * + * Link Layer Discovery Protocol + * + */ + +#include <stdlib.h> +#include <errno.h> +#include <byteswap.h> +#include <ipxe/iobuf.h> +#include <ipxe/netdevice.h> +#include <ipxe/if_ether.h> +#include <ipxe/settings.h> +#include <ipxe/lldp.h> + +/** An LLDP settings block */ +struct lldp_settings { + /** Reference counter */ + struct refcnt refcnt; + /** Settings interface */ + struct settings settings; + /** List of LLDP settings blocks */ + struct list_head list; + /** Name */ + const char *name; + /** LLDP data */ + void *data; + /** Length of LLDP data */ + size_t len; +}; + +/** LLDP settings scope */ +static const struct settings_scope lldp_settings_scope; + +/** List of LLDP settings blocks */ +static LIST_HEAD ( lldp_settings ); + +/** + * Free LLDP settings block + * + * @v refcnt Reference counter + */ +static void lldp_free ( struct refcnt *refcnt ) { + struct lldp_settings *lldpset = + container_of ( refcnt, struct lldp_settings, refcnt ); + + DBGC ( lldpset, "LLDP %s freed\n", lldpset->name ); + list_del ( &lldpset->list ); + free ( lldpset->data ); + free ( lldpset ); +} + +/** + * Find LLDP settings block + * + * @v netdev Network device + * @ret lldpset LLDP settings block + */ +static struct lldp_settings * lldp_find ( struct net_device *netdev ) { + struct lldp_settings *lldpset; + + /* Find matching LLDP settings block */ + list_for_each_entry ( lldpset, &lldp_settings, list ) { + if ( netdev_settings ( netdev ) == lldpset->settings.parent ) + return lldpset; + } + + return NULL; +} + +/** + * Check applicability of LLDP setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @ret applies Setting applies within this settings block + */ +static int lldp_applies ( struct settings *settings __unused, + const struct setting *setting ) { + + return ( setting->scope == &lldp_settings_scope ); +} + +/** + * Fetch value of LLDP setting + * + * @v settings Settings block + * @v setting Setting to fetch + * @v buf Buffer to fill with setting data + * @v len Length of buffer + * @ret len Length of setting data, or negative error + */ +static int lldp_fetch ( struct settings *settings, + struct setting *setting, + void *buf, size_t len ) { + struct lldp_settings *lldpset = + container_of ( settings, struct lldp_settings, settings ); + union { + uint32_t high; + uint8_t raw[4]; + } tag_prefix; + uint32_t tag_low; + uint8_t tag_type; + uint8_t tag_index; + uint8_t tag_offset; + uint8_t tag_length; + const void *match; + const void *data; + size_t match_len; + size_t remaining; + const struct lldp_tlv *tlv; + unsigned int tlv_type_len; + unsigned int tlv_type; + unsigned int tlv_len; + + /* Parse setting tag */ + tag_prefix.high = htonl ( setting->tag >> 32 ); + tag_low = setting->tag; + tag_type = ( tag_low >> 24 ); + tag_index = ( tag_low >> 16 ); + tag_offset = ( tag_low >> 8 ); + tag_length = ( tag_low >> 0 ); + + /* Identify match prefix */ + match_len = tag_offset; + if ( match_len > sizeof ( tag_prefix ) ) + match_len = sizeof ( tag_prefix ); + if ( ! tag_prefix.high ) + match_len = 0; + match = &tag_prefix.raw[ sizeof ( tag_prefix ) - match_len ]; + + /* Locate matching TLV */ + for ( data = lldpset->data, remaining = lldpset->len ; remaining ; + data += tlv_len, remaining -= tlv_len ) { + + /* Parse TLV header */ + if ( remaining < sizeof ( *tlv ) ) { + DBGC ( lldpset, "LLDP %s underlength TLV header\n", + lldpset->name ); + DBGC_HDA ( lldpset, 0, data, remaining ); + break; + } + tlv = data; + data += sizeof ( *tlv ); + remaining -= sizeof ( *tlv ); + tlv_type_len = ntohs ( tlv->type_len ); + tlv_type = LLDP_TLV_TYPE ( tlv_type_len ); + if ( tlv_type == LLDP_TYPE_END ) + break; + tlv_len = LLDP_TLV_LEN ( tlv_type_len ); + if ( remaining < tlv_len ) { + DBGC ( lldpset, "LLDP %s underlength TLV value\n", + lldpset->name ); + DBGC_HDA ( lldpset, 0, data, remaining ); + break; + } + DBGC2 ( lldpset, "LLDP %s found type %d:\n", + lldpset->name, tlv_type ); + DBGC2_HDA ( lldpset, 0, data, tlv_len ); + + /* Check for matching tag type */ + if ( tlv_type != tag_type ) + continue; + + /* Check for matching prefix */ + if ( tlv_len < match_len ) + continue; + if ( memcmp ( data, match, match_len ) != 0 ) + continue; + + /* Check for matching index */ + if ( tag_index-- ) + continue; + + /* Skip offset */ + if ( tlv_len < tag_offset ) + return 0; + data += tag_offset; + tlv_len -= tag_offset; + + /* Set type if not already specified */ + if ( ! setting->type ) { + setting->type = ( tag_length ? &setting_type_hex : + &setting_type_string ); + } + + /* Extract value */ + if ( tag_length && ( tlv_len > tag_length ) ) + tlv_len = tag_length; + if ( len > tlv_len ) + len = tlv_len; + memcpy ( buf, data, len ); + return tlv_len; + } + + return -ENOENT; +} + +/** LLDP settings operations */ +static struct settings_operations lldp_settings_operations = { + .applies = lldp_applies, + .fetch = lldp_fetch, +}; + +/** + * Process LLDP packet + * + * @v iobuf I/O buffer + * @v netdev Network device + * @v ll_dest Link-layer destination address + * @v ll_source Link-layer source address + * @v flags Packet flags + * @ret rc Return status code + */ +static int lldp_rx ( struct io_buffer *iobuf, struct net_device *netdev, + const void *ll_dest, const void *ll_source, + unsigned int flags __unused ) { + struct lldp_settings *lldpset; + size_t len; + void *data; + int rc; + + /* Find matching LLDP settings block */ + lldpset = lldp_find ( netdev ); + if ( ! lldpset ) { + DBGC ( netdev, "LLDP %s has no \"%s\" settings block\n", + netdev->name, LLDP_SETTINGS_NAME ); + rc = -ENOENT; + goto err_find; + } + + /* Create trimmed copy of received LLDP data */ + len = iob_len ( iobuf ); + data = malloc ( len ); + if ( ! data ) { + rc = -ENOMEM; + goto err_alloc; + } + memcpy ( data, iobuf->data, len ); + + /* Free any existing LLDP data */ + free ( lldpset->data ); + + /* Transfer data to LLDP settings block */ + lldpset->data = data; + lldpset->len = len; + data = NULL; + DBGC2 ( lldpset, "LLDP %s src %s ", + lldpset->name, netdev->ll_protocol->ntoa ( ll_source ) ); + DBGC2 ( lldpset, "dst %s\n", netdev->ll_protocol->ntoa ( ll_dest ) ); + DBGC2_HDA ( lldpset, 0, lldpset->data, lldpset->len ); + + /* Success */ + rc = 0; + + free ( data ); + err_alloc: + err_find: + free_iob ( iobuf ); + return rc; +} + +/** LLDP protocol */ +struct net_protocol lldp_protocol __net_protocol = { + .name = "LLDP", + .net_proto = htons ( ETH_P_LLDP ), + .rx = lldp_rx, +}; + +/** + * Create LLDP settings block + * + * @v netdev Network device + * @ret rc Return status code + */ +static int lldp_probe ( struct net_device *netdev ) { + struct lldp_settings *lldpset; + int rc; + + /* Allocate LLDP settings block */ + lldpset = zalloc ( sizeof ( *lldpset ) ); + if ( ! lldpset ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &lldpset->refcnt, lldp_free ); + settings_init ( &lldpset->settings, &lldp_settings_operations, + &lldpset->refcnt, &lldp_settings_scope ); + list_add_tail ( &lldpset->list, &lldp_settings ); + lldpset->name = netdev->name; + + /* Register settings */ + if ( ( rc = register_settings ( &lldpset->settings, netdev_settings ( netdev ), + LLDP_SETTINGS_NAME ) ) != 0 ) { + DBGC ( lldpset, "LLDP %s could not register settings: %s\n", + lldpset->name, strerror ( rc ) ); + goto err_register; + } + DBGC ( lldpset, "LLDP %s registered\n", lldpset->name ); + + ref_put ( &lldpset->refcnt ); + return 0; + + unregister_settings ( &lldpset->settings ); + err_register: + ref_put ( &lldpset->refcnt ); + err_alloc: + return rc; +} + +/** LLDP driver */ +struct net_driver lldp_driver __net_driver = { + .name = "LLDP", + .probe = lldp_probe, +}; diff --git a/src/net/ndp.c b/src/net/ndp.c index c8e8ebad3..373a9360b 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -140,7 +140,7 @@ static int ndp_tx_request ( struct net_device *netdev, /* Construct multicast destination address */ memset ( &sin6_dest, 0, sizeof ( sin6_dest ) ); sin6_dest.sin6_family = AF_INET6; - sin6_dest.sin6_scope_id = netdev->index; + sin6_dest.sin6_scope_id = netdev->scope_id; ipv6_solicited_node ( &sin6_dest.sin6_addr, net_dest ); /* Construct neighbour header */ @@ -177,7 +177,7 @@ static int ndp_tx_router_solicitation ( struct net_device *netdev ) { /* Construct multicast destination address */ memset ( &sin6_dest, 0, sizeof ( sin6_dest ) ); sin6_dest.sin6_family = AF_INET6; - sin6_dest.sin6_scope_id = netdev->index; + sin6_dest.sin6_scope_id = netdev->scope_id; ipv6_all_routers ( &sin6_dest.sin6_addr ); /* Construct router solicitation */ diff --git a/src/net/netdev_settings.c b/src/net/netdev_settings.c index cc2e10354..fb98663ca 100644 --- a/src/net/netdev_settings.c +++ b/src/net/netdev_settings.c @@ -370,8 +370,8 @@ struct settings_operations netdev_settings_operations = { static struct settings * netdev_redirect ( struct settings *settings ) { struct net_device *netdev; - /* Redirect to most recently opened network device */ - netdev = last_opened_netdev(); + /* Redirect to "netX" network device */ + netdev = find_netdev ( settings->name ); if ( netdev ) { return netdev_settings ( netdev ); } else { diff --git a/src/net/netdevice.c b/src/net/netdevice.c index 5df306e8d..07961bf20 100644 --- a/src/net/netdevice.c +++ b/src/net/netdevice.c @@ -55,9 +55,6 @@ struct list_head net_devices = LIST_HEAD_INIT ( net_devices ); /** List of open network devices, in reverse order of opening */ static struct list_head open_net_devices = LIST_HEAD_INIT ( open_net_devices ); -/** Network device index */ -static unsigned int netdev_index = 0; - /** Network polling profiler */ static struct profiler net_poll_profiler __profiler = { .name = "net.poll" }; @@ -723,6 +720,7 @@ int register_netdev ( struct net_device *netdev ) { struct ll_protocol *ll_protocol = netdev->ll_protocol; struct net_driver *driver; struct net_device *duplicate; + unsigned int i; uint32_t seed; int rc; @@ -737,18 +735,6 @@ int register_netdev ( struct net_device *netdev ) { ll_protocol->ll_header_len ); } - /* Reject network devices that are already available via a - * different hardware device. - */ - duplicate = find_netdev_by_ll_addr ( ll_protocol, netdev->ll_addr ); - if ( duplicate && ( duplicate->dev != netdev->dev ) ) { - DBGC ( netdev, "NETDEV rejecting duplicate (phys %s) of %s " - "(phys %s)\n", netdev->dev->name, duplicate->name, - duplicate->dev->name ); - rc = -EEXIST; - goto err_duplicate; - } - /* Reject named network devices that already exist */ if ( netdev->name[0] && ( duplicate = find_netdev ( netdev->name ) ) ) { DBGC ( netdev, "NETDEV rejecting duplicate name %s\n", @@ -757,12 +743,21 @@ int register_netdev ( struct net_device *netdev ) { goto err_duplicate; } - /* Record device index and create device name */ + /* Assign a unique device name, if not already set */ if ( netdev->name[0] == '\0' ) { - snprintf ( netdev->name, sizeof ( netdev->name ), "net%d", - netdev_index ); + for ( i = 0 ; ; i++ ) { + snprintf ( netdev->name, sizeof ( netdev->name ), + "net%d", i ); + if ( find_netdev ( netdev->name ) == NULL ) + break; + } + } + + /* Assign a unique non-zero scope ID */ + for ( netdev->scope_id = 1 ; ; netdev->scope_id++ ) { + if ( find_netdev_by_scope_id ( netdev->scope_id ) == NULL ) + break; } - netdev->index = ++netdev_index; /* Use least significant bits of the link-layer address to * improve the randomness of the (non-cryptographic) random @@ -916,10 +911,6 @@ void unregister_netdev ( struct net_device *netdev ) { DBGC ( netdev, "NETDEV %s unregistered\n", netdev->name ); list_del ( &netdev->list ); netdev_put ( netdev ); - - /* Reset network device index if no devices remain */ - if ( list_empty ( &net_devices ) ) - netdev_index = 0; } /** Enable or disable interrupts @@ -962,17 +953,17 @@ struct net_device * find_netdev ( const char *name ) { } /** - * Get network device by index + * Get network device by scope ID * * @v index Network device index * @ret netdev Network device, or NULL */ -struct net_device * find_netdev_by_index ( unsigned int index ) { +struct net_device * find_netdev_by_scope_id ( unsigned int scope_id ) { struct net_device *netdev; /* Identify network device by index */ list_for_each_entry ( netdev, &net_devices, list ) { - if ( netdev->index == index ) + if ( netdev->scope_id == scope_id ) return netdev; } @@ -1000,27 +991,6 @@ struct net_device * find_netdev_by_location ( unsigned int bus_type, } /** - * Get network device by link-layer address - * - * @v ll_protocol Link-layer protocol - * @v ll_addr Link-layer address - * @ret netdev Network device, or NULL - */ -struct net_device * find_netdev_by_ll_addr ( struct ll_protocol *ll_protocol, - const void *ll_addr ) { - struct net_device *netdev; - - list_for_each_entry ( netdev, &net_devices, list ) { - if ( ( netdev->ll_protocol == ll_protocol ) && - ( memcmp ( netdev->ll_addr, ll_addr, - ll_protocol->ll_addr_len ) == 0 ) ) - return netdev; - } - - return NULL; -} - -/** * Get most recently opened network device * * @ret netdev Most recently opened network device, or NULL @@ -1171,12 +1141,12 @@ static void net_step ( struct process *process __unused ) { } /** - * Get the VLAN tag (when VLAN support is not present) + * Get the VLAN tag control information (when VLAN support is not present) * * @v netdev Network device * @ret tag 0, indicating that device is not a VLAN device */ -__weak unsigned int vlan_tag ( struct net_device *netdev __unused ) { +__weak unsigned int vlan_tci ( struct net_device *netdev __unused ) { return 0; } diff --git a/src/net/peerblk.c b/src/net/peerblk.c index f8994f42c..bbd5f16ed 100644 --- a/src/net/peerblk.c +++ b/src/net/peerblk.c @@ -1033,7 +1033,8 @@ static int peerblk_parse_iv ( struct peerdist_block *peerblk, size_t buf_len, } /* Set initialisation vector */ - cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data ); + cipher_setiv ( peerblk->cipher, peerblk->cipherctx, msg->msg.iv.data, + blksize ); return 0; } diff --git a/src/net/peerdisc.c b/src/net/peerdisc.c index d7e0d2989..86ff94a87 100644 --- a/src/net/peerdisc.c +++ b/src/net/peerdisc.c @@ -189,7 +189,7 @@ static void peerdisc_socket_tx ( const char *uuid, const char *id ) { /* Skip unopened network devices */ if ( ! netdev_is_open ( netdev ) ) continue; - address.st.st_scope_id = netdev->index; + address.st.st_scope_id = netdev->scope_id; /* Discard request (for test purposes) if applicable */ if ( inject_fault ( PEERDISC_DISCARD_RATE ) ) diff --git a/src/net/tcp/httpcore.c b/src/net/tcp/httpcore.c index fd94b5f08..9ad39656d 100644 --- a/src/net/tcp/httpcore.c +++ b/src/net/tcp/httpcore.c @@ -830,7 +830,9 @@ static int http_transfer_complete ( struct http_transaction *http ) { */ static int http_format_headers ( struct http_transaction *http, char *buf, size_t len ) { + struct parameters *params = http->uri->params; struct http_request_header *header; + struct parameter *param; size_t used; size_t remaining; char *line; @@ -844,7 +846,7 @@ static int http_format_headers ( struct http_transaction *http, char *buf, DBGC2 ( http, "HTTP %p TX %s\n", http, buf ); used += ssnprintf ( ( buf + used ), ( len - used ), "\r\n" ); - /* Construct all headers */ + /* Construct all fixed headers */ for_each_table_entry ( header, HTTP_REQUEST_HEADERS ) { /* Determine header value length */ @@ -869,6 +871,23 @@ static int http_format_headers ( struct http_transaction *http, char *buf, used += ssnprintf ( ( buf + used ), ( len - used ), "\r\n" ); } + /* Construct parameter headers, if any */ + if ( params ) { + + /* Construct all parameter headers */ + for_each_param ( param, params ) { + + /* Skip non-header parameters */ + if ( ! ( param->flags & PARAMETER_HEADER ) ) + continue; + + /* Add parameter */ + used += ssnprintf ( ( buf + used ), ( len - used ), + "%s: %s\r\n", param->key, + param->value ); + } + } + /* Construct terminating newline */ used += ssnprintf ( ( buf + used ), ( len - used ), "\r\n" ); @@ -1851,14 +1870,15 @@ static struct http_state http_trailers = { */ /** - * Construct HTTP parameter list + * Construct HTTP form parameter list * * @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_params ( struct parameters *params, char *buf, size_t len ) { +static size_t http_form_params ( struct parameters *params, char *buf, + size_t len ) { struct parameter *param; ssize_t remaining = len; size_t frag_len; @@ -1867,6 +1887,10 @@ static size_t http_params ( struct parameters *params, char *buf, size_t len ) { len = 0; for_each_param ( param, params ) { + /* Skip non-form parameters */ + if ( ! ( param->flags & PARAMETER_FORM ) ) + continue; + /* Add the "&", if applicable */ if ( len ) { if ( remaining > 0 ) @@ -1904,53 +1928,59 @@ static size_t http_params ( struct parameters *params, char *buf, size_t len ) { } /** - * Open HTTP transaction for simple GET URI - * - * @v xfer Data transfer interface - * @v uri Request URI - * @ret rc Return status code - */ -static int http_open_get_uri ( struct interface *xfer, struct uri *uri ) { - - return http_open ( xfer, &http_get, uri, NULL, NULL ); -} - -/** - * Open HTTP transaction for simple POST URI + * Open HTTP transaction for simple URI * * @v xfer Data transfer interface * @v uri Request URI * @ret rc Return status code */ -static int http_open_post_uri ( struct interface *xfer, struct uri *uri ) { +int http_open_uri ( struct interface *xfer, struct uri *uri ) { struct parameters *params = uri->params; struct http_request_content content; + struct http_method *method; + const char *type; void *data; size_t len; size_t check_len; int rc; - /* Calculate length of parameter list */ - len = http_params ( params, NULL, 0 ); + /* Calculate length of form parameter list, if any */ + len = ( params ? http_form_params ( params, NULL, 0 ) : 0 ); - /* Allocate temporary parameter list */ - data = zalloc ( len + 1 /* NUL */ ); - if ( ! data ) { - rc = -ENOMEM; - goto err_alloc; - } + /* Use POST if and only if there are form parameters */ + if ( len ) { - /* Construct temporary parameter list */ - check_len = http_params ( params, data, ( len + 1 /* NUL */ ) ); - assert ( check_len == len ); + /* Use POST */ + method = &http_post; + type = "application/x-www-form-urlencoded"; + + /* Allocate temporary form parameter list */ + data = zalloc ( len + 1 /* NUL */ ); + if ( ! data ) { + rc = -ENOMEM; + goto err_alloc; + } + + /* Construct temporary form parameter list */ + check_len = http_form_params ( params, data, + ( len + 1 /* NUL */ ) ); + assert ( check_len == len ); + + } else { + + /* Use GET */ + method = &http_get; + type = NULL; + data = NULL; + } /* Construct request content */ - content.type = "application/x-www-form-urlencoded"; + content.type = type; content.data = data; content.len = len; /* Open HTTP transaction */ - if ( ( rc = http_open ( xfer, &http_post, uri, NULL, &content ) ) != 0 ) + if ( ( rc = http_open ( xfer, method, uri, NULL, &content ) ) != 0 ) goto err_open; err_open: @@ -1959,23 +1989,6 @@ static int http_open_post_uri ( struct interface *xfer, struct uri *uri ) { return rc; } -/** - * Open HTTP transaction for simple URI - * - * @v xfer Data transfer interface - * @v uri Request URI - * @ret rc Return status code - */ -int http_open_uri ( struct interface *xfer, struct uri *uri ) { - - /* 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/src/net/tcp/iscsi.c b/src/net/tcp/iscsi.c index e36d5619d..dd20849ce 100644 --- a/src/net/tcp/iscsi.c +++ b/src/net/tcp/iscsi.c @@ -46,6 +46,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/base16.h> #include <ipxe/base64.h> #include <ipxe/ibft.h> +#include <ipxe/blockdev.h> #include <ipxe/efi/efi_path.h> #include <ipxe/iscsi.h> @@ -86,6 +87,10 @@ FEATURE ( FEATURE_PROTOCOL, "iSCSI", DHCP_EB_FEATURE_ISCSI, 1 ); __einfo_error ( EINFO_EINVAL_NO_INITIATOR_IQN ) #define EINFO_EINVAL_NO_INITIATOR_IQN \ __einfo_uniqify ( EINFO_EINVAL, 0x05, "No initiator IQN" ) +#define EINVAL_MAXBURSTLENGTH \ + __einfo_error ( EINFO_EINVAL_MAXBURSTLENGTH ) +#define EINFO_EINVAL_MAXBURSTLENGTH \ + __einfo_uniqify ( EINFO_EINVAL, 0x06, "Invalid MaxBurstLength" ) #define EIO_TARGET_UNAVAILABLE \ __einfo_error ( EINFO_EIO_TARGET_UNAVAILABLE ) #define EINFO_EIO_TARGET_UNAVAILABLE \ @@ -281,6 +286,9 @@ static int iscsi_open_connection ( struct iscsi_session *iscsi ) { /* Assign fresh initiator task tag */ iscsi_new_itt ( iscsi ); + /* Set default operational parameters */ + iscsi->max_burst_len = ISCSI_MAX_BURST_LEN; + /* Initiate login */ iscsi_start_login ( iscsi ); @@ -736,16 +744,20 @@ static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi, "MaxConnections=1%c" "InitialR2T=Yes%c" "ImmediateData=No%c" - "MaxRecvDataSegmentLength=8192%c" - "MaxBurstLength=262144%c" - "FirstBurstLength=65536%c" + "MaxRecvDataSegmentLength=%d%c" + "MaxBurstLength=%d%c" + "FirstBurstLength=%d%c" "DefaultTime2Wait=0%c" "DefaultTime2Retain=0%c" "MaxOutstandingR2T=1%c" "DataPDUInOrder=Yes%c" "DataSequenceInOrder=Yes%c" "ErrorRecoveryLevel=0%c", - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ); + 0, 0, 0, 0, 0, + ISCSI_MAX_RECV_DATA_SEG_LEN, 0, + ISCSI_MAX_BURST_LEN, 0, + ISCSI_FIRST_BURST_LEN, 0, + 0, 0, 0, 0, 0, 0 ); } return used; @@ -909,6 +921,31 @@ static int iscsi_handle_authmethod_value ( struct iscsi_session *iscsi, } /** + * Handle iSCSI MaxBurstLength text value + * + * @v iscsi iSCSI session + * @v value MaxBurstLength value + * @ret rc Return status code + */ +static int iscsi_handle_maxburstlength_value ( struct iscsi_session *iscsi, + const char *value ) { + unsigned long max_burst_len; + char *end; + + /* Update maximum burst length */ + max_burst_len = strtoul ( value, &end, 0 ); + if ( *end ) { + DBGC ( iscsi, "iSCSI %p invalid MaxBurstLength \"%s\"\n", + iscsi, value ); + return -EINVAL_MAXBURSTLENGTH; + } + if ( max_burst_len < iscsi->max_burst_len ) + iscsi->max_burst_len = max_burst_len; + + return 0; +} + +/** * Handle iSCSI CHAP_A text value * * @v iscsi iSCSI session @@ -1148,6 +1185,7 @@ struct iscsi_string_type { /** iSCSI text strings that we want to handle */ static struct iscsi_string_type iscsi_string_types[] = { { "TargetAddress", iscsi_handle_targetaddress_value }, + { "MaxBurstLength", iscsi_handle_maxburstlength_value }, { "AuthMethod", iscsi_handle_authmethod_value }, { "CHAP_A", iscsi_handle_chap_a_value }, { "CHAP_I", iscsi_handle_chap_i_value }, @@ -1848,6 +1886,24 @@ static int iscsi_scsi_command ( struct iscsi_session *iscsi, } /** + * Update SCSI block device capacity + * + * @v iscsi iSCSI session + * @v capacity Block device capacity + */ +static void iscsi_scsi_capacity ( struct iscsi_session *iscsi, + struct block_device_capacity *capacity ) { + unsigned int max_count; + + /* Limit maximum number of blocks per transfer to fit MaxBurstLength */ + if ( capacity->blksize ) { + max_count = ( iscsi->max_burst_len / capacity->blksize ); + if ( max_count < capacity->max_count ) + capacity->max_count = max_count; + } +} + +/** * Get iSCSI ACPI descriptor * * @v iscsi iSCSI session @@ -1862,6 +1918,7 @@ static struct acpi_descriptor * iscsi_describe ( struct iscsi_session *iscsi ) { static struct interface_operation iscsi_control_op[] = { INTF_OP ( scsi_command, struct iscsi_session *, iscsi_scsi_command ), INTF_OP ( xfer_window, struct iscsi_session *, iscsi_scsi_window ), + INTF_OP ( block_capacity, struct iscsi_session *, iscsi_scsi_capacity ), INTF_OP ( intf_close, struct iscsi_session *, iscsi_close ), INTF_OP ( acpi_describe, struct iscsi_session *, iscsi_describe ), EFI_INTF_OP ( efi_describe, struct iscsi_session *, efi_iscsi_path ), diff --git a/src/net/tls.c b/src/net/tls.c index 4aa4d9e29..000a8a785 100644 --- a/src/net/tls.c +++ b/src/net/tls.c @@ -86,14 +86,10 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINFO_EINVAL_HANDSHAKE \ __einfo_uniqify ( EINFO_EINVAL, 0x08, \ "Invalid Handshake record" ) -#define EINVAL_STREAM __einfo_error ( EINFO_EINVAL_STREAM ) -#define EINFO_EINVAL_STREAM \ - __einfo_uniqify ( EINFO_EINVAL, 0x09, \ - "Invalid stream-ciphered record" ) -#define EINVAL_BLOCK __einfo_error ( EINFO_EINVAL_BLOCK ) -#define EINFO_EINVAL_BLOCK \ +#define EINVAL_IV __einfo_error ( EINFO_EINVAL_IV ) +#define EINFO_EINVAL_IV \ __einfo_uniqify ( EINFO_EINVAL, 0x0a, \ - "Invalid block-ciphered record" ) + "Invalid initialisation vector" ) #define EINVAL_PADDING __einfo_error ( EINFO_EINVAL_PADDING ) #define EINFO_EINVAL_PADDING \ __einfo_uniqify ( EINFO_EINVAL, 0x0b, \ @@ -105,7 +101,7 @@ FILE_LICENCE ( GPL2_OR_LATER ); #define EINVAL_MAC __einfo_error ( EINFO_EINVAL_MAC ) #define EINFO_EINVAL_MAC \ __einfo_uniqify ( EINFO_EINVAL, 0x0d, \ - "Invalid MAC" ) + "Invalid MAC or authentication tag" ) #define EINVAL_TICKET __einfo_error ( EINFO_EINVAL_TICKET ) #define EINFO_EINVAL_TICKET \ __einfo_uniqify ( EINFO_EINVAL, 0x0e, \ @@ -387,10 +383,12 @@ static void free_tls ( struct refcnt *refcnt ) { tls_clear_cipher ( tls, &tls->rx_cipherspec ); tls_clear_cipher ( tls, &tls->rx_cipherspec_pending ); free ( tls->server_key ); + free ( tls->handshake_ctx ); list_for_each_entry_safe ( iobuf, tmp, &tls->rx_data, list ) { list_del ( &iobuf->list ); free_iob ( iobuf ); } + free_iob ( tls->rx_handshake ); x509_chain_put ( tls->certs ); x509_chain_put ( tls->chain ); x509_root_put ( tls->root ); @@ -568,8 +566,8 @@ static void tls_prf ( struct tls_connection *tls, const void *secret, va_start ( seeds, out_len ); if ( tls_version ( tls, TLS_VERSION_TLS_1_2 ) ) { - /* Use P_SHA256 for TLSv1.2 and later */ - tls_p_hash_va ( tls, &sha256_algorithm, secret, secret_len, + /* Use handshake digest PRF for TLSv1.2 and later */ + tls_p_hash_va ( tls, tls->handshake_digest, secret, secret_len, out, out_len, seeds ); } else { /* Use combination of P_MD5 and P_SHA-1 for TLSv1.1 @@ -662,9 +660,9 @@ static void tls_generate_master_secret ( struct tls_connection *tls, static int tls_generate_keys ( struct tls_connection *tls ) { struct tls_cipherspec *tx_cipherspec = &tls->tx_cipherspec_pending; struct tls_cipherspec *rx_cipherspec = &tls->rx_cipherspec_pending; - size_t hash_size = tx_cipherspec->suite->digest->digestsize; + size_t hash_size = tx_cipherspec->suite->mac_len; size_t key_size = tx_cipherspec->suite->key_len; - size_t iv_size = tx_cipherspec->suite->cipher->blocksize; + size_t iv_size = tx_cipherspec->suite->fixed_iv_len; size_t total = ( 2 * ( hash_size + key_size + iv_size ) ); uint8_t key_block[total]; uint8_t *key; @@ -716,15 +714,13 @@ static int tls_generate_keys ( struct tls_connection *tls ) { key += key_size; /* TX initialisation vector */ - cipher_setiv ( tx_cipherspec->suite->cipher, - tx_cipherspec->cipher_ctx, key ); + memcpy ( tx_cipherspec->fixed_iv, key, iv_size ); DBGC ( tls, "TLS %p TX IV:\n", tls ); DBGC_HD ( tls, key, iv_size ); key += iv_size; /* RX initialisation vector */ - cipher_setiv ( rx_cipherspec->suite->cipher, - rx_cipherspec->cipher_ctx, key ); + memcpy ( rx_cipherspec->fixed_iv, key, iv_size ); DBGC ( tls, "TLS %p RX IV:\n", tls ); DBGC_HD ( tls, key, iv_size ); key += iv_size; @@ -736,6 +732,83 @@ static int tls_generate_keys ( struct tls_connection *tls ) { /****************************************************************************** * + * Handshake verification + * + ****************************************************************************** + */ + +/** + * Clear handshake digest algorithm + * + * @v tls TLS connection + */ +static void tls_clear_handshake ( struct tls_connection *tls ) { + + /* Select null digest algorithm */ + tls->handshake_digest = &digest_null; + + /* Free any existing context */ + free ( tls->handshake_ctx ); + tls->handshake_ctx = NULL; +} + +/** + * Select handshake digest algorithm + * + * @v tls TLS connection + * @v digest Handshake digest algorithm + * @ret rc Return status code + */ +static int tls_select_handshake ( struct tls_connection *tls, + struct digest_algorithm *digest ) { + + /* Clear existing handshake digest */ + tls_clear_handshake ( tls ); + + /* Allocate and initialise context */ + tls->handshake_ctx = malloc ( digest->ctxsize ); + if ( ! tls->handshake_ctx ) + return -ENOMEM; + tls->handshake_digest = digest; + digest_init ( digest, tls->handshake_ctx ); + + return 0; +} + +/** + * Add handshake record to verification hash + * + * @v tls TLS connection + * @v data Handshake record + * @v len Length of handshake record + * @ret rc Return status code + */ +static int tls_add_handshake ( struct tls_connection *tls, + const void *data, size_t len ) { + struct digest_algorithm *digest = tls->handshake_digest; + + digest_update ( digest, tls->handshake_ctx, data, len ); + return 0; +} + +/** + * Calculate handshake verification hash + * + * @v tls TLS connection + * @v out Output buffer + * + * Calculates the digest over all handshake messages seen so far. + */ +static void tls_verify_handshake ( struct tls_connection *tls, void *out ) { + struct digest_algorithm *digest = tls->handshake_digest; + uint8_t ctx[ digest->ctxsize ]; + + memcpy ( ctx, tls->handshake_ctx, sizeof ( ctx ) ); + digest_final ( digest, ctx, out ); +} + +/****************************************************************************** + * * Cipher suite management * ****************************************************************************** @@ -801,15 +874,15 @@ static int tls_set_cipher ( struct tls_connection *tls, struct tls_cipher_suite *suite ) { struct pubkey_algorithm *pubkey = suite->pubkey; struct cipher_algorithm *cipher = suite->cipher; - struct digest_algorithm *digest = suite->digest; size_t total; void *dynamic; /* Clear out old cipher contents, if any */ tls_clear_cipher ( tls, cipherspec ); - + /* Allocate dynamic storage */ - total = ( pubkey->ctxsize + 2 * cipher->ctxsize + digest->digestsize ); + total = ( pubkey->ctxsize + cipher->ctxsize + suite->mac_len + + suite->fixed_iv_len ); dynamic = zalloc ( total ); if ( ! dynamic ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for crypto " @@ -821,8 +894,8 @@ static int tls_set_cipher ( struct tls_connection *tls, cipherspec->dynamic = dynamic; cipherspec->pubkey_ctx = dynamic; dynamic += pubkey->ctxsize; cipherspec->cipher_ctx = dynamic; dynamic += cipher->ctxsize; - cipherspec->cipher_next_ctx = dynamic; dynamic += cipher->ctxsize; - cipherspec->mac_secret = dynamic; dynamic += digest->digestsize; + cipherspec->mac_secret = dynamic; dynamic += suite->mac_len; + cipherspec->fixed_iv = dynamic; dynamic += suite->fixed_iv_len; assert ( ( cipherspec->dynamic + total ) == dynamic ); /* Store parameters */ @@ -841,6 +914,7 @@ static int tls_set_cipher ( struct tls_connection *tls, static int tls_select_cipher ( struct tls_connection *tls, unsigned int cipher_suite ) { struct tls_cipher_suite *suite; + struct digest_algorithm *digest; int rc; /* Identify cipher suite */ @@ -851,6 +925,12 @@ static int tls_select_cipher ( struct tls_connection *tls, return -ENOTSUP_CIPHER; } + /* Set handshake digest algorithm */ + digest = ( tls_version ( tls, TLS_VERSION_TLS_1_2 ) ? + suite->handshake : &md5_sha1_algorithm ); + if ( ( rc = tls_select_handshake ( tls, digest ) ) != 0 ) + return rc; + /* Set ciphers */ if ( ( rc = tls_set_cipher ( tls, &tls->tx_cipherspec_pending, suite ) ) != 0 ) @@ -964,46 +1044,6 @@ tls_signature_hash_digest ( struct tls_signature_hash_id code ) { /****************************************************************************** * - * Handshake verification - * - ****************************************************************************** - */ - -/** - * Add handshake record to verification hash - * - * @v tls TLS connection - * @v data Handshake record - * @v len Length of handshake record - */ -static void tls_add_handshake ( struct tls_connection *tls, - const void *data, size_t len ) { - - digest_update ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx, - data, len ); - digest_update ( &sha256_algorithm, tls->handshake_sha256_ctx, - data, len ); -} - -/** - * Calculate handshake verification hash - * - * @v tls TLS connection - * @v out Output buffer - * - * Calculates the MD5+SHA1 or SHA256 digest over all handshake - * messages seen so far. - */ -static void tls_verify_handshake ( struct tls_connection *tls, void *out ) { - struct digest_algorithm *digest = tls->handshake_digest; - uint8_t ctx[ digest->ctxsize ]; - - memcpy ( ctx, tls->handshake_ctx, sizeof ( ctx ) ); - digest_final ( digest, ctx, out ); -} - -/****************************************************************************** - * * Record handling * ****************************************************************************** @@ -1043,12 +1083,6 @@ static void tls_restart ( struct tls_connection *tls ) { assert ( ! is_pending ( &tls->server_negotiation ) ); assert ( ! is_pending ( &tls->validation ) ); - /* (Re)initialise handshake context */ - digest_init ( &md5_sha1_algorithm, tls->handshake_md5_sha1_ctx ); - digest_init ( &sha256_algorithm, tls->handshake_sha256_ctx ); - tls->handshake_digest = &sha256_algorithm; - tls->handshake_ctx = tls->handshake_sha256_ctx; - /* (Re)start negotiation */ tls->tx_pending = TLS_TX_CLIENT_HELLO; tls_tx_resume ( tls ); @@ -1065,7 +1099,7 @@ static void tls_restart ( struct tls_connection *tls ) { * @ret rc Return status code */ static int tls_send_handshake ( struct tls_connection *tls, - void *data, size_t len ) { + const void *data, size_t len ) { /* Add to handshake digest */ tls_add_handshake ( tls, data, len ); @@ -1075,12 +1109,16 @@ static int tls_send_handshake ( struct tls_connection *tls, } /** - * Transmit Client Hello record + * Digest or transmit Client Hello record * * @v tls TLS connection + * @v action Action to take on Client Hello record * @ret rc Return status code */ -static int tls_send_client_hello ( struct tls_connection *tls ) { +static int tls_client_hello ( struct tls_connection *tls, + int ( * action ) ( struct tls_connection *tls, + const void *data, + size_t len ) ) { struct tls_session *session = tls->session; size_t name_len = strlen ( session->name ); struct { @@ -1140,7 +1178,7 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) | htonl ( sizeof ( hello ) - sizeof ( hello.type_length ) ) ); - hello.version = htons ( tls->version ); + hello.version = htons ( TLS_VERSION_MAX ); memcpy ( &hello.random, &tls->client_random, sizeof ( hello.random ) ); hello.session_id_len = tls->session_id_len; memcpy ( hello.session_id, tls->session_id, @@ -1188,7 +1226,18 @@ static int tls_send_client_hello ( struct tls_connection *tls ) { memcpy ( hello.extensions.session_ticket.data, session->ticket, sizeof ( hello.extensions.session_ticket.data ) ); - return tls_send_handshake ( tls, &hello, sizeof ( hello ) ); + return action ( tls, &hello, sizeof ( hello ) ); +} + +/** + * Transmit Client Hello record + * + * @v tls TLS connection + * @ret rc Return status code + */ +static int tls_send_client_hello ( struct tls_connection *tls ) { + + return tls_client_hello ( tls, tls_send_handshake ); } /** @@ -1634,9 +1683,14 @@ static int tls_send_certificate_verify ( struct tls_connection *tls ) { * @ret rc Return status code */ static int tls_send_change_cipher ( struct tls_connection *tls ) { - static const uint8_t change_cipher[1] = { 1 }; + static const struct { + uint8_t spec; + } __attribute__ (( packed )) change_cipher = { + .spec = TLS_CHANGE_CIPHER_SPEC, + }; + return tls_send_plaintext ( tls, TLS_TYPE_CHANGE_CIPHER, - change_cipher, sizeof ( change_cipher ) ); + &change_cipher, sizeof ( change_cipher ) ); } /** @@ -1683,20 +1737,27 @@ static int tls_send_finished ( struct tls_connection *tls ) { * Receive new Change Cipher record * * @v tls TLS connection - * @v data Plaintext record - * @v len Length of plaintext record + * @v iobuf I/O buffer * @ret rc Return status code */ static int tls_new_change_cipher ( struct tls_connection *tls, - const void *data, size_t len ) { + struct io_buffer *iobuf ) { + const struct { + uint8_t spec; + } __attribute__ (( packed )) *change_cipher = iobuf->data; + size_t len = iob_len ( iobuf ); int rc; - if ( ( len != 1 ) || ( *( ( uint8_t * ) data ) != 1 ) ) { + /* Sanity check */ + if ( ( sizeof ( *change_cipher ) != len ) || + ( change_cipher->spec != TLS_CHANGE_CIPHER_SPEC ) ) { DBGC ( tls, "TLS %p received invalid Change Cipher\n", tls ); - DBGC_HD ( tls, data, len ); + DBGC_HD ( tls, change_cipher, len ); return -EINVAL_CHANGE_CIPHER; } + iob_pull ( iobuf, sizeof ( *change_cipher ) ); + /* Change receive cipher spec */ if ( ( rc = tls_change_cipher ( tls, &tls->rx_cipherspec_pending, &tls->rx_cipherspec ) ) != 0 ) { DBGC ( tls, "TLS %p could not activate RX cipher: %s\n", @@ -1712,25 +1773,27 @@ static int tls_new_change_cipher ( struct tls_connection *tls, * Receive new Alert record * * @v tls TLS connection - * @v data Plaintext record - * @v len Length of plaintext record + * @v iobuf I/O buffer * @ret rc Return status code */ -static int tls_new_alert ( struct tls_connection *tls, const void *data, - size_t len ) { +static int tls_new_alert ( struct tls_connection *tls, + struct io_buffer *iobuf ) { const struct { uint8_t level; uint8_t description; char next[0]; - } __attribute__ (( packed )) *alert = data; + } __attribute__ (( packed )) *alert = iobuf->data; + size_t len = iob_len ( iobuf ); /* Sanity check */ if ( sizeof ( *alert ) != len ) { DBGC ( tls, "TLS %p received overlength Alert\n", tls ); - DBGC_HD ( tls, data, len ); + DBGC_HD ( tls, alert, len ); return -EINVAL_ALERT; } + iob_pull ( iobuf, sizeof ( *alert ) ); + /* Handle alert */ switch ( alert->level ) { case TLS_ALERT_WARNING: DBGC ( tls, "TLS %p received warning alert %d\n", @@ -1898,22 +1961,18 @@ static int tls_new_server_hello ( struct tls_connection *tls, DBGC ( tls, "TLS %p using protocol version %d.%d\n", tls, ( version >> 8 ), ( version & 0xff ) ); - /* Use MD5+SHA1 digest algorithm for handshake verification - * for versions earlier than TLSv1.2. - */ - if ( ! tls_version ( tls, TLS_VERSION_TLS_1_2 ) ) { - tls->handshake_digest = &md5_sha1_algorithm; - tls->handshake_ctx = tls->handshake_md5_sha1_ctx; - } + /* Select cipher suite */ + if ( ( rc = tls_select_cipher ( tls, hello_b->cipher_suite ) ) != 0 ) + return rc; + + /* Add preceding Client Hello to handshake digest */ + if ( ( rc = tls_client_hello ( tls, tls_add_handshake ) ) != 0 ) + return rc; /* Copy out server random bytes */ memcpy ( &tls->server_random, &hello_a->random, sizeof ( tls->server_random ) ); - /* Select cipher suite */ - if ( ( rc = tls_select_cipher ( tls, hello_b->cipher_suite ) ) != 0 ) - return rc; - /* Check session ID */ if ( hello_a->session_id_len && ( hello_a->session_id_len == tls->session_id_len ) && @@ -2348,38 +2407,33 @@ static int tls_new_finished ( struct tls_connection *tls, * Receive new Handshake record * * @v tls TLS connection - * @v data Plaintext record - * @v len Length of plaintext record + * @v iobuf I/O buffer * @ret rc Return status code */ static int tls_new_handshake ( struct tls_connection *tls, - const void *data, size_t len ) { - size_t remaining = len; + struct io_buffer *iobuf ) { + size_t remaining; int rc; - while ( remaining ) { + while ( ( remaining = iob_len ( iobuf ) ) ) { const struct { uint8_t type; tls24_t length; uint8_t payload[0]; - } __attribute__ (( packed )) *handshake = data; + } __attribute__ (( packed )) *handshake = iobuf->data; const void *payload; size_t payload_len; size_t record_len; /* Parse header */ if ( sizeof ( *handshake ) > remaining ) { - DBGC ( tls, "TLS %p received underlength Handshake\n", - tls ); - DBGC_HD ( tls, data, remaining ); - return -EINVAL_HANDSHAKE; + /* Leave remaining fragment unconsumed */ + break; } payload_len = tls_uint24 ( &handshake->length ); if ( payload_len > ( remaining - sizeof ( *handshake ) ) ) { - DBGC ( tls, "TLS %p received overlength Handshake\n", - tls ); - DBGC_HD ( tls, data, len ); - return -EINVAL_HANDSHAKE; + /* Leave remaining fragment unconsumed */ + break; } payload = &handshake->payload; record_len = ( sizeof ( *handshake ) + payload_len ); @@ -2426,65 +2480,87 @@ static int tls_new_handshake ( struct tls_connection *tls, * which are explicitly excluded). */ if ( handshake->type != TLS_HELLO_REQUEST ) - tls_add_handshake ( tls, data, record_len ); + tls_add_handshake ( tls, handshake, record_len ); /* Abort on failure */ if ( rc != 0 ) return rc; /* Move to next handshake record */ - data += record_len; - remaining -= record_len; + iob_pull ( iobuf, record_len ); } return 0; } /** - * Receive new record + * Receive new unknown record + * + * @v tls TLS connection + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int tls_new_unknown ( struct tls_connection *tls __unused, + struct io_buffer *iobuf ) { + + /* RFC4346 says that we should just ignore unknown record types */ + iob_pull ( iobuf, iob_len ( iobuf ) ); + return 0; +} + +/** + * Receive new data record * * @v tls TLS connection - * @v type Record type * @v rx_data List of received data buffers * @ret rc Return status code */ -static int tls_new_record ( struct tls_connection *tls, unsigned int type, - struct list_head *rx_data ) { +static int tls_new_data ( struct tls_connection *tls, + struct list_head *rx_data ) { struct io_buffer *iobuf; - int ( * handler ) ( struct tls_connection *tls, const void *data, - size_t len ); int rc; - /* Deliver data records to the plainstream interface */ - if ( type == TLS_TYPE_DATA ) { - - /* Fail unless we are ready to receive data */ - if ( ! tls_ready ( tls ) ) - return -ENOTCONN; - - /* Deliver each I/O buffer in turn */ - while ( ( iobuf = list_first_entry ( rx_data, struct io_buffer, - list ) ) ) { - list_del ( &iobuf->list ); - if ( ( rc = xfer_deliver_iob ( &tls->plainstream, - iobuf ) ) != 0 ) { - DBGC ( tls, "TLS %p could not deliver data: " - "%s\n", tls, strerror ( rc ) ); - return rc; - } + /* Fail unless we are ready to receive data */ + if ( ! tls_ready ( tls ) ) + return -ENOTCONN; + + /* Deliver each I/O buffer in turn */ + while ( ( iobuf = list_first_entry ( rx_data, struct io_buffer, + list ) ) ) { + list_del ( &iobuf->list ); + if ( ( rc = xfer_deliver_iob ( &tls->plainstream, + iobuf ) ) != 0 ) { + DBGC ( tls, "TLS %p could not deliver data: " + "%s\n", tls, strerror ( rc ) ); + return rc; } - return 0; } - /* For all other records, merge into a single I/O buffer */ - iobuf = iob_concatenate ( rx_data ); - if ( ! iobuf ) { - DBGC ( tls, "TLS %p could not concatenate non-data record " - "type %d\n", tls, type ); - return -ENOMEM_RX_CONCAT; - } + return 0; +} + +/** + * Receive new record + * + * @v tls TLS connection + * @v type Record type + * @v rx_data List of received data buffers + * @ret rc Return status code + */ +static int tls_new_record ( struct tls_connection *tls, unsigned int type, + struct list_head *rx_data ) { + int ( * handler ) ( struct tls_connection *tls, + struct io_buffer *iobuf ); + struct io_buffer *tmp = NULL; + struct io_buffer **iobuf; + int rc; + + /* Deliver data records as-is to the plainstream interface */ + if ( type == TLS_TYPE_DATA ) + return tls_new_data ( tls, rx_data ); - /* Determine handler */ + /* Determine handler and fragment buffer */ + iobuf = &tmp; switch ( type ) { case TLS_TYPE_CHANGE_CIPHER: handler = tls_new_change_cipher; @@ -2494,19 +2570,44 @@ static int tls_new_record ( struct tls_connection *tls, unsigned int type, break; case TLS_TYPE_HANDSHAKE: handler = tls_new_handshake; + iobuf = &tls->rx_handshake; break; default: - /* RFC4346 says that we should just ignore unknown - * record types. - */ - handler = NULL; - DBGC ( tls, "TLS %p ignoring record type %d\n", tls, type ); + DBGC ( tls, "TLS %p unknown record type %d\n", tls, type ); + handler = tls_new_unknown; break; } - /* Handle record and free I/O buffer */ - rc = ( handler ? handler ( tls, iobuf->data, iob_len ( iobuf ) ) : 0 ); - free_iob ( iobuf ); + /* Merge into a single I/O buffer */ + if ( *iobuf ) + list_add ( &(*iobuf)->list, rx_data ); + *iobuf = iob_concatenate ( rx_data ); + if ( ! *iobuf ) { + DBGC ( tls, "TLS %p could not concatenate non-data record " + "type %d\n", tls, type ); + rc = -ENOMEM_RX_CONCAT; + goto err_concatenate; + } + + /* Handle record */ + if ( ( rc = handler ( tls, *iobuf ) ) != 0 ) + goto err_handle; + + /* Discard I/O buffer if empty */ + if ( ! iob_len ( *iobuf ) ) { + free_iob ( *iobuf ); + *iobuf = NULL; + } + + /* Sanity check */ + assert ( tmp == NULL ); + + return 0; + + err_handle: + free_iob ( *iobuf ); + *iobuf = NULL; + err_concatenate: return rc; } @@ -2522,17 +2623,15 @@ static int tls_new_record ( struct tls_connection *tls, unsigned int type, * * @v cipherspec Cipher specification * @v ctx Context - * @v seq Sequence number - * @v tlshdr TLS header + * @v authhdr Authentication header */ static void tls_hmac_init ( struct tls_cipherspec *cipherspec, void *ctx, - uint64_t seq, struct tls_header *tlshdr ) { - struct digest_algorithm *digest = cipherspec->suite->digest; + struct tls_auth_header *authhdr ) { + struct tls_cipher_suite *suite = cipherspec->suite; + struct digest_algorithm *digest = suite->digest; - hmac_init ( digest, ctx, cipherspec->mac_secret, digest->digestsize ); - seq = cpu_to_be64 ( seq ); - hmac_update ( digest, ctx, &seq, sizeof ( seq ) ); - hmac_update ( digest, ctx, tlshdr, sizeof ( *tlshdr ) ); + hmac_init ( digest, ctx, cipherspec->mac_secret, suite->mac_len ); + hmac_update ( digest, ctx, authhdr, sizeof ( *authhdr ) ); } /** @@ -2568,104 +2667,43 @@ static void tls_hmac_final ( struct tls_cipherspec *cipherspec, void *ctx, * Calculate HMAC * * @v cipherspec Cipher specification - * @v seq Sequence number - * @v tlshdr TLS header + * @v authhdr Authentication header * @v data Data * @v len Length of data * @v mac HMAC to fill in */ static void tls_hmac ( struct tls_cipherspec *cipherspec, - uint64_t seq, struct tls_header *tlshdr, + struct tls_auth_header *authhdr, const void *data, size_t len, void *hmac ) { struct digest_algorithm *digest = cipherspec->suite->digest; uint8_t ctx[ hmac_ctxsize ( digest ) ]; - tls_hmac_init ( cipherspec, ctx, seq, tlshdr ); + tls_hmac_init ( cipherspec, ctx, authhdr ); tls_hmac_update ( cipherspec, ctx, data, len ); tls_hmac_final ( cipherspec, ctx, hmac ); } /** - * Allocate and assemble stream-ciphered record from data and MAC portions - * - * @v tls TLS connection - * @ret data Data - * @ret len Length of data - * @ret digest MAC digest - * @ret plaintext_len Length of plaintext record - * @ret plaintext Allocated plaintext record - */ -static void * __malloc -tls_assemble_stream ( struct tls_connection *tls, const void *data, size_t len, - void *digest, size_t *plaintext_len ) { - size_t mac_len = tls->tx_cipherspec.suite->digest->digestsize; - void *plaintext; - void *content; - void *mac; - - /* Calculate stream-ciphered struct length */ - *plaintext_len = ( len + mac_len ); - - /* Allocate stream-ciphered struct */ - plaintext = malloc ( *plaintext_len ); - if ( ! plaintext ) - return NULL; - content = plaintext; - mac = ( content + len ); - - /* Fill in stream-ciphered struct */ - memcpy ( content, data, len ); - memcpy ( mac, digest, mac_len ); - - return plaintext; -} - -/** - * Allocate and assemble block-ciphered record from data and MAC portions + * Calculate HMAC over list of I/O buffers * - * @v tls TLS connection - * @ret data Data - * @ret len Length of data - * @ret digest MAC digest - * @ret plaintext_len Length of plaintext record - * @ret plaintext Allocated plaintext record - */ -static void * tls_assemble_block ( struct tls_connection *tls, - const void *data, size_t len, - void *digest, size_t *plaintext_len ) { - size_t blocksize = tls->tx_cipherspec.suite->cipher->blocksize; - size_t mac_len = tls->tx_cipherspec.suite->digest->digestsize; - size_t iv_len; - size_t padding_len; - void *plaintext; - void *iv; - void *content; - void *mac; - void *padding; - - /* TLSv1.1 and later use an explicit IV */ - iv_len = ( tls_version ( tls, TLS_VERSION_TLS_1_1 ) ? blocksize : 0 ); - - /* Calculate block-ciphered struct length */ - padding_len = ( ( blocksize - 1 ) & -( iv_len + len + mac_len + 1 ) ); - *plaintext_len = ( iv_len + len + mac_len + padding_len + 1 ); - - /* Allocate block-ciphered struct */ - plaintext = malloc ( *plaintext_len ); - if ( ! plaintext ) - return NULL; - iv = plaintext; - content = ( iv + iv_len ); - mac = ( content + len ); - padding = ( mac + mac_len ); - - /* Fill in block-ciphered struct */ - tls_generate_random ( tls, iv, iv_len ); - memcpy ( content, data, len ); - memcpy ( mac, digest, mac_len ); - memset ( padding, padding_len, ( padding_len + 1 ) ); + * @v cipherspec Cipher specification + * @v authhdr Authentication header + * @v list List of I/O buffers + * @v mac HMAC to fill in + */ +static void tls_hmac_list ( struct tls_cipherspec *cipherspec, + struct tls_auth_header *authhdr, + struct list_head *list, void *hmac ) { + struct digest_algorithm *digest = cipherspec->suite->digest; + uint8_t ctx[ hmac_ctxsize ( digest ) ]; + struct io_buffer *iobuf; - return plaintext; + tls_hmac_init ( cipherspec, ctx, authhdr ); + list_for_each_entry ( iobuf, list, list ) { + tls_hmac_update ( cipherspec, ctx, iobuf->data, + iob_len ( iobuf ) ); + } + tls_hmac_final ( cipherspec, ctx, hmac ); } /** @@ -2679,34 +2717,47 @@ static void * tls_assemble_block ( struct tls_connection *tls, */ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, const void *data, size_t len ) { - struct tls_header plaintext_tlshdr; - struct tls_header *tlshdr; struct tls_cipherspec *cipherspec = &tls->tx_cipherspec; - struct cipher_algorithm *cipher = cipherspec->suite->cipher; + struct tls_cipher_suite *suite = cipherspec->suite; + struct cipher_algorithm *cipher = suite->cipher; + struct digest_algorithm *digest = suite->digest; + struct { + uint8_t fixed[suite->fixed_iv_len]; + uint8_t record[suite->record_iv_len]; + } __attribute__ (( packed )) iv; + struct tls_auth_header authhdr; + struct tls_header *tlshdr; void *plaintext = NULL; - size_t plaintext_len; + size_t plaintext_len = len; struct io_buffer *ciphertext = NULL; size_t ciphertext_len; - size_t mac_len = cipherspec->suite->digest->digestsize; - uint8_t mac[mac_len]; + size_t padding_len; + uint8_t mac[digest->digestsize]; + void *tmp; int rc; - /* Construct header */ - plaintext_tlshdr.type = type; - plaintext_tlshdr.version = htons ( tls->version ); - plaintext_tlshdr.length = htons ( len ); - - /* Calculate MAC */ - tls_hmac ( cipherspec, tls->tx_seq, &plaintext_tlshdr, data, len, mac ); - - /* Allocate and assemble plaintext struct */ - if ( is_stream_cipher ( cipher ) ) { - plaintext = tls_assemble_stream ( tls, data, len, mac, - &plaintext_len ); + /* Construct initialisation vector */ + memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) ); + tls_generate_random ( tls, iv.record, sizeof ( iv.record ) ); + + /* Construct authentication data */ + authhdr.seq = cpu_to_be64 ( tls->tx_seq ); + authhdr.header.type = type; + authhdr.header.version = htons ( tls->version ); + authhdr.header.length = htons ( len ); + + /* Calculate padding length */ + plaintext_len += suite->mac_len; + if ( is_block_cipher ( cipher ) ) { + padding_len = ( ( ( cipher->blocksize - 1 ) & + -( plaintext_len + 1 ) ) + 1 ); } else { - plaintext = tls_assemble_block ( tls, data, len, mac, - &plaintext_len ); + padding_len = 0; } + plaintext_len += padding_len; + + /* Allocate plaintext */ + plaintext = malloc ( plaintext_len ); if ( ! plaintext ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for " "plaintext\n", tls, plaintext_len ); @@ -2714,11 +2765,32 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, goto done; } + /* Assemble plaintext */ + tmp = plaintext; + memcpy ( tmp, data, len ); + tmp += len; + if ( suite->mac_len ) + tls_hmac ( cipherspec, &authhdr, data, len, mac ); + memcpy ( tmp, mac, suite->mac_len ); + tmp += suite->mac_len; + memset ( tmp, ( padding_len - 1 ), padding_len ); + tmp += padding_len; + assert ( tmp == ( plaintext + plaintext_len ) ); DBGC2 ( tls, "Sending plaintext data:\n" ); DBGC2_HD ( tls, plaintext, plaintext_len ); + /* Set initialisation vector */ + cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) ); + + /* Process authentication data, if applicable */ + if ( is_auth_cipher ( cipher ) ) { + cipher_encrypt ( cipher, cipherspec->cipher_ctx, &authhdr, + NULL, sizeof ( authhdr ) ); + } + /* Allocate ciphertext */ - ciphertext_len = ( sizeof ( *tlshdr ) + plaintext_len ); + ciphertext_len = ( sizeof ( *tlshdr ) + sizeof ( iv.record ) + + plaintext_len + cipher->authsize ); ciphertext = xfer_alloc_iob ( &tls->cipherstream, ciphertext_len ); if ( ! ciphertext ) { DBGC ( tls, "TLS %p could not allocate %zd bytes for " @@ -2731,11 +2803,14 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) ); tlshdr->type = type; tlshdr->version = htons ( tls->version ); - tlshdr->length = htons ( plaintext_len ); - memcpy ( cipherspec->cipher_next_ctx, cipherspec->cipher_ctx, - cipher->ctxsize ); - cipher_encrypt ( cipher, cipherspec->cipher_next_ctx, plaintext, + tlshdr->length = htons ( ciphertext_len - sizeof ( *tlshdr ) ); + memcpy ( iob_put ( ciphertext, sizeof ( iv.record ) ), iv.record, + sizeof ( iv.record ) ); + cipher_encrypt ( cipher, cipherspec->cipher_ctx, plaintext, iob_put ( ciphertext, plaintext_len ), plaintext_len ); + cipher_auth ( cipher, cipherspec->cipher_ctx, + iob_put ( ciphertext, cipher->authsize ) ); + assert ( iob_len ( ciphertext ) == ciphertext_len ); /* Free plaintext as soon as possible to conserve memory */ free ( plaintext ); @@ -2751,8 +2826,6 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, /* Update TX state machine to next record */ tls->tx_seq += 1; - memcpy ( tls->tx_cipherspec.cipher_ctx, - tls->tx_cipherspec.cipher_next_ctx, cipher->ctxsize ); done: free ( plaintext ); @@ -2761,88 +2834,38 @@ static int tls_send_plaintext ( struct tls_connection *tls, unsigned int type, } /** - * Split stream-ciphered record into data and MAC portions + * Verify block padding * * @v tls TLS connection - * @v rx_data List of received data buffers - * @v mac MAC to fill in + * @v iobuf Last received I/O buffer + * @ret len Padding length, or negative error * @ret rc Return status code */ -static int tls_split_stream ( struct tls_connection *tls, - struct list_head *rx_data, void **mac ) { - size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize; - struct io_buffer *iobuf; - - /* Extract MAC */ - iobuf = list_last_entry ( rx_data, struct io_buffer, list ); - assert ( iobuf != NULL ); - if ( iob_len ( iobuf ) < mac_len ) { - DBGC ( tls, "TLS %p received underlength MAC\n", tls ); - DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_STREAM; - } - iob_unput ( iobuf, mac_len ); - *mac = iobuf->tail; - - return 0; -} - -/** - * Split block-ciphered record into data and MAC portions - * - * @v tls TLS connection - * @v rx_data List of received data buffers - * @v mac MAC to fill in - * @ret rc Return status code - */ -static int tls_split_block ( struct tls_connection *tls, - struct list_head *rx_data, void **mac ) { - size_t mac_len = tls->rx_cipherspec.suite->digest->digestsize; - struct io_buffer *iobuf; - size_t iv_len; - uint8_t *padding_final; +static int tls_verify_padding ( struct tls_connection *tls, + struct io_buffer *iobuf ) { uint8_t *padding; - size_t padding_len; - - /* TLSv1.1 and later use an explicit IV */ - iobuf = list_first_entry ( rx_data, struct io_buffer, list ); - iv_len = ( tls_version ( tls, TLS_VERSION_TLS_1_1 ) ? - tls->rx_cipherspec.suite->cipher->blocksize : 0 ); - if ( iob_len ( iobuf ) < iv_len ) { - DBGC ( tls, "TLS %p received underlength IV\n", tls ); - DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_BLOCK; - } - iob_pull ( iobuf, iv_len ); + unsigned int pad; + unsigned int i; + size_t len; /* Extract and verify padding */ - iobuf = list_last_entry ( rx_data, struct io_buffer, list ); - padding_final = ( iobuf->tail - 1 ); - padding_len = *padding_final; - if ( ( padding_len + 1 ) > iob_len ( iobuf ) ) { + padding = ( iobuf->tail - 1 ); + pad = *padding; + len = ( pad + 1 ); + if ( len > iob_len ( iobuf ) ) { DBGC ( tls, "TLS %p received underlength padding\n", tls ); DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_BLOCK; + return -EINVAL_PADDING; } - iob_unput ( iobuf, ( padding_len + 1 ) ); - for ( padding = iobuf->tail ; padding < padding_final ; padding++ ) { - if ( *padding != padding_len ) { + for ( i = 0 ; i < pad ; i++ ) { + if ( *(--padding) != pad ) { DBGC ( tls, "TLS %p received bad padding\n", tls ); - DBGC_HD ( tls, padding, padding_len ); + DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); return -EINVAL_PADDING; } } - /* Extract MAC */ - if ( iob_len ( iobuf ) < mac_len ) { - DBGC ( tls, "TLS %p received underlength MAC\n", tls ); - DBGC_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - return -EINVAL_BLOCK; - } - iob_unput ( iobuf, mac_len ); - *mac = iobuf->tail; - - return 0; + return len; } /** @@ -2856,54 +2879,129 @@ static int tls_split_block ( struct tls_connection *tls, static int tls_new_ciphertext ( struct tls_connection *tls, struct tls_header *tlshdr, struct list_head *rx_data ) { - struct tls_header plaintext_tlshdr; struct tls_cipherspec *cipherspec = &tls->rx_cipherspec; - struct cipher_algorithm *cipher = cipherspec->suite->cipher; - struct digest_algorithm *digest = cipherspec->suite->digest; - uint8_t ctx[ hmac_ctxsize ( digest ) ]; + struct tls_cipher_suite *suite = cipherspec->suite; + struct cipher_algorithm *cipher = suite->cipher; + struct digest_algorithm *digest = suite->digest; + size_t len = ntohs ( tlshdr->length ); + struct { + uint8_t fixed[suite->fixed_iv_len]; + uint8_t record[suite->record_iv_len]; + } __attribute__ (( packed )) iv; + struct tls_auth_header authhdr; uint8_t verify_mac[digest->digestsize]; + uint8_t verify_auth[cipher->authsize]; + struct io_buffer *first; + struct io_buffer *last; struct io_buffer *iobuf; void *mac; - size_t len = 0; + void *auth; + size_t check_len; + int pad_len; int rc; + /* Locate first and last data buffers */ + assert ( ! list_empty ( rx_data ) ); + first = list_first_entry ( rx_data, struct io_buffer, list ); + last = list_last_entry ( rx_data, struct io_buffer, list ); + + /* Extract initialisation vector */ + if ( iob_len ( first ) < sizeof ( iv.record ) ) { + DBGC ( tls, "TLS %p received underlength IV\n", tls ); + DBGC_HD ( tls, first->data, iob_len ( first ) ); + return -EINVAL_IV; + } + memcpy ( iv.fixed, cipherspec->fixed_iv, sizeof ( iv.fixed ) ); + memcpy ( iv.record, first->data, sizeof ( iv.record ) ); + iob_pull ( first, sizeof ( iv.record ) ); + len -= sizeof ( iv.record ); + + /* Extract unencrypted authentication tag */ + if ( iob_len ( last ) < cipher->authsize ) { + DBGC ( tls, "TLS %p received underlength authentication tag\n", + tls ); + DBGC_HD ( tls, last->data, iob_len ( last ) ); + return -EINVAL_MAC; + } + iob_unput ( last, cipher->authsize ); + len -= cipher->authsize; + auth = last->tail; + + /* Construct authentication data */ + authhdr.seq = cpu_to_be64 ( tls->rx_seq ); + authhdr.header.type = tlshdr->type; + authhdr.header.version = tlshdr->version; + authhdr.header.length = htons ( len ); + + /* Set initialisation vector */ + cipher_setiv ( cipher, cipherspec->cipher_ctx, &iv, sizeof ( iv ) ); + + /* Process authentication data, if applicable */ + if ( is_auth_cipher ( cipher ) ) { + cipher_decrypt ( cipher, cipherspec->cipher_ctx, &authhdr, + NULL, sizeof ( authhdr ) ); + } + /* Decrypt the received data */ + check_len = 0; list_for_each_entry ( iobuf, &tls->rx_data, list ) { cipher_decrypt ( cipher, cipherspec->cipher_ctx, iobuf->data, iobuf->data, iob_len ( iobuf ) ); + check_len += iob_len ( iobuf ); } + assert ( check_len == len ); - /* Split record into content and MAC */ - if ( is_stream_cipher ( cipher ) ) { - if ( ( rc = tls_split_stream ( tls, rx_data, &mac ) ) != 0 ) - return rc; - } else { - if ( ( rc = tls_split_block ( tls, rx_data, &mac ) ) != 0 ) - return rc; + /* Strip block padding, if applicable */ + if ( is_block_cipher ( cipher ) ) { + pad_len = tls_verify_padding ( tls, last ); + if ( pad_len < 0 ) { + /* Assume zero padding length to avoid timing attacks */ + pad_len = 0; + } + iob_unput ( last, pad_len ); + len -= pad_len; } - /* Calculate total length */ + /* Extract decrypted MAC */ + if ( iob_len ( last ) < suite->mac_len ) { + DBGC ( tls, "TLS %p received underlength MAC\n", tls ); + DBGC_HD ( tls, last->data, iob_len ( last ) ); + return -EINVAL_MAC; + } + iob_unput ( last, suite->mac_len ); + len -= suite->mac_len; + mac = last->tail; + + /* Dump received data */ DBGC2 ( tls, "Received plaintext data:\n" ); + check_len = 0; list_for_each_entry ( iobuf, rx_data, list ) { DBGC2_HD ( tls, iobuf->data, iob_len ( iobuf ) ); - len += iob_len ( iobuf ); + check_len += iob_len ( iobuf ); } + assert ( check_len == len ); + + /* Generate MAC */ + authhdr.header.length = htons ( len ); + if ( suite->mac_len ) + tls_hmac_list ( cipherspec, &authhdr, rx_data, verify_mac ); + + /* Generate authentication tag */ + cipher_auth ( cipher, cipherspec->cipher_ctx, verify_auth ); /* Verify MAC */ - plaintext_tlshdr.type = tlshdr->type; - plaintext_tlshdr.version = tlshdr->version; - plaintext_tlshdr.length = htons ( len ); - tls_hmac_init ( cipherspec, ctx, tls->rx_seq, &plaintext_tlshdr ); - list_for_each_entry ( iobuf, rx_data, list ) { - tls_hmac_update ( cipherspec, ctx, iobuf->data, - iob_len ( iobuf ) ); - } - tls_hmac_final ( cipherspec, ctx, verify_mac ); - if ( memcmp ( mac, verify_mac, sizeof ( verify_mac ) ) != 0 ) { + if ( memcmp ( mac, verify_mac, suite->mac_len ) != 0 ) { DBGC ( tls, "TLS %p failed MAC verification\n", tls ); return -EINVAL_MAC; } + /* Verify authentication tag */ + if ( memcmp ( auth, verify_auth, cipher->authsize ) != 0 ) { + DBGC ( tls, "TLS %p failed authentication tag verification\n", + tls ); + return -EINVAL_MAC; + } + /* Process plaintext record */ if ( ( rc = tls_new_record ( tls, tlshdr->type, rx_data ) ) != 0 ) return rc; @@ -3008,13 +3106,24 @@ static struct interface_descriptor tls_plainstream_desc = * @ret rc Returned status code */ static int tls_newdata_process_header ( struct tls_connection *tls ) { + struct tls_cipherspec *cipherspec = &tls->rx_cipherspec; + struct cipher_algorithm *cipher = cipherspec->suite->cipher; + size_t iv_len = cipherspec->suite->record_iv_len; size_t data_len = ntohs ( tls->rx_header.length ); size_t remaining = data_len; size_t frag_len; + size_t reserve; struct io_buffer *iobuf; struct io_buffer *tmp; int rc; + /* Sanity check */ + assert ( ( TLS_RX_BUFSIZE % cipher->alignsize ) == 0 ); + + /* Calculate alignment reservation at start of first data buffer */ + reserve = ( ( -iv_len ) & ( cipher->alignsize - 1 ) ); + remaining += reserve; + /* Allocate data buffers now that we know the length */ assert ( list_empty ( &tls->rx_data ) ); while ( remaining ) { @@ -3049,6 +3158,13 @@ static int tls_newdata_process_header ( struct tls_connection *tls ) { */ iob_reserve ( iobuf, ( iob_tailroom ( iobuf ) - frag_len ) ); + /* Ensure first buffer length will be aligned to a + * multiple of the cipher alignment size after + * stripping the record IV. + */ + iob_reserve ( iobuf, reserve ); + reserve = 0; + /* Add I/O buffer to list */ list_add_tail ( &iobuf->list, &tls->rx_data ); } @@ -3495,6 +3611,7 @@ int add_tls ( struct interface *xfer, const char *name, tls_clear_cipher ( tls, &tls->tx_cipherspec_pending ); tls_clear_cipher ( tls, &tls->rx_cipherspec ); tls_clear_cipher ( tls, &tls->rx_cipherspec_pending ); + tls_clear_handshake ( tls ); tls->client_random.gmt_unix_time = time ( NULL ); iob_populate ( &tls->rx_header_iobuf, &tls->rx_header, 0, sizeof ( tls->rx_header ) ); diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index a335a778a..bd2c4a197 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -46,7 +46,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/dhcp.h> #include <ipxe/dhcpopts.h> #include <ipxe/dhcppkt.h> -#include <ipxe/dhcp_arch.h> +#include <ipxe/dhcparch.h> #include <ipxe/features.h> #include <config/dhcp.h> @@ -571,6 +571,10 @@ static void dhcp_request_rx ( struct dhcp_session *dhcp, if ( peer->sin_port != htons ( BOOTPS_PORT ) ) return; + /* Filter out non-selected servers */ + if ( server_id.s_addr != dhcp->server.s_addr ) + return; + /* Handle DHCPNAK */ if ( msgtype == DHCPNAK ) { dhcp_defer ( dhcp ); @@ -580,8 +584,6 @@ static void dhcp_request_rx ( struct dhcp_session *dhcp, /* Filter out unacceptable responses */ if ( msgtype /* BOOTP */ && ( msgtype != DHCPACK ) ) return; - if ( server_id.s_addr != dhcp->server.s_addr ) - return; if ( ip.s_addr != dhcp->offer.s_addr ) return; @@ -599,6 +601,12 @@ static void dhcp_request_rx ( struct dhcp_session *dhcp, return; } + /* Unregister any existing ProxyDHCP or PXEBS settings */ + if ( ( settings = find_settings ( PROXYDHCP_SETTINGS_NAME ) ) != NULL ) + unregister_settings ( settings ); + if ( ( settings = find_settings ( PXEBS_SETTINGS_NAME ) ) != NULL ) + unregister_settings ( settings ); + /* Perform ProxyDHCP if applicable */ if ( dhcp->proxy_offer /* Have ProxyDHCP offer */ && ( ! dhcp->no_pxedhcp ) /* ProxyDHCP not disabled */ ) { diff --git a/src/net/udp/dhcpv6.c b/src/net/udp/dhcpv6.c index 253032e4e..9e27dec6f 100644 --- a/src/net/udp/dhcpv6.c +++ b/src/net/udp/dhcpv6.c @@ -40,7 +40,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #include <ipxe/crc32.h> #include <ipxe/errortab.h> #include <ipxe/ipv6.h> -#include <ipxe/dhcp_arch.h> +#include <ipxe/dhcparch.h> #include <ipxe/dhcpv6.h> /** @file @@ -955,7 +955,7 @@ int start_dhcpv6 ( struct interface *job, struct net_device *netdev, addresses.client.sin6.sin6_port = htons ( DHCPV6_CLIENT_PORT ); addresses.server.sin6.sin6_family = AF_INET6; ipv6_all_dhcp_relay_and_servers ( &addresses.server.sin6.sin6_addr ); - addresses.server.sin6.sin6_scope_id = netdev->index; + addresses.server.sin6.sin6_scope_id = netdev->scope_id; addresses.server.sin6.sin6_port = htons ( DHCPV6_SERVER_PORT ); /* Construct client DUID from system UUID */ diff --git a/src/net/vlan.c b/src/net/vlan.c index 90f2934de..d73a95711 100644 --- a/src/net/vlan.c +++ b/src/net/vlan.c @@ -55,6 +55,12 @@ struct vlan_device { unsigned int priority; }; +/** Automatic VLAN device link-layer address */ +static uint8_t vlan_auto_ll_addr[ETH_ALEN]; + +/** Automatic VLAN tag */ +static unsigned int vlan_auto_tag; + /** * Open VLAN device * @@ -199,8 +205,7 @@ static void vlan_sync ( struct net_device *netdev ) { * @v tag VLAN tag * @ret netdev VLAN device, if any */ -static struct net_device * vlan_find ( struct net_device *trunk, - unsigned int tag ) { +struct net_device * vlan_find ( struct net_device *trunk, unsigned int tag ) { struct net_device *netdev; struct vlan_device *vlan; @@ -288,17 +293,17 @@ struct net_protocol vlan_protocol __net_protocol = { }; /** - * Get the VLAN tag + * Get the VLAN tag control information * * @v netdev Network device - * @ret tag VLAN tag, or 0 if device is not a VLAN device + * @ret tci VLAN tag control information, or 0 if not a VLAN device */ -unsigned int vlan_tag ( struct net_device *netdev ) { +unsigned int vlan_tci ( struct net_device *netdev ) { struct vlan_device *vlan; if ( netdev->op == &vlan_operations ) { vlan = netdev->priv; - return vlan->tag; + return ( VLAN_TCI ( vlan->tag, vlan->priority ) ); } else { return 0; } @@ -449,6 +454,47 @@ int vlan_destroy ( struct net_device *netdev ) { } /** + * Configure automatic VLAN device + * + * @v ll_addr Link-layer address + * @v tag VLAN tag + */ +void vlan_auto ( const void *ll_addr, unsigned int tag ) { + + /* Record link-layer address and VLAN tag */ + memcpy ( vlan_auto_ll_addr, ll_addr, ETH_ALEN ); + vlan_auto_tag = tag; +} + +/** + * Create automatic VLAN device + * + * @v trunk Trunk network device + * @ret rc Return status code + */ +static int vlan_probe ( struct net_device *trunk ) { + int rc; + + /* Do nothing unless an automatic VLAN exists */ + if ( ! vlan_auto_tag ) + return 0; + + /* Ignore non-trunk devices */ + if ( ! vlan_can_be_trunk ( trunk ) ) + return 0; + + /* Ignore non-matching link-layer addresses */ + if ( memcmp ( trunk->ll_addr, vlan_auto_ll_addr, ETH_ALEN ) != 0 ) + return 0; + + /* Create automatic VLAN device */ + if ( ( rc = vlan_create ( trunk, vlan_auto_tag, 0 ) ) != 0 ) + return rc; + + return 0; +} + +/** * Handle trunk network device link state change * * @v trunk Trunk network device @@ -504,6 +550,7 @@ static void vlan_remove ( struct net_device *trunk ) { /** VLAN driver */ struct net_driver vlan_driver __net_driver = { .name = "VLAN", + .probe = vlan_probe, .notify = vlan_notify, .remove = vlan_remove, }; |
