I merged the changes relevant to the flow control patches in putty trunk with your source...
code compiles without warnings, and with appropriately tuned send/receive buffers in windows, there are now signifiant performance increases...
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\AFD\Parameters]
"DefaultSendWindow"=dword:003d0900
"DefaultReceiveWindow"=dword:003d0900
[HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters]
"GlobalMaxTcpWindowSize"=dword:003d0900
"TcpWindowSize"=dword:003d0900
"SackOpts"=dword:00000001
"Tcp1323Opts"=dword:00000001
"DefaultReceiveWindow"=dword:003d0900
"DefaultSendWindow"=dword:003d0900
This is setting send/receive buffers to 4MB, I'm testing on 100Mbit/sec connection over fairly high latency (200-300ms). Laptop was a Macbook pro running leopard, with WindowsXP SP2 running within parallels, connected via 802.11n wireless, uplinked to the internet over 100Mb/s. Remote server was running Linux 2.6.17, with OpenSSH 4.3p2 with hpn-ssh patches applied, and tweaked window size, connected via Gigabit Ethernet.
Testing with a 100MB file, in order to give the connections time to 'ramp up', each run averaged 3 times.
I'm not sure why there is a difference in speeds in directions, as I've not yet been able to test with anything other than my laptop.
And just to confirm, I just merged what I viewed to be the relevant parts from putty CVS commits: 7679 and 7735, as per the details on the putty wishlist.
My reasons for doing it are simply that I understand its quite a big job too completely merge with an up to date putty source tree, and I needed better performance today :)
diff -ru winscp405/putty/putty.h winscp405.patched/putty/putty.h
--- winscp405/putty/putty.h 2006-09-20 20:02:26.000000000 +0200
+++ winscp405.patched/putty/putty.h 2007-11-12 16:09:19.000000000 +0100
@@ -556,6 +556,7 @@
int sshbug_ignore1, sshbug_plainpw1, sshbug_rsa1,
sshbug_hmac2, sshbug_derivekey2, sshbug_rsapad2,
sshbug_pksessid2, sshbug_rekey2;
+ int ssh_simple;
/* Options for pterm. Should split out into platform-dependent part. */
int stamp_utmp;
int login_shell;
diff -ru winscp405/putty/ssh.c winscp405.patched/putty/ssh.c
--- winscp405/putty/ssh.c 2007-09-01 22:47:20.000000000 +0200
+++ winscp405.patched/putty/ssh.c 2007-11-12 20:48:02.000000000 +0100
@@ -263,10 +263,12 @@
#define SSH_MAX_BACKLOG 32768
#ifdef MPEXT
#define OUR_V2_MAXPKT 0x4000UL
-#define OUR_V2_WINSIZE (OUR_V2_MAXPKT * 4)
+#define OUR_V2_WINSIZE 16384
+#define OUR_V2_BIGWIN 0x7fffffff
#else
#define OUR_V2_WINSIZE 16384
#define OUR_V2_MAXPKT 0x4000UL
+#define OUR_V2_BIGWIN 0x7fffffff
#endif
const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
@@ -336,6 +338,13 @@
CHAN_SOCKDATA_DORMANT /* one the remote hasn't confirmed */
};
+
+struct winadj {
+ struct winadj *next;
+ unsigned size;
+};
+
+
/*
* 2-3-4 tree storing channels.
*/
@@ -363,7 +372,10 @@
struct ssh2_data_channel {
bufchain outbuffer;
unsigned remwindow, remmaxpkt;
- unsigned locwindow;
+ int remlocwin;
+ struct winadj *winadj_head, *winadj_tail;
+ enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
+ int locwindow, locmaxwin;
} v2;
} v;
union {
@@ -451,7 +463,7 @@
static int ssh2_try_send(struct ssh_channel *c);
static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
-static void ssh2_set_window(struct ssh_channel *c, unsigned newwin);
+static void ssh2_set_window(struct ssh_channel *c, int newwin);
static int ssh_sendbuffer(void *handle);
static void ssh2_timer(void *ctx, long now);
static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
@@ -955,9 +967,9 @@
int do_blank = FALSE, blank_prefix = 0;
/* "Session data" packets - omit the data field */
if (st->pktin->type == SSH2_MSG_CHANNEL_DATA) {
- do_blank = TRUE; blank_prefix = 4;
- } else if (st->pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
do_blank = TRUE; blank_prefix = 8;
+ } else if (st->pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA) {
+ do_blank = TRUE; blank_prefix = 12;
}
if (do_blank) {
blank.offset = blank_prefix;
@@ -3471,7 +3483,7 @@
ssh1_throttle(ssh, -1);
}
} else {
- ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
+ ssh2_set_window(c, c->v.v2.locmaxwin - bufsize);
}
}
@@ -5423,7 +5435,7 @@
/*
* Potentially enlarge the window on an SSH-2 channel.
*/
-static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
+static void ssh2_set_window(struct ssh_channel *c, int newwin)
{
Ssh ssh = c->ssh;
@@ -5442,8 +5454,38 @@
*
* "Significant" is arbitrarily defined as half the window size.
*/
- if (newwin > c->v.v2.locwindow * 2) {
+ if (newwin / 2 >= c->v.v2.locwindow) {
struct Packet *pktout;
+ struct winadj *wa;
+ if (newwin == c->v.v2.locmaxwin &&
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
+ ssh2_pkt_addbool(pktout, TRUE);
+ ssh2_pkt_send(ssh, pktout);
+
+ /*
+ * CHANNEL_FAILURE doesn't come with any indication of
+ * what message caused it, so we have to keep track of the
+ * outstanding CHANNEL_REQUESTs ourselves.
+ */
+ wa = snew(struct winadj);
+ wa->size = newwin - c->v.v2.locwindow;
+ wa->next = NULL;
+ if (!c->v.v2.winadj_head)
+ c->v.v2.winadj_head = wa;
+ else
+ c->v.v2.winadj_tail->next = wa;
+ c->v.v2.winadj_tail = wa;
+ if (c->v.v2.throttle_state != UNTHROTTLED)
+ c->v.v2.throttle_state = UNTHROTTLING;
+ } else {
+ /* Pretend the WINDOW_ADJUST was acked immediately. */
+ c->v.v2.remlocwin = newwin;
+ c->v.v2.throttle_state = THROTTLED;
+ }
+
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
ssh2_pkt_adduint32(pktout, c->remoteid);
@@ -5453,6 +5495,42 @@
}
}
+
+static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
+{
+ /*
+ * The only time this should get called is for "winadj@putty"
+ * messages sent above. All other channel requests are either
+ * sent with want_reply false or are sent before this handler gets
+ * installed.
+ */
+ unsigned i = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+ struct winadj *wa;
+
+ c = find234(ssh->channels, &i, ssh_channelfind);
+ if (!c)
+ return; /* nonexistent channel */
+ wa = c->v.v2.winadj_head;
+ if (!wa)
+ logevent("excess SSH_MSG_CHANNEL_FAILURE");
+ else {
+ c->v.v2.winadj_head = wa->next;
+ c->v.v2.remlocwin += wa->size;
+ sfree(wa);
+ /*
+ * winadj messages are only sent when the window is fully open,
+ * so if we get an ack of one, we know any pending unthrottle
+ * is complete.
+ */
+ if (c->v.v2.throttle_state == UNTHROTTLING)
+ c->v.v2.throttle_state = UNTHROTTLED;
+ }
+}
+
+
+
+
static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
{
unsigned i = ssh_pkt_getuint32(pktin);
@@ -5480,6 +5558,7 @@
if (data) {
int bufsize = 0;
c->v.v2.locwindow -= length;
+ c->v.v2.remlocwin -= length;
switch (c->type) {
case CHAN_MAINSESSION:
bufsize =
@@ -5540,11 +5619,20 @@
break;
}
/*
+ * If it looks like the remote end hit the end of its window,
+ * and we didn't want it to do that, think about using a
+ * larger window.
+ */
+ if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED &&
+ c->v.v2.locmaxwin < 0x40000000)
+ c->v.v2.locmaxwin += OUR_V2_WINSIZE;
+
+ /*
* If we are not buffering too much data,
* enlarge the window again at the remote side.
*/
- if (bufsize < OUR_V2_WINSIZE)
- ssh2_set_window(c, OUR_V2_WINSIZE - bufsize);
+ ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
+ c->v.v2.locmaxwin - bufsize : 0);
}
}
@@ -5972,9 +6060,12 @@
} else {
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->v.v2.locwindow = OUR_V2_WINSIZE;
+ c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
c->v.v2.remwindow = winsize;
c->v.v2.remmaxpkt = pktsize;
+ c->v.v2.remlocwin = OUR_V2_WINSIZE;
+ c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
+ c->v.v2.throttle_state = UNTHROTTLED;
bufchain_init(&c->v.v2.outbuffer);
add234(ssh->channels, c);
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
@@ -6941,7 +7032,12 @@
s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
ssh2_pkt_addstring(s->pktout, "session");
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
- ssh->mainchan->v.v2.locwindow = OUR_V2_WINSIZE;
+ ssh->mainchan->v.v2.locwindow = ssh->mainchan->v.v2.locmaxwin =
+ ssh->mainchan->v.v2.remlocwin =
+ ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ ssh->mainchan->v.v2.winadj_head = NULL;
+ ssh->mainchan->v.v2.winadj_tail = NULL;
+ ssh->mainchan->v.v2.throttle_state = UNTHROTTLED;
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_send(ssh, s->pktout);
@@ -6965,8 +7061,48 @@
add234(ssh->channels, ssh->mainchan);
update_specials_menu(ssh->frontend);
logevent("Opened channel for session");
- } else
- ssh->mainchan = NULL;
+ } else {
+ ssh->mainchan = snew(struct ssh_channel);
+ ssh->mainchan->ssh = ssh;
+ ssh->mainchan->localid = alloc_channel_id(ssh);
+ s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
+ ssh2_pkt_addstring(s->pktout, "session");
+ ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
+ ssh->mainchan->v.v2.locwindow = ssh->mainchan->v.v2.locmaxwin =
+ ssh->mainchan->v.v2.remlocwin =
+ ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ ssh->mainchan->v.v2.winadj_head = NULL;
+ ssh->mainchan->v.v2.winadj_tail = NULL;
+ ssh->mainchan->v.v2.throttle_state = UNTHROTTLED;
+ ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
+ ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
+ ssh2_pkt_send(ssh, s->pktout);
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION) {
+ bombout(("Server refused to open a session"));
+ crStopV;
+ /* FIXME: error data comes back in FAILURE packet */
+ }
+ if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
+ bombout(("Server's channel confirmation cited wrong channel"));
+ crStopV;
+ }
+ if (ssh_pkt_getuint32(pktin) != ssh->mainchan->localid) {
+ bombout(("Server's channel confirmation cited wrong channel"));
+ crStopV;
+ window and leave the flow control to TCP.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
+ ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org");
+ ssh2_pkt_addbool(s->pktout, 0); /* no reply */
+ ssh2_pkt_send(ssh, s->pktout);
+
/*
* Potentially enable X11 forwarding.
*/
@@ -7229,6 +7377,7 @@
if (ssh->eof_needed)
ssh_special(ssh, TS_EOF);
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure;
/*
* Transfer data!
*/
@@ -7981,9 +8130,11 @@
ssh1_throttle(ssh, -1);
}
} else {
- if (ssh->mainchan && ssh->mainchan->closes == 0)
- ssh2_set_window(ssh->mainchan, OUR_V2_WINSIZE - bufsize);
+ if (ssh->mainchan)
+ ssh2_set_window(ssh->mainchan,
+ ssh->mainchan->v.v2.locmaxwin - bufsize);
}
+
}
void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
@@ -8005,8 +8156,11 @@
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
ssh2_pkt_addstring(pktout, "direct-tcpip");
ssh2_pkt_adduint32(pktout, c->localid);
- c->v.v2.locwindow = OUR_V2_WINSIZE;
- ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
+ c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
+ c->v.v2.remlocwin = OUR_V2_WINSIZE;
+ c->v.v2.winadj_head = c->v.v2.winadj_head = NULL;
+ c->v.v2.throttle_state = UNTHROTTLED;
+
ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_addstring(pktout, hostname);
ssh2_pkt_adduint32(pktout, port);