gensio(5)

๐Ÿ’– gensio(5) โ€” How to specify a gensio ๐Ÿ’–

okay so welcome to the File Formats Manual entry for gensio, bestie. This is the whole entire vibe of how you write a gensio string. Buckle up, it's long, but it's so worth it. ๐ŸŽ€


โœจ NAME โœจ

gensio โ€” How to specify a gensio ๐Ÿ’…


โœจ SYNOPSIS โœจ

<type>[(options)][,gensio|terminaloptions]

โœจ DESCRIPTION โœจ

gensio stands for GENeral Stream Input Output. No cap. It gives you an abstraction for all kinds of stream I/O, and it even makes some packet I/O look like stream I/O (hi, UDP ๐Ÿ‘‹). And like, the whole point? It makes creating encrypted and authenticated connections genuinely easy. Obsessed. ๐Ÿ’–

The gensio library specifies a connection (a gensio) using a string format. That format is: a gensio type, then optional options in parentheses. If it's a terminal gensio (one that sits at the bottom of the stack), it can take more terminaloptions separated by a comma. If it's a filter gensio (not at the bottom of the stack), then another gensio has to be specified after the comma. Like, for instance:

serialdev,/dev/ttyS0

That one specifies a serial port gensio. Or:

tcp(readbuf=100),localhost,4000

That's a TCP connection with a 100 byte read buffer, connecting to port 4000 on localhost. Or:

telnet,tcp,localhost,4000

Which is a telnet filter sitting on top of a TCP connection. Stacked. Iconic. ๐Ÿ‘‘

Heads up bestie: regular options always go inside the parentheses. Only terminaloptions go after a comma on a terminal gensio. Don't mix them up, it's giving chaos. ๐Ÿ˜ญ

When you're specifying a gensio, you can add quotes (single or double) to remove the special meaning of some characters, so you can like, have commas inside options and stuff. They can also be escaped with a "\". For instance, if you're specifying a laddr in an sctp address, you need to do this. Check it:

sctp(laddr=localhost,4001),localhost,3023

This one will fail because the option splitting code will split at the commas. ๐Ÿ’€ Instead, do:

sctp(laddr="localhost,4001"),localhost,3023

...and it will work. Slay. โœจ

Accepter gensios โ€” the ones used for accepting connections, as opposed to connecting gensios โ€” are specified in exactly the same way. Each individual type can vary, and some gensios are only connecting gensios. The options can also vary between the accepter and connecting gensios of the same type. For instance, an accepting TCP gensio doesn't have to have a hostname, but a connecting one does. The audacity, honestly.

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. Just so you know. ๐Ÿซถ

The gensio library has a concept of client and server. Accepted gensios are generally considered servers. Connecting gensios are generally considered clients. Some gensios may allow this to be overridden.


๐ŸŒˆ reliable or not reliable ๐ŸŒˆ

A gensio may be reliable or not. A reliable gensio will reliably deliver all data in sequence, like TCP. A gensio that is not reliable may drop data or deliver data out of sequence, like UDP. You can query this with gensio_is_reliable().


๐Ÿ“ฆ packet or not packet ๐Ÿ“ฆ

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 get combined with another write into more than 15 bytes. ๐Ÿ˜ตโ€๐Ÿ’ซ

Packet I/O requires careful use to make it work correctly. Bestie, pay attention.

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. Fr fr.


โœ‰๏ธ message oriented โœ‰๏ธ

A gensio may be message oriented. This implementation is stolen from SCTP (even though it's not really supported on Linux at the moment, rip ๐Ÿ™ˆ). Basically it 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 is assumed to be the end of a message.

If the write does not accept all the data, the "eom" is ignored, and 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 way that it just comes back around ๐Ÿ’…


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. Surprise! ๐Ÿฆ‹

Unless otherwise noted, every gensio takes a:

  • readbuf=<n> โ€” option to specify the read buffer size. ๐Ÿ’–

โœจ DEFAULTS โœจ

Every option to a gensio (including the serialdev and ipmisol options), unless otherwise 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 the "mode" default, for instance, could really screw things up. Not me warning you dramatically, I'm warning you for real. ๐Ÿ˜ญ

For string defaults, setting the default value to NULL causes the gensio to use its backup default. ๐Ÿซถ


๐ŸŒŠ Streams and Channels ๐ŸŒŠ

Some gensios support the concept of a stream and/or a channel. Two different things, bestie, don't get it twisted.

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. The way it refuses to let go ๐Ÿ’€

See the individual gensio description for more information on streams and channels. ๐Ÿ‘‘

๐Ÿ” PUBLIC KEY CRYPTOGRAPHY ๐Ÿ”

okay bestie so the ssl and certauth gensios are out here doing public key cryptography and likeโ€ฆ what even is that? This section is a lil vibe check on how it all works. If you already know this stuff you can totally skip it, no offense taken ๐Ÿ’…

Public key crypto is what authenticates and encrypts stuff at the beginning of a session. It's a pretty expensive operation and it's generally not used to encrypt the data after that beginning part. It's giving "opening act, not the whole concert" โœจ

So in public key cryptography you've got three main characters:

  • The private key ๐Ÿ”‘
  • The certificate (or public key) ๐Ÿ“œ
  • The certificate authority (CA) ๐Ÿ‘‘

The private key isโ€ฆ private. Like, genuinely. You don't even send your private key to a certificate authority when they sign your certificate. Keep it private, non-readable by everyone else, protect it with your life. If it becomes known, your certificate becomes useless to you and literally anyone can impersonate you ๐Ÿ˜ญ

The certificate is the public key, and it's mathematically associated with a single private key. It's public โ€” anyone can use it to test that you have the private key by asking you to sign some data. The data inside the certificate (like the Common Name) is part of the certificate. If that data gets modified, certificate validation will fail. periodt.

The CA is generally a trusted third party that validates you and signs your certificate (usually for a fee ๐Ÿ’ธ). CAs issue their own public certificates that are well-known and generally already sitting on your system. Those CA certificates are what prove your certificate is valid.

โœ๏ธ SIGNING

So we mentioned signing already but never actually described it, rude of us, let's fix that. Basically: you use your private key to generate a value over some given data that proves you have the private key. The certificate is used to mathematically verify the signature.

Two things normally go down with this:

In a public key exchange, the entity that wants to be authorized sends a certificate. The authorizing entity looks through its CA for a certificate that has signed the sent certificate. If it finds one that can validate the sent certificate, then the sent certificate is valid โœ…

After that, the authorizing entity sends some generally random data over to the other entity. That other entity takes the data โ€” maybe plus some other important data it wants to make sure wasn't modified in transit โ€” and signs it. It sends the signature back. The authorizing entity can then use the data and the signature to validate that the sending entity really does have the private key associated with the certificate.

This is basically how https works. Note that it's the web client that authenticates the web server, not the other way around. This proves you're connecting to the entity you say you're connecting to. Authenticating a web client to the web server is generally done with a whole different mechanism (though SSL/TLS used by the ssl gensio does have a way to do it, it's just not generally used for that purpose).

In the web server scenario, the data in the certificate โ€” specifically the Common Name and Subject Alternate Name โ€” must match the name of the web page you're connecting to. The ssl and certauth gensios do not do this authentication. That's up to the user if it's necessary. you're on your own bestie ๐Ÿซถ

๐Ÿ”’ ENCRYPTING

The certificate can be used to encrypt data that can only be decrypted with the private key. When you're 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 it would require certificates and private keys on both ends. exhausting. no thanks ๐Ÿ’€

๐Ÿชž SELF-SIGNED CERTIFICATES

It's 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 to use the system you're logging in to.

ssh also stores places it has connected to in .ssh/known_hosts, which is the CA in the opposite direction. That's why it asks you if you've never connected to a system before โ€” it doesn't have the key in its CA. And that's why, if you connect to a system you've connected to before and the certificates don't match or fail validation, it complains about it.

So if you're using self-signed certificates, 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, which is exactly why third-party trusted CAs exist. main character energy, honestly ๐Ÿ‘‘

๐Ÿค TRUST AND CRYPTOGRAPHY

We keep saying "trust" up in here. 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 it to you, and that it wasn't 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. ๐Ÿ“š


๐ŸŒ IPv6, IPv4, and host names ๐ŸŒ

heads up bestie: a single hostname may result in more than one address. For instance, it may have both an IPv4 and IPv6 address. These get treated just like multiple hostnames. For instance, if you use localhost as a host name, you generally get the IPv4 and IPv6 version:

$ gensiot -a -p tcp,localhost,1234
Address 0(0): ipv6,::1,1234
Address 1(0): ipv4,127.0.0.1,1234

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:

$ gensiot -a -p tcp,ipv6n4,localhost,1234
Address 0(0): ipv6,::ffff:127.0.0.1,1234

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, in which case 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. So you may need to be fairly specific with addresses. ๐Ÿ‘€

You can specify a subnet mask with an address, as:

192.168.33.0/24

This will mostly be ignored except for calls to gensio_addr_addr_present() which will compare the given address using the subnet mask specified. In the given case it will accept any address from 192.168.33.0 to 192.168.33.255, per normal subnet mask usage. Nothing currently in the gensio library uses this, but ser2net uses this with its remaddr option to allow subnets to be allowed as remote addresses.

In general IPv6 addresses are preferred if both are available. โœจ


โณ gtime โณ

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. ๐Ÿ•ฐ๏ธ


๐Ÿšฐ Drain Timeout ๐Ÿšฐ

Many gensios store data queued to be sent. On a close, by default they will wait until the output has completely been sent before finishing the close.

This is normally what you want, but it may not be desirable in some situations. So these gensios will implement a drain_timeout option, and there is a corresponding control for setting it after creation.

The drain_timeout is the amount of time to wait for data to drain on a close. This sets the time (in milliseconds) that the gensio will wait for the data to drain before shutting down with pending data. If you set this to a negative value, it will wait forever, which is the default. Though not really forever โ€” some lower layer is going to time out and shut the connection down before you reach this. ๐Ÿ˜…


๐Ÿงฎ nbits in auxdata ๐Ÿงฎ

Some gensios have the ability to transfer data in numbers of bits that are not a multiple of 8. Ones that do bit-level processing, like hdlc and convcode, do this.

These gensios will receive an "nbits" parameter in auxdata on an interface they get data from, and they will supply "nbits" to data they supply to the next layer. This is in the form "nbits=n" where n is a number.

"nbits" must always be between 1 and 7 smaller than the supplied buffer length (in bytes) times 8. So if you have a 10 byte buffer length, nbits can range from 79 and 73. Or, in other words, the buffer length is always rounded up to the next byte from the number of bits, ((nbits + 7) / 8).

If an interface takes "nbits", and it is not supplied, it assumes that nbits is (<buffer length> * 8).

This does mean that if an interface receives data, it must always take a multiple of 8 bits if it does not consume the entire buffer. There is no way to pass the number of bits consumed back โ€” only the number of bytes. ๐Ÿ’€


๐ŸŒ TCP ๐ŸŒ

tcp[(<options>)][,<hostname>],<port>[[,<hostname>],<port>[...]]
hostname = [ipv4|ipv6|ipv6n4,]<name>

okay so bestie, a TCP connecting gensio must have the hostname specified. No hostname? No connection. ๐Ÿ’€ You can list multiple hostname/port pairs, and for a connecting TCP gensio, each one gets tried in sequence until a connection is established. For acceptor gensios, every single hostname/port pair you specify will be listened to. periodt. ๐Ÿ‘‘


๐ŸŒ€ Dynamic Ports ๐ŸŒ€

For accepters, if you set the port to 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. iykyk. โœจ


๐Ÿ“จ Out Of Band Data ๐Ÿ“จ

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.

Not me being dramatic, that's literally what the manpage says. ๐Ÿ™ˆ

Note that "oobtcp" is also delivered and accepted in auxdata, so you can tell TCP oob data from other oob data. ๐Ÿ’…


๐ŸŽ€ Options ๐ŸŽ€

In addition to readbuf, the tcp gensio takes the following options:

  • nodelay[=true|false] โ€” Sets nodelay on the socket.
  • laddr=<addr> โ€” An address specification to bind to on the local socket to set the local address.
  • reuseaddr[=true|false] โ€” Set SO_REUSEADDR on the socket. For accepting sockets, this lets the socket be reused as soon as it is closed. If false, there will be a delay before it can be used.

For connecting sockets, it lets the laddr set be used for multiple connections as long as they aren't to the same destination. There are some restrictions here, see "SO_REUSEADDR" in "man 7 socket".

Defaults to true. This may not be the best default, there are some possible races from reusing sockets too fast.

  • tcpd=on|print|off โ€” Accepter only, sets tcpd handling on the socket. If "on", tcpd is enforced and the connection is just closed on a tcpd denial. "print" is like on, except it writes "Access Denied" on the socket before closing it. "off" disabled tcpd handling on the socket. Defaults to on.
Not available if tcpd is disabled at compile time.
  • tcpdname=<name> โ€” Accepter only, sets the name to use for tcpd access control. This defaults to "gensio", and the default can be overridden with gensio_set_progname(). This option allows you to override it on a per-gensio accepter basis.
Not available if tcpd is disabled at compile time.

๐Ÿ“ Remote Address String ๐Ÿ“

The remote address will be in the format "[ipv4|ipv6],<addr>,<port>" where the address is in numeric format, IPv4, or IPv6.


๐Ÿ“ Remote Address ๐Ÿ“

TCP returns a standard struct sockaddr for GENSIO_CONTROL_RADDR_BIN control.


๐Ÿš€ Direct Allocation ๐Ÿš€

Allocated as a terminal gensio with gdata as a "const struct gensio_addr *".


๐Ÿ“ฆ UDP ๐Ÿ“ฆ

udp[(<options>)][,<hostname>],<port>[[,<hostname>],<port>[...]]
hostname = [ipv4|ipv6|ipv6n4,]<name>
unixdgram[(<options>)][,<socket_path>]

A UDP gensio creates a UDP socket, but it makes it look like an unreliable stream of data. The specification is the same as a TCP socket, except that a UDP socket is created, obviously. ๐Ÿ’€

A unixdgram socket is a Unix datagram socket. It has similar semantics to UDP as described below. The only difference is that if you transmit without assigning an laddr, you will not have an address. The accepting gensio will not get an address, and if it tries to transmit back to you it will get an error. no cap. ๐Ÿซถ

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.

A UDP 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. lowkey wild. ๐Ÿ˜ญ

Note that UDP accepter gensios are not really required for using UDP, they 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.


โœจ Options โœจ

In addition to readbuf, the udp gensio takes the following options:

  • laddr=<addr> โ€” An address specification to bind to on the local socket to set the local address.
  • nocon[=true|false] โ€” Don't be connection oriented, just receive all packets and deliver them without establishing connections. Only valid for the client gensio. The receive address is passed into the auxdata prefixed by "addr:", this is the address formatted by gensio_addr_to_str().
  • mloop[=true|false] โ€” UDP only. If false, multicast packets transmitted will not be received on the local host. If true, they will.
  • mttl=[1-255] โ€” UDP only. Set the multicast time-to-live value. The default is 1, meaning multicast stays in the local network. Increasing this value increases the number of hops over multicast routers a send packet will traverse.
  • mcast=<addr> โ€” UDP only. Add an address to receive multicast packets on. There is no port number, this is just addresses. You can specify multiple addresses in a single multicast option and/or the multicast option can be used multiple times to add multiple multicast addresses.
  • reuseaddr[=true|false] โ€” UDP only. Set SO_REUSEADDR on the socket, good for connecting and accepting gensios. Defaults to false.
  • delsock[=true|false] โ€” unixdomain only. If the socket path already exists, delete it before opening the socket.
  • umode=[0-7|[rwx]*] โ€” unixdomain only. Set the user file mode for the unix socket file. This is the usual read(4)/write(2)/execute(2) bitmask per chmod, but only for the user portion. If a mode is specified, all other modes default to "6" (rw) unless they are specified, and the final mode is modified by the umask per standard *nix semantics. If no mode is specified, it is set to the default and not modified. Note that the perm option below is probably a better way to set this.
  • gmode=[0-7|[rwx]*] โ€” unixdomain only. Set the group file mode for the unix socket file, see umode for details.
  • omode=[0-7|[rwx]*] โ€” unixdomain only. Set the other file mode for the unix socket file, see umode for details.
  • perm=[0-7][0-7][0-7] โ€” unixdomain only. Set the full mode for the unix socket file per standard *nix semantics, modified by umask as the above mode operations are.
  • owner=<name> โ€” unixdomain only. Set the owner of the unix socket file to the given user.
  • group=<name> โ€” unixdomain only. Set the group of the unix socket file to the given group.

๐Ÿ“ Remote Address String ๐Ÿ“

The remote address will be in the format "[ipv4|ipv6],<addr>,<port>" where the address is in numeric format, IPv4, or IPv6. Or it will be "unix,<socket path>" for unixdomain.


๐Ÿ“ Remote Address ๐Ÿ“

UDP returns a standard struct sockaddr for GENSIO_CONTROL_RADDR_BIN control.


๐ŸŒˆ UDP Multicast ๐ŸŒˆ

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:

"udp(mcast='ff02::1',laddr='ipv6,3000',nocon),ff02::1,3000"

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.


๐Ÿš€ Direct Allocation ๐Ÿš€

Allocated as a terminal gensio with gdata as a "const struct gensio_addr *".

SCTP ๐Ÿงต

sctp[(<options>)][,<hostname>],<port>[[,<hostname>],<port>[...]]
hostname = [ipv4|ipv6|ipv6n4,]<name>

okay bestie so you set up an SCTP gensio exactly like a UDP or TCP one, syntax-wise, but the semantics are a whole different vibe, no cap. For a connecting gensio, it's gonna try to build a multi-homed connect using all the hostnames and ports you handed it. Every single port has to be the same, periodt.

For an accepter gensio, it makes one single socket with all those addresses as possible destinations. And yeah โ€” again, all the ports must be the same. ๐Ÿ’…

SCTP gensios are reliable โœจ. They are not packet oriented at the moment, tho. Why? Because there's currently no support for SCTP_EXPLICIT_EOR in the Linux implementation of SCTP, and without that it would be hard to make it packet oriented. That's the whole reason, fr fr.

Now โ€” careful, bestie ๐Ÿšจ โ€” when you're 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 it will disconnect quickly because it cannot communicate on the IPv4 side. For instance, this accepter:

tools/gensiot -a "sctp,ipv6,::,1234"

and this connector:

tools/gensiot "sctp,::1,1234"

will fail this way because the connector will support IPv4 add but the accepter will not. ๐Ÿ˜ญ


Nagle and SCTP ๐ŸŒ

SCTP implements the Nagle algorithm by default, and that 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. Like, genuinely aware. ๐Ÿซถ

Options ๐ŸŽ€

In addition to readbuf, the sctp gensio takes the following options:

  • instreams=<n>
  • ostreams=<n> โ€” these specify the number of incoming and outgoing streams for the connection. The default is one. The stream is given in the auxdata for read and write in the format "stream=<n>". ๐ŸŒŠ
  • sack_freq=<n>
  • sack_delay=<n> โ€” these two specify the handling of selective acknowledgements (sacks). sack_freq sets the number of outstanding packets that must be received before sending a sack. The default is 1, meaning it doesn't wait at all. sack_delay sets the maximum time before a sack is sent if outstanding packets are present, in milliseconds. The default is 10, but this is disabled if sack_freq is set to 1. Setting either of these to 0 enables the system defaults. ๐Ÿ•ฐ๏ธ
  • nodelay[=true|false] โ€” sets nodelay on the socket.
  • laddr=<addr> โ€” an address specification to bind to on the local socket to set the local address.
  • reuseaddr[=true|false] โ€” set SO_REUSEADDR on the socket.
    • For accepting sockets, this lets the socket be reused as soon as it is closed. If false, there will be a delay before it can be used.
    • For connecting sockets, it lets the laddr set be used for multiple connections as long as they aren't to the same destination. There are some restrictions here, see "SO_REUSEADDR" in man 7 socket.
    • Defaults to true. This may not be the best default โ€” there are some possible races from reusing sockets too fast. ๐Ÿ˜ฌ

Port 0 is supported just like TCP for accepters, see Dynamic Ports in the TCP section above for details.

SCTP 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. ๐Ÿ“ฌ

See documentation on SCTP for more details.


Remote Address String ๐ŸŒˆ

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.

Remote Address ๐ŸŒˆ

SCTP returns a packed struct sockaddr for GENSIO_CONTROL_RADDR_BIN control, per SCTP semantics.

Direct Allocation ๐ŸŒˆ

Allocated as a terminal gensio with gdata as a "const struct gensio_addr *"


UNIX ๐Ÿง

unix[(<options>)],<socket_path>
unixseq[(<options>)],<socket_path>

Create a unix domain stream socket as an accepter, or connect to a unix domain socket as a connecter. unixseq is like unix, but it creates SOCK_SEQPACKET sockets instead of SOCK_STREAM sockets. It works the same as unix, except that it is packet-oriented. ๐ŸŽ

And okay, this is gonna sound strange but hear me out โ€” the unix gensio is available on Windows, and works about the same as it does on Unix systems. unixseq and unixdgram are not currently available on Windows, though. However, you cannot set the permissions like you can on Windows. It's possible to create something that might work, but it's complicated. For permissions, you can create a directory with the permissions you want and then create the socket in that directory. The socket will inherit the permissions of the directory by default. ๐Ÿ โœจ

A file will be created with the given socket path, and 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 the 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. ๐Ÿ’€


Options ๐ŸŽ€

In addition to readbuf, the unix gensio takes the following options:

  • delsock[=true|false] โ€” if the socket path already exists, delete it before opening the socket. ๐Ÿ—‘๏ธ
  • umode=[0-7|[rwx]*] โ€” set the user file mode for the unix socket file. This is the usual read(4)/write(2)/execute(1) bitmask per chmod, but only for the user portion. If a mode is specified, all other modes default to "6" (rw) unless they are specified, and the final mode is modified by the umask per standard *nix semantics. If no mode is specified, it is set to the default and not modified. Note that the perm option below is probably a better way to set this. Not available on Windows.
  • gmode=[0-7|[rwx]*] โ€” set the group file mode for the unix socket file, see umode for details.
  • omode=[0-7|[rwx]*] โ€” set the other file mode for the unix socket file, see umode for details.
  • perm=[0-7][0-7][0-7] โ€” set the full mode for the unix socket file per standard *nix semantics, modified by umask as the above mode operations are. Not available on windows.
  • owner=<name> โ€” set the owner of the unix socket file to the given user. Not available on windows.
  • group=<name> โ€” set the group of the unix socket file to the given group. Not available on windows.
  • peerusers=<name>[;<name>[;...]] โ€” sets the *nix users allowed to connect to this socket. If set, any connection received from a user that is not in this user list will cause the connection to be immediately closed. You may use peerusers and peergroups together, and if so the connection is permitted if either check succeeds. Not available on windows. ๐Ÿšช
  • peergrps=<name>[;<name>[;...]] โ€” sets the *nix groups allowed to connect to this socket. If set, any connection received from a users that whose group is not in this list will cause the connection to be immediately closed. Not available on windows. ๐Ÿšช

Remote Address String ๐ŸŒˆ

The remote address will be: "unix,<socket path>".

Remote Address ๐ŸŒˆ

UNIX returns a standard struct sockaddr_un for GENSIO_CONTROL_RADDR_BIN control.

Direct Allocation ๐ŸŒˆ

Allocated as a terminal gensio with gdata as a "const struct gensio_addr *"

serialdev ๐Ÿ”Œ๐Ÿ’–

connecting =
serialdev[(<options>)],<device>[,<serialoption>[,<serialoption>]]

Okay bestie, a serialdev connection is a local serial port. Simple as. The <device> is a /dev/xxx type situation on Unix-type systems, and it genuinely needs to be a real stream device of some type that normal termios or serial processing actually works on, so don't go throwing weird stuff at it.

On Windows systems, the <device> is //./COM<n> where <n> is the port number you see in the device manager. And uh โ€” heads up โ€” these can move around when you reboot; the original author isn't sure there's a way to fix that, and honestly, mood. ๐Ÿ˜ญ

For non-serial devices, use the "dev" gensio instead. Please. It's right there.

And this is, no surprise, a serial gensio. ๐Ÿ’…

You can also use "sdev" instead of "serialdev" for shorthand, because typing is hard and we're all busy.

โš ๏ธ Real talk: there's one problem with serialdev and UUCP locking โ€” 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 just be careful, okay? Be so careful. ๐Ÿซถ

Options โœจ

On top of readbuf, the serialdev gensio takes the following options, and yes we're doing all of them:

  • uucplock[=true|false] โ€” turn UUCP locking on or off for the device. Default is true for everything except pty devices and /dev/tty. This gets ignored on systems that don't support it.
  • flock[=true|false] โ€” turn flock locking on or off for the device. Default is true for everything except /dev/tty. Note that this does both an "flock(fd, LOCK_EX | LOCK_NB)" and an "ioctl(fd, TIOCNXCL)" to lock the device. Also ignored on systems that don't support it.
  • drain_time=off|<time in 100ths of a second> โ€” the total amount of time to wait for the data to be sent on the serial port at close time. Close gets delayed by this amount of time, or until all the data is transmitted. Default is off. When you're setting the default value for this one, "off" will not be accepted โ€” use -1 instead.
  • char_drain_wait=off|<time in 100ths of a second> โ€” at close time, if this much time elapses and no character is sent, finish the close. Default is 50 (that's .5 seconds). โš ๏ธ Note that if both this and drain_time are off, and the serial port is hung on flow control, it will never close. Like. Never ever. Also: when setting the default value for this, "off" will not be accepted, use -1 instead.
  • wronly[=true|false] โ€” set the device to write only. Default is false.
  • rdonly[=true|false] โ€” set the device to read only. Default is false.

Serialoptions ๐ŸŽ€

There are a plethora of serialoptions, available as defaults. Slay. ๐Ÿ’…

  • [speed=]<speed><parity><databits><stopbits> โ€” this is a normal serial port configuration specification, and the default is "9600N81". The speed= at the beginning is optional, but speed is a default type for this.
    • The <speed> (or baud rate) setting is a number. Depending on the specific serial port, this can be almost any number. Some serial ports on some systems support any arbitrary number, others only have specific numbers that are supported. It's giving โœจhardware dependentโœจ.
    • The <parity> setting is one of "N", "E", "O", "M", "S" for none, even, odd, mark, and space.
    • The <databits> value is a number 5-8. It sets the number of bits sent for each character, and is generally 8.
    • The <stopbits> value is the number of bits to mark the end of a character, either 1 or 2.

Really you just need to know what the value you're connecting to is, bestie. For more details, see https://en.wikipedia.org/wiki/Serial_port.

  • wronly[=true|false] โ€” ๐Ÿšจ this option is deprecated. Use the "dev" gensio for non-serial port devices and the wronly option in the parameters instead. What it does: sets the device to write only, and no termios definition is done on the port. You can do this 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.
  • nobreak[=true|false] โ€” clear the break line at start (or don't clear it). Default is to not clear it (false).
  • rs485=off|<delay rts before send>:<delay rts after send>[:<conf>[:<conf>]] โ€” set up RS-485 for the serial port. The first two parameters set the RTS delay (in milliseconds) of RTS before and after sending. The conf values can be:
    • "rts_on_send" โ€” RTS set when sending
    • "rts_after_send" โ€” RTS set after sending
    • "rx_during_tx" โ€” can receive and transmit at the same time
    • "terminate_bus" โ€” enable bus termination

Using "off" will disable rs485; that's useful for overriding a user-defined default setting. Default is "off".

  • xonxoff[=true|false] โ€” enable/disable xon/xoff flow control. Default is false.
  • rtscts[=true|false] โ€” enable/disable rts/cts flow control. Default is false.
  • local[=true|false] โ€” ignore/don't ignore the modem control lines. The default is to not ignore them (false). However, if you don't ignore the modem control lines, it can result in long shutdown delays. Just so you know. ๐Ÿ™ˆ
  • dtr[=on|off] โ€” force the DTR line to be on or off when the gensio is opened. โš ๏ธ Note that the order of the dtr and rts options matters โ€” whichever comes first in the options will be set first. This can be useful if the lines need to be sequenced in a certain order for the piece of hardware involved.
  • rts[=on|off] โ€” force the RTS line to be on or off when the gensio is opened. See the dtr section above for notes on the ordering of lines.
  • hangup-when-done[=true|false] โ€” when set to true, raise the modem control lines (DTR and possibly RTS) when the device is opened, and lower them on close. This will cause DTR to be asserted on open and be cleared on close. If RTS is not assigned with rtscts it will do the same with the RTS line. Default is to not modify the value at startup and inherit the previous value. If you modify the value, the value will be retained after close. Otherwise it won't work properly on close.

On the default for this, it's a integer: -1 means don't set the value, 0 means turn off, 1 means turn on.

  • custspeed[=true|false] โ€” allow/don't allow setting of custom baud rates. Ignored if custom baud rates are not supported. Normally only standard baud rates are supported (1200, 2400, etc). If supported by the hardware, this allows any arbitrary value to be set. ๐Ÿ‘‘

Remote Address String ๐Ÿ’Œ

The remote address string is the device and serial params, in a format like "9600N81[,<option>[,<option>[...]]]". Options are one of: XONXOFF, RTSCTS, CLOCAL, HANGUP_WHEN_DONE, RTSHI, RTSLO, DTRHI, DRLO, offline.


Remote ID ๐Ÿท๏ธ

The remote ID for the serial dev is the file descriptor returned as an integer.


Direct Allocation x โญ

Allocated as a terminal gensio with gdata as a "const char *" with the device and parameters string.


dev ๐ŸŒˆ

dev[(<options>)],<device>

So dev is like a serialdev gensio, except it does not do any serial port processing like termios or break processing. Iconic, honestly. This can be used for /dev/lpX devices for instance. It's read/write by default, so 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.

It 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's giving โœจmysteryโœจ. And it does not take serial options.

๐Ÿ’ป stdio

accepter = stdio[(options)]
connecting = stdio[(options)],<program>

okay so the stdio gensio is a weird little guy but honestly it's so useful, bestie. it's giving "chaotic but does the job." ๐Ÿ’…

A connecting stdio gensio runs the program you gave it (unless self is set as an option) and hooks its standard input and output up to the gensio's main channel. so like, running a program and chatting with it is literally effortless. if you want the stderr from the gensio, you open a channel on the main channel gensio โ€” more on that below, keep reading bestie ๐Ÿซถ

NOTE: even 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 whatever they want the program to run under, but keep the effective uid and gid at the (probably root) values so they can be restored to those values after opening the pty. the group list also gets 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. and you cannot re-open the gensio until both are closed. no sneaky shortcuts, bestie โœจ

The connecting gensio supports 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 connects stdin and stdout of the running program to the gensio.

โœจ Options โœจ

In addition to readbuf, a connecting stdio takes the following options:

  • self[=true|false] โ€” instead of starting a program, connect to the running program's stdio. this is the same as an accepting stdio gensio, except you don't have to go through the accept process. iconic, honestly.
  • console[=true|false] โ€” like self, except connect to the running program's console device, /dev/tty on Unix and CONIN$/CONOUT$ on Windows. super handy for reading passwords and such ๐Ÿ”
  • start-dir=path โ€” start the subprogram in the given path instead of the current directory.
  • raw[=true|false] โ€” for a "self", console, or accepter version of the gensio, put the I/O in "raw" mode, meaning character at a time, turn off ^C, etc. This will fail on pipes or files.
  • stderr-to-stdout โ€” send standard error output to standard out instead of having a separate channel for it.
  • noredir-stderr โ€” do not modify the stderr for the program, use the calling program's stderr. This can be useful if you want to see stderr output from a program.

๐Ÿฆ‹ Channels ๐Ÿฆ‹

The stdio connecting gensio that starts 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 to get stderr output. The args for gensio_alloc_channel() may be an optional "readbuf=<num>" and sets the size of the input buffer.

๐ŸŒธ Remote Address String ๐ŸŒธ

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 '\\'.

๐Ÿ’œ Remote ID ๐Ÿ’œ

The remote ID is the pid for the remote device, only for connecting gensios that start a program, not for any other type.

๐ŸŽ€ Direct Allocation ๐ŸŽ€

Allocated as a terminal gensio with gdata as a "const char * const args[]" for the gensio. gdata is not used for the accepter.


๐Ÿ” echo

connecting = echo[(options)]

The echo gensio just echos back all the data written to it. literally that's it. useful for testing, bestie ๐Ÿงช

โœจ Options โœจ

In addition to readbuf, a connecting echo takes the following options:

  • noecho[=true|false] โ€” instead of echoing the data, just become a data sink. a void. an abyss. ๐Ÿ’€
  • data=<str> โ€” supply the given data as the data to be read. When combined with noecho, it will create a gensio that just supplies the given data then returns a GE_REMCLOSE when done.

๐ŸŒธ Remote Address String ๐ŸŒธ

The remote address string is "echo".

๐ŸŽ€ Direct Allocation ๐ŸŽ€

Allocated as a terminal gensio, gdata is not used.


๐Ÿ“ file

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 one. Semantics on files for stdio do not always work correctly (we told you, bestie, use this instead ๐Ÿ˜ญ).

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. always. ready. ๐Ÿ’…

โœจ Options โœจ

In addition to readbuf, a connecting file takes the following options:

  • infile=filename โ€” set the input filename.
  • outfile=filename โ€” set the output filename.
  • read_close[=true|false] โ€” if this is true (the default), cause a GE_REMCLOSE error on read when all the file data is ready. If false, just stop returning data and don't do the GE_REMCLOSE. This is useful if you just want to inject a bunch of data then do nothing.
  • create[=true|false] โ€” create the output file if it does not exist. Ignored for read.
  • append[=true|false] โ€” open the file in append mode. If not specified, it will be opened at the beginning of the file and if writing, it will overwrite. Ignored for read.
  • trunc[=true|false] โ€” truncate the file on open. All data will be deleted from the file when opened. Ignored for read. (bestie. that's a delete everything button. be careful ๐Ÿซฃ)
  • binary[=true|false] โ€” open the file in binary mode. This is ignored on *nix systems, but for Windows it affects how newlines are handled.
  • umode=[0-7|[rwx]*] โ€” set the user file mode for the file if the file is created. This is the usual read(4)/write(2)/execute(2) bitmask per chmod, but only for the user portion. If a mode is specified, all other modes default to "6" (rw) unless they are specified, and the final mode is modified by the umask per standard *nix semantics. If no mode is specified, it is set to the default and not modified. Note that the perm option below is probably a better way to set this.
  • gmode=[0-7|[rwx]*] โ€” set the group file mode for the file if the file is created, see umode for details.
  • omode=[0-7|[rwx]*] โ€” set the other file mode for the file if the file is created, see umode for details.
  • perm=[0-7][0-7][0-7] โ€” set the full mode for the file per standard *nix semantics, modified by umask as the above mode operations are.

๐ŸŒธ Remote Address String ๐ŸŒธ

The remote address string is "file([infile=<filename][,][outfile=<filename>])".

๐ŸŽ€ Direct Allocation ๐ŸŽ€

Allocated as a terminal gensio, gdata is not used.


๐Ÿชซ dummy

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. that's the whole bit. main character energy of doing absolutely nothing and serving anyway โœจ

๐ŸŽ€ Direct Allocation ๐ŸŽ€

Allocated as a terminal gensio, gdata is not used.


๐Ÿ”Œ ipmisol

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. just a heads up ๐Ÿ‘‘

โœจ Options โœจ

In addition to readbuf, the ipmisol gensio takes the following options:

  • writebuf=<n> โ€” to set the size of the write buffer.
  • drain_timeout=<n> โ€” see the section on Drain Timeout.

It also takes the following ipmisol options:

  • 9600, 19200, 38400, 57600, 115200 โ€” baud rate, 9600 by default. Anything after the number is ignored, for compatibility with things like "N81". You cannot set those values for ipmisol, they are fixed at "N81".
  • nobreak[=true|false] โ€” disable break processing. False by default.
  • authenticated[=true|false] โ€” enable or disable authentication. True by default.
  • encrypted[=true|false] โ€” enable or disable encrypted. True by default.
  • deassert-CTS-DCD-DSR-on-connect[=true|false] โ€” False by default.
  • shared-serial-alert=fail|deferred|succeed โ€” behavior of handling serial alerts. fail by default.
  • ack-timeout=<number> โ€” the time (in microseconds) when an ack times out. 1000000 by default.
  • ack-retries=<number> โ€” the number of times (ack-timeouts) an ack is re-sent before giving up. 10 by default.

๐ŸŽ€ Direct Allocation ๐ŸŽ€

Allocated as a terminal gensio with gdata as a "const char *".


๐Ÿ“ก telnet

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 and supports RFC2217. it's giving layer cake but make it networking ๐Ÿฐ

โœจ Options โœจ

In addition to readbuf, the telnet gensio takes the following options:

  • writebuf=<n> โ€” set the size of the write buffer.
  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • rfc2217[=true|false] โ€” enable or disable RFC2217 mode for this gensio. If this is enabled and the remote end of the connection supports RFC2217 also, the gensio will be set up as a serial gensio and you can do normal serial gensio handling on it.
  • winsize[=true|false] โ€” enable or disable RFC1073, window size change notification. Default is false.
  • mode=client|server โ€” set the telnet mode to client or server. This lets you run a telnet server on a connecting gensio, or a telnet client on an accepter gensio.

๐ŸŒˆ Remote info ๐ŸŒˆ

telnet passes remote id, remote address, and remote string to the child gensio.

โœจ ssl โœจ

okay bestie, THIS is the one everyone's always asking about ๐Ÿ’…

accepter = ssl[(options)]
connecting = ssl[(options)]

so an SSL gensio is literally just the SSL/TLS protocol running on top of another gensio ๐Ÿซถ and that underneath gensio has to be reliable, no cap. that's the whole vibe.

using it is lowkey straightforward โ€” the actual hard part is the certificates ๐Ÿ˜ญ a server SSL gensio must be handed a certificate and a key. a client SSL gensio must be handed a certificate authority. and what's the client doing with that CA? it uses it to verify that the server actually has the proper certificate and keys ๐Ÿ‘‘

and yes, there are options so the server can ask the client for its certificate and validate it. mutual auth slay ๐Ÿ’–

SSL gensios are reliable. they're also packet-oriented. both things, at once, iconic.


๐ŸŒธ Options ๐ŸŒธ

on top of readbuf, the SSL gensio takes all of these:

  • writebuf=<n> โ€” sets the size of the write buffer, periodt.
  • drain_timeout=<n> โ€” go read the section on Drain Timeout, it's all there bestie.
  • CA=<filepath> โ€” tells it where to look for certificates for authorization. here's the tea: if this ends in a "/", then it's expected to be a directory holding files with certificates that have to be hashed per OpenSSL (peep the "openssl rehash" command for the deets ๐Ÿ‘€). otherwise it's a single file that holds one or more certificates. if you don't specify it, the default CA path gets used. and heads up โ€” setting this to an empty string disables it, so you can override a default value if you need to ๐Ÿ”ฅ
  • key=<filename> โ€” the file to grab the private key for the server. required for servers. it may also be specified for clients, and it's required for the client if the server requires a certificate (like, if it has CA set). iykyk ๐Ÿ”‘
  • cert=<filename> โ€” the file that contains the certificate used for the connection. if you don't specify this, the certificate is expected to be sitting in the key file. and same energy as CA: setting this to an empty string disables it, so you can override a default value when you gotta ๐Ÿ’…
  • mode=client|server โ€” normally an accepter gensio is in server mode and a connecting gensio is in client mode. this option lets you switch the roles and run in SSL server mode on a connecting gensio, or the other way around. plot twist ๐ŸŒˆ
  • con-timeout=<gtime> โ€” the timeout for a connection to complete. if the connect process doesn't finish in that amount of time, the connection gets dropped ๐Ÿ’€ default is 60 seconds. check the "gtime" section for more on how to set the time. โš ๏ธ and note this: the default setting for con-timeout is not a gtime, it's an integer in seconds.
  • clientauth[=true|false] โ€” normally a client is not authorized by the server. turning this on requires that the client provide a certificate, and then it authorizes that certificate. ignored for client mode ๐Ÿ™ˆ
  • allow-authfail[=true|false] โ€” normally, if the remote end certificate isn't valid, the SSL gensio will just close the connection. this option lets the open succeed even with an invalid or missing certificate. but listen โ€” the user should verify that authentication is set using gensio_is_authenticated().

verification of the common name is not done. the application should do that itself, and it can fetch the common name and other certificate data through a control interface.

you can use self-signed certificates with this interface. just be aware of the security ramifications fr fr. this gensio is fairly flexible, but you have to use it carefully if you want 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's validated, using the GENSIO_EVENT_PRECERT_VERIFY or GENSIO_ACC_EVENT_PRECERT_VERIFY events. this gives the user room to validate data from the certificate (like the common name) with GENSIO_CONTROL_GET_PEER_CERT_NAME, or to set a certificate authority for the validation with GENSIO_CONTROL_CERT_AUTH ๐Ÿ‘‘


๐Ÿฆ‹ Remote info ๐Ÿฆ‹

ssl passes remote id, remote address, and remote string down to the child gensio. that's it, that's the whole section ๐Ÿ’œ

certauth ๐ŸŽ€

accepter = certauth[(options)]
connecting = certauth[(options)]

Okay bestie, so a certauth gensio is a whole authentication protocol that runs on top of another gensio. And likeโ€ฆ that gensio underneath has to be reliable and encrypted, no exceptions, that's the vibe check. ๐Ÿ”’

It's literally the reverse of SSL, and I'm obsessed. The client has a key and a certificate, the server has a certificate authority (CA). It also supports password authentication, because sometimes we do things the easy way. ๐Ÿซถ

Once authentication happens, this gensio just becomes a passthrough. It's giving chill. But heads up: the readbuf option is not available in this gensio. Not a thing. Gone. ๐Ÿ™ˆ


Options โœจ

The certauth gensio takes the following options:

  • drain_timeout=<n> โ€” see the section on Drain Timeout, it's whole thing on its own.
  • CA=<filepath> โ€” where to look for certificates for authorization. If it ends in a "/", it's expected to be a directory full of files with certificates that must be hashed per OpenSSL (go check the rehash section in gensio-keygen(1) for the deets). Otherwise it's a single file holding one or more certificates. If you don't specify it, the default CA path for openssl gets used. This one is server only, clients literally ignore it. And bestie, note that setting it to an empty string disables it, so you can override a default value if you need to. ๐Ÿ’…
  • key=<filename> โ€” the file to grab the private key for the client from. Required for clients. Ignored on the server.
  • cert=<filename> โ€” also specifies the file to get the private key for the client (yes, the original says this, we don't question it ๐Ÿ’€). Required for clients, ignored on server. If you don't specify it, the certificate is expected to live in the key file. And setting it to an empty string disables it, so you can override a default value if necessary.
  • username=<name> โ€” a username to authenticate with on the remote end. Required for the client, ignored on the server.
  • service=<string> โ€” sets the remote service the client is requesting. Optional, but the other end may straight up reject the connection if it's not supplied. Ignored on the server.
  • password=<string> โ€” the password to use for password authentication. Not recommended, bestie โ€” the callback is more secure to use. ๐Ÿ‘‘
  • mode=client|server โ€” normally an accepter gensio is in server mode and a connecting gensio is in client mode. This lets you switch the roles and run in server mode on a connecting gensio, or vice versa. Main character energy, honestly.
  • allow-authfail[=true|false] โ€” normally if the remote end certificate is not valid, the certauth gensio closes the connection. This option lets the open succeed anyway, with an invalid or missing certificate. > Note that the user should verify that authentication is set using gensio_is_authenticated(). Don't skip that part. ๐Ÿšจ
  • use-child-auth[=true|false] โ€” if the child gensio is already authenticated, then don't run the protocol, just go straight into passthrough mode and do zero authentication. Effortless.
  • enable-password[=true|false] โ€” on the server, allow passwords for login. On the client, send a password if asked for one. By default passwords are disabled. And ngl, passwords are much less secure than certificates, so this is discouraged. ๐Ÿ˜ญ
  • require-password[=true|false] โ€” on the server, also require a password for login if certificates pass. Super useful on Windows systems where a password is pretty much required to log in but you still want certificate authentication.
  • enable-2fa[=true|false] โ€” on the server, request 2-factor authentication data from the client. Only useful when the 2-factor data is known before startup, like Google Authenticator or other things of that nature. It is not useful for text/email types of things that send the data after the connection is initiated โ€” but those are usually interactive and can be handled with interactive methods. ๐ŸŒธ
  • 2fa=<string> โ€” on the client, provide the given 2-factor authentication data to the server if it asks for it.

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. That's on you, bestie. ๐Ÿ’–

  • con-timeout=<gtime> โ€” the timeout for a connection to complete. If the connect process doesn't finish in that amount of time, the connection gets dropped. The default is 60 seconds. See the "gtime" section for more info on how to set the time. > Note that the default setting for con-timeout is not a gtime, it is an integer in seconds. Little icon of weirdness, but okay. โœจ

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. No cap. ๐Ÿ”ฅ

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's 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 connection being shut down. Choose wisely. ๐Ÿ’…

The callbacks are:

  • GENSIO_EVENT_AUTH_BEGIN / GENSIO_ACC_EVENT_AUTH_BEGIN โ€” on the server side, this is called when authentication is requested by the client. The username will be available if the user provided it via GENSIO_CONTROL_USERNAME.
  • GENSIO_EVENT_PRECERT_VERIFY / GENSIO_ACC_EVENT_PRECERT_VERIFY โ€” on the server side, this is called after the certificate has been received but before it is verified. The user can use this to query the certificate and update the certificate authority based on username or certificate information. Very spy-movie. ๐Ÿ•ต๏ธ
  • GENSIO_EVENT_VERIFY_PASSWORD / GENSIO_ACC_EVENT_VERIFY_PASSWORD โ€” on the server side, this is called if the certificate verification has failed and after the password has been requested from the remote end. The password is passed in, and it is cleared immediately after this call. Poof. ๐Ÿ’€
  • GENSIO_EVENT_REQUEST_PASSWORD / GENSIO_ACC_EVENT_REQUEST_PASSWORD โ€” on the client side, this is called if the server requests that a password be sent and the password is not already set for the gensio. The requested password is immediately cleared after being sent to the server.

Remote info ๐Ÿฆ‹

certauth passes remote id, remote address, and remote string to the child gensio. That's it, that's the whole section, she's simple like that. ๐ŸŽ€

๐ŸŽ€ mux ๐ŸŽ€

accepter = mux[(options)]
connecting = mux[(options)]

okay bestie, listen up because the mux gensio is kind of a big deal ๐Ÿ’… It's a gensio filter that lets you stack one or more channels on top of a single gensio โ€” like, multiplexing the connection, exactly what it says on the tin โœจ

Every single channel gets its own full individual flow-control, no sharing, no drama. And the channels are message oriented (as described above ๐Ÿ‘€), so you can literally run a mux with no extra channels at all and just use it for message demarcation. Iconic behaviour honestly. They also support out-of-bounds messages, which we'll get to ๐ŸŒˆ


โœจ Options โœจ

A mux gensio takes the following options:

  • max_channels=<n> โ€” allow at most <n> channels to be created in the mux. The default is 1000. Minimum value of <n> is 1, max is 65536. That's the whole range, don't get cute ๐Ÿซถ
  • service=<string> โ€” set the remote service requested by the client. It's optional, but the other end may straight up reject the connection if you don't supply it ๐Ÿ˜ญ Ignored on the server. Server said "not my problem".
  • mode=client|server โ€” by default a mux accepter is a server and a mux connecter is a client. The protocol is mostly symmetric, but it's genuinely hard to kick things off properly if both sides try to start things at the same time ๐Ÿ’€ This option lets you override the default mode in case you have some special need to do so.

When the open is complete on the mux gensio, it'll work just like a transparent filter with message demarcation. In effect, you have one channel open. Slay ๐ŸŒธ


Creating Channels

To create a channel, call the gensio_alloc_channel() function on the mux gensio. This returns 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 gets 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, no shortcuts bestie โญ

As you might imagine (ngl the original said "imaging" and I'm not gonna pretend that's a word ๐Ÿ’€), 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 gets 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 โœจ

And yes, you can modify the service value after you allocate the channel but before you open it. Main character timing ๐Ÿ’…


Out Of Band Messages

mux supports out of band (oob) data, which is data that will be delivered normally ๐Ÿ“ฌ It comes in a normal read, but with "oob" in the auxdata. You send oob data by adding "oob" to the write auxdata. And you should normally use the "eom" flag so the end of the out of band message is marked, fr fr ๐ŸŽ€

This is so you can send special data outside of the normal processing of data. Sneaky. Love that for you.


Mux Events

As you might imagine, normal data events come through the gensio channel they're associated with. Close events will, too ๐Ÿ‘‘

If a mux gensio closes abnormally (like the underlying connection fails), you'll get a read error on each channel. Every single one. Not a vibe.

New channel events โ€” and other global events coming from lower gensios, like if you're running over ssl or telnet โ€” come in through the original gensio normally. However, you can close that, and another channel will be chosen to receive those events. In general, it's best to handle the global events on all channels and not assume. Assume = you get got ๐Ÿ™ˆ


Closing and Freeing a Mux

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've closed a mux gensio, you can re-open it with gensio_open(). It will not recreate all the channels for you: you'll have one channel, and the channel you use to call gensio_open() will become the first channel.

You cannot re-open individual channels. Periodt. ๐Ÿ’€

To free a mux gensio, you must free all the channels on it. All of them. Every last one ๐Ÿงš


๐Ÿฆ‹ pty ๐Ÿฆ‹

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 connecting gensios are supported, so don't try to accept with this one bestie.

pty has some unusual handling to allow execution of login shells of users from root ๐Ÿ‘‘

On Unix type systems, if the first character of the program is -, the - 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 gid to the current real uid and gid 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 at 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 the 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. Four controls, four little knobs, chef's kiss ๐Ÿ‘‘

If no program is specified, when the pty gensio is opened it'll 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 โญ


โœจ Options โœจ

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, no exceptions ๐Ÿซถ

  • link=<filename> โ€” create a symbolic link from filename to the pty name. This way you can have a fixed reference to refer to the pty ๐Ÿ”— Standard permissions apply. Without forcelink, the open will fail if filename already exists. Unix only.
  • forcelink[=true|false] โ€” if link is specified, and a file already exists where the symbolic link is, replace it.
Be careful ๐Ÿ’€ it deletes whatever is there indiscriminately. Like, no thoughts, just deletion.
  • start-dir=path โ€” start the subprogram in the given path instead of the current directory.
  • raw[=true|false] โ€” causes the pty to be set in raw mode at startup. This is important for anything that will start writing immediately to the pty โœจ If you don't set this, echo will be on and writes on the master will be echoed back (annoying!!). In general this is useful for unattached ptys. It was added for testing, but it'll be useful in many situations ๐ŸŒธ
  • umode=[0-7|[rwx]*] โ€” set the user file mode for the pty slave. This is the usual read(4)/write(2)/execute(2) bitmask per chmod, but only for the user portion. If a mode is specified, all other modes default to "6" (rw) unless they're specified, and the final mode is modified by the umask per standard *nix semantics. If no mode is specified, it's set to the default and not modified. Note that the perm option below is probably a better way to set this ๐Ÿ’…
  • gmode=[0-7|[rwx]*] โ€” set the group file mode for the pty slave, see umode for details.
  • omode=[0-7|[rwx]*] โ€” set the other file mode for the pty slave, see umode for details.
  • perm=[0-7][0-7][0-7] โ€” set the full mode for the pty per standard *nix semantics, modified by umask as the above mode operations are.
  • owner=<name> โ€” set the owner of the slave pty to the given user.
  • group=<name> โ€” set the group of the slave pty to the given group.
  • user=<name> โ€” Windows only, create the process under the given user instead of the calling process' user. This generally requires special privileges ๐Ÿ‘‘
  • passwd=<name> โ€” Windows only, user must be specified to use. Log on the user with the given password. If a password is not given, an S4U logon will be attempted, but that may not work so well, as some things are missing from the access token of the user ๐Ÿ˜ญ
NOTE: There are significant security issues with passing a password this way. You must use proper password handling. Do not be sloppy about this one, bestie, I'm serious ๐Ÿ™ˆ
  • 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.

Remote Address String

The remote address string is the program and arguments run on the pty ๐ŸŽ€ The arguments 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 \\.


Remote Address

The address is a pointer to an integer, the ptym file descriptor is returned. addrlen must be set to sizeof(int) when passed in. Don't forget that part โญ


Local Address String

This returns the device of the pty, generally /dev/pts/<n> ๐Ÿฆ‹ This is useful for pty gensios with no program โ€” it lets you 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. Yes, it can change. Keep up ๐Ÿ’…


Direct Allocation

Allocated as a terminal gensio with gdata as a "const char * const args[]".


๐Ÿ’œ msgdelim ๐Ÿ’œ

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. It's a support character, not the lead, and that's okay ๐Ÿ˜ญ

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. Fine. Reasonable. Acceptable. ๐Ÿ‘‘


โœจ Options โœจ

In addition to readbuf, the msgdelim gensio takes the following options:

  • writebuf=<n> โ€” set the size of the write buffer.
  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • crc[=on|off] โ€” enable/disable the CRC at the end of the packet. Useful if you're running over a reliable protocol, and especially for testing relpkt so you can fuzz it and bypass the crc errors ๐ŸŒˆ

relpkt โœจ

accepter = relpkt[(options)]
connecting = relpkt[(options)]

okay so bestie, relpkt is giving glow-up energy โ€” it takes an unreliable packet interface (right now that's literally only UDP and msgdelim) and turns it into a reliable packet interface. that means you can have a reliable connection over a serial port (with msgdelim) or over UDP. ๐Ÿ’…

but ngl, UDP is not recommended โ€” it doesn't handle flow control and stuff in a friendly manner. it's giving chaos gremlin. ๐Ÿ™ˆ

relpkt is also kinda unusual when it comes to clients and servers. the protocol is symmetric for the most part, so you can literally start two clients and they will connect to each other โ€” if they're started relatively close in time to avoid one timing out. meanwhile a relpkt server will just... wait forever for an incoming connection on an open. patient queen. ๐Ÿ‘‘

because it's packet-oriented, when you use relpkt as a stream it might need the chardelay gensio on top of it to collect individual small writes into a group and make things more efficient. if you write individual bytes, it will be extremely inefficient โ€” like texting one letter per message. ๐Ÿ’€

relpkt does not support readbuf. it supports the following:

  • max_pktsize=<n> โ€” sets the maximum size of a packet. this may be reduced by the remote end, but will never be exceeded. it must be at least 5 bytes shorter than the maximum packet size of the interface below it. this defaults to 123 (msgdelim max packet size - 5). if you run it over UDP, this should probably be increased for performance. ๐Ÿ”ฅ
  • max_packets=<n> โ€” sets the maximum number of outstanding packets. this may be reduced by the remote end, but will never be exceeded.
  • mode=client|server โ€” by default a relpkt is a server on an accepter and a client on a connecter. see the discussion above on clients and servers. โœจ
  • timeout=<gtime> โ€” specify the time for a timeout. if relpkt sends a message requiring an ack, it will wait this long before resending the packet. see the "gtime" section for more info on how to set the time. if you don't specify a time modifier, the default is in seconds. the default is one second. โฐ
  • max_timeouts=<n> โ€” the maximum number of timeouts before giving up on a connection. the default is 5. ๐Ÿ˜ญ

ratelimit ๐Ÿšฆ

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. the readbuf option is not currently available in this gensio.

Options

the ratelimit gensio takes the following options:

  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • xmit_len=<n> โ€” the number of bytes to allow before a delay. note that the delay occurs after a single write succeeds, so if a single byte is written it will delay. if a write is done of more than this length, it will be limited to this length. defaults to 1.
  • xmit_delay=<gtime> โ€” the amount of time to wait between writes. see the "gtime" section for information for this time specification. this must be supplied. ๐Ÿซถ

chardelay ๐Ÿฆ‹

accepter = chardelay[(options)]
connecting = chardelay[(options)]

collect characters being transmitted and hold them until a certain amount of time has passed, a certain number of characters are received, or optionally one of a set of strings is transmitted. it's giving โœจpatienceโœจ.

heads up: this only applies to transmitted data. received data is passed through transparently. the readbuf option is not available in this gensio.

Options

the chardelay gensio takes the following options:

  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • min-delay=<gtime> โ€” after a character is received, wait the given amount of time until sending any stored characters. a new character will reset this timer, so basically this sets the amount of time when no characters are sent before what is in the buffer is sent. default is 1 millisecond. โฑ๏ธ
  • max-delay=<gtime> โ€” sets the maximum amount of time after a character is received before the write buffer is sent. this is not reset by new characters, so any character added to the write buffer is guaranteed to be sent after this delay. default is 20 milliseconds.
  • writebuf=<n> โ€” the output buffer size. when this many characters are queued in the write buffer, the buffer is sent immediately. default is 1024. ๐Ÿ“ฆ
  • sendon=<str> โ€” this is one or more strings, separated by space characters, that cause the buffer to be sent immediately. this is a standard C-like string. note that a space character is the only character that separates strings. tabs, newline, etc. do not. if you want to send a space, you must use two \ characters to escape it. note that this only does a simple comparison, it does not generate a finite state automata or anything like that, so strings in strings might be missed. ๐Ÿ‘€

Examples

assume we have the following:

chardelay(min-delay=5s,max-delay=10s,sendon="\r \\  \n")

this means that if a character is sent, this will wait 5 seconds until that character is sent, unless another character is received in that time, then the 5 second timer is restarted. however, that character will be sent within 10 seconds no matter what. if a carriage return (\r), a newline (\n) or a space character (notice the double \ as discussed above) is transmitted, whatever is in the buffer is sent immediately. iconic behaviour fr fr. ๐ŸŽ€


trace ๐Ÿ”

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. main character energy for troubleshooting bestie.

note that the trace gensio with the dir option 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. if you want to see the data before delivery to the gensio/user above/below, use the b4dir option, but remember all the data may not be accepted. you can use dir and b4dir at the same time. ๐Ÿ’…

Options

trace does not support readbuf. it supports the following options:

  • dir=none|read|write|both โ€” sets what data is traced. "none" means no data is traced (the default), "read" traces data flowing up the gensio stack (read by the parent), write traces data flowing down the gensio stack (written by the parent), and "both" traces reads and writes. โœจ
  • b4dir=none|read|write|both โ€” like dir, but traces the data before the handler is called with all the data presented to the output. all the data may not be accepted, as mentioned above, but it will let you see the data before the handler in case that's useful.
  • block=none|read|write|both โ€” blocks data in one or both directions. "none" means data flows both directions (the default), "read" means data flowing up the gensio stack is discarded, write means data flowing down the gensio stack is discarded, and "both" discards both reads and writes. data that is discarded is not traced. ๐Ÿ’€
  • raw[=yes|no] โ€” if set, traced data will be written as raw bytes. if not set, traced data will be written in human-readable form.
  • file=<filename> โ€” the filename to write trace data to. if not supplied, tracing is disabled. note that unless delold is specified, the file is opened append, so it will add new trace data onto the end of an existing file. ๐Ÿ“Ž
  • stderr[=yes|no] โ€” send the trace output to standard error. overrides file.
  • stdout[=yes|no] โ€” send the trace output to standard output. overrides file and stderr.
  • delold[=yes|no] โ€” delete the old data in the file instead of appending to the file. ๐Ÿงน

perf ๐Ÿ“Š

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. it's literally a little speed test bestie. ๐ŸŽ๏ธ

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.

Options

perf does not support readbuf. it supports the following options:

  • writebuf=<n> โ€” sets the size of the buffer to write to the child gensio. each write will be this size. this defaults to 0.
  • write_len=<n> โ€” the number of bytes to write.
  • expect_len=<n> โ€” the number of bytes to expect from the other end. ๐Ÿ‘‘

conacc ๐Ÿงš

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 0) you can get a different address for the re-opened gensio. so you must refetch the local address or local port in this case. no cap, that's a real gotcha. ๐Ÿ˜ญ

Options

the readbuf option is not accepted.

  • retry-time=<gtime> โ€” instead of restarting a closed connection immediately, this will cause a delay between the close and the re-open. also, if the connect fails, it will not disable itself, it will wait another retry-time period and try the connect again. see the section on gtime above. the unit defaults to milliseconds. the default value is 0. โณ

Direct Allocation

allocated as a terminal gensio with gdata as a "const char *". that is the specification of the gensio below it. ๐ŸŒˆ

โœจ mdns โœจ

connecting = mdns[(options)][,<name>]

Okay so this gensio is like the friend who does all the detective work for you ๐Ÿ” โ€” it uses mDNS to sniff out a service and then justโ€ฆ connects to it. Convenient much? It figures out the connection type, the address, and the port all on its own, and it'll auto-add telnet and rfc2217 if they're available. So thoughtful. The mDNS name can be passed as an option or as the string after the comma up there in the synopsis. Either way bestie.

The name, type, host, and domain strings can all be wildcarded in various ways โ€” the deets live in "STRING VALUES FOR WATCHES" over in gensio_mdns(3).

๐ŸŽ€ Options ๐ŸŽ€

The readbuf option is accepted here, but if you don't specify it, the default readbuf value for the sub-gensio gets used instead. And on top of readbuf, the mdns gensio also takes all of these:

  • nostack[=true|false] โ€” by default mdns will try to use the "gensiostack" text string from the mDNS service. Not feeling that vibe? Flip this on and it stops doing it. Periodt.
  • ignore-v6-link-local[=true|false] โ€” ignore IPv6 link local addresses, the ones that start with "fe80:". They cause problems on some systems, so this is your escape hatch ๐Ÿซถ
  • mdnstimeout=<gtime> โ€” how long to wait for mdns to find the item. Peep the "gtime" section for how to set the time. Default unit is milliseconds, default time is 1 second. Lowkey important: the default setting for mdnstimeout is not a gtime, it's an integer in milliseconds. You can also write "timeout" instead of "mdnstimeout", but not in the default.
  • name=<mdnsstr> โ€” set the mdns name to search for. You generally want this one, otherwise you'll just get the first thing that comes up from the search ๐Ÿ™ˆ
  • type=<mdnsstr> โ€” set the mdns type to search for.
  • domain=<mdnsstr> โ€” set the mdns domain to search for. You generally don't use this.
  • host=<mdnsstr> โ€” set the mdns host to search for. You generally don't use this either.
  • txt=<key>:<value> โ€” require the service to have a TXT record matching that key and value. It's an exact match against the standard mDNS TXT key=value entry. You can specify this option multiple times, and all the specified TXT entries must be present on the same service. No splitting them across services, that's not the move.
  • interface=<num> โ€” set the network interface number to search on. Default is -1, which means all interfaces ๐ŸŒˆ
  • nettype=unspec|ipv4|ipv6 โ€” the network type to search for. unspec means any type; otherwise you can limit it to ipv4 or ipv6.
  • nodelay[=true|false] โ€” sets nodelay on the socket. Ignored for udp. Note that the default value for mdns is ignored here โ€” if you don't set it, it takes the default value for whichever sub-gensio gets chosen.
  • laddr=<addr> โ€” an address specification to bind to on the local socket, so you can set the local address.

๐ŸŒธ Direct Allocation ๐ŸŒธ

Allocated as a terminal gensio with gdata as a "const char *". That's the specification of the mdns as a string. Slay.


๐Ÿ’… kiss ๐Ÿ’…

accepter = kiss[(options)]
connecting = kiss[(options)]

A gensio that implements the KISS amateur radio protocol for transferring data between a TNC and AX25. Details at http://www.ax25.net/kiss.aspx if you wanna read up ๐Ÿ“ป It's got a whole bunch of tunable parameters, and honestly? You probably shouldn't mess with most of them. Just trust the process.

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. Don't set it? Then the TNC is assumed to be zero. On reading, "tnc:<n>" will always be in one of the auxdata fields. And do not โ€” I repeat, do not โ€” set a TNC larger than the configured number of TNCs ๐Ÿ’€

There's an 8-bit protocol field in the AX25 frame called the PID. It's not passed in the data โ€” it also rides along in the auxdata with the format "pid:<n>". Want to set the pid? Pass it in the auxdata on write. Don't set it? It defaults to 240 (0xf0).

โญ Options โญ

kiss supports these options:

  • readbuf=<n> โ€” sets the maximum packet size that can be read from the TNC. Defaults to 256.
  • writebuf=<n> โ€” sets the maximum packet size that can be written to the TNC. Defaults to 256.
  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • tncs=<n> โ€” the number of supported TNCs. Defaults to 1.
  • server[=yes|no] โ€” is this a KISS server or client? A client will set the tunable parameters on the TNC. A server is expected to be a TNC, but this isn't fully implemented yet since you can't get the tunable parameters. (WIP energy ๐Ÿ˜ญ)
  • setupstr=<string> โ€” send the given string at startup to set up the device.
  • setup-delay=<n milliseconds> โ€” wait the given number of milliseconds after sending the setup string before continuing operation.
  • d710[=yes|no] โ€” if set to yes or if just "d710" is given, set the setupstr to a value for the Kenwood D710 radio. Same as doing:
setupstr='xflow on\rhbaud 1200\rkiss on\rrestart\r'
  • d710-9600[=yes|no] โ€” if set to yes or if just "d710-9600" is given, set the setupstr to a value for the Kenwood D710 radio at 9600 bps. Same as doing:
setupstr='xflow on\rhbaud 9600\rkiss on\rrestart\r'
  • txdelay=<n> โ€” the KISS txdelay parameter, in milliseconds, rounded to the nearest power of ten. Max value is 2550. Default is 50ms.
  • persist=<n> โ€” the KISS "P" parameter, ranging from 0-255. Ngl, I don't know what this means and the spec isn't clear either, so you'll have to figure it out on your own ๐Ÿซ  Default is 63.
  • slottime=<n> โ€” the KISS slot time parameter, in milliseconds, rounded to the nearest power of 10. Max value is 2550ms, default is 100ms.
  • fullduplex[=yes|no] โ€” sets full duplex on the connection.
  • sethardware=<n> โ€” a hardware-specific control value ranging from 0-255. Its meaning depends on the hardware. By default it is not set.

โœจ ax25 โœจ

accepter = ax25[(options)]
connecting = ax25[(options)]

Okay bestie, this one is a gensio that does the AX25 amateur radio protocol for moving data around ๐Ÿ’–. You'd basically always run it sitting on top of a KISS gensio. For the full tea, hit up http://www.ax25.net ๐Ÿซถ

This is a totally normal packet-oriented interface, no cap.

It's also a channel-based implementation โœจ. Every single AX25 connection to a remote system gets its own channel. You can also have an unconnected channel if all you wanna do is receive UI frames ๐ŸŒธ.

There is no server/client setting on an ax25 gensio โ€” the protocol is symmetric, so it literally doesn't need one ๐Ÿ’…. An ax25 accepter will grab the first connection that shows up and hand it to you as a gensio, but after that there's genuinely no difference.

When it's running as an accepter, incoming connections get delivered as new connections, not new channels. That's the move because it lets you use an ax25 gensio more naturally underneath a server without your code having to know anything about ax25 ๐Ÿงš. Like, say you wanna spin up a simple AX25 server that reflects all incoming data โ€” you could make a reflector:

greflector -t kiss,tcp,1234

then you could create the server:

gensiot -i 'echo' --server -a \
    'ax25(laddr=test-1),conacc,kiss,tcp,localhost,1234'

then you could connect to it:

gensiot
'ax25(laddr=test-2,addr="0,test-1,test-2"),kiss,tcp,localhost,1234'

So to actually use this: allocate an ax25 gensio. If you wanna make a connection, you must set the addr to a destination address. By default the laddr gets 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 in a sec). Then you open the gensio. An ax25 gensio with no address just opens immediately and starts receiving UI packets as directed. With an addr set, it'll attempt to make a connection. Once that's up (or it fails), the open callback fires ๐Ÿ’–.

๐ŸŽ€ AX25 Addresses ๐ŸŽ€

A gensio AX25 address string looks like this:

[ax25:]tncport,dest[:c|r],src[:c|r][,extra1[:h]
[,extra2[:h][..]]]

tncport is a number from 0-15 specifying which TNC to use ๐ŸŒˆ. dest and src are subaddresses specifying the destination and source addresses. Note that the source address better match an laddr, or the connection will NOT work ๐Ÿ’€. extra fields are also subaddresses. These get used for routing (a function that is deprecated and no longer use) and by APRS for its own purposes โ€” see the APRS spec for the deets. 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 look like:

callsign-n

where callsign is a set of alphanumeric digits (a-zA-Z0-9) and n is a number 0-15. Lower case digits get converted to upper case in the address and are not converted back on receipt ๐Ÿ™ˆ.

๐ŸŒธ Handling unconnected packets (UI frames) ๐ŸŒธ

To receive UI frames, you have to enable the GENSIO_CONTROL_ENABLE_OOB control on the gensio. Set the value to 1 and you'll receive UI frames where the destination matches one of your laddrs โœจ. Set it to 2 and you'll receive all UI frames (promiscuous mode ๐Ÿ‘€). 0 disables receipt of UI frames entirely.

UI frames get reported with the "oob" in the auxdata, like out of band data. Important bestie: 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'd 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 ๐Ÿซถ.

๐Ÿ’œ Raw Data ๐Ÿ’œ

If you set the raw option, raw data will be reported in the read callback as explained by the option docs. If you set a "raw" auxdata when writing to the channel, it will send the raw data you give. A CRC will be added, but nothing else โœจ.

๐Ÿ‘‘ Options ๐Ÿ‘‘

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:

  • readbuf=<n> โ€” sets the maximum packet size that can be read. Defaults to 256.
  • writebuf=<n> โ€” sets the maximum packet size that can be written. Defaults to 256.
  • outgen=<str> โ€” lets you specify a different output gensio stack for the output. It'll write output to this gensio and use the other child for input only. This can only be set on an initial gensio, not sub-channels, and only on connecting gensios.
  • readwindow=<n> โ€” sets the maximum packet size that can be received and unacked. Defaults to 7 for extended and 4 for non-extended.
  • writewindow=<n> โ€” sets the maximum number of packets that can be sent unacked to the remote side. Defaults to 7 for extended and 4 for non-extended.
  • extended=<n> โ€” sets the extended mode. Setting it to 1 enables 7-bit sequence numbers, and the connection will first attempt to use 7-bit sequence numbers. If that fails, it falls back to 3-bit sequence numbers. Setting the value to 2 enables a non-standard operation that adds parameter negotiation to the connection startup. Setting the readbuf, writebuf, readwindow, and writewindow without extended=2 really isn't very useful, because without the negotiation it'll be forced to fall back to the defaults. If extended=2 fails, it falls back to extended=1.
  • laddr=<subaddr>[;<subaddr[...]] โ€” sets the addresses the ax25 gensio will receive connections on. Except for UI frames and raw mode, the destination of a packet must match one of these addresses to be handled. This can only be set on the accepter or on the first connection and applies to all channels. These can also be added and removed with the GENSIO_CONTROL_ADD_LADDR and GENSIO_CONTROL_DEL_LADDR controls.
  • uiaddr=<subaddr>[;<subaddr[...]] โ€” sets the addresses for the channel that will receive UI addresses. If not in promiscuous UI mode, it will only receive UI frames sent to addresses in this list. These can also be added with controls. These can also be added and removed with the GENSIO_CONTROL_ADD_MCAST and GENSIO_CONTROL_DEL_MCAST controls.
  • addr=<ax25addr> โ€” sets the address of the remote end of the connection. Packets will be sent to this address.
  • crc[=yes|no] โ€” enable CRC checking. Default is off.
  • ign_emb_ua[=yes|no] โ€” some AX.25 stacks don't properly reset themselves when they get a second SABM (due to a timeout) but they still send a UA ๐Ÿ˜ค. If you're getting errors about receiving a UA while connected and the connection hangs, try enabling this option.
  • srt=<n> โ€” the initial smoothed round trip time for the connection, in milliseconds. See the spec for details, you probably don't need to mess with it. Note that this value you set here is multiplied by the number of digipeaters between the source and the destination for the actual value. Defaults to 1500.
  • t2=<n> โ€” the timer 2 value for the connection, in milliseconds. See the spec for details, you probably don't need to mess with it. Defaults to 2 seconds.
  • t3=<n> โ€” the timer 3 value for the connection, in milliseconds. See the spec for details, you probably don't need to mess with it. Defaults to 300 seconds.
  • retries=<n> โ€” number of retries on a send before giving up and dropping the connection. See the spec for details, you probably don't need to mess with it. Defaults to 10.
  • heard[=yes|no] โ€” when any packet is heard from the TNC, report the address on the read callback. There will be no data, and "oob", "heard", and "addr:" fields will be in the auxdata. Note that if you disable read, any heard reports that come in will be dropped and not reported ๐Ÿ™ˆ. Any UI packets reported will not be reported as heard packets โ€” you already have all the information you need in the UI report ๐Ÿ’….
  • raw[=yes|no] โ€” when any packet is heard from the TNC, report the raw data in the read callback. This is the full packet, including the address, but without the CRC. "oob" and "raw" fields will be in the auxdata. Note that if you disable read, any raw reports that come in will be dropped and not reported.

๐Ÿฆ‹ xlt ๐Ÿฆ‹

accepter = xlt[(options)]
connecting = xlt[(options)]

A gensio that translates characters โœจ. It's primarily for translating line feeds and carriage returns, but it may eventually get extended to do string substitutions and such ๐Ÿ”ฎ.

The readbuf option is not available in this gensio.

๐Ÿ’– Options ๐Ÿ’–

  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • in=<n>:<m> โ€” translate character n (a number, 0xnn for hex, 0nnn for octal, or nn for decimal) to character m on data read from the gensio.
  • out=<n>:<m> โ€” like in, but translate character n to character m on data written to the gensio.
  • nlcr โ€” translate new lines to carriage returns on data read from the gensio, and carriage returns to new lines on data written to the gensio.
  • crnl โ€” translate carriage returns to new lines on data read from the gensio, and new lines to carriage returns on data written to the gensio.

๐ŸŒˆ keepopen ๐ŸŒˆ

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.

โญ Options โญ

  • retry-time=<gtime> โ€” set the retry interval. See the section on gtime for detail on this. Defaults to milliseconds if no unit is given. The default is 1 second.
  • discard-badwrites[=yes|no] โ€” normally this gensio will flow-control the upper layer when the lower gensio is not open. If you enable this, it'll just throw write data away if the lower gensio is not open ๐Ÿ’€.

โœจ script โœจ

connecting = script[(options)] accepting = script[(options)]

ok bestie, script is a filter gensio and it is literally giving "let my external program talk to the child first" energy ๐Ÿ’…. Here's the tea: it lets an external program interact with the child gensio before it reports that it's 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 ๐Ÿ’€.

heads up bestie: the readbuf option is not available in this gensio ๐Ÿ™ˆ.

๐ŸŒธ Options ๐ŸŒธ

  • script=<string> โ€” run the program given in the string as the external program. This is equivalent to doing: gensio=stdio(noredirstderr),<string>, it just a shortcut ๐Ÿซถ.
  • gensio=<string> โ€” instead of running a program, run the given gensio. When the gensio closes, handle an error. If the gensio supported getting an error code, that is done.

๐Ÿฆ‹ sound ๐Ÿฆ‹

connecting = sound[(options)],<device>

The sound gensio gives you access to sound devices on the platform. It's an unusual gensio in many respects, no cap. 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 consume 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 the first line of the output of the aplay/gsound list. 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:

0:Microphone (Realtek(R) Audio)     input,inchans=2
0:Speakers (Realtek(R) Audio)       output,outchans=2

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:

0:USB Audio CODEC   output,outchans=2
1:USB Audio CODEC   input,inchans=2
2:Mac mini Speakers output,outchans=2

where you could use USB Audio for the name.

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 ๐Ÿ™ˆ.

๐ŸŽ€ Options ๐ŸŽ€

  • <samplerate>-<channels>-<format> โ€” for simple applications, the sound gensio takes a compact and simple format as an option. It specifies those things for both the input and output channel. So, for instance, sound(44100-2-float) would be CD-rate two channel using floating point numbers โœจ.
  • list[=yes|no] โ€” instead of opening a sound device, the gensio will provide data about the various sound interfaces available. Every other option but type is ignored. If type is supplied, list the interfaces for that type. If type is not supplied, list the interfaces for the default one. 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.
  • outdev=<str> โ€” if the output device has a different name than the input device, it must be specified separately.
  • inbufsize=<n>, outbufsize=<n>, bufsize=<n> โ€” specify the buffer size, in samples. This defaults to 1024.
  • innbufs=<n>, outnbufs=<n>, nbufs=<n> โ€” specify the number of buffers to use. This may or may not be used depending on the interface type. It's ignored for file, for instance. Defaults to 100.
  • chans=<n>, inchans=<n>, outchans=<n> โ€” set the number of input and output channels. One of these must be specified, if you say chans it will set both the input and output number of channels. If you only specify in or out, the other is not enabled.
  • inrate=<n>, outrate=<n>, rate=<n> โ€” the sample rate, in samples per second. Common ones are 44100 (CD), 48000 (DAT).
  • inhwrateonly=<n>, outhwrateonly=<n>, hwrateonly=<n> โ€” only allow hardware-supported sample rates. Only supported by ALSA.
  • intype=<n>, outtype=<n>, type=<n> โ€” set the interface type. Must be alsa (Linux only), win (Windows only), portaudio (MacOS, generally) or file. If not set, this default to alsa on Linux, win on Windows, and portaudio no Macos.
  • informat=<n>, outformat=<n>, format=<n> โ€” set the type of data to supply to the user. This is one of: float64, float, s32, s32, s16, or s8. User samples are always signed and either floating point or integer. Floating point is normalized from -1.0 to 1.0. You must specify this.
  • inpformat=<n>, outpformat=<n>, pformat=<n> โ€” the data format on the PCM size. You have all the data formats specified for format above. The integer ones can be prefixed with "u" instead of "s" to make them unsigned (like u16), and all may be suffixed with "le" or "be" to make them little or big endian (like float64_le). If you do not specify this, the gensio will attempt the same format as the user format. If that doesn't work, it will attempt to pick the best matching format and convert.

โœจ soapy โœจ

connecting = soapy[(options)],<device string>

Okay bestie, listen โ€” this one is a gensio that gives you access to SDR devices through the SoapySDR library. It's literally just a thin wrapper around the SoapySDR C interface, that's the whole vibe ๐Ÿ’…

Data formats are exactly like the sound gensio above, except this one also gives you the complex versions. Those use the same name as the sound ones but with a "c" tacked onto the end. Cute, right? ๐ŸŽ€

The device string at the end of the gensio spec is the same thing you'd hand to the "SoapySDRUtil" utility to identify the devices. Go read that for the deets, iykyk ๐Ÿฆ‹

And like the sound gensio, most of the options below come in an "in", an "out", and a plain flavor. They set the receive, transmit, or both channels. Periodt.


  • inchannel=<n>, outchannel=<n> โ€” picks the channel on the SDR device. You must specify at least one of these, no cap ๐Ÿซถ
  • inbufsize=<n>, outbufsize=<n>, bufsize=<n> โ€” sets the buffer size, in samples (not bytes, bestie, don't get it twisted). Defaults to 1024.
  • innbufs=<n>, outnbufs=<n>, nbufs=<n> โ€” how many buffers to use. Defaults to 100.
  • inrate=<n>, outrate=<n>, rate=<n> โ€” the sample rate, in samples per second. You can slap a "K", "M", or "G" on the end for KHz, MHz, and GHz. Lowkey this may be constrained by the particular hardware ๐Ÿ‘‘
  • informat=<n>, outformat=<n>, format=<n> โ€” sets the type of data you get handed. One of: float64, float, s32, s32, s16, or s8, or any of those ending in "c" for complex values. User samples are always signed and either floating point or integer. Floating point is normalized from -1.0 to 1.0. Defaults to floatc. This may be constrained by the particular hardware too โœจ
  • infreq=<n>, outfreq=<n>, freq=<n> โ€” the frequency to tune the SDR to. May not be supported on all devices ๐Ÿ˜ญ. You can put a "K", "M", or "G" on the end for KHz, MHz, and GHz. Defaults to not specified.
  • inbandwidth=<n>, outbandwidth=<n>, bandwidth=<n> โ€” the bandwidth for the channel. May not be supported on all devices. You can put a "K", "M", or "G" on the end for KHz, MHz, and GHz. Defaults to not specified.
  • inagc=<n>, outagc=<n>, agc=<n> โ€” automatic gain control for the channel. May not be supported on all devices. Defaults to off.
  • ingain=<n>, outgain=<n>, gain=<n> โ€” the gain for the channel. May not be supported on all devices. Defaults to not specified.
  • inantenna=<n>, outantenna=<n>, antenna=<n> โ€” which antenna to use for the channel. May not be supported on all devices. Defaults to not specified.
  • in_auto_dc_off[=<true|false>], out_auto_dc_off=..., auto_dc_off=... โ€” enable or disable automatic DC offset. May not be supported on all devices. It defaults to not being set.
  • in_dc_off_i=<n>, out_dc_off_i=..., dc_off_i=..., in_dc_off_q=..., out_dc_off_q=..., dc_off_q=... โ€” sets the DC offset for the I and Q. This is a value from 0-1. May not be supported on all devices. It defaults to not being set.
  • in_auto_iq_bal[=<true|false>], out_auto_iq_bal=..., auto_iq_bal=... โ€” enable or disable automatic DC offset. May not be supported on all devices. It defaults to not being set.
  • in_iq_bal_i=<n>, out_iq_bal_i=..., iq_bal_i=..., in_iq_bal_q=..., out_iq_bal_q=..., iq_bal_q=... โ€” sets the DC offset for the I and Q. This is a value from 0-1. May not be supported on all devices. It defaults to not being set.
  • in_freq_corr=<n>, out_freq_corr=..., freq_corr=... โ€” sets the frequency correction for the oscillator. This is specified in parts-per-million (PPM). May not be supported on all devices. It defaults to not being set. ๐ŸŒธ

๐ŸŽ€ fsk ๐ŸŽ€

connecting = afskmdm[(options)]
connecting = fsk[(options)]

So this one is a filter gensio that sits on top of the sound gensio and implements a Frequency Shift Keying modem, the kind used on AX.25 amateur radio. It has to sit on top of a sound gensio โ€” or, to be more accurate, a gensio that implements the GENSIO_CONTROL_IN_BUFSIZE, GENSIO_CONTROL_OUT_BUFSIZE, GENSIO_CONTROL_IN_FORMAT, and GENSIO_CONTROL_OUT_FORMAT controls. ๐Ÿ’…

If you're using afskmdm, then some parameters get changed from fsk to better match the standard 1200 baud AFSK. Slay.

Heads up bestie: the sound gensio must supply a float user format. No float, no vibes.

๐Ÿ”‘ Keying the Transmitter

By default, fsk will not do anything specific to turn the transmitter on and off. In that case the keying has to be VOX, like a SignaLink. Very hands-off of it.

If you need some other way to key the transmitter, fsk provides a key option. This creates a gensio that gets 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, and setting it "off" will enable the transmitter. Gotta love an inverted situation ๐Ÿ˜ญ

You could do

keytype=rts,key="serialdev,/dev/serial/by-
path/pci-0000:00:14.0-usb-0:4.4.2.4.2:1.0-port0"

to set up an RTS-based serial control on a port. Iconic.

There is a cm108 soundcard GPIO gensio available. See that gensio for details, but you would generally do something like:

keytype=cm108

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

keytype=rw,key="cm108gpio,<dev>"

See the cm108gpio gensio docs for details. ๐Ÿซถ

If you need something more sophisticated, you could create your own program to do the keying and add:

key="stdio,mykeyprog --parm1 --parm2",keyon="1",keyoff="0"

to your fsk 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, okay, nothing extra) to the stdin of the program. When the transmission was complete, it would write a "0". And not numbers โ€” these are "0" and "1" characters, bestie, characters. ๐Ÿ’€

If your program sends output to stderr, you probably want to add stderr-to-stdout to the stdin so the stderr buffers don't fill up and block the program. Then stderr data will be ignored. Vibe check passed. โœ…

You can use rigctld with this. I have the following rigctld setup on my system:

rigctld -m 1022 -r /dev/ttyUSB0 -s 4800 -P CM108 -p /dev/hidraw1

And I use the following in my configuration:

fsk(key="tcp,localhost,4532",keyon="T 1\n",keyoff="T 0\n")

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. Lowkey convenient ๐Ÿ’…

Anything read in from the key gensio is ignored. Noted, moving on.

โš™๏ธ Options

  • outgen=<str> โ€” allow a different output gensio stack to be specified for the output. It will write output to this gensio and use the other child for input only. Main character energy, honestly.
  • rx[=true|false] โ€” enables/disables the receiver. Default is true.
  • tx[=true|false] โ€” enables/disables the transmitter. Default is true.
  • readbuf=<n> โ€” sets the maximum packet size that can be read. Defaults to 256.
  • writebuf=<n> โ€” sets the maximum packet size that can be written. Defaults to 256.
  • drain_timeout=<n> โ€” see the section on Drain Timeout.
  • nchans=<n>, in_nchans=<n>, out_nchans=<n> โ€” specify how many channels are coming from/going to the sound gensio. The sound gensio interleaves the sound for multiple channels. By default this is fetched from the sound gensio. ๐ŸŽง
  • bufsize=<n>, in_bufsize=<n>, out_bufsize=<n> โ€” set the incoming buffer size in frames. This can be useful if the data comes in as complex but is advertised by the child gensio as float, for instance. Sneaky but valid โœจ
  • chan=<n>, in_chan=<n>, out_chan=<n> โ€” which specific channel to use. Only one channel is used for input sound and output sound; the others are ignored or filled with zeros. By default this is zero.
  • out_chans=<n> โ€” specify a bitmask of which channels to output sound on. The same sound will be output for all selected channels. So n=1 will only output on channel 0, n=3 will output on channels 0 and 1, etc. By default only channel 0 is output on. Math girlies rise ๐Ÿงš
  • bps=<n>, in_bps=<n>, out_bps=<n> โ€” sets the data rate in bits per second for the data. Defaults to 2400 for fsk and 1200 for afskmdm.
  • samplerate=<n>, in_samplerate=<n>, out_samplerate=<n> โ€” the sample rate, samples per second, of the data. By default this is fetched from the sound gensio.
  • format=none|float|floatc, in_format=..., out_format=... โ€” sets the data format coming from the child. Defaults to the value fetched from the child. Only supports float (real) and floatc (complex).
  • checkax25[=true|false] โ€” check that the packet is a valid AX.25 packet, that it has valid AX25 length and address. Normally the AX.25 layer does this, but this can be used when testing without an AX.25 layer to reject bad packets. Defaults to false. Note that if you set crc to false, this option will not work. Don't say I didn't warn you ๐Ÿ˜ค
  • crc[=true|false] โ€” check and send CRCs on packets. Normally you want the fsk code to do this, but it can be useful for testing. Defaults to false for fsk, true for afskmdm.
  • inv[=true|false], in_inv=..., out_inv=... โ€” inverts the data on transmit and receive. Default is false for fsk, true for afskmdm.
  • diff[=true|false], in_diff=..., out_diff=... โ€” do differential encoding, where the values are driven by frequency changes, not by frequency values. Default is false for fsk, true for afskmdm. It's giving frequency delta realness ๐ŸŒˆ
  • raw[=true|false] โ€” instead of doing HDLC processing, just stream up the FSK bits to the layer above and accept streamed FSK bits to transmit. No wmsg processing is done with this option. The transmitter is keyed whenever bits are being transmitted.

This forces crc to false. No arguing, no negotiating ๐Ÿ’…

Defaults to true for fsk, false for afskmdm.

  • mark=<n>, in_mark=<n>, out_mark=<n> โ€” sets the mark frequency for FSK, aka the higher one, aka the 1 bit. defaults to 1200 for AFSK. for fsk it defaults to (bps * 4) if freqadj is set, bps if freqadj is not set and the data format is float (real valued), and (bps / 4) if freqadj is not set and the data format is floatc (complex, or I/Q). bestie it's giving math but we move ๐Ÿ’…
  • space=<n>, in_space=<n>, out_space=<n> โ€” sets the space frequency for FSK, the lower one, the 0 bit. defaults to 2200 for afskmdm.

for fsk, this defaults to (mark - bps / 2). and this, combined with the mark default above, defaults to MSK. slay.

see the whole discussion on freqadj down below for the details, no skipping ๐Ÿ‘‘

  • in_freqadj[=true|false], out_freqadj=..., freqadj=... โ€” okay so if freqadj is not set, then for real-valued (float) data we assume the frequency is set so that the mark and space frequencies sit at their appropriate offsets. for complex (I/Q) data, we assume the set frequency is half-way between the mark and the space (which means the space frequency will be negative, yes really).

if freqadj is set, it automatically adjusts the frequency of the child gensio to keep the frequencies away from zero so we dodge those DC offsets. this tells the child gensio to adjust its frequency, and any frequency set later, so that the mark and space frequencies land in the proper location if the set frequency is the center frequency. remember: the frequency you set for FSK on an SDR is generally the center frequency, precisely between the mark and space frequencies.

this is designed to work with the default mark and space settings we just talked about above.

for instance (iconic example incoming): if in_bps is 25000, the in_mark is 100000 and in_space is 87500. if the frequency is 450000000, that means the space frequency is 449993750 and the mark frequency is 450006250. we want to shift the frequency so the space will appear at 87500 after downconversion, so we set the frequency down by -93750, to 449906250, which will put the space at 87500 after downconversion. see how that works? ๐Ÿงš

why do we do all this? to keep our working frequencies away from zero. some SDRs have significant DC bias at zero, and with MSK the space frequency will be very close to zero if it's downconverted so it was at (in_bps / 2). you can do the high pass filter, but that has a significant affect on the space frequency, causing errors especially around certainty calculations. so we downconvert it and move the frequencies up so they aren't close to zero. and we move them up enough to give some room around the frequencies to adjust for frequency deviation.

transmit has the same DC bias issue, so it gets adjusted the same way.

defaults to false. DC bias is a problem on most SDRs, so you almost certainly want to set this if you're sitting on the soapy gensio and using MSK, otherwise you have to do all this calculation yourself. you might be able to adjust the soapy gensio for the DC bias, but honestly it's really more reliable to just set this and not worry about it. periodt.

  • uncert[=true|false] โ€” in raw mode, provide certainty data about each bit that is received, in the auxdata. if this is set, auxdata will have a string starting with "uncert=" followed by a raw void pointer. that pointer is an array of unsigned chars, each ranging from 0-50, giving the uncertainty of the particular bit it is associated with. a 0 means high certainty that the bit is correct. a 50 means there's only a 50% chance the bit is correct. ๐Ÿ˜ญ

to get that array, do something like:

unsigned char *uncertarray = NULL; for (i = 0; auxdata &&
auxdata[i]; i++) {
   if (strncmp(auxdata[i], "uncert=") == 0) {
       memcpy(&uncertarray, auxdata[i] + 7, sizeof(uncertarray));
       break;
   }
}

the array is for each bit in the incoming data, indexed LSB first. so it'll be 8 times the size of the incoming data.

this is ignored if not raw. defaults to false.

  • certmult=<n> โ€” multiplies the certainty by the given floating point value when calculating it. depending on the various settings the certainty will vary, so you'll need to adjust this depending on your settings. this affects the certainty calculations for wmsgs and the uncertainty calculations for delivered raw data. defaults to 1.0 for afskmdm, 100 for fsk.
  • wmsgs=<n> โ€” the number of working receive messages at the same time. if a bit fails the certainty test (see below), then for every current working message, two are created, one with each bit possibility, up to the count specified in this option. these are kept until a message is correctly received (with CRC) or the series of bits is not valid.

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, iykyk. defaults to 1 for fsk, 32 for afskmdm.

  • wmsg-extra=<n> โ€” sets the number of extra amplification levels for the mark and space frequencies to try. if set to 1, an extra complete detection is done with the space frequency amplified by 3dB, then the mark frequency amplified by 3dB. if set to 2, it'll do this with 3dB then 6dB, and so on. this can help compensate for filtering on the different frequencies. defaults to 0 for fsk, 1 for afskmdm.
  • min-certainty=<float> โ€” a floating point number that specifies the minimum certainty above which a bit is considered good. the ratio of the signal power for the mark and space frequencies is calculated as the certainty. if that certainty is below min-certainty, it does the wmsgs procedure above. defaults to 2.0.
  • filttype=[fir|iir|none] โ€” a 2nd-order low-pass Butterworth IIR filter or a low-pass FIR filter is implemented on the input. this selects which filter, or no filter. the IIR filter doesn't use very much CPU; the FIR filter uses a lot of CPU at higher sample rates. this is mostly for experimentation. the default is IIR for sample rates above 30000Hz and FIR for lower sample rates. lower sample rates don't work well with the IIR filter, but there's not much difference at higher sample rates.
  • lpcutoff=<n> โ€” sets the cutoff frequency for the input low pass filter. setting it to zero disables it. the default is 2300 for afskmdm. for fsk this defaults to zero. generally you can set the bandwidth on the SDR so you don't have to filter here. no cap.

heads up though: this is not a complex filter. it's a simple real filter; on complex data it's applied to the I and Q equally. which means on complex input, if you set it to, say, 10KHz, it will filter at above 10KHz and below -10KHz.

  • lpgain=<n> โ€” sets the gain for the low-pass filter. defaults to .9.
  • hpcutoff=<n> โ€” sets the cutoff frequency for the input high pass filter. setting it to zero disables it. the default is 0. this is useful if you have DC bias or low frequency noise on the signal.

like the lpcutoff low pass filter, this is a simple real filter. go read that one for the details ๐Ÿซถ

  • lpgain=<n> โ€” sets the gain for the low-pass filter. defaults to .9. (yes bestie, this one shows up twice in the original. not my fault, I just report what's there ๐Ÿ’€)
  • maxadj=<n> โ€” the input processing adjusts the frame position to keep it aligned with the incoming data. this sets the maximum it can move the frame position (in frames) on each adjustment. if 0 (the default) this will be automatically calculated. don't mess with this unless you really know what you are doing.
  • trfreq=<n> โ€” sets the transition frequency width for the FIR filter. this is ignored for the IIR filter. the default is 500Hz. smaller numbers make a sharper cutoff but result in more CPU being used.
  • tx-preamble=<n> โ€” the time in milliseconds at the beginning of a message that is just flags. this lets the transmitter turn on and the other end synchronize. default is 300.
  • tx-tail=<n> โ€” the time in milliseconds at the end of the message that is just flags. default is 100.
  • tx-predelay=<n> โ€” the amount of time to wait after another sender has finished sending a message before the transmit is started. default is 500ms.
  • volume=<float> โ€” sets the transmit volume, from 0.0-1.0. values above 1.0 will clip. default is .75.
  • key=<gensio string> โ€” this creates a gensio that will be used to key the transmitter on and off. see the discussion above on keying for more details.
  • keytype=rw|rts|rtsinv|dtr|dtrinv|cm108 โ€” sets the type of keying done, per the discussion above. this is rw by default.
  • keyon=<string>, keyoff=<string> โ€” the strings to send to the key gensio to turn the transmitter on and off. for keytype=rw only. the default is "T 10 and "T 00 by default, which is what rigctld takes.
  • keybit=<n> โ€” sets the bit number to use on the cm108 keytype. this is 3 by default.
  • full-duplex[=yes|no] โ€” treat the read and write streams as completely independent. transmit does not check if something is being received before sending; all sends just start immediately. main character energy fr fr ๐Ÿ”ฅ

๐Ÿ’– hdlc ๐Ÿ’–

connecting = hdlc[(options)]

Okay bestie, this one is an HDLC coder/decoder, and its whole personality is that it wants to sit on top of a modem โ€” like fsk โ€” in raw mode and just do HDLC for you. ๐Ÿ’…

Heads up though: the data coming out of the bottom of this thing is gonna be bigger than what goes in the top, because it does a preamble, a postamble, and bit stuffing. (It's giving โœจ extra โœจ, but it's math, not drama.) Here's the equation for the size below:

size = (<max_size> + 2) + (((<max_size> + 2) * 8) / 5 + 7) / 8

The + 2 is for the CRC, then you convert to bits and divide by 5 to get the maximum total number of stuffed zero bits, then add 7 for proper rounding when converting back to bytes, and then convert to bytes. This applies to both read and write. No cap. ๐Ÿงฎ

This gensio supports nbits on the lower interface only. Peep the "nbits in auxdata" section of this document for the deets.


โœจ Options โœจ

  • readbuf=<n> โ€” Sets the maximum packet size that can be read. Defaults to 256. ๐ŸŽ€
  • writebuf=<n> โ€” Sets the maximum packet size that can be written. Defaults to 256.
  • checkax25[=true|false] โ€” Check that the packet is a valid AX.25 packet, that it has valid AX25 length and address. Normally the AX.25 layer does this, but you can use this when you're testing without an AX.25 layer to reject bad packets. Defaults to false. โš ๏ธ Note that if you set crc to false, this option will not work. (It's giving broken-promise energy, so don't do that.)
  • crc[=true|false] โ€” Check and send CRCs on packets. Normally you want the fsk code to do this, but it can be useful for testing. Defaults to true.
  • wmsgs=<n> โ€” The number of working receive messages at the same time. If the bit fails the certainty test (see below), for every current working message, two are created, one with each bit possibility, up to the count specified in this option. These are kept until a message is correctly received (with CRC) or the series of bits is not valid.

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. ๐Ÿคฏ Iconic behavior honestly.

Note that you have to have uncertainty values from the layer below for this to be meaningful. If you don't have that, values above 1 here don't have any meaning.

Defaults to 32.

  • max-uncertainty โ€” The maximum uncertainty from the modem that makes the bit questionable and invokes the wmsg processing. Any value below this is considered good.

Uncertainty from the modem is a value from 0-100, where 0 means the value is sure (0% uncertain, or 100% certain). Values will only range from 0-50, though, because if it were more than 50% uncertain it would chose the other value. Defaults to 25.

  • tx-preamble โ€” The number of sync characters (0x7e) to send at the beginning of a packet to allow the receiver to detect the packet and align itself. Defaults to 25.
  • tx-postamble โ€” The number of sync characters to send at the end of a packet to mark the end. Defaults to 2.

๐Ÿฆ‹ convcode ๐Ÿฆ‹

connecting = convcode(options)

This is a convolutional coder/decoder, bestie. It's designed to sit on top of a packet receiver/transmitter, and it deals with packets, not streaming data. ๐Ÿ“ฆ

You must set k and the polynomials with p. No skipping, that's the whole thing.

The data coming into/out of the bottom of the coder will be more than double the size of the data on the top. The equation is:

<size in bits> = (<top size in bytes> + (k - 1)) * 8 * 2

This gensio supports nbits on the upper and lower interface. See the "nbits in auxdata" section of this document for details. ๐ŸŒˆ


โœจ Options โœจ

  • readbuf=<n> โ€” Sets the maximum packet size that can be read. Defaults to 256.
  • writebuf=<n> โ€” Sets the maximum packet size that can be written. Defaults to 256.
  • k=<n> โ€” Set the polynomial length for the coding.
  • p=<n> โ€” Add a polynomial to the set of polynomials. You can have a coder with up to 8 polynomials. (Eight!! That's a whole squad. ๐Ÿซถ)

๐Ÿ”ฅ axfec ๐Ÿ”ฅ

connecting = axfec(options)

This one is an interleaver, convolutional coder, and HDLC processor designed to work with Onsemi's AX5043 radio in FEC mode. It's pretty specific, and there are lots of caveats to making it work properly here and on the AX5043. These pieces cannot be easily separated. End of packet detection, alignment, and initial synchronization all kind of tie together into one state machine and require all the parts to interchange information pretty tightly. The coupling is just too high to split it up. (They're codependent, your honor. ๐Ÿ˜ญ)

This is here primarily for the AMSAT PacSat satellites which use the AX5043 with FEC. But if you are using the AX5043 with FEC, well, you don't want to have to write all this code yourself. Trust. ๐Ÿ’…

But it's also a general-purpose layer for doing FEC on a data link, and works well for that. Versatile queen. ๐Ÿ‘‘


โœจ Options โœจ

  • readbuf=<n> โ€” Sets the maximum packet size that can be read. Defaults to 256.
  • writebuf=<n> โ€” Sets the maximum packet size that can be written. Defaults to 256.
  • tx-preamble โ€” The number of sync characters (0x7e) to send at the beginning of a packet to allow the receiver to detect the packet and align itself. Defaults to 15.

๐ŸŽ€ cm108gpio ๐ŸŽ€

connecting = cm108gpio[(options)],<soundcard>

This lets a GPIO on a cm108 soundcard 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. Sorry babe, it's just not on the menu.

โœจ Options โœจ

  • bit=1-8 โ€” The bit number to control. The default is 3.

๐ŸŒธ Forking and gensios ๐ŸŒธ

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:

gensio_disable(io)

on all the gensios that fork is not using, then free the gensios. Don't use close, use free. Then you should call:

gensio_acc_disable(acc)

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. Periodt. โœ‹

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[k], 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. ๐Ÿ”ฅ

๐Ÿ’€ Not calling handle_fork() is the kind of chaos that ends friendships, bestie. Just call it.

โญ SEE ALSO โญ

gensiot(1), sctp(7), udp(7), tcp(7), unix(7), gensio_open(3), gensio_close(3), gensio_control(3), gensio_set_sync(3), gensio_write(3), gensio_acc_set_sync(3), gensio_acc_control(3), gensio_os_funcs(3), str_to_gensio(3), str_to_gensio_accepter(3), gensio_set_read_callback_enable(3), gensio_err(3), gensio_mdns(3), gensio_get_type(3), gensio_acc_get_type(3), gensio_acc_set_accept_callback_enable(3), gensio_event(3), gensio_accepter_event(3), gensio_acc_shutdown(3), gensio_set_callback(3) gensio_acc_set_callback(3), gensio_add_default(3), gensio_alloc_channel(3), gensio_set_log_mask(3), gensio_acc_startup(3), gensio_acc_shutdown(3), gensio_bswap(3), gensio_atomics(3), gensio_refcount(3)


๐Ÿ’œ KNOWN PROBLEMS ๐Ÿ’œ

None. (Not a single one. We love a flawless queen. โœจ)


๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Corey Minyard <minyard@acm.org>


And that's the whole entire page, bestie!! ๐ŸŽ‰ We did it. Every last gensio, every last option, every last caveat โ€” all of it. This final part came out of the gensio(5) manpage, "Specifying a gensio," dated 01/02/19, so shoutout to that version and that date for being the source of all this lore. Go forth and connect things, you gorgeous genius. ๐ŸŒˆ๐Ÿฆ‹๐Ÿ’–๐Ÿ“

๐Ÿ“ฆ where this page comes from