GCC Code Coverage Report
Directory: ../../../../../../source/ Exec Total Coverage
File: source/extensions/filters/listener/proxy_protocol/proxy_protocol.cc Lines: 144 158 91.1 %
Date: 2018-06-25 Branches: 175 334 52.4 %

Line Branch Exec Source
1
#include "extensions/filters/listener/proxy_protocol/proxy_protocol.h"
2
3
#include <string.h>
4
#include <sys/ioctl.h>
5
#include <unistd.h>
6
7
#include <algorithm>
8
#include <cstdint>
9
#include <memory>
10
#include <string>
11
12
#include "envoy/common/exception.h"
13
#include "envoy/event/dispatcher.h"
14
#include "envoy/network/listen_socket.h"
15
#include "envoy/stats/stats.h"
16
17
#include "common/common/assert.h"
18
#include "common/common/empty_string.h"
19
#include "common/common/utility.h"
20
#include "common/network/address_impl.h"
21
#include "common/network/utility.h"
22
23
namespace Envoy {
24
namespace Extensions {
25
namespace ListenerFilters {
26
namespace ProxyProtocol {
27
28

35
Config::Config(Stats::Scope& scope) : stats_{ALL_PROXY_PROTOCOL_STATS(POOL_COUNTER(scope))} {}
29
30
30
Network::FilterStatus Filter::onAccept(Network::ListenerFilterCallbacks& cb) {
31
30
  ENVOY_LOG(debug, "proxy_protocol: New connection accepted");
32
30
  Network::ConnectionSocket& socket = cb.socket();
33
30
  ASSERT(file_event_.get() == nullptr);
34
30
  file_event_ =
35

90
      cb.dispatcher().createFileEvent(socket.fd(),
36
70
                                      [this](uint32_t events) {
37
35
                                        ASSERT(events == Event::FileReadyType::Read);
38
35
                                        onRead();
39
35
                                      },
40
60
                                      Event::FileTriggerType::Edge, Event::FileReadyType::Read);
41
30
  cb_ = &cb;
42
30
  return Network::FilterStatus::StopIteration;
43
}
44
45
35
void Filter::onRead() {
46
  try {
47
35
    onReadWorker();
48
24
  } catch (const EnvoyException& ee) {
49
12
    config_->stats_.downstream_cx_proxy_proto_error_.inc();
50
12
    cb_->continueFilterChain(false);
51
  }
52
35
}
53
54
35
void Filter::onReadWorker() {
55
35
  Network::ConnectionSocket& socket = cb_->socket();
56
63
  PpHeader hdr;
57
35
  hdr.valid = false;
58

35
  if (!readProxyHeader(socket.fd(), hdr)) {
59
7
    return;
60
  }
61
62
20
  if (hdr.valid) {
63
64
    // Error check the source and destination fields. Most errors are caught by the address
65
    // parsing above, but a malformed IPv6 address may combine with a malformed port and parse as
66
    // an IPv6 address when parsing for an IPv4 address. Remote address refers to the source
67
    // address.
68

18
    const auto remote_version = hdr.remote_address->ip()->version();
69

18
    const auto local_version = hdr.local_address->ip()->version();
70

18
    if (remote_version != hdr.protocol_version || local_version != hdr.protocol_version) {
71

2
      throw EnvoyException("failed to read proxy protocol");
72
    }
73
    // Check that both addresses are valid unicast addresses, as required for TCP
74


31
    if (!hdr.remote_address->ip()->isUnicastAddress() ||
75

15
        !hdr.local_address->ip()->isUnicastAddress()) {
76

2
      throw EnvoyException("failed to read proxy protocol");
77
    }
78
79
    // Only set the local address if it really changed, and mark it as address being restored.
80

14
    if (*hdr.local_address != *socket.localAddress()) {
81
14
      socket.setLocalAddress(hdr.local_address, true);
82
    }
83
14
    socket.setRemoteAddress(hdr.remote_address);
84
  }
85
86
  // Release the file event so that we do not interfere with the connection read events.
87
16
  file_event_.reset();
88

16
  cb_->continueFilterChain(true);
89
}
90
91
//
92
// Skip the first 12-bytes
93
// Next is ver/cmd
94
5
void Filter::parseV2Header(char* buf, size_t len, PpHeader& hdr) {
95
5
  int ver_cmd = buf[PP2_SIGNATURE_LEN];
96
5
  if (((ver_cmd & 0xf0) >> 4) != PP2_VERSION)
97
    throw EnvoyException("Unsupported V2 proxy protocol version");
98
  // Only do connections on behalf of another user, not
99
  // internally-driven health-checks. If its not on behalf
100
  // of someone, or its not AF_INET{6} / STREAM/DGRAM, ignore
101
  // and use the real-remote info
102
5
  if ((ver_cmd & 0xf) == PP2_ONBEHALF_OF) {
103
5
    uint8_t proto_family = buf[PP2_SIGNATURE_LEN + 1];
104

7
    if ((((proto_family & 0xf0) >> 4) == PP2_AF_INET ||
105
7
         ((proto_family & 0xf0) >> 4) == PP2_AF_INET6) &&
106
7
        (((proto_family & 0x0f) == PP2_TP_STREAM) || ((proto_family & 0x0f) == PP2_TP_DGRAM))) {
107
5
      hdr.valid = true;
108
5
      if (((proto_family & 0xf0) >> 4) == PP2_AF_INET) {
109
        typedef struct {
110
          uint32_t src_addr;
111
          uint32_t dst_addr;
112
          uint16_t src_port;
113
          uint16_t dst_port;
114
        } pp_ipv4_addr;
115
        pp_ipv4_addr* v4;
116
3
        if (len < PP2_HEADER_LEN + sizeof(pp_ipv4_addr))
117
          throw EnvoyException("Unsupported V2 proxy protocol inet4 length");
118
3
        hdr.protocol_version = Network::Address::IpVersion::v4;
119
3
        v4 = reinterpret_cast<pp_ipv4_addr*>(&buf[PP2_HEADER_LEN]);
120
        sockaddr_in sa4;
121
3
        sa4.sin_family = AF_INET;
122
3
        sa4.sin_port = (v4->src_port);
123
3
        sa4.sin_addr.s_addr = (v4->src_addr);
124
3
        hdr.remote_address = std::make_shared<Network::Address::Ipv4Instance>(&sa4);
125
126
3
        sa4.sin_port = (v4->dst_port);
127
3
        sa4.sin_addr.s_addr = (v4->dst_addr);
128
3
        hdr.local_address = std::make_shared<Network::Address::Ipv4Instance>(&sa4);
129
2
      } else if (((proto_family & 0xf0) >> 4) == PP2_AF_INET6) {
130
        typedef struct {
131
          uint8_t src_addr[16];
132
          uint8_t dst_addr[16];
133
          uint16_t src_port;
134
          uint16_t dst_port;
135
        } pp_ipv6_addr;
136
        pp_ipv6_addr* v6;
137
2
        if (len < PP2_HEADER_LEN + sizeof(pp_ipv6_addr))
138
          throw EnvoyException("Unsupported V2 proxy protocol inet6 length");
139
2
        hdr.protocol_version = Network::Address::IpVersion::v6;
140
2
        v6 = reinterpret_cast<pp_ipv6_addr*>(&buf[PP2_HEADER_LEN]);
141
        sockaddr_in6 sa6;
142
2
        sa6.sin6_family = AF_INET;
143
2
        sa6.sin6_port = (v6->src_port);
144
2
        memcpy(sa6.sin6_addr.s6_addr, v6->src_addr, sizeof(sa6.sin6_addr.s6_addr));
145
2
        hdr.remote_address = std::make_shared<Network::Address::Ipv6Instance>(sa6);
146
147
2
        sa6.sin6_port = (v6->dst_port);
148
2
        memcpy(sa6.sin6_addr.s6_addr, v6->dst_addr, sizeof(sa6.sin6_addr.s6_addr));
149
2
        hdr.local_address = std::make_shared<Network::Address::Ipv6Instance>(sa6);
150
      }
151
    }
152
  }
153
5
}
154
155
22
void Filter::parseV1Header(char* buf, size_t len, PpHeader& hdr) {
156
44
  std::string proxy_line;
157
22
  proxy_line.assign(buf, len);
158
22
  const auto trimmed_proxy_line = StringUtil::rtrim(proxy_line);
159
160
  // Parse proxy protocol line with format: PROXY TCP4/TCP6/UNKNOWN SOURCE_ADDRESS
161
  // DESTINATION_ADDRESS SOURCE_PORT DESTINATION_PORT.
162

44
  const auto line_parts = StringUtil::splitToken(trimmed_proxy_line, " ", true);
163



22
  if (line_parts.size() < 2 || line_parts[0] != "PROXY") {
164

1
    throw EnvoyException("failed to read proxy protocol");
165
  }
166
167
  // If the line starts with UNKNOWN we know it's a proxy protocol line, so we can remove it from
168
  // the socket and continue. According to spec "real connection's parameters" should be used, so
169
  // we should NOT restore the addresses in this case.
170

21
  if (line_parts[1] != "UNKNOWN") {
171
    // If protocol not UNKNOWN, src and dst addresses have to be present.
172
19
    if (line_parts.size() != 6) {
173

1
      throw EnvoyException("failed to read proxy protocol");
174
    }
175
18
    hdr.valid = true;
176
177
    // TODO(gsagula): parseInternetAddressAndPort() could be modified to take two string_view
178
    // arguments, so we can eliminate allocation here.
179

18
    if (line_parts[1] == "TCP4") {
180
10
      hdr.protocol_version = Network::Address::IpVersion::v4;
181
20
      hdr.remote_address = Network::Utility::parseInternetAddressAndPort(
182


30
          std::string{line_parts[2]} + ":" + std::string{line_parts[4]});
183
20
      hdr.local_address = Network::Utility::parseInternetAddressAndPort(
184


30
          std::string{line_parts[3]} + ":" + std::string{line_parts[5]});
185

8
    } else if (line_parts[1] == "TCP6") {
186
7
      hdr.protocol_version = Network::Address::IpVersion::v6;
187
11
      hdr.remote_address = Network::Utility::parseInternetAddressAndPort(
188


18
          "[" + std::string{line_parts[2]} + "]:" + std::string{line_parts[4]});
189
7
      hdr.local_address = Network::Utility::parseInternetAddressAndPort(
190


11
          "[" + std::string{line_parts[3]} + "]:" + std::string{line_parts[5]});
191
    } else {
192

1
      throw EnvoyException("failed to read proxy protocol");
193
    }
194
  }
195
15
}
196
197
40
bool Filter::readProxyHeader(int fd, PpHeader& hdr) {
198
45
  while (buf_off_ < MAX_PROXY_PROTO_LEN) {
199
    int bytes_avail;
200
201
40
    if (ioctl(fd, FIONREAD, &bytes_avail) < 0)
202
      throw EnvoyException("failed to read proxy protocol (no bytes avail)");
203
204


40
    ENVOY_LOG(trace, "readProxyHeader {} avail", bytes_avail);
205
40
    if (bytes_avail == 0) {
206
34
      return false;
207
    }
208
209
33
    bytes_avail = std::min(size_t(bytes_avail), MAX_PROXY_PROTO_LEN - buf_off_);
210
211
33
    ssize_t nread = recv(fd, buf_ + buf_off_, bytes_avail, MSG_PEEK);
212


33
    ENVOY_LOG(trace, "readProxyHeader {} peeked", nread);
213
214

33
    if (nread == -1 && errno == EAGAIN) {
215
      return false;
216
33
    } else if (nread < 1) {
217
      throw EnvoyException("failed to read proxy protocol (no bytes read)");
218
    }
219
220
33
    if (buf_off_ + nread >= PP2_HEADER_LEN) {
221
29
      const char* sig = PP2_SIGNATURE;
222
29
      if (!memcmp(buf_, sig, PP2_SIGNATURE_LEN)) {
223


5
        ENVOY_LOG(trace, "readProxyHeader header_version=2");
224
5
        header_version_ = 2;
225
24
      } else if (memcmp(buf_, PP1_SIGNATURE, PP1_SIGNATURE_LEN)) {
226
        // If its not v2, and can't be v1
227
        // no sense hanging around, its invalid
228


1
        ENVOY_LOG(trace, "readProxyHeader ???? not PROXY.... for v1");
229

1
        throw EnvoyException("failed to read proxy protocol (exceed max v1 header len)");
230
      }
231
    }
232
233
32
    if (header_version_ == 2) {
234
5
      int ver_cmd = buf_[PP2_SIGNATURE_LEN];
235
5
      if (((ver_cmd & 0xf0) >> 4) != PP2_VERSION)
236
        throw EnvoyException("Unsupported V2 proxy protocol version");
237
5
      ssize_t addr_len = (buf_[PP2_HEADER_LEN - 2] << 8) + (buf_[PP2_HEADER_LEN - 1] << 0);
238
5
      if (addr_len > PP2_ADDR_LEN_UNIX)
239
        throw EnvoyException("Unsupported V2 proxy protocol length");
240
241
5
      if (ssize_t(buf_off_) + nread >= addr_len + PP2_HEADER_LEN) {
242
5
        ssize_t exp = (addr_len + PP2_HEADER_LEN) - buf_off_;
243
5
        nread = recv(fd, buf_ + buf_off_, exp, 0);
244
5
        if (nread != exp)
245
          throw EnvoyException("failed to read proxy protocol (insufficient data)");
246
247
5
        parseV2Header(buf_, addr_len + PP2_HEADER_LEN, hdr);
248
5
        return true;
249
      } else {
250
        nread = recv(fd, buf_ + buf_off_, bytes_avail, 0);
251
        ENVOY_LOG(trace, "readProxyHeader(v2) popped {} from pipe", nread);
252
        ASSERT(nread == bytes_avail);
253
254
        buf_off_ += nread;
255
      }
256
    } else {
257
      // continue searching buf_ from where we left off
258
1751
      for (; search_index_ < buf_off_ + nread; search_index_++) {
259

885
        if (buf_[search_index_] == '\n' && buf_[search_index_ - 1] == '\r') {
260
23
          if (search_index_ == 1) {
261
            // This could be the binary protocol. It cannot be the ascii protocol
262
1
            header_version_ = -2;
263
          } else {
264
22
            header_version_ = 1;
265
22
            search_index_++;
266
          }
267
23
          break;
268
        }
269
      }
270
271
      // If we bailed on the first char, we might be v2, but are for
272
      // sure not v1. Thus we can read up to min(PP2_HEADER_LEN, bytes_avail)
273
      // If we bailed after first char, but before we hit \r\n, read
274
      // up to search_index_.
275
      // We're asking only for bytes we've already seen so there should
276
      // be no block or fail
277
278
      size_t ntoread;
279
27
      if (header_version_ == -2) {
280
1
        ntoread = bytes_avail;
281
      } else {
282
26
        ntoread = search_index_ - buf_off_;
283
      }
284
285
27
      nread = recv(fd, buf_ + buf_off_, ntoread, 0);
286


27
      ENVOY_LOG(trace, "readProxyHeader(v1) popped {} from pipe", nread);
287
27
      ASSERT(size_t(nread) == ntoread);
288
289
27
      buf_off_ += nread;
290
291
27
      if (header_version_ == 1) {
292
22
        parseV1Header(buf_, buf_off_, hdr);
293
15
        return true;
294
      }
295
    }
296
  }
297
298
  throw EnvoyException("failed to read proxy protocol (exceed max v2 header len)");
299
}
300
301
} // namespace ProxyProtocol
302
} // namespace ListenerFilters
303
} // namespace Extensions
304
51
} // namespace Envoy