OpenVPN / openvpn3-linux

OpenVPN 3 Linux client
GNU Affero General Public License v3.0
547 stars 142 forks source link

unstable dbus events (gdbus) (v18) #129

Closed jkotra closed 2 years ago

jkotra commented 2 years ago

Issue

I listen to StatusChange signal from net.openvpn.v3.sessions and reply accordingly, since v18 update, no events are being emitted most of the time. once in 10-20 tries, (2 4 Username/password credentials needed) gets emitted but nothing other then/after that.

Demo

from gi.repository import Gio, GLib

def sub_callback(connection, sender_name, object_path, interface_name, signal_name, parameters):
    status = GLib.Variant("(uus)", parameters)
    major = status.get_child_value(0).get_uint32()
    minor = status.get_child_value(1).get_uint32()
    reason = status.get_child_value(2).get_string()
    print("{} {} {}".format(major, minor, reason))

conn = Gio.bus_get_sync(Gio.BusType.SYSTEM, None)

conn.signal_subscribe("net.openvpn.v3.sessions",
                    None,
                    "StatusChange",
                    None,
                    None,
                    Gio.DBusSignalFlags.NONE,
                    sub_callback)

loop = GLib.MainLoop()
loop.run()
dsommers commented 2 years ago

Yes, this is expected. With v18_beta several signals are no longer broadcasted as it was before. Session specific signals now needs to be be "subscribed" to, where part of this process the request is going through a proper ACL check. In v17_beta and older, anyone could catch Log and StatusChange events. As of v18_beta, only users owning the session or granted access (via openvpn3 session-acl) will receive these signals after requesting it.

You need to call the net.openvpn.v3.sessions.LogForward method with the true boolean to enable Log and StatusChange events, which is then tied to a specific session. This needs to be done for all sessions you want to receive log signals from.

What is being broadcasted still is the SessionManagerEvent which gives just a very basic information that a session is created and started or disconnected and destroyed.

A very simple and naïve example, using the openvpn3 Python module:

import dbus
from dbus.mainloop.glib import DBusGMainLoop
from gi.repository import GLib
import openvpn3
from openvpn3.constants import SessionManagerEventType
import time

# Set up GLib/D-Bus event loops and connect
mainloop = GLib.MainLoop()
dbusloop = DBusGMainLoop(set_as_default=True)
sysbus = dbus.SystemBus(mainloop=dbusloop)

# Callback functions for log events and status manager events
def log_event_cb(group, catg, msg):
    print("{} {} {}".format(group, catg, msg))

def statusmgr_event_cb(event):
    global session
    print(str(event))
    if event.GetType() == SessionManagerEventType.SESS_CREATED:
        # Hack to allow the client backend to settle and initialize
        time.sleep(1)
        session = smgr.Retrieve(event.GetPath())
        session.LogCallback(log_event_cb)
    elif event.GetType() == SessionManagerEventType.SESS_DESTROYED:
        session.LogCallback(None)

# Prepare Session Manager connection and activate callback functions
smgr = openvpn3.SessionManager(sysbus)
smgr.SessionManagerCallback(statusmgr_event_cb)

# Listen and handle events
mainloop.run()

Consider this a rough example, and if you want to capture log events from multiple VPN sessions - the global session need to be done more clever to register all session objects in a container with a lookup. The time.sleep(1) hack should also be avoided by rather having a loop with proper exception handling. But the advantage of the openvpn3 module is that it does all the proper LogForward calls in the LogCallback() setup method.

jkotra commented 2 years ago

thanks @dsommers :+1: .

dsommers commented 2 years ago

Nice implementation you have there in eOVPN, @jkotra! Wonderful to see the D-Bus API being used by other client front-ends.

I have one remark though. You should ideally use the openpvn3.constants instead of hard-coding the various status codes. Those constants are not guaranteed to be stable between different versions, even though we try to avoid changing it without a good reason.

dsommers commented 2 years ago

Looking deeper

jkotra commented 2 years ago

Thanks for the comprehensive code review 🤗

I will work on them this week.

jkotra commented 2 years ago

Hard coded StatusMajor and StatusMinor values - also here; I will consider to package a development package which can be used when compiling external projects - which provides a header file with these values.

I got an Unconventional (?) Idea, how about exposing this enum as dbus method returning {us} (kv pair/dict)?

To get these constants from python lib inside flatpak container, i had to do some tricks that are prone to failure in older distros.

Possible solutions:

(willing/intrested to work on this feature)