From 8644d2c4c78775b4f0951fa273edf60cd653243c Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Thu, 6 Oct 2016 07:43:52 +0000 Subject: [PATCH] #1332: add a "list-mdns" subcommand for showing mdns sessions git-svn-id: https://xpra.org/svn/Xpra/trunk@14011 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/man/xpra.1 | 5 ++ src/xpra/net/mdns/avahi_listener.py | 117 ++++++++++++++++++++++++++++ src/xpra/scripts/main.py | 83 +++++++++++++++++++- 3 files changed, 204 insertions(+), 1 deletion(-) create mode 100755 src/xpra/net/mdns/avahi_listener.py diff --git a/src/man/xpra.1 b/src/man/xpra.1 index 9b7815bb28..26beb2985a 100644 --- a/src/man/xpra.1 +++ b/src/man/xpra.1 @@ -246,6 +246,8 @@ xpra \- viewer for remote, persistent X applications .HP \fBxpra\fP \fBlist\fP [\fB\-\-socket\-dir\fP=\fIDIR\fP] .HP +\fBxpra\fP \fBlist-mdns\fP +.HP \fBxpra\fP \fBupgrade\fP \fI:[DISPLAY]\fP [...any options accepted by \fBxpra start\fP...] .PD @@ -312,6 +314,9 @@ appear until you attach with \fBxpra attach\fP. \fBxpra list\fP Show a list of xpra servers you have running on the current host. .TP +\fBxpra\fP \fBlist-mdns\fP +Show a list of xpra servers found via mDNS. (local network) +.TP \fBxpra showconfig\fP Shows the configuration that would be used with other sub-commands, taking into account the command line arguments. diff --git a/src/xpra/net/mdns/avahi_listener.py b/src/xpra/net/mdns/avahi_listener.py new file mode 100755 index 0000000000..eda3a5995d --- /dev/null +++ b/src/xpra/net/mdns/avahi_listener.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +# This file is part of Xpra. +# Copyright (C) 2009-2016 Antoine Martin +# Xpra is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +import avahi +import dbus + +from xpra.net.mdns import XPRA_MDNS_TYPE +from xpra.dbus.common import init_system_bus +from xpra.dbus.helper import dbus_to_native + +from xpra.log import Logger +log = Logger("network", "mdns") + + +class AvahiListener: + + def __init__(self, service_type, mdns_found=None, mdns_add=None, mdns_remove=None): + log("AvahiListener%s", (service_type, mdns_found, mdns_add, mdns_remove)) + try: + self.bus = init_system_bus() + assert self.bus + except Exception as e: + log.warn("failed to connect to the system dbus: %s", e) + log.warn(" either start a dbus session or disable mdns support") + return + self.sdref = None + self.readers = [] + self.resolvers = [] + self.service_type = service_type + self.mdns_found = mdns_found + self.mdns_add = mdns_add + self.mdns_remove = mdns_remove + self.server = None + + def resolve_error(self, *args): + log.error("AvahiListener.resolve_error%s", args) + + def service_resolved(self, interface, protocol, name, stype, domain, host, x, address, port, text_array, v): + log("AvahiListener.service_resolved%s", (interface, protocol, name, stype, domain, host, x, address, port, "..", v)) + if self.mdns_add: + #parse text data: + text = {} + try: + for text_line in text_array: + line = "" + for b in text_line: + line += chr(b.real) + parts = line.split("=", 1) + if len(parts)==2: + text[parts[0]] = parts[1] + log(" text=%s", text) + except Exception: + log.error("failed to parse text record", exc_info=True) + nargs = (dbus_to_native(x) for x in (interface, name, domain, host, address, port, text)) + self.mdns_add(*nargs) + + def service_found(self, interface, protocol, name, stype, domain, flags): + log("service_found%s", (interface, protocol, name, stype, domain, flags)) + if flags & avahi.LOOKUP_RESULT_LOCAL: + # local service, skip + pass + if self.mdns_found: + self.mdns_found(dbus_to_native(interface), dbus_to_native(name)) + self.server.ResolveService(interface, protocol, name, stype, + domain, avahi.PROTO_UNSPEC, dbus.UInt32(0), + reply_handler=self.service_resolved, error_handler=self.resolve_error) + + def service_removed(self, interface, protocol, name, stype, domain, flags): + log("service_removed%s", (interface, protocol, name, stype, domain, flags)) + if self.mdns_remove: + nargs = (dbus_to_native(x) for x in (interface, protocol, name, stype, domain, flags)) + self.mdns_remove(*nargs) + + + def start(self): + self.server = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, '/'), 'org.freedesktop.Avahi.Server') + log("AvahiListener.start() server=%s", self.server) + + self.sbrowser = dbus.Interface(self.bus.get_object(avahi.DBUS_NAME, + self.server.ServiceBrowserNew(avahi.IF_UNSPEC, + avahi.PROTO_UNSPEC, XPRA_MDNS_TYPE, 'local', dbus.UInt32(0))), + avahi.DBUS_INTERFACE_SERVICE_BROWSER) + log("AvahiListener.start() service browser=%s", self.sbrowser) + self.sbrowser.connect_to_signal("ItemNew", self.service_found) + self.sbrowser.connect_to_signal("ItemRemove", self.service_removed) + + def stop(self): + #FIXME: how do we tell dbus we are no longer interested? + pass + + +def main(): + def mdns_found(*args): + print("mdns_found: %s" % (args, )) + def mdns_add(*args): + print("mdns_add: %s" % (args, )) + def mdns_remove(*args): + print("mdns_remove: %s" % (args, )) + + from xpra.dbus.common import loop_init + loop_init() + listener = AvahiListener(XPRA_MDNS_TYPE, mdns_found, mdns_add, mdns_remove) + try: + from xpra.gtk_common.gobject_compat import import_glib + glib = import_glib() + glib.idle_add(listener.start) + glib.MainLoop().run() + finally: + listener.stop() + + +if __name__ == "__main__": + main() diff --git a/src/xpra/scripts/main.py b/src/xpra/scripts/main.py index 3acb927b94..66ddc9c517 100755 --- a/src/xpra/scripts/main.py +++ b/src/xpra/scripts/main.py @@ -82,6 +82,11 @@ def nox(): from xpra.x11.bindings.wait_for_x_server import wait_for_x_server #@UnresolvedImport @UnusedImport except: supports_server = False +try: + from xpra.net import mdns + supports_mdns = bool(mdns) +except: + supports_mdns = False #this parse doesn't exit when it encounters an error, @@ -236,6 +241,8 @@ def do_parse_cmdline(cmdline, defaults): "\t%prog version [DISPLAY]\n" "\t%prog showconfig\n" ] + if supports_mdns: + command_options.append("\t%prog list-mdns\n") server_modes = [] if supports_server: server_modes.append("start") @@ -1031,7 +1038,7 @@ def show_sound_codec_help(is_server, speaker_codecs, microphone_codecs): def configure_logging(options, mode): to = sys.stderr - if mode in ("showconfig", "info", "control", "list", "attach", "stop", "version", "print", "opengl", "test-connect"): + if mode in ("showconfig", "info", "control", "list", "list-mdns", "attach", "stop", "version", "print", "opengl", "test-connect"): to = sys.stdout #a bit naughty here, but it's easier to let xpra.log initialize #the logging system every time, and just undo things here.. @@ -1170,6 +1177,8 @@ def run_mode(script_file, error_cb, options, args, mode, defaults): return run_stopexit(mode, error_cb, options, args) elif mode == "list" and (supports_server or supports_shadow): return run_list(error_cb, options, args) + elif mode == "list-mdns" and supports_mdns: + return run_list_mdns(error_cb, options, args) elif mode in ("_proxy", "_proxy_start", "_proxy_start_desktop", "_shadow_start") and (supports_server or supports_shadow): nox() return run_proxy(error_cb, options, script_file, args, mode, defaults) @@ -2439,6 +2448,78 @@ def may_cleanup_socket(state, display, sockpath, clean_states=[DotXpra.DEAD]): sys.stdout.write(" (cleaned up)") sys.stdout.write("\n") +def run_list_mdns(error_cb, opts, extra_args): + no_gtk() + if len(extra_args)<=1: + try: + MDNS_WAIT = int(extra_args[0]) + except: + MDNS_WAIT = 5 + else: + error_cb("too many arguments for mode") + assert supports_mdns + from xpra.net.mdns import XPRA_MDNS_TYPE + try: + from xpra.net.mdns.avahi_listener import AvahiListener + except ImportError: + error_cb("sorry, 'list-mdns' is not supported on this platform yet") + from xpra.net.net_util import if_indextoname + from xpra.dbus.common import loop_init + from xpra.gtk_common.gobject_compat import import_glib + glib = import_glib() + loop_init() + try: + import collections + found = collections.OrderedDict() + except: + found = {} + shown = set() + def show_new_found(): + new_found = [x for x in found.keys() if x not in shown] + for uq in new_found: + recs = found[uq] + for i, rec in enumerate(recs): + iface, _, _, host, address, port, text = rec + display = text.get("display") + mode = text.get("mode", "") + username = text.get("username", "") + session = text.get("session") + if i==0: + print("* user '%s' on '%s'" % (username, host)) + if session: + print(" session '%s'" % session) + print(" + %s endpoint on host %s, port %i, interface %s" % (mode, address, port, iface)) + dstr = "" + if display.startswith(":"): + dstr = display[1:] + uri = "%s/%s@%s:%s/%s" % (mode, username, address, port, dstr) + print(" \"%s\"" % uri) + shown.add(uq) + def mdns_add(interface, name, domain, host, address, port, text): + text = text or {} + iface = interface + if if_indextoname: + iface = if_indextoname(interface) + username = text.get("username", "") + uq = text.get("uuid", len(found)), username, host + found.setdefault(uq, []).append((iface, name, domain, host, address, port, text)) + glib.timeout_add(1000, show_new_found) + listener = AvahiListener(XPRA_MDNS_TYPE, mdns_add=mdns_add) + print("Looking for xpra services via mdns") + try: + glib.idle_add(listener.start) + loop = glib.MainLoop() + glib.timeout_add(MDNS_WAIT*1000, loop.quit) + loop.run() + finally: + listener.stop() + if not found: + print("no services found") + else: + from xpra.util import engs + print("%i service%s found" % (len(found), engs(found))) + + def run_list(error_cb, opts, extra_args): no_gtk() if extra_args: