Add SOCKS proxy support to RemoteForward (closes #252)

feature/forwarding
Kelvin Schoofs 3 years ago
parent 3ad7bae30b
commit 7bde31c3b2

@ -7,7 +7,7 @@ import type { Connection } from "./connection";
import type { FileSystemConfig } from './fileSystemConfig';
import { Logging, LOGGING_NO_STACKTRACE } from './logging';
import type { Manager } from './manager';
import { capitalize, promptQuickPick } from './ui-utils';
import { promptQuickPick } from './ui-utils';
import { toPromise } from './utils';
/** Represents a dynamic port forwarding (DynamicForward) */
@ -39,7 +39,8 @@ export function isActivePortForwarding(apf: any): apf is ActivePortForwarding {
}
function validateLocalRemoteForwarding(forwarding: PortForwardingLocalRemote) {
if (forwarding.localPort === undefined && !forwarding.localAddress) {
if (forwarding.type === 'local' && forwarding.localPort === undefined && !forwarding.localAddress) {
// The 'remote' one can omit both `localPort` and `localAddress`, indicating it's the proxy thing
throw new Error(`Missing both 'localPort' and 'localAddress' fields for a ${forwarding.type} port forwarding`);
}
if (forwarding.remotePort === undefined && !forwarding.remoteAddress) {
@ -80,17 +81,27 @@ async function createLocalForwarding(connection: Connection, forwarding: PortFor
async function createRemoteForwarding(connection: Connection, forwarding: PortForwardingLocalRemote): Promise<ActivePortForwarding> {
validateLocalRemoteForwarding(forwarding);
const { Server, Auth } = await import('node-socksv5');
let socksServer: import('node-socksv5').Server | undefined;
if (forwarding.localPort === undefined && !forwarding.localPort) {
// Expect a proxy server
socksServer = new Server({ auths: [Auth.none()] });
}
const channels = new Set<Duplex>();
const onSocket = (channel: Duplex) => {
channels.add(channel);
channel.on('close', () => channels.delete(socket));
channel.on('close', () => channels.delete(channel));
let socket: net.Socket;
if (forwarding.localPort === undefined) {
if (socksServer) {
socket = new net.Socket();
((socksServer as any).onConnection as (typeof socksServer)['onConnection'])(channel as net.Socket);
} else if (forwarding.localPort === undefined) {
socket = net.createConnection(forwarding.localAddress!);
socket.on('connect', () => socket.pipe(channel).pipe(socket));
} else {
socket = net.createConnection(forwarding.localPort!, forwarding.localAddress!);
socket.on('connect', () => socket.pipe(channel).pipe(socket));
}
socket.on('connect', () => socket.pipe(channel).pipe(socket));
};
let unlisten: () => void;
if (forwarding.remotePort === undefined) {
@ -279,7 +290,14 @@ async function promptBindAddress(): Promise<[port: number, address?: string] | u
export async function promptPortForwarding(config: FileSystemConfig): Promise<PortForwarding | undefined> {
// TODO: RemoteForward allows omitting the local address/port, making it act as a reverse DynamicForward instead
// TODO: Make use of config with future GatewayPorts fields and such to suggest default values
const type = await promptQuickPick<PortForwarding['type']>('Select type of port forwarding', ['local', 'remote', 'dynamic'], capitalize);
const types = {
local: 'Local forward',
remote: 'Remote forward',
remoteProxy: 'Remote proxy (client SOCKSv5 ← server)',
dynamic: 'Dynamic forward (client → server SOCKSv5)'
};
type Type = keyof typeof types;
const type = await promptQuickPick<Type>('Select type of port forwarding', Object.keys(types) as Type[], key => types[key]);
if (!type) return undefined;
if (type === 'local' || type === 'remote') {
const local = await promptAddressOrPath('local');
@ -287,8 +305,13 @@ export async function promptPortForwarding(config: FileSystemConfig): Promise<Po
const remote = await promptAddressOrPath('remote');
if (!remote) return undefined;
const [localPort, localAddress] = local;
const [remotePort, remoteAddress] = remote
const [remotePort, remoteAddress] = remote;
return { type, localAddress, localPort, remoteAddress, remotePort };
} else if (type === 'remoteProxy') {
const remote = await promptAddressOrPath('remote');
if (!remote) return undefined;
const [remotePort, remoteAddress] = remote;
return { type: 'remote', remoteAddress, remotePort };
} else if (type === 'dynamic') {
const bind = await promptBindAddress();
if (!bind) return undefined;

@ -80,7 +80,11 @@ export function formatItem(item: FileSystemConfig | Connection | SSHFileSystem |
let label = iconInLabel ? '$(ports-forward-icon) ' : '';
const [forw] = item;
if (forw.type === 'local' || forw.type === 'remote') {
label += forw.localPort === undefined ? forw.localAddress : `${forw.localAddress || '?'}:${forw.localPort}` || '?';
if (forw.localPort || forw.localAddress) {
label += forw.localPort === undefined ? forw.localAddress : `${forw.localAddress || '?'}:${forw.localPort}` || '?';
} else {
label += 'SOCKSv5';
}
label += forw.type === 'local' ? ' → ' : ' ← ';
label += forw.remotePort === undefined ? forw.remoteAddress : `${forw.remoteAddress || '?'}:${forw.remotePort}` || '?';
} else if (forw.type === 'dynamic') {
@ -93,6 +97,7 @@ export function formatItem(item: FileSystemConfig | Connection | SSHFileSystem |
return {
item, label, contextValue: 'forwarding',
detail, tooltip: detail,
collapsibleState: vscode.TreeItemCollapsibleState.Expanded,
iconPath: new vscode.ThemeIcon('ports-forward-icon'),
};
}

Loading…
Cancel
Save