1
#include "source/extensions/transport_sockets/http_11_proxy/connect.h"
2

            
3
#include <sstream>
4

            
5
#include "envoy/network/transport_socket.h"
6

            
7
#include "source/common/buffer/buffer_impl.h"
8
#include "source/common/common/scalar_to_byte_vector.h"
9
#include "source/common/common/utility.h"
10
#include "source/common/config/well_known_names.h"
11
#include "source/common/http/header_utility.h"
12
#include "source/common/network/address_impl.h"
13
#include "source/common/runtime/runtime_features.h"
14

            
15
namespace Envoy {
16
namespace Extensions {
17
namespace TransportSockets {
18
namespace Http11Connect {
19

            
20
bool UpstreamHttp11ConnectSocket::isValidConnectResponse(absl::string_view response_payload,
21
                                                         bool& headers_complete,
22
28
                                                         size_t& bytes_processed) {
23
28
  SelfContainedParser parser;
24

            
25
28
  bytes_processed = parser.parser().execute(response_payload.data(), response_payload.length());
26
28
  headers_complete = parser.headersComplete();
27

            
28
28
  return parser.parser().getStatus() != Http::Http1::ParserStatus::Error &&
29
28
         parser.headersComplete() && parser.parser().statusCode() == Http::Code::OK;
30
28
}
31

            
32
UpstreamHttp11ConnectSocket::UpstreamHttp11ConnectSocket(
33
    Network::TransportSocketPtr&& transport_socket,
34
    Network::TransportSocketOptionsConstSharedPtr options,
35
    std::shared_ptr<const Upstream::HostDescription> host,
36
    absl::optional<Network::TransportSocketOptions::Http11ProxyInfo> proxy_info)
37
33
    : PassthroughSocket(std::move(transport_socket)), options_(options) {
38
  // If the filter state metadata has populated the relevant entries in the transport socket
39
  // options, we want to maintain the original behavior of this transport socket.
40
33
  if (options_ && options_->http11ProxyInfo()) {
41
24
    handleProxyInfoConnect(options_->http11ProxyInfo().value());
42
24
    return;
43
24
  }
44

            
45
9
  if (proxy_info.has_value()) {
46
2
    Network::TransportSocketOptions::Http11ProxyInfo actual_info = proxy_info.value();
47
2
    if (actual_info.hostname.empty() && host) {
48
1
      actual_info.hostname = host->hostname().empty() ? host->address()->asStringView()
49
1
                                                      : absl::StrCat(host->hostname(), ":",
50
                                                                     host->address()->ip()->port());
51
1
    }
52
2
    handleProxyInfoConnect(actual_info);
53
2
    return;
54
2
  }
55

            
56
  // The absence of proxy info from the transport socket options means that we should use the host
57
  // address of the provided HostDescription if it has the appropriate metadata set.
58
14
  for (auto& metadata : {host->metadata(), host->localityMetadata()}) {
59
14
    if (metadata == nullptr) {
60
11
      continue;
61
11
    }
62

            
63
3
    const bool has_proxy_addr = metadata->typed_filter_metadata().contains(
64
3
        Config::MetadataFilters::get().ENVOY_HTTP11_PROXY_TRANSPORT_SOCKET_ADDR);
65
3
    if (has_proxy_addr) {
66
3
      handleHostMetadataConnect(host);
67
3
    }
68
3
  }
69
7
}
70

            
71
// Helper method to create a properly formatted CONNECT request with Host header.
72
31
std::string UpstreamHttp11ConnectSocket::formatConnectRequest(absl::string_view target) {
73
31
  return absl::StrCat("CONNECT ", target, " HTTP/1.1\r\n", "Host: ", target, "\r\n\r\n");
74
31
}
75

            
76
inline void UpstreamHttp11ConnectSocket::handleProxyInfoConnect(
77
26
    const Network::TransportSocketOptions::Http11ProxyInfo& proxy_info) {
78
26
  if (transport_socket_->ssl()) {
79
24
    std::string target = absl::StrCat(
80
24
        proxy_info.hostname, Http::HeaderUtility::hostHasPort(proxy_info.hostname) ? "" : ":443");
81

            
82
24
    if (!Runtime::runtimeFeatureEnabled(
83
24
            "envoy.reloadable_features.http_11_proxy_connect_legacy_format")) {
84
      // RFC 9110 compliant CONNECT format that includes Host header.
85
23
      header_buffer_.add(formatConnectRequest(target));
86
23
    } else {
87
      // Legacy behavior: no Host header for backward compatibility.
88
1
      header_buffer_.add(absl::StrCat("CONNECT ", target, " HTTP/1.1\r\n\r\n"));
89
1
    }
90
24
    need_to_strip_connect_response_ = true;
91
24
  }
92
26
}
93

            
94
inline void UpstreamHttp11ConnectSocket::handleHostMetadataConnect(
95
3
    std::shared_ptr<const Upstream::HostDescription> host) {
96
3
  if (!Runtime::runtimeFeatureEnabled(
97
3
          "envoy.reloadable_features.http_11_proxy_connect_legacy_format")) {
98
    // Prefer <host-name>:<port> for RFC 9110 compliance, unless URI is <host-ip>:<port>.
99
2
    std::string target;
100
2
    if (!host->hostname().empty()) {
101
1
      const uint32_t port = host->address()->ip()->port();
102
1
      target = absl::StrCat(host->hostname(), ":", port);
103
1
    } else {
104
1
      target = host->address()->asStringView();
105
1
    }
106
2
    header_buffer_.add(formatConnectRequest(target));
107
2
  } else {
108
    // Legacy behavior: <host-ip>:<port> format, no Host header for backward compatibility.
109
1
    header_buffer_.add(
110
1
        absl::StrCat("CONNECT ", host->address()->asStringView(), " HTTP/1.1\r\n\r\n"));
111
1
  }
112
3
  need_to_strip_connect_response_ = true;
113
3
}
114

            
115
void UpstreamHttp11ConnectSocket::setTransportSocketCallbacks(
116
33
    Network::TransportSocketCallbacks& callbacks) {
117
33
  transport_socket_->setTransportSocketCallbacks(callbacks);
118
33
  callbacks_ = &callbacks;
119
33
}
120

            
121
120
Network::IoResult UpstreamHttp11ConnectSocket::doWrite(Buffer::Instance& buffer, bool end_stream) {
122
120
  if (header_buffer_.length() > 0) {
123
21
    return writeHeader();
124
21
  }
125
99
  if (!need_to_strip_connect_response_) {
126
    // Don't pass events up until the connect response is read because TLS reads
127
    // kick off writes which don't pass through the transport socket.
128
84
    return transport_socket_->doWrite(buffer, end_stream);
129
84
  }
130
15
  return Network::IoResult{Network::PostIoAction::KeepOpen, 0, false};
131
99
}
132

            
133
65
Network::IoResult UpstreamHttp11ConnectSocket::doRead(Buffer::Instance& buffer) {
134
65
  if (need_to_strip_connect_response_) {
135
    // Limit the CONNECT response headers to an arbitrary 2000 bytes.
136
21
    constexpr uint32_t MAX_RESPONSE_HEADER_SIZE = 2000;
137
21
    char peek_buf[MAX_RESPONSE_HEADER_SIZE];
138
21
    Api::IoCallUint64Result result =
139
21
        callbacks_->ioHandle().recv(peek_buf, MAX_RESPONSE_HEADER_SIZE, MSG_PEEK);
140
21
    if (!result.ok() && result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) {
141
1
      return {Network::PostIoAction::Close, 0, false};
142
1
    }
143
20
    absl::string_view peek_data(peek_buf, result.return_value_);
144
20
    size_t bytes_processed = 0;
145
20
    bool headers_complete = false;
146
20
    bool is_valid_connect_response =
147
20
        isValidConnectResponse(peek_data, headers_complete, bytes_processed);
148

            
149
20
    if (!headers_complete) {
150
3
      if (peek_data.size() == MAX_RESPONSE_HEADER_SIZE) {
151
1
        ENVOY_CONN_LOG(trace, "failed to receive CONNECT headers within {} bytes",
152
1
                       callbacks_->connection(), MAX_RESPONSE_HEADER_SIZE);
153
1
        return {Network::PostIoAction::Close, 0, false};
154
1
      }
155
2
      ENVOY_CONN_LOG(trace, "Incomplete CONNECT header: {} bytes received",
156
2
                     callbacks_->connection(), peek_data.size());
157
2
      return Network::IoResult{Network::PostIoAction::KeepOpen, 0, false};
158
3
    }
159
17
    if (!is_valid_connect_response) {
160
1
      ENVOY_CONN_LOG(trace, "Response does not appear to be a successful CONNECT upgrade",
161
1
                     callbacks_->connection());
162
1
      return {Network::PostIoAction::Close, 0, false};
163
1
    }
164

            
165
16
    result = callbacks_->ioHandle().read(buffer, bytes_processed);
166
16
    if (!result.ok() || result.return_value_ != bytes_processed) {
167
2
      ENVOY_CONN_LOG(trace, "failed to drain CONNECT header", callbacks_->connection());
168
2
      return {Network::PostIoAction::Close, 0, false};
169
2
    }
170
14
    buffer.drain(bytes_processed);
171

            
172
14
    ENVOY_CONN_LOG(trace, "Successfully stripped {} bytes of CONNECT header",
173
14
                   callbacks_->connection(), bytes_processed);
174
14
    need_to_strip_connect_response_ = false;
175
14
    callbacks_->flushWriteBuffer();
176
14
  }
177
58
  return transport_socket_->doRead(buffer);
178
65
}
179

            
180
21
Network::IoResult UpstreamHttp11ConnectSocket::writeHeader() {
181
21
  Network::PostIoAction action = Network::PostIoAction::KeepOpen;
182
21
  uint64_t bytes_written = 0;
183
40
  do {
184
40
    if (header_buffer_.length() == 0) {
185
19
      break;
186
19
    }
187

            
188
21
    Api::IoCallUint64Result result = callbacks_->ioHandle().write(header_buffer_);
189

            
190
21
    if (!result.ok()) {
191
2
      ENVOY_CONN_LOG(trace, "Failed writing CONNECT header. write error: {}",
192
2
                     callbacks_->connection(), result.err_->getErrorDetails());
193
2
      if (result.err_->getErrorCode() != Api::IoError::IoErrorCode::Again) {
194
1
        action = Network::PostIoAction::Close;
195
1
      }
196
2
      break;
197
2
    }
198
19
    ENVOY_CONN_LOG(trace, "Writing CONNECT header. write returned: {}", callbacks_->connection(),
199
19
                   result.return_value_);
200
19
    bytes_written += result.return_value_;
201
19
  } while (true);
202

            
203
  return {action, bytes_written, false};
204
21
}
205

            
206
UpstreamHttp11ConnectSocketFactory::UpstreamHttp11ConnectSocketFactory(
207
    Network::UpstreamTransportSocketFactoryPtr transport_socket_factory,
208
    absl::optional<Network::TransportSocketOptions::Http11ProxyInfo> proxy_info)
209
19
    : PassthroughFactory(std::move(transport_socket_factory)), proxy_info_(proxy_info) {}
210

            
211
Network::TransportSocketPtr UpstreamHttp11ConnectSocketFactory::createTransportSocket(
212
    Network::TransportSocketOptionsConstSharedPtr options,
213
14
    std::shared_ptr<const Upstream::HostDescription> host) const {
214
14
  auto inner_socket = transport_socket_factory_->createTransportSocket(options, host);
215
14
  if (inner_socket == nullptr) {
216
1
    return nullptr;
217
1
  }
218
13
  return std::make_unique<UpstreamHttp11ConnectSocket>(std::move(inner_socket), options, host,
219
13
                                                       proxy_info_);
220
14
}
221

            
222
void UpstreamHttp11ConnectSocketFactory::hashKey(
223
17
    std::vector<uint8_t>& key, Network::TransportSocketOptionsConstSharedPtr options) const {
224
17
  PassthroughFactory::hashKey(key, options);
225
17
  if (options && options->http11ProxyInfo().has_value()) {
226
11
    pushScalarToByteVector(
227
11
        StringUtil::CaseInsensitiveHash()(options->http11ProxyInfo()->proxy_address->asString()),
228
11
        key);
229
11
    pushScalarToByteVector(StringUtil::CaseInsensitiveHash()(options->http11ProxyInfo()->hostname),
230
11
                           key);
231
11
  }
232
17
}
233

            
234
} // namespace Http11Connect
235
} // namespace TransportSockets
236
} // namespace Extensions
237
} // namespace Envoy