1. Introdução
Trabalho como programador no departamento de desenvolvimento e teste de ferramentas de segurança para plataformas móveis da empresa Código de Segurança. A equipe de desenvolvimento móvel foi encarregada de transportar a biblioteca de plataforma cruzada Continent-AP da estação de assinante, que já havia operado com sucesso no IOS e no Android. O principal problema era que o sistema operacional Sailfish não é tão bem documentado quanto o Android ou IOS, mas graças aos caras das plataformas móveis abertas que compartilharam a documentação.
Arquitetura de API VPN no sistema operacional Sailfish
Para todas as conexões de rede no sistema operacional Sailfish, o serviço ConnMan é responsável, a saber:
- Digitalize redes Wi-Fi e redes celulares e conecte-se a elas;
- compartilhamento de conexão (ponto de acesso Wi-Fi);
- tradução, conexões de saída para o modo de voo;
- Gerenciamento de conexão VPN.
Vou falar sobre o último ponto em mais detalhes. O ConnMan possui vários tipos de plugins VPN predefinidos. Os widgets QML para criar e configurar conexões são integrados ao firmware.
Fig. 1 Menu Sistema Sailfish OS para configurar e gerenciar conexões VPNA interface do usuário do nosso cliente VPN é um pacote RPM e, durante a instalação, não se integra à janela do sistema VPN na seção Configurações, mas parece um aplicativo separado. Provavelmente, haverá um artigo separado sobre o desenvolvimento da interface do usuário; portanto, a próxima história será sobre o desenvolvimento do plug-in ConnMan no C / C ++.
Fig. 2 GUI para AP do continente por SailfishO sistema operacional VPN-api Sailfish consiste nos seguintes componentes, mostraremos pelo exemplo do nosso cliente VPN:
- ConnMan é o processo iniciado quando o sistema operacional Sailfish é iniciado.
- O connman-vpnd é um processo daemon iniciado pelo ConnMan e usado para gerenciar conexões VPN de vários provedores, inicializar e desinstalar a interface tun, atribuir configurações de rede (endereços IP, rotas, servidores DNS) recebidas via DBus a ele. No nosso caso, um provedor chamado continente.
- O plug-in VPN continent-proto-plugin.so é uma biblioteca que possui uma declaração de macro para carregar no tempo de execução e funções chamadas quando os métodos da interface net.connman.vpn.Connection são chamados.
- O aplicativo em segundo plano (arquivo binário / usr / sbin / continente) é um cliente de console para conectar-se ao CD (Continent Access Server), recebendo configurações de rede dele, que são transmitidas para connman-vpnd.
- O ConnMan Task é um processo para iniciar, parar e monitorar um cliente do console em execução.
- DBus-api - representa connman-vpnd, ou seja, net.connman.vpn com as interfaces net.connman.vpn.Manager, net.connman.vpn.Connection.
Fig. 3 Interação de componentes entre si no Sailfish "Continent-AP"Plugin VPN
Todos os plugins de terceiros não incluídos na distribuição do ConnMan representam a biblioteca e devem ser colocados em / usr / lib / connman / plugins-vpn / durante a instalação através do gerenciador de pacotes.
Um plug-in pode ter um arquivo de configuração, no qual indicamos de qual usuário executar o arquivo binário e prescrevemos seus direitos. O arquivo deve estar localizado no sistema ao longo do caminho /etc/connman/vpn-plugin/continent.conf, e seu nome deve corresponder ao nome do nosso provedor, no nosso caso continent.conf.
Conteúdo do arquivo, por exemplo:
[VPN Binary] User = nemo Group = vpn SupplementaryGroups = inet,net_admin
O plug-in continent-proto-plugin.so no ConnMan é registrado usando a macro CONNMAN_PLUGIN_DEFINE (nome, descrição, versão, init, exit), em nosso exemplo, a chamada de macro será semelhante a esta:
CONNMAN_PLUGIN_DEFINE(continent, "continent VPN plugin", CONNMAN_VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT, continent_init, continent_exit);
O argumento do nome (continente) deve estar sem aspas. As funções continent_init, continent_exit são chamadas quando o plug-in é carregado e descarregado, por exemplo, quando systemctl restart connman é chamado durante a instalação do RPM. A função continent_init possui uma chamada para as funções vpn_register e connman_dbus_get_connection.
vpn_register(name, driver, binary_path)
nome - o nome do provedor que está sendo registrado, no nosso caso, é "continente";
estrutura driver - struct vpn_driver contendo ponteiros para funções de retorno de chamada, por exemplo, ao acessar o plug-in através do DBus;
binary_path - caminho para o arquivo binário, no nosso caso, é "/ usr / sbin / continente".
A função connman_dbus_get_connection permite obter a conexão DBus estabelecida, conexão DBusConnection *.
A função continent_exit é necessária para cancelar o registro do plug-in no ConnMan e fechar a conexão DBus.
Uma instância do provedor de VPN é criada quando o DBus chama o método net.connman.vpn.Manager.Create, um arquivo de configurações é criado automaticamente para ele no diretório /var/lib/connman/provider_${Host}_{VPN.Domain}. O provedor é excluído chamando net.connman.vpn.Manager.Remove. Quando o método net.connman.vpn.Connection.Connect é chamado, as configurações são carregadas no provedor struct vpn_provider * criado.
Eu também gostaria de falar sobre algumas funções no struct vpn_driver, algumas delas são necessárias para implementação.
connect - callback, chamado quando net.connman.vpn.Connection.Connect é chamado com o endereço correspondente do objeto DBus via DBus, tem a assinatura:
static int continent_connect( struct vpn_provider *provider, struct connman_task *task, const char *if_name, vpn_provider_connect_cb_t cb, const char *dbus_sender, void *user_data)
O segundo argumento para esse retorno de chamada é uma tarefa struct connman_task *, ele executará o arquivo binário, mas você precisará passar argumentos antes de iniciar, por exemplo, o host e a porta do servidor:
connman_task_add_argument(task, "--host", value); connman_task_add_argument(task, "--port", value);
Armazenamos alguns parâmetros no arquivo de configuração da instância do objeto provedor, como descrito acima, e obtemos isso chamando a função vpn_provider_get_string, por exemplo:
char * value = vpn_provider_get_string(provider , “Host”)
em que o provedor é uma instância do struct vpn_provider.
connman_task_add_argument(task, "--dev-name", if_name).
A linha acima ilustra o nome da interface virtual que o ConnMan-vpnd inicializa e fornece a leitura e gravação de pacotes IP da instância da interface TUN criada para a instância atual do provedor VPN. No processo em segundo plano, resta abrir o dispositivo e obter um descritor de arquivo para leitura / gravação.
Uma observação breve: durante o desenvolvimento do plug-in, verificou-se que a interface TUN é gerada como a interface de rota padrão.
connman_task_add_argument(task, "--dbus-busname", dbus_bus_get_unique_name(connection)); connman_task_add_argument(task, "--dbus-interface", CONNMAN_TASK_INTERFACE); connman_task_add_argument(task, "--dbus-path", connman_task_get_path(task));
Para obter feedback entre o aplicativo em segundo plano e o plug-in VPN, passamos o endereço e o caminho do ConnManTask DBus para a instância atual. Para isso, precisamos chamar connman_dbus_get_connection na função de inicialização.
Iniciamos o processo em segundo plano:
err = connman_task_run(task, continent_died, data, &data->stdin_fd, NULL, NULL);
continent_died - retorno de chamada chamado quando o processo em segundo plano termina. Nele, descobrimos o código de erro para encerrar o processo, implantar a memória, excluir as rotas adicionadas.
notify - callback, chamado quando net.connman.Task.notify é chamado através do DBus, nele recebemos mensagens DBus de um aplicativo em segundo plano em execução. O principal é a transmissão dos parâmetros de rede: o endereço da interface TUN, o servidor DNS na rede virtual, etc. Os parâmetros de rede são empacotados no DBusMessage na forma de um dicionário e transferidos para a Tarefa ConnMan, na qual os parâmetros Dbus são enviados quando o aplicativo em segundo plano é iniciado.
Um exemplo de inicialização da interface TUN na função de notificação:
struct connman_ipaddress * ipaddress = connman_ipaddress_alloc(AF_INET); connman_ipaddress_set_ipv4(ipaddress, address, netmask, remote_server_ip); connman_ipaddress_set_peer(ipaddress, peer); vpn_provider_set_ipaddress(provider, ipaddress); vpn_provider_set_nameservers(provider, “8.8.8.8”); return VPN_STATE_CONNECT;
Também passamos valores intermediários, por exemplo, se queremos notificar a interface do usuário do evento gravando nas propriedades da conexão atual, que a interface do usuário pode aprender com o método net.connman.vpn.Connection.GetProperties, ao alterar Propriedades, o ConnMan envia um sinal DBus PropertyChanged, por exemplo: vpn_provider_set_string (provedor, chave, valor).
desconexão - retorno de chamada que é chamado quando net.connman.vpn.Connection.Disconnect é chamado via DBus
O processo de parada ocorre enviando um sinal SIGTERM; se nenhuma desconexão ocorrer dentro de 3 segundos, um sinal SIGKILL é enviado.
Desenvolvendo e depurando um plug-in VPN
A montagem do plug-in VPN do Continent-AP e seus componentes é realizada na máquina virtual Sailfish Build Engine OS (Caixa Virtual), que faz parte do Sailfish SDK. Para construir o plug-in, você precisa das bibliotecas: connman-devel, dbus-1, glibs-2.0, que instalamos efetuando login via ssh:
ssh -p 2222 -i ~/SailfishOS/vmshare/ssh/private_keys/engine/mersdk mersdk@localhost
Usamos o utilitário sb2 (Scratchbox 2) - kit de ferramentas para compilação cruzada. Instalamos os pacotes necessários para as plataformas i486 e armv7hl:
sb2 -t SailfishOS-3.0.1.11-i486 -m sdk-install -R zypper -n in cmake patchelf chrpath connman-devel systemd-compat-libs systemd-devel
sb2 -t SailfishOS-3.0.1.11-armv7hl -m sdk-install -R zypper -n in cmake patchelf chrpath connman-devel systemd-compat-libs systemd-devel
Systemd-compat-libs e systemd-devel são necessários para saída no log do sistema usando a função sd_journal_print. Instalamos o Cmake, uma vez que nosso projeto o utiliza, isso simplifica muito a montagem para diferentes plataformas.
Iniciamos a montagem do plug-in VPN e de seus componentes através do sb2 sdk-build:
sb2 -t SailfishOS-3.0.1.11-armv7hl -m sdk-build cmake . && make sb2 -t SailfishOS-3.0.1.11-i486 -m sdk-build cmake . && make
Em seguida, colocamos os arquivos binários e bibliotecas coletados em nosso projeto de interface do usuário, que contém o arquivo SPEC para gerar o pacote RPM Continent-AP Sailfish, ajustando ligeiramente a seção para instalar nossos arquivos nas pastas de sistema do dispositivo no arquivo SPEC, por exemplo:
%files %defattr(-,root,root,-) %{_sbindir}/continent %{_libdir}/connman/plugins-vpn/continent-proto-plugin.so
O plugin foi desenvolvido separadamente da interface do usuário no emulador, que faz parte do Sailfish SDK, e o gdbus e journalctl com a opção -f no modo de saída de log constante ajudaram bastante as ferramentas de depuração.
Por exemplo, criando uma instância de um provedor usando o gdbus:
gdbus call --system --dest=net.connman.vpn --object-path / --method net.connman.vpn.Manager.Create "{ 'Type': <'continent'>, … }"
Os dispositivos de teste foram INOI R7 (telefone), INOI T8 (tablet) e um emulador baseado no VirtualBox
Links úteis:
- O código fonte do ConnMan adaptado para Sailfish pode ser encontrado aqui .
- Esqueleto - projeto de plugin
- sailfishos.org/wiki