- better packet handling

- (hopefully) faster SFTP uploads


git-svn-id: http://phpseclib.svn.sourceforge.net/svnroot/phpseclib/trunk@46 21d32557-59b3-4da0-833f-c5933fad653e
This commit is contained in:
Jim Wigginton 2009-09-17 03:19:20 +00:00
parent 23c41a60e8
commit 6516ba29a6
2 changed files with 127 additions and 69 deletions

View File

@ -48,7 +48,7 @@
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMIX Jim Wigginton
* @license http://www.gnu.org/licenses/lgpl.txt
* @version $Id: SFTP.php,v 1.7 2009-08-29 19:23:25 terrafrost Exp $
* @version $Id: SFTP.php,v 1.8 2009-09-17 03:19:20 terrafrost Exp $
* @link http://phpseclib.sourceforge.net
*/
@ -123,17 +123,6 @@ class Net_SFTP extends Net_SSH2 {
*/
var $window_size = 0x7FFFFFFF;
/**
* The Packet Size
*
* Maximum max packet size
*
* @var Integer
* @see Net_SSH2::exec()
* @access private
*/
var $packet_size = 0x4000;
/**
* The Client Channel
*
@ -145,6 +134,17 @@ class Net_SFTP extends Net_SSH2 {
*/
var $client_channel = 1;
/**
* Packet Size
*
* Maximum packet size
*
* @see Net_SSH2::exec()
* @var Integer
* @access private
*/
var $packet_size_server_to_client = 0x4000;
/**
* The Request ID
*
@ -164,11 +164,20 @@ class Net_SFTP extends Net_SSH2 {
* concurrent actions, so it's somewhat academic, here.
*
* @var Integer
* @see Net_SFTP::_send_sftp_packet()
* @see Net_SFTP::_get_sftp_packet()
* @access private
*/
var $packet_type = -1;
/**
* Packet Buffer
*
* @var String
* @see Net_SFTP::_get_sftp_packet()
* @access private
*/
var $packet_buffer = '';
/**
* Extensions supported by the server
*
@ -308,7 +317,7 @@ class Net_SFTP extends Net_SSH2 {
}
$packet = pack('CNa*N3',
NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', $this->client_channel, $this->window_size, $this->packet_size);
NET_SSH2_MSG_CHANNEL_OPEN, strlen('session'), 'session', $this->client_channel, $this->window_size, $this->packet_size_server_to_client);
if (!$this->_send_binary_packet($packet)) {
return false;
@ -324,9 +333,11 @@ class Net_SFTP extends Net_SSH2 {
switch ($type) {
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
$this->_string_shift($response, 4);
$this->_string_shift($response, 4); // skip over client channel
list(, $server_channel) = unpack('N', $this->_string_shift($response, 4));
$this->server_channels[$this->client_channel] = $server_channel;
$this->server_channels[$client_channel] = $server_channel;
$this->_string_shift($response, 4); // skip over (server) window size
list(, $this->packet_size_client_to_server) = unpack('N', $this->_string_shift($response, 4));
break;
case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
user_error('Unable to open channel', E_USER_NOTICE);
@ -872,22 +883,10 @@ class Net_SFTP extends Net_SSH2 {
$size = strlen($data);
}
$i = 0;
$sftp_packet_size = 34000; // PuTTY uses 4096
while ($sent < $size) {
/*
"The 'maximum packet size' specifies the maximum size of an individual data packet that can be sent to the
sender. For example, one might want to use smaller packets for interactive connections to get better
interactive response on slow links."
-- http://tools.ietf.org/html/rfc4254#section-5.1
per that, we're going to assume that the 'maximum packet size' field of the SSH_MSG_CHANNEL_OPEN message
does not apply to the client. the client is the one who sends the SSH_MSG_CHANNEL_OPEN message, anyway,
so it's not as if the above could be referring to the server.
the reason that's mentioned is because sending $this->packet_size as the payload will result in a packet
that's larger than $this->packet_size, but that's not a problem, as per the above.
*/
$temp = $mode == NET_SFTP_LOCAL_FILE ? fread($fp, $this->packet_size) : $this->_string_shift($data, $this->packet_size);
$temp = $mode == NET_SFTP_LOCAL_FILE ? fread($fp, $sftp_packet_size) : $this->_string_shift($data, $sftp_packet_size);
$packet = pack('Na*N3a*', strlen($handle), $handle, 0, $sent, strlen($temp), $temp);
if (!$this->_send_sftp_packet(NET_SFTP_WRITE, $packet)) {
fclose($fp);
@ -895,6 +894,10 @@ class Net_SFTP extends Net_SSH2 {
}
$sent+= strlen($temp);
$i++;
}
while ($i-- > 0){
$this->_get_sftp_packet();
if ($this->packet_type != NET_SFTP_STATUS) {
user_error('Expected SSH_FXP_STATUS', E_USER_NOTICE);
@ -986,9 +989,10 @@ class Net_SFTP extends Net_SSH2 {
$content = '';
}
define('DEBUG2', true);
$read = 0;
while ($read < $attrs['size']) {
$packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 100000); // 100000 is completely arbitrarily chosen
$packet = pack('Na*N3', strlen($handle), $handle, 0, $read, 1 << 20); //100000); // 100000 is completely arbitrarily chosen
if (!$this->_send_sftp_packet(NET_SFTP_READ, $packet)) {
return false;
}
@ -1014,6 +1018,9 @@ class Net_SFTP extends Net_SSH2 {
return false;
}
}
echo "decrypted: " . $this->decrypt->total2 . "\r\n";
echo "encrypted: " . $this->encrypt->total2 . "\r\n";
echo "...: " . $this->encrypt->tests . "\r\n";
if (!$this->_send_sftp_packet(NET_SFTP_CLOSE, pack('Na*', strlen($handle), $handle))) {
return false;
@ -1172,12 +1179,12 @@ class Net_SFTP extends Net_SSH2 {
*/
function _send_sftp_packet($type, $data)
{
$data = $this->request_id !== false ?
$packet = $this->request_id !== false ?
pack('NCNa*', strlen($data) + 5, $type, $this->request_id, $data) :
pack('NCa*', strlen($data) + 1, $type, $data);
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
$result = $this->_send_binary_packet(pack('CN2a*', NET_SSH2_MSG_CHANNEL_DATA, $this->server_channels[$this->client_channel], strlen($data), $data));
$result = $this->_send_channel_packet($packet);
$stop = strtok(microtime(), ' ') + strtok('');
if (defined('NET_SFTP_LOGGING')) {
@ -1194,6 +1201,10 @@ class Net_SFTP extends Net_SSH2 {
*
* See '6. General Packet Format' of draft-ietf-secsh-filexfer-13 for more info.
*
* Incidentally, the number of SSH_MSG_CHANNEL_DATA messages has no bearing on the number of SFTP packets present.
* There can be one SSH_MSG_CHANNEL_DATA messages containing two SFTP packets or there can be two SSH_MSG_CHANNEL_DATA
* messages containing one SFTP packet.
*
* @see Net_SFTP::_send_sftp_packet()
* @return String
* @access private
@ -1202,48 +1213,50 @@ class Net_SFTP extends Net_SSH2 {
{
$start = strtok(microtime(), ' ') + strtok(''); // http://php.net/microtime#61838
$packet = $this->_get_channel_packet();
if (is_bool($packet)) {
$this->packet_type = false;
return false;
}
/*
normally, strlen($packet) == $length, however, for really large packets, strlen($packet) < $length. what this means,
when it happens, is that the string is being spanned out across multiple SSH_MSG_CHANNEL_DATA messages and we'll
need to read them multiple times until we reach $length bytes.
presumably, strlen($packet) > $length would never happen unless you were trying to employ steganography or
something
*/
list(, $length) = unpack('N', $this->_string_shift($packet, 4));
$this->packet_type = ord($this->_string_shift($packet));
if ($this->request_id !== false) {
$this->_string_shift($packet, 4); // remove the request id
$length-= 5; // account for the request id and the packet type
} else {
$length-= 1; // account for the packet type
}
$packet = substr($packet, 0, $length); // just in case strlen($packet) > $length
$length-= strlen($packet);
while ($length > 0) {
// SFTP packet length
while (strlen($this->packet_buffer) < 4) {
$temp = $this->_get_channel_packet();
if (is_bool($temp)) {
$this->packet_type = false;
$this->packet_buffer = '';
return false;
}
$packet.= $temp;
$length-= strlen($temp);
$this->packet_buffer.= $temp;
}
list(, $length) = unpack('N', $this->_string_shift($this->packet_buffer, 4));
$tempLength = $length;
$tempLength-= strlen($this->packet_buffer);
// SFTP packet type and data payload
while ($tempLength > 0) {
$temp = $this->_get_channel_packet();
if (is_bool($temp)) {
$this->packet_type = false;
$this->packet_buffer = '';
return false;
}
$this->packet_buffer.= $temp;
$tempLength-= strlen($temp);
}
$stop = strtok(microtime(), ' ') + strtok('');
$this->packet_type = ord($this->_string_shift($this->packet_buffer));
if (defined('NET_SFTP_LOGGING')) {
$this->packet_type_log[] = '<- ' . $this->packet_types[$this->packet_type] .
' (' . round($stop - $start, 4) . 's)';
$this->packet_log[] = $packet;
}
return $packet;
if ($this->request_id !== false) {
$this->_string_shift($this->packet_buffer, 4); // remove the request id
$length-= 5; // account for the request id and the packet type
} else {
$length-= 1; // account for the packet type
}
return $this->_string_shift($this->packet_buffer, $length);
}
/**

View File

@ -41,7 +41,7 @@
* @author Jim Wigginton <terrafrost@php.net>
* @copyright MMVII Jim Wigginton
* @license http://www.gnu.org/licenses/lgpl.txt
* @version $Id: SSH2.php,v 1.20 2009-08-29 19:23:25 terrafrost Exp $
* @version $Id: SSH2.php,v 1.21 2009-09-17 03:19:20 terrafrost Exp $
* @link http://phpseclib.sourceforge.net
*/
@ -426,6 +426,18 @@ class Net_SSH2 {
*/
var $server_channels = array();
/**
* Packet Size
*
* Maximum packet size
*
* @see Net_SSH2::_send_channel_packet()
* @see Net_SSH2::exec()
* @var Integer
* @access private
*/
var $packet_size_client_to_server = 0;
/**
* Message Number Log
*
@ -1245,14 +1257,13 @@ class Net_SSH2 {
// open request, and 'sender channel' is the channel number allocated by
// the other side.
$client_channel = 0; // PuTTy uses 0x100
// RFC4254 defines the window size as "bytes the other party can send before it must wait for the window to be
// adjusted". 0x7FFFFFFF is, at 4GB, the max size. technically, it should probably be decremented, but, honestly,
// if you're transfering more than 4GB, you probably shouldn't be using phpseclib, anyway.
// RFC4254 defines the (client) window size as "bytes the other party can send before it must wait for the window to
// be adjusted". 0x7FFFFFFF is, at 4GB, the max size. technically, it should probably be decremented, but,
// honestly, if you're transfering more than 4GB, you probably shouldn't be using phpseclib, anyway.
// see http://tools.ietf.org/html/rfc4254#section-5.2 for more info
$window_size = 0x7FFFFFFF;
// 0x8000 is the maximum max packet size, per http://tools.ietf.org/html/rfc4253#section-6.1, although since PuTTy
// uses 0x4000, that's what will be used here, as well. 0x7FFFFFFF could be used, as well (i've not encountered
// any problems, using it, myself), but that's not what the specs say, so whatever.
// uses 0x4000, that's what will be used here, as well.
$packet_size = 0x4000;
$packet = pack('CNa*N3',
@ -1272,9 +1283,11 @@ class Net_SSH2 {
switch ($type) {
case NET_SSH2_MSG_CHANNEL_OPEN_CONFIRMATION:
$this->_string_shift($response, 4);
$this->_string_shift($response, 4); // skip over client channel
list(, $server_channel) = unpack('N', $this->_string_shift($response, 4));
$this->server_channels[$client_channel] = $server_channel;
$this->_string_shift($response, 4); // skip over (server) window size
list(, $this->packet_size_client_to_server) = unpack('N', $this->_string_shift($response, 4));
break;
case NET_SSH2_MSG_CHANNEL_OPEN_FAILURE:
user_error('Unable to open channel', E_USER_NOTICE);
@ -1307,6 +1320,9 @@ class Net_SSH2 {
return $this->_disconnect(NET_SSH2_DISCONNECT_BY_APPLICATION);
}
// although, in theory, the size of SSH_MSG_CHANNEL_REQUEST could exceed the maximum packet size established by
// SSH_MSG_CHANNEL_OPEN_CONFIRMATION, RFC4254#section-5.1 states that the "maximum packet size" refers to the
// "maximum size of an individual data packet". ie. SSH_MSG_CHANNEL_DATA. RFC4254#section-5.2 corroborates.
$packet = pack('CNNa*CNa*',
NET_SSH2_MSG_CHANNEL_REQUEST, $server_channel, strlen('exec'), 'exec', 1, strlen($command), $command);
if (!$this->_send_binary_packet($packet)) {
@ -1425,7 +1441,7 @@ class Net_SSH2 {
$this->get_seq_no++;
if (defined('NET_SSH2_LOGGING')) {
$this->message_number_log[] = '-> ' . $this->message_numbers[ord($payload[0])] .
$this->message_number_log[] = '<- ' . $this->message_numbers[ord($payload[0])] .
' (' . round($stop - $start, 4) . 's)';
$this->message_log[] = $payload;
}
@ -1645,6 +1661,35 @@ class Net_SSH2 {
return $result;
}
/**
* Sends channel data
*
* Spans multiple SSH_MSG_CHANNEL_DATAs if appropriate
*
* @return Boolean
* @access private
*/
function _send_channel_packet($data)
{
while (strlen($data) > $this->packet_size_client_to_server) {
$packet = pack('CN2a*',
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$this->client_channel],
$this->packet_size_client_to_server,
$this->_string_shift($data, $this->packet_size_client_to_server)
);
if (!$this->_send_binary_packet($packet)) {
return false;
}
}
return $this->_send_binary_packet(pack('CN2a*',
NET_SSH2_MSG_CHANNEL_DATA,
$this->server_channels[$this->client_channel],
strlen($data),
$data));
}
/**
* Disconnect
*