D-Bus (Desktop Bus)

D-Bus 시스템/세션 버스 아키텍처, 메시지 모델(Method Call/Signal/Property), 버스 이름과 객체 경로 체계, 타입 시스템, 서비스 활성화, 보안 모델(Policy/Polkit), 커널과의 관계(AF_UNIX/kdbus), 주요 서비스, 도구(busctl/dbus-monitor), 프로그래밍 인터페이스(sd-bus/GIO), 성능과 대안 분석을 종합 정리

선행 학습: 이 문서를 읽기 전에 IPC (Inter-Process Communication)의 Unix 도메인 소켓(Unix Domain Socket) 개념과 systemd의 유닛(Unit) 관리 기초를 먼저 이해하면 도움이 됩니다.

핵심 요약

  • D-Bus는 프로세스 간 통신(IPC)을 위한 메시지 버스 시스템(Message Bus System)으로, 하나의 데몬(Daemon)이 여러 프로세스 사이의 메시지를 중개합니다.
  • 시스템 버스(System Bus)는 시스템 전체 서비스(systemd, NetworkManager, BlueZ 등)가 사용하고, 세션 버스(Session Bus)는 사용자 데스크톱 애플리케이션이 사용합니다.
  • 통신 방식은 메서드 호출(Method Call)(요청-응답), 시그널(Signal)(브로드캐스트), 프로퍼티(Property)(속성 조회·변경) 세 가지 패턴으로 나뉩니다.
  • 모든 서비스는 버스 이름(Bus Name)으로 식별되고, 서비스 내부의 기능은 객체 경로(Object Path)인터페이스(Interface)로 구조화됩니다.
  • 보안은 D-Bus 정책(Policy)(누가 어떤 메서드를 호출할 수 있는지)과 Polkit(대화형 권한 승인)으로 계층적으로 관리됩니다.

단계별 이해

  1. 시스템 버스 탐색: busctl 명령으로 현재 시스템에 등록된 모든 D-Bus 서비스 목록을 확인합니다.
  2. 메시지 관찰: dbus-monitor --system으로 시스템 버스에서 실시간으로 오가는 메시지를 관찰합니다.
  3. 메서드 호출: busctl call이나 dbus-send로 서비스의 메서드를 직접 호출해 봅니다.
  4. 서비스 작성: sd-bus 라이브러리로 자신만의 D-Bus 서비스를 C 코드로 작성해 봅니다.

D-Bus 아키텍처(Architecture)

D-Bus는 중앙 집중형 메시지 버스 구조를 사용합니다. 클라이언트(Client)와 서비스(Service)가 직접 통신하지 않고, 중간에 위치한 버스 데몬(Bus Daemon)이 메시지를 라우팅합니다. 이 구조 덕분에 서비스 발견(Service Discovery), 활성화(Activation), 접근 제어(Access Control)를 한 곳에서 관리할 수 있습니다.

시스템 버스와 세션 버스

리눅스 시스템에서는 항상 두 종류의 버스가 동작합니다.

구분시스템 버스(System Bus)세션 버스(Session Bus)
소켓 경로/run/dbus/system_bus_socket$XDG_RUNTIME_DIR/bus 또는 추상 소켓(Abstract Socket)
데몬 실행부팅 시 systemd가 시작사용자 로그인 시 시작
주요 사용자systemd, NetworkManager, BlueZ, udisks, logind데스크톱 앱(파일 관리자, 알림, 미디어 플레이어)
보안 정책엄격 — 루트 권한 서비스 위주, Polkit 인증 필요느슨 — 같은 사용자의 프로세스끼리 자유 통신
설정 파일/usr/share/dbus-1/system.conf/usr/share/dbus-1/session.conf

버스 데몬 구현체

D-Bus 사양(Specification)과 버스 데몬 구현은 분리되어 있습니다. 주요 구현체는 두 가지입니다.

구현체특징사용 배포판
dbus-daemon원래의 참조 구현(Reference Implementation). freedesktop.org에서 개발. 단일 스레드(Single-threaded) 이벤트 루프(Event Loop) 방식대부분의 배포판 (전통적 기본값)
dbus-broker성능 최적화된 대안. 커널의 epoll과 비차단(Non-blocking) I/O를 적극 활용. 메시지 정렬(Message Ordering)을 보장하면서도 높은 처리량(Throughput) 달성Fedora, RHEL 8+, Arch Linux (기본값)

두 구현체 모두 같은 D-Bus 프로토콜(Protocol)을 사용하므로, 클라이언트 코드는 어떤 데몬이 동작하든 동일하게 작동합니다.

dbus-broker 내부 구조

dbus-broker는 단일 프로세스인 dbus-daemon과 달리, 역할을 분리한 다중 컴포넌트(Multi-component) 구조를 사용합니다.

컴포넌트역할특징
dbus-broker-launch런처(Launcher) — 설정 파일 파싱, 정책 컴파일, broker 프로세스 관리정책을 사전 컴파일(Pre-compile)하여 broker에게 전달. 런타임 XML 파싱 오버헤드 제거
dbus-broker브로커(Broker) — 실제 메시지 라우팅과 전달최소 권한(Minimal Privilege)으로 실행. epoll + 비차단 I/O로 고처리량 달성
bus driverorg.freedesktop.DBus 인터페이스 구현이름 등록(RequestName), 매칭 규칙(AddMatch), 서비스 활성화 등 버스 관리 기능
controllersystemd와의 통합 인터페이스소켓 활성화(Socket Activation) 수신, 서비스 시작 요청을 systemd에 위임

소켓 활성화(Socket Activation)

현대 시스템에서 dbus-daemon/dbus-broker는 systemd 소켓 활성화(Socket Activation)로 시작됩니다. systemd가 먼저 소켓을 열어 두고, 첫 연결이 들어오면 버스 데몬을 시작합니다.

# /usr/lib/systemd/system/dbus.socket
[Unit]
Description=D-Bus System Message Bus Socket

[Socket]
ListenStream=/run/dbus/system_bus_socket
SocketMode=0666

[Install]
WantedBy=sockets.target
# /usr/lib/systemd/system/dbus-broker.service (Fedora/RHEL)
[Unit]
Description=D-Bus System Message Bus
Requires=dbus.socket

[Service]
Type=notify
ExecStart=/usr/bin/dbus-broker-launch --scope system
NotifyAccess=main
WatchdogSec=3min

이 구조 덕분에 부팅 초기에 D-Bus 소켓이 준비되어 있고, 다른 서비스들이 D-Bus 연결을 시도하면 그때 버스 데몬이 시작됩니다. 이는 부팅 순서 의존성(Boot Order Dependency) 문제를 해결합니다.

세션 버스 (Session Bus) 소켓: $XDG_RUNTIME_DIR/bus — 사용자별 1개 파일 관리자 org.gnome.Nautilus 알림 데몬 org.freedesktop.Notifications dbus-daemon (세션 인스턴스) 미디어 플레이어 org.mpris.MediaPlayer2 입력기 org.freedesktop.IBus 시스템 버스 (System Bus) 소켓: /run/dbus/system_bus_socket — 시스템 전체 1개 nmcli / nmtui (클라이언트 도구) systemctl (systemd 클라이언트) dbus-daemon (시스템 인스턴스) org.freedesktop.systemd1 org.freedesktop.NetworkManager org.bluez ... (logind, udisks2, resolved 등) 전송 계층: AF_UNIX 소켓 (SOCK_STREAM)

메시지 라우팅 과정

클라이언트가 메서드를 호출하면 다음과 같은 과정을 거칩니다.

  1. 클라이언트가 메서드 호출 메시지를 버스 데몬의 소켓으로 전송합니다.
  2. 버스 데몬이 메시지의 목적지(Destination) 필드를 확인하여 해당 서비스를 찾습니다.
  3. 서비스가 현재 실행 중이 아니면, 서비스 활성화(Service Activation)를 통해 서비스를 시작합니다.
  4. 버스 데몬이 보안 정책(Security Policy)을 검사하여 호출이 허용되는지 확인합니다.
  5. 허용되면 메시지를 목적지 서비스의 소켓으로 전달합니다.
  6. 서비스가 처리 후 응답 메시지(Method Return) 또는 오류 메시지(Error)를 버스 데몬으로 보내고, 데몬이 이를 원래 호출자에게 전달합니다.

메시지 모델(Message Model)

D-Bus의 모든 통신은 메시지(Message) 단위로 이루어집니다. 메시지는 헤더(Header)와 본문(Body)으로 구성되며, 헤더에는 메시지 타입과 라우팅 정보가, 본문에는 실제 데이터가 담깁니다.

메시지 타입

타입방향응답 여부설명
METHOD_CALL클라이언트 → 서비스응답 대기서비스의 특정 메서드를 호출. serial 번호로 응답과 매칭
METHOD_RETURN서비스 → 클라이언트메서드 호출 성공 시 결과 반환. reply_serial로 원래 요청 식별
ERROR서비스 → 클라이언트메서드 호출 실패 시 오류 이름과 메시지 반환
SIGNAL서비스 → 구독자 전체응답 없음이벤트 발생 알림. 특정 목적지 없이 매칭 규칙(Match Rule)에 따라 전달

메시지 헤더 필드

필드타입설명예시
PATHOBJECT_PATH대상 객체의 경로/org/freedesktop/NetworkManager
INTERFACESTRING메서드가 속한 인터페이스org.freedesktop.NetworkManager
MEMBERSTRING호출할 메서드 또는 시그널 이름GetDevices
DESTINATIONSTRING목적지 버스 이름org.freedesktop.NetworkManager
SENDERSTRING발신자의 고유 이름 (데몬이 자동 설정):1.42
SIGNATURESIGNATURE본문(Body) 데이터의 타입 시그니처ao (객체 경로 배열)
SERIALUINT32메시지 일련번호 (응답 매칭용)42

와이어 포맷(Wire Format)

D-Bus 메시지는 네트워크 바이트 순서가 아닌, 발신자의 엔디언(Endianness)을 따릅니다. 수신자는 첫 바이트로 엔디언을 판별합니다.

오프셋크기필드설명
01엔디언(Endianness)l(0x6C) = 리틀엔디언, B(0x42) = 빅엔디언
11메시지 타입1=METHOD_CALL, 2=METHOD_RETURN, 3=ERROR, 4=SIGNAL
21플래그(Flags)비트 마스크: 0x1=NO_REPLY_EXPECTED, 0x2=NO_AUTO_START, 0x4=ALLOW_INTERACTIVE_AUTHORIZATION
31프로토콜 버전항상 1 (현재 사양 기준)
4-74본문 길이(Body Length)헤더 이후 본문 데이터의 바이트 수
8-114시리얼(Serial)메시지 일련번호. 응답 매칭에 사용
12-가변헤더 필드 배열a(yv) 형식 — (필드 코드, variant 값) 쌍의 배열. 8바이트 경계로 패딩

메시지 플래그(Message Flags)

플래그설명사용 시점
NO_REPLY_EXPECTED0x1응답을 기대하지 않음. 서비스는 METHOD_RETURN을 보내지 않아도 됨단방향 알림, 성능 최적화 (응답 대기 제거)
NO_AUTO_START0x2서비스가 실행 중이 아니면 활성화하지 않고 오류 반환서비스 존재 여부 확인, 선택적 기능 호출
ALLOW_INTERACTIVE_AUTHORIZATION0x4Polkit 대화형 인증(비밀번호 입력)을 허용GUI 애플리케이션에서 권한 상승이 필요한 작업
# NO_REPLY_EXPECTED 플래그로 호출 (응답 대기 없음)
busctl call --expect-reply=no org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.systemd1.Manager Reload

# NO_AUTO_START 플래그로 호출 (자동 활성화 비활성화)
busctl call --auto-start=no org.example.Optional \
  /org/example/Optional org.example.Optional Ping
클라이언트 A dbus-daemon 서비스 B 1 METHOD_CALL serial=42, dest=org.example.Service 2 METHOD_CALL (전달) 정책 검사 통과 → 서비스로 전달 3 METHOD_RETURN reply_serial=42, body=(결과 데이터) 4 METHOD_RETURN (전달) reply_serial=42로 원래 요청과 매칭 시그널 브로드캐스트 5 SIGNAL PropertiesChanged (목적지 없음) 6 SIGNAL (전달) 매칭 규칙(Match Rule) 일치하는 구독자에게 전달

프로퍼티(Property) 접근

D-Bus 서비스의 상태 값은 프로퍼티(Property)로 노출됩니다. 프로퍼티는 실제로 org.freedesktop.DBus.Properties 인터페이스의 메서드 호출로 구현됩니다.

# 특정 프로퍼티 읽기
busctl get-property org.freedesktop.NetworkManager \
  /org/freedesktop/NetworkManager \
  org.freedesktop.NetworkManager State

# 모든 프로퍼티 나열
busctl introspect org.freedesktop.NetworkManager \
  /org/freedesktop/NetworkManager \
  org.freedesktop.NetworkManager

# 프로퍼티 변경 시그널 구독
dbus-monitor --system "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'"

프로퍼티가 변경되면 서비스는 PropertiesChanged 시그널을 발생시킵니다. 이 시그널은 변경된 프로퍼티 이름과 새 값, 그리고 값이 비싸서 시그널에 포함하지 않은(invalidated) 프로퍼티 목록을 전달합니다.

버스 이름과 객체 경로(Bus Names & Object Paths)

버스 이름의 두 가지 유형

유형형식특징예시
고유 이름(Unique Name):숫자.숫자연결 시 데몬이 자동 할당. 연결 수명 동안 유일. 다른 프로세스가 사용 불가:1.42, :1.203
잘 알려진 이름(Well-known Name)역DNS(Reverse DNS)서비스가 명시적으로 요청하여 등록. 사람이 읽을 수 있음. 소유자(Owner) 변경 가능org.freedesktop.NetworkManager

하나의 연결(Connection)이 여러 개의 잘 알려진 이름을 소유할 수 있고, 잘 알려진 이름의 소유권(Ownership)은 큐(Queue)로 관리됩니다. 현재 소유자가 해제하면 큐의 다음 요청자가 자동으로 소유자가 됩니다.

이름 소유권 관리

RequestName 메서드로 잘 알려진 이름을 요청할 때 동작 플래그(Flag)를 지정할 수 있습니다.

플래그동작
DBUS_NAME_FLAG_ALLOW_REPLACEMENT0x1다른 프로세스가 REPLACE_EXISTING으로 요청하면 소유권을 양보
DBUS_NAME_FLAG_REPLACE_EXISTING0x2현재 소유자가 ALLOW_REPLACEMENT를 설정했으면 소유권을 빼앗음
DBUS_NAME_FLAG_DO_NOT_QUEUE0x4소유 실패 시 큐에 대기하지 않고 즉시 실패 반환
# 이름 소유권 확인
busctl list --system | grep NetworkManager
# org.freedesktop.NetworkManager  1234  NetworkManager  root  :1.15  ...

# 이름 소유자 변경 모니터링
dbus-monitor --system "type='signal',member='NameOwnerChanged'"
# signal sender=org.freedesktop.DBus -> dest=(null)
#   member=NameOwnerChanged
#   string "org.freedesktop.NetworkManager"
#   string ":1.15"    ← 이전 소유자
#   string ":1.203"   ← 새 소유자 (서비스 재시작 시)

# 특정 이름의 소유자 PID 확인
busctl status org.freedesktop.NetworkManager

NameOwnerChanged 시그널은 서비스의 생명주기(Lifecycle)를 추적하는 핵심 메커니즘입니다. 이전 소유자가 빈 문자열이면 서비스가 새로 시작된 것이고, 새 소유자가 빈 문자열이면 서비스가 종료된 것입니다.

객체 경로(Object Path)

D-Bus 서비스 내부에서 특정 기능 단위를 식별하는 경로입니다. 파일 시스템 경로처럼 /로 구분된 계층 구조를 사용합니다.

/org/freedesktop /org/freedesktop/NetworkManager 인터페이스: org.freedesktop.NetworkManager .../Devices .../Devices/1 .../Devices/2 org.freedesktop .NetworkManager.Device org.freedesktop .NetworkManager.Device.Wireless .../Settings .../Settings/1 org.freedesktop .NetworkManager.Settings.Connection .../ActiveConnection .../ActiveConnection/1 org.freedesktop.NetworkManager .Connection.Active 각 객체 경로(Object Path)는 하나 이상의 인터페이스(Interface)를 구현합니다. 동적 객체(장치, 연결)는 런타임에 생성·삭제되며 번호가 부여됩니다. $ busctl tree org.freedesktop.NetworkManager

이름 규칙 요약

요소형식 규칙예시
버스 이름역DNS, 마침표(.) 구분, 최소 2개 요소org.freedesktop.NetworkManager
객체 경로슬래시(/) 구분, 파일 경로 형태/org/freedesktop/NetworkManager/Devices/1
인터페이스역DNS, 버스 이름과 동일한 형식org.freedesktop.NetworkManager.Device
멤버(메서드/시그널)CamelCase, 마침표 없음GetDevices, StateChanged

인터페이스(Interface)와 인트로스펙션(Introspection)

인터페이스(Interface)는 하나의 객체(Object)가 제공하는 메서드(Method), 시그널(Signal), 프로퍼티(Property)의 집합입니다. 하나의 객체가 여러 인터페이스를 동시에 구현할 수 있습니다.

표준 인터페이스

모든 D-Bus 객체가 반드시(또는 관례적으로) 구현하는 표준 인터페이스가 있습니다.

인터페이스주요 멤버설명
org.freedesktop.DBus.IntrospectableIntrospect()xml객체의 인터페이스·메서드·시그널·프로퍼티를 XML로 반환
org.freedesktop.DBus.PropertiesGet(), Set(), GetAll(), PropertiesChanged 시그널프로퍼티 읽기·쓰기·변경 알림을 통합 관리
org.freedesktop.DBus.PeerPing(), GetMachineId()연결 상태 확인(Liveness Check)과 머신 식별
org.freedesktop.DBus.ObjectManagerGetManagedObjects(), InterfacesAdded/Removed 시그널하위 객체 트리를 한 번에 조회, 객체 추가·제거 알림

인트로스펙션 XML

Introspect() 메서드를 호출하면 객체의 전체 API 구조를 XML로 받을 수 있습니다.

# busctl로 인트로스펙션 실행
busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1
<!-- Introspect() 반환 XML 예시 (요약) -->
<node name="/org/freedesktop/systemd1">
  <interface name="org.freedesktop.systemd1.Manager">
    <method name="StartUnit">
      <arg name="name" type="s" direction="in"/>
      <arg name="mode" type="s" direction="in"/>
      <arg name="job" type="o" direction="out"/>
    </method>
    <signal name="UnitNew">
      <arg name="id" type="s"/>
      <arg name="unit" type="o"/>
    </signal>
    <property name="Version" type="s" access="read"/>
  </interface>
  <interface name="org.freedesktop.DBus.Properties">
    <!-- Get, Set, GetAll, PropertiesChanged -->
  </interface>
  <node name="unit"/>
  <node name="job"/>
</node>
XML 구조 설명
  • <node> 객체 경로 하나를 나타냅니다. 하위 <node>는 자식 객체 경로를 의미합니다.
  • <interface> 이 객체가 구현하는 인터페이스입니다. 이름은 역DNS 형식입니다.
  • <method> 호출 가능한 메서드. <arg direction="in">은 입력, "out"은 반환값입니다.
  • <signal> 서비스가 발생시키는 이벤트. 모든 인자는 출력 방향입니다.
  • <property> access="read"는 읽기 전용, "readwrite"는 읽기·쓰기 가능합니다.
  • type 속성 s=문자열, o=객체 경로 등 D-Bus 타입 시그니처를 나타냅니다.

busctl introspect 출력 읽기

busctl introspect는 인트로스펙션 결과를 테이블 형태로 보여줍니다.

$ busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1 org.freedesktop.systemd1.Manager
NAME                       TYPE      SIGNATURE  RESULT/VALUE  FLAGS
.GetUnit                   method    s          o             -
.ListUnits                 method    -          a(ssssssouso) -
.Reload                    method    -          -             -
.StartUnit                 method    ss         o             -
.StopUnit                  method    ss         o             -
.UnitNew                   signal    so         -             -
.UnitRemoved               signal    so         -             -
.Version                   property  s          "256"         const
.NNames                    property  u          412           emits-change
출력 칼럼 설명
  • NAME 멤버 이름. 마침표(.)로 시작합니다.
  • TYPE method, signal, property 중 하나입니다.
  • SIGNATURE 메서드의 입력 타입 시그니처. -는 인자 없음을 의미합니다.
  • RESULT/VALUE 메서드의 반환 타입 시그니처, 또는 프로퍼티의 현재 값입니다.
  • FLAGS const=읽기 전용(변경 불가), emits-change=변경 시 PropertiesChanged 시그널 발생, emits-invalidation=값 없이 변경 알림만.

인터페이스 설계 패턴

D-Bus 서비스를 설계할 때 반복적으로 사용되는 패턴이 있습니다.

패턴구조적용 시점예시
매니저 패턴(Manager)단일 루트 객체에 모든 메서드 집중전역 상태 관리, 설정 변경org.freedesktop.systemd1.Manager
컬렉션 패턴(Collection)ObjectManager + 번호 매겨진 하위 객체들동적 리소스 (장치, 연결, 세션)NetworkManager의 .../Devices/1, BlueZ의 .../dev_XX_XX
프로퍼티 중심(Properties-heavy)메서드는 최소, 대부분 프로퍼티로 상태 노출읽기 위주, PropertiesChanged로 변경 추적UPower의 배터리 상태
시그널 구동(Signal-driven)이벤트를 시그널로 브로드캐스트, 클라이언트가 구독비동기 알림, 로그 스트리밍logind의 PrepareForSleep, NM의 StateChanged

어노테이션(Annotation) 태그

인트로스펙션 XML에서 <annotation> 요소는 멤버에 대한 메타데이터(Metadata)를 제공합니다.

어노테이션 이름의미
org.freedesktop.DBus.Deprecatedtrue이 멤버는 더 이상 사용하지 않음(Deprecated). 새 코드에서 사용 금지
org.freedesktop.DBus.Property.EmitsChangedSignaltrue / invalidates / const / false프로퍼티 변경 시 PropertiesChanged 시그널 발생 방식. invalidates는 이름만 알리고 값은 생략
org.freedesktop.DBus.Method.NoReplytrue이 메서드는 응답을 반환하지 않음 (fire-and-forget)
org.freedesktop.systemd1.Privilegedtrue이 멤버는 루트 권한 또는 Polkit 인증이 필요

gdbus-codegen 코드 생성

GLib 기반 프로젝트에서는 gdbus-codegen으로 인트로스펙션 XML에서 C 바인딩(Binding)을 자동 생성할 수 있습니다.

# 1. 서비스에서 인트로스펙션 XML 추출
gdbus introspect --system --dest org.freedesktop.UPower \
  --object-path /org/freedesktop/UPower --xml > upower.xml

# 2. C 코드 생성 (헤더 + 소스)
gdbus-codegen --interface-prefix org.freedesktop.UPower \
  --generate-c-code upower-generated \
  --c-namespace UPower \
  upower.xml

# 3. 생성된 파일: upower-generated.h, upower-generated.c
# 프록시(Proxy) 객체로 메서드 호출:
#   UPowerDevice *proxy = upower_device_proxy_new_for_bus_sync(...);
#   gdouble percentage = upower_device_get_percentage(proxy);

D-Bus 타입 시스템(Type System)

D-Bus는 자체 타입 시스템(Type System)을 가지고 있으며, 메시지의 본문 데이터는 이 타입 시스템에 따라 직렬화(Serialization)됩니다. 각 타입은 한 글자의 시그니처 문자(Signature Character)로 표현됩니다.

기본 타입(Basic Types)

시그니처이름크기설명
yBYTE1바이트부호 없는 8비트 정수
bBOOLEAN4바이트0(false) 또는 1(true)
nINT162바이트부호 있는 16비트 정수
qUINT162바이트부호 없는 16비트 정수
iINT324바이트부호 있는 32비트 정수
uUINT324바이트부호 없는 32비트 정수
xINT648바이트부호 있는 64비트 정수
tUINT648바이트부호 없는 64비트 정수
dDOUBLE8바이트IEEE 754 배정밀도 부동소수점
sSTRING가변UTF-8 문자열 (NUL 종단)
oOBJECT_PATH가변D-Bus 객체 경로 문자열
gSIGNATURE가변D-Bus 타입 시그니처 문자열
hUNIX_FD4바이트유닉스 파일 디스크립터(SCM_RIGHTS로 전달)

컨테이너 타입(Container Types)

시그니처이름설명예시
aTARRAY같은 타입 T의 0개 이상 나열ai = INT32 배열, as = 문자열 배열
(T₁T₂...)STRUCT서로 다른 타입의 고정 조합(si) = (문자열, INT32) 쌍
a{KV}DICT키-값 쌍의 배열 (사전). 키는 기본 타입만 가능a{sv} = 문자열→variant 사전
vVARIANT런타임에 타입이 결정되는 값. 시그니처가 값과 함께 전달됨프로퍼티의 값 타입으로 자주 사용

시그니처 읽기 예제

시그니처의미사용처
a{sv}문자열 → 임의 타입 사전프로퍼티 집합, 옵션 전달 (가장 흔한 패턴)
ao객체 경로 배열GetDevices() 반환값
a{sa{sv}}문자열 → (문자열 → variant 사전) 사전GetManagedObjects() — 인터페이스별 프로퍼티 맵
(ssa{sv})(문자열, 문자열, 사전) 구조체PropertiesChanged 시그널 — (인터페이스, 변경된 프로퍼티, 무효화 목록)
a(iiay)(INT32, INT32, 바이트 배열) 구조체의 배열IP 주소 목록 (주소 체계, 프리픽스, 주소 바이트)
# busctl에서 시그니처 확인
busctl call org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.systemd1.Manager \
  ListUnits
# 반환 시그니처: a(ssssssouso) — 유닛 정보 구조체의 배열

마샬링 정렬 규칙(Marshalling Alignment)

D-Bus 와이어 포맷에서 각 타입은 자연 정렬(Natural Alignment)을 따릅니다. 이전 필드 끝부터 다음 필드 시작까지 필요한 만큼 패딩(Padding) 바이트(0x00)가 삽입됩니다.

타입정렬(바이트)직렬화 형태
BYTE (y)11바이트 그대로
BOOLEAN (b), INT32 (i), UINT32 (u)44바이트 정수 (엔디언 따름)
INT16 (n), UINT16 (q)22바이트 정수
INT64 (x), UINT64 (t), DOUBLE (d)88바이트
STRING (s), OBJECT_PATH (o)4UINT32 길이 + UTF-8 바이트 + NUL
SIGNATURE (g)1BYTE 길이 + ASCII 바이트 + NUL
ARRAY (a)4UINT32 바이트 수 + 패딩 + 요소들
STRUCT ((...))88바이트 경계로 패딩 후 필드들
DICT_ENTRY ({...})8STRUCT와 동일
VARIANT (v)1SIGNATURE(타입) + 패딩 + 값

Variant 중첩과 제한

# 복합 Variant 값 설정 (a{sv} 사전 안에 다양한 타입)
busctl call --user com.example.Config \
  /com/example/Config com.example.Config SetOptions \
  a{sv} 3 \
    "timeout" v u 30 \
    "name" v s "production" \
    "tags" v as 2 "web" "api"
# timeout → UINT32(30), name → STRING, tags → STRING 배열
D-Bus 타입 시스템 제한:
  • 컨테이너 중첩(Nesting)은 최대 64레벨까지 허용됩니다 (예: 배열 안의 구조체 안의 배열... 64단계)
  • 시그니처 문자열은 최대 255바이트까지입니다
  • 빈 배열(a{sv} 0)은 유효하지만, 빈 구조체(())는 허용되지 않습니다
  • 사전(Dict)의 키는 반드시 기본 타입(Basic Type)이어야 합니다 — 배열이나 구조체는 키로 사용 불가

서비스 활성화(Service Activation)

D-Bus의 서비스 활성화(Service Activation)는 서비스가 실행되지 않은 상태에서 클라이언트가 해당 서비스를 호출하면 자동으로 서비스를 시작하는 메커니즘입니다.

서비스 파일(.service)

서비스 활성화를 위해서는 .service 파일이 필요합니다.

버스 종류서비스 파일 경로
시스템 버스/usr/share/dbus-1/system-services/
세션 버스/usr/share/dbus-1/services/
# /usr/share/dbus-1/system-services/org.freedesktop.NetworkManager.service
[D-BUS Service]
Name=org.freedesktop.NetworkManager
Exec=/usr/sbin/NetworkManager --no-daemon
User=root
SystemdService=NetworkManager.service
서비스 파일 설명
  • Name= 이 서비스가 소유할 잘 알려진 이름(Well-known Name)입니다.
  • Exec= 서비스를 직접 실행할 때의 명령어입니다. dbus-daemon이 직접 exec합니다.
  • User= 시스템 버스 서비스의 실행 사용자입니다.
  • SystemdService= systemd 환경에서는 Exec 대신 이 systemd 유닛을 통해 서비스를 시작합니다. 리소스 제한과 로그 관리를 systemd에 위임할 수 있습니다.

활성화 시퀀스

클라이언트 dbus-daemon systemd 서비스 1 METHOD_CALL dest=org.example.MyService 서비스 미등록 2 StartUnit() SystemdService= 참조 3 프로세스 시작 ExecStart= 실행 4 RequestName() org.example.MyService 등록 큐에서 꺼냄 5 METHOD_CALL (전달) 6 METHOD_RETURN 7 METHOD_RETURN (전달) 클라이언트는 활성화를 인식하지 못함

클라이언트 입장에서는 서비스가 이미 실행 중이든 활성화를 통해 시작되든 동일하게 동작합니다. 단, 서비스 시작에 시간이 걸리므로 첫 호출의 응답 시간이 길어질 수 있습니다. busctl --auto-start=no로 자동 활성화를 비활성화할 수 있습니다.

systemd 유닛 타입: Type=dbus vs Type=notify

D-Bus 서비스의 systemd 유닛을 작성할 때, 준비 완료(Readiness) 신호 방식을 선택해야 합니다.

유닛 타입준비 판단 기준장점사용 시점
Type=dbus서비스가 BusName=에 지정된 이름을 D-Bus에 등록하면 준비 완료D-Bus 이름 등록과 systemd 준비가 자동으로 동기화D-Bus 이름 등록이 곧 서비스 준비를 의미하는 경우
Type=notify서비스가 sd_notify("READY=1")을 호출하면 준비 완료내부 초기화(DB 연결 등)가 완료된 후 명시적으로 알림 가능D-Bus 이름 등록 이후에도 추가 초기화가 필요한 경우
# Type=dbus 예시 — D-Bus 이름 등록 시점에 준비 완료
[Service]
Type=dbus
BusName=org.freedesktop.NetworkManager
ExecStart=/usr/sbin/NetworkManager --no-daemon

# Type=notify 예시 — sd_notify("READY=1") 호출 시점에 준비 완료
[Service]
Type=notify
ExecStart=/usr/lib/bluetooth/bluetoothd --nodetach
NotifyAccess=main

자동 시작 제어

D-Bus 서비스 파일에 SystemdService=가 있으면 dbus-daemon은 직접 프로세스를 시작하지 않고 systemd에 위임합니다. 이를 통해 LimitNOFILE=, MemoryMax= 같은 리소스 제한(Resource Limit)과 journalctl 로그 통합을 활용할 수 있습니다.

자동 활성화를 원하지 않는 서비스는 .service 파일을 제공하지 않거나, 클라이언트에서 NO_AUTO_START 플래그를 설정합니다.

보안 모델(Security Model)

D-Bus의 보안은 여러 계층(Layer)으로 구성되어, 각 단계에서 접근을 제어합니다.

클라이언트 METHOD_CALL D-Bus 정책 system.conf system.d/*.conf UID/GID 기반 allow/deny 규칙 거부 → AccessDenied 오류 Polkit 대화형 권한 승인 *.policy 파일 Action ID 기반 비밀번호 입력 요청 거부 → NotAuthorized 오류 MAC SELinux / AppArmor 보안 컨텍스트 기반 dbus send/receive 정책으로 중재 거부 → AVC denied 1단계 2단계 (선택) 3단계 (활성화 시) 모든 단계를 통과해야 메서드 호출이 서비스에 전달됩니다

D-Bus 정책 파일

시스템 버스의 정책 파일은 /usr/share/dbus-1/system.d/ 또는 /etc/dbus-1/system.d/에 위치합니다. 서비스별로 하나의 .conf 파일을 가집니다.

정책 규칙 속성

<allow><deny> 요소에서 사용할 수 있는 속성입니다. 여러 속성을 조합하면 AND 조건으로 동작합니다.

속성적용 대상설명
send_destination송신메시지를 보낼 목적지 버스 이름
send_interface송신메시지의 인터페이스 (send_destination과 함께 사용 권장)
send_member송신특정 메서드/시그널 이름만 허용
send_type송신method_call, signal 등 메시지 타입 제한
send_path송신특정 객체 경로로만 메시지 허용
receive_sender수신특정 버스 이름에서 오는 메시지만 수신 허용
receive_interface수신특정 인터페이스의 메시지만 수신 허용
own이름 등록특정 잘 알려진 이름의 소유를 허용
own_prefix이름 등록접두사가 일치하는 이름의 소유를 허용 (예: com.example)
user정책 범위<policy user="root"> — 특정 사용자에게만 적용
group정책 범위<policy group="netdev"> — 특정 그룹에게만 적용
context정책 범위"default" (모든 연결) 또는 "mandatory" (최종 결정, 오버라이드 불가)
<!-- /usr/share/dbus-1/system.d/org.freedesktop.NetworkManager.conf -->
<busconfig>
  <!-- NetworkManager가 시스템 버스에서 이름을 소유하도록 허용 -->
  <policy user="root">
    <allow own="org.freedesktop.NetworkManager"/>
  </policy>

  <!-- 모든 사용자가 인트로스펙션과 프로퍼티 읽기 가능 -->
  <policy context="default">
    <allow send_destination="org.freedesktop.NetworkManager"
           send_interface="org.freedesktop.DBus.Introspectable"/>
    <allow send_destination="org.freedesktop.NetworkManager"
           send_interface="org.freedesktop.DBus.Properties"/>
  </policy>

  <!-- netdev 그룹 사용자만 설정 변경 가능 -->
  <policy group="netdev">
    <allow send_destination="org.freedesktop.NetworkManager"
           send_interface="org.freedesktop.NetworkManager"/>
  </policy>
</busconfig>

Polkit 통합

D-Bus 정책이 "이 사용자가 이 메서드를 호출할 수 있는가"를 결정하는 1차 관문이라면, Polkit은 "이 작업에 관리자 인증이 필요한가"를 결정하는 2차 관문입니다.

서비스가 Polkit을 사용하는 흐름:

  1. 클라이언트가 D-Bus 메서드를 호출합니다.
  2. 서비스가 메서드 핸들러(Handler)에서 polkit_authority_check_authorization()을 호출합니다.
  3. Polkit이 .policy 파일과 .rules 파일을 검사하여 허용/거부/인증 요구를 결정합니다.
  4. 인증이 필요하면 Polkit 에이전트(Agent)가 사용자에게 비밀번호 입력 대화상자를 표시합니다.
  5. 인증 성공 시 서비스가 요청을 처리합니다.
# Polkit 액션 목록 확인
pkaction | grep NetworkManager

# 특정 액션의 상세 정보
pkaction --verbose --action-id org.freedesktop.NetworkManager.settings.modify.system

Polkit .policy 파일

.policy 파일은 /usr/share/polkit-1/actions/에 위치하며, 액션(Action)별 기본 권한을 정의합니다.

<!-- /usr/share/polkit-1/actions/org.freedesktop.NetworkManager.policy (요약) -->
<?xml version="1.0" encoding="UTF-8"?>
<policyconfig>
  <action id="org.freedesktop.NetworkManager.settings.modify.system">
    <description>시스템 네트워크 설정 변경</description>
    <message>시스템 네트워크 설정을 변경하려면 인증이 필요합니다</message>
    <defaults>
      <allow_any>auth_admin</allow_any>
      <allow_inactive>auth_admin</allow_inactive>
      <allow_active>auth_admin_keep</allow_active>
    </defaults>
  </action>
</policyconfig>
Polkit 권한 값 설명
  • allow_any 원격 세션을 포함한 모든 클라이언트에 적용되는 기본값입니다.
  • allow_inactive 로컬이지만 비활성(예: VT 전환된) 세션의 클라이언트에 적용됩니다.
  • allow_active 현재 활성 로컬 세션의 클라이언트에 적용됩니다.
  • auth_admin 관리자(Administrator) 비밀번호를 매번 입력해야 합니다.
  • auth_admin_keep 관리자 비밀번호를 입력하면 일정 시간(기본 5분) 동안 재인증 없이 허용됩니다.
  • yes 인증 없이 즉시 허용됩니다 (주의 필요).

Polkit .rules 파일

.rules 파일은 /etc/polkit-1/rules.d/에 JavaScript로 작성합니다. .policy의 기본값을 조건부로 오버라이드(Override)합니다.

// /etc/polkit-1/rules.d/10-networkmanager.rules
// wheel 그룹 사용자에게 NetworkManager 설정 변경을 비밀번호 없이 허용
polkit.addRule(function(action, subject) {
    if (action.id === "org.freedesktop.NetworkManager.settings.modify.system" &&
        subject.isInGroup("wheel")) {
        return polkit.Result.YES;
    }
});

강제 접근 제어(MAC) 통합

SELinux가 활성화된 시스템에서는 D-Bus 메시지 전송·수신에 대해 추가 보안 컨텍스트(Security Context) 검사가 이루어집니다. dbus { send_msg receive_msg } 권한 클래스로 제어합니다.

# SELinux D-Bus 거부 로그 확인
ausearch -m AVC --comm dbus-daemon --recent
# type=AVC msg=audit(...): avc:  denied  { send_msg } for
#   scontext=system_u:system_r:httpd_t:s0
#   tcontext=system_u:system_r:NetworkManager_t:s0
#   tclass=dbus

AppArmor 환경(Ubuntu 등)에서는 D-Bus 프로파일(Profile)에서 dbus senddbus receive 규칙으로 특정 버스 이름·인터페이스·멤버에 대한 접근을 제어합니다.

# AppArmor D-Bus 프로파일 예시 (/etc/apparmor.d/usr.sbin.NetworkManager)
# 시스템 버스에서 이름 바인딩 허용
dbus bind bus=system name=org.freedesktop.NetworkManager,

# systemd로부터의 메서드 호출 수신 허용
dbus receive bus=system
     peer=(label=/usr/lib/systemd/systemd),

# 모든 클라이언트에 시그널 송신 허용
dbus send bus=system
     interface=org.freedesktop.NetworkManager
     member={StateChanged,PropertiesChanged},

# Properties 인터페이스 읽기 허용
dbus send bus=system
     interface=org.freedesktop.DBus.Properties
     member={Get,GetAll},

흔한 D-Bus 보안 실수

실수위험올바른 방법
context="default"에서 send_interface 없이 send_destination만 허용모든 인터페이스의 모든 메서드가 노출됨반드시 send_interface를 함께 지정하여 노출 범위를 제한
own_prefix를 너무 넓게 설정의도하지 않은 버스 이름을 등록할 수 있음own_prefix 대신 own으로 정확한 이름만 허용
Polkit에서 allow_active=yes 남용로컬 로그인 사용자가 인증 없이 관리 작업 수행 가능auth_admin_keep을 사용하고, .rules로 특정 그룹만 허용
UNIX_FD를 받은 후 유효성 검증 없이 사용악의적 fd(예: /dev/kmem)를 전달받을 수 있음받은 fd의 fstat() 결과를 검증하고, 예상 타입이 아니면 거부
시그널에 민감한 데이터 포함시그널은 매칭 규칙만 있으면 누구나 수신 가능민감한 데이터는 메서드 반환값으로만 전달 (Polkit 인증 후)
보안 원칙: D-Bus 정책은 기본 거부(Default Deny)로 작성합니다. 시스템 버스의 기본 정책은 모든 send_destination을 거부하므로, 각 서비스의 .conf 파일에서 필요한 최소한의 접근만 명시적으로 허용해야 합니다.

커널과의 관계(Kernel Relationship)

D-Bus는 사용자 공간(Userspace) 프로토콜이지만, 그 기반은 커널이 제공하는 기능에 깊이 의존합니다.

AF_UNIX 소켓 전송

D-Bus의 기본 전송 계층(Transport Layer)은 AF_UNIX 소켓(SOCK_STREAM)입니다. 커널이 제공하는 다음 기능을 활용합니다:

커널 기능D-Bus 용도
SCM_CREDENTIALS연결 시 상대방의 PID, UID, GID를 커널이 검증하여 전달. D-Bus 정책 검사의 기반
SCM_RIGHTS유닉스 파일 디스크립터(Unix FD)를 프로세스 간 전달. D-Bus 타입 h(UNIX_FD)의 구현 기반
추상 소켓(Abstract Socket)파일 시스템에 존재하지 않는 소켓. 세션 버스에서 네임스페이스(Namespace) 격리에 활용
SO_PEERCRED소켓 옵션으로 연결된 피어(Peer)의 자격 증명(Credentials)을 조회
사용자 공간 (Userspace) 클라이언트 sendmsg(fd, msg) METHOD_CALL 직렬화 dbus-daemon recvmsg → 정책검사 → 라우팅 → sendmsg 서비스 recvmsg(fd, msg) METHOD_CALL 역직렬화 커널 (Kernel) AF_UNIX 소켓 버퍼 클라이언트 ↔ 데몬 AF_UNIX 소켓 버퍼 데몬 ↔ 서비스 SCM_CREDENTIALS: PID, UID, GID 검증 CS #1 CS #2 CS #3 CS #4 CS = 컨텍스트 스위치(Context Switch). 단일 METHOD_CALL → METHOD_RETURN 왕복에 최소 8회 발생 dbus-broker는 epoll 기반으로 CS 오버헤드를 최소화하지만, 데몬 경유 구조 자체는 동일 요청 경로 전달 경로 커널 보안 메커니즘

SCM_CREDENTIALS 상세

D-Bus 데몬이 클라이언트를 식별하는 핵심 메커니즘은 SO_PEERCRED 소켓 옵션과 SCM_CREDENTIALS 보조 메시지(Ancillary Message)입니다.

/* SO_PEERCRED로 연결된 피어의 자격 증명 조회 */
#include <sys/socket.h>

struct ucred cred;
socklen_t len = sizeof(cred);

if (getsockopt(client_fd, SOL_SOCKET, SO_PEERCRED, &cred, &len) == 0) {
    printf("PID=%d, UID=%d, GID=%d\n", cred.pid, cred.uid, cred.gid);
    /* dbus-daemon은 이 정보로 정책의 user=, group= 규칙을 평가 */
}

/* SCM_RIGHTS로 파일 디스크립터 전달 (D-Bus UNIX_FD 타입의 기반) */
struct msghdr msg = { 0 };
struct cmsghdr *cmsg;
char cmsgbuf[CMSG_SPACE(sizeof(int))];

msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);

cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
*((int *)CMSG_DATA(cmsg)) = file_fd;  /* 전달할 fd */

sendmsg(socket_fd, &msg, 0);
/* 수신 측에서 recvmsg()로 fd를 받음 — 커널이 fd 테이블 복제 수행 */
코드 설명
  • SO_PEERCRED 커널이 소켓 연결 시점에 피어의 PID/UID/GID를 기록합니다. 이 값은 위조할 수 없으므로 신뢰할 수 있는 신원 확인(Identity Verification) 수단입니다.
  • SCM_RIGHTS 프로세스의 파일 디스크립터를 다른 프로세스로 전달합니다. 커널이 수신 프로세스의 fd 테이블에 새 항목을 만들어 줍니다. D-Bus의 h 타입이 이 메커니즘을 사용합니다.

strace로 D-Bus 통신 관찰

# dbus-daemon의 소켓 I/O 추적
$ strace -e trace=sendmsg,recvmsg -p $(pidof dbus-daemon) 2>&1 | head -8

recvmsg(12, {
  msg_iov=[{iov_base="l\1\0\1"...  ← 'l'=리틀엔디언, 1=METHOD_CALL
            iov_len=184}],
  msg_control=[{cmsg_level=SOL_SOCKET,
                cmsg_type=SCM_CREDENTIALS,  ← 커널이 자동 첨부
                cmsg_data={pid=3847, uid=1000, gid=1000}}]
}, 0) = 184

sendmsg(15, {  ← fd 15 = 목적지 서비스의 소켓
  msg_iov=[{iov_base="l\1\0\1"...
            iov_len=184}]
}, 0) = 184

cgroup 통합

dbus-broker는 커널의 cgroup(Control Group) 정보를 추가적인 발신자 식별 수단으로 활용합니다. /proc/PID/cgroup을 읽어 발신 프로세스가 속한 systemd 슬라이스(Slice)와 서비스 유닛(Unit)을 파악할 수 있으며, 이를 통해 PID보다 더 안정적인 프로세스 식별이 가능합니다. PID는 재사용(Reuse)될 수 있지만, cgroup 경로는 서비스 수명 동안 유일합니다.

kdbus 역사

kdbus는 D-Bus를 커널 내부에 구현하려는 시도였습니다. 2013년부터 2015년까지 Greg Kroah-Hartman이 주도하여 커널 메인라인(Mainline) 합류를 시도했으나, 여러 이유로 거부되었습니다:

kdbus의 유산으로 Bus1 프로젝트가 탄생했고, 이는 범용 커널 IPC 기반 구조를 지향합니다. 하지만 역시 커널 메인라인에는 합류하지 못했고, dbus-broker의 사용자 공간 최적화가 실질적인 대안이 되었습니다.

컨테이너 환경의 D-Bus(D-Bus in Container Environments)

Docker, Podman, LXC 등 컨테이너 환경에서 D-Bus를 사용하려면 네임스페이스 격리, 소켓 경로, 자격 증명(Credential) 전달 등 여러 문제를 해결해야 합니다. 컨테이너가 호스트의 시스템 버스에 접근하는 방식과 내부에서 독립적인 D-Bus 데몬을 운영하는 방식은 보안과 격리 수준에서 큰 차이를 보입니다.

패턴 A: 호스트 소켓 바인드 마운트 호스트 (Host) dbus-daemon (시스템 인스턴스) systemd NetworkManager firewalld /run/dbus/system_bus_socket 컨테이너 (Container) 컨테이너 프로세스 (App) busctl, systemctl 등 D-Bus 클라이언트 bind mount: -v /run/dbus/...:/run/dbus/... 패턴 B: 컨테이너 내부 독립 데몬 호스트 (Host) D-Bus 연결 없음 (격리) 컨테이너 (Container) dbus-daemon (컨테이너 내부 인스턴스) systemd (PID 1) App 서비스 firewalld /run/dbus/system_bus_socket (컨테이너 로컬) 완전 격리 (Fully Isolated) 바인드 마운트 (보안 위험) 내부 D-Bus 연결 dbus-daemon 보안 경고 영역

네임스페이스 격리(Namespace Isolation)

컨테이너(Container) 환경에서 D-Bus와 리눅스 네임스페이스(Namespace)의 관계를 이해하는 것은 올바른 구성의 전제 조건입니다.

추상 소켓 vs 파일 시스템 소켓: D-Bus 시스템 버스는 일반적으로 파일 시스템 경로 소켓(/run/dbus/system_bus_socket)을 사용하므로 마운트 네임스페이스만 신경 쓰면 됩니다. 반면 세션 버스가 추상 소켓을 사용하는 경우 네트워크 네임스페이스 격리도 장벽이 됩니다.

컨테이너에서 D-Bus가 필요한 이유

대부분의 단순 컨테이너(마이크로서비스, 웹 서버 등)는 D-Bus가 필요하지 않습니다. 그러나 다음과 같은 시나리오에서는 컨테이너 내부에서 D-Bus 통신이 필수적입니다.

시나리오D-Bus 의존 서비스권장 패턴
systemd 기반 컨테이너 (CI/CD, 인프라 테스트)systemd, journald, logind내부 독립 데몬
네트워크 관리 컨테이너NetworkManager, firewalld, resolved내부 독립 데몬
호스트 서비스 모니터링/제어호스트의 systemd1, UPower호스트 소켓 바인드 마운트
데스크톱 애플리케이션 컨테이너화알림 데몬, IBus, PipeWire세션 버스 프록시
하드웨어 관리udisks2, BlueZ, UPower호스트 소켓 바인드 마운트

호스트 소켓 바인드 마운트(Host Socket Bind Mount)

가장 간단한 방법은 호스트의 시스템 버스 소켓을 컨테이너에 바인드 마운트하는 것입니다. 컨테이너 프로세스가 호스트의 dbus-daemon에 직접 연결됩니다.

# 호스트 시스템 버스 소켓을 컨테이너에 공유
docker run -it \
  -v /run/dbus/system_bus_socket:/run/dbus/system_bus_socket \
  ubuntu:24.04 bash

# 컨테이너 내부에서 호스트의 시스템 버스에 접근
apt-get update && apt-get install -y dbus
busctl list --system          # 호스트의 서비스 목록이 보임
busctl tree org.freedesktop.systemd1  # 호스트 systemd 객체 탐색
보안 경고: 호스트 소켓 바인드 마운트는 컨테이너에게 호스트 시스템 버스의 모든 서비스에 대한 접근 권한을 부여합니다. 컨테이너가 org.freedesktop.systemd1.Manager.StartUnit을 호출하여 호스트의 서비스를 시작/중지하거나, org.freedesktop.login1.Manager.PowerOff를 호출하여 호스트를 종료할 수 있습니다. 신뢰할 수 없는 컨테이너에는 절대 사용하지 마십시오.

PID/UID 불일치 문제: 바인드 마운트 환경에서 호스트의 dbus-daemonSO_PEERCRED로 연결 프로세스의 자격 증명을 확인합니다. PID 네임스페이스 사용 시 커널이 호스트 관점의 PID를 반환하므로 D-Bus 정책은 정상 동작하지만, GetConnectionUnixProcessID 같은 API로 조회한 PID는 컨테이너 내부에서 의미가 없습니다.

# 컨테이너 내부에서 자신의 D-Bus 연결 정보 확인
busctl --system call org.freedesktop.DBus /org/freedesktop/DBus \
  org.freedesktop.DBus GetConnectionUnixProcessID s ":1.234"
# → 호스트 PID가 반환됨 (컨테이너 PID와 다름)

컨테이너 내부 독립 D-Bus 데몬

컨테이너가 자체적인 D-Bus 환경이 필요하면서 호스트와 격리되어야 하는 경우, 컨테이너 내부에서 독립적인 dbus-daemon을 실행합니다.

# Dockerfile: 독립 D-Bus 데몬이 있는 컨테이너
FROM ubuntu:24.04

RUN apt-get update && apt-get install -y \
    dbus \
    && rm -rf /var/lib/apt/lists/*

# machine-id 생성 (D-Bus 필수)
RUN dbus-uuidgen --ensure=/etc/machine-id

# 시스템 버스 소켓 디렉터리 생성
RUN mkdir -p /run/dbus

# dbus-daemon을 포그라운드로 실행
CMD ["dbus-daemon", "--system", "--nofork", "--nopidfile"]

여러 프로세스가 필요한 경우 슈퍼바이저(Supervisor) 패턴을 사용합니다.

# entrypoint.sh: dbus-daemon을 백그라운드로 시작 후 주 프로세스 실행
#!/bin/bash
mkdir -p /run/dbus
dbus-uuidgen --ensure=/etc/machine-id
dbus-daemon --system --nofork &
# D-Bus 소켓이 준비될 때까지 대기
while [ ! -S /run/dbus/system_bus_socket ]; do sleep 0.1; done
# 주 애플리케이션 실행
exec "$@"
machine-id 필수: D-Bus는 /etc/machine-id 파일을 요구합니다. 이 파일이 없으면 dbus-daemon이 시작되지 않습니다. dbus-uuidgen --ensure=/etc/machine-id로 생성하거나, /var/lib/dbus/machine-id에 심볼릭 링크(Symlink)를 설정합니다.

systemd 컨테이너와 D-Bus

systemd를 PID 1로 실행하는 컨테이너에서는 dbus.socket 유닛이 자동으로 활성화되어 dbus-daemon(또는 dbus-broker)을 소켓 활성화(Socket Activation) 방식으로 시작합니다.

# systemd를 PID 1로 실행하는 Docker 컨테이너
docker run -d --name systemd-container \
  --privileged \
  --cgroupns=host \
  -v /sys/fs/cgroup:/sys/fs/cgroup:rw \
  --tmpfs /run \
  --tmpfs /run/lock \
  ubuntu-systemd:24.04 /sbin/init

# 컨테이너 내부에서 D-Bus 서비스 확인
docker exec systemd-container busctl list --system

--privileged 플래그는 모든 Linux 기능(Capability)을 부여하므로 보안 위험이 큽니다. 최소 권한 원칙을 따르려면 필요한 기능만 명시적으로 추가합니다.

옵션설명보안 수준
--privileged모든 기능 부여, 장치 접근 허용최저 (비권장)
--cap-add SYS_ADMINcgroup/mount 조작 허용낮음
--cap-add SYS_ADMIN --cap-add NET_ADMINsystemd + 네트워크 서비스낮음
--cap-add SYS_BOOTreboot 시스콜 허용 (systemd 종료용)중간
# 최소 권한으로 systemd 컨테이너 실행 (cgroup v2)
docker run -d --name systemd-min \
  --cap-add SYS_ADMIN \
  --cap-add SYS_BOOT \
  --cgroupns=host \
  --security-opt seccomp=unconfined \
  -v /sys/fs/cgroup:/sys/fs/cgroup:rw \
  --tmpfs /run \
  --tmpfs /run/lock \
  --stop-signal SIGRTMIN+3 \
  ubuntu-systemd:24.04 /sbin/init
cgroup v2 주의: cgroup v2 환경에서 systemd 컨테이너를 실행하려면 --cgroupns=host(또는 private + cgroup 위임)와 /sys/fs/cgroup 마운트가 필요합니다. cgroup이 올바르게 설정되지 않으면 systemd가 dbus.socket을 포함한 유닛 활성화에 실패합니다.

Podman과 Docker 비교

Podman은 데몬리스(Daemonless) 아키텍처와 루트리스(Rootless) 모드로 D-Bus 관련 동작이 Docker와 다릅니다.

기능DockerPodman
systemd 컨테이너수동 설정 필요 (--privileged, tmpfs 등)--systemd=true 자동 구성
루트리스(Rootless)rootless Docker 별도 설치기본 지원 (사용자 네임스페이스)
cgroup 위임--cgroupns=host 수동 설정자동 위임 (systemd --user)
D-Bus 소켓 기본 동작명시적 바인드 마운트 필요--systemd=true 시 자동 구성
컨테이너 관리 유닛별도 systemd 유닛 작성podman generate systemd
세션 버스 접근수동 마운트 ($XDG_RUNTIME_DIR/bus)--systemd=true 시 자동
# Podman: systemd 컨테이너 (자동 D-Bus 구성)
podman run -d --name systemd-pod \
  --systemd=true \
  ubuntu-systemd:24.04 /sbin/init

# Podman이 자동으로 처리하는 항목:
#   - /run을 tmpfs로 마운트
#   - /sys/fs/cgroup 적절한 마운트
#   - SIGRTMIN+3 정지 시그널 설정
#   - 환경 변수 container=podman 설정

# Podman: 호스트 세션 버스를 루트리스 컨테이너에 공유
podman run -it \
  -v $XDG_RUNTIME_DIR/bus:$XDG_RUNTIME_DIR/bus \
  -e DBUS_SESSION_BUS_ADDRESS="unix:path=$XDG_RUNTIME_DIR/bus" \
  ubuntu:24.04 bash
podman generate systemd: podman generate systemd --name systemd-pod 명령으로 컨테이너를 관리하는 systemd 유닛 파일을 자동 생성할 수 있습니다. 이 유닛은 호스트의 systemd/D-Bus를 통해 컨테이너 수명 주기를 관리합니다.

컨테이너 D-Bus 보안(Container D-Bus Security)

호스트 시스템 버스를 컨테이너에 노출하면 공격 표면(Attack Surface)이 크게 확장됩니다. 주요 위험과 완화 방법을 정리합니다.

호스트 버스 노출 시 주요 위험:

완화 전략 1: D-Bus 정책 파일로 접근 제한

<!-- /etc/dbus-1/system.d/container-restrict.conf -->
<!-- 특정 UID(컨테이너 프로세스)의 접근을 제한 -->
<busconfig>
  <policy user="container-user">
    <!-- 기본 거부 -->
    <deny send_destination="*"/>
    <!-- 특정 인터페이스만 허용 -->
    <allow send_destination="org.freedesktop.hostname1"/>
    <allow send_destination="org.freedesktop.timedate1"/>
  </policy>
</busconfig>

완화 전략 2: xdg-dbus-proxy 필터링

# xdg-dbus-proxy로 허용된 인터페이스만 노출
xdg-dbus-proxy \
  unix:path=/run/dbus/system_bus_socket \
  /run/container-dbus-proxy \
  --filter \
  --talk=org.freedesktop.hostname1 \
  --talk=org.freedesktop.timedate1 \
  --call=org.freedesktop.DBus=Hello \
  --call=org.freedesktop.DBus=AddMatch

# 컨테이너는 프록시 소켓을 사용
docker run -it \
  -v /run/container-dbus-proxy:/run/dbus/system_bus_socket \
  ubuntu:24.04 bash

완화 전략 3: AppArmor/SELinux 프로파일

# AppArmor: D-Bus 접근 제한 프로파일
profile container-dbus flags=(attach_disconnected) {
  # D-Bus 시스템 버스 소켓 접근 허용
  /run/dbus/system_bus_socket rw,

  # 특정 D-Bus 대상만 허용
  dbus (send) bus=system peer=(name=org.freedesktop.hostname1),
  dbus (receive) bus=system peer=(name=org.freedesktop.hostname1),

  # 나머지 D-Bus 통신 거부
  deny dbus bus=system,
}
# SELinux: 컨테이너의 D-Bus 소켓 접근 제어
# container_t 도메인에서 system_dbusd_t로의 통신 허용
allow container_t system_dbusd_t:unix_stream_socket connectto;
allow container_t system_dbusd_var_run_t:sock_file write;
최선의 방법: 가능하면 호스트 소켓을 직접 노출하지 않고, xdg-dbus-proxy를 중간에 배치하여 필요한 인터페이스만 화이트리스트(Whitelist) 방식으로 허용하는 것이 가장 안전합니다. 독립 데몬 패턴은 격리 수준이 가장 높지만, 호스트 서비스에 접근해야 하는 경우에는 사용할 수 없습니다.

주요 D-Bus 서비스(Major D-Bus Services)

현대 리눅스 시스템에서 시스템 버스를 사용하는 주요 서비스들입니다.

서비스버스 이름주 객체 경로용도
systemdorg.freedesktop.systemd1/org/freedesktop/systemd1유닛 관리(시작/중지/재시작), 상태 조회
logindorg.freedesktop.login1/org/freedesktop/login1세션·시트(Seat)·사용자 관리, 전원 제어
NetworkManagerorg.freedesktop.NetworkManager/org/freedesktop/NetworkManager네트워크 연결 관리, Wi-Fi, VPN
BlueZorg.bluez/org/bluez블루투스(Bluetooth) 어댑터·장치 관리
udisks2org.freedesktop.UDisks2/org/freedesktop/UDisks2디스크·파티션·파일 시스템 관리
UPowerorg.freedesktop.UPower/org/freedesktop/UPower전원 장치(배터리) 상태 모니터링
resolvedorg.freedesktop.resolve1/org/freedesktop/resolve1DNS 이름 해석(Resolution) 관리
timesyncdorg.freedesktop.timesync1/org/freedesktop/timesync1NTP 시간 동기화
firewalldorg.fedoraproject.FirewallD1/org/fedoraproject/FirewallD1방화벽 존·규칙 관리 (상세)
PipeWireorg.freedesktop.impl.portal.ScreenCast오디오·비디오 라우팅 (Portal 인터페이스)
# 시스템 버스의 모든 서비스 목록
busctl list --system

# 특정 서비스의 객체 트리
busctl tree org.freedesktop.systemd1

# systemd 버전 프로퍼티 조회
busctl get-property org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.systemd1.Manager Version

# NetworkManager 상태 조회
busctl call org.freedesktop.NetworkManager \
  /org/freedesktop/NetworkManager \
  org.freedesktop.DBus.Properties Get ss \
  org.freedesktop.NetworkManager State

D-Bus 도구(Tools)

도구 비교

도구패키지특징권장 용도
busctlsystemd가장 현대적. 트리 탐색, 인트로스펙션, 호출, 모니터링, pcap 캡처 지원일상 디버깅, 시스템 관리 (권장)
dbus-senddbus경량. 시스템/세션 버스 호출. 출력이 간결셸 스크립트, 간단한 호출
dbus-monitordbus실시간 메시지 모니터링. 필터 표현식 지원메시지 흐름 디버깅
gdbusglib2GLib 기반. 인트로스펙션, 호출, 모니터링GNOME/GTK 환경 디버깅
D-Feetd-feetGUI 브라우저. 서비스/객체/인터페이스 탐색시각적 API 탐색

busctl 사용법

# 시스템 버스의 모든 서비스 나열
busctl list --system

# 서비스의 객체 트리 표시
busctl tree org.freedesktop.systemd1

# 특정 객체의 인터페이스·메서드·프로퍼티 조회
busctl introspect org.freedesktop.systemd1 /org/freedesktop/systemd1

# 메서드 호출 (인자 타입과 값 전달)
busctl call org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.systemd1.Manager \
  StartUnit ss "sshd.service" "replace"

# 프로퍼티 읽기
busctl get-property org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.systemd1.Manager Version

# 실시간 모니터링
busctl monitor org.freedesktop.NetworkManager

# pcap 형식으로 캡처 (Wireshark로 분석 가능)
busctl capture org.freedesktop.systemd1 > dbus-capture.pcap

dbus-send 사용법

# systemd에서 유닛 시작
dbus-send --system --print-reply --dest=org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.systemd1.Manager.StartUnit \
  string:"sshd.service" string:"replace"

# 프로퍼티 읽기
dbus-send --system --print-reply --dest=org.freedesktop.systemd1 \
  /org/freedesktop/systemd1 \
  org.freedesktop.DBus.Properties.Get \
  string:"org.freedesktop.systemd1.Manager" string:"Version"

# 세션 버스에서 데스크톱 알림 보내기
dbus-send --session --print-reply --dest=org.freedesktop.Notifications \
  /org/freedesktop/Notifications \
  org.freedesktop.Notifications.Notify \
  string:"test" uint32:0 string:"" string:"D-Bus 테스트" \
  string:"이것은 D-Bus 알림입니다" array:string:"" dict:string:string:"" int32:5000

dbus-monitor 사용법

# 시스템 버스의 모든 메시지 모니터링
dbus-monitor --system

# 특정 인터페이스의 시그널만 필터링
dbus-monitor --system "type='signal',interface='org.freedesktop.NetworkManager'"

# 특정 서비스로 향하는 메서드 호출만 필터링
dbus-monitor --system "type='method_call',destination='org.freedesktop.systemd1'"

# PropertiesChanged 시그널 모니터링
dbus-monitor --system "type='signal',member='PropertiesChanged'"

프로그래밍 인터페이스(Programming Interfaces)

D-Bus 프로그래밍 라이브러리는 여러 가지가 있으며, 용도에 따라 선택합니다.

라이브러리언어특징권장 대상
sd-busCsystemd 내장. 현대적 API, 비동기 지원, 커널 크레덴셜 검증 통합시스템 서비스, 데몬
GDBus (GIO)CGLib/GObject 기반. 코드 생성기(gdbus-codegen) 지원GNOME/GTK 애플리케이션
dbus-pythonPythonlibdbus 래핑(Wrapping). GLib 메인 루프 필요프로토타이핑, 스크립트
dasbusPythonGDBus 기반 현대적 Python 바인딩Python 3 애플리케이션
libdbusC원래의 참조 구현. 저수준(Low-level), 직접 사용 비권장다른 바인딩의 기반

sd-bus 예제: 클라이언트(메서드 호출)

#include <stdio.h>
#include <systemd/sd-bus.h>

int main(void) {
    sd_bus *bus = NULL;
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = NULL;
    const char *version;
    int r;

    /* 시스템 버스에 연결 */
    r = sd_bus_open_system(&bus);
    if (r < 0) {
        fprintf(stderr, "버스 연결 실패: %s\n", strerror(-r));
        return 1;
    }

    /* systemd Manager의 Version 프로퍼티 읽기 */
    r = sd_bus_get_property(
        bus,
        "org.freedesktop.systemd1",           /* 버스 이름 */
        "/org/freedesktop/systemd1",           /* 객체 경로 */
        "org.freedesktop.systemd1.Manager",    /* 인터페이스 */
        "Version",                              /* 프로퍼티 이름 */
        &error,
        &reply,
        "s"                                      /* 프로퍼티 타입 */
    );
    if (r < 0) {
        fprintf(stderr, "프로퍼티 읽기 실패: %s\n", error.message);
        goto finish;
    }

    r = sd_bus_message_read(reply, "s", &version);
    if (r < 0) {
        fprintf(stderr, "메시지 파싱 실패: %s\n", strerror(-r));
        goto finish;
    }

    printf("systemd version: %s\n", version);

finish:
    sd_bus_error_free(&error);
    sd_bus_message_unref(reply);
    sd_bus_unref(bus);
    return r < 0 ? 1 : 0;
}
코드 설명
  • 12행 sd_bus_open_system()은 시스템 버스(/run/dbus/system_bus_socket)에 연결합니다. 세션 버스는 sd_bus_open_user()를 사용합니다.
  • 19-30행 sd_bus_get_property()는 내부적으로 org.freedesktop.DBus.Properties.Get 메서드를 호출합니다. 마지막 인자 "s"는 반환값이 문자열임을 나타냅니다.
  • 36행 sd_bus_message_read()로 응답 메시지의 본문에서 데이터를 추출합니다. 시그니처 "s"에 맞춰 문자열 포인터가 설정됩니다.
  • 43-45행 sd-bus는 수동 리소스 해제가 필요합니다. __attribute__((cleanup))을 활용한 자동 해제 매크로도 제공됩니다.
# 컴파일
gcc -o dbus-version dbus-version.c $(pkg-config --cflags --libs libsystemd)

sd-bus 예제: 서비스(메서드 노출)

#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>

/* 메서드 핸들러: Greeting 메서드 */
static int method_greeting(
        sd_bus_message *msg,
        void *userdata,
        sd_bus_error *ret_error) {
    const char *name;
    int r;

    r = sd_bus_message_read(msg, "s", &name);
    if (r < 0)
        return r;

    char buf[256];
    snprintf(buf, sizeof(buf), "안녕하세요, %s!", name);

    return sd_bus_reply_method_return(msg, "s", buf);
}

/* 인터페이스 vtable 정의 */
static const sd_bus_vtable example_vtable[] = {
    SD_BUS_VTABLE_START(0),
    SD_BUS_METHOD("Greeting", "s", "s", method_greeting,
                 SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_VTABLE_END,
};

int main(void) {
    sd_bus *bus = NULL;
    sd_bus_slot *slot = NULL;
    int r;

    r = sd_bus_open_user(&bus);
    if (r < 0) return 1;

    /* vtable을 객체 경로에 등록 */
    r = sd_bus_add_object_vtable(
        bus, &slot,
        "/com/example/Greeter",       /* 객체 경로 */
        "com.example.Greeter",         /* 인터페이스 이름 */
        example_vtable,
        NULL
    );
    if (r < 0) return 1;

    /* 잘 알려진 이름 요청 */
    r = sd_bus_request_name(bus, "com.example.Greeter", 0);
    if (r < 0) return 1;

    /* 이벤트 루프 */
    for (;;) {
        r = sd_bus_process(bus, NULL);
        if (r < 0) break;
        if (r > 0) continue;

        r = sd_bus_wait(bus, (uint64_t)-1);
        if (r < 0) break;
    }

    sd_bus_slot_unref(slot);
    sd_bus_unref(bus);
    return 0;
}
코드 설명
  • 6-21행 method_greeting은 D-Bus 메서드 호출을 처리하는 콜백(Callback)입니다. 입력으로 문자열("s")을 받고, 문자열("s")을 반환합니다.
  • 24-29행 sd_bus_vtable은 인터페이스의 메서드·프로퍼티·시그널을 선언적으로 정의합니다. SD_BUS_VTABLE_UNPRIVILEGED는 누구나 호출 가능하다는 의미입니다.
  • 40-47행 sd_bus_add_object_vtable()로 vtable을 특정 객체 경로와 인터페이스에 연결합니다. 해당 경로로 메서드 호출이 오면 자동으로 매칭됩니다.
  • 50행 sd_bus_request_name()으로 잘 알려진 이름을 등록합니다. 이후 다른 프로세스가 이 이름으로 메서드를 호출할 수 있습니다.
  • 53-59행 sd_bus_process()로 대기 중인 메시지를 처리하고, sd_bus_wait()로 새 메시지를 기다립니다. 실전에서는 sd-event 이벤트 루프와 통합합니다.
# 서비스를 빌드하고 실행
gcc -o greeter-service greeter-service.c $(pkg-config --cflags --libs libsystemd)
./greeter-service &

# 다른 터미널에서 호출
busctl --user call com.example.Greeter \
  /com/example/Greeter \
  com.example.Greeter \
  Greeting s "세계"
# 출력: s "안녕하세요, 세계!"

Python 예제

import dbus

# 시스템 버스에 연결
bus = dbus.SystemBus()

# NetworkManager 프록시 객체 획득
nm_proxy = bus.get_object(
    'org.freedesktop.NetworkManager',
    '/org/freedesktop/NetworkManager'
)

# 인터페이스를 통한 메서드 호출
nm_iface = dbus.Interface(nm_proxy, 'org.freedesktop.NetworkManager')
devices = nm_iface.GetDevices()

# Properties 인터페이스로 프로퍼티 읽기
props_iface = dbus.Interface(nm_proxy, 'org.freedesktop.DBus.Properties')
state = props_iface.Get('org.freedesktop.NetworkManager', 'State')

print(f"NetworkManager 상태: {state}")
print(f"장치 수: {len(devices)}")
for dev_path in devices:
    dev_proxy = bus.get_object('org.freedesktop.NetworkManager', dev_path)
    dev_props = dbus.Interface(dev_proxy, 'org.freedesktop.DBus.Properties')
    iface_name = dev_props.Get('org.freedesktop.NetworkManager.Device', 'Interface')
    print(f"  장치: {iface_name} ({dev_path})")
# 시그널 구독 예제 (GLib 이벤트 루프 필요)
import dbus
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib

DBusGMainLoop(set_as_default=True)
bus = dbus.SystemBus()

def on_properties_changed(interface, changed, invalidated):
    print(f"인터페이스: {interface}")
    for key, value in changed.items():
        print(f"  변경: {key} = {value}")

bus.add_signal_receiver(
    on_properties_changed,
    signal_name='PropertiesChanged',
    dbus_interface='org.freedesktop.DBus.Properties',
    bus_name='org.freedesktop.NetworkManager'
)

loop = GLib.MainLoop()
print("프로퍼티 변경 모니터링 중... (Ctrl+C로 종료)")
loop.run()

성능과 대안(Performance & Alternatives)

D-Bus 오버헤드 분석

D-Bus의 성능 오버헤드(Overhead)는 다음 요소에서 발생합니다:

메시지 지연 벤치마크

다음은 D-Bus 메서드 호출의 왕복 지연(Round-trip Latency) 근사치입니다. 실제 값은 하드웨어, 커널 버전, 부하(Load)에 따라 다릅니다.

시나리오dbus-daemondbus-broker직접 Unix 소켓
빈 메서드 호출 (인자 없음)~50-80 µs~20-35 µs~5-10 µs
1 KB 페이로드(Payload)~60-100 µs~25-45 µs~8-15 µs
64 KB 페이로드~200-400 µs~80-150 µs~30-60 µs
시그널 브로드캐스트 (10개 구독자)~200-500 µs~50-100 µs— (직접 구현 필요)
프로퍼티 Get (단일 값)~50-80 µs~20-35 µs— (프로토콜 없음)

dbus-daemon vs dbus-broker 성능

항목dbus-daemondbus-broker
메시지 처리량기준~3-10배 향상
지연 시간기준~50% 감소
메모리 사용기준유사하거나 약간 적음
활성화 방식자체 fork/execsystemd에 위임
정책 파싱런타임 XML 파싱사전 컴파일된 정책
매칭 규칙 처리선형 검색(Linear Scan)인덱싱(Indexing) 기반 — 구독자 수에 무관한 성능
메시지 정렬글로벌 순서 보장글로벌 순서 보장 + 커널 타임스탬프 활용

메시지 크기별 처리량

메시지 본문 크기dbus-daemon (msg/sec)dbus-broker (msg/sec)주요 병목
0 B (빈 메서드)~10,000-15,000~50,000-80,000컨텍스트 스위치, 정책 평가
64 B~9,000-14,000~45,000-70,000컨텍스트 스위치
1 KB~7,000-10,000~30,000-50,000직렬화 + 소켓 버퍼 복사
16 KB~2,000-4,000~10,000-20,000메모리 복사 지배적
64 KB~500-1,000~3,000-5,000소켓 버퍼 크기 제한
256 KB~150-300~800-1,500메시지 분할 + 재조립(Fragmentation)

UNIX_FD를 이용한 대용량 데이터 전달

대용량 데이터를 D-Bus 메시지 본문에 직렬화하면 성능이 급격히 저하됩니다. 대신 memfd_create()로 공유 메모리(Shared Memory)를 만들고, 그 파일 디스크립터(UNIX_FD)를 D-Bus로 전달하면 제로카피(Zero-copy)에 가까운 성능을 얻을 수 있습니다.

# 패턴: 대용량 데이터를 fd로 전달하는 서비스 설계
# 1. 서비스가 memfd_create()로 익명 파일 생성
# 2. 데이터를 memfd에 write()
# 3. fd를 D-Bus METHOD_RETURN의 'h' 타입으로 반환
# 4. 클라이언트가 fd를 받아 mmap()으로 직접 접근

# 실제 예: PipeWire는 오디오/비디오 버퍼를 memfd+fd-passing으로 전달
# 실제 예: Flatpak Portal은 파일 선택 결과를 fd로 전달

비동기 배치 호출

sd_bus_call_async()를 사용하면 여러 메서드 호출을 응답을 기다리지 않고 연속으로 전송할 수 있습니다. 응답은 이벤트 루프(Event Loop)에서 콜백(Callback)으로 처리됩니다. 이 방식은 단일 호출의 지연 시간을 줄이지는 않지만, 여러 호출의 총 소요 시간을 크게 단축합니다.

D-Bus 대안 기술

기술특징D-Bus 대비 장점D-Bus 대비 단점
VarlinkJSON 기반 IPC, Unix 소켓 직접 연결데몬 없음(오버헤드 ↓), 간단한 프로토콜서비스 발견·활성화 없음
gRPCProtocol Buffers, HTTP/2 기반고성능, 코드 생성, 언어 중립시스템 서비스 통합 미흡, 무거움
직접 Unix 소켓커스텀 프로토콜최소 오버헤드, 최대 유연성서비스 발견·보안·인트로스펙션 직접 구현
BinderAndroid IPC, 커널 드라이버 기반제로카피, 낮은 지연Linux 데스크톱/서버 미지원
AF_VSOCK호스트-게스트 IPCVM 경계를 넘는 통신범용 IPC 용도가 아님
IPC 기술 선택 서비스 발견/자동 활성화 필요? Yes D-Bus No 스키마 기반 API 정의/코드 생성 필요? Yes gRPC No 고처리량/대용량 데이터 전송? Yes 직접 소켓 / 공유 메모리 No 호스트 ↔ 게스트 VM 통신? Yes AF_VSOCK No Varlink systemd/NM/BlueZ 통합 마이크로서비스, 언어 중립 memfd + mmap, 스트리밍 virtio-vsock 드라이버 간단한 JSON IPC (systemd 도구류)
선택 기준: 시스템 서비스 간 통합이 필요하면 D-Bus가 사실상 표준(De facto Standard)입니다. 고성능 데이터 전송이 필요하면 Varlink이나 직접 소켓을, 마이크로서비스(Microservice) 아키텍처에서는 gRPC를 고려합니다. 하나의 서비스에서 D-Bus(제어 채널)와 직접 소켓(데이터 채널)을 병행 사용하는 것도 일반적입니다 — PipeWire가 이 패턴의 대표적인 예입니다.

디버깅(Debugging)

자주 발생하는 오류

오류 메시지원인해결 방법
org.freedesktop.DBus.Error.ServiceUnknown요청한 버스 이름을 소유한 서비스가 없음서비스 실행 여부 확인, .service 파일 존재 확인
org.freedesktop.DBus.Error.AccessDeniedD-Bus 정책에 의한 거부해당 서비스의 .conf 정책 파일 확인
org.freedesktop.DBus.Error.UnknownMethod존재하지 않는 메서드 호출busctl introspect로 실제 메서드 이름 확인
org.freedesktop.DBus.Error.InvalidArgs메서드 인자의 타입이나 개수가 잘못됨인트로스펙션으로 정확한 시그니처 확인
org.freedesktop.DBus.Error.TimedOut서비스가 응답하지 않음 (기본 25초)서비스 상태 확인, journalctl -u 서비스로 로그 확인
org.freedesktop.DBus.Error.NoReply서비스가 응답 전에 종료됨서비스 크래시 여부 확인, coredump 분석

디버깅 기법

# 1. 서비스 존재 여부 확인
busctl list --system | grep NetworkManager

# 2. 서비스의 프로세스 정보 확인
busctl status org.freedesktop.NetworkManager

# 3. 실시간 메시지 추적
busctl monitor org.freedesktop.NetworkManager

# 4. D-Bus 데몬 자체의 통계
busctl call org.freedesktop.DBus /org/freedesktop/DBus \
  org.freedesktop.DBus.Debug.Stats GetStats

# 5. journalctl로 D-Bus 관련 로그 확인
journalctl -u dbus.service --since "5 minutes ago"

# 6. strace로 소켓 통신 추적
strace -e trace=sendmsg,recvmsg -p $(pidof dbus-daemon) 2>&1 | head -50

# 7. dbus-daemon 상세 로그 활성화
# /usr/share/dbus-1/system.conf에서 syslog="true" 설정 후:
DBUS_VERBOSE=1 dbus-daemon --system --nofork

디버깅용 환경 변수

환경 변수효과예시
DBUS_VERBOSE=1dbus-daemon의 상세 디버그 로그 출력DBUS_VERBOSE=1 dbus-daemon --session --nofork
DBUS_SESSION_BUS_ADDRESS세션 버스 소켓 주소를 명시적으로 지정unix:path=/run/user/1000/bus
DBUS_SYSTEM_BUS_ADDRESS시스템 버스 소켓 주소를 명시적으로 지정unix:path=/run/dbus/system_bus_socket
G_DBUS_DEBUG=allGLib/GDBus 라이브러리의 디버그 로그 출력G_DBUS_DEBUG=message,payload,address
SYSTEMD_LOG_LEVEL=debugsd-bus 관련 systemd 라이브러리의 디버그 로그SYSTEMD_LOG_LEVEL=debug busctl call ...

실전 트러블슈팅 시나리오

시나리오 1: 서비스가 시작되지 않음 (ServiceUnknown)

# 1단계: 서비스가 버스에 등록되어 있는지 확인
busctl list --system | grep "원하는.서비스.이름"

# 2단계: D-Bus .service 파일이 존재하는지 확인
ls /usr/share/dbus-1/system-services/ | grep "원하는.서비스"

# 3단계: systemd 유닛 상태 확인
systemctl status 해당-서비스.service
journalctl -u 해당-서비스.service --since "5 min ago" --no-pager

# 4단계: 수동으로 서비스 시작 시도
systemctl start 해당-서비스.service
# 실패하면 journalctl에서 원인 확인

시나리오 2: 권한 거부 (AccessDenied)

# 1단계: 호출자의 UID/GID 확인
id

# 2단계: 해당 서비스의 D-Bus 정책 파일 찾기
ls /usr/share/dbus-1/system.d/ /etc/dbus-1/system.d/ | grep "서비스이름"

# 3단계: 정책 파일에서 허용 규칙 확인
grep -A5 'send_destination.*서비스이름' /usr/share/dbus-1/system.d/서비스이름.conf

# 4단계: SELinux AVC 거부 여부 확인 (RHEL/Fedora)
ausearch -m AVC --recent | grep dbus

# 5단계: AppArmor 거부 여부 확인 (Ubuntu/SUSE)
journalctl -k | grep DENIED | grep dbus

시나리오 3: 메서드 호출 타임아웃 (TimedOut)

# 1단계: 서비스 프로세스가 살아있는지 확인
busctl status org.서비스.이름

# 2단계: 서비스의 CPU/메모리 상태 확인 (이벤트 루프 블로킹?)
top -p $(busctl status org.서비스.이름 | grep PID | awk '{print $NF}')

# 3단계: 더 긴 타임아웃으로 재시도
busctl call --timeout=60 org.서비스.이름 /경로 인터페이스 메서드

# 4단계: 서비스 로그에서 블로킹 원인 확인
journalctl -u 서비스.service --since "2 min ago" | tail -30

journalctl D-Bus 필터링 패턴

# dbus-daemon 로그
journalctl -u dbus.service --since "10 min ago"

# dbus-broker 로그 (Fedora/RHEL 8+)
journalctl -u dbus-broker.service --since "10 min ago"

# 특정 서비스의 D-Bus 관련 로그만 필터링
journalctl -u NetworkManager --grep "dbus\|DBus\|bus" --since "10 min ago"

# 커널 감사(Audit) 로그에서 D-Bus 관련 항목
journalctl -k --grep "dbus\|avc.*dbus" --since "10 min ago"
디버깅 팁:
  • busctl call --expect-reply=no: 응답을 기다리지 않는 fire-and-forget 호출로, 서비스의 메서드 수신 여부만 확인할 때 유용합니다.
  • busctl call --timeout=N: 타임아웃을 초 단위로 조절합니다. 기본값은 25초입니다.
  • busctl --user vs --system: 세션 버스와 시스템 버스를 명시적으로 구분하여 테스트합니다. 잘못된 버스에 연결하는 실수가 의외로 잦습니다.

Wireshark를 이용한 D-Bus 분석

busctl capture로 생성한 pcap 파일을 Wireshark에서 분석할 수 있습니다. Wireshark는 D-Bus 프로토콜 디섹터(Dissector)를 내장하고 있어, 메시지 헤더와 본문을 구조적으로 볼 수 있습니다.

# 시스템 버스의 특정 서비스 메시지를 캡처
busctl capture --system org.freedesktop.NetworkManager > nm-dbus.pcap

# Wireshark로 열기
wireshark nm-dbus.pcap

D-Bus 프로토콜 상세(Protocol Details)

D-Bus 프로토콜은 바이너리 메시지(Binary Message) 형식으로 정의되며, 각 메시지는 고정 헤더(Fixed Header), 헤더 필드 배열(Header Fields Array), 본문(Body)으로 구성됩니다.

메시지 고정 헤더

모든 D-Bus 메시지는 12바이트 또는 16바이트의 고정 헤더로 시작합니다.

오프셋크기필드설명
01Endianness'l' = 리틀엔디언, 'B' = 빅엔디언
11Message Type1=METHOD_CALL, 2=METHOD_RETURN, 3=ERROR, 4=SIGNAL
21Flags비트 0: NO_REPLY_EXPECTED, 비트 1: NO_AUTO_START
31Protocol Version현재 항상 1
44Body Length본문의 바이트 길이 (0이면 본문 없음)
84Serial발신자가 부여한 메시지 고유 번호 (응답 매칭에 사용)

헤더 필드 상세

고정 헤더 뒤에 가변 길이 헤더 필드 배열이 따릅니다. 각 필드는 (코드, VARIANT) 형태입니다.

코드필드 이름필수 여부타입설명
1PATHMETHOD_CALL, SIGNALOBJECT_PATH목적지 객체 경로 (/org/freedesktop/NetworkManager)
2INTERFACESIGNAL (METHOD_CALL 권장)STRING인터페이스 이름 (org.freedesktop.DBus.Properties)
3MEMBERMETHOD_CALL, SIGNALSTRING메서드/시그널 이름 (Get, PropertiesChanged)
4ERROR_NAMEERRORSTRING오류 이름 (org.freedesktop.DBus.Error.ServiceUnknown)
5REPLY_SERIALMETHOD_RETURN, ERRORUINT32원본 METHOD_CALL의 Serial 번호
6DESTINATION선택STRING수신자 버스 이름 (유니크 또는 잘 알려진)
7SENDER데몬이 추가STRING발신자 유니크 이름 (데몬이 자동 설정)
8SIGNATURE본문이 있을 때SIGNATURE본문의 타입 시그니처 ("sa{sv}")
9UNIX_FDSFD 전달 시UINT32SCM_RIGHTS로 전달된 파일 디스크립터 수

메시지 타입별 동작

클라이언트 데몬 서비스 정상 호출 METHOD_CALL (serial=1) METHOD_CALL (라우팅) METHOD_RETURN (reply_serial=1) METHOD_RETURN 오류 응답 METHOD_CALL (serial=2) ERROR (reply_serial=2) ERROR: ServiceUnknown 시그널 SIGNAL (브로드캐스트) SIGNAL (매칭 규칙 일치 시) METHOD_CALL METHOD_RETURN ERROR SIGNAL Serial 번호로 요청-응답 매칭. SIGNAL은 응답 없음 (단방향 브로드캐스트)

D-Bus 타입 시스템 상세(Type System Details)

D-Bus의 타입 시스템은 시그니처 문자열(Signature String)로 표현되며, 기본 타입(Basic Type)과 컨테이너 타입(Container Type)으로 구분됩니다.

기본 타입 상세

시그니처타입 이름크기(바이트)정렬설명C 대응 타입
yBYTE11부호 없는 8비트 정수uint8_t
bBOOLEAN440=FALSE, 1=TRUE (UINT32로 마샬링)int
nINT1622부호 있는 16비트 정수int16_t
qUINT1622부호 없는 16비트 정수uint16_t
iINT3244부호 있는 32비트 정수int32_t
uUINT3244부호 없는 32비트 정수uint32_t
xINT6488부호 있는 64비트 정수int64_t
tUINT6488부호 없는 64비트 정수uint64_t
dDOUBLE88IEEE 754 배정밀도 부동소수점double
sSTRING가변4UTF-8 문자열 (길이 접두사 + NUL 종료)const char *
oOBJECT_PATH가변4객체 경로 형식의 문자열const char *
gSIGNATURE가변1타입 시그니처 문자열 (최대 255바이트)const char *
hUNIX_FD44파일 디스크립터 인덱스 (SCM_RIGHTS)int

컨테이너 타입과 시그니처

시그니처타입설명예시
a + 타입ARRAY동일 타입 요소의 배열as = 문자열 배열, ai = INT32 배열
(...)STRUCT고정 타입 요소의 튜플(si) = 문자열+INT32, (ssi) = 3개 필드
a{KV}DICT키-값 배열 (키는 기본 타입만)a{sv} = 문자열→VARIANT 사전
vVARIANT임의 타입 값 (시그니처 포함)PropertiesChanged 시그널의 값
# 시그니처 예시와 busctl 출력

# a{sv}: Properties.GetAll의 반환 타입
busctl call org.freedesktop.NetworkManager \
    /org/freedesktop/NetworkManager \
    org.freedesktop.DBus.Properties GetAll s \
    "org.freedesktop.NetworkManager"
# → a{sv} 23 "Version" s "1.44.2" "State" u 70 ...

# (ssa{sv}): 인트로스펙션 XML의 구조적 표현
# s = 인터페이스 이름
# s = 멤버 이름
# a{sv} = 속성 사전

# VARIANT의 시그니처 확인
busctl get-property org.freedesktop.NetworkManager \
    /org/freedesktop/NetworkManager \
    org.freedesktop.NetworkManager State
# → u 70  (u = UINT32, 70 = NM_STATE_CONNECTED_GLOBAL)

마샬링(Marshaling) 규칙

D-Bus 마샬링은 메모리 정렬(Alignment)을 엄격히 적용합니다. 각 타입은 자신의 정렬 크기의 배수 오프셋에서 시작해야 하며, 필요한 경우 NUL 패딩(Padding)이 삽입됩니다.

/* sd-bus를 이용한 마샬링 예제 */
#include <systemd/sd-bus.h>

/* a{sv} 사전 마샬링 */
sd_bus_message *msg;
sd_bus_message_new_method_call(bus, &msg,
    "org.example.Service",
    "/org/example/Object",
    "org.example.Interface",
    "Configure");

/* 사전 열기: a{sv} */
sd_bus_message_open_container(msg, 'a', "{sv}");

/* 첫 번째 항목: "Name" → "test" */
sd_bus_message_open_container(msg, 'e', "sv");
sd_bus_message_append(msg, "s", "Name");
sd_bus_message_open_container(msg, 'v', "s");
sd_bus_message_append(msg, "s", "test");
sd_bus_message_close_container(msg);  /* v */
sd_bus_message_close_container(msg);  /* e */

/* 두 번째 항목: "Port" → 8080 */
sd_bus_message_open_container(msg, 'e', "sv");
sd_bus_message_append(msg, "s", "Port");
sd_bus_message_open_container(msg, 'v', "u");
sd_bus_message_append(msg, "u", 8080);
sd_bus_message_close_container(msg);  /* v */
sd_bus_message_close_container(msg);  /* e */

sd_bus_message_close_container(msg);  /* a */

/* 메시지 전송 */
sd_bus_call(bus, msg, 0, &error, &reply);

이름 소유권(Name Ownership)

D-Bus에서 서비스를 식별하는 두 가지 이름 체계가 있습니다.

유니크 이름과 잘 알려진 이름

구분유니크 이름(Unique Name)잘 알려진 이름(Well-Known Name)
형식:1.42 (콜론으로 시작)org.freedesktop.NetworkManager (역도메인)
할당데몬이 연결 시 자동 부여프로세스가 명시적으로 요청
수명연결 해제 시 소멸소유자가 해제하거나 연결 해제 시 소멸
고유성버스 내에서 항상 유일한 번에 하나의 소유자만 가능
용도내부 라우팅, 발신자 식별서비스 발견, 클라이언트 접근

이름 큐잉(Name Queuing)

잘 알려진 이름에 대해 여러 프로세스가 소유를 요청하면 큐(Queue)에 대기합니다. 현재 소유자가 해제하면 큐의 다음 대기자가 자동으로 소유권을 획득합니다.

# 이름 소유 요청 (RequestName)
busctl call org.freedesktop.DBus /org/freedesktop/DBus \
    org.freedesktop.DBus RequestName su \
    "org.example.MyService" 0

# 반환 코드:
# 1 = DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER (즉시 획득)
# 2 = DBUS_REQUEST_NAME_REPLY_IN_QUEUE (큐에 대기)
# 3 = DBUS_REQUEST_NAME_REPLY_EXISTS (이미 다른 소유자, 큐잉 미요청)
# 4 = DBUS_REQUEST_NAME_REPLY_ALREADY_OWNER (이미 자신이 소유)

# 현재 이름 소유자 확인
busctl call org.freedesktop.DBus /org/freedesktop/DBus \
    org.freedesktop.DBus GetNameOwner s \
    "org.freedesktop.NetworkManager"
# → s ":1.6"

# 이름의 큐 상태 확인
busctl call org.freedesktop.DBus /org/freedesktop/DBus \
    org.freedesktop.DBus ListQueuedOwners s \
    "org.freedesktop.NetworkManager"
# → as 1 ":1.6"

# 이름 소유권 변경 시그널 감시
busctl monitor --match "type='signal',sender='org.freedesktop.DBus',member='NameOwnerChanged'"
/* sd-bus에서 이름 소유 요청 */
#include <systemd/sd-bus.h>

int r;

/* 이름 요청: DBUS_NAME_FLAG_REPLACE_EXISTING | DBUS_NAME_FLAG_ALLOW_REPLACEMENT */
r = sd_bus_request_name(bus,
    "org.example.MyService",
    SD_BUS_NAME_REPLACE_EXISTING | SD_BUS_NAME_ALLOW_REPLACEMENT);

if (r < 0) {
    fprintf(stderr, "이름 요청 실패: %s\n", strerror(-r));
    return r;
}
/* r > 0이면 소유 성공 */

systemd 연동(systemd Integration)

systemd는 D-Bus를 핵심 통신 채널로 사용합니다. systemd-logind, systemd-resolved, systemd-networkd 등 주요 서비스가 D-Bus 인터페이스를 노출합니다.

D-Bus 서비스 활성화

D-Bus 활성화(Bus Activation)는 서비스가 아직 실행되지 않았을 때 첫 메서드 호출 시 자동으로 서비스를 시작하는 메커니즘입니다.

# D-Bus 서비스 파일
# /usr/share/dbus-1/system-services/org.example.MyService.service
[D-BUS Service]
Name=org.example.MyService
Exec=/usr/bin/my-service
User=root
SystemdService=my-service.service

# 대응하는 systemd 유닛 파일
# /etc/systemd/system/my-service.service
[Unit]
Description=My D-Bus Service
After=dbus.socket

[Service]
Type=dbus
BusName=org.example.MyService
ExecStart=/usr/bin/my-service

[Install]
WantedBy=multi-user.target
Alias=dbus-org.example.MyService.service

systemd-logind D-Bus API 실전

# 현재 세션 목록
busctl call org.freedesktop.login1 \
    /org/freedesktop/login1 \
    org.freedesktop.login1.Manager \
    ListSessions
# → a(susso) 2 1 1000 "user" "/org/freedesktop/login1/session/_31" ...

# 시스템 전원 관리
busctl call org.freedesktop.login1 \
    /org/freedesktop/login1 \
    org.freedesktop.login1.Manager \
    PowerOff b true
# → Polkit 인증 요청 발생

# Inhibitor Lock (전원 관리 방지)
busctl call org.freedesktop.login1 \
    /org/freedesktop/login1 \
    org.freedesktop.login1.Manager \
    Inhibit ssss \
    "shutdown:sleep" "MyApp" "작업 진행 중" "delay"
# → h 0  (파일 디스크립터 반환 — 닫으면 잠금 해제)

# PrepareForShutdown 시그널 모니터링
busctl monitor org.freedesktop.login1 \
    --match "interface='org.freedesktop.login1.Manager',member='PrepareForShutdown'"

sd-bus 서비스 작성 완전 예제

/* sd-bus를 이용한 완전한 D-Bus 서비스 예제 */
#include <stdio.h>
#include <stdlib.h>
#include <systemd/sd-bus.h>
#include <systemd/sd-event.h>

static int counter = 0;

/* 메서드 핸들러: Increment */
static int method_increment(sd_bus_message *msg,
                             void *userdata,
                             sd_bus_error *error)
{
    int32_t delta;
    int r;

    r = sd_bus_message_read(msg, "i", &delta);
    if (r < 0)
        return r;

    counter += delta;

    /* PropertiesChanged 시그널 발행 */
    sd_bus_emit_properties_changed(
        sd_bus_message_get_bus(msg),
        "/org/example/Counter",
        "org.example.Counter",
        "Value", NULL);

    return sd_bus_reply_method_return(msg, "i", counter);
}

/* 프로퍼티 getter: Value */
static int property_get_value(sd_bus *bus,
    const char *path, const char *iface,
    const char *property, sd_bus_message *reply,
    void *userdata, sd_bus_error *error)
{
    return sd_bus_message_append(reply, "i", counter);
}

/* 인터페이스 vtable */
static const sd_bus_vtable counter_vtable[] = {
    SD_BUS_VTABLE_START(0),
    SD_BUS_METHOD("Increment", "i", "i",
        method_increment, SD_BUS_VTABLE_UNPRIVILEGED),
    SD_BUS_PROPERTY("Value", "i",
        property_get_value, 0,
        SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
    SD_BUS_SIGNAL("Overflow", "i", 0),
    SD_BUS_VTABLE_END,
};

int main(void) {
    sd_bus *bus = NULL;
    sd_bus_slot *slot = NULL;

    sd_bus_open_system(&bus);

    /* 객체에 인터페이스 등록 */
    sd_bus_add_object_vtable(bus, &slot,
        "/org/example/Counter",
        "org.example.Counter",
        counter_vtable, NULL);

    /* 버스 이름 요청 */
    sd_bus_request_name(bus,
        "org.example.Counter", 0);

    /* 이벤트 루프 실행 */
    for (;;) {
        sd_bus_process(bus, NULL);
        sd_bus_wait(bus, (uint64_t)-1);
    }

    sd_bus_slot_unref(slot);
    sd_bus_unref(bus);
    return 0;
}
코드 설명
  • SD_BUS_VTABLE — vtable은 인터페이스의 메서드, 프로퍼티, 시그널을 선언적으로 정의합니다. sd-bus가 인트로스펙션 XML을 자동 생성하고, 타입 검증과 디스패치를 처리합니다.
  • SD_BUS_VTABLE_UNPRIVILEGED — 이 플래그가 있으면 비특권 사용자도 호출할 수 있습니다. 없으면 root 또는 동일 UID만 호출 가능합니다.
  • PROPERTY_EMITS_CHANGE — 프로퍼티 값이 바뀌면 sd_bus_emit_properties_changed() 호출 시 PropertiesChanged 시그널을 발행합니다.
  • sd_bus_process + sd_bus_wait — 이벤트 루프입니다. process()가 대기 중인 메시지를 처리하고, wait()가 새 메시지를 기다립니다. 프로덕션에서는 sd_event와 통합하는 것이 좋습니다.

컨테이너 환경(Container Environment Details)

컨테이너에서 D-Bus를 사용하는 패턴과 보안 고려 사항을 상세히 다룹니다.

컨테이너 D-Bus 접근 패턴

패턴방법보안 수준용도
소켓 바인드 마운트 -v /run/dbus/system_bus_socket:/run/dbus/system_bus_socket 낮음 (호스트 버스 직접 접근) 호스트 서비스(NM, logind) 제어
독립 데몬 컨테이너 내부에 별도 dbus-daemon 실행 높음 (격리) 컨테이너 내부 IPC
xdg-dbus-proxy Flatpak 프록시로 특정 버스 이름만 허용 중간 (필터링) Flatpak/Snap 앱
소켓 활성화 systemd 소켓 유닛으로 컨테이너 내 서비스 활성화 중간 On-demand 컨테이너
# 패턴 1: 호스트 시스템 버스 바인드 마운트
podman run --rm -it \
    -v /run/dbus/system_bus_socket:/run/dbus/system_bus_socket:ro \
    my-container busctl list
# ⚠ 호스트의 모든 D-Bus 서비스에 접근 가능 — 보안 위험

# 패턴 2: xdg-dbus-proxy로 필터링
xdg-dbus-proxy \
    unix:path=/run/dbus/system_bus_socket \
    /run/container-dbus-socket \
    --filter \
    --talk=org.freedesktop.NetworkManager \
    --see=org.freedesktop.login1
# --talk: 메서드 호출과 시그널 수신 허용
# --see: 이름 존재 확인만 허용 (호출 불가)

# 패턴 3: 컨테이너 내부 독립 D-Bus
# Dockerfile에서:
# RUN dbus-uuidgen > /var/lib/dbus/machine-id
# CMD ["dbus-daemon", "--system", "--fork"] && exec my-service

# 패턴 4: Kubernetes에서 D-Bus 소켓 공유 (hostPath)
# volumes:
#   - name: dbus-socket
#     hostPath:
#       path: /run/dbus/system_bus_socket
#       type: Socket

커널 측 IPC 최적화(Kernel IPC Optimization)

D-Bus 성능은 커널의 AF_UNIX 소켓 구현에 크게 의존합니다. 커널 측 최적화 기법과 dbus-broker의 성능 향상 전략을 살펴봅니다.

AF_UNIX 소켓 최적화

커널 파라미터기본값D-Bus 영향튜닝 가이드
net.core.wmem_max212992큰 메시지 전송 시 버퍼 부족대용량 FD 전달 서비스는 증가 고려
net.core.rmem_max212992메시지 수신 버퍼busctl capture 시 증가 필요
net.unix.max_dgram_qlen10SOCK_DGRAM 큐 길이D-Bus는 SOCK_STREAM 사용 (무관)
kernel.unprivileged_userns_clone배포판별 상이컨테이너 네임스페이스 격리보안과 기능 간 균형
dbus-daemon (전통) XML 정책 파서 select()/poll() 이벤트 메시지 복사 + 직렬화/역직렬화 선형 매칭 규칙 검색 O(n) 단일 스레드, recvmsg→정책→sendmsg 메시지당 최소 2회 컨텍스트 스위치 dbus-broker (최적화) 사전 컴파일된 정책 epoll + 이벤트 배치 제로카피 메시지 전달 (가능 시) 해시 기반 매칭 규칙 O(1) 단일 스레드, epoll 기반 배치 처리 메시지 처리량 2~5배 향상 vs

성능 비교 측정

# dbus-broker vs dbus-daemon 벤치마크
# dbus-send를 이용한 간단한 왕복 시간 측정

# 1000회 Ping 호출 시간 측정
time for i in $(seq 1 1000); do
    busctl call org.freedesktop.DBus \
        /org/freedesktop/DBus \
        org.freedesktop.DBus.Peer Ping >/dev/null
done

# perf로 dbus-broker 핫스팟 분석
perf record -g -p $(pidof dbus-broker) -- sleep 10
perf report --sort dso,symbol

# strace로 시스템 콜 통계
strace -c -p $(pidof dbus-broker) -e trace=sendmsg,recvmsg,epoll_wait -- sleep 5

# busctl monitor로 메시지 속도 관찰
busctl monitor --system 2>&1 | pv -l -i 5 > /dev/null
# → 초당 처리 메시지 수 확인

실전 예제 종합(Practical Examples)

gdbus 명령어 예제

# gdbus (GLib 기반 D-Bus 도구)

# 인트로스펙션
gdbus introspect --system \
    --dest org.freedesktop.NetworkManager \
    --object-path /org/freedesktop/NetworkManager

# 메서드 호출
gdbus call --system \
    --dest org.freedesktop.NetworkManager \
    --object-path /org/freedesktop/NetworkManager \
    --method org.freedesktop.DBus.Properties.Get \
    "org.freedesktop.NetworkManager" "State"
# → (<uint32 70>,)

# 시그널 모니터링
gdbus monitor --system \
    --dest org.freedesktop.NetworkManager \
    --object-path /org/freedesktop/NetworkManager

Python D-Bus 예제

#!/usr/bin/env python3
# pydbus를 이용한 D-Bus 클라이언트
from pydbus import SystemBus
from gi.repository import GLib

bus = SystemBus()

# NetworkManager 프록시 객체 생성
nm = bus.get("org.freedesktop.NetworkManager")

# 프로퍼티 읽기
print(f"NM Version: {nm.Version}")
print(f"NM State: {nm.State}")

# 메서드 호출: 모든 디바이스 경로 가져오기
devices = nm.GetDevices()
for dev_path in devices:
    dev = bus.get("org.freedesktop.NetworkManager", dev_path)
    print(f"  {dev.Interface}: type={dev.DeviceType}, state={dev.State}")

# 시그널 수신 (비동기)
def on_state_changed(state):
    states = {10: "ASLEEP", 20: "DISCONNECTED",
              70: "CONNECTED_GLOBAL"}
    print(f"NM state → {states.get(state, state)}")

nm.StateChanged.connect(on_state_changed)

# GLib 이벤트 루프 실행
loop = GLib.MainLoop()
try:
    loop.run()
except KeyboardInterrupt:
    loop.quit()

C 클라이언트 예제

/* sd-bus를 이용한 D-Bus 클라이언트 — NM 상태 조회 */
#include <stdio.h>
#include <systemd/sd-bus.h>

int main(void) {
    sd_bus *bus = NULL;
    sd_bus_error error = SD_BUS_ERROR_NULL;
    sd_bus_message *reply = NULL;
    uint32_t state;
    int r;

    r = sd_bus_open_system(&bus);
    if (r < 0) {
        fprintf(stderr, "버스 연결 실패: %s\n", strerror(-r));
        return 1;
    }

    /* Properties.Get 호출 */
    r = sd_bus_get_property(bus,
        "org.freedesktop.NetworkManager",
        "/org/freedesktop/NetworkManager",
        "org.freedesktop.NetworkManager",
        "State",
        &error, &reply, "u");

    if (r < 0) {
        fprintf(stderr, "프로퍼티 조회 실패: %s\n",
                error.message);
        goto finish;
    }

    sd_bus_message_read(reply, "u", &state);
    printf("NetworkManager State: %u\n", state);

finish:
    sd_bus_error_free(&error);
    sd_bus_message_unref(reply);
    sd_bus_unref(bus);
    return r < 0 ? 1 : 0;
}
빌드 방법:
gcc -o dbus-client dbus-client.c $(pkg-config --cflags --libs libsystemd)
./dbus-client
# → NetworkManager State: 70

매칭 규칙(Match Rules) 상세

D-Bus 매칭 규칙은 클라이언트가 수신할 시그널이나 메시지를 필터링하는 데 사용됩니다. AddMatch 메서드로 등록합니다.

매칭 키설명예시
type메시지 타입type='signal'
sender발신자 버스 이름sender='org.freedesktop.NetworkManager'
interface인터페이스 이름interface='org.freedesktop.DBus.Properties'
member메서드/시그널 이름member='PropertiesChanged'
path객체 경로 (정확 일치)path='/org/freedesktop/NetworkManager'
path_namespace객체 경로 접두사path_namespace='/org/freedesktop'
destination수신자 버스 이름destination=':1.42'
arg0~arg63본문의 N번째 인자값arg0='org.freedesktop.NetworkManager'
arg0namespace첫 인자의 접두사 매칭arg0namespace='org.freedesktop'
eavesdrop모든 메시지 수신 (특권)eavesdrop='true'
# busctl monitor에서 매칭 규칙 사용

# NetworkManager의 PropertiesChanged 시그널만 수신
busctl monitor --match "type='signal',\
sender='org.freedesktop.NetworkManager',\
interface='org.freedesktop.DBus.Properties',\
member='PropertiesChanged'"

# 특정 객체 경로 하위의 모든 시그널
busctl monitor --match "type='signal',\
path_namespace='/org/freedesktop/NetworkManager/Devices'"

# dbus-monitor 동등 표현
dbus-monitor --system "type='signal',\
sender='org.freedesktop.login1',\
member='PrepareForShutdown'"

표준 D-Bus 오류 이름

오류 이름의미일반적 원인
org.freedesktop.DBus.Error.ServiceUnknown목적지 서비스가 존재하지 않음서비스 미실행, 이름 오타
org.freedesktop.DBus.Error.UnknownMethod메서드가 존재하지 않음인터페이스/메서드 이름 오류
org.freedesktop.DBus.Error.UnknownInterface인터페이스가 존재하지 않음인터페이스 이름 오류
org.freedesktop.DBus.Error.UnknownObject객체 경로가 존재하지 않음경로 오류
org.freedesktop.DBus.Error.AccessDenied정책에 의해 거부됨D-Bus 정책 파일 미설정
org.freedesktop.DBus.Error.InvalidArgs인자 타입/수 불일치시그니처 불일치
org.freedesktop.DBus.Error.Timeout응답 타임아웃 (기본 25초)서비스 블로킹, 과부하
org.freedesktop.DBus.Error.NoReply서비스가 응답 없이 종료서비스 크래시
org.freedesktop.DBus.Error.NameHasNoOwner이름에 소유자 없음서비스 미실행

참고 자료

다음 학습:
  • IPC (Inter-Process Communication) — Unix 도메인 소켓, pipe, System V IPC 등 커널 IPC 메커니즘 전반
  • systemd — D-Bus를 핵심 통신 채널로 사용하는 시스템·서비스 관리자
  • firewalld — D-Bus API를 통한 동적 방화벽 관리 실전 예제