diff --git a/server/src/uds/REST/methods/tunnel.py b/server/src/uds/REST/methods/tunnel.py index aa0343f0..b17d5248 100644 --- a/server/src/uds/REST/methods/tunnel.py +++ b/server/src/uds/REST/methods/tunnel.py @@ -11,7 +11,7 @@ # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. -# * Neither the name of Virtual Cable S.L. nor the names of its contributors +# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # @@ -31,9 +31,9 @@ """ import logging import typing -from uds.models.user_service import UserService from uds import models +from uds.core import managers from uds.REST import Handler from uds.REST import AccessDenied from uds.core.auths.auth import isTrustedSource @@ -42,6 +42,7 @@ from uds.core.util.stats import events logger = logging.getLogger(__name__) +MAX_SESSION_LENGTH = 60*60*24*7 # Enclosed methods under /tunnel path class Tunnel(Handler): @@ -56,12 +57,12 @@ class Tunnel(Handler): Processes get requests, currently none """ logger.debug( - 'Tunnel parameters for GET: %s from %s', self._args, self._request.ip + 'Tunnel parameters for GET: %s (%s) from %s', self._args, self._params, self._request.ip ) if ( not isTrustedSource(self._request.ip) - or len(self._args) > 2 + or len(self._args) != 2 or len(self._args[0]) != 48 ): # Invalid requests @@ -69,16 +70,22 @@ class Tunnel(Handler): # Try to get ticket from DB try: - user, userService, host, port, _ = models.TicketStore.get_for_tunnel( + user, userService, host, port, extra = models.TicketStore.get_for_tunnel( self._args[0] ) - start = len(self._args) == 2 # Start requests include source IP request - - if net.ipToLong(self._args[1][:32]) == 0: - raise Exception('Invalid from IP') - data = {} - if start: + if self._args[1][:4] == 'stop': + sent, recv = self._params['sent'], self._params['recv'] + # Ensures extra exists... + extra = extra or {} + now = models.getSqlDatetimeAsUnix() + totalTime = now - extra.get('b', now-1) + msg = f'User {user.name} stopped tunnel {extra.get("t", "")[:8]}... to {host}:{port}: s:{sent}/r:{recv}/t:{totalTime}.' + log.doLog(user.manager, log.INFO, msg) + log.doLog(userService, log.INFO, msg) + else: + if net.ipToLong(self._args[1][:32]) == 0: + raise Exception('Invalid from IP') events.addEvent( userService.deployed_service, events.ET_TUNNEL_ACCESS, @@ -87,10 +94,22 @@ class Tunnel(Handler): dstip=host, uniqueid=userService.unique_id, ) - msg = f'User {user.name} started tunnel to {host}:{port} from {self._args[1]}.' + msg = f'User {user.name} started tunnel {self._args[0][:8]}... to {host}:{port} from {self._args[1]}.' log.doLog(user.manager, log.INFO, msg) log.doLog(userService, log.INFO, msg) - data = {'host': host, 'port': port} + # Generate new, notify only, ticket + rstr = managers.cryptoManager().randomString(length=8) + notifyTicket = models.TicketStore.create_for_tunnel( + userService=userService, + port=port, + host=host, + extra={'t': self._args[0], 'b': models.getSqlDatetimeAsUnix()}, + validity=MAX_SESSION_LENGTH) + data = { + 'host': host, + 'port': port, + 'notify': notifyTicket + } return data except Exception as e: diff --git a/tunnel-server/src/uds_tunnel/consts.py b/tunnel-server/src/uds_tunnel/consts.py index 394d7fdf..de6ca4d7 100644 --- a/tunnel-server/src/uds_tunnel/consts.py +++ b/tunnel-server/src/uds_tunnel/consts.py @@ -57,4 +57,4 @@ COMMAND_LENGTH = 4 COMMAND_OPEN = b'OPEN' COMMAND_TEST = b'TEST' COMMAND_STAT = b'STAT' # full stats -COMMAND_INFO = b'INFO' # Basic stats \ No newline at end of file +COMMAND_INFO = b'INFO' # Basic stats, currently same as FULL \ No newline at end of file diff --git a/tunnel-server/src/uds_tunnel/proxy.py b/tunnel-server/src/uds_tunnel/proxy.py index 9c2905c6..4b83e1ed 100644 --- a/tunnel-server/src/uds_tunnel/proxy.py +++ b/tunnel-server/src/uds_tunnel/proxy.py @@ -52,6 +52,19 @@ class Proxy: self.cfg = cfg self.ns = ns + @staticmethod + def _getUdsUrl(cfg: config.ConfigurationType, ticket: bytes, msg: str) -> typing.MutableMapping[str, typing.Any]: + try: + url = cfg.uds_server + '/' + ticket.decode() + '/' + msg + r = requests.get(url, headers={'content-type': 'application/json'}) + if not r.ok: + raise Exception(r.content) + + return r.json() + except Exception as e: + raise Exception(f'TICKET COMMS ERROR: {e!s}') + + @staticmethod def getFromUds( cfg: config.ConfigurationType, ticket: bytes, @@ -70,16 +83,12 @@ class Proxy: continue # Correctus raise Exception(f'TICKET INVALID (char {i} at pos {n})') - try: - url = cfg.uds_server + '/' + ticket.decode() + '/' + address[0] - r = requests.get(url, headers={'content-type': 'application/json'}) - if not r.ok: - raise Exception(r.content) - - return r.json() - except Exception as e: - raise Exception(f'TICKET COMMS ERROR: {e!s}') + return Proxy._getUdsUrl(cfg, ticket, address[0]) + @staticmethod + def notifyEndToUds(cfg: config.ConfigurationType, ticket: bytes, counter: stats.Stats) -> None: + msg = f'stop?sent={counter.sent}&recv={counter.recv}' + Proxy._getUdsUrl(cfg, ticket, msg) # Ignore results @staticmethod async def doProxy(source, destination, counter: stats.StatsSingleCounter) -> None: @@ -194,6 +203,7 @@ class Proxy: logger.debug('PROXIES READY') logger.debug('Proxies finalized: %s', grp.exceptions) + await curio.run_in_thread(Proxy.notifyEndToUds, self.cfg, result['notify'].encode(), counter) except Exception as e: if consts.DEBUG: diff --git a/tunnel-server/src/udstunnel.cfg b/tunnel-server/src/udstunnel.cfg index 23f15505..ff5c71fd 100644 --- a/tunnel-server/src/udstunnel.cfg +++ b/tunnel-server/src/udstunnel.cfg @@ -4,7 +4,7 @@ # pidfile = /tmp/udstunnel.pid # Log level, valid are DEBUG, INFO, WARN, ERROR. Defaults to ERROR -loglevel = INFO +loglevel = DEBUG # Log file, Defaults to stdout # logfile = /tmp/tunnel.log