用于Sailfish OS的Continent-AP VPN插件的开发

引言


我在Security Code公司的移动平台安全工具的开发和测试部门担任程序员。 移动开发团队的任务是移植订户站的Continent-AP跨平台库,该库已在IOS和Android上成功运行。 主要问题是Sailfish OS的文档记录不如Android或IOS,但要感谢共享文档的Open Mobile Platforms的人员。

Sailfish OS中的VPN API架构


对于Sailfish OS中的所有网络连接,ConnMan服务负责:

  • 扫描Wi-Fi网络和蜂窝网络并连接到它们;
  • 共享连接(Wi-Fi热点);
  • 翻译,输出与飞行模式的连接;
  • VPN连接管理。

我将更详细地讨论最后一点。 ConnMan有几种类型的预定义VPN插件。 用于创建和配置连接的QML小部件已集成到固件中。

图片
1个用于配置和管理VPN连接的Sailfish OS系统菜单

我们的VPN客户端的用户界面是RPM软件包,在安装过程中不会集成到“设置”部分的“ VPN系统”窗口中,而是一个单独的应用程序。 可能会有单独的文章介绍UI的开发,因此下一个故事是有关C / C ++中ConnMan插件的开发的。

图片
2个Sailfish的Continent-AP GUI

VPN-api Sailfish操作系统包含以下组件,我们将以我们的VPN客户端示例进行展示:

  1. ConnMan是在Sailfish OS启动时启动的过程。
  2. connman-vpnd是由ConnMan启动的守护进程,用于管理各种提供程序的VPN连接,初始化和取消初始化tun接口,以及将通过DBus接收的网络设置(IP地址,路由,DNS服务器)分配给它。 在我们的例子中,一个提供者名为大洲。
  3. continent-proto-plugin.so VPN插件是一个库,该库具有用于加载到运行时的宏声明以及在调用net.connman.vpn.Connection接口方法时调用的函数。
  4. 后台应用程序(二进制文件/ usr / sbin / continent)是一个控制台客户端,用于连接到CD(Continent Access Server),并从中接收网络设置,并将其传递给connman-vpnd。
  5. ConnMan Task是用于启动,停止和监视正在运行的控制台客户端的过程。
  6. DBus-api-代表connman-vpnd,即net.connman.vpn,其接口为net.connman.vpn.Manager,net.connman.vpn.Connection。

图片
3“ Continent-AP”旗鱼中组件之间的相互作用

VPN插件


所有不属于ConnMan发行版的第三方插件都代表该库,并且在通过软件包管理器进行安装时,应将其放置在/ usr / lib / connman / plugins-vpn /中。

插件可能具有配置文件,在其中我们指示从哪个用户运行二进制文件,并规定其权限。 该文件应位于/etc/connman/vpn-plugin/continent.conf路径下的系统中,其名称应与我们的提供者的名称相对应,在本例中为continent.conf。

文件内容例如:

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

使用宏CONNMAN_PLUGIN_DEFINE(名称,描述,版本,init,exit)注册ConnMan中的continent-proto-plugin.so插件,在我们的示例中,宏调用将如下所示:

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

名称(大陆)参数必须不带引号。 例如,当在RPM安装过程中调用systemctl restart connman时,将在加载和卸载插件时调用continent_init,continent_exit函数。 continent_init函数可调用vpn_register和connman_dbus_get_connection函数。

 vpn_register(name, driver, binary_path) 

名称-正在注册的提供者的名称,在我们的情况下为“大陆”;
driver-struct vpn_driver结构,包含指向回调函数的指针,例如,通过DBus访问插件时;
binary_path-二进制文件的路径,在我们的例子中是“ / usr / sbin / continent”。

connman_dbus_get_connection函数允许您获取已建立的DBus连接DBusConnection *连接。

需要使用continent_exit函数才能在ConnMan中注销插件并关闭DBus连接。

当DBus调用net.connman.vpn.Manager.Create方法时,将创建一个VPN提供程序实例,并在/var/lib/connman/provider_${Hostasket_{VPN.Domain}目录中为其自动创建一个设置文件。 通过调用net.connman.vpn.Manager.Remove删除提供程序。 调用net.connman.vpn.Connection.Connect方法时,设置将加载到创建的struct vpn_provider *提供程序中。

我也想谈谈struct vpn_driver中的一些功能,其中一些功能是实现所必需的。

connect-通过DBus使用DBus对象的相应地址调用net.connman.vpn.Connection.Connect时调用的回调,其签名为:

 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) 

此回调的第二个参数是struct connman_task *任务,它将运行二进制文件,但是您需要在启动之前传递参数,例如,服务器的主机和端口:

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

我们将一些参数存储在提供程序对象实例的配置文件中,如上所述,并通过调用vpn_provider_get_string函数来获取它,例如:
 char * value = vpn_provider_get_string(provider , “Host”) 

其中provider是struct vpn_provider的实例。

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

上面的行说明了ConnMan-vpnd初始化的虚拟接口的名称,并提供了从为VPN提供程序的当前实例引发的TUN接口实例读取和写入IP数据包的功能。 在后台过程中,打开设备并获得用于读取/写入的文件描述符仍然是我们的工作。

简短说明:在插件开发期间,事实证明TUN接口被提升为默认路由接口。

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

为了在后台应用程序和VPN插件之间提供反馈,我们将ConnManTask DBus地址和路径传递到当前实例,为此,我们需要在初始化函数中调用connman_dbus_get_connection。

我们开始后台过程:

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

continent_died-后台进程终止时调用的回调。 在其中,我们找到了终止进程,部署内存,删除添加的路由的错误代码。

notify-回调,当通过DBus调用net.connman.Task.notify时调用,其中我们从正在运行的后台应用程序接收DBus消息。 最主要的是网络参数的传输:TUN接口的地址,虚拟网络中的DNS服务器等。 网络参数以字典的形式打包在DBusMessage中,然后传输到ConnMan Task,在其中启动后台应用程序时发送Dbus参数。

在notify函数中初始化TUN接口的示例:

 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; 

我们还传递中间值,例如,如果我们想通过写入当前连接的属性来通知UI事件,UI可以从net.connman.vpn.Connection.GetProperties方法中学到,当更改Properties时,ConnMan发送DBus信号PropertyChanged,例如:vpn_provider_set_string (提供者,键,值)。

断开连接-通过DBus调用net.connman.vpn.Connection.Disconnect时调用的回调
通过发送SIGTERM信号进行停止过程,如果3秒钟内没有断开连接,则发送SIGKILL信号。

开发和调试VPN插件


Continent-AP VPN插件及其组件的组装在Sailfish Build Engine OS虚拟机(Virtual Box)上执行,该虚拟机是Sailfish SDK的一部分。 要构建插件,您需要以下库:connman-devel,dbus-1,glibs-2.0,我们通过ssh登录来安装它们:

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

我们使用sb2实用程序(Scratchbox 2)-交叉编译工具包。 我们为i486和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 

需要使用sd_journal_print函数将systemd-compat-libs和systemd-devel输出到系统日志。 我们安装了Cmake,因为我们的项目使用它,所以大大简化了不同平台的组装。

我们通过sb2 sdk-build开始VPN插件及其组件的组装:

 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 


接下来,我们将收集到的二进制文件和库放入我们的UI项目,该项目包含一个SPEC文件,用于生成Continent-AP Sailfish分发RPM软件包,并稍稍调整了将文件安装到SPEC文件中的设备系统文件夹中的部分,例如:

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

该插件是与仿真器的UI分开开发的,后者是Sailfish SDK的一部分,并且在恒定日志输出模式下,带有-f选项的gdbus和journalctl作为调试工具很有用。

例如,使用gdbus创建提供程序的实例:

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

测试设备是INOI R7(电话),INOI T8(平板电脑)和基于VirtualBox的仿真器

有用的链接:


  1. 在此处找到适用于Sailfish的ConnMan源代码。
  2. 骨架-插件项目
  3. sailfishos.org/wiki

Source: https://habr.com/ru/post/zh-CN471306/


All Articles