david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

Add preliminary support for MTFTP.

This commit is contained in:
Michael Brown 2007-12-26 18:51:20 +00:00
parent 9d4e4dbc32
commit f3265b4bf8
2 changed files with 299 additions and 114 deletions

View File

@ -29,6 +29,8 @@
#define TFTP_ERR_UNKNOWN_USER 7 /**< No such user */
#define TFTP_ERR_BAD_OPTS 8 /**< Option negotiation failed */
#define MTFTP_PORT 1759 /**< Default MTFTP server port */
/** A TFTP read request (RRQ) packet */
struct tftp_rrq {
uint16_t opcode;

View File

@ -74,24 +74,23 @@ struct tftp_request {
* "tsize" option, this value will be zero.
*/
unsigned long tsize;
/** Multicast address
*
* This is the destination address for multicast data
* transmissions.
*/
struct sockaddr_tcpip multicast;
/** Master client
*
* True if this is the client responsible for sending ACKs.
*/
int master;
/** Server port
*
* This is the port to which RRQ packets are sent.
*/
unsigned int port;
/** Peer address
*
* The peer address is determined by the first response
* received to the TFTP RRQ.
*/
struct sockaddr_tcpip peer;
/** Request flags */
unsigned int flags;
/** MTFTP timeout count */
unsigned int mtftp_timeouts;
/** Block bitmap */
struct bitmap bitmap;
/** Maximum known length
@ -110,6 +109,21 @@ struct tftp_request {
struct retry_timer timer;
};
/** TFTP request flags */
enum {
/** Send ACK packets */
TFTP_FL_SEND_ACK = 0x0001,
/** Request blksize and tsize options */
TFTP_FL_RRQ_SIZES = 0x0002,
/** Request multicast option */
TFTP_FL_RRQ_MULTICAST = 0x0004,
/** Perform MTFTP recovery on timeout */
TFTP_FL_MTFTP_RECOVERY = 0x0008,
};
/** Maximum number of MTFTP open requests before falling back to TFTP */
#define MTFTP_MAX_TIMEOUTS 3
/**
* Free TFTP request
*
@ -147,6 +161,67 @@ static void tftp_done ( struct tftp_request *tftp, int rc ) {
xfer_close ( &tftp->xfer, rc );
}
/**
* Reopen TFTP socket
*
* @v tftp TFTP connection
* @ret rc Return status code
*/
static int tftp_reopen ( struct tftp_request *tftp ) {
struct sockaddr_tcpip server;
int rc;
/* Close socket */
xfer_close ( &tftp->socket, 0 );
/* Disable ACK sending. */
tftp->flags &= ~TFTP_FL_SEND_ACK;
/* Reset peer address */
memset ( &tftp->peer, 0, sizeof ( tftp->peer ) );
/* Open socket */
memset ( &server, 0, sizeof ( server ) );
server.st_port = htons ( tftp->port );
if ( ( rc = xfer_open_named_socket ( &tftp->socket, SOCK_DGRAM,
( struct sockaddr * ) &server,
tftp->uri->host, NULL ) ) != 0 ) {
DBGC ( tftp, "TFTP %p could not open socket: %s\n",
tftp, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Reopen TFTP multicast socket
*
* @v tftp TFTP connection
* @v local Local socket address
* @ret rc Return status code
*/
static int tftp_reopen_mc ( struct tftp_request *tftp,
struct sockaddr *local ) {
int rc;
/* Close multicast socket */
xfer_close ( &tftp->mc_socket, 0 );
/* Open multicast socket. We never send via this socket, so
* use the local address as the peer address (since the peer
* address cannot be NULL).
*/
if ( ( rc = xfer_open_socket ( &tftp->mc_socket, SOCK_DGRAM,
local, local ) ) != 0 ) {
DBGC ( tftp, "TFTP %p could not open multicast "
"socket: %s\n", tftp, strerror ( rc ) );
return rc;
}
return 0;
}
/**
* Presize TFTP receive buffers and block bitmap
*
@ -201,6 +276,35 @@ void tftp_set_request_blksize ( unsigned int blksize ) {
tftp_request_blksize = blksize;
}
/**
* MTFTP multicast receive address
*
* This is treated as a global configuration parameter.
*/
static struct sockaddr_in tftp_mtftp_socket = {
.sin_family = AF_INET,
.sin_addr.s_addr = htonl ( 0xefff0101 ),
.sin_port = htons ( 3001 ),
};
/**
* Set MTFTP multicast address
*
* @v address Multicast IPv4 address
*/
void tftp_set_mtftp_address ( struct in_addr address ) {
tftp_mtftp_socket.sin_addr = address;
}
/**
* Set MTFTP multicast port
*
* @v port Multicast port
*/
void tftp_set_mtftp_port ( unsigned int port ) {
tftp_mtftp_socket.sin_port = htons ( port );
}
/**
* Transmit RRQ
*
@ -227,11 +331,19 @@ static int tftp_send_rrq ( struct tftp_request *tftp ) {
/* Build request */
rrq = iob_put ( iobuf, sizeof ( *rrq ) );
rrq->opcode = htons ( TFTP_RRQ );
iob_put ( iobuf,
snprintf ( rrq->data, iob_tailroom ( iobuf ),
"%s%coctet%cblksize%c%d%ctsize%c0%cmulticast%c",
path, 0, 0, 0, tftp_request_blksize, 0,
0, 0, 0 ) + 1 );
iob_put ( iobuf, snprintf ( iobuf->tail, iob_tailroom ( iobuf ),
"%s%coctet", path, 0 ) + 1 );
if ( tftp->flags & TFTP_FL_RRQ_SIZES ) {
iob_put ( iobuf, snprintf ( iobuf->tail,
iob_tailroom ( iobuf ),
"blksize%c%d%ctsize%c0", 0,
tftp_request_blksize, 0, 0 ) + 1 );
}
if ( tftp->flags & TFTP_FL_RRQ_MULTICAST ) {
iob_put ( iobuf, snprintf ( iobuf->tail,
iob_tailroom ( iobuf ),
"multicast%c", 0 ) + 1 );
}
/* RRQ always goes to the address specified in the initial
* xfer_open() call
@ -283,16 +395,15 @@ static int tftp_send_packet ( struct tftp_request *tftp ) {
stop_timer ( &tftp->timer );
start_timer ( &tftp->timer );
/* If we are the master client, send RRQ or ACK as appropriate */
if ( tftp->master ) {
if ( ! tftp->peer.st_family ) {
return tftp_send_rrq ( tftp );
} else {
return tftp_send_ack ( tftp );
}
/* Send RRQ or ACK as appropriate */
if ( ! tftp->peer.st_family ) {
return tftp_send_rrq ( tftp );
} else {
/* Do nothing when not the master client */
return 0;
if ( tftp->flags & TFTP_FL_SEND_ACK ) {
return tftp_send_ack ( tftp );
} else {
return 0;
}
}
}
@ -305,12 +416,61 @@ static int tftp_send_packet ( struct tftp_request *tftp ) {
static void tftp_timer_expired ( struct retry_timer *timer, int fail ) {
struct tftp_request *tftp =
container_of ( timer, struct tftp_request, timer );
int rc;
if ( fail ) {
tftp_done ( tftp, -ETIMEDOUT );
/* If we are doing MTFTP, attempt the various recovery strategies */
if ( tftp->flags & TFTP_FL_MTFTP_RECOVERY ) {
if ( tftp->peer.st_family ) {
/* If we have received any response from the server,
* try resending the RRQ to restart the download.
*/
DBGC ( tftp, "TFTP %p attempting reopen\n", tftp );
if ( ( rc = tftp_reopen ( tftp ) ) != 0 )
goto err;
} else {
/* Fall back to plain TFTP after several attempts */
tftp->mtftp_timeouts++;
DBGC ( tftp, "TFTP %p timeout %d waiting for MTFTP "
"open\n", tftp, tftp->mtftp_timeouts );
if ( tftp->mtftp_timeouts > MTFTP_MAX_TIMEOUTS ) {
DBGC ( tftp, "TFTP %p falling back to plain "
"TFTP\n", tftp );
tftp->flags = TFTP_FL_RRQ_SIZES;
/* Close multicast socket */
xfer_close ( &tftp->mc_socket, 0 );
/* Reset retry timer */
start_timer_nodelay ( &tftp->timer );
/* The blocksize may change: discard
* the block bitmap
*/
bitmap_free ( &tftp->bitmap );
memset ( &tftp->bitmap, 0,
sizeof ( tftp->bitmap ) );
/* Reopen on standard TFTP port */
tftp->port = TFTP_PORT;
if ( ( rc = tftp_reopen ( tftp ) ) != 0 )
goto err;
}
}
} else {
tftp_send_packet ( tftp );
/* Not doing MTFTP (or have fallen back to plain
* TFTP); fail as per normal.
*/
if ( fail ) {
rc = -ETIMEDOUT;
goto err;
}
}
tftp_send_packet ( tftp );
return;
err:
tftp_done ( tftp, rc );
}
/**
@ -366,15 +526,16 @@ static int tftp_process_tsize ( struct tftp_request *tftp,
*/
static int tftp_process_multicast ( struct tftp_request *tftp,
const char *value ) {
struct sockaddr_in *sin = ( struct sockaddr_in * ) &tftp->multicast;
union {
struct sockaddr sa;
struct sockaddr_in sin;
} socket;
char buf[ strlen ( value ) + 1 ];
char *addr;
char *port;
char *port_end;
char *mc;
char *mc_end;
struct sockaddr *mc_peer;
struct sockaddr *mc_local;
int rc;
/* Split value into "addr,port,mc" fields */
@ -394,45 +555,33 @@ static int tftp_process_multicast ( struct tftp_request *tftp,
*(mc++) = '\0';
/* Parse parameters */
if ( *addr ) {
if ( inet_aton ( addr, &sin->sin_addr ) == 0 ) {
if ( strtoul ( mc, &mc_end, 0 ) == 0 )
tftp->flags &= ~TFTP_FL_SEND_ACK;
if ( *mc_end ) {
DBGC ( tftp, "TFTP %p multicast invalid mc %s\n", tftp, mc );
return -EINVAL;
}
DBGC ( tftp, "TFTP %p is%s the master client\n",
tftp, ( ( tftp->flags & TFTP_FL_SEND_ACK ) ? "" : " not" ) );
if ( *addr && *port ) {
socket.sin.sin_family = AF_INET;
if ( inet_aton ( addr, &socket.sin.sin_addr ) == 0 ) {
DBGC ( tftp, "TFTP %p multicast invalid IP address "
"%s\n", tftp, addr );
return -EINVAL;
}
DBGC ( tftp, "TFTP %p multicast IP address %s\n",
tftp, inet_ntoa ( sin->sin_addr ) );
}
if ( *port ) {
sin->sin_port = htons ( strtoul ( port, &port_end, 0 ) );
tftp, inet_ntoa ( socket.sin.sin_addr ) );
socket.sin.sin_port = htons ( strtoul ( port, &port_end, 0 ) );
if ( *port_end ) {
DBGC ( tftp, "TFTP %p multicast invalid port %s\n",
tftp, port );
return -EINVAL;
}
DBGC ( tftp, "TFTP %p multicast port %d\n",
tftp, ntohs ( sin->sin_port ) );
}
tftp->master = strtoul ( mc, &mc_end, 0 );
if ( *mc_end ) {
DBGC ( tftp, "TFTP %p multicast invalid mc %s\n", tftp, mc );
return -EINVAL;
}
DBGC ( tftp, "TFTP %p is%s the master client\n",
tftp, ( tftp->master ? "" : " not" ) );
/* Open multicast socket, if new address specified */
if ( *addr || *port ) {
xfer_close ( &tftp->mc_socket, 0 );
mc_peer = ( ( struct sockaddr * ) &tftp->peer );
mc_local = ( ( struct sockaddr * ) &tftp->multicast );
mc_local->sa_family = mc_peer->sa_family;
if ( ( rc = xfer_open_socket ( &tftp->mc_socket, SOCK_DGRAM,
mc_peer, mc_local ) ) != 0 ) {
DBGC ( tftp, "TFTP %p could not open multicast "
"socket: %s\n", tftp, strerror ( rc ) );
tftp, ntohs ( socket.sin.sin_port ) );
if ( ( rc = tftp_reopen_mc ( tftp, &socket.sa ) ) != 0 )
return rc;
}
}
return 0;
@ -611,10 +760,10 @@ static int tftp_rx_data ( struct tftp_request *tftp,
}
/** Translation between TFTP errors and internal error numbers */
static const uint8_t tftp_errors[] = {
[TFTP_ERR_FILE_NOT_FOUND] = PXENV_STATUS_TFTP_FILE_NOT_FOUND,
[TFTP_ERR_ACCESS_DENIED] = PXENV_STATUS_TFTP_ACCESS_VIOLATION,
[TFTP_ERR_ILLEGAL_OP] = PXENV_STATUS_TFTP_UNKNOWN_OPCODE,
static const int tftp_errors[] = {
[TFTP_ERR_FILE_NOT_FOUND] = ENOENT,
[TFTP_ERR_ACCESS_DENIED] = EACCES,
[TFTP_ERR_ILLEGAL_OP] = ENOTSUP,
};
/**
@ -645,7 +794,7 @@ static int tftp_rx_error ( struct tftp_request *tftp, void *buf, size_t len ) {
if ( err < ( sizeof ( tftp_errors ) / sizeof ( tftp_errors[0] ) ) )
rc = -tftp_errors[err];
if ( ! rc )
rc = -PXENV_STATUS_TFTP_CANNOT_OPEN_CONNECTION;
rc = -ENOTSUP;
/* Close TFTP request */
tftp_done ( tftp, rc );
@ -736,32 +885,29 @@ static int tftp_socket_deliver_iob ( struct xfer_interface *socket,
struct tftp_request *tftp =
container_of ( socket, struct tftp_request, socket );
/* Enable sending ACKs when we receive a unicast packet. This
* covers three cases:
*
* 1. Standard TFTP; we should always send ACKs, and will
* always receive a unicast packet before we need to send the
* first ACK.
*
* 2. RFC2090 multicast TFTP; the only unicast packets we will
* receive are the OACKs; enable sending ACKs here (before
* processing the OACK) and disable it when processing the
* multicast option if we are not the master client.
*
* 3. MTFTP; receiving a unicast datagram indicates that we
* are the "master client" and should send ACKs.
*/
tftp->flags |= TFTP_FL_SEND_ACK;
return tftp_rx ( tftp, iobuf, meta );
}
/**
* TFTP connection closed by network stack
*
* @v socket Transport layer interface
* @v rc Reason for close
*/
static void tftp_socket_close ( struct xfer_interface *socket, int rc ) {
struct tftp_request *tftp =
container_of ( socket, struct tftp_request, socket );
DBGC ( tftp, "TFTP %p socket closed: %s\n",
tftp, strerror ( rc ) );
/* Any close counts as an error */
if ( ! rc )
rc = -ECONNRESET;
tftp_done ( tftp, rc );
}
/** TFTP socket operations */
static struct xfer_interface_operations tftp_socket_operations = {
.close = tftp_socket_close,
.close = ignore_xfer_close,
.vredirect = xfer_vopen,
.seek = ignore_xfer_seek,
.window = unlimited_xfer_window,
@ -787,29 +933,9 @@ static int tftp_mc_socket_deliver_iob ( struct xfer_interface *mc_socket,
return tftp_rx ( tftp, iobuf, meta );
}
/**
* TFTP multicast connection closed by network stack
*
* @v socket Multicast transport layer interface
* @v rc Reason for close
*/
static void tftp_mc_socket_close ( struct xfer_interface *mc_socket,
int rc ) {
struct tftp_request *tftp =
container_of ( mc_socket, struct tftp_request, mc_socket );
DBGC ( tftp, "TFTP %p multicast socket closed: %s\n",
tftp, strerror ( rc ) );
/* The multicast socket may be closed when we receive a new
* OACK and open/reopen the socket; we should not call
* tftp_done() at this point.
*/
}
/** TFTP multicast socket operations */
static struct xfer_interface_operations tftp_mc_socket_operations = {
.close = tftp_mc_socket_close,
.close = ignore_xfer_close,
.vredirect = xfer_vopen,
.seek = ignore_xfer_seek,
.window = unlimited_xfer_window,
@ -846,15 +972,17 @@ static struct xfer_interface_operations tftp_xfer_operations = {
};
/**
* Initiate TFTP download
* Initiate TFTP/TFTM/MTFTP download
*
* @v xfer Data transfer interface
* @v uri Uniform Resource Identifier
* @ret rc Return status code
*/
int tftp_open ( struct xfer_interface *xfer, struct uri *uri ) {
static int tftp_core_open ( struct xfer_interface *xfer, struct uri *uri,
unsigned int default_port,
struct sockaddr *multicast,
unsigned int flags ) {
struct tftp_request *tftp;
struct sockaddr_tcpip server;
int rc;
/* Sanity checks */
@ -874,17 +1002,20 @@ int tftp_open ( struct xfer_interface *xfer, struct uri *uri ) {
xfer_init ( &tftp->mc_socket, &tftp_mc_socket_operations,
&tftp->refcnt );
tftp->blksize = TFTP_DEFAULT_BLKSIZE;
tftp->master = 1;
tftp->flags = flags;
tftp->timer.expired = tftp_timer_expired;
/* Open socket */
memset ( &server, 0, sizeof ( server ) );
server.st_port = htons ( uri_port ( tftp->uri, TFTP_PORT ) );
if ( ( rc = xfer_open_named_socket ( &tftp->socket, SOCK_DGRAM,
( struct sockaddr * ) &server,
uri->host, NULL ) ) != 0 )
tftp->port = uri_port ( tftp->uri, default_port );
if ( ( rc = tftp_reopen ( tftp ) ) != 0 )
goto err;
/* Open multicast socket */
if ( multicast ) {
if ( ( rc = tftp_reopen_mc ( tftp, multicast ) ) != 0 )
goto err;
}
/* Start timer to initiate RRQ */
start_timer_nodelay ( &tftp->timer );
@ -901,8 +1032,60 @@ int tftp_open ( struct xfer_interface *xfer, struct uri *uri ) {
return rc;
}
/**
* Initiate TFTP download
*
* @v xfer Data transfer interface
* @v uri Uniform Resource Identifier
* @ret rc Return status code
*/
static int tftp_open ( struct xfer_interface *xfer, struct uri *uri ) {
return tftp_core_open ( xfer, uri, TFTP_PORT, NULL,
TFTP_FL_RRQ_SIZES );
}
/** TFTP URI opener */
struct uri_opener tftp_uri_opener __uri_opener = {
.scheme = "tftp",
.open = tftp_open,
};
/**
* Initiate TFTM download
*
* @v xfer Data transfer interface
* @v uri Uniform Resource Identifier
* @ret rc Return status code
*/
static int tftm_open ( struct xfer_interface *xfer, struct uri *uri ) {
return tftp_core_open ( xfer, uri, TFTP_PORT, NULL,
( TFTP_FL_RRQ_SIZES |
TFTP_FL_RRQ_MULTICAST ) );
}
/** TFTM URI opener */
struct uri_opener tftm_uri_opener __uri_opener = {
.scheme = "tftm",
.open = tftm_open,
};
/**
* Initiate MTFTP download
*
* @v xfer Data transfer interface
* @v uri Uniform Resource Identifier
* @ret rc Return status code
*/
static int mtftp_open ( struct xfer_interface *xfer, struct uri *uri ) {
return tftp_core_open ( xfer, uri, MTFTP_PORT,
( struct sockaddr * ) &tftp_mtftp_socket,
TFTP_FL_MTFTP_RECOVERY );
}
/** MTFTP URI opener */
struct uri_opener mtftp_uri_opener __uri_opener = {
.scheme = "mtftp",
.open = mtftp_open,
};