diff --git a/doc/environment_variables.md b/doc/environment_variables.md index 8dafefa82..04bb6374f 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -11,8 +11,10 @@ can be set. checked in order, and the first one that has a value is used. * no_grpc_proxy, no_proxy - A comma separated list of hostnames to connect to without using a proxy even - if a proxy is set. These variables are checked in order, and the first one + A comma separated list of hostnames, IP addresses, + or CIDR blocks to connect to without using a proxy even + if a proxy is set, for example: no_proxy=example.com,192.168.0.1,192.168.0.0/16. + These variables are checked in order, and the first one that has a value is used. * GRPC_SSL_CIPHER_SUITES @@ -66,4 +68,4 @@ can be set. * GRPC_NODE_USE_ALTERNATIVE_RESOLVER Allows changing dns resolve behavior and parse DNS server authority as described in https://github.com/grpc/grpc/blob/master/doc/naming.md - true - use alternative resolver - - false - use default resolver (default) \ No newline at end of file + - false - use default resolver (default) diff --git a/packages/grpc-js/src/http_proxy.ts b/packages/grpc-js/src/http_proxy.ts index 88d621bd2..c40d207a0 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -17,7 +17,7 @@ import { log } from './logging'; import { LogVerbosity } from './constants'; -import { Socket } from 'net'; +import { isIPv4, Socket } from 'net'; import * as http from 'http'; import * as logging from './logging'; import { @@ -119,6 +119,58 @@ function getNoProxyHostList(): string[] { } } +interface CIDRNotation { + ip: number; + prefixLength: number; +} + +/* + * The groups correspond to CIDR parts as follows: + * 1. ip + * 2. prefixLength + */ + +export function parseCIDR(cidrString: string): CIDRNotation | null { + const splitRange = cidrString.split('/'); + if (splitRange.length !== 2) { + return null; + } + const prefixLength = parseInt(splitRange[1], 10); + if (!isIPv4(splitRange[0]) || Number.isNaN(prefixLength) || prefixLength < 0 || prefixLength > 32) { + return null; + } + return { + ip: ipToInt(splitRange[0]), + prefixLength: prefixLength + }; +} + +function ipToInt(ip: string) { + return ip.split(".").reduce((acc, octet) => (acc << 8) + parseInt(octet, 10), 0); +} + +function isIpInCIDR(cidr: CIDRNotation, serverHost: string) { + const ip = cidr.ip; + const mask = -1 << (32 - cidr.prefixLength); + const hostIP = ipToInt(serverHost); + + return (hostIP & mask) === (ip & mask); +} + +function hostMatchesNoProxyList(serverHost: string): boolean { + for (const host of getNoProxyHostList()) { + const parsedCIDR = parseCIDR(host); + // host is a CIDR and serverHost is an IP address + if (isIPv4(serverHost) && parsedCIDR && isIpInCIDR(parsedCIDR, serverHost)) { + return true; + } else if (serverHost.endsWith(host)) { + // host is a single IP or a domain name suffix + return true; + } + } + return false; +} + export interface ProxyMapResult { target: GrpcUri; extraOptions: ChannelOptions; @@ -147,13 +199,9 @@ export function mapProxyName( return noProxyResult; } const serverHost = hostPort.host; - for (const host of getNoProxyHostList()) { - if (host === serverHost) { - trace( - 'Not using proxy for target in no_proxy list: ' + uriToString(target) - ); - return noProxyResult; - } + if (hostMatchesNoProxyList(serverHost)) { + trace('Not using proxy for target in no_proxy list: ' + uriToString(target)); + return noProxyResult; } const extraOptions: ChannelOptions = { 'grpc.http_connect_target': uriToString(target),