_v1parser.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. # -*- test-case-name: twisted.protocols.haproxy.test.test_v1parser -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. IProxyParser implementation for version one of the PROXY protocol.
  6. """
  7. from typing import Tuple, Union
  8. from zope.interface import implementer
  9. from twisted.internet import address
  10. from . import _info, _interfaces
  11. from ._exceptions import (
  12. InvalidNetworkProtocol,
  13. InvalidProxyHeader,
  14. MissingAddressData,
  15. convertError,
  16. )
  17. @implementer(_interfaces.IProxyParser)
  18. class V1Parser:
  19. """
  20. PROXY protocol version one header parser.
  21. Version one of the PROXY protocol is a human readable format represented
  22. by a single, newline delimited binary string that contains all of the
  23. relevant source and destination data.
  24. """
  25. PROXYSTR = b"PROXY"
  26. UNKNOWN_PROTO = b"UNKNOWN"
  27. TCP4_PROTO = b"TCP4"
  28. TCP6_PROTO = b"TCP6"
  29. ALLOWED_NET_PROTOS = (
  30. TCP4_PROTO,
  31. TCP6_PROTO,
  32. UNKNOWN_PROTO,
  33. )
  34. NEWLINE = b"\r\n"
  35. def __init__(self) -> None:
  36. self.buffer = b""
  37. def feed(
  38. self, data: bytes
  39. ) -> Union[Tuple[_info.ProxyInfo, bytes], Tuple[None, None]]:
  40. """
  41. Consume a chunk of data and attempt to parse it.
  42. @param data: A bytestring.
  43. @type data: L{bytes}
  44. @return: A two-tuple containing, in order, a
  45. L{_interfaces.IProxyInfo} and any bytes fed to the
  46. parser that followed the end of the header. Both of these values
  47. are None until a complete header is parsed.
  48. @raises InvalidProxyHeader: If the bytes fed to the parser create an
  49. invalid PROXY header.
  50. """
  51. self.buffer += data
  52. if len(self.buffer) > 107 and self.NEWLINE not in self.buffer:
  53. raise InvalidProxyHeader()
  54. lines = (self.buffer).split(self.NEWLINE, 1)
  55. if not len(lines) > 1:
  56. return (None, None)
  57. self.buffer = b""
  58. remaining = lines.pop()
  59. header = lines.pop()
  60. info = self.parse(header)
  61. return (info, remaining)
  62. @classmethod
  63. def parse(cls, line: bytes) -> _info.ProxyInfo:
  64. """
  65. Parse a bytestring as a full PROXY protocol header line.
  66. @param line: A bytestring that represents a valid HAProxy PROXY
  67. protocol header line.
  68. @type line: bytes
  69. @return: A L{_interfaces.IProxyInfo} containing the parsed data.
  70. @raises InvalidProxyHeader: If the bytestring does not represent a
  71. valid PROXY header.
  72. @raises InvalidNetworkProtocol: When no protocol can be parsed or is
  73. not one of the allowed values.
  74. @raises MissingAddressData: When the protocol is TCP* but the header
  75. does not contain a complete set of addresses and ports.
  76. """
  77. originalLine = line
  78. proxyStr = None
  79. networkProtocol = None
  80. sourceAddr = None
  81. sourcePort = None
  82. destAddr = None
  83. destPort = None
  84. with convertError(ValueError, InvalidProxyHeader):
  85. proxyStr, line = line.split(b" ", 1)
  86. if proxyStr != cls.PROXYSTR:
  87. raise InvalidProxyHeader()
  88. with convertError(ValueError, InvalidNetworkProtocol):
  89. networkProtocol, line = line.split(b" ", 1)
  90. if networkProtocol not in cls.ALLOWED_NET_PROTOS:
  91. raise InvalidNetworkProtocol()
  92. if networkProtocol == cls.UNKNOWN_PROTO:
  93. return _info.ProxyInfo(originalLine, None, None)
  94. with convertError(ValueError, MissingAddressData):
  95. sourceAddr, line = line.split(b" ", 1)
  96. with convertError(ValueError, MissingAddressData):
  97. destAddr, line = line.split(b" ", 1)
  98. with convertError(ValueError, MissingAddressData):
  99. sourcePort, line = line.split(b" ", 1)
  100. with convertError(ValueError, MissingAddressData):
  101. destPort = line.split(b" ")[0]
  102. if networkProtocol == cls.TCP4_PROTO:
  103. return _info.ProxyInfo(
  104. originalLine,
  105. address.IPv4Address("TCP", sourceAddr.decode(), int(sourcePort)),
  106. address.IPv4Address("TCP", destAddr.decode(), int(destPort)),
  107. )
  108. return _info.ProxyInfo(
  109. originalLine,
  110. address.IPv6Address("TCP", sourceAddr.decode(), int(sourcePort)),
  111. address.IPv6Address("TCP", destAddr.decode(), int(destPort)),
  112. )