blob: a4ebf216404308aa6113687e22d41ea7118b8b25 [file] [log] [blame]
//
//
// Copyright 2016 gRPC authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
//
#include <grpc/support/port_platform.h>
#include "src/core/client_channel/http_proxy_mapper.h"
#include <stdint.h>
#include <string.h>
#include <memory>
#include <string>
#include <utility>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.h"
#include "absl/strings/escaping.h"
#include "absl/strings/match.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/types/optional.h"
#include <grpc/impl/channel_arg_names.h>
#include <grpc/support/alloc.h>
#include <grpc/support/log.h>
#include "src/core/lib/address_utils/parse_address.h"
#include "src/core/lib/address_utils/sockaddr_utils.h"
#include "src/core/lib/channel/channel_args.h"
#include "src/core/lib/gpr/string.h"
#include "src/core/lib/gprpp/env.h"
#include "src/core/lib/gprpp/host_port.h"
#include "src/core/lib/gprpp/memory.h"
#include "src/core/lib/iomgr/resolve_address.h"
#include "src/core/lib/transport/http_connect_handshaker.h"
#include "src/core/lib/uri/uri_parser.h"
namespace grpc_core {
namespace {
bool ServerInCIDRRange(const grpc_resolved_address& server_address,
absl::string_view cidr_range) {
std::pair<absl::string_view, absl::string_view> possible_cidr =
absl::StrSplit(cidr_range, absl::MaxSplits('/', 1), absl::SkipEmpty());
if (possible_cidr.first.empty() || possible_cidr.second.empty()) {
return false;
}
auto proxy_address = StringToSockaddr(possible_cidr.first, 0);
if (!proxy_address.ok()) {
return false;
}
uint32_t mask_bits = 0;
if (absl::SimpleAtoi(possible_cidr.second, &mask_bits)) {
grpc_sockaddr_mask_bits(&*proxy_address, mask_bits);
return grpc_sockaddr_match_subnet(&server_address, &*proxy_address,
mask_bits);
}
return false;
}
bool ExactMatchOrSubdomain(absl::string_view host_name,
absl::string_view host_name_or_domain) {
return absl::EndsWithIgnoreCase(host_name, host_name_or_domain);
}
// Parses the list of host names, addresses or subnet masks and returns true if
// the target address or host matches any value.
bool AddressIncluded(
const absl::optional<grpc_resolved_address>& target_address,
absl::string_view host_name, absl::string_view addresses_and_subnets) {
for (absl::string_view entry :
absl::StrSplit(addresses_and_subnets, ',', absl::SkipEmpty())) {
absl::string_view sanitized_entry = absl::StripAsciiWhitespace(entry);
if (ExactMatchOrSubdomain(host_name, sanitized_entry) ||
(target_address.has_value() &&
ServerInCIDRRange(*target_address, sanitized_entry))) {
return true;
}
}
return false;
}
///
/// Parses the 'https_proxy' env var (fallback on 'http_proxy') and returns the
/// proxy hostname to resolve or nullopt on error. Also sets 'user_cred' to user
/// credentials if present in the 'http_proxy' env var, otherwise leaves it
/// unchanged.
///
absl::optional<std::string> GetHttpProxyServer(
const ChannelArgs& args, absl::optional<std::string>* user_cred) {
GPR_ASSERT(user_cred != nullptr);
absl::StatusOr<URI> uri;
// We check the following places to determine the HTTP proxy to use, stopping
// at the first one that is set:
// 1. GRPC_ARG_HTTP_PROXY channel arg
// 2. grpc_proxy environment variable
// 3. https_proxy environment variable
// 4. http_proxy environment variable
// If none of the above are set, then no HTTP proxy will be used.
//
absl::optional<std::string> uri_str =
args.GetOwnedString(GRPC_ARG_HTTP_PROXY);
if (!uri_str.has_value()) uri_str = GetEnv("grpc_proxy");
if (!uri_str.has_value()) uri_str = GetEnv("https_proxy");
if (!uri_str.has_value()) uri_str = GetEnv("http_proxy");
if (!uri_str.has_value()) return absl::nullopt;
// an empty value means "don't use proxy"
if (uri_str->empty()) return absl::nullopt;
uri = URI::Parse(*uri_str);
if (!uri.ok() || uri->authority().empty()) {
gpr_log(GPR_ERROR, "cannot parse value of 'http_proxy' env var. Error: %s",
uri.status().ToString().c_str());
return absl::nullopt;
}
if (uri->scheme() != "http") {
gpr_log(GPR_ERROR, "'%s' scheme not supported in proxy URI",
uri->scheme().c_str());
return absl::nullopt;
}
// Split on '@' to separate user credentials from host
char** authority_strs = nullptr;
size_t authority_nstrs;
gpr_string_split(uri->authority().c_str(), "@", &authority_strs,
&authority_nstrs);
GPR_ASSERT(authority_nstrs != 0); // should have at least 1 string
absl::optional<std::string> proxy_name;
if (authority_nstrs == 1) {
// User cred not present in authority
proxy_name = authority_strs[0];
} else if (authority_nstrs == 2) {
// User cred found
*user_cred = authority_strs[0];
proxy_name = authority_strs[1];
gpr_log(GPR_DEBUG, "userinfo found in proxy URI");
} else {
// Bad authority
proxy_name = absl::nullopt;
}
for (size_t i = 0; i < authority_nstrs; i++) {
gpr_free(authority_strs[i]);
}
gpr_free(authority_strs);
return proxy_name;
}
// Adds the default port if target does not contain a port.
std::string MaybeAddDefaultPort(absl::string_view target) {
absl::string_view host;
absl::string_view port;
SplitHostPort(target, &host, &port);
if (port.empty()) {
return JoinHostPort(host, kDefaultSecurePortInt);
}
return std::string(target);
}
absl::optional<std::string> GetChannelArgOrEnvVarValue(
const ChannelArgs& args, absl::string_view channel_arg,
const char* env_var) {
auto arg_value = args.GetOwnedString(channel_arg);
if (arg_value.has_value()) {
return arg_value;
}
return GetEnv(env_var);
}
absl::optional<grpc_resolved_address> GetAddressProxyServer(
const ChannelArgs& args) {
auto address_value = GetChannelArgOrEnvVarValue(
args, GRPC_ARG_ADDRESS_HTTP_PROXY, HttpProxyMapper::kAddressProxyEnvVar);
if (!address_value.has_value()) {
return absl::nullopt;
}
auto address = StringToSockaddr(*address_value);
if (!address.ok()) {
gpr_log(GPR_ERROR, "cannot parse value of '%s' env var. Error: %s",
HttpProxyMapper::kAddressProxyEnvVar,
address.status().ToString().c_str());
return absl::nullopt;
}
return *address;
}
} // namespace
absl::optional<std::string> HttpProxyMapper::MapName(
absl::string_view server_uri, ChannelArgs* args) {
if (!args->GetBool(GRPC_ARG_ENABLE_HTTP_PROXY).value_or(true)) {
return absl::nullopt;
}
absl::optional<std::string> user_cred;
auto name_to_resolve = GetHttpProxyServer(*args, &user_cred);
if (!name_to_resolve.has_value()) return name_to_resolve;
absl::StatusOr<URI> uri = URI::Parse(server_uri);
if (!uri.ok() || uri->path().empty()) {
gpr_log(GPR_ERROR,
"'http_proxy' environment variable set, but cannot "
"parse server URI '%s' -- not using proxy. Error: %s",
std::string(server_uri).c_str(), uri.status().ToString().c_str());
return absl::nullopt;
}
if (uri->scheme() == "unix") {
gpr_log(GPR_INFO, "not using proxy for Unix domain socket '%s'",
std::string(server_uri).c_str());
return absl::nullopt;
}
if (uri->scheme() == "vsock") {
gpr_log(GPR_INFO, "not using proxy for VSock '%s'",
std::string(server_uri).c_str());
return absl::nullopt;
}
// Prefer using 'no_grpc_proxy'. Fallback on 'no_proxy' if it is not set.
auto no_proxy_str = GetEnv("no_grpc_proxy");
if (!no_proxy_str.has_value()) {
no_proxy_str = GetEnv("no_proxy");
}
if (no_proxy_str.has_value()) {
std::string server_host;
std::string server_port;
if (!SplitHostPort(absl::StripPrefix(uri->path(), "/"), &server_host,
&server_port)) {
gpr_log(GPR_INFO,
"unable to split host and port, not checking no_proxy list for "
"host '%s'",
std::string(server_uri).c_str());
} else {
auto address = StringToSockaddr(server_host, 0);
if (AddressIncluded(address.ok()
? absl::optional<grpc_resolved_address>(*address)
: absl::nullopt,
server_host, *no_proxy_str)) {
gpr_log(GPR_INFO, "not using proxy for host in no_proxy list '%s'",
std::string(server_uri).c_str());
return absl::nullopt;
}
}
}
*args = args->Set(GRPC_ARG_HTTP_CONNECT_SERVER,
MaybeAddDefaultPort(absl::StripPrefix(uri->path(), "/")));
if (user_cred.has_value()) {
// Use base64 encoding for user credentials as stated in RFC 7617
std::string encoded_user_cred = absl::Base64Escape(*user_cred);
*args = args->Set(
GRPC_ARG_HTTP_CONNECT_HEADERS,
absl::StrCat("Proxy-Authorization:Basic ", encoded_user_cred));
}
return name_to_resolve;
}
absl::optional<grpc_resolved_address> HttpProxyMapper::MapAddress(
const grpc_resolved_address& address, ChannelArgs* args) {
auto proxy_address = GetAddressProxyServer(*args);
if (!proxy_address.has_value()) {
return absl::nullopt;
}
auto address_string = grpc_sockaddr_to_string(&address, true);
if (!address_string.ok()) {
gpr_log(GPR_ERROR, "Unable to convert address to string: %s",
std::string(address_string.status().message()).c_str());
return absl::nullopt;
}
std::string host_name, port;
if (!SplitHostPort(*address_string, &host_name, &port)) {
gpr_log(GPR_ERROR, "Address %s cannot be split in host and port",
address_string->c_str());
return absl::nullopt;
}
auto enabled_addresses = GetChannelArgOrEnvVarValue(
*args, GRPC_ARG_ADDRESS_HTTP_PROXY_ENABLED_ADDRESSES,
kAddressProxyEnabledAddressesEnvVar);
if (!enabled_addresses.has_value() ||
!AddressIncluded(address, host_name, *enabled_addresses)) {
return absl::nullopt;
}
*args = args->Set(GRPC_ARG_HTTP_CONNECT_SERVER, *address_string);
return proxy_address;
}
void RegisterHttpProxyMapper(CoreConfiguration::Builder* builder) {
builder->proxy_mapper_registry()->Register(
true /* at_start */,
std::unique_ptr<ProxyMapperInterface>(new HttpProxyMapper()));
}
} // namespace grpc_core