xref: /trafficserver/iocore/net/ProxyProtocol.cc (revision adf3299d)
1 /** @file
2  *
3  *  PROXY protocol definitions and parsers.
4  *
5  *  @section license License
6  *
7  *  Licensed to the Apache Software Foundation (ASF) under one
8  *  or more contributor license agreements.  See the NOTICE file
9  *  distributed with this work for additional information
10  *  regarding copyright ownership.  The ASF licenses this file
11  *  to you under the Apache License, Version 2.0 (the
12  *  "License"); you may not use this file except in compliance
13  *  with the License.  You may obtain a copy of the License at
14  *
15  *      http://www.apache.org/licenses/LICENSE-2.0
16  *
17  *  Unless required by applicable law or agreed to in writing, software
18  *  distributed under the License is distributed on an "AS IS" BASIS,
19  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20  *  See the License for the specific language governing permissions and
21  *  limitations under the License.
22  */
23 
24 #include "tscore/ink_assert.h"
25 #include "tscpp/util/TextView.h"
26 #include "ProxyProtocol.h"
27 #include "I_NetVConnection.h"
28 
29 bool
ssl_has_proxy_v1(NetVConnection * sslvc,char * buffer,int64_t * bytes_r)30 ssl_has_proxy_v1(NetVConnection *sslvc, char *buffer, int64_t *bytes_r)
31 {
32   ts::TextView tv;
33 
34   tv.assign(buffer, *bytes_r);
35 
36   // Client must send at least 15 bytes to get a reasonable match.
37   if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) {
38     Debug("proxyprotocol_v1", "ssl_has_proxy_v1: not enough recv'd");
39     return false;
40   }
41 
42   // if we don't have the PROXY preface, we don't have a ProxyV1 header
43   if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buffer, PROXY_V1_CONNECTION_PREFACE_LEN)) {
44     Debug("proxyprotocol_v1", "ssl_has_proxy_v1: failed the memcmp(%s, %s, %lu)", PROXY_V1_CONNECTION_PREFACE, buffer,
45           PROXY_V1_CONNECTION_PREFACE_LEN);
46     return false;
47   }
48 
49   //  Find the terminating newline
50   ts::TextView::size_type pos = tv.find('\n');
51   if (pos == tv.npos) {
52     Debug("proxyprotocol_v1", "ssl_has_proxy_v1: newline not found");
53     return false;
54   }
55 
56   // Parse the TextView before moving the bytes in the buffer
57   if (!proxy_protov1_parse(sslvc, tv)) {
58     *bytes_r = -EAGAIN;
59     return false;
60   }
61   *bytes_r -= pos + 1;
62   if (*bytes_r <= 0) {
63     *bytes_r = -EAGAIN;
64   } else {
65     Debug("ssl", "Moving %" PRId64 " characters remaining in the buffer from %p to %p", *bytes_r, buffer + pos + 1, buffer);
66     memmove(buffer, buffer + pos + 1, *bytes_r);
67   }
68   return true;
69 }
70 
71 bool
http_has_proxy_v1(IOBufferReader * reader,NetVConnection * netvc)72 http_has_proxy_v1(IOBufferReader *reader, NetVConnection *netvc)
73 {
74   char buf[PROXY_V1_CONNECTION_HEADER_LEN_MAX + 1];
75   ts::TextView tv;
76 
77   tv.assign(buf, reader->memcpy(buf, sizeof(buf), 0));
78 
79   // Client must send at least 15 bytes to get a reasonable match.
80   if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) {
81     return false;
82   }
83 
84   if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buf, PROXY_V1_CONNECTION_PREFACE_LEN)) {
85     return false;
86   }
87 
88   // Find the terminating LF, which should already be in the buffer.
89   ts::TextView::size_type pos = tv.find('\n');
90   if (pos == tv.npos) { // not found, it's not a proxy protocol header.
91     return false;
92   }
93   reader->consume(pos + 1); // clear out the header.
94 
95   // Now that we know we have a valid PROXY V1 preface, let's parse the
96   // remainder of the header
97 
98   return proxy_protov1_parse(netvc, tv);
99 }
100 
101 bool
proxy_protov1_parse(NetVConnection * netvc,ts::TextView hdr)102 proxy_protov1_parse(NetVConnection *netvc, ts::TextView hdr)
103 {
104   static const std::string_view PREFACE{PROXY_V1_CONNECTION_PREFACE, PROXY_V1_CONNECTION_PREFACE_LEN};
105   ts::TextView token;
106   in_port_t port;
107 
108   // All the cases are special and sequence, might as well unroll them.
109 
110   // The header should begin with the PROXY preface
111   token = hdr.split_prefix_at(' ');
112   if (0 == token.size() || token != PREFACE) {
113     Debug("proxyprotocol_v1", "proxy_protov1_parse: header [%.*s] does not start with preface [%.*s]", static_cast<int>(hdr.size()),
114           hdr.data(), static_cast<int>(PREFACE.size()), PREFACE.data());
115     return false;
116   }
117   Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = PREFACE", static_cast<int>(token.size()), token.data());
118 
119   // The INET protocol family - TCP4, TCP6 or UNKNOWN
120   token = hdr.split_prefix_at(' ');
121   if (0 == token.size()) {
122     return false;
123   }
124   Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = INET Family", static_cast<int>(token.size()), token.data());
125 
126   // Next up is the layer 3 source address
127   // - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff
128   token = hdr.split_prefix_at(' ');
129   if (0 == token.size()) {
130     return false;
131   }
132   Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Address", static_cast<int>(token.size()), token.data());
133   if (0 != netvc->set_proxy_protocol_src_addr(token)) {
134     return false;
135   }
136 
137   // Next is the layer3 destination address
138   // - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff
139   token = hdr.split_prefix_at(' ');
140   if (0 == token.size()) {
141     return false;
142   }
143   Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Address", static_cast<int>(token.size()), token.data());
144   if (0 != netvc->set_proxy_protocol_dst_addr(token)) {
145     return false;
146   }
147 
148   // Next is the TCP source port represented as a decimal number in the range of [0..65535] inclusive.
149   token = hdr.split_prefix_at(' ');
150   if (0 == token.size()) {
151     return false;
152   }
153   Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Port", static_cast<int>(token.size()), token.data());
154 
155   if (0 == (port = ts::svtoi(token))) {
156     Debug("proxyprotocol_v1", "proxy_protov1_parse: src port [%d] token [%.*s] failed to parse", port,
157           static_cast<int>(token.size()), token.data());
158     return false;
159   }
160   netvc->set_proxy_protocol_src_port(port);
161 
162   // Next is the TCP destination port represented as a decimal number in the range of [0..65535] inclusive.
163   // Final trailer is CR LF so split at CR.
164   token = hdr.split_prefix_at('\r');
165   if (0 == token.size()) {
166     return false;
167   }
168   Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Port", static_cast<int>(token.size()), token.data());
169   if (0 == (port = ts::svtoi(token))) {
170     Debug("proxyprotocol_v1", "proxy_protov1_parse: dst port [%d] token [%.*s] failed to parse", port,
171           static_cast<int>(token.size()), token.data());
172     return false;
173   }
174   netvc->set_proxy_protocol_dst_port(port);
175 
176   netvc->set_proxy_protocol_version(NetVConnection::ProxyProtocolVersion::V1);
177 
178   return true;
179 }
180