Skip to content

Instantly share code, notes, and snippets.

@cetaSYN
Last active January 25, 2020 18:34
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cetaSYN/ece2c3a0f6ccd625230844f02f50344f to your computer and use it in GitHub Desktop.
Save cetaSYN/ece2c3a0f6ccd625230844f02f50344f to your computer and use it in GitHub Desktop.
Communicate between subnets by leveraging mDNS DNS-SD TXT records and mDNS reflection
#!/usr/bin/env python3
__author__ = 'cetaSYN'
import argparse
import queue
import signal
import socket
import sys
import threading
import time
import zeroconf
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--address', default='224.225.226.227') # Random multicast reserved address to not collide
parser.add_argument('--port', default=0) # Arbitrary port
parser.add_argument('--name', default=socket.gethostname())
args = parser.parse_args()
zconf = zeroconf.Zeroconf()
sml = ServiceMessageListener(
zconf,
name=args.name,
address=args.address,
port=args.port,
)
zconf.add_service_listener('_mdnsmsg._udp.local.', sml)
def close(signum, frame):
print('Stopping...')
sml.closing = True
zconf.close()
sys.exit()
signal.signal(signal.SIGINT, close)
while True:
sml.msg_queue.put(input("> "))
class ServiceMessageListener(zeroconf.ServiceListener):
def __init__(self, zconf, name, address, port):
self.prev_msg = None
self.closing = False
self.zconf = zconf
self.name = name
self.address = address
self.port = port
self.msg_queue = queue.Queue()
self.service_info = zeroconf.ServiceInfo(
'_mdnsmsg._udp.local.',
'{}._mdnsmsg._udp.local.'.format(name),
address=socket.inet_pton(socket.AF_INET, address),
port=port,
properties={'msg': 'Init'},
other_ttl=3
)
self.zconf.register_service(self.service_info)
threading.Thread(target=self.send_queue_worker).start()
def remove_service(self, _, type_, name):
pass
def add_service(self, _, type_, name):
self.handle_msg(type_, name)
def update_service(self, _, type_, name):
self.handle_msg(type_, name)
def handle_msg(self, type_, name):
info = self.zconf.get_service_info(type_, name)
if info.get_name() == self.name: return # Ignore ourselves
if socket.inet_ntop(socket.AF_INET, info.address) != self.address \
or info.port != self.port:
return # Must match ServiceInfo
msg = next(iter(info.properties.values()))
if msg != self.prev_msg:
self.prev_msg = msg
print(msg.decode(), end='\n> ')
def send_queue_worker(self):
while not self.closing:
try:
msg = self.msg_queue.get(timeout=5)
self.service_info._set_properties({'msg': msg})
self.zconf.update_service(self.service_info)
except queue.Empty:
pass
if __name__ == '__main__':
main()
@cetaSYN
Copy link
Author

cetaSYN commented Jan 20, 2020

Spent the weekend playing with multicast-DNS reflection as a means to communicate between otherwise firewall-isolated subnets.
Turns out DNS Service Discovery TXT Records, reflected by Avahi, does the job well.

Screenshot from 2020-01-20 00-31-49

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment