diff options
author | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:16:14 +0300 |
---|---|---|
committer | shmel1k <shmel1k@ydb.tech> | 2023-11-26 18:43:30 +0300 |
commit | b8cf9e88f4c5c64d9406af533d8948deb050d695 (patch) | |
tree | 218eb61fb3c3b96ec08b4d8cdfef383104a87d63 /contrib/python/Twisted/py3/twisted/protocols/haproxy | |
parent | 523f645a83a0ec97a0332dbc3863bb354c92a328 (diff) | |
download | ydb-b8cf9e88f4c5c64d9406af533d8948deb050d695.tar.gz |
add kikimr_configure
Diffstat (limited to 'contrib/python/Twisted/py3/twisted/protocols/haproxy')
8 files changed, 699 insertions, 0 deletions
diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/__init__.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/__init__.py new file mode 100644 index 0000000000..2d13bf5b4c --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/__init__.py @@ -0,0 +1,10 @@ +# -*- test-case-name: twisted.protocols.haproxy.test -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +HAProxy PROXY protocol implementations. +""" +__all__ = ["proxyEndpoint"] + +from ._wrapper import proxyEndpoint diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py new file mode 100644 index 0000000000..9a521ea249 --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_exceptions.py @@ -0,0 +1,49 @@ +# -*- test-case-name: twisted.protocols.haproxy.test -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +HAProxy specific exceptions. +""" + +import contextlib +from typing import Callable, Generator, Type + + +class InvalidProxyHeader(Exception): + """ + The provided PROXY protocol header is invalid. + """ + + +class InvalidNetworkProtocol(InvalidProxyHeader): + """ + The network protocol was not one of TCP4 TCP6 or UNKNOWN. + """ + + +class MissingAddressData(InvalidProxyHeader): + """ + The address data is missing or incomplete. + """ + + +@contextlib.contextmanager +def convertError( + sourceType: Type[BaseException], targetType: Callable[[], BaseException] +) -> Generator[None, None, None]: + """ + Convert an error into a different error type. + + @param sourceType: The type of exception that should be caught and + converted. + @type sourceType: L{BaseException} + + @param targetType: The type of exception to which the original should be + converted. + @type targetType: L{BaseException} + """ + try: + yield + except sourceType as e: + raise targetType().with_traceback(e.__traceback__) diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_info.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_info.py new file mode 100644 index 0000000000..9dda6e06ef --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_info.py @@ -0,0 +1,34 @@ +# -*- test-case-name: twisted.protocols.haproxy.test -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +IProxyInfo implementation. +""" +from typing import Optional + +from zope.interface import implementer + +import attr + +from twisted.internet.interfaces import IAddress +from ._interfaces import IProxyInfo + + +@implementer(IProxyInfo) +@attr.s(frozen=True, slots=True, auto_attribs=True) +class ProxyInfo: + """ + A data container for parsed PROXY protocol information. + + @ivar header: The raw header bytes extracted from the connection. + @type header: C{bytes} + @ivar source: The connection source address. + @type source: L{twisted.internet.interfaces.IAddress} + @ivar destination: The connection destination address. + @type destination: L{twisted.internet.interfaces.IAddress} + """ + + header: bytes + source: Optional[IAddress] + destination: Optional[IAddress] diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py new file mode 100644 index 0000000000..8fe90ea37a --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_interfaces.py @@ -0,0 +1,63 @@ +# -*- test-case-name: twisted.protocols.haproxy.test -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Interfaces used by the PROXY protocol modules. +""" +from typing import Tuple, Union + +import zope.interface + + +class IProxyInfo(zope.interface.Interface): + """ + Data container for PROXY protocol header data. + """ + + header = zope.interface.Attribute( + "The raw byestring that represents the PROXY protocol header.", + ) + source = zope.interface.Attribute( + "An L{twisted.internet.interfaces.IAddress} representing the " + "connection source." + ) + destination = zope.interface.Attribute( + "An L{twisted.internet.interfaces.IAddress} representing the " + "connection destination." + ) + + +class IProxyParser(zope.interface.Interface): + """ + Streaming parser that handles PROXY protocol headers. + """ + + def feed(data: bytes) -> Union[Tuple[IProxyInfo, bytes], Tuple[None, None]]: + """ + Consume a chunk of data and attempt to parse it. + + @param data: A bytestring. + @type data: bytes + + @return: A two-tuple containing, in order, an L{IProxyInfo} and any + bytes fed to the parser that followed the end of the header. Both + of these values are None until a complete header is parsed. + + @raises InvalidProxyHeader: If the bytes fed to the parser create an + invalid PROXY header. + """ + + def parse(line: bytes) -> IProxyInfo: + """ + Parse a bytestring as a full PROXY protocol header line. + + @param line: A bytestring that represents a valid HAProxy PROXY + protocol header line. + @type line: bytes + + @return: An L{IProxyInfo} containing the parsed data. + + @raises InvalidProxyHeader: If the bytestring does not represent a + valid PROXY header. + """ diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py new file mode 100644 index 0000000000..834ccb7354 --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_parser.py @@ -0,0 +1,75 @@ +# -*- test-case-name: twisted.protocols.haproxy.test.test_parser -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Parser for 'haproxy:' string endpoint. +""" +from typing import Mapping, Tuple + +from zope.interface import implementer + +from twisted.internet import interfaces +from twisted.internet.endpoints import ( + IStreamServerEndpointStringParser, + _WrapperServerEndpoint, + quoteStringArgument, + serverFromString, +) +from twisted.plugin import IPlugin +from . import proxyEndpoint + + +def unparseEndpoint(args: Tuple[object, ...], kwargs: Mapping[str, object]) -> str: + """ + Un-parse the already-parsed args and kwargs back into endpoint syntax. + + @param args: C{:}-separated arguments + + @param kwargs: C{:} and then C{=}-separated keyword arguments + + @return: a string equivalent to the original format which this was parsed + as. + """ + + description = ":".join( + [quoteStringArgument(str(arg)) for arg in args] + + sorted( + "{}={}".format( + quoteStringArgument(str(key)), quoteStringArgument(str(value)) + ) + for key, value in kwargs.items() + ) + ) + return description + + +@implementer(IPlugin, IStreamServerEndpointStringParser) +class HAProxyServerParser: + """ + Stream server endpoint string parser for the HAProxyServerEndpoint type. + + @ivar prefix: See L{IStreamServerEndpointStringParser.prefix}. + """ + + prefix = "haproxy" + + def parseStreamServer( + self, reactor: interfaces.IReactorCore, *args: object, **kwargs: object + ) -> _WrapperServerEndpoint: + """ + Parse a stream server endpoint from a reactor and string-only arguments + and keyword arguments. + + @param reactor: The reactor. + + @param args: The parsed string arguments. + + @param kwargs: The parsed keyword arguments. + + @return: a stream server endpoint + @rtype: L{IStreamServerEndpoint} + """ + subdescription = unparseEndpoint(args, kwargs) + wrappedEndpoint = serverFromString(reactor, subdescription) + return proxyEndpoint(wrappedEndpoint) diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py new file mode 100644 index 0000000000..fed987c33a --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v1parser.py @@ -0,0 +1,142 @@ +# -*- test-case-name: twisted.protocols.haproxy.test.test_v1parser -*- + +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +IProxyParser implementation for version one of the PROXY protocol. +""" +from typing import Tuple, Union + +from zope.interface import implementer + +from twisted.internet import address +from . import _info, _interfaces +from ._exceptions import ( + InvalidNetworkProtocol, + InvalidProxyHeader, + MissingAddressData, + convertError, +) + + +@implementer(_interfaces.IProxyParser) +class V1Parser: + """ + PROXY protocol version one header parser. + + Version one of the PROXY protocol is a human readable format represented + by a single, newline delimited binary string that contains all of the + relevant source and destination data. + """ + + PROXYSTR = b"PROXY" + UNKNOWN_PROTO = b"UNKNOWN" + TCP4_PROTO = b"TCP4" + TCP6_PROTO = b"TCP6" + ALLOWED_NET_PROTOS = ( + TCP4_PROTO, + TCP6_PROTO, + UNKNOWN_PROTO, + ) + NEWLINE = b"\r\n" + + def __init__(self) -> None: + self.buffer = b"" + + def feed( + self, data: bytes + ) -> Union[Tuple[_info.ProxyInfo, bytes], Tuple[None, None]]: + """ + Consume a chunk of data and attempt to parse it. + + @param data: A bytestring. + @type data: L{bytes} + + @return: A two-tuple containing, in order, a + L{_interfaces.IProxyInfo} and any bytes fed to the + parser that followed the end of the header. Both of these values + are None until a complete header is parsed. + + @raises InvalidProxyHeader: If the bytes fed to the parser create an + invalid PROXY header. + """ + self.buffer += data + if len(self.buffer) > 107 and self.NEWLINE not in self.buffer: + raise InvalidProxyHeader() + lines = (self.buffer).split(self.NEWLINE, 1) + if not len(lines) > 1: + return (None, None) + self.buffer = b"" + remaining = lines.pop() + header = lines.pop() + info = self.parse(header) + return (info, remaining) + + @classmethod + def parse(cls, line: bytes) -> _info.ProxyInfo: + """ + Parse a bytestring as a full PROXY protocol header line. + + @param line: A bytestring that represents a valid HAProxy PROXY + protocol header line. + @type line: bytes + + @return: A L{_interfaces.IProxyInfo} containing the parsed data. + + @raises InvalidProxyHeader: If the bytestring does not represent a + valid PROXY header. + + @raises InvalidNetworkProtocol: When no protocol can be parsed or is + not one of the allowed values. + + @raises MissingAddressData: When the protocol is TCP* but the header + does not contain a complete set of addresses and ports. + """ + originalLine = line + proxyStr = None + networkProtocol = None + sourceAddr = None + sourcePort = None + destAddr = None + destPort = None + + with convertError(ValueError, InvalidProxyHeader): + proxyStr, line = line.split(b" ", 1) + + if proxyStr != cls.PROXYSTR: + raise InvalidProxyHeader() + + with convertError(ValueError, InvalidNetworkProtocol): + networkProtocol, line = line.split(b" ", 1) + + if networkProtocol not in cls.ALLOWED_NET_PROTOS: + raise InvalidNetworkProtocol() + + if networkProtocol == cls.UNKNOWN_PROTO: + return _info.ProxyInfo(originalLine, None, None) + + with convertError(ValueError, MissingAddressData): + sourceAddr, line = line.split(b" ", 1) + + with convertError(ValueError, MissingAddressData): + destAddr, line = line.split(b" ", 1) + + with convertError(ValueError, MissingAddressData): + sourcePort, line = line.split(b" ", 1) + + with convertError(ValueError, MissingAddressData): + destPort = line.split(b" ")[0] + + if networkProtocol == cls.TCP4_PROTO: + return _info.ProxyInfo( + originalLine, + address.IPv4Address("TCP", sourceAddr.decode(), int(sourcePort)), + address.IPv4Address("TCP", destAddr.decode(), int(destPort)), + ) + + return _info.ProxyInfo( + originalLine, + address.IPv6Address("TCP", sourceAddr.decode(), int(sourcePort)), + address.IPv6Address("TCP", destAddr.decode(), int(destPort)), + ) diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py new file mode 100644 index 0000000000..5b8e587401 --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_v2parser.py @@ -0,0 +1,217 @@ +# -*- test-case-name: twisted.protocols.haproxy.test.test_v2parser -*- + +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +IProxyParser implementation for version two of the PROXY protocol. +""" + +import binascii +import struct +from typing import Callable, Tuple, Type, Union + +from zope.interface import implementer + +from constantly import ValueConstant, Values # type: ignore[import] +from typing_extensions import Literal + +from twisted.internet import address +from twisted.python import compat +from . import _info, _interfaces +from ._exceptions import ( + InvalidNetworkProtocol, + InvalidProxyHeader, + MissingAddressData, + convertError, +) + + +class NetFamily(Values): + """ + Values for the 'family' field. + """ + + UNSPEC = ValueConstant(0x00) + INET = ValueConstant(0x10) + INET6 = ValueConstant(0x20) + UNIX = ValueConstant(0x30) + + +class NetProtocol(Values): + """ + Values for 'protocol' field. + """ + + UNSPEC = ValueConstant(0) + STREAM = ValueConstant(1) + DGRAM = ValueConstant(2) + + +_HIGH = 0b11110000 +_LOW = 0b00001111 +_LOCALCOMMAND = "LOCAL" +_PROXYCOMMAND = "PROXY" + + +@implementer(_interfaces.IProxyParser) +class V2Parser: + """ + PROXY protocol version two header parser. + + Version two of the PROXY protocol is a binary format. + """ + + PREFIX = b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A" + VERSIONS = [32] + COMMANDS = {0: _LOCALCOMMAND, 1: _PROXYCOMMAND} + ADDRESSFORMATS = { + # TCP4 + 17: "!4s4s2H", + 18: "!4s4s2H", + # TCP6 + 33: "!16s16s2H", + 34: "!16s16s2H", + # UNIX + 49: "!108s108s", + 50: "!108s108s", + } + + def __init__(self) -> None: + self.buffer = b"" + + def feed( + self, data: bytes + ) -> Union[Tuple[_info.ProxyInfo, bytes], Tuple[None, None]]: + """ + Consume a chunk of data and attempt to parse it. + + @param data: A bytestring. + @type data: bytes + + @return: A two-tuple containing, in order, a L{_interfaces.IProxyInfo} + and any bytes fed to the parser that followed the end of the + header. Both of these values are None until a complete header is + parsed. + + @raises InvalidProxyHeader: If the bytes fed to the parser create an + invalid PROXY header. + """ + self.buffer += data + if len(self.buffer) < 16: + raise InvalidProxyHeader() + + size = struct.unpack("!H", self.buffer[14:16])[0] + 16 + if len(self.buffer) < size: + return (None, None) + + header, remaining = self.buffer[:size], self.buffer[size:] + self.buffer = b"" + info = self.parse(header) + return (info, remaining) + + @staticmethod + def _bytesToIPv4(bytestring: bytes) -> bytes: + """ + Convert packed 32-bit IPv4 address bytes into a dotted-quad ASCII bytes + representation of that address. + + @param bytestring: 4 octets representing an IPv4 address. + @type bytestring: L{bytes} + + @return: a dotted-quad notation IPv4 address. + @rtype: L{bytes} + """ + return b".".join( + ("%i" % (ord(b),)).encode("ascii") for b in compat.iterbytes(bytestring) + ) + + @staticmethod + def _bytesToIPv6(bytestring: bytes) -> bytes: + """ + Convert packed 128-bit IPv6 address bytes into a colon-separated ASCII + bytes representation of that address. + + @param bytestring: 16 octets representing an IPv6 address. + @type bytestring: L{bytes} + + @return: a dotted-quad notation IPv6 address. + @rtype: L{bytes} + """ + hexString = binascii.b2a_hex(bytestring) + return b":".join( + (f"{int(hexString[b : b + 4], 16):x}").encode("ascii") + for b in range(0, 32, 4) + ) + + @classmethod + def parse(cls, line: bytes) -> _info.ProxyInfo: + """ + Parse a bytestring as a full PROXY protocol header. + + @param line: A bytestring that represents a valid HAProxy PROXY + protocol version 2 header. + @type line: bytes + + @return: A L{_interfaces.IProxyInfo} containing the + parsed data. + + @raises InvalidProxyHeader: If the bytestring does not represent a + valid PROXY header. + """ + prefix = line[:12] + addrInfo = None + with convertError(IndexError, InvalidProxyHeader): + # Use single value slices to ensure bytestring values are returned + # instead of int in PY3. + versionCommand = ord(line[12:13]) + familyProto = ord(line[13:14]) + + if prefix != cls.PREFIX: + raise InvalidProxyHeader() + + version, command = versionCommand & _HIGH, versionCommand & _LOW + if version not in cls.VERSIONS or command not in cls.COMMANDS: + raise InvalidProxyHeader() + + if cls.COMMANDS[command] == _LOCALCOMMAND: + return _info.ProxyInfo(line, None, None) + + family, netproto = familyProto & _HIGH, familyProto & _LOW + with convertError(ValueError, InvalidNetworkProtocol): + family = NetFamily.lookupByValue(family) + netproto = NetProtocol.lookupByValue(netproto) + if family is NetFamily.UNSPEC or netproto is NetProtocol.UNSPEC: + return _info.ProxyInfo(line, None, None) + + addressFormat = cls.ADDRESSFORMATS[familyProto] + addrInfo = line[16 : 16 + struct.calcsize(addressFormat)] + if family is NetFamily.UNIX: + with convertError(struct.error, MissingAddressData): + source, dest = struct.unpack(addressFormat, addrInfo) + return _info.ProxyInfo( + line, + address.UNIXAddress(source.rstrip(b"\x00")), + address.UNIXAddress(dest.rstrip(b"\x00")), + ) + + addrType: Union[Literal["TCP"], Literal["UDP"]] = "TCP" + if netproto is NetProtocol.DGRAM: + addrType = "UDP" + addrCls: Union[ + Type[address.IPv4Address], Type[address.IPv6Address] + ] = address.IPv4Address + addrParser: Callable[[bytes], bytes] = cls._bytesToIPv4 + if family is NetFamily.INET6: + addrCls = address.IPv6Address + addrParser = cls._bytesToIPv6 + + with convertError(struct.error, MissingAddressData): + info = struct.unpack(addressFormat, addrInfo) + source, dest, sPort, dPort = info + + return _info.ProxyInfo( + line, + addrCls(addrType, addrParser(source).decode(), sPort), + addrCls(addrType, addrParser(dest).decode(), dPort), + ) diff --git a/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py new file mode 100644 index 0000000000..935dbfa9e2 --- /dev/null +++ b/contrib/python/Twisted/py3/twisted/protocols/haproxy/_wrapper.py @@ -0,0 +1,109 @@ +# -*- test-case-name: twisted.protocols.haproxy.test.test_wrapper -*- + +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Protocol wrapper that provides HAProxy PROXY protocol support. +""" +from typing import Optional, Union + +from twisted.internet import interfaces +from twisted.internet.endpoints import _WrapperServerEndpoint +from twisted.protocols import policies +from . import _info +from ._exceptions import InvalidProxyHeader +from ._v1parser import V1Parser +from ._v2parser import V2Parser + + +class HAProxyProtocolWrapper(policies.ProtocolWrapper): + """ + A Protocol wrapper that provides HAProxy support. + + This protocol reads the PROXY stream header, v1 or v2, parses the provided + connection data, and modifies the behavior of getPeer and getHost to return + the data provided by the PROXY header. + """ + + def __init__( + self, factory: policies.WrappingFactory, wrappedProtocol: interfaces.IProtocol + ): + super().__init__(factory, wrappedProtocol) + self._proxyInfo: Optional[_info.ProxyInfo] = None + self._parser: Union[V2Parser, V1Parser, None] = None + + def dataReceived(self, data: bytes) -> None: + if self._proxyInfo is not None: + return self.wrappedProtocol.dataReceived(data) + + parser = self._parser + if parser is None: + if ( + len(data) >= 16 + and data[:12] == V2Parser.PREFIX + and ord(data[12:13]) & 0b11110000 == 0x20 + ): + self._parser = parser = V2Parser() + elif len(data) >= 8 and data[:5] == V1Parser.PROXYSTR: + self._parser = parser = V1Parser() + else: + self.loseConnection() + return None + + try: + self._proxyInfo, remaining = parser.feed(data) + if remaining: + self.wrappedProtocol.dataReceived(remaining) + except InvalidProxyHeader: + self.loseConnection() + + def getPeer(self) -> interfaces.IAddress: + if self._proxyInfo and self._proxyInfo.source: + return self._proxyInfo.source + assert self.transport + return self.transport.getPeer() + + def getHost(self) -> interfaces.IAddress: + if self._proxyInfo and self._proxyInfo.destination: + return self._proxyInfo.destination + assert self.transport + return self.transport.getHost() + + +class HAProxyWrappingFactory(policies.WrappingFactory): + """ + A Factory wrapper that adds PROXY protocol support to connections. + """ + + protocol = HAProxyProtocolWrapper + + def logPrefix(self) -> str: + """ + Annotate the wrapped factory's log prefix with some text indicating + the PROXY protocol is in use. + + @rtype: C{str} + """ + if interfaces.ILoggingContext.providedBy(self.wrappedFactory): + logPrefix = self.wrappedFactory.logPrefix() + else: + logPrefix = self.wrappedFactory.__class__.__name__ + return f"{logPrefix} (PROXY)" + + +def proxyEndpoint( + wrappedEndpoint: interfaces.IStreamServerEndpoint, +) -> _WrapperServerEndpoint: + """ + Wrap an endpoint with PROXY protocol support, so that the transport's + C{getHost} and C{getPeer} methods reflect the attributes of the proxied + connection rather than the underlying connection. + + @param wrappedEndpoint: The underlying listening endpoint. + @type wrappedEndpoint: L{IStreamServerEndpoint} + + @return: a new listening endpoint that speaks the PROXY protocol. + @rtype: L{IStreamServerEndpoint} + """ + return _WrapperServerEndpoint(wrappedEndpoint, HAProxyWrappingFactory) |