Skip to content

Commit

Permalink
feat: added connectionEstablishedCallback to Proxy
Browse files Browse the repository at this point in the history
Added a callback to the `Proxy` that is called when a `ForwardConnection` or `ReverseConnection` is established and authenticated. It is called with the following connection information; `remoteNodeId`, `remoteHost`, `remotePort` and `type`. They type signifies if it was a forward or reverse connection. Note that this is only triggered by composed connections.

Added a test for if the callback was called when a `ReverseConnection` is established.

Relates #332
Relates #344
  • Loading branch information
tegefaulkes committed Jun 10, 2022
1 parent 5122b98 commit 24abbbd
Show file tree
Hide file tree
Showing 3 changed files with 156 additions and 2 deletions.
29 changes: 28 additions & 1 deletion src/network/Proxy.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import type { AddressInfo, Socket } from 'net';
import type { Host, Port, Address, ConnectionInfo, TLSConfig } from './types';
import type {
Host,
Port,
Address,
ConnectionInfo,
TLSConfig,
ConnectionEstablishedCallback,
} from './types';
import type { ConnectionsForward } from './ConnectionForward';
import type { NodeId } from '../nodes/types';
import type { Timer } from '../types';
Expand Down Expand Up @@ -48,6 +55,7 @@ class Proxy {
proxy: new Map(),
reverse: new Map(),
};
protected connectionEstablishedCallback: ConnectionEstablishedCallback;

constructor({
authToken,
Expand All @@ -56,6 +64,7 @@ class Proxy {
connEndTime = 1000,
connPunchIntervalTime = 1000,
connKeepAliveIntervalTime = 1000,
connectionEstablishedCallback = () => {},
logger,
}: {
authToken: string;
Expand All @@ -64,6 +73,7 @@ class Proxy {
connEndTime?: number;
connPunchIntervalTime?: number;
connKeepAliveIntervalTime?: number;
connectionEstablishedCallback?: ConnectionEstablishedCallback;
logger?: Logger;
}) {
this.logger = logger ?? new Logger(Proxy.name);
Expand All @@ -77,6 +87,7 @@ class Proxy {
this.server = http.createServer();
this.server.on('request', this.handleRequest);
this.server.on('connect', this.handleConnectForward);
this.connectionEstablishedCallback = connectionEstablishedCallback;
this.logger.info(`Created ${Proxy.name}`);
}

Expand Down Expand Up @@ -521,6 +532,14 @@ class Proxy {
timer,
);
conn.compose(clientSocket);
// With the connection composed without error we can assume that the
// connection was established and verified
await this.connectionEstablishedCallback({
remoteNodeId: conn.getServerNodeIds()[0],
remoteHost: conn.host,
remotePort: conn.port,
type: 'forward',
});
}

protected async establishConnectionForward(
Expand Down Expand Up @@ -687,6 +706,14 @@ class Proxy {
timer,
);
await conn.compose(utpConn, timer);
// With the connection composed without error we can assume that the
// connection was established and verified
await this.connectionEstablishedCallback({
remoteNodeId: conn.getClientNodeIds()[0],
remoteHost: conn.host,
remotePort: conn.port,
type: 'reverse',
});
}

protected async establishConnectionReverse(
Expand Down
11 changes: 11 additions & 0 deletions src/network/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ type ConnectionInfo = {
remotePort: Port;
};

type ConnectionData = {
remoteNodeId: NodeId;
remoteHost: Host;
remotePort: Port;
type: 'forward' | 'reverse';
};

type ConnectionEstablishedCallback = (data: ConnectionData) => any;

type PingMessage = {
type: 'ping';
};
Expand All @@ -73,6 +82,8 @@ export type {
TLSConfig,
ProxyConfig,
ConnectionInfo,
ConnectionData,
ConnectionEstablishedCallback,
PingMessage,
PongMessage,
NetworkMessage,
Expand Down
118 changes: 117 additions & 1 deletion tests/network/Proxy.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { AddressInfo, Socket } from 'net';
import type { KeyPairPem } from '@/keys/types';
import type { Host, Port } from '@/network/types';
import type { ConnectionData, Host, Port } from '@/network/types';
import net from 'net';
import http from 'http';
import tls from 'tls';
Expand Down Expand Up @@ -2973,4 +2973,120 @@ describe(Proxy.name, () => {
utpSocket.unref();
await serverClose();
});
test('connectionEstablishedCallback is called when a ReverseConnection is established', async () => {
const clientKeyPair = await keysUtils.generateKeyPair(1024);
const clientKeyPairPem = keysUtils.keyPairToPem(clientKeyPair);
const clientCert = keysUtils.generateCertificate(
clientKeyPair.publicKey,
clientKeyPair.privateKey,
clientKeyPair.privateKey,
86400,
);
const clientCertPem = keysUtils.certToPem(clientCert);
const {
serverListen,
serverClose,
serverConnP,
serverConnEndP,
serverConnClosedP,
serverHost,
serverPort,
} = tcpServer();
await serverListen(0, localHost);
const clientNodeId = keysUtils.certNodeId(clientCert)!;
let callbackData: ConnectionData | undefined;
const proxy = new Proxy({
logger: logger,
authToken: '',
connectionEstablishedCallback: (data) => {
callbackData = data;
},
});
await proxy.start({
serverHost: serverHost(),
serverPort: serverPort(),
proxyHost: localHost,
tlsConfig: {
keyPrivatePem: keyPairPem.privateKey,
certChainPem: certPem,
},
});

const proxyHost = proxy.getProxyHost();
const proxyPort = proxy.getProxyPort();
const { p: clientReadyP, resolveP: resolveClientReadyP } = promise<void>();
const { p: clientSecureConnectP, resolveP: resolveClientSecureConnectP } =
promise<void>();
const { p: clientCloseP, resolveP: resolveClientCloseP } = promise<void>();
const utpSocket = UTP({ allowHalfOpen: true });
const utpSocketBind = promisify(utpSocket.bind).bind(utpSocket);
const handleMessage = async (data: Buffer) => {
const msg = networkUtils.unserializeNetworkMessage(data);
if (msg.type === 'ping') {
resolveClientReadyP();
await send(networkUtils.pongBuffer);
}
};
utpSocket.on('message', handleMessage);
const send = async (data: Buffer) => {
const utpSocketSend = promisify(utpSocket.send).bind(utpSocket);
await utpSocketSend(data, 0, data.byteLength, proxyPort, proxyHost);
};
await utpSocketBind(0, localHost);
const utpSocketPort = utpSocket.address().port;
await proxy.openConnectionReverse(
localHost,
utpSocketPort as Port,
);
const utpConn = utpSocket.connect(proxyPort, proxyHost);
const tlsSocket = tls.connect(
{
key: Buffer.from(clientKeyPairPem.privateKey, 'ascii'),
cert: Buffer.from(clientCertPem, 'ascii'),
socket: utpConn,
rejectUnauthorized: false,
},
() => {
resolveClientSecureConnectP();
},
);
let tlsSocketEnded = false;
tlsSocket.on('end', () => {
tlsSocketEnded = true;
if (utpConn.destroyed) {
tlsSocket.destroy();
} else {
tlsSocket.end();
tlsSocket.destroy();
}
});
tlsSocket.on('close', () => {
resolveClientCloseP();
});
await send(networkUtils.pingBuffer);
expect(proxy.getConnectionReverseCount()).toBe(1);
await clientReadyP;
await clientSecureConnectP;
await serverConnP;
await proxy.closeConnectionReverse(
localHost,
utpSocketPort as Port,
);
expect(proxy.getConnectionReverseCount()).toBe(0);
await clientCloseP;
await serverConnEndP;
await serverConnClosedP;
expect(tlsSocketEnded).toBe(true);
utpSocket.off('message', handleMessage);
utpSocket.close();
utpSocket.unref();
await proxy.stop();
await serverClose();

// Checking callback data
expect(callbackData?.remoteNodeId.equals(clientNodeId)).toBe(true);
expect(callbackData?.remoteHost).toEqual(localHost);
expect(callbackData?.remotePort).toEqual(utpSocketPort);
expect(callbackData?.type).toEqual('reverse');
});
});

0 comments on commit 24abbbd

Please sign in to comment.