def test_discover_homekit_devices_missing_c(self): zeroconf = Zeroconf() desc = { 'id': '00:00:01:00:00:03', 'md': 'unittest', 's#': '1', 'ci': '5', 'sf': '0' } info = ServiceInfo('_hap._tcp.local.', 'foo3._hap._tcp.local.', addresses=[socket.inet_aton('127.0.0.1')], port=1234, properties=desc, weight=0, priority=0) zeroconf.unregister_all_services() zeroconf.register_service(info, allow_name_change=True) result = discover_homekit_devices() test_device = self.find_device(desc, result) zeroconf.unregister_all_services() self.assertIsNone(test_device)
class DiscoveryServerService: _SERVICE_TYPE = '_http._tcp.local.' _SERVICE_NAME = 'sound-system-server._http._tcp.local.' def __init__(self, host, port): self._host = host self._port = port self._zeroconf = None self._info = None self._registered = False def start(self): if not self._registered: self._zeroconf = Zeroconf() self._start_service() def stop(self): if self._registered: self._zeroconf.unregister_all_services() self._zeroconf = None self._registered = False def _start_service(self): self._info = ServiceInfo(self._SERVICE_TYPE, self._SERVICE_NAME, socket.inet_aton(self._host), self._port, 0, 0, {}) logging.debug('Registered zeroconf service %s host %s:%d', self._SERVICE_NAME, self._host, self._port) self._zeroconf.register_service(self._info) self._registered = True
class MdnsBrowser: def __init__(self, acteur): self.__logger = logging.getLogger(__name__ + '.' + self.__class__.__name__) self.zeroconf: Optional[Zeroconf] = None service_types = [ '_mghttps._tcp.local.', '_mgamqps._tcp.local.', ] self.listener = MdnsListener(acteur) # Activer zeroconf avec IPv4 et IPv6 (all) try: self.zeroconf = Zeroconf(ip_version=IPVersion.All) self.browser = ServiceBrowser(self.zeroconf, service_types, listener=self.listener) except OSError: self.__logger.warning( "Erreur chargement mdns avec IPv4 et IPv6, tenter de charger avec IPv4 uniquement" ) self.zeroconf = Zeroconf(ip_version=IPVersion.V4Only) self.browser = ServiceBrowser(self.zeroconf, service_types, listener=self.listener) def entretien(self): pass def fermer(self): self.zeroconf.unregister_all_services() self.zeroconf.close()
def main(): # Catch CNTRL-C signel global shutdown signal.signal(signal.SIGINT, signal_cntrl_c) init_logging("", 2) configs = load_configfile("bridge.ini") ignore_list = configs.getlist('Device', 'Ignore') context = zmq.Context.instance() zeroconf = Zeroconf() listener = ZeroConfDashTCPListener(context) browser = ServiceBrowser(zeroconf, "_DashIO._tcp.local.", listener) pinger = TCPPoller(port=5000, context=context) b = tcp_dashBridge( configs.get('Dash', 'Username'), configs.get('Dash', 'Password'), host=configs.get('Dash', 'Server'), port=configs.getint('Dash', 'Port'), ignore_devices=ignore_list, context=context ) while not shutdown: time.sleep(5) zeroconf.unregister_all_services() zeroconf.close()
class HomeKitServer(ThreadingMixIn, HTTPServer): def __init__(self, config_file, logger=sys.stderr): """ Create a new server that acts like a homekit accessory. :param config_file: the file that contains the configuration data. Must be a string representing an absolute path to the file :param logger: this can be None to disable logging, sys.stderr to use the default behaviour of the python implementation or an instance of logging.Logger to use this. """ if logger is None or logger == sys.stderr or isinstance( logger, logging.Logger): self.logger = logger else: raise Exception('Invalid logger given.') self.data = HomeKitServerData(config_file) self.data.increase_configuration_number() self.sessions = {} self.zeroconf = Zeroconf() self.mdns_type = '_hap._tcp.local.' self.mdns_name = self.data.name + '._hap._tcp.local.' self.accessories = Accessories() HTTPServer.__init__(self, (self.data.ip, self.data.port), HomeKitRequestHandler) def publish_device(self): desc = { 'md': 'My Lightbulb', # model name of accessory 'ci': Categories[ 'Lightbulb'], # category identifier (page 254, 2 means bridge) 'pv': '1.0', # protocol version 'c#': str(self.data.configuration_number), # configuration (consecutive number, 1 or greater, must be changed on every configuration change) 'id': self.data. accessory_pairing_id_bytes, # id MUST look like Mac Address 'ff': '0', # feature flags 's#': '1', # must be 1 'sf': '1' # status flag, lowest bit encodes pairing status, 1 means unpaired } if self.data.is_paired: desc['sf'] = '0' info = ServiceInfo(self.mdns_type, self.mdns_name, socket.inet_aton(self.data.ip), self.data.port, 0, 0, desc, 'ash-2.local.') self.zeroconf.unregister_all_services() self.zeroconf.register_service(info) def unpublish_device(self): self.zeroconf.unregister_all_services() def shutdown(self): # tell all handlers to close the connection for session in self.sessions: self.sessions[session]['handler'].close_connection = True self.socket.close() HTTPServer.shutdown(self)
def mDNS(): #zerconf info #logging.basicConfig(level=logging.DEBUG) #logging.getLogger('zeroconf').setLevel(logging.DEBUG) info = [] zeroconf = Zeroconf(InterfaceChoice.All) output = subprocess.check_output('for /f "usebackq tokens=2 delims=:" %f in (`ipconfig ^| findstr /c:"IPv4 Address"`) do @echo off && echo%f',shell=True , stderr=subprocess.STDOUT,stdin=subprocess.PIPE).decode("utf-8") #F**K PYTHON output = output[:-1] i = 0 infs = sorted(output.split('\n')) for ip in output.split('\n'): i += 1 print(ip) info.append(ServiceInfo( "_coap._udp.local.", "hwmon" + str(i) + "._coap._udp.local.", addresses=[socket.inet_aton(ip)], port=5683, server="hwmon" + str(i) + ".local.", #host_ttl=0, #other_ttl=0, )) zeroconf.register_service(info[-1]) print("service started") while (stop_threads == False): output = subprocess.check_output('for /f "usebackq tokens=2 delims=:" %f in (`ipconfig ^| findstr /c:"IPv4 Address"`) do @echo off && echo%f',shell=True , stderr=subprocess.STDOUT,stdin=subprocess.PIPE).decode("utf-8") #F**K PYTHON TWICE output = output[:-1] i = 0 if(sorted(output.split('\n')) != infs): for inf in info: try: zeroconf.unregister_service(inf) except : pass zeroconf.unregister_all_services() info.clear() print("new interface") for ip in output.split('\n'): i += 1 print(ip) info.append(ServiceInfo( "_coap._udp.local.", "hwmon" + str(i) + "._coap._udp.local.", addresses=[socket.inet_aton(ip)], port=5683, server="hwmon" + str(i) + ".local.", #host_ttl=0, #other_ttl=0, )) zeroconf.register_service(info[-1]) infs = sorted(output.split('\n')) time.sleep(1) zeroconf.close()
class IPBroadcaster(): def __init__(self): print("Starting service") self.ip_version = IPVersion.V4Only self.service = None self.hostname = "" self.ip_address = "" self.zeroconf = None def __del__(self): print("Stopping service") self.zeroconf.close() def createService(self): self.hostname = socket.gethostname() self.ip_address = get_ip_address() type = '' if IS_PRINTER: type = 'nordin_printer' else: type = 'nordin_device' desc = { 'type': type, 'name': self.hostname, 'series': HARDWARE_SERIES, 'version': HARDWARE_VERSION } return ServiceInfo( "_http._tcp.local.", "{}beacon._http._tcp.local.".format(self.hostname), addresses=[socket.inet_aton(self.ip_address)], port=5000, properties=desc, server="{}.local.".format(self.hostname), ) def start(self): try: self.zeroconf.unregister_service(self.service) self.zeroconf.close() except AttributeError: pass self.service = self.createService() print("Registering mDNS") self.zeroconf = Zeroconf(ip_version=self.ip_version) try: self.zeroconf.register_service(self.service) except NonUniqueNameException: self.zeroconf.unregister_all_services() print("Nonunique-error")
def test_find_with_device(self): zeroconf = Zeroconf() desc = {'id': '00:00:02:00:00:02'} info = ServiceInfo('_hap._tcp.local.', 'foo1._hap._tcp.local.', address=socket.inet_aton('127.0.0.1'), port=1234, properties=desc, weight=0, priority=0) zeroconf.unregister_all_services() zeroconf.register_service(info, allow_name_change=True) result = find_device_ip_and_port('00:00:02:00:00:02', 10) zeroconf.unregister_all_services() self.assertIsNotNone(result) self.assertEqual(result['ip'], '127.0.0.1')
def main(): # Catch CNTRL-C signel global shutdown signal.signal(signal.SIGINT, signal_cntrl_c) init_logging("", 2) context = zmq.Context.instance() zeroconf = Zeroconf() listener = ZeroConfListener(context) browser = ServiceBrowser(zeroconf, "_DashZMQ._tcp.local.", listener) b = zmq_tcpBridge(tcp_port=5001, context=context) while not shutdown: time.sleep(1) print("Goodbye") zeroconf.unregister_all_services() b.close() time.sleep(1) zeroconf.close()
class AirDropServer: """ Announces an HTTPS AirDrop server in the local network via mDNS. """ def __init__(self, config): self.config = config # Use IPv6 self.serveraddress = ('::', self.config.port) self.ServerClass = HTTPServerV6 self.ServerClass.allow_reuse_address = False self.ip_addr = AirDropUtil.get_ip_for_interface(self.config.interface, ipv6=True) if self.ip_addr is None: if self.config.interface == 'awdl0': raise RuntimeError( 'Interface {} does not have an IPv6 address. ' 'Make sure that `owl` is running.'.format( self.config.interface)) else: raise RuntimeError( 'Interface {} does not have an IPv6 address'.format( self.config.interface)) self.Handler = AirDropServerHandler self.Handler.config = self.config self.zeroconf = Zeroconf(interfaces=[str(self.ip_addr)], ip_version=IPVersion.V6Only, apple_p2p=platform.system() == 'Darwin') self.http_server = self._init_server() self.service_info = self._init_service() def _init_service(self): properties = self.get_properties() server = self.config.host_name + '.local.' service_name = self.config.service_id + '._airdrop._tcp.local.' info = ServiceInfo('_airdrop._tcp.local.', service_name, port=self.config.port, properties=properties, server=server, addresses=[self.ip_addr.packed]) return info def start_service(self): logger.info('Announcing service: host {}, address {}, port {}'.format( self.config.host_name, self.ip_addr, self.config.port)) self.zeroconf.register_service(self.service_info) def _init_server(self): try: httpd = self.ServerClass(self.serveraddress, self.Handler) except OSError: # Address in use. Change port self.config.port = self.config.port + 1 self.serveraddress = (self.serveraddress[0], self.config.port) httpd = self.ServerClass(self.serveraddress, self.Handler) # Adapt socket for awdl0 if self.config.interface == 'awdl0' and platform.system() == 'Darwin': httpd.socket.setsockopt(socket.SOL_SOCKET, 0x1104, 1) httpd.socket = self.config.get_ssl_context().wrap_socket( sock=httpd.socket, server_side=True) return httpd def start_server(self): logger.info('Starting HTTPS server') self.http_server.serve_forever() def stop(self): self.zeroconf.unregister_all_services() self.http_server.shutdown() def get_properties(self): properties = {b'flags': str(self.config.flags).encode('utf-8')} return properties
# clean failed node if not len(failed_node) == 0: for node_name in failed_node: print(f"I: Node {node_name} failed.") node_list[node_name][2].close() del reverse_node_list[str(node_list[node_name][0])] del node_list[node_name] # print(node_list) except KeyboardInterrupt as e: # Shutdown the server print(e) exit(0) finally: print("I: cleaning up") zeroconf.unregister_all_services() zeroconf.close() for _, node in node_list.items(): node[2].close() for s in telnet_sockets_going_in: s.close() for s in telnet_sockets_going_out: s.close() telnet_server.close() telnet_server.close()
class TCPConnection(threading.Thread): """Setups and manages a connection thread to iotdashboard via TCP.""" def _zconf_publish_tcp(self, port): zconf_desc = {'ConnectionUUID': self.connection_id} zconf_info = ServiceInfo( "_DashIO._tcp.local.", "{}._DashIO._tcp.local.".format(self.connection_id), addresses=[socket.inet_aton(self.local_ip)], port=port, properties=zconf_desc, server=self.host_name + ".", ) self.zeroconf.register_service(zconf_info) def add_device(self, device): device.rx_zmq_sub.connect( CONNECTION_PUB_URL.format(id=self.connection_id)) device.rx_zmq_sub.setsockopt(zmq.SUBSCRIBE, self.b_connection_id) device.add_control(self.tcp_control) self.rx_zmq_sub.connect(DEVICE_PUB_URL.format(id=device.zmq_pub_id)) self.rx_zmq_sub.setsockopt_string(zmq.SUBSCRIBE, device.zmq_pub_id) @staticmethod def _is_port_in_use(port): with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as port_s: return port_s.connect_ex(('localhost', port)) == 0 def __init__(self, ip_address="*", port=5650, use_zero_conf=True, context=None): """TCP Connection Parameters --------- ip_address (str, optional): IP Address to use. Defaults to "*". port (int, optional): Port to use. Defaults to 5650. use_zero_conf (bool, optional): Use mDNS to advertise the connection. Defaults to True. context (optional): ZMQ context. Defaults to None. """ threading.Thread.__init__(self, daemon=True) self.context = context or zmq.Context.instance() self.connection_id = shortuuid.uuid() self.b_connection_id = self.connection_id.encode('utf-8') self.use_zeroconf = use_zero_conf while self._is_port_in_use(port) and use_zero_conf: # increment port until we find one that is free. port += 1 self.ext_url = "tcp://" + ip_address + ":" + str(port) self.socket_ids = [] self.running = True host_name = socket.gethostname() host_list = host_name.split(".") # rename for .local mDNS advertising self.host_name = f"{host_list[0]}.local" self.local_ip = ip.get_local_ip_address() if self.use_zeroconf: self.zeroconf = Zeroconf(ip_version=IPVersion.V4Only) self.tcp_control = TCP(self.connection_id, "", 0) self._zconf_publish_tcp(port) else: self.tcp_control = TCP(self.connection_id, self.local_ip, port) self.start() time.sleep(1) def close(self): """Close the connection.""" if self.use_zeroconf: self.zeroconf.unregister_all_services() self.zeroconf.close() self.running = False def run(self): tcpsocket = self.context.socket(zmq.STREAM) def _zmq_tcp_send(tcp_id, data): try: tcpsocket.send(tcp_id, zmq.SNDMORE) tcpsocket.send(data, zmq.NOBLOCK) except zmq.error.ZMQError as zmq_e: logging.debug("Sending TX Error: %s", zmq_e) # self.socket_ids.remove(tcp_id) tx_zmq_pub = self.context.socket(zmq.PUB) tx_zmq_pub.bind(CONNECTION_PUB_URL.format(id=self.connection_id)) self.rx_zmq_sub = self.context.socket(zmq.SUB) # Subscribe on ALL, and my connection self.rx_zmq_sub.setsockopt_string(zmq.SUBSCRIBE, "ALL") self.rx_zmq_sub.setsockopt_string(zmq.SUBSCRIBE, "ALARM") self.rx_zmq_sub.setsockopt_string(zmq.SUBSCRIBE, self.connection_id) # rx_zmq_sub.setsockopt_string(zmq.SUBSCRIBE, "ANNOUNCE") tcpsocket.bind(self.ext_url) tcpsocket.set(zmq.SNDTIMEO, 5) poller = zmq.Poller() poller.register(tcpsocket, zmq.POLLIN) poller.register(self.rx_zmq_sub, zmq.POLLIN) tcp_id = tcpsocket.recv() tcpsocket.recv() # empty data here while self.running: try: socks = dict(poller.poll(50)) except zmq.error.ContextTerminated: break if tcpsocket in socks: tcp_id = tcpsocket.recv() message = tcpsocket.recv() if tcp_id not in self.socket_ids: logging.debug("Added Socket ID: %s", tcp_id.hex()) self.socket_ids.append(tcp_id) logging.debug("TCP ID: %s, Rx:\n%s", tcp_id.hex(), message.decode('utf-8').rstrip()) if message: tx_zmq_pub.send_multipart( [self.b_connection_id, tcp_id, message]) else: if tcp_id in self.socket_ids: logging.debug("Removed Socket ID: %s", tcp_id.hex()) self.socket_ids.remove(tcp_id) if self.rx_zmq_sub in socks: [address, msg_id, data] = self.rx_zmq_sub.recv_multipart() if not data: continue if address == b'ALL': for tcp_id in self.socket_ids: logging.debug("TCP ID: %s, Tx:\n%s", tcp_id.hex(), data.decode('utf-8').rstrip()) _zmq_tcp_send(tcp_id, data) elif address == self.b_connection_id: logging.debug("TCP ID: %s, Tx:\n%s", msg_id.hex(), data.decode('utf-8').rstrip()) _zmq_tcp_send(msg_id, data) for tcp_id in self.socket_ids: _zmq_tcp_send(tcp_id, b'') tcpsocket.close() tx_zmq_pub.close() self.rx_zmq_sub.close()
class AutoAnnounce(object): def __init__(self, port, ssl=False, run=True): """ Announces network settings via mdns :type self: AutoAnnounce :param self: AutoAnnounce :type port: int :param port: port of service to announce :type ssl: bool :param ssl: flag that determine this service uses ssl :type run: bool :param run: flag that determine whether the announce service starts automatically """ try: self.__running = True protocol = 'http' if ssl: protocol = 'https' self.zeroconf = Zeroconf() ip, hostname = self.get_local_ip() desc = { 'version': RemoteDslrApi.__version__, 'api_url': protocol + '://' + ip + ':' + str(port) + '/api/', 'url': protocol + '://' + ip + ':' + str(port) } self.service = ServiceInfo("_http._tcp.local.", "RemoteDslrApi._http._tcp.local.", socket.inet_aton(ip), port, 0, 0, desc, hostname) # logging.basicConfig(level=logging.DEBUG) # logging.getLogger('zeroconf').setLevel(logging.DEBUG) if run: self.run() except Exception as ex: print ex def run(self): """ announces current configuration via mdns :type self: AutoAnnounce """ self.zeroconf.register_service(self.service) def close(self): """ stop announcing :type self: AutoAnnounce """ self.zeroconf.unregister_all_services() self.zeroconf.close() self.__running = False @staticmethod def get_local_ip(): """ returns current ip and hostname :returns: a tuple with IP and Hostname :rtype: (string, string) """ s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(('8.8.8.8', 53)) ip = s.getsockname()[0] s.close() hostname = socket.gethostname() return ip, hostname
class MainWindow(tk.Tk): __EVENT_DATE_TIME_UPDATE = "<<UPDATE_DATE_TIME_EVENT>>" __EVENT_COMMAND_UPDATE = "<<UPDATE_COMMAND_EVENT>>" __EVENT_EMAIL_UPDATE = "<<UPDATE_EMAIL_EVENT>>" __EVENT_NEWS_UPDATE = "<<UPDATE_NEWS_EVENT>>" __EVENT_STOCK_UPDATE = "<<UPDATE_STOCK_EVENT>>" __EVENT_WEATHER_UPDATE = "<<UPDATE_WEATHER_EVENT>>" def __init__(self, ip=None): super().__init__() self.protocol('WM_DELETE_WINDOW', self.on_exit) self.resizable(width=False, height=False) self.geometry('{w}x{h}+{x}+{y}'.format(w=800, h=480, x=0, y=0)) self.overrideredirect(True) # disable title bar etc. self.sock_data = None self.logger = logging.getLogger(__name__) if ip is None: raise ValueError("Listen ip is None") self.socket_server = CustomTCPServer((ip, 0), ClientRequestHandler, gui=self, logger=self.logger) self.socket_thread = threading.Thread(target=self.socket_server.serve_forever) self.socket_thread.setDaemon(True) self.socket_thread.start() self.ip = self.socket_server.socket.getsockname()[0] self.port = self.socket_server.socket.getsockname()[1] self.logger.info("Listening on " + self.ip + ":" + str(self.port)) self.zeroconf = Zeroconf() self.__setup_zeroconf() self.logger.info("Zeroconf service registered.") self.bind(sequence=self.__EVENT_DATE_TIME_UPDATE, func=self.handle_date_time_update) self.bind(sequence=self.__EVENT_COMMAND_UPDATE, func=self.handle_command_update) self.bind(sequence=self.__EVENT_EMAIL_UPDATE, func=self.handle_email_update) self.bind(sequence=self.__EVENT_NEWS_UPDATE, func=self.handle_news_update) self.bind(sequence=self.__EVENT_STOCK_UPDATE, func=self.handle_exchange_update) self.bind(sequence=self.__EVENT_WEATHER_UPDATE, func=self.handle_weather_update) self.mail_frame = None self.news_label = None self.exchange_label = None self.btn_quit = None self.after(10, self.__init_widgets__) def __init_widgets__(self): __first_row = tk.Frame(self, borderwidth=0, width=800, height=100) self.mail_frame = MailFrame(master=__first_row, width=200, height=100) self.mail_frame.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=50) self.time_frame = TimeFrame(master=__first_row, width=400, height=100) self.time_frame.pack(side=tk.LEFT, anchor=tk.CENTER, fill=tk.X, expand=True) self.weather_frame = WeatherFrame(master=__first_row, width=200, height=100) self.weather_frame.pack(side=tk.LEFT, fill=tk.X, expand=True) __first_row.pack(fill=tk.X, expand=True) self.news_label = SlidingLabel(master=self, text_length=25) self.news_label.pack(after=__first_row, fill=tk.X) self.news_label.load_lines(["Breaking News"]) self.exchange_label = SlidingLabel(master=self, separator="|", text_length=25) self.exchange_label.pack(after=self.news_label, fill=tk.X) self.exchange_label.load_lines(["Exchange Rates"]) def trigger_gui_update(self, data_type): if data_type == UpdateType.DATETIME: self.event_generate(self.__EVENT_DATE_TIME_UPDATE) elif data_type == UpdateType.COMMAND: self.event_generate(self.__EVENT_COMMAND_UPDATE) elif data_type == UpdateType.EMAIL: self.event_generate(self.__EVENT_EMAIL_UPDATE) elif data_type == UpdateType.NEWS: self.event_generate(self.__EVENT_NEWS_UPDATE) elif data_type == UpdateType.EXCHANGE: self.event_generate(self.__EVENT_STOCK_UPDATE) elif data_type == UpdateType.WEATHER: self.event_generate(self.__EVENT_WEATHER_UPDATE) else: self.logger.error("Unknown data type: " + data_type) def handle_date_time_update(self, event): time_string = self.sock_data['time'] date_string = self.sock_data['date'] self.time_frame.update_date_time(time_string=time_string, date_sting=date_string) def handle_command_update(self, event): cmd = self.sock_data if 'quit' == cmd: self.on_exit() def handle_email_update(self, event): print(self.sock_data) self.mail_frame.update_count(self.sock_data) def handle_news_update(self, event): self.news_label.load_lines(self.sock_data) def handle_exchange_update(self, event): self.exchange_label.load_lines(self.sock_data) def handle_weather_update(self, event): data = self.sock_data self.weather_frame.update_data(temperature=data['temp'], city=data['city']) pass def on_exit(self): self.logger.info("Unregistering Zeroconf services...") self.zeroconf.unregister_all_services() self.zeroconf.close() self.logger.info("Closing socket...") self.socket_server.socket.shutdown(socket.SHUT_RDWR) self.socket_server.socket.close() self.logger.info("Exiting...") self.destroy() def __setup_zeroconf(self): service_desc = {} self.zeroconf_info = ServiceInfo("_scpi-raw._tcp.local.", "Auxiliary Display on {host}._scpi-raw._tcp.local.".format( host=socket.gethostname()), address=socket.inet_aton(self.ip), port=self.port, properties=service_desc) self.zeroconf.register_service(self.zeroconf_info)
class StreamAdvert(threading.Thread): def __init__(self, config): threading.Thread.__init__(self) self.daemon = True self.config = config self.logger = logging.getLogger("visiond." + __name__) # Attempt to redirect the default handler into our log files default_zeroconf_logger = logging.getLogger("zeroconf") default_zeroconf_logger.setLevel(logging.INFO) # TODO: Set based on config default_zeroconf_logger.propagate = True for handler in logging.getLogger("visiond").handlers: default_zeroconf_logger.addHandler(handler) self.zeroconf = None self._should_shutdown = threading.Event() self._q = queue.Queue() self.ip_version = IPVersion.V4Only # IPVersion.All self.service_info = self.build_service_info() def build_service_info(self, props=None, _type='visiond'): if _type == 'visiond': _subdesc = "{}:{}".format(socket.gethostname(), self.config.args.name if self.config.args.name else self.config.args.output_port) _rtspurl = f"rtsp://{socket.getfqdn()}:{self.config.args.output_port}/video" return ServiceInfo( "_rtsp._udp.local.", f"{_type} ({_subdesc}) ._rtsp._udp.local.", addresses=[socket.inet_aton(self.config.args.output_dest)], port=int(self.config.args.output_port), properties={ "port": self.config.args.output_port, "name": _subdesc, "service_type": "visiond", "rtspUrl": _rtspurl, "uuid": self.instance_uuid(_rtspurl), } ) elif _type == 'webrtc': _subdesc = "{}:{}".format(socket.gethostname(), self.config.args.name if self.config.args.name else 6011) _wsEndpoint = f"wss://{socket.getfqdn()}:6011" return ServiceInfo( "_webrtc._udp.local.", f"visiond-webrtc ({_subdesc})._webrtc._udp.local.", addresses=[socket.inet_aton('0.0.0.0')], port=6011, properties={ "hostname": socket.getfqdn(), "port": 6011, "name": _subdesc, "service_type": "webrtc", "wsEndpoint": _wsEndpoint, "uuid": self.instance_uuid(_wsEndpoint), }, ) def instance_uuid(self, url): # Create a repeatable uuid based on unique url return str(uuid.uuid5(uuid.NAMESPACE_URL, url)) def run(self): self.logger.info("Zeroconf advertisement thread is starting...") try: self.zeroconf = Zeroconf(ip_version=self.ip_version) self.register_service(self.service_info) except OSError as e: # the port was blocked self.logger.info.error( f"Unable to start zeroconf advertisement thread due to {e}" ) self.clean_up() while not self._should_shutdown.is_set(): try: # The following will block for at most [timeout] seconds desc_update = self._q.get(block=True, timeout=2) except queue.Empty: desc_update = None if desc_update: self.update_service(desc_update) # We only get here when shutdown has been called self.clean_up() def clean_up(self): self.logger.info("Zeroconf advertisement thread is stopping...") if self.zeroconf: self.zeroconf.unregister_all_services() self.zeroconf.close() self.logger.info("Zeroconf advertisement thread has stopped.") def register_service(self, service_info): self.zeroconf.register_service(service_info, cooperating_responders=True) def update_service(self, desc_update): # it does not look like there is a nice way to update # the properties field of a service. # Make a new service with the same details, # but update the properties. # Merge the dicts and apply the updates self.service_info = self.build_service_info(desc_update) self.zeroconf.update_service(self.service_info) def unregister_service(self): self.zeroconf.unregister_service(self.service_info) def shutdown(self): self._should_shutdown.set() def update(self, desc_update): self._q.put_nowait(desc_update)
class zmq_tcpBridge(threading.Thread): """Setups and manages a connection thread to iotdashboard via TCP.""" def connect_zmq_device(self, name, ip_address, sub_port, pub_port): if name not in self.devices: self.devices.append(name) logging.debug("Connect: %s, %s, %s, %s", name.decode('utf-8'), ip_address.decode('utf-8'), sub_port.decode('utf-8'), pub_port.decode('utf-8')) tx_url = "tcp://{}:{}".format(ip_address.decode('utf-8'), sub_port.decode('utf-8')) rx_url = "tcp://{}:{}".format(ip_address.decode('utf-8'), pub_port.decode('utf-8')) self.tx_zmq_pub.connect(tx_url) self.rx_zmq_sub.connect(rx_url) def disconnect_zmq_device(self, name, ip_address, sub_port, pub_port): if name in self.devices: self.devices.remove(name) tx_url = "tcp://{}:{}".format(ip_address, sub_port) rx_url = "tcp://{}:{}".format(ip_address, pub_port) self.tx_zmq_pub.disconnect(tx_url) self.rx_zmq_sub.disconnect(rx_url) # Badness 10000 def __get_local_ip_address(self): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) return s.getsockname()[0] def __zconf_publish_tcp(self, port): zconf_desc = { 'deviceID': self.device_id, 'deviceType': self.device_type, 'deviceName': self.device_name } zconf_info = ServiceInfo( "_DashIO._tcp.local.", "Bridge-{}._DashIO._tcp.local.".format(port), addresses=[socket.inet_aton(self.local_ip)], port=port, properties=zconf_desc, server=self.host_name + ".", ) self.zeroconf.register_service(zconf_info) self.zero_service_list.append(zconf_info) def __init__(self, tcp_port=5000, context=None): """ """ threading.Thread.__init__(self, daemon=True) self.device_id = "3141592654" self.device_type = "TCPBridge" self.device_name = "MulipleTCP" self.local_ip = self.__get_local_ip_address() self.ext_url = "tcp://" + self.local_ip + ":" + str(tcp_port) self.host_name = socket.gethostname() hs = self.host_name.split(".") # rename for .local mDNS advertising self.host_name = "{}.local".format(hs[0]) self.zeroconf = Zeroconf(ip_version=IPVersion.V4Only) self.zero_service_list = [] self.__zconf_publish_tcp(tcp_port) logging.debug("HostName: %s", self.host_name) logging.debug(" IP: %s", self.local_ip) self.context = context or zmq.Context.instance() self.socket_ids = [] self.devices = [] self.running = True self.start() def close(self): for id in self.socket_ids: self._zmq_send(id, "") self.zeroconf.unregister_all_services() self.zeroconf.close() self.running = False def run(self): self.tx_zmq_pub = self.context.socket(zmq.PUB) self.rx_zmq_sub = self.context.socket(zmq.SUB) self.rx_zmq_sub.setsockopt(zmq.SUBSCRIBE, b'') rx_zconf_pull = self.context.socket(zmq.PULL) rx_zconf_pull.connect("inproc://zconf") # Subscribe on ALL, and my connection self.tcpsocket = self.context.socket(zmq.STREAM) self.tcpsocket.bind(self.ext_url) self.tcpsocket.set(zmq.SNDTIMEO, 5) poller = zmq.Poller() poller.register(self.tcpsocket, zmq.POLLIN) poller.register(self.rx_zmq_sub, zmq.POLLIN) poller.register(rx_zconf_pull, zmq.POLLIN) def __zmq_tcp_send(id, data): try: self.tcpsocket.send(id, zmq.SNDMORE) self.tcpsocket.send(data, zmq.NOBLOCK) except zmq.error.ZMQError as e: logging.debug("Sending TX Error: " + str(e)) self.socket_ids.remove(id) while self.running: socks = dict(poller.poll(50)) if self.tcpsocket in socks: id = self.tcpsocket.recv() message = self.tcpsocket.recv() if id not in self.socket_ids: logging.debug("Added Socket ID: " + id.hex()) self.socket_ids.append(id) logging.debug("TCP ID: %s, RX: %s", id.hex(), message.decode('utf-8').rstrip()) if message: logging.debug("ZMQ PUB TX: %s", message.decode('utf-8').rstrip()) self.tx_zmq_pub.send(message) else: if id in self.socket_ids: logging.debug("Removed Socket ID: " + id.hex()) self.socket_ids.remove(id) if self.rx_zmq_sub in socks: data = self.rx_zmq_sub.recv() for id in self.socket_ids: logging.debug("TCP ID: %s, Tx: %s", id.hex(), data.decode('utf-8').rstrip()) __zmq_tcp_send(id, data) if rx_zconf_pull in socks: name, action, ip_address, sub_port, pub_port = rx_zconf_pull.recv_multipart( ) if action == b'add': logging.debug("Added device: %s", name.decode('utf-8')) self.connect_zmq_device(name, ip_address, sub_port, pub_port) elif action == b'remove': logging.debug("Remove device: %s", name.decode('utf-8')) try: self.disconnect_zmq_device(name, ip_address, sub_port, pub_port) except zmq.error.ZMQError: pass self.zeroconf.unregister_all_services() self.zeroconf.close() self.tcpsocket.close() self.tx_zmq_pub.close() self.rx_zmq_sub.close() self.rx_zconf_pull.close()
class Server(object): ''' NetRNG server ''' def __init__(self, listen_address=None, port=None, max_clients=None, sample_size_bytes=None, hwrng_device=None, use_zeroconf=False): log.info('NetRNG server: initializing') # Listen address used by the server self.listen_address = listen_address # TCP port to listen on self.port = port # Maximum number of clients to accept, this prevents your HWRNG from being # overloaded, starving clients. This requires testing and depends entirely on # how fast your HWRNG can be read. A device that can spit out 1mbps (100KB/s) could # give 100 clients 1KB/s, but a device that can only generate 128bps may only # be able to serve 1 client slowly self.max_clients = max_clients # How much random data to request from the device for each client push self.sample_size_bytes = sample_size_bytes # Source device to use for random data, should be something fast and # high quality, DON'T set this to /dev/random self.hwrng_device = hwrng_device # open the hwrng for reading later during client requests self.hwrng = open(self.hwrng_device, 'rb') # lock to prevent multiple clients from getting the same random samples self.rng_lock = RLock() self.use_zeroconf = use_zeroconf if self.use_zeroconf: self.zeroconf_controller = Zeroconf() def broadcast_service(self): if self.listen_address == '0.0.0.0': raise Exception('NetRNG server: zeroconf currently requires a specific listen address in /etc/netrng.conf') desc = {'version': __version__} info = ServiceInfo('_netrng._tcp.local.', '{}._netrng._tcp.local.'.format(socket.gethostname()), socket.inet_aton(self.listen_address), self.port, 0, 0, desc) log.info('NetRNG server: registering service with Bonjour: %s', info) self.zeroconf_controller.register_service(info) def unregister_service(self): log.info('NetRNG server: unregistering all bonjour services') self.zeroconf_controller.unregister_all_services() def serve(self, sock, address): ''' Serves client connections providing random samples to them in a one-to-many request response architecture, with locking to ensure each client gets unique samples ''' log.debug('NetRNG server: client connected %s', address) try: while True: log.debug('NetRNG server: receive cycle start') requestmsg = b"" with Timeout(3, gevent.Timeout): while True: data = sock.recv(1024) requestmsg = requestmsg + data log.debug('NetRNG server: receive cycle') if SOCKET_DELIMITER in requestmsg: break gevent.sleep() requestmsg = requestmsg.replace(SOCKET_DELIMITER, b'') request = msgpack.unpackb(requestmsg) log.debug('NetRNG server: receive cycle done') log.debug('NetRNG server: request received %s', request) if request[b'get'] == b'sample': with self.rng_lock: log.debug('NetRNG server: rng lock acquired') sample = self.hwrng.read(self.sample_size_bytes) log.debug('NetRNG server: rng lock released') log.debug('NetRNG server: sending response') responsemsg = msgpack.packb({b'push': b'sample', b'sample': sample}) sock.sendall(responsemsg + SOCKET_DELIMITER) if request[b'get'] == b'heartbeat': log.debug('NetRNG server: sending heartbeat response to %s', address) responsemsg = msgpack.packb({b'push': b'heartbeat'}) sock.sendall(responsemsg + SOCKET_DELIMITER) except socket.error as e: if isinstance(e.args, tuple): if e[0] == errno.EPIPE: log.debug('NetRNG server: client disconnected %s', address) else: log.exception('NetRNG server: socket error %s', e) except gevent.Timeout as timeout: log.debug('NetRNG server: client socket timeout') except Exception as e: log.exception('NetRNG server: %s', e) finally: sock.close() def calibrate(self): ''' Naive implementation of auto-calibration for entropy source, should check how much entropy can be received in a given number of seconds and use that information to decide how much entropy can be distributed per second. With that information, it should be possible to decide how many clients can be promised `sample_size_bytes` per second ''' log.info('NetRNG server: starting entropy source performance calibration') calibration_period = 15 # seconds received_entropy = "" stop_time = time.time() + calibration_period while time.time() < stop_time: with self.rng_lock: received_entropy += self.hwrng.read(self.sample_size_bytes) received_entropy_size = len(received_entropy) received_entropy_per_second = received_entropy_size / calibration_period log.info('NetRNG server: entropy source can provide %.2f bytes per second', received_entropy_per_second) def start(self): ''' Server starts listening on a TCP socket and spawns a greenlet for each new connection. Blocks caller. ''' self.pool = Pool(self.max_clients) self.server = StreamServer((self.listen_address, self.port), self.serve, spawn=self.pool) log.info('NetRNG server: serving up to %d connections on %s:%d)', self.max_clients, self.listen_address, self.port) try: self.server.start() if self.use_zeroconf: self.broadcast_service() gevent.wait() except KeyboardInterrupt as e: log.debug('NetRNG server: exiting due to keyboard interrupt') sys.exit(0) def stop(self): ''' Server stops listening on the TCP socket, stops accepting new connections and finally kills spawned connection handlers ''' log.debug('NetRNG server: stopping server and killing existing client connections') if self.use_zeroconf: self.unregister_service() self.server.stop()