Virtual RS232 Port from Userspace

Hey All, I am working on a job system for something very specific, and I require having a virtual serial device which has been established in userspace, with an unmodified linux kernel for portability. For kicks I started by using socat to create a pty at /dev/ttyACM99 and bridging to a TCP socket, however, when I run my serial script obviously the modem control ioctls create an error, and I cannot just modify the script as it is arbitrary. An additional requirement is I need to be able to read set ioctls, and whats acting like a serial device is a application running on the same machine.

I have found several solutions, all of which require me to add to then rebuild the linux kernel, or load a module, which I would really like to avoid doing as it creates a huge pain when looking at production deployment.

I am wondering if one of you happens to have some linux black magic I can’t seem to find in the linux docs.

With a bit of coding you could use LD_PRELOAD to intercept ioctl’s and make them just return the answer you want.

A quick search took me here: https://www.improwis.com/projects/sw_SerialOverIP_RFC2217/

Their recommendations of ttynvt and rfc2217_server.py sound promising.

1 Like

userio sounds promising (8. The userio Protocol — The Linux Kernel documentation) but I have never used it, nor do I understand how it works from reading that page :thinking: - I don’t see where the sent data goes to, or where the received data comes from.

1 Like

Well now I’m embarrassed because I did find and read that link, clearly not well enough, I remember coming up with a reason to skip over each solution but you’re right ttynvt looks like it does exactly what I want, I’ll take it for a spin.

The LD_PRELOAD trick was the bit of linux black magic I was looking for, after posting I started to read into eBPF but that would certainly be a more correct way to do the same thing.

Of course, ttynvt requires FUSE and I can’t use it because I’m running in a docker container and cannot enable for security reasons. sigh. Will be digging into uio thanks to @xzpfzxds or socat-rfc2217.

I’m going to summarize where I’m at because I think my own problem given its constraints is interesting, and maybe someone can tell me what I’m settling on is a dumb solution before I really dig in. I see one way my solution might bite me but I need to read more docs.

I need a virtual rs232 interface.

I have the following constraints-

  • I cannot modify the linux kernel because I need to just deploy this on other peoples machines and I don’t really want to move from mainline in general. I also don’t want to load kernel modules but if I had to I could.
  • I need to receive modem ioctls from a virtual serial interface which rules out most solutions. It needs to be capable of running arbitrary scripts and binaries, but will mostly be python scripts.
  • This is running in a docker container, so that rules out any solution using FUSE for security reasons. This rules out the ttynvt approach.
  • Using docker also complicates matters because from what I could tell, that sadly ruled out uio unless I wanted to write an additional application to run outside of docker which could be possible but would be annoying for multiple reasons.

Now, while LD_PRELOAD is a great solution, this becomes more complicated considering I need this to run on multiple ISAs as I use an M1, I wanted to show off on a few 80 core ARM64 servers, and I have coworkers and physical servers that require x86-64, and I also have a x86 laptop. Its a good and usable solution, but I am considering one other method for additional portability that doesn’t require I maintain shared object files.

One part of my dependency chain intrigued me, libseccomp provides additional isolation of each job step within the docker container, eBPF is quite portable, and I could apply a syscall filter for the ioctl syscall with a pty’s file descriptor to create a SIGSYS signal to handle ioctls (Normally “bad syscalls” will by default terminate the job step).

This has been an adventure.

You could also try making your own ptrace sandbox - it might be easier to use.

(the security issues with SYS_PTRACE were fixed in modern kernels, it’s generally ok to let docker add the capability to the container as long as you’re not adding a bunch of other ones at the same time).

1 Like