From d428c5019bc326746fd7c69f1972d2b861f6488b Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Sat, 18 Jan 2020 13:43:25 -0500 Subject: [PATCH 01/11] confserver proxy - app is proxying correctly --- bumper/__init__.py | 11 ++- bumper/confserver.py | 174 +++++++++++++++++++++++++++++++++++-------- 2 files changed, 152 insertions(+), 33 deletions(-) diff --git a/bumper/__init__.py b/bumper/__init__.py index ff03f00..7fd97b7 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -55,6 +55,7 @@ bumper_debug = strtobool(os.environ.get("BUMPER_DEBUG")) or False use_auth = False token_validity_seconds = 3600 # 1 hour db = None +bumper_proxy_mode = strtobool(os.environ.get("BUMPER_PROXY_MODE")) or False mqtt_server = None mqtt_helperbot = None @@ -220,7 +221,10 @@ async def start(): await asyncio.sleep(0.1) # Start web servers - conf_server.confserver_app() + if bumper_proxy_mode: + conf_server.confserver_proxy_app() + else: + conf_server.confserver_app() asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True)) asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False)) @@ -350,12 +354,17 @@ def main(argv=None): help="announce address to bots on checkin", ) parser.add_argument("--debug", action="store_true", help="enable debug logs") + parser.add_argument("--proxy-mode", action="store_true", help="enable proxy mode") args = parser.parse_args(args=argv) if args.debug: bumper_debug = True + if args.proxy_mode: + global bumper_proxy_mode + bumper_proxy_mode = True + if args.listen: bumper_listen = args.listen diff --git a/bumper/confserver.py b/bumper/confserver.py index 8b9cec2..61fb666 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -12,6 +12,7 @@ from bumper import plugins from datetime import datetime, timedelta import asyncio from aiohttp import web +import aiohttp import aiohttp_jinja2 import jinja2 import uuid @@ -54,6 +55,93 @@ class ConfServer: def get_milli_time(self, timetoconvert): return int(round(timetoconvert * 1000)) + def confserver_proxy_app(self): + self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[ + self.log_all_requests, + ]) + + + self.app.add_routes( + [ + web.route("*", "/{path:.*}", self.handle_proxy, name="confserver_proxy"), + ] + ) + + async def handle_proxy(self, request): + + try: + ecoresp = "" + + # Ecovacs IP for proxy mode testing + ecouser_net_ip = "47.88.66.164" + ecovacs_com_ip = "47.252.51.29" + eco_us_api = "47.89.135.130" + mq_na_ip = "47.254.52.46" + recommender_ip = "47.111.101.11" + bigdata_international_ip = "47.88.132.151" + bigdata_northamerica_ip = "47.88.66.111" + + server_port = 443 #default to 443 + if "_SSLProtocolTransport" != type(request.transport).__name__ and "_SelectorSocketTransport" != type(request.transport).__name__: #check not ssl transport class + if "_extra" in request.transport: + if "sockname" in request.transport._extra: + server_port = request.transport._extra["sockname"][1] + + if request.raw_path == "/": + return web.Response(text="Bumper in Proxy Mode") + if request.raw_path == "/lookup.do": + return await self.handle_lookup(request) + #ecorequest = f"{request.scheme}://{ecouser_net_ip}" + elif "ecovacs.com" in request.host: + ecorequest = f"{request.scheme}://{ecovacs_com_ip}" + elif "ecouser.net" in request.host: + ecorequest = f"{request.scheme}://{ecouser_net_ip}" + elif "eco-us-api" in request.host: + ecorequest = f"{request.scheme}://{eco_us_api}" + elif "mq-" in request.host: + ecorequest = f"{request.scheme}://{mq_na_ip}" + elif "recommender" in request.host: + ecorequest = f"{request.scheme}://{recommender_ip}" + elif "bigdata-international" in request.host: + ecorequest = f"{request.scheme}://{bigdata_international_ip}" + elif "bigdata-northamerica" in request.host: + ecorequest = f"{request.scheme}://{bigdata_northamerica_ip}" + else: + ecorequest = f"{request.scheme}://{request.host}" + + if server_port != 443: + ecorequest = f"{ecorequest}:{server_port}" + + ecorequest = f"{ecorequest}{request.path_qs}" + requestheaders = {'host': request.host} + async with aiohttp.ClientSession(headers=requestheaders, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: + if request.content.total_bytes > 0: + confserverlog.debug(f"Proxy Request to EcoVacs (body=true) (host:{request.host}) - {ecorequest} - {request._read_bytes}") + jdata = json.loads(request._read_bytes.decode('utf8').replace("'",'"')) #convert bytes to json for sending + async with session.request(request.method, ecorequest, json=jdata) as resp: + ecoresp = await resp.text() + + else: + confserverlog.debug(f"Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}") + async with session.request(request.method, ecorequest) as resp: + ecoresp = await resp.text() + + confserverlog.debug(f"Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") + + if resp.status == 200: + ecoresp = json.loads(ecoresp) + + if resp.status == 200: + return web.json_response(ecoresp) + else: + return web.Response(text=ecoresp) + + + except Exception as e: + confserverlog.exception("{}".format(e)) + return web.Response(text="") + + def confserver_app(self): self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[ self.log_all_requests, @@ -236,41 +324,63 @@ class ConfServer: postbody = None response = await handler(request) - if not "application/octet-stream" in response.content_type: - logall = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - }, + if response: + if not "application/octet-stream" in response.content_type: + try: + logall = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + }, - "response": { - "response_body": f"{json.loads(response.body)}", - "status": f"{response.status}", - } - } - else: - logall = { - "request": { - "route_name": f"{request.match_info.route.name}", - "method": f"{request.method}", - "path": f"{request.path}", - "query_string": f"{request.query_string}", - "raw_path": f"{request.raw_path}", - "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', - "body": f"{postbody}", - }, + "response": { + #"response_body": f"{json.loads(response.body)}", + "response_body": f"{json.loads(response.text)}", + "status": f"{response.status}", + } + } + except Exception as e: + logall = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + }, - "response": { - "status": f"{response.status}", - } - } + "response": { + #"response_body": f"{json.loads(response.body)}", + "response_body": f"{(response.text)}", + "status": f"{response.status}", + } + } - confserverlog.debug(json.dumps(logall)) + else: + logall = { + "request": { + "route_name": f"{request.match_info.route.name}", + "method": f"{request.method}", + "path": f"{request.path}", + "query_string": f"{request.query_string}", + "raw_path": f"{request.raw_path}", + "raw_headers": f'{",".join(map("{}".format, request.raw_headers))}', + "body": f"{postbody}", + }, + + "response": { + "status": f"{response.status}", + } + } + + confserverlog.debug(json.dumps(logall)) return response From aca40e0e574b6e64797565bd2e95ebf608c776f1 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 00:51:22 -0500 Subject: [PATCH 02/11] mqtt proxy - mqtt and confserver proxy mode working --- bumper/__init__.py | 13 +++ bumper/confserver.py | 82 +++++++++++++---- bumper/mqttserver.py | 208 ++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 272 insertions(+), 31 deletions(-) diff --git a/bumper/__init__.py b/bumper/__init__.py index 7fd97b7..8369477 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -105,6 +105,16 @@ mqttserverlog.addHandler(mqtt_rotate) # Override the logging level # mqttserverlog.setLevel(logging.INFO) +proxymodelog = logging.getLogger("proxymode") +proxymode_rotate = RotatingFileHandler( + "logs/proxymode.log", maxBytes=5000000, backupCount=5 +) +proxymode_rotate.setFormatter(logformat) +proxymodelog.addHandler(proxymode_rotate) + +# Override the logging level +# mqttserverlog.setLevel(logging.INFO) + ### Additional MQTT Logs translog = logging.getLogger("transitions") translog.addHandler(mqtt_rotate) @@ -207,6 +217,8 @@ async def start(): # Start MQTT Server asyncio.create_task(mqtt_server.broker_coro()) + await asyncio.sleep(0.5) #Wait half a sec for broker to start + # Start MQTT Helperbot asyncio.create_task(mqtt_helperbot.start_helper_bot()) @@ -225,6 +237,7 @@ async def start(): conf_server.confserver_proxy_app() else: conf_server.confserver_app() + asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf1_listen_port, usessl=True)) asyncio.create_task(conf_server.start_site(conf_server.app, address=bumper_listen, port=conf2_listen_port, usessl=False)) diff --git a/bumper/confserver.py b/bumper/confserver.py index 61fb666..234fd5a 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -19,6 +19,7 @@ import uuid import xml.etree.ElementTree as ET + class aiohttp_filter(logging.Filter): def filter(self, record): if ( @@ -41,6 +42,7 @@ logging.getLogger("aiohttp.access").addFilter( aiohttp_filter() ) # Add logging filter above to aiohttp.access +proxymodelog = logging.getLogger("proxymode") class ConfServer: def __init__(self, address, usessl=False): @@ -56,9 +58,11 @@ class ConfServer: return int(round(timetoconvert * 1000)) def confserver_proxy_app(self): - self.app = web.Application(loop=asyncio.get_event_loop(), middlewares=[ + + self.app = web.Application(middlewares=[ self.log_all_requests, ]) + aiohttp_jinja2.setup(self.app, loader=jinja2.FileSystemLoader(os.path.join(bumper.bumper_dir,"bumper","web","templates"))) self.app.add_routes( @@ -66,6 +70,8 @@ class ConfServer: web.route("*", "/{path:.*}", self.handle_proxy, name="confserver_proxy"), ] ) + + return self.app async def handle_proxy(self, request): @@ -88,10 +94,9 @@ class ConfServer: server_port = request.transport._extra["sockname"][1] if request.raw_path == "/": - return web.Response(text="Bumper in Proxy Mode") + return await self.handle_base(request) if request.raw_path == "/lookup.do": - return await self.handle_lookup(request) - #ecorequest = f"{request.scheme}://{ecouser_net_ip}" + return await self.handle_lookup(request) #use bumper to handle lookup so bot gets Bumper IP and not Ecovacs elif "ecovacs.com" in request.host: ecorequest = f"{request.scheme}://{ecovacs_com_ip}" elif "ecouser.net" in request.host: @@ -116,29 +121,38 @@ class ConfServer: requestheaders = {'host': request.host} async with aiohttp.ClientSession(headers=requestheaders, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: if request.content.total_bytes > 0: - confserverlog.debug(f"Proxy Request to EcoVacs (body=true) (host:{request.host}) - {ecorequest} - {request._read_bytes}") - jdata = json.loads(request._read_bytes.decode('utf8').replace("'",'"')) #convert bytes to json for sending + proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=true) (host:{request.host}) - {ecorequest} - {request._read_bytes}") + jdata = request._read_bytes.decode('utf8') + jdata = json.loads(jdata) async with session.request(request.method, ecorequest, json=jdata) as resp: ecoresp = await resp.text() + ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) + proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") else: - confserverlog.debug(f"Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}") + proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}") async with session.request(request.method, ecorequest) as resp: - ecoresp = await resp.text() - - confserverlog.debug(f"Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") - + ecoresp = await asyncio.shield(resp.text()) + ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) + proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") + if resp.status == 200: - ecoresp = json.loads(ecoresp) - - if resp.status == 200: - return web.json_response(ecoresp) + if resp.content_type == "application/json": + ecoresp = json.loads(ecoresp) + return web.json_response(ecoresp) + else: + return web.Response(text=ecoresp) + else: return web.Response(text=ecoresp) - - + + except asyncio.CancelledError as e: + proxymodelog.error(f"Request cancelled or timeout - {ecorequest} - {jdata}") + return web.Response(text="") + pass + except Exception as e: - confserverlog.exception("{}".format(e)) + proxymodelog.exception("{}".format(e)) return web.Response(text="") @@ -198,9 +212,11 @@ class ConfServer: async def start_site(self, app, address='localhost', port=8080, usessl=False): + runner = web.AppRunner(app) self.runners.append(runner) await runner.setup() + if usessl: ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key) @@ -217,6 +233,36 @@ class ConfServer: ) await site.start() + + def start_site_thread(self, app, address='localhost', port=8080, usessl=False): + #test for new thread and loop + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + runner = web.AppRunner(app) + self.runners.append(runner) + #await runner.setup() + loop.run_until_complete(runner.setup()) #for thread test + + if usessl: + ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH) + ssl_ctx.load_cert_chain(bumper.server_cert, bumper.server_key) + site = web.TCPSite( + runner, + host=address, + port=port, + ssl_context=ssl_ctx, + ) + + else: + site = web.TCPSite( + runner, host=address, port=port + ) + + #await site.start() + #for thread test + loop.run_until_complete(site.start()) + loop.run_forever() async def start_server(self): try: diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index f53d1f8..6529006 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -14,10 +14,21 @@ import json from datetime import datetime, timedelta import bumper from passlib.apps import custom_app_context as pwd_context +import ssl +import tempfile + +from urllib.parse import urlparse, urlunparse +from hbmqtt.mqtt.protocol.client_handler import ClientProtocolHandler +from hbmqtt.adapters import StreamReaderAdapter, StreamWriterAdapter, WebSocketsReader, WebSocketsWriter +from websockets.uri import InvalidURI +from websockets.exceptions import InvalidHandshake +from hbmqtt.mqtt.protocol.handler import ProtocolHandlerException +from hbmqtt.mqtt.connack import CONNECTION_ACCEPTED helperbotlog = logging.getLogger("helperbot") boterrorlog = logging.getLogger("boterror") mqttserverlog = logging.getLogger("mqttserver") +proxymodelog = logging.getLogger("proxymode") class MQTTHelperBot: @@ -45,8 +56,10 @@ class MQTTHelperBot: await self.Client.subscribe( [ ("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0), + ("iot/p2p/+/+/+/+/+/+/+/+/+/+", QOS_0), ("iot/p2p/+", QOS_0), ("iot/atr/+", QOS_0), + ] ) @@ -216,10 +229,120 @@ class MQTTServer: except Exception as e: mqttserverlog.exception("{}".format(e)) +class BumperProxyModeMQTTClient(MQTTClient): + ecohelpername = "" + async def _connect_coro(self): #Override default to ignore ssl verification + kwargs = dict() + + # Decode URI attributes + uri_attributes = urlparse(self.session.broker_uri) + scheme = uri_attributes.scheme + secure = True if scheme in ('mqtts', 'wss') else False + self.session.username = self.session.username if self.session.username else uri_attributes.username + self.session.password = self.session.password if self.session.password else uri_attributes.password + self.session.remote_address = uri_attributes.hostname + self.session.remote_port = uri_attributes.port + if scheme in ('mqtt', 'mqtts') and not self.session.remote_port: + self.session.remote_port = 8883 if scheme == 'mqtts' else 1883 + if scheme in ('ws', 'wss') and not self.session.remote_port: + self.session.remote_port = 443 if scheme == 'wss' else 80 + if scheme in ('ws', 'wss'): + # Rewrite URI to conform to https://tools.ietf.org/html/rfc6455#section-3 + uri = (scheme, self.session.remote_address + ":" + str(self.session.remote_port), uri_attributes[2], + uri_attributes[3], uri_attributes[4], uri_attributes[5]) + self.session.broker_uri = urlunparse(uri) + # Init protocol handler + #if not self._handler: + self._handler = ClientProtocolHandler(self.plugins_manager, loop=self._loop) + + if secure: + sc = ssl.create_default_context( + ssl.Purpose.SERVER_AUTH, + cafile=self.session.cafile, + capath=self.session.capath, + cadata=self.session.cadata) + if 'certfile' in self.config and 'keyfile' in self.config: + sc.load_cert_chain(self.config['certfile'], self.config['keyfile']) + if 'check_hostname' in self.config and isinstance(self.config['check_hostname'], bool): + sc.check_hostname = self.config['check_hostname'] + + sc.verify_mode = ssl.CERT_NONE #Ignore verify of cert + kwargs['ssl'] = sc + + try: + reader = None + writer = None + self._connected_state.clear() + # Open connection + if scheme in ('mqtt', 'mqtts'): + conn_reader, conn_writer = \ + await asyncio.open_connection( + self.session.remote_address, + self.session.remote_port, loop=self._loop, **kwargs) + reader = StreamReaderAdapter(conn_reader) + writer = StreamWriterAdapter(conn_writer) + elif scheme in ('ws', 'wss'): + websocket = await websockets.connect( + self.session.broker_uri, + subprotocols=['mqtt'], + loop=self._loop, + extra_headers=self.extra_headers, + **kwargs) + reader = WebSocketsReader(websocket) + writer = WebSocketsWriter(websocket) + # Start MQTT protocol + self._handler.attach(self.session, reader, writer) + return_code = await self._handler.mqtt_connect() + if return_code is not CONNECTION_ACCEPTED: + self.session.transitions.disconnect() + self.logger.warning("Connection rejected with code '%s'" % return_code) + exc = ConnectException("Connection rejected by broker") + exc.return_code = return_code + raise exc + else: + # Handle MQTT protocol + await self._handler.start() + self.session.transitions.connect() + self._connected_state.set() + self.logger.debug("connected to %s:%s" % (self.session.remote_address, self.session.remote_port)) + return return_code + except InvalidURI as iuri: + self.logger.warning("connection failed: invalid URI '%s'" % self.session.broker_uri) + self.session.transitions.disconnect() + raise ConnectException("connection failed: invalid URI '%s'" % self.session.broker_uri, iuri) + except InvalidHandshake as ihs: + self.logger.warning("connection failed: invalid websocket handshake") + self.session.transitions.disconnect() + raise ConnectException("connection failed: invalid websocket handshake", ihs) + except (ProtocolHandlerException, ConnectionError, OSError) as e: + self.logger.warning("MQTT connection failed: %r" % e) + self.session.transitions.disconnect() + raise ConnectException(e) + + async def get_msg(self): + try: + while self._connected_state._value: + message = await self.deliver_message() + msgdata = str(message.data.decode("utf-8")) + + proxymodelog.info(f"MQTT Proxy Client - Message Received From Ecovacs - Topic: {message.topic} - Message: {msgdata}") + ttopic = message.topic.split("/") + self.ecohelpername = ttopic[3] + ttopic[3] = "proxyhelper" + ttopic_comb = "/".join(ttopic) + proxymodelog.info(f"MQTT Proxy Client - Converted Topic From {message.topic} TO {ttopic_comb}") + proxymodelog.info(f"MQTT Proxy Client - Proxy Forward Message to Helperbot - Topic: {ttopic_comb} - Message: {msgdata.encode()}") + await bumper.mqtt_helperbot.Client.publish( + ttopic_comb, msgdata.encode(), QOS_0 + ) + + except Exception as e: + proxymodelog.error(f"MQTT Proxy Client - get_msg Exception - {e}") class BumperMQTTServer_Plugin: + proxyclients = {} def __init__(self, context): - self.context = context + self.context = context try: self.auth_config = self.context.config["auth"] self._users = dict() @@ -232,6 +355,8 @@ class BumperMQTTServer_Plugin: except Exception as e: mqttserverlog.exception("{}".format(e)) + + async def authenticate(self, *args, **kwargs): authenticated = False @@ -256,6 +381,24 @@ class BumperMQTTServer_Plugin: ) mqttserverlog.info(f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]} - Class: {tmpbotdetail[0]}") authenticated = True + mq_na_ip = "47.254.52.46" + if authenticated and bumper.bumper_proxy_mode: + proxymodelog.info(f"MQTT Proxy Mode - Proxy Bot to MQTT - Client_id: {client_id} - Username: {username}") + + self.proxyclients[client_id] = BumperProxyModeMQTTClient( + client_id=client_id, config={"check_hostname": False} + ) + + try: + await self.proxyclients[client_id].connect( + f"mqtts://{username}:{password}@{mq_na_ip}:8883", + ) + except Exception as e: + mqttserverlog.error(f"MQTT Proxy Mode - Exception connecting with proxy to ecovacs - {e}") + pass + proxymodelog.info(f"MQTT Proxy Mode - Proxy Bot Connected - Client_id: {client_id}") + asyncio.create_task(self.proxyclients[client_id].get_msg()) + else: tmpclientdetail = str(didsplit[1]).split("/") @@ -327,6 +470,17 @@ class BumperMQTTServer_Plugin: except FileNotFoundError: self.context.logger.warning(f"Password file {password_file} not found") + async def on_broker_client_subscribed(self, client_id, topic, qos): + if bumper.bumper_proxy_mode: #if proxy mode, also subscribe on ecovacs server + if client_id in self.proxyclients: + await self.proxyclients[client_id].subscribe( + [ + (topic, qos) + ] + ) + #return + #pass + async def on_broker_client_connected(self, client_id): didsplit = str(client_id).split("@") @@ -336,16 +490,39 @@ class BumperMQTTServer_Plugin: bumper.bot_set_mqtt(bot["did"], True) return - clientresource = didsplit[1].split("/")[1] - client = bumper.client_get(clientresource) - if client: - bumper.client_set_mqtt(client["resource"], True) - return + if len(didsplit) > 1: + clientresource = didsplit[1].split("/")[1] + client = bumper.client_get(clientresource) + if client: + bumper.client_set_mqtt(client["resource"], True) + return async def on_broker_message_received(self, client_id, message): - self.handle_helperbot_msg(client_id, message) + await self.handle_helperbot_msg(client_id, message) - def handle_helperbot_msg(self, client_id, message): + async def handle_helperbot_msg(self, client_id, message): + if bumper.bumper_proxy_mode: + if client_id in self.proxyclients: + msgdata = str(message.data.decode("utf-8")) + if not str(message.topic).split("/")[3] == "proxyhelper": # if from proxyhelper, don't send back to ecovacs...yet + if str(message.topic).split("/")[6] == "proxyhelper": + ttopic = message.topic.split("/") + ttopic[6] = self.proxyclients[client_id].ecohelpername + ttopic_join = "/".join(ttopic) + proxymodelog.info(f"MQTT Proxy Client - Bot Message Converted Topic From {message.topic} TO {ttopic_join} with message: {msgdata}") + else: + ttopic_join = message.topic + proxymodelog.info(f"MQTT Proxy Client - Bot Message From {ttopic_join} with message: {msgdata}") + + try: + # Send back to ecovacs + proxymodelog.info(f"MQTT Proxy Client - Proxy Forward Message to Ecovacs - Topic: {ttopic_join} - Message: {msgdata.encode()}") + await self.proxyclients[client_id].publish( + ttopic_join, msgdata.encode(), message.qos + ) + except Exception as e: + proxymodelog.error(f"MQTT Proxy Client - Forwarding to Ecovacs Exception - {e}") + if str(message.topic).split("/")[6] == "helperbot": # Response to command @@ -407,6 +584,10 @@ class BumperMQTTServer_Plugin: async def on_broker_client_disconnected(self, client_id): + if bumper.bumper_proxy_mode: + if client_id in self.proxyclients: + await self.proxyclients[client_id].disconnect() + didsplit = str(client_id).split("@") bot = bumper.bot_get(didsplit[0]) @@ -414,8 +595,9 @@ class BumperMQTTServer_Plugin: bumper.bot_set_mqtt(bot["did"], False) return - clientresource = didsplit[1].split("/")[1] - client = bumper.client_get(clientresource) - if client: - bumper.client_set_mqtt(client["resource"], False) - return + if len(didsplit) > 1: + clientresource = didsplit[1].split("/")[1] + client = bumper.client_get(clientresource) + if client: + bumper.client_set_mqtt(client["resource"], False) + return From 3e8ad68eb274dcf2f433604bfb27ad2913967ca9 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 08:15:02 -0500 Subject: [PATCH 03/11] slim helperbot subscriptions replace subscriptions with consolidated iot/# --- bumper/mqttserver.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 6529006..aa7bd47 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -55,11 +55,7 @@ class MQTTHelperBot: ) await self.Client.subscribe( [ - ("iot/p2p/+/+/+/+/helperbot/bumper/helperbot/+/+/+", QOS_0), - ("iot/p2p/+/+/+/+/+/+/+/+/+/+", QOS_0), - ("iot/p2p/+", QOS_0), - ("iot/atr/+", QOS_0), - + ("iot/#", QOS_0), ] ) From 3b7b31af47df3630e3b37a9fd6b3906451b006c8 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 08:24:31 -0500 Subject: [PATCH 04/11] log client topic subscription log client topic subscription --- bumper/mqttserver.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index aa7bd47..31cf830 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -474,6 +474,9 @@ class BumperMQTTServer_Plugin: (topic, qos) ] ) + else: + proxymodelog.info(f"MQTT Proxy Mode - New MQTT Topic Subscription - Client: {client_id} - Topic: {topic}") + #return #pass From bdc452b99c7a424a15f39b3a6922a88a23d61983 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 08:34:08 -0500 Subject: [PATCH 05/11] reset connection status at startup reset bot and client connections to false for xmpp/mqtt at startup --- bumper/__init__.py | 3 +++ bumper/db.py | 11 +++++++++++ 2 files changed, 14 insertions(+) diff --git a/bumper/__init__.py b/bumper/__init__.py index 8369477..5b1c079 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -171,6 +171,9 @@ xmpp_listen_port = 5223 async def start(): + #Reset xmpp/mqtt to false in database for bots and clients + bot_reset_connectionStatus() + client_reset_connectionStatus() try: loop = asyncio.get_event_loop() diff --git a/bumper/db.py b/bumper/db.py index 71c158a..3bb4b39 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -289,6 +289,11 @@ def bot_remove(did): if bot: bots.remove(doc_ids=[bot.doc_id]) +def bot_reset_connectionStatus(): + bots = db_get().table("bots") + for bot in bots: + bot_set_mqtt(bot["did"], False) + bot_set_xmpp(bot["did"], False) def bot_get(did): bots = db_get().table("bots") @@ -345,6 +350,12 @@ def client_add(userid, realm, resource): bumperlog.info("Adding new client with resource {}".format(newclient.resource)) client_full_upsert(newclient.asdict()) +def client_reset_connectionStatus(): + clients = db_get().table("clients") + for client in clients: + client_set_mqtt(client["resource"], False) + client_set_xmpp(client["resource"], False) + def client_remove(resource): clients = db_get().table("clients") client = client_get(resource) From 904167a8c5032c0c7ca6af6b7d238a0bba4426a8 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 10:30:10 -0500 Subject: [PATCH 06/11] proxymode db move settings to db and rework code to support --- bumper/__init__.py | 13 ++++++++++ bumper/confserver.py | 48 +++++++++++++++++-------------------- bumper/db.py | 56 ++++++++++++++++++++++++++++++++++++++++++++ bumper/mqttserver.py | 11 +++++++-- 4 files changed, 100 insertions(+), 28 deletions(-) diff --git a/bumper/__init__.py b/bumper/__init__.py index 5b1c079..3b15523 100644 --- a/bumper/__init__.py +++ b/bumper/__init__.py @@ -171,6 +171,8 @@ xmpp_listen_port = 5223 async def start(): + #config_proxyMode_deleteTable() #delete existing proxymode table + #Reset xmpp/mqtt to false in database for bots and clients bot_reset_connectionStatus() client_reset_connectionStatus() @@ -237,6 +239,17 @@ async def start(): # Start web servers if bumper_proxy_mode: + bumperlog.info("Proxy Mode Enabled") + if config_proxyMode_countEntries() == 0: # check if proxymode servers are entered + bumperlog.info("Proxy Mode - No Servers, Loading Defaults (US)") + config_proxyMode_defaults() # set defaults if 0 + + configproxy = config_proxyMode_getall() + cntentries = len(configproxy) + proxymodelog.info(f"Loaded {cntentries} entries from proxyconfig") + for entry in configproxy: + proxymodelog.info(f"Config Entry {entry}") + conf_server.confserver_proxy_app() else: conf_server.confserver_app() diff --git a/bumper/confserver.py b/bumper/confserver.py index 234fd5a..36eb2b5 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -78,15 +78,6 @@ class ConfServer: try: ecoresp = "" - # Ecovacs IP for proxy mode testing - ecouser_net_ip = "47.88.66.164" - ecovacs_com_ip = "47.252.51.29" - eco_us_api = "47.89.135.130" - mq_na_ip = "47.254.52.46" - recommender_ip = "47.111.101.11" - bigdata_international_ip = "47.88.132.151" - bigdata_northamerica_ip = "47.88.66.111" - server_port = 443 #default to 443 if "_SSLProtocolTransport" != type(request.transport).__name__ and "_SelectorSocketTransport" != type(request.transport).__name__: #check not ssl transport class if "_extra" in request.transport: @@ -97,26 +88,31 @@ class ConfServer: return await self.handle_base(request) if request.raw_path == "/lookup.do": return await self.handle_lookup(request) #use bumper to handle lookup so bot gets Bumper IP and not Ecovacs - elif "ecovacs.com" in request.host: - ecorequest = f"{request.scheme}://{ecovacs_com_ip}" - elif "ecouser.net" in request.host: - ecorequest = f"{request.scheme}://{ecouser_net_ip}" - elif "eco-us-api" in request.host: - ecorequest = f"{request.scheme}://{eco_us_api}" - elif "mq-" in request.host: - ecorequest = f"{request.scheme}://{mq_na_ip}" - elif "recommender" in request.host: - ecorequest = f"{request.scheme}://{recommender_ip}" - elif "bigdata-international" in request.host: - ecorequest = f"{request.scheme}://{bigdata_international_ip}" - elif "bigdata-northamerica" in request.host: - ecorequest = f"{request.scheme}://{bigdata_northamerica_ip}" + + matchproxy = bumper.config_proxyMode_getServerIP("app", request.host) + + if matchproxy: + proxymodelog.info(f"Matched {request.host} to entry in proxyconfig!") + ecorequest = f"{request.scheme}://{matchproxy}" else: - ecorequest = f"{request.scheme}://{request.host}" + proxymodelog.info(f"No match for {request.host} in proxyconfig!") + if "ecovacs.com" in request.host: + proxymodelog.info(f"ecovacs.com in {request.host} defaulting to ecovacs.com IP!") + matchproxy = bumper.config_proxyMode_getServerIP("app", "ecovacs.com") + ecorequest = f"{request.scheme}://{matchproxy}" + elif "ecouser.net" in request.host: + proxymodelog.info(f"ecouser.net in {request.host} defaulting to ecouser.net IP!") + matchproxy = bumper.config_proxyMode_getServerIP("app", "ecouser.net") + ecorequest = f"{request.scheme}://{matchproxy}" + else: + proxymodelog.info(f"No matches for {request.host} defaulting to ecovacs.com IP!") + matchproxy = bumper.config_proxyMode_getServerIP("app", "ecovacs.com") + ecorequest = f"{request.scheme}://{matchproxy}" if server_port != 443: ecorequest = f"{ecorequest}:{server_port}" + proxymodelog.info(f"{request.host} - {ecorequest}") ecorequest = f"{ecorequest}{request.path_qs}" requestheaders = {'host': request.host} async with aiohttp.ClientSession(headers=requestheaders, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: @@ -126,14 +122,14 @@ class ConfServer: jdata = json.loads(jdata) async with session.request(request.method, ecorequest, json=jdata) as resp: ecoresp = await resp.text() - ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) + #ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") else: proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}") async with session.request(request.method, ecorequest) as resp: ecoresp = await asyncio.shield(resp.text()) - ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) + #ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") if resp.status == 200: diff --git a/bumper/db.py b/bumper/db.py index 3bb4b39..7488104 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -32,9 +32,65 @@ def db_get(): db.table("clients", cache_size=0) db.table("bots", cache_size=0) db.table("tokens", cache_size=0) + db.table("config_proxymode", cache_size=0) return db +def config_proxyMode_deleteTable(): + opendb = db_get() + opendb.purge_table("config_proxymode") + +def config_proxyMode_defaults(): + defaults = [ + {"type":"app","host":"gl-us-api.ecovacs.com","ip":"47.252.51.29","match":"gl-us-"}, + {"type":"app","host":"gl-us-openapi.ecovacs.com","ip":"47.252.51.29"}, + {"type":"app","host":"portal-ww.ecouser.net","ip":"47.88.66.164","match":"portal-"}, + {"type":"app","host":"bigdata-northamerica.ecovacs.com","ip":"47.88.66.111"}, + {"type":"app","host":"bigdata-international.ecovacs.com","ip":"47.88.132.151"}, + {"type":"app","host":"eco-us-api.ecovacs.com","ip":"47.89.135.130","match":"eco-us-"}, + {"type":"app","host":"ecovacs.com","ip":"47.90.210.46"}, + {"type":"app","host":"ecouser.net","ip":"116.62.93.217"}, + {"type":"mqtt_server","host":"mq-ww.ecouser.net","ip":"47.254.52.46"}, + ] + opendb = db_get() + with opendb: + config = opendb.table("config_proxymode") + config.insert_multiple(defaults) + +def config_proxyMode_getServerIP(type, host): + opendb = db_get() + with opendb: + proxyconfig = opendb.table("config_proxymode") + proxy = Query() + if type == "mqtt_server": + entry = proxyconfig.get((proxy.type == type)) + + else: + entry = proxyconfig.get((proxy.type == type) & (proxy.host == host)) + + if entry: + return entry["ip"] + + else: + proxylist = proxyconfig.search(Query()) + for proxy in proxylist: # check for sub matches + if "match" in proxy: + if proxy["match"] in host: + return proxy["ip"] + return None + +def config_proxyMode_countEntries(): + opendb = db_get() + with opendb: + config = opendb.table("config_proxymode") + return len(config) + +def config_proxyMode_getall(): + opendb = db_get() + with opendb: + config = opendb.table("config_proxymode") + return config.search(Query()) + def user_add(userid): newuser = BumperUser() diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 31cf830..1e63718 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -377,7 +377,14 @@ class BumperMQTTServer_Plugin: ) mqttserverlog.info(f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]} - Class: {tmpbotdetail[0]}") authenticated = True - mq_na_ip = "47.254.52.46" + mqtt_server = bumper.config_proxyMode_getServerIP("mqtt_server","") + if mqtt_server: + proxymodelog.info(f"MQTT Proxy Mode - Using server {mqtt_server}") + else: + proxymodelog.error(f"MQTT Proxy Mode - No server found! Load defaults or set mqtt_server in config_proxymode table!") + proxymodelog.exception(f"MQTT Proxy Mode - Exiting due to no MQTT Server configured!") + exit(1) + if authenticated and bumper.bumper_proxy_mode: proxymodelog.info(f"MQTT Proxy Mode - Proxy Bot to MQTT - Client_id: {client_id} - Username: {username}") @@ -387,7 +394,7 @@ class BumperMQTTServer_Plugin: try: await self.proxyclients[client_id].connect( - f"mqtts://{username}:{password}@{mq_na_ip}:8883", + f"mqtts://{username}:{password}@{mqtt_server}:8883", ) except Exception as e: mqttserverlog.error(f"MQTT Proxy Mode - Exception connecting with proxy to ecovacs - {e}") From a4a77708e5ccb6d5ce7b75fd4af6b81fb9ae355e Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 11:27:22 -0500 Subject: [PATCH 07/11] add bumper api to proxymode add bumper api to proxymode --- bumper/confserver.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bumper/confserver.py b/bumper/confserver.py index 36eb2b5..51db531 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -67,6 +67,10 @@ class ConfServer: self.app.add_routes( [ + + web.get("/bot/remove/{did}", self.handle_RemoveBot, name='remove-bot'), + web.get("/client/remove/{resource}", self.handle_RemoveClient, name='remove-client'), + web.get("/restart_{service}", self.handle_RestartService, name='restart-service'), web.route("*", "/{path:.*}", self.handle_proxy, name="confserver_proxy"), ] ) @@ -87,7 +91,7 @@ class ConfServer: if request.raw_path == "/": return await self.handle_base(request) if request.raw_path == "/lookup.do": - return await self.handle_lookup(request) #use bumper to handle lookup so bot gets Bumper IP and not Ecovacs + return await self.handle_lookup(request) #use bumper to handle lookup so bot gets Bumper IP and not Ecovacs matchproxy = bumper.config_proxyMode_getServerIP("app", request.host) From c2bcb137693acb40164a8a58f7ef831e8feca6bb Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 12:01:01 -0500 Subject: [PATCH 08/11] support android app handle form data sent by android apps --- bumper/confserver.py | 20 ++++++++++++-------- bumper/db.py | 6 +++--- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/bumper/confserver.py b/bumper/confserver.py index 51db531..b6a4403 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -122,18 +122,22 @@ class ConfServer: async with aiohttp.ClientSession(headers=requestheaders, connector=aiohttp.TCPConnector(verify_ssl=False)) as session: if request.content.total_bytes > 0: proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=true) (host:{request.host}) - {ecorequest} - {request._read_bytes}") - jdata = request._read_bytes.decode('utf8') - jdata = json.loads(jdata) - async with session.request(request.method, ecorequest, json=jdata) as resp: - ecoresp = await resp.text() - #ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) - proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") + if request.content_type == "application/x-www-form-urlencoded": # android apps use form + fdata = await request.post() + async with session.request(request.method, ecorequest, data=fdata) as resp: + ecoresp = await resp.text() + proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") + else: # handle json + jdata = request._read_bytes.decode('utf8') + jdata = json.loads(jdata) + async with session.request(request.method, ecorequest, json=jdata) as resp: + ecoresp = await resp.text() + proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") else: proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}") async with session.request(request.method, ecorequest) as resp: - ecoresp = await asyncio.shield(resp.text()) - #ecoresp = ecoresp.replace("portal-ww.ecouser.net", ecouser_net_ip) + ecoresp = await resp.text() proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") if resp.status == 200: diff --git a/bumper/db.py b/bumper/db.py index 7488104..dfa001e 100644 --- a/bumper/db.py +++ b/bumper/db.py @@ -42,12 +42,12 @@ def config_proxyMode_deleteTable(): def config_proxyMode_defaults(): defaults = [ - {"type":"app","host":"gl-us-api.ecovacs.com","ip":"47.252.51.29","match":"gl-us-"}, + {"type":"app","host":"gl-us-api.ecovacs.com","ip":"47.252.51.29","match":"gl-"}, {"type":"app","host":"gl-us-openapi.ecovacs.com","ip":"47.252.51.29"}, {"type":"app","host":"portal-ww.ecouser.net","ip":"47.88.66.164","match":"portal-"}, {"type":"app","host":"bigdata-northamerica.ecovacs.com","ip":"47.88.66.111"}, - {"type":"app","host":"bigdata-international.ecovacs.com","ip":"47.88.132.151"}, - {"type":"app","host":"eco-us-api.ecovacs.com","ip":"47.89.135.130","match":"eco-us-"}, + {"type":"app","host":"bigdata-international.ecovacs.com","ip":"47.88.132.151","match":"bigdata-"}, + {"type":"app","host":"eco-us-api.ecovacs.com","ip":"47.89.135.130","match":"eco-"}, {"type":"app","host":"ecovacs.com","ip":"47.90.210.46"}, {"type":"app","host":"ecouser.net","ip":"116.62.93.217"}, {"type":"mqtt_server","host":"mq-ww.ecouser.net","ip":"47.254.52.46"}, From 346895eff36dca9409a664c86c844d9d8776db20 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 12:07:05 -0500 Subject: [PATCH 09/11] fix tests --- bumper/mqttserver.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bumper/mqttserver.py b/bumper/mqttserver.py index 1e63718..f6f61ed 100644 --- a/bumper/mqttserver.py +++ b/bumper/mqttserver.py @@ -377,15 +377,16 @@ class BumperMQTTServer_Plugin: ) mqttserverlog.info(f"Bumper Authentication Success - Bot - SN: {username} - DID: {didsplit[0]} - Class: {tmpbotdetail[0]}") authenticated = True - mqtt_server = bumper.config_proxyMode_getServerIP("mqtt_server","") - if mqtt_server: - proxymodelog.info(f"MQTT Proxy Mode - Using server {mqtt_server}") - else: - proxymodelog.error(f"MQTT Proxy Mode - No server found! Load defaults or set mqtt_server in config_proxymode table!") - proxymodelog.exception(f"MQTT Proxy Mode - Exiting due to no MQTT Server configured!") - exit(1) if authenticated and bumper.bumper_proxy_mode: + mqtt_server = bumper.config_proxyMode_getServerIP("mqtt_server","") + if mqtt_server: + proxymodelog.info(f"MQTT Proxy Mode - Using server {mqtt_server}") + else: + proxymodelog.error(f"MQTT Proxy Mode - No server found! Load defaults or set mqtt_server in config_proxymode table!") + proxymodelog.exception(f"MQTT Proxy Mode - Exiting due to no MQTT Server configured!") + exit(1) + proxymodelog.info(f"MQTT Proxy Mode - Proxy Bot to MQTT - Client_id: {client_id} - Username: {username}") self.proxyclients[client_id] = BumperProxyModeMQTTClient( From f97c07ac7b15f7a2fd5b4f30d40b3f015f15a5a4 Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 12:36:36 -0500 Subject: [PATCH 10/11] handle images and bytes handle images and bytes --- bumper/confserver.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bumper/confserver.py b/bumper/confserver.py index b6a4403..1e454ed 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -137,13 +137,18 @@ class ConfServer: else: proxymodelog.info(f"HTTP Proxy Request to EcoVacs (body=false) (host:{request.host}) - {ecorequest}") async with session.request(request.method, ecorequest) as resp: - ecoresp = await resp.text() + if resp.content_type == "application/octet-stream": + ecoresp = await resp.read() + else: + ecoresp = await resp.text() proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") if resp.status == 200: if resp.content_type == "application/json": ecoresp = json.loads(ecoresp) return web.json_response(ecoresp) + elif resp.content_type == "application/octet-stream": + return web.Response(body=ecoresp) else: return web.Response(text=ecoresp) From fe8e4b06156d9696a0d4709b52532c2ef29f2a8e Mon Sep 17 00:00:00 2001 From: Brian Martin Date: Mon, 20 Jan 2020 12:49:19 -0500 Subject: [PATCH 11/11] show for binary/images --- bumper/confserver.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/bumper/confserver.py b/bumper/confserver.py index 1e454ed..dfe348b 100644 --- a/bumper/confserver.py +++ b/bumper/confserver.py @@ -139,9 +139,10 @@ class ConfServer: async with session.request(request.method, ecorequest) as resp: if resp.content_type == "application/octet-stream": ecoresp = await resp.read() + proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - ") else: ecoresp = await resp.text() - proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") + proxymodelog.info(f"HTTP Proxy Response from EcoVacs (URL: {ecorequest}) - (Status: {resp.status}) - {ecoresp}") if resp.status == 200: if resp.content_type == "application/json":