1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | import { isIPv4, isIPv6 } from 'net';
|
18 | import { StatusObject } from './call-interface';
|
19 | import { ChannelOptions } from './channel-options';
|
20 | import { LogVerbosity, Status } from './constants';
|
21 | import { Metadata } from './metadata';
|
22 | import { registerResolver, Resolver, ResolverListener } from './resolver';
|
23 | import { Endpoint, SubchannelAddress } from './subchannel-address';
|
24 | import { GrpcUri, splitHostPort, uriToString } from './uri-parser';
|
25 | import * as logging from './logging';
|
26 |
|
27 | const TRACER_NAME = 'ip_resolver';
|
28 |
|
29 | function trace(text: string): void {
|
30 | logging.trace(LogVerbosity.DEBUG, TRACER_NAME, text);
|
31 | }
|
32 |
|
33 | const IPV4_SCHEME = 'ipv4';
|
34 | const IPV6_SCHEME = 'ipv6';
|
35 |
|
36 |
|
37 |
|
38 |
|
39 | const DEFAULT_PORT = 443;
|
40 |
|
41 | class IpResolver implements Resolver {
|
42 | private endpoints: Endpoint[] = [];
|
43 | private error: StatusObject | null = null;
|
44 | private hasReturnedResult = false;
|
45 | constructor(
|
46 | target: GrpcUri,
|
47 | private listener: ResolverListener,
|
48 | channelOptions: ChannelOptions
|
49 | ) {
|
50 | trace('Resolver constructed for target ' + uriToString(target));
|
51 | const addresses: SubchannelAddress[] = [];
|
52 | if (!(target.scheme === IPV4_SCHEME || target.scheme === IPV6_SCHEME)) {
|
53 | this.error = {
|
54 | code: Status.UNAVAILABLE,
|
55 | details: `Unrecognized scheme ${target.scheme} in IP resolver`,
|
56 | metadata: new Metadata(),
|
57 | };
|
58 | return;
|
59 | }
|
60 | const pathList = target.path.split(',');
|
61 | for (const path of pathList) {
|
62 | const hostPort = splitHostPort(path);
|
63 | if (hostPort === null) {
|
64 | this.error = {
|
65 | code: Status.UNAVAILABLE,
|
66 | details: `Failed to parse ${target.scheme} address ${path}`,
|
67 | metadata: new Metadata(),
|
68 | };
|
69 | return;
|
70 | }
|
71 | if (
|
72 | (target.scheme === IPV4_SCHEME && !isIPv4(hostPort.host)) ||
|
73 | (target.scheme === IPV6_SCHEME && !isIPv6(hostPort.host))
|
74 | ) {
|
75 | this.error = {
|
76 | code: Status.UNAVAILABLE,
|
77 | details: `Failed to parse ${target.scheme} address ${path}`,
|
78 | metadata: new Metadata(),
|
79 | };
|
80 | return;
|
81 | }
|
82 | addresses.push({
|
83 | host: hostPort.host,
|
84 | port: hostPort.port ?? DEFAULT_PORT,
|
85 | });
|
86 | }
|
87 | this.endpoints = addresses.map(address => ({ addresses: [address] }));
|
88 | trace('Parsed ' + target.scheme + ' address list ' + addresses);
|
89 | }
|
90 | updateResolution(): void {
|
91 | if (!this.hasReturnedResult) {
|
92 | this.hasReturnedResult = true;
|
93 | process.nextTick(() => {
|
94 | if (this.error) {
|
95 | this.listener.onError(this.error);
|
96 | } else {
|
97 | this.listener.onSuccessfulResolution(
|
98 | this.endpoints,
|
99 | null,
|
100 | null,
|
101 | null,
|
102 | {}
|
103 | );
|
104 | }
|
105 | });
|
106 | }
|
107 | }
|
108 | destroy(): void {
|
109 | this.hasReturnedResult = false;
|
110 | }
|
111 |
|
112 | static getDefaultAuthority(target: GrpcUri): string {
|
113 | return target.path.split(',')[0];
|
114 | }
|
115 | }
|
116 |
|
117 | export function setup() {
|
118 | registerResolver(IPV4_SCHEME, IpResolver);
|
119 | registerResolver(IPV6_SCHEME, IpResolver);
|
120 | }
|