從需求去理解 Linux dbus與基於dbus協議的無agent軟體管理

鋼閘門發表於2021-09-12

What is IPC

IPC [Inter-Process Communication] 程式間通訊,指至少兩個程式或執行緒間傳送資料或訊號的一些技術或方法。在Linux/Unix中,提供了許多IPC。Unix七大IPC:

  • Pipe:無名管道,最基本的IPC,單向通訊,僅在父/子程式之間,也就是將一個程式的輸出直接交給另一個程式的輸入。常見使用為 ps -ef|grep xxx
  • FIFO [(First in, First out)] 或 有名管道(named pipe:與Pipe不同,FIFO可以讓兩個不相關的程式可以使用FIFO。單向。
  • Socket 和 Unix Domain Socket:socket和Unix套接字,雙向。適用於網路通訊,但也可以在本地使用。適用於不同的協議。
  • 訊息佇列 Message Queue: SysV 訊息佇列、POSIX 訊息佇列。
  • Signal: 訊號,是傳送到正在執行的程式通知以觸發其事件的特定行為,是IPC的一種有限形式。
  • Semaphore:訊號量,通常用於IPC或同一程式內的執行緒間通訊。他們之間使用佇列進行訊息傳遞、控制或內容的傳遞。(常見SysV 訊號量、POSIX 訊號量)
  • Shared memory:(常見SysV 共享記憶體、POSIX 共享記憶體)。共享記憶體,是在程式(程式)之間傳遞資料的有效方式,目的是在其之間提供通訊。

每種IPC都有不通的特點,每種方式對資源的使用及效能都是不通的

  • 管道 I/O是最快的,但為單向通訊,需要工作在 父/子 程式關係之間。
  • UNIX 套接字可以在本地連線不同的程式,並且具有更高的頻寬,並且沒有固有的訊息邊界。
  • TCP/IP套接字可以連線任何程式。並且可以通過網路連線,但是對資源會有更多的開銷,同樣的沒有固定的訊息邊界。

Reference

comparsion Unix/Linux IPC

What is D-Bus

提到,D-Bus就不能不提一下freedesktop,而 D-Bus 僅僅作為freedesktop.org的一部分。

D-Bus 桌面匯流排 (Desktop Bus),的簡寫,也是Linux- IPC機制,不同於Unix 7大基礎IPC的是,D-Bus是在這些IPC型別之上實現的中介軟體IPC,D-Bus使用了基礎IPC中一種過多種,其設計的目的是在Linux桌面環境,提供服務的標準化。但目前並沒有合入主線核心中。

作為中介軟體IPC,D-Bus的效能較低與其他IPC模式,因為在通訊過程中會進行很多上下文切換,如果通過Dbus來傳送訊息,會先將其傳送到核心,然後將其送回D-Bus。AF_BUS 補丁是新的套接字型別,用來減少D-Bus上下文的切換。

更多可參考:https://en.wikipedia.org/wiki/D-Bus

D-Bus組成

D-Bus是 一個IPC的實現方式,在架構上分位三層。

  • Layer 1 libdbus:freedesktop機構提供的一個免費開源的一個由C語言編寫的 low-level API 。是提供dbus功能的庫。是高階API繫結的低階API。
  • Layer 2 dbus daemon:dbus實現的IPC守護進行,隨Linux啟動,通過不通程式對其的連線,實現了多程式間訊息的路由(包含核心、網路、桌面等)
  • Layer 3 Wapper libraries (high-level API):low-level API libdbus的封裝 ,例如 libdbus-qt libdbus-python github.com/godbus/dbus,這些不同程式語言實現的Wapper是不同開發者應該使用的lib,其簡化了D-Bus的開發難度。

使用 D-Bus 進行程式間通訊

Reference

dbus-tutorial

dbus 基本概念

匯流排

在 D-Bus 中,bus是一個核心概念。它是應用程式可以進行方法呼叫、傳送訊號和偵聽訊號的通道。有兩種預定義的bus:會話匯流排系統匯流排

  • 會話匯流排(Session Bus):普通程式建立,可同時存在多條。會話匯流排屬於某個程式私有,它用於程式間傳遞訊息。

  • 系統匯流排(System Bus):在引導時就會啟動,它由作業系統和後臺程式使用,安全性非常好,以使得任意的應用程式不能欺騙系統事件。當然,如果一個應用程式需要接受來自系統匯流排的訊息,他也可以直接連線到系統匯流排中,但是他能傳送的訊息是受限的。系統匯流排最常見的用途是在系統範圍事件發生時傳送系統範圍的通知。新增新的儲存裝置、網路連線更改事件和關閉相關事件都是系統匯流排何時更適合通訊匯流排的示例。

通常情況下只存在一個System Bus,但可以存在多個Session Bus(每個桌面會話一個)。

匯流排以dbus-daemon的形式存在與系統中,該程式專門將訊息從一個程式傳遞到另一個程式。該守護程式還將向匯流排上的所有應用程式轉發通知。

bus name

匯流排名稱 Bus Name,不能單單以字面意思 匯流排名稱 來理解,官方對其解釋為:Connections have one or more bus names associated with them. A connection has exactly one bus name that is a unique connection name.,可以出bus name其實是用來連線名稱。主要是用來標識一個應用和訊息匯流排的連線。匯流排名稱主要分為兩類:唯一名稱與公共名稱。

  • 唯一連線名稱 unique connection names :以冒號(':')字元開頭的 bus name是唯一的連線名稱。例如 :1.0。每個連線都有一個唯一名。在一個 訊息匯流排的生命期內,不會有兩個連線有相同的唯一名。
  • 公共連線名稱 well-known bus names:公共名稱是以反向DNS域名(小寫)例如:org.fedoraproject.FirewallD1
    • 如果DNS 域名包含連字元/減號,則應將其替換為下劃線,如果包含數字,則應通過新增下劃線進行轉義。例如: 7-zip.org的bus name應該定義為 org._7_zip.Archiver

Reference

bus name

物件路徑

物件路徑(Object Paths) 是用於引用物件例項的名稱(類似於 C++ 或 Java 物件)。從概念上來說,D-Bus在訊息交換中每個參與者都有任意個物件例項,如檔案系統一樣,Dbus中的參與者中的物件例項也會形成一個層次樹。如,在CentOS7中 firewalld開發的D-Bus API 使用了/org/fedoraproject/FirewallD1的層次結構。

在定義一個物件路徑時,需要注意以下:

  • 路徑可以是任意長度
  • 路徑必須以 ASCII '/'(整數 47)字元開頭,並且必須由以斜槓字元分隔的元素組成。
  • 每個元素只能包含 ASCII 字元 [AZ][az][0-9]_
  • 不允許出現 空字串
  • 多個 / 字元不能依次出現。 除非路徑是根路徑(單個/字元),否則不允許尾隨 /字元。

介面名稱

interface,在每個 Object Path都包含多個介面,一般情況下介面名稱應以反向 DNS 域名開頭(小寫),(同 Java 中的介面名稱)。在命名規則上,與bus name相同。

例如:CentOS7中 firewalld開發的D-Bus API 定義的管理zone的介面 org.fedoraproject.FirewallD1.config.zone。如果DNS名稱中包含-,則應將其替換為下劃線 _。如果DNS 域名包含緊跟在 . 之後的數字,則介面名稱應在數字之前新增一個下劃線。例如,如果 7-zip.org 外掛定義了一個介面,應該被命名為org._7_zip.Plugin.

成員方法名稱

成員方法名稱,Member names ,對於定義了介面後,需要實現其介面的放法,如需要獲得firewalld的zone時,就可以呼叫 org.fedoraproject.FirewallD1.getDefaultZone 。在D-Bus中Member names通常由“駝峰式”(camel-case)命名 。

dbus

在Linux中,如CentOS dbus包括 dbus daemon及一些cli commad。這些包

dbuslib

D-Bus的訊息

最基本的D-Bus協議是一對一的通訊協議。與直接使用socket不同,D-Bus是面向訊息的協議。 D-Bus的所有功能都是通過在連線上流動的訊息完成的。

而在D-Bus中有四種型別的訊息

  • METHOD_CALL 方法呼叫
  • METHOD_RETURN 方法返回
  • ERROR 錯誤
  • SIGNAL 訊號:與方法呼叫不同,訊號發射沒有響應。訊號發射只是一個型別為 SIGNAL 的訊息。它必須具有三個標頭欄位:PATH給出發出訊號的物件,加上INTERFACEMEMBER給出訊號的完全限定名稱。

訊息返回的型別

Conventional name 十進位制值 說明
INVALID 0 這是個無效型別
METHOD_CALL 1 方法呼叫,該方法會有提示
METHOD_RETURN 2 方法返回的資料
ERROR 3 錯誤返回,第一個是其錯誤的資訊
SIGNAL 4 訊號的發射

CentOS的dbus服務管理

在CentOS7中,作為systemd的一部分D-BUS會從Systemd獲取套接字檔案描述符,並使用D-Bus交換當前程式生成的socket資訊。而PID 1 不使用 PolicyKit 來控制對特權操作的訪問,而是完全依賴於 low-level API D-Bus 。(這樣做是為了避免 PolicyKit 和 systemd/PID 1 之間的迴圈依賴。)而有些特權程式(例如關機/重啟/掛起/登陸)可以通過logind進行管理的。

由此,可以知道在CentOS中,dbus相關的服務大概有 dbus,與 logind

dbus包含:

  • dbus-daemon:dbus架構中 layer 2的 dbus-damon

  • dbus-send: dbus提供的命令列工具,可以用dbus-send來傳送訊息。

  • dbus-monitor: dbus提供的命令列工具,用於監視匯流排上流動的訊息。

  • dbus-launch: shell指令碼啟動訊息匯流排的命令列工具

dbus配置檔案說明

dbus-daemon守護程式,有兩個配置檔案,一個為 session bus,另外一個為 system bus。

標準的system bus檔案 /usr/local/share/dbus-1/system.conf session bus配置 /usr/local/share/dbus-1/session.conf中配置。在一般情況下,不會操作這兩個檔案,因其會引入 /etc/dbus-1 中的system.confsession.conf

配置檔案包含的標籤:

更多的註釋可以參考:dbus-daemon

# 根元素
<busconfig>

  <!-- 根據指定的 -system或 -session 來選擇的配置檔案 -->
  <type>system</type>

  <!-- dbus-daemon執行的使用者 -->
  <user>dbus</user>

  <!-- Fork into daemon mode -->
  <fork/>

  <!-- We use system service launching using a helper -->
  <standard_system_servicedirs/>

  <!-- This is a setuid helper that is used to launch system services -->
  <servicehelper>//usr/libexec/dbus-1/dbus-daemon-launch-helper</servicehelper>

  <!-- Write a pid file -->
  <pidfile>/run/dbus/messagebus.pid</pidfile>

  <!-- Enable logging to syslog -->
  <syslog/>

  <!-- 指定授權機制。如果不存在,所有的機制都被允許。 -->
  <auth>EXTERNAL</auth>

  <!-- 匯流排監聽的地址,支援unix socket,tcp,system等
		-->
  <listen>unix:path=/run/dbus/system_bus_socket</listen>
  <listen>unix:path=/tmp/foo</listen>
  <listen>tcp:host=localhost,port=1234</listen>  
    
    
  <policy context="default">
    <!-- All users can connect to system bus -->
    <allow user="*"/>

    <!-- Holes must be punched in service configuration files for
         name ownership and sending method calls -->
    <deny own="*"/>
    <deny send_type="method_call"/>

    <!-- Signals and reply messages (method returns, errors) are allowed
         by efault -->
    <allow send_type="signal"/>
    <allow send_requested_reply="true" send_type="method_return"/>
    <allow send_requested_reply="true" send_type="error"/>

    <!-- All messages may be received by default -->
    <allow receive_type="method_call"/>
    <allow receive_type="method_return"/>
    <allow receive_type="error"/>
    <allow receive_type="signal"/>

    <!-- Allow anyone to talk to the message bus -->
    <allow send_destination="org.freedesktop.DBus"
           send_interface="org.freedesktop.DBus" />
    <allow send_destination="org.fedoraproject.FirewallD1" 
	send_interface="org.fedorapproject.FirewallD1" />
    <allow send_destination="org.freedesktop.DBus"
           send_interface="org.freedesktop.DBus.Introspectable"/>
    <!-- But disallow some specific bus services -->
    <deny send_destination="org.freedesktop.DBus"
          send_interface="org.freedesktop.DBus"
          send_member="UpdateActivationEnvironment"/>
    <deny send_destination="org.freedesktop.DBus"
          send_interface="org.freedesktop.DBus.Debug.Stats"/>
    <deny send_destination="org.freedesktop.DBus"
          send_interface="org.freedesktop.systemd1.Activator"/>
  </policy>

  <!-- Only systemd, which runs as root, may report activation failures. -->
  <policy user="root">
    <allow send_destination="org.freedesktop.DBus"
           send_interface="org.freedesktop.systemd1.Activator"/>
  </policy>

  <!-- root may monitor the system bus. -->
  <policy user="root">
    <allow send_destination="org.freedesktop.DBus"
           send_interface="org.freedesktop.DBus.Monitoring"/>
  </policy>

  <!-- If the Stats interface was enabled at compile-time, root may use it.
       Copy this into system.local.conf or system.d/*.conf if you want to
       enable other privileged users to view statistics and debug info -->
  <policy user="root">
    <allow send_destination="org.freedesktop.DBus"
           send_interface="org.freedesktop.DBus.Debug.Stats"/>
  </policy>

  <!-- Include legacy configuration location -->
  <include ignore_missing="yes">/etc/dbus-1/system.conf</include>

  <!-- 包含的子配置檔案. -->
  <includedir>system.d</includedir>

  <includedir>/etc/dbus-1/system.d</includedir>

  <!-- This is included last so local configuration can override what's 
       in this standard file -->
  <include ignore_missing="yes">/etc/dbus-1/system-local.conf</include>

  <include if_selinux_enabled="yes" selinux_root_relative="yes">contexts/dbus_contexts</include>

</busconfig>

通過命令列傳送dbus訊息

dbus支援通過命令傳送一個dbus訊息,如獲取可用的dbus 服務。

dbus-send --session \
--dest=org.freedesktop.DBus \
--type=method_call \
--print-reply \
/org/freedesktop/DBus \
org.freedesktop.DBus.ListNames

method return time=1631452206.288425 sender=org.freedesktop.DBus -> destination=:1.29 serial=3 reply_serial=2
   array [
      string "org.freedesktop.DBus"
      string "org.freedesktop.login1"
      string "org.freedesktop.systemd1"
      string "org.fedoraproject.FirewallD1"
      string "org.freedesktop.PolicyKit1"
      string ":1.17"
      string ":1.0"
      string ":1.29"
      string ":1.18"
      string ":1.1"
   ]

返回org.freedesktop.DBus service

dbus-send --session \
--dest=org.freedesktop.DBus \
--type=method_call \
--print-reply \
/org/freedesktop/DBus \
org.freedesktop.DBus.Introspectable.Introspect

使用dbus api操作linux防火牆

Firewalld是一個基於動態區域的防火牆守護程式,自 2009 年左右開始開發,目前為Fedora 18 以及隨後的 RHEL7 和 CentOS 7 中的預設防火牆機制。

Firewalld被配置為systemd D-Bus 服務。請注意下面的“Type=dbus”指令。

# cat /usr/lib/systemd/system/firewalld.service 
[Unit]
Description=firewalld - dynamic firewall daemon
Before=network.target
Before=libvirtd.service
Before=NetworkManager.service
Conflicts=iptables.service ip6tables.service ebtables.service

[Service]
EnvironmentFile=-/etc/sysconfig/firewalld
ExecStart=/usr/sbin/firewalld --nofork --nopid $FIREWALLD_ARGS
ExecReload=/bin/kill -HUP $MAINPID
# supress to log debug and error output also to /var/log/messages
StandardOutput=null
StandardError=null
Type=dbus
BusName=org.fedoraproject.FirewallD1

[Install]
WantedBy=basic.target
Alias=dbus-org.fedoraproject.FirewallD1.service

知道了firewalld服務是基於D-Bus的,就可以通過D-Bus來操作防火牆。

檢視dbus註冊的服務是否包含firewalld,這裡需要注意的是,firewalld依賴dbus服務,每次啟動firewalld時註冊到dbus匯流排內。所以需要先啟動dbus-daemonfirewalld 服務。

dbus-send --system --dest=org.freedesktop.DBus --type=method_call --print-reply \
/org/freedesktop/DBus org.freedesktop.DBus.ListNames | grep FirewallD

檢視得知 org.fedoraproject.FirewallD1 為firewalld介面

檢視介面所擁有的方法、屬性、訊號等資訊

dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply \
/org/fedoraproject/FirewallD1 org.freedesktop.DBus.Introspectable.Introspect

獲得zone

firewall-cmd --get-zones

dbus-send --system \
--dest=org.fedoraproject.FirewallD1 \
--print-reply \
--type=method_call /org/fedoraproject/FirewallD1 \
org.fedoraproject.FirewallD1.zone.getZones

檢視zone內的條目資訊

# firewall-cmd --zone=public --list-all

dbus-send --system --dest=org.fedoraproject.FirewallD1 --print-reply --type=method_call \
/org/fedoraproject/FirewallD1 org.fedoraproject.FirewallD1.getZoneSettings string:"public"

相關文章