blob: 83c3bcf9dfe0bc0405ff0fdd434701b7bf066008 [file] [log] [blame]
/** @file
*
* PROXY protocol definitions and parsers.
*
* @section license License
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "tscore/ink_assert.h"
#include "tscpp/util/TextView.h"
#include "ProxyProtocol.h"
#include "I_NetVConnection.h"
bool
ssl_has_proxy_v1(NetVConnection *sslvc, char *buffer, int64_t *bytes_r)
{
ts::TextView tv;
tv.assign(buffer, *bytes_r);
// Client must send at least 15 bytes to get a reasonable match.
if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) {
Debug("proxyprotocol_v1", "ssl_has_proxy_v1: not enough recv'd");
return false;
}
// if we don't have the PROXY preface, we don't have a ProxyV1 header
if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buffer, PROXY_V1_CONNECTION_PREFACE_LEN)) {
Debug("proxyprotocol_v1", "ssl_has_proxy_v1: failed the memcmp(%s, %s, %lu)", PROXY_V1_CONNECTION_PREFACE, buffer,
PROXY_V1_CONNECTION_PREFACE_LEN);
return false;
}
// Find the terminating newline
ts::TextView::size_type pos = tv.find('\n');
if (pos == tv.npos) {
Debug("proxyprotocol_v1", "ssl_has_proxy_v1: newline not found");
return false;
}
// Parse the TextView before moving the bytes in the buffer
if (!proxy_protov1_parse(sslvc, tv)) {
*bytes_r = -EAGAIN;
return false;
}
*bytes_r -= pos + 1;
if (*bytes_r <= 0) {
*bytes_r = -EAGAIN;
} else {
Debug("ssl", "Moving %" PRId64 " characters remaining in the buffer from %p to %p", *bytes_r, buffer + pos + 1, buffer);
memmove(buffer, buffer + pos + 1, *bytes_r);
}
return true;
}
bool
http_has_proxy_v1(IOBufferReader *reader, NetVConnection *netvc)
{
char buf[PROXY_V1_CONNECTION_HEADER_LEN_MAX + 1];
ts::TextView tv;
tv.assign(buf, reader->memcpy(buf, sizeof(buf), 0));
// Client must send at least 15 bytes to get a reasonable match.
if (tv.size() < PROXY_V1_CONNECTION_HEADER_LEN_MIN) {
return false;
}
if (0 != memcmp(PROXY_V1_CONNECTION_PREFACE, buf, PROXY_V1_CONNECTION_PREFACE_LEN)) {
return false;
}
// Find the terminating LF, which should already be in the buffer.
ts::TextView::size_type pos = tv.find('\n');
if (pos == tv.npos) { // not found, it's not a proxy protocol header.
return false;
}
reader->consume(pos + 1); // clear out the header.
// Now that we know we have a valid PROXY V1 preface, let's parse the
// remainder of the header
return proxy_protov1_parse(netvc, tv);
}
bool
proxy_protov1_parse(NetVConnection *netvc, ts::TextView hdr)
{
static const std::string_view PREFACE{PROXY_V1_CONNECTION_PREFACE, PROXY_V1_CONNECTION_PREFACE_LEN};
ts::TextView token;
in_port_t port;
// All the cases are special and sequence, might as well unroll them.
// The header should begin with the PROXY preface
token = hdr.split_prefix_at(' ');
if (0 == token.size() || token != PREFACE) {
Debug("proxyprotocol_v1", "proxy_protov1_parse: header [%.*s] does not start with preface [%.*s]", static_cast<int>(hdr.size()),
hdr.data(), static_cast<int>(PREFACE.size()), PREFACE.data());
return false;
}
Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = PREFACE", static_cast<int>(token.size()), token.data());
// The INET protocol family - TCP4, TCP6 or UNKNOWN
token = hdr.split_prefix_at(' ');
if (0 == token.size()) {
return false;
}
Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = INET Family", static_cast<int>(token.size()), token.data());
// Next up is the layer 3 source address
// - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff
token = hdr.split_prefix_at(' ');
if (0 == token.size()) {
return false;
}
Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Address", static_cast<int>(token.size()), token.data());
if (0 != netvc->set_proxy_protocol_src_addr(token)) {
return false;
}
// Next is the layer3 destination address
// - 255.255.255.255 or ffff:f...f:ffff ffff:f...f:fff
token = hdr.split_prefix_at(' ');
if (0 == token.size()) {
return false;
}
Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Address", static_cast<int>(token.size()), token.data());
if (0 != netvc->set_proxy_protocol_dst_addr(token)) {
return false;
}
// Next is the TCP source port represented as a decimal number in the range of [0..65535] inclusive.
token = hdr.split_prefix_at(' ');
if (0 == token.size()) {
return false;
}
Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Source Port", static_cast<int>(token.size()), token.data());
if (0 == (port = ts::svtoi(token))) {
Debug("proxyprotocol_v1", "proxy_protov1_parse: src port [%d] token [%.*s] failed to parse", port,
static_cast<int>(token.size()), token.data());
return false;
}
netvc->set_proxy_protocol_src_port(port);
// Next is the TCP destination port represented as a decimal number in the range of [0..65535] inclusive.
// Final trailer is CR LF so split at CR.
token = hdr.split_prefix_at('\r');
if (0 == token.size()) {
return false;
}
Debug("proxyprotocol_v1", "proxy_protov1_parse: [%.*s] = Destination Port", static_cast<int>(token.size()), token.data());
if (0 == (port = ts::svtoi(token))) {
Debug("proxyprotocol_v1", "proxy_protov1_parse: dst port [%d] token [%.*s] failed to parse", port,
static_cast<int>(token.size()), token.data());
return false;
}
netvc->set_proxy_protocol_dst_port(port);
netvc->set_proxy_protocol_version(NetVConnection::ProxyProtocolVersion::V1);
return true;
}