The Forkpty Router

Dealing with containers is now a common deployment pattern. One of the most annoying tasks when dealing with jails/namespacesis ‘attaching’ to already running instances.

The forkpty router aims at simplifyng the process giving a pseudoterminal server to your uWSGI instances.

A client connect to the socket exposed by the forkpty router and get a new pseudoterminal connected to a process (generally a shell, but can be whatever you want)

uwsgi mode VS raw mode

Clients connecting to the forkpty router can use two protocols for data exchange: uwsgi and raw mode.

The raw mode simply maps the socket to the pty, for such a reason you will not be able to resize your terminal or send specific signals.The advantage of this mode is in performance: no overhead for each char.

The uwsgi mode encapsulates every instruction (stdin, signals, window changes) in a uwsgi packet. This is very similar to how ssh works, so if youplan to use the forkpty router for shell sessions the uwsgi mode is the best choice (in terms of user experience).

The overhead of the uwsgi protocol (worst case) is 5 bytes for each stdin event (single char)

Running the forkpty router

The plugin is not builtin by default, so you have to compile it:

  1. uwsgi --build-plugin plugins/forkptyrouter

or, using the old plugin build system:

  1. python uwsgiconfig.py --plugin plugins/forkptyrouter

generally compiling the pty plugin is required too (for client access)

  1. uwsgi --build-plugin plugins/pty

or again, using the old build system:

  1. python uwsgiconfig.py --plugin plugins/pty

Alternatively, you can build all in one shot with:

  1. UWSGI_EMBED_PLUGINS=pty,forkptyrouter make

Now you can run the forkptyrouter as a standard gateway (we use UNIX socket as we want a communication channel with jails, and we unshare the uts namespace to give a new hostname)

  1. [uwsgi]
  2. master = true
  3. unshare = uts
  4. exec-as-root = hostname iaminajail
  5. uid = kratos
  6. gid = kratos
  7. forkpty-router = /tmp/fpty.socket

and connect with the pty client:

  1. uwsgi --pty-connect /tmp/fpty.socket

now you have a shell (/bin/sh by default) in the uWSGI instance. Running hostname will give you ‘iaminajail’

Eventually you can avoid using uWSGI to attacj to the pty and instead you can rely on this simple python script:

  1. import socket
  2. import sys
  3. import os
  4. import select
  5. import copy
  6. from termios import *
  7. import atexit
  8.  
  9. s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
  10. s.connect(sys.argv[1])
  11.  
  12. tcattr = tcgetattr(0)
  13. orig_tcattr = copy.copy(tcattr)
  14. atexit.register(tcsetattr, 0, TCSANOW, orig_tcattr)
  15.  
  16. tcattr[0] |= IGNPAR
  17. tcattr[0] &= ~(ISTRIP | IMAXBEL | BRKINT | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF);
  18. tcattr[0] &= ~IUCLC;
  19. tcattr[3] &= ~(ICANON | ECHO | ECHOE | ECHOK | ECHONL);
  20. tcattr[3] &= ~IEXTEN;
  21. tcattr[1] &= ~OPOST;
  22. tcattr[6][VMIN] = 1;
  23. tcattr[6][VTIME] = 0;
  24.  
  25. tcsetattr(0, TCSANOW, tcattr);
  26.  
  27. while True:
  28. (rl, wl, xl) = select.select([0, s], [], [])
  29. if s in rl:
  30. buf = s.recv(4096)
  31. if not buf: break
  32. os.write(1, buf)
  33. if 0 in rl:
  34. buf = os.read(0, 4096)
  35. if not buf: break
  36. s.send(buf)

The previous example uses raw mode, if you resize the client terminal you will se no updates.

To use the ‘uwsgi’ mode add a ‘u’:

  1. [uwsgi]
  2. master = true
  3. unshare = uts
  4. exec-as-root = hostname iaminajail
  5. uid = kratos
  6. gid = kratos
  7. forkpty-urouter = /tmp/fpty.socket
  1. uwsgi --pty-uconnect /tmp/fpty.socket

a single instance can expose both protocols on different sockets

  1. [uwsgi]
  2. master = true
  3. unshare = uts
  4. exec-as-root = hostname iaminajail
  5. uid = kratos
  6. gid = kratos
  7. forkpty-router = /tmp/raw.socket
  8. forkpty-urouter = /tmp/uwsgi.socket

Changing the default command

By default the forkpty router run /bin/sh on new connections.

You can change the command using the –forkptyrouter-command

  1. [uwsgi]
  2. master = true
  3. unshare = uts
  4. exec-as-root = hostname iaminajail
  5. uid = kratos
  6. gid = kratos
  7. forkpty-router = /tmp/raw.socket
  8. forkpty-urouter = /tmp/uwsgi.socket
  9. forkptyrouter-command= /bin/zsh