Esto es gensio (pronunciado gen'-see-oh), un marco para brindar una vista consistente de varios tipos de E/S de flujo (y paquetes). Creas un objeto gensio (o un gensio) y puedes usar ese gensio sin tener que saber demasiado sobre lo que sucede debajo. Puede apilar gensio encima de otro para agregar funcionalidad de protocolo. Por ejemplo, puede crear un gensio TCP, apilar SSL encima y apilar Telnet encima. Admite varios puertos serie y E/S de red. También admite interfaces de sonido. Los gensios que se acumulan sobre otros gensios se denominan filtros.
Puedes hacer lo mismo con los puertos receptores. Puede configurar un aceptador gensio para aceptar conexiones en una pila. Entonces, en nuestro ejemplo anterior, puede configurar TCP para escuchar en un puerto específico y apilar automáticamente SSL y Telnet encima cuando se establezca la conexión, y no se le informará hasta que todo esté listo.
gensio funciona en Linux, BSD, MacOS y Windows. En Windows, le brinda una interfaz controlada por eventos con capacidad para un solo subproceso (pero también con capacidad para múltiples subprocesos) (con interfaces de bloqueo disponibles) para simplificar la programación con muchas E/S. Contribuye en gran medida a facilitar la escritura de código portátil controlado por E/S.
Una característica muy importante de gensio es que hace que establecer conexiones cifradas y autenticadas sea mucho más fácil que sin él. Más allá de la gestión básica de claves, en realidad no es más difícil que TCP o cualquier otra cosa. Ofrece mayor flexibilidad para controlar el proceso de autenticación si es necesario. Es realmente fácil de usar.
Tenga en cuenta que la página de manual gensio(5) tiene más detalles sobre los tipos individuales de gensio.
Para obtener instrucciones sobre cómo crear esto desde el código fuente, consulte la sección "Construcción" al final.
Hay un par de herramientas disponibles que utilizan gensios, tanto como ejemplo como para probar cosas. Estos son:
Un demonio similar a sshd que utiliza certauth, ssl y SCTP o TCP gensios para realizar conexiones. Utiliza autenticación PAM estándar y utiliza ptys. Consulte gtlsshd(8) para obtener más detalles.
Hay un elemento en Preguntas frecuentes. Primero llamado "Cómo ejecutar gtlsshd en Windows", consulte ese y la sección Construyendo en Windows a continuación para obtener más detalles, ya que hay algunas cosas difíciles que debe manejar.
Los siguientes gensios están disponibles en la biblioteca:
Un aceptador gensio que toma una cadena de pila gensio como parámetro. Esto le permite utilizar un gensio como aceptador. Cuando se inicia conacc, abre gensio, y cuando se abre gensio informa un nuevo hijo para el aceptador. Cuando el niño cierra, intenta abrirlo nuevamente y realizar el proceso nuevamente (a menos que las aceptaciones hayan sido deshabilitadas en conacc).
¿Por qué querrías usar esto? Digamos que en ser2net desea conectar un puerto serie a otro. Podrías tener una conexión como:
connection : &con0
accepter : conacc,serialdev,/dev/ttyS1,115200
connector : serialdev,/dev/ttyS2,115200
Y conectaría /dev/ttyS1 a /dev/ttyS2. Sin conacc, no se podría utilizar serialdev como aceptador. También le permitiría usar gtlsshd en un puerto serie si quisiera inicios de sesión autenticados cifrados a través de un puerto serie. Si ejecutó gtlsshd con lo siguiente:
gtlsshd --notcp --nosctp --oneshot --nodaemon --other_acc
' conacc,relpkt(mode=server),msgdelim,/dev/ttyUSB1,115200n81 '
Podrías conectarte con:
gtlssh --transport ' relpkt,msgdelim,/dev/ttyUSB2,115200n81 ' USB2
Esto crea un transporte de paquetes confiable a través de un puerto serie. Se requiere mode=server para que relpkt se ejecute como servidor, ya que normalmente se ejecutaría como cliente ya que no se inicia como aceptador. El ssl gensio (que se ejecuta a través del transporte) requiere una comunicación confiable, por lo que no se ejecutará directamente a través de un puerto serie.
Sí, parece un revoltijo de letras.
Un filtro gensio que se coloca encima del gensio de sonido y realiza un módem de codificación por cambio de frecuencia de audio, como el que se usa en la radioafición AX.25.
Un protocolo de radioaficionado para radio por paquetes. Para utilizar esto por completo, necesitaría escribir código, ya que utiliza canales y datos oob para información innumerable, pero puede hacer cosas básicas solo con gensiot si todo lo que necesita es un canal de comunicación. Por ejemplo, si desea conversar con alguien por radio y el puerto de besos está en 8001 en ambas máquinas, en la máquina receptora puede ejecutar:
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),kiss,conacc,tcp,localhost,8001 '
el cual se conectará al TNC y esperará una conexión en la dirección AE5KM-1. Entonces podrías ejecutar:
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,localhost,8001 '
en la otra máquina. Esto se conectará a la otra máquina a través del TNC 0 con la dirección proporcionada. Luego, todo lo que escriba en uno aparecerá en el otro, línea por línea. Escribe "Ctrl-D" para salir. La parte 'stdio(self)' desactiva el modo sin procesar, por lo que es una línea a la vez y obtienes eco local. De lo contrario, cada carácter que escriba enviaría un paquete y no podría ver lo que estaba escribiendo.
Para conectar al sistema BBS N5COR-11 AX.25, harías:
gensiot -i ' xlt(nlcr),stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,N5COR-11,AE5KM-2"),kiss,tcp,localhost,8001 '
La mayoría de los sistemas BBS usan CR, no NL, para la nueva línea, por lo que xlt gensio se usa para traducir estos caracteres entrantes.
Por supuesto, al ser gensio, puedes poner cualquier gensio viable debajo de ax25 que desees. Entonces, si desea probar o probar sin una radio, puede utilizar ax25 a través de multidifusión UDP. Aquí está el lado del aceptador:
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),conacc, '
' udp(mcast="ipv4,224.0.0.20",laddr="ipv4,1234",nocon), '
' ipv4,224.0.0.20,1234 '
y aquí está el lado del conector:
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"), '
' udp(mcast="ipv4,224.0.0.20",laddr="ipv4,1234",nocon), '
' ipv4,224.0.0.20,1234 '
Kiss no es necesario porque UDP ya es un medio orientado a paquetes. O puede utilizar el programa greflector para crear una situación de radio simulada. En la máquina "radiopi2", ejecute:
greflector kiss,tcp,1234
lo que creará un programa que reflejará todas las entradas recibidas en todas las demás conexiones. Luego en el lado del aceptador:
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),kiss,conacc,tcp,radiopi2,1234 '
y el lado de conexión:
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,radiopi2,1234 '
El código de prueba utiliza el reflector para algunas pruebas, ya que es muy conveniente de usar.
Todos estos están documentados en detalle en gensio(5). A menos que se indique lo contrario, todos ellos están disponibles como aceptadores o gensios de conexión.
Puedes crear tus propios gensios y registrarlos en la biblioteca y apilarlos junto con los otros gensios.
La forma más sencilla de hacer esto es robar código de un gensio que haga lo que usted desea y luego modificarlo para crear su propio gensio. Desafortunadamente, no existe buena documentación sobre cómo hacer esto.
El archivo de inclusión include/gensio/gensio_class.h tiene la interfaz entre la biblioteca principal de gensio y gensio. Todas las llamadas de gensio llegan a través de una única función con números para identificar la función que se solicita. Tienes que asignar todo esto a las operaciones reales. Esto es algo doloroso, pero facilita mucho la compatibilidad hacia adelante y hacia atrás.
Crear tu propio gensio de esta manera es bastante complejo. La máquina de estados para algo como esto puede ser sorprendentemente compleja. La limpieza es la parte más difícil. Debe asegurarse de no recibir todas las devoluciones de llamada y que no se pueda volver a llamar a ningún temporizador en una condición de carrera al apagar. Sólo los gensios más simples (echo, dummy), los gensios extraños (conadd, keepopen, stdio) y los gensios que tienen canales (mux, ax25) implementan directamente la interfaz. Todo lo demás utiliza include/gensio/gensio_base.h. gensio_base proporciona la máquina de estados básica para un gensio. Tiene una porción de filtro (que es opcional) y una porción de bajo nivel (ll), que no lo es.
La interfaz de filtro pasa datos a través de ella para el procesamiento. Esto se usa para cosas como ssl, certauth, ratelimit, etc. Filter gensios usaría esto. Todos estos usan gensio_ll_gensio (para apilar un gensio encima de otro gensio) para el ll.
Los gensios terminales tienen cada uno su propio ll y generalmente no tienen filtro. Para lls basados en un descriptor de archivo (fd), se utiliza gensio_ll_fd. También hay un ll para IPMI serial-over-lan (ipmisol) y para sonido. La mayoría de los terminales gensios (tcp, udp, sctp, serial port, pty) usan fd ll, obviamente.
Una vez que tenga un gensio, puede compilarlo como un módulo y pegarlo en $(moduleinstalldir)/<version>. Entonces el gensio simplemente lo recogerá y lo utilizará. También puede vincularlo con su aplicación y realizar la función de inicio desde su aplicación.
Ya se ha analizado mdns gensio, pero la biblioteca gensio proporciona una interfaz mDNS fácil de usar. El archivo de inclusión está en gensio_mdns.h, y puede usar la página de manual gensio_mdns(3) para obtener más información al respecto.
Para realizar una conexión mdns usando gensiot, digamos que tiene ser2net configurado con mdns habilitado como:
connection : &my-port
accepter : telnet(rfc2217),tcp,3001
connector : serialdev,/dev/ttyUSB1,115200N81
options :
mdns : true
entonces puedes conectarlo con gensiot:
gensiot ' mdns,my-port '
gensiot encontrará el servidor, el puerto y si telnet y rfc2217 están habilitados y realizará la conexión.
Además, existe una herramienta gmdns que permite realizar consultas y publicidad, y gtlssh puede realizar consultas mDNS para encontrar servicios. Si tiene inicios de sesión autenticados seguros para ser2net y habilita mdns en ser2net, como:
connection : &access-console
accepter : telnet(rfc2217),mux,certauth(),ssl,tcp,3001
connector : serialdev,/dev/ttyUSBaccess,115200N81
options :
mdns : true
hace que la configuración sea muy conveniente, ya que puedes simplemente hacer:
gtlssh -m access-console
Así es, puede usar directamente el nombre de la conexión, sin necesidad de conocer el host, si telnet o rfc2217 están habilitados o cuál es el puerto. Aún debe configurar las claves y demás en el servidor ser2net, por supuesto, según esas instrucciones.
gensio tiene una interfaz orientada a objetos controlada por eventos. También están disponibles interfaces síncronas. En gensio se trata de dos objetos principales: un gensio y un aceptador de gensio. Un gensio proporciona una interfaz de comunicación donde se puede conectar, desconectar, escribir, recibir, etc.
Un aceptador gensio le permite recibir conexiones entrantes. Si entra conexión te da un gensio.
La interfaz está controlada por eventos porque, en su mayor parte, no bloquea en absoluto. Si abre un gensio, le da una devolución de llamada que se llamará cuando la conexión esté activa o la conexión falle. Lo mismo para cerrar. Una escritura devolverá el número de bytes aceptados, pero es posible que no tome todos los bytes (o incluso ninguno de los bytes) y la persona que llama debe tenerlo en cuenta.
Las interfaces de apertura y cierre tienen una interfaz de bloqueo secundaria para mayor comodidad. Estos terminan en _s. Esto es por conveniencia, pero no es necesario y el uso de estos debe ser cuidadoso porque realmente no puedes usarlos desde las devoluciones de llamada.
Hablando de devoluciones de llamada, los datos y la información que llegan de gensio al usuario se realizan con una función de devolución de llamada. Lee datos y, cuando el gensio está listo para escribir, los datos regresan en una devolución de llamada. Se utiliza una interfaz similar para llamar desde el usuario a la capa gensio, pero está oculta para el usuario. Este tipo de interfaz es fácilmente extensible, se pueden agregar fácilmente nuevas operaciones sin romper las interfaces antiguas.
La biblioteca proporciona varias formas de crear un gensio o un aceptador de gensio. La forma principal es str_to_gensio() y str_to_gensio_accepter(). Estos proporcionan una manera de especificar una pila de gensios o aceptadores como una cadena y construir. En general, deberías utilizar esta interfaz si puedes.
En general, las interfaces que no dependen del rendimiento están basadas en cadenas. Verás esto en gensio_control y en datos auxiliares en la interfaz de lectura y escritura para controlar ciertos aspectos de la escritura.
La biblioteca también proporciona formas de configurar tus gensios creando cada uno individualmente. En algunas situaciones, esto puede ser necesario, pero limita la capacidad de utilizar nuevas funciones de la biblioteca gensio a medida que se amplía.
Si un gensio admite múltiples flujos (como SCTP), los números de flujo se pasan en los datos auxiliares con "stream=n". Los arroyos no tienen control de flujo individual.
Los canales, por otro lado, son flujos de datos separados a través de la misma conexión. Los canales se representan como gensios separados y se pueden controlar el flujo individualmente.
Hay algunos archivos de inclusión con los que quizás tengas que lidiar cuando uses gensios:
Estos están en su mayor parte documentados en las páginas de manual.
Para crear sus propios gensios, los siguientes archivos de inclusión están disponibles:
Cada archivo de inclusión tiene mucha documentación sobre las llamadas y los controladores individuales.
gensio tiene su propio conjunto de errores para abstraerlo de los errores del sistema operativo (llamado GE_xxx) y proporcionar más flexibilidad en el informe de errores. Estos están en el archivo de inclusión gensio_err.h (incluido automáticamente desde gensio.h) y pueden traducirse de números a una cadena significativa con gensio_err_to_str(). Se define que cero no es un error.
Si se produce un error del sistema operativo no reconocido, se devuelve GE_OSERR y se informa un registro a través de la interfaz de registro del controlador del sistema operativo.
Una cosa un poco molesta de gensio es que requiere que usted proporcione un controlador de sistema operativo (struct gensio_os_funcs) para manejar funciones de tipo sistema operativo como asignación de memoria, mutex, la capacidad de manejar descriptores de archivos, temporizadores y tiempo, y algunas otras cosas.
La biblioteca proporciona varios controladores de sistema operativo. Puede llamar a gensio_alloc_os_funcs() para asignar uno predeterminado para su sistema (POSIX o Windows). Puede ver esa página de manual para obtener más detalles. Esta será generalmente la opción de mejor rendimiento que tenga para su sistema.
Para sistemas POSIX, hay disponibles controladores de sistema operativo para glib y TCL, asignados con gensio_glib_funcs_alloc() y gensio_tcl_funcs_alloc(). Estos realmente no funcionan muy bien, especialmente desde el punto de vista del rendimiento, las API para glib y TCL no están bien diseñadas para lo que hace gensio. TCL solo puede admitir operaciones de un solo subproceso. La operación multiproceso simplista solo tiene un subproceso a la vez esperando E/S. Pero funcionan y las pruebas se realizan con ellos. Estos no están disponibles en Windows debido a abstracciones deficientes en simplismo y a la falta de motivación en TCL.
Pero si está utilizando algo más como X Windows, etc. que tiene su propio bucle de eventos, es posible que necesite adaptar uno a sus necesidades. Pero lo bueno es que puedes hacer esto e integrar gensio con prácticamente cualquier cosa.
También hay una interfaz de camarero que proporciona una manera conveniente de esperar a que ocurran cosas mientras se ejecuta el bucle de eventos. Así es como generalmente ingresa al bucle de eventos, porque proporciona una forma conveniente de indicar cuándo ha terminado y necesita salir del bucle.
La documentación para esto se encuentra en:
incluir/gensio/gensio_os_funcs.h
La biblioteca gensio es totalmente compatible con subprocesos y es completamente segura para subprocesos. Sin embargo, utiliza señales en el sistema POSIX y COM en sistemas Windows, por lo que se requiere cierta configuración.
El hilo "principal" debe llamar a gensio_os_proc_setup() al inicio y llamar a gensio_os_proc_cleanup() cuando esté completo. Esto configura señales y controladores de señales, almacenamiento local de subprocesos en Windows y otro tipo de cosas.
Puede generar nuevos hilos a partir de un hilo que ya esté configurado usando gensio_os_new_thread(). Esto le brinda un subproceso básico del sistema operativo y está configurado correctamente para gensio.
Si tiene un hilo creado por otros medios que desea usar en gensio, siempre y cuando el hilo cree otro hilo y no realice ninguna función de bloqueo (cualquier tipo de espera, procesamiento en segundo plano, funciones que terminan en _s como read_s, etc.) no es necesario configurarlos. De esa manera, algún hilo externo puede escribir datos, activar otro hilo o hacer cosas así.
Si un hilo externo necesita hacer esas cosas, debería llamar a gensio_os_thread_setup().
Como se menciona en la sección de subprocesos, la biblioteca gensio en Unix utiliza señales para reactivaciones entre subprocesos. Miré detenidamente y realmente no hay otra forma de hacerlo de manera limpia. Pero Windows también tiene un par de cosas similares a señales, y también están disponibles en gensio.
Si usa gensio_alloc_os_funcs(), obtendrá funciones del sistema operativo utilizando la señal pasada para IPC. Puede pasar GENSIO_OS_FUNCS_DEFAULT_THREAD_SIGNAL para la señal si desea el valor predeterminado, que es SIGUSR1. La señal que uses será bloqueada y asumida por gensio, no podrás usarla.
gensio también proporciona un manejo genérico para algunas señales. En Unix, manejará SIGHUP a través de la función gensio_os_proc_register_reload_handler().
En Windows y Unix, puede usar gensio_os_proce_register_term_handler(), que manejará las solicitudes de terminación (SIGINT, SIGTERM, SIGQUIT en Unix) y gensio_os_proc_register_winsize_handler() (SIGWINCH en Unix). La forma en que llegan a través de Windows es un poco más complicada, pero invisible para el usuario.
Todas las devoluciones de llamada provienen de una rutina de espera, no del controlador de señales. Eso debería simplificar mucho tu vida.
Puede consultar las páginas de manual para obtener más detalles sobre todo esto.
Para crear un gensio, la forma general de hacerlo es llamar str_to_gensio()
con una cadena con el formato adecuado. La cadena tiene el formato siguiente:
<tipo>[([<opción>[,<opción[...]]])][,<tipo>...][,<opción final>[,<opción final>]]
La end option
es para gensios terminales, o los que están en la parte inferior de la pila. Por ejemplo, tcp,localhost,3001
creará un gensio que se conecta al puerto 3001 en localhost. Para un puerto serie, un ejemplo es serialdev,/dev/ttyS0,9600N81
creará una conexión al puerto serie /dev/ttyS0.
Esto le permite apilar capas gensio encima de capas gensio. Por ejemplo, para superponer telnet a una conexión TCP:
telnet,tcp,localhost,3001
Supongamos que desea habilitar RFC2217 en su conexión telnet. Puedes agregar una opción para hacer eso:
telnet(rfc2217=true),tcp,localhost,3001
Cuando creas un gensio, proporcionas una devolución de llamada con datos del usuario. Cuando ocurren eventos en un gensio, se llamará la devolución de llamada para que el usuario pueda manejarlo.
Un aceptador gensio es similar a un gensio de conexión, pero con str_to_gensio_accepter()
en su lugar. El formato es el mismo. Por ejemplo:
telnet(rfc2217=true),tcp,3001
creará un aceptador TCP con telnet en la parte superior. Para los aceptantes, generalmente no es necesario especificar el nombre de host si desea vincularse a todas las interfaces en la máquina local.
Una vez que haya creado un gensio, aún no está abierto ni operativo. Para usarlo, debes abrirlo. Para abrirlo, haga:
struct gensio * io ;
int rv ;
rv = str_to_gensio ( "tcp,localhost,3001" , oshnd ,
tcpcb , mydata , & io );
if ( rv ) { handle error }
rv = gensio_open ( io , tcp_open_done , mydata );
if ( rv ) { handle error }
Tenga en cuenta que cuando gensio_open()
regresa, gensio no está abierto. Debe esperar hasta que se llame a la devolución de llamada ( tcp_open_done()
en este caso). Después de eso, puedes usarlo.
Una vez que el gensio esté abierto, no obtendrá ningún dato inmediatamente porque la recepción está desactivada. Debe llamar a gensio_set_read_callback_enable()
para activar y desactivar si se llamará la devolución de llamada ( tcpcb
en este caso) cuando se reciban datos.
Cuando se llama al controlador de lectura, se pasan el búfer y la longitud. No es necesario que maneje todos los datos si no puede. Debes actualizar el buflen con la cantidad de bytes que realmente manejaste. Si no maneja datos, los datos no manejados se almacenarán en gensio para más adelante. No es que si no maneja todos los datos, deba desactivar la habilitación de lectura o el evento se llamará nuevamente inmediatamente.
Si algo sale mal en una conexión, se llama al controlador de lectura con un conjunto de errores. buf
y buflen
serán NULL en este caso.
Para escribir, puede llamar gensio_write()
para escribir datos. Puedes usar gensio_write()
en cualquier momento en un gensio abierto. Es posible que gensio_write()
no tome todos los datos que escriba. El parámetro count
devuelve el número de bytes realmente tomados en la llamada de escritura.
Puede diseñar su código para llamar a gensio_set_write_callback_enable()
cuando tenga datos para enviar y gensio llamará a la devolución de llamada lista para escribir y podrá escribir desde la devolución de llamada. Generalmente esto es más simple, pero habilitar y deshabilitar la devolución de llamada de escritura agrega cierta sobrecarga.
Un enfoque más eficiente es escribir datos cuando sea necesario y tener la devolución de llamada de escritura deshabilitada. Si la operación de escritura devuelve menos que la solicitud completa, el otro extremo tiene control de flujo y debe habilitar la devolución de llamada de escritura y esperar hasta que se llame antes de enviar más datos.
En las devoluciones de llamada, puede obtener los datos del usuario que pasó a la llamada de creación con gensio_get_user_data()
.
Tenga en cuenta que si abre y cierra inmediatamente un gensio, esto está bien, incluso si no se ha llamado a la devolución de llamada abierta. Sin embargo, en ese caso es posible que se llame o no a la devolución de llamada abierta, por lo que puede ser difícil manejar esto correctamente.
Puedes realizar E/S síncronas básicas con gensios. Esto es útil en algunas situaciones en las que necesitas leer algo en línea. Para hacer esto, llame a:
err = gensio_set_sync ( io );
El gensio dado dejará de entregar eventos de lectura y escritura. Se entregan otros eventos. Entonces puedes hacer:
err = gensio_read_s ( io , & count , data , datalen , & timeout );
err = gensio_write_s ( io , & count , data , datalen , & timeout );
El recuento se establece en el número real de bytes leídos/escritos. Puede ser NULL si no te importa (aunque eso no tiene mucho sentido para leer).
El tiempo de espera puede ser NULL; de ser así, espere una eternidad. Si establece un tiempo de espera, se actualiza según la cantidad de tiempo restante.
Tenga en cuenta que las señales harán que estos regresen inmediatamente, pero no se informa ningún error.
Las lecturas se bloquearán hasta que entren algunos datos y los devuelvan. No espera hasta que el búfer esté lleno. El tiempo de espera es un valor de tiempo, la lectura esperará esa cantidad de tiempo para que se complete y regrese. Un tiempo de espera no es un error, el recuento simplemente se pondrá a cero.
Bloque de escritura hasta que se escribe todo el búfer o se agota el tiempo de espera. Nuevamente, el tiempo de espera no es un error, el total de bytes realmente escritos se devuelve en recuento.
Una vez que haya terminado de realizar E/S síncronas con un gensio, llame a:
err = gensio_clear_sync ( io );
y la entrega a través de la interfaz del evento continuará como antes. No debe estar en una llamada de lectura o escritura sincrónica al llamar a esto, los resultados no estarán definidos.
Tenga en cuenta que se seguirán produciendo otras E/S en otros gensios mientras se espera una E/S sincrónica.
Actualmente no existe una manera de esperar varios gensios con E/S sincrónicas. Si está haciendo eso, debería utilizar la E/S controlada por eventos. Es más eficiente y, de todos modos, al final terminas haciendo lo mismo.
Al igual que un gensio, un aceptador de gensio no está operativo cuando lo creas. Debes llamar gensio_acc_startup()
para habilitarlo:
struct gensio_accepter * acc ;
int rv ;
rv = str_to_gensio_accepter ( "tcp,3001" , oshnd ,
tcpacccb , mydata , & acc );
if ( rv ) { handle error }
rv = gensio_startup ( acc );
if ( rv ) { handle error }
Tenga en cuenta que no hay una devolución de llamada a la llamada de inicio para saber cuándo está habilitada, porque no hay una necesidad real de saberlo porque no puede escribir en ella, solo realiza devoluciones de llamada.
Incluso después de iniciar el aceptador, no hará nada hasta que llame gensio_acc_set_accept_callback_enable()
para habilitar esa devolución de llamada.
Cuando se llama a la devolución de llamada, le proporciona un gensio en el parámetro data
que ya está abierto con la lectura deshabilitada. Un gensio recibido de un aceptador de gensio puede tener algunas limitaciones. Por ejemplo, es posible que no pueda cerrarlo y volver a abrirlo.
Los aceptadores gensio pueden realizar aceptaciones sincrónicas usando gensio_acc_set_sync()
y gensio_acc_accept_s
. Consulte las páginas de manual sobre estos para obtener más detalles.
struct gensio_os_funcs
tiene una devolución de llamada de vlog para manejar registros internos de gensio. Estos se llaman cuando sucede algo importante pero gensio no tiene forma de informar un error. También se puede llamar para facilitar el diagnóstico de un problema cuando algo sale mal.
Cada una de las clases de aceptador gensio y gensio tiene subclases para manejar E/S serie y configurar todos los parámetros asociados con un puerto serie.
Puedes descubrir si un gensio (o cualquiera de sus hijos) es un puerto serie llamando gensio_to_sergensio()
. Si eso devuelve NULL, no es un sergensio y ninguno de sus hijos es sergensio. Si devuelve un valor no NULL, devuelve el objeto sergensio para que lo utilice. Tenga en cuenta que el gensio devuelto por sergensio_to_gensio()
será el que se pase a gensio_to_sergensio()
, no necesariamente el gensio con el que sergensio está directamente asociado.
Un sergensio puede ser un cliente, lo que significa que puede establecer configuraciones en serie, o puede ser un servidor, lo que significa que recibirá configuraciones en serie desde el otro extremo de la conexión.
La mayoría de los sergensios son sólo cliente: serialdev (puerto serie normal), ipmisol y stdio aceptador. Actualmente sólo telnet tiene capacidades tanto de cliente como de servidor.
NOTA: La interfaz de Python que se describe aquí está en desuso. Utilice el que está en c++/swig/pygensio ahora.
Puede acceder a prácticamente toda la interfaz gensio a través de Python, aunque se hace de manera un poco diferente a la interfaz C.
Dado que Python está completamente orientado a objetos, gensios y los aceptadores de gensio son objetos de primera clase, junto con gensio_os_funcs, sergensios y camareros.
Aquí tenéis un pequeño programa:
import gensio
class Logger :
def gensio_log ( self , level , log ):
print ( "***%s log: %s" % ( level , log ))
class GHandler :
def __init__ ( self , o , to_write ):
self . to_write = to_write
self . waiter = gensio . waiter ( o )
self . readlen = len ( to_write )
def read_callback ( self , io , err , buf , auxdata ):
if err :
print ( "Got error: " + err )
return 0
print ( "Got data: " + buf );
self . readlen -= len ( buf )
if self . readlen == 0 :
io . read_cb_enable ( False )
self . waiter . wake ()
return len ( buf )
def write_callback ( self , io ):
print ( "Write ready!" )
if self . to_write :
written = io . write ( self . to_write , None )
if ( written >= len ( self . to_write )):
self . to_write = None
io . write_cb_enable ( False )
else :
self . to_write = self . to_write [ written :]
else :
io . write_cb_enable ( False )
def open_done ( self , io , err ):
if err :
print ( "Open error: " + err );
self . waiter . wake ()
else :
print ( "Opened!" )
io . read_cb_enable ( True )
io . write_cb_enable ( True )
def wait ( self ):
self . waiter . wait_timeout ( 1 , 2000 )
o = gensio . alloc_gensio_selector ( Logger ())
h = GHandler ( o , "This is a test" )
g = gensio . gensio ( o , "telnet,tcp,localhost,2002" , h )
g . open ( h )
h . wait ()
La interfaz es una traducción bastante directa de la interfaz C. Hay una representación de Python de la interfaz en swig/python/gensiodoc.py, puede verla para obtener documentación.
La interfaz de C++ está documentada en c++/README.rst.
La nueva interfaz de pygensio es una implementación más limpia que utiliza directores de tragos en lugar de devoluciones de llamadas codificadas a mano en Python. Consulte el archivo README.rst en c++/swig/pygensio. También hay OS_Funcs glib y tcl en los directorios glib y tcl.
La interfaz C++ completa está disponible para los programas Go a través de Swig y Swig Directors. Consulte c++/swig/go/README.rst para obtener más detalles.
Este es un sistema de autoconf normal, nada especial. Tenga en cuenta que si obtiene esto directamente de git, no incluirá la infraestructura de compilación. Hay un script llamado "reconf" en el directorio principal que lo creará por usted.
Si no conoce la configuración automática, el archivo INSTALL tiene información o búsquelo en Google.
Para construir gensio completamente, necesita lo siguiente:
Lo siguiente configura todo excepto openipmi en ubuntu 20.04:
- sudo apto instalar gcc g++ git swig python3-dev libssl-dev pkg-config
- libavahi-client-dev avahi-daemon libtool autoconf automake make libsctp-dev libpam-dev libwrap0-dev libglib2.0-dev tcl-dev libasound2-dev libudev-dev
En Redhat, libwrap desapareció, por lo que no lo usará, y swig no parece estar disponible, por lo que tendrá que compilarlo usted mismo con al menos soporte para Go y Python. Aquí está el comando para sistemas tipo Redhat:
- sudo yum instalar gcc gcc-c++ git python3-devel trago openssl-devel
- PKG-Config Avahi-devel Libtool AutoConf Autoconf Autoconf Make LKSCTP Tools-devel Pam-devel Glib2-devel TCL-devel alsa-lib-devel Systemd-devel
Es posible que deba hacer lo siguiente para habilitar el acceso a los paquetes de desarrollo:
sudo dnf config-manager --set-set-habilitado desarrollo
Y obtenga los módulos de kernel SCTP, es posible que tenga que hacer:
sudo yum instalar kernel-modules-extratra
Para usar el idioma GO, debe obtener una versión de SWIG 4.1.0 o más. Es posible que tenga que sacar una versión de borde de sangrado de GIT y usarlo.
El manejo de la configuración de instalación de Python es un poco doloroso. Por defecto, los scripts de compilación lo pondrán donde sea que el programa Python espera que estén instalados programas de Python. Un usuario normal generalmente no tiene acceso de escritura a ese directorio.
Para anular esto, puede usar las opciones-with-pythoninstall y-with-pythoninstallib de configurar o puede establecer las variables de entorno PythonStallDir y PythonInstallLibdir para donde desea que las bibliotecas y los módulos vayan.
Tenga en cuenta que es posible que deba establecer--With-Uucp-Locking a su LockDir (en los sistemas más antiguos es/var/bloqueo, que es el valor predeterminado. En más nuevo puede ser/ejecutar/listil/lockdev. También es posible que deba ser Un miembro de los grupos de dialout y cerraduras para poder abrir dispositivos en serie y/o cerraduras.
El soporte del idioma GO requiere que se instale y en la ruta.
Mientras continuaba agregando Gensios a la biblioteca, como Crypto, MDNS, Sound, IPMI, SCTP, etc. El número de dependencias en la biblioteca se estaba saliendo de control. ¿Por qué debería estar cargando Libaso, o Libopenipmi, si no lo necesita? Además, aunque la biblioteca admitió agregar sus propios Gensios a través de una API programática, no tenía una forma estándar de agregarlos para el sistema para que pudiera escribir su propio Gensio y dejar que todos en el sistema lo usen.
La Biblioteca Gensio admite la carga de Gensios dinámicamente o los construye en la biblioteca. Por defecto, si crea bibliotecas compartidas, todos los gensios se compilan como módulos para la carga dinámica e instalados en un lugar que lo haga posible. Si no crea bibliotecas compartidas, todos los gensios están integrados en la biblioteca. Pero puedes anular este comportamiento.
Para establecer todos los gensios para incorporarse a la biblioteca, puede agregar "-with-all-gensios = sí" en la línea de comandos de configuración y los incorporará a la biblioteca.
También puede configurarlos para que todos se carguen dinámicamente agregando "-with-all-gensios = dinamic", pero este es el valor predeterminado.
También puede deshabilitar todos los gensios de forma predeterminada especificando "-with-all-gensios = no". Entonces no se construirán Gensios de forma predeterminada. Esto es útil si solo desea unos pocos gensios, puede apagarlos todos y luego habilitar los que desee.
Para establecer cómo se construyen los gensios individuales, usted lo hace "-with- <gensio> = x" donde x es "no (no construir), sí (construir en biblioteca) o dinámico (ejecutable cargado dinámicamente). Por ejemplo, Si solo quisiera construir el TCP Gensio en la biblioteca y hacer que el resto sea dinámico, podría configurar todos los gensios dinámicos y luego agregar "-with net = yes".
Estos módulos se colocan de forma predeterminada en
Tenga en cuenta que la carga dinámica siempre está disponible, incluso si construye todos los gensios en la biblioteca. Por lo tanto, aún puede agregar sus propios Gensios agregando al directorio adecuado.
Gensios se cargará primero desde la variable de entorno LD_Library_Path, luego desde Gensio_Library_Path, luego desde la ubicación predeterminada.
MacOS, siendo una especie de * nix, se construye bastante limpiamente con Homebrew (https://brew.sh). Debe, por supuesto, instalar todas las bibliotecas que necesita. La mayoría de todo funciona, con las siguientes excepciones:
* CM108GPIO * SCTP * Bloqueo de UUCP
El código DNSSD incorporado se usa para MDNS, por lo que no es necesario Avahi.
El bloqueo de los flock para puertos serie funciona, por lo que realmente no se requiere bloqueo de UUCP.
Openipmi debería funcionar, pero no está disponible en Homebrew, por lo que tendrá que construirlo usted mismo.
Instale el software necesario:
- PKG Instalar GCC Portaudio Autoconf Automake LIBTOOL MDNSRESPONDER SWIG
- ir python3 gmake
Debe usar GMake para compilarlo, por alguna razón, la marca estándar en BSD no acepta la variable "C ++" en una lista de requisitos. Lo siguiente no funciona y no se compilan:
* SCTP * ipmisol * CM108GPIO
Agregue lo siguiente a /etc/rc.conf:
mdnsd_enable = sí
Y reiniciar o iniciar el servicio.
El Pty Gensio falla el OomTest (OomTest 14), parece haber algo con los BSD PTYS. Estoy viendo un carácter 07 insertado en el flujo de datos en los casos. Sin embargo, no he pasado demasiado tiempo en él, pero dado que esto se prueba mucho en Linux y MacOS, no creo que el problema esté en el código Gensio.
La biblioteca Gensio se puede construir en Windows usando MingW64. Las siguientes cosas no funcionan:
* SCTP * Pam * librap * ipmisol
Tampoco necesita instalar ALSA, utiliza la interfaz de sonido de Windows para el sonido.
El CM108GPIO utiliza interfaces nativas de Windows, por lo que no se requiere UDEV.
Se utilizan las interfaces MDNS incorporadas de Windows, por lo que no necesita Avahi o DNSSD. Deberá instalar la biblioteca PCRE si desea expresiones regulares en ella.
Debe obtener MSYS2 de https://msys2.org. Luego instale autoconf, automake, libbtool, git, make y swig como herramientas de host:
Pacman -S Autoconf Automake Libtool Git Make Swig
Debe instalar la versión MINGW-W64-X86_64-XXX de todas las bibliotecas o la versión MINGW-W64-I686-XXX de todas las bibliotecas. 32 bits no está bien probado:
PACMAN -S MINGW-W64-X86_64-GCC MingW-W64-X86_64-Python3 MingW-W64-X86_64-PCRE mingw-w64-x86_64-openssl
para MingW64, o para UCRT64:
PACMAN -S MINGW-W64-UCRT-X86_64-GCC MingW-W64-UCRT-X86_64-PYTHON3 MingW-W64-UCRT-X86_64-PCRE mingw-w64-ucrt-x86_64-openssl
Para Go, instale Go de https://go.dev y inicie sesión y vuelva a iniciar sesión. Debería estar en la ruta, pero si no es así, deberá agregarlo a la ruta. No he hecho ir trabajando en MingW32, pero no he probado una versión de GO de 32 bits.
Para GTLSSHD, - -Sysconfdir no tiene significado en Windows. En cambio, el SysConf DIR es relativo al parche del ejecutable, en ../etc/gtlssh. Entonces, si GTLSSHD está en:
C:/archivos de programa/gensio/bin/gtlsshd
El sysconfdir será:
C:/archivos de programa/gensio/etc/gtlssh
Para la instalación estándar, puede ejecutar:
../configure --sbindir =/gensio/bin --libExecdir =/gensio/bin --Mandir =/Gensio/Man--Includedir =/Gensio/Incluye -with-pythoninstall =/gensio/python3--prefix =/gensio
Y cuando ejecuta "hacer instalar destdir = ..." y establece Destdir a donde quiere que vaya, como "C:/archivos de programa". Luego puede agregar eso a la ruta usando el panel de control. Para usar GTLSSHD, crea un directorio ETC/GTLSSHD en el directorio Gensio. Debe establecer los permisos en este directorio para que solo el sistema y los administradores tengan acceso, como:
PS C: Archivos de programa (x86) Gensio etc> ICACLS GTLSSH GTLSSH NT Authority System: (oi) (CI) (f) Builtin Administradores: (OI) (CI) (f)
De lo contrario, GTLSSHD fallará con un error sobre los permisos en la clave. Puede establecer estos permisos en el archivo .key en lugar del directorio, pero tendrá que configurarlo nuevamente cada vez que genere una nueva clave.
Para usar el compilador de configuración Inno, "hacer instalar destdir = $ home/install" y luego ejecutar Inno en gensio.iss. Creará un instalador ejecutable para instalar Gensio.
Luego, debe eliminar los archivos .la del directorio de instalación, ya que arruinan la vinculación con otras cosas:
rm $ home/install/gensio/lib/*. La
Hay una serie de pruebas para Gensios. Todos se ejecutan en Linux si tienes el módulo del núcleo SerialSim. Además de los puertos serie, se ejecutan en otras plataformas, ya que los Gensios son compatibles con esa plataforma.
Las pruebas de puerto serie requieren el módulo del núcleo SerialSim y la interfaz Python. Estos están en https://github.com/cminyard/serialsim y permiten que las pruebas usen un puerto serie simulado para leer la línea de control del módem, inyectar errores, etc.
Puede superar sin serialsim si tiene tres dispositivos seriales: uno enganchado en el modo Echo (RX y TX unidos) y dos dispositivos seriales conectados juntos, la E/S en un dispositivo va/viene del otro. Esto debería funcionar en plataformas no linux. Luego establezca las siguientes variables de entorno:
export GENSIO_TEST_PIPE_DEVS= " /dev/ttyxxx:/dev/ttywww "
export GENSIO_TEST_ECHO_DEV= " /dev/ttyzzz "
No podrá probar ModemState o Rs485.
También requieren el programa IPMI_SIM desde la biblioteca OpenIpmi en https://github.com/cminyard/openipmi para ejecutar las pruebas de ipmisol.
Para ejecutar las pruebas, debe habilitar una depuración interna para obtener el efecto completo. Generalmente quieres ejecutar algo como:
./configure --enable-internal-trace CFLAGS= ' -g -Wall '
También puede activar -O3 en los CFLAGS, si lo desea, pero hace que la depuración sea más difícil.
Hay dos tipos básicos de pruebas. Las pruebas de Python son pruebas funcionales que prueban la interfaz Python y la biblioteca Gensio. Actualmente están bien, pero hay mucho espacio para mejorar. Si desea ayudar, puede escribir pruebas.
El OomTest solía ser un probador fuera de la memoria, pero se ha transformado en algo más extenso. Aterre un programa de Gensiot con variables de entorno específicas para hacer que falle en ciertos puntos, y que haga fugas de memoria y otras verificaciones de memoria. Escribe datos al Gensiot a través de su Stdin y recibe datos sobre Stdout. Algunas pruebas (como SerialDev) usan un eco. Otras pruebas hacen una conexión separada a través de la red y los datos fluyen en Stdin y regresan sobre la conexión separada, y fluye hacia la conexión separada y regresa a través de Stdout. OOMTEST es multiprocesado y el número de subprocesos se puede controlar. OomTest ha encontrado muchos errores. Tiene muchas perillas, pero debe mirar el código fuente de las opciones. Debe documentarse, si alguien le gustaría ser voluntario ...
Para configurar para Fuzzing, instale AFL, luego configure con lo siguiente:
mkdir Zfuzz ; cd Zfuzz
../configure --enable-internal-trace=yes --disable-shared --with-go=no
CC=afl-gcc CXX=afl-g++
O use Clang, si está disponible:
../configure --enable-internal-trace=yes --disable-shared --with-go=no
CC=afl-clang-fast CXX=afl-clang-fast++ LIBS= ' -lstdc++ '
No estoy seguro de por qué el asunto de las libs es necesario anteriormente, pero tuve que agregarlo para que lo compilara.
Luego construir. Luego, "pruebas de CD" y ejecute "hacer test_fuzz_xxx" donde xxx es uno de: ctauth, mux, ssl, telnet o relpkt. Probablemente necesitará ajustar algunas cosas, AFL le dirá. Tenga en cuenta que se ejecutará para siempre, deberá ^c cuando haya terminado.
El MakFile en Tests/Makefile.am tiene instrucciones sobre cómo manejar una falla para reproducirse para la depuración.
Ejecutar cobertura de código en la biblioteca es bastante fácil. Primero debe configurar el código para habilitar la cobertura:
mkdir Ocov ; cd Ocov
../configure --enable-internal-trace=yes
CC= ' gcc -fprofile-arcs -ftest-coverage '
CXX= ' g++ -fprofile-arcs -ftest-coverage '
La compilación y ejecución "hacer cheque".
Para generar el informe, ejecute:
gcovr -f ' .*/.libs/.* ' -e ' .*python.* '
Esto generará un resumen. Si desea ver la cobertura de líneas individuales en un archivo, puede hacer:
cd lib
gcov -o .libs/ * .o
Puede buscar en los archivos .gcov individuales creados para obtener información sobre lo que está cubierto. Vea los documentos de GCOV para obtener detalles.
Al momento de escribir, estaba obteniendo alrededor del 74% de cobertura de código, por lo que eso es realmente bastante bueno. Trabajaré para mejorar eso, principalmente a través de pruebas funcionales mejoradas.
Ser2Net se usa para probar algunas cosas, principalmente la configuración del puerto serie (termios y RFC2217). Puede construir Ser2Net contra la versión GCOV de la biblioteca Gensio y ejecutar "hacer check" en Ser2Net para obtener cobertura en esas partes. Con eso, veo alrededor del 76% de cobertura, por lo que no agrega mucho al total.
Sería bueno poder combinar esto con borroso, pero no estoy seguro de cómo hacerlo. AFL hace su propia cosa con la cobertura de código. Parece haber un paquete AFL-CoV que de alguna manera integró GCOV, pero no lo he investigado.