diff --git a/src/client/rpcNodes.ts b/src/client/rpcNodes.ts index f2a8bea665..c1dbb1aaa3 100644 --- a/src/client/rpcNodes.ts +++ b/src/client/rpcNodes.ts @@ -8,10 +8,9 @@ import * as grpc from '@grpc/grpc-js'; import * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; import * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; import * as utils from '../client/utils'; -import * as nodesUtils from '../nodes/utils'; +import { utils as nodesUtils, errors as nodesErrors } from '../nodes'; import * as grpcUtils from '../grpc/utils'; -import * as nodesErrors from '../nodes/errors'; -import { makeNodeId } from '../nodes/utils'; +import * as networkUtils from '../network/utils'; const createNodesRPC = ({ nodeManager, @@ -44,13 +43,13 @@ const createNodesRPC = ({ if (!validNodeId) { throw new nodesErrors.ErrorInvalidNodeId(); } - const validHost = nodesUtils.isValidHost( + const validHost = networkUtils.isValidHost( call.request.getAddress()!.getHost(), ); if (!validHost) { throw new nodesErrors.ErrorInvalidHost(); } - await nodeManager.setNode(makeNodeId(call.request.getNodeId()), { + await nodeManager.setNode(nodesUtils.makeNodeId(call.request.getNodeId()), { ip: call.request.getAddress()!.getHost(), port: call.request.getAddress()!.getPort(), } as NodeAddress); @@ -74,7 +73,7 @@ const createNodesRPC = ({ ); call.sendMetadata(responseMeta); const status = await nodeManager.pingNode( - makeNodeId(call.request.getNodeId()), + nodesUtils.makeNodeId(call.request.getNodeId()), ); response.setSuccess(status); } catch (err) { @@ -98,7 +97,7 @@ const createNodesRPC = ({ await sessionManager.generateToken(), ); call.sendMetadata(responseMeta); - const remoteNodeId = makeNodeId(call.request.getNodeId()); + const remoteNodeId = nodesUtils.makeNodeId(call.request.getNodeId()); const gestaltInvite = await notificationsManager.findGestaltInvite( remoteNodeId, ); @@ -137,7 +136,7 @@ const createNodesRPC = ({ await sessionManager.generateToken(), ); call.sendMetadata(responseMeta); - const nodeId = makeNodeId(call.request.getNodeId()); + const nodeId = nodesUtils.makeNodeId(call.request.getNodeId()); const address = await nodeManager.findNode(nodeId); response .setNodeId(nodeId) diff --git a/src/network/errors.ts b/src/network/errors.ts index 219e6c8b89..9b03420b45 100644 --- a/src/network/errors.ts +++ b/src/network/errors.ts @@ -83,6 +83,8 @@ class ErrorCertChainKeyInvalid extends ErrorCertChain {} */ class ErrorCertChainSignatureInvalid extends ErrorCertChain {} +class ErrorHostnameResolutionFailed extends ErrorNetwork {} + export { ErrorNetwork, ErrorForwardProxyNotStarted, @@ -110,4 +112,5 @@ export { ErrorCertChainNameInvalid, ErrorCertChainKeyInvalid, ErrorCertChainSignatureInvalid, + ErrorHostnameResolutionFailed, }; diff --git a/src/network/utils.ts b/src/network/utils.ts index f5d20c63dc..744dc3bad8 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -1,13 +1,14 @@ import type { Socket } from 'net'; import type { TLSSocket } from 'tls'; -import type { Host, Port, Address, NetworkMessage } from './types'; +import type { Host, Hostname, Port, Address, NetworkMessage } from './types'; import type { Certificate, PublicKey } from '../keys/types'; import type { NodeId } from '../nodes/types'; import { Buffer } from 'buffer'; import { IPv4, IPv6, Validator } from 'ip-num'; +import dns from 'dns'; import * as networkErrors from './errors'; -import { isEmptyObject } from '../utils'; +import { isEmptyObject, promisify } from '../utils'; import { utils as keysUtils } from '../keys'; const pingBuffer = serializeNetworkMessage({ @@ -53,6 +54,35 @@ function parseAddress(address: string): [Host, Port] { return [dstHost as Host, dstPort as Port]; } +/** + * Validates that a provided host address is a valid IPv4 or IPv6 address. + */ + function isValidHost(host: string): boolean { + const [isIPv4] = Validator.isValidIPv4String(host); + const [isIPv6] = Validator.isValidIPv6String(host); + return isIPv4 || isIPv6; +} + +/** + * Resolves a provided hostname to its respective IP address (type Host). + */ +async function resolveHost(host: Host | Hostname): Promise { + // If already IPv4/IPv6 address, return it + if (isValidHost(host)) { + return host as Host; + } + const lookup = promisify(dns.lookup).bind(dns); + let resolvedHost; + try { + // Resolve the hostname and get the IPv4 address + resolvedHost = await lookup(host, 4); + } catch (e) { + throw new networkErrors.ErrorHostnameResolutionFailed(e.message); + } + // Returns an array of [ resolved address, family (4 or 6) ] + return resolvedHost[0] as Host; +} + /** * Zero IPs should be resolved to localhost when used as the target * This is usually done automatically, but utp-native doesn't do this @@ -317,6 +347,8 @@ export { toAuthToken, buildAddress, parseAddress, + isValidHost, + resolveHost, resolvesZeroIP, serializeNetworkMessage, unserializeNetworkMessage, diff --git a/src/nodes/utils.ts b/src/nodes/utils.ts index 0fade0e4f8..9e6c9f77a6 100644 --- a/src/nodes/utils.ts +++ b/src/nodes/utils.ts @@ -66,15 +66,6 @@ function makeNodeId(arg: any): NodeId { return makeIdString(arg, 32, 'base32hex'); } -/** - * Validates that a provided host address is a valid IPv4 or IPv6 address. - */ -function isValidHost(host: string): boolean { - const [isIPv4] = Validator.isValidIPv4String(host); - const [isIPv6] = Validator.isValidIPv6String(host); - return isIPv4 || isIPv6; -} - /** * Node ID to an array of 8-bit unsigned ints */ @@ -103,7 +94,6 @@ export { calculateBucketIndex, isNodeId, makeNodeId, - isValidHost, nodeIdToU8, sortByDistance, }; diff --git a/tests/network/utils.test.ts b/tests/network/utils.test.ts index d71e27d594..c32b6cd7ba 100644 --- a/tests/network/utils.test.ts +++ b/tests/network/utils.test.ts @@ -1,6 +1,6 @@ import type { Host, Port } from '@/network/types'; -import * as networkUtils from '@/network/utils'; +import { utils as networkUtils, errors as networkErrors } from '@/network'; describe('utils', () => { test('building addresses', async () => { @@ -23,4 +23,14 @@ describe('utils', () => { ), ).toBe('::1' as Host); }); + test('resolving hostnames', async () => { + await expect( + networkUtils.resolveHost('www.google.com' as Host) + ).resolves.toBeDefined(); + let host = await networkUtils.resolveHost('www.google.com' as Host); + expect(networkUtils.isValidHost(host)).toBeTruthy(); + await expect( + networkUtils.resolveHost('invalidHostname' as Host) + ).rejects.toThrow(networkErrors.ErrorHostnameResolutionFailed); + }); });