Skip to main content

Building a VxWorks Telnet Client: Protocol, NVT Negotiation, and C Implementation

·1664 words·8 mins
VxWorks Telnet TCP NVT C Programming Embedded Systems Networking Real-Time OS
Table of Contents

Building a VxWorks Telnet Client: Protocol, NVT Negotiation, and C Implementation

Telnet is a classic TCP-based remote terminal protocol that remains useful in embedded systems, industrial controllers, network equipment, and legacy VxWorks environments. Although Telnet itself is relatively simple, implementing a reliable client requires more than opening a TCP socket and exchanging strings.

A VxWorks Telnet client must correctly handle the Network Virtual Terminal (NVT) model, interpret Telnet negotiation commands, manage authentication prompts, process remote command output, and safely handle TCP’s byte-stream semantics.

This article analyzes the architecture of a bare-metal Telnet client implemented in C for VxWorks, explains the Telnet protocol state machine, and highlights several important reliability and safety improvements.

🌐 Understanding the Telnet Protocol and NVT
#

Telnet operates over TCP and normally uses port 23. It follows a client-server architecture in which the client establishes a TCP connection to a remote Telnet server and exchanges terminal data over the connection.

At the protocol level, Telnet defines a Network Virtual Terminal (NVT). NVT provides a standardized character-oriented interface that allows systems with different operating systems, character sets, and terminal implementations to communicate using a common representation.

One important characteristic of Telnet is that normal application data and protocol commands share the same TCP stream.

🔧 The IAC Command Mechanism
#

Telnet uses the IAC (Interpret As Command) byte, 0xFF, to distinguish protocol commands from ordinary terminal data.

When the receiver encounters IAC, the following bytes must be interpreted according to the Telnet command format rather than treated as normal text.

The most common commands include:

Command Hex Meaning
IAC 0xFF Interpret As Command
WILL 0xFB Sender will perform an option
WONT 0xFC Sender will not perform an option
DO 0xFD Request the remote side to perform an option
DONT 0xFE Request the remote side to stop performing an option
SB 0xFA Begin option subnegotiation
SE 0xF0 End option subnegotiation

For example, when a server sends:

IAC DO <option>

it is asking the client to enable or perform the specified option.

Likewise:

IAC WILL <option>

indicates that the sender intends to enable a particular option.

Options that require additional parameters can use the SB and SE subnegotiation mechanism. Terminal window size is a common example.

🧩 Telnet Client State Machine
#

A practical VxWorks Telnet client can be divided into five sequential stages:

[1. TCP Connect]
        |
        v
[2. NVT Negotiation]
        |
        v
[3. User Login]
        |
        v
[4. Command Loop]
        |
        v
[5. Disconnect]

Separating the implementation into these stages makes the client easier to debug and maintain.

1. TCP Connection
#

The tcp_client_conn() function establishes the underlying TCP connection.

Its responsibilities typically include:

  • Resolving the destination IP address.
  • Calling inet_addr() or hostGetByName() when hostname resolution is required.
  • Initializing a sockaddr_in structure.
  • Setting the destination port to TCP port 23.
  • Creating a BSD socket.
  • Calling connect() to establish the session.

Once the TCP connection succeeds, the client can begin processing Telnet protocol traffic.

2. NVT Option Negotiation
#

The negotiate() function handles Telnet option negotiation.

It examines incoming IAC sequences and generates appropriate responses. Typical behavior includes:

  • Responding to DO with WONT when the client does not support the requested option.
  • Responding to WILL with DO when the client accepts the requested option.
  • Processing supported subnegotiation commands.
  • Handling terminal window-size negotiation when required.

This stage is essential because a Telnet server may send negotiation commands immediately after the TCP connection is established.

3. Authentication
#

The telnet_login() function automates the login procedure.

A simple embedded implementation can identify expected prompts and then transmit the corresponding credentials.

For example, the state machine may behave approximately as follows:

Receive login prompt
        |
        v
Send username
        |
        v
Receive password prompt
        |
        v
Send password
        |
        v
Detect shell prompt
        |
        v
Login successful

The original implementation identifies prompts using delimiters such as : and ].

This approach can work for a controlled target environment, but it is less reliable when different Telnet servers use different prompt formats.

4. Command Interaction
#

After authentication, functions such as send_cmd() and telnet_receive_data_add_deal() handle normal interaction with the remote system.

The basic sequence is:

  1. Append the appropriate line ending to the command.
  2. Send the command through the TCP socket.
  3. Receive the server response.
  4. Process echoed command text.
  5. Parse the returned data.
  6. Detect the remote shell prompt indicating that the command has completed.

A typical application-level flow is therefore:

Application
    |
    +--> send_cmd()
    |
    v
TCP Socket
    |
    v
Telnet Server
    |
    v
Command Execution
    |
    v
TCP Response
    |
    v
telnet_receive_data_add_deal()

5. Connection Cleanup
#

When the remote operation is complete, finish_equipment_operation() can evaluate termination conditions and close the socket.

For example, the application may terminate when it receives a predefined marker such as:

END

The socket should then be released with close() and any associated buffers or application resources should be cleaned up.

🛡️ Important TCP and Buffer Handling Improvements
#

Although a fixed-purpose Telnet client can work reliably in a controlled environment, several implementation details deserve attention before deploying it in a production VxWorks system.

Avoid Sending C String Terminators
#

A common mistake is transmitting:

strlen(send_buffer) + 1

bytes when sending a normal C string.

The additional byte is the terminating '\0', which is an implementation detail of the C string rather than part of the Telnet command.

Telnet communication normally uses actual protocol data and line endings such as:

\r\n

or:

\n

Therefore, sending the null terminator can produce unexpected behavior with some Telnet servers or terminal implementations.

A safer approach is generally:

send(client_socket, send_buffer, strlen(send_buffer), 0);

provided that the buffer contains the intended Telnet data.

Handle TCP as a Byte Stream
#

TCP does not preserve application-level message boundaries.

A single call to send() does not guarantee that the receiver obtains the same amount of data in a single recv() call.

For example, the sender might transmit:

login: username password $

while the receiver obtains:

login:

followed by:

 username

and finally:

 password $

The reverse can also happen: several logical messages may arrive together in one recv() call.

Therefore, Telnet parsing should operate on a byte stream rather than assuming that every recv() corresponds to a complete Telnet message or prompt.

🔒 Prevent Receive-Buffer Overruns
#

Another important issue concerns the use of string functions after recv().

The recv() function returns the number of bytes actually received. It does not automatically append a null terminator.

Code such as:

strlen(receive_buffer)

is unsafe unless the buffer has first been explicitly terminated.

A safer pattern is:

int length;

length = recv(client_socket,
              receive_buffer,
              BUFFER_SIZE - 1,
              0);

if (length > 0)
{
    receive_buffer[length] = '\0';
}

This ensures that subsequent C string operations do not continue beyond the valid received data.

For a fully binary-safe Telnet parser, an even better approach is to retain the returned byte count and process the buffer using explicit lengths rather than relying on null-terminated strings.

🔄 Improve Prompt Detection
#

The original implementation uses expressions such as:

if (buffer[length - 3 < 0 ? 0 : length - 3] == ']')

to determine whether a login operation has completed.

This may work when communicating with one specific piece of equipment, but it tightly couples the client to a particular prompt format.

Different Telnet servers may produce prompts such as:

login:
Password:
>
#

or:

router#

A more portable implementation can search for recognizable prompt strings or patterns.

For example:

if (strstr(buffer, "login:") != NULL)
{
    /* Send username */
}

if (strstr(buffer, "Password:") != NULL)
{
    /* Send password */
}

if (strstr(buffer, "#") != NULL)
{
    /* Shell prompt detected */
}

For production systems, prompt detection should preferably be implemented as an explicit state machine rather than relying on a single character near the end of a buffer.

🧠 Use an Explicit Telnet Parser
#

A more robust implementation separates Telnet protocol processing from application-level command parsing.

A useful architecture is:

                  TCP Socket
                      |
                      v
              +---------------+
              | Receive Buffer |
              +---------------+
                      |
                      v
              +---------------+
              | Telnet Parser |
              +---------------+
                 /          \
                /            \
               v              v
       IAC / Negotiation    Normal Data
               |              |
               v              v
        Option Handler    Application Parser
                              |
                              v
                       Login / Commands

The Telnet parser should distinguish at least three categories of input:

  1. Normal data — terminal text passed to the application.
  2. Negotiation commandsWILL, WONT, DO, and DONT.
  3. Subnegotiation data — information enclosed between SB and SE.

This separation prevents Telnet control bytes from accidentally being interpreted as ordinary application text.

⚙️ Recommended Production Improvements #

For a more robust VxWorks Telnet client, several improvements are worth implementing:

  • Treat TCP input as a continuous byte stream.
  • Never assume one recv() call equals one complete response.
  • Explicitly null-terminate buffers before using C string functions.
  • Validate every recv(), send(), socket(), and connect() return value.
  • Avoid transmitting the C string '\0' unless the remote protocol explicitly requires it.
  • Implement Telnet negotiation as a dedicated parser.
  • Support SB/SE subnegotiation correctly when required.
  • Replace hard-coded prompt characters with configurable prompt patterns.
  • Protect against buffer overflow and malformed Telnet sequences.
  • Separate transport, Telnet protocol, authentication, and application logic.
  • Add timeout handling so a stalled remote device cannot block the application indefinitely.

📌 Summary
#

A VxWorks Telnet client is essentially composed of several layers:

+----------------------------------+
| Application Command Processing   |
+----------------------------------+
| Login / Prompt State Machine     |
+----------------------------------+
| Telnet NVT / Option Negotiation  |
+----------------------------------+
| TCP Socket Layer                 |
+----------------------------------+
| VxWorks Network Stack            |
+----------------------------------+

The basic implementation can be summarized as:

TCP Connect
NVT Negotiation
Login
Command Transmission
Response Parsing
Prompt Detection
Disconnect

For controlled embedded environments, a lightweight implementation can be sufficient. However, production-quality deployments should pay particular attention to TCP stream boundaries, Telnet negotiation, buffer management, timeout handling, and prompt detection.

The most important principle is to avoid treating Telnet as a simple string-based protocol. Telnet is a byte-stream protocol carrying both application data and control commands, so a reliable client must first distinguish protocol-level traffic from terminal data before handing the remaining data to the application layer.

Related

VxWorks: Check File System Capacity and Free Space
·1365 words·7 mins
VxWorks VxWorks File System DosFS HRFS Statfs Embedded Systems C Programming RTOS
VxWorks Programming Guide: Tasks, IPC, I/O, Semaphores
·2629 words·13 mins
VxWorks RTOS C Programming TaskLib SemLib MsgQLib Socket IPC Embedded Systems
VxWorks USB HCD Driver Development: OHCI Layer Explained
·2368 words·12 mins
VxWorks USB USB Driver HCD OHCI UHCI Embedded Systems Real-Time OS Device Drivers