diff --git a/doc/environment_variables.md b/doc/environment_variables.md index 8dafefa8..1259332e 100644 --- a/doc/environment_variables.md +++ b/doc/environment_variables.md @@ -14,6 +14,7 @@ can be set. 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 that has a value is used. + The `no_proxy` environment variable could be a comma-separated list of hostnames, IP addresses, or CIDR blocks (no_proxy=example.com,192.168.0.1,192.168.0.0/16). * GRPC_SSL_CIPHER_SUITES A colon separated list of cipher suites to use with OpenSSL @@ -66,4 +67,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 88d621bd..a38e546b 100644 --- a/packages/grpc-js/src/http_proxy.ts +++ b/packages/grpc-js/src/http_proxy.ts @@ -119,6 +119,56 @@ function getNoProxyHostList(): string[] { } } +interface CIDRNotation { + ip: number; + prefixLength: number; +} + +/* + * The groups correspond to CIDR parts as follows: + * 1. ip + * 2. prefixLength + */ +const CIDR_REGEX = /^([0-9.]+)(?:\/([0-9]+))?$/; + +export function parseCIDR(cidrString: string): CIDRNotation | null { + const parsedCIDR = CIDR_REGEX.exec(cidrString); + if (parsedCIDR && parsedCIDR.length === 3) { + return { + ip: ipToInt(parsedCIDR[1]), + prefixLength: parseInt(parsedCIDR[2] ?? '32', 10), + }; + } + return null; +} + +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); + + const networkAddress = ip & mask; + const broadcastAddress = networkAddress | ~mask; + + return hostIP >= networkAddress && hostIP <= broadcastAddress; +} + +function checkHostInNoProxyHostList(serverHost: string): boolean { + for (const host of getNoProxyHostList()) { + const parsedCIDR = parseCIDR(host); + // host is a single IP address or a CIDR notation or a domain + if (parsedCIDR && isIpInCIDR(parsedCIDR, serverHost) || serverHost.endsWith(host)) { + trace('Not using proxy for target in no_proxy list: ' + serverHost); + return true; + } + } + return false; +} + export interface ProxyMapResult { target: GrpcUri; extraOptions: ChannelOptions; @@ -147,13 +197,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 (checkHostInNoProxyHostList(serverHost)) { + trace('Not using proxy for target in no_proxy list: ' + uriToString(target)); + return noProxyResult; } const extraOptions: ChannelOptions = { 'grpc.http_connect_target': uriToString(target),