_v1parser.py 4.2 KB

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