back to index

COM over TCP/IP, RFC2217

Serial port over network - COM/UART/tty over TCP/IP, RFC2217
      hardware, signals and speeds
      ioctl calls and structures
            c_cflag, control mode flags - speed, parity, bits
            c_iflag, input mode flags - input data handling/translations
            c_oflag, output mode flags - output data handling/translations
            c_lflag, line discipline flags - terminal control, control characters processing
            c_cc[] array, control characters
            terminal-initiated signals (when ISIG enabled)
Serial-over-TCP/IP, RFC-2217
      RFC 854, other telnet RFCs
      RFC 2217
            Control sequences, telnet console examples
            Control sequences, serial port examples
Serial port over network
      linux servers
   - good
            ser2net - good?
            remserial - bad (no RFC2217)
      linux client
            ttynvt - good
            remserial - bad (no ioctls)
            socat - bad (no ioctls?)
            socat-rfc2217 (todo: check)
      windows client
            hercules terminal
            HW VSP 3
            Free Virtual Serial Ports
ttynvt modifications
      possible future expansion
                  crude solution:

Serial port over network - COM/UART/tty over TCP/IP, RFC2217

hardware, signals and speeds

moved to /tables/serial.webt

ioctl calls and structures

The serial port state is described in ioctl structure, termios. The structures are as follows:

asm-generic/termios.h                    asm-generic/termbits.h
#define NCC 8                  #define NCCS 19         #define NCCS 19
struct termio { struct termios { struct termios2 { unsigned short c_iflag; tcflag_t c_iflag; tcflag_t c_iflag; /* input mode flags */ unsigned short c_oflag; tcflag_t c_oflag; tcflag_t c_oflag; /* output mode flags */ unsigned short c_cflag; tcflag_t c_cflag; tcflag_t c_cflag; /* control mode flags */ unsigned short c_lflag; tcflag_t c_lflag; tcflag_t c_lflag; /* local mode flags */ unsigned char c_line; cc_t c_line; cc_t c_line; /* line discipline */ unsigned char c_cc[NCC]; cc_t c_cc[NCCS]; cc_t c_cc[NCCS]; /* control characters */ }; }; speed_t c_ispeed; /* input speed */ speed_t c_ospeed; /* output speed */ };

The termios structure is almost identical to termio, with exception of more indirect specification of data type and more control characters (19 instead of 8).

The termios2 structure is almost identical to termios, with added fields for integer-specified input and output data rate. (Few port controllers allow different speeds in each direction, this is uncommon. Usually both input and output data rate match.)

The c_cc structure is an array of control characters; position in array corresponds to a function, value corresponds to character

The bit flags for control lines are as follows:

bit    TIOCM_[ref]
0x0001  LE        in     DSR line    Line Enable
0x0002  DTR       OUT    DTR line
0x0004  RTS       OUT    RTS line
0x0008  ST                           secondary transmit, usually unused
0x0010  SR                           secondary receive, usually unused
0x0020  CTS       in     CTS line
0x0040  CAR,CD    in     DCD line
0x0080  RNG,RI    in     RI line
0x0100  DSR       in     DSR line
0x2000  OUT1      OUT?               Unassigned Programmable Output 1, usually unused
0x4000  OUT2      OUT?               Unassigned Programmable Output 2, usually unused
0x8000  LOOP      out    loopback    usually unused or used only for testing

ioctls as follows:


termios data structure flags:[ref]

c_cflag, control mode flags - speed, parity, bits

0x100f CBAUD           MASK: baud rates
 0x0000         B0        no speed - hang up
 0x0001        B50
 0x0002        B75
 0x0003       B110
 0x0004       B134
 0x0005       B150
 0x0006       B200
 0x0007       B300
 0x0008       B600
 0x0009      B1200
 0x000a      B1800
 0x000b      B2400
 0x000c      B4800
 0x000d      B9600
 0x000e     B19200,EXTA   on some systems, external-clock input A
 0x000f     B38400,EXTB   on some systems, external-clock input B; 38400 baudrate often needs a specific kludge
0x1000 CBAUDEX         mask for extended baudrates flag, for rates not defined in POSIX.1
 0x1000     BOTHER        other unspecified baud rate
 0x1001     B57600
 0x1002    B115200
 0x1003    B230400        highest usual value; top for FTDI at 12 MHz?
 0x1004    B460800
 0x1005    B500000
 0x1006    B576000
 0x1007    B921600
 0x1008   B1000000
 0x1009   B1152000
 0x100a   B1500000
 0x100b   B2000000        top for FTDI at 48 MHz?
 0x100c   B2500000
 0x100d   B3000000
 0x100e   B3500000
 0x100f   B4000000
0x0030 CSIZE           MASK: data bits
 0x0000        CS5        5 bits
 0x0010        CS6        6 bits
 0x0020        CS7        7 bits
 0x0030        CS8        8 bits
0x0040  CSTOPB         stop bits - 1 when unset, 2 when set
0x0080  CREAD          enable receiver
0x0100  PARENB         parity enabled
0x0200  PARODD         odd parity - even when unset, odd when set
0x0400  HUPCL          lower modem control lines (hang up) after last process closes device
0x0800  CLOCAL         ignore modem control lines

c_iflag, input mode flags - input data handling/translations

0x0001  IGNBRK      ignore BREAK if set; if IGNBRK=BRKINT=0, BREAK is received as 0x00 byte, if PARMRK set BREAK received as 0xFF 0x00 0x00
0x0002  BRKINT      if IGNBRK not set, BREAK flushes I/O queue and (if allowed) sends SIGINT to terminal
0x0004  IGNPAR      ignore framing and parity errors
0x0008  PARMRK      meaningful if IGNPAR=0,INPCK=1; if set, parity error byte 0xXX is received as 0xFF 0x00 0xXX
0x0010  INPCK       enable input parity check
0x0020  ISTRIP      strip off 8th bit, MSB
0x0040  INLCR       translate 0x0A to 0x0D (LF to CR)
0x0080  IGNCR       ignore 0x0D
0x0100  ICRNL       translate 0x0A to 0x0D (CR to LF, if IGNCR not set)
0x0200  IUCLC       translate uppercase to lowercase
0x0400  IXON        output XON/XOFF flow control enable
0x0800  IXANY       if set, any character restarts output (not just XON/START/^Q)
0x1000  IXOFF       input XON/XOFF flow control enable
0x2000  IMAXBEL     non-POSIX; ring bell if queue full; linux ignores, acts like always set
0x4000  IUTF8       non-POSIX; in linux, allows correct erasing of characters with utf8

c_oflag, output mode flags - output data handling/translations

0x0001  OPOST       implementation-defined input processing
0x0002  OLCUC       translate lowercase to uppercase
0x0004  ONLCR       translate 0x0A to 0x0D (LF to CR)
0x0008  OCRNL       translate 0x0D to 0x0A (CR to LF)
0x0010  ONOCR       no output of 0x0D/CR on column 0
0x0020  ONLRET      don't output 0x0D/CR
0x0040  OFILL       send fill characters for delay (keep line busy instead of pausing)
0x0080  OFDEL       not in linux; if set, fill char is 0x7F/DEL, otherwise fill char is 0x00
0x0100  NLDLY       MASK: newline delay (for mechanical terminals)
 0x0000    NL0
 0x0100    NL1
0x0600  CRDLY       MASK: carriage return delay (for mechanical terminals)
 0x0000    CR0
 0x0200    CR1
 0x0400    CR2
 0x0600    CR3
0x1800  TABDLY      MASK: horizontal tab delay (for mechanical terminals)
 0x0000    TAB0
 0x0800    TAB1
 0x1000    TAB2
 0x1800    TAB3,XTABS   expand tabs to spaces
0x2000  BSDLY       MASK: backspace delay (for mechanical terminals, not implemented)
 0x0000    BS0
 0x2000    BS1
0x4000  VTDLY       MASK: vertical tab delay
 0x0000    VT0
 0x4000    VT1
0x8000  FFDLY       MASK: form feed delay
 0x0000    FF0
 0x8000    FF1

c_lflag, line discipline flags - terminal control, control characters processing

0x0001  ISIG        if set: if INTR, QUIT, SUSP, DSUSP received, generate corresponding signal
0x0002  ICANON      canonical mode
0x0004  XCASE       if set if ICANON set, terminal uppercase-only (not in linux)
0x0008  ECHO        echo input to output
0x0010  ECHOE       if ICANON set, if set ERASE erases preceding input character, WERASE erases word
0x0020  ECHOK       if ICANON set, if set KILL erases current line
0x0040  ECHONL      if ICANON set, echo 0x0A/NL even if ECHO not set
0x0080  NOFLSH      disable flushing input/output queues when generating signals for INT, QUIT, SUSP
0x0100  TOSTOP      send SIGTTOU
0x0200  ECHOCTL     (non-POSIX) if ECHO set, show control chars other than 0x09/TAB, 0x0A/NL, START, STOP as ^x (eg 0x08/DEL = ^H)
0x0400  ECHOPRT     (non-POSIX) if ECHO set if ICANON set characters printed as they are erased
0x0800  ECHOKE      (non-POSIX) if ICANON set, if set, KILL erases each character on line
0x1000  FLUSHO      (non-POSIX, not in linux) output being flushed, triggered by typing DISCARD char
0x2000  PENDIN      (non-POSIX, not in linux) all chars in input queue reprinted when char read
0x4000  IEXTEN      implementation-defined input processing
0x8000  EXTPROC

Canonical mode - sends data line by line (on CR or LF); noncanonical mode - sends each byte immediately

Raw mode - noncanonical, no echo, no special processing:

c_cc[] array, control characters

pos      char     hex  ^x  POSIX Linux needed-flags
 0  ETX  VINTR     03  ^C              ISIG          sends SIGINT
 1  FS   VQUIT     1c  ^\              ISIG          sends SIGQUIT
 2  DEL  VERASE    7f                  ICANON        also 0x08, BS, ^H
 3  NAK  VKILL     15  ^U              ICANON        also ^X; erases input since last EOF
 4  EOT  VEOF      04  ^D              ICANON        send line immediately without witing for EOL; if first char of the line, send EOF/end of file
 5       VTIME      0                  not-ICANON    time in milliseconds for noncanonical read
 6       VMIN       0                  not-ICANON    minimum number of characters for noncanonical read
 7       VSWTCH     0        no   no   n/a           SystemV shell switch
 8  DC1  VSTART    11  ^Q              IXON          XON, start output after suspended by VSTOP
 9  DC3  VSTOP     13  ^S              IXON          XOFF, stop output until VSTART typed
10  SUB  VSUSP     1a  ^Z              ISIG          send SIGTSTP signal
11       VEOL       0                  ICANON        end of line
12  DC2  VREPRINT  12  ^R    no        ICANON,IEXTEN reprint unread characters
13  SI   VDISCARD  0f  ^O    no   no          IEXTEN (start/stop discarding pending input)
14  ETB  VWERASE   17  ^W    no        ICANON,IEXTEN word erase
15  SYN  VLNEXT    16  ^V    no               IEXTEN quotes next input, removes special meaning
16       VEOL2      0        no   no   ICANON        (another end of line)
    EM   VDSUSP   (19) ^Y    no   no   ISIG,  IEXTEN (sends SIGTSTP)
    DC4  VSTATUS  (14) ^T    no   no   ISIG?         (display status, send SIGINFO - not in linux)

default value (from strace):
   0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  17  18
 x03 x1c x7f x15 x04 x00 x00 x00 x11 x13 x1a x00 x12 x0f x17 x16 x00 x00 x00

VTIME and VMIN control read() behavior:
0       0           read() immediately returns, with zero length (no data) if no byte available
0       nonzero     read() blocks until VMIN bytes available
nonzero 0           read() returns after VTIME, with or without data
nonzero nonzero     read() returns after VTIME or after VMIN bytes available

terminal-initiated signals (when ISIG enabled)

Serial-over-TCP/IP, RFC-2217

The age of modems is mostly over, the stage was seized by computer networks. Ethernet, wifi, and many other technologies replaced serial lines. Except in communication with various devices - the serial, usually in the form of TTL-level UART (Arduino, ESP8266, various microcontrollers) or RS232 (various peripherals) or RS485 (industrial buses), is still a king there. Even many USB devices act as serial ports, whether as /dev/ttyACM* or /dev/ttyUSB*.

The problem was encountered even in the old times. The telnet protocol, short for "teletype network", was born. And extended a number of times, to add features as the era required.

Telnet itself is good for "plain" sending of data streams back and forth, with some additional negotiation functions. However, serial ports have a number of "out of band" features, controlled by ioctls - everything from bitrate to modem control lines to sending a break.

But there are ways for encoding such out-of-band activity as in-band signaling.

RFC 854, other telnet RFCs


For telnet itself, the signaling is based on a dedicated escape character, 0xFF, named IAC, "interpret as command". After it, one or two other bytes are sent with the command and a parameter.

(if 0xFF has to be send through as a character, send it twice; first time it gets interpreted as IAC, the next command 0xff then means "send 0xff through".)


Several codes are defined:

Options: [ref]

RFC 2217

The 0x2C option for the telnet serial port operation includes subcommands:

TODO: examples of command sequences

Control sequences, telnet console examples

The responses to commands do not have to arrive in order.

CAUTION: The commands are not particularly synchronized with the data output (due to data buffering between the running program and the port output), so eg. commands to handle control lines can be executed before the nearly preceding data bytes made their way out of the port to freedom (or enslavement in another hardware).

For clarity, sequences "ff fa" and "ff f0" are written together as "fffa" and "fff0".

ff fb 01  IAC WILL ECHO
ff fe 01  IAC DONT ECHO
ff fd 01  IAC DO ECHO
ff fd 2c IAC DO COM-PORT

identify terminal type:

fffa 18 00 ..... fff0  IAC SB TERMINAL-TYPE IS <code> IAC SE

stop a process in terminal

[press ctrl-c on keyboard]
ff f4
[remote side sends SIGINT to running process]
(duplicate of direct-sending of ctrl-C, same principle different mechanism as not all ancient terminals could do this)

Control sequences, serial port examples

...picocom initialization on connection to virtual port
ff fb 2c IAC WILL COM-PORT // local-computer is able to act as a serial port
ff fb 01  IAC WILL ECHO // remote-port is able to send echo
ff fe 01  IAC DONT ECHO // local computer does not want remote to echo
ff fb 03  IAC WILL SUPPRESS-GOAHEAD // remote port is able to suppress the half-duplex GOAHEAD control
ff fd 03  IAC DO SUPPRESS-GOAGEAD // local computer wants remote to not send GOAHEAD
ff fd 00  IAC DO BINARY // remote port wants local to send binary data
ff fb 2c IAC WILL COM-PORT // remote port is able to act as a COM port
ff fd 2c IAC DO COM-PORT // local computer wants remote to act as a COM port
ff fd 2c IAC DO COM-PORT // remote port wants local computer to act as a COM port
fffa 2c 01 00 00 25 80  fff0  IAC SB COM-PORT SET-BAUDRATE 0x00002580  IAC SE // set remote baud rate to 9600, aka 0x2580
fffa 2c 6b 00  fff0  IAC SB COM-PORT MODEMSTATE-MASK 0x00  IAC SE // remote port wants to not send any modemstate updates
fffa 2c 02 08  fff0  IAC SB COM-PORT SET-DATASIZE 0x08  IAC SE // set remote data size to 8 bits
fffa 2c 03 01  fff0  IAC SB COM-PORT SET-PARITY 0x01  IAC SE // set remote parity to none
fffa 2c 04 01  fff0  IAC SB COM-PORT SET-STOPSIZE 0x01  IAC SE // set remote stopbits to 1
fffa 2c 05 01  fff0  IAC SB COM-PORT SET-CONTROL 0x01  IAC SE // execute remote command 0x01, "set flow control to none"
fffa 2c 6b 00  fff0  IAC SB COM-PORT rNOTIFY-MODEMST 0x00  IAC SE // remote confirms modemstate mask
fffa 2c 65 00 00 25 80  fff0  IAC SB COM-PORT rSET-BAUDRATE 0x00002580  IAC SE // remote confirms baudrate set to 9600
fffa 2c 66 08  fff0  IAC SB COM-PORT rSET-DATASIZE 0x08  IAC SE // remote confirms data size set to 8 bits
fffa 2c 67 01  fff0  IAC SB COM-PORT rSET-PARITY 0x01  IAC SE // remote confirms parity set to none
fffa 2c 68 01  fff0  IAC SB COM-PORT rSET-STOPSIZE 0x01  IAC SE // remote confirms stopbits set to 1
fffa 2c 69 01  fff0  IAC SB COM-PORT rSET-CONTROL 0x01  IAC SE // remote confirms command to flow control none
...set RTS+ (send SET-CONTROL 0x0b == "Set RTS Signal State ON")
fffa 2c 05 0b fff0  IAC SB COM-PORT SET-CONTROL 0x0b IAC SE // execute remote command 0x0b, "Set RTS Signal State ON"
fffa 2c 69 0b fff0  IAC SB COM-PORT rSET-CONTROL 0x0b IAC SE // remote confirms command to set RTS to ON
...set RTS- (send SET-CONTROL 0x0c == "Set RTS Signal State OFF")
fffa 2c 05 0c fff0  IAC SB COM-PORT SET-CONTROL 0x0c IAC SE // execute remote command 0x0b, "Set RTS Signal State OFF"
fffa 2c 69 0c fff0  IAC SB COM-PORT rSET-CONTROL 0x0c IAC SE // remote confirms command to set RTS to OFF

Serial port over network

Sometimes it is needed to make a remote serial port appear as local one, typically with TCP/IP network in between. Telnet-based serial connection can be utilized.

The same control sequences defined in original telnet are extended with the 0x2C (44d) code for the serial port related operations.

Here we need two ends of communication:

linux servers - good

Python has a highly powerful library for handling serial ports, aptly named pyserial. is a reference implementation of such a server.

Very handy for debugging, can be easily edited to serve other purposes.

Fairly complete support of pretty much all the baudrate and control line commands.

Runs on any platform on which Python 3 can be installed.

 python3 ./ -v -v -v -v -v -p 5000 /dev/ttyUSB0

(runs daemon on port 5000, physical port /dev/ttyUSB0, verbose about what it is doing)

ser2net - good?

Written in C. Available as a standard package for many distros (incl. Raspbian, OpenWRT). Runs as a daemon.

TODO: find out how good command support it has, if it can handle arbitrary baudrates.


(configuration in /etc/ser2net.conf)

remserial - bad (no RFC2217) Does not seem to correctly handle the RFC2217 commands. Good enough for basic shoveling data back and forth without need to handle control lines or change speeds.

Fairly simple C implementation but no support for RFC2217 commands.

linux client

ttynvt - good

Written in C. Userspace-implemented character device driver, via CUSE. Capable of handling ioctls native-like.

Runs in a master process and two slave threads that handle the communication itself. When using strace to debug what's happening, don't forget to attach it to both the master process PID and two other (usually subsequent) PIDs or a lot of activity will appear to be missing.

Sends debug information to syslog. Copious data can be obtained by setting syslog level properly.

 ttynvt -D 9 -M 199 -m 6 -n ttyNVT0 -S

(runs daemon that creates /dev/ttyNVT0 with major:minor devnode 199:6, and on port-open connects to where it expects a RFC2217-compliant server)

remserial - bad (no ioctls)

Written in C. Available for many distros. Can create local devicefile-like interface, linked to a pseudoterminal (pty).

No support for ioctl interception.

socat - bad (no ioctls?)

Written in C. Available for many distros. Can create local devicefile-like interface, linked to a pseudoterminal (pty). Does not seem to be able to intercept ioctls.

TODO: check

socat-rfc2217 (todo: check)

windows client

hercules terminal

Not a device driver, only a userspace terminal for handling local serial ports and some connections to remote devices. A versatile tool for simplest terminal operations, supports hex.

todo: look at its proprietary extensions for GPIOs, implement the calls to ttyvnt


Free Virtual Serial Ports

ttynvt modifications

The original implementation of serial port did not support arbitrary baudrates, uses the termios structure. Newer variant is not limited by the hardcoded baudrate list, uses extended termios2 structure and modified ioctl calls.

ttynvt of course did not support these. (Because nothing can be perfect. Oh well, let's start with good-enough.)

To obtain support for arbitrary baud rates, and for compatibility with newer software and it's ioctl calls, the missing ioctls were crudely hacked into the original ttynvt implementation.

possible future expansion

Experiences were obtained with handling ioctl calls, allowing to further extend the virtual port functionality - add "vendor-specific" command sequences, eg. for handling various GPIO and bitbanging modes on USB-serial dongles.

Server-side then also has to be modified for support of such.

Virtual port servers can be implemented with relative ease, to appear like a RFC2217-compliant remote serial port (and to appear like a real local serial port in the system). Emulation of various devices (eg. position-faking GPS, for testing or "testing" purposes) is one of the options. Or maybe a protocol translator between one vendor's software and another vendor's device. (This can also be done locally, using the same CUSE trick ttynvt does to act as a proxy between a local serial port, but a python implementation of just the server side may be easier to both write and debug.)

The extensions for control signals can be used for including such out of band data over RS485. A two-port microcontroller to translate messages, split data stream from command stream, handle packetization/addressing for the bus.

issues was crashing on port open: --port /dev/ttyNVT0 chip_id v2.7-dev
Serial port /dev/ttyNVT1
Traceback (most recent call last):
File "/usr/miniconda3/lib/python3.7/site-packages/serial/", line 501, in read
'device reports readiness to read but returned no data '
serial.serialutil.SerialException: device reports readiness to read but returned no data (device disconnected or multiple access on port?)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/bin/", line 2966, in <module>
File "/usr/local/bin/", line 2959, in _main
File "/usr/local/bin/", line 2653, in main
esp = ESPLoader.detect_chip(each_port, initial_baud, args.before, args.trace)
File "/usr/local/bin/", line 261, in detect_chip
File "/usr/local/bin/", line 462, in connect
last_error = self._connect_attempt(mode=mode, esp32r0_delay=False)
File "/usr/local/bin/", line 442, in _connect_attempt
File "/usr/local/bin/", line 381, in sync
File "/usr/local/bin/", line 334, in command
p =
File "/usr/local/bin/", line 279, in read
return next(self._slip_reader)
File "/usr/local/bin/", line 1875, in slip_reader
read_bytes = if waiting == 0 else waiting)
File "/usr/miniconda3/lib/python3.7/site-packages/serial/", line 509, in read
raise SerialException('read failed: {}'.format(e))
serial.serialutil.SerialException: read failed: device reports readiness to read but returned no data (device disconnected or multiple access on port?)

crude solution:

in slip_reader(): replace

        read_bytes = if waiting == 0 else waiting)
          read_bytes = if waiting == 0 else waiting)
        except (OSError, serial.serialutil.SerialException):
          print("[caught error: no data]")


If you have any comments or questions about the topic, please let me know here:
Your name:
Your email:
Leave this empty!
Only spambots enter stuff here.