Let's begin with a high-level SFTP message exchanges.
I want to say up front that despite the similar name,
SFTP operates nothing like classic FTP in terms of message formatting.
The initial exchange consists of the client sending the
INIT message containing an integer which represents the
SFTP version commonly seen as 3.
This occurs on TCP port 22 after the initial TCP
and SSH initialization processes.
The server replies with a version message also containing an
integer representation of the SFTP version.
You might be wondering why there isn't a specific SFTP authentication process.
SFTP relies on SSH,
and SSH handles the authentication before these steps even occur.
The SFTP communication pattern for basic file
downloads includes five basic steps.
I'm changing the diagram format slightly because we don't
need to focus on TCP port numbers anymore.
First, the open message is sent, which opens the file for reading.
All SFTP messages,
with the exception of the INIT and version messages described previously,
contain a request ID.
Similar to the ICMP echo sequence numbers described in
my ICMP Protocol Deep Dive course,
this ID marries up a client's message to the server's reply.
The server replies with the handle message containing the
request ID of 1 in reply to the open message.
It contains a string representing a handle to the file.
The client should not interpret this in any way,
and in my test runs, the handle is a string containing eight 0s.
The client replies with a read message for the specific
handle returned by the handle message.
There is also an integer offset used to measure progress.
For large files,
the offset keeps increasing so the server knows which blocks to send next.
You might be thinking, why all this confusion?
Why are handles needed?
The reason is to ensure that there is a unique pointer in memory for
SFTP clients to access a file for reading or writing.
In response to the read message,
the server sends back data messages that carry the file's contents.
There is a Boolean and a file flag that is set on the last data message,
as large files require many packets.
Notice the matching request IDs between the read and data messages.
A client could be issuing multiple reads on multiple handles at once,
and the client needs to reconcile the proper downloads with the proper handles.
Finally, the client ends the transfer with a close command,
specifying the handle one last time to delete it.
The upload process is also, generally speaking, five steps.
I am omitting some administrative steps around file permissions
and attributes to focus on the core operation.
The open message works the same way for uploads as it does for downloads.
This time,
a new file is being created on the server or an existing file is being modified.
The server replies with the handle message,
again matching the request ID of the open message sent by the client.
This contains the files handle,
which in my experiments have always been eight 0s.
The client sends the right message, which contains a few interesting fields.
First and most obviously, the data to be written is included,
which form the contents of the file.
The handle must also be included, but due to cleanliness,
I am not including it in the diagram.
Like the read message, there is an integer offset to help transfer large files.
To acknowledge a successful write, the server sends back a status message,
which has both an integer return code and some string text.
The code should be 0, meaning okay, and the text is the string success.
Finally, the client ends the transfer with a close command. The handle is included so that the server knows which stream to close.