gensio(5) | File Formats Manual | gensio(5) |
gensio - How to specify a gensio
<type>[(options)][,gensio|terminaloptions]
gensio stands for GENeral Stream Input Output. It provides an abstraction for all kinds of stream I/O, and even makes some packet I/O look like stream I/O (like UDP). In particular, gensio makes it easy to create encrypted and authenticated connections.
The gensio library specifies a connection (gensio) using a string format. This consists of a gensio type, optional options in parenthesis. For a terminal gensio (one that is at the bottom of the stack), it may take more options separated by a comma. For filter gensios (ones not on the bottom of the stack) another gensio must be specified after the comma. For instance:
specifies a serial port gensio. Or:
specifies a TCP connection with a 100 byte read buffer, to connect to port 4000 on localhost. Or:
specifies a telnet filter on top of a TCP connection.
When specifying a gensio, you can add quotes (single or double) to remove the special meaning for some characters, so you can have commas in options and such. They may also be escaped with a "\". For instance, if you are specifying a laddr in an sctp address, you need to do this. The following address:
will result in a failure because the option splitting code will split at the commas. Instead, do:
and it will work.
Accepter gensios, gensios used for accepting connections, as opposed to connecting gensios, are specified in the same way. Each individual type can vary, and some gensios are only connecting gensios. The options can vary from the accepter and connecting gensios of the same type. For instance, an accepting TCP gensio does not have to have a hostname, but a connecting one does.
When an accepter gensio receives a connection, it will create an accepted gensio. This works mostly like a connecting gensio, except some functions may be limited. You may not be able to close and then open an accepted gensio.
The gensio library has a concept of client and server. The accepted gensios are generally considered servers. Connecting gensios are generally considered clients. Some gensios may allow this to be overridden.
A gensio may be reliable or not. A reliable gensio will reliably deliver all data in sequence, like TCP. An gensio that is not reliable may drop data or deliver data out of sequence, like UDP. This can be queried with gensio_is_reliable().
A gensio may be packet or not. A packet gensio will exactly match up writes and reads. So if you write 15 bytes into one side, a 15 byte read for that data will appear on the other side. A gensio that is not packet will not respect write boundaries, that 15 byte write may result in multiple reads or it may be combined with another write into more than 15 bytes. Packet I/O requires careful use to make it work correctly. If the gensio doesn't have room for another packet, a write will succeed but write 0 bytes. Otherwise if you write larger than a packet size, it will only take the number of bytes that can fit into a packet, so it will truncate. This is useful if you want to stream over a packet interface, but if you really want packets you have to make sure all the sizes are set properly. Particularly, you must set the max read and write size to the same value. You should check on writes that it wrote all the data. You can do partial read handling, if you like, but you will only get the rest of the packet on the second and following read.
A gensio may be message oriented. This implementation is stolen from SCTP (even though it's not really supported on Linux at the moment). It basically means you can explicitly mark message boundaries when sending data, and that explicit mark will be set on the read side. You do this by adding an "eom" auxdata on the write; the end of that write it assumed to be the end of a message. If the write does not accept all the data, the "eom" is ignored, you must write the remaining data again with "eom" set. You may also do partial write of messages and set "eom" at the end. On the receive side, "eom" will be set when the end of a message is delivered. The data delivered in the receive callback will be only the data for that message. If the user does not accept all the data, the data left in the message is again presented to the user with "eom" set.
The options vary greatly between the different gensios. Each gensio type will be covered in a separate section. Also note that gensio types can be dynamically added by the user, so there may be gensios available that are not described here.
Unless otherwise noted, every gensio takes a:
Every option to a gensio (including the serialdev and ipmisol options), unless othersize stated, is available as a default for the gensios. You can use gensio_set_default() to set the default value used by all gensios allocated after that point. If you leave the class NULL, it will set the base default, which will affect all gensios unless they have an override. If you set the class, it will only affect gensios with that class name.
Be very careful with some defaults. Setting "mode" default, for instance, could really screw things up.
For string defaults, setting the default value to NULL causes the gensio to use it's backup default.
Some gensio types support serial port setting options. Standard serial ports, IPMI Serial Over LAN, and telnet with RFC2217 enabled.
A client serial gensio can set and get serial port options using the sergensio_xxx() functions. Server serial gensios receive requests from the client via GENSIO_EVENT_SER_xxx events in the callback.
Some gensios support the concept of a stream and/or a channel.
A stream is delivered as part of the normal data stream of a gensio. The "default" stream will be treated normally. All other streams will have "stream=<x>" given in the auxdata to specify which stream to write on or which stream was read from. Streams cannot be individually flow controlled.
A channel is a flow of data like a stream, but it can be individually flow controlled. It appears as a new gensio in the GENSIO_EVENT_NEW_CHANNEL callback. You can create a channel with gensio_alloc_channel() and then open it with gensio_open(). Once open, a channel works like a normal gensio. If you close the main channel for a gensio, the other channels will stay open; the resources for the main channel will still be kept around until all channels are closed.
See the indvidual gensio description for more information on streams and channels.
The ssl and certauth gensios use public key cryptography. This section gives a little overview of how that works. You can safely skip this section if you already understand these concepts.
Public key cryptography is used to authenticate and encrypt information at the beginning of a session. It is a fairly expensive operation and is not generally used to encrypt information after the beginning.
In public key cryptography, you have three basic things: A private key, a certificate (or public key), and a certificate authority (CA).
The private key is just that: private. You don't even send your private key to a certificate authority for signing of your certificate. Keep it private, non-readable by everyone else. Protect it, if it becomes known your certificate becomes useless to you, anyone can impersonate you.
The certificate is the public key, and is mathematically associated with a single private key. It's just that, public, anyone can use it to test that you have the private key by asking you to sign some data. The data in the certificate (like the Common Name) is part of the certificate. If that data is modified, the certificate validation will fail.
The CA is generally a trusted third-party that validates you and signs your certificate (generally for a fee). CAs issue their own public certificates that are well-known and generally available on your system. The CA certificates are used to prove that your certificate is valid.
The process if signing has been mentioned already, but not described. Basically, you use your private key to generate a value over some given data that proves you have the private key. The certificate is ised to mathematically verify the signature.
Two things are normally done with this:
In a public key exchange, the entity wishing to be authorized sends a certificate. The authorizing entity will look through it's CA for a certificate that has signed the sent certificate. If the authorizing entity finds a certificate that can be used to validate the sent certificate, the sent certificate is valid.
After that, the authorizing entity sends some generally random data to the other entity. The other entity will take that data, perhaps some other important data that it want to make sure is not modified in the transfer, and signs that data. It sends the signature back to the authorizing entity. The authorizing entity can then use the data and the signature to validate that the sending entity has the private key associated with the certificate.
This is basically how https works. Note it is the web client that authenticates the web server, not the other way around. This proves that you are connecting to the entity you say you are connecting to. The authentication of a web client to the web server is generally done via a different mechanism (though SSL/TLS used by the ssl gensio has a way to do it, it is not generally used for that purpose).
In the web server scenario, data in the certificate (specifically the Common Name and Subject Alternate Name) must match the name of the web page to which you are connecting. The ssl and certauth gensios do not do this authentication, that is up to the user if it is necessary.
The certificate can be used to encrypt data that can only be decrypted with the private key. When establishing an encrypted connection, this is generally used to transfer a symmetric cryptography key from one entity to another (the authorizing entity to the requesting entity in the case above). You could encrypt all the data with the public key, but that is very expensive and in our example above would require certificates and private keys on both ends.
It is possible to create a certificate that can act as its own certificate authority. This is how ssh works. You create a public and private key. You put the public key in the .ssh/authorized_keys directory on systems where you want to log in. The certificate acts as both the public key (as part of the initial transfer) and the CA (in the authorized_key directory) for authorizing you use of the system you are logging in to.
ssh also stores places it has connected to in .ssh/known_hosts, which is the CA in the opposite direction. This is why it asks you if you have never connected to a system before, it doesn't have the key in its CA. Or why, if you connect to a system you have connected to before and the certificates don't match or fail validation, it complains about it.
So if you are using self-signed certificates, you need to be careful to put only ones you trust in the CA. This is obviously not possible in a large system like the world wide web, thus the creation of third-party trusted CAs.
The above discussions mention trust several times. Cryptography does not remove the need for trust. It just makes trust more convenient. If someone sends you a certificate, you need to validate that it was actually them that sent you the certificate, and that it was not modified in transit. If you add that certificate to your CA, you are trusting the certificate, and you better make sure (with fingerprints, generally, see the openssl docs for details) that it came from a trusted entity.
The hardest part of cryptography in general is key management. Breaking cryptographic algorithms is really hard. Getting people to divulge private keys or use the wrong keys is a lot easier.
For more on cryptography in general, and cryptography and trust, Bruce Schneier has some excellent books on the subject.
Note that a single hostname may result in more than one address. For instance, it may have both an IPv4 and IPv6 address. These are treated just like multiple hostnames. For instance, if you use localhost as a host name, you generally get the IPv4 and IPv6 version:
The hostname may be prefixed with ipv4 or ipv6, which will force the connections to those protocols. Specifying ipv6n4 will create a socket that is IPv6 but will handle IPv4 connections.
Note that using ipv6n4 will not automatically create a socket that is available to IPv4. You can do, say tcp,ipv6n4,::1,1234 and it will work, but since ::1 is not IPv4, you won't be able to get to it from IPv4. You have to specify an IPv4 address, like: tcp,ipv6n4,127.0.0.1,1234 which will result in a single IPv6 socket mapped into IPv4 space:
If you do not specify a hostname on an accepting gensio (like sctp,1234) it will only create an IPv6 socket that is IPv4 mapped. This way it will accept both IPv4 and IPv6 connections. Even though getaddrinfo would normally return two addresses, only the IPv6 one is used unless there are no IPv6 addresses configured where it will return an IPv4 address.
In general, for connecting gensios only the first address that is found will be used. SCTP is the exception, it will do multi-homing on all the addresses that come up. Do you may need to be fairly specific with addresses.
In general IPv6 addresses are preferred if both are available.
Time consists of a set of numbers each followed by a single letter. That letter may be 'D', 'H', 'M', 's', 'm', 'u', or 'n', meaning days, hours, minutes, seconds, milliseconds, microseconds, or nanoseconds. So, for instance, "10D5H4M9s100u" would be ten days, 5 hours, 4 minutes, 9 seconds, 100 microseconds. If a plain number with no letter at the end is given, the value may default to a specific unit. That will be specified by the specific gensio.
tcp[(<options>)][,<hostname>],<port>[[,<hostname>],<port>[...]]
hostname = [ipv4|ipv6|ipv6n4,]<name>
A TCP connecting gensio must have the hostname specified. Multiple hostname/port pairs may be specified. For a connecting TCP gensio, each one will be tried in sequence until a connection is established. For acceptor gensios, every specified hostname/port pair will be listened to.
For accepters, if the port is specified as zero, a random port in the dynamic port range specified by IANA will be chosen. If more than one address is present, the same port will be chosen for all addresses. You can fetch the port using the gensio_acc_control() function with the option GENSIO_ACC_CONTROL_LPORT.
TCP supports out of band (oob) data, which is data that will be delivered out of order as soon as possible. This comes in a normal read, but with "oob" in the auxdata. You can send oob data by adding "oob" to the write auxdata, but oob writes are limited to 1 byte and writing any more than this results in undefined behavior. Note that "oobtcp" is also delivered and accepted in auxdata, so you can tell TCP oob data from other oob data.
In addition to readbuf, the tcp gensio takes the following options:
The remote address will be in the format "[ipv4|ipv6],<addr>,<port>" where the address is in numeric format, IPv4, or IPv6.
TCP returns a standard struct sockaddr for GENSIO_CONTROL_RADDR_BIN control.
Allocated as a terminal gensio with gdata as a "const struct gensio_addr *"
udp[(<options>)][,<hostname>],<port>[[,<hostname>],<port>[...]]
hostname = [ipv4|ipv6|ipv6n4,]<name>
A UDP gensio creates a UDP socket, but it makes it look like an unrealiable stream of data. The specification is the same as a TCP socket, except that a UDP socket is created, obviously.
The semantics of a UDP socket are a little bit strange. A connecting UDP socket is fairly straightforward, it opens a local socket and sends data to the remote socket.
An accepter gensio is not so straightforward. The accepter gensio will create a new accepted gensio for any packet it receives from a new remote host. If you disable read on any of the accepted gensio or disable accepts on the accepting gensio, it will stop all reads on all gensios associated with that accepting gensio.
Note that UDP accepter gensios are not really required for using UDP, the are primarily there for handling ser2net accepter semantics. You can create two connecting UDP gensios and communicate between them.
UDP gensios are not reliable, but are obviously packet-oriented.
Port 0 is supported just like TCP for accepters, see Dynamic Ports in the TCP section above for details.
The destination address defaults to the one specified on the gensio specifier (for connecting gensios) or the remote address that initiated the connection (for accepting gensios), but may be overridden using "addr:<addr>" in the write auxdata.
In addition to readbuf, the udp gensio takes the following options:
The remote address will be in the format "[ipv4|ipv6],<addr>,<port>" where the address is in numeric format, IPv4, or IPv6.
UDP returns a standard struct sockaddr for GENSIO_CONTROL_RADDR_BIN control.
Multicast can be used on UDP gensios with the nocon, maddr and laddr options. To set up a multicast, create a client UDP gensio and set the laddr for the receive port and the destination address to the multicast and enable nocon, like:
and you will receive and send data on the multicast address. The laddr option is required to set the port to receive on. It means you will have a local address, too, and will receive packets on that, too.
Allocated as a terminal gensio with gdata as a "const struct gensio_addr *"
sctp[(<options>)][,<hostname>],<port>[[,<hostname>],<port>[...]]
hostname = [ipv4|ipv6|ipv6n4,]<name>
An SCTP gensio is specified like a UDP or TCP one. However, the semantics are different. For a connecting gensio, it will attempt to create a multi-homed connect with all the specified hostnames and ports. All the ports must be the same.
For an accepter gensio, it will create a single socket with all the specified addresses as possible destinations. Again, all the ports must be the same.
SCTP gensios are reliable. They are not, at the moment, packet oriented. There is currently no support of SCTP_EXPLICIT_EOR in the Linux implementation of SCTP, and without that it would be hard to make it packet oriented.
When specifying IPv6 addresses that might map to IPv4, you must be careful. If one side can do IPv4 and the other side can only do IPv6, the connection may come up, but will disconnect quickly because it cannot communicate on the IPv4 side. For instance, the following accepter:
and the following connector:
will fail this way because the connector will support IPv4 add but the accepter will not.
SCTP implements the Nagle algorithm by default, which can interact badly if sack_freq is set to more than one. At least Linux defaults sack_freq to 2, but the gensio overrides this to avoid surprising behaviour. What happens is in some situations you can get an outstanding packet that is unacked, since sack_freq is greater than one. The Nagle algorithm will not send any new data until any already sent data is acked. So one end is waiting for a new packet to send a sack, and the other end is holding data until it gets a sack. So you get stuck waiting for the sack_delay where the sack will go out and kick things back off again.
You need to be aware of this if you modify sack_freq.
In addition to readbuf, the sctp gensio takes the following options:
Port 0 is supported just like TCP for accepters, see Dynamic Ports in the TCP section above for details.
SCTP support out of band (oob) data, which is data that will be delivered out of order as soon as possible. This comes in a normal read, but with "oob" in the auxdata. You can send oob data by adding "oob" to the write auxdata.
See documentation on SCTP for more details.
The remote address will be in the format "[ipv4|ipv6],<addr>,<port>[;[ipv4|ipv6],<addr>,<port>[...]]" where the address is in numeric format, IPv4, or IPv6. Each remote address for the SCTP connection is listed.
SCTP returns a packed struct sockaddr for GENSIO_CONTROL_RADDR_BIN control, per SCTP semantics.
Allocated as a terminal gensio with gdata as a "const struct gensio_addr *"
unix[(<options>)],<socket_path>
Create a unix domain socket as an accepter, or connect to a unix domain socket as a connecter.
A file will be created with the given socket path, you must have permissions to create a writeable file in that location. If the file already exists, an error will be returned on an accepter socket unless you specify delsock which will cause the file to be deleted.
You should read the unix(7) man page for details on the semantics of these sockets, especially permissions. The options below allow setting various permission and ownership of the file, but this may not have any effect on who can open socket depending on the particular operating system. Portable programs should not rely on these permissions for security. Also note that Linux remote credentials are not currently implemented.
In addition to readbuf, the unix gensio takes the following options:
The remote address will be: "unix,<socket path>".
UNIX returns a standard struct sockaddr_un for GENSIO_CONTROL_RADDR_BIN control.
Allocated as a terminal gensio with gdata as a "const struct gensio_addr *"
serialdev[(<options>)],<device>[,<serialoption>[,<serialoption>]]
A serialdev connection is a local serial port. The device is a /dev/xxx type, and should be real stream device of some type that normal termios or serial processing work on. For non-serial devices, use the "dev" gensio.
This is, no surprise, a serial gensio.
You can also use "sdev" instead of "serialdev" for shorthand.
One problem with serialdev and UUCP locking is that if you fork() a process while one is open, the forked process will have the serialdev but the value in the UUCP lockfile will be incorrect. There's not much that can be done about this, so be careful.
In addition to readbuf, the serialdev gensio takes the following options:
There are a plethora of serialoptions, available as defaults:
Set the device to write only. No termios definition is done on the port. This can be done to talk to a line printer port, for instance. False by default. Note that for historical reasons this is different than the wronly option set in the parameters, this one sets both turns off termio process and sets wronly.
Using "off" will disable rs485; that is useful for overriding a user-defined default setting. Default is "off".
On the default for this, it's a integer, -1 means don't set the value, 0 means turn off, 1 means turn on.
The remote address string is the device and serial parms, in a format like "9600N81[,<option>[,<option>[...]]]". Options are one of: XONXOFF, RTSCTS, CLOCAL, HANGUP_WHEN_DONE, RTSHI, RTSLO, DTRHI, DRLO, offline.
The remote ID for the serial dev is the file descriptor returned as an integer.
Allocated as a terminal gensio with gdata as a "const char *" with the device and parameters string.
dev[(<options>)],<device> This is like a serialdev gensio, but doesn't do any serial port processing like termios or break processing. This can be used for /dev/lpX devices for instance. This is read/write by default, you must set the wronly option to disable read access for a write-only device and rdonly to disable write access for read-only devices.
This takes the same options as serialdev except that it does not do locking. The drain_time and char_drain_wait options may or may not work. It does not take serial options.
accepter = stdio[(options)]
connecting = stdio[(options)],<program>
The stdio gensio is a fairly strange one, but it's fairly useful.
A connecting stdio gensio runs the given program (unless self is set as an option) and connects its standard input and output to the gensio's main channel. So it's easy to run a program and interact with it. If you want to get stderr from the gensio, open a channel on the main channel gensio, see below.
NOTE: Though epoll() doesn't work with normal files, the stdio gensio has special handling for normal files so they mostly work. This can have surprising side effects if you use stdin as a normal file. When you hit the end of stdin, the stdio gensio will return GE_REMCLOSE and you may shut down the gensio and possibly lose any output going to stdout. Use the file gensio if you can.
For connecting gensios, in the forked process, the code will set the effective (and saved) uid and guid to the current real uid and guid if the effective and real uids are different. This way a user can set the real uid and gid to what they want the program to run under, but keep the effective uid and gid to the (probably root) values so they can be restored to those values after opening the pty. The group list is also set to the groups the real userid is in. Note that nothing is done if the effective and real userids are the same.
If you have both stdin/stdout and stderr opened, you must close both of them to completely close the gensio. You cannot re-open the gensio until both are closed.
The connecting gensio support the GENSIO_CONTROL_ENVIRONMENT control to allow the environment to be set for the new process.
An accepting gensio immediately does a connection when started and connection stdin and stdout of the running program to the gensio.
In addition to readbuf, a connecting stdio takes the following options:
The stdio connecting gensio that start another program does not provide stderr as part of the main gensio. You must create a channel with gensio_alloc_channnel() and then open it get stderr output. The args for gensio_alloc_channel() may be an optional "readbuf=<num>" and sets the size of the input buffer.
The remote address string is either "stdio,<args>" for the main channel or "stderr,<args>" for the error channel. The args will be a set of quoted strings with a space between each string, one for each argument, with '"' around each argument, each '"' in the string converted to '\"' and each '\' in the string converted to '\\'.
The remote ID is the pid for the remote device, only for connecting gensios that start a program, not for any other type.
Allocated as a terminal gensio with gdata as a "const char * const args[]" for the gensio. gdata is not used for the accepter.
connecting = echo[(options)]
The echo gensio just echos back all the data written to it. Useful for testing.
In addition to readbuf, a connecting echo takes the following options:
The remote address string is "echo".
Allocated as a terminal gensio, gdata is not used.
connecting = file[(options)]
The file gensio opens an (optional) input file and (optional) output file for the gensio. If you need to read/write a file in gensio, you must use this. Semantics on files for stdio do not alway work correctly.
If an input file is specified, data will be read from the input file and delivered on the read interface of the gensio until end of file, when the GE_REMCLOSE error will be returned.
If an output file is specified, data will be written to the output file on writes. The output file is always ready to receive data.
In addition to readbuf, a connecting file takes the following options:
The remote address string is "file([infile=<filename][,][outfile=<filename>])".
Allocated as a terminal gensio, gdata is not used.
accepter = dummy
The dummy gensio accepter is useful for place holding, where you don't want a real working accepter but need to put something.
As you might guess, it doesn't do anything.
Allocated as a terminal gensio, gdata is not used.
ipmisol[(options)],<openipmi arguments>[,ipmisol option[,...]]
An ipmisol gensio creates an IPMI Serial Over LAN connection to an IPMI server. See the OpenIPMI documentation for information on the arguments.
This is a serial gensio, but the baud rate settings are fairly limited.
In addition to readbuf, the ipmisol gensio takes the following options:
It also takes the following ipmisol options:
Allocated as a terminal gensio with gdata as a "const char *".
accepter = telnet[(options)]
connecting = telnet[(options)]
A telnet gensio is a filter that sits on top of another gensio. It runs the standard telnet protocol andn support RFC2217.
In addition to readbuf, the telnet gensio takes the following options:
telnet passes remote id, remote address, and remote string to the child gensio.
accepter = ssl[(options)]
connecting = ssl[(options)]
An SSL gensio runs the SSL/TLS protocol on top of another gensio. That gensio must be reliable.
Use is pretty straightforward. The hardest part about using the SSL gensio is the certificates. A server SSL gensio must be given a certificate and a key. A client SSL gensio must be given a certificate authority. A client will user the certificate authority to verify that the server has the proper certificate and keys.
The gensio has options to have the server request the certificate from the client and validate it.
SSL gensios are reliable. They are also packet-oriented.
In addition to readbuf, the SSL gensio takes the following options:
Verification of the common name is not done. The application should do this, it can fetch the common name and other certificate data through a control interface.
You can use self-signed certificates in this interface. Just be aware of the security ramifications. This gensio is fairly flexible, but you must use it carefully to have a secure interface.
The SSL gensio will call the gensio event callback (client) or the gensio acceptor event callback (server) after the certificate is received but before it is validated with the GENSIO_EVENT_PRECERT_VERIFY or GENSIO_ACC_EVENT_PRECERT_VERIFY events. This allows the user to validate data from the certificate (like common name) with GENSIO_CONTROL_GET_PEER_CERT_NAME or set a certificate authority for the validation with GENSIO_CONTROL_CERT_AUTH.
ssl passes remote id, remote address, and remote string to the child gensio.
accepter = certauth[(options)]
connecting = certauth[(options)]
A certauth gensio runs an authentication protocol to on top of another gensio. That gensio must be reliable and encrypted.
This is like the reverse of SSL. The client has a key and certificate, the server has a certificate authority (CA). This also supports password authentication.
Once authentication occurs, this gensio acts as a passthrough. The readbuf option is not available in the gensio.
The certauth gensio takes the following options:
Verification of the common name is not done. The application should do this, it can fetch the common name and other certificate data through a control interface. It may also use the username fetched through the control interface.
You can use self-signed certificates in this interface. Just be aware of the security ramifications. This gensio is fairly flexible, but you must use it carefully to have secure authentication.
The certauth gensio has 4 major callbacks dealing with authentication of the user. They may or may not be called depending on the circumstances. The normal events come in if you have allocated a gensio and are doing an open. The _ACC_ events come in if it is coming in from an accept and there is no gensio reported yet. In the _ACC_ case, be careful, do not use the given gensio for anything but checking certificate and username parameters, and do not save it.
All these calls should return 0 if they want the authentication to immediately succeed, EKEYREJECTED if they reject the authentication, ENOTSUP if they want certauth to ignore that part of the authentication, or any other errno will result in the connetion being shut down.
The callbacks are:
certauth passes remote id, remote address, and remote string to the child gensio.
accepter = mux[(options)]
connecting = mux[(options)]
A mux gensio is a gensio filter that allows one or more channels to be created on top of a single gensio, multiplexing the connection. Each channel has full individual flow-control. The channels are message oriented as described above, and use can use a mux without additional channels to just do message demarcation. They also support out-of-bounds messages.
A mux gensio takes the following options:
When the open is complete on the mux gensio, it will work just like a transparent filter with message demarcation. In effect, you have one channel open.
To create a channel, call the gensio_alloc_channel() function on the mux gensio. This will return a new gensio that is a channel on the mux gensio. You can pass in arguments, which is an array of strings, currently readbuf, writebuf, and service are accepted. The service you set here will be set on the remote channel so the other end can fetch it. The new channel needs to be opened with gensio_open() before it can be used.
As you might imaging, the other end of a mux needs to know about the new channel. If one end (either end, doesn't matter) calls gensio_alloc_channel() and then opens the new channel, the other end will get a GENSIO_EVENT_NEW_CHANNEL event as described in the Streams and Channels section. You can call it using any mux channel. The first element in the auxdata is the service.
You can modify the service value after you allocate the channel but before you open it.
mux support out of band (oob) data, which is data that will be delivered normally. This comes in a normal read, but with "oob" in the auxdata. You can send oob data by adding "oob" to the write auxdata. You should normally use the "eom" flag so the end of the out of band messages ismarked.
This is so you can send special data outside of the normal processing of data.
As you might imaging, normal data events come through the gensio channel they are associated with. Close events will, too. If a mux gensio closes abnormally (like the underlying connection fails) you will get a read error on each channel.
New channel events (and other global events coming from lower gensios, like if you are running over ssl or telnet) come in through the original gensio normally. However, you can close that, another channel will be chosen to receive those event. In general, it's best to handle the global events on all channels and not assume.
To close a mux gensio, just close all channels associated with it. There is no global close mechanism (you would not believe the complexity that adds). Once you have closed a mux gensio, you can re-open it with gensio_open(). It will not recreate all the channels for you, you will have one channel, and the channel you use to call gensio_open() will become the first channel.
You cannot re-open individual channels.
To free a mux gensio, you must free all the channels on it.
connecting = pty[(options)][,<program>]
Create a pty and optionally run a program on the other end of it. With a program specified, this is sort of like stdio, but the program is running on a terminal. Only connection gensios are supported.
pty has some unusual handling to allow execution of login shells of users from root.
In Unix type systems, if the first character of the program is '-', it is removed from the execution of the command but left in argv[0]. This will cause a shell to act as a login shell.
On Unix type systems, in the forked process, the code will set the effective (and saved) uid and guid to the current real uid and guid if the effective and real uids are different. This way a user can set the real uid and gid to what they want the program to run under, but keep the effective uid and gid to the (probably root) values so they can be restored to those values after opening the pty. The group list is also set to the groups the real userid is in. Note that nothing is done if the effective and real userids are the same.
On Windows, the new process is set to the user in the impersonation token if it is set. In that case, the new process will be run in a new process group. If no impersonation token is set, the new process will run as the user of calling process in the same process group as the calling process.
The pty gensio supports the GENSIO_CONTROL_ENVIRONMENT control to allow the environment to be set for the new process. GENSIO_CONTROL_ARGS sets the arguments as an argv array. GENSIO_CONTROL_WIN_SIZE sets the terminal size in characters. GENSIO_CONTROL_START_DIR sets the directory the new process runs in.
If no program is specified, when the pty gensio is opened it will just create the pty but won't attach anything to it. Another program can open the pty. You can get the slave pty device by getting the local address string.
In addition to readbuf, the pty gensio takes the following options. These options are only allowed if the pty is unattached. ptys with programs run on them need to follow the standard semantics.
NOTE: There are significant security issues with passing a password this way. You must use proper password handling. module=<name> Windows only, user must be specified to use. Create the new user token with the given module. If not specified, "gensio" is used.
The remote address string the program and arguments run on the pty. The argiuments will be a set of quoted strings with a space between each string, one for each argument, with '"' around each argument, each '"' in the string converted to '\"' and each '\' in the string converted to '\\'.
The address is a pointer to an integer, the ptym file descriptor is returned. addrlen must be set to sizeof(int) when passed in.
This returns the device of the pty, generally /dev/pts/<n>. This is useful for pty gensios with no program, it allows you to get the value for the other end to connect to. Note that if you close and re-open a pty gensio, you may be a different local address string.
Allocated as a terminal gensio with gdata as a "const char * const args[]".
accepter = msgdelim[(options)]
connecting = msgdelim[(options)]
A message delimiter converts an unreliable stream (like a serial port) into an unreliable packet interface. This is primarily to allow a reliable packet interface like relpkt to run on top of a serial port. It does not support streaming of data, so it's not very useful by itself.
Messages are delimited with a start of message and end of message and have a CRC16 on the end of them.
This is primarily for use on serial ports and other streams that can munge up the data. Use the mux gensio for TCP and such.
The default buffer size is 128 bytes, which is ok for a reasonably fast serial port interface.
In addition to readbuf, the msgdelim gensio takes the following options:
accepter = relpkt[(options)]
connecting = relpkt[(options)]
Converts an unreliable packet interface (currently only UDP and msgdelim) into a reliable packet interface. This lets you have a reliable connection over a serial port (with msgdelim) or UDP.
Note that UDP is not recommended, it doesn't handle flow control and such in a friendly manner.
relpkt is unusual in dealing with clients and servers. The protocol is symmetric, for the most part, you can start two clients and they will connect to each other, if they are started relatively close in time to avoid one timing out. A relpkt server will simply wait forever for an incoming connection on an open.
relpkt does not support readbuf. It supports the following:
accepter = ratelimit[(options)]
connecting = ratelimit[(options)]
Limit the transmit rate of data going through this filter gensio. A number of bytes is let through, then transmit is delayed until the given delay has passed. Receive is not currently rate limited, but that may be added in the future.
accepter = trace[(options)]
connecting = trace[(options)]
A transparent gensio that allows the data passing through it to be sent to a file. Useful for debugging. It can also block data in either direction.
Note that the trace gensio only prints data that is accepted by the other end. So, for instance, if the trace gensio receives 100 bytes of read data, it will deliver it immediately to the gensio above it. If that only accepts 40 bytes, trace will only print 40 bytes and will only accept 40 bytes from the gensio below it. Same for sent data.
trace does not support readbuf. It supports the following options:
accepter = perf[(options)]
connecting = perf[(options)]
A gensio for measuring throughput for a connection. This allows the performance of a connection to be measured. It does not pass any data through. Instead, it writes raw data to the lower layer (if write_len is set) and reads data from the lower layer, counting the bytes and measuring the time.
To the upper layer, perf prints out statistics about the data transfer. It prints out the number of bytes written and read each second, and at the end it prints a total.
If write_len and/or expect_len is non-zero, then the filter will return GE_REMCLOSE when it runs out of write data and has received all expected data. If both are zero, the connection will not be closed by the gensio.
perf does not support readbuf. It supports the following options:
accepter = conacc[(options)],<gensio string>
conacc is a gensio accepter that takes a gensio as a child. When the accepter is started, it opens the child gensio. When the child gensio finishes the open, it reports a new child for the accepter. The reported gensio can be used normally.
When the gensio is closed, when the close completes the accepter will attempt to re-open the gensio immediately and will disable itself if the connect fails, unless retry-time is set. This means that if the gensio has some sort of random address (like a pty or a tcp address with the port set to zero) you can get a different address for the re-opened gensio. So you must refetch the local address or local port in this case.
The readbuf option is not accepted.
Allocated as a terminal gensio with gdata as a "const char *". That is the specification of the gensio below it.
connecting = mdns[(options)][,<name>]
This gensio uses mDNS to find a service and then attempts to connect to it. This can be convenient, it finds the connection type, address, and port for you, automatically adds telnet and rfc2217 if it's available. The mDNS name can be set as an option or as the string after the comma show above.
The name, type, host, and domain strings can be wildcarded in various ways. See "STRING VALUES FOR WATCHES" in gensio_mdns(3) for details.
The readbuf option is accepted, but if it is not specified the default value for readbuf will be taken for the sub-gensio is taken. In addition to readbuf, the mdns gensio takes the following options:
Allocated as a terminal gensio with gdata as a "const char *". That is the specification of the mdns as a string.
accepter = kiss[(options)]
connecting = kiss[(options)]
A gensio that implements the KISS amateur radio protocol for transferring data between a TNC and AX25. See http://www.ax25.net/kiss.aspx for details. It contains a number of tunable parameters, you probably shouldn't mess with most of them.
This is a normal packet-oriented interface.
KISS supports multiple TNCs underneath it. To write to a specific TNC, you must use the auxdata string "tnc:<n>" when writing. If you don't set that, the TNC is assumed to be zero. On reading, "tnc:<n>" will always be in one of the auxdata fields. You shouldn't set a TNC larger than the configured number of TNCs.
There is a 8-bit protocol field in the AX25 frame call the PID. This is not passed in the data, it is also in the auxdata with the format "pid:<n>". And to set the pid, pass it in the auxdata on write. If you don't set the pid, it defaults to 240 (0xf0).
kiss supports the following options:
accepter = ax25[(options)]
connecting = ax25[(options)]
A gensio that implements the AX25 amateur radio protocol for transferring data. You would generally run this on top of a KISS gensio. See http://www.ax25.net for details.
This is a normal packet-oriented interface.
It is also a channel-based implementation. Each AX25 connection to a remote system is implemented as a channel. You can also have an unconnected channel if you want to just receive UI frames.
There is no server/client setting on an ax25 gensio. The protocol is symmetric, so it's not necessary. An ax25 accepter will take the first connection that comes in and deliver it as a gensio, but after that there's really no difference.
When running as an accepter, incoming connections will be delivered as new connections, not new channels. This allows you to use an ax25 gensio more naturally underneath a server without having to know about ax25 in the code. For instance, to create a simple AX25 server that reflects all incoming data, you could create a reflector:
then you could create the server:
then you could connect to it:
So to use this, allocate an ax25 gensio. If you want to make a connection, you must set the addr to a destination address. By default the laddr will be set from the addr if the addr is supplied. If you just want UI frames, you don't need to set an address (more on that later). Then you open the gensio. An ax25 gensio without an address will just open immediately and start receiving UI packets as directed. With an addr set, it will attempt to make a connection. Once that is up (or fails), the open callback is called.
A gensio AX25 address string is in the form:
tncport is a number from 0-15 specifying which TNC to use. dest and src and subaddresses specifying the destination and source addresses. Note that the source address better match an laddr, or the connection won't work. extra fields are also subaddresses. These are used for routing (a function that is deprecated and no longer use) and by APRS for it's own purposes, see the APRS spec for details. The c, r, and h values are bits in the address that you generally don't care about, except for the h field for APRS.
Subaddresses are in the form
where callsign is a set of alphanumeric digits (a-zA-Z0-9) and n is a number 0-15. Lower case digits are converted to upper case in the address and not converted back on receipt.
To receive UI frames, you must enable the GENSIO_CONTROL_ENABLE_OOB control on the gensio. If you set the value to 1, you will receive UI frames where the destination matches one of your laddrs. If you set it to 2, you will receive all UI frames (promiscuous mode). 0 disables receipt of UI frames.
UI frames are reported with the "oob" in the auxdata, like out of band data. Note that you must completely handle all the UI frame in the call. If you don't consume all the data, it will not be called again.
You can have a channel that receives both connected data and UI frames, but you would generally have a separate channel with no addr set for receiving UI frames. If you don't set the laddr field, that channel can only be used for promiscuous mode.
On received packets, the auxdata will contain a field starting with "addr:" and the rest is the gensio AX25 address in the packet. When sending a UI frame, you must set "oob" and "addr:" fields in the auxdata, with a valid address in the "addr:" field.
Option values for channels are all inherited from the options used to create the first channel, except for addr (which must be supplied for each channel if you care) and laddr (which is only used on the first channel. ax25 supports the following options:
Setting the value to 2 will enable a non-standard operation that will add parameter negotiation to the connection startup. Setting the readbuf, writebuf, readwindow, and writewindow without extended=2 really isn't very useful as without the negotiation it will be forced to fall back to the defaults. If extended=2 fails, it will fall back to extended=1.
If you do not set this, but addr is set, the laddr will be set from the source address of addr.
accepter = xlt[(options)]
connecting = xlt[(options)]
A gensio that translates characters. This is primarily for translating line feeds and carraige returns, but may eventually be extended to do string substitutions and such.
The readbuf option is not available in this gensio.
connecting = keepopen[(options)]
A filter gensio that makes it appear to the user that the gensios below it are always open. If the gensio below it goes away, this will attempt to re-open it periodically.
The readbuf option is not available in this gensio.
connecting = script[(options)] accepting = script[(options)]
A filter gensio that allows an external program to interact with the child gensio before reporting that it is open. The external program runs with its stdio connected to the child of this gensio, so writes from the external program get written to the child and reads from the child get written to the external program's stdin. If the program returns without error, the open for this gensio succeeds. If the program returns an error, GE_LOCALCLOSED is reported as an open error.
The readbuf option is not available in this gensio.
connecting = sound[(options)],<device>
The sound gensio provides access to sound devices on the platform. It's an unusual gensio in many respects. It's not terribly useful for normal streaming operation. It is a streaming operation, though, so it still fits. But to avoid underruns and overruns, the stream has to be continuously supplied or consumed.
The stream consists of bytes of binary data in the host's byte order. If the low-level sound interface uses a different byte order, it is converted to the host's order in the gensio. The gensio can also convert data types. If you ask for a float format and the low level driver can only do integers, the gensio will convert for you. Each data item (byte, int16, etc) is called a sample. So the driver has a user format (what the user sees) and a PCM format (what the driver uses).
A stream has a number of channels, at least one. Stereo, for instance, has two channels. A frame consists of samples for all channels. Frames are always interleaved; the individual channels appear as successive data items in the stream. So in stereo, sample1 is channel 1 in the first frame, sample2 is channel 2 in the first frame, sample 3 is channel 1 in the second frame, etc.
A buffer is a set of frames. A buffers's size is specified in frames, not bytes. Reads are always provided in buffer size chunks. If you do not comsume all the data in a buffer, it will give you the rest of the buffer the next read, until the buffer is consumed. Writes can be written in any size, but it's generally most efficient to always write buffer sized chunks.
A number of buffers can also be specified. You generally want a large enough number that data can be smoothly written into the device without delay if the program lags a bit, but too large and you end up with lag if you want to change the data being written..
A sound gensio may have input, output, or both. You specify which you want by setting the number of channels to a non-zero value. Most of the parameters can be prefixed with "in" or "out" to specify that the parameter only applies to the input and output device. If you don't have that prefix, it will apply to both the input and output. Note that the input and output streams are completely independent; they can have different type, format, rates, etc.
Different sound interfaces are available on platforms. For Linux, alsa and file interfaces are available, and possibly portaudio, depending on how it is compiled. On Windows, win and file interfaces are available and possibly portaudio. On Macos portaudio and file are available.
The device is specified on the command line. The name is system-dependent. For alsa, the name must match exactly. You can use "aplay -L" or "arecord -L" to list input and output devices available. Or you can use the "gsound -L" program.
For Windows and portaudio, the name just has to match part of the device's name. Windows names always start with a number. So, for instance, if the output of "gsound -L" on your windows platform is:
The numbers at the beginning are subject to change, so it's better to use part of the name like "Realtek", which will match both input and output. It will not select an output-only device for input or an input-only device for output.
On MacOS with portaudio you might see:
For file type, the device name is a filename; data is streamed to/from the file in the PCM format. If the filename is "-", then stdio is used instead of opening a file.
The readbuf option is not available in this gensio, obviously.
The data will be a string with each interfaces separated by a newline. There will be an interface name, a tab, and the interface specification.
connecting = afskmcm[(options)]
A filter gensio that sits on top of the sound gensio and implements an Audio Frequency Shift Keying modem, like is used on AX.25 amateur radio. It must sit on top of a sound gensio. Or, more accurately a gensio that implements the GENSIO_CONTROL_IN_BUFSIZE, GENSIO_CONTROL_OUT_BUFSIZE, GENSIO_CONTROL_IN_FORMAT, and GENSIO_CONTROL_OUT_FORMAT controls.
Note that the sound gensio must supply a float user format.
By default, afskmcm will not do anything specific to turn the transmitter on and off. In this case the keying must be VOX, like a SignaLink.
If you need some other way to key the transmitter, afskmcm provide a key option. This create a gensio that is used to turn the transmitter on and off. Different types of keying options exist, specified by keytype.
If keytype is rw, which is the default, then it will open the key gensio and send the keyon string and the keyoff string for this purpose.
If keytype is one of rts, rtsinv, dts, dtsinv then it will open the key gensio as a serial device and set the RTS/DTR lines appropriately. The "inv" options mean that setting the line "on" will turn off the transmitter, setting it "off" will enable the transmitter.
You could do
to set up an RTS-base serial control on a port.
There is a cm108 soundcard GPIO gensio available. See that gensio for details, but you would generally do something like:
and this gensio will fetch the sound card identifier from the sound device and use the cm108gpio gensio to key the device. If you have some special setup where the default won't work, you can use
See the cm108gpio gensio docs for details.
If you need something more sophisicated, you could create your own program to do the keying and add:
to your afskmdm option and it would run the mykeyprog program when opened. When it needed to transmit, it would write a "1" (no newline or anything sent) to the stdin of the program. When the transmission was complete, it would write a "0". Not numbers, these are "0" and "1" characters.
If you program sends output to stderr, you probably want to add stderr-to-stdout to the stdin so the stderr buffers doesn't fill up and block the program. Then stderr data will be ignored.
You can use rigctld with this. I have the following rigctld setup on my system:
And I use the following in my configuration:
You have to be careful of the quoting here, as the key specification must be in quotes because it will have commas. Notice the use of "\n" in the strings for a new line. Normal C "\" escape sequences are handled. Also, since the defaults for keyon and keyoff are what's given above, you don't have to specify those here.
Anything read in from the key gensio is ignored.
This way, if wmsg is 2^n, a message can be correctly decoded if up to n bits are wrong. So it's sort of an error correction code without a code. Defaults to 32.
connecting = cm108gpio[(options)],<soundcard>
This allows a GPIO on a cm108 soundcard to be controlled. Some radio sound card devices use one of these bits to key the transmitter. Any write with a '1' in it will enable the GPIO, any write with a '0' in it will disable the GPIO. There are 8 available GPIOs, ranging 1-8, most devices use 3.
For Linux, <soundcard> is the soundcard number or device name given to open the sound device, generally what's right after the ":", like "plughw,1,0" would be "1". If it was "plughw,Device,0", it would be "Device".
For Windows, <soundcard> is the same thing you put for the soundcard, like "USB PnP Sound", or whatever "gsound -L" returns for your device.
The readbuf option is not available in this gensio.
Unlike normal file descriptors, when you fork with a gensio, you now have two unassociated copies of the gensios. So if you do operations on one, it might mess up the other. Even a close might cause issues, if you close an SSL connection, it sends data to the other end to close the connection, even if the other fork doesn't want that.
To avoid issues with this, you should generally first make sure that no thread is in a wait, service call, or any type of thing that would examine file descriptors or timers and act on them. This is very important, and you must do it before you fork.
Then after you fork, you should call:
on all the gensios that fork is not using, then free the gensios. Don't use close, use free. Then you should call:
on every gensio accepter that fork is not using, then free them. If a connection is in progress and has not been reported to the user, it will be disabled then closed.
You cannot share a gensio between two different processes. If a gensio is used in one fork, it must be disabled and closed in the other fork.
Another issue with forking on Linux is epoll. An epoll fd is not duplicated on a fork, both forks get the same epoll fd. If you close the epoll fd in one for, it will close it for the other. To avoid this issue, the os handler has a handle_fork() function that you must call after a fork in the new fork (not the old one). It will handle any cleanup required after the fork. Other systems may require other cleanups after a fork, so you should always call this after a fork.
gensiot(1), sctp(7), udp(7), tcp(7), unix(7)
None.
Corey Minyard <minyard@acm.org>
01/02/19 | Specifying a gensio |