Windows2003-3790/base/boot/tftplib/conn.c

947 lines
23 KiB
C
Raw Normal View History

2001-01-01 00:00:00 +01:00
/*++
Copyright (c) 1997 Microsoft Corporation
Module Name:
conn.c
Abstract:
Boot loader TFTP connection handling routines.
Author:
Chuck Lenzmeier (chuckl) December 27, 1996
based on code by Mike Massa (mikemas) Feb 21, 1992
based on SpiderTCP code
Revision History:
Notes:
--*/
#include "precomp.h"
#pragma hdrstop
ULONG
ConnItoa (
IN ULONG Value,
OUT PUCHAR Buffer
);
ULONG
ConnSafeAtol (
IN PUCHAR Buffer,
IN PUCHAR BufferEnd
);
BOOLEAN
ConnSafeStrequal (
IN PUCHAR Buffer,
IN PUCHAR BufferEnd,
IN PUCHAR CompareString
);
ULONG
ConnSafeStrsize (
IN PUCHAR Buffer,
IN PUCHAR BufferEnd
);
ULONG
ConnStrsize (
IN PUCHAR Buffer
);
NTSTATUS
ConnInitialize (
IN OUT PCONNECTION *Connection,
IN USHORT Operation,
IN ULONG RemoteHost,
IN USHORT RemotePort,
IN PUCHAR Filename,
IN ULONG BlockSize,
IN OUT PULONG FileSize
)
//
// Open up the connection, make a request packet, and send the
// packet out on it. Allocate space for the connection control
// block and fill it in. Allocate another packet for data and,
// on writes, another to hold received packets. Don't wait
// for connection ack; it will be waited for in cn_rcv or cn_wrt.
// Return pointer to the connection control block, or NULL on error.
//
//
{
NTSTATUS status;
PCONNECTION connection;
PTFTP_PACKET packet;
ULONG length;
ULONG stringSize;
PUCHAR options;
PUCHAR end;
BOOLEAN blksizeAcked;
BOOLEAN tsizeAcked;
DPRINT( TRACE, ("ConnInitialize\n") );
//#if 0 //
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
return STATUS_SUCCESS;
#endif
connection = &NetTftpConnection;
*Connection = connection;
RtlZeroMemory( connection, sizeof(CONNECTION) );
connection->Synced = FALSE; // connection not synchronized yet
connection->Operation = Operation;
connection->RemoteHost = RemoteHost;
connection->LocalPort = UdpAssignUnicastPort();
connection->RemotePort = RemotePort;
connection->Timeout = INITIAL_TIMEOUT;
connection->Retransmissions = 0;
connection->LastSentPacket = NetTftpPacket[0];
connection->CurrentPacket = NetTftpPacket[1];
if ( Operation == TFTP_RRQ ) {
connection->LastReceivedPacket = connection->CurrentPacket;
} else {
connection->LastReceivedPacket = NetTftpPacket[2];
}
packet = connection->LastSentPacket;
packet->Opcode = Operation;
//
// TFTP_PACKET structure defines the packet structure for
// TFTP ACK/DATA packets. We're initialing a RRQ/WRQ packet
// which has a different format. We overload this structure to
// the RRQ/WRQ format, graphically depicted below.
// 2 bytes string 1 byte string 1 byte
// ------------------------------------------------
// | Opcode | Filename | 0 | Mode | 0 |
// ------------------------------------------------
//
options = (PUCHAR)&packet->BlockNumber; // start of file name
//
// the TFTP spec doesn't impose a limit on path length.
//
ASSERT(ConnStrsize(Filename) < DEFAULT_BLOCK_SIZE);
strcpy( options, Filename );
//DPRINT( LOUD, ("ConnInitialize: opening %s\n", options) );
length = ConnStrsize( options );
options += length;
length += sizeof(packet->Opcode);
ASSERT(length+sizeof("octet") <= MAXIMUM_TFTP_PACKET_LENGTH);
strcpy( options, "octet" );
length += sizeof("octet");
options += sizeof("octet");
if ( BlockSize == 0 ) {
BlockSize = DEFAULT_BLOCK_SIZE;
}
ASSERT(length+sizeof("blksize") <= MAXIMUM_TFTP_PACKET_LENGTH);
strcpy( options, "blksize" );
length += sizeof("blksize");
options += sizeof("blksize");
ASSERT(length+sizeof("9999") <= MAXIMUM_TFTP_PACKET_LENGTH);
stringSize = ConnItoa( BlockSize, options );
DPRINT( REAL_LOUD, ("ConnInitialize: requesting block size = %s\n", options) );
length += stringSize;
options += stringSize;
ASSERT(length+sizeof("tsize") <= MAXIMUM_TFTP_PACKET_LENGTH);
strcpy( options, "tsize" );
length += sizeof("tsize");
options += sizeof("tsize");
stringSize = ConnItoa( (Operation == TFTP_RRQ) ? 0 : *FileSize, options );
DPRINT( REAL_LOUD, ("ConnInitialize: requesting transfer size = %s\n", options) );
ASSERT(length+stringSize <= MAXIMUM_TFTP_PACKET_LENGTH);
length += stringSize;
options += stringSize;
ConnSendPacket( connection, packet, length );
connection->BlockNumber = 0;
connection->BlockSize = BlockSize;
status = ConnWait( connection, TFTP_OACK, &packet );
if ( NT_SUCCESS(status) ) {
options = (PUCHAR)&packet->BlockNumber;
end = (PUCHAR)packet + connection->LastReceivedLength;
blksizeAcked = FALSE;
tsizeAcked = FALSE;
while ( (options < end) && (!blksizeAcked || !tsizeAcked
) ) {
if ( ConnSafeStrequal(options, end, "blksize") ) {
options += sizeof("blksize");
DPRINT( REAL_LOUD, ("ConnInitialize: received block size = %s\n", options) );
BlockSize = ConnSafeAtol( options, end );
if ( (BlockSize < 8) || (BlockSize > connection->BlockSize) ) {
goto bad_options;
}
options += ConnStrsize(options);
connection->BlockSize = BlockSize;
DPRINT( REAL_LOUD, ("ConnInitialize: block size for transfer = %d\n", BlockSize) );
blksizeAcked = TRUE;
} else if ( ConnSafeStrequal(options, end, "tsize") ) {
options += sizeof("tsize");
DPRINT( REAL_LOUD, ("ConnInitialize: received transfer size = %s\n", options) );
BlockSize = ConnSafeAtol( options, end ); // use this as a temp variable
if ( BlockSize == (ULONG)-1 ) {
goto bad_options;
}
options += ConnStrsize(options);
if ( Operation == TFTP_RRQ ) {
*FileSize = BlockSize;
}
tsizeAcked = TRUE;
} else {
DPRINT( ERROR, ("ConnInitialize: skipping unrecognized option %s\n", options) );
options += ConnSafeStrsize( options, end );
options += ConnSafeStrsize( options, end );
}
}
if ( !blksizeAcked || !tsizeAcked ) {
goto bad_options;
}
if ( Operation == TFTP_RRQ ) {
DPRINT( REAL_LOUD, ("ConnInitialize: ACKing OACK\n") );
ConnAck( connection );
}
}
return status;
bad_options:
DPRINT( ERROR, ("ConnInitialize: bad options in OACK\n") );
ConnError(
connection,
connection->RemoteHost,
connection->RemotePort,
TFTP_ERROR_OPTION_NEGOT_FAILED,
"Bad TFTP options"
);
return STATUS_UNSUCCESSFUL;
} // ConnInitialize
NTSTATUS
ConnReceive (
IN PCONNECTION Connection,
OUT PTFTP_PACKET *Packet
)
//
// Receive a tftp packet into the packet buffer pointed to by Connection->CurrentPacket.
// The packet to be received must be a packet of block number Connection->BlockNumber.
// Returns a pointer to the tftp part of received packet. Also performs
// ack sending and retransmission.
//
{
NTSTATUS status;
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
ASSERT( FALSE );
return STATUS_SUCCESS;
#endif
status = ConnWait( Connection, TFTP_DATA, Packet );
if ( NT_SUCCESS(status) ) {
Connection->CurrentPacket = Connection->LastReceivedPacket;
Connection->CurrentLength = Connection->LastReceivedLength;
ConnAck( Connection );
}
return status;
} // ConnReceive
NTSTATUS
ConnSend (
IN PCONNECTION Connection,
IN ULONG Length
)
//
// Write the data packet contained in Connection->CurrentPacket, with data length len,
// to the net. Wait first for an ack for the previous packet to arrive,
// retransmitting it as needed. Then fill in the net headers, etc. and
// send the packet out. Return TRUE if the packet is sent successfully,
// or FALSE if a timeout or error occurs.
//
{
NTSTATUS status;
PTFTP_PACKET packet;
PVOID temp;
USHORT blockNumber;
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
ASSERT( FALSE );
return STATUS_SUCCESS;
#endif
packet = Connection->CurrentPacket;
packet->Opcode = TFTP_DATA;
blockNumber = Connection->BlockNumber + 1;
if ( blockNumber == 0 ) {
blockNumber = 1;
}
packet->BlockNumber = SWAP_WORD( blockNumber );
Length += sizeof(packet->Opcode) + sizeof(packet->BlockNumber);
if ( Connection->BlockNumber != 0 ) {
status = ConnWait( Connection, TFTP_DACK, NULL );
if ( !NT_SUCCESS(status) ) {
return status;
}
}
Connection->BlockNumber = blockNumber; // next expected block number
Connection->Retransmissions = 0;
temp = Connection->LastSentPacket; // next write packet buffer
ConnSendPacket( Connection, Connection->CurrentPacket, Length ); // sets up LastSent...
Connection->CurrentPacket = temp; // for next ConnPrepareSend
return STATUS_SUCCESS;
} // ConnSend
NTSTATUS
ConnWait (
IN PCONNECTION Connection,
IN USHORT Opcode,
OUT PTFTP_PACKET *Packet OPTIONAL
)
//
// Wait for a valid tftp packet of the specified type to arrive on the
// specified tftp connection, retransmitting the previous packet as needed up
// to the timeout period. When a packet comes in, check it out.
// Return a pointer to the received packet or NULL if error or timeout.
//
{
ULONG now;
ULONG timeout;
ULONG remoteHost;
USHORT remotePort;
PTFTP_PACKET packet;
ULONG length;
USHORT blockNumber;
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
return STATUS_SUCCESS;
#endif
while ( TRUE) {
now = SysGetRelativeTime();
timeout = Connection->NextRetransmit - now;
DPRINT( REAL_LOUD, ("ConnWait: now=%d, next retransmit=%d, timeout=%d\n",
now, Connection->NextRetransmit, timeout) );
length = UdpReceive(
Connection->LastReceivedPacket,
sizeof(TFTP_HEADER) + Connection->BlockSize,
&remoteHost,
&remotePort,
timeout
);
if ( length <= 0 ) {
if ( !ConnRetransmit( Connection, TRUE ) ) {
break;
}
continue;
}
//
// Got a packet; check it out.
//
packet = Connection->LastReceivedPacket;
//
// First, check the received length for validity.
//
Connection->LastReceivedLength = length;
if ( (length < sizeof(TFTP_HEADER)) ||
((packet->Opcode == TFTP_DATA) &&
(length > (sizeof(TFTP_HEADER) + Connection->BlockSize))) ) {
ConnError(
Connection,
remoteHost,
remotePort,
TFTP_ERROR_UNDEFINED,
"Bad TFTP packet length"
);
continue;
}
//
// Next, check for correct remote host.
//
if ( remoteHost != Connection->RemoteHost ) {
ConnError(
Connection,
remoteHost,
remotePort,
TFTP_ERROR_UNKNOWN_TRANSFER_ID,
"Sorry, wasn't talking to you!"
);
continue;
}
//
// Next, the remote port. If still unsynchronized, use his port.
//
blockNumber = SWAP_WORD( packet->BlockNumber );
if ( !Connection->Synced &&
(((packet->Opcode == Opcode) &&
((Opcode == TFTP_OACK) || (blockNumber == Connection->BlockNumber))) ||
(packet->Opcode == TFTP_ERROR)) ) {
Connection->Synced = TRUE;
Connection->RemotePort = remotePort;
Connection->Timeout = TIMEOUT; // normal data timeout
} else if ( remotePort != Connection->RemotePort ) {
ConnError(
Connection,
remoteHost,
remotePort,
TFTP_ERROR_UNKNOWN_TRANSFER_ID,
"Unexpected port number"
);
continue;
}
//
// Now check out the TFTP opcode.
//
if ( packet->Opcode == Opcode ) {
if ( (Opcode == TFTP_OACK) || (blockNumber == Connection->BlockNumber) ) {
if ( Packet != NULL ) {
*Packet = packet;
}
Connection->Timeout = TIMEOUT; // normal data timeout
return STATUS_SUCCESS;
} else if ( (blockNumber == Connection->BlockNumber - 1) &&
(Opcode == TFTP_DATA) ) {
if ( !ConnRetransmit( Connection, FALSE ) ) {
break;
}
} else if ( blockNumber > Connection->BlockNumber ) {
DPRINT( ERROR, ("ConnWait: Block number too high (%d vs. %d)\n",
blockNumber, Connection->BlockNumber) );
ConnError(
Connection,
remoteHost,
remotePort,
TFTP_ERROR_ILLEGAL_OPERATION,
"Block number greater than expected"
);
return STATUS_UNSUCCESSFUL;
} else { // old duplicate; ignore
continue;
}
} else if ( packet->Opcode == TFTP_OACK ) {
DPRINT( ERROR, ("ConnWait: received duplicate OACK packet\n") );
if ( Connection->BlockNumber == 1 ) {
if ( !ConnRetransmit( Connection, FALSE ) ) {
break;
}
}
} else if ( packet->Opcode == TFTP_ERROR ) {
//DPRINT( ERROR, ("ConnWait: received error packet; code %x, msg %s\n",
// packet->BlockNumber, packet->Data) );
return STATUS_UNSUCCESSFUL;
} else { // unexpected TFTP opcode
DPRINT( ERROR, ("ConnWait: received unknown TFTP opcode %d\n", packet->Opcode) );
ConnError(
Connection,
remoteHost,
remotePort,
TFTP_ERROR_ILLEGAL_OPERATION,
"Bad opcode received"
);
return STATUS_UNSUCCESSFUL;
}
}
DPRINT( ERROR, ("ConnWait: timeout\n") );
ConnError(
Connection,
Connection->RemoteHost,
Connection->RemotePort,
TFTP_ERROR_UNDEFINED,
"Timeout on receive" );
return STATUS_IO_TIMEOUT;
} // ConnWait
VOID
ConnAck (
IN PCONNECTION Connection
)
//
// Generate and send an ack packet for the specified connection. Also
// update the block number. Use the packet stored in Connection->LastSent to build
// the ack in.
//
{
PTFTP_PACKET packet;
ULONG length;
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
ASSERT( FALSE );
return;
#endif
packet = Connection->LastSentPacket;
length = 4;
packet->Opcode = TFTP_DACK;
packet->BlockNumber = SWAP_WORD( Connection->BlockNumber );
ConnSendPacket( Connection, packet, length );
Connection->Retransmissions = 0;
Connection->BlockNumber++;
if ( Connection->BlockNumber == 0 ) {
Connection->BlockNumber = 1;
}
return;
} // ConnAck
VOID
ConnError (
IN PCONNECTION Connection,
IN ULONG RemoteHost,
IN USHORT RemotePort,
IN USHORT ErrorCode,
IN PUCHAR ErrorMessage
)
//
// Make an error packet to send to the specified foreign host and port
// with the specified error code and error message. This routine is
// used to send error messages in response to packets received from
// unexpected foreign hosts or tid's as well as those received for the
// current connection. It allocates a packet specially
// for the error message because such error messages will not be
// retransmitted. Send it out on the connection.
//
{
PTFTP_PACKET packet;
ULONG length;
DPRINT( CONN_ERROR, ("ConnError: code %x, msg %s\n", ErrorCode, ErrorMessage) );
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
return;
#endif
packet = (PTFTP_PACKET)NetTftpPacket[2];
length = 4;
packet->Opcode = TFTP_ERROR;
packet->BlockNumber = ErrorCode;
strcpy( packet->Data, ErrorMessage );
length += ConnStrsize(ErrorMessage);
UdpSend( packet, length, RemoteHost, RemotePort );
return;
} // ConnError
VOID
ConnSendPacket (
IN PCONNECTION Connection,
IN PVOID Packet,
IN ULONG Length
)
//
// Send the specified packet, with the specified tftp length (length -
// udp and ip headers) out on the current connection. Fill in the
// needed parts of the udp and ip headers, byte-swap the tftp packet,
// etc; then write it out. Then set up for retransmit.
//
{
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
ASSERT( FALSE );
return;
#endif
UdpSend(
Packet,
Length,
Connection->RemoteHost,
Connection->RemotePort
);
Connection->LastSentPacket = Packet;
Connection->LastSentLength = Length;
Connection->NextRetransmit = SysGetRelativeTime() + Connection->Timeout;
return;
} // ConnSendPacket
PTFTP_PACKET
ConnPrepareSend (
IN PCONNECTION Connection
)
//
// Return a pointer to the next tftp packet suitable for filling for
// writes on the connection.
//
{
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
ASSERT( FALSE );
return NULL;
#endif
return Connection->CurrentPacket;
} // ConnPrepareSend
NTSTATUS
ConnWaitForFinalAck (
IN PCONNECTION Connection
)
//
// Finish off a write connection. Wait for the last ack, then
// close the connection and return.
//
{
return ConnWait( Connection, TFTP_DACK, NULL );
} // ConnWaitForFinalAck
BOOLEAN
ConnRetransmit (
IN PCONNECTION Connection,
IN BOOLEAN Timeout
)
//
// Retransmit the last-sent packet, up to MAX_RETRANS times. Exponentially
// back off the timeout time up to a maximum of MAX_TIMEOUT. This algorithm
// may be replaced by a better one in which the timeout time is set from
// the maximum round-trip time to date.
// The second argument indicates whether the retransmission is due to the
// arrival of a duplicate packet or a timeout. If a duplicate, don't include
// this retransmission in the maximum retransmission count.
//
{
//#if 0
#ifdef EFI
//
// There's nothing to do here for an EFI environment.
//
ASSERT( FALSE );
return TRUE;
#endif
if ( Timeout ) {
//
// This is a timeout. Check the retransmit count.
//
if ( ++Connection->Retransmissions >= MAX_RETRANS ) {
//
// Retransmits exhausted.
//
return FALSE;
}
} else {
//
// Duplicate packet. If we just sent a packet, don't send
// another one. This deals with the case where we receive
// multiple identical packets in rapid succession, possibly
// due to network problems or slowness at the remote computer.
//
if ( Connection->NextRetransmit == SysGetRelativeTime() + Connection->Timeout ) {
return TRUE;
}
}
Connection->Timeout <<= 1;
if ( Connection->Timeout > MAX_TIMEOUT ) {
Connection->Timeout = MAX_TIMEOUT;
}
ConnSendPacket( Connection, Connection->LastSentPacket, Connection->LastSentLength );
return TRUE;
} // ConnRetransmit
ULONG
ConnSafeAtol (
IN PUCHAR Buffer,
IN PUCHAR BufferEnd
)
{
ULONG value;
UCHAR c;
value = 0;
while ( Buffer < BufferEnd ) {
c = *Buffer++;
if ( c == 0 ) {
return value;
}
if ( (c < '0') || (c > '9') ) {
break;
}
value = (value * 10) + (c - '0');
}
return (ULONG)-1;
} // ConnSafeAtol
ULONG
ConnItoa (
IN ULONG Value,
OUT PUCHAR Buffer
)
{
PUCHAR p;
ULONG digit;
UCHAR c;
p = Buffer;
//
// Put the value string into the buffer in reverse order.
//
do {
digit = Value % 10;
Value /= 10;
*p++ = (UCHAR)(digit + '0');
} while ( Value > 0 );
//
// Terminate the string and move back to the last character in the string.
//
digit = (ULONG)(p - Buffer + 1); // size of string (including terminator)
*p-- = 0;
//
// Reverse the string.
//
do {
c = *p;
*p-- = *Buffer;
*Buffer++ = c;
} while ( Buffer < p );
return digit;
} // ConnItoa
BOOLEAN
ConnSafeStrequal (
IN PUCHAR Buffer,
IN PUCHAR BufferEnd,
IN PUCHAR CompareString
)
{
while ( Buffer < BufferEnd ) {
if ( *Buffer != *CompareString ) {
return FALSE;
}
if ( *CompareString == 0 ) {
return TRUE;
}
Buffer++;
CompareString++;
}
return FALSE;
} // ConnSafeStrequal
ULONG
ConnSafeStrsize (
IN PUCHAR Buffer,
IN PUCHAR BufferEnd
)
{
PUCHAR eos;
eos = Buffer;
while ( eos < BufferEnd ) {
if ( *eos++ == 0 ) {
return (ULONG)(eos - Buffer);
}
}
return 0;
} // ConnSafeStrsize
ULONG
ConnStrsize (
IN PUCHAR Buffer
)
{
PUCHAR eos;
eos = Buffer;
while ( *eos++ != 0 ) ;
return (ULONG)(eos - Buffer);
} // ConnStrsize