Post a reply

Options
Add an Attachment

If you do not want to add an Attachment to your Post, please leave the Fields blank.

(maximum 10 MB; please compress large files; only common media, archive, text and programming file formats are allowed)

Options

Topic review

martin

Re: High Performance SSH/SCP - HPN-SSH

This issue has been added to tracker.
martin

Thanks. Good to know.
andrew_m

Hi,

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...

compiled with borland c++ builder 6 personal, so with the -DNO_FILEZILLA option

Registry settings I used were:

[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.

WinSCP 4.05 standard: 434KB/s upload / 141KB/s download
WinSCP 4.05+patches: 1451KB/s upload / 404KB/s download

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 :)

hope its useful.

thanks

Andrew

Patch is here:

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);
andrew_m

thank you!
martin

Re: High Performance SSH/SCP - HPN-SSH

Please direct request of this kind to authors of PuTTY.