You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by zw...@apache.org on 2021/02/23 17:38:58 UTC

[trafficserver] branch 9.1.x updated: Add PROXY Protocol Builder (#7445)

This is an automated email from the ASF dual-hosted git repository.

zwoop pushed a commit to branch 9.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/9.1.x by this push:
     new f46ce85  Add PROXY Protocol Builder (#7445)
f46ce85 is described below

commit f46ce85cfa86db9ea361659139f621b28dcd54c0
Author: Masaori Koshiba <ma...@apache.org>
AuthorDate: Fri Feb 5 08:19:34 2021 +0900

    Add PROXY Protocol Builder (#7445)
    
    (cherry picked from commit 1baffec5f2d5ce5272c9af5d46b2b54f4b9bbe6a)
---
 include/tscore/ink_inet.h                   |   6 +-
 iocore/net/ProxyProtocol.cc                 | 186 +++++++++++++++++++++++++++-
 iocore/net/ProxyProtocol.h                  |   3 +
 iocore/net/unit_tests/test_ProxyProtocol.cc | 100 +++++++++++++++
 4 files changed, 292 insertions(+), 3 deletions(-)

diff --git a/include/tscore/ink_inet.h b/include/tscore/ink_inet.h
index c0a7cc1..c683643 100644
--- a/include/tscore/ink_inet.h
+++ b/include/tscore/ink_inet.h
@@ -181,8 +181,10 @@ inkcoreapi uint32_t ats_inet_addr(const char *s);
 const char *ats_ip_ntop(const struct sockaddr *addr, char *dst, size_t size);
 
 // --
-/// Size in bytes of an IPv6 address.
-static size_t const TS_IP6_SIZE = sizeof(in6_addr);
+/// Size in bytes of an port and IPv4/IPv6 address.
+static constexpr size_t TS_IP4_SIZE  = sizeof(in_addr_t); ///< 4
+static constexpr size_t TS_IP6_SIZE  = sizeof(in6_addr);  ///< 16
+static constexpr size_t TS_PORT_SIZE = sizeof(in_port_t); ///< 2
 
 /// Reset an address to invalid.
 /// @note Useful for marking a member as not yet set.
diff --git a/iocore/net/ProxyProtocol.cc b/iocore/net/ProxyProtocol.cc
index 452f63b..1d406a7 100644
--- a/iocore/net/ProxyProtocol.cc
+++ b/iocore/net/ProxyProtocol.cc
@@ -26,7 +26,10 @@
 #include "I_EventSystem.h"
 #include "I_NetVConnection.h"
 
+#include "tscore/BufferWriter.h"
 #include "tscore/ink_assert.h"
+#include "tscore/ink_string.h"
+#include "tscore/ink_inet.h"
 #include "tscpp/util/TextView.h"
 
 namespace
@@ -42,6 +45,8 @@ constexpr ts::TextView PPv1_PROTO_UNKNOWN = "UNKNOWN"sv;
 constexpr ts::TextView PPv1_PROTO_TCP4    = "TCP4"sv;
 constexpr ts::TextView PPv1_PROTO_TCP6    = "TCP6"sv;
 
+constexpr std::string_view PPv1_DELIMITER = " "sv;
+
 constexpr uint8_t PPv2_CMD_LOCAL = 0x20;
 constexpr uint8_t PPv2_CMD_PROXY = 0x21;
 
@@ -55,7 +60,7 @@ constexpr uint8_t PPv2_PROTO_UNIX_DATAGRAM = 0x32;
 
 constexpr uint16_t PPv2_ADDR_LEN_INET  = 4 + 4 + 2 + 2;
 constexpr uint16_t PPv2_ADDR_LEN_INET6 = 16 + 16 + 2 + 2;
-// constexpr uint16_t PPv2_ADDR_LEN_UNIX  = 108 + 108;
+constexpr uint16_t PPv2_ADDR_LEN_UNIX  = 108 + 108;
 
 struct PPv2Hdr {
   uint8_t sig[12]; ///< preface
@@ -302,6 +307,149 @@ proxy_protocol_v2_parse(ProxyProtocol *pp_info, const ts::TextView &msg)
   return 0;
 }
 
+/**
+   Build PROXY Protocol v1
+ */
+size_t
+proxy_protocol_v1_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &pp_info)
+{
+  if (max_buf_len < PPv1_CONNECTION_HEADER_LEN_MAX) {
+    return 0;
+  }
+
+  ts::FixedBufferWriter bw{reinterpret_cast<char *>(buf), max_buf_len};
+
+  // preface
+  bw.write(PPv1_CONNECTION_PREFACE);
+  bw.write(PPv1_DELIMITER);
+
+  // the proxied INET protocol and family
+  if (pp_info.src_addr.isIp4()) {
+    bw.write(PPv1_PROTO_TCP4);
+  } else if (pp_info.src_addr.isIp6()) {
+    bw.write(PPv1_PROTO_TCP6);
+  } else {
+    bw.write(PPv1_PROTO_UNKNOWN);
+  }
+  bw.write(PPv1_DELIMITER);
+
+  // the layer 3 source address
+  char src_ip_buf[INET6_ADDRSTRLEN];
+  ats_ip_ntop(pp_info.src_addr, src_ip_buf, sizeof(src_ip_buf));
+  size_t src_ip_len = strnlen(src_ip_buf, sizeof(src_ip_buf));
+
+  bw.write(src_ip_buf, src_ip_len);
+  bw.write(PPv1_DELIMITER);
+
+  // the layer 3 destination address
+  char dst_ip_buf[INET6_ADDRSTRLEN];
+  ats_ip_ntop(pp_info.dst_addr, dst_ip_buf, sizeof(dst_ip_buf));
+  size_t dst_ip_len = strnlen(dst_ip_buf, sizeof(dst_ip_buf));
+
+  bw.write(dst_ip_buf, dst_ip_len);
+  bw.write(PPv1_DELIMITER);
+
+  // TCP source port
+  {
+    size_t len = ink_small_itoa(ats_ip_port_host_order(pp_info.src_addr), bw.auxBuffer(), bw.remaining());
+    bw.fill(len);
+    bw.write(PPv1_DELIMITER);
+  }
+
+  // TCP destination port
+  {
+    size_t len = ink_small_itoa(ats_ip_port_host_order(pp_info.dst_addr), bw.auxBuffer(), bw.remaining());
+    bw.fill(len);
+  }
+
+  bw.write("\r\n");
+
+  return bw.size();
+}
+
+/**
+   Build PROXY Protocol v2
+
+   UDP, Unix Domain Socket, and TLV fields are not supported yet
+ */
+size_t
+proxy_protocol_v2_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &pp_info)
+{
+  if (max_buf_len < PPv2_CONNECTION_HEADER_LEN) {
+    return 0;
+  }
+
+  ts::FixedBufferWriter bw{reinterpret_cast<char *>(buf), max_buf_len};
+
+  // # proxy_hdr_v2
+  // ## preface
+  bw.write(PPv2_CONNECTION_PREFACE);
+
+  // ## version and command
+  // TODO: support PPv2_CMD_LOCAL for health check
+  bw.write(static_cast<char>(PPv2_CMD_PROXY));
+
+  // ## family & address
+  // TODO: support UDP
+  switch (pp_info.src_addr.family()) {
+  case AF_INET:
+    bw.write(static_cast<char>(PPv2_PROTO_TCP4));
+    break;
+  case AF_INET6:
+    bw.write(static_cast<char>(PPv2_PROTO_TCP6));
+    break;
+  case AF_UNIX:
+    bw.write(static_cast<char>(PPv2_PROTO_UNIX_STREAM));
+    break;
+  default:
+    bw.write(static_cast<char>(PPv2_PROTO_UNSPEC));
+    break;
+  }
+
+  // ## len field. this will be set at the end of this function
+  const size_t len_field_offset = bw.size();
+  bw.fill(2);
+
+  ink_release_assert(bw.size() == PPv2_CONNECTION_HEADER_LEN);
+
+  // # proxy_addr
+  // TODO: support UDP
+  switch (pp_info.src_addr.family()) {
+  case AF_INET: {
+    bw.write(&ats_ip4_addr_cast(pp_info.src_addr), TS_IP4_SIZE);
+    bw.write(&ats_ip4_addr_cast(pp_info.dst_addr), TS_IP4_SIZE);
+    bw.write(&ats_ip_port_cast(pp_info.src_addr), TS_PORT_SIZE);
+    bw.write(&ats_ip_port_cast(pp_info.dst_addr), TS_PORT_SIZE);
+
+    break;
+  }
+  case AF_INET6: {
+    bw.write(&ats_ip6_addr_cast(pp_info.src_addr), TS_IP6_SIZE);
+    bw.write(&ats_ip6_addr_cast(pp_info.dst_addr), TS_IP6_SIZE);
+    bw.write(&ats_ip_port_cast(pp_info.src_addr), TS_PORT_SIZE);
+    bw.write(&ats_ip_port_cast(pp_info.dst_addr), TS_PORT_SIZE);
+
+    break;
+  }
+  case AF_UNIX: {
+    // unsupported yet
+    bw.fill(PPv2_ADDR_LEN_UNIX);
+    break;
+  }
+  default:
+    // do nothing
+    break;
+  }
+
+  // # Additional TLVs (pp2_tlv)
+  // unsupported yet
+
+  // Set len field (number of following bytes part of the header) in the hdr
+  uint16_t len = htons(bw.size() - PPv2_CONNECTION_HEADER_LEN);
+  memcpy(buf + len_field_offset, &len, sizeof(uint16_t));
+  return bw.size();
+}
+
 } // namespace
 
 /**
@@ -326,3 +474,39 @@ proxy_protocol_parse(ProxyProtocol *pp_info, ts::TextView tv)
 
   return len;
 }
+
+/**
+   PROXY Protocol Builder
+ */
+size_t
+proxy_protocol_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &pp_info, ProxyProtocolVersion force_version)
+{
+  ProxyProtocolVersion version = pp_info.version;
+  if (force_version != ProxyProtocolVersion::UNDEFINED) {
+    version = force_version;
+  }
+
+  size_t len = 0;
+
+  if (version == ProxyProtocolVersion::V1) {
+    len = proxy_protocol_v1_build(buf, max_buf_len, pp_info);
+  } else if (version == ProxyProtocolVersion::V2) {
+    len = proxy_protocol_v2_build(buf, max_buf_len, pp_info);
+  } else {
+    ink_abort("PROXY Protocol Version is undefined");
+  }
+
+  return len;
+}
+
+ProxyProtocolVersion
+proxy_protocol_version_cast(int i)
+{
+  switch (i) {
+  case 1:
+  case 2:
+    return static_cast<ProxyProtocolVersion>(i);
+  default:
+    return ProxyProtocolVersion::UNDEFINED;
+  }
+}
diff --git a/iocore/net/ProxyProtocol.h b/iocore/net/ProxyProtocol.h
index dedb31d..c0726e1 100644
--- a/iocore/net/ProxyProtocol.h
+++ b/iocore/net/ProxyProtocol.h
@@ -51,3 +51,6 @@ const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108;
 const size_t PPv2_CONNECTION_HEADER_LEN     = 16;
 
 extern size_t proxy_protocol_parse(ProxyProtocol *pp_info, ts::TextView tv);
+extern size_t proxy_protocol_build(uint8_t *buf, size_t max_buf_len, const ProxyProtocol &pp_info,
+                                   ProxyProtocolVersion force_version = ProxyProtocolVersion::UNDEFINED);
+extern ProxyProtocolVersion proxy_protocol_version_cast(int i);
diff --git a/iocore/net/unit_tests/test_ProxyProtocol.cc b/iocore/net/unit_tests/test_ProxyProtocol.cc
index e4fbb03..9f8b7c4 100644
--- a/iocore/net/unit_tests/test_ProxyProtocol.cc
+++ b/iocore/net/unit_tests/test_ProxyProtocol.cc
@@ -427,3 +427,103 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]")
     }
   }
 }
+
+TEST_CASE("ProxyProtocol v1 Builder", "[ProxyProtocol][ProxyProtocolv1]")
+{
+  SECTION("TCP over IPv4")
+  {
+    uint8_t buf[PPv1_CONNECTION_HEADER_LEN_MAX] = {0};
+
+    ProxyProtocol pp_info;
+    pp_info.version   = ProxyProtocolVersion::V1;
+    pp_info.ip_family = AF_INET;
+    ats_ip_pton("192.0.2.1:50000", pp_info.src_addr);
+    ats_ip_pton("198.51.100.1:443", pp_info.dst_addr);
+
+    size_t len = proxy_protocol_build(buf, sizeof(buf), pp_info);
+
+    std::string_view expected = "PROXY TCP4 192.0.2.1 198.51.100.1 50000 443\r\n"sv;
+
+    CHECK(len == expected.size());
+    CHECK(memcmp(buf, expected.data(), expected.size()) == 0);
+  }
+
+  SECTION("TCP over IPv6")
+  {
+    uint8_t buf[PPv1_CONNECTION_HEADER_LEN_MAX] = {0};
+
+    ProxyProtocol pp_info;
+    pp_info.version   = ProxyProtocolVersion::V1;
+    pp_info.ip_family = AF_INET6;
+    ats_ip_pton("[2001:db8:0:1::]:50000", pp_info.src_addr);
+    ats_ip_pton("[2001:db8:0:2::]:443", pp_info.dst_addr);
+
+    size_t len = proxy_protocol_build(buf, sizeof(buf), pp_info);
+
+    std::string_view expected = "PROXY TCP6 2001:db8:0:1:: 2001:db8:0:2:: 50000 443\r\n"sv;
+
+    CHECK(len == expected.size());
+    CHECK(memcmp(buf, expected.data(), expected.size()) == 0);
+  }
+}
+
+TEST_CASE("ProxyProtocol v2 Builder", "[ProxyProtocol][ProxyProtocolv2]")
+{
+  SECTION("TCP over IPv4 / no TLV")
+  {
+    uint8_t buf[1024] = {0};
+
+    ProxyProtocol pp_info;
+    pp_info.version   = ProxyProtocolVersion::V2;
+    pp_info.ip_family = AF_INET;
+    ats_ip_pton("192.0.2.1:50000", pp_info.src_addr);
+    ats_ip_pton("198.51.100.1:443", pp_info.dst_addr);
+
+    size_t len = proxy_protocol_build(buf, sizeof(buf), pp_info);
+
+    uint8_t expected[] = {
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< sig
+      0x55, 0x49, 0x54, 0x0A,                         ///<
+      0x21,                                           ///< ver_vmd
+      0x11,                                           ///< fam
+      0x00, 0x0C,                                     ///< len
+      0xC0, 0x00, 0x02, 0x01,                         ///< src_addr
+      0xC6, 0x33, 0x64, 0x01,                         ///< dst_addr
+      0xC3, 0x50,                                     ///< src_port
+      0x01, 0xBB,                                     ///< dst_port
+    };
+
+    CHECK(len == sizeof(expected));
+    CHECK(memcmp(expected, buf, len) == 0);
+  }
+
+  SECTION("TCP over IPv6 / no TLV")
+  {
+    uint8_t buf[1024] = {0};
+
+    ProxyProtocol pp_info;
+    pp_info.version   = ProxyProtocolVersion::V2;
+    pp_info.ip_family = AF_INET6;
+    ats_ip_pton("[2001:db8:0:1::]:50000", pp_info.src_addr);
+    ats_ip_pton("[2001:db8:0:2::]:443", pp_info.dst_addr);
+
+    size_t len = proxy_protocol_build(buf, sizeof(buf), pp_info);
+
+    uint8_t expected[] = {
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< sig
+      0x55, 0x49, 0x54, 0x0A,                         ///<
+      0x21,                                           ///< ver_vmd
+      0x21,                                           ///< fam
+      0x00, 0x24,                                     ///< len
+      0x20, 0x01, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x01, ///< src_addr
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ///<
+      0x20, 0x01, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x02, ///< dst_addr
+      0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ///<
+      0xC3, 0x50,                                     ///< src_port
+      0x01, 0xBB,                                     ///< dst_port
+    };
+
+    CHECK(len == sizeof(expected));
+    CHECK(memcmp(expected, buf, len) == 0);
+  }
+}