123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- # -*- 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 constantly import Values, ValueConstant
- from zope.interface import implementer
- from twisted.internet import address
- from twisted.python import compat
- from ._exceptions import (
- convertError, InvalidProxyHeader, InvalidNetworkProtocol,
- MissingAddressData
- )
- from . import _info
- from . import _interfaces
- 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(object):
- """
- 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):
- self.buffer = b''
- def feed(self, data):
- """
- 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):
- """
- 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):
- """
- 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(
- ('%x' % (int(hexString[b:b+4], 16),)).encode('ascii')
- for b in range(0, 32, 4)
- )
- @classmethod
- def parse(cls, line):
- """
- 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 = 'TCP'
- if netproto is NetProtocol.DGRAM:
- addrType = 'UDP'
- addrCls = address.IPv4Address
- addrParser = 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), sPort),
- addrCls(addrType, addrParser(dest), dPort),
- )
|