文章目录
- 介绍
- FDBus 中间件模型
- FDBus 寻址和组网
- Server地址
- Server命名和地址分配
- name server使用如下规则分配server地址:
- 多主机组网
- host server的工作原理
- client 与 server 建立连接过程
- 服务名字的唯一性
- 心跳检测,重连和上线、离线检测
- 安全性
- 主机节点鉴权
- 服务访问鉴权
- 安全等级与访问权限
- 安全策略配置文件
- 参数配置文件/etc/fdbus/fdbus.fdb
- 主机配置文件/etc/fdbus/host.fdb
- Server配置文件/etc/fdbus/server/server_name.fdb
- 调试和日志
- FDBus内部结构
源网址
DBus (Fast Distributed Bus) 是一种 IPC 机制, 用于进程间通信或进程.
与 DBus 类似, 但是其功能更齐全, 新能更高, 使用便利, 除了支持主机内的 IPC, 还能再多个主机之间组网, 同时可以制定安全策略, 支持不同的安全等级.
tips: IPC 机制还有 fifo管道, share memory, semaphore, message queue, socket …
介绍
FDbus 基于 Socket (TCP 和 Unix domain) 之上, 采用 Google protobuf 做序列化和反序列化. FDBus 支持字符串形式的名字作为server地址. 通过 name server 自动为 server 分配Unix domain 地址和 TCP 端口号, 实现 client 和server 之间用服务名字寻址.
FDBus旨在为client-server之间提供面向连接,伸缩性强,安全可靠的IPC机制,进而发展成一套中间件开发框架,用于开发跨平台(Windows,QNX,Linux),多线程/多进程协同工作的中间件层。FDBus开发框架适用于在定制系统上开发交互复杂的分布式项目,包括:
基于Linux的车载ECU,包括仪表,娱乐主机,TBox,通过以太网连接的域控制器
Hypervisors上多个Guest OS之间的通信
为Android系统提供跨主机的IPC机制 (目前不支持Java API)
基于Linux的小型通信设备,例如家用路由器
其它基于Linux的工控设备,智能设备
基于Windows开发的自动化测试设备
背景
不像其它内核,Linux一直没有自己独特且好用的IPC机制。Windows,Mac OS,QNX都有这样的机制,即便是基于Linux的Android也开发了用于IPC的binder。Linux内核只提供一些最基础的组件-socket,pipe,message queue,shared memory等等。这也符合Linux的理念:每个工具只做一件事,把它做好。但是现实往往非常复杂,只做一件事远不能解决现实中遇到的问题,更不要说产品开发和大型商用项目。举个例子,订阅-广播是一个最基本的通信需求,但没有一个基础组件能够满足。
Linux实际上也有一个功能强大的IPC:D-Bus。它有完善的方法调用机制和事件广播机制;它还包含一些诸如安全策略和服务按需启动之类的高级功能。但对它最大的争议是性能:它的性能非常低,由于要经daemon中转,一个请求-回复需要来回复制十次消息,四次消息验证,以及四次上下文切换。因此它只能用于处理实时要求较低,数据量较小的控制命令和消息传递,否则还是得求助于基础IPC框架。为此有人将D-Bus写进内核,产生了KDBus,虽然性能上得到提升,但缺点也很明显,只能在单机上运行,不支持跨主机。在这种情况下Android的Binder也够用,况且Binder已经被内核接受了,KDBus至今还没“转正”。另外,无论是DBus还是KDBus,提供还是基础API,离“中间件开发框架”还有较大差距。但各行业包括车载领域对此都有越来越强烈需求,从而各种各样的DBus封装随之产生:Qt DBus,gDBus,commonAPI,DBus-C++… 但这些封装或者从属于大框架,或者缺少维护,总之使用起来并不友好。
在Linux以及以太网使用越来越广泛的车载领域,缺乏合适的IPC逐渐成为突出问题:公司原来自创的IPC机制由于技术落后,定制痕迹明显,已经无法满足分布式,高性能,安全性要求,却又无法为新平台找到合适的IPC机制,更别说基于IPC机制衍生出的中间件开发框架。以太网在车载的应用催生了SOME/IP(Scalable service-Oriented MiddlewarE over IP)。SOME/IP同样是一套比较完善的IPC规范,甚至是专门为车载领域开发的。但是就如名字里暗示的,它是基于IP层的,在单机上性能并不好。且SOME/IP开源的实现也很少,GENIVI组织贡献了vsomeip,但活跃度非常低,GENIVI本身就是个松散的组织,参与者多,开发者少。与DBus不同,SOME/IP是为车载打造,应用范围很窄,不可能指望形成一个活跃的社区聚集一群专业的程序员来维护开放的源码(这大概也是GENIVI不成气候的原因)。最后很可能的情况是,不得不花钱购买闭源软件。
FDBus就是为解决以上各种问题而开发的,具有如下
特点:
- 分布式:基于TCP socket和Unix Domain socket(UDS),既可用于本地IPC,也支持网络主机之间的IPC
- 跨平台:目前已在Windows,Linux和QNX上验证
- 高性能:点对点直接通信,不通过中央Hub或Broker转发
- 安全性:能够为server的方法调用也事件广播配置不同等级的访问权限,只有权限足够高的client才能特点方法和接收特定事件
- 服务名解析:server地址用名字标识,通过name server注册服务和解析名字,从而server可以在全网络任意部署
- 支持跨平台的中间件开发框架,包括如下组件:
-
- Thread模型
-
- Event Loop
-
- 基于Job-Worker的线程间通信
-
- 基于Event Loop的Timer
-
- 基于Event Loop的watch
-
- Mutex
-
- Semaphore
-
- Socket
-
- Notification
- IPC采用Client-Server模式,支持如下通信模式:
-
- 带超时的同步请求-答复
-
- 带超时的异步请求-答复
-
- 无答复的命令请求
-
- 注册-发布模式,实现多点广播
- IPC消息采用Protocol buffer序列化和反序列化,支持IDL代码生成,高效简便;也支持raw data格式,便于大量数据传输
- 可靠的心跳和重连机制,确保无论网络状况如何,无论哪个服务重新上线或重启,通信各方都能保持连接
- C++实现,易于开发和维护
FDBus 中间件模型
-
Infrastructure层多个进程, 这些进程可以在不同的主机系统上, 每个进程中又可以运行多个线程, FDBus 在线程基础上运行特定的 event loop 循环事件, 将通用的线程强化为能够执行 job, timer, watch, worker thread
-
FDBus 通信双方 client 和 server 统称为 endpoint, endpoint 可以部署在不同的 worker thread 上, 也可以多个 endpoint 共享一个worker thread. 即Server在指定的 worker thread 上处理 client 请求, client 在指定的 worker thread 上处理 server 的异步回复和广播事件
- 中间层开发这可以根据情况用一个 worker thread 来处理多个 endpoint 事件, 避免消耗过多线程 (并发问题).
- 当然也可以用多个 worker thread 为一个 endpoint 服务, 例如创建多个线程来分别执行文件下载, 视频编解码, 外设IO等操作
-
多线程协同工作(线程间通信)要求线程之间能够传递消息和数据,例如文件下载完毕要通知endpoint做后续处理。在进程内由于可以访问同一地址空间,最好的通信载体是对象 - 既能承载数据,还能指定数据的处理方式。job就是FDBus在线程之间传递的对象,通过job在线程之间的传递和执行实现进程间通信。
-
FDBus 更重要的功能是进程间通信 IPC, 进程之间无法直接传递对象, 只能以消息的形式交互, 并且消息传输过程中需要做序列化,接收到消息后需要做反序列化。
- 每种IPC机制,包括Binder,SOME/IP,DBus,都有自己的序列化方式。序列化方式的好坏直接影响到通信效率,负载,对数据结构的支持程度以及使用的便利性。
- FDBus没有自己的序列化方式,直接采用google protocol buffer,使用方便,功能齐全,并支持idl自动代码生成。
- 数据在进程间的通信采用socket,包括Unix Domain Socket(UDS)和TCP socket。具体采用哪种类型,系统会根据Client和Server的部署而自动选择:如果在同一主机内部使用UDS,否则使用TCP socket。对于Windows,由于不支持UDS,全部采用TCP socket。
FDBus 寻址和组网
Server地址
server地址是server在网络中的标识,通过该标识client可以找到指定的server并与之建立通信
前面提到,FDBus支持UDS和TCP socket,每种socket有自己的命名方式和命名空间。为了统一FDBus使用如下规则定义Server地址:
- UDS:file://socket文件名
- TCP socket:tcp://ip地址:端口号
不同于一般意义上的socket server,FDBus server可以同时绑定多个地址,每个地址都可接受client的连接。一旦连接上,每个地址都提供同样的服务,所以client可以选择任意一个地址建立连接。下面是一个FDBus server地址绑定示意图:
上图中,server绑定了一个UDS地址:file:///tmp/fdb-ipc1。同一主机上的client可以用该地址发起连接,当然也可连接到其它任意地址,但无疑使用UDS是最高效的,且UDS支持peer credentials,进而支持安全策略。
由于主机有多个网口,server还可以在每个网口上各绑定一个地址(端口号):tcp://192.168.1.2:60004和tcp://192.168.0.1:60004。每个地址用于连接对应网段的client。
Server命名和地址分配
用上述地址定位server使用不方便,不直观,地址会随组网方式变化而改变,无法灵活部署。为此FDBus增加了一种寻址方式:server名字寻址。每个server可以拥有自己的名字;运行一个叫name server的服务,负责为server分配地址,管理server名字与地址的映射,解析server名字,发布server地址。name server有点类似internet上的DNS。为支持server名字寻址,在两种URL之外增加一种格式,作为名字地址,如下所示:
- svc://server名字
名字地址是虚拟地址, 无论 server 位于何处,只要它的名字地址不变,client 都可以通过该地址与之建立联系。如果 server 调用 bind() 绑定名字地址(svc://开头的地址),name server 会为其分配实际地址(tcp://或file://开头的地址),并将名字和地址注册到映射表里。如果 client 连接名字地址,name server 会根据名字查找 server 的实际地址,选择一个最合适的实际地址发布给 client。client 通过该地址与 server 建立点对点的直连。下图是在 name server 协助下,client 和 Server 利用名字地址建立连接的流程:
- 首先client调用connect(“svc://medisServer”)要求和名叫mediaServer的server建立连接。由于使用的是名字地址,FDBus会向name server索要mediaServer的实际地址。
- 但现在mediaServer还没有上线,所以无法解析该名字,只是订阅了对该服务的上线通知。不久后server调用bind(“svc://mediaServer”)上线,由于使用名字地址,同样会向name server发出请求。name server为其注册名字,分配UDS和TCP地址并返给server。server分别绑定每个实际地址,成功后通知name server。
- name server向整个系统发布该server上线的消息以及server地址:广播给本地client的是UDS地址,广播给其它节点上client的是TCP地址。client利用收到的地址与server建立连接,同时client和server都能收到onOnline()的事件通知。
name server使用如下规则分配server地址:
Server |
TCP Address |
UDS Address |
host server |
port No. 61000 |
/tmp/fdb-hs |
name server |
port No. 61001 |
/tmp/fdb-ns |
user servers |
Port 61002 – Port 65535或系统自动分配 |
/tmp/fdb-ipc0, /tmp/fdb-ipc1 … |
多主机组网
- 由于name server的地址是固定的,endpoint启动后,会自动连接到name
- server注册(server)或解析(client)名字。 如果有多台主机,每台主机上运行自己的name server,负责各自的名字服务,那么这些主机就成了孤岛,无法通过svc://server_name这样的服务名称互相连接。当然client可以绕开name server,用实际地址直接连接上server,但这样无法灵活部署和组网
- 为了支持跨网络的名字解析,需要有一个服务来管理系统里所有主机,把主机信息同步给所有name server,进而这些name server之间可以建立连接,协同工作,共同完成全网范围内的名字服务。这个服务就是host server
host server的工作原理
- 整个网络运行一个 host server,可以位于任意一台大家都能访问的主机上。所有主机的name server都连接到host server,向它注册自己所在的主机。host server维护一张包含各主机ip地址的主机列表,并把该表同步给网络里所有的name server。name server根据该表与网络里所有主机上的name server建立连接。
- 一旦所有主机上的name server都两两建立连接,就可以通过一套内部协议完成跨主机的服务名解析和服务上线通知。例如当一台主机上的client向本地name server请求解析服务名对应的地址,本地name server可以把该请求广播给所有相连的name server,在整个网络范围内查找服务。下面是整个系统组网示例图:
上图中,name server和host server之间建立星形连接,name server和name server之间两两相连,组成一张网。在这个系统里,name server和host server的主要任务如下:
client 与 server 建立连接过程
- name server连接到host server,把所在的主机注册进host server (星形连接)
- host server收集所有主机信息,形成主机地址表
- host server把主机地址表广播给所有name server
- name server从表中获得其它主机上name server的地址,并与之建立两两相连
- 所有server与本地name server相连,向其注册服务名字。本地name server将新注册的服务广播给本地的client以及网络里所有其它的name server
- 其它name server收到该广播后,同样在本地做一个广播,通知给所有client。通过这种方式把服务上线的消息扩散到整个网络
- 所有client与本地name server相连,申请服务名字解析。本地name server搜索自身的server地址映射表,同时把申请发送给其它所有name server
- 其它name server收到申请后,搜索各自的server地址映射表,并把结果返回给发起申请的name server
- name server把收到的返回结果转发给发起申请的client,client利用结果里的实际地址与服务建立直连。通过这种方式可以找到所有主机上的服务
从上图也可以看出,一旦client和server之间建立连接,所有的通信都通过这个连接完成,无需经过中间环节转发
服务名字的唯一性
- 由于每台主机都有自己的 name server,所以在主机内部,服务不能重名,但是在不同主机上可以重名。
- 在这种情况下,当 client 申请名字解析时,可能会收到来自不同主机反馈 server 信息。client 可以定制连接策略:
- 总是连接新的server
- 只连接第一个server
- 或是只连接指定的server
心跳检测,重连和上线、离线检测
为了使整个系统可靠运行,保证任何服务都可以优雅重启(重启后整个系统依旧正常工作),FDBus有完善的心跳检测和上线、离线检测机制:
- endpoints 和 name server 之间有重连机制, 确保 name server 重新启动后 endpoint 重视能够与之建立连接
- name server 和 host server 之间有心跳检测机制, 一旦心跳消失, name server 回尝试与 host server 重连, 确保 name server 与 host server 之间连接的可靠性
- name server 和 name server 之间的连接建立由 host server 来保证, 当 name server 上线时, host server 会通知所有其他的 name server 与之建立连接, 也会通知该 name server 与其他所有 name server 建立连接, 双向都寻求连接
- client 和 server 之间的连接建立由 name server 来保证, server 上线时, name server 会通知 client 与之建立连接
安全性
随着系统越来越复杂和开放,安全性已经成为系统架构设计时关注的焦点。一个开放的系统意味着可能存在入侵者,一旦入侵者访问了不应该访问的资源就可能造成信息泄露或破坏。
另一方面,一个系统的安全性是建立在信任链的基础之上的,只有满足必要的安全基础才能构筑自身的安全能力。对于FDBus来说,有以下两个前提条件:
- 最基本的前提是FDBus自身的完整性:系统中运行的FDBus库,name server,host server都是合法的,没有被篡改或替换,否则无论如何也保证不了安全性。这一点通过操作系统能够权限管理,secure boot,DM-verity/FS-verity,安全升级,SELinux等机制来确保。
- 其次在网络上,FDBus消息是以明文传递的,一旦有人非法监听网络报文就有可能造成信息泄露以及安全漏洞。所以讨论FDBus安全性的另一前提是入侵者无法截取网络报文,获得FDBus在链路上传递的数据。将来会对关键数据如token加密,但目前暂时还没有实现。
基于这些假设,FDBus面临的攻击主要来自三方面:
- 非法主机连接到FDBus总线上并运行非法client访问其它主机上的server
- 在一台合法主机上运行非法client访问主机里的FDBus server
- 合法的主机上运行合法client,但试图获取没有权限的数据或执行没有权限的操作。基于以上攻击,FDBus从如下方面确保系统安全运行:
主机节点的鉴权:所有加入FDBus的主机都划分成不同的安全等级
服务访问的鉴权:所有client都划分成不同的安全等级
访问限制:将server的方法调用和事件广播划分安全等级,对应的client才能调用与server安全等级相匹配的的方法和注册相匹配的事件广播
主机节点鉴权
服务访问鉴权
安全等级与访问权限
安全策略配置文件
参数配置文件/etc/fdbus/fdbus.fdb
主机配置文件/etc/fdbus/host.fdb
Server配置文件/etc/fdbus/server/server_name.fdb
调试和日志
FDBus内部结构