The final piece of SSH-2’s layered architecture is the connection layer, which provides network services such as interactive logins, remote commands, and port forwarding on top of the transport layer, which supplies the necessary security.
Once established, an SSH connection can host one or more SSH channels, which are logical data pipes multiplexed over the connection. The client can open multiple channels on the one connection to the same server, and perform different network tasks on different channels. In practice, SSH implementations rarely use multiple channels on a connection, preferring to open a new connection for each channel.
An important feature of SSH channels is flow control. Data may only be sent across a channel when the recipient has indicated they are ready to receive it – a form of sliding-window flow control. The size of the window is established by the recipient when the channel is opened, and the window size is decremented as data is sent. Periodically, the recipient sends a message to increase the window size.
The SSH_MSG_CHANNEL_OPEN message used to open a network session is shown below. This session might be subsequently used for a terminal session, to run a remote command, or to start a subsystem such as SFTP.
byte SSH_MSG_CHANNEL_OPEN string "session" uint32 sender channel uint32 initial window size uint32 maximum packet size
The initial window size sets the number of bytes the recipient of this message can send to the sender, while the maximum packet size is the largest amount of data that it will accept in a single message.
The recipient of this message replies with an SSH_MSG_CHANNEL_OPEN_CONFIRMATION message if it is prepared to open the requested channel:
byte SSH_MSG_CHANNEL_OPEN_CONFIRMATION uint32 recipient channel uint32 sender channel uint32 initial window size uint32 maximum packet size
Once a channel has been successfully opened, data can be exchanged, and channel-specific requests can sent. When the sliding-window size for either the client or server becomes too small, the owner of the window sends a SSH_MSG_CHANNEL_WINDOW_ADJUST message to increase it:
byte SSH_MSG_CHANNEL_WINDOW_ADJUST uint32 recipient channel uint32 bytes to add
Data is sent across the channel via the SSH_MSG_CHANNEL_DATA message. How the data is used will depend on the type of channel that has been established:
byte SSH_MSG_CHANNEL_DATA uint32 recipient channel string data
Channel requests are used to perform particular actions over a channel. Common requests include starting a shell or exec’ing a remote command. For example, a remote shell is started by the request shown below:
byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "shell" boolean want reply
A remote command is exec’ed by the following request:
byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "exec" boolean want reply string command
Finally, an SFTP subsystem can be opened by this request:
byte SSH_MSG_CHANNEL_REQUEST uint32 recipient channel string "subsystem" boolean want reply string "sftp-server"
Subsystems are sets of remote commands that are pre-defined on the server machine. The most common is SFTP, which provides commands to transfer and manipulate files. The subsystem commands (including the SFTP protocol) run over SSH, i.e. data for the subsystem commands is sent in SSH_MSG_CHANNEL_DATA messages. When one of these messages arrives at the client or server, it is passed to the subsystem for processing. More details on the SFTP subsystem will be discussed in the next post.
Once either the client or server has finished using the channel, it must be closed. The SSH_MSG_CHANNEL_EOF message is sent to indicate no more data will be sent in the direction of this message. The SSH_MSG_CHANNEL_CLOSE message indicates the channel is now closed. The recipient must reply with an SSH_MSG_CHANNEL_CLOSE if they have not already sent one. Once closed, the channel cannot be re-opened.