Skip to content

Commit 14d3c67

Browse files
committed
feat(plugins): mask public ip in webrtc
1 parent 60bcf86 commit 14d3c67

File tree

17 files changed

+297
-55
lines changed

17 files changed

+297
-55
lines changed

core/lib/CorePlugins.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import { IInteractionGroups, IInteractionStep } from '@secret-agent/interfaces/I
1010
import IInteractionsHelper from '@secret-agent/interfaces/IInteractionsHelper';
1111
import IPoint from '@secret-agent/interfaces/IPoint';
1212
import ICorePlugin, {
13-
IHumanEmulator,
1413
IBrowserEmulator,
14+
IBrowserEmulatorClass,
1515
IBrowserEmulatorConfig,
16-
ISelectBrowserMeta,
1716
ICorePluginClass,
18-
IOnClientCommandMeta,
19-
IBrowserEmulatorClass,
17+
IHumanEmulator,
2018
IHumanEmulatorClass,
19+
IOnClientCommandMeta,
20+
ISelectBrowserMeta,
2121
} from '@secret-agent/interfaces/ICorePlugin';
2222
import ICorePlugins from '@secret-agent/interfaces/ICorePlugins';
2323
import ICorePluginCreateOptions from '@secret-agent/interfaces/ICorePluginCreateOptions';
@@ -26,6 +26,7 @@ import { PluginTypes } from '@secret-agent/interfaces/IPluginTypes';
2626
import requirePlugins from '@secret-agent/plugin-utils/lib/utils/requirePlugins';
2727
import IHttp2ConnectSettings from '@secret-agent/interfaces/IHttp2ConnectSettings';
2828
import IDeviceProfile from '@secret-agent/interfaces/IDeviceProfile';
29+
import IHttpSocketAgent from '@secret-agent/interfaces/IHttpSocketAgent';
2930
import Core from '../index';
3031

3132
const DefaultBrowserEmulatorId = 'default-browser-emulator';
@@ -136,6 +137,14 @@ export default class CorePlugins implements ICorePlugins {
136137
this.instances.filter(p => p.onTlsConfiguration).forEach(p => p.onTlsConfiguration(settings));
137138
}
138139

140+
public async onHttpAgentInitialized(agent: IHttpSocketAgent): Promise<void> {
141+
await Promise.all(
142+
this.instances
143+
.filter(p => p.onHttpAgentInitialized)
144+
.map(p => p.onHttpAgentInitialized(agent)),
145+
);
146+
}
147+
139148
public async onNewPuppetPage(page: IPuppetPage): Promise<void> {
140149
await Promise.all(
141150
this.instances.filter(p => p.onNewPuppetPage).map(p => p.onNewPuppetPage(page)),

core/lib/Session.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,7 @@ export default class Session extends TypedEventEmitter<{
267267
requestSession.on('resource-state', this.onResourceStates.bind(this));
268268
requestSession.on('socket-close', this.onSocketClose.bind(this));
269269
requestSession.on('socket-connect', this.onSocketConnect.bind(this));
270+
await this.plugins.onHttpAgentInitialized(requestSession.requestAgent);
270271
}
271272

272273
public nextTabId(): number {

full-client/test/basic.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ describe('basic Full Client tests', () => {
6060
it('should get unreachable proxy errors in the client', async () => {
6161
const agent = await handler.createAgent({
6262
upstreamProxyUrl: koaServer.baseUrl,
63+
upstreamProxyIpMask: {
64+
proxyIp: '127.0.0.1',
65+
publicIp: '127.0.0.1',
66+
},
6367
});
6468
Helpers.needsClosing.push(agent);
6569
await expect(agent.goto(`${koaServer.baseUrl}/`)).rejects.toThrow();

interfaces/ICorePlugin.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import ITlsSettings from './ITlsSettings';
1212
import IHttpResourceLoadDetails from './IHttpResourceLoadDetails';
1313
import { IPuppetPage } from './IPuppetPage';
1414
import { IPuppetWorker } from './IPuppetWorker';
15-
import IViewport from './IViewport';
16-
import IGeolocation from './IGeolocation';
1715
import IDeviceProfile from './IDeviceProfile';
1816
import IHttp2ConnectSettings from './IHttp2ConnectSettings';
17+
import IHttpSocketAgent from './IHttpSocketAgent';
18+
import ISessionCreateOptions from './ISessionCreateOptions';
1919

2020
export default interface ICorePlugin
2121
extends ICorePluginMethods,
@@ -108,6 +108,7 @@ export interface IBrowserEmulatorMethods {
108108
onDnsConfiguration?(settings: IDnsSettings): Promise<any> | void;
109109
onTcpConfiguration?(settings: ITcpSettings): Promise<any> | void;
110110
onTlsConfiguration?(settings: ITlsSettings): Promise<any> | void;
111+
onHttpAgentInitialized?(agent: IHttpSocketAgent): Promise<any> | void;
111112

112113
onHttp2SessionConnect?(
113114
request: IHttpResourceLoadDetails,
@@ -122,12 +123,10 @@ export interface IBrowserEmulatorMethods {
122123
websiteHasFirstPartyInteraction?(url: URL): Promise<any> | void; // needed for implementing first-party cookies
123124
}
124125

125-
export interface IBrowserEmulatorConfig {
126-
viewport?: IViewport;
127-
geolocation?: IGeolocation;
128-
timezoneId?: string;
129-
locale?: string;
130-
}
126+
export type IBrowserEmulatorConfig = Pick<
127+
ISessionCreateOptions,
128+
'viewport' | 'geolocation' | 'timezoneId' | 'locale' | 'upstreamProxyIpMask' | 'upstreamProxyUrl'
129+
>;
131130

132131
// decorator for browser emulator classes. hacky way to check the class implements statics we need
133132
// eslint-disable-next-line @typescript-eslint/no-unused-vars

interfaces/IHttpSocketAgent.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import IHttpSocketConnectOptions from './IHttpSocketConnectOptions';
2+
import IHttpSocketWrapper from './IHttpSocketWrapper';
3+
4+
export default interface IHttpSocketAgent {
5+
createSocketConnection(options: IHttpSocketConnectOptions): Promise<IHttpSocketWrapper>;
6+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
export default interface IHttpSocketConnectOptions {
2+
host: string;
3+
port: string;
4+
isSsl: boolean;
5+
keepAlive?: boolean;
6+
debug?: boolean;
7+
servername?: string;
8+
isWebsocket?: boolean;
9+
keylogPath?: string;
10+
proxyUrl?: string;
11+
}

interfaces/IHttpSocketWrapper.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as net from 'net';
2+
3+
export default interface IHttpSocketWrapper {
4+
id: number;
5+
alpn: string;
6+
socket: net.Socket;
7+
dnsResolvedIp: string;
8+
remoteAddress: string;
9+
localAddress: string;
10+
serverName: string;
11+
12+
createTime: Date;
13+
dnsLookupTime: Date;
14+
connectTime: Date;
15+
errorTime: Date;
16+
closeTime: Date;
17+
18+
isConnected: boolean;
19+
isClosing: boolean;
20+
21+
isHttp2(): boolean;
22+
close(): void;
23+
}

interfaces/ISessionCreateOptions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export default interface ISessionCreateOptions extends ISessionOptions {
1414
timezoneId?: string;
1515
locale?: string;
1616
upstreamProxyUrl?: string;
17+
upstreamProxyIpMask?: { publicIp?: string; proxyIp?: string; ipLookupService?: string };
1718
input?: { command?: string } & any;
1819
geolocation?: IGeolocation;
1920
dependencyMap?: { [clientPluginId: string]: string[] };

mitm-socket/index.ts

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ import Log from '@secret-agent/commons/Logger';
55
import { TypedEventEmitter } from '@secret-agent/commons/eventUtils';
66
import Resolvable from '@secret-agent/commons/Resolvable';
77
import { createIpcSocketPath } from '@secret-agent/commons/IpcUtils';
8+
import IHttpSocketConnectOptions from '@secret-agent/interfaces/IHttpSocketConnectOptions';
9+
import IHttpSocketWrapper from '@secret-agent/interfaces/IHttpSocketWrapper';
810
import MitmSocketSession from './lib/MitmSocketSession';
911

1012
const { log } = Log(module);
1113

1214
let idCounter = 0;
1315

14-
export default class MitmSocket extends TypedEventEmitter<{
15-
connect: void;
16-
dial: void;
17-
eof: void;
18-
close: void;
19-
}> {
16+
export default class MitmSocket
17+
extends TypedEventEmitter<{
18+
connect: void;
19+
dial: void;
20+
eof: void;
21+
close: void;
22+
}>
23+
implements IHttpSocketWrapper {
2024
public get isWebsocket(): boolean {
2125
return this.connectOpts.isWebsocket === true;
2226
}
@@ -50,7 +54,7 @@ export default class MitmSocket extends TypedEventEmitter<{
5054
private socketReadyPromise = new Resolvable<void>();
5155
private readonly callStack: string;
5256

53-
constructor(readonly sessionId: string, readonly connectOpts: IGoTlsSocketConnectOpts) {
57+
constructor(readonly sessionId: string, readonly connectOpts: IHttpSocketConnectOptions) {
5458
super();
5559
this.callStack = new Error().stack.replace('Error:', '').trim();
5660
this.serverName = connectOpts.servername;
@@ -226,18 +230,6 @@ export default class MitmSocket extends TypedEventEmitter<{
226230
}
227231
}
228232

229-
export interface IGoTlsSocketConnectOpts {
230-
host: string;
231-
port: string;
232-
isSsl: boolean;
233-
keepAlive?: boolean;
234-
debug?: boolean;
235-
servername?: string;
236-
isWebsocket?: boolean;
237-
keylogPath?: string;
238-
proxyUrl?: string;
239-
}
240-
241233
class Socks5ProxyConnectError extends Error {}
242234
class HttpProxyConnectError extends Error {}
243235
class SocketConnectError extends Error {}

mitm/lib/MitmRequestAgent.ts

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import MitmSocket, { IGoTlsSocketConnectOpts } from '@secret-agent/mitm-socket';
1+
import MitmSocket from '@secret-agent/mitm-socket';
22
import * as http2 from 'http2';
33
import { ClientHttp2Session, Http2ServerRequest } from 'http2';
44
import Log from '@secret-agent/commons/Logger';
@@ -10,6 +10,7 @@ import ITcpSettings from '@secret-agent/interfaces/ITcpSettings';
1010
import ITlsSettings from '@secret-agent/interfaces/ITlsSettings';
1111
import Resolvable from '@secret-agent/commons/Resolvable';
1212
import IHttp2ConnectSettings from '@secret-agent/interfaces/IHttp2ConnectSettings';
13+
import IHttpSocketConnectOptions from '@secret-agent/interfaces/IHttpSocketConnectOptions';
1314
import IMitmRequestContext from '../interfaces/IMitmRequestContext';
1415
import MitmRequestContext from './MitmRequestContext';
1516
import RequestSession from '../handlers/RequestSession';
@@ -135,27 +136,7 @@ export default class MitmRequestAgent {
135136
return await pool.isHttp2(false, () => this.createSocketConnection(options));
136137
}
137138

138-
private async assignSocket(
139-
ctx: IMitmRequestContext,
140-
options: IGoTlsSocketConnectOpts & { headers: IResourceHeaders },
141-
): Promise<MitmSocket> {
142-
ctx.setState(ResourceState.GetSocket);
143-
const pool = this.getSocketPoolByOrigin(ctx.url.origin);
144-
145-
options.isSsl = ctx.isSSL;
146-
options.keepAlive = !(
147-
(options.headers.connection ?? options.headers.Connection) as string
148-
)?.match(/close/i);
149-
options.isWebsocket = ctx.isUpgrade;
150-
151-
const mitmSocket = await pool.getSocket(options.isWebsocket, () =>
152-
this.createSocketConnection(options),
153-
);
154-
MitmRequestContext.assignMitmSocket(ctx, mitmSocket);
155-
return mitmSocket;
156-
}
157-
158-
private async createSocketConnection(options: IGoTlsSocketConnectOpts): Promise<MitmSocket> {
139+
public async createSocketConnection(options: IHttpSocketConnectOptions): Promise<MitmSocket> {
159140
const session = this.session;
160141

161142
const dnsLookupTime = new Date();
@@ -187,6 +168,25 @@ export default class MitmRequestAgent {
187168
return mitmSocket;
188169
}
189170

171+
private async assignSocket(
172+
ctx: IMitmRequestContext,
173+
options: IHttpSocketConnectOptions & { headers: IResourceHeaders },
174+
): Promise<MitmSocket> {
175+
ctx.setState(ResourceState.GetSocket);
176+
const pool = this.getSocketPoolByOrigin(ctx.url.origin);
177+
178+
options.isSsl = ctx.isSSL;
179+
options.keepAlive = !((options.headers.connection ??
180+
options.headers.Connection) as string)?.match(/close/i);
181+
options.isWebsocket = ctx.isUpgrade;
182+
183+
const mitmSocket = await pool.getSocket(options.isWebsocket, () =>
184+
this.createSocketConnection(options),
185+
);
186+
MitmRequestContext.assignMitmSocket(ctx, mitmSocket);
187+
return mitmSocket;
188+
}
189+
190190
private getSocketPoolByOrigin(origin: string): SocketPool {
191191
let lookup = origin.split('://').pop();
192192
if (!lookup.includes(':') && origin.includes('://')) {

0 commit comments

Comments
 (0)