david/ipxe
david
/
ipxe
Archived
1
0
Fork 0
This repository has been archived on 2020-12-06. You can view files and clone it, but cannot push or open issues or pull requests.
ipxe/src/net/udp.c

442 lines
11 KiB
C
Raw Normal View History

2006-06-25 07:13:17 +02:00
#include <stdint.h>
#include <stdlib.h>
2006-06-25 07:13:17 +02:00
#include <string.h>
#include <assert.h>
#include <byteswap.h>
#include <errno.h>
#include <ipxe/tcpip.h>
#include <ipxe/iobuf.h>
#include <ipxe/xfer.h>
#include <ipxe/open.h>
#include <ipxe/uri.h>
#include <ipxe/netdevice.h>
#include <ipxe/udp.h>
2006-06-25 07:13:17 +02:00
/** @file
*
* UDP protocol
*/
FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
/**
* A UDP connection
*
*/
struct udp_connection {
/** Reference counter */
struct refcnt refcnt;
/** List of UDP connections */
struct list_head list;
/** Data transfer interface */
struct interface xfer;
/** Local socket address */
struct sockaddr_tcpip local;
/** Remote socket address */
struct sockaddr_tcpip peer;
};
/**
* List of registered UDP connections
*/
static LIST_HEAD ( udp_conns );
2006-06-28 09:46:28 +02:00
/* Forward declatations */
static struct interface_descriptor udp_xfer_desc;
struct tcpip_protocol udp_protocol __tcpip_protocol;
2006-06-28 09:46:28 +02:00
2006-06-25 07:13:17 +02:00
/**
* Check if local UDP port is available
*
* @v port Local port number
* @ret port Local port number, or negative error
2006-06-25 07:13:17 +02:00
*/
static int udp_port_available ( int port ) {
struct udp_connection *udp;
list_for_each_entry ( udp, &udp_conns, list ) {
if ( udp->local.st_port == htons ( port ) )
return -EADDRINUSE;
2007-01-16 04:19:40 +01:00
}
return port;
}
/**
* Open a UDP connection
*
* @v xfer Data transfer interface
* @v peer Peer socket address, or NULL
* @v local Local socket address, or NULL
* @v promisc Socket is promiscuous
* @ret rc Return status code
*/
static int udp_open_common ( struct interface *xfer,
struct sockaddr *peer, struct sockaddr *local,
int promisc ) {
struct sockaddr_tcpip *st_peer = ( struct sockaddr_tcpip * ) peer;
struct sockaddr_tcpip *st_local = ( struct sockaddr_tcpip * ) local;
struct udp_connection *udp;
int port;
int rc;
/* Allocate and initialise structure */
2007-07-06 21:08:41 +02:00
udp = zalloc ( sizeof ( *udp ) );
if ( ! udp )
return -ENOMEM;
DBGC ( udp, "UDP %p allocated\n", udp );
ref_init ( &udp->refcnt, NULL );
intf_init ( &udp->xfer, &udp_xfer_desc, &udp->refcnt );
if ( st_peer )
memcpy ( &udp->peer, st_peer, sizeof ( udp->peer ) );
if ( st_local )
memcpy ( &udp->local, st_local, sizeof ( udp->local ) );
/* Bind to local port */
if ( ! promisc ) {
port = tcpip_bind ( st_local, udp_port_available );
if ( port < 0 ) {
rc = port;
DBGC ( udp, "UDP %p could not bind: %s\n",
udp, strerror ( rc ) );
goto err;
}
udp->local.st_port = htons ( port );
DBGC ( udp, "UDP %p bound to port %d\n",
udp, ntohs ( udp->local.st_port ) );
}
/* Attach parent interface, transfer reference to connection
* list and return
*/
intf_plug_plug ( &udp->xfer, xfer );
list_add ( &udp->list, &udp_conns );
return 0;
err:
ref_put ( &udp->refcnt );
return rc;
2006-06-25 07:13:17 +02:00
}
/**
* Open a UDP connection
*
* @v xfer Data transfer interface
* @v peer Peer socket address
* @v local Local socket address, or NULL
* @ret rc Return status code
*/
int udp_open ( struct interface *xfer, struct sockaddr *peer,
struct sockaddr *local ) {
return udp_open_common ( xfer, peer, local, 0 );
}
2006-06-25 07:13:17 +02:00
/**
* Open a promiscuous UDP connection
*
* @v xfer Data transfer interface
* @ret rc Return status code
*
* Promiscuous UDP connections are required in order to support the
* PXE API.
*/
int udp_open_promisc ( struct interface *xfer ) {
return udp_open_common ( xfer, NULL, NULL, 1 );
}
/**
* Close a UDP connection
*
* @v udp UDP connection
* @v rc Reason for close
*/
static void udp_close ( struct udp_connection *udp, int rc ) {
/* Close data transfer interface */
intf_shutdown ( &udp->xfer, rc );
/* Remove from list of connections and drop list's reference */
list_del ( &udp->list );
ref_put ( &udp->refcnt );
DBGC ( udp, "UDP %p closed\n", udp );
}
/**
* Transmit data via a UDP connection to a specified address
2006-06-25 07:13:17 +02:00
*
* @v udp UDP connection
* @v iobuf I/O buffer
* @v src Source address, or NULL to use default
* @v dest Destination address, or NULL to use default
* @v netdev Network device, or NULL to use default
* @ret rc Return status code
2006-06-25 07:13:17 +02:00
*/
static int udp_tx ( struct udp_connection *udp, struct io_buffer *iobuf,
struct sockaddr_tcpip *src, struct sockaddr_tcpip *dest,
struct net_device *netdev ) {
struct udp_header *udphdr;
size_t len;
2007-01-16 04:19:40 +01:00
int rc;
/* Check we can accommodate the header */
if ( ( rc = iob_ensure_headroom ( iobuf,
MAX_LL_NET_HEADER_LEN ) ) != 0 ) {
free_iob ( iobuf );
return rc;
}
2006-06-28 09:46:28 +02:00
/* Fill in default values if not explicitly provided */
if ( ! src )
src = &udp->local;
if ( ! dest )
dest = &udp->peer;
2006-06-25 07:13:17 +02:00
/* Add the UDP header */
udphdr = iob_push ( iobuf, sizeof ( *udphdr ) );
len = iob_len ( iobuf );
udphdr->dest = dest->st_port;
udphdr->src = src->st_port;
udphdr->len = htons ( len );
2006-07-20 01:38:05 +02:00
udphdr->chksum = 0;
udphdr->chksum = tcpip_chksum ( udphdr, len );
2006-06-28 09:46:28 +02:00
/* Dump debugging information */
DBGC2 ( udp, "UDP %p TX %d->%d len %d\n", udp,
ntohs ( udphdr->src ), ntohs ( udphdr->dest ),
ntohs ( udphdr->len ) );
2006-06-28 09:46:28 +02:00
/* Send it to the next layer for processing */
if ( ( rc = tcpip_tx ( iobuf, &udp_protocol, src, dest, netdev,
2007-01-16 04:19:40 +01:00
&udphdr->chksum ) ) != 0 ) {
DBGC ( udp, "UDP %p could not transmit packet: %s\n",
udp, strerror ( rc ) );
2007-01-16 04:19:40 +01:00
return rc;
}
return 0;
}
/**
* Identify UDP connection by local address
*
* @v local Local address
* @ret udp UDP connection, or NULL
*/
static struct udp_connection * udp_demux ( struct sockaddr_tcpip *local ) {
static const struct sockaddr_tcpip empty_sockaddr = { .pad = { 0, } };
struct udp_connection *udp;
list_for_each_entry ( udp, &udp_conns, list ) {
if ( ( ( udp->local.st_family == local->st_family ) ||
( udp->local.st_family == 0 ) ) &&
( ( udp->local.st_port == local->st_port ) ||
( udp->local.st_port == 0 ) ) &&
( ( memcmp ( udp->local.pad, local->pad,
sizeof ( udp->local.pad ) ) == 0 ) ||
( memcmp ( udp->local.pad, empty_sockaddr.pad,
sizeof ( udp->local.pad ) ) == 0 ) ) ) {
return udp;
}
}
return NULL;
}
2006-06-25 07:13:17 +02:00
/**
* Process a received packet
*
* @v iobuf I/O buffer
* @v netdev Network device
* @v st_src Partially-filled source address
* @v st_dest Partially-filled destination address
* @v pshdr_csum Pseudo-header checksum
* @ret rc Return status code
2006-06-25 07:13:17 +02:00
*/
static int udp_rx ( struct io_buffer *iobuf,
struct net_device *netdev __unused,
struct sockaddr_tcpip *st_src,
struct sockaddr_tcpip *st_dest, uint16_t pshdr_csum ) {
struct udp_header *udphdr = iobuf->data;
struct udp_connection *udp;
struct xfer_metadata meta;
size_t ulen;
unsigned int csum;
int rc = 0;
2006-06-28 09:46:28 +02:00
/* Sanity check packet */
if ( iob_len ( iobuf ) < sizeof ( *udphdr ) ) {
DBG ( "UDP packet too short at %zd bytes (min %zd bytes)\n",
iob_len ( iobuf ), sizeof ( *udphdr ) );
rc = -EINVAL;
goto done;
2006-06-28 09:46:28 +02:00
}
ulen = ntohs ( udphdr->len );
if ( ulen < sizeof ( *udphdr ) ) {
DBG ( "UDP length too short at %zd bytes "
"(header is %zd bytes)\n", ulen, sizeof ( *udphdr ) );
rc = -EINVAL;
goto done;
}
if ( ulen > iob_len ( iobuf ) ) {
DBG ( "UDP length too long at %zd bytes (packet is %zd "
"bytes)\n", ulen, iob_len ( iobuf ) );
rc = -EINVAL;
goto done;
2006-06-28 09:46:28 +02:00
}
if ( udphdr->chksum ) {
csum = tcpip_continue_chksum ( pshdr_csum, iobuf->data, ulen );
if ( csum != 0 ) {
DBG ( "UDP checksum incorrect (is %04x including "
"checksum field, should be 0000)\n", csum );
rc = -EINVAL;
goto done;
}
2006-06-28 09:46:28 +02:00
}
/* Parse parameters from header and strip header */
st_src->st_port = udphdr->src;
st_dest->st_port = udphdr->dest;
udp = udp_demux ( st_dest );
iob_unput ( iobuf, ( iob_len ( iobuf ) - ulen ) );
iob_pull ( iobuf, sizeof ( *udphdr ) );
2006-06-28 09:46:28 +02:00
/* Dump debugging information */
DBGC2 ( udp, "UDP %p RX %d<-%d len %zd\n", udp,
ntohs ( udphdr->dest ), ntohs ( udphdr->src ), ulen );
2006-06-28 09:46:28 +02:00
/* Ignore if no matching connection found */
if ( ! udp ) {
DBG ( "No UDP connection listening on port %d\n",
ntohs ( udphdr->dest ) );
rc = -ENOTCONN;
goto done;
}
2006-06-28 09:46:28 +02:00
/* Pass data to application */
memset ( &meta, 0, sizeof ( meta ) );
meta.src = ( struct sockaddr * ) st_src;
meta.dest = ( struct sockaddr * ) st_dest;
rc = xfer_deliver ( &udp->xfer, iob_disown ( iobuf ), &meta );
done:
free_iob ( iobuf );
return rc;
2006-06-25 07:13:17 +02:00
}
struct tcpip_protocol udp_protocol __tcpip_protocol = {
2006-06-28 09:46:28 +02:00
.name = "UDP",
.rx = udp_rx,
[tcpip] Avoid generating positive zero for transmitted UDP checksums TCP/IP checksum fields are one's complement values and therefore have two possible representations of zero: positive zero (0x0000) and negative zero (0xffff). In RFC768, UDP over IPv4 exploits this redundancy to repurpose the positive representation of zero (0x0000) to mean "no checksum calculated"; checksums are optional for UDP over IPv4. In RFC2460, checksums are made mandatory for UDP over IPv4. The wording of the RFC is such that the UDP header is mandated to use only the negative representation of zero (0xffff), rather than simply requiring the checksum to be correct but allowing for either representation of zero to be used. In RFC1071, an example algorithm is given for calculating the TCP/IP checksum. This algorithm happens to produce only the positive representation of zero (0x0000); this is an artifact of the way that unsigned arithmetic is used to calculate a signed one's complement sum (and its final negation). A common misconception has developed (exemplified in RFC1624) that this artifact is part of the specification. Many people have assumed that the checksum field should never contain the negative representation of zero (0xffff). A sensible receiver will calculate the checksum over the whole packet and verify that the result is zero (in whichever representation of zero happens to be generated by the receiver's algorithm). Such a receiver will not care which representation of zero happens to be used in the checksum field. However, there are receivers in existence which will verify the received checksum the hard way: by calculating the checksum over the remainder of the packet and comparing the result against the checksum field. If the representation of zero used by the receiver's algorithm does not match the representation of zero used by the transmitter (and so placed in the checksum field), and if the receiver does not explicitly allow for both representations to compare as equal, then the receiver may reject packets with a valid checksum. For UDP, the combined RFCs effectively mandate that we should generate only the negative representation of zero in the checksum field. For IP, TCP and ICMP, the RFCs do not mandate which representation of zero should be used, but the misconceptions which have grown up around RFC1071 and RFC1624 suggest that it would be least surprising to generate only the positive representation of zero in the checksum field. Fix by ensuring that all of our checksum algorithms generate only the positive representation of zero, and explicitly inverting this in the case of transmitted UDP packets. Reported-by: Wissam Shoukair <wissams@mellanox.com> Tested-by: Wissam Shoukair <wissams@mellanox.com> Signed-off-by: Michael Brown <mcb30@ipxe.org>
2015-09-10 14:19:16 +02:00
.zero_csum = TCPIP_NEGATIVE_ZERO_CSUM,
.tcpip_proto = IP_UDP,
2006-06-25 07:13:17 +02:00
};
/***************************************************************************
*
* Data transfer interface
*
***************************************************************************
*/
/**
* Allocate I/O buffer for UDP
*
* @v udp UDP connection
* @v len Payload size
* @ret iobuf I/O buffer, or NULL
*/
static struct io_buffer * udp_xfer_alloc_iob ( struct udp_connection *udp,
size_t len ) {
struct io_buffer *iobuf;
iobuf = alloc_iob ( MAX_LL_NET_HEADER_LEN + len );
if ( ! iobuf ) {
DBGC ( udp, "UDP %p cannot allocate buffer of length %zd\n",
udp, len );
return NULL;
}
iob_reserve ( iobuf, MAX_LL_NET_HEADER_LEN );
return iobuf;
}
/**
* Deliver datagram as I/O buffer
*
* @v udp UDP connection
* @v iobuf Datagram I/O buffer
* @v meta Data transfer metadata
* @ret rc Return status code
*/
static int udp_xfer_deliver ( struct udp_connection *udp,
struct io_buffer *iobuf,
struct xfer_metadata *meta ) {
/* Transmit data, if possible */
return udp_tx ( udp, iobuf, ( ( struct sockaddr_tcpip * ) meta->src ),
( ( struct sockaddr_tcpip * ) meta->dest ),
meta->netdev );
}
/** UDP data transfer interface operations */
static struct interface_operation udp_xfer_operations[] = {
INTF_OP ( xfer_deliver, struct udp_connection *, udp_xfer_deliver ),
INTF_OP ( xfer_alloc_iob, struct udp_connection *, udp_xfer_alloc_iob ),
INTF_OP ( intf_close, struct udp_connection *, udp_close ),
};
/** UDP data transfer interface descriptor */
static struct interface_descriptor udp_xfer_desc =
INTF_DESC ( struct udp_connection, xfer, udp_xfer_operations );
/***************************************************************************
*
* Openers
*
***************************************************************************
*/
/** UDP IPv4 socket opener */
struct socket_opener udp_ipv4_socket_opener __socket_opener = {
.semantics = UDP_SOCK_DGRAM,
.family = AF_INET,
.open = udp_open,
};
/** UDP IPv6 socket opener */
struct socket_opener udp_ipv6_socket_opener __socket_opener = {
.semantics = UDP_SOCK_DGRAM,
.family = AF_INET6,
.open = udp_open,
};
/** Linkage hack */
int udp_sock_dgram = UDP_SOCK_DGRAM;
/**
* Open UDP URI
*
* @v xfer Data transfer interface
* @v uri URI
* @ret rc Return status code
*/
static int udp_open_uri ( struct interface *xfer, struct uri *uri ) {
struct sockaddr_tcpip peer;
/* Sanity check */
if ( ! uri->host )
return -EINVAL;
memset ( &peer, 0, sizeof ( peer ) );
peer.st_port = htons ( uri_port ( uri, 0 ) );
return xfer_open_named_socket ( xfer, SOCK_DGRAM,
( struct sockaddr * ) &peer,
uri->host, NULL );
}
/** UDP URI opener */
struct uri_opener udp_uri_opener __uri_opener = {
.scheme = "udp",
.open = udp_open_uri,
};