Skip to content
40 changes: 31 additions & 9 deletions dbus_next/message_bus.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ def __init__(self,
# buffer messages until connect
self._buffered_messages = []
self._serial = 0
self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM | socket.SOCK_NONBLOCK)
self._stream = self._sock.makefile('rwb')
self._fd = self._sock.fileno()
self._user_message_handlers = []
# the key is the name and the value is the unique name of the owner.
# This cache is kept up to date by the NameOwnerChanged signal and is
Expand Down Expand Up @@ -503,23 +500,48 @@ def _setup_socket(self):

for transport, options in self._bus_address:
filename = None
ip_addr = ''
ip_port = 0

if transport == 'unix':
self._sock = socket.socket(socket.AF_UNIX,
socket.SOCK_STREAM | socket.SOCK_NONBLOCK)
self._stream = self._sock.makefile('rwb')
self._fd = self._sock.fileno()

if 'path' in options:
filename = options['path']
elif 'abstract' in options:
filename = f'\0{options["abstract"]}'
else:
raise InvalidAddressError('got unix transport with unknown path specifier')

try:
self._sock.connect(filename)
break
except Exception as e:
err = e

elif transport == 'tcp':
self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._stream = self._sock.makefile('rwb')
self._fd = self._sock.fileno()

if 'host' in options:
ip_addr = options['host']
if 'port' in options:
ip_port = int(options['port'])

try:
self._sock.connect((ip_addr, ip_port))
self._sock.setblocking(False)
break
except Exception as e:
err = e

else:
raise InvalidAddressError(f'got unknown address transport: {transport}')

try:
self._sock.connect(filename)
break
except Exception as e:
err = e

if err:
raise err

Expand Down
33 changes: 33 additions & 0 deletions examples/aio-tcp-notification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env python3

# In order for this to work a local tcp connection to the DBus a port
# must be opened to forward to the dbus socket file. The easiest way
# to achieve this is using "socat":
# socat TCP-LISTEN:55556,reuseaddr,fork,range=127.0.0.1/32 UNIX-CONNECT:$(echo $DBUS_SESSION_BUS_ADDRESS | sed 's/unix:path=//g')
# For actual DBus transport over network the authentication might
# be a further problem. More information here:
# https://dbus.freedesktop.org/doc/dbus-specification.html#auth-mechanisms

import sys
import os
sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/..'))

from dbus_next.aio import MessageBus

import asyncio

loop = asyncio.get_event_loop()


async def main():
bus = await MessageBus(bus_address="tcp:host=127.0.0.1,port=55556").connect()
introspection = await bus.introspect('org.freedesktop.Notifications',
'/org/freedesktop/Notifications')
obj = bus.get_proxy_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications',
introspection)
notification = obj.get_interface('org.freedesktop.Notifications')
await notification.call_notify("test.py", 0, "", "DBus Test", "Test notification", [""], dict(),
5000)


loop.run_until_complete(main())
4 changes: 4 additions & 0 deletions test/test_address_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ def test_valid_addresses():
})],
'unix:escaped=hello%20world': [('unix', {
'escaped': 'hello world'
})],
'tcp:host=127.0.0.1,port=55556': [('tcp', {
'host': '127.0.0.1',
'port': '55556'
})]
}

Expand Down
12 changes: 12 additions & 0 deletions test/test_aio_low_level.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from dbus_next.aio import MessageBus
from dbus_next import Message, MessageType, MessageFlag

import asyncio
import pytest


Expand Down Expand Up @@ -136,3 +137,14 @@ def message_handler(signal):
assert signal.member == 'SomeSignal'
assert signal.signature == 's'
assert signal.body == ['a signal']


@pytest.mark.asyncio
async def test_connection_via_tcp():
server = await asyncio.start_server(None, '127.0.0.1', 55556)
bus = MessageBus(bus_address="tcp:host=127.0.0.1,port=55556")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you call connect() to send a message across?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically yes. You can specify the callback function and receive the "AUTH EXTERNAL ..." which can be replied with "OK" but then the system got stock (probably due to a lack of the Hello message).

But thats not ultimately necessary to unit test this function because the proof of a network connection seams good enough for me.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd really like to see a message sent across the connection and successful auth annonymous. #54 for example will add assumptions that the socket is unix that would break this feature and have these tests still pass.

For an example of how to add dbus configuration to the test suites to listen on a tcp address, see the playerctl dockerfile and test suite which is based on this project.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the point and I tried to get it working locally - but it did not work before the weekend ended. Somehow the session.conf line order fragility, systemd and even apparmor joined forces to prevent any feeling of success 😞

Maybe I am also not clever enough or at least not experienced enough in DBus/Docker for this. If you don't want to merge it, it's not a problem either because my company can live with my fork as well. The possibility of a TCP connection is all they needed - I just thought it would be nice to upstream it for other users and to ensure a future for this feature.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok then this might be a bit more complicated. I can merge it with an issue to create tests if it's a big task. How exactly are you using it? The usage example you gave did some kind of socket forwarding.

assert bus._sock.getpeername()[0] == '127.0.0.1'
assert bus._sock.getsockname()[0] == '127.0.0.1'
assert bus._sock.gettimeout() == 0
assert bus._stream.closed is False
server.close()