# gensio **Repository Path**: bluewind/gensio ## Basic Information - **Project Name**: gensio - **Description**: No description available - **Primary Language**: Unknown - **License**: GPL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-10-13 - **Last Updated**: 2024-10-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README =========================== gensio - General Stream I/O =========================== This is gensio (pronounced gen'-see-oh), a framework for giving a consistent view of various stream (and packet) I/O types. You create a gensio object (or a gensio), and you can use that gensio without having to know too much about what is going on underneath. You can stack gensio on top of another one to add protocol functionality. For instance, you can create a TCP gensio, stack SSL on top of that, and stack Telnet on top of that. It supports a number of network I/O and serial ports. It also supports sound interfaces. gensios that stack on other gensios are called filters. You can do the same thing with receiving ports. You can set up a gensio accepter to accept connections in a stack. So in our previous example, you can setup TCP to listen on a specific port and automatically stack SSL and Telnet on top when the connection comes in, and you are not informed until everything is ready. gensio works on Linux, BSDs, MacOS, and Windows. On Windows, it gives you a single-threaded capable (but also multi-thread capable) event-driven interface (with blocking interfaces available) to simplify programming with lots of I/Os. It goes a long way to making writing portable I/O driven code easy. A *very* important feature of gensio is that it makes establishing encrypted and authenticated connections much easier than without it. Beyond basic key management, it's really no harder than TCP or anything else. It offers extended flexibility for controlling the authentication process if needed. It's really easy to use. Note that the gensio(5) man page has more details on individual gensio types. For instructions on building this from source, see the "Building" section at the end. gensio tools ============ A couple of tools are available that use gensios, both as an example and for trying things out. These are: gensiot A tool for making basic gensio connections. You can create any arbitrary gensio setup you like. See gensiot(1) for details. gtlsshd An sshd-like daemon that uses certauth, ssl, and SCTP or TCP gensios for making connections. It uses standard PAM authentication and uses ptys. See gtlsshd(8) for details. There is a item in FAQ.rst named "How to run gtlsshd on Windows", see that and the Building on Windows section below for more details, as there are a few tricky things you have to handle. gtlssh An ssh-like program that can connect to gtlsshd. It can also be used with ser2net to make establishing encrypted and authenticated connections easier. See gtlssh(1) for details. Available gensios ================= The following gensios are available in the library: sctp Normal SCTP communication. Streams and out of bound data are supported. End of message demarcation is not supported because it doesn't currently work on Linux. tcp Normal TCP communication. Out of bound data is supported. unix Unix stream domain socket. unixdgram Unix datagram domain socket. This is sort-of connection oriented. unixseq Unix seqpacket domain socket. Probably only works on Linux. udp Sort-of connection oriented UDP. stdio Access to either the calling program's stdio, or the ability to run a program and connect to its stdin, stdout, and stderr. NOTE: Do not use this for file I/O. Use the file gensio. file Used for accessing files. Allows both input and output file, and streams the data to/from the files. No accepter available. pty Run a program in a PTY and use the gensio to communicate with its tty. No accepter available. serialdev Connect to a device. You can use "sdev" as shorthand for "serialdev". It can hook to termios type devices, like ptys and /dev/tty, more than just serial ports. No accepter available. dev Connects to devices (like serialdev does) but does not do any serial port processing. It also has a write-only option for talking to printer ports or other write-only devices. It also has a rdonly option for talking to read-only devices. No accepter available. ipmisol Connect to a remote over IPMI SOL. Full serial port capabilities are available. No accepter available, unfortunately. dummy An accepter that doesn't do anything except look like an accepter to the user. Useful in some situations where an accepter is expected but you don't need to do anything. echo A gensio that echos everything that is sent to it. Useful for testing. No accepter available. telnet A filter gensio that implements the telnet protocol. It can do full serial support with RFC2217. ssl Implement SSL/TLS as a gensio filter. It supports client authentication, too. certauth A user authentication protocol implemented as a gensio filter. mux A channel multiplexer. You can create channels on top of it using open_channel(). Channels work as normal gensio, so you can have a number of gensios running on top of a single gensio. It also has end-of-message demarcation and obviously full flow-control capability individually on each channel. If you just need a gensio with end-of-message demarcation, you can use this as without creating channels. msgdelim Converts an unreliable stream interface into an unreliable packet interface. This is primarily so a reliable packet interface like relpkt can run over a serial port. It does not support streaming of data, so it's not very useful by itself. relpkt Converts an unreliable packet interface to a reliable packet interface (that also supports streaming). Made for running over msgdelim. It will run over UDP, but it's not ideal for that because it doesn't do all the internet-friendly flow control and such that SCTP and TCP do. trace A transparent gensio that allows the data read and/or written to be sent to a file, either as raw data or as human-readable hex data. It can also be used to block data flowing in one or both directions. perf A gensio that can send/receive data on top of a stack of gensios and measure the throughput on the channel. The received data from perf is information about the channel throughput. conacc A gensio accepter that takes a gensio stack string as a parameter. This lets you use a gensio as an accepter. When conacc is started, it opens the gensio, and when the gensio opens it reports a new child for the accepter. When the child closes it attempts to open the child again and go through the process again (unless accepts have been disabled on conacc). Why would you want to use this? Say in ser2net you wanted to connect one serial port to another. You could have a connection like: .. code-block:: yaml connection: &con0 accepter: conacc,serialdev,/dev/ttyS1,115200 connector: serialdev,/dev/ttyS2,115200 And it would connect /dev/ttyS1 to /dev/ttyS2. Without conacc, you could not use serialdev as an accepter. It would also let you use gtlsshd on a serial port if you wanted encrypted authenticated logins over a serial port. If you ran gtlsshd with the following: .. code-block:: bash gtlsshd --notcp --nosctp --oneshot --nodaemon --other_acc 'conacc,relpkt(mode=server),msgdelim,/dev/ttyUSB1,115200n81' You could connect with: .. code-block:: bash gtlssh --transport 'relpkt,msgdelim,/dev/ttyUSB2,115200n81' USB2 This creates a reliable packet transport over a serial port. The mode=server is required to make relpkt run as the server, since it would normally run as a client since it is not being started as an accepter. The ssl gensio (which runs over the transport) requires reliable communication, so it won't run directly over a serial port. xlt This gensio allows character translations to be done on data flowing through this filter. It's primarily to convert carraige returns and line feeds. mdns This gensio uses mDNS to lookup a service (protocol type, network type, port, address) and then connect to that service. If you have a program like ser2net that advertise mDNS service, you don't have to worry about finding port numbers and such, it's all handled for you. keepopen This gensio presents an always open connection to the upper layer and keeps the lower layer connection open. If it closes, it re-opens it. script This gensio executes an external program with the external program's stdio connected to the child of this gensio. Once the external program terminates, this gensio will report that it is open and pass all the data through. This can be used to run scripts to set things up on a connection before hooking to the parent gensio. sound A gensio that provides access to sound devices and files. It's a little complicated, read the docs in gensio.5 afskmdm Yes, it looks like a jumble of letters. A filter gensio that sits on top of the sound gensio and does an Audio Frequency Shift Keying modem, like is used on AX.25 amateur radio. kiss An amateur radio protocol for talking to TNCs. This is used by AX25 in many cases. ax25 An amateur radio protocol for packet radio. To fully use this you would need to write code, since it uses channels and oob data for unnumbered information, but you can do basic things with just gensiot if all you need is one communication channel. For instance, if you wanted to chat with someone over the radio, and the kiss port is on 8001 on both machines, on the accepting machine you can run: .. code-block:: bash gensiot -i 'stdio(self)' -a \ 'ax25(laddr=AE5KM-1),kiss,conacc,tcp,localhost,8001' which will hook to the TNC and wait for a connection on address AE5KM-1. Then you could run: .. code-block:: bash gensiot -i 'stdio(self)' \ 'ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,localhost,8001' on the other machine. This will connect to the other machine over TNC 0 with the given address. Then anything you type in one will appear on the other, a line at a time. Type "Ctrl-D" to exit. The 'stdio(self)' part turns off raw mode, so it's a line at a time and you get local echo. Otherwise every character you types would send a packet and you couldn't see what you were typing. To hook to the N5COR-11 AX.25 BBS system, you would do: .. code-block:: bash gensiot -i 'xlt(nlcr),stdio(self)' \ 'ax25(laddr=AE5KM-2,addr="0,N5COR-11,AE5KM-2"),kiss,tcp,localhost,8001' Most BBS systems use CR, not NL, for the new line, so the xlt gensio is used to translate incoming these characters. Of course, this being gensio, you can put any workable gensio underneath ax25 that you would like. So if you want to play around or test without a radio, you could do ax25 over UDP multicast. Here's the accepter side: .. code-block:: bash gensiot -i 'stdio(self)' -a \ 'ax25(laddr=AE5KM-1),conacc,'\ 'udp(mcast="ipv4,224.0.0.20",laddr="ipv4,1234",nocon),'\ 'ipv4,224.0.0.20,1234' and here's the connector side: .. code-block:: bash gensiot -i 'stdio(self)' \ 'ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),'\ 'udp(mcast="ipv4,224.0.0.20",laddr="ipv4,1234",nocon),'\ 'ipv4,224.0.0.20,1234' kiss is not required because UDP is already a packet-oriented media. Or you can use the greflector program to create a simulated radio situation. On the machine "radiopi2", run: .. code-block:: bash greflector kiss,tcp,1234 which will create a program that will reflect all received input to all other connections. Then on the accepter side: .. code-block:: bash gensiot -i 'stdio(self)' -a \ 'ax25(laddr=AE5KM-1),kiss,conacc,tcp,radiopi2,1234' and the connecting side: .. code-block:: bash gensiot -i 'stdio(self)' \ 'ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,radiopi2,1234' The test code uses the reflector for some testing, since it's so convenient to use. ratelimit Limit the data throughput for a gensio stack. cm108gpio Allow a GPIO on a CMedia CM108 or equivalent sound device to be controlled. Used with afskmdm for keying a transmitter. These are all documented in detail in gensio(5). Unless otherwise stated, these all are available as accepters or connecting gensios. Creating Your Own Gensios ========================= You can create your own gensios and register them with the library and stack them along with the other gensios. The easiest way to do this is to steal code from a gensio that does kind of what you want, then modify it to create your own gensio. There is, unfortunately, no good documentation on how to do this. The include file include/gensio/gensio_class.h has the interface between the main gensio library and the gensio. The gensio calls all come through a single function with numbers to identify the function being requested. You have to map all these to the actual operations. This is somewhat painful, but it makes forwards and backwards compatibility much easier. Creating your own gensio this way is fairly complex. The state machine for something like this can be surprisingly complex. Cleanup is the hardest part. You have to make sure you are out of all callbacks and no timers might be called back in a race condition at shutdown. Only the simplest gensios (echo, dummy), strange gensios (conadd, keepopen, stdio), and gensios that have channels (mux, ax25) directly implement the interface. Everything else uses include/gensio/gensio_base.h. gensio_base provides the basic state machine for a gensio. It has a filter portion (which is optional) and a low-level (ll) portion, which is not. The filter interface has data run through it for the processing. This is used for things like ssl, certauth, ratelimit, etc. Filter gensios would use this. These all use gensio_ll_gensio (for stacking a gensio on top of another gensio) for the ll. Terminal gensios each have their own ll and generally no filter. For lls based on a file descriptor (fd), gensio_ll_fd is used. There is also an ll for IPMI serial-over-lan (ipmisol) and for sound. Most of the terminal gensios (tcp, udp, sctp, serial port, pty) use the fd ll, obviously. Once you have a gensio, you can compile it as a module and stick it in $(moduleinstalldir)/. Then the gensio will just pick it up and use it. You can also link it in with your application and do the init function from your application. mDNS support ============ The mdns gensio has already been discussed, but the gensio library provides an easy to use mDNS interface. The include file for it is in gensio_mdns.h, and you can use the gensio_mdns(3) man page to get more information on it. To make an mdns connection using gensiot, say you have ser2net set up with mdns enabled like: .. code-block:: yaml connection: &my-port accepter: telnet(rfc2217),tcp,3001 connector: serialdev,/dev/ttyUSB1,115200N81 options: mdns: true then you can connection to it with gensiot: .. code-block:: bash gensiot 'mdns,my-port' gensiot will find the server, port, and whether telnet and rfc2217 are enabled and make the connection. In addition, there is an gmdns tool that lets you do queries and advertising, and gtlssh can do mDNS queries to find services. If you have secure authenticated logins for ser2net, and you enable mdns on ser2net, like: .. code-block:: yaml connection: &access-console accepter: telnet(rfc2217),mux,certauth(),ssl,tcp,3001 connector: serialdev,/dev/ttyUSBaccess,115200N81 options: mdns: true it makes the setup very convenient, as you can just do: .. code-block:: bash gtlssh -m access-console That's right, you can just directly use the connection name, no need to know the host, whether telnet or rfc2217 is enabled, or what the port is. You still have to set up the keys and such on the ser2net server, of course, per those instructions. General Concepts ================ gensio has an object oriented interface that is event-driven. Synchronous interfaces are also available. You deal with two main objects in gensio: a gensio and a gensio accepter. A gensio provides a communication interface where you can connect, disconnect, write, receive, etc. A gensio accepter lets you receive incoming connections. If a connection comes in, it gives you a gensio. The interface is event-driven because it is, for the most part, completely non-blocking. If you open a gensio, you give it a callback that will be called when the connection is up, or the connection fails. Same for close. A write will return the number of bytes accepted, but it may not take all the bytes (or even any of the bytes) and the caller must account for that. The open and close interfaces have a secondary blocking interface for convenience. These end in _s. This is for convenience, but it's not necessary and use of these must be careful because you can't really use them from callbacks. Speaking of callbacks, data and information coming from gensio to the user is done with a function callback. Read data, and when the gensio is ready for write data comes back in a callback. A similar interface is used for calling from the user to the gensio layer, but it is hidden from the user. This sort of interface is easily extensible, new operations can be easily added without breaking old interfaces. The library provides several ways to create a gensio or gensio accepter. The main way is str_to_gensio() and str_to_gensio_accepter(). These provide a way to specify a stack of gensios or accepters as a string and build. In general, you should use this interface if you can. In general, interfaces that are not performance sensitive are string based. You will see this in gensio_control, and in auxiliary data in the read and write interface to control certain aspects of the write. The library also provides ways to set up your gensios by individually creating each one. In some situations this might be necessary, but it limits the ability to use new features of the gensio library as it gets extended. If a gensio supports multiple streams (like SCTP), stream numbers are passed in the auxdata with "stream=n". Streams are not individually flow controlled. Channels, on the other hand, are separate flows of data over the same connection. Channels are represented as separate gensios, and they can be individually flow controlled. Include Files ============= There are a few include files you might need to deal with when using gensios: gensio.h The main include files for gensios and gensio accepters. sergensio.h Serial port handling gensios and gensio accepters. gensio_os_funcs.h The definition for an OS handler. argvutils.h Many gensio functions take an argv array, this is utilities for dealing with argvs. gensio_selector.h A definition for a default OS handler. These are for the most part documented in the man pages. For creating your own gensios, the following include files are available for you: gensio_class.h The main include file for creating your own gensio. sergensio_class.h The main include file for creating your own serial port gensio. gensio_base.h This handles a lot of the boiler plate for a gensio. Most of the standard gensios use this. It splits the gensio function into an optional filter, and a lower layer interface called an ll. gensio_ll_fd.h An ll that provides most of the boilerplate for dealing with a file descriptor. gensio_ll_gensio.h An ll that provides all that is necessary for stacking a gensio on top of another gensio. The filter gensios (telnet, ssl, etc.) use this as the ll. Each include file has lots of documentation about the individual calls and handlers. Errors ====== gensio has it's own set of errors to abstract it from the OS errors (named GE_xxx) and provide more flexibility in error reporting. These are in the gensio_err.h include file (automatically included from gensio.h) and may be translated from numbers to a meaningful string with gensio_err_to_str(). Zero is defined to be not an error. If an unrecognized operating system error occurs, GE_OSERR is returned and a log is reported through the OS handler log interface. OS Handler ========== One slightly annoying thing about gensio is that it requires you to provide an OS handler (struct gensio_os_funcs) to handle OS-type functions like memory allocation, mutexes, the ability to handle file descriptors, timers and time, and a few other things. The library does provide several OS handlers. You can call gensio_alloc_os_funcs() to allocate a default one for your system (POSIX or Windows). You can see that man page for more details. This will generally be the best performing option you have for your system. For POSIX systems, OS handlers for glib and TCL are available, allocated with gensio_glib_funcs_alloc() and gensio_tcl_funcs_alloc(). These really don't work very well, especially from a performance point of view, the APIs for glib and TCL are not well designed for what gensio does. TCL can only support single-threaded operation. glib multithreaded operation only has one thread at a time waiting for I/O. But they do work, and the tests are run with them. These are not available on Windows because of poor abstractions on glib and because of lack of motivation on TCL. But if you are using something else like X Windows, etc that has it's own event loop, you may need to adapt one for your needs. But the good thing is that you can do this, and integrate gensio with pretty much anything. There is also a waiter interface that provides a convenient way to wait for things to occur while running the event loop. This is how you generally enter the event loop, because it provides a convenient way to signal when you are done and need to leave the loop. Documentation for this is in:: include/gensio/gensio_os_funcs.h Threads ======= The gensio library fully supports threads and is completely thread-safe. However, it uses signals on POSIX system, and COM on Windows systems, so some setup is required. The "main" thread should call gensio_os_proc_setup() at startup and call gensio_os_proc_cleanup() when it is complete. This sets up signals and signal handlers, thread local storage on Windows, and other sorts of things. You can spawn new threads from a thread that is already set up using gensio_os_new_thread(). This gives you a basic OS thread and is configured properly for gensio. If you have a thread created by other means that you want to use in gensio, as long as the thread create another thread and doesn't do any blocking functions (any sort of wait, background processing, functions that end in _s like read_s, etc.) you don't have to set them up. That way, some external thread can write data, wake another thread, or do things like that. If an external thread needs to do those things, it should call gensio_os_thread_setup(). Signals ======= As mentioned in the threads section, the gensio library on Unix uses signals for inter-thread wakeups. I looked hard, and there's really no other way to do this cleanly. But Windows has a couple of signal-like things, too, and these are available in gensio, also. If you use gensio_alloc_os_funcs(), you will get an OS funcs using the passed in signal for IPC. You can pass in GENSIO_OS_FUNCS_DEFAULT_THREAD_SIGNAL for the signal if you want the default, which is SIGUSR1. The signal you use will be blocked and taken over by gensio, you can't use it. gensio also provides some generic handling for a few signals. On Unix, it will handle SIGHUP through the gensio_os_proc_register_reload_handler() function. On Windows and Unix you can use gensio_os_proce_register_term_handler(), which will handle termination requests (SIGINT, SIGTERM, SIGQUIT on Unix) and gensio_os_proc_register_winsize_handler() (SIGWINCH on Unix). How these come in through Windows is a little messier, but invisible to the user. All the callbacks from from a waiting routine's wait, *not* from the signal handler. That should simplify your life a lot. You can see the man pages for more details on all of these. Creating a gensio ================= Connecting gensios ------------------ To create a gensio, the general way to do this is to call ``str_to_gensio()`` with a properly formatted string. The string is formatted like so:: [([