From f374b671bfe1a11e9c8fe74efe896469be4fde66 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Thu, 31 Oct 2019 14:55:22 +0100 Subject: [PATCH 1/8] Test: Replace EXTERNAL auth with ANONYMOUS auth. --- dbus_next/_private/auth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dbus_next/_private/auth.py b/dbus_next/_private/auth.py index 40ed2c1..2e34e06 100644 --- a/dbus_next/_private/auth.py +++ b/dbus_next/_private/auth.py @@ -12,7 +12,8 @@ class AuthResponse(enum.Enum): def auth_external(): hex_uid = str(os.getuid()).encode().hex() - return f'AUTH EXTERNAL {hex_uid}\r\n'.encode() + return f'AUTH ANONYMOUS\r\n'.encode() + #return f'AUTH EXTERNAL {hex_uid}\r\n'.encode() def auth_begin(): From ad6b7461ebd68be82ba2c290c623a038f301f75d Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Thu, 31 Oct 2019 14:58:37 +0100 Subject: [PATCH 2/8] Support network (tcp) connections with the path "tcp:host=[ip],port=[port]". Not yet working yet: asyncio gets stuck in permanent loop. --- dbus_next/message_bus.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/dbus_next/message_bus.py b/dbus_next/message_bus.py index d20e371..39dff9d 100644 --- a/dbus_next/message_bus.py +++ b/dbus_next/message_bus.py @@ -54,9 +54,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 = [] self._name_owners = {} self._path_exports = {} @@ -427,23 +424,46 @@ 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)) + 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 From 9eaa471eb5f516cdc0475195d832bf2fe1453425 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Fri, 15 Nov 2019 08:47:26 +0100 Subject: [PATCH 3/8] Communication must be nonblocking. Must be called after connect, results in an error otherwise. DBus over TCP/IP now working. --- dbus_next/message_bus.py | 1 + 1 file changed, 1 insertion(+) diff --git a/dbus_next/message_bus.py b/dbus_next/message_bus.py index 39dff9d..e44a5e2 100644 --- a/dbus_next/message_bus.py +++ b/dbus_next/message_bus.py @@ -457,6 +457,7 @@ def _setup_socket(self): try: self._sock.connect((ip_addr, ip_port)) + self._sock.setblocking(False) break except Exception as e: err = e From abd9035f7de18955f44fbd473d39cb6d0e87dba0 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Tue, 26 Nov 2019 10:27:24 +0100 Subject: [PATCH 4/8] Use external authentification in local use cases and anonymous authentification for dbus-via-tcp. --- dbus_next/_private/auth.py | 6 +++--- dbus_next/aio/message_bus.py | 9 +++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/dbus_next/_private/auth.py b/dbus_next/_private/auth.py index 2e34e06..eaa828b 100644 --- a/dbus_next/_private/auth.py +++ b/dbus_next/_private/auth.py @@ -9,12 +9,12 @@ class AuthResponse(enum.Enum): ERROR = 'ERROR' AGREE_UNIX_FD = 'AGREE_UNIX_FD' +def auth_anonymous(): + return f'AUTH ANONYMOUS\r\n'.encode() def auth_external(): hex_uid = str(os.getuid()).encode().hex() - return f'AUTH ANONYMOUS\r\n'.encode() - #return f'AUTH EXTERNAL {hex_uid}\r\n'.encode() - + return f'AUTH EXTERNAL {hex_uid}\r\n'.encode() def auth_begin(): return b'BEGIN\r\n' diff --git a/dbus_next/aio/message_bus.py b/dbus_next/aio/message_bus.py index b57b63e..04fe090 100644 --- a/dbus_next/aio/message_bus.py +++ b/dbus_next/aio/message_bus.py @@ -2,7 +2,7 @@ from .._private.unmarshaller import Unmarshaller from ..message import Message from ..constants import BusType, NameFlag, RequestNameReply, ReleaseNameReply, MessageType, MessageFlag -from .._private.auth import auth_external, auth_parse_line, auth_begin, AuthResponse +from .._private.auth import auth_external, auth_anonymous, auth_parse_line, auth_begin, AuthResponse from ..errors import AuthError, DBusError from .proxy_object import ProxyObject from .. import introspection as intr @@ -10,6 +10,7 @@ import logging import asyncio import traceback +import socket from typing import Optional @@ -54,7 +55,11 @@ async def connect(self) -> 'MessageBus': future = self._loop.create_future() await self._loop.sock_sendall(self._sock, b'\0') - await self._loop.sock_sendall(self._sock, auth_external()) + # external authentification does not work on TCP connections + if self._sock.family == socket.AF_INET: + await self._loop.sock_sendall(self._sock, auth_anonymous()) + else: + await self._loop.sock_sendall(self._sock, auth_external()) response, args = auth_parse_line(await self._auth_readline()) if response != AuthResponse.OK: From 62e1c7c566eefbae41b50194720d8396b4bf1fb7 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Tue, 25 Aug 2020 15:18:46 +0200 Subject: [PATCH 5/8] message_bus.py: Remove whitespaces from empty lines. (lint) --- dbus_next/message_bus.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/dbus_next/message_bus.py b/dbus_next/message_bus.py index 68f0b6e..517e0b7 100644 --- a/dbus_next/message_bus.py +++ b/dbus_next/message_bus.py @@ -507,37 +507,37 @@ def _setup_socket(self): 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}') From d8a85d89b3596fdd430112196fcd65550014b174 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Tue, 25 Aug 2020 15:34:16 +0200 Subject: [PATCH 6/8] Fix another formatting issue. --- dbus_next/message_bus.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dbus_next/message_bus.py b/dbus_next/message_bus.py index 517e0b7..7d9ca27 100644 --- a/dbus_next/message_bus.py +++ b/dbus_next/message_bus.py @@ -504,7 +504,8 @@ def _setup_socket(self): ip_port = 0 if transport == 'unix': - self._sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM | socket.SOCK_NONBLOCK) + self._sock = socket.socket(socket.AF_UNIX, + socket.SOCK_STREAM | socket.SOCK_NONBLOCK) self._stream = self._sock.makefile('rwb') self._fd = self._sock.fileno() From f743031c2161a752a139b0950bebab294c2a0c85 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Sun, 30 Aug 2020 19:13:16 +0200 Subject: [PATCH 7/8] Added two tests to verify the functionality of DBus via TCP: - Add a check to the test_valid_addresses using a tcp address - Added test_connection_via_tcp to the aio low level tests. A local network socket is opened with asyncio and the MessageBus is initialized which calls setup_socket. Afterwards its asserted that the network connection was successful and properly configured Added a simple example to try out the DBus via TCP functionality. --- examples/aio-tcp-notification.py | 33 ++++++++++++++++++++++++++++++++ test/test_address_parser.py | 4 ++++ test/test_aio_low_level.py | 12 ++++++++++++ 3 files changed, 49 insertions(+) create mode 100755 examples/aio-tcp-notification.py diff --git a/examples/aio-tcp-notification.py b/examples/aio-tcp-notification.py new file mode 100755 index 0000000..357c6ff --- /dev/null +++ b/examples/aio-tcp-notification.py @@ -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()) diff --git a/test/test_address_parser.py b/test/test_address_parser.py index 3991024..5d63c3f 100644 --- a/test/test_address_parser.py +++ b/test/test_address_parser.py @@ -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' })] } diff --git a/test/test_aio_low_level.py b/test/test_aio_low_level.py index 1042a1a..e8ee4e0 100644 --- a/test/test_aio_low_level.py +++ b/test/test_aio_low_level.py @@ -1,6 +1,7 @@ from dbus_next.aio import MessageBus from dbus_next import Message, MessageType, MessageFlag +import asyncio import pytest @@ -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") + assert bus._sock.getpeername()[0] == '127.0.0.1' + assert bus._sock.getsockname()[0] == '127.0.0.1' + assert bus._sock.getblocking() is False + assert bus._stream.closed is False + server.close() From 48d8a3c0266a60d95f885b1a817a1b5121609807 Mon Sep 17 00:00:00 2001 From: Martin Babutzka Date: Sun, 30 Aug 2020 19:17:26 +0200 Subject: [PATCH 8/8] Fix: getblocking is not available prior python 3.7. --- test/test_aio_low_level.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_aio_low_level.py b/test/test_aio_low_level.py index e8ee4e0..f855e26 100644 --- a/test/test_aio_low_level.py +++ b/test/test_aio_low_level.py @@ -145,6 +145,6 @@ async def test_connection_via_tcp(): bus = MessageBus(bus_address="tcp:host=127.0.0.1,port=55556") assert bus._sock.getpeername()[0] == '127.0.0.1' assert bus._sock.getsockname()[0] == '127.0.0.1' - assert bus._sock.getblocking() is False + assert bus._sock.gettimeout() == 0 assert bus._stream.closed is False server.close()