I was reading some Go code that sends a file over an SSH client.


One of the things it does is a scp -t /remote/path. What is that -t flag for? I did a man scp, but it doesn't seem to be documented. I ran the command locally and it seems to behave like cat.

After running scp -t remotely, the code then sends bytes to the server like this.

// Some special control header? What is this?
fmt.Fprintf(w, "C%#o %d %s\n", mode, size, fileName)
// Send file bytes.
io.Copy(w, contents)
// Send termination signal?
fmt.Fprint(w, "\x00")

What is this protocol? Is it documented anywhere?

  • scp's -t, -f and -d options are not documented because they're not user-serviceable; they're only used when starting the "server" scp on the remote machine. – mosvy Sep 7 '19 at 2:51
  • 1
    How the SCP protocol works – Mark Plotnick Sep 7 '19 at 8:57

AFAIK, the scp protocol isn't documented anywhere else than it the source code of scp.c, so here is an attempt to an outline of how scp works.

When either the source or the destination is a remote machine, scp will use ssh to connect and start a scp program on it: if the copy direction is from the remote to the local, it will be started as scp -f src (from / source), otherwise it will be started as scp -t dst (to / sink), and the local scp will assume the opposite posture.

After this the two scp processes are running on both ends of the scp connection, using it as their stdin/stdout and passing the file data and metadata over it.

Both ends could use the following responses to acknowledge messages or signal some error condition:

  1. "\0": OK

  2. "\1%s\n", err_msg: non-fatal error

  3. "\2%s\n", err_msg: fatal error

The transfer starts by the to / sink scp sending a \0 (OK) ack.

Then the from / source scp will use the following messages:

  1. "C%04o %lld %s\n", mode, size, filename: create a file

    this is followed by size bytes of file data, and an ack (\0 = OK)

  2. "D%04o 0 %.1024s\n", mode, dirname: start of directory

    recursively followed by C, D or T messages, until a

  3. "E\n": end of directory

  4. "T%llu 0 %llu 0\n", mtime, atime: file times

    this is sent before the C or D messages, if the -p switch was used.

These messages will have to be ack'ed by the other side before proceding further, including the C before sending the file data, and the ack sent after it.

In the C and D messages above, newlines and other control chars (except \t and \x7f) in the file/dir name will be escaped as eg. \^J, but will not be unescaped at the destination; a literal \^J or \^M in the original name will be left as-is.

The difference between fatal and non-fatal errors is not consistent; only \1 (non-fatal) errors will be generated by either side, but some of them will be considered fatal, and scp will exit upon sending or receiving them, leaving the other side to hold both pieces. Both sides will exit upon a \2 (fatal) error or anything unexpected.

Unlike in http, there are no provisions for sending the file data by chunks; if the source scp is no longer able to read some big file it has started sending after the C message, it will send up to its size NUL bytes before the \1 error message / nak.

  • Correct. Just worth mentioning, that the local side does not have to be the scp (and does not have to use the ssh). It can be another implementation of the SCP protocol. Actually, like in case of OP, where the Go code implements the local-side-scp. While the remote side will indeed almost always be the OpenSSH scp (or at least has to believe like it and be named scp). See Explanation for SCP protocol implementation in JSch library. – Martin Prikryl Sep 8 '19 at 16:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.