Development of Continent-AP VPN Plugin for Sailfish OS

Introduction



I work as a programmer in the development and testing department of security products for mobile platforms of the Security Code company. The mobile development team was tasked with porting the Continent-AP cross-platform library of the subscriber station, which had already successfully operated on IOS and Android. The main problem was that the Sailfish OS is not as well documented as Android or IOS, but thanks to the guys from Open Mobile Platforms who shared the documentation.



Sailfish OS VPN architecture



For all network connections in the Sailfish OS, the ConnMan service is responsible, namely:





I’ll tell you a little more about the last point. ConnMan has several types of predefined VPN plugins. QML widgets for creating and configuring connections are integrated into the firmware.



image

Fig. 1 Sailfish OS System Menu for Configuring and Managing VPN Connections



The UI of our VPN client is an RPM package and during installation it does not integrate into the system window of the VPN in the Settings section, but looks like a separate application. There will probably be a separate article about the development of UI, so the following story will be about the development of the ConnMan plugin in C / C ++.



image

Fig. 2 Continent-AP GUI by Sailfish



VPN-api OS Sailfish consists of the following components, we show on the example of our VPN-client:



  1. ConnMan is the process that starts when the Sailfish OS starts.
  2. connman-vpnd is a daemon process launched by ConnMan and used to manage VPN connections of various providers, initialize and de-initialize the tun interface, assign network settings (IP addresses, routes, DNS servers) received via DBus to it. In our case, a provider named continent.
  3. The continent-proto-plugin.so VPN plugin is a library that has a macro declaration for loading into runtime and functions that are called when net.connman.vpn.Connection interface methods are called.
  4. The background application (binary file / usr / sbin / continent) is a console client for connecting to the CD (Continent Access Server), receiving network settings from it, which it passes to connman-vpnd.
  5. ConnMan Task is a process for starting, stopping, and monitoring a running console client.
  6. DBus-api - represents connman-vpnd, namely net.connman.vpn with the interfaces net.connman.vpn.Manager, net.connman.vpn.Connection.


image

Fig. 3 Interaction of components among themselves in "Continent-AP" Sailfish



VPN plugin



All third-party plugins not included in the ConnMan distribution represent the library and should be installed in / usr / lib / connman / plugins-vpn / when installing through the package manager.



A plugin can have a configuration file, in which we indicate which user to run the binary file from, and prescribe its rights. The file should be located in the system along the path /etc/connman/vpn-plugin/continent.conf, and its name should correspond to the name of our provider, in our case continent.conf.



File contents for example:



[VPN Binary] User = nemo Group = vpn SupplementaryGroups = inet,net_admin
      
      





The plugin continent-proto-plugin.so in ConnMan is registered using the macro CONNMAN_PLUGIN_DEFINE (name, description, version, init, exit), in our example, the macro call will look like this:



 CONNMAN_PLUGIN_DEFINE(continent, "continent VPN plugin", CONNMAN_VERSION, CONNMAN_PLUGIN_PRIORITY_DEFAULT, continent_init, continent_exit);
      
      





The name (continent) argument must be without quotation marks. The continent_init, continent_exit functions are called when the plugin is loaded and unloaded, for example, when systemctl restart connman is called during RPM installation. The continent_init function has a call to the vpn_register and connman_dbus_get_connection functions.



 vpn_register(name, driver, binary_path)
      
      





name - the name of the provider being registered, in our case it is “continent”;

driver - struct vpn_driver structure containing pointers to callback functions, for example, when accessing the plugin through DBus;

binary_path - path to the binary file, in our case it is “/ usr / sbin / continent”.



The connman_dbus_get_connection function allows you to get the established DBus connection, DBusConnection * connection.



The continent_exit function is required to unregister the plugin in ConnMan and close the DBus connection.



A VPN provider instance is created when DBus calls the net.connman.vpn.Manager.Create method, a settings file is automatically created for it in the /var/lib/connman/provider_${Hostasket_{VPN.Domain} directory. The provider is deleted by calling net.connman.vpn.Manager.Remove. When the net.connman.vpn.Connection.Connect method is called, the settings are loaded into the created struct vpn_provider * provider.



I would also like to talk about some functions in struct vpn_driver, some of them are required for implementation.



connect - callback, called when net.connman.vpn.Connection.Connect is called with the corresponding address of the DBus object via DBus, has the signature:



 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)
      
      





The second argument to this callback is struct connman_task * task, it will run the binary file, but you must pass arguments before starting, for example, the host and port of the server:



 connman_task_add_argument(task, "--host", value); connman_task_add_argument(task, "--port", value);
      
      





We store some parameters in the configuration file of the provider object instance, it was described above, and we get it by calling the vpn_provider_get_string function, for example:

 char * value = vpn_provider_get_string(provider , “Host”)
      
      





where provider is an instance of struct vpn_provider.



 connman_task_add_argument(task, "--dev-name", if_name).
      
      





The line above illustrates the name of the virtual interface that ConnMan-vpnd initializes and provides for reading and writing IP packets from the TUN interface instance raised for the current instance of the VPN provider. In the background process, it remains for us to open the device and get a file descriptor for reading / writing.



A short note: during the development of the plugin it turned out that the TUN interface is raised as the default route interface.



 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));
      
      





For feedback between the background application and the VPN plugin, we pass the ConnManTask DBus address and path to the current instance, for this we needed to call connman_dbus_get_connection in the initialization function.



We start the background process:



 err = connman_task_run(task, continent_died, data, &data->stdin_fd, NULL, NULL);
      
      





continent_died - callback called when the background process terminates. In it, we find out the error code for terminating the process, deploy the memory, delete the added routes.



notify - callback, called when net.connman.Task.notify is called via DBus, in it we receive DBus messages from a running background application. The main thing is the transmission of network parameters: the address of the TUN interface, the DNS server in the virtual network, etc. Network parameters are packed into DBusMessage in the form of a dictionary and transferred to the ConnMan Task, in which Dbus parameters sent when the background application is launched.



An example of initializing the TUN interface in the notify function:



 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;
      
      





We also pass intermediate values, for example, if we want to notify the UI of the event by writing to the properties of the current connection, which the UI can learn from the net.connman.vpn.Connection.GetProperties method, when changing Properties ConnMan sends a DBus signal PropertyChanged, for example: vpn_provider_set_string (provider, key, value).



disconnect - callback, called when net.connman.vpn.Connection.Disconnect is called via DBus

The stopping process occurs by sending a SIGTERM signal; if no shutdown has occurred within 3 seconds, a SIGKILL signal is sent.



Developing and debugging a VPN plugin



The assembly of the Continent-AP VPN plug-in and its components is performed on the Sailfish Build Engine OS virtual machine (Virtual Box), which is part of the Sailfish SDK. To build the plugin you need the libraries: connman-devel, dbus-1, glibs-2.0, which we install by logging in via ssh:



 ssh -p 2222 -i ~/SailfishOS/vmshare/ssh/private_keys/engine/mersdk mersdk@localhost
      
      





We use the sb2 utility (Scratchbox 2) - toolkit for cross-compilation. We install the necessary packages for the i486 and armv7hl platforms:



 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 and systemd-devel are needed for output to the system log using the sd_journal_print function. We install Cmake, since our project uses it, this greatly simplifies the assembly for different platforms.



We start the assembly of the VPN plugin and its components through 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
      
      







Next, we put the collected binary files and libraries into our UI project, which contains a SPEC file for generating the Continent-AP Sailfish distribution RPM package, slightly adjusting the section for installing our files in the device’s system folders in the SPEC file, for example:



 %files %defattr(-,root,root,-) %{_sbindir}/continent %{_libdir}/connman/plugins-vpn/continent-proto-plugin.so
      
      





The plugin was developed separately from the UI on the emulator, which is part of the Sailfish SDK, and gdbus and journalctl with the -f option in the constant log output mode helped a lot as debugging tools.



For example, creating an instance of a provider using gdbus:



 gdbus call --system --dest=net.connman.vpn --object-path / --method net.connman.vpn.Manager.Create "{ 'Type': <'continent'>, … }"
      
      





The test devices were INOI R7 (phone), INOI T8 (tablet) and an emulator based on VirtualBox



Useful links:



  1. ConnMan source code adapted for Sailfish can be found here .
  2. Skeleton - plugin project
  3. sailfishos.org/wiki



All Articles