1
#if defined(__linux__)
2

            
3
// `struct tcp_info` is defined in two places: /usr/include/netinet/tcp.h (included from
4
// envoy/common/platform.h) and /usr/include/linux/tcp.h. The former version is older and doesn't
5
// contain all the fields needed. Including both headers results in a compilation error due to the
6
// duplicate (and different) definitions of `struct tcp_info`. To work around this, define
7
// `DO_NOT_INCLUDE_NETINET_TCP_H` to prevent inclusion of the wrong version.
8
#define DO_NOT_INCLUDE_NETINET_TCP_H 1
9

            
10
#include "source/extensions/transport_sockets/tcp_stats/tcp_stats.h"
11

            
12
#include <linux/tcp.h>
13

            
14
#include "envoy/buffer/buffer.h"
15
#include "envoy/network/connection.h"
16

            
17
#include "source/common/common/assert.h"
18
#include "source/common/protobuf/utility.h"
19

            
20
namespace Envoy {
21
namespace Extensions {
22
namespace TransportSockets {
23
namespace TcpStats {
24

            
25
Config::Config(const envoy::extensions::transport_sockets::tcp_stats::v3::Config& config_proto,
26
               Stats::Scope& scope)
27
8
    : stats_(generateStats(scope)),
28
8
      update_period_(PROTOBUF_GET_OPTIONAL_MS(config_proto, update_period)) {}
29

            
30
8
TcpStats Config::generateStats(Stats::Scope& scope) {
31
8
  const std::string prefix("tcp_stats");
32
8
  return TcpStats{ALL_TCP_STATS(POOL_COUNTER_PREFIX(scope, prefix),
33
8
                                POOL_GAUGE_PREFIX(scope, prefix),
34
8
                                POOL_HISTOGRAM_PREFIX(scope, prefix))};
35
8
}
36

            
37
TcpStatsSocket::TcpStatsSocket(ConfigConstSharedPtr config,
38
                               Network::TransportSocketPtr inner_socket)
39
7
    : PassthroughSocket(std::move(inner_socket)), config_(std::move(config)) {}
40

            
41
7
void TcpStatsSocket::setTransportSocketCallbacks(Network::TransportSocketCallbacks& callbacks) {
42
7
  callbacks_ = &callbacks;
43
7
  transport_socket_->setTransportSocketCallbacks(callbacks);
44
7
}
45

            
46
7
void TcpStatsSocket::onConnected() {
47
7
  if (config_->update_period_.has_value()) {
48
6
    timer_ = callbacks_->connection().dispatcher().createTimer([this]() {
49
6
      recordStats();
50
6
      timer_->enableTimer(config_->update_period_.value());
51
6
    });
52
4
    timer_->enableTimer(config_->update_period_.value());
53
4
  }
54

            
55
7
  transport_socket_->onConnected();
56
7
}
57

            
58
4
void TcpStatsSocket::closeSocket(Network::ConnectionEvent event) {
59
  // Record final values.
60
4
  recordStats();
61

            
62
  // Ensure gauges are zero'd out at the end of a connection no matter what the OS told us.
63
4
  if (last_cx_tx_unsent_bytes_ > 0) {
64
2
    config_->stats_.cx_tx_unsent_bytes_.sub(last_cx_tx_unsent_bytes_);
65
2
  }
66
4
  if (last_cx_tx_unacked_segments_ > 0) {
67
1
    config_->stats_.cx_tx_unacked_segments_.sub(last_cx_tx_unacked_segments_);
68
1
  }
69

            
70
4
  if (timer_ != nullptr) {
71
1
    timer_->disableTimer();
72
1
  }
73

            
74
4
  transport_socket_->closeSocket(event);
75
4
}
76

            
77
10
absl::optional<struct tcp_info> TcpStatsSocket::querySocketInfo() {
78
10
  struct tcp_info info;
79
10
  memset(&info, 0, sizeof(info));
80
10
  socklen_t optlen = sizeof(info);
81
10
  const auto result = callbacks_->ioHandle().getOption(IPPROTO_TCP, TCP_INFO, &info, &optlen);
82
10
  if (result.return_value_ != 0) {
83
1
    ENVOY_LOG(debug, "Failed getsockopt(IPPROTO_TCP, TCP_INFO): rc {} errno {} optlen {}",
84
1
              result.return_value_, result.errno_, optlen);
85
1
    return absl::nullopt;
86
9
  } else {
87
9
    return info;
88
9
  }
89
10
}
90

            
91
10
void TcpStatsSocket::recordStats() {
92
10
  absl::optional<struct tcp_info> tcp_info = querySocketInfo();
93
10
  if (!tcp_info.has_value()) {
94
1
    return;
95
1
  }
96

            
97
63
  auto update_counter = [](Stats::Counter& counter, auto& last_value, auto current_value) {
98
63
    int64_t diff = static_cast<int64_t>(current_value) - static_cast<int64_t>(last_value);
99
63
    ASSERT(diff >= 0);
100
63
    if (diff > 0) {
101
28
      counter.add(diff);
102
28
    }
103
63
    last_value = current_value;
104
63
  };
105

            
106
18
  auto update_gauge = [](Stats::Gauge& gauge, auto& last_value, auto current_value) {
107
18
    static_assert(sizeof(last_value) == sizeof(current_value));
108
18
    int64_t diff = static_cast<int64_t>(current_value) - static_cast<int64_t>(last_value);
109
18
    gauge.add(diff);
110
18
    last_value = current_value;
111
18
  };
112

            
113
  // This is before the update to `cx_tx_data_segments_` and `cx_tx_retransmitted_segments_` because
114
  // they use the same metrics, and `update_counter` will update `last_...`, so this needs to use
115
  // those `last_...` values (and not update them) first.
116
  //
117
  // Don't record a value if the numerator is negative, or the denominator is zero or negative
118
  // (prevent divide-by-zero).
119
9
  if ((tcp_info->tcpi_data_segs_out > last_cx_tx_data_segments_) &&
120
9
      (tcp_info->tcpi_total_retrans >= last_cx_tx_retransmitted_segments_)) {
121
    // uint32 * uint32 cannot overflow a uint64, so this can safely be done as integer math
122
    // instead of floating point.
123
5
    static_assert((sizeof(tcp_info->tcpi_total_retrans) == sizeof(uint32_t)) &&
124
5
                  (Stats::Histogram::PercentScale < UINT32_MAX));
125

            
126
5
    const uint32_t data_segs_out_diff = tcp_info->tcpi_data_segs_out - last_cx_tx_data_segments_;
127
5
    const uint32_t retransmitted_segs_diff =
128
5
        tcp_info->tcpi_total_retrans - last_cx_tx_retransmitted_segments_;
129
5
    const uint64_t percent_retransmissions =
130
5
        (static_cast<uint64_t>(retransmitted_segs_diff) *
131
5
         static_cast<uint64_t>(Stats::Histogram::PercentScale)) /
132
5
        static_cast<uint64_t>(data_segs_out_diff);
133
5
    config_->stats_.cx_tx_percent_retransmitted_segments_.recordValue(percent_retransmissions);
134
5
  }
135

            
136
9
  update_counter(config_->stats_.cx_tx_segments_, last_cx_tx_segments_, tcp_info->tcpi_segs_out);
137
9
  update_counter(config_->stats_.cx_rx_segments_, last_cx_rx_segments_, tcp_info->tcpi_segs_in);
138
9
  update_counter(config_->stats_.cx_tx_data_segments_, last_cx_tx_data_segments_,
139
9
                 tcp_info->tcpi_data_segs_out);
140
9
  update_counter(config_->stats_.cx_rx_data_segments_, last_cx_rx_data_segments_,
141
9
                 tcp_info->tcpi_data_segs_in);
142
9
  update_counter(config_->stats_.cx_tx_retransmitted_segments_, last_cx_tx_retransmitted_segments_,
143
9
                 tcp_info->tcpi_total_retrans);
144
9
  update_counter(config_->stats_.cx_rx_bytes_received_, last_cx_rx_bytes_received_,
145
9
                 tcp_info->tcpi_bytes_received);
146
9
  update_counter(config_->stats_.cx_tx_bytes_sent_, last_cx_tx_bytes_sent_,
147
9
                 tcp_info->tcpi_bytes_sent);
148

            
149
9
  update_gauge(config_->stats_.cx_tx_unsent_bytes_, last_cx_tx_unsent_bytes_,
150
9
               tcp_info->tcpi_notsent_bytes);
151
9
  update_gauge(config_->stats_.cx_tx_unacked_segments_, last_cx_tx_unacked_segments_,
152
9
               tcp_info->tcpi_unacked);
153

            
154
9
  config_->stats_.cx_rtt_us_.recordValue(tcp_info->tcpi_rtt);
155
9
  config_->stats_.cx_rtt_variance_us_.recordValue(tcp_info->tcpi_rttvar);
156
9
}
157

            
158
} // namespace TcpStats
159
} // namespace TransportSockets
160
} // namespace Extensions
161
} // namespace Envoy
162
#endif // defined(__linux__)