Il s'agit de gensio (prononcé gen'-see-oh), un framework permettant de donner une vue cohérente des différents types d'E/S de flux (et de paquets). Vous créez un objet gensio (ou une gensio), et vous pouvez utiliser cette gensio sans avoir à trop en savoir sur ce qui se passe en dessous. Vous pouvez empiler gensio sur un autre pour ajouter des fonctionnalités de protocole. Par exemple, vous pouvez créer une gensio TCP, empiler SSL par-dessus et empiler Telnet par-dessus. Il prend en charge un certain nombre d'E/S réseau et de ports série. Il prend également en charge les interfaces sonores. les gensios qui s'empilent sur d'autres gensios sont appelés filtres.
Vous pouvez faire la même chose avec les ports de réception. Vous pouvez configurer un accepteur gensio pour accepter les connexions dans une pile. Ainsi, dans notre exemple précédent, vous pouvez configurer TCP pour écouter sur un port spécifique et empiler automatiquement SSL et Telnet lorsque la connexion est établie, et vous n'êtes informé que lorsque tout est prêt.
gensio fonctionne sous Linux, BSD, MacOS et Windows. Sous Windows, il vous offre une interface événementielle compatible monothread (mais également multithread) (avec des interfaces de blocage disponibles) pour simplifier la programmation avec de nombreuses E/S. Cela contribue grandement à faciliter l’écriture de code piloté par E/S portable.
Une caractéristique très importante de gensio est qu'il rend l'établissement de connexions cryptées et authentifiées beaucoup plus facile que sans lui. Au-delà de la gestion de base des clés, ce n'est vraiment pas plus difficile que TCP ou autre. Il offre une flexibilité étendue pour contrôler le processus d'authentification si nécessaire. C'est vraiment facile à utiliser.
Notez que la page de manuel gensio(5) contient plus de détails sur les types individuels de gensio.
Pour obtenir des instructions sur la construction de ceci à partir des sources, consultez la section « Construction » à la fin.
Quelques outils sont disponibles qui utilisent gensios, à la fois comme exemple et pour essayer des choses. Ce sont :
Un démon de type sshd qui utilise certauth, ssl et gensios SCTP ou TCP pour établir des connexions. Il utilise l'authentification PAM standard et utilise ptys. Voir gtlsshd(8) pour plus de détails.
Il y a un élément dans FAQ.rst nommé "Comment exécuter gtlsshd sous Windows", consultez-le ainsi que la section Construire sous Windows ci-dessous pour plus de détails, car vous devez gérer quelques éléments délicats.
Les gensios suivantes sont disponibles dans la bibliothèque :
Un accepteur gensio qui prend une chaîne de pile gensio comme paramètre. Cela vous permet d'utiliser une gensio comme accepteur. Lorsque conacc est démarré, il ouvre la gensio, et lorsque la gensio s'ouvre, il signale un nouvel enfant à l'accepteur. Lorsque l'enfant se ferme, il tente de l'ouvrir à nouveau et de recommencer le processus (sauf si les acceptations ont été désactivées lors de la conacc).
Pourquoi voudriez-vous utiliser ceci ? Disons dans ser2net que vous vouliez connecter un port série à un autre. Vous pourriez avoir une connexion comme :
connection : &con0
accepter : conacc,serialdev,/dev/ttyS1,115200
connector : serialdev,/dev/ttyS2,115200
Et cela connecterait /dev/ttyS1 à /dev/ttyS2. Sans conacc, vous ne pourriez pas utiliser serialdev comme accepteur. Cela vous permettrait également d'utiliser gtlsshd sur un port série si vous souhaitez des connexions authentifiées cryptées sur un port série. Si vous avez exécuté gtlsshd avec ce qui suit :
gtlsshd --notcp --nosctp --oneshot --nodaemon --other_acc
' conacc,relpkt(mode=server),msgdelim,/dev/ttyUSB1,115200n81 '
Vous pourriez vous connecter avec :
gtlssh --transport ' relpkt,msgdelim,/dev/ttyUSB2,115200n81 ' USB2
Cela crée un transport de paquets fiable sur un port série. Le mode=server est requis pour que relpkt s'exécute en tant que serveur, car il s'exécuterait normalement en tant que client puisqu'il n'est pas démarré en tant qu'accepteur. La gensio SSL (qui s'exécute sur le transport) nécessite une communication fiable, elle ne fonctionnera donc pas directement sur un port série.
Oui, cela ressemble à un fouillis de lettres.
Un gensio de filtre qui se trouve au-dessus du gensio sonore et fait un modem de modulation de fréquence audio, comme celui utilisé sur la radio amateur AX.25.
Un protocole radioamateur pour la radio par paquets. Pour l'utiliser pleinement, vous devrez écrire du code, car il utilise des canaux et des données oob pour des informations non numérotées, mais vous pouvez faire des choses de base avec juste gensiot si tout ce dont vous avez besoin est un canal de communication. Par exemple, si vous souhaitez discuter avec quelqu'un par radio et que le port Kiss est sur 8001 sur les deux machines, sur la machine qui accepte, vous pouvez exécuter :
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),kiss,conacc,tcp,localhost,8001 '
qui se connectera au TNC et attendra une connexion à l'adresse AE5KM-1. Ensuite, vous pouvez exécuter :
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,localhost,8001 '
sur l'autre machine. Cela se connectera à l'autre machine via TNC 0 avec l'adresse donnée. Ensuite, tout ce que vous tapez dans l’un apparaîtra dans l’autre, ligne par ligne. Tapez "Ctrl-D" pour quitter. La partie 'stdio(self)' désactive le mode brut, c'est donc une ligne à la fois et vous obtenez un écho local. Sinon, chaque caractère que vous tapez enverrait un paquet et vous ne pourriez pas voir ce que vous tapez.
Pour vous connecter au système N5COR-11 AX.25 BBS, vous devez :
gensiot -i ' xlt(nlcr),stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,N5COR-11,AE5KM-2"),kiss,tcp,localhost,8001 '
La plupart des systèmes BBS utilisent CR, et non NL, pour la nouvelle ligne, donc la gensio xlt est utilisée pour traduire ces caractères entrants.
Bien sûr, étant donné qu'il s'agit d'une gensio, vous pouvez placer n'importe quelle gensio exploitable sous ax25 que vous souhaitez. Donc, si vous souhaitez jouer ou tester sans radio, vous pouvez effectuer une multidiffusion ax25 sur UDP. Voici le côté accepteur :
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 '
et voici le côté connecteur :
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 n'est pas requis car UDP est déjà un média orienté paquets. Ou vous pouvez utiliser le programme greflector pour créer une situation radio simulée. Sur la machine "radiopi2", exécutez :
greflector kiss,tcp,1234
ce qui créera un programme qui reflétera toutes les entrées reçues sur toutes les autres connexions. Puis du côté de l'accepteur :
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),kiss,conacc,tcp,radiopi2,1234 '
et le côté connexion :
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,radiopi2,1234 '
Le code de test utilise le réflecteur pour certains tests, car il est très pratique à utiliser.
Tout cela est documenté en détail dans gensio(5). Sauf indication contraire, ils sont tous disponibles en tant qu'accepteurs ou gensios de connexion.
Vous pouvez créer vos propres gensios, les enregistrer dans la bibliothèque et les empiler avec les autres gensios.
Le moyen le plus simple de procéder est de voler le code d'une gensio qui fait ce que vous voulez, puis de le modifier pour créer votre propre gensio. Il n’existe malheureusement aucune bonne documentation sur la façon de procéder.
Le fichier d'inclusion include/gensio/gensio_class.h a l'interface entre la bibliothèque gensio principale et la gensio. Les appels gensio passent tous par une seule fonction avec des numéros pour identifier la fonction demandée. Vous devez mapper tout cela aux opérations réelles. C'est quelque peu pénible, mais cela rend la compatibilité ascendante et descendante beaucoup plus facile.
Créer votre propre gensio de cette manière est assez complexe. La machine à états utilisée pour quelque chose comme ceci peut être étonnamment complexe. Le nettoyage est la partie la plus difficile. Vous devez vous assurer que vous n'êtes plus éligible à tous les rappels et qu'aucun minuteur ne peut être rappelé en cas de situation de concurrence critique à l'arrêt. Seules les gensios les plus simples (echo, dummy), les gensios étranges (conadd, keepopen, stdio) et les gensios qui ont des canaux (mux, ax25) implémentent directement l'interface. Tout le reste utilise include/gensio/gensio_base.h. gensio_base fournit la machine à états de base pour une gensio. Il comporte une partie filtre (qui est facultative) et une partie bas niveau (ll), qui ne l'est pas.
L'interface de filtre contient des données pour le traitement. Ceci est utilisé pour des choses comme SSL, certauth, ratelimit, etc. Les gens de filtres l'utiliseraient. Ceux-ci utilisent tous gensio_ll_gensio (pour empiler une gensio sur une autre gensio) pour le ll.
Les gensios terminaux ont chacun leur propre ll et généralement pas de filtre. Pour les lls basés sur un descripteur de fichier (fd), gensio_ll_fd est utilisé. Il existe également un ll pour IPMI série sur réseau local (ipmisol) et pour le son. La plupart des gens de terminaux (tcp, udp, sctp, port série, pty) utilisent évidemment le fd ll.
Une fois que vous avez une gensio, vous pouvez la compiler en tant que module et la coller dans $(moduleinstalldir)/<version>. Ensuite, la gensio le récupérera et l'utilisera. Vous pouvez également le lier à votre application et exécuter la fonction init depuis votre application.
La gensio mdns a déjà été évoquée, mais la bibliothèque gensio fournit une interface mDNS facile à utiliser. Le fichier d'inclusion correspondant se trouve dans gensio_mdns.h, et vous pouvez utiliser la page de manuel gensio_mdns(3) pour obtenir plus d'informations à ce sujet.
Pour établir une connexion mdns à l'aide de gensiot, disons que vous avez configuré ser2net avec mdns activé comme :
connection : &my-port
accepter : telnet(rfc2217),tcp,3001
connector : serialdev,/dev/ttyUSB1,115200N81
options :
mdns : true
alors vous pouvez vous y connecter avec gensiot :
gensiot ' mdns,my-port '
gensiot trouvera le serveur, le port et si telnet et rfc2217 sont activés et établira la connexion.
De plus, il existe un outil gmdns qui vous permet d'effectuer des requêtes et de la publicité, et gtlssh peut effectuer des requêtes mDNS pour trouver des services. Si vous disposez de connexions authentifiées sécurisées pour ser2net et que vous activez mdns sur ser2net, comme :
connection : &access-console
accepter : telnet(rfc2217),mux,certauth(),ssl,tcp,3001
connector : serialdev,/dev/ttyUSBaccess,115200N81
options :
mdns : true
cela rend la configuration très pratique, comme vous pouvez simplement le faire :
gtlssh -m access-console
C'est vrai, vous pouvez simplement utiliser directement le nom de la connexion, pas besoin de connaître l'hôte, si telnet ou rfc2217 est activé, ni quel est le port. Vous devez toujours configurer les clés et autres sur le serveur ser2net, bien sûr, selon ces instructions.
gensio possède une interface orientée objet et pilotée par les événements. Des interfaces synchrones sont également disponibles. Vous traitez avec deux objets principaux dans gensio : une gensio et une gensio accepteur. Une gensio fournit une interface de communication où vous pouvez vous connecter, vous déconnecter, écrire, recevoir, etc.
Un accepteur gensio vous permet de recevoir des connexions entrantes. Si une connexion arrive, cela vous donne une gensio.
L’interface est événementielle car elle est, pour la plupart, totalement non bloquante. Si vous ouvrez une gensio, vous lui donnez un rappel qui sera appelé lorsque la connexion sera établie ou que la connexion échouera. Idem pour la fermeture. Une écriture renverra le nombre d'octets acceptés, mais elle ne peut pas prendre tous les octets (ni même aucun des octets) et l'appelant doit en tenir compte.
Les interfaces d'ouverture et de fermeture disposent d'une interface de blocage secondaire pour plus de commodité. Ceux-ci se terminent par _s. C'est pour plus de commodité, mais ce n'est pas nécessaire et leur utilisation doit être prudente car vous ne pouvez pas vraiment les utiliser à partir des rappels.
Parler de rappels, de données et d'informations provenant de gensio vers l'utilisateur se fait avec un rappel de fonction. Lisez les données, et lorsque la gensio est prête à écrire, les données reviennent dans un rappel. Une interface similaire est utilisée pour appeler l'utilisateur vers la couche gensio, mais elle est cachée à l'utilisateur. Ce type d'interface est facilement extensible, de nouvelles opérations peuvent être facilement ajoutées sans casser les anciennes interfaces.
La bibliothèque propose plusieurs façons de créer une gensio ou une gensio accepteur. La méthode principale est str_to_gensio() et str_to_gensio_accepter(). Ceux-ci fournissent un moyen de spécifier une pile de gensios ou d'accepteurs sous forme de chaîne et de construction. En général, vous devriez utiliser cette interface si vous le pouvez.
En général, les interfaces qui ne sont pas sensibles aux performances sont basées sur des chaînes. Vous le verrez dans gensio_control, et dans les données auxiliaires de l'interface de lecture et d'écriture pour contrôler certains aspects de l'écriture.
La bibliothèque propose également des moyens de configurer vos gensios en les créant individuellement. Dans certaines situations, cela peut être nécessaire, mais cela limite la possibilité d'utiliser les nouvelles fonctionnalités de la bibliothèque gensio au fur et à mesure de son extension.
Si une gensio prend en charge plusieurs flux (comme SCTP), les numéros de flux sont transmis dans les données auxiliaires avec "stream=n". Les flux ne sont pas contrôlés individuellement.
Les canaux, en revanche, sont des flux de données distincts circulant sur la même connexion. Les canaux sont représentés comme des gensios distincts et leur flux peut être contrôlé individuellement.
Il y a quelques fichiers d'inclusion que vous devrez peut-être gérer lorsque vous utilisez gensios :
Celles-ci sont pour la plupart documentées dans les pages de manuel.
Pour créer vos propres gensios, les fichiers d'inclusion suivants sont à votre disposition :
Chaque fichier d'inclusion contient de nombreuses documentations sur les appels et les gestionnaires individuels.
gensio possède son propre ensemble d'erreurs pour l'abstraire des erreurs du système d'exploitation (nommé GE_xxx) et offrir plus de flexibilité dans le rapport d'erreurs. Ceux-ci se trouvent dans le fichier d'inclusion gensio_err.h (automatiquement inclus depuis gensio.h) et peuvent être traduits de nombres en une chaîne significative avec gensio_err_to_str(). Zéro est défini comme n’étant pas une erreur.
Si une erreur non reconnue du système d'exploitation se produit, GE_OSERR est renvoyé et un journal est signalé via l'interface de journal du gestionnaire de système d'exploitation.
Une chose légèrement ennuyeuse à propos de gensio est qu'il vous oblige à fournir un gestionnaire de système d'exploitation (struct gensio_os_funcs) pour gérer les fonctions de type système d'exploitation telles que l'allocation de mémoire, les mutex, la capacité de gérer les descripteurs de fichiers, les minuteries et l'heure, et quelques autres choses.
La bibliothèque fournit plusieurs gestionnaires de système d'exploitation. Vous pouvez appeler gensio_alloc_os_funcs() pour en attribuer un par défaut à votre système (POSIX ou Windows). Vous pouvez voir cette page de manuel pour plus de détails. Ce sera généralement l’option la plus performante dont vous disposez pour votre système.
Pour les systèmes POSIX, des gestionnaires de système d'exploitation pour glib et TCL sont disponibles, alloués avec gensio_glib_funcs_alloc() et gensio_tcl_funcs_alloc(). Celles-ci ne fonctionnent vraiment pas très bien, notamment du point de vue des performances, les API pour glib et TCL ne sont pas bien conçues pour ce que fait gensio. TCL ne peut prendre en charge que les opérations monothread. L'opération multithread glib n'a qu'un seul thread à la fois en attente d'E/S. Mais ils fonctionnent et les tests sont effectués avec eux. Ceux-ci ne sont pas disponibles sous Windows en raison de mauvaises abstractions sur glib et d'un manque de motivation sur TCL.
Mais si vous utilisez quelque chose d'autre comme X Windows, etc. qui possède sa propre boucle d'événements, vous devrez peut-être en adapter une à vos besoins. Mais ce qui est bien, c'est que vous pouvez le faire et intégrer Gensio avec à peu près n'importe quoi.
Il existe également une interface de serveur qui offre un moyen pratique d'attendre que des choses se produisent lors de l'exécution de la boucle d'événements. C'est ainsi que vous entrez généralement dans la boucle d'événements, car cela constitue un moyen pratique de signaler lorsque vous avez terminé et que vous devez quitter la boucle.
La documentation à ce sujet se trouve dans :
include/gensio/gensio_os_funcs.h
La bibliothèque gensio prend entièrement en charge les threads et est totalement thread-safe. Cependant, il utilise des signaux sur le système POSIX et COM sur les systèmes Windows, une configuration est donc requise.
Le thread "principal" doit appeler gensio_os_proc_setup() au démarrage et appeler gensio_os_proc_cleanup() une fois l'opération terminée. Cela configure les signaux et les gestionnaires de signaux, le stockage local des threads sous Windows et d'autres sortes de choses.
Vous pouvez générer de nouveaux threads à partir d'un thread déjà configuré en utilisant gensio_os_new_thread(). Cela vous donne un thread de base du système d'exploitation et est correctement configuré pour gensio.
Si vous avez un thread créé par d'autres moyens que vous souhaitez utiliser dans gensio, tant que le thread crée un autre thread et n'effectue aucune fonction de blocage (toute sorte d'attente, traitement en arrière-plan, fonctions qui se terminent par _s comme read_s, etc.), vous n'avez pas besoin de les configurer. De cette façon, un thread externe peut écrire des données, réveiller un autre thread ou faire des choses comme ça.
Si un thread externe doit faire ces choses, il doit appeler gensio_os_thread_setup().
Comme mentionné dans la section threads, la bibliothèque gensio sous Unix utilise des signaux pour les réveils inter-thread. J'ai bien cherché, et il n'y a vraiment pas d'autre moyen de faire cela proprement. Mais Windows a également quelques éléments de type signal, et ceux-ci sont également disponibles dans gensio.
Si vous utilisez gensio_alloc_os_funcs(), vous obtiendrez une fonction du système d'exploitation utilisant le signal transmis pour IPC. Vous pouvez transmettre GENSIO_OS_FUNCS_DEFAULT_THREAD_SIGNAL pour le signal si vous souhaitez la valeur par défaut, qui est SIGUSR1. Le signal que vous utilisez sera bloqué et repris par gensio, vous ne pourrez pas l'utiliser.
gensio fournit également une gestion générique pour quelques signaux. Sous Unix, il gérera SIGHUP via la fonction gensio_os_proc_register_reload_handler().
Sous Windows et Unix, vous pouvez utiliser gensio_os_proce_register_term_handler(), qui gérera les demandes de terminaison (SIGINT, SIGTERM, SIGQUIT sous Unix) et gensio_os_proc_register_winsize_handler() (SIGWINCH sous Unix). La manière dont ceux-ci arrivent via Windows est un peu plus compliquée, mais invisible pour l'utilisateur.
Tous les rappels provenant de l'attente d'une routine d'attente, et non du gestionnaire de signal. Cela devrait beaucoup vous simplifier la vie.
Vous pouvez consulter les pages de manuel pour plus de détails sur tout cela.
Pour créer une gensio, la manière générale de procéder est d'appeler str_to_gensio()
avec une chaîne correctement formatée. La chaîne est formatée comme suit :
<type>[([<option>[,<option[...]]])][,<type>...][,<end option>[,<end option>]]
L' end option
concerne les gensios terminaux ou ceux qui se trouvent en bas de la pile. Par exemple, tcp,localhost,3001
créera une gensio qui se connectera au port 3001 sur localhost. Pour un port série, un exemple est serialdev,/dev/ttyS0,9600N81
créera une connexion au port série /dev/ttyS0.
Cela vous permet d'empiler des couches gensio au-dessus des couches gensio. Par exemple, pour superposer Telnet à une connexion TCP :
telnet,tcp,localhost,3001
Supposons que vous souhaitiez activer RFC2217 sur votre connexion Telnet. Vous pouvez ajouter une option pour ce faire :
telnet(rfc2217=true),tcp,localhost,3001
Lorsque vous créez une gensio, vous fournissez un rappel avec les données utilisateur. Lorsque des événements se produisent sur une gensio, le rappel sera appelé afin que l'utilisateur puisse le gérer.
Une gensio accepteur est similaire à une gensio de connexion, mais avec str_to_gensio_accepter()
à la place. Le format est le même. Par exemple:
telnet(rfc2217=true),tcp,3001
créera un accepteur TCP avec telnet en haut. Pour les accepteurs, vous n'avez généralement pas besoin de spécifier le nom d'hôte si vous souhaitez vous lier à toutes les interfaces de la machine locale.
Une fois que vous avez créé une gensio, elle n'est pas encore ouverte ni opérationnelle. Pour l'utiliser, vous devez l'ouvrir. Pour l'ouvrir, faites :
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 }
Notez que lorsque gensio_open()
revient, la gensio n'est pas ouverte. Vous devez attendre que le rappel ( tcp_open_done()
dans ce cas) soit appelé. Après cela, vous pouvez l'utiliser.
Une fois la gensio ouverte, vous n'obtiendrez pas immédiatement de données car la réception est désactivée. Vous devez appeler gensio_set_read_callback_enable()
pour activer et désactiver si le rappel ( tcpcb
dans ce cas) sera appelé lors de la réception des données.
Lorsque le gestionnaire de lecture est appelé, le tampon et la longueur sont transmis. Vous n'êtes pas obligé de gérer toutes les données si vous ne le pouvez pas. Vous devez mettre à jour le buflen avec le nombre d'octets que vous avez réellement gérés. Si vous ne gérez pas de données, les données non traitées seront mises en mémoire tampon dans la gensio pour plus tard. Non pas que si vous ne gérez pas toutes les données, vous devez désactiver l'activation de lecture, sinon l'événement sera immédiatement rappelé.
Si quelque chose ne va pas lors d'une connexion, le gestionnaire de lecture est appelé avec un jeu d'erreurs. buf
et buflen
seront NULL dans ce cas.
Pour l'écriture, vous pouvez appeler gensio_write()
pour écrire des données. Vous pouvez utiliser gensio_write()
à tout moment sur une gensio ouverte. gensio_write()
peut ne pas prendre toutes les données que vous y écrivez. Le paramètre count
renvoie le nombre d'octets réellement pris dans l'appel d'écriture.
Vous pouvez concevoir votre code pour appeler gensio_set_write_callback_enable()
lorsque vous avez des données à envoyer et la gensio appellera le rappel prêt à écrire et vous pourrez écrire à partir du rappel. C'est généralement plus simple, mais l'activation et la désactivation du rappel d'écriture ajoute une certaine surcharge.
Une approche plus efficace consiste à écrire des données chaque fois que vous en avez besoin et à désactiver le rappel d'écriture. Si l'opération d'écriture renvoie moins que la requête complète, l'autre extrémité est contrôlée en flux et vous devez activer le rappel d'écriture et attendre qu'il soit appelé avant d'envoyer plus de données.
Dans les rappels, vous pouvez obtenir les données utilisateur que vous avez transmises à l'appel de création avec gensio_get_user_data()
.
Notez que si vous ouvrez puis fermez immédiatement une gensio, c'est très bien, même si le rappel d'ouverture n'a pas été appelé. Le rappel ouvert peut cependant être appelé ou non dans ce cas, il peut donc être difficile de gérer cela correctement.
Vous pouvez effectuer des E/S synchrones de base avec gensios. Ceci est utile dans certaines situations où vous devez lire quelque chose en ligne. Pour ce faire, appelez :
err = gensio_set_sync ( io );
La gensio donnée cessera de délivrer des événements de lecture et d'écriture. D'autres événements sont livrés. Ensuite vous pouvez faire :
err = gensio_read_s ( io , & count , data , datalen , & timeout );
err = gensio_write_s ( io , & count , data , datalen , & timeout );
Count est défini sur le nombre réel d’octets lus/écrits. Cela peut être NULL si vous ne vous en souciez pas (même si cela n'a pas beaucoup de sens pour la lecture).
Le délai d'attente peut être NULL, si c'est le cas, attendez indéfiniment. Si vous définissez un délai d'attente, il est mis à jour en fonction du temps restant.
Notez que les signaux provoqueront leur retour immédiat, mais aucune erreur n'est signalée.
Les lectures seront bloquées jusqu'à ce que certaines données arrivent et renvoient ces données. Il n'attend pas que le tampon soit plein. timeout est une valeur de temps, la lecture attendra ce laps de temps pour que la lecture se termine et revienne. Un timeout n'est pas une erreur, le décompte sera simplement remis à zéro.
Écrit le bloc jusqu'à ce que tout le tampon soit écrit ou qu'un délai d'attente se produise. Encore une fois, le délai d'attente n'est pas une erreur, le nombre total d'octets réellement écrits est renvoyé en nombre.
Une fois que vous avez terminé les E/S synchrones avec une gensio, appelez :
err = gensio_clear_sync ( io );
et la livraison via l'interface d'événement continuera comme avant. Vous ne devez pas être dans un appel de lecture ou d'écriture synchrone lorsque vous appelez ceci, les résultats seront indéfinis.
Notez que d'autres E/S sur d'autres gensios se produiront toujours en attendant des E/S synchrones.
Il n'existe actuellement aucun moyen d'attendre plusieurs gensios avec des E/S synchrones. Si vous faites cela, vous devriez simplement utiliser les E/S pilotées par les événements. C'est plus efficace, et de toute façon, vous finissez par faire la même chose.
Comme une gensio, une gensio accepteur n'est pas opérationnelle lorsque vous la créez. Vous devez appeler gensio_acc_startup()
pour l'activer :
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 }
Notez qu'il n'y a pas de rappel à l'appel de démarrage pour savoir quand il est activé, car il n'y a pas vraiment besoin de le savoir car vous ne pouvez pas y écrire, il n'effectue que des rappels.
Même après avoir démarré l'accepteur, il ne fera toujours rien tant que vous n'aurez pas appelé gensio_acc_set_accept_callback_enable()
pour activer ce rappel.
Lorsque le rappel est appelé, il vous donne une gensio dans le paramètre data
déjà ouvert avec la lecture désactivée. Une gensio reçue d'un accepteur de gensio peut avoir certaines limitations. Par exemple, vous ne pourrez peut-être pas le fermer puis le rouvrir.
Les accepteurs gensio peuvent effectuer des acceptations synchrones en utilisant gensio_acc_set_sync()
et gensio_acc_accept_s
. Consultez les pages de manuel à ce sujet pour plus de détails.
struct gensio_os_funcs
a un rappel de vlog pour gérer les journaux gensio internes. Ceux-ci sont appelés lorsque quelque chose d'important se produit mais que gensio n'a aucun moyen de signaler une erreur. Il peut également être appelé pour faciliter le diagnostic d'un problème en cas de problème.
Les classes gensio et gensio accepter ont chacune des sous-classes pour gérer les E/S série et définir tous les paramètres associés à un port série.
Vous pouvez découvrir si une gensio (ou l'un de ses enfants) est un port série en appelant gensio_to_sergensio()
. Si cela renvoie NULL, ce n'est pas un sergensio et aucun de ses enfants n'est un sergensio. S'il renvoie une valeur non NULL, il renvoie l'objet sergensio que vous pouvez utiliser. Notez que la gensio renvoyée par sergensio_to_gensio()
sera celle transmise à gensio_to_sergensio()
, pas nécessairement la gensio à laquelle sergensio est directement associé.
Un sergensio peut être un client, ce qui signifie qu'il peut définir des paramètres série, ou un serveur, ce qui signifie qu'il recevra les paramètres série de l'autre extrémité de la connexion.
La plupart des sergensios sont uniquement clients : serialdev (port série normal), ipmisol et stdio accepter. Actuellement, seul Telnet possède à la fois des capacités client et serveur.
REMARQUE : L'interface Python décrite ici est obsolète. Utilisez celui de c++/swig/pygensio maintenant.
Vous pouvez accéder à presque toute l'interface gensio via python, bien que cela soit fait un peu différemment de l'interface C.
Puisque python est entièrement orienté objet, les gensios et les gensio accepteurs sont des objets de première classe, avec gensio_os_funcs, sergensios et waiters.
Voici un petit programme :
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 ()
L'interface est une traduction assez directe de l'interface C. Une représentation python de l'interface se trouve dans swig/python/gensiodoc.py, vous pouvez la voir pour la documentation.
L'interface C++ est documentée dans c++/README.rst.
La nouvelle interface pygensio est une implémentation plus propre utilisant des directeurs swig au lieu de rappels codés manuellement en python. Voir le README.rst dans c++/swig/pygensio. Il existe également des OS_Funcs glib et tcl dans les répertoires glib et tcl.
L'interface C++ complète est disponible pour les programmes Go via les directeurs swig et swig. Voir c++/swig/go/README.rst pour plus de détails.
Il s'agit d'un système d'autoconf normal, rien de spécial. Notez que si vous obtenez cela directement depuis git, l'infrastructure de construction n'est pas incluse. Il existe un script nommé "reconf" dans le répertoire principal qui le créera pour vous.
Si vous ne connaissez pas l'autoconf, le fichier INSTALL contient des informations, ou recherchez-le sur Google.
Pour créer entièrement Gensio, vous avez besoin des éléments suivants :
Ce qui suit définit tout sauf openipmi sur Ubuntu 20.04 :
- sudo apt installer 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
Sur Redhat, libwrap a disparu, vous ne l'utiliserez donc pas, et swig ne semble pas être disponible, vous devrez donc le construire vous-même avec au moins le support go et python. Voici la commande pour les systèmes de type Redhat :
- sudo yum install gcc gcc-c++ git python3-devel swig openssl-devel
- PKG-Config avahi-devel libtool AutoConf Automake Faire le LKSCTP-TOOLS-Devel Pam-Devel GLIB2-Devel TCL-Devel ALSA-LIB-Devel Systemd-Devel
Vous devrez peut-être effectuer ce qui suit pour permettre l'accès aux packages de développement:
Sudo DNF config-manager - Devel compatible
Et obtenez les modules du noyau SCTP, vous devrez peut-être faire:
sudo yum installer le noyau-modules-extra
Pour utiliser le langage GO, vous devez obtenir une version de SWIG 4.1.0 ou plus. Vous devrez peut-être retirer une version de bord de saignement de Git et l'utiliser.
La gestion de la configuration d'installation de Python est un peu pénible. Par défaut, les scripts de construction le mettront partout où le programme Python s'attend à ce que les programmes Python soient installés. Un utilisateur normal n'a généralement pas accès à l'écriture à ce répertoire.
Pour remplacer cela, vous pouvez utiliser les options de configuration --with-pythoninstall et --with-pythoninstallLib ou vous pouvez définir les variables d'environnement PythoninstallDir et Pythoninstalllibdir pour où vous souhaitez que les bibliothèques et les modules soient.
Notez que vous devrez peut-être définir - avec-uucp verrouiller à votre lockdir (sur les anciens systèmes, c'est / var / lock, qui est la valeur par défaut. Sur Un membre des groupes Dialout et Lock pour pouvoir ouvrir des dispositifs de série et / ou des verrous.
GO Language Support nécessite d'être installé et sur le chemin.
Alors que je continuais à ajouter des Gensios à la bibliothèque, comme la crypto, les MDN, le son, l'IPMI, le SCTP, etc. Le nombre de dépendances dans la bibliothèque devenait incontrôlable. Pourquoi devriez-vous charger Libasound, ou Libopenipmi, si vous n'en avez pas besoin? De plus, bien que la bibliothèque ait pris en charge l'ajout de vos propres gensios via une API programmatique, il n'avait aucun moyen standard de les ajouter pour le système afin que vous puissiez écrire votre propre Gensio et laisser tout le monde sur le système l'utiliser.
La bibliothèque Gensio prend en charge le chargement de Gensios dynamiquement ou les construise dans la bibliothèque. Par défaut, si vous créez des bibliothèques partagées, tous les Gensios sont compilés sous forme de modules pour le chargement dynamique et installés dans un endroit qui le permet. Si vous ne créez pas de bibliothèques partagées, tous les Gensios sont intégrés à la bibliothèque. Mais vous pouvez remplacer ce comportement.
Pour définir tous les Gensios à intégrer dans la bibliothèque, vous pouvez ajouter "- avec all-Gensios = Oui" sur la ligne de commande de configuration et il les construira dans la bibliothèque.
Vous pouvez également les définir sur tous les personnes chargées dynamiquement en ajoutant "--with-all-gensios = dynamic", mais c'est la valeur par défaut.
Vous pouvez également désactiver tous les Gensios par défaut en spécifiant "- avec all-Gensios = non". Alors aucun gensios ne sera construit par défaut. Ceci est utile si vous ne voulez que quelques gensios, vous pouvez les désactiver tous, puis activer, puis ceux que vous voulez.
Pour définir la construction individuelle des Gensios, vous faites "- avec- <gensio> = x" où x est "Non (ne construisez pas), oui (construire dans la bibliothèque) ou dynamique (exécutable chargé dynamiquement). Par exemple, Si vous vouliez uniquement construire le Gensio TCP dans la bibliothèque et faire la dynamique de repos, vous pouvez configurer tous les gensios dynamiques, puis ajouter "- with-net = oui".
Ces modules sont placés par défaut dans
Notez que le chargement dynamique est toujours disponible, même si vous construisez tous les Gensios de la bibliothèque. Ainsi, vous pouvez toujours ajouter vos propres gensios en ajoutant ensuite au répertoire approprié.
Les Gensios seront chargés d'abord à partir de la variable d'environnement LD_LIBRARY_PATH, puis à partir de Gensio_Library_Path, puis à partir de l'emplacement par défaut.
MacOS, étant une sorte de * Nix, se construit assez proprement avec Homebrew (https://brew.sh). Vous devez, bien sûr, installer toutes les bibliothèques dont vous avez besoin. Presque tout fonctionne, avec les exceptions suivantes:
* CM108GPIO * SCTP * verrouillage UUCP
Le code DNSSD intégré est utilisé pour MDNS, donc Avahi n'est pas requis.
Le verrouillage du troupeau pour les ports série fonctionne, donc le verrouillage UUCP n'est vraiment pas nécessaire.
OpenIPMI devrait fonctionner, mais il n'est pas disponible dans Homebrew, vous devrez donc le construire vous-même.
Installez le logiciel nécessaire:
- pkg installer gcc portaudio autoconf automake libtool mdnsponder swig
- Go Python3 Gmake
Vous devez utiliser GMake pour le compiler, pour une raison quelconque, la marque standard sur BSD n'accepte pas la variable "C ++" dans une liste d'exigences. Les éléments suivants ne fonctionnent pas et ne sont pas compilés:
* SCTP * ipmisol * CM108GPIO
Ajoutez ce qui suit à /etc/rc.conf:
mdnsd_enable = oui
Et redémarrer ou démarrer le service.
Le Gensio Pty échoue au Oomtest (Oomtest 14), il semble y avoir quelque chose avec les BSD PTY. Je vois un caractère 07 inséré dans le flux de données dans les cas. Je n'ai pas passé trop de temps dessus, mais comme cela est fortement testé sur Linux et MacOS, je ne pense pas que le problème soit dans le code Gensio.
La bibliothèque Gensio peut être construite sous Windows à l'aide de MingW64. Les choses suivantes ne fonctionnent pas:
* SCTP * Pam * libwrap * ipmisol
Vous n'avez pas non plus besoin d'installer ALSA, il utilise l'interface Sound Windows pour le son.
Le CM108GPIO utilise des interfaces Windows natives, donc Udev n'est pas requis.
Les interfaces MDNS intégrées Windows sont utilisées, vous n'avez donc pas besoin d'Avahi ou de DNSSD. Vous devrez installer la bibliothèque PCRE si vous souhaitez des expressions régulières.
Vous devez obtenir MSYS2 à partir de https://msys2.org. Installez ensuite AutoConf, Automake, Libtool, Git, Make et Swig comme outils d'hôte:
Pacman -s AutoConf Automake libtool git Faire Swig
Vous devez installer la version MingW-W64-X86_64-XXX de toutes les bibliothèques ou la version MingW-W64-I686-XXX de toutes les bibliothèques. 32 bits n'est pas bien testé:
Pacman -s mingw-w64-x86_64-gcc mingw-w64-x86_64-python3 mingw-w64-x86_64-pcre Mingw-W64-X86_64-OpenSSL
pour Mingw64, ou pour 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
Pour aller, installer Go de https://go.dev et se connecter et vous connecter. Il devrait alors être sur le chemin, mais si ce n'est pas le cas, vous devrez l'ajouter au chemin. Je n'ai pas fait de travail sur MingW32, mais je n'ai pas essayé de version 32 bits de GO.
Pour GTLSSHD, - SysConfdir n'a aucun sens sur Windows. Au lieu de cela, le Dir sysconf est relatif au patch de l'exécutable, dans ../etc/gtlssh. Donc, si GTLSSHD est dans:
C: / / Program Files / Gensio / Bin / GTLSSHD
Le Sysconfdir sera:
C: / / Program Files / Gensio / etc / GTLSSH
Pour une installation standard, vous pouvez fonctionner:
../configure --sbindir = / gensio / bin - libexecdir = / gensio / bin --Mandir = / Gensio / Man - includedir = / gensio / inclure --with-pythoninstall = / gensio / python3 --prefix = / genio
Et lorsque vous exécutez "Faire installer destdir = ..." et que vous définissez destdir là où vous voulez qu'il aille, comme "C: / Fichiers de programme". Ensuite, vous pouvez l'ajouter au chemin à l'aide du panneau de commande. Pour utiliser GTLSSHD, vous créez un répertoire ETC / GTLSSHD dans le répertoire Gensio. Vous devez définir les autorisations sur ce répertoire afin que seuls le système et les administrateurs aient accès, comme:
PS C: Program Files (x86) Gensio etc> icacls gtlsh GTLSSH NT Authority System: (Oi) (CI) (F) Administrateurs intégrés: (OI) (CI) (F)
Sinon, GTLSSHD échouera avec une erreur concernant les autorisations sur la clé. Vous pouvez définir ces autorisations sur le fichier .key au lieu du répertoire, mais vous devrez le regères à nouveau chaque fois que vous générez une nouvelle clé.
Pour utiliser le compilateur Inno Configuration, faites "Faire installer destdir = $ home / install", puis exécutez Inno sur gensio.iss. Il créera un installateur exécutable pour installer Gensio.
Ensuite, vous devez supprimer les fichiers .la du répertoire d'installation, car ils bousculent en liant avec d'autres choses:
rm $ home / install / gensio / lib / *. la
Il existe un certain nombre de tests pour Gensios. Ils fonctionnent tous sur Linux si vous avez le module du noyau SerialSim. Outre les ports série, ils fonctionnent sur d'autres plates-formes car les Gensios sont pris en charge sur cette plate-forme.
Les tests de port série nécessitent le module du noyau SerialSim et l'interface Python. Celles-ci sont sur https://github.com/cminyard/serialsim et permettent aux tests d'utiliser un port série simulé pour lire la ligne de contrôle du modem, injecter des erreurs, etc.
Vous pouvez vous débrouiller sans SerialSim si vous avez trois dispositifs série: un en mode Echo (RX et TX liés) et deux périphériques de série accrochés ensemble, les E / S sur un appareil vont / proviennent de l'autre. Cela devrait fonctionner sur les plates-formes non linux. Définissez ensuite les variables d'environnement suivantes:
export GENSIO_TEST_PIPE_DEVS= " /dev/ttyxxx:/dev/ttywww "
export GENSIO_TEST_ECHO_DEV= " /dev/ttyzzz "
Il ne pourra pas tester ModemState ou RS485.
Ils ont également besoin du programme IPMI_SIM de la bibliothèque OpenIPMI à https://github.com/cminyard/openIPMI pour exécuter les tests IPMISOL.
Pour exécuter les tests, vous devez permettre un débogage interne pour obtenir le plein effet. Vous voulez généralement exécuter quelque chose comme:
./configure --enable-internal-trace CFLAGS= ' -g -Wall '
Vous pouvez également activer -O3 dans les CFLAG, si vous le souhaitez, mais cela rend le débogage plus difficile.
Il existe deux types de tests de base. Les tests Python sont des tests fonctionnels testant à la fois l'interface Python et la bibliothèque Gensio. Actuellement, ils vont bien, mais il y a beaucoup de place à l'amélioration. Si vous souhaitez aider, vous pouvez écrire des tests.
Le Oomtest était autrefois un testeur hors mémoire, mais s'est transformé en quelque chose de plus étendu. Il engendre un programme Geniot avec des variables d'environnement spécifiques pour le faire échouer à certains points et faire des fuites de mémoire et d'autres vérifications de la mémoire. Il écrit des données au Geniot via son stdin et reçoit des données sur STDOUT. Certains tests (comme SerialDev) utilisent un écho. D'autres tests établissent une connexion distincte sur le réseau et les données circulent à la fois dans STDIN et revient sur la connexion distincte, et se déroule dans la connexion séparée et revient via STDOUT. Oomtest est multi-thread et le nombre de threads peut être contrôlé. Oomtest a trouvé beaucoup de bugs. Il a beaucoup de boutons, mais vous devez consulter le code source des options. Il doit être documenté, si quelqu'un souhaitait faire du bénévolat ...
Pour configurer pour fuzzing, installez AFL, puis configurez avec les suivants:
mkdir Zfuzz ; cd Zfuzz
../configure --enable-internal-trace=yes --disable-shared --with-go=no
CC=afl-gcc CXX=afl-g++
Ou utilisez Clang, si disponible:
../configure --enable-internal-trace=yes --disable-shared --with-go=no
CC=afl-clang-fast CXX=afl-clang-fast++ LIBS= ' -lstdc++ '
Je ne sais pas pourquoi le truc des libs est nécessaire ci-dessus, mais j'ai dû l'ajouter pour le faire compiler.
Puis construire. Ensuite, les "tests de CD" et l'exécution "font du test_fuzz_xxx" où xxx est l'un des: certAuth, MUX, SSL, Telnet ou Relpkt. Vous devrez probablement ajuster certaines choses, AFL vous le dira. Notez qu'il fonctionnera pour toujours, vous devrez le ^ c lorsque vous aurez terminé.
Le makefile dans les tests / makefile.am a des instructions sur la façon de gérer le défaut de se reproduire pour le débogage.
L'exécution de la couverture de code dans la bibliothèque est assez facile. Vous devez d'abord configurer le code pour activer la couverture:
mkdir Ocov ; cd Ocov
../configure --enable-internal-trace=yes
CC= ' gcc -fprofile-arcs -ftest-coverage '
CXX= ' g++ -fprofile-arcs -ftest-coverage '
La compilation et exécutez "faire un chèque".
Pour générer le rapport, exécutez:
gcovr -f ' .*/.libs/.* ' -e ' .*python.* '
Cela générera un résumé. Si vous souhaitez voir la couverture des lignes individuelles dans un fichier, vous pouvez faire:
cd lib
gcov -o .libs/ * .o
Vous pouvez consulter les fichiers .gcov individuels créés pour des informations sur ce qui est couvert. Voir les documents GCOV pour plus de détails.
Au moment de la rédaction du présent document, j'obtenais environ 74% de couverture de code, donc c'est vraiment très bon. Je vais travailler pour améliorer cela, principalement grâce à des tests fonctionnels améliorés.
Ser2Net est utilisé pour tester certaines choses, principalement la configuration du port série (Termios et RFC2217). Vous pouvez créer Ser2Net contre la version GCOV de la bibliothèque Gensio et exécuter "Créer un chèque" dans Ser2Net pour obtenir une couverture sur ces pièces. Avec cela, je vois une couverture d'environ 76%, donc cela n'ajoute pas grand-chose au total.
Ce serait bien de pouvoir combiner cela avec du fuzzing, mais je ne sais pas comment le faire. AFL fait son propre truc avec la couverture du code. Il semble y avoir un package AFL-CoV qui a en quelque sorte intégré GCOV, mais je ne l'ai pas examiné.