1
#include "source/extensions/bootstrap/reverse_tunnel/common/rping_interceptor.h"
2

            
3
#include "source/extensions/bootstrap/reverse_tunnel/common/reverse_connection_utility.h"
4

            
5
namespace Envoy {
6
namespace Extensions {
7
namespace Bootstrap {
8
namespace ReverseConnection {
9

            
10
Api::IoCallUint64Result RpingInterceptor::read(Buffer::Instance& buffer,
11
100
                                               absl::optional<uint64_t> max_length) {
12
  // Perform the actual read first.
13
100
  Api::IoCallUint64Result result = IoSocketHandleImpl::read(buffer, max_length);
14
100
  ENVOY_LOG(trace, "RpingInterceptor: read result: {}", result.return_value_);
15

            
16
  // If RPING keepalives are still active, check whether the incoming data is a RPING message.
17
100
  if (ping_echo_active_ && result.err_ == nullptr && result.return_value_ > 0) {
18
29
    const uint64_t expected = ReverseConnectionUtility::PING_MESSAGE.size();
19

            
20
    // Compare up to the expected size using a zero-copy view.
21
29
    const uint64_t len = std::min<uint64_t>(buffer.length(), expected);
22
29
    const char* data = static_cast<const char*>(buffer.linearize(len));
23
29
    absl::string_view peek_sv{data, static_cast<size_t>(len)};
24

            
25
    // Check if we have a complete RPING message.
26
29
    if (len == expected && ReverseConnectionUtility::isPingMessage(peek_sv)) {
27
      // Found a complete RPING. Echo and drain it from the buffer.
28
9
      buffer.drain(expected);
29
9
      onPingMessage();
30

            
31
      // If buffer only contained RPING, return showing we processed it.
32
9
      if (buffer.length() == 0) {
33
6
        return Api::IoCallUint64Result{expected, Api::IoError::none()};
34
6
      }
35

            
36
      // RPING followed by application data. Disable echo and return the remaining data.
37
3
      ENVOY_LOG(trace,
38
3
                "RpingInterceptor: received application data after RPING, "
39
3
                "disabling RPING echo for FD: {}",
40
3
                fd_);
41
3
      ping_echo_active_ = false;
42
      // The adjusted return value is the number of bytes excluding the drained RPING. It should be
43
      // transparent to upper layers that the RPING was processed.
44
3
      const uint64_t adjusted =
45
3
          (result.return_value_ >= expected) ? (result.return_value_ - expected) : 0;
46
3
      return Api::IoCallUint64Result{adjusted, Api::IoError::none()};
47
9
    }
48

            
49
    // If partial data could be the start of RPING (only when fewer than expected bytes).
50
20
    if (len < expected) {
51
2
      const absl::string_view rping_prefix =
52
2
          ReverseConnectionUtility::PING_MESSAGE.substr(0, static_cast<size_t>(len));
53
2
      if (peek_sv == rping_prefix) {
54
2
        ENVOY_LOG(trace,
55
2
                  "RpingInterceptor: partial RPING received ({} bytes), waiting "
56
2
                  "for more.",
57
2
                  len);
58
2
        return result; // Wait for more data.
59
2
      }
60
2
    }
61

            
62
    // Data is not RPING (complete or partial). Disable echo permanently.
63
18
    ENVOY_LOG(trace,
64
18
              "RpingInterceptor: received application data ({} bytes), "
65
18
              "disabling RPING echo for FD: {}",
66
18
              len, fd_);
67
18
    ping_echo_active_ = false;
68
18
  }
69

            
70
89
  return result;
71
100
}
72

            
73
} // namespace ReverseConnection
74
} // namespace Bootstrap
75
} // namespace Extensions
76
} // namespace Envoy