D-Bus 规范

内容

修订历史

最新修订

修订版本 0.42

2023年08月21日

  • GetConnectionCredentials 可以返回 ProcessFD

修订版本 0.41

2023年02月08日

2022-10-05

  • 澄清在Linux上使用抽象套接字不需要unix:tmpdir
  • 提及Linux命名空间对抽象套接字的影响

修订版本 0.39

2022-09-22

  • 在反向域名中记录对国际化域名的建议
  • 澄清有关 AF_UNIX sockets 的文档

修订版本 0.38

2022年02月23日

  • 添加 ActivatableServicesChanged 信号和功能标志
  • * 在地址中是可选转义的

修订版本 0.37

2021年12月17日

  • 更新可互操作的 DBUS_COOKIE_SHA1 超时建议
  • 澄清数组和变体的填充要求
  • 描述可互操作的机器 ID 来源
  • 澄清字典(dict-entry 数组)类型的使用

修订版本 0.36

2020年04月21日

  • 修复消息部分注释十六进制转储中的拼写错误

修订版本 0.35

2019年05月13日

  • 将 UnixGroupIDs 添加到 GetConnectionCredentials
  • 避免在定义接口名称语法时出现冗余

修订版本 0.34

2018年12月04日

pwithnall

  • 修正 ObjectManager 示例 AddMatch 规则

修订版本 0.33

2018年04月27日

smcv

  • 在Unix上弃用TCP
  • 在所有地方弃用非本地TCP

修订版本 0.32

2018年01月30日

smcv

  • 废弃总线名称中的连字符/减号,推荐使用下划线进行替换
  • 记录接口和总线名称中转义前导数字的约定(org._7_zip)
  • 建议尽可能使用SASL EXTERNAL,否则使用DBUS_COOKIE_SHA1
  • 消息总线不应接受SASL ANONYMOUS
  • 记录非空SASL授权标识字符串的含义
  • 记录SASL ERROR的可选参数
  • 记录发送每个SASL命令的主体以及可能的回复
  • 记录用于协商Unix fd传递的认证状态
  • 中继消息的服务器应删除其不理解的头字段
  • 澄清每个头字段的控制者
  • 记录HeaderFiltering消息总线特性标志
  • 非消息总线服务器可以使用SENDER和DESTINATION字段

修订版本 0.31

2017-06-29

smcv,TG

  • 不要要求特定实现的搜索路径优先级最低
  • 正确的正则表达式语法用于包含连字符、斜杠和下划线的地址中,以便按预期工作
  • 在同一部分描述所有消息总线方法
  • 澄清调用消息总线方法的正确对象路径
  • 记录消息总线实现了Introspectable、Peer和Properties
  • 为消息总线功能发现添加新的Features和Interfaces属性
  • 添加unix:dir=...,类似于unix:tmpdir=...,但永远不使用抽象套接字
  • 不要求从不具备足够权限的连接中接受eavesdrop='true'以成功使用它
  • 正式弃用窃听,转而使用BecomeMonitor

修订版本 0.30

2016年11月28日

smcv, PW

更清晰地定义行话术语服务激活和自启动。在服务文件中记录 SystemdService 键。记录 AppArmor 与服务激活的交互方式,以及服务文件中的新 AssumedAppArmorLabel 键(dbus-daemon 1.11.8)。澄清 Properties.GetAll 的预期行为。在大多数示例中使用带版本的接口和总线名称。

修订版本 0.29

2016年10月10日

PW

Introspection arguments may contain annotations; recommend against using the object path '/'

修订版本 0.28

2016年08月15日

PW

澄清序列化

修订版本 0.27

2015年12月02日

LU

服务不应发送不需要的回复

修订版本 0.26

2015年02月19日

smcv, rh

GetConnectionCredentials 可以返回 LinuxSecurityLabel 或 WindowsSID;添加特权 BecomeMonitor 方法

修订版本 0.25

2014年11月10日

smcv, lennart

ALLOW_INTERACTIVE_AUTHORIZATION 标志,EmitsChangedSignal=const

修订版本 0.24

2014年10月01日

SMcV

非方法调用即使没有NO_REPLY_EXPECTED也不期望回复;文档中说明如何引用匹配规则

修订版本 0.23

2014年01月06日

SMcV, CY

method call messages with no INTERFACE may be considered an error; document tcp:bind=... and nonce-tcp:bind=...; define listenable and connectable addresses

修订版本 0.22

2013-10-09

添加 GetConnectionCredentials,记录 GetAtdAuditSessionData,记录 GetConnectionSELinuxSecurityContext,记录并更正 .service 文件的语法和命名

修订版本 0.21

2013年04月25日

smcv

允许在UTF-8中使用Unicode非字符(Unicode勘误表#9)

修订版本 0.20

2013年2月22日

smcv, 沃尔特斯

为了清晰起见重新组织,删除关于基本类型的虚假声明,提及/o/fd/DBus

修订版本 0.19

2012年2月20日

smcv/lp

正式定义唯一连接名称和知名总线名称;记录接口、总线、成员和错误名称以及对象路径的最佳实践;记录Unix上会话和系统服务的搜索路径;记录systemd传输

修订版本 0.18

2011年7月29日

smcv

定义窃听、单播、广播;添加窃听匹配关键字;将类型系统提升为顶级部分

修订版本 0.17

2011年6月1日

smcv/davidz

定义 ObjectManager; 保留 GVariant 使用的额外伪类型码

修订版本 0.16

2011年4月11日

添加 path_namespace,arg0namespace;argNpath 匹配对象路径

修订版本 0.15

2010年11月3日

修订版本 0.14

2010年5月12日

修订版本 0.13

2009年12月23日

修订版本 0.12

2006年11月7日

修订版本 0.11

2005年2月6日

修订版本 0.10

2005年1月28日

修订版本 0.9

2005年1月7日

修订版本 0.8

2003年9月6日

首次发布的文档。

D-Bus 是一个用于低开销、易于使用的进程间通信(IPC)系统。更详细地说:

  • D-Bus使用二进制协议,因此开销较低,并且无需转换为XML等文本格式。由于D-Bus旨在用于潜在的高分辨率同机IPC,而不是主要用于Internet IPC,这是一个有趣的优化。D-Bus还设计为避免往返并允许异步操作,类似于X协议。
  • D-Bus易于使用,因为它使用消息而不是字节流来工作,并自动处理许多复杂的IPC问题。此外,D-Bus库设计为以一种方式封装,使开发人员可以使用其框架现有的对象/类型系统,而不是专门为IPC学习新的系统。

D-Bus基本协议是一对一(点对点或客户端-服务器)协议,详细规定在“消息协议”一节中。也就是说,它是一个应用程序与另一个单一应用程序进行通信的系统。然而,该协议的主要预期应用是D-Bus 消息总线,详细规定在“消息总线规范”一节中。消息总线是一个特殊的应用程序,接受来自多个其他应用程序的连接,并在它们之间转发消息。

D-Bus 的用途包括通知系统变化(例如相机插入计算机时的通知,或某个软件的新版本已安装的通知),或桌面互操作性,例如文件监控服务或配置服务。

D-Bus 专为两个特定用例而设计:

  • 用于系统向用户会话发送通知,并允许系统请求用户会话输入的“系统总线”。
  • 用于实现 GNOME 和 KDE 等桌面环境的“会话总线”。

D-Bus并非旨在成为任何可能应用程序的通用IPC系统,并故意省略了许多其他IPC系统中存在的功能。

同时,总线守护程序提供了许多其他IPC系统中找不到的功能,例如单所有者的“总线名称”(类似于X选择)、按需启动服务和安全策略。在许多方面,这些功能是开发D-Bus的主要动机;如果IPC是唯一目标,那么其他系统就足够了。

D-Bus可能在意想不到的应用中变得有用,但是未来版本的规范和参考实现可能不会包含干扰核心用例的功能。

本文档中的关键词“MUST”、“MUST NOT”、“REQUIRED”、“SHALL”、“SHALL NOT”、“SHOULD”、“SHOULD NOT”、“RECOMMENDED”、“MAY”和“OPTIONAL”应按照 RFC 2119 中的描述进行解释。但是,该文档可能需要进行严格的审计,以确保这样做是有意义的。此外,这些关键词不应大写。

协议和规范稳定性

D-Bus协议自2006年11月8日起已冻结(只允许兼容扩展)。然而,为了使可互操作的重新实现成为可能,该规范仍需要进行相当多的工作,而无需参考D-Bus参考实现。因此,该规范未标记为1.0。要将其标记为1.0,我们希望看到有人投入大量精力来澄清规范语言,并扩展规范以涵盖参考实现行为的更多方面。

在这项工作完成之前,任何尝试重新实现 D-Bus 的行为可能需要查看参考实现和/或在 D-Bus 邮件列表上询问有关预期行为的问题。欢迎在列表上提出问题。

尽管如此,这份文件应该是一个有用的起点,并且据我们所知是准确的,尽管不完整。

D-Bus拥有一种类型系统,其中各种类型的值可以被序列化为一系列字节,称为标准方式中的_wire format。将值从其他表示形式转换为wire format称为marshaling,将其从wire format转换回来称为unmarshaling。

D-Bus协议在编组数据中不包括类型标签;一块编组值必须有一个已知的_类型签名_。类型签名由零个或多个_单个完整类型_组成,每个完整类型由一个或多个_类型代码_组成。

类型码是表示值类型的ASCII字符。由于使用ASCII字符,类型签名将始终形成有效的ASCII字符串。通过简单的字符串比较确定两个类型签名是否等效。

一个单一完整类型是一系列类型代码,完整描述一个类型:基本类型或单一完全描述的容器类型。一个单一完整类型是基本类型代码、变体类型代码、带有其元素类型的数组或带有其字段的结构体(所有这些都在下面定义)。因此,以下签名不是单一完整类型:

    "aa"
    "(ii"
    "ii)"

以下签名包含多个完整类型:

    "ii"
    "爱爱"

(ii)(ii)

请注意,一个完整的类型可能包含多个其他完整的类型,通过包含一个结构体或字典条目。

最简单的类型代码是 基本类型,这些类型的结构完全由它们的 1 个字符类型代码定义。基本类型包括固定类型和类似字符串的类型。

固定类型是基本类型,其值具有固定长度,即BYTE、BOOLEAN、DOUBLE、UNIX_FD,以及长度为16、32或64位的有符号或无符号整数。

作为一个简单的例子,32位整数(INT32)的类型代码是ASCII字符 'i'。因此,包含单个INT32值的数值块的签名将是:

i

包含两个 INT32 的数值块将具有这个签名:

      "ii"

固定类型的特征列在这个表中。

_类似字符串的类型_是具有可变长度的基本类型。任何类似字符串类型的值在概念上是由0个或多个以UTF-8编码的Unicode代码点组成,其中不能包含U+0000。UTF-8文本必须严格验证:特别是,它不能包含过长的序列或超出U+10FFFF的代码点。

自 D-Bus 规范版本 0.21 起,根据 Unicode 修正案 #9,UTF-8 字符串中允许使用“非字符” U+FDD0 到 U+FDEF、U+nFFFE 和 U+nFFFF(但请注意,旧版本的 D-Bus 会拒绝这些非字符)。

字符串类型的编组格式都以单个零(NUL)字节结尾,但该字节不被视为文本的一部分。

字符串类型的特征列在这个表中。

对象路径是用于引用对象实例的名称。在概念上,D-Bus 消息交换中的每个参与者可能拥有任意数量的对象实例(类似于 C++ 或 Java 对象),每个实例都会有一个路径。就像文件系统一样,应用程序中的对象实例形成一个分层树。

对象路径通常通过以反转的域名开头并包含接口版本号进行命名空间划分,方式与接口名称众所周知的总线名称相同。这样可以在同一进程中实现多个服务或多个服务版本,即使这些服务共享连接但无法以其他方式合作(例如,如果它们由不同的插件实现)。

使用/作为对象路径是允许的,但不建议,因为这样会使接口的版本控制变得困难。从D-Bus对象发出的任何信号都与服务的唯一总线名称相关联,而不是其众所周知的名称。这意味着信号的接收者必须完全依赖信号名称和对象路径来确定信号的来源接口。

例如,如果example.com的所有者正在为音乐播放器开发D-Bus API,他们可能会使用以/com/example/MusicPlayer1开头的对象路径层次结构。

以下规则定义了一个有效的对象路径。实现必须不发送或接受具有无效对象路径的消息。

  • 路径可以是任意长度。
  • 路径必须以 ASCII '/'(整数 47)字符开头,并且必须由斜杠字符分隔的元素组成。
  • 每个元素只能包含ASCII字符"[A-Z][a-z][0-9]_"
  • 任何元素都不能是空字符串。
  • 连续出现多个'/'字符是不允许的。
  • 除非路径是根路径(单个'/'字符),否则不允许使用结尾的'/'字符。

实现不得发送或接受无效的签名。有效的签名将符合以下规则:

  • 签名是单个完整类型的列表。数组必须有元素类型,结构体必须同时有开放和关闭括号。
  • 仅允许在签名中使用类型代码、开括号、闭括号和开花括号。STRUCT 类型代码不允许在签名中使用,因为括号被用于替代。同样,DICT_ENTRY 类型代码也不允许在签名中使用,因为花括号被用于替代。
  • 容器类型嵌套的最大深度为32个数组类型代码和32个开括号。这意味着递归的最大总深度为64,对于一个“数组的数组的数组的...结构的结构的结构的...”,其中有32个数组和32个结构。
  • 签名的最大长度为255。

当签名出现在消息中时,编组格式保证其后会跟随一个空字节(可以解释为C风格字符串终止或INVALID类型码),但这在概念上不是签名的一部分。

除了基本类型外,还有四种_容器_类型:STRUCTARRAYVARIANTDICT_ENTRY

STRUCT有一个类型码,ASCII字符'r',但此类型码不会出现在签名中。相反,ASCII字符'('和')'用于标记结构的开始和结束。因此,例如,包含两个整数的结构将具有此签名:

(ii)

结构体可以嵌套,例如一个包含整数和另一个结构体的结构体:

(i(ii))

存储该结构的值块将包含三个整数;类型签名允许您区分“(i(ii))”和“((ii)i)”或“(iii)”或“iii”。

在 D-Bus 协议中,STRUCT 类型代码 'r' 目前未被使用,但在实现协议的代码中很有用。指定此类型代码是为了使这些代码能够在非协议上下文中进行互操作。

括号内不允许为空结构;至少必须有一个类型代码。

ARRAY 的 ASCII 字符为 'a',作为类型代码。数组类型代码后必须跟着一个完整的类型。数组后面的单一完整类型是每个数组元素的类型。因此,简单示例如下:

ai

32 位整数数组。但数组可以是任何类型,比如这个包含两个 int32 字段的结构体数组:

      "a(ii)"

或者这个整数数组的数组:

      "aai"

VARIANT 的类型代码是 ASCII 字符 'v'。类型为 VARIANT 的编组值将具有单个完整类型的签名作为 value 的一部分。该签名将后跟该类型的编组值。

与消息签名不同,变体签名只能包含单个完整类型。因此,“i”、“ai”或“(ii)”都可以,但“ii”不行。使用变体可能导致总消息深度大于64,包括其他容器类型,如结构。

一个 DICT_ENTRY 的工作方式与结构体完全相同,但它使用花括号而不是括号,并且有更多限制。这些限制包括:它只能作为数组元素类型出现;花括号内部必须有两个单一完整类型;第一个单一完整类型("键")必须是基本类型而不是容器类型。实现必须不接受数组之外的字典条目,不接受零个、一个或超过两个字段的字典条目,也不接受具有非基本类型键的字典条目。字典条目始终是键值对。

DICT_ENTRY中的第一个字段始终是键。如果在DICT_ENTRY的相同数组中出现两次相同的键,则消息被视为损坏。但是,出于性能原因,实现不需要拒绝具有重复键的字典。

在大多数语言中,字典条目的数组会被表示为映射、哈希表或字典对象。

以下表格总结了 D-Bus 类型。

D-Bus为其类型系统定义了一种编组格式,该格式用于D-Bus消息。这不是类型系统的唯一可能编组格式:例如,GVariant(GLib的一部分)重用了D-Bus类型系统,但实现了另一种编组格式。

给定类型签名,一块字节可以转换为类型化的值。本节描述了字节块的格式。字节顺序和对齐问题对所有 D-Bus 类型都统一处理。

字节块有一个关联的字节顺序。必须以某种方式发现字节顺序;对于 D-Bus 消息,字节顺序是消息头的一部分,如 “消息格式”一节 中所述。暂时假定字节顺序已知为小端或大端。

每个字节块中的值都是按照“自然”方式对齐的,例如4字节值对齐到4字节边界,8字节值对齐到8字节边界。边界是全局计算的,相对于消息中的第一个字节。为了正确对齐一个值,在该值之前可能需要“对齐填充”。对齐填充必须始终是正确对齐下一个值所需的最小填充,并且必须始终由空字节组成。对齐填充不能保持未初始化状态(不能包含垃圾),也不能使用比所需更多的填充。

作为自然对齐的例外,STRUCTDICT_ENTRY 的值始终对齐到 8 字节边界,而不考虑其内容的对齐方式。

要编组和解组固定类型,只需从与签名中的每种类型代码对应的数据块中读取一个值。所有有符号整数值都以二进制补码编码,DOUBLE 值是 IEEE 754 双精度浮点数,BOOLEAN 值以 32 位编码(只使用最低有效位)。

字符串类型(STRING、OBJECT_PATH 和 SIGNATURE)都被编组为一个固定长度的无符号整数 n,表示可变部分的长度,然后是 n 个非零字节的 UTF-8 文本,最后是一个单个零字节(nul),该字节不被视为文本的一部分。字符串类型的对齐方式与 n 的对齐方式相同:n 需要的任何填充都会立即出现在 n 之前。在 n 和字符串文本之间,以及字符串文本和尾随的 nul 之间永远不会有对齐填充。如果消息中有下一个值,那么下一个值的对齐填充将从尾随的 nul 之后开始。

对于 STRING 和 OBJECT_PATH 类型,n 被编码为 4 字节(一个 UINT32),导致 4 字节对齐。对于 SIGNATURE 类型,n 被编码为单个字节(一个 UINT8)。因此,在 SIGNATURE 之前永远不需要对齐填充。

例如,如果当前位置距离小端消息开头的字节数是8的倍数,则字符串'foo'、'+'和'bar'将按顺序序列化如下:

无需填充,我们已经是4的倍数 0x03 0x00 0x00 0x00 ‘foo’的长度=3 0x66 0x6f 0x6f ‘foo’ 0x00 尾随的空字符 无需填充,我们已经是4的倍数 0x01 0x00 0x00 0x00 ‘+’的长度=1 0x2b ‘+’ 0x00 尾随的空字符 0x00 0x00 填充2字节以达到下一个4的倍数 0x03 0x00 0x00 0x00 ‘bar’的长度=3 0x62 0x61 0x72 ‘bar’ 0x00 尾随的空字符

数组被编组为UINT32 n,表示以字节为单位的数组数据长度,后跟对齐填充以对齐数组元素类型的边界,然后是按顺序编组的n字节数组元素。n不包括长度后的填充,或最后一个元素后的任何填充。即n应该能够被数组中的元素数量整除。请注意,即使没有第一个元素(空数组,其中n为零),第一个元素的对齐填充也是必需的。

例如,如果消息中的当前位置是8字节的倍数且字节顺序为大端序,则仅包含64位整数5的数组将被编组为:

00 00 00 08 n = 8 字节的数据 00 00 00 00 填充至 8 字节边界 00 00 00 00 00 00 00 05 第一个元素 = 5

数组的最大长度定义为2的26次方或67108864(64 MiB)。实现不得发送或接受超过此长度的数组。

结构体和字典条目的编组方式与它们的内容相同,但它们的对齐方式始终是8字节边界,即使它们的内容通常对齐要求不那么严格。

变体被编组为内容的 SIGNATURE(必须是单个完整类型)后跟具有由该签名给出的类型的编组值。 变体具有与签名相同的1字节对齐方式,这意味着在变体之前永远不需要对齐填充。 使用变体不能导致总消息深度大于64,包括其他容器类型,如结构。 (请参阅Valid Signatures。)

需要注意的是,虽然变体本身不需要任何对齐填充,但其中包含的值需要根据其类型的对齐规则进行填充。

例如,如果消息中的当前位置是8字节的倍数,并且字节顺序是大端序,包含64位整数5的变体将被编组为:

0x01 0x74 0x00 签名字节(长度 = 1,签名 = 't' 和尾随的空字符) 0x00 0x00 0x00 0x00 0x00 填充至8字节边界 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x05 包含值的8字节

D-Bus编组摘要

鉴于这一切,类型在传输时如下编组:

消息由 标头正文 组成。如果你把消息想象成一个包裹,那么标头就是地址,正文则包含了包裹的内容。消息传递系统使用标头信息来确定消息的发送位置和如何解释它;接收者解释消息的正文。

消息体由零个或多个_参数_组成,这些参数是已输入的值,例如整数或字节数组。

标题和正文都使用 D-Bus 类型系统 和格式来序列化数据。

消息由标题和正文组成。标题是一个具有固定签名和含义的数值块。正文是一个独立的数值块,其签名在标题中指定。

标题的长度必须是8的倍数,这样在将整个消息存储在单个缓冲区中时,正文可以从8字节边界开始。如果标题自然不以8字节边界结束,则必须添加最多7字节的空初始化对齐填充。

消息正文无需以8字节边界结束。

消息的最大长度,包括头部、头部对齐填充和正文,为2的27次方或134217728(128 MiB)。实现不得发送或接受超过此大小的消息。

头部的签名是:

yyyyuua(yv)

更易读的写法是:

BYTE, BYTE, BYTE, BYTE, UINT32, UINT32, ARRAY of STRUCT of (BYTE,VARIANT)

这些值具有以下含义:

标题的第二个字节中可能出现的消息类型是:

标题的第三个字节中可能出现的标志:

标题末尾的数组包含 标题字段,其中每个字段都是一个1字节的字段代码,后面跟着字段值。标题必须包含其消息类型所需的标题字段,以及零个或多个任何可选的标题字段。该协议规范的未来版本可能会添加新字段。实现不得发明自己的标题字段;只有对该规范的更改才能引入新的标题字段。

如果一个实现看到一个意料之外的头字段代码,它必须接受并忽略该字段,因为它将成为此规范的新版本(但兼容)。这也适用于出现在意外消息中的已知头字段,例如:如果一个信号有一个回复序列号,即使在此规范的这个版本中它没有意义,也必须被忽略。

然而,实现不得发送或接受已知标题字段,其中存储在字段值中的类型错误。例如,具有类型为UINT32INTERFACE字段的消息将被视为损坏。

服务器实现可能会从一个互不信任的客户端中继消息到另一个客户端,比如消息总线,应该删除服务器不认识的标头字段。然而,客户端必须假设服务器没有这样做,除非有相反的证据,比如已经检查了 HeaderFiltering 消息总线功能

消息总线控制的新标题字段(类似于 SENDER)可能会在将来的规范中添加。这样的消息字段通常只应添加到将要传递给明确请求它们的客户端的消息中(例如通过调用某些方法),并且消息总线应该从转发的所有其他消息中删除这些标题字段。这种设计原则有两个主要目的。一个是在将消息传递给不感兴趣的新标题字段的客户端时避免不必要的内存和吞吐量开销。另一个是给客户端一个理由调用请求这些消息的方法(否则,客户端将无法工作)。这是可取的,因为查看该方法调用的回复是检查消息总线保证过滤可能由恶意对等方发送的伪造标题字段的自然方式。

以下是当前定义的标题字段:

D-Bus 消息中的各种名称有一些限制。

总线名称、接口和成员的名称长度最大为255个字符。

接口的名称具有类型 STRING,这意味着它们必须是有效的 UTF-8。但是,还有一些特定于接口名称的额外限制:

  • 接口名称由用句点('.')分隔的2个或更多元素组成。所有元素必须至少包含一个字符。
  • 每个元素只能包含ASCII字符"[A-Z][a-z][0-9]_",且不能以数字开头。
  • 接口名称长度不得超过最大长度。

接口名称应以接口作者的反向 DNS 域名(小写)开头,就像 Java 中的接口名称一样。接口名称的其余部分通常由单词连在一起组成,每个单词的首字母大写("驼峰命名法")。可以使用多个层次的层次结构。在名称中包含接口的主要版本也是一个好主意,如果进行了不兼容的更改,则递增版本号;这样,一个对象可以并行实现多个版本的接口,如果需要的话。

例如,如果example.com的所有者正在为音乐播放器开发D-Bus API,他们可能定义名为com.example.MusicPlayer1com.example.MusicPlayer1.Trackcom.example.MusicPlayer1.Seekable的接口。

如果作者的DNS域名包含连字符/减号字符('-'),这些字符在D-Bus接口名称中是不允许的,应该用下划线替换。如果DNS域名中的一个句点('.')后面紧跟一个数字(接口名称中也不允许),接口名称应在该数字前添加下划线。例如,如果7-zip.org的所有者为进程外插件定义一个接口,可能命名为org._7_zip.Plugin

如果作者的DNS域名是国际化域名(IDN),例如 δοκιμή.example,则应使用ASCII编码(称为ACE编码或Punycode),例如 xn--jxalpdlp.example 作为反转域名形式的基础。与任何其他名称一样,在反转域名形式中,连字符/减号字符应替换为下划线,例如 example.xn__jxalpdlp.ExampleService1。有关国际化域名的更多信息,请参阅RFC 5890“应用程序的国际化域名(IDNA):定义和文档框架”

D-Bus 在 Java 中称为类和接口的概念之间没有区别:在 D-Bus 上,两者都可以通过接口名称来识别。

连接与一个或多个总线名称相关联。连接具有一个确切的总线名称,即_唯一连接名称_。唯一连接名称将在整个连接的生命周期内保持不变。总线名称的类型为STRING,这意味着它必须是有效的UTF-8。但是,总线名称还有一些特定的附加限制:

  • 以冒号(':')字符开头的总线名称是唯一的连接名称。其他总线名称称为 众所周知的总线名称
  • 公交车名称由一个或多个由句点('.')字符分隔的元素组成。所有元素必须至少包含一个字符。
  • 每个元素只能包含ASCII字符"[A-Z][a-z][0-9]_-",在新的总线名称中不鼓励使用"-"。只有作为唯一连接名称一部分的元素才能以数字开头,其他总线名称中的元素不得以数字开头。
  • 公交车名称必须至少包含一个'.'(句点)字符(因此至少包含两个元素)。
  • 公交车名称不能以'.'(句点)字符开头。
  • 公交车名称不得超过最大名称长度。

请注意,总线名称中允许使用连字符('-')字符,但在接口名称中不允许。在涉及到 D-Bus 的各种规范和 API 中,如 Flatpak 应用程序 ID、桌面入口规范中的 DBusActivatable 接口以及应用程序的“主”接口和对象路径与总线名称相似的约定中,使用连字符可能会导致问题或不被允许。为避免需要特殊处理的情况,建议新的 D-Bus 名称始终将连字符替换为下划线。

接口名称类似,众所周知的总线名称应以接口作者的反向DNS域名(小写)开头,其余部分通常由单词连在一起组成,首字母大写。与接口名称一样,在众所周知的总线名称中包含版本号是个好主意;如果需要向后兼容,可以同时为多个版本设置众所周知的总线名称。

接口名称一样,如果作者的DNS域名包含连字符/减号字符,则应将其替换为下划线,如果包含前导数字,则应通过在前面添加下划线进行转义,并且国际化域名(IDN)在将破折号替换为下划线之前需要以其ASCII形式(ACE编码,Punycode)进行编码。例如,如果7-zip.org的所有者为一个归档应用程序使用了一个D-Bus名称,它可能被命名为org._7_zip.Archiver,而δοκιμή.example的所有者可能使用名称example.xn__jxalpdlp.ExampleService1

如果一个知名的总线名称暗示存在一个“主”接口,那么该“主”接口通常会被赋予与知名总线名称相同的名称,并位于相应的对象路径上。例如,如果example.com的所有者正在为音乐播放器开发一个D-Bus API,他们可能定义任何使用知名名称com.example.MusicPlayer1的应用程序应该在对象路径/com/example/MusicPlayer1上拥有一个实现com.example.MusicPlayer1接口的对象。

成员(即方法或信号)名称:

  • 仅能包含 ASCII 字符 "[A-Z][a-z][0-9]_",且不能以数字开头。
  • 不能包含'.'(句点)字符。
  • 不得超过最大名称长度。
  • 必须至少为1字节长度。

在 D-Bus 上,成员名称通常由大写单词组成,不带标点符号("驼峰命名法")。方法名称通常应该是动词,例如 GetItems,信号名称通常应该是事件描述,例如 ItemsChanged

错误名称与接口名称具有相同的限制。

错误名称与接口名称具有相同的命名约定,通常包含.Error.;例如,example.com 的所有者可能定义错误com.example.MusicPlayer1.Error.FileNotFoundcom.example.MusicPlayer1.Error.OutOfMemory。D-Bus 本身定义的错误,如org.freedesktop.DBus.Error.Failed,遵循类似的模式。

每种消息类型(METHOD_CALLMETHOD_RETURNERRORSIGNAL)都有自己的预期使用约定和标题字段。本节描述了这些约定。

一些消息会在远程对象上触发操作。这些消息称为方法调用消息,类型标记为 METHOD_CALL。这些消息在典型程序中自然地映射到对象的方法。

要求方法调用消息必须具有一个MEMBER头字段,指示方法的名称。可选地,消息具有一个INTERFACE字段,指定方法所属的接口。强烈建议在所有方法调用消息中包含INTERFACE

如果同一对象上的两个或多个接口具有相同名称的方法,且缺少INTERFACE字段,则无法确定将调用其中哪个方法。实现可以选择返回错误,或将消息传递为如果它具有这些接口中的任意一个。

在某些情况下(比如众所周知的系统总线),消息通过一个访问控制列表来过滤,该列表位于远程对象实现之外。如果该过滤器通过匹配接口来拒绝某些消息,或者仅接受发送到特定接口的消息,那么它还必须拒绝没有 INTERFACE 的消息:否则,恶意应用程序可能会利用这一点绕过过滤器。

方法调用消息还包括一个 PATH 字段,指示要在其上调用方法的对象。如果调用通过消息总线传递,消息还将具有一个 DESTINATION 字段,指定接收消息的连接的名称。

当应用程序处理方法调用消息时,需要返回一个回复。回复由REPLY_SERIAL头字段标识,指示正在回复的METHOD_CALL的序列号。回复可以是两种类型之一; 要么是METHOD_RETURN,要么是ERROR

如果回复的类型是 METHOD_RETURN,则回复消息的参数是方法调用的返回值或“输出参数”。如果回复的类型是 ERROR,则表示已抛出“异常”,调用失败;不会提供返回值。对同一方法调用发送多个回复是没有意义的。

即使方法调用没有返回值,也需要一个 METHOD_RETURN 回复,这样调用者就会知道方法已成功处理。

回复消息中的 METHOD_RETURNERROR 必须包含 REPLY_SERIAL 头字段。

如果METHOD_CALL消息具有标志NO_REPLY_EXPECTED,则接收该方法的应用程序不应发送回复消息(无论回复是METHOD_RETURN还是ERROR)。

除非消息具有标志 NO_AUTO_START,否则如果目标名称不存在,则将在传递消息之前启动(激活)拥有目标名称的程序。请参阅名为“消息总线启动服务(激活)”的部分。消息将被保留,直到新程序成功启动或启动失败;在失败的情况下,将返回错误。此标志仅在消息总线上下文中相关,在没有中间总线的一对一通信中将被忽略。

将方法调用映射到本机API

D-Bus的API可能会将方法调用映射到特定编程语言(如C++)中的方法调用,或将在接口定义语言(IDL)中编写的方法调用映射到D-Bus消息。

在这种类型的 API 中,方法的参数通常被称为“in”(表示在 METHOD_CALL 中发送),或者“out”(表示在 METHOD_RETURN 中返回)。一些 API(如 CORBA)还有“inout” 参数,既被发送又被接收,即调用者传入一个值,该值会被修改。在 D-Bus 中,一个“inout” 参数等同于一个“in” 参数,后跟一个“out” 参数。你不能通过引用在网络上传递数据,因此“inout” 只是进程内 API 的一种幻觉。

给定一个具有零个或一个返回值的方法,后面是零个或多个参数,其中每个参数可以是“in”、“out”或“inout”,调用者通过按顺序附加每个“in”或“inout”参数来构造消息。“out”参数不会在调用者的消息中表示。

接收方通过首先附加任何返回值,然后按顺序附加每个“out”或“inout”参数来构造回复。回复消息中不包含“in”参数。

错误回复通常在具有异常处理机制的语言中映射为异常。

在从本地API转换为D-Bus时,或许将D-Bus命名约定("FooBar")自动映射到本地约定,比如"fooBar"或"foo_bar",可能是个不错的选择。只要你能说本地API是专门为D-Bus编写的,这就没问题。在编写将在总线上导出的对象实现时,这是最有意义的。用于调用远程D-Bus对象的对象代理可能需要能够调用任何D-Bus方法,因此这样的魔术名称映射可能会成为一个问题。

本规范不要求对本地 API 绑定做任何事情;上述只是对绑定之间保持一致性的建议约定。

与方法调用不同,信号发射没有回复。信号发射只是一条类型为 SIGNAL 的单个消息。它必须具有三个头字段:PATH 指定发射信号的对象,以及 INTERFACEMEMBER 分别指定信号的完全限定名称。对于信号,INTERFACE 头是必需的,尽管对于方法调用来说是可选的。

类型为 ERROR 的消息最常见的情况是作为对 METHOD_CALL 的回复,但也可能作为对任何类型消息的回复返回。例如,消息总线在信号发出时如果没有足够的内存发送信号,将会返回一个 ERROR

一个 ERROR 可能有任意数量的参数,但如果第一个参数是一个 STRING,那么它必须是一个错误消息。错误消息可以被记录或以某种方式显示给用户。

本文档中的注释

本文档使用简单的伪 IDL 描述特定的方法调用和信号。以下是一个方法调用的示例:

org.freedesktop.DBus.StartServiceByName (in STRING name, in UINT32 flags, out UINT32 resultcode)

这意味着 INTERFACE = org.freedesktop.DBus,MEMBER = StartServiceByName,METHOD_CALL 参数是 STRINGUINT32METHOD_RETURN 参数是 UINT32。请记住 MEMBER 字段不能包含任何 '.'(句号)字符,因此已知在“IDL”中名称的最后一部分是成员名称。

在 C++ 中,可能会看起来像这样:

unsigned int org::freedesktop::DBus::StartServiceByName (const char
*name, unsigned int flags);

或者同样有效的是,返回值可以作为参数传递:

        void org::freedesktop::DBus::StartServiceByName (const char   \*name,                                                             unsigned int  flags,                                                             unsigned int \*resultcode);

API设计者真的可以自行决定如何展示这个。你可以设计一个API,在C++中不使用命名空间,使用STL或Qt,使用可变参数,或者任何你想要的。

信号的书写方式如下:

org.freedesktop.DBus.NameLost (STRING name)

信号不指定“输入”与“输出”,因为只有一个方向是可能的。

在实际的 API 实现中,不鼓励使用这种简陋的伪 IDL;你可以使用你所用语言的本地表示法,或者例如使用 COM 或 CORBA IDL。

无效的协议和规范扩展

出于安全考虑,D-Bus协议应严格解析和验证,除了已定义的扩展点。任何无效的协议或规范违规都应立即断开连接,而不通知另一端。应仔细考虑例外情况,例如,对于广泛部署的实现的众所周知的特殊情况可能需要例外。在连接的另一端被100%信任且已知友好的情况下,出于性能原因跳过验证在某些情况下也是有道理的。

一般来说,违反规范中的“必须”要求应被视为可能试图利用安全漏洞,而违反“应该”建议则应被视为合法的(尽管在某些情况下可能应生成错误)。

以下扩展点是有意内置到 D-Bus 中的,不得视为无效协议。这些扩展点旨在供将来版本的规范使用,而非供第三方使用。目前,第三方要扩展 D-Bus 而不破坏互操作性的唯一方法是在认证协议的一部分引入一种协商新功能支持的方式,使用以 EXTENSION_ 为前缀的命令。目前还没有标准的协商功能的方式。

  • 在认证协议中(请参见“认证协议”一节),未知命令会导致错误而不是断开连接。这样可以为协议的未来扩展留下空间。以EXTENSION_开头的命令为第三方保留。
  • 认证协议支持可插拔的认证机制。
  • 未知类型的消息(不是 METHOD_CALLMETHOD_RETURNERRORSIGNAL 之一)将被忽略。但是,未知类型的消息必须像已知消息一样保持格式良好。它们仍然具有正常的标头和正文。
  • 未知或意外的字段代码的标题字段必须被忽略,尽管它们仍然必须是格式良好的。
  • 当然可以添加新的标准接口(带有新的方法和信号)。

在消息流开始之前,两个应用程序必须进行身份验证。身份验证使用简单的明文协议;该协议是SASL配置文件,与SASL规范直接对应。这里不使用消息编码,只使用明文消息。

在 D-Bus 中使用 SASL 需要我们定义非空授权标识字符串的含义。在 Unix 平台上使用 D-Bus 时,非空的 SASL 授权标识代表一个 Unix 用户。完全由 ASCII 十进制数字组成的授权标识代表 POSIX 定义的数字用户 ID,例如 0 代表 root 用户或 1000 代表许多系统上创建的第一个用户。不要求接受或支持非数字授权标识,但如果使用,必须解释为 POSIX struct passwdpw_name 字段中找到的登录名,例如 root,并标准化为相应的数字用户 ID。为了最佳互操作性,客户端和服务器应使用数字用户 ID。

当在Windows平台上使用D-Bus时,非空的SASL授权标识表示Windows安全标识符(SID)的字符串形式,例如S-1-5-21-3623811015-3361044348-30300820-1013表示域或本地计算机用户,S-1-5-18表示LOCAL_SYSTEM用户。用户界面上的用户名,如AdministratorLOCAL_SYSTEM,在D-Bus协议中不使用。

在示例中,“C:”和“S:”分别表示客户端发送的行和服务器发送的行。客户端发送第一行,服务器必须对客户端的每一行作出单行回复,只有一种例外情况:对于BEGIN命令不需要回复。

该协议是基于行的协议,每行以\r\n结尾。每行以全大写的ASCII命令名称开头,仅包含字符范围[A-Z_], 一个空格,然后是命令的任何参数,最后是结束该行的\r\n。该协议区分大小写。所有字节必须在ASCII字符集中。客户端到服务器的命令如下:

  • AUTH \[机制\] \[初始响应\]
  • 取消
  • 开始
  • 数据 <十六进制编码的数据>
  • 错误 \[人类可读的错误解释\]
  • NEGOTIATE_UNIX_FD

从服务器到客户端如下所示:

  • 拒绝 <以空格分隔的机制名称列表>
  • OK <GUID in hex>
  • 数据 <十六进制编码的数据>
  • 错误 \[人类可读的错误解释\]
  • AGREE_UNIX_FD

非官方扩展命令集必须以字母“EXTENSION_”开头,以避免与未来官方命令发生冲突。例如,“EXTENSION_COM_MYDOMAIN_DO_STUFF”。

特殊凭证传递空字节

连接到服务器后,客户端必须发送一个空字节。在某些使用sendmsg()与SCM_CREDS或SCM_CREDENTIALS通过UNIX域套接字传递凭据信息的操作系统上,此字节可能附带凭据信息。然而,即使在不需要发送字节以传输凭据的操作系统上,也必须发送空字节,即使在其他类型的套接字上也是如此。本文档中描述的文本协议从单个空字节开始。如果从客户端接收到的第一个字节不是空字节,则服务器可能会断开该客户端的连接。

在初始字节之外的任何上下文中,空字节都是一个错误;该协议仅支持ASCII。

发送的凭据和空字节一起可用于 SASL 机制 EXTERNAL。

客户端发送 AUTH 命令给服务器。服务器会回复 DATA、OK 或 REJECTED。

如果 AUTH 命令没有参数,则是请求列出可用的机制。服务器必须用一个 REJECTED 命令列出它理解的机制,或者返回一个错误。

如果 AUTH 命令指定了一种机制,并且服务器支持该机制,服务器应该开始使用 DATA 命令与客户端交换 SASL 挑战-响应数据。

如果服务器不支持 AUTH 命令中指定的机制,则必须发送一个 REJECTED 命令,列出它支持的机制,或者发送一个错误。

如果提供了 [initial-response] 参数,则旨在与没有初始挑战(或空白初始挑战)的机制一起使用,就好像它是初始 DATA 命令的参数。如果所选机制具有初始挑战并且提供了 [initial-response],服务器应通过发送 REJECTED 拒绝认证。

如果在交换数据命令后身份验证成功,则必须向客户端发送一个OK命令。

客户端发送 CANCEL 命令到服务器。服务器回复 REJECTED。

在发送BEGIN命令之前的任何时间,客户端都可以发送CANCEL命令。收到CANCEL命令后,服务器必须发送REJECTED命令并中止当前的身份验证交换。

DATA 命令可能来自客户端或服务器,只是包含一个十六进制编码的数据块,根据正在使用的 SASL 机制进行解释。如果由客户端发送,服务器会回复 DATA、OK 或 REJECTED。

某些SASL机制支持发送“空字符串”; FIXME 我们需要一些方法来实现这一点。

客户端发送 BEGIN 命令给服务器。服务器不会回复。

BEGIN 命令表示客户端已经收到来自服务器的 OK 命令,并完成了希望进行的任何特性协商,宣告消息流即将开始。

服务器在接收客户端的BEGIN命令中的\r\n后,第一个八位字节必须是经过认证/加密的D-Bus消息流的第一个八位字节。

与所有其他命令不同,服务器不会用自己的认证命令回复BEGIN命令。在回复BEGIN命令之前的命令后的\r\n之后,客户端接收到的下一个八位组必须是D-Bus消息的经过认证/加密的流的第一个八位组。

服务器向客户端发送 REJECTED 命令。

REJECTED 命令表示当前的身份验证交换失败,进一步交换数据是不合适的。客户端通常会尝试另一种机制,或尝试提供不同的响应来应对挑战。

可选地,REJECTED 命令有一个以空格分隔的可用认证机制列表作为参数。如果服务器曾经提供了一组支持的机制列表,那么每次发送 REJECTED 消息时都必须提供相同的列表。客户端可以忽略在第一次之后收到的所有列表。

服务器向客户端发送 OK 命令。

OK 命令表示客户端已经通过身份验证。客户端现在可以继续进行 Unix 文件描述符传递的协商。为此,它应向服务器发送 NEGOTIATE_UNIX_FD。

否则,客户端必须通过发送BEGIN命令,然后是其消息流,或者断开连接来响应OK命令。在接收到BEGIN命令后,服务器不得使用此协议接受额外的命令。进一步的通信将是一系列D-Bus消息(可选加密,根据协商),而不是此协议。

如果没有协商,客户端在OK命令的\r\n之后接收到的第一个八位组必须是D-Bus消息的经过认证/加密的流的第一个八位组。如果客户端协商Unix文件描述符传递,客户端在AGREE_UNIX_FD或ERROR回复的\r\n之后接收到的第一个八位组必须是经过认证/加密的流的第一个八位组。

OK 命令有一个参数,即服务器的 GUID。有关服务器 GUID 的更多信息,请参阅“服务器地址”一节

ERROR 命令可以在任一方向发送。如果由客户端发送,服务器将回复 REJECTED。

ERROR命令表示服务器或客户端不知道某个命令,不接受当前上下文中给定的命令,或者无法理解命令的参数。这允许协议进行扩展;客户端或服务器可以发送一个只存在于新协议版本中的命令,如果收到ERROR而不是适当的响应,则可以回退到使用其他技术。

如果发送了错误,发送错误的服务器或客户端必须继续,就好像导致错误的命令从未收到过一样。然而,接收到错误的服务器或客户端应尝试除了导致错误的操作之外的其他操作;如果只是取消/拒绝认证。

如果 D-Bus 协议在将来发生不兼容的更改,实现新协议的应用程序可能会通过发送新命令并从不理解它的应用程序接收错误来检查新协议的支持。因此,认证协议的错误特性是一个逃生口,让我们能够在将来协商扩展或更改 D-Bus 协议。

协商_UNIX_FD 命令

客户端向服务器发送 NEGOTIATE_UNIX_FD 命令。服务器回复 AGREE_UNIX_FD 或 ERROR。

NEGOTIATE_UNIX_FD 命令表示客户端支持 Unix 文件描述符传递。此命令只能在连接经过身份验证后发送,即在客户端收到 OK 后发送。此命令只能在支持 Unix 文件描述符传递的传输上发送。

在收到 NEGOTIATE_UNIX_FD 后,服务器必须以 AGREE_UNIX_FD 或 ERROR 做出回应。如果所选择的传输方式支持 Unix 文件描述符传递且服务器支持此功能,则应该回应前者。如果传输方式不支持 Unix 文件描述符传递、服务器不支持此功能,或者服务器因安全或其他原因决定不启用文件描述符传递,则应该回应后者。

服务器发送 AGREE_UNIX_FD 命令给客户端。

AGREE_UNIX_FD 命令表示服务器支持 Unix 文件描述符传递。此命令只能在连接经过身份验证后发送,并且客户端发送 NEGOTIATE_UNIX_FD 以启用 Unix 文件描述符传递。此命令只能在支持 Unix 文件描述符传递的传输上发送。

在收到 AGREE_UNIX_FD 后,客户端必须用 BEGIN 做出回应,然后发送其消息流,或者断开连接。服务器在接收到 BEGIN 命令后不能再使用该协议接受额外的命令。进一步的通信将是一系列 D-Bus 消息(可选加密,根据协商),而不是使用该协议。

身份验证和协商协议的未来扩展是可能的。为此可能会引入新命令。如果客户端或服务器收到未知命令,应该以错误回复,而不将其视为致命错误。新命令可以在身份验证之前和之后引入,即在OK命令之前和之后。

图1. 成功外部认证示例

31303030 是 ASCII 十进制数 "1000" 的十六进制表示,因此在这个示例中,客户端将作为 Unix uid 1000 进行身份验证。

        C: AUTH EXTERNAL 31303030
        S: OK 1234deadbeef
        C: BEGIN

图 2. 找出机制然后选择一个示例

        C: 认证
        S: 拒绝 KERBEROS_V4 SKEY
        C: 认证 SKEY 7ab83f32ee
        S: 数据 8799cabb2ea93e
        C: 数据 8ac876e8f68ee9809bfa876e6f9876g8fa8e76e98f
        S: OK 1234deadbeef
        C: 开始

图 3. 客户端发送未知命令,然后回退到常规身份验证

532d312d352d3138 是 Windows SID "S-1-5-18" 的十六进制表示形式,因此在此示例中,客户端正在以 Windows SID S-1-5-18 进行身份验证。

        C: FOOBAR
        S: ERROR
        C: AUTH EXTERNAL 532d312d352d3138
        S: OK 1234deadbeef
        C: BEGIN

图 4. 服务器不支持初始认证机制示例

        C: AUTH EXTERNAL
        S: REJECTED KERBEROS_V4 SKEY
        C: AUTH SKEY 7ab83f32ee
        S: DATA 8799cabb2ea93e
        C: DATA 8ac876e8f68ee9809bfa876e6f9876g8fa8e76e98f
        S: OK 1234deadbeef
        C: BEGIN

图 5. 错误密码示例或类似情况,随后成功重试

        C: 认证外部 736d6376
        S: 拒绝 KERBEROS_V4 SKEY
        C: 认证 SKEY 7ab83f32ee
        S: 数据 8799cabb2ea93e
        C: 数据 8ac876e8f68ee9809bfa876e6f9876g8fa8e76e98f
        S: 拒绝
        C: 认证 SKEY 7ab83f32ee
        S: 数据 8799cabb2ea93e
        C: 数据 8ac876e8f68ee9809bfa876e6f9876g8fa8e76e98f
        S: OK 1234deadbeef
        C: 开始

图 6. skey 取消并重新启动示例

        C: AUTH EXTERNAL 32303438
        S: REJECTED KERBEROS_V4 SKEY
        C: AUTH SKEY 7ab83f32ee
        S: DATA 8799cabb2ea93e
        C: CANCEL
        S: REJECTED
        C: AUTH SKEY 7ab83f32ee
        S: DATA 8799cabb2ea93e
        C: DATA 8ac876e8f68ee9809bfa876e6f9876g8fa8e76e98f
        S: OK 1234deadbeef
        C: BEGIN

图 7. 成功进行外部认证并成功协商 Unix FD 传递的示例

        C: AUTH EXTERNAL 31303030
        S: OK 1234deadbeef
        C: NEGOTIATE_UNIX_FD
        S: AGREE_UNIX_FD
        C: BEGIN

图 8. 成功进行外部认证,但未成功协商 Unix FD 传递的示例

        C: AUTH EXTERNAL 31303030
        S: OK 1234deadbeef
        C: NEGOTIATE_UNIX_FD
        S: ERROR Not supported on this OS
        C: BEGIN

认证状态图

本节记录了客户端和服务器的身份验证协议状态机。这可能是实现协议的最可靠方式。

为了更准确地描述协议状态机与认证机制之间的交互,使用以下符号:MECH(CHALL)表示服务器挑战CHALL被输入到机制MECH中,该机制返回其中之一

  • CONTINUE(RESP) 表示继续进行身份验证对话,并将 RESP 作为响应发送到服务器;
  • OK(RESP) 表示在将 RESP 发送到服务器后,认证对话的客户端部分已经完成,服务器应该返回“OK”;
  • 错误表示 CHALL 无效,无法处理。

RESP 和 CHALL 都可以为空。

客户端首先通过默认机制获得初始响应,并发送 AUTH MECH RESP,或者如果机制未提供初始响应则发送 AUTH MECH。如果机制返回 CONTINUE,则客户端开始处于_WaitingForData_状态,如果机制返回 OK,则客户端开始处于_WaitingForOK_状态。

客户端应该跟踪可用的机制以及已经尝试过的机制。此列表用于决定发送哪个 AUTH 命令。当列表耗尽时,客户端应该放弃并关闭连接。

等待数据

  • 接收数据挑战

MECH(CHALL) 返回 CONTINUE(RESP) → 发送 DATA RESP,转到_WaitingForData_

MECH(CHALL) 返回 OK(RESP) → 发送 DATA RESP,转到_WaitingForOK_

MECH(CHALL) 返回错误 → 发送错误 \[msg\],转到_WaitingForData_

  • 接收到 REJECTED [mechs] → 发送 AUTH [next mech],转到 WaitingForData 或 WaitingForOK
  • 接收错误 → 发送取消,转到_WaitingForReject_
  • 接收 OK → 已验证,选择一个:

send NEGOTIATE_UNIX_FD, goto WaitingForAgreeUnixFD

发送 BEGIN,终止身份验证对话(成功)
  • 收到其他内容 → 发送错误,转到_WaitingForData_

等待OK

  • 接收 OK → 已验证,选择一个:

send NEGOTIATE_UNIX_FD, goto WaitingForAgreeUnixFD

发送 BEGIN,终止身份验证对话(成功)

  • 接收 REJECTED [mechs] → 发送 AUTH [next mech],转到 WaitingForDataWaitingForOK
  • 接收数据 → 发送取消,转到_WaitingForReject_
  • 收到错误 → 发送取消,转至_WaitingForReject_
  • 接收到其他内容 → 发送错误,转到_WaitingForOK_

等待拒绝

  • 接收 REJECTED [mechs] → 发送 AUTH [next mech],转到 WaitingForDataWaitingForOK
  • 收到其他任何内容 → 终止认证对话,断开连接

等待同意Unix FD。到达这种状态时,客户端已经通过身份验证。

  • 接收 AGREE_UNIX_FD → 启用 Unix fd 传递,发送 BEGIN,终止认证对话(成功)
  • 收到错误 → 禁用 Unix fd 传递,发送开始,终止身份验证对话(成功)
  • 收到其他任何内容 → 终止认证对话,断开连接

对于服务器 MECH(RESP) 意味着客户端响应 RESP 被馈送到机制 MECH,该机制返回其中之一

  • CONTINUE(CHALL) 意味着继续认证对话并将 CHALL 作为挑战发送给客户端;
  • OK 表示客户端已成功进行了身份验证;
  • REJECTED 表示客户端未能通过身份验证或 RESP 中存在错误。

服务器开始于状态_WaitingForAuth_。如果客户端被拒绝太多次,服务器必须断开与客户端的连接。

等待授权

  • 接收 AUTH → 发送 REJECTED \[mechs\],转到 WaitingForAuth
  • 接收 AUTH MECH RESP

MECH not valid mechanism → send REJECTED \[mechs\], goto WaitingForAuth

MECH(RESP) 返回 CONTINUE(CHALL) → 发送 DATA CHALL,转到_WaitingForData_

MECH(RESP) 返回 OK → 发送 OK,转到_WaitingForBegin_

MECH(RESP) 返回 REJECTED → 发送 REJECTED [mechs],转到_WaitingForAuth_

  • 接收 BEGIN → 终止认证对话,断开连接
  • 收到错误 → 发送被拒绝 [mechs],转到_WaitingForAuth_
  • 收到其他内容 → 发送错误,转到_WaitingForAuth_

等待数据

  • 收到数据响应

MECH(RESP) 返回 CONTINUE(CHALL) → 发送 DATA CHALL,跳转至_WaitingForData_

MECH(RESP) 返回 OK → 发送 OK,转到_WaitingForBegin_

MECH(RESP) 返回 REJECTED → 发送 REJECTED [mechs],转到_WaitingForAuth_

  • 接收 BEGIN → 终止认证对话,断开连接
  • 接收 CANCEL → 发送 REJECTED \[mechs\], 转到 WaitingForAuth
  • 收到错误 → 发送被拒绝 [mechs],转到_WaitingForAuth_
  • 收到其他内容 → 发送错误,转到_WaitingForData_

等待开始

  • 接收 BEGIN → 终止身份验证对话,客户端已验证
  • 接收 NEGOTIATE_UNIX_FD → 发送 AGREE_UNIX_FD 或 ERROR,转到 WaitingForBegin
  • 接收 CANCEL → 发送 REJECTED \[mechs\], 转到 WaitingForAuth
  • 收到错误 → 发送被拒绝 [mechs],转到_WaitingForAuth_
  • 接收到其他内容 → 发送错误,转到_WaitingForBegin_

认证机制

本节描述了一些实际 D-Bus 实现通常支持的身份验证机制。D-Bus 协议还允许任何其他标准的 SASL 机制,尽管 D-Bus 的实现通常不支持。

EXTERNAL 机制在RFC 4422“简单认证和安全层(SASL)”附录A“SASL EXTERNAL 机制”中定义。这是在可以通过非通道方式传输凭据的平台上推荐的认证机制,特别是可以通过unix: 传输进行凭据传递的 Unix 平台。

在Unix平台上,可互操作的客户端应优先发送整数Unix用户ID的ASCII十进制字符串形式作为授权标识,例如1000。当通过认证协议编码为十六进制时,通常会得到类似AUTH EXTERNAL 31303030的行,后跟\r\n。

在 Windows 平台上,使用 EXTERNAL 机制的客户端应该将 Windows 安全标识符以字符串形式作为授权标识,例如 S-1-5-21-3623811015-3361044348-30300820-1013 用于域或本地计算机用户,或 S-1-5-18 用于 LOCAL_SYSTEM 用户。在经过身份验证协议的十六进制编码后,通常会得到类似 AUTH EXTERNAL 532d312d352d3138 的行,然后是 \r\n。

DBUS_COOKIE_SHA1 是一种特定于 D-Bus 的 SASL 机制。其参考实现是 D-Bus 参考实现的一部分。

该机制旨在确认客户端能够读取用户拥有的私有文件。如果客户端能够证明自己可以访问存储在该文件中的秘密 cookie,那么客户端就得到了认证。因此,DBUS_COOKIE_SHA1 的安全性取决于安全的主目录。这是在无法使用 EXTERNAL 的平台和配置中推荐的认证机制。

在整个描述过程中,“十六进制编码”必须以小写字母 a 到 f 输出;在 DBUS_COOKIE_SHA1 机制中不能使用大写字母 A 到 F。

身份验证的进行如下:

  • 客户端发送要进行身份验证的用户名,以十六进制编码。
  • 服务器发送其“cookie上下文”的名称(见下文);一个空格字符;客户端必须展示知识的秘密cookie的整数ID;一个空格字符;然后是一个随机生成的挑战字符串,所有这些都被十六进制编码为一个单一字符串。
  • 客户端定位 cookie 并生成自己的随机生成的挑战字符串。然后客户端将服务器解码的挑战、一个“:”字符、自己的挑战、另一个“:”字符和 cookie 连接在一起。它计算这个组合字符串的 SHA-1 哈希作为十六进制摘要。然后客户端将自己的挑战字符串、一个空格字符和 SHA-1 十六进制摘要连接起来,对结果进行十六进制编码,并将其发送回服务器。
  • 服务器生成与客户端使用的相同的连接字符串,并计算其SHA-1哈希值。它将哈希值与从客户端接收到的哈希值进行比较;如果两个哈希值匹配,则客户端经过身份验证。

每个服务器都有一个“cookie上下文”,这是一个名称,用于标识适用于该服务器的一组cookie。一个示例上下文可能是“org_freedesktop_session_bus”。上下文名称必须是有效的ASCII、非零长度,并且不能包含斜杠(“/”)、反斜杠(“\”)、空格(“ ”)、换行符("\n")、回车符("\r")、制表符("\t")或句点(“.”)。有一个默认上下文,“org_freedesktop_general”,如果服务器没有另行指定,则使用该上下文。

Cookies are stored in a user's home directory, in the directory ~/.dbus-keyrings/. This directory must not be readable or writable by other users. If it is, clients and servers must ignore it. The directory contains cookie files named after the cookie context.

一个 cookie 文件包含一行一个 cookie。每行有三个以空格分隔的字段:

  • cookie ID号码,必须是非负整数,并且不能在同一文件中使用两次。
  • cookie的创建时间,以UNIX时间戳格式表示。
  • cookie本身是一个十六进制编码的随机字节块。cookie的长度可以是任意的,但显然长度越长,安全性就越高。

只有服务器进程才能修改 cookie 文件。它们必须按照以下步骤进行操作:

  • 将锁文件名设为 cookie 文件名加上“.lock”。服务器应尝试使用 O_CREAT | O_EXCL 创建此文件。如果文件创建失败,则锁定失败。服务器应在合理的时间内重试,然后可以选择删除现有的锁定,以免用户需要手动删除过时的锁定。
  • 一旦锁定文件被创建,服务器将加载 cookie 文件。然后应删除任何旧的 cookie(超时时间可以相当短),或者超过合理的未来时间(以便 cookie 永远不会意外变成永久的,如果某个时刻时钟被设置在遥远的未来)。参考实现会删除超过未来 5 分钟或过去 7 分钟的 cookie。为了实现互操作性,建议在其他实现中使用相同的任意时间。
  • 如果没有足够新的 cookie,服务器会生成一个新的 cookie。为了避免虚假的身份验证失败,接近删除时间的 cookie 不应该用于新的身份验证操作。例如,这样可以避免客户端开始使用一个年龄为 6 分 59 秒的 cookie,随后由于花费 2 秒时间,导致身份验证失败,此时 cookie 的年龄变为 7 分 01 秒,超过 7 分钟,导致服务器将其删除。参考实现在最近的 cookie 超过 5 分钟时生成一个新的 cookie,给客户端至少 2 分钟时间完成身份验证。为了实现互操作性,建议在其他实现中使用相同的任意时间。
  • 必须将修剪和可能添加的 cookie 文件以原子方式重新保存(使用一个临时文件,然后将其重命名)。
  • 必须通过删除锁定文件来释放锁定。

客户端无需锁定文件即可加载文件,因为服务器需要原子性保存文件。

服务器地址由传输名称后跟一个冒号组成,然后是一个可选的、以逗号分隔的键值对列表,格式为key=value。每个值都经过转义处理。

例如:

unix:path=/tmp/dbus-test

地址是 /tmp/dbus-test 的 Unix 套接字。

值转义类似于 URI 转义,但更简单。

  • 可选转义字节集为:[-0-9A-Za-z_/.*]。要进行转义,必须将不在可选转义字节集中的每个 字节(注意,不是字符)替换为 ASCII 百分号(%)和字节的十六进制值。十六进制值必须始终是两位数,即使第一位是零。如果需要,可以对可选转义字节进行转义。
  • 要取消转义,请在值中附加每个字节;如果一个字节是ASCII百分号(%)字符,则改为附加其后的十六进制值。如果%字节后面没有两个十六进制数字,则出错。如果看到未转义的非可选转义字节,则出错。

可选转义字节集旨在保持地址的可读性和便利性。

服务器可以指定一个键值对,键为guid,值为十六进制编码的16字节序列。名为“UUIDs”的部分描述了guid字段的格式。如果存在,此UUID可用于区分一个服务器地址与另一个。服务器应为其监听的每个地址使用不同的UUID。例如,如果消息总线守护程序同时提供UNIX域套接字和TCP连接,但无论客户端如何连接都对待客户端相同,那么这两个连接在连接后是等效的,但应具有不同的UUID以区分连接的类型。

地址 UUID 功能的目的是允许客户端避免向同一服务器打开多个相同的连接,通过允许客户端检查地址是否对应于已存在的连接。比较两个地址是不够的,因为地址可以被不同的服务器回收利用,并且等效的地址如果仅仅作为字符串比较可能看起来不同(例如,在 TCP 地址中的主机可以作为 IP 地址或主机名给出)。

请注意,尽管API和文档的其余部分说“UUID”,但地址键是guid,出于历史原因。

\[澄清是否尝试连接到每个地址是必须的还是只是建议\] 连接服务器时,可以用分号分隔多个服务器地址。然后库将尝试连接到第一个地址,如果失败,它将尝试连接到下一个指定的地址,依此类推。例如

unix:path=/tmp/dbus-test;unix:path=/tmp/dbus-test2

一些地址是_connectable_。可连接的地址包含足够的信息,使客户端能够连接到它。例如,tcp:host=127.0.0.1,port=4242 是一个可连接的地址。并非每个可连接的地址都可以进行监听:例如,无法在unixexec:地址上进行监听。

一些地址是可监听的。可监听的地址包含足够的信息,使服务器能够监听它,生成一个可连接的地址(可能与原始地址不同)。许多可监听的地址是不可连接的:例如,tcp:host=127.0.0.1 是可监听的,但不可连接(因为它没有指定端口号)。

监听一个不可连接的地址将导致一个可连接的地址,该地址与可监听的地址不同。例如,监听 tcp:host=127.0.0.1 可能导致可连接的地址 tcp:host=127.0.0.1,port=30958,监听 unix:tmpdir=/tmp 可能导致可连接的地址 unix:abstract=/tmp/dbus-U8OSdmf7,或者监听 unix:runtime=yes 可能导致可连接的地址 unix:path=/run/user/1234/bus

\[FIXME 我们需要详细指定每种传输方式及其可能的参数\] 当前的传输方式包括:Unix 域套接字(包括 Linux 上的抽象命名空间)、launchd、systemd、TCP/IP、执行的子进程以及使用进程内管道的调试/测试传输。未来可能的传输方式包括通过 X11 协议进行隧道传输。

Unix 域套接字可以是文件系统中的路径,也可以是 Linux 内核中的路径,它们可以是抽象的,类似于路径,但不会显示在文件系统中。

当由 D-Bus 库打开 Unix 套接字时,套接字地址长度不包括整个 struct sockaddr_un,而只包括路径名或抽象字符串的长度(以及其他字段)。

它们是 D-Bus 推荐的传输方式,可以单独使用,也可以与 systemdlaunchd 地址一起使用。

指定pathabstract的Unix地址既可被监听也可被连接。指定tmpdirdir的Unix地址只能被监听:相应的可连接地址将指定pathabstract。同样,指定runtime的Unix地址只能被监听,相应的可连接地址将指定path

Unix 域套接字地址由"unix:"前缀标识,并支持以下键/值对:

必须提供 pathabstractruntimedirtmpdir 中的一个键。

launchd 是一个开源的服务器管理系统,用于取代 Apple Mac OS X 版本 10.4 及以上的 init、inetd 和 cron。它为每个用户提供一个共享的会话总线地址,并废弃了在OSX上启用 X11 的 D-Bus 启动器。

launchd在环境变量中通过DBUS_LAUNCHD_SESSION_BUS_SOCKET变量分配一个套接字并提供unix路径。由launchd生成的每个进程(或者如果由launchd启动,则为dbus-daemon)都可以通过其环境访问它。其他进程可以通过执行以下命令查询launchd套接字:$ launchctl getenv DBUS_LAUNCHD_SESSION_BUS_SOCKET通常由D-Bus客户端库执行,因此不必手动执行。

launchd 在 Microsoft Windows 上不可用。

launchd 地址是可监听和可连接的。

launchd 地址由"launchd:"前缀标识,并支持以下键/值对:

必须提供 env 键。

systemd 是一个开源的服务器管理系统,用于取代较新的 Linux 系统上的 init 和 inetd。它支持套接字激活。使用 D-Bus systemd 传输从 systemd 获取套接字激活文件描述符,并在当前进程由其进行套接字激活时将其用作 D-Bus 传输。

systemd 传输仅接受通过套接字激活传递的一个或多个 Unix 域或 TCP 流套接字。强烈建议使用 Unix 域套接字。

systemd 传输协议在非 Linux 操作系统上不可用。

systemd 传输不定义任何参数键。

systemd 地址是可监听的,但不可连接。相应的可连接地址是套接字的 unixtcp 地址。

tcp 传输提供了基于 TCP/IP 的连接,用于连接位于同一主机或不同主机上的客户端。

类似于远程 X11,TCP 传输没有完整性或机密性保护,因此通常只应在本地环回接口上使用,例如使用类似 tcp:host=127.0.0.1tcp:host=localhost 的地址。特别是,将众所周知的系统总线或众所周知的会话总线配置为侦听非环回 TCP 地址是不安全的。

在Windows和大多数Unix平台上,TCP堆栈无法通过TCP连接传输凭据,因此EXTERNAL认证机制通常无法在此传输中工作(尽管D-Bus的参考实现能够通过端口号识别Windows上的回环TCPv4连接,部分启用EXTERNAL机制)。通常使用DBUS_COOKIE_SHA1机制。

开发人员有时会被诱惑使用远程TCP作为调试工具。然而,如果在成品中保留了这种功能,结果将会非常不安全。开发人员应该使用安全外壳或类似协议中继连接,而不是使用远程TCP。

远程TCP连接在历史上有时用于在受信任的本地区域网络中的不同计算机上的相同用户的登录会话之间共享单个会话总线,与未加密的远程X11、NFS共享的主目录和NIS(YP)身份验证结合使用。这种方法在同一局域网上容易受到攻击者的攻击,应被强烈废弃;更具体地说,它与未加密的远程X11和NFSv2/NFSv3 一样不安全。D-Bus维护者建议每个(用户、计算机)对使用一个单独的会话总线,只能从该计算机内部访问。

所有 tcp 地址都是可监听的。指定了 hostport,且 port 不为零的 tcp 地址也是可连接的。

TCP/IP套接字地址由"tcp:"前缀标识,并支持以下键/值对:

带有随机数认证的TCP套接字

nonce-tcp 传输提供了一个修改后的 TCP 传输,使用简单的身份验证机制,以确保只有对文件系统中某个位置具有读取权限的客户端才能连接到服务器。服务器将一个秘密,即 nonce,写入文件,只有在客户端连接后立即发送 nonce 的情况下才会接受传入的客户端连接。nonce 机制无需设置,与身份验证部分描述的高级身份验证机制无关。

nonce-tcp 传输在概念上类似于 DBUS_COOKIE_SHA1 认证机制和 tcp 传输的组合,最初似乎是由于对 SASL 认证机制的误解而实现的。

与普通的tcp传输一样,nonce-tcp传输没有完整性或机密性保护,因此通常只应在本地环回接口上使用,例如使用类似 tcp:host=127.0.0.1tcp:host=localhost 的地址。其他用途是不安全的。有关这些传输已被使用的情况以及这些传输的替代方案,请参阅名为“TCP套接字”的部分

在启动时,服务器生成一个随机的16字节nonce,并将其写入用户临时目录中的文件。nonce文件位置作为服务器的D-Bus地址的一部分发布,使用“noncefile”键值对。在接受连接后,服务器从套接字中读取16字节。如果读取的字节与nonce文件中存储的nonce不匹配,服务器必须立即断开连接。如果nonce与接收到的字节序列匹配,则接受客户端,并且传输行为类似于普通的tcp传输。

成功连接到服务器套接字后,客户端必须从服务器通过noncefile=键值对发布的文件中读取 nonce,并通过套接字发送它。之后,传输的行为就像普通的 tcp 传输。

所有的 nonce-tcp 地址都是可监听的。在指定了 hostportnoncefile 的 nonce-tcp 地址中,且 port 不为零时,也是可连接的。

Nonce TCP/IP套接字地址使用“nonce-tcp:”前缀,并支持以下键/值对:

在 Windows 上不可用的已执行子进程。

unixexec 地址是可连接的,但不可监听。

执行的子进程地址由“unixexec:”前缀标识,并支持以下键/值对:

元传输是一种具有特殊增强或行为的传输方式。目前可用的元传输包括:autolaunch

autolaunch 传输提供了一种方式,让 dbus 客户端自动检测正在运行的 dbus 会话总线,并在不存在时自动启动会话总线。

在Unix系统上,autolaunch地址是可连接的,但不可监听。

在 Windows 上,autolaunch 地址既可连接又可监听。

Autolaunch 地址使用 'autolaunch:' 前缀,并支持以下键/值对:

在启动时,服务器打开特定于平台的传输,创建一个互斥体和一个包含相关会话总线地址的共享内存区段。这个互斥体将被 dbus 客户端库检查,以侦测正在运行的 dbus 会话总线。对互斥体和共享内存区段的访问受到全局锁的保护。

在最近的实现中,autolaunch 传输使用本地主机上的 tcp 传输,端口由操作系统选择。这些细节可能会在未来发生变化。

免责声明:最近的实施处于早期阶段,可能无法在所有情况下正常工作和/或可能存在安全问题。因此,该实施尚未有文档记录。

一个工作的 D-Bus 实现在两个地方使用通用唯一标识符。首先,每个服务器地址都有一个 UUID 用于标识该地址,如 “服务器地址” 部分所述。其次,每个运行 D-Bus 客户端或服务器的操作系统内核实例都有一个 UUID 用于标识该内核,通过调用方法 org.freedesktop.DBus.Peer.GetMachineId() 检索到(参见 org.freedesktop.DBus.Peer 部分)。

本文档中的术语“UUID”是字面上的,即通用唯一标识符。它并非指的是RFC4122,并且实际上,D-Bus UUID与该RFC不兼容。

UUID必须包含128位数据并进行十六进制编码。十六进制编码的字符串不能包含连字符或其他非十六进制数字字符,必须恰好为32个字符长。为生成UUID,当前参考实现将96位随机数据与自UNIX纪元以来的32位时间(按大端字节顺序)连接起来。

如果随机数生成器质量很高,只需生成128位的随机数据可能更好。如果随机比特不够随机,时间戳可能有所帮助。使用高质量的随机数生成器,即使只有96位,碰撞的可能性极低,因此这在某种程度上是学术性的。

实现应该在 UUID 的前 96 位使用随机数据。

查看本文档中的“符号说明”部分以获取本部分使用的符号说明的详细信息。有一些标准接口可能在各种 D-Bus 应用程序中很有用。

org.freedesktop.DBus.Peer

接口org.freedesktop.DBus.Peer有两个方法:

org.freedesktop.DBus.Peer.Ping () org.freedesktop.DBus.Peer.GetMachineId (out STRING machine_uuid)

收到 METHOD_CALL 消息 org.freedesktop.DBus.Peer.Ping 后,应用程序除了像往常一样回复 METHOD_RETURN 外不应做任何其他操作。发送 ping 不管发送到哪个对象路径都没关系。参考实现会自动处理这个方法。

收到 METHOD_CALL 消息 org.freedesktop.DBus.Peer.GetMachineId 后,应用程序应该回复一个包含表示进程正在运行的机器身份的十六进制编码 UUID 的 METHOD_RETURN。这个 UUID 在单个系统上的所有进程中必须相同,至少在系统下次重新启动之前是相同的。如果可能的话,它应该在重新启动后保持不变,但这并不总是可能实现的,也不是保证的。不管 GetMachineId 发送到哪个对象路径都没有关系。参考实现会自动处理这个方法。

在Unix系统上,实现应尝试从 /var/lib/dbus/machine-id/etc/machine-id 读取机器ID。后者由 systemd 定义,但不使用systemd的系统可能提供等效的文件。如果两者都存在,则预期它们具有相同的内容,如果它们不同,规范不定义哪个优先级更高(参考实现更喜欢 /var/lib/dbus/machine-id,但 sd-bus 不是这样)。

在 Windows 上,硬件配置文件 GUID 被用作机器 ID,去除了标点符号。可以使用 GetCurrentHwProfile 函数获取。

UUID 旨在表示操作系统的每个实例,因此可能代表在虚拟机上运行的虚拟机,而不是物理机。基本上,如果两个进程看到相同的 UUID,则它们还应该看到相同的共享内存、UNIX 域套接字、进程 ID 和其他需要进程之间共享运行 OS 内核的特性。

UUID通常用于其他程序可能使用主机名的地方。主机名可以在无需重新启动的情况下更改,或者只是"localhost" - 因此UUID更加稳健。

“UUIDs” 部分解释了 UUID 的格式。

org.freedesktop.DBus.Introspectable

这个接口有一个方法:

org.freedesktop.DBus.Introspectable.Introspect (out STRING xml_data)

对象实例可能实现 Introspect,该方法返回对象的 XML 描述,包括其接口(带有信号和方法)、对象路径树中位于其下方的对象以及其属性。

“Introspection Data Format” 部分描述了这个 XML 字符串的格式。

org.freedesktop.DBus.Properties

许多本地API将具有对象属性或属性的概念。这些可以通过org.freedesktop.DBus.Properties接口公开。

可用属性及其可写性可通过调用org.freedesktop.DBus.Introspectable.Introspect来确定,参见名为“org.freedesktop.DBus.Introspectable”的部分

接口名称可以提供空字符串;在这种情况下,如果对象上有多个具有相同名称的属性,则结果是未定义的(根据任意确定性规则选择一个,或返回错误,是合理的可能性)。

如果调用 org.freedesktop.DBus.Properties.GetAll 时使用有效的接口名称,但该接口不包含任何属性,则应返回一个空数组。如果使用对调用者不可访问的某些属性的有效接口名称进行调用(例如,由服务中实现的每个属性访问控制导致的情况),则应该在结果数组中将这些属性静默地省略。如果为任何此类属性调用 org.freedesktop.DBus.Properties.Get,则应返回适当的访问控制错误。

如果对象上的一个或多个属性发生更改,则可能会发出org.freedesktop.DBus.Properties.PropertiesChanged信号(此信号在0.14版中添加):

org.freedesktop.DBus.Properties.PropertiesChanged (STRING interface_name, ARRAY of DICT_ENTRY<STRING,VARIANT> changed_properties, ARRAY<STRING> invalidated_properties);

其中 changed_properties 是包含更改属性及其新值的字典,invalidated_properties 是一个属性数组,表示属性已更改,但值未传达。

是否支持PropertiesChanged信号可以通过调用org.freedesktop.DBus.Introspectable.Introspect来确定。请注意,该信号可能会针对一个对象提供支持,但可能会因性能或安全原因而在每个属性上的使用方式有所不同。每个属性(或父接口)必须使用org.freedesktop.DBus.Property.EmitsChangedSignal注解来传达这一点(通常默认值true足以表示不需要使用该注解)。有关此注解的详细信息,请参见“Introspection Data Format”部分

org.freedesktop.DBus.ObjectManager

API 可选择利用此接口来处理一个或多个对象子树。每个子树的根实现此接口,以便其他应用程序可以通过单个方法调用获取所有对象、接口和属性。如果预计对象树的用户对树中所有对象的所有接口感兴趣,则适合使用此接口;如果预计对象的用户对对象的一个小子集、它们的一个小子集或两者都感兴趣,则应使用更细粒度的 API。

应用程序可以使用的获取所有对象和属性的方法是 GetManagedObjects:

org.freedesktop.DBus.ObjectManager.GetManagedObjects (out ARRAY of DICT_ENTRY<OBJPATH,ARRAY of DICT_ENTRY<STRING,ARRAY of DICT_ENTRY<STRING,VARIANT>>> objpath_interfaces_and_properties);

该方法的返回值是一个字典,其键是对象路径。所有返回的对象路径都是实现此接口的对象路径的子级,即它们的对象路径以ObjectManager的对象路径加上'/'开头。

每个值都是一个字典,其键是接口名称。内部字典中的每个值都是与对象路径和接口组合对应的 org.freedesktop.DBus.Properties.GetAll() 方法返回的相同字典。如果一个接口没有属性,则返回空字典。

使用以下两个信号发出更改:

org.freedesktop.DBus.ObjectManager.InterfacesAdded (OBJPATH object_path, ARRAY of DICT_ENTRY<STRING,ARRAY of DICT_ENTRY<STRING,VARIANT>> interfaces_and_properties); org.freedesktop.DBus.ObjectManager.InterfacesRemoved (OBJPATH object_path, ARRAY<STRING> interfaces);

当添加新对象或现有对象获得一个或多个接口时,将发出InterfacesAdded信号。每当对象被移除或失去一个或多个接口时,将发出InterfacesRemoved信号。InterfacesAdded信号的第二个参数包含一个字典,其中包含已添加到给定对象路径的接口和属性(如果有)。类似地,InterfacesRemoved信号的第二个参数包含一个已移除的接口数组。请注意,不会使用此接口报告现有接口上的属性更改-应用程序还应监视每个对象上现有的PropertiesChanged信号。

应用程序不应导出实现此接口的对象的子对象(直接或间接),但这些子对象未在给定对象的GetManagedObjects()方法的回复中返回。

ObjectManager 接口的目的是使编写强大的客户端实现变得简单。简单的客户端实现只需要进行两个方法调用:

org.freedesktop.DBus.AddMatch (bus_proxy, "type='signal',sender='org.example.App2',path_namespace='/org/example/App2'"); objects = org.freedesktop.DBus.ObjectManager.GetManagedObjects (app_proxy);

在消息总线和远程应用程序的 ObjectManager 中,分别。每当创建新的远程对象(或现有对象获得新接口)时,将发出 InterfacesAdded 信号,由于此信号包含接口的所有属性,因此不需要在远程对象上调用 org.freedesktop.Properties 接口。此外,由于初始的 AddMatch() 规则已经包括来自新创建子对象的信号消息,因此不需要进行新的 AddMatch() 调用。

D-Bus 规范的版本 0.17 中添加了 org.freedesktop.DBus.ObjectManager 接口。

自省数据格式

所述“org.freedesktop.DBus.Introspectable”部分,对象可以在运行时进行内省,返回描述对象的XML字符串。相同的XML格式也可以在其他上下文中使用,例如作为生成静态语言绑定的“IDL”.

这里是内省数据的示例:

    <!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
     "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
    <node name="/com/example/sample_object0">
      <interface name="com.example.SampleInterface0">
        <method name="Frobate">
          <arg name="foo" type="i" direction="in"/>
          <arg name="bar" type="s" direction="out"/>
          <arg name="baz" type="a{us}" direction="out"/>
          <annotation name="org.freedesktop.DBus.Deprecated" value="true"/>
        </method>
        <method name="Bazify">
          <arg name="bar" type="(iiu)" direction="in"/>
          <arg name="bar" type="v" direction="out"/>
        </method>
        <method name="Mogrify">
          <arg name="bar" type="(iiav)" direction="in"/>
        </method>
        <signal name="Changed">
          <arg name="new_value" type="b"/>
        </signal>
        <property name="Bar" type="y" access="readwrite"/>
      </interface>
      <node name="child_of_sample_object"/>
      <node name="another_child_of_sample_object"/>
   </node>

需要编写更正式的DTD和规范,但这里有一些快速的笔记。

  • 只有根<node>元素可以省略节点名称,因为它被认为是被内省的对象。如果根<node>有名称属性,则必须是绝对对象路径。如果子<node>具有对象路径,则它们必须是相对的。
  • 如果一个子<node>有任何子元素,则它们必须代表子元素的完整内省。如果一个子<node>是空的,则它可能有或没有子元素;必须内省子元素以找出。意图是,如果一个对象知道它的子元素“快速”内省,它可以继续返回它们的信息,否则可以省略。
  • <arg> 上的 direction 元素可以省略,此时对于方法调用默认为“in”,对于信号默认为“out”。信号只允许“out”,因此虽然可以指定 direction,但毫无意义。
  • 可能的方向是“in”和“out”,与CORBA不同,没有“inout”
  • 可能的属性访问标志是“readwrite”、“read”和“write”
  • 一个 <node> 当然可以列出多个接口。
  • 参数上的“name”属性是可选的。

方法、接口、属性、信号和参数元素可能具有“注释”,这些注释是元数据的通用键/值对。在概念上类似于Java的注释和C#的属性。知名的注释:

消息总线规范

消息总线接受来自一个或多个应用程序的连接。一旦连接,应用程序可以与其他连接到总线的应用程序交换消息。

为了在连接之间路由消息,消息总线保持从名称到连接的映射。每个连接都有一个在总线寿命内唯一自动分配的名称。应用程序可以为连接请求额外的名称。额外的名称通常是“众所周知的名称”,如“com.example.TextEditor1”。当一个名称绑定到一个连接时,该连接被称为 拥有 该名称。

公交车本身拥有一个特殊的名称,org.freedesktop.DBus,其中一个位于 /org/freedesktop/DBus 的对象实现了 org.freedesktop.DBus 接口。该服务允许应用程序对公交车本身发出管理请求。例如,应用程序可以要求公交车为连接分配一个名称。

每个名称可能有_排队所有者_。当应用程序请求连接的名称已被使用时,总线将可选择将连接添加到等待名称的队列中。如果名称的当前所有者断开连接或释放名称,则队列中的下一个连接将成为新的所有者。

此功能会导致正确的事情发生,例如,如果您启动两个文本编辑器; 第一个可能会请求“com.example.TextEditor1”,而第二个将排队作为该名称的可能所有者。当第一个退出时,第二个将接管。

应用程序可以向特定接收者或消息总线本身发送单播消息,也可以向所有感兴趣的接收者广播消息。有关详细信息,请参阅名为“消息总线消息路由”的部分

每个连接至少有一个名称,在连接时分配,并在对org.freedesktop.DBus.Hello方法调用的响应中返回。这个自动分配的名称称为连接的_唯一名称_。唯一名称永远不会被重用于对同一总线的两个不同连接。

拥有唯一名称是与消息总线进行交互的先决条件。逻辑上讲,唯一名称始终是应用程序拥有的第一个名称,也是失去所有权的最后一个名称。

唯一连接名称必须以字符':'(ASCII冒号字符)开头;不是唯一名称的总线名称不得以此字符开头。(总线必须拒绝任何应用程序尝试手动请求以':'开头的名称。)此限制绝对防止“欺骗”;发送到唯一名称的消息将始终发送到预期的连接。

当连接关闭时,它拥有的所有名称都将被删除(或者如果有的话,转移到队列中的下一个连接)。

连接可以使用 org.freedesktop.DBus.RequestName 消息请求将其他名称与其关联。名为“总线名称”的部分描述了有效名称的格式。这些名称可以使用 org.freedesktop.DBus.ReleaseName 消息再次释放。

消息总线消息路由

消息可能具有DESTINATION字段(请参见所谓“标题字段”部分),导致_单播消息_。如果存在DESTINATION字段,则它通过名称指定消息接收者。方法调用和回复通常会指定此字段。消息总线必须发送带有DESTINATION字段设置为指定接收者的消息(任何类型),无论接收者是否设置了匹配规则以匹配消息。

当消息总线接收到信号时,如果DESTINATION字段不存在,则被视为_广播信号_,并发送给所有与消息匹配规则匹配的应用程序。大多数信号消息都是广播消息,目前在本规范中未定义其他任何可广播的消息类型。

单播信号消息(带有 DESTINATION 字段的消息)并不常用,但它们被视为任何单播消息:它们将被传递给指定的接收者,而不考虑其匹配规则。单播信号的一个用途是避免竞争条件,即在预期接收者调用 名为“org.freedesktop.DBus.AddMatch”的部分 以接收该信号之前发出信号:如果信号直接通过单播消息发送给该接收者,则根本不需要添加匹配规则,也不会出现竞争条件。对于那些安全策略阻止窃听的消息总线,单播信号的另一个用途是发送只应对一个接收者可见的敏感信息。

当消息总线接收到方法调用时,如果DESTINATION字段不存在,则将该调用视为标准的一对一消息,并由消息总线本身进行解释。例如,发送一个不带DESTINATIONorg.freedesktop.DBus.Peer.Ping消息将导致消息总线本身立即回复 ping;消息总线不会将此消息显示给其他应用程序。

继续 org.freedesktop.DBus.Peer.Ping 示例,如果 ping 消息以 DESTINATION 名称为 com.yoyodyne.Screensaver 发送,那么该 ping 将被转发,预计 Yoyodyne Corporation 屏幕保护程序将回复该 ping。

消息总线实现可能会施加安全策略,阻止发送或接收某些消息。当由于安全策略而无法发送或接收方法调用消息时,消息总线应发送错误回复,除非原始消息带有 NO_REPLY 标志。

接收到一个目的地指示不同接收者的单播消息被称为窃听。在充当安全边界(如标准系统总线)的消息总线上,安全策略通常应该防止窃听,因为单播消息通常是私密的,可能包含安全敏感信息。

窃听与具有非平凡访问控制限制的总线交互效果不佳,已被弃用。BecomeMonitor方法(请参阅名为“org.freedesktop.DBus.Monitoring.BecomeMonitor”的部分)提供了一种更可取的监视总线的方式。

窃听主要用于调试工具,例如 D-Bus 参考实现中的 dbus-monitor 工具。窃听消息总线的工具应小心避免对发送给不同客户端的消息做出回复或错误响应。

客户端可能会尝试通过添加匹配规则(请参阅名为“匹配规则”的部分)来窃听,其中包含 eavesdrop='true' 匹配。为了与旧的消息总线实现兼容,如果添加这样的匹配规则导致错误回复,客户端可以退而添加相同的规则,但省略 eavesdrop 匹配。

消息总线路由协议的一个重要部分是匹配规则。匹配规则描述了应该发送给客户端的消息,基于消息的内容。广播信号仅发送给具有合适匹配规则的客户端:这样可以避免唤醒客户端进程处理与该客户端无关的信号。

将客户列为其DESTINATION的消息无需匹配客户的匹配规则,而是无论如何都会发送给该客户。因此,匹配规则主要用于接收广播信号的子集。

匹配规则也可以用于窃听(参见所谓“窃听”一节),如果消息总线的安全策略允许,但这种用法已被弃用,推荐使用BecomeMonitor方法(参见所谓“org.freedesktop.DBus.Monitoring.BecomeMonitor”一节)。

匹配规则是使用 AddMatch 总线方法添加的(请参阅名为“org.freedesktop.DBus.AddMatch”的部分)。规则被指定为逗号分隔的键/值对字符串。从规则中排除键表示通配符匹配。例如,从匹配规则中排除成员但添加发送者将允许该发送者的所有消息通过。完整规则的示例将是"type='signal',sender='org.freedesktop.DBus',interface='org.freedesktop.DBus',member='Foo',path='/bar/foo',destination=':452345.34',arg2='bar'"

以下表格描述了可用于创建匹配规则的键。

消息总线启动服务(激活)

消息总线可以代表其他应用程序启动应用程序。这被称为服务激活或激活。可以以这种方式启动的应用程序称为服务或可激活服务。

启动服务 应被视为与服务激活同义。

在 D-Bus 中,服务激活通常是通过 自动启动 完成的。在自动启动中,应用程序向特定的众所周知的名称发送消息,例如 com.example.TextEditor1,而不在消息头中指定 NO_AUTO_START 标志。如果总线上没有任何应用程序拥有请求的名称,但总线守护程序确实知道如何为该名称启动一个可激活服务,那么总线守护程序将启动该服务,等待其请求该名称,并将消息传递给它。

应用程序也可以发送显式请求来启动服务:这是一种与自动启动不同的激活形式。有关详细信息,请参阅名为“org.freedesktop.DBus.StartServiceByName”的部分

在任何情况下,这意味着一个合同与名称com.example.TextEditor1一起记录,该名称的所有者将提供什么对象,以及这些对象将具有哪些接口。

为了找到与特定名称对应的可执行文件,总线守护程序会查找 服务描述文件。服务描述文件定义了名称到可执行文件的映射。不同类型的消息总线会在不同位置查找这些文件,参见所谓“众所周知的消息总线实例”部分

服务描述文件的文件扩展名为'.service'。消息总线只会加载以'.service'结尾的服务描述文件;所有其他文件将被忽略。文件格式类似于桌面条目。所有服务描述文件必须使用UTF-8编码。为确保不会发生名称冲突,服务文件必须使用与消息和服务名称相同的命名空间机制。

在众所周知的系统总线上,服务描述文件的名称必须是其众所周知的名称加上.service,例如com.example.ConfigurationDatabase1.service

在众所周知的会话总线上,服务应该遵循与系统总线上相同的服务描述文件命名约定,但为了向后兼容,它们不是必须这样做。

\[应该更好地指定文件格式,而不是"类似于.desktop条目",尤其是因为.desktop条目已经规范不清楚。;-)\] 这些规范中的部分同样适用于服务文件:

  • 通用语法
  • 评论格式

服务描述文件必须包含一个D-BUS Service组,至少包含Name(服务的众所周知名称)和Exec(要执行的命令)两个键。

图 9. 示例服务描述文件

        # 示例服务描述文件
        \[D-BUS 服务\]
        Name=com.example.ConfigurationDatabase1
        Exec=/usr/bin/sample-configd

此外,在 Unix 上的众所周知的系统总线的服务描述文件必须包含一个 User 键,其值是用户帐户的名称(例如 root)。系统服务将以该用户身份运行。

当应用程序请求按名称启动服务时,总线守护程序会尝试查找将拥有该名称的服务。然后尝试生成与其关联的可执行文件。如果失败,它将报告错误。

在众所周知的系统总线上,同一目录中的两个 .service 文件不可能提供相同的服务,因为它们被限制为具有与服务名称匹配的名称。

在众所周知的会话总线上,如果同一目录中的两个 .service 文件提供相同的服务名称,则结果是未定义的。分发者应避免这种情况,例如通过根据其服务名称命名会话服务的 .service 文件。

如果不同目录中的两个 .service 文件提供相同的服务名称,则使用优先级更高的目录中的文件:例如,在系统总线上,/usr/local/share/dbus-1/system-services 中的 .service 文件优先于 /usr/share/dbus-1/system-services 中的文件。

可执行文件启动时将设置环境变量DBUS_STARTER_ADDRESS为消息总线的地址,以便连接并请求适当的名称。

启动的可执行文件可能想要知道启动它的消息总线是否是众所周知的消息总线之一(请参见名为“众所周知的消息总线实例”的部分)。为了方便起见,如果消息总线是众所周知的消息总线之一,总线还必须设置DBUS_STARTER_BUS_TYPE环境变量。目前为该变量定义的值是system表示系统范围的消息总线,session表示每个登录会话的消息总线。新的可执行文件仍然必须连接到DBUS_STARTER_ADDRESS中给定的地址,但可以假定所得到的连接是到众所周知的消息总线。

[应该在某处设置超时时间,可以在 .service 文件中指定,由客户端指定,或者只是一个全局值,如果被激活的客户端在超时时间内未能连接,应发送错误信息回去。 ext]

消息总线服务范围

服务的“范围”是指其“每个-”,例如每个会话、每台机器、每个主目录或每个显示器。参考实现尚不支持在与消息总线本身不同的范围内启动服务。例如,如果您在会话总线上启动服务,则其范围是每个会话。

我们可以为总线名称添加一个可选的范围。例如,对于每个(显示,会话对),我们可以在登录时自动生成每个显示器的唯一ID,并通过执行特殊的“设置显示ID”二进制文件将其设置在屏幕0上。该ID将存储在 _DBUS_DISPLAY_ID 属性中,并将是一串随机字节的字符串。然后将使用此ID来限定名称。启动/定位服务可以通过ID-名称对而不仅仅是通过名称来完成。

将其与每个显示器范围进行对比。为了实现这一点,我们希望一个单一的总线跨越所有使用特定显示器的会话。因此,我们可能会在显示器的屏幕0上设置一个 _DBUS_DISPLAY_BUS_ADDRESS 属性,指向这个总线。

服务描述文件可能包含一个SystemdService键。其值是systemd服务的名称,例如dbus-com.example.MyDaemon.service

如果存在此键,总线守护程序可以通过向 systemd 发送请求来激活此 D-Bus 服务,要求 systemd 启动名称为 SystemdService 值的 systemd 服务。例如,参考 dbus-daemon 具有 --systemd-activation 选项,启动时由 systemd 启用此功能并提供该选项。

在众所周知的系统总线上,通常的做法是将SystemdService设置为dbus-,后跟众所周知的总线名称,再跟上.service,然后将该名称注册为真实systemd服务的别名。这样可以独立于系统是否在启动期间由systemd启动服务,启用或禁用服务的D-Bus激活。

使用 AppArmor 进行激活中介

请参考AppArmor文档以获取有关AppArmor的一般信息,以及在与支持此功能的内核和dbus-daemon一起使用时如何调解D-Bus消息。

在最近版本的参考dbus-daemon中,dbus send 类型的 AppArmor 策略规则也用于控制自动启动:如果向可激活服务的众所周知名称发送消息,则 dbus-daemon 将尝试确定在自动启动之前是否将消息传递给该服务,通过对结果进程的凭据做出一些假设。

如果它继续自动启动,当服务出现时,dbus-daemon 会重复策略检查(使用服务的真实凭据,可能不完全相同)然后再传递消息。在实践中,这第二次检查通常会比第一次更严格;第一次检查只有在存在类似 deny dbus send peer=(label=/usr/bin/protected) 这样匹配对等方特定凭据的“黑名单”规则时才会更严格,但 AppArmor 通常以“白名单”方式使用,因此不适用。

为支持此过程,服务描述文件可能包含一个AssumedAppArmorLabel键。其值是一个AppArmor标签的名称,例如/usr/sbin/mydaemon。如果存在,AppArmor对自动启动服务的消息进行调解,决定是否允许自动启动发生,基于这样的假设:激活的服务将受限于指定的标签;特别是,形式为dbus send peer=(label=/usr/sbin/mydaemon)deny dbus send peer=(label=/usr/sbin/mydaemon)的规则将匹配它,根据需要允许或拒绝(即使实际上没有加载该名称的配置文件)。

否则,AppArmor 对自动启动服务的消息进行调解,决定是否允许自动启动而无需指定任何特定标签。特别是,任何形式为 dbus send peer=(label=X)deny dbus send peer=(label=X) 的规则(对于任何 X 的值,包括特殊标签 unconfined)都不会影响自动启动是否被允许。

在决定是否允许自动启动时,类型为dbus receive的规则不会被检查;只有在服务启动后,决定是否传递导致自动启动操作的消息时,它们才会被检查服务的配置文件。

通过名为“org.freedesktop.DBus.StartServiceByName”的部分进行的显式激活目前不受此调解的影响:如果要防止受限进程启动任意服务,则不应允许调用该方法。

知名消息总线实例

这里定义了两个标准消息总线实例,以及如何定位它们以及它们的服务文件存放位置。

登录会话消息总线

每次用户登录时,可能会启动一个登录会话消息总线。用户登录会话中的所有应用程序可以使用此消息总线相互交互。

登录会话消息总线的地址存储在 DBUS_SESSION_BUS_ADDRESS 环境变量中。如果该变量未设置,应用程序也可以尝试从 X 窗口系统根窗口属性 _DBUS_SESSION_BUS_ADDRESS 中读取地址。根窗口属性必须是 STRING 类型。环境变量应优先于根窗口属性。

登录会话消息总线的地址存储在DBUS_SESSION_BUS_ADDRESS环境变量中。如果未设置DBUS_SESSION_BUS_ADDRESS,或者设置为字符串"autolaunch:",系统应使用特定于平台的方法来定位正在运行的D-Bus会话服务器,或者在找不到正在运行的实例时启动一个。请注意,不建议使用此机制来尝试确定守护程序是否正在运行。由于总线守护程序可能在确定之前或之后启动,因此尝试进行此确定是存在竞争条件的。因此,建议应用程序不要尝试为其功能目的进行此确定,而是应尝试启动服务器。

对于 X 窗口系统,应用程序必须定位由连接而成的原子表示的选择的窗口所有者:

  • 字符串 "_DBUS_SESSION_BUS_SELECTION_"
  • 当前用户的用户名
  • 字符 '_'(下划线)
  • 机器的ID

拥有此 X 选择的窗口定义了以下属性:

在这个窗口中,至少必须存在 \_DBUS_SESSION_BUS_ADDRESS 属性。

如果无法找到 X 选择或者从窗口读取属性失败,实现必须得出没有运行 D-Bus 服务器的结论,并继续启动一个新服务器。(请参阅下面的并发问题)

无法连接到获取的 D-Bus 服务器地址必须被视为致命的连接错误,并应报告给应用程序。

作为一种替代方案,实现可以在当前用户的主目录中的子目录.dbus/session-bus/中找到以下文件中的信息:

  • 机器的ID
  • '-'(破折号)
  • 删除屏幕号的 X 显示,如果存在以下前缀,则移除:":", "localhost:" ,"localhost.localdomain:"。也就是说,"localhost:10.0" 的显示将仅产生数字 "10"

此文件的内容包括 NAME=value 赋值对和以 # 开头的行是注释(否则不允许有注释)。以下变量名已定义:

在这个文件中,至少必须存在 DBUS_SESSION_BUS_ADDRESS 变量。

打开此文件失败必须被解释为服务器未运行。因此,如果无法打开文件,实现必须继续尝试启动新的总线服务器。

然而,成功打开此文件不得导致认为服务器正在运行。因此,通过备用方法获取的总线地址连接失败不得被视为致命错误。如果无法建立连接,实现必须继续检查 X 选择设置或自行启动服务器。

如果实现得出结论,D-Bus 服务器未在运行,则必须尝试启动新服务器,并且还必须确保作为“autolaunch”机制的结果启动的守护进程提供上述查找机制,以便后续调用可以找到新启动的服务器。实现还必须确保如果发生两个或更多并发初始化,只有一个服务器保持运行,所有其他初始化能够获取此服务器的地址并连接到它。换句话说,实现必须确保在尝试设置时不存在 X 选择,而不允许另一个进程在验证和设置之间设置选择(例如,通过使用 XGrabServer / XungrabServer)。

在Unix系统上,会话总线应该按照XDG基本目录规范中定义的方式,在$XDG_DATA_DIRS/dbus-1/services中搜索.service文件。实现也可以在XDG目录之外搜索其他位置,优先级可能高于或低于XDG目录。

如 XDG 基本目录规范所述,软件包应将其会话 .service 文件安装到其配置的 ${datadir}/dbus-1/services,其中 ${datadir} 的定义遵循 GNU 编码标准。系统管理员或用户可以通过设置 XDG_DATA_DIRS 或通过符号链接到默认位置来安排这些服务文件被读取。

计算机可能有一个系统消息总线,所有系统上的应用程序都可以访问。这个消息总线可以用来广播系统事件,比如添加新的硬件设备、打印队列的更改等。

系统消息总线的地址存储在DBUS_SYSTEM_BUS_ADDRESS环境变量中。如果该变量未设置,应用程序应尝试连接到众所周知的地址unix:path=/var/run/dbus/system_bus_socket。众所周知的系统总线实现应监听一个地址,以确保连接成功。

在已知/var/run//run/是同义的系统上(例如大多数Linux操作系统发行版),实现可能更倾向于利用这一知识连接或监听unix:path=/run/dbus/system_bus_socket,这在早期启动和晚期关闭过程中具有一些较小的技术优势。

在实践中,D-Bus的实现通常具有用于系统总线地址的构建时配置选项,其默认值通常取决于其他构建时选项,比如安装前缀(特别是对于 D-Bus 的参考实现 dbus 而言)。打算提供对众所周知的系统总线访问权限的发行版应该验证他们是否在使用可互操作的地址。

在Unix系统上,系统总线应默认搜索/usr/local/share/dbus-1/system-services/usr/share/dbus-1/system-services/lib/dbus-1/system-services中的.service文件,按照这种优先顺序。它也可以搜索其他特定于实现的位置,但不应根据环境变量改变这些位置。

软件包应将其系统 .service 文件安装到配置的 ${datadir}/dbus-1/system-services,其中 ${datadir} 的定义遵循 GNU 编码标准。系统管理员可以通过编辑系统总线的配置文件或将其符号链接到默认位置来安排这些服务文件被读取。

特殊消息总线名称 org.freedesktop.DBus 响应一些额外消息,位于对象路径 /org/freedesktop/DBus。当发出 名为“org.freedesktop.DBus.NameOwnerChanged 信号时,也使用该对象路径。

出于历史原因,org.freedesktop.DBus 接口中的一些方法可以在多个对象路径上使用。消息总线实现应接受在规范版本 0.26 之前添加的方法调用,无论在哪个对象路径上。消息总线实现不应接受在意外对象路径上的更新方法调用,并作为安全强化措施,对于安全敏感的旧方法调用可能会在意外对象路径上调用时拒绝,并显示错误 org.freedesktop.DBus.Error.AccessDenied。客户端软件应将所有方法调用发送到 /org/freedesktop/DBus,而不是依赖于此。

除了下面列出的方法调用之外,消息总线还应该实现标准的Introspectable、Properties和Peer接口(参见标准接口部分)。对Properties和Peer接口的支持是在消息总线参考实现的1.11.x版本中添加的。

org.freedesktop.DBus.Hello

作为一种方法:

STRING 你好 ()

回复参数:

在应用程序能够向其他应用程序发送消息之前,它必须向消息总线发送 org.freedesktop.DBus.Hello 消息以获取唯一名称。如果一个没有唯一名称的应用程序尝试向另一个应用程序发送消息,或者向消息总线本身发送不是 org.freedesktop.DBus.Hello 消息的消息,它将被从总线断开连接。

如果客户端希望与总线断开连接,它只需关闭套接字(或其他通信通道),而无需发送相应的“断开连接”请求。

org.freedesktop.DBus.RequestName

作为一种方法:

        请求名称 (输入 字符串 名称, 输入 32位无符号整数 标志)

消息参数:

回复参数:

请求消息总线将给定的名称分配给方法调用者。每个名称维护一个可能所有者的队列,队列的头部是名称的主要或当前所有者。队列中的每个潜在所有者都保留其最新RequestName调用中的DBUS_NAME_FLAG_ALLOW_REPLACEMENT和DBUS_NAME_FLAG_DO_NOT_QUEUE设置。调用RequestName时会发生以下情况:

  • 如果方法调用者当前是名称的主要所有者,则使用新的 RequestName 调用的值更新 DBUS_NAME_FLAG_ALLOW_REPLACEMENT 和 DBUS_NAME_FLAG_DO_NOT_QUEUE 值,然后不会发生其他任何事情。
  • 如果当前的主所有者(队列的首位)设置了 DBUS_NAME_FLAG_ALLOW_REPLACEMENT 标志,并且 RequestName 调用带有 DBUS_NAME_FLAG_REPLACE_EXISTING 标志,则 RequestName 的调用者将取代队列首位的当前主所有者,当前主所有者移动到队列的第二位置。如果 RequestName 的调用者之前在队列中,其标志将被新的 RequestName 的值更新,并将其移动到队列的首位。
  • 如果无法替换,并且方法调用者当前在队列中但不是主要所有者,则其标志将使用新的 RequestName 调用的值进行更新。
  • 如果无法替换,并且方法调用者当前不在队列中,则将方法调用者附加到队列中。
  • 如果队列中的任何连接设置了 DBUS_NAME_FLAG_DO_NOT_QUEUE 并且不是主要所有者,则将其从队列中移除。这可能适用于先前的主要所有者(如果被替换)或方法调用者(如果在仍然卡在队列中时更新了 DBUS_NAME_FLAG_DO_NOT_QUEUE 标志,或者刚刚被添加到队列并设置了该标志)。

请注意,DBUS_NAME_FLAG_REPLACE_EXISTING 会导致“插队”,即使队列中已经有另一个应用程序指定了 DBUS_NAME_FLAG_REPLACE_EXISTING。如果一个不允许替换的主要所有者离开,而下一个主要所有者允许替换,就会出现这种情况。在这种情况下,指定了 DBUS_NAME_FLAG_REPLACE_EXISTING 的排队项目 不会 自动替换新的主要所有者。换句话说,DBUS_NAME_FLAG_REPLACE_EXISTING 不会被保存,它只在调用 RequestName 时使用。这是有意为之,以避免在两个应用程序都是 DBUS_NAME_FLAG_ALLOW_REPLACEMENT 和 DBUS_NAME_FLAG_REPLACE_EXISTING 时陷入无限循环。

flags 参数包含以下任意逻辑 OR 连接在一起的值:

返回代码可以是以下值之一:

org.freedesktop.DBus.ReleaseName

作为一种方法:

        UINT32 ReleaseName (in STRING name)

消息参数:

回复参数:

请求消息总线释放方法调用者对给定名称的所有权声明。如果调用者是主要所有者,则如果有其他所有者在等待,则将从队列中选择新的主要所有者。如果调用者正在等待名称的队列中,则将其从队列中移除,并且如果稍后名称可用,则不会使其成为名称的所有者。如果名称的队列中没有其他所有者,则将完全从总线中移除该名称。返回代码可以是以下值之一:

org.freedesktop.DBus.ListQueuedOwners

作为一种方法:

在 STRING 名称中,STRING ListQueuedOwners 的数组

消息参数:

回复参数:

列出当前排队等待总线名称的连接(请参阅Queued Name Owner)。

org.freedesktop.DBus.ListNames

作为一种方法:

ARRAY of STRING ListNames ()

回复参数:

返回总线上当前拥有的所有名称的列表。

org.freedesktop.DBus.ListActivatableNames

作为一种方法:

返回字符串数组 ListActivatableNames ()

回复参数:

返回可以在总线上激活的所有名称列表。

org.freedesktop.DBus.NameHasOwner

作为一种方法:

BOOLEAN NameHasOwner (in STRING name)

消息参数:

回复参数:

检查指定名称是否存在(当前是否有所有者)。

org.freedesktop.DBus.NameOwnerChanged

这是一个信号:

NameOwnerChanged (STRING name, STRING old_owner, STRING new_owner)

消息参数:

这个信号表示名称所有者已更改。这也是用于检测总线上新名称出现的信号。

org.freedesktop.DBus.NameLost

这是一个信号:

NameLost (STRING name)

消息参数:

当特定应用程序失去对名称的所有权时,将发送此信号。

org.freedesktop.DBus.NameAcquired

这是一个信号:

NameAcquired (STRING name)

消息参数:

当特定应用程序获得名称所有权时,将发送此信号。

org.freedesktop.DBus.ActivatableServicesChanged

这是一个信号:

ActivatableServicesChanged ()

当由ListActivatableNames()返回的可激活服务列表可能已更改时,将发送此信号(请参见名为“org.freedesktop.DBus.ListActivatableNames”的部分)。已缓存有关可激活服务的信息的客户端应再次调用ListActivatableNames()以更新其缓存。

此信号的存在由总线特性属性指示(有关详细信息,请参阅名为“org.freedesktop.DBus.Features”的部分)。在没有此特性的旧实现中,无法在可激活名称列表发生更改时获得通知。

org.freedesktop.DBus.StartServiceByName

作为一种方法:

        通过名称启动服务 (输入 字符串 名称, 输入 UINT32 标志)

消息参数:

回复参数:

尝试启动与名称关联的可执行文件(服务激活),作为显式请求。这是一种不依赖于自动启动的替代方法。有关服务如何激活以及自动启动和显式激活之间的区别的更多信息,请参阅“消息总线启动服务(激活)”部分

通常最好执行自动启动,而不是调用此方法。这是因为调用此方法存在时间检查/使用问题:如果调用者要求消息总线启动服务,以便同一调用者可以对该服务进行后续方法调用,那么消息总线能够启动所需服务并不意味着在调用者进行这些后续方法调用时它不会崩溃或以其他方式退出。因此,调用此方法并不能消除调用者处理方法调用错误的需要。鉴于这一事实,通常更简单的做法是依赖自动启动,其中所需服务作为第一次方法调用的副作用而启动。

返回值可以是以下值之一:

org.freedesktop.DBus.UpdateActivationEnvironment

作为一种方法:

在ARRAY of DICT_ENTRY<STRING,STRING>环境中更新激活环境(UpdateActivationEnvironment)

消息参数:

通常,会话总线激活的服务会继承总线守护程序的环境。当激活服务时,此方法会添加或修改该环境。

某些总线实例,比如标准系统总线,可能会禁止某些或所有调用者访问此方法。

请注意,环境变量的名称和值都必须是有效的 UTF-8 编码。无法使用无效的 UTF-8 数据更新激活环境。

org.freedesktop.DBus.GetNameOwner

作为一种方法:

获取名称所有者 (输入名称为字符串 name)

消息参数:

回复参数:

返回给定名称的主所有者的唯一连接名称。如果请求的名称没有所有者,则返回 org.freedesktop.DBus.Error.NameHasNoOwner 错误。

org.freedesktop.DBus.GetConnectionUnixUser

作为一种方法:

获取连接的Unix用户(在字符串bus_name中)

消息参数:

回复参数:

返回连接到服务器的进程的Unix用户ID。如果无法确定(例如,因为进程不在与总线守护程序相同的机器上),则返回错误。

org.freedesktop.DBus.GetConnectionUnixProcessID

作为一种方法:

获取连接的Unix进程ID(在字符串bus_name中)

消息参数:

回复参数:

返回与服务器连接的进程的 Unix 进程 ID。如果无法确定(例如,因为进程不在与总线守护程序相同的机器上),则返回错误。

org.freedesktop.DBus.GetConnectionCredentials

作为一种方法:

获取连接凭据的字典项数组<字符串,变体> GetConnectionCredentials(输入字符串 bus_name)

消息参数:

回复参数:

返回与服务器连接的进程的尽可能多的凭据。如果无法确定某些凭据(例如,因为进程不在与总线守护程序相同的机器上,或者因为总线守护程序的此版本不支持特定的安全框架),或者如果这些凭据的值无法按照此处记录的方式表示,则这些凭据将被省略。

返回的字典中不包含“.”的键由本规范定义。支持本文档未提及的凭据框架的总线守护程序实现者应该要么为本规范贡献补丁,要么使用包含“.”并以反向域名开头的键。

这种方法是在 D-Bus 1.7 中添加的,旨在减少列出进程凭据所需的往返次数。在旧版本中,调用此方法将失败:应用程序应通过使用单独的方法来恢复,例如 org.freedesktop.DBus.GetConnectionUnixUser” 部分

org.freedesktop.DBus.GetAdtAuditSessionData

作为一种方法:

获取 Adt 审计会话数据的字节数组 (输入参数为字符串 bus_name)

消息参数:

回复参数:

返回 Solaris ADT 使用的审计数据,以未指定的二进制格式。如果您知道这意味着什么,请通过 D-Bus bug 跟踪系统贡献文档。出于历史原因,此方法位于核心 DBus 接口上;将来应通过 org.freedesktop.DBus.GetConnectionCredentials” 部分 提供相同信息。

org.freedesktop.DBus.GetConnectionSELinuxSecurityContext

作为一种方法:

获取连接 SELinux 安全上下文的字节数组 (在字符串 bus_name 中)

消息参数:

回复参数:

返回 SELinux 使用的安全上下文,格式未指定。如果您知道这意味着什么,请通过 D-Bus bug 跟踪系统贡献文档。出于历史原因,此方法位于核心 DBus 接口上;将来应通过 org.freedesktop.DBus.GetConnectionCredentials” 部分 提供相同信息。

org.freedesktop.DBus.AddMatch

作为一种方法:

在 STRING 规则中添加匹配项

消息参数:

向消息总线添加匹配规则以匹配通过消息总线传递的消息(请参阅#消息总线路由匹配规则)。如果总线资源不足,则返回org.freedesktop.DBus.Error.OOM错误。

org.freedesktop.DBus.RemoveMatch

作为一种方法:

在 STRING 规则中删除匹配项

消息参数:

删除第一个匹配的规则(请参见匹配规则部分)。如果未找到规则,则返回org.freedesktop.DBus.Error.MatchRuleNotFound错误。

org.freedesktop.DBus.GetId

作为一种方法:

GetId (out STRING id)

回复参数:

获取总线的唯一ID。这里的唯一ID在总线守护进程监听的所有地址(TCP、UNIX域套接字等)之间共享,并且其格式在UUIDs一节中描述。总线监听的每个地址也有自己的唯一ID,如Server Addresses一节所述。总线和地址的ID是不相关的。还有一个每台机器的ID,在org.freedesktop.DBus.Peer一节中描述,并由org.freedesktop.DBus.Peer.GetMachineId()返回。对于桌面会话总线,总线ID可以用作唯一标识用户会话的方式。

org.freedesktop.DBus.Monitoring.BecomeMonitor

作为一种方法:

BecomeMonitor (在字符串数组规则中,在32位无符号整数标志中)

消息参数:

将连接转换为 监视连接,可用作调试/监控工具。只有在此总线上具有特权的用户(根据某些特定实现的定义)才能创建监视连接。

监视连接会丢失所有总线名称,包括唯一的连接名称和所有匹配规则。不允许在监视连接上发送消息:应用程序应该使用私有连接进行监视。

监视连接可能会接收所有消息,甚至是本应只发送给其他某些连接的消息("窃听")。第一个参数是匹配规则列表,这些规则将替换此连接先前激活的任何匹配规则。这些匹配规则始终被视为包含特殊的 eavesdrop='true' 成员。

作为一个特例,空的匹配规则列表(本应匹配不到任何内容,使监视器变得无用)被视为匹配所有消息的简写。

第二个参数可能用于标志,以影响将来 D-Bus 版本中监视器连接的行为。

消息总线的实现应尽量减少监控的副作用 — 特别是,与普通的窃听不同,监控系统总线不需要放宽访问控制规则,这会改变可以传递到它们(非监控)目的地的消息集。然而,监控将不可避免地增加消息总线的资源消耗。在边缘情况下,如果没有监控,时间或内存几乎不够,这可能导致消息传递失败,而在其他情况下则可能成功。

特殊消息总线名称 org.freedesktop.DBus 在对象路径 /org/freedesktop/DBus 上导出了几个属性(请参阅名为“org.freedesktop.DBus.Properties”的部分)。

org.freedesktop.DBus.Features

作为一个属性:

只读常量数组的字符串特性

此属性列出消息总线提供的抽象“特性”,客户端可以使用它来检测与其通信的消息总线的功能。此属性是消息总线参考实现版本1.11.x中添加的。

返回的数组中不包含“.”的项目由本规范定义。希望广告功能但未在本文档中提及的总线守护程序实现者应该要么向本规范贡献补丁,要么使用包含“.”并以其自己的反向域名开头的键,例如 com.example.MyBus.SubliminalMessages

本规范中当前定义的功能如下:

ActivatableServicesChanged

每当其可激活服务列表可能已更改时,此消息总线会发出ActivatableServicesChanged信号(有关详细信息,请参阅名为“org.freedesktop.DBus.ActivatableServicesChanged”的部分)。

AppArmor

此消息总线通过AppArmor安全框架过滤消息。只有在运行时启用并激活AppArmor调解时,才应该宣传此功能;仅仅编译支持AppArmor不应导致在消息总线实例上宣传此功能,如果消息总线或操作系统配置禁用了该功能。

HeaderFiltering

此消息总线保证在中继消息时会删除它不理解的标头字段,因此,客户端接收到一个最近定义的标头字段,该字段被指定由消息总线控制,可以安全地假定该字段实际上是由消息总线设置的。这种检查是必要的,因为旧的消息总线实现不能保证以这种方式过滤标头,因此,恶意客户端可以通过旧的不理解该标头字段的消息总线发送任何最近定义的标头字段及其选择的精心制作的值。

SELinux

此消息总线通过 SELinux 安全框架过滤消息。类似于 apparmor,此功能只有在运行时启用并处于活动状态的 SELinux 介入时才应该被广告宣传(如果 SELinux 处于宽容模式,仍被视为活动状态)。

SystemdActivation

当要求激活具有.service文件中SystemdService字段的服务时,此消息总线将执行systemd激活(详细信息请参阅systemd激活部分)。

org.freedesktop.DBus.Interfaces

作为一个属性:

只读常量数组的字符串接口

此属性列出了/org/freedesktop/DBus对象提供的接口,客户端可以使用它来检测与其通信的消息总线的功能。与标准的Introspectable接口不同,查询此属性不需要解析XML。此属性是消息总线参考实现版本1.11.x中添加的。

此属性的值中不包括标准的org.freedesktop.DBusorg.freedesktop.DBus.Properties接口,因为可以从成功调用org.freedesktop.DBus.Properties上请求org.freedesktop.DBus属性的方法来推断它们的存在。此属性的值中也不包括标准的org.freedesktop.DBus.Peerorg.freedesktop.DBus.Introspectable接口,因为它们不表示消息总线实现的特性。

本术语表定义了本规范中使用的一些术语。

公交车名称

消息总线维护名称和连接之间的关联。(通常,每个应用程序有一个连接。)总线名称只是用于定位连接的标识符。例如,假设的 com.yoyodyne.Screensaver 名称可能用于向Yoyodyne Corporation的屏幕保护程序发送消息。如果消息总线将应用程序的连接与名称关联起来,则称该应用程序 拥有 一个名称。名称还可能具有 排队所有者(请参阅 排队名称所有者)。总线为每个连接分配一个唯一名称,请参阅 唯一连接名称。其他名称可以被视为“众所周知的名称”,用于查找提供特定功能的应用程序。

查看名为“总线名称”的部分以获取有关总线名称的语法和命名约定的详细信息。

消息

消息是通过 D-Bus 协议进行通信的原子单位。它由一个 header 和一个 body 组成;body 由 arguments 组成。

消息总线

消息总线是一种特殊的应用程序,用于在连接到消息总线的一组应用程序之间转发或路由消息。它还管理用于路由消息的名称。

名称

查看 Bus Name。"Name" 也可用于指代 D-Bus 中的其他名称,如接口名称。

命名空间

用于在定义新接口、总线名称等时防止冲突。使用的约定与 Java 用于定义类的约定相同:一个反转的域名。请参阅名为“总线名称”的部分名为“接口名称”的部分名为“错误名称”的部分名为“有效对象路径”的部分

对象

每个应用程序包含 对象,这些对象具有 接口方法。对象通过名字引用,称为 路径

一对一

一个应用程序直接与另一个应用程序通信,而无需通过消息总线。一对一连接可能是“点对点”或“客户端对服务器”。在连接经过身份验证后,D-Bus协议没有客户端与服务器的概念;消息的流动是对称的(全双工)。

路径

在 D-Bus 中,对象引用(对象名称)被组织成类似文件系统的层次结构,因此每个对象都由路径命名。与 LDAP 一样,“文件”和“目录”之间没有区别;一个路径可以引用一个对象,同时它下面还可以有子对象。

排队名称所有者

每个总线名称都有一个主要所有者;发送到该名称的消息将发送给主要所有者。但是,某些名称还维护着一个队列,其中包含“等待接手”的次要所有者。如果主要所有者释放名称,则队列中的第一个次要所有者将自动成为名称的新所有者。

服务

服务是由总线守护程序启动的可执行文件。服务通常保证某些特定功能,例如它们可能保证请求特定名称,如"com.example.Screensaver1",拥有单例对象"/com/example/Screensaver1",并且该对象将实现接口"com.example.Screensaver1.Control"。

服务描述文件

".service files" 告诉总线有关可以启动的服务应用程序(请参阅Service)。最重要的是,它们提供了总线名称到服务的映射,这些服务在启动时将请求这些名称。

唯一连接名称

消息总线自动分配给每个连接的特殊名称。此名称永远不会更改所有者,并且将是唯一的(在消息总线的生命周期内永不重复使用)。它将以一个':'字符开头。

总结
D-Bus是一个低开销、易于使用的进程间通信(IPC)系统。它采用二进制协议,避免了转换为XML等文本格式,设计用于高分辨率同机器IPC,避免往返并支持异步操作。D-Bus基本协议是一对一的协议,但主要应用是D-Bus消息总线,允许多个应用连接并转发消息。D-Bus用于系统变化通知、桌面互操作等,设计用于系统总线和会话总线。它不是通用IPC系统,故意省略了其他IPC系统的许多功能。总线守护进程提供许多其他IPC系统中找不到的功能,如单所有者“总线名称”、按需启动服务和安全策略。D-Bus可能在未预料的应用中有用,但未来版本不会加入干扰核心用例的功能。D-Bus协议自2006年11月8日起冻结,但规范仍需进一步完善以实现可互操作的重新实现。