[HTTP Proxy] Support CIDR blocks in `no_proxy` config (#31119)

This commit adds support for using CIDR blocks defined in the `no_proxy`
environment variable. For example:

```
http_proxy=http://localhost:8080 no_proxy=10.10.0.0/24
```

The example above would bypass the proxy if the server IP matched
10.10.0.0 - 10.10.0.255.

Closes #22681

---------

Co-authored-by: Yash Tibrewal <yashkt@google.com>
pull/32764/head
Stan Hu 2 years ago committed by GitHub
parent a5ce9c8947
commit 4110dea333
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      BUILD
  2. 35
      src/core/ext/filters/client_channel/http_proxy.cc
  3. 95
      test/core/client_channel/http_proxy_mapper_test.cc

@ -2886,6 +2886,7 @@ grpc_cc_library(
"http_connect_handshaker",
"iomgr_timer",
"orphanable",
"parse_address",
"protobuf_duration_upb",
"ref_counted_ptr",
"server_address",

@ -20,15 +20,19 @@
#include "src/core/ext/filters/client_channel/http_proxy.h"
#include <stdint.h>
#include <string.h>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/ascii.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"
@ -39,6 +43,8 @@
#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"
@ -52,6 +58,31 @@
namespace grpc_core {
namespace {
bool ServerInCIDRRange(absl::string_view server_host,
absl::string_view no_proxy_entry) {
auto server_address = StringToSockaddr(server_host, 0);
if (!server_address.ok()) {
return false;
}
std::pair<absl::string_view, absl::string_view> possible_cidr =
absl::StrSplit(no_proxy_entry, absl::MaxSplits('/', 2),
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;
}
///
/// 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
@ -168,7 +199,9 @@ absl::optional<std::string> HttpProxyMapper::MapName(
std::vector<absl::string_view> no_proxy_hosts =
absl::StrSplit(*no_proxy_str, ',', absl::SkipEmpty());
for (const auto& no_proxy_entry : no_proxy_hosts) {
if (absl::EndsWithIgnoreCase(server_host, no_proxy_entry)) {
auto entry = absl::StripAsciiWhitespace(no_proxy_entry);
if (absl::EndsWithIgnoreCase(server_host, entry) ||
ServerInCIDRRange(server_host, entry)) {
gpr_log(GPR_INFO, "not using proxy for host in no_proxy list '%s'",
std::string(server_uri).c_str());
use_proxy = false;

@ -67,6 +67,101 @@ TEST(NoProxyTest, EmptyEntries) {
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
}
// Test entries with CIDR blocks (Class A) in 'no_proxy' list.
TEST(NoProxyTest, CIDRClassAEntries) {
ScopedSetEnv no_proxy("foo.com,192.168.0.255/8");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
// address matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.0.1.1:443", &args),
absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
// address not matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///193.0.1.1:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), "193.0.1.1:443");
}
// Test entries with CIDR blocks (Class B) in 'no_proxy' list.
TEST(NoProxyTest, CIDRClassBEntries) {
ScopedSetEnv no_proxy("foo.com,192.168.0.255/16");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
// address matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.1.5:443", &args),
absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
// address not matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.169.1.1:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), "192.169.1.1:443");
}
// Test entries with CIDR blocks (Class C) in 'no_proxy' list.
TEST(NoProxyTest, CIDRClassCEntries) {
ScopedSetEnv no_proxy("foo.com,192.168.0.255/24");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
// address matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.0.5:443", &args),
absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
// address not matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.1.1:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), "192.168.1.1:443");
}
// Test entries with CIDR blocks (exact match) in 'no_proxy' list.
TEST(NoProxyTest, CIDREntriesExactMatch) {
ScopedSetEnv no_proxy("foo.com,192.168.0.4/32");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
// address matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.0.4:443", &args),
absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
// address not matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.0.5:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), "192.168.0.5:443");
}
// Test entries with IPv6 CIDR blocks in 'no_proxy' list.
TEST(NoProxyTest, CIDREntriesIPv6ExactMatch) {
ScopedSetEnv no_proxy("foo.com,2002:db8:a::45/64");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
// address matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName(
"dns:///[2002:0db8:000a:0000:0000:0000:0000:0001]:443", &args),
absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
// address not matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName(
"dns:///[2003:0db8:000a:0000:0000:0000:0000:0000]:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER),
"[2003:0db8:000a:0000:0000:0000:0000:0000]:443");
}
// Test entries with whitespaced CIDR blocks in 'no_proxy' list.
TEST(NoProxyTest, WhitespacedEntries) {
ScopedSetEnv no_proxy("foo.com, 192.168.0.255/24");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
// address matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.0.5:443", &args),
absl::nullopt);
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), absl::nullopt);
// address not matching no_proxy cidr block
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.1.0:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), "192.168.1.0:443");
}
// Test entries with invalid CIDR blocks in 'no_proxy' list.
TEST(NoProxyTest, InvalidCIDREntries) {
ScopedSetEnv no_proxy("foo.com, 192.168.0.255/33");
auto args = ChannelArgs().Set(GRPC_ARG_HTTP_PROXY, "http://proxy.google.com");
EXPECT_EQ(HttpProxyMapper().MapName("dns:///192.168.1.0:443", &args),
"proxy.google.com");
EXPECT_EQ(args.GetString(GRPC_ARG_HTTP_CONNECT_SERVER), "192.168.1.0:443");
}
} // namespace
} // namespace testing
} // namespace grpc_core

Loading…
Cancel
Save