Isto é gensio (pronuncia-se gen'-see-oh), uma estrutura para fornecer uma visão consistente de vários tipos de E/S de fluxo (e pacote). Você cria um objeto gensio (ou gensio) e pode usá-lo sem precisar saber muito sobre o que está acontecendo por baixo. Você pode empilhar o gensio em cima de outro para adicionar funcionalidade de protocolo. Por exemplo, você pode criar um gensio TCP, empilhar SSL em cima dele e empilhar Telnet em cima disso. Ele suporta diversas E/S de rede e portas seriais. Ele também oferece suporte a interfaces de som. gensios que se acumulam em outros gensios são chamados de filtros.
Você pode fazer a mesma coisa com as portas de recebimento. Você pode configurar um aceitador gensio para aceitar conexões em uma pilha. Portanto, em nosso exemplo anterior, você pode configurar o TCP para escutar em uma porta específica e empilhar automaticamente SSL e Telnet quando a conexão for estabelecida, e você não será informado até que tudo esteja pronto.
gensio funciona em Linux, BSDs, MacOS e Windows. No Windows, ele oferece uma interface orientada a eventos com capacidade de thread único (mas também multithread) (com interfaces de bloqueio disponíveis) para simplificar a programação com muitas E/Ss. É um grande passo para facilitar a escrita de código orientado por E/S portátil.
Uma característica muito importante do gensio é que ele torna o estabelecimento de conexões criptografadas e autenticadas muito mais fácil do que sem ele. Além do gerenciamento básico de chaves, não é mais difícil que o TCP ou qualquer outra coisa. Oferece flexibilidade estendida para controlar o processo de autenticação, se necessário. É muito fácil de usar.
Observe que a página man do gensio(5) tem mais detalhes sobre tipos individuais de gensio.
Para obter instruções sobre como compilar isso a partir do código-fonte, consulte a seção "Construindo" no final.
Estão disponíveis algumas ferramentas que usam gensios, tanto como exemplo quanto para experimentar. Estes são:
Um daemon semelhante a sshd que usa certauth, ssl e gensios SCTP ou TCP para fazer conexões. Ele usa autenticação PAM padrão e usa ptys. Veja gtlsshd(8) para detalhes.
Há um item no FAQ.rst chamado "Como executar o gtlsshd no Windows", veja isso e a seção Construindo no Windows abaixo para obter mais detalhes, pois há algumas coisas complicadas que você precisa lidar.
Os seguintes gênios estão disponíveis na biblioteca:
Um aceitador gensio que usa uma string de pilha gensio como parâmetro. Isso permite que você use um gensio como aceitante. Quando o conacc é iniciado, ele abre o gensio, e quando o gensio abre ele reporta um novo filho para o aceitante. Quando o filho fecha, ele tenta abri-lo novamente e passar pelo processo novamente (a menos que a aceitação tenha sido desativada no conacc).
Por que você iria querer usar isso? Digamos que no ser2net você queira conectar uma porta serial a outra. Você poderia ter uma conexão como:
connection : &con0
accepter : conacc,serialdev,/dev/ttyS1,115200
connector : serialdev,/dev/ttyS2,115200
E conectaria /dev/ttyS1 a /dev/ttyS2. Sem o conacc, você não poderia usar o serialdev como aceitante. Também permitiria usar gtlsshd em uma porta serial se você desejasse logins autenticados criptografados em uma porta serial. Se você executou gtlsshd com o seguinte:
gtlsshd --notcp --nosctp --oneshot --nodaemon --other_acc
' conacc,relpkt(mode=server),msgdelim,/dev/ttyUSB1,115200n81 '
Você pode se conectar com:
gtlssh --transport ' relpkt,msgdelim,/dev/ttyUSB2,115200n81 ' USB2
Isto cria um transporte de pacotes confiável através de uma porta serial. O mode=server é necessário para fazer o relpkt rodar como servidor, já que normalmente seria executado como cliente, já que não está sendo iniciado como aceitante. O SSL gensio (que é executado no transporte) requer comunicação confiável, portanto não será executado diretamente em uma porta serial.
Sim, parece uma confusão de letras.
Um filtro gensio que fica em cima do som gensio e faz um modem Audio Frequency Shift Keying, como o usado no rádio amador AX.25.
Um protocolo de rádio amador para rádio de pacotes. Para usar isso completamente, você precisaria escrever código, já que ele usa canais e dados oob para informações não numeradas, mas você pode fazer coisas básicas apenas com gensiot se tudo que você precisa é de um canal de comunicação. Por exemplo, se você quiser conversar com alguém pelo rádio, e a porta do beijo estiver em 8001 em ambas as máquinas, na máquina aceitante você pode executar:
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),kiss,conacc,tcp,localhost,8001 '
que se conectará ao TNC e aguardará uma conexão no endereço AE5KM-1. Então você poderia executar:
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,localhost,8001 '
na outra máquina. Isto irá conectar-se à outra máquina através do TNC 0 com o endereço fornecido. Então, tudo o que você digitar em um aparecerá no outro, uma linha por vez. Digite “Ctrl-D” para sair. A parte 'stdio(self)' desativa o modo raw, então é uma linha por vez e você obtém eco local. Caso contrário, cada caractere digitado enviaria um pacote e você não conseguiria ver o que estava digitando.
Para conectar-se ao sistema N5COR-11 AX.25 BBS, você faria:
gensiot -i ' xlt(nlcr),stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,N5COR-11,AE5KM-2"),kiss,tcp,localhost,8001 '
A maioria dos sistemas BBS usa CR, e não NL, para a nova linha, então o xlt gensio é usado para traduzir esses caracteres recebidos.
Claro, sendo este gensio, você pode colocar qualquer gensio viável abaixo do ax25 que desejar. Então, se você quiser brincar ou testar sem rádio, você pode fazer ax25 por multicast UDP. Aqui está o lado do aceitante:
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 '
e aqui está o lado do 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 '
O beijo não é necessário porque o UDP já é uma mídia orientada a pacotes. Ou você pode usar o programa greflector para criar uma situação de rádio simulada. Na máquina "radiopi2", execute:
greflector kiss,tcp,1234
que criará um programa que refletirá todas as entradas recebidas para todas as outras conexões. Então, do lado do aceitante:
gensiot -i ' stdio(self) ' -a
' ax25(laddr=AE5KM-1),kiss,conacc,tcp,radiopi2,1234 '
e o lado de conexão:
gensiot -i ' stdio(self) '
' ax25(laddr=AE5KM-2,addr="0,AE5KM-1,AE5KM-2"),kiss,tcp,radiopi2,1234 '
O código de teste usa o refletor para alguns testes, pois é muito conveniente de usar.
Tudo isso está documentado detalhadamente em gensio(5). Salvo indicação em contrário, todos estes estão disponíveis como aceitantes ou gênios de conexão.
Você pode criar seus próprios gensios e registrá-los na biblioteca e empilhá-los junto com os demais gensios.
A maneira mais fácil de fazer isso é roubar o código de um gensio que faz exatamente o que você deseja e, em seguida, modificá-lo para criar seu próprio gensio. Infelizmente, não existe uma boa documentação sobre como fazer isso.
O arquivo de inclusão include/gensio/gensio_class.h possui a interface entre a biblioteca principal do gensio e o gensio. Todas as chamadas do gensio vêm através de uma única função com números para identificar a função que está sendo solicitada. Você tem que mapear tudo isso para as operações reais. Isso é um pouco doloroso, mas torna a compatibilidade com versões anteriores e posteriores muito mais fácil.
Criar seu próprio gensio dessa forma é bastante complexo. A máquina de estado para algo assim pode ser surpreendentemente complexa. A limpeza é a parte mais difícil. Você deve ter certeza de que está fora de todos os retornos de chamada e que nenhum temporizador pode ser chamado de volta em uma condição de corrida no desligamento. Apenas os gensios mais simples (echo, dummy), gensios estranhos (conadd, keepopen, stdio) e gensios que possuem canais (mux, ax25) implementam diretamente a interface. Todo o resto usa include/gensio/gensio_base.h. gensio_base fornece a máquina de estado básica para um gensio. Possui uma porção de filtro (que é opcional) e uma porção de baixo nível (ll), que não é.
A interface do filtro possui dados que passam por ela para processamento. Isso é usado para coisas como SSL, certauth, ratelimit, etc. Filter gensios usaria isso. Todos eles usam gensio_ll_gensio (para empilhar um gensio em cima de outro gensio) para o ll.
Cada um dos gensios terminais tem seu próprio preenchimento e geralmente nenhum filtro. Para lls baseados em um descritor de arquivo (fd), gensio_ll_fd é usado. Há também um ll para IPMI serial-over-lan (ipmisol) e para som. A maioria dos gênios de terminal (tcp, udp, sctp, porta serial, pty) usa o fd ll, obviamente.
Depois de ter um gensio, você pode compilá-lo como um módulo e colá-lo em $(moduleinstalldir)/<versão>. Então o gensio irá simplesmente pegá-lo e usá-lo. Você também pode vinculá-lo ao seu aplicativo e executar a função init do seu aplicativo.
O mdns gensio já foi discutido, mas a biblioteca gensio fornece uma interface mDNS fácil de usar. O arquivo de inclusão para ele está em gensio_mdns.h, e você pode usar a página man gensio_mdns(3) para obter mais informações sobre ele.
Para fazer uma conexão mdns usando geniot, digamos que você tenha o ser2net configurado com o mdns habilitado como:
connection : &my-port
accepter : telnet(rfc2217),tcp,3001
connector : serialdev,/dev/ttyUSB1,115200N81
options :
mdns : true
então você pode conectá-lo com o geniot:
gensiot ' mdns,my-port '
O gensiot encontrará o servidor, a porta e se o telnet e o rfc2217 estão habilitados e fará a conexão.
Além disso, existe uma ferramenta gmdns que permite fazer consultas e publicidade, e gtlssh pode fazer consultas mDNS para encontrar serviços. Se você tiver logins autenticados seguros para ser2net e ativar mdns em ser2net, como:
connection : &access-console
accepter : telnet(rfc2217),mux,certauth(),ssl,tcp,3001
connector : serialdev,/dev/ttyUSBaccess,115200N81
options :
mdns : true
isso torna a configuração muito conveniente, pois você pode simplesmente fazer:
gtlssh -m access-console
Isso mesmo, você pode usar diretamente o nome da conexão, sem necessidade de saber o host, se o telnet ou rfc2217 está habilitado, ou qual é a porta. Você ainda precisa configurar as chaves e outras coisas no servidor ser2net, é claro, de acordo com essas instruções.
gensio possui uma interface orientada a objetos orientada a eventos. Interfaces síncronas também estão disponíveis. Você lida com dois objetos principais no gensio: um gensio e um aceitador de gensio. Um gensio fornece uma interface de comunicação onde você pode conectar, desconectar, escrever, receber, etc.
Um aceitador gensio permite receber conexões de entrada. Se entrar uma conexão, isso te dá um gênio.
A interface é orientada a eventos porque, em sua maior parte, é completamente sem bloqueio. Se você abrir um gensio, você fornecerá um retorno de chamada que será chamado quando a conexão estiver ativa ou quando a conexão falhar. O mesmo para perto. Uma gravação retornará o número de bytes aceitos, mas pode não ocupar todos os bytes (ou mesmo qualquer um dos bytes) e o chamador deve levar isso em consideração.
As interfaces de abertura e fechamento possuem uma interface de bloqueio secundária por conveniência. Eles terminam em _s. Isso é por conveniência, mas não é necessário e o uso deles deve ser cuidadoso porque você realmente não pode usá-los em retornos de chamada.
Falando em callbacks, os dados e informações que chegam do gensio ao usuário são feitos por meio de uma função callback. Leia os dados e, quando o gensio estiver pronto para gravar os dados, ele retornará em um retorno de chamada. Uma interface semelhante é usada para chamar o usuário para a camada gensio, mas está oculta do usuário. Este tipo de interface é facilmente extensível, novas operações podem ser facilmente adicionadas sem quebrar as interfaces antigas.
A biblioteca fornece várias maneiras de criar um gensio ou aceitador gensio. A forma principal é str_to_gensio() e str_to_gensio_accepter(). Eles fornecem uma maneira de especificar uma pilha de gensios ou aceitantes como uma string e construção. Em geral, você deve usar esta interface, se puder.
Em geral, as interfaces que não são sensíveis ao desempenho são baseadas em strings. Você verá isso em gensio_control e em dados auxiliares na interface de leitura e gravação para controlar certos aspectos da gravação.
A biblioteca também oferece maneiras de configurar seus gensios criando cada um individualmente. Em algumas situações isto pode ser necessário, mas limita a capacidade de usar novos recursos da biblioteca gensio à medida que ela é estendida.
Se um gensio suportar múltiplos streams (como SCTP), os números dos streams serão passados no auxdata com "stream=n". Os fluxos não são controlados individualmente.
Os canais, por outro lado, são fluxos separados de dados na mesma conexão. Os canais são representados como gensios separados e podem ter fluxo controlado individualmente.
Existem alguns arquivos de inclusão com os quais você pode precisar lidar ao usar gensios:
Em sua maioria, eles estão documentados nas páginas de manual.
Para criar seus próprios gensios, os seguintes arquivos incluídos estão disponíveis para você:
Cada arquivo de inclusão possui muita documentação sobre as chamadas e manipuladores individuais.
gensio possui seu próprio conjunto de erros para abstraí-lo dos erros do sistema operacional (denominado GE_xxx) e fornecer mais flexibilidade no relatório de erros. Eles estão no arquivo de inclusão gensio_err.h (incluído automaticamente em gensio.h) e podem ser traduzidos de números para uma string significativa com gensio_err_to_str(). Zero é definido como não sendo um erro.
Se ocorrer um erro de sistema operacional não reconhecido, GE_OSERR será retornado e um log será relatado por meio da interface de log do manipulador do SO.
Uma coisa um pouco irritante sobre o gensio é que ele exige que você forneça um manipulador de sistema operacional (struct gensio_os_funcs) para lidar com funções do tipo sistema operacional, como alocação de memória, mutexes, a capacidade de lidar com descritores de arquivos, temporizadores e tempo, e algumas outras coisas.
A biblioteca fornece vários manipuladores de sistema operacional. Você pode chamar gensio_alloc_os_funcs() para alocar um padrão para o seu sistema (POSIX ou Windows). Você pode ver essa página de manual para mais detalhes. Geralmente, essa será a opção de melhor desempenho que você tem para o seu sistema.
Para sistemas POSIX, estão disponíveis manipuladores de SO para glib e TCL, alocados com gensio_glib_funcs_alloc() e gensio_tcl_funcs_alloc(). Eles realmente não funcionam muito bem, especialmente do ponto de vista do desempenho, as APIs para glib e TCL não são bem projetadas para o que o gensio faz. O TCL só pode suportar operações de thread único. A operação multithreaded simplista tem apenas um thread por vez aguardando E/S. Mas eles funcionam e os testes são executados com eles. Eles não estão disponíveis no Windows devido à falta de abstrações no glib e à falta de motivação no TCL.
Mas se você estiver usando algo como X Windows, etc., que possui seu próprio loop de eventos, pode ser necessário adaptar um para suas necessidades. Mas o bom é que você pode fazer isso e integrar o gensio com praticamente qualquer coisa.
Há também uma interface de espera que fornece uma maneira conveniente de aguardar que as coisas ocorram durante a execução do loop de eventos. É assim que você geralmente entra no loop de eventos, porque fornece uma maneira conveniente de sinalizar quando você termina e precisa sair do loop.
A documentação para isso está em:
include/gensio/gensio_os_funcs.h
A biblioteca gensio oferece suporte total a threads e é totalmente segura para threads. No entanto, ele usa sinais no sistema POSIX e COM nos sistemas Windows, portanto, é necessária alguma configuração.
O thread "principal" deve chamar gensio_os_proc_setup() na inicialização e chamar gensio_os_proc_cleanup() quando estiver concluído. Isso configura sinais e manipuladores de sinais, encadeia armazenamento local no Windows e outros tipos de coisas.
Você pode gerar novos threads a partir de um thread que já está configurado usando gensio_os_new_thread(). Isso fornece um thread básico do sistema operacional e está configurado corretamente para o gensio.
Se você tiver uma thread criada por outros meios que deseja usar no gensio, desde que a thread crie outra thread e não faça nenhuma função de bloqueio (qualquer tipo de espera, processamento em segundo plano, funções que terminam em _s como read_s, etc.) você não precisa configurá-los. Dessa forma, algum thread externo pode gravar dados, ativar outro thread ou fazer coisas assim.
Se um thread externo precisar fazer essas coisas, ele deverá chamar gensio_os_thread_setup().
Conforme mencionado na seção de threads, a biblioteca gensio no Unix usa sinais para ativações entre threads. Eu olhei bem e realmente não há outra maneira de fazer isso de forma limpa. Mas o Windows também tem algumas coisas semelhantes a sinais, e elas também estão disponíveis no gensio.
Se você usar gensio_alloc_os_funcs(), você obterá funções do sistema operacional usando o sinal transmitido para IPC. Você pode passar GENSIO_OS_FUNCS_DEFAULT_THREAD_SIGNAL para o sinal se desejar o padrão, que é SIGUSR1. O sinal que você usa será bloqueado e assumido pelo gensio, você não poderá usá-lo.
gensio também fornece manipulação genérica para alguns sinais. No Unix, ele irá manipular o SIGHUP através da função gensio_os_proc_register_reload_handler().
No Windows e Unix você pode usar gensio_os_proce_register_term_handler(), que irá lidar com solicitações de encerramento (SIGINT, SIGTERM, SIGQUIT no Unix) e gensio_os_proc_register_winsize_handler() (SIGWINCH no Unix). A forma como eles chegam pelo Windows é um pouco mais confusa, mas invisível para o usuário.
Todos os retornos de chamada da espera de uma rotina em espera, não do manipulador de sinal. Isso deve simplificar muito a sua vida.
Você pode ver as páginas de manual para obter mais detalhes sobre tudo isso.
Para criar um gensio, a maneira geral de fazer isso é chamar str_to_gensio()
com uma string formatada corretamente. A string é formatada assim:
<tipo>[([<opção>[,<opção[...]]])][,<tipo>...][,<opção final>[,<opção final>]]
A end option
é para gensios terminais, ou aqueles que ficam no fundo da pilha. Por exemplo, tcp,localhost,3001
criará um gensio que se conecta à porta 3001 no localhost. Para uma porta serial, um exemplo é serialdev,/dev/ttyS0,9600N81
criará uma conexão com a porta serial /dev/ttyS0.
Isso permite empilhar camadas gensio sobre camadas gensio. Por exemplo, para colocar telnet em camadas sobre uma conexão TCP:
telnet,tcp,localhost,3001
Digamos que você queira ativar o RFC2217 na sua conexão Telnet. Você pode adicionar uma opção para fazer isso:
telnet(rfc2217=true),tcp,localhost,3001
Ao criar um gensio, você fornece um retorno de chamada com dados do usuário. Quando eventos acontecem em um gensio, o callback será chamado para que o usuário possa tratá-lo.
Um aceitador gensio é semelhante a um gensio de conexão, mas com str_to_gensio_accepter()
em vez disso. O formato é o mesmo. Por exemplo:
telnet(rfc2217=true),tcp,3001
criará um aceitante TCP com telnet no topo. Para aceitadores, geralmente não é necessário especificar o nome do host se quiser vincular-se a todas as interfaces na máquina local.
Depois de criar um gensio, ele ainda não estará aberto ou operacional. Para usá-lo, você precisa abri-lo. Para abri-lo, faça:
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 }
Observe que quando gensio_open()
retorna, o gensio não está aberto. Você deve esperar até que o retorno de chamada ( tcp_open_done()
neste caso) seja chamado. Depois disso, você pode usá-lo.
Depois que o gensio estiver aberto, você não obterá nenhum dado imediatamente porque o recebimento está desativado. Você deve chamar gensio_set_read_callback_enable()
para ativar e desativar se o retorno de chamada ( tcpcb
neste caso) será chamado quando os dados forem recebidos.
Quando o manipulador de leitura é chamado, o buffer e o comprimento são passados. Você não precisa manipular todos os dados se não puder. Você deve atualizar o buflen com o número de bytes que você realmente administrou. Se você não manipular dados, os dados não manipulados serão armazenados em buffer no gensio para uso posterior. Não que se você não manipular todos os dados, deva desligar a habilitação de leitura ou o evento será imediatamente chamado novamente.
Se algo der errado em uma conexão, o manipulador de leitura será chamado com um conjunto de erros. buf
e buflen
serão NULL neste caso.
Para escrever, você pode chamar gensio_write()
para escrever dados. Você pode usar gensio_write()
a qualquer momento em um gensio aberto. gensio_write()
pode não levar todos os dados que você escreve nele. O parâmetro count
retorna o número de bytes realmente obtidos na chamada de gravação.
Você pode projetar seu código para chamar gensio_set_write_callback_enable()
quando tiver dados para enviar e o gensio chamará o retorno de chamada pronto para gravação e você poderá escrever a partir do retorno de chamada. Geralmente isso é mais simples, mas ativar e desativar o retorno de chamada de gravação adiciona alguma sobrecarga.
Uma abordagem mais eficiente é gravar dados sempre que necessário e desabilitar o retorno de chamada de gravação. Se a operação de gravação retornar menos que a solicitação completa, a outra extremidade será controlada por fluxo e você deverá ativar o retorno de chamada de gravação e aguardar até que ele seja chamado antes de enviar mais dados.
Nos retornos de chamada, você pode obter os dados do usuário passados para a chamada de criação com gensio_get_user_data()
.
Observe que se você abrir e fechar imediatamente um gensio, tudo bem, mesmo que o retorno de chamada aberto não tenha sido chamado. O retorno de chamada aberto pode ou não ser chamado nesse caso, portanto, pode ser difícil lidar com isso corretamente.
Você pode fazer E/S síncrona básica com gensios. Isso é útil em algumas situações em que você precisa ler algo in-line. Para fazer isso, ligue:
err = gensio_set_sync ( io );
O gensio fornecido deixará de entregar eventos de leitura e gravação. Outros eventos são entregues. Então você pode fazer:
err = gensio_read_s ( io , & count , data , datalen , & timeout );
err = gensio_write_s ( io , & count , data , datalen , & timeout );
Count é definido como o número real de bytes lidos/gravados. Pode ser NULL se você não se importa (embora isso não faça muito sentido para leitura).
O tempo limite pode ser NULL; nesse caso, espere para sempre. Se você definir um tempo limite, ele será atualizado com o tempo restante.
Observe que os sinais farão com que eles retornem imediatamente, mas nenhum erro será relatado.
As leituras serão bloqueadas até que alguns dados cheguem e os retornem. Ele não espera até que o buffer esteja cheio. timeout é um timeval, a leitura aguardará esse período de tempo para que a leitura seja concluída e retorne. Um tempo limite não é um erro, a contagem será apenas zerada.
Grava o bloco até que todo o buffer seja gravado ou ocorra um tempo limite. Novamente, o tempo limite não é um erro, o total de bytes realmente gravados é retornado em contagem.
Quando terminar de fazer E/S síncrona com um gensio, chame:
err = gensio_clear_sync ( io );
e a entrega através da interface do evento continuará como antes. Você não deve estar em uma chamada de leitura ou gravação síncrona ao chamar isso, os resultados serão indefinidos.
Observe que outras E/S em outros gensios ainda ocorrerão ao aguardar E/S síncrona
Atualmente não há uma maneira de esperar por vários gensios com E/S síncrona. Se você estiver fazendo isso, você deve apenas usar a E/S orientada a eventos. É mais eficiente e você acaba fazendo a mesma coisa no final.
Assim como um gensio, um aceitador de gensio não está operacional quando você o cria. Você deve chamar gensio_acc_startup()
para habilitá-lo:
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 }
Observe que não há retorno de chamada para a chamada de inicialização para saber quando ela está habilitada, porque não há necessidade real de saber porque você não pode escrever nela, ela apenas faz retornos de chamada.
Mesmo depois de iniciar o aceitante, ele ainda não fará nada até que você chame gensio_acc_set_accept_callback_enable()
para ativar esse retorno de chamada.
Quando o callback é chamado, ele dá um gensio no parâmetro data
que já está aberto com leitura desabilitada. Um gensio recebido de um aceitador de gensio pode ter algumas limitações. Por exemplo, talvez você não consiga fechá-lo e reabri-lo.
Os aceitadores gensio podem fazer aceitações síncronas usando gensio_acc_set_sync()
e gensio_acc_accept_s
. Consulte as páginas de manual para obter detalhes.
struct gensio_os_funcs
possui um retorno de chamada de vlog para lidar com logs internos do gensio. Eles são chamados quando algo significativo acontece, mas o gensio não tem como relatar um erro. Também pode ser chamado para facilitar o diagnóstico de um problema quando algo dá errado.
Cada uma das classes aceitadoras gensio e gensio possui subclasses para lidar com E/S serial e definir todos os parâmetros associados a uma porta serial.
Você pode descobrir se um gensio (ou qualquer um de seus filhos) é uma porta serial chamando gensio_to_sergensio()
. Se retornar NULL, não é um sergensio e nenhum de seus filhos é sergensios. Se retornar diferente de NULL, ele retornará o objeto sergensio para você usar. Observe que o gensio retornado por sergensio_to_gensio()
será aquele passado para gensio_to_sergensio()
, não necessariamente o gensio ao qual sergensio está diretamente associado.
Um sergensio pode ser um cliente, o que significa que pode definir configurações seriais, ou pode ser um servidor, o que significa que receberá configurações seriais do outro lado da conexão.
A maioria dos sergensios são apenas clientes: serialdev (porta serial normal), ipmisol e stdio accepter. Atualmente, apenas o telnet possui recursos de cliente e servidor.
NOTA: A interface python descrita aqui está obsoleta. Use aquele em c++/swig/pygensio agora.
Você pode acessar praticamente toda a interface do gensio por meio de python, embora seja feito de maneira um pouco diferente da interface C.
Como o python é totalmente orientado a objetos, gensios e aceitadores gensio são objetos de primeira classe, junto com gensio_os_funcs, sergensios e waiters.
Aqui está um pequeno 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 ()
A interface é uma tradução bastante direta da interface C. Uma representação python da interface está em swig/python/gensiodoc.py, você pode ver isso na documentação.
A interface C++ está documentada em c++/README.rst.
A nova interface pygensio é uma implementação mais limpa usando diretores swig em vez de retornos de chamada codificados manualmente em python. Consulte o README.rst em c++/swig/pygensio. Existem também OS_Funcs glib e tcl nos diretórios glib e tcl.
A interface C++ completa está disponível para programas Go por meio dos diretores swig e swig. Consulte c++/swig/go/README.rst para obter detalhes.
Este é um sistema autoconf normal, nada de especial. Observe que se você obtiver isso diretamente do git, não terá a infraestrutura de construção incluída. Existe um script chamado “reconf” no diretório principal que irá criá-lo para você.
Se você não conhece o autoconf, o arquivo INSTALL contém algumas informações, ou pesquise no Google.
Para construir totalmente o gensio, você precisa do seguinte:
O seguinte define tudo, exceto openipmi, no Ubuntu 20.04:
- sudo apt 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
No Redhat, o libwrap desapareceu, então você não o usará, e o swig não parece estar disponível, então você terá que construí-lo sozinho com pelo menos suporte go e python. Aqui está o comando para sistemas do tipo Redhat:
- sudo yum instalar gcc gcc-c++ git python3-devel gole openssl-devel
- pkg-config avahi-devel libtool Autoconf Automake Faça lksctp-tools-devel pam-devel glib2-devel
Você pode precisar fazer o seguinte para permitir o acesso aos pacotes de desenvolvimento:
SUDO DNF Config-manager-Desenvolto habilitado
E obtenha os módulos do kernel SCTP, você pode ter que fazer:
sudo yum install modules-extra
Para usar o idioma Go, você deve obter uma versão do SWIG 4.1.0 ou maior. Você pode ter que retirar uma versão de borda sangrando do Git e usá -la.
O manuseio da configuração de instalação do Python é um pouco de dor. Por padrão, os scripts de construção o colocarão onde quer que o programa Python espere que os programas Python instalados estejam. Um usuário normal geralmente não tem acesso a esse diretório.
Para substituir isso, você pode usar as opções de configuração de configuração de configuração de configuração ou ----with-pythoninstalllib ou definir as variáveis de ambiente PythonInstalldir e PythonInstalllibdir para onde você deseja que as bibliotecas e os módulos sejam.
Observe que você pode precisar definir-com bloqueio de UUCP para o seu Lockdir (em sistemas mais antigos, ele é/var/bloqueio, que é o padrão. No mais novo um membro de grupos de discagem e bloqueio para poder abrir dispositivos e/ou bloqueios.
O suporte ao idioma GO exige que vá para ser instalado e no caminho.
Enquanto eu continuava a adicionar gensios à biblioteca, como criptografia, mdns, som, ipmi, sctp etc. O número de dependências na biblioteca estava saindo de controle. Por que você deveria estar carregando Libasound, ou Libopenipmi, se você não precisa? Além disso, embora a biblioteca suportasse a adição de seus próprios gensios por meio de uma API programática, ela não tinha uma maneira padrão de adicioná -los ao sistema para que você pudesse escrever seu próprio Gensio e deixar todos no sistema usá -lo.
A biblioteca Gensio suporta carregar gensios dinamicamente ou construí -los na biblioteca. Por padrão, se você criar bibliotecas compartilhadas, todos os gensios serão compilados como módulos para carregamento dinâmico e instalados em um local que possibilita. Se você não criar bibliotecas compartilhadas, todos os Gensios serão incorporados à biblioteca. Mas você pode substituir esse comportamento.
Para definir todos os gensios a serem incorporados na biblioteca, você pode adicionar "--with-all-gensios = sim" na linha de comando de configuração e ela os criará na biblioteca.
Você também pode configurá-los como todos carregados dinamicamente, adicionando "--with-all-gensios = dinâmico", mas esse é o padrão.
Você também pode desativar todos os gensios por padrão, especificando "--with-all-gensios = não". Em seguida, nenhum Gensios será construído por padrão. Isso é útil se você quiser apenas alguns gensios, poderá desligar todos eles e ativar então aqueles que deseja.
Para definir como os gensios individuais são construídos, você faz "---with- <gensio> = x" onde x é "não (não construa), sim (construa na biblioteca) ou dinâmico (executável carregado dinamicamente). Por exemplo,, por exemplo,, por exemplo, Se você quisesse construir o TCP Gensio na biblioteca e tornar o restante dinâmico, você pode configurar para todos os gensios dinâmicos e adicione "---with-Net = sim".
Esses módulos são colocados por padrão em
Observe que o carregamento dinâmico está sempre disponível, mesmo se você construir todos os Gensios na biblioteca. Assim, você ainda pode adicionar seus próprios gensios adicionando ao diretório adequado.
O GENSIOS será carregado primeiro na variável de ambiente LD_LIBRARY_PATH, depois de gensio_library_path, depois no local padrão.
MacOS, sendo uma espécie de * nix, constrói bem de maneira limpa com o homebrew (https://brew.sh). É claro que você precisa instalar todas as bibliotecas necessárias. Quase tudo funciona, com as seguintes exceções:
* CM108GPIO * SCTP * bloqueio da UUCP
O código DNSSD interno é usado para MDNs, portanto avahi não é necessária.
O bloqueio de rebanho para as portas seriais funciona, portanto o bloqueio da UUCP realmente não é necessário.
O OpenIPMI deve funcionar, mas não está disponível no Homebrew, então você precisaria construí -lo sozinho.
Instale o software necessário:
- pkg install gcc Portaudio Autoconf Automake Libtool MdnsResponder Swig
- vá python3 gmake
Você precisa usar o GMake para compilá -lo, por algum motivo, a marca padrão no BSD não aceita a variável "C ++" em uma lista de requisitos. Os seguintes não funcionam e não são compilados:
* SCTP * ipmisol * CM108GPIO
Adicione o seguinte a /etc/rc.conf:
mdnsd_enable = sim
E reinicie ou inicie o serviço.
O Pty Gensio falha no oomtest (oomtest 14), parece haver algo com os BSD Ptys. Estou vendo um caractere 07 inserido no fluxo de dados nos casos. No entanto, não gastei muito tempo, mas como isso é fortemente testado em Linux e MacOS, não acho que o problema esteja no código Gensio.
A biblioteca Gensio pode ser construída no Windows usando o MingW64. As seguintes coisas não funcionam:
* SCTP * Pam * libwrap * ipmisol
Você também não precisa instalar o ALSA, ele usa a interface de som do Windows para som.
O CM108GPIO usa interfaces nativas do Windows, para que o UDEV não seja necessário.
As interfaces MDNs embutidas do Windows são usadas, para que você não precise de Avahi ou DNSSD. Você precisará instalar a biblioteca do PCRE se desejar expressões regulares nela.
Você precisa obter MSYS2 de https://msys2.org. Em seguida, instale a AutoConf, Autorake, Libtool, Git, Make e Swig como ferramentas de host:
Pacman -S Autoconf Automake Libtool Git Make Swig
Você precisa instalar a versão Mingw-W64-X86_64-XXX de todas as bibliotecas ou a versão Mingw-W64-I686-XXX de todas as bibliotecas. 32 bits não está bem testado:
Pacman -S Mingw-W64-X86_64-GCC Mingw-W64-X86_64-Python3 Mingw-W64-X86_64-PCRE Mingw-W64-X86_64-Openssl
para Mingw64, ou 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 ir, instale, vá de https://go.dev e faça o login e faça o login. Ele deve estar no caminho, mas se não for, você precisará adicioná -lo ao caminho. Eu não comecei a trabalhar no Mingw32, mas não experimentei uma versão de 32 bits do Go.
Para GTLSSHD, - -SysConfdir não tem significado no Windows. Em vez disso, o diretor sysconf é relativo ao patch do executável, em ../etc/gtlssh. Então, se o GTLSSHD estiver em:
C:/Arquivos de programas/gensio/bin/gtlsshd
O sysconfdir será:
C:/Arquivos de programas/gensio/etc/gtlssh
Para instalação padrão, você pode executar:
../configure -sbindir =/gensio/bin - -Libexecdir =/gensio/bin -mandir =/gensio/man --includedir =/gensio/incluir ---with-pythoninstall =/gensio/python3 --prefix =/gensio
E quando você executa "Make Install Destdir = ..." e você define o Destdir para onde deseja que ele vá, como "C:/Arquivos de Programas". Em seguida, você pode adicioná -lo ao caminho usando o painel de controle. Para usar o GTLSSHD, você cria um diretório etc/gtlsshd no diretório Gensio. Você deve definir as permissões neste diretório para que apenas o sistema e os administradores tenham acesso, como:
PS C: Arquivos de Programas (x86) Gensio etc> ICACLS GTLSSH GTLSSH nt Authority System: (OI) (IC) (f) Administradores construídos: (OI) (IC) (f)
Caso contrário, o GTLSSHD falhará com um erro sobre permissões na chave. Você pode definir essas permissão no arquivo .Key em vez do diretório, mas precisará defini -lo novamente toda vez que gerar uma nova chave.
Para usar o compilador de configuração do Inno, faça "Faça instalar destdir = $ home/install" e depois execute inno no gensio.iss. Ele criará um instalador executável para a instalação do Gensio.
Então você precisa remover os arquivos .LA do diretório de instalação, enquanto eles estragam a ligação com outras coisas:
rm $ home/install/gensio/lib/*
Existem vários testes para Gensios. Todos eles são executados no Linux se você tiver o módulo Kernel SerialSim. Além das portas em série, elas são executadas em outras plataformas, pois os Gensios são suportados nessa plataforma.
Os testes de porta serial requerem o módulo do kernel serialsim e a interface Python. Estes estão em https://github.com/cminyard/serialsim e permitem que os testes usem uma porta serial simulada para ler a linha de controle moderna, erros de injeção, etc.
Você pode sobreviver sem serialsim se tiver três dispositivos seriais: um conectado no modo de eco (RX e TX amarrados) e dois dispositivos seriais conectados em conjunto fazem com que a E/S em um dispositivo vai/vem de outro. Isso deve funcionar em plataformas não-Linux. Em seguida, defina as seguintes variáveis de ambiente:
export GENSIO_TEST_PIPE_DEVS= " /dev/ttyxxx:/dev/ttywww "
export GENSIO_TEST_ECHO_DEV= " /dev/ttyzzz "
Não será capaz de testar o ModemState ou RS485.
Eles também exigem o programa IPMI_SIM da biblioteca OpenIPMI em https://github.com/cminyard/openipmi para executar os testes ipmisol.
Para executar os testes, você precisa permitir que alguma depuração interna obtenha o efeito total. Você geralmente quer executar algo como:
./configure --enable-internal-trace CFLAGS= ' -g -Wall '
Você também pode ativar -o3 nos cflags, se quiser, mas isso dificulta a depuração.
Existem dois tipos básicos de testes. Os testes Python são testes funcionais testando a interface Python e a biblioteca Gensio. Atualmente, eles estão bem, mas há muito espaço para melhorias. Se você quiser ajudar, pode escrever testes.
O Oomtest costumava ser um testador fora da memória, mas se transformou em algo mais extenso. Ele gera um programa de gensiot com variáveis de ambiente específicas para fazer com que ele falhe em determinados pontos e faça vazamento de memória e outras verificações de memória. Ele grava dados para o gensiot através de seu stdin e recebe dados no stdout. Alguns testes (como serialdev) usam um eco. Outros testes criam uma conexão separada sobre a rede e os fluxos de dados no stdin e voltam sobre a conexão separada, e flui para a conexão separada e volta através do stdout. Oomtest é multi-thread e o número de threads pode ser controlado. Oomtest encontrou muitos bugs. Ele tem muitos botões, mas você precisa olhar para o código -fonte das opções. Ele precisa ser documentado, se alguém quiser ser voluntário ...
Para configurar -se para fuzzing, instale a AFL e configure com o seguinte:
mkdir Zfuzz ; cd Zfuzz
../configure --enable-internal-trace=yes --disable-shared --with-go=no
CC=afl-gcc CXX=afl-g++
Ou use Clang, se disponível:
../configure --enable-internal-trace=yes --disable-shared --with-go=no
CC=afl-clang-fast CXX=afl-clang-fast++ LIBS= ' -lstdc++ '
Não sei por que a coisa da LIBS é necessária acima, mas tive que adicioná -lo para compilar.
Em seguida, construa. Em seguida, "CD Testes" e Run "Make test_fuzz_xxx", onde xxx é um dos: Certauth, Mux, SSL, Telnet ou Relpkt. Você provavelmente precisará ajustar algumas coisas, a AFL dirá. Observe que ele será executado para sempre, você precisará ^c quando terminar.
O Makefile em testes/makefile.am tem instruções sobre como lidar com uma falha em reproduzir para depuração.
A cobertura de código na biblioteca é bem fácil. Primeiro, você precisa configurar o código para ativar a cobertura:
mkdir Ocov ; cd Ocov
../configure --enable-internal-trace=yes
CC= ' gcc -fprofile-arcs -ftest-coverage '
CXX= ' g++ -fprofile-arcs -ftest-coverage '
A compilação e a execução "Faça o check".
Para gerar o relatório, execute:
gcovr -f ' .*/.libs/.* ' -e ' .*python.* '
Isso gerará um resumo. Se você quiser ver a cobertura de linhas individuais em um arquivo, você pode fazer:
cd lib
gcov -o .libs/ * .o
Você pode procurar nos arquivos .gcov individuais criados para obter informações sobre o que é coberto. Veja os documentos do GCOV para obter detalhes.
No momento da redação deste artigo, eu estava recebendo cerca de 74% de cobertura de código, então isso é muito bom. Estarei trabalhando para melhorar isso, principalmente por meio de testes funcionais aprimorados.
O SER2NET é usado para testar algumas coisas, principalmente a configuração da porta serial (Termios e RFC2217). Você pode construir o Ser2net contra a versão GCOV da biblioteca Gensio e executar "Faça o check" no Ser2net para obter cobertura nessas partes. Com isso, estou vendo cerca de 76% de cobertura, por isso não adiciona muito ao total.
Seria bom ser capaz de combinar isso com o Fuzzing, mas não tenho certeza de como fazer isso. AFL faz sua própria coisa com cobertura de código. Parece haver um pacote AFL-CoV que, de alguma forma, o GCOV integrado, mas eu não analisei.