From f1bbfe45748e00531571a441325017d16c6f153d Mon Sep 17 00:00:00 2001 From: Jan-Piet Mens Date: Wed, 13 Nov 2013 11:59:06 +0100 Subject: [PATCH] Add support for DNS SRV records adds preliminary support for DNS SRV records in a new function connect_srv(domain=None). If domain is None a lookup on the domain of the host is attempted. Otherwise domain is a DNS domain name to query for the SRV records. change service to IANA-assigned Signed-off-by: Jan-Piet Mens --- examples/sub-srv.py | 68 +++++++++++++++++++++++++++++++++++++++++ src/paho/mqtt/client.py | 43 ++++++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100755 examples/sub-srv.py diff --git a/examples/sub-srv.py b/examples/sub-srv.py new file mode 100755 index 0000000..b5103a0 --- /dev/null +++ b/examples/sub-srv.py @@ -0,0 +1,68 @@ +#!/usr/bin/python + +# Copyright (c) 2010-2013 Roger Light +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Distribution License v1.0 +# which accompanies this distribution. +# +# The Eclipse Distribution License is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# Contributors: +# Roger Light - initial implementation +# Copyright (c) 2010,2011 Roger Light +# All rights reserved. + +# This shows a simple example of an MQTT subscriber. + +import sys +try: + import paho.mqtt.client as mqtt +except ImportError: + # This part is only required to run the example from within the examples + # directory when the module itself is not installed. + # + # If you have the module installed, just use "import paho.mqtt.client" + import os + import inspect + cmd_subfolder = os.path.realpath(os.path.abspath(os.path.join(os.path.split(inspect.getfile( inspect.currentframe() ))[0],"../src"))) + if cmd_subfolder not in sys.path: + sys.path.insert(0, cmd_subfolder) + import paho.mqtt.client as mqtt + +def on_connect(mqttc, obj, rc): + print "Connected to %s:%s" % (mqttc._host, mqttc._port) + +def on_message(mqttc, obj, msg): + print(msg.topic+" "+str(msg.qos)+" "+str(msg.payload)) + +def on_publish(mqttc, obj, mid): + print("mid: "+str(mid)) + +def on_subscribe(mqttc, obj, mid, granted_qos): + print("Subscribed: "+str(mid)+" "+str(granted_qos)) + +def on_log(mqttc, obj, level, string): + print(string) + +# If you want to use a specific client id, use +# mqttc = mqtt.Client("client-id") +# but note that the client id must be unique on the broker. Leaving the client +# id parameter empty will generate a random id for you. +mqttc = mqtt.Client() +mqttc.on_message = on_message +mqttc.on_connect = on_connect +mqttc.on_publish = on_publish +mqttc.on_subscribe = on_subscribe +# Uncomment to enable debug messages +#mqttc.on_log = on_log +mqttc.connect_srv("mosquitto.org", 60) +mqttc.subscribe("$SYS/broker/version", 0) + + +rc = 0 +while rc == 0: + rc = mqttc.loop() + +print("rc: "+str(rc)) diff --git a/src/paho/mqtt/client.py b/src/paho/mqtt/client.py index 1ba6f9b..13365b1 100755 --- a/src/paho/mqtt/client.py +++ b/src/paho/mqtt/client.py @@ -25,6 +25,11 @@ import struct import sys import threading import time +HAVE_DNS = True +try: + import dns.resolver +except ImportError: + HAVE_DNS = False if sys.version_info[0] < 3: PROTOCOL_NAME = "MQIsdp" @@ -540,6 +545,44 @@ class Client: self.connect_async(host, port, keepalive, bind_address) return self.reconnect() + def connect_srv(self, domain=None, keepalive=60, bind_address=""): + """Connect to a remote broker. + + domain is the DNS domain to search for SRV records; if None, + try to determine local domain name. + keepalive and bind_address are as for connect() + """ + + if HAVE_DNS == False: + raise ValueError('No DNS resolver library found.') + + if domain is None: + domain = socket.getfqdn() + domain = domain[domain.find('.') + 1:] + + try: + rr = '_mqtt._tcp.%s' % domain + if self._ssl is not None: + # IANA specifies secure-mqtt (not mqtts) for port 8883 + rr = '_secure-mqtt._tcp.%s' % domain + answers = [] + for answer in dns.resolver.query(rr, dns.rdatatype.SRV): + addr = answer.target.to_text()[:-1] + answers.append((addr, answer.port, answer.priority, answer.weight)) + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer, dns.resolver.NoNameservers): + raise ValueError("No answer/NXDOMAIN for SRV in %s" % (domain)) + + # FIXME: doesn't account for weight + for answer in answers: + host, port, prio, weight = answer + + try: + return self.connect(host, port, keepalive, bind_address) + except: + pass + + raise ValueError("No SRV hosts responded") + def connect_async(self, host, port=1883, keepalive=60, bind_address=""): """Connect to a remote broker asynchronously. This is a non-blocking connect call that can be used with loop_start() to provide very quick -- 2.39.5