引言
我在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 GUIVPN-api Sailfish操作系统包含以下组件,我们将以我们的VPN客户端示例进行展示:
- ConnMan是在Sailfish OS启动时启动的过程。
- connman-vpnd是由ConnMan启动的守护进程,用于管理各种提供程序的VPN连接,初始化和取消初始化tun接口,以及将通过DBus接收的网络设置(IP地址,路由,DNS服务器)分配给它。 在我们的例子中,一个提供者名为大洲。
- continent-proto-plugin.so VPN插件是一个库,该库具有用于加载到运行时的宏声明以及在调用net.connman.vpn.Connection接口方法时调用的函数。
- 后台应用程序(二进制文件/ usr / sbin / continent)是一个控制台客户端,用于连接到CD(Continent Access Server),并从中接收网络设置,并将其传递给connman-vpnd。
- ConnMan Task是用于启动,停止和监视正在运行的控制台客户端的过程。
- 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的仿真器
有用的链接:
- 可在此处找到适用于Sailfish的ConnMan源代码。
- 骨架-插件项目
- sailfishos.org/wiki