RAHHH
This commit is contained in:
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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__)
|
||||
@@ -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]
|
||||
@@ -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.
|
||||
"""
|
||||
@@ -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)
|
||||
@@ -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)),
|
||||
)
|
||||
@@ -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
|
||||
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),
|
||||
)
|
||||
@@ -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)
|
||||
@@ -0,0 +1,7 @@
|
||||
# -*- test-case-name: twisted.protocols.haproxy.test -*-
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Unit tests for L{twisted.protocols.haproxy}.
|
||||
"""
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,133 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Tests for L{twisted.protocols.haproxy._parser}.
|
||||
"""
|
||||
from typing import Type, Union
|
||||
|
||||
from twisted.internet.endpoints import (
|
||||
TCP4ServerEndpoint,
|
||||
TCP6ServerEndpoint,
|
||||
UNIXServerEndpoint,
|
||||
_parse as parseEndpoint,
|
||||
_WrapperServerEndpoint,
|
||||
serverFromString,
|
||||
)
|
||||
from twisted.internet.testing import MemoryReactor
|
||||
from twisted.trial.unittest import SynchronousTestCase as TestCase
|
||||
from .._parser import unparseEndpoint
|
||||
from .._wrapper import HAProxyWrappingFactory
|
||||
|
||||
|
||||
class UnparseEndpointTests(TestCase):
|
||||
"""
|
||||
Tests to ensure that un-parsing an endpoint string round trips through
|
||||
escaping properly.
|
||||
"""
|
||||
|
||||
def check(self, input: str) -> None:
|
||||
"""
|
||||
Check that the input unparses into the output, raising an assertion
|
||||
error if it doesn't.
|
||||
|
||||
@param input: an input in endpoint-string-description format. (To
|
||||
ensure determinism, keyword arguments should be in alphabetical
|
||||
order.)
|
||||
@type input: native L{str}
|
||||
"""
|
||||
self.assertEqual(unparseEndpoint(*parseEndpoint(input)), input)
|
||||
|
||||
def test_basicUnparse(self) -> None:
|
||||
"""
|
||||
An individual word.
|
||||
"""
|
||||
self.check("word")
|
||||
|
||||
def test_multipleArguments(self) -> None:
|
||||
"""
|
||||
Multiple arguments.
|
||||
"""
|
||||
self.check("one:two")
|
||||
|
||||
def test_keywords(self) -> None:
|
||||
"""
|
||||
Keyword arguments.
|
||||
"""
|
||||
self.check("aleph=one:bet=two")
|
||||
|
||||
def test_colonInArgument(self) -> None:
|
||||
"""
|
||||
Escaped ":".
|
||||
"""
|
||||
self.check("hello\\:colon\\:world")
|
||||
|
||||
def test_colonInKeywordValue(self) -> None:
|
||||
"""
|
||||
Escaped ":" in keyword value.
|
||||
"""
|
||||
self.check("hello=\\:")
|
||||
|
||||
def test_colonInKeywordName(self) -> None:
|
||||
"""
|
||||
Escaped ":" in keyword name.
|
||||
"""
|
||||
self.check("\\:=hello")
|
||||
|
||||
|
||||
class HAProxyServerParserTests(TestCase):
|
||||
"""
|
||||
Tests that the parser generates the correct endpoints.
|
||||
"""
|
||||
|
||||
def onePrefix(
|
||||
self,
|
||||
description: str,
|
||||
expectedClass: Union[
|
||||
Type[TCP4ServerEndpoint],
|
||||
Type[TCP6ServerEndpoint],
|
||||
Type[UNIXServerEndpoint],
|
||||
],
|
||||
) -> _WrapperServerEndpoint:
|
||||
"""
|
||||
Test the C{haproxy} enpdoint prefix against one sub-endpoint type.
|
||||
|
||||
@param description: A string endpoint description beginning with
|
||||
C{haproxy}.
|
||||
@type description: native L{str}
|
||||
|
||||
@param expectedClass: the expected sub-endpoint class given the
|
||||
description.
|
||||
@type expectedClass: L{type}
|
||||
|
||||
@return: the parsed endpoint
|
||||
@rtype: L{IStreamServerEndpoint}
|
||||
|
||||
@raise twisted.trial.unittest.Failtest: if the parsed endpoint doesn't
|
||||
match expectations.
|
||||
"""
|
||||
reactor = MemoryReactor()
|
||||
endpoint = serverFromString(reactor, description)
|
||||
self.assertIsInstance(endpoint, _WrapperServerEndpoint)
|
||||
assert isinstance(endpoint, _WrapperServerEndpoint)
|
||||
self.assertIsInstance(endpoint._wrappedEndpoint, expectedClass)
|
||||
self.assertIs(endpoint._wrapperFactory, HAProxyWrappingFactory)
|
||||
return endpoint
|
||||
|
||||
def test_tcp4(self) -> None:
|
||||
"""
|
||||
Test if the parser generates a wrapped TCP4 endpoint.
|
||||
"""
|
||||
self.onePrefix("haproxy:tcp:8080", TCP4ServerEndpoint)
|
||||
|
||||
def test_tcp6(self) -> None:
|
||||
"""
|
||||
Test if the parser generates a wrapped TCP6 endpoint.
|
||||
"""
|
||||
self.onePrefix("haproxy:tcp6:8080", TCP6ServerEndpoint)
|
||||
|
||||
def test_unix(self) -> None:
|
||||
"""
|
||||
Test if the parser generates a wrapped UNIX endpoint.
|
||||
"""
|
||||
self.onePrefix("haproxy:unix:address=/tmp/socket", UNIXServerEndpoint)
|
||||
@@ -0,0 +1,149 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for L{twisted.protocols.haproxy.V1Parser}.
|
||||
"""
|
||||
|
||||
from twisted.internet import address
|
||||
from twisted.trial import unittest
|
||||
from .. import _v1parser
|
||||
from .._exceptions import InvalidNetworkProtocol, InvalidProxyHeader, MissingAddressData
|
||||
|
||||
|
||||
class V1ParserTests(unittest.TestCase):
|
||||
"""
|
||||
Test L{twisted.protocols.haproxy.V1Parser} behaviour.
|
||||
"""
|
||||
|
||||
def test_missingPROXYHeaderValue(self) -> None:
|
||||
"""
|
||||
Test that an exception is raised when the PROXY header is missing.
|
||||
"""
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
_v1parser.V1Parser.parse,
|
||||
b"NOTPROXY ",
|
||||
)
|
||||
|
||||
def test_invalidNetworkProtocol(self) -> None:
|
||||
"""
|
||||
Test that an exception is raised when the proto is not TCP or UNKNOWN.
|
||||
"""
|
||||
self.assertRaises(
|
||||
InvalidNetworkProtocol,
|
||||
_v1parser.V1Parser.parse,
|
||||
b"PROXY WUTPROTO ",
|
||||
)
|
||||
|
||||
def test_missingSourceData(self) -> None:
|
||||
"""
|
||||
Test that an exception is raised when the proto has no source data.
|
||||
"""
|
||||
self.assertRaises(
|
||||
MissingAddressData,
|
||||
_v1parser.V1Parser.parse,
|
||||
b"PROXY TCP4 ",
|
||||
)
|
||||
|
||||
def test_missingDestData(self) -> None:
|
||||
"""
|
||||
Test that an exception is raised when the proto has no destination.
|
||||
"""
|
||||
self.assertRaises(
|
||||
MissingAddressData,
|
||||
_v1parser.V1Parser.parse,
|
||||
b"PROXY TCP4 127.0.0.1 8080 8888",
|
||||
)
|
||||
|
||||
def test_fullParsingSuccess(self) -> None:
|
||||
"""
|
||||
Test that parsing is successful for a PROXY header.
|
||||
"""
|
||||
info = _v1parser.V1Parser.parse(
|
||||
b"PROXY TCP4 127.0.0.1 127.0.0.1 8080 8888",
|
||||
)
|
||||
self.assertIsInstance(info.source, address.IPv4Address)
|
||||
assert isinstance(info.source, address.IPv4Address)
|
||||
assert isinstance(info.destination, address.IPv4Address) # type: ignore[unreachable]
|
||||
self.assertEqual(info.source.host, "127.0.0.1")
|
||||
self.assertEqual(info.source.port, 8080)
|
||||
self.assertEqual(info.destination.host, "127.0.0.1")
|
||||
self.assertEqual(info.destination.port, 8888)
|
||||
|
||||
def test_fullParsingSuccess_IPv6(self) -> None:
|
||||
"""
|
||||
Test that parsing is successful for an IPv6 PROXY header.
|
||||
"""
|
||||
info = _v1parser.V1Parser.parse(
|
||||
b"PROXY TCP6 ::1 ::1 8080 8888",
|
||||
)
|
||||
self.assertIsInstance(info.source, address.IPv6Address)
|
||||
assert isinstance(info.source, address.IPv6Address)
|
||||
assert isinstance(info.destination, address.IPv6Address) # type: ignore[unreachable]
|
||||
self.assertEqual(info.source.host, "::1")
|
||||
self.assertEqual(info.source.port, 8080)
|
||||
self.assertEqual(info.destination.host, "::1")
|
||||
self.assertEqual(info.destination.port, 8888)
|
||||
|
||||
def test_fullParsingSuccess_UNKNOWN(self) -> None:
|
||||
"""
|
||||
Test that parsing is successful for a UNKNOWN PROXY header.
|
||||
"""
|
||||
info = _v1parser.V1Parser.parse(
|
||||
b"PROXY UNKNOWN anything could go here",
|
||||
)
|
||||
self.assertIsNone(info.source)
|
||||
self.assertIsNone(info.destination)
|
||||
|
||||
def test_feedParsing(self) -> None:
|
||||
"""
|
||||
Test that parsing happens when fed a complete line.
|
||||
"""
|
||||
parser = _v1parser.V1Parser()
|
||||
info, remaining = parser.feed(b"PROXY TCP4 127.0.0.1 127.0.0.1 ")
|
||||
self.assertFalse(info)
|
||||
self.assertFalse(remaining)
|
||||
info, remaining = parser.feed(b"8080 8888")
|
||||
self.assertFalse(info)
|
||||
self.assertFalse(remaining)
|
||||
info, remaining = parser.feed(b"\r\n")
|
||||
self.assertFalse(remaining)
|
||||
assert remaining is not None
|
||||
assert info is not None
|
||||
self.assertIsInstance(info.source, address.IPv4Address)
|
||||
assert isinstance(info.source, address.IPv4Address)
|
||||
assert isinstance(info.destination, address.IPv4Address) # type: ignore[unreachable]
|
||||
self.assertEqual(info.source.host, "127.0.0.1")
|
||||
self.assertEqual(info.source.port, 8080)
|
||||
self.assertEqual(info.destination.host, "127.0.0.1")
|
||||
self.assertEqual(info.destination.port, 8888)
|
||||
|
||||
def test_feedParsingTooLong(self) -> None:
|
||||
"""
|
||||
Test that parsing fails if no newline is found in 108 bytes.
|
||||
"""
|
||||
parser = _v1parser.V1Parser()
|
||||
info, remaining = parser.feed(b"PROXY TCP4 127.0.0.1 127.0.0.1 ")
|
||||
self.assertFalse(info)
|
||||
self.assertFalse(remaining)
|
||||
info, remaining = parser.feed(b"8080 8888")
|
||||
self.assertFalse(info)
|
||||
self.assertFalse(remaining)
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
parser.feed,
|
||||
b" " * 100,
|
||||
)
|
||||
|
||||
def test_feedParsingOverflow(self) -> None:
|
||||
"""
|
||||
Test that parsing leaves overflow bytes in the buffer.
|
||||
"""
|
||||
parser = _v1parser.V1Parser()
|
||||
info, remaining = parser.feed(
|
||||
b"PROXY TCP4 127.0.0.1 127.0.0.1 8080 8888\r\nHTTP/1.1 GET /\r\n",
|
||||
)
|
||||
self.assertTrue(info)
|
||||
self.assertEqual(remaining, b"HTTP/1.1 GET /\r\n")
|
||||
self.assertFalse(parser.buffer)
|
||||
@@ -0,0 +1,368 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for L{twisted.protocols.haproxy.V2Parser}.
|
||||
"""
|
||||
|
||||
from twisted.internet import address
|
||||
from twisted.trial import unittest
|
||||
from .. import _v2parser
|
||||
from .._exceptions import InvalidProxyHeader
|
||||
|
||||
V2_SIGNATURE = b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
||||
|
||||
|
||||
def _makeHeaderIPv6(
|
||||
sig: bytes = V2_SIGNATURE,
|
||||
verCom: bytes = b"\x21",
|
||||
famProto: bytes = b"\x21",
|
||||
addrLength: bytes = b"\x00\x24",
|
||||
addrs: bytes = ((b"\x00" * 15) + b"\x01") * 2,
|
||||
ports: bytes = b"\x1F\x90\x22\xB8",
|
||||
) -> bytes:
|
||||
"""
|
||||
Construct a version 2 IPv6 header with custom bytes.
|
||||
|
||||
@param sig: The protocol signature; defaults to valid L{V2_SIGNATURE}.
|
||||
@type sig: L{bytes}
|
||||
|
||||
@param verCom: Protocol version and command. Defaults to V2 PROXY.
|
||||
@type verCom: L{bytes}
|
||||
|
||||
@param famProto: Address family and protocol. Defaults to AF_INET6/STREAM.
|
||||
@type famProto: L{bytes}
|
||||
|
||||
@param addrLength: Network-endian byte length of payload. Defaults to
|
||||
description of default addrs/ports.
|
||||
@type addrLength: L{bytes}
|
||||
|
||||
@param addrs: Address payload. Defaults to C{::1} for source and
|
||||
destination.
|
||||
@type addrs: L{bytes}
|
||||
|
||||
@param ports: Source and destination ports. Defaults to 8080 for source
|
||||
8888 for destination.
|
||||
@type ports: L{bytes}
|
||||
|
||||
@return: A packet with header, addresses, and ports.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
return sig + verCom + famProto + addrLength + addrs + ports
|
||||
|
||||
|
||||
def _makeHeaderIPv4(
|
||||
sig: bytes = V2_SIGNATURE,
|
||||
verCom: bytes = b"\x21",
|
||||
famProto: bytes = b"\x11",
|
||||
addrLength: bytes = b"\x00\x0C",
|
||||
addrs: bytes = b"\x7F\x00\x00\x01\x7F\x00\x00\x01",
|
||||
ports: bytes = b"\x1F\x90\x22\xB8",
|
||||
) -> bytes:
|
||||
"""
|
||||
Construct a version 2 IPv4 header with custom bytes.
|
||||
|
||||
@param sig: The protocol signature; defaults to valid L{V2_SIGNATURE}.
|
||||
@type sig: L{bytes}
|
||||
|
||||
@param verCom: Protocol version and command. Defaults to V2 PROXY.
|
||||
@type verCom: L{bytes}
|
||||
|
||||
@param famProto: Address family and protocol. Defaults to AF_INET/STREAM.
|
||||
@type famProto: L{bytes}
|
||||
|
||||
@param addrLength: Network-endian byte length of payload. Defaults to
|
||||
description of default addrs/ports.
|
||||
@type addrLength: L{bytes}
|
||||
|
||||
@param addrs: Address payload. Defaults to 127.0.0.1 for source and
|
||||
destination.
|
||||
@type addrs: L{bytes}
|
||||
|
||||
@param ports: Source and destination ports. Defaults to 8080 for source
|
||||
8888 for destination.
|
||||
@type ports: L{bytes}
|
||||
|
||||
@return: A packet with header, addresses, and ports.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
return sig + verCom + famProto + addrLength + addrs + ports
|
||||
|
||||
|
||||
def _makeHeaderUnix(
|
||||
sig: bytes = V2_SIGNATURE,
|
||||
verCom: bytes = b"\x21",
|
||||
famProto: bytes = b"\x31",
|
||||
addrLength: bytes = b"\x00\xD8",
|
||||
addrs: bytes = (
|
||||
b"\x2F\x68\x6F\x6D\x65\x2F\x74\x65\x73\x74\x73\x2F"
|
||||
b"\x6D\x79\x73\x6F\x63\x6B\x65\x74\x73\x2F\x73\x6F"
|
||||
b"\x63\x6B" + (b"\x00" * 82)
|
||||
)
|
||||
* 2,
|
||||
) -> bytes:
|
||||
"""
|
||||
Construct a version 2 IPv4 header with custom bytes.
|
||||
|
||||
@param sig: The protocol signature; defaults to valid L{V2_SIGNATURE}.
|
||||
@type sig: L{bytes}
|
||||
|
||||
@param verCom: Protocol version and command. Defaults to V2 PROXY.
|
||||
@type verCom: L{bytes}
|
||||
|
||||
@param famProto: Address family and protocol. Defaults to AF_UNIX/STREAM.
|
||||
@type famProto: L{bytes}
|
||||
|
||||
@param addrLength: Network-endian byte length of payload. Defaults to 108
|
||||
bytes for 2 null terminated paths.
|
||||
@type addrLength: L{bytes}
|
||||
|
||||
@param addrs: Address payload. Defaults to C{/home/tests/mysockets/sock}
|
||||
for source and destination paths.
|
||||
@type addrs: L{bytes}
|
||||
|
||||
@return: A packet with header, addresses, and8 ports.
|
||||
@rtype: L{bytes}
|
||||
"""
|
||||
return sig + verCom + famProto + addrLength + addrs
|
||||
|
||||
|
||||
class V2ParserTests(unittest.TestCase):
|
||||
"""
|
||||
Test L{twisted.protocols.haproxy.V2Parser} behaviour.
|
||||
"""
|
||||
|
||||
def test_happyPathIPv4(self) -> None:
|
||||
"""
|
||||
Test if a well formed IPv4 header is parsed without error.
|
||||
"""
|
||||
header = _makeHeaderIPv4()
|
||||
self.assertTrue(_v2parser.V2Parser.parse(header))
|
||||
|
||||
def test_happyPathIPv6(self) -> None:
|
||||
"""
|
||||
Test if a well formed IPv6 header is parsed without error.
|
||||
"""
|
||||
header = _makeHeaderIPv6()
|
||||
self.assertTrue(_v2parser.V2Parser.parse(header))
|
||||
|
||||
def test_happyPathUnix(self) -> None:
|
||||
"""
|
||||
Test if a well formed UNIX header is parsed without error.
|
||||
"""
|
||||
header = _makeHeaderUnix()
|
||||
self.assertTrue(_v2parser.V2Parser.parse(header))
|
||||
|
||||
def test_invalidSignature(self) -> None:
|
||||
"""
|
||||
Test if an invalid signature block raises InvalidProxyError.
|
||||
"""
|
||||
header = _makeHeaderIPv4(sig=b"\x00" * 12)
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
_v2parser.V2Parser.parse,
|
||||
header,
|
||||
)
|
||||
|
||||
def test_invalidVersion(self) -> None:
|
||||
"""
|
||||
Test if an invalid version raises InvalidProxyError.
|
||||
"""
|
||||
header = _makeHeaderIPv4(verCom=b"\x11")
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
_v2parser.V2Parser.parse,
|
||||
header,
|
||||
)
|
||||
|
||||
def test_invalidCommand(self) -> None:
|
||||
"""
|
||||
Test if an invalid command raises InvalidProxyError.
|
||||
"""
|
||||
header = _makeHeaderIPv4(verCom=b"\x23")
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
_v2parser.V2Parser.parse,
|
||||
header,
|
||||
)
|
||||
|
||||
def test_invalidFamily(self) -> None:
|
||||
"""
|
||||
Test if an invalid family raises InvalidProxyError.
|
||||
"""
|
||||
header = _makeHeaderIPv4(famProto=b"\x40")
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
_v2parser.V2Parser.parse,
|
||||
header,
|
||||
)
|
||||
|
||||
def test_invalidProto(self) -> None:
|
||||
"""
|
||||
Test if an invalid protocol raises InvalidProxyError.
|
||||
"""
|
||||
header = _makeHeaderIPv4(famProto=b"\x24")
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
_v2parser.V2Parser.parse,
|
||||
header,
|
||||
)
|
||||
|
||||
def test_localCommandIpv4(self) -> None:
|
||||
"""
|
||||
Test that local does not return endpoint data for IPv4 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv4(verCom=b"\x20")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_localCommandIpv6(self) -> None:
|
||||
"""
|
||||
Test that local does not return endpoint data for IPv6 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv6(verCom=b"\x20")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_localCommandUnix(self) -> None:
|
||||
"""
|
||||
Test that local does not return endpoint data for UNIX connections.
|
||||
"""
|
||||
header = _makeHeaderUnix(verCom=b"\x20")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_proxyCommandIpv4(self) -> None:
|
||||
"""
|
||||
Test that proxy returns endpoint data for IPv4 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv4(verCom=b"\x21")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertTrue(info.source)
|
||||
self.assertIsInstance(info.source, address.IPv4Address)
|
||||
self.assertTrue(info.destination)
|
||||
self.assertIsInstance(info.destination, address.IPv4Address)
|
||||
|
||||
def test_proxyCommandIpv6(self) -> None:
|
||||
"""
|
||||
Test that proxy returns endpoint data for IPv6 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv6(verCom=b"\x21")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertTrue(info.source)
|
||||
self.assertIsInstance(info.source, address.IPv6Address)
|
||||
self.assertTrue(info.destination)
|
||||
self.assertIsInstance(info.destination, address.IPv6Address)
|
||||
|
||||
def test_proxyCommandUnix(self) -> None:
|
||||
"""
|
||||
Test that proxy returns endpoint data for UNIX connections.
|
||||
"""
|
||||
header = _makeHeaderUnix(verCom=b"\x21")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertTrue(info.source)
|
||||
self.assertIsInstance(info.source, address.UNIXAddress)
|
||||
self.assertTrue(info.destination)
|
||||
self.assertIsInstance(info.destination, address.UNIXAddress)
|
||||
|
||||
def test_unspecFamilyIpv4(self) -> None:
|
||||
"""
|
||||
Test that UNSPEC does not return endpoint data for IPv4 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv4(famProto=b"\x01")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_unspecFamilyIpv6(self) -> None:
|
||||
"""
|
||||
Test that UNSPEC does not return endpoint data for IPv6 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv6(famProto=b"\x01")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_unspecFamilyUnix(self) -> None:
|
||||
"""
|
||||
Test that UNSPEC does not return endpoint data for UNIX connections.
|
||||
"""
|
||||
header = _makeHeaderUnix(famProto=b"\x01")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_unspecProtoIpv4(self) -> None:
|
||||
"""
|
||||
Test that UNSPEC does not return endpoint data for IPv4 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv4(famProto=b"\x10")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_unspecProtoIpv6(self) -> None:
|
||||
"""
|
||||
Test that UNSPEC does not return endpoint data for IPv6 connections.
|
||||
"""
|
||||
header = _makeHeaderIPv6(famProto=b"\x20")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_unspecProtoUnix(self) -> None:
|
||||
"""
|
||||
Test that UNSPEC does not return endpoint data for UNIX connections.
|
||||
"""
|
||||
header = _makeHeaderUnix(famProto=b"\x30")
|
||||
info = _v2parser.V2Parser.parse(header)
|
||||
self.assertFalse(info.source)
|
||||
self.assertFalse(info.destination)
|
||||
|
||||
def test_overflowIpv4(self) -> None:
|
||||
"""
|
||||
Test that overflow bits are preserved during feed parsing for IPv4.
|
||||
"""
|
||||
testValue = b"TEST DATA\r\n\r\nTEST DATA"
|
||||
header = _makeHeaderIPv4() + testValue
|
||||
parser = _v2parser.V2Parser()
|
||||
info, overflow = parser.feed(header)
|
||||
self.assertTrue(info)
|
||||
self.assertEqual(overflow, testValue)
|
||||
|
||||
def test_overflowIpv6(self) -> None:
|
||||
"""
|
||||
Test that overflow bits are preserved during feed parsing for IPv6.
|
||||
"""
|
||||
testValue = b"TEST DATA\r\n\r\nTEST DATA"
|
||||
header = _makeHeaderIPv6() + testValue
|
||||
parser = _v2parser.V2Parser()
|
||||
info, overflow = parser.feed(header)
|
||||
self.assertTrue(info)
|
||||
self.assertEqual(overflow, testValue)
|
||||
|
||||
def test_overflowUnix(self) -> None:
|
||||
"""
|
||||
Test that overflow bits are preserved during feed parsing for Unix.
|
||||
"""
|
||||
testValue = b"TEST DATA\r\n\r\nTEST DATA"
|
||||
header = _makeHeaderUnix() + testValue
|
||||
parser = _v2parser.V2Parser()
|
||||
info, overflow = parser.feed(header)
|
||||
self.assertTrue(info)
|
||||
self.assertEqual(overflow, testValue)
|
||||
|
||||
def test_segmentTooSmall(self) -> None:
|
||||
"""
|
||||
Test that an initial payload of less than 16 bytes fails.
|
||||
"""
|
||||
testValue = b"NEEDMOREDATA"
|
||||
parser = _v2parser.V2Parser()
|
||||
self.assertRaises(
|
||||
InvalidProxyHeader,
|
||||
parser.feed,
|
||||
testValue,
|
||||
)
|
||||
@@ -0,0 +1,375 @@
|
||||
# Copyright (c) Twisted Matrix Laboratories.
|
||||
# See LICENSE for details.
|
||||
|
||||
"""
|
||||
Test cases for L{twisted.protocols.haproxy.HAProxyProtocol}.
|
||||
"""
|
||||
from typing import Optional
|
||||
from unittest import mock
|
||||
|
||||
from twisted.internet import address
|
||||
from twisted.internet.protocol import Factory, Protocol
|
||||
from twisted.internet.testing import StringTransportWithDisconnection
|
||||
from twisted.trial import unittest
|
||||
from .._wrapper import HAProxyWrappingFactory
|
||||
|
||||
|
||||
class StaticProtocol(Protocol):
|
||||
"""
|
||||
Protocol stand-in that maintains test state.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.source: Optional[address.IAddress] = None
|
||||
self.destination: Optional[address.IAddress] = None
|
||||
self.data = b""
|
||||
self.disconnected = False
|
||||
|
||||
def dataReceived(self, data: bytes) -> None:
|
||||
assert self.transport
|
||||
self.source = self.transport.getPeer()
|
||||
self.destination = self.transport.getHost()
|
||||
self.data += data
|
||||
|
||||
|
||||
class HAProxyWrappingFactoryV1Tests(unittest.TestCase):
|
||||
"""
|
||||
Test L{twisted.protocols.haproxy.HAProxyWrappingFactory} with v1 PROXY
|
||||
headers.
|
||||
"""
|
||||
|
||||
def test_invalidHeaderDisconnects(self) -> None:
|
||||
"""
|
||||
Test if invalid headers result in connectionLost events.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv4Address("TCP", "127.1.1.1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
transport.protocol = proto
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"NOTPROXY anything can go here\r\n")
|
||||
self.assertFalse(transport.connected)
|
||||
|
||||
def test_invalidPartialHeaderDisconnects(self) -> None:
|
||||
"""
|
||||
Test if invalid headers result in connectionLost events.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv4Address("TCP", "127.1.1.1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
transport.protocol = proto
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"PROXY TCP4 1.1.1.1\r\n")
|
||||
proto.dataReceived(b"2.2.2.2 8080\r\n")
|
||||
self.assertFalse(transport.connected)
|
||||
|
||||
def test_preDataReceived_getPeerHost(self) -> None:
|
||||
"""
|
||||
Before any data is received the HAProxy protocol will return the same peer
|
||||
and host as the IP connection.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv4Address("TCP", "127.0.0.1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection(
|
||||
hostAddress=mock.sentinel.host_address,
|
||||
peerAddress=mock.sentinel.peer_address,
|
||||
)
|
||||
proto.makeConnection(transport)
|
||||
self.assertEqual(proto.getHost(), mock.sentinel.host_address)
|
||||
self.assertEqual(proto.getPeer(), mock.sentinel.peer_address)
|
||||
|
||||
def test_validIPv4HeaderResolves_getPeerHost(self) -> None:
|
||||
"""
|
||||
Test if IPv4 headers result in the correct host and peer data.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv4Address("TCP", "127.0.0.1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"PROXY TCP4 1.1.1.1 2.2.2.2 8080 8888\r\n")
|
||||
self.assertEqual(proto.getPeer().host, "1.1.1.1")
|
||||
self.assertEqual(proto.getPeer().port, 8080)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().host,
|
||||
"1.1.1.1",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().port,
|
||||
8080,
|
||||
)
|
||||
self.assertEqual(proto.getHost().host, "2.2.2.2")
|
||||
self.assertEqual(proto.getHost().port, 8888)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().host,
|
||||
"2.2.2.2",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().port,
|
||||
8888,
|
||||
)
|
||||
|
||||
def test_validIPv6HeaderResolves_getPeerHost(self) -> None:
|
||||
"""
|
||||
Test if IPv6 headers result in the correct host and peer data.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"PROXY TCP6 ::1 ::2 8080 8888\r\n")
|
||||
self.assertEqual(proto.getPeer().host, "::1")
|
||||
self.assertEqual(proto.getPeer().port, 8080)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().host,
|
||||
"::1",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().port,
|
||||
8080,
|
||||
)
|
||||
self.assertEqual(proto.getHost().host, "::2")
|
||||
self.assertEqual(proto.getHost().port, 8888)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().host,
|
||||
"::2",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().port,
|
||||
8888,
|
||||
)
|
||||
|
||||
def test_overflowBytesSentToWrappedProtocol(self) -> None:
|
||||
"""
|
||||
Test if non-header bytes are passed to the wrapped protocol.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"PROXY TCP6 ::1 ::2 8080 8888\r\nHTTP/1.1 / GET")
|
||||
self.assertEqual(proto.wrappedProtocol.data, b"HTTP/1.1 / GET")
|
||||
|
||||
def test_overflowBytesSentToWrappedProtocolChunks(self) -> None:
|
||||
"""
|
||||
Test if header streaming passes extra data appropriately.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"PROXY TCP6 ::1 ::2 ")
|
||||
proto.dataReceived(b"8080 8888\r\nHTTP/1.1 / GET")
|
||||
self.assertEqual(proto.wrappedProtocol.data, b"HTTP/1.1 / GET")
|
||||
|
||||
def test_overflowBytesSentToWrappedProtocolAfter(self) -> None:
|
||||
"""
|
||||
Test if wrapper writes all data to wrapped protocol after parsing.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"PROXY TCP6 ::1 ::2 ")
|
||||
proto.dataReceived(b"8080 8888\r\nHTTP/1.1 / GET")
|
||||
proto.dataReceived(b"\r\n\r\n")
|
||||
self.assertEqual(proto.wrappedProtocol.data, b"HTTP/1.1 / GET\r\n\r\n")
|
||||
|
||||
|
||||
class HAProxyWrappingFactoryV2Tests(unittest.TestCase):
|
||||
"""
|
||||
Test L{twisted.protocols.haproxy.HAProxyWrappingFactory} with v2 PROXY
|
||||
headers.
|
||||
"""
|
||||
|
||||
IPV4HEADER = (
|
||||
# V2 Signature
|
||||
b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
||||
# V2 PROXY command
|
||||
b"\x21"
|
||||
# AF_INET/STREAM
|
||||
b"\x11"
|
||||
# 12 bytes for 2 IPv4 addresses and two ports
|
||||
b"\x00\x0C"
|
||||
# 127.0.0.1 for source and destination
|
||||
b"\x7F\x00\x00\x01\x7F\x00\x00\x01"
|
||||
# 8080 for source 8888 for destination
|
||||
b"\x1F\x90\x22\xB8"
|
||||
)
|
||||
IPV6HEADER = (
|
||||
# V2 Signature
|
||||
b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
||||
# V2 PROXY command
|
||||
b"\x21"
|
||||
# AF_INET6/STREAM
|
||||
b"\x21"
|
||||
# 16 bytes for 2 IPv6 addresses and two ports
|
||||
b"\x00\x24"
|
||||
# ::1 for source and destination
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
||||
b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"
|
||||
# 8080 for source 8888 for destination
|
||||
b"\x1F\x90\x22\xB8"
|
||||
)
|
||||
|
||||
_SOCK_PATH = (
|
||||
b"\x2F\x68\x6F\x6D\x65\x2F\x74\x65\x73\x74\x73\x2F\x6D\x79\x73\x6F"
|
||||
b"\x63\x6B\x65\x74\x73\x2F\x73\x6F\x63\x6B" + (b"\x00" * 82)
|
||||
)
|
||||
UNIXHEADER = (
|
||||
(
|
||||
# V2 Signature
|
||||
b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"
|
||||
# V2 PROXY command
|
||||
b"\x21"
|
||||
# AF_UNIX/STREAM
|
||||
b"\x31"
|
||||
# 108 bytes for 2 null terminated paths
|
||||
b"\x00\xD8"
|
||||
# /home/tests/mysockets/sock for source and destination paths
|
||||
)
|
||||
+ _SOCK_PATH
|
||||
+ _SOCK_PATH
|
||||
)
|
||||
|
||||
def test_invalidHeaderDisconnects(self) -> None:
|
||||
"""
|
||||
Test if invalid headers result in connectionLost events.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
transport.protocol = proto
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(b"\x00" + self.IPV4HEADER[1:])
|
||||
self.assertFalse(transport.connected)
|
||||
|
||||
def test_validIPv4HeaderResolves_getPeerHost(self) -> None:
|
||||
"""
|
||||
Test if IPv4 headers result in the correct host and peer data.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv4Address("TCP", "127.0.0.1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(self.IPV4HEADER)
|
||||
self.assertEqual(proto.getPeer().host, "127.0.0.1")
|
||||
self.assertEqual(proto.getPeer().port, 8080)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().host,
|
||||
"127.0.0.1",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().port,
|
||||
8080,
|
||||
)
|
||||
self.assertEqual(proto.getHost().host, "127.0.0.1")
|
||||
self.assertEqual(proto.getHost().port, 8888)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().host,
|
||||
"127.0.0.1",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().port,
|
||||
8888,
|
||||
)
|
||||
|
||||
def test_validIPv6HeaderResolves_getPeerHost(self) -> None:
|
||||
"""
|
||||
Test if IPv6 headers result in the correct host and peer data.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv4Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(self.IPV6HEADER)
|
||||
self.assertEqual(proto.getPeer().host, "0:0:0:0:0:0:0:1")
|
||||
self.assertEqual(proto.getPeer().port, 8080)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().host,
|
||||
"0:0:0:0:0:0:0:1",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().port,
|
||||
8080,
|
||||
)
|
||||
self.assertEqual(proto.getHost().host, "0:0:0:0:0:0:0:1")
|
||||
self.assertEqual(proto.getHost().port, 8888)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().host,
|
||||
"0:0:0:0:0:0:0:1",
|
||||
)
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().port,
|
||||
8888,
|
||||
)
|
||||
|
||||
def test_validUNIXHeaderResolves_getPeerHost(self) -> None:
|
||||
"""
|
||||
Test if UNIX headers result in the correct host and peer data.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.UNIXAddress(b"/home/test/sockets/server.sock"),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(self.UNIXHEADER)
|
||||
self.assertEqual(proto.getPeer().name, b"/home/tests/mysockets/sock")
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getPeer().name,
|
||||
b"/home/tests/mysockets/sock",
|
||||
)
|
||||
self.assertEqual(proto.getHost().name, b"/home/tests/mysockets/sock")
|
||||
self.assertEqual(
|
||||
proto.wrappedProtocol.transport.getHost().name,
|
||||
b"/home/tests/mysockets/sock",
|
||||
)
|
||||
|
||||
def test_overflowBytesSentToWrappedProtocol(self) -> None:
|
||||
"""
|
||||
Test if non-header bytes are passed to the wrapped protocol.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(self.IPV6HEADER + b"HTTP/1.1 / GET")
|
||||
self.assertEqual(proto.wrappedProtocol.data, b"HTTP/1.1 / GET")
|
||||
|
||||
def test_overflowBytesSentToWrappedProtocolChunks(self) -> None:
|
||||
"""
|
||||
Test if header streaming passes extra data appropriately.
|
||||
"""
|
||||
factory = HAProxyWrappingFactory(Factory.forProtocol(StaticProtocol))
|
||||
proto = factory.buildProtocol(
|
||||
address.IPv6Address("TCP", "::1", 8080),
|
||||
)
|
||||
transport = StringTransportWithDisconnection()
|
||||
proto.makeConnection(transport)
|
||||
proto.dataReceived(self.IPV6HEADER[:18])
|
||||
proto.dataReceived(self.IPV6HEADER[18:] + b"HTTP/1.1 / GET")
|
||||
self.assertEqual(proto.wrappedProtocol.data, b"HTTP/1.1 / GET")
|
||||
Reference in New Issue
Block a user