1// Type-Length-Value splitting and parsing for proxy protocol V2
2// See spec https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt sections 2.2 to 2.7 and
3
4package proxyproto
5
6import (
7	"encoding/binary"
8	"errors"
9	"fmt"
10	"math"
11)
12
13const (
14	// Section 2.2
15	PP2_TYPE_ALPN           PP2Type = 0x01
16	PP2_TYPE_AUTHORITY      PP2Type = 0x02
17	PP2_TYPE_CRC32C         PP2Type = 0x03
18	PP2_TYPE_NOOP           PP2Type = 0x04
19	PP2_TYPE_UNIQUE_ID      PP2Type = 0x05
20	PP2_TYPE_SSL            PP2Type = 0x20
21	PP2_SUBTYPE_SSL_VERSION PP2Type = 0x21
22	PP2_SUBTYPE_SSL_CN      PP2Type = 0x22
23	PP2_SUBTYPE_SSL_CIPHER  PP2Type = 0x23
24	PP2_SUBTYPE_SSL_SIG_ALG PP2Type = 0x24
25	PP2_SUBTYPE_SSL_KEY_ALG PP2Type = 0x25
26	PP2_TYPE_NETNS          PP2Type = 0x30
27
28	// Section 2.2.7, reserved types
29	PP2_TYPE_MIN_CUSTOM     PP2Type = 0xE0
30	PP2_TYPE_MAX_CUSTOM     PP2Type = 0xEF
31	PP2_TYPE_MIN_EXPERIMENT PP2Type = 0xF0
32	PP2_TYPE_MAX_EXPERIMENT PP2Type = 0xF7
33	PP2_TYPE_MIN_FUTURE     PP2Type = 0xF8
34	PP2_TYPE_MAX_FUTURE     PP2Type = 0xFF
35)
36
37var (
38	ErrTruncatedTLV    = errors.New("proxyproto: truncated TLV")
39	ErrMalformedTLV    = errors.New("proxyproto: malformed TLV Value")
40	ErrIncompatibleTLV = errors.New("proxyproto: incompatible TLV type")
41)
42
43// PP2Type is the proxy protocol v2 type
44type PP2Type byte
45
46// TLV is a uninterpreted Type-Length-Value for V2 protocol, see section 2.2
47type TLV struct {
48	Type  PP2Type
49	Value []byte
50}
51
52// SplitTLVs splits the Type-Length-Value vector, returns the vector or an error.
53func SplitTLVs(raw []byte) ([]TLV, error) {
54	var tlvs []TLV
55	for i := 0; i < len(raw); {
56		tlv := TLV{
57			Type: PP2Type(raw[i]),
58		}
59		if len(raw)-i <= 2 {
60			return nil, ErrTruncatedTLV
61		}
62		tlvLen := int(binary.BigEndian.Uint16(raw[i+1 : i+3])) // Max length = 65K
63		i += 3
64		if i+tlvLen > len(raw) {
65			return nil, ErrTruncatedTLV
66		}
67		// Ignore no-op padding
68		if tlv.Type != PP2_TYPE_NOOP {
69			tlv.Value = make([]byte, tlvLen)
70			copy(tlv.Value, raw[i:i+tlvLen])
71		}
72		i += tlvLen
73		tlvs = append(tlvs, tlv)
74	}
75	return tlvs, nil
76}
77
78// JoinTLVs joins multiple Type-Length-Value records.
79func JoinTLVs(tlvs []TLV) ([]byte, error) {
80	var raw []byte
81	for _, tlv := range tlvs {
82		if len(tlv.Value) > math.MaxUint16 {
83			return nil, fmt.Errorf("proxyproto: cannot format TLV %v with length %d", tlv.Type, len(tlv.Value))
84		}
85		var length [2]byte
86		binary.BigEndian.PutUint16(length[:], uint16(len(tlv.Value)))
87		raw = append(raw, byte(tlv.Type))
88		raw = append(raw, length[:]...)
89		raw = append(raw, tlv.Value...)
90	}
91	return raw, nil
92}
93
94// Registered is true if the type is registered in the spec, see section 2.2
95func (p PP2Type) Registered() bool {
96	switch p {
97	case PP2_TYPE_ALPN,
98		PP2_TYPE_AUTHORITY,
99		PP2_TYPE_CRC32C,
100		PP2_TYPE_NOOP,
101		PP2_TYPE_UNIQUE_ID,
102		PP2_TYPE_SSL,
103		PP2_SUBTYPE_SSL_VERSION,
104		PP2_SUBTYPE_SSL_CN,
105		PP2_SUBTYPE_SSL_CIPHER,
106		PP2_SUBTYPE_SSL_SIG_ALG,
107		PP2_SUBTYPE_SSL_KEY_ALG,
108		PP2_TYPE_NETNS:
109		return true
110	}
111	return false
112}
113
114// App is true if the type is reserved for application specific data, see section 2.2.7
115func (p PP2Type) App() bool {
116	return p >= PP2_TYPE_MIN_CUSTOM && p <= PP2_TYPE_MAX_CUSTOM
117}
118
119// Experiment is true if the type is reserved for temporary experimental use by application developers, see section 2.2.7
120func (p PP2Type) Experiment() bool {
121	return p >= PP2_TYPE_MIN_EXPERIMENT && p <= PP2_TYPE_MAX_EXPERIMENT
122}
123
124// Future is true is the type is reserved for future use, see section 2.2.7
125func (p PP2Type) Future() bool {
126	return p >= PP2_TYPE_MIN_FUTURE
127}
128
129// Spec is true if the type is covered by the spec, see section 2.2 and 2.2.7
130func (p PP2Type) Spec() bool {
131	return p.Registered() || p.App() || p.Experiment() || p.Future()
132}
133