Skip to content

006: Reading Protocol Version Exchange

Shane DeSeranno edited this page Feb 15, 2017 · 8 revisions

First, let's create a constant in the Server class to hold our server's Protocol Version:

    public class Server 
    { 
        public const string ProtocolVersionExchange = "SSH-2.0-SSHServer"; 
        ...
    }

Now, let's open the Client code and add two member variables to track the state of reading this value, and the value we have read.

    public class Client 
    {
        ...
        private bool m_HasCompletedProtocolVersionExchange = false; 
        private string m_ProtocolVersionExchange; 
        ...
    }

In the constructor of the Client, we should set some Socket settings that are helpful for real time TCP connections, as well as Send(...) our Protocol Version to the client.

        public Client(Socket socket, ILogger logger)
        {
            m_Socket = socket;
            m_Logger = logger;

            m_Socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
            m_Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);

            // 4.2.Protocol Version Exchange - https://tools.ietf.org/html/rfc4253#section-4.2
            Send($"{Server.ProtocolVersionExchange}\r\n");
        }

Now we need to go ahead and write the Send() method, which is pretty simple:

        private void Send(string message)
        {
            m_Logger.LogDebug($"Sending raw string: {message.Trim()}");
            Send(Encoding.UTF8.GetBytes(message));
        }

        private void Send(byte[] data)
        {
            if (!IsConnected)
                return;

            m_Socket.Send(data);
        }

Excellent! Now the last piece is updating our Poll() method to read the client's Protocol Version:

        public void Poll()
        {
            if (!IsConnected)
                return;

            bool dataAvailable = m_Socket.Poll(0, SelectMode.SelectRead);
            if (dataAvailable)
            {
                int read = m_Socket.Available;
                if (read < 1)
                {
                    Disconnect();
                    return;
                }

                if (!m_HasCompletedProtocolVersionExchange)
                {
                    // Wait for CRLF
                    try
                    {
                        ReadProtocolVersionExchange();
                        if (m_HasCompletedProtocolVersionExchange)
                        {
                            // TODO: Consider processing Protocol Version Exchange for validity
                            m_Logger.LogDebug($"Received ProtocolVersionExchange: {m_ProtocolVersionExchange}");
                        }
                    }
                    catch (Exception)
                    {
                        Disconnect();
                        return;
                    }
                }

                if (m_HasCompletedProtocolVersionExchange)
                {
                    // TODO: Read and process packets
                }
            }
        }

        // Read 1 byte from the socket until we find "\r\n"
        private void ReadProtocolVersionExchange()
        {
            NetworkStream stream = new NetworkStream(m_Socket, false);
            string result = null;

            List<byte> data = new List<byte>();

            bool foundCR = false;
            int value = stream.ReadByte();
            while (value != -1)
            {
                if (foundCR && (value == '\n'))
                {
                    // DONE
                    result = Encoding.UTF8.GetString(data.ToArray());
                    m_HasCompletedProtocolVersionExchange = true;
                    break;
                }

                if (value == '\r')
                    foundCR = true;
                else
                {
                    foundCR = false;
                    data.Add((byte)value);
                }

                value = stream.ReadByte();
            }

            m_ProtocolVersionExchange += result;
        }

I know this a big dump of code, but basically it divides the Poll() into two parts, the first part just reads until it gets a [CR][LF] and collects that string, once we have received a [CR][LF], then the code will be ready to Read and process packets. This will come in a future article...

If we sync to the tag Reading_Protocol_Version_Exchange, you can run this code. This time, however, I suggest you use an SSH client to connect. If you are successful, you will see:

info: SSHServer[0]
      Starting up...
info: SSHServer[0]
      Listening on port: 22
dbug: SSHServer[0]
      New Client: 127.0.0.1:3305
dbug: 127.0.0.1:3305[0]
      Sending raw string: SSH-2.0-SSHServer
dbug: 127.0.0.1:3305[0]
      Received ProtocolVersionExchange: SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.1

Excellent! You can see the message you sent, and also the valid message received from the SSH client! Section 5 of the RFC covers how to handle compatibility with other versions of the protocol, but we are only going to support 2.0, so we can skip with that section. In the next topic, I'll cover the 6. Binary Packet Protocol section of the RFC.