You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ma...@apache.org on 2021/01/25 23:05:00 UTC

[trafficserver] branch master updated: Add incoming PROXY Protocol v2 support (#7340)

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

masaori pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new f7bdee6  Add incoming PROXY Protocol v2 support (#7340)
f7bdee6 is described below

commit f7bdee616ebec26b2746ce50c75eaacab7571554
Author: Masaori Koshiba <ma...@apache.org>
AuthorDate: Tue Jan 26 08:04:44 2021 +0900

    Add incoming PROXY Protocol v2 support (#7340)
    
    TCP support only for now. UDP, UNIX Domain Socket, and TLVs are out of scope.
---
 .../configuration/proxy-protocol.en.rst            |   6 +-
 iocore/net/ProxyProtocol.cc                        | 137 +++++++++-
 iocore/net/ProxyProtocol.h                         |   2 +-
 iocore/net/unit_tests/test_ProxyProtocol.cc        | 292 ++++++++++++++++++++-
 4 files changed, 424 insertions(+), 13 deletions(-)

diff --git a/doc/admin-guide/configuration/proxy-protocol.en.rst b/doc/admin-guide/configuration/proxy-protocol.en.rst
index 8df27d1..adf61f6 100644
--- a/doc/admin-guide/configuration/proxy-protocol.en.rst
+++ b/doc/admin-guide/configuration/proxy-protocol.en.rst
@@ -31,7 +31,7 @@ TLS connections.
 
 .. note::
 
-    The current version only supports transforming client IP from PROXY Version 1
+    The current version only supports transforming client IP from PROXY Version 1/2
     header to the Forwarded: header.
 
 In the current implementation, the client IP address in the PROXY protocol header
@@ -41,7 +41,7 @@ is passed to the origin server via an HTTP `Forwarded:
 The Proxy Protocol must be enabled on each port.  See
 :ts:cv:`proxy.config.http.server_ports` for information on how to enable the
 Proxy Protocol on a port.  Once enabled, all incoming requests must be prefaced
-with the PROXY v1 header.  Any request not preface by this header will be
+with the PROXY v1/v2 header.  Any request not preface by this header will be
 dropped.
 
 As a security measure, an optional list of trusted IP addresses may be
@@ -50,7 +50,7 @@ configured with :ts:cv:`proxy.config.http.proxy_protocol_allowlist`.
    .. important::
 
        If the allowlist is configured, requests will only be accepted from these
-       IP addresses and must be prefaced with the PROXY v1 header.
+       IP addresses and must be prefaced with the PROXY v1/v2 header.
 
 See :ts:cv:`proxy.config.http.insert_forwarded` for configuration information.
 Detection of the PROXY protocol header is automatic.  If the PROXY header
diff --git a/iocore/net/ProxyProtocol.cc b/iocore/net/ProxyProtocol.cc
index 2de8673..452f63b 100644
--- a/iocore/net/ProxyProtocol.cc
+++ b/iocore/net/ProxyProtocol.cc
@@ -34,15 +34,57 @@ namespace
 using namespace std::literals;
 
 constexpr ts::TextView PPv1_CONNECTION_PREFACE = "PROXY"sv;
-constexpr ts::TextView PPv2_CONNECTION_PREFACE = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A\x02"sv;
+constexpr ts::TextView PPv2_CONNECTION_PREFACE = "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"sv;
 
 constexpr size_t PPv1_CONNECTION_HEADER_LEN_MIN = 15;
-constexpr size_t PPv2_CONNECTION_HEADER_LEN_MIN = 16;
 
 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 uint8_t PPv2_CMD_LOCAL = 0x20;
+constexpr uint8_t PPv2_CMD_PROXY = 0x21;
+
+constexpr uint8_t PPv2_PROTO_UNSPEC        = 0x00;
+constexpr uint8_t PPv2_PROTO_TCP4          = 0x11;
+constexpr uint8_t PPv2_PROTO_UDP4          = 0x12;
+constexpr uint8_t PPv2_PROTO_TCP6          = 0x21;
+constexpr uint8_t PPv2_PROTO_UDP6          = 0x22;
+constexpr uint8_t PPv2_PROTO_UNIX_STREAM   = 0x31;
+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;
+
+struct PPv2Hdr {
+  uint8_t sig[12]; ///< preface
+  uint8_t ver_cmd; ///< protocol version and command
+  uint8_t fam;     ///< protocol family and transport
+  uint16_t len;    ///< number of following bytes part of the header
+  union {
+    // for TCP/UDP over IPv4, len = 12 (PPv2_ADDR_LEN_INET)
+    struct {
+      uint32_t src_addr;
+      uint32_t dst_addr;
+      uint16_t src_port;
+      uint16_t dst_port;
+    } ip4;
+    // for TCP/UDP over IPv6, len = 36 (PPv2_ADDR_LEN_INET6)
+    struct {
+      uint8_t src_addr[16];
+      uint8_t dst_addr[16];
+      uint16_t src_port;
+      uint16_t dst_port;
+    } ip6;
+    // for AF_UNIX sockets, len = 216 (PPv2_ADDR_LEN_UNIX)
+    struct {
+      uint8_t src_addr[108];
+      uint8_t dst_addr[108];
+    } unix;
+  } addr;
+};
+
 /**
    PROXY Protocol v1 Parser
 
@@ -166,13 +208,100 @@ proxy_protocol_v1_parse(ProxyProtocol *pp_info, ts::TextView hdr)
 /**
    PROXY Protocol v2 Parser
 
+   TODO: TLVs Support
+
    @return read length
  */
 size_t
-proxy_protocol_v2_parse(ProxyProtocol * /*pp_info*/, ts::TextView /*hdr*/)
+proxy_protocol_v2_parse(ProxyProtocol *pp_info, const ts::TextView &msg)
 {
+  ink_release_assert(msg.size() >= PPv2_CONNECTION_HEADER_LEN);
+
+  const PPv2Hdr *hdr_v2 = reinterpret_cast<const PPv2Hdr *>(msg.data());
+
+  // Assuming PREFACE check is done
+
+  // length check
+  const uint16_t len     = ntohs(hdr_v2->len);
+  const size_t total_len = PPv2_CONNECTION_HEADER_LEN + len;
+
+  if (msg.size() < total_len) {
+    return 0;
+  }
+
+  // protocol version and command
+  switch (hdr_v2->ver_cmd) {
+  case PPv2_CMD_LOCAL: {
+    // protocol byte should be UNSPEC (\x00) with LOCAL command
+    if (hdr_v2->fam != PPv2_PROTO_UNSPEC) {
+      return 0;
+    }
+
+    pp_info->version   = ProxyProtocolVersion::V2;
+    pp_info->ip_family = AF_UNSPEC;
+
+    return total_len;
+  }
+  case PPv2_CMD_PROXY: {
+    switch (hdr_v2->fam) {
+    case PPv2_PROTO_TCP4: {
+      if (len < PPv2_ADDR_LEN_INET) {
+        return 0;
+      }
+
+      IpAddr src_addr(reinterpret_cast<in_addr_t>(hdr_v2->addr.ip4.src_addr));
+      pp_info->src_addr.assign(src_addr, hdr_v2->addr.ip4.src_port);
+
+      IpAddr dst_addr(reinterpret_cast<in_addr_t>(hdr_v2->addr.ip4.dst_addr));
+      pp_info->dst_addr.assign(dst_addr, hdr_v2->addr.ip4.dst_port);
+
+      pp_info->version   = ProxyProtocolVersion::V2;
+      pp_info->ip_family = AF_INET;
+
+      break;
+    }
+    case PPv2_PROTO_TCP6: {
+      if (len < PPv2_ADDR_LEN_INET6) {
+        return 0;
+      }
+
+      IpAddr src_addr(reinterpret_cast<in6_addr const &>(hdr_v2->addr.ip6.src_addr));
+      pp_info->src_addr.assign(src_addr, hdr_v2->addr.ip6.src_port);
+
+      IpAddr dst_addr(reinterpret_cast<in6_addr const &>(hdr_v2->addr.ip6.dst_addr));
+      pp_info->dst_addr.assign(dst_addr, hdr_v2->addr.ip6.dst_port);
+
+      pp_info->version   = ProxyProtocolVersion::V2;
+      pp_info->ip_family = AF_INET6;
+
+      break;
+    }
+    case PPv2_PROTO_UDP4:
+      [[fallthrough]];
+    case PPv2_PROTO_UDP6:
+      [[fallthrough]];
+    case PPv2_PROTO_UNIX_STREAM:
+      [[fallthrough]];
+    case PPv2_PROTO_UNIX_DATAGRAM:
+      [[fallthrough]];
+    case PPv2_PROTO_UNSPEC:
+      [[fallthrough]];
+    default:
+      // unsupported
+      return 0;
+    }
+
+    // TODO: Parse TLVs
+
+    return total_len;
+  }
+  default:
+    break;
+  }
+
   return 0;
 }
+
 } // namespace
 
 /**
@@ -187,7 +316,7 @@ proxy_protocol_parse(ProxyProtocol *pp_info, ts::TextView tv)
   if (tv.size() >= PPv1_CONNECTION_HEADER_LEN_MIN && PPv1_CONNECTION_PREFACE.isPrefixOf(tv)) {
     // Client must send at least 15 bytes to get a reasonable match.
     len = proxy_protocol_v1_parse(pp_info, tv);
-  } else if (tv.size() >= PPv2_CONNECTION_HEADER_LEN_MIN && PPv2_CONNECTION_PREFACE.isPrefixOf(tv)) {
+  } else if (tv.size() >= PPv2_CONNECTION_HEADER_LEN && PPv2_CONNECTION_PREFACE.isPrefixOf(tv)) {
     len = proxy_protocol_v2_parse(pp_info, tv);
   } else {
     // if we don't have the PROXY preface, we don't have a ProxyProtocol header
diff --git a/iocore/net/ProxyProtocol.h b/iocore/net/ProxyProtocol.h
index 84491bc..dedb31d 100644
--- a/iocore/net/ProxyProtocol.h
+++ b/iocore/net/ProxyProtocol.h
@@ -48,6 +48,6 @@ struct ProxyProtocol {
 };
 
 const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108;
-const size_t PPv2_CONNECTION_HEADER_LEN_MAX = 16;
+const size_t PPv2_CONNECTION_HEADER_LEN     = 16;
 
 extern size_t proxy_protocol_parse(ProxyProtocol *pp_info, ts::TextView tv);
diff --git a/iocore/net/unit_tests/test_ProxyProtocol.cc b/iocore/net/unit_tests/test_ProxyProtocol.cc
index da2b754..e4fbb03 100644
--- a/iocore/net/unit_tests/test_ProxyProtocol.cc
+++ b/iocore/net/unit_tests/test_ProxyProtocol.cc
@@ -124,13 +124,16 @@ TEST_CASE("PROXY Protocol v1 Parser", "[ProxyProtocol][ProxyProtocolv1]")
 
 TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]")
 {
-  SECTION("TCP over IPv4")
+  IpEndpoint src_addr;
+  IpEndpoint dst_addr;
+
+  SECTION("TCP over IPv4 without TLVs")
   {
     uint8_t raw_data[] = {
-      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< sig
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
       0x55, 0x49, 0x54, 0x0A,                         ///<
-      0x02,                                           ///< ver_vmd
-      0x11,                                           ///< fam
+      0x21,                                           ///< version & command
+      0x11,                                           ///< protocol & family
       0x00, 0x0C,                                     ///< len
       0xC0, 0x00, 0x02, 0x01,                         ///< src_addr
       0xC6, 0x33, 0x64, 0x01,                         ///< dst_addr
@@ -141,7 +144,286 @@ TEST_CASE("PROXY Protocol v2 Parser", "[ProxyProtocol][ProxyProtocolv2]")
     ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
 
     ProxyProtocol pp_info;
-    // TODO: add test when implemented. Just checking this doesn't crash for now
+    REQUIRE(proxy_protocol_parse(&pp_info, tv) == tv.size());
+
+    REQUIRE(ats_ip_pton("192.0.2.1:50000", src_addr) == 0);
+    REQUIRE(ats_ip_pton("198.51.100.1:443", dst_addr) == 0);
+
+    CHECK(pp_info.version == ProxyProtocolVersion::V2);
+    CHECK(pp_info.ip_family == AF_INET);
+    CHECK(pp_info.src_addr == src_addr);
+    CHECK(pp_info.dst_addr == dst_addr);
+  }
+
+  SECTION("TCP over IPv6 without TLVs")
+  {
+    uint8_t raw_data[] = {
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+      0x55, 0x49, 0x54, 0x0A,                         ///<
+      0x21,                                           ///< version & command
+      0x21,                                           ///< protocol & family
+      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
+    };
+
+    ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+    ProxyProtocol pp_info;
+    REQUIRE(proxy_protocol_parse(&pp_info, tv) == tv.size());
+
+    REQUIRE(ats_ip_pton("[2001:db8:0:1::]:50000", src_addr) == 0);
+    REQUIRE(ats_ip_pton("[2001:db8:0:2::]:443", dst_addr) == 0);
+
+    CHECK(pp_info.version == ProxyProtocolVersion::V2);
+    CHECK(pp_info.ip_family == AF_INET6);
+    CHECK(pp_info.src_addr == src_addr);
+    CHECK(pp_info.dst_addr == dst_addr);
+  }
+
+  SECTION("LOCAL command - health check")
+  {
+    uint8_t raw_data[] = {
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+      0x55, 0x49, 0x54, 0x0A,                         ///<
+      0x20,                                           ///< version & command
+      0x00,                                           ///< protocol & family
+      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
+    };
+
+    ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+    ProxyProtocol pp_info;
+    REQUIRE(proxy_protocol_parse(&pp_info, tv) == tv.size());
+
+    CHECK(pp_info.version == ProxyProtocolVersion::V2);
+    CHECK(pp_info.ip_family == AF_UNSPEC);
+  }
+
+  SECTION("UNSPEC - unknownun/specified/unsupported transport protocol & address family")
+  {
+    uint8_t raw_data[] = {
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+      0x55, 0x49, 0x54, 0x0A,                         ///<
+      0x21,                                           ///< version & command
+      0x00,                                           ///< protocol & family
+      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
+    };
+
+    ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+    ProxyProtocol pp_info;
     REQUIRE(proxy_protocol_parse(&pp_info, tv) == 0);
+
+    CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+    CHECK(pp_info.ip_family == AF_UNSPEC);
+  }
+
+  // TLVs are not supported yet. Checking TLVs are skipped as expected for now.
+  SECTION("TLVs")
+  {
+    uint8_t raw_data[] = {
+      0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+      0x55, 0x49, 0x54, 0x0A,                         ///<
+      0x21,                                           ///< version & command
+      0x11,                                           ///< protocol & family
+      0x00, 0x11,                                     ///< len
+      0xC0, 0x00, 0x02, 0x01,                         ///< src_addr
+      0xC6, 0x33, 0x64, 0x01,                         ///< dst_addr
+      0xC3, 0x50,                                     ///< src_port
+      0x01, 0xBB,                                     ///< dst_port
+      0x01, 0x00, 0x02, 0x68, 0x32,                   /// PP2_TYPE_ALPN (h2)
+    };
+
+    ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+    ProxyProtocol pp_info;
+    REQUIRE(proxy_protocol_parse(&pp_info, tv) == tv.size());
+
+    REQUIRE(ats_ip_pton("192.0.2.1:50000", src_addr) == 0);
+    REQUIRE(ats_ip_pton("198.51.100.1:443", dst_addr) == 0);
+
+    CHECK(pp_info.version == ProxyProtocolVersion::V2);
+    CHECK(pp_info.ip_family == AF_INET);
+    CHECK(pp_info.src_addr == src_addr);
+    CHECK(pp_info.dst_addr == dst_addr);
+  }
+
+  SECTION("Malformed Headers")
+  {
+    ProxyProtocol pp_info;
+
+    SECTION("invalid preface")
+    {
+      uint8_t raw_data[] = {
+        0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, ///< preface
+        0xDE, 0xAD, 0xBE, 0xEF,                         ///<
+        0x21,                                           ///< version & command
+        0x21,                                           ///< protocol & family
+        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
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
+
+    SECTION("unsupported version & command")
+    {
+      uint8_t raw_data[] = {
+        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+        0x55, 0x49, 0x54, 0x0A,                         ///<
+        0xFF,                                           ///< version & command
+        0x21,                                           ///< protocol & family
+        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
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
+
+    SECTION("unsupported protocol & family")
+    {
+      uint8_t raw_data[] = {
+        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+        0x55, 0x49, 0x54, 0x0A,                         ///<
+        0x21,                                           ///< version & command
+        0xFF,                                           ///< protocol & family
+        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
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
+
+    SECTION("invalid len value - too long")
+    {
+      uint8_t raw_data[] = {
+        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+        0x55, 0x49, 0x54, 0x0A,                         ///<
+        0x21,                                           ///< version & command
+        0x21,                                           ///< protocol & family
+        0x00, 0x25,                                     ///< 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
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
+
+    SECTION("invalid len - actual buffer is shorter than the value")
+    {
+      uint8_t raw_data[] = {
+        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+        0x55, 0x49, 0x54, 0x0A,                         ///<
+        0x21,                                           ///< version & command
+        0x21,                                           ///< protocol & family
+        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,                                           ///< dst_port
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
+
+    SECTION("invalid len - too short for INET")
+    {
+      uint8_t raw_data[] = {
+        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+        0x55, 0x49, 0x54, 0x0A,                         ///<
+        0x21,                                           ///< version & command
+        0x11,                                           ///< protocol & family
+        0x00, 0x0C,                                     ///< len
+        0xC0, 0x00,                                     ///< src_addr
+        0xC6, 0x33,                                     ///< dst_addr
+        0xC3, 0x50,                                     ///< src_port
+        0x01, 0xBB,                                     ///< dst_port
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
+
+    SECTION("invalid len - too short for INET6")
+    {
+      uint8_t raw_data[] = {
+        0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, ///< preface
+        0x55, 0x49, 0x54, 0x0A,                         ///<
+        0x21,                                           ///< version & command
+        0x21,                                           ///< protocol & family
+        0x00, 0x24,                                     ///< len
+        0x20, 0x01, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x01, ///< src_addr
+        0x20, 0x01, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x02, ///< dst_addr
+        0xC3, 0x50,                                     ///< src_port
+        0x01, 0xBB,                                     ///< dst_port
+      };
+
+      ts::TextView tv(reinterpret_cast<char *>(raw_data), sizeof(raw_data));
+
+      CHECK(proxy_protocol_parse(&pp_info, tv) == 0);
+      CHECK(pp_info.version == ProxyProtocolVersion::UNDEFINED);
+      CHECK(pp_info.ip_family == AF_UNSPEC);
+    }
   }
 }