Shell Protocol

The shell protocol is implemented by both the shell service and any clients interfacing with the service.

All messages in the shell protocol are encoded as CBOR arrays and are sent in UDP packets. The first value in the encoded list is the channel_id. The second value will be the command and any additional values will be parameters for the command.

{ channel_id, command, parameters.. }

Messages

The primary purpose of the shell protocol is to allow the spawning and controlling of remote processes. These messages are used by shell clients to direct the shell service in this work of manipulating processes.

Spawn Process

This message is sent to the shell service to request a child process to be spawned. It contains a channel ID, the string ‘spawn’, a command, and spawn options. The command can be an absolute path to a binary or something in the system $PATH.

{ channel_id, 'spawn', command, options.. }

The possible values in the options argument are as follows:

  • args - An array of arguments to pass to the child process
  • pty - A boolean specifying whether a new pty is needed
  • env - An array of environment variable entries in the form "KEY=val"
  • cwd - The current working directory of the child process
  • uid - The uid of the process
  • gid - The gid of the process
  • detached - Determines if the child process should be detached from the service

Example of starting a long running shell:

{ 1, 'spawn', 'sh', { detached = true, pty = true, args = { '-l' } } }

Write to Stdin

This message is sent to the shell service to write data to the stdin of a child process. It contains a channel ID, the string ‘stdin’, and a data string. The data string will be written directly to the stdin of the child process.

{ channel_id, 'stdin', data }

Close Stdin

This message is sent to the shell service to close the stdin of a child process. It contains a channel ID and the string ‘stdin’.

After this message is received the shell service will close the stdin pipe for the specified child process. Any future messages attempting to write to stdin for this process will result in an error.

{ channel_id, 'stdin' }

Send Signal

This message is sent to the shell service to signal a child process. It contains a channel ID, the string ‘kill’, and optionally a signal number. If the signal number is omitted then SIGTERM will be sent.

{ channel_id, 'kill', signal }

A list of available signals can be found here.

Example usages:

Send SIGTERM to a child process:

{ channel_id, 'kill' }

Send SIGKILL to a child process:

{ channel_id, 'kill', 9 }

Resize Terminal

This message is sent to the shell service to resize the pseudo terminal of a child process, if one exists. It contains a channel ID, the string ‘resize’, the desired number of columns and the desired number of rows.

{ channel_id, 'resize', columns, rows }

Example message - Resizing a pseudo terminal to 10x10:

{ 1, 'resize', 10, 10 }

Process Created

This message is sent from the shell service when a process has been created. It contains the channel ID, the string ‘pid’ and a decimal number which is the pid.

{ channel_id, 'pid', pid }

Example message - A process has been created with a pid of 10:

{ 1, 'pid', 10 }

Stdout Data

This message is sent from the shell service when a process has produced data via stdout. It contains the channel ID, the string ‘stdout’, and a string of the stdout data.

{ channel_id, 'stdout', data }

Example message - ls producing directory output of shell-client:

{ 12, 'stdout', 'deps\ninit.lua\nlibs\nmain.lua\npackage.lua\nREADME.md\ntests\n' }

Stdout Closed

This message is sent from the shell service when a process’s stdout pipe has been closed. It contains the channel ID and the string ‘stdout’.

{ channel_id, 'stdout' }

Stderr Data

This message is sent from the shell service when a process has produced data via stderr. It contains the channel ID, the string stderr, and a string of the stderr data.

{ channel_id, 'stderr', data }

Example message - The result of running ls with an invalid argument:

{ 13, 'stderr', "Try 'ls --help' for more information.\n" }

Stderr Closed

This message is sent from the shell service when a process’s stderr pipe has been closed. It contains the channel ID and the string ‘stderr’.

{ channel_id, 'stderr' }

Process Exited

This message is sent from the shell service when a process has exited. It contains the channel ID, the string ‘exit’, the exit signal and the exit code.

{ channel_id, 'exit', code, signal }

Example messages

The result of a process exiting normally:

{ 14, 'exit', 0, 0 }

The result of sending a SIGKILL to a process:

{ 14, 'exit', 0, 9 }

Request List of Processes

This message is sent to the shell service to request a list of the current processes running in the shell service. It contains the channel ID and the string ‘list’.

{ channel_id, 'list' }

List of Processes

This message is sent from the shell service when a list of processes is requested. It contains the channel ID, the string ‘list’, and a list of objects containing process information (channel_id, path and pid). The channel ID can be used to communicate with the corresponding process in the list.

{ channel_id, 'list', { [channel_id] = { path, pid } } }

Example list of processes:

{ 16, 'list', { [12] = { path = 'sh', pid = 45 }, [14] = { path = 'sh', pid = 50 } } }

Example Usages

Running a Short-Lived Process

The goal here is to run uname -a on a remote machine via the shell service and see the output. The shell client randomly chooses 35 as its channel_id and sends a spawn command with the arguments.

Client: { 35, 'spawn', 'uname', { args = {'-a'} } }

The service sends back multiple messages in quick succession because this is a short-lived process.

Server: { 35, 'pid', 26191 }
Server: { 35, 'stdout', 'Linux vagrant 4.4.0-128-generic #154-Ubuntu SMP Fri May 25 14:15:18 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux' }
Server: { 35, 'stdout' }
Server: { 35, 'stderr' }
Server: { 35, 'exit', 0, 0 }

Running a Long-Lived Process

The goal here is to open a bash shell on a remote machine via the shell service and use that shell to execute commands.

Starting the Process

The shell client randomly chooses 55 as its channel_id and sends a spawn command with the arguments.

Client: { 55, 'spawn', 'sh', { detached = true, pty = true, args = { '-l' } } }

The service responds back with the pid of the newly created process.

Server: { 55, 'pid', 26825 }
Server: { 55, 'stdout', '\027kvagrant@vagrant:/home/vagrant\027\\' }
Server: { 55, 'stdout', '[vagrant@vagrant vagrant]$ ' }

Finding the Process

The shell client can send the list command over a new channel_id to find this process and its information.

Client: { 65, 'list' }

The service responds with the list of current processes.

Server: { 65, 'list', { [55] = { path = '/bin/sh', pid = 26825 } } }

Sending Data to the Process

The shell client can use the channel_id to send data to the stdin of the process.

Client: { 55, 'stdin', 'echo hello\n' }

The server will write this data to the stdin of the process and send back any data received over stdout.

Server: { 55, 'stdout', 'echo hello\r\n' }
Server: { 55, 'stdout', 'hello\r\n\027kvagrant@vagrant:/home/vagrant\027\\' }
Server: { 55, 'stdout', '[vagrant@vagrant vagrant]$ ' }

Killing the Process

Once the shell client is finished it can use the kill command to terminate the process.

Client: { 55, 'kill' }

The service will terminate the process, respond with any data which was sent via stdout or stderr and send the exit message.

Server: { 55, 'stdout', 'logout\r\n' }
Server: { 55, 'exit', 0, 0 }