ssh_connection(3)

πŸ“¦
version 29.0.6 Β· 3 versions
, in ubuntu stonking

πŸ’– NAME πŸ’–

ssh_connection β€” this module is the one that gives you API functions to send SSH Connection Protocol events over to the other side of an SSH channel. It's giving bridge energy, fr. 🫢

🌸 DESCRIPTION 🌸

The SSH Connection Protocol (RFC 4254) is what clients and servers β€” aka SSH channels β€” use to communicate over the SSH connection. The API functions in this module send SSH Connection Protocol events, and those events show up as messages on the remote channel that's handling the remote channel. The Erlang format of those messages is (see also below):

{ssh_cm, ssh:connection_ref/0 , channel_msg/0 }

If you're using the ssh_client_channel behavior to implement the channel process, these messages get handled by handle_ssh_msg/2. slay. ✨


πŸ¦‹ DATA TYPES πŸ¦‹

channel_id()

channel_id() :: ssh:channel_id().

channel_msg()

channel_msg() ::
          data_ch_msg() |
          eof_ch_msg() |
          closed_ch_msg() |
          pty_ch_msg() |
          env_ch_msg() |
          shell_ch_msg() |
          exec_ch_msg() |
          signal_ch_msg() |
          window_change_ch_msg() |
          exit_status_ch_msg() |
          exit_signal_ch_msg().

Okay so like the intro said, SSH Connection Protocol events get handled as messages. If you're writing a channel handling process without the ssh_client_channel behavior helping you out, your process has to handle those messages itself. iykyk. πŸ’…

closed_ch_msg()

closed_ch_msg() :: {closed, ssh:channel_id()}.

This event gets sent when someone calls ssh_connection:close/2. Both handling this event and sending it are already taken care of by the ssh_client_channel behavior. so you can chill. πŸŽ€

connection_ref()

connection_ref() :: ssh:connection_ref().

data_ch_msg()

data_ch_msg() ::
          {data,
           ssh:channel_id(),
           ssh_data_type_code(),
           Data :: binary()}.

Data has arrived on the channel, bestie. This event gets sent when ssh_connection:send/3,4,5 is called. πŸ”₯

env_ch_msg()

env_ch_msg() ::
          {env,
           ssh:channel_id(),
           want_reply(),
           Var :: string(),
           Value :: string()}.

Environment variables can be passed to the shell/command that's gonna start later. This event gets sent when ssh_connection:setenv/5 is called. πŸ“

eof_ch_msg()

eof_ch_msg() :: {eof, ssh:channel_id()}.

This one means the other side is sending no more data. It gets sent when ssh_connection:send_eof/2 is called. πŸ’€

event()

event() :: {ssh_cm, ssh:connection_ref(), channel_msg()}.

exec_ch_msg()

exec_ch_msg() ::
          {exec, ssh:channel_id(), want_reply(), Command :: string()}.

This message is like "hey server, start running this command." It gets sent when ssh_connection:exec/4 is called. periodt. πŸ‘‘

exit_signal_ch_msg()

exit_signal_ch_msg() ::
          {exit_signal,
           ssh:channel_id(),
           ExitSignal :: string(),
           ErrorMsg :: string(),
           LanguageString :: string()}.

A remote execution can go out violently because of a signal. When that happens, this message can be received. For the deets on valid string values, peep RFC 4254 Section 6.10, which shows a special case of these signals. 😭

exit_status_ch_msg()

exit_status_ch_msg() ::
          {exit_status,
           ssh:channel_id(),
           ExitStatus :: non_neg_integer()}.

When the command running at the other end finishes, this message can be sent to return the command's exit status. A zero exit_status usually means the command ended successfully. This event gets sent when ssh_connection:exit_status/3 is called. 🌈

pty_ch_msg()

pty_ch_msg() ::
          {pty,
           ssh:channel_id(),
           want_reply(),
           {Terminal :: string(),
            CharWidth :: non_neg_integer(),
            RowHeight :: non_neg_integer(),
            PixelWidth :: non_neg_integer(),
            PixelHeight :: non_neg_integer(),
            TerminalModes :: [term_mode()]}}.

reason()

reason() :: closed | timeout.

This is the result of a call.

If the request reached the peer, got handled, and the response made it back to the requesting node, then req_status/0 is the status reported from the peer.

If not, reason/0 tells you what went wrong:

  • closed β€” means the channel or connection was closed when trying to send the request.
  • timeout β€” means the operation exceeded a time limit.

req_status()

req_status() :: success | failure.

The status of a request. It corresponds to the SSH_MSG_CHANNEL_SUCCESS and SSH_MSG_CHANNEL_FAILURE values in RFC 4254, Section 5.4. πŸ’–

result()

result() :: req_status() | {error, reason()}.

shell_ch_msg()

shell_ch_msg() :: {shell, ssh:channel_id(), want_reply()}.

This message is asking for the user default shell to be started at the other end. It gets sent when ssh_connection:shell/2 is called. ✨

signal_ch_msg()

signal_ch_msg() ::
          {signal, ssh:channel_id(), SignalName :: string()}.

You can deliver a signal to the remote process/service with this message. Some systems don't support signals, and if that's the case they're supposed to ignore this message. Right now there's no function to generate this event, because the signals it's talking about are OS-level and not something an Erlang program makes. πŸ’…

ssh_data_type_code()

ssh_data_type_code() :: non_neg_integer().

The valid values are 0 ("normal") and 1 ("stderr"), see RFC 4254, Section 5.2. πŸŽ€

term_mode()

term_mode() ::
          {Opcode :: atom() | byte(), Value :: non_neg_integer()}.

So a pseudo-terminal got requested for the session. Terminal is the value of the TERM environment variable value, aka vt100. Zero dimension parameters must be ignored. The character/row dimensions override the pixel dimensions (when non-zero). Pixel dimensions refer to the drawable area of the window. Opcode in the TerminalModes list is the mnemonic name, represented as a lowercase Erlang atom, defined in RFC 4254, Section 8. It can also be an Opcode if the mnemonic name is not listed in the RFC. Example: OP code: 53, mnemonic name ECHO erlang atom: echo. This event gets sent when ssh_connection:ptty_alloc/4 is called. 🌸

want_reply()

want_reply() :: boolean().

Messages that include a WantReply expect the channel handling process to call ssh_connection:reply_request/4 with the boolean value of WantReply as the second argument. πŸ‘‘

window_change_ch_msg()

window_change_ch_msg() ::
          {window_change,
           ssh:channel_id(),
           CharWidth :: non_neg_integer(),
           RowHeight :: non_neg_integer(),
           PixelWidth :: non_neg_integer(),
           PixelHeight :: non_neg_integer()}.

When the window (terminal) size changes on the client side, it can send a message to the server side to inform it of the new dimensions. No API function generates this event. πŸ’œ

🌸 FUNCTIONS 🌸


✨ adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok

adjust_window(ConnectionRef, ChannelId, NumOfBytes) -> ok
                       when
                           ConnectionRef :: ssh:connection_ref(),
                           ChannelId :: ssh:channel_id(),
                           NumOfBytes :: integer().

Okay so this one tweaks the SSH flow-control window πŸ’§ β€” and bestie, both the client-side AND server-side channel processes need to do this, it's a two-hands situation fr fr.

⚠️ NOTE: if your channels are built with the ssh_client_channel behavior, you usually do not need to call this at all, because flow control is already handled by the behavior, iykyk πŸ‘‘ The behavior bumps the window every single time the callback handle_ssh_msg/2 returns after it's done processing channel data. Considerate of it honestly.

✨ close(ConnectionRef, ChannelId) -> ok

close(ConnectionRef, ChannelId) -> ok
                when
                    ConnectionRef :: ssh:connection_ref(),
                    ChannelId :: ssh:channel_id().

A server- or client-channel process can peace out of their session whenever it wants by yeeting a close event πŸ’…

⚠️ NOTE: this function gets called by the ssh_client_channel behavior when the channel is terminated β€” see ssh_client_channel. So if you're using the behavior, girly pop, do not call this yourself. It's handled. Let it cook.

✨ exec(ConnectionRef, ChannelId, Command, Timeout) -> result()

exec(ConnectionRef, ChannelId, Command, Timeout) -> result()
               when
                   ConnectionRef :: ssh:connection_ref(),
                   ChannelId :: ssh:channel_id(),
                   Command :: string(),
                   Timeout :: timeout().

This is what a client-channel process calls to ask the server "hey babe, go run this command for me 🫢". And the result isn't one single reply β€” it's a whole lil' parade of messages, in this exact pattern. The very last one is a channel close message, because an exec request is a one-time execution: it finishes the job, then shuts the channel down. Done and dusted. ✨

  • N x data message(s) β€” the output of the command can be a single line or thousands of lines, depends entirely on the command tbh.
  • 0 or 1 x eof message β€” says no more data is coming.
  • 0 or 1 x exit signal message β€” not every system sends signals. For the details on valid string values, hit up RFC 4254, Section 6.10.
  • 0 or 1 x exit status message β€” the SSH Connection Protocol recommends sending this one, but that is lowkey not always the case πŸ™ˆ
  • 1 x closed status message β€” tells you that the ssh_client_channel started for running this command has now been shut down.

Peep the User's Guide section on One-Time Execution for examples, it's giving tutorial energy.

⚠️ NOTE: if the command generates a big ol' pile of output data, you might have to adjust the window manually to actually receive all of it. See ssh_connectino:adjust_window/3.

✨ exit_status(ConnectionRef, ChannelId, Status) -> ok

exit_status(ConnectionRef, ChannelId, Status) -> ok
                        when
                            ConnectionRef :: ssh:connection_ref(),
                            ChannelId :: ssh:channel_id(),
                            Status :: integer().

A server-channel process calls this to send the exit status of a command back to the client. Communication queen behavior. πŸ‘‘


✨ ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result()

ptty_alloc(ConnectionRef, ChannelId, Options, Timeout) -> result()
                       when
                           ConnectionRef :: ssh:connection_ref(),
                           ChannelId :: ssh:channel_id(),
                           Options :: proplists:proplist(),
                           Timeout :: timeout().

Sends an SSH Connection Protocol pty_req to allocate a pseudo-terminal πŸŽ€ An SSH client process is the one that calls this.

Options:

  • {term, string()} β€” defaults to os:getenv("TERM"), or vt100 if that comes back undefined.
  • {width, integer()} β€” defaults to 80 if pixel_width isn't defined.
  • {height, integer()} β€” defaults to 24 if pixel_height isn't defined.
  • {pixel_width, integer()} β€” straight up disregarded if width is defined. ignored. ghosted. πŸ’€
  • {pixel_height, integer()} β€” disregarded if height is defined.
  • {pty_opts, [{posix_atom(), integer()}]} β€” this one can just be an empty list, no pressure. Otherwise, go see the possible POSIX names in Section 8 in RFC 4254.

✨ reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok

reply_request(ConnectionRef, WantReply, Status, ChannelId) -> ok
                          when
                              ConnectionRef :: ssh:connection_ref(),
                              WantReply :: boolean(),
                              Status :: req_status(),
                              ChannelId :: ssh:channel_id().

Sends status replies to requests where the requester has explicitly said they want a status report β€” that means WantReply = true. If WantReply is false, calling this just becomes a "noop" 😴 (nothing happens, all vibes, no function). You call this while you're handling an SSH Connection Protocol message that contains a WantReply boolean value.


✨ send(connection_ref(), channel_id(), iodata(), timeout()) -> ok | {error, reason()}; (connection_ref(), channel_id(), ssh_data_type_code(), iodata()) -> ok | {error, reason()}.

send(connection_ref(), channel_id(), iodata(), timeout()) ->
                 ok | {error, reason()};
             (connection_ref(),
              channel_id(),
              ssh_data_type_code(),
              iodata()) ->
                 ok | {error, reason()}.

Depending on the input arguments you pass, this is equivalent to one of the send/5 calls listed below. Basically it's the choose-your-own-adventure version. πŸ¦‹

  • Equivalent to send(ConnectionRef, ChannelId, 0, Data, TimeOut) if you call it with TimeOut being an integer.
  • Equivalent to send(ConnectionRef, ChannelId, 0, Data, infinity) if you call it with TimeOut being the atom infinity.
  • Equivalent to send(ConnectionRef, ChannelId, Type, Data, infinity) if you call it with a last argument that is not an integer and not the atom infinity.

✨ send(connection_ref(), channel_id(), ssh_data_type_code(), iodata(), timeout()) -> ok | {error, reason()}.

send(connection_ref(),
          channel_id(),
          ssh_data_type_code(),
          iodata(),
          timeout()) ->
             ok | {error, reason()}.

Client- and server-channel processes call this to send data to each other 🫢

⚠️ IMPORTANT: the function subsystem/4 and any subsequent calls to send/3,4,5 must be executed in the same process. Same process bestie, no splitting up, that's the rule. 😀

✨ send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}

send_eof(ConnectionRef, ChannelId) -> ok | {error, closed}
                     when
                         ConnectionRef :: ssh:connection_ref(),
                         ChannelId :: ssh:channel_id().

Sends EOF on channel ChannelId. That's it, that's the whole thing. Short and iconic. ✨


✨ session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize, Timeout) -> Result

session_channel(ConnectionRef, InitialWindowSize, MaxPacketSize,
                     Timeout) ->
                        Result
                        when
                            ConnectionRef :: ssh:connection_ref(),
                            InitialWindowSize ::
                                pos_integer() | undefined,
                            MaxPacketSize :: pos_integer() | undefined,
                            Timeout :: timeout(),
                            Result ::
                                {ok, ssh:channel_id()} |
                                {error, reason()}.

Opens a channel for an SSH session πŸ’œ And listen β€” the channel id you get back from this function is the exact id you plug in as input to all the other functions in this module. It's the key to the whole group chat, fr fr.


✨ setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> success

setenv(ConnectionRef, ChannelId, Var, Value, Timeout) -> success
               when
                   ConnectionRef :: ssh:connection_ref(),
                   ChannelId :: ssh:channel_id(),
                   Var :: string(),
                   Value :: string(),
                   Timeout :: timeout().

You can pass environment variables before starting up the shell or the command 🌈 A client channel process is the one that calls this.


✨ shell(ConnectionRef, ChannelId) -> Result

shell(ConnectionRef, ChannelId) -> Result
              when
                  ConnectionRef :: ssh:connection_ref(),
                  ChannelId :: ssh:channel_id(),
                  Result :: ok | success | failure | {error, timeout}.

A client channel process calls this to request that the user's default shell (typically the one defined in /etc/passwd on Unix systems πŸ“) gets executed over on the server end.

πŸ‘€ Note: the return value here is ok, and not success like the other functions in this module. This is a fault that was introduced so long ago that changing it now would break a large number of existing software. So we all just live with it. It's canon. Historical artifact era. πŸ’€

✨ subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) -> result()

subsystem(ConnectionRef, ChannelId, Subsystem, Timeout) ->
                      result()
                      when
                          ConnectionRef :: ssh:connection_ref(),
                          ChannelId :: ssh:channel_id(),
                          Subsystem :: string(),
                          Timeout :: timeout().

A client-channel process calls this to request that a predefined subsystem gets executed on the server 🧚

⚠️ The function subsystem/4 and any subsequent calls to send/3,4,5 must be executed in the same process. (Yes, same warning as before. It really means it. 😭)

And that's a wrap on ssh_connection(3), bestie!! πŸŽ€ Written by Ericsson AB, from ssh 6.0.5, and this manual page is dated... well, the version is right there and the page is filed under ssh_connection(3). Go forth and multiplex your channels responsibly, you gorgeous little network gremlin πŸ’–πŸ¦‹βœ¨

πŸ“¦ where this page comes from