Skip to content

Commit b71e141

Browse files
committed
fix(mitm-socket): fix cpu spiking sockets
1 parent b1e05d7 commit b71e141

File tree

19 files changed

+239
-91
lines changed

19 files changed

+239
-91
lines changed

.github/workflows/lint-and-test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
fail-fast: false
6666
matrix:
6767
os: [macos-latest, windows-latest, ubuntu-latest]
68-
node-version: [12, 13]
68+
node-version: [12, 14]
6969

7070
runs-on: ${{ matrix.os }}
7171

@@ -102,6 +102,8 @@ jobs:
102102
- name: Install mitm
103103
run: yarn run build
104104
working-directory: ./mitm-socket
105+
env:
106+
SA_REBUILD_MITM_SOCKET: true
105107

106108
- name: Copy built mitm
107109
run: cp -r mitm-socket/dist build/mitm-socket
@@ -114,7 +116,7 @@ jobs:
114116
SA_REPLAY_SKIP_BINARY_DOWNLOAD: 1
115117

116118
- name: Run tests
117-
run: yarn jest --testTimeout=15000 --maxConcurrency=2
119+
run: yarn jest --testTimeout=15000 --runInBand
118120
working-directory: ./build
119121
env:
120122
SA_SHOW_REPLAY: false

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ emulators/data/DOM-*.json
2626
/.eslintcache
2727
/.network.db
2828
/website/scripts/generateAwaitedDOM.js
29+
connect

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
12.7.0
1+
12.19.0

core/lib/Session.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import RequestSession, {
88
IRequestSessionRequestEvent,
99
IRequestSessionResponseEvent,
1010
IResourceStateChangeEvent,
11+
ISocketEvent,
1112
} from '@secret-agent/mitm/handlers/RequestSession';
1213
import * as Os from 'os';
1314
import IPuppetContext, {
@@ -131,6 +132,8 @@ export default class Session {
131132
requestSession.on('response', this.onMitmResponse.bind(this));
132133
requestSession.on('http-error', this.onMitmError.bind(this));
133134
requestSession.on('resource-state', this.onResourceStates.bind(this));
135+
requestSession.on('socket-close', this.onSocketClose.bind(this));
136+
requestSession.on('socket-connect', this.onSocketConnect.bind(this));
134137
}
135138

136139
public async createTab() {
@@ -208,6 +211,14 @@ export default class Session {
208211
this.sessionState.captureResourceState(event.context.id, event.context.stateChanges);
209212
}
210213

214+
private onSocketClose(event: ISocketEvent) {
215+
this.sessionState.captureSocketEvent(event);
216+
}
217+
218+
private onSocketConnect(event: ISocketEvent) {
219+
this.sessionState.captureSocketEvent(event);
220+
}
221+
211222
private async onNewTab(
212223
parentTab: Tab,
213224
page: IPuppetPage,

core/test/interact.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ beforeAll(async () => {
1111
HumanEmulatorGhost.maxScrollDelayMillis = 0;
1212
koaServer = await Helpers.runKoaServer();
1313
});
14-
afterAll(Helpers.afterAll);
14+
afterAll(Helpers.afterAll, 30e3);
1515
afterEach(Helpers.afterEach);
1616

1717
describe.each([['ghost'], ['basic'], ['skipper']])(
@@ -32,6 +32,7 @@ describe.each([['ghost'], ['basic'], ['skipper']])(
3232
});
3333
const meta = await Core.createTab({ humanEmulatorId });
3434
const core = Core.byTabId[meta.tabId];
35+
Helpers.needsClosing.push(core);
3536
// @ts-ignore
3637
const session = core.session;
3738
await core.goto(`${koaServer.baseUrl}/mouse`);
@@ -70,6 +71,7 @@ describe.each([['ghost'], ['basic'], ['skipper']])(
7071
});
7172
const meta = await Core.createTab({ humanEmulatorId });
7273
const core = Core.byTabId[meta.tabId];
74+
Helpers.needsClosing.push(core);
7375

7476
await core.goto(`${koaServer.baseUrl}/input`);
7577
await core.execJsPath(['document', ['querySelector', 'input'], ['focus']]);
@@ -114,6 +116,7 @@ describe.each([['ghost'], ['basic'], ['skipper']])(
114116
});
115117
const meta = await Core.createTab({ humanEmulatorId });
116118
const core = Core.byTabId[meta.tabId];
119+
Helpers.needsClosing.push(core);
117120
await core.goto(`${koaServer.baseUrl}/longpage`);
118121

119122
const click = async (selector: string) => {

emulate-browsers/base/injected-scripts/polyfill.changes.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,12 @@ for (const change of args.changes || []) {
2323
const { descriptor } = descriptorInHierarchy;
2424

2525
if (change.propertyName === '_value') {
26-
descriptor.value = change.property;
27-
Object.defineProperty(parent, property, descriptor);
26+
if (descriptor.get) {
27+
descriptor.get = proxyGetter(parent, property, () => change.property);
28+
} else {
29+
descriptor.value = change.property;
30+
Object.defineProperty(parent, property, descriptor);
31+
}
2832
}
2933

3034
if (change.propertyName === '_get') {

mitm-socket/index.ts

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,51 @@
11
import { ChildProcess, spawn } from 'child_process';
22
import * as net from 'net';
33
import { promises as fs, unlink } from 'fs';
4-
import Log from '@secret-agent/commons/Logger';
5-
import { IBoundLog } from '@secret-agent/core-interfaces/ILog';
6-
import { EventEmitter } from 'events';
7-
import { createPromise } from '@secret-agent/commons/utils';
84
import * as os from 'os';
9-
105
import { v1 as uuid } from 'uuid';
6+
import Log from '@secret-agent/commons/Logger';
7+
import { createPromise } from '@secret-agent/commons/utils';
8+
import { TypedEventEmitter } from '@secret-agent/commons/eventUtils';
119

1210
const { log } = Log(module);
1311

1412
const ext = os.platform() === 'win32' ? '.exe' : '';
1513
const libPath = `${__dirname}/dist/connect${ext}`;
1614

17-
export default class MitmSocket {
15+
let idCounter = 0;
16+
17+
export default class MitmSocket extends TypedEventEmitter<{
18+
connect: void;
19+
dial: void;
20+
close: void;
21+
}> {
1822
public readonly socketPath: string;
1923
public alpn = 'http/1.1';
2024
public socket: net.Socket;
2125
public remoteAddress: string;
2226
public localAddress: string;
27+
public serverName: string;
28+
29+
public id = (idCounter += 1);
30+
31+
public spawnTime: Date;
32+
public dialTime: Date;
33+
public connectTime: Date;
34+
public closeTime: Date;
35+
36+
public get pid() {
37+
return this.child?.pid;
38+
}
2339

2440
private isClosing = false;
2541
private isConnected = false;
2642
private child: ChildProcess;
27-
private emitter = new EventEmitter();
2843
private connectError?: string;
2944

30-
private readonly logger: IBoundLog;
31-
3245
constructor(readonly sessionId: string, readonly connectOpts: IGoTlsSocketConnectOpts) {
46+
super();
3347
const id = uuid();
48+
this.serverName = connectOpts.servername;
3449
this.socketPath =
3550
os.platform() === 'win32' ? `\\\\.\\pipe\\sa-${id}` : `${os.tmpdir()}/sa-${id}.sock`;
3651
this.logger = log.createChild(module, { sessionId });
@@ -55,18 +70,15 @@ export default class MitmSocket {
5570
this.connectOpts.tcpWindowSize = tcpVars.windowSize;
5671
}
5772

58-
public on(event: 'close', listener: (socket: net.Socket) => any) {
59-
this.emitter.on(event, listener);
60-
}
61-
6273
public isHttp2() {
6374
return this.alpn === 'h2';
6475
}
6576

6677
public close() {
6778
if (this.isClosing) return;
79+
this.closeTime = new Date();
6880
this.isClosing = true;
69-
this.emitter.emit('close');
81+
this.emit('close');
7082
this.cleanupSocket();
7183
this.closeChild();
7284
}
@@ -94,6 +106,7 @@ export default class MitmSocket {
94106
const child = spawn(libPath, [this.socketPath, JSON.stringify(this.connectOpts)], {
95107
stdio: ['pipe', 'pipe', 'pipe'],
96108
});
109+
this.spawnTime = new Date();
97110
this.child = child;
98111
child.stdout.setEncoding('utf8');
99112
child.stderr.setEncoding('utf8');
@@ -168,19 +181,23 @@ export default class MitmSocket {
168181
private onChildProcessMessage(messages: string) {
169182
for (const message of messages.split(/\r?\n/)) {
170183
if (message.startsWith('[DomainSocketPiper.Dialed]')) {
184+
this.dialTime = new Date();
171185
const matches = message.match(/Remote: (.+), Local: (.+)/);
172186
if (matches?.length) {
173187
this.remoteAddress = matches[1];
174188
this.localAddress = matches[2];
175189
}
190+
this.emit('dial');
176191
} else if (message === '[DomainSocketPiper.ReadyForConnect]') {
177192
this.onListening();
178193
} else if (message.startsWith('[DomainSocketPiper.Connected]')) {
179194
this.isConnected = true;
195+
this.connectTime = new Date();
180196
const matches = message.match(/ALPN: (.+)/);
181197
if (matches?.length) {
182198
this.alpn = matches[1];
183199
}
200+
this.emit('connect');
184201
this.logger.stats('SocketHandler.Connected', {
185202
alpn: this.alpn,
186203
host: this.connectOpts?.host,

mitm-socket/install.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ if (!fs.existsSync(outDir)) {
1212
fs.mkdirSync(outDir);
1313
}
1414
const { version } = packageJson;
15-
const releasesAssetsUrl = `https://github.com/ulixee/secret-agent/releases/download/v${version}/`;
15+
const releasesAssetsUrl = `https://github.com/ulixee/secret-agent/releases/download/v${version}`;
1616

1717
// tslint:disable:no-console
1818

mitm-socket/lib/domain_socket_piper.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,22 +52,29 @@ func (piper *DomainSocketPiper) Pipe(remoteConn net.Conn, sigc chan os.Signal) {
5252
copyUntilTimeout := func(dst io.Writer, src net.Conn, notifyChan chan error, counter *uint32) {
5353
var err error
5454
var n int
55-
var one = make([]byte, 1)
55+
var data []byte = make([]byte, 1024)
5656

5757
for {
5858
if atomic.LoadUint32(counter) > 0 {
5959
return
6060
}
6161

62-
src.SetReadDeadline(time.Now().Add(readTimeout))
63-
n, err = src.Read(one)
62+
n, err = src.Read(data)
63+
6464
if err != nil {
6565
notifyChan <- err
66-
} else if n > 0 {
67-
dst.Write(one)
68-
_, err := io.Copy(dst, src)
66+
if atomic.LoadUint32(counter) > 0 {
67+
return
68+
}
69+
if err == io.EOF {
70+
time.Sleep(1 * time.Second)
71+
}
72+
} else if n == 0 {
73+
time.Sleep(200 * time.Millisecond)
74+
} else {
75+
_, err = dst.Write(data[:n])
6976
if err != nil {
70-
notifyChan <- err
77+
notifyChan <- err
7178
}
7279
}
7380
}
@@ -84,6 +91,7 @@ func (piper *DomainSocketPiper) Pipe(remoteConn net.Conn, sigc chan os.Signal) {
8491
for {
8592
select {
8693
case <-sigc:
94+
atomic.AddUint32(&piper.completeCounter, 1)
8795
if piper.debug {
8896
fmt.Println("DomainSocket -> Sigc")
8997
}
@@ -117,7 +125,7 @@ func (piper *DomainSocketPiper) Pipe(remoteConn net.Conn, sigc chan os.Signal) {
117125
return
118126
}
119127

120-
case <-time.After(50 * time.Millisecond):
128+
case <-time.After(100 * time.Millisecond):
121129
if atomic.LoadUint32(&piper.completeCounter) > 0 {
122130
if piper.debug {
123131
fmt.Println("DomainSocket -> Closed")

mitm-socket/test/MitmSocket.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ test('should handle http2 requests', async () => {
3636
const client = http2.connect('https://secretagent.dev', {
3737
createConnection: () => tlsConnection.socket,
3838
});
39-
Helpers.onClose(() => new Promise(resolve => client.close(resolve)));
39+
closeAfterTest(client);
4040

4141
const request = client.request({ ':path': '/' });
4242
const httpResponse = await readResponse(request);
@@ -58,7 +58,7 @@ test('should be able to hit google using a Chrome79 Emulator', async () => {
5858
const client = http2.connect('https://www.google.com', {
5959
createConnection: () => tlsConnection.socket,
6060
});
61-
Helpers.onClose(() => new Promise(resolve => client.close(resolve)));
61+
closeAfterTest(client);
6262

6363
const request = client.request({ ':path': '/' });
6464
const httpResponse = await readResponse(request);
@@ -67,7 +67,7 @@ test('should be able to hit google using a Chrome79 Emulator', async () => {
6767
});
6868

6969
test('should be able to hit optimove using a Chrome79 Emulator', async () => {
70-
const tlsConnection = new MitmSocket('2', {
70+
const tlsConnection = new MitmSocket('optimove', {
7171
host: 'www.gstatic.com',
7272
port: '443',
7373
servername: 'www.gstatic.com',
@@ -82,7 +82,7 @@ test('should be able to hit optimove using a Chrome79 Emulator', async () => {
8282
const client = http2.connect('https://www.gstatic.com', {
8383
createConnection: () => tlsConnection.socket,
8484
});
85-
Helpers.onClose(() => new Promise(resolve => client.close(resolve)));
85+
closeAfterTest(client);
8686

8787
const request = client.request({
8888
':path': '/firebasejs/4.9.1/firebase.js',
@@ -108,7 +108,7 @@ test.skip('should be able to get scripts from unpkg using Chrome79 emulator', as
108108
const client = http2.connect('https://unpkg.com', {
109109
createConnection: () => tlsConnection.socket,
110110
});
111-
trackClose(client);
111+
closeAfterTest(client);
112112

113113
{
114114
const request = client.request({ ':path': '/react@16.7.0/umd/react.production.min.js' });
@@ -322,12 +322,12 @@ async function startProxy() {
322322
});
323323
proxy.unref();
324324

325-
trackClose(proxy);
325+
closeAfterTest(proxy);
326326
await proxyPromise.promise;
327327
return proxy;
328328
}
329329

330-
function trackClose(closable: { close: (...args: any[]) => any }) {
330+
function closeAfterTest(closable: { close: (...args: any[]) => any }) {
331331
Helpers.onClose(() => new Promise(resolve => closable.close(() => process.nextTick(resolve))));
332332
}
333333

0 commit comments

Comments
 (0)