diff options
| author | Holger Lubitz | 2007-08-02 00:13:40 +0200 |
|---|---|---|
| committer | Holger Lubitz | 2007-08-02 00:13:40 +0200 |
| commit | 58f5565eb04f08c51b4f123930294c3d033e3a8e (patch) | |
| tree | 6df9ba9712544bad49f991006471f7e6c6511f84 /src/net | |
| parent | make bcopy use memmove (diff) | |
| parent | Initrd concatenation now working (diff) | |
| download | ipxe-58f5565eb04f08c51b4f123930294c3d033e3a8e.tar.gz ipxe-58f5565eb04f08c51b4f123930294c3d033e3a8e.tar.xz ipxe-58f5565eb04f08c51b4f123930294c3d033e3a8e.zip | |
Merge branch 'master' into strings
Diffstat (limited to 'src/net')
| -rw-r--r-- | src/net/aoe.c | 130 | ||||
| -rw-r--r-- | src/net/ndp.c | 5 | ||||
| -rw-r--r-- | src/net/tcp/http.c | 45 | ||||
| -rw-r--r-- | src/net/tcp/https.c | 46 | ||||
| -rw-r--r-- | src/net/tcp/iscsi.c | 116 | ||||
| -rw-r--r-- | src/net/tls.c | 1733 | ||||
| -rw-r--r-- | src/net/udp/dhcp.c | 166 |
7 files changed, 2131 insertions, 110 deletions
diff --git a/src/net/aoe.c b/src/net/aoe.c index 36721df64..fd82665d1 100644 --- a/src/net/aoe.c +++ b/src/net/aoe.c @@ -19,6 +19,7 @@ #include <stddef.h> #include <string.h> #include <stdio.h> +#include <stdlib.h> #include <errno.h> #include <assert.h> #include <byteswap.h> @@ -29,7 +30,7 @@ #include <gpxe/uaccess.h> #include <gpxe/ata.h> #include <gpxe/netdevice.h> -#include <gpxe/async.h> +#include <gpxe/process.h> #include <gpxe/aoe.h> /** @file @@ -43,6 +44,14 @@ struct net_protocol aoe_protocol; /** List of all AoE sessions */ static LIST_HEAD ( aoe_sessions ); +static void aoe_free ( struct refcnt *refcnt ) { + struct aoe_session *aoe = + container_of ( refcnt, struct aoe_session, refcnt ); + + netdev_put ( aoe->netdev ); + free ( aoe ); +} + /** * Mark current AoE command complete * @@ -55,8 +64,8 @@ static void aoe_done ( struct aoe_session *aoe, int rc ) { aoe->command->cb.cmd_stat = aoe->status; aoe->command = NULL; - /* Mark async operation as complete */ - async_done ( &aoe->async, rc ); + /* Mark operation as complete */ + aoe->rc = rc; } /** @@ -266,45 +275,98 @@ struct net_protocol aoe_protocol __net_protocol = { }; /** - * Open AoE session - * - * @v aoe AoE session - */ -void aoe_open ( struct aoe_session *aoe ) { - memcpy ( aoe->target, ethernet_protocol.ll_broadcast, - sizeof ( aoe->target ) ); - aoe->tag = AOE_TAG_MAGIC; - aoe->timer.expired = aoe_timer_expired; - list_add ( &aoe->list, &aoe_sessions ); -} - -/** - * Close AoE session - * - * @v aoe AoE session - */ -void aoe_close ( struct aoe_session *aoe ) { - list_del ( &aoe->list ); -} - -/** * Issue ATA command via an open AoE session * - * @v aoe AoE session + * @v ata ATA device * @v command ATA command - * @v parent Parent asynchronous operation * @ret rc Return status code - * - * Only one command may be issued concurrently per session. This call - * is non-blocking; use async_wait() to wait for the command to - * complete. */ -int aoe_issue ( struct aoe_session *aoe, struct ata_command *command, - struct async *parent ) { +static int aoe_command ( struct ata_device *ata, + struct ata_command *command ) { + struct aoe_session *aoe = + container_of ( ata->backend, struct aoe_session, refcnt ); + int rc; + aoe->command = command; aoe->status = 0; aoe->command_offset = 0; aoe_send_command ( aoe ); - async_init ( &aoe->async, &default_async_operations, parent ); + + aoe->rc = -EINPROGRESS; + while ( aoe->rc == -EINPROGRESS ) + step(); + rc = aoe->rc; + + return rc; +} + +static int aoe_detached_command ( struct ata_device *ata __unused, + struct ata_command *command __unused ) { + return -ENODEV; +} + +void aoe_detach ( struct ata_device *ata ) { + struct aoe_session *aoe = + container_of ( ata->backend, struct aoe_session, refcnt ); + + stop_timer ( &aoe->timer ); + ata->command = aoe_detached_command; + list_del ( &aoe->list ); + ref_put ( ata->backend ); + ata->backend = NULL; +} + +static int aoe_parse_root_path ( struct aoe_session *aoe, + const char *root_path ) { + char *ptr; + + if ( strncmp ( root_path, "aoe:", 4 ) != 0 ) + return -EINVAL; + ptr = ( ( char * ) root_path + 4 ); + + if ( *ptr++ != 'e' ) + return -EINVAL; + + aoe->major = strtoul ( ptr, &ptr, 10 ); + if ( *ptr++ != '.' ) + return -EINVAL; + + aoe->minor = strtoul ( ptr, &ptr, 10 ); + if ( *ptr ) + return -EINVAL; + return 0; } + +int aoe_attach ( struct ata_device *ata, struct net_device *netdev, + const char *root_path ) { + struct aoe_session *aoe; + int rc; + + /* Allocate and initialise structure */ + aoe = zalloc ( sizeof ( *aoe ) ); + if ( ! aoe ) + return -ENOMEM; + aoe->refcnt.free = aoe_free; + aoe->netdev = netdev_get ( netdev ); + memcpy ( aoe->target, ethernet_protocol.ll_broadcast, + sizeof ( aoe->target ) ); + aoe->tag = AOE_TAG_MAGIC; + aoe->timer.expired = aoe_timer_expired; + + /* Parse root path */ + if ( ( rc = aoe_parse_root_path ( aoe, root_path ) ) != 0 ) + goto err; + + /* Attach parent interface, transfer reference to connection + * list, and return + */ + ata->backend = ref_get ( &aoe->refcnt ); + ata->command = aoe_command; + list_add ( &aoe->list, &aoe_sessions ); + return 0; + + err: + ref_put ( &aoe->refcnt ); + return rc; +} diff --git a/src/net/ndp.c b/src/net/ndp.c index 7c684e7c1..3b6984db8 100644 --- a/src/net/ndp.c +++ b/src/net/ndp.c @@ -65,8 +65,9 @@ ndp_find_entry ( struct in6_addr *in6 ) { * @v ll_addr Link-layer address * @v state State of the entry - one of the NDP_STATE_XXX values */ -void add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6, - void *ll_addr, int state ) { +static void +add_ndp_entry ( struct net_device *netdev, struct in6_addr *in6, + void *ll_addr, int state ) { struct ndp_entry *ndp; ndp = &ndp_table[next_new_ndp_entry++ % NUM_NDP_ENTRIES]; diff --git a/src/net/tcp/http.c b/src/net/tcp/http.c index 88958af5a..727c03334 100644 --- a/src/net/tcp/http.c +++ b/src/net/tcp/http.c @@ -40,7 +40,6 @@ #include <gpxe/tcpip.h> #include <gpxe/process.h> #include <gpxe/linebuf.h> -#include <gpxe/tls.h> #include <gpxe/http.h> /** HTTP receive state */ @@ -459,15 +458,21 @@ static struct xfer_interface_operations http_xfer_operations = { }; /** - * Initiate an HTTP connection + * Initiate an HTTP connection, with optional filter * * @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 * @ret rc Return status code */ -int http_open ( struct xfer_interface *xfer, struct uri *uri ) { +int http_open_filter ( struct xfer_interface *xfer, struct uri *uri, + unsigned int default_port, + int ( * filter ) ( struct xfer_interface *xfer, + struct xfer_interface **next ) ) { struct http_request *http; struct sockaddr_tcpip server; + struct xfer_interface *socket; int rc; /* Sanity checks */ @@ -486,20 +491,17 @@ int http_open ( struct xfer_interface *xfer, struct uri *uri ) { /* Open socket */ memset ( &server, 0, sizeof ( server ) ); - server.st_port = htons ( uri_port ( http->uri, HTTP_PORT ) ); - if ( ( rc = xfer_open_named_socket ( &http->socket, SOCK_STREAM, + server.st_port = htons ( uri_port ( http->uri, default_port ) ); + socket = &http->socket; + if ( filter ) { + if ( ( rc = filter ( socket, &socket ) ) != 0 ) + goto err; + } + if ( ( rc = xfer_open_named_socket ( socket, SOCK_STREAM, ( struct sockaddr * ) &server, uri->host, NULL ) ) != 0 ) goto err; -#if 0 - if ( strcmp ( http->uri->scheme, "https" ) == 0 ) { - st->st_port = htons ( uri_port ( http->uri, HTTPS_PORT ) ); - if ( ( rc = add_tls ( &http->stream ) ) != 0 ) - goto err; - } -#endif - /* Attach to parent interface, mortalise self, and return */ xfer_plug_plug ( &http->xfer, xfer ); ref_put ( &http->refcnt ); @@ -513,14 +515,19 @@ int http_open ( struct xfer_interface *xfer, struct uri *uri ) { return rc; } +/** + * Initiate an HTTP connection + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int http_open ( struct xfer_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, }; - -/** HTTPS URI opener */ -struct uri_opener https_uri_opener __uri_opener = { - .scheme = "https", - .open = http_open, -}; diff --git a/src/net/tcp/https.c b/src/net/tcp/https.c new file mode 100644 index 000000000..148e4bf01 --- /dev/null +++ b/src/net/tcp/https.c @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2007 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * Secure Hyper Text Transfer Protocol (HTTPS) + * + */ + +#include <stddef.h> +#include <gpxe/open.h> +#include <gpxe/tls.h> +#include <gpxe/http.h> + +/** + * Initiate an HTTPS connection + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int https_open ( struct xfer_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, +}; diff --git a/src/net/tcp/iscsi.c b/src/net/tcp/iscsi.c index 273f0d68c..e64591713 100644 --- a/src/net/tcp/iscsi.c +++ b/src/net/tcp/iscsi.c @@ -41,16 +41,16 @@ */ /** iSCSI initiator name (explicitly specified) */ -char *iscsi_initiator_iqn; +static char *iscsi_explicit_initiator_iqn; /** Default iSCSI initiator name (constructed from hostname) */ -char *iscsi_default_initiator_iqn; +static char *iscsi_default_initiator_iqn; /** iSCSI username */ -char *iscsi_username; +static char *iscsi_username; /** iSCSI password */ -char *iscsi_password; +static char *iscsi_password; static void iscsi_start_tx ( struct iscsi_session *iscsi ); static void iscsi_start_login ( struct iscsi_session *iscsi ); @@ -78,6 +78,8 @@ static void iscsi_free ( struct refcnt *refcnt ) { free ( iscsi->target_address ); free ( iscsi->target_iqn ); + free ( iscsi->username ); + free ( iscsi->password ); chap_finish ( &iscsi->chap ); iscsi_rx_buffered_data_done ( iscsi ); free ( iscsi ); @@ -436,22 +438,16 @@ static int iscsi_tx_data_out ( struct iscsi_session *iscsi ) { */ static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi, void *data, size_t len ) { - char *initiator_iqn; unsigned int used = 0; unsigned int i; if ( iscsi->status & ISCSI_STATUS_STRINGS_SECURITY ) { - initiator_iqn = iscsi_initiator_iqn; - if ( ! initiator_iqn ) - initiator_iqn = iscsi_default_initiator_iqn; - if ( ! initiator_iqn ) - initiator_iqn = "iqn.2000-09.org.etherboot:UNKNOWN"; used += ssnprintf ( data + used, len - used, "InitiatorName=%s%c" "TargetName=%s%c" "SessionType=Normal%c" "AuthMethod=CHAP,None%c", - initiator_iqn, 0, + iscsi_initiator_iqn(), 0, iscsi->target_iqn, 0, 0, 0 ); } @@ -460,10 +456,10 @@ static int iscsi_build_login_request_strings ( struct iscsi_session *iscsi, } if ( ( iscsi->status & ISCSI_STATUS_STRINGS_CHAP_RESPONSE ) && - iscsi_username ) { + iscsi->username ) { used += ssnprintf ( data + used, len - used, "CHAP_N=%s%cCHAP_R=0x", - iscsi_username, 0 ); + iscsi->username, 0 ); for ( i = 0 ; i < iscsi->chap.response_len ; i++ ) { used += ssnprintf ( data + used, len - used, "%02x", iscsi->chap.response[i] ); @@ -647,9 +643,9 @@ static int iscsi_handle_chap_i_value ( struct iscsi_session *iscsi, * challenge. */ chap_set_identifier ( &iscsi->chap, identifier ); - if ( iscsi_password ) { - chap_update ( &iscsi->chap, iscsi_password, - strlen ( iscsi_password ) ); + if ( iscsi->password ) { + chap_update ( &iscsi->chap, iscsi->password, + strlen ( iscsi->password ) ); } return 0; @@ -1279,10 +1275,43 @@ static void iscsi_socket_close ( struct xfer_interface *socket, int rc ) { } } +/** + * Handle redirection event + * + * @v socket Transport layer interface + * @v type Location type + * @v args Remaining arguments depend upon location type + * @ret rc Return status code + */ +static int iscsi_vredirect ( struct xfer_interface *socket, int type, + va_list args ) { + struct iscsi_session *iscsi = + container_of ( socket, struct iscsi_session, socket ); + va_list tmp; + struct sockaddr *peer; + + /* Intercept redirects to a LOCATION_SOCKET and record the IP + * address for the iBFT. This is a bit of a hack, but avoids + * inventing an ioctl()-style call to retrieve the socket + * address from a data-xfer interface. + */ + if ( type == LOCATION_SOCKET ) { + va_copy ( tmp, args ); + ( void ) va_arg ( tmp, int ); /* Discard "semantics" */ + peer = va_arg ( tmp, struct sockaddr * ); + memcpy ( &iscsi->target_sockaddr, peer, + sizeof ( iscsi->target_sockaddr ) ); + va_end ( tmp ); + } + + return xfer_vopen ( socket, type, args ); +} + + /** iSCSI socket operations */ static struct xfer_interface_operations iscsi_socket_operations = { .close = iscsi_socket_close, - .vredirect = xfer_vopen, + .vredirect = iscsi_vredirect, .seek = ignore_xfer_seek, .window = unlimited_xfer_window, .alloc_iob = default_xfer_alloc_iob, @@ -1461,6 +1490,32 @@ static int iscsi_parse_root_path ( struct iscsi_session *iscsi, } /** + * Set iSCSI authentication details + * + * @v iscsi iSCSI session + * @v username Username, if any + * @v password Password, if any + * @ret rc Return status code + */ +static int iscsi_set_auth ( struct iscsi_session *iscsi, + const char *username, const char *password ) { + + if ( username ) { + iscsi->username = strdup ( username ); + if ( ! iscsi->username ) + return -ENOMEM; + } + + if ( password ) { + iscsi->password = strdup ( password ); + if ( ! iscsi->password ) + return -ENOMEM; + } + + return 0; +} + +/** * Attach iSCSI interface * * @v scsi SCSI device @@ -1482,6 +1537,10 @@ int iscsi_attach ( struct scsi_device *scsi, const char *root_path ) { /* Parse root path */ if ( ( rc = iscsi_parse_root_path ( iscsi, root_path ) ) != 0 ) goto err; + /* Set fields not specified by root path */ + if ( ( rc = iscsi_set_auth ( iscsi, iscsi_username, + iscsi_password ) ) != 0 ) + goto err; /* Sanity checks */ if ( ! iscsi->target_address ) { @@ -1533,7 +1592,7 @@ static int apply_dhcp_iscsi_string ( unsigned int tag, /* Identify string and prefix */ switch ( tag ) { case DHCP_ISCSI_INITIATOR_IQN: - string = &iscsi_initiator_iqn; + string = &iscsi_explicit_initiator_iqn; break; case DHCP_EB_USERNAME: string = &iscsi_username; @@ -1584,3 +1643,24 @@ struct dhcp_option_applicator dhcp_iscsi_applicators[] __dhcp_applicator = { .apply = apply_dhcp_iscsi_string, }, }; + +/**************************************************************************** + * + * Initiator name + * + */ + +/** + * Get iSCSI initiator IQN + * + * @v iscsi iSCSI session + * @ret rc Return status code + */ +const char * iscsi_initiator_iqn ( void ) { + + if ( iscsi_explicit_initiator_iqn ) + return iscsi_explicit_initiator_iqn; + if ( iscsi_default_initiator_iqn ) + return iscsi_default_initiator_iqn; + return "iqn.2000-09.org.etherboot:UNKNOWN"; +} diff --git a/src/net/tls.c b/src/net/tls.c new file mode 100644 index 000000000..64e44b55d --- /dev/null +++ b/src/net/tls.c @@ -0,0 +1,1733 @@ +/* + * Copyright (C) 2007 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/** + * @file + * + * Transport Layer Security Protocol + */ + +#include <stdint.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <byteswap.h> +#include <gpxe/hmac.h> +#include <gpxe/md5.h> +#include <gpxe/sha1.h> +#include <gpxe/aes.h> +#include <gpxe/rsa.h> +#include <gpxe/xfer.h> +#include <gpxe/open.h> +#include <gpxe/filter.h> +#include <gpxe/tls.h> + +static int tls_send_plaintext ( struct tls_session *tls, unsigned int type, + const void *data, size_t len ); +static void tls_clear_cipher ( struct tls_session *tls, + struct tls_cipherspec *cipherspec ); + +/** + * Free TLS session + * + * @v refcnt Reference counter + */ +static void free_tls ( struct refcnt *refcnt ) { + struct tls_session *tls = + container_of ( refcnt, struct tls_session, refcnt ); + + /* Free dynamically-allocated resources */ + tls_clear_cipher ( tls, &tls->tx_cipherspec ); + tls_clear_cipher ( tls, &tls->tx_cipherspec_pending ); + tls_clear_cipher ( tls, &tls->rx_cipherspec ); + tls_clear_cipher ( tls, &tls->rx_cipherspec_pending ); + free ( tls->rsa_mod ); + free ( tls->rsa_pub_exp ); + free ( tls->rx_data ); + + /* Free TLS structure itself */ + free ( tls ); +} + +/** + * Finish with TLS session + * + * @v tls TLS session + * @v rc Status code + */ +static void tls_close ( struct tls_session *tls, int rc ) { + + /* Remove process */ + process_del ( &tls->process ); + + /* Close ciphertext and plaintext streams */ + xfer_nullify ( &tls->cipherstream.xfer ); + xfer_close ( &tls->cipherstream.xfer, rc ); + xfer_nullify ( &tls->plainstream.xfer ); + xfer_close ( &tls->plainstream.xfer, rc ); +} + +/****************************************************************************** + * + * Random number generation + * + ****************************************************************************** + */ + +/** + * Generate random data + * + * @v data Buffer to fill + * @v len Length of buffer + */ +static void tls_generate_random ( void *data, size_t len ) { + /* FIXME: Some real random data source would be nice... */ + memset ( data, 0x01, len ); +} + +/** + * Update HMAC with a list of ( data, len ) pairs + * + * @v digest Hash function to use + * @v digest_ctx Digest context + * @v args ( data, len ) pairs of data, terminated by NULL + */ +static void tls_hmac_update_va ( struct crypto_algorithm *digest, + void *digest_ctx, va_list args ) { + void *data; + size_t len; + + while ( ( data = va_arg ( args, void * ) ) ) { + len = va_arg ( args, size_t ); + hmac_update ( digest, digest_ctx, data, len ); + } +} + +/** + * Generate secure pseudo-random data using a single hash function + * + * @v tls TLS session + * @v digest Hash function to use + * @v secret Secret + * @v secret_len Length of secret + * @v out Output buffer + * @v out_len Length of output buffer + * @v seeds ( data, len ) pairs of seed data, terminated by NULL + */ +static void tls_p_hash_va ( struct tls_session *tls, + struct crypto_algorithm *digest, + void *secret, size_t secret_len, + void *out, size_t out_len, + va_list seeds ) { + uint8_t secret_copy[secret_len]; + uint8_t digest_ctx[digest->ctxsize]; + uint8_t digest_ctx_partial[digest->ctxsize]; + uint8_t a[digest->digestsize]; + uint8_t out_tmp[digest->digestsize]; + size_t frag_len = digest->digestsize; + va_list tmp; + + /* Copy the secret, in case HMAC modifies it */ + memcpy ( secret_copy, secret, secret_len ); + secret = secret_copy; + DBGC2 ( tls, "TLS %p %s secret:\n", tls, digest->name ); + DBGC2_HD ( tls, secret, secret_len ); + + /* Calculate A(1) */ + hmac_init ( digest, digest_ctx, secret, &secret_len ); + va_copy ( tmp, seeds ); + tls_hmac_update_va ( digest, digest_ctx, tmp ); + va_end ( tmp ); + hmac_final ( digest, digest_ctx, secret, &secret_len, a ); + DBGC2 ( tls, "TLS %p %s A(1):\n", tls, digest->name ); + DBGC2_HD ( tls, &a, sizeof ( a ) ); + + /* Generate as much data as required */ + while ( out_len ) { + /* Calculate output portion */ + hmac_init ( digest, digest_ctx, secret, &secret_len ); + hmac_update ( digest, digest_ctx, a, sizeof ( a ) ); + memcpy ( digest_ctx_partial, digest_ctx, digest->ctxsize ); + va_copy ( tmp, seeds ); + tls_hmac_update_va ( digest, digest_ctx, tmp ); + va_end ( tmp ); + hmac_final ( digest, digest_ctx, + secret, &secret_len, out_tmp ); + + /* Copy output */ + if ( frag_len > out_len ) + frag_len = out_len; + memcpy ( out, out_tmp, frag_len ); + DBGC2 ( tls, "TLS %p %s output:\n", tls, digest->name ); + DBGC2_HD ( tls, out, frag_len ); + + /* Calculate A(i) */ + hmac_final ( digest, digest_ctx_partial, + secret, &secret_len, a ); + DBGC2 ( tls, "TLS %p %s A(n):\n", tls, digest->name ); + DBGC2_HD ( tls, &a, sizeof ( a ) ); + + out += frag_len; + out_len -= frag_len; + } +} + +/** + * Generate secure pseudo-random data + * + * @v tls TLS session + * @v secret Secret + * @v secret_len Length of secret + * @v out Output buffer + * @v out_len Length of output buffer + * @v ... ( data, len ) pairs of seed data, terminated by NULL + */ +static void tls_prf ( struct tls_session *tls, void *secret, size_t secret_len, + void *out, size_t out_len, ... ) { + va_list seeds; + va_list tmp; + size_t subsecret_len; + void *md5_secret; + void *sha1_secret; + uint8_t out_md5[out_len]; + uint8_t out_sha1[out_len]; + unsigned int i; + + va_start ( seeds, out_len ); + + /* Split secret into two, with an overlap of up to one byte */ + subsecret_len = ( ( secret_len + 1 ) / 2 ); + md5_secret = secret; + sha1_secret = ( secret + secret_len - subsecret_len ); + + /* Calculate MD5 portion */ + va_copy ( tmp, seeds ); + tls_p_hash_va ( tls, &md5_algorithm, md5_secret, subsecret_len, + out_md5, out_len, seeds ); + va_end ( tmp ); + + /* Calculate SHA1 portion */ + va_copy ( tmp, seeds ); + tls_p_hash_va ( tls, &sha1_algorithm, sha1_secret, subsecret_len, + out_sha1, out_len, seeds ); + va_end ( tmp ); + + /* XOR the two portions together into the final output buffer */ + for ( i = 0 ; i < out_len ; i++ ) { + *( ( uint8_t * ) out + i ) = ( out_md5[i] ^ out_sha1[i] ); + } + + va_end ( seeds ); +} + +/** + * Generate secure pseudo-random data + * + * @v secret Secret + * @v secret_len Length of secret + * @v out Output buffer + * @v out_len Length of output buffer + * @v label String literal label + * @v ... ( data, len ) pairs of seed data + */ +#define tls_prf_label( tls, secret, secret_len, out, out_len, label, ... ) \ + tls_prf ( (tls), (secret), (secret_len), (out), (out_len), \ + label, ( sizeof ( label ) - 1 ), __VA_ARGS__, NULL ) + +/****************************************************************************** + * + * Secret management + * + ****************************************************************************** + */ + +/** + * Generate master secret + * + * @v tls TLS session + * + * The pre-master secret and the client and server random values must + * already be known. + */ +static void tls_generate_master_secret ( struct tls_session *tls ) { + DBGC ( tls, "TLS %p pre-master-secret:\n", tls ); + DBGC_HD ( tls, &tls->pre_master_secret, + sizeof ( tls->pre_master_secret ) ); + DBGC ( tls, "TLS %p client random bytes:\n", tls ); + DBGC_HD ( tls, &tls->client_random, sizeof ( tls->server_random ) ); + DBGC ( tls, "TLS %p server random bytes:\n", tls ); + DBGC_HD ( tls, &tls->server_random, sizeof ( tls->server_random ) ); + + tls_prf_label ( tls, tls->pre_master_secret, + sizeof ( tls->pre_master_secret ), + tls->master_secret, sizeof ( tls->master_secret ), + "master secret", + tls->client_random, sizeof ( tls->client_random ), + tls->server_random, sizeof ( tls->server_random ) ); + + DBGC ( tls, "TLS %p generated master secret:\n", tls ); + DBGC_HD ( tls, &tls->master_secret, sizeof ( tls->master_secret ) ); +} + +/** + * Generate key material + * + * @v tls TLS session + * + * The master secret must already be known. + */ +static int tls_generate_keys ( struct tls_session *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->digest->digestsize; + size_t key_size = tx_cipherspec->key_len; + size_t iv_size = tx_cipherspec->cipher->blocksize; + size_t total = ( 2 * ( hash_size + key_size + iv_size ) ); + uint8_t key_block[total]; + uint8_t *key; + int rc; + + /* Generate key block */ + tls_prf_label ( tls, tls->master_secret, sizeof ( tls->master_secret ), + key_block, sizeof ( key_block ), "key expansion", + tls->server_random, sizeof ( tls->server_random ), + tls->client_random, sizeof ( tls->client_random ) ); + + /* Split key block into portions */ + key = key_block; + + /* TX MAC secret */ + memcpy ( tx_cipherspec->mac_secret, key, hash_size ); + DBGC ( tls, "TLS %p TX MAC secret:\n", tls ); + DBGC_HD ( tls, key, hash_size ); + key += hash_size; + + /* RX MAC secret */ + memcpy ( rx_cipherspec->mac_secret, key, hash_size ); + DBGC ( tls, "TLS %p RX MAC secret:\n", tls ); + DBGC_HD ( tls, key, hash_size ); + key += hash_size; + + /* TX key */ + if ( ( rc = cipher_setkey ( tx_cipherspec->cipher, + tx_cipherspec->cipher_ctx, + key, key_size ) ) != 0 ) { + DBGC ( tls, "TLS %p could not set TX key: %s\n", + tls, strerror ( rc ) ); + return rc; + } + DBGC ( tls, "TLS %p TX key:\n", tls ); + DBGC_HD ( tls, key, key_size ); + key += key_size; + + /* RX key */ + if ( ( rc = cipher_setkey ( rx_cipherspec->cipher, + rx_cipherspec->cipher_ctx, + key, key_size ) ) != 0 ) { + DBGC ( tls, "TLS %p could not set TX key: %s\n", + tls, strerror ( rc ) ); + return rc; + } + + /* FIXME: AES needs to be fixed to not require this */ + AES_convert_key ( rx_cipherspec->cipher_ctx ); + + DBGC ( tls, "TLS %p RX key:\n", tls ); + DBGC_HD ( tls, key, key_size ); + key += key_size; + + /* TX initialisation vector */ + cipher_setiv ( tx_cipherspec->cipher, tx_cipherspec->cipher_ctx, key ); + DBGC ( tls, "TLS %p TX IV:\n", tls ); + DBGC_HD ( tls, key, iv_size ); + key += iv_size; + + /* RX initialisation vector */ + cipher_setiv ( rx_cipherspec->cipher, rx_cipherspec->cipher_ctx, key ); + DBGC ( tls, "TLS %p RX IV:\n", tls ); + DBGC_HD ( tls, key, iv_size ); + key += iv_size; + + assert ( ( key_block + total ) == key ); + + return 0; +} + +/****************************************************************************** + * + * Cipher suite management + * + ****************************************************************************** + */ + +/** + * Clear cipher suite + * + * @v cipherspec TLS cipher specification + */ +static void tls_clear_cipher ( struct tls_session *tls __unused, + struct tls_cipherspec *cipherspec ) { + free ( cipherspec->dynamic ); + memset ( cipherspec, 0, sizeof ( cipherspec ) ); + cipherspec->pubkey = &crypto_null; + cipherspec->cipher = &crypto_null; + cipherspec->digest = &crypto_null; +} + +/** + * Set cipher suite + * + * @v tls TLS session + * @v cipherspec TLS cipher specification + * @v pubkey Public-key encryption elgorithm + * @v cipher Bulk encryption cipher algorithm + * @v digest MAC digest algorithm + * @v key_len Key length + * @ret rc Return status code + */ +static int tls_set_cipher ( struct tls_session *tls, + struct tls_cipherspec *cipherspec, + struct crypto_algorithm *pubkey, + struct crypto_algorithm *cipher, + struct crypto_algorithm *digest, + size_t key_len ) { + 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 ); + dynamic = malloc ( total ); + if ( ! dynamic ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes for crypto " + "context\n", tls, total ); + return -ENOMEM; + } + memset ( dynamic, 0, total ); + + /* Assign storage */ + 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; + assert ( ( cipherspec->dynamic + total ) == dynamic ); + + /* Store parameters */ + cipherspec->pubkey = pubkey; + cipherspec->cipher = cipher; + cipherspec->digest = digest; + cipherspec->key_len = key_len; + + return 0; +} + +/** + * Select next cipher suite + * + * @v tls TLS session + * @v cipher_suite Cipher suite specification + * @ret rc Return status code + */ +static int tls_select_cipher ( struct tls_session *tls, + unsigned int cipher_suite ) { + struct crypto_algorithm *pubkey = &crypto_null; + struct crypto_algorithm *cipher = &crypto_null; + struct crypto_algorithm *digest = &crypto_null; + size_t key_len = 0; + int rc; + + switch ( cipher_suite ) { + case htons ( TLS_RSA_WITH_AES_128_CBC_SHA ): + key_len = ( 128 / 8 ); + cipher = &aes_algorithm; + digest = &sha1_algorithm; + break; + case htons ( TLS_RSA_WITH_AES_256_CBC_SHA ): + key_len = ( 256 / 8 ); + cipher = &aes_algorithm; + digest = &sha1_algorithm; + break; + default: + DBGC ( tls, "TLS %p does not support cipher %04x\n", + tls, ntohs ( cipher_suite ) ); + return -ENOTSUP; + } + + /* Set ciphers */ + if ( ( rc = tls_set_cipher ( tls, &tls->tx_cipherspec_pending, pubkey, + cipher, digest, key_len ) ) != 0 ) + return rc; + if ( ( rc = tls_set_cipher ( tls, &tls->rx_cipherspec_pending, pubkey, + cipher, digest, key_len ) ) != 0 ) + return rc; + + DBGC ( tls, "TLS %p selected %s-%s-%d-%s\n", tls, + pubkey->name, cipher->name, ( key_len * 8 ), digest->name ); + + return 0; +} + +/** + * Activate next cipher suite + * + * @v tls TLS session + * @v pending Pending cipher specification + * @v active Active cipher specification to replace + * @ret rc Return status code + */ +static int tls_change_cipher ( struct tls_session *tls, + struct tls_cipherspec *pending, + struct tls_cipherspec *active ) { + + /* Sanity check */ + if ( /* FIXME (when pubkey is not hard-coded to RSA): + * ( pending->pubkey == &crypto_null ) || */ + ( pending->cipher == &crypto_null ) || + ( pending->digest == &crypto_null ) ) { + DBGC ( tls, "TLS %p refusing to use null cipher\n", tls ); + return -ENOTSUP; + } + + tls_clear_cipher ( tls, active ); + memswap ( active, pending, sizeof ( *active ) ); + return 0; +} + +/****************************************************************************** + * + * Handshake verification + * + ****************************************************************************** + */ + +/** + * Add handshake record to verification hash + * + * @v tls TLS session + * @v data Handshake record + * @v len Length of handshake record + */ +static void tls_add_handshake ( struct tls_session *tls, + const void *data, size_t len ) { + + digest_update ( &md5_algorithm, tls->handshake_md5_ctx, data, len ); + digest_update ( &sha1_algorithm, tls->handshake_sha1_ctx, data, len ); +} + +/** + * Calculate handshake verification hash + * + * @v tls TLS session + * @v out Output buffer + * + * Calculates the MD5+SHA1 digest over all handshake messages seen so + * far. + */ +static void tls_verify_handshake ( struct tls_session *tls, void *out ) { + struct crypto_algorithm *md5 = &md5_algorithm; + struct crypto_algorithm *sha1 = &sha1_algorithm; + uint8_t md5_ctx[md5->ctxsize]; + uint8_t sha1_ctx[sha1->ctxsize]; + void *md5_digest = out; + void *sha1_digest = ( out + md5->digestsize ); + + memcpy ( md5_ctx, tls->handshake_md5_ctx, sizeof ( md5_ctx ) ); + memcpy ( sha1_ctx, tls->handshake_sha1_ctx, sizeof ( sha1_ctx ) ); + digest_final ( md5, md5_ctx, md5_digest ); + digest_final ( sha1, sha1_ctx, sha1_digest ); +} + +/****************************************************************************** + * + * Record handling + * + ****************************************************************************** + */ + +/** + * Transmit Handshake record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_send_handshake ( struct tls_session *tls, + void *data, size_t len ) { + + /* Add to handshake digest */ + tls_add_handshake ( tls, data, len ); + + /* Send record */ + return tls_send_plaintext ( tls, TLS_TYPE_HANDSHAKE, data, len ); +} + +/** + * Transmit Client Hello record + * + * @v tls TLS session + * @ret rc Return status code + */ +static int tls_send_client_hello ( struct tls_session *tls ) { + struct { + uint32_t type_length; + uint16_t version; + uint8_t random[32]; + uint8_t session_id_len; + uint16_t cipher_suite_len; + uint16_t cipher_suites[2]; + uint8_t compression_methods_len; + uint8_t compression_methods[1]; + } __attribute__ (( packed )) hello; + + memset ( &hello, 0, sizeof ( hello ) ); + hello.type_length = ( cpu_to_le32 ( TLS_CLIENT_HELLO ) | + htonl ( sizeof ( hello ) - + sizeof ( hello.type_length ) ) ); + hello.version = htons ( TLS_VERSION_TLS_1_0 ); + memcpy ( &hello.random, tls->client_random, sizeof ( hello.random ) ); + hello.cipher_suite_len = htons ( sizeof ( hello.cipher_suites ) ); + hello.cipher_suites[0] = htons ( TLS_RSA_WITH_AES_128_CBC_SHA ); + hello.cipher_suites[1] = htons ( TLS_RSA_WITH_AES_256_CBC_SHA ); + hello.compression_methods_len = sizeof ( hello.compression_methods ); + + return tls_send_handshake ( tls, &hello, sizeof ( hello ) ); +} + +/** + * Transmit Client Key Exchange record + * + * @v tls TLS session + * @ret rc Return status code + */ +static int tls_send_client_key_exchange ( struct tls_session *tls ) { + /* FIXME: Hack alert */ + RSA_CTX *rsa_ctx; + RSA_pub_key_new ( &rsa_ctx, tls->rsa_mod, tls->rsa_mod_len, + tls->rsa_pub_exp, tls->rsa_pub_exp_len ); + struct { + uint32_t type_length; + uint16_t encrypted_pre_master_secret_len; + uint8_t encrypted_pre_master_secret[rsa_ctx->num_octets]; + } __attribute__ (( packed )) key_xchg; + + memset ( &key_xchg, 0, sizeof ( key_xchg ) ); + key_xchg.type_length = ( cpu_to_le32 ( TLS_CLIENT_KEY_EXCHANGE ) | + htonl ( sizeof ( key_xchg ) - + sizeof ( key_xchg.type_length ) ) ); + key_xchg.encrypted_pre_master_secret_len + = htons ( sizeof ( key_xchg.encrypted_pre_master_secret ) ); + + /* FIXME: Hack alert */ + DBGC ( tls, "RSA encrypting plaintext, modulus, exponent:\n" ); + DBGC_HD ( tls, &tls->pre_master_secret, + sizeof ( tls->pre_master_secret ) ); + DBGC_HD ( tls, tls->rsa_mod, tls->rsa_mod_len ); + DBGC_HD ( tls, tls->rsa_pub_exp, tls->rsa_pub_exp_len ); + RSA_encrypt ( rsa_ctx, tls->pre_master_secret, + sizeof ( tls->pre_master_secret ), + key_xchg.encrypted_pre_master_secret, 0 ); + DBGC ( tls, "RSA encrypt done. Ciphertext:\n" ); + DBGC_HD ( tls, &key_xchg.encrypted_pre_master_secret, + sizeof ( key_xchg.encrypted_pre_master_secret ) ); + RSA_free ( rsa_ctx ); + + + return tls_send_handshake ( tls, &key_xchg, sizeof ( key_xchg ) ); +} + +/** + * Transmit Change Cipher record + * + * @v tls TLS session + * @ret rc Return status code + */ +static int tls_send_change_cipher ( struct tls_session *tls ) { + static const uint8_t change_cipher[1] = { 1 }; + return tls_send_plaintext ( tls, TLS_TYPE_CHANGE_CIPHER, + change_cipher, sizeof ( change_cipher ) ); +} + +/** + * Transmit Finished record + * + * @v tls TLS session + * @ret rc Return status code + */ +static int tls_send_finished ( struct tls_session *tls ) { + struct { + uint32_t type_length; + uint8_t verify_data[12]; + } __attribute__ (( packed )) finished; + uint8_t digest[MD5_DIGEST_SIZE + SHA1_DIGEST_SIZE]; + + memset ( &finished, 0, sizeof ( finished ) ); + finished.type_length = ( cpu_to_le32 ( TLS_FINISHED ) | + htonl ( sizeof ( finished ) - + sizeof ( finished.type_length ) ) ); + tls_verify_handshake ( tls, digest ); + tls_prf_label ( tls, tls->master_secret, sizeof ( tls->master_secret ), + finished.verify_data, sizeof ( finished.verify_data ), + "client finished", digest, sizeof ( digest ) ); + + return tls_send_handshake ( tls, &finished, sizeof ( finished ) ); +} + +/** + * Receive new Change Cipher record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_change_cipher ( struct tls_session *tls, + void *data, size_t len ) { + int rc; + + if ( ( len != 1 ) || ( *( ( uint8_t * ) data ) != 1 ) ) { + DBGC ( tls, "TLS %p received invalid Change Cipher\n", tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL; + } + + 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", + tls, strerror ( rc ) ); + return rc; + } + tls->rx_seq = ~( ( uint64_t ) 0 ); + + return 0; +} + +/** + * Receive new Alert record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_alert ( struct tls_session *tls, void *data, size_t len ) { + struct { + uint8_t level; + uint8_t description; + char next[0]; + } __attribute__ (( packed )) *alert = data; + void *end = alert->next; + + /* Sanity check */ + if ( end != ( data + len ) ) { + DBGC ( tls, "TLS %p received overlength Alert\n", tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL; + } + + switch ( alert->level ) { + case TLS_ALERT_WARNING: + DBGC ( tls, "TLS %p received warning alert %d\n", + tls, alert->description ); + return 0; + case TLS_ALERT_FATAL: + DBGC ( tls, "TLS %p received fatal alert %d\n", + tls, alert->description ); + return -EPERM; + default: + DBGC ( tls, "TLS %p received unknown alert level %d" + "(alert %d)\n", tls, alert->level, alert->description ); + return -EIO; + } +} + +/** + * Receive new Server Hello record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_server_hello ( struct tls_session *tls, + void *data, size_t len ) { + struct { + uint32_t type_length; + uint16_t version; + uint8_t random[32]; + uint8_t session_id_len; + char next[0]; + } __attribute__ (( packed )) *hello_a = data; + struct { + uint8_t session_id[hello_a->session_id_len]; + uint16_t cipher_suite; + uint8_t compression_method; + char next[0]; + } __attribute__ (( packed )) *hello_b = ( void * ) &hello_a->next; + void *end = hello_b->next; + int rc; + + /* Sanity check */ + if ( end != ( data + len ) ) { + DBGC ( tls, "TLS %p received overlength Server Hello\n", tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL; + } + + /* Check protocol version */ + if ( ntohs ( hello_a->version ) < TLS_VERSION_TLS_1_0 ) { + DBGC ( tls, "TLS %p does not support protocol version %d.%d\n", + tls, ( ntohs ( hello_a->version ) >> 8 ), + ( ntohs ( hello_a->version ) & 0xff ) ); + return -ENOTSUP; + } + + /* 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; + + /* Generate secrets */ + tls_generate_master_secret ( tls ); + if ( ( rc = tls_generate_keys ( tls ) ) != 0 ) + return rc; + + return 0; +} + +/** + * Receive new Certificate record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_certificate ( struct tls_session *tls, + void *data, size_t len ) { + struct { + uint32_t type_length; + uint8_t length[3]; + uint8_t first_cert_length[3]; + uint8_t asn1_start[0]; + } __attribute__ (( packed )) *certificate = data; + uint8_t *cert = certificate->asn1_start; + int offset = 0; + + /* FIXME */ + (void) len; + + if (asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0 || + asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0 || + asn1_skip_obj(cert, &offset, ASN1_EXPLICIT_TAG) || + asn1_skip_obj(cert, &offset, ASN1_INTEGER) || + asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) || + asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) || + asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) || + asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) || + asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0 || + asn1_skip_obj(cert, &offset, ASN1_SEQUENCE) || + asn1_next_obj(cert, &offset, ASN1_BIT_STRING) < 0) { + DBGC ( tls, "TLS %p invalid certificate\n", tls ); + DBGC_HD ( tls, cert + offset, 64 ); + return -EPERM; + } + + offset++; + + if (asn1_next_obj(cert, &offset, ASN1_SEQUENCE) < 0) { + DBGC ( tls, "TLS %p invalid certificate\n", tls ); + DBGC_HD ( tls, cert + offset, 64 ); + return -EPERM; + } + + tls->rsa_mod_len = asn1_get_int(cert, &offset, &tls->rsa_mod); + tls->rsa_pub_exp_len = asn1_get_int(cert, &offset, &tls->rsa_pub_exp); + + DBGC_HD ( tls, tls->rsa_mod, tls->rsa_mod_len ); + DBGC_HD ( tls, tls->rsa_pub_exp, tls->rsa_pub_exp_len ); + + return 0; +} + +/** + * Receive new Server Hello Done record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_server_hello_done ( struct tls_session *tls, + void *data, size_t len ) { + struct { + uint32_t type_length; + char next[0]; + } __attribute__ (( packed )) *hello_done = data; + void *end = hello_done->next; + + /* Sanity check */ + if ( end != ( data + len ) ) { + DBGC ( tls, "TLS %p received overlength Server Hello Done\n", + tls ); + DBGC_HD ( tls, data, len ); + return -EINVAL; + } + + /* Check that we are ready to send the Client Key Exchange */ + if ( tls->tx_state != TLS_TX_NONE ) { + DBGC ( tls, "TLS %p received Server Hello Done while in " + "TX state %d\n", tls, tls->tx_state ); + return -EIO; + } + + /* Start sending the Client Key Exchange */ + tls->tx_state = TLS_TX_CLIENT_KEY_EXCHANGE; + + return 0; +} + +/** + * Receive new Finished record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_finished ( struct tls_session *tls, + void *data, size_t len ) { + + /* FIXME: Handle this properly */ + tls->tx_state = TLS_TX_DATA; + ( void ) data; + ( void ) len; + return 0; +} + +/** + * Receive new Handshake record + * + * @v tls TLS session + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_handshake ( struct tls_session *tls, + void *data, size_t len ) { + uint8_t *type = data; + int rc; + + switch ( *type ) { + case TLS_SERVER_HELLO: + rc = tls_new_server_hello ( tls, data, len ); + break; + case TLS_CERTIFICATE: + rc = tls_new_certificate ( tls, data, len ); + break; + case TLS_SERVER_HELLO_DONE: + rc = tls_new_server_hello_done ( tls, data, len ); + break; + case TLS_FINISHED: + rc = tls_new_finished ( tls, data, len ); + break; + default: + DBGC ( tls, "TLS %p ignoring handshake type %d\n", + tls, *type ); + rc = 0; + break; + } + + /* Add to handshake digest (except for Hello Requests, which + * are explicitly excludede). + */ + if ( *type != TLS_HELLO_REQUEST ) + tls_add_handshake ( tls, data, len ); + + return rc; +} + +/** + * Receive new record + * + * @v tls TLS session + * @v type Record type + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_new_record ( struct tls_session *tls, + unsigned int type, void *data, size_t len ) { + + switch ( type ) { + case TLS_TYPE_CHANGE_CIPHER: + return tls_new_change_cipher ( tls, data, len ); + case TLS_TYPE_ALERT: + return tls_new_alert ( tls, data, len ); + case TLS_TYPE_HANDSHAKE: + return tls_new_handshake ( tls, data, len ); + case TLS_TYPE_DATA: + return xfer_deliver_raw ( &tls->plainstream.xfer, data, len ); + default: + /* RFC4346 says that we should just ignore unknown + * record types. + */ + DBGC ( tls, "TLS %p ignoring record type %d\n", tls, type ); + return 0; + } +} + +/****************************************************************************** + * + * Record encryption/decryption + * + ****************************************************************************** + */ + +/** + * Calculate HMAC + * + * @v tls TLS session + * @v cipherspec Cipher specification + * @v seq Sequence number + * @v tlshdr TLS header + * @v data Data + * @v len Length of data + * @v mac HMAC to fill in + */ +static void tls_hmac ( struct tls_session *tls __unused, + struct tls_cipherspec *cipherspec, + uint64_t seq, struct tls_header *tlshdr, + const void *data, size_t len, void *hmac ) { + struct crypto_algorithm *digest = cipherspec->digest; + uint8_t digest_ctx[digest->ctxsize]; + + hmac_init ( digest, digest_ctx, cipherspec->mac_secret, + &digest->digestsize ); + seq = cpu_to_be64 ( seq ); + hmac_update ( digest, digest_ctx, &seq, sizeof ( seq ) ); + hmac_update ( digest, digest_ctx, tlshdr, sizeof ( *tlshdr ) ); + hmac_update ( digest, digest_ctx, data, len ); + hmac_final ( digest, digest_ctx, cipherspec->mac_secret, + &digest->digestsize, hmac ); +} + +/** + * Allocate and assemble stream-ciphered record from data and MAC portions + * + * @v tls TLS session + * @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_stream ( struct tls_session *tls, + const void *data, size_t len, + void *digest, size_t *plaintext_len ) { + size_t mac_len = tls->tx_cipherspec.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 + * + * @v tls TLS session + * @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_session *tls, + const void *data, size_t len, + void *digest, size_t *plaintext_len ) { + size_t blocksize = tls->tx_cipherspec.cipher->blocksize; + size_t iv_len = blocksize; + size_t mac_len = tls->tx_cipherspec.digest->digestsize; + size_t padding_len; + void *plaintext; + void *iv; + void *content; + void *mac; + void *padding; + + /* FIXME: TLSv1.1 has an explicit IV */ + iv_len = 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 */ + memset ( iv, 0, iv_len ); + memcpy ( content, data, len ); + memcpy ( mac, digest, mac_len ); + memset ( padding, padding_len, ( padding_len + 1 ) ); + + return plaintext; +} + +/** + * Send plaintext record + * + * @v tls TLS session + * @v type Record type + * @v data Plaintext record + * @v len Length of plaintext record + * @ret rc Return status code + */ +static int tls_send_plaintext ( struct tls_session *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; + void *plaintext = NULL; + size_t plaintext_len; + struct io_buffer *ciphertext = NULL; + size_t ciphertext_len; + size_t mac_len = cipherspec->digest->digestsize; + uint8_t mac[mac_len]; + int rc; + + /* Construct header */ + plaintext_tlshdr.type = type; + plaintext_tlshdr.version = htons ( TLS_VERSION_TLS_1_0 ); + plaintext_tlshdr.length = htons ( len ); + + /* Calculate MAC */ + tls_hmac ( tls, cipherspec, tls->tx_seq, &plaintext_tlshdr, + data, len, mac ); + + /* Allocate and assemble plaintext struct */ + if ( is_stream_cipher ( cipherspec->cipher ) ) { + plaintext = tls_assemble_stream ( tls, data, len, mac, + &plaintext_len ); + } else { + plaintext = tls_assemble_block ( tls, data, len, mac, + &plaintext_len ); + } + if ( ! plaintext ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes for " + "plaintext\n", tls, plaintext_len ); + rc = -ENOMEM; + goto done; + } + + DBGC2 ( tls, "Sending plaintext data:\n" ); + DBGC2_HD ( tls, plaintext, plaintext_len ); + + /* Allocate ciphertext */ + ciphertext_len = ( sizeof ( *tlshdr ) + plaintext_len ); + ciphertext = xfer_alloc_iob ( &tls->cipherstream.xfer, + ciphertext_len ); + if ( ! ciphertext ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes for " + "ciphertext\n", tls, ciphertext_len ); + rc = -ENOMEM; + goto done; + } + + /* Assemble ciphertext */ + tlshdr = iob_put ( ciphertext, sizeof ( *tlshdr ) ); + tlshdr->type = type; + tlshdr->version = htons ( TLS_VERSION_TLS_1_0 ); + tlshdr->length = htons ( plaintext_len ); + memcpy ( cipherspec->cipher_next_ctx, cipherspec->cipher_ctx, + cipherspec->cipher->ctxsize ); + if ( ( rc = cipher_encrypt ( cipherspec->cipher, + cipherspec->cipher_next_ctx, plaintext, + iob_put ( ciphertext, plaintext_len ), + plaintext_len ) ) != 0 ) { + DBGC ( tls, "TLS %p could not encrypt: %s\n", + tls, strerror ( rc ) ); + DBGC_HD ( tls, plaintext, plaintext_len ); + goto done; + } + + /* Free plaintext as soon as possible to conserve memory */ + free ( plaintext ); + plaintext = NULL; + + /* Send ciphertext */ + rc = xfer_deliver_iob ( &tls->cipherstream.xfer, ciphertext ); + ciphertext = NULL; + if ( rc != 0 ) { + DBGC ( tls, "TLS %p could not deliver ciphertext: %s\n", + tls, strerror ( rc ) ); + goto done; + } + + /* Update TX state machine to next record */ + tls->tx_seq += 1; + memcpy ( tls->tx_cipherspec.cipher_ctx, + tls->tx_cipherspec.cipher_next_ctx, + tls->tx_cipherspec.cipher->ctxsize ); + + done: + free ( plaintext ); + free_iob ( ciphertext ); + return rc; +} + +/** + * Split stream-ciphered record into data and MAC portions + * + * @v tls TLS session + * @v plaintext Plaintext record + * @v plaintext_len Length of record + * @ret data Data + * @ret len Length of data + * @ret digest MAC digest + * @ret rc Return status code + */ +static int tls_split_stream ( struct tls_session *tls, + void *plaintext, size_t plaintext_len, + void **data, size_t *len, void **digest ) { + void *content; + size_t content_len; + void *mac; + size_t mac_len; + + /* Decompose stream-ciphered data */ + mac_len = tls->rx_cipherspec.digest->digestsize; + if ( plaintext_len < mac_len ) { + DBGC ( tls, "TLS %p received underlength record\n", tls ); + DBGC_HD ( tls, plaintext, plaintext_len ); + return -EINVAL; + } + content_len = ( plaintext_len - mac_len ); + content = plaintext; + mac = ( content + content_len ); + + /* Fill in return values */ + *data = content; + *len = content_len; + *digest = mac; + + return 0; +} + +/** + * Split block-ciphered record into data and MAC portions + * + * @v tls TLS session + * @v plaintext Plaintext record + * @v plaintext_len Length of record + * @ret data Data + * @ret len Length of data + * @ret digest MAC digest + * @ret rc Return status code + */ +static int tls_split_block ( struct tls_session *tls, + void *plaintext, size_t plaintext_len, + void **data, size_t *len, + void **digest ) { + void *iv; + size_t iv_len; + void *content; + size_t content_len; + void *mac; + size_t mac_len; + void *padding; + size_t padding_len; + unsigned int i; + + /* Decompose block-ciphered data */ + if ( plaintext_len < 1 ) { + DBGC ( tls, "TLS %p received underlength record\n", tls ); + DBGC_HD ( tls, plaintext, plaintext_len ); + return -EINVAL; + } + iv_len = tls->rx_cipherspec.cipher->blocksize; + + /* FIXME: TLSv1.1 uses an explicit IV */ + iv_len = 0; + + mac_len = tls->rx_cipherspec.digest->digestsize; + padding_len = *( ( uint8_t * ) ( plaintext + plaintext_len - 1 ) ); + if ( plaintext_len < ( iv_len + mac_len + padding_len + 1 ) ) { + DBGC ( tls, "TLS %p received underlength record\n", tls ); + DBGC_HD ( tls, plaintext, plaintext_len ); + return -EINVAL; + } + content_len = ( plaintext_len - iv_len - mac_len - padding_len - 1 ); + iv = plaintext; + content = ( iv + iv_len ); + mac = ( content + content_len ); + padding = ( mac + mac_len ); + + /* Verify padding bytes */ + for ( i = 0 ; i < padding_len ; i++ ) { + if ( *( ( uint8_t * ) ( padding + i ) ) != padding_len ) { + DBGC ( tls, "TLS %p received bad padding\n", tls ); + DBGC_HD ( tls, plaintext, plaintext_len ); + return -EINVAL; + } + } + + /* Fill in return values */ + *data = content; + *len = content_len; + *digest = mac; + + return 0; +} + +/** + * Receive new ciphertext record + * + * @v tls TLS session + * @v tlshdr Record header + * @v ciphertext Ciphertext record + * @ret rc Return status code + */ +static int tls_new_ciphertext ( struct tls_session *tls, + struct tls_header *tlshdr, void *ciphertext ) { + struct tls_header plaintext_tlshdr; + struct tls_cipherspec *cipherspec = &tls->rx_cipherspec; + size_t record_len = ntohs ( tlshdr->length ); + void *plaintext = NULL; + void *data; + size_t len; + void *mac; + size_t mac_len = cipherspec->digest->digestsize; + uint8_t verify_mac[mac_len]; + int rc; + + /* Allocate buffer for plaintext */ + plaintext = malloc ( record_len ); + if ( ! plaintext ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes for " + "decryption buffer\n", tls, record_len ); + rc = -ENOMEM; + goto done; + } + + /* Decrypt the record */ + if ( ( rc = cipher_decrypt ( cipherspec->cipher, + cipherspec->cipher_ctx, ciphertext, + plaintext, record_len ) ) != 0 ) { + DBGC ( tls, "TLS %p could not decrypt: %s\n", + tls, strerror ( rc ) ); + DBGC_HD ( tls, ciphertext, record_len ); + goto done; + } + + /* Split record into content and MAC */ + if ( is_stream_cipher ( cipherspec->cipher ) ) { + if ( ( rc = tls_split_stream ( tls, plaintext, record_len, + &data, &len, &mac ) ) != 0 ) + goto done; + } else { + if ( ( rc = tls_split_block ( tls, plaintext, record_len, + &data, &len, &mac ) ) != 0 ) + goto done; + } + + /* Verify MAC */ + plaintext_tlshdr.type = tlshdr->type; + plaintext_tlshdr.version = tlshdr->version; + plaintext_tlshdr.length = htons ( len ); + tls_hmac ( tls, cipherspec, tls->rx_seq, &plaintext_tlshdr, + data, len, verify_mac); + if ( memcmp ( mac, verify_mac, mac_len ) != 0 ) { + DBGC ( tls, "TLS %p failed MAC verification\n", tls ); + DBGC_HD ( tls, plaintext, record_len ); + goto done; + } + + DBGC2 ( tls, "Received plaintext data:\n" ); + DBGC2_HD ( tls, data, len ); + + /* Process plaintext record */ + if ( ( rc = tls_new_record ( tls, tlshdr->type, data, len ) ) != 0 ) + goto done; + + rc = 0; + done: + free ( plaintext ); + return rc; +} + +/****************************************************************************** + * + * Plaintext stream operations + * + ****************************************************************************** + */ + +/** + * Close interface + * + * @v xfer Plainstream data transfer interface + * @v rc Reason for close + */ +static void tls_plainstream_close ( struct xfer_interface *xfer, int rc ) { + struct tls_session *tls = + container_of ( xfer, struct tls_session, plainstream.xfer ); + + tls_close ( tls, rc ); +} + +/** + * Check flow control window + * + * @v xfer Plainstream data transfer interface + * @ret len Length of window + */ +static size_t tls_plainstream_window ( struct xfer_interface *xfer ) { + struct tls_session *tls = + container_of ( xfer, struct tls_session, plainstream.xfer ); + + /* Block window unless we are ready to accept data */ + if ( tls->tx_state != TLS_TX_DATA ) + return 0; + + return filter_window ( xfer ); +} + +/** + * Deliver datagram as raw data + * + * @v xfer Plainstream data transfer interface + * @v data Data buffer + * @v len Length of data buffer + * @ret rc Return status code + */ +static int tls_plainstream_deliver_raw ( struct xfer_interface *xfer, + const void *data, size_t len ) { + struct tls_session *tls = + container_of ( xfer, struct tls_session, plainstream.xfer ); + + /* Refuse unless we are ready to accept data */ + if ( tls->tx_state != TLS_TX_DATA ) + return -ENOTCONN; + + return tls_send_plaintext ( tls, TLS_TYPE_DATA, data, len ); +} + +/** TLS plaintext stream operations */ +static struct xfer_interface_operations tls_plainstream_operations = { + .close = tls_plainstream_close, + .vredirect = ignore_xfer_vredirect, + .seek = filter_seek, + .window = tls_plainstream_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = tls_plainstream_deliver_raw, +}; + +/****************************************************************************** + * + * Ciphertext stream operations + * + ****************************************************************************** + */ + +/** + * Close interface + * + * @v xfer Plainstream data transfer interface + * @v rc Reason for close + */ +static void tls_cipherstream_close ( struct xfer_interface *xfer, int rc ) { + struct tls_session *tls = + container_of ( xfer, struct tls_session, cipherstream.xfer ); + + tls_close ( tls, rc ); +} + +/** + * Handle received TLS header + * + * @v tls TLS session + * @ret rc Returned status code + */ +static int tls_newdata_process_header ( struct tls_session *tls ) { + size_t data_len = ntohs ( tls->rx_header.length ); + + /* Allocate data buffer now that we know the length */ + assert ( tls->rx_data == NULL ); + tls->rx_data = malloc ( data_len ); + if ( ! tls->rx_data ) { + DBGC ( tls, "TLS %p could not allocate %zd bytes " + "for receive buffer\n", tls, data_len ); + return -ENOMEM; + } + + /* Move to data state */ + tls->rx_state = TLS_RX_DATA; + + return 0; +} + +/** + * Handle received TLS data payload + * + * @v tls TLS session + * @ret rc Returned status code + */ +static int tls_newdata_process_data ( struct tls_session *tls ) { + int rc; + + /* Process record */ + if ( ( rc = tls_new_ciphertext ( tls, &tls->rx_header, + tls->rx_data ) ) != 0 ) + return rc; + + /* Increment RX sequence number */ + tls->rx_seq += 1; + + /* Free data buffer */ + free ( tls->rx_data ); + tls->rx_data = NULL; + + /* Return to header state */ + tls->rx_state = TLS_RX_HEADER; + + return 0; +} + +/** + * Receive new ciphertext + * + * @v app Stream application + * @v data Data received + * @v len Length of received data + * @ret rc Return status code + */ +static int tls_cipherstream_deliver_raw ( struct xfer_interface *xfer, + const void *data, size_t len ) { + struct tls_session *tls = + container_of ( xfer, struct tls_session, cipherstream.xfer ); + size_t frag_len; + void *buf; + size_t buf_len; + int ( * process ) ( struct tls_session *tls ); + int rc; + + while ( len ) { + /* Select buffer according to current state */ + switch ( tls->rx_state ) { + case TLS_RX_HEADER: + buf = &tls->rx_header; + buf_len = sizeof ( tls->rx_header ); + process = tls_newdata_process_header; + break; + case TLS_RX_DATA: + buf = tls->rx_data; + buf_len = ntohs ( tls->rx_header.length ); + process = tls_newdata_process_data; + break; + default: + assert ( 0 ); + return -EINVAL; + } + + /* Copy data portion to buffer */ + frag_len = ( buf_len - tls->rx_rcvd ); + if ( frag_len > len ) + frag_len = len; + memcpy ( ( buf + tls->rx_rcvd ), data, frag_len ); + tls->rx_rcvd += frag_len; + data += frag_len; + len -= frag_len; + + /* Process data if buffer is now full */ + if ( tls->rx_rcvd == buf_len ) { + if ( ( rc = process ( tls ) ) != 0 ) { + tls_close ( tls, rc ); + return rc; + } + tls->rx_rcvd = 0; + } + } + + return 0; +} + +/** TLS ciphertext stream operations */ +static struct xfer_interface_operations tls_cipherstream_operations = { + .close = tls_cipherstream_close, + .vredirect = xfer_vopen, + .seek = filter_seek, + .window = filter_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = tls_cipherstream_deliver_raw, +}; + +/****************************************************************************** + * + * Controlling process + * + ****************************************************************************** + */ + +/** + * TLS TX state machine + * + * @v process TLS process + */ +static void tls_step ( struct process *process ) { + struct tls_session *tls = + container_of ( process, struct tls_session, process ); + int rc; + + /* Wait for cipherstream to become ready */ + if ( ! xfer_window ( &tls->cipherstream.xfer ) ) + return; + + switch ( tls->tx_state ) { + case TLS_TX_NONE: + /* Nothing to do */ + break; + case TLS_TX_CLIENT_HELLO: + /* Send Client Hello */ + if ( ( rc = tls_send_client_hello ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not send Client Hello: %s\n", + tls, strerror ( rc ) ); + goto err; + } + tls->tx_state = TLS_TX_NONE; + break; + case TLS_TX_CLIENT_KEY_EXCHANGE: + /* Send Client Key Exchange */ + if ( ( rc = tls_send_client_key_exchange ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could send Client Key Exchange: " + "%s\n", tls, strerror ( rc ) ); + goto err; + } + tls->tx_state = TLS_TX_CHANGE_CIPHER; + break; + case TLS_TX_CHANGE_CIPHER: + /* Send Change Cipher, and then change the cipher in use */ + if ( ( rc = tls_send_change_cipher ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not send Change Cipher: " + "%s\n", tls, strerror ( rc ) ); + goto err; + } + if ( ( rc = tls_change_cipher ( tls, + &tls->tx_cipherspec_pending, + &tls->tx_cipherspec )) != 0 ){ + DBGC ( tls, "TLS %p could not activate TX cipher: " + "%s\n", tls, strerror ( rc ) ); + goto err; + } + tls->tx_seq = 0; + tls->tx_state = TLS_TX_FINISHED; + break; + case TLS_TX_FINISHED: + /* Send Finished */ + if ( ( rc = tls_send_finished ( tls ) ) != 0 ) { + DBGC ( tls, "TLS %p could not send Finished: %s\n", + tls, strerror ( rc ) ); + goto err; + } + tls->tx_state = TLS_TX_NONE; + break; + case TLS_TX_DATA: + /* Nothing to do */ + break; + default: + assert ( 0 ); + } + + return; + + err: + tls_close ( tls, rc ); +} + +/****************************************************************************** + * + * Instantiator + * + ****************************************************************************** + */ + +int add_tls ( struct xfer_interface *xfer, struct xfer_interface **next ) { + struct tls_session *tls; + + /* Allocate and initialise TLS structure */ + tls = malloc ( sizeof ( *tls ) ); + if ( ! tls ) + return -ENOMEM; + memset ( tls, 0, sizeof ( *tls ) ); + tls->refcnt.free = free_tls; + filter_init ( &tls->plainstream, &tls_plainstream_operations, + &tls->cipherstream, &tls_cipherstream_operations, + &tls->refcnt ); + tls_clear_cipher ( tls, &tls->tx_cipherspec ); + tls_clear_cipher ( tls, &tls->tx_cipherspec_pending ); + tls_clear_cipher ( tls, &tls->rx_cipherspec ); + tls_clear_cipher ( tls, &tls->rx_cipherspec_pending ); + *( ( uint32_t * ) tls->client_random ) = 0; /* GMT Unix time */ + tls_generate_random ( ( tls->client_random + 4 ), + ( sizeof ( tls->client_random ) - 4 ) ); + *( ( uint16_t * ) tls->pre_master_secret ) + = htons ( TLS_VERSION_TLS_1_0 ); + tls_generate_random ( ( tls->pre_master_secret + 2 ), + ( sizeof ( tls->pre_master_secret ) - 2 ) ); + digest_init ( &md5_algorithm, tls->handshake_md5_ctx ); + digest_init ( &sha1_algorithm, tls->handshake_sha1_ctx ); + tls->tx_state = TLS_TX_CLIENT_HELLO; + process_init ( &tls->process, tls_step, &tls->refcnt ); + + /* Attach to parent interface, mortalise self, and return */ + xfer_plug_plug ( &tls->plainstream.xfer, xfer ); + *next = &tls->cipherstream.xfer; + ref_put ( &tls->refcnt ); + return 0; +} + diff --git a/src/net/udp/dhcp.c b/src/net/udp/dhcp.c index 26059341c..f8f59e2e4 100644 --- a/src/net/udp/dhcp.c +++ b/src/net/udp/dhcp.c @@ -24,6 +24,7 @@ #include <byteswap.h> #include <gpxe/if_ether.h> #include <gpxe/netdevice.h> +#include <gpxe/device.h> #include <gpxe/xfer.h> #include <gpxe/open.h> #include <gpxe/job.h> @@ -108,7 +109,7 @@ static uint32_t dhcp_xid ( struct net_device *netdev ) { } /** Options common to all DHCP requests */ -struct dhcp_option_block dhcp_request_options = { +static struct dhcp_option_block dhcp_request_options = { .data = dhcp_request_options_data, .max_len = sizeof ( dhcp_request_options_data ), .len = sizeof ( dhcp_request_options_data ), @@ -270,8 +271,8 @@ static int copy_dhcp_packet_encap_options ( struct dhcp_packet *dhcppkt, * @c options may specify a single options block, or be left as NULL * in order to copy options from all registered options blocks. */ -int copy_dhcp_packet_options ( struct dhcp_packet *dhcppkt, - struct dhcp_option_block *options ) { +static int copy_dhcp_packet_options ( struct dhcp_packet *dhcppkt, + struct dhcp_option_block *options ) { return copy_dhcp_packet_encap_options ( dhcppkt, options, 0 ); } @@ -289,9 +290,10 @@ int copy_dhcp_packet_options ( struct dhcp_packet *dhcppkt, * dhcp_packet structure that can be passed to * set_dhcp_packet_option() or copy_dhcp_packet_options(). */ -int create_dhcp_packet ( struct net_device *netdev, uint8_t msgtype, - void *data, size_t max_len, - struct dhcp_packet *dhcppkt ) { +static int create_dhcp_packet ( struct net_device *netdev, + unsigned int msgtype, + void *data, size_t max_len, + struct dhcp_packet *dhcppkt ) { struct dhcphdr *dhcphdr = data; int rc; @@ -475,6 +477,121 @@ static struct dhcp_option_block * dhcp_parse ( const struct dhcphdr *dhcphdr, /**************************************************************************** * + * Whole-packet construction + * + */ + +/** DHCP network device descriptor */ +struct dhcp_netdev_desc { + /** Bus type ID */ + uint8_t type; + /** Vendor ID */ + uint16_t vendor; + /** Device ID */ + uint16_t device; +} __attribute__ (( packed )); + +/** + * Create DHCP request + * + * @v netdev Network device + * @v msgtype DHCP message type + * @v options DHCP server response options, or NULL + * @v data Buffer for DHCP packet + * @v max_len Size of DHCP packet buffer + * @v dhcppkt DHCP packet structure to fill in + * @ret rc Return status code + */ +int create_dhcp_request ( struct net_device *netdev, int msgtype, + struct dhcp_option_block *options, + void *data, size_t max_len, + struct dhcp_packet *dhcppkt ) { + struct device_description *desc = &netdev->dev->desc; + struct dhcp_netdev_desc dhcp_desc; + int rc; + + /* Create DHCP packet */ + if ( ( rc = create_dhcp_packet ( netdev, msgtype, data, max_len, + dhcppkt ) ) != 0 ) { + DBG ( "DHCP could not create DHCP packet: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Copy in options common to all requests */ + if ( ( rc = copy_dhcp_packet_options ( dhcppkt, + &dhcp_request_options )) !=0 ){ + DBG ( "DHCP could not set common DHCP options: %s\n", + strerror ( rc ) ); + return rc; + } + + /* Copy any required options from previous server repsonse */ + if ( options ) { + if ( ( rc = copy_dhcp_packet_option ( dhcppkt, options, + DHCP_SERVER_IDENTIFIER, + DHCP_SERVER_IDENTIFIER ) ) != 0 ) { + DBG ( "DHCP could not set server identifier " + "option: %s\n", strerror ( rc ) ); + return rc; + } + if ( ( rc = copy_dhcp_packet_option ( dhcppkt, options, + DHCP_EB_YIADDR, + DHCP_REQUESTED_ADDRESS ) ) != 0 ) { + DBG ( "DHCP could not set requested address " + "option: %s\n", strerror ( rc ) ); + return rc; + } + } + + /* Add options to identify the network device */ + dhcp_desc.type = desc->bus_type; + dhcp_desc.vendor = htons ( desc->vendor ); + dhcp_desc.device = htons ( desc->device ); + if ( ( rc = set_dhcp_packet_option ( dhcppkt, DHCP_EB_BUS_ID, + &dhcp_desc, + sizeof ( dhcp_desc ) ) ) != 0 ) { + DBG ( "DHCP could not set bus ID option: %s\n", + strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Create DHCP response + * + * @v netdev Network device + * @v msgtype DHCP message type + * @v options DHCP options, or NULL + * @v data Buffer for DHCP packet + * @v max_len Size of DHCP packet buffer + * @v dhcppkt DHCP packet structure to fill in + * @ret rc Return status code + */ +int create_dhcp_response ( struct net_device *netdev, int msgtype, + struct dhcp_option_block *options, + void *data, size_t max_len, + struct dhcp_packet *dhcppkt ) { + int rc; + + /* Create packet and copy in options */ + if ( ( rc = create_dhcp_packet ( netdev, msgtype, data, max_len, + dhcppkt ) ) != 0 ) { + DBG ( " failed to build packet" ); + return rc; + } + if ( ( rc = copy_dhcp_packet_options ( dhcppkt, options ) ) != 0 ) { + DBG ( " failed to copy options" ); + return rc; + } + + return 0; +} + +/**************************************************************************** + * * DHCP to UDP interface * */ @@ -556,8 +673,8 @@ static int dhcp_send_request ( struct dhcp_session *dhcp ) { struct xfer_metadata meta = { .netdev = dhcp->netdev, }; - struct dhcp_packet dhcppkt; struct io_buffer *iobuf; + struct dhcp_packet dhcppkt; int rc; DBGC ( dhcp, "DHCP %p transmitting %s\n", @@ -577,40 +694,15 @@ static int dhcp_send_request ( struct dhcp_session *dhcp ) { return -ENOMEM; /* Create DHCP packet in temporary buffer */ - if ( ( rc = create_dhcp_packet ( dhcp->netdev, dhcp->state, - iobuf->data, iob_tailroom ( iobuf ), - &dhcppkt ) ) != 0 ) { - DBGC ( dhcp, "DHCP %p could not create DHCP packet: %s\n", + if ( ( rc = create_dhcp_request ( dhcp->netdev, dhcp->state, + dhcp->options, iobuf->data, + iob_tailroom ( iobuf ), + &dhcppkt ) ) != 0 ) { + DBGC ( dhcp, "DHCP %p could not construct DHCP request: %s\n", dhcp, strerror ( rc ) ); goto done; } - /* Copy in options common to all requests */ - if ( ( rc = copy_dhcp_packet_options ( &dhcppkt, - &dhcp_request_options ) ) != 0){ - DBGC ( dhcp, "DHCP %p could not set common DHCP options: %s\n", - dhcp, strerror ( rc ) ); - goto done; - } - - /* Copy any required options from previous server repsonse */ - if ( dhcp->options ) { - if ( ( rc = copy_dhcp_packet_option ( &dhcppkt, dhcp->options, - DHCP_SERVER_IDENTIFIER, - DHCP_SERVER_IDENTIFIER ) ) != 0 ) { - DBGC ( dhcp, "DHCP %p could not set server identifier " - "option: %s\n", dhcp, strerror ( rc ) ); - goto done; - } - if ( ( rc = copy_dhcp_packet_option ( &dhcppkt, dhcp->options, - DHCP_EB_YIADDR, - DHCP_REQUESTED_ADDRESS ) ) != 0 ) { - DBGC ( dhcp, "DHCP %p could not set requested address " - "option: %s\n", dhcp, strerror ( rc ) ); - goto done; - } - } - /* Transmit the packet */ iob_put ( iobuf, dhcppkt.len ); rc = xfer_deliver_iob_meta ( &dhcp->xfer, iobuf, &meta ); |
