|
| 1 | +# -*- coding: utf-8 -*- |
| 2 | +""" |
| 3 | + proxy.py |
| 4 | + ~~~~~~~~ |
| 5 | + ⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on |
| 6 | + Network monitoring, controls & Application development, testing, debugging. |
| 7 | +
|
| 8 | + :copyright: (c) 2013-present by Abhinav Singh and contributors. |
| 9 | + :license: BSD, see LICENSE for more details. |
| 10 | +""" |
| 11 | +from abc import ABC, abstractmethod |
| 12 | + |
| 13 | +import ssl |
| 14 | +import socket |
| 15 | +import logging |
| 16 | + |
| 17 | +from typing import Tuple, List, Optional, Any |
| 18 | + |
| 19 | +from ...common.types import Readables, Writables |
| 20 | +from ...core.connection import TcpServerConnection |
| 21 | + |
| 22 | +logger = logging.getLogger(__name__) |
| 23 | + |
| 24 | + |
| 25 | +class TcpUpstreamConnectionHandler(ABC): |
| 26 | + """:class:`~proxy.core.base.TcpUpstreamConnectionHandler` can |
| 27 | + be used to insert an upstream server connection lifecycle within |
| 28 | + asynchronous proxy.py lifecycle. |
| 29 | +
|
| 30 | + Call `initialize_upstream` to initialize the upstream connection object. |
| 31 | + Then, directly use ``self.upstream`` object within your class. |
| 32 | +
|
| 33 | + .. spelling:: |
| 34 | +
|
| 35 | + tcp |
| 36 | + """ |
| 37 | + |
| 38 | + def __init__(self, *args: Any, **kwargs: Any) -> None: |
| 39 | + # This is currently a hack, see comments below for rationale, |
| 40 | + # will be fixed later. |
| 41 | + super().__init__(*args, **kwargs) # type: ignore |
| 42 | + self.upstream: Optional[TcpServerConnection] = None |
| 43 | + # TODO: Currently, :class:`~proxy.core.base.TcpUpstreamConnectionHandler` |
| 44 | + # is used within :class:`~proxy.plugin.ReverseProxyPlugin` and |
| 45 | + # :class:`~proxy.plugin.ProxyPoolPlugin`. |
| 46 | + # |
| 47 | + # For both of which we expect a 4-tuple as arguments |
| 48 | + # containing (uuid, flags, client, event_queue). |
| 49 | + # We really don't need the rest of the args here. |
| 50 | + # May be uuid? May be event_queue in the future. |
| 51 | + # But certainly we don't not client here. |
| 52 | + # A separate tunnel class must be created which handles |
| 53 | + # client connection too. |
| 54 | + # |
| 55 | + # Both :class:`~proxy.plugin.ReverseProxyPlugin` and |
| 56 | + # :class:`~proxy.plugin.ProxyPoolPlugin` are currently |
| 57 | + # calling client queue within `handle_upstream_data` callback. |
| 58 | + # |
| 59 | + # This can be abstracted out too. |
| 60 | + self.server_recvbuf_size = args[1].server_recvbuf_size |
| 61 | + self.total_size = 0 |
| 62 | + |
| 63 | + @abstractmethod |
| 64 | + def handle_upstream_data(self, raw: memoryview) -> None: |
| 65 | + pass |
| 66 | + |
| 67 | + def initialize_upstream(self, addr: str, port: int) -> None: |
| 68 | + self.upstream = TcpServerConnection(addr, port) |
| 69 | + |
| 70 | + def get_descriptors(self) -> Tuple[List[socket.socket], List[socket.socket]]: |
| 71 | + if not self.upstream: |
| 72 | + return [], [] |
| 73 | + return [self.upstream.connection], [self.upstream.connection] if self.upstream.has_buffer() else [] |
| 74 | + |
| 75 | + def read_from_descriptors(self, r: Readables) -> bool: |
| 76 | + if self.upstream and self.upstream.connection in r: |
| 77 | + try: |
| 78 | + raw = self.upstream.recv(self.server_recvbuf_size) |
| 79 | + if raw is not None: |
| 80 | + self.total_size += len(raw) |
| 81 | + self.handle_upstream_data(raw) |
| 82 | + else: |
| 83 | + return True # Teardown because upstream proxy closed the connection |
| 84 | + except ssl.SSLWantReadError: |
| 85 | + logger.info('Upstream SSLWantReadError, will retry') |
| 86 | + return False |
| 87 | + except ConnectionResetError: |
| 88 | + logger.debug('Connection reset by upstream') |
| 89 | + return True |
| 90 | + return False |
| 91 | + |
| 92 | + def write_to_descriptors(self, w: Writables) -> bool: |
| 93 | + if self.upstream and self.upstream.connection in w and self.upstream.has_buffer(): |
| 94 | + try: |
| 95 | + self.upstream.flush() |
| 96 | + except ssl.SSLWantWriteError: |
| 97 | + logger.info('Upstream SSLWantWriteError, will retry') |
| 98 | + return False |
| 99 | + except BrokenPipeError: |
| 100 | + logger.debug('BrokenPipeError when flushing to upstream') |
| 101 | + return True |
| 102 | + return False |
0 commit comments