Skip to content

Commit 99a47eb

Browse files
committed
feat: loading TLS certs from memory and wireshark debugging
* Fixes #2 [ci skip]
1 parent 0683e75 commit 99a47eb

15 files changed

+599
-333
lines changed

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ path = "src/native/napi/lib.rs"
1414
napi = { version = "2", features = ["async", "napi6", "serde-json"] }
1515
serde = { version = "1.0", features = ["derive"] }
1616
napi-derive = "2"
17-
quiche = "0.16.0"
18-
boring = "2.0.0"
17+
quiche = { version = "0.16.0", features = ["boringssl-boring-crate", "boringssl-vendored"] }
18+
boring = "2.1.0"
1919

2020
[build-dependencies]
2121
napi-build = "2"

index.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ export function retry(scid: Uint8Array, dcid: Uint8Array, newScid: Uint8Array, t
132132
export function versionIsSupported(version: number): boolean
133133
export class Config {
134134
constructor()
135+
static withBoringSslCtx(certPem?: Uint8Array | undefined | null, keyPem?: Uint8Array | undefined | null): Config
135136
loadCertChainFromPemFile(file: string): void
136137
loadPrivKeyFromPemFile(file: string): void
137138
loadVerifyLocationsFromFile(file: string): void
@@ -173,6 +174,7 @@ export class Connection {
173174
*/
174175
static connect(serverName: string | undefined | null, scid: Uint8Array, localHost: HostPort, remoteHost: HostPort, config: Config): Connection
175176
static accept(scid: Uint8Array, odcid: Uint8Array | undefined | null, localHost: HostPort, remoteHost: HostPort, config: Config): Connection
177+
setKeylog(path: string): void
176178
setSession(session: Uint8Array): void
177179
recv(data: Uint8Array, recvInfo: RecvInfo): number
178180
/**

shell.nix

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ mkShell {
1111
rustc
1212
cargo
1313
cmake
14+
# Rust bindgen hook (necessary to build boring)
15+
rustPlatform.bindgenHook
16+
jetbrains.webstorm
1417
];
1518
# Don't set rpath for native addons
1619
NIX_DONT_SET_RPATH = true;
1720
NIX_NO_SELF_RPATH = true;
21+
RUST_SRC_PATH = "${rustPlatform.rustLibSrc}";
1822
shellHook = ''
1923
echo "Entering $(npm pkg get name)"
2024
set -o allexport

src/QUICClient.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ConnectionId, ConnectionIdString, Crypto, Host, Hostname, Port } from './types';
22
import type { Header, Config, Connection } from './native/types';
3+
import type { QUICConfig } from './config';
34
import Logger from '@matrixai/logger';
45
import {
56
CreateDestroy,
@@ -11,6 +12,7 @@ import { Quiche, quiche, Type } from './native';
1112
import * as utils from './utils';
1213
import * as errors from './errors';
1314
import * as events from './events';
15+
import { clientDefault } from './config';
1416
import QUICSocket from './QUICSocket';
1517
import QUICConnection from './QUICConnection';
1618
import QUICConnectionMap from './QUICConnectionMap';
@@ -56,6 +58,7 @@ class QUICClient extends EventTarget {
5658
socket,
5759
resolveHostname = utils.resolveHostname,
5860
logger = new Logger(`${this.name}`),
61+
config = {},
5962
}: {
6063
host: Host | Hostname,
6164
port: Port,
@@ -68,34 +71,15 @@ class QUICClient extends EventTarget {
6871
socket?: QUICSocket;
6972
resolveHostname?: (hostname: Hostname) => Host | PromiseLike<Host>;
7073
logger?: Logger;
74+
config?: Partial<QUICConfig>;
7175
}) {
72-
let isSocketShared: boolean;
76+
const quicConfig = {
77+
...clientDefault,
78+
...config
79+
};
7380
const scidBuffer = new ArrayBuffer(quiche.MAX_CONN_ID_LEN);
7481
await crypto.ops.randomBytes(scidBuffer);
7582
const scid = new QUICConnectionId(scidBuffer);
76-
const config = new quiche.Config();
77-
// TODO: disable this (because we still need to run with TLS)
78-
config.verifyPeer(false);
79-
config.grease(true);
80-
config.setMaxIdleTimeout(5000);
81-
config.setMaxRecvUdpPayloadSize(quiche.MAX_DATAGRAM_SIZE);
82-
config.setMaxSendUdpPayloadSize(quiche.MAX_DATAGRAM_SIZE);
83-
config.setInitialMaxData(10000000);
84-
config.setInitialMaxStreamDataBidiLocal(1000000);
85-
config.setInitialMaxStreamDataBidiRemote(1000000);
86-
config.setInitialMaxStreamsBidi(100);
87-
config.setInitialMaxStreamsUni(100);
88-
config.setDisableActiveMigration(true);
89-
config.setApplicationProtos(
90-
[
91-
'hq-interop',
92-
'hq-29',
93-
'hq-28',
94-
'hq-27',
95-
'http/0.9'
96-
]
97-
);
98-
config.enableEarlyData();
9983
let address = utils.buildAddress(host, port);
10084
logger.info(`Create ${this.name} to ${address}`);
10185
let [host_] = await utils.resolveHost(host, resolveHostname);
@@ -104,12 +88,28 @@ class QUICClient extends EventTarget {
10488
// in this case, 0.0.0.0 is resolved to 127.0.0.1 and :: and ::0 is
10589
// resolved to ::1
10690
host_ = utils.resolvesZeroIP(host_);
91+
const {
92+
p: errorP,
93+
rejectP: rejectErrorP
94+
} = utils.promise();
95+
const handleQUICSocketError = (e: events.QUICSocketErrorEvent) => {
96+
rejectErrorP(e.detail);
97+
};
98+
const handleConnectionError = (e: events.QUICConnectionErrorEvent) => {
99+
rejectErrorP(e.detail);
100+
};
101+
let isSocketShared: boolean;
107102
if (socket == null) {
108103
socket = new QUICSocket({
109104
crypto,
110105
resolveHostname,
111106
logger: logger.getChild(QUICSocket.name)
112107
});
108+
socket.addEventListener(
109+
'error',
110+
handleQUICSocketError,
111+
{ once: true }
112+
);
113113
isSocketShared = false;
114114
await socket.start({
115115
host: localHost,
@@ -159,28 +159,30 @@ class QUICClient extends EventTarget {
159159
host: host_,
160160
port
161161
},
162-
config,
162+
config: quicConfig,
163163
logger: logger.getChild(`${QUICConnection.name} ${scid}`)
164164
});
165-
166-
const {
167-
p: errorP,
168-
rejectP: rejectErrorP
169-
} = utils.promise();
170-
const handleConnectionError = (e: events.QUICConnectionErrorEvent) => {
171-
rejectErrorP(e.detail);
172-
};
173165
connection.addEventListener(
174166
'error',
175167
handleConnectionError,
176168
{ once: true }
177169
);
170+
console.log('CLIENT TRIGGER SEND');
178171
// This will not raise an error
179172
await connection.send();
180-
181173
// This will wait to be established, while also rejecting on error
182-
await Promise.race([connection.establishedP, errorP]);
174+
try {
175+
await Promise.race([connection.establishedP, errorP]);
176+
} catch (e) {
177+
if (!isSocketShared) {
178+
// Stop our own socket
179+
await socket.stop();
180+
}
181+
throw e;
182+
}
183183

184+
// Remove the temporary socket error handler
185+
socket.removeEventListener('error', handleQUICSocketError);
184186
// Remove the temporary connection error handler
185187
connection.removeEventListener('error', handleConnectionError);
186188

src/QUICConnection.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import type QUICSocket from './QUICSocket';
22
import type QUICConnectionMap from './QUICConnectionMap';
33
import type QUICConnectionId from './QUICConnectionId';
4+
5+
// This is specialized type
6+
import type { QUICConfig } from './config';
7+
8+
49
import type { Host, ConnectionId, Port, StreamId, RemoteInfo } from './types';
510
import type { Config, Connection, SendInfo, ConnectionErrorCode } from './native/types';
611
import {
@@ -15,6 +20,7 @@ import { quiche } from './native';
1520
import * as events from './events';
1621
import * as utils from './utils';
1722
import * as errors from './errors';
23+
import { buildQuicheConfig } from './config';
1824

1925
/**
2026
* Think of this as equivalent to `net.Socket`.
@@ -33,7 +39,6 @@ class QUICConnection extends EventTarget {
3339
public readonly connectionId: QUICConnectionId;
3440
public readonly type: 'client' | 'server';
3541

36-
3742
public conn: Connection;
3843
public connectionMap: QUICConnectionMap;
3944
public streamMap: Map<StreamId, QUICStream> = new Map();
@@ -95,10 +100,11 @@ class QUICConnection extends EventTarget {
95100
scid: QUICConnectionId;
96101
socket: QUICSocket;
97102
remoteInfo: RemoteInfo;
98-
config: Config;
103+
config: QUICConfig;
99104
logger?: Logger;
100105
}) {
101106
logger.info(`Connect ${this.name}`);
107+
const quicheConfig = buildQuicheConfig(config);
102108
const conn = quiche.Connection.connect(
103109
null,
104110
scid,
@@ -110,8 +116,12 @@ class QUICConnection extends EventTarget {
110116
host: remoteInfo.host,
111117
port: remoteInfo.port,
112118
},
113-
config
119+
quicheConfig
114120
);
121+
// This will output to the log keys file path
122+
if (config.logKeys != null) {
123+
conn.setKeylog(config.logKeys);
124+
}
115125
const connection = new this({
116126
type: 'client',
117127
conn,
@@ -140,10 +150,11 @@ class QUICConnection extends EventTarget {
140150
dcid: QUICConnectionId;
141151
socket: QUICSocket;
142152
remoteInfo: RemoteInfo;
143-
config: Config;
153+
config: QUICConfig;
144154
logger?: Logger;
145155
}): Promise<QUICConnection> {
146156
logger.info(`Accept ${this.name}`);
157+
const quicheConfig = buildQuicheConfig(config);
147158
const conn = quiche.Connection.accept(
148159
scid,
149160
dcid,
@@ -155,8 +166,12 @@ class QUICConnection extends EventTarget {
155166
host: remoteInfo.host,
156167
port: remoteInfo.port,
157168
},
158-
config
169+
quicheConfig
159170
);
171+
// This will output to the log keys file path
172+
if (config.logKeys != null) {
173+
conn.setKeylog(config.logKeys);
174+
}
160175
const connection = new this({
161176
type: 'server',
162177
conn,
@@ -212,6 +227,13 @@ class QUICConnection extends EventTarget {
212227

213228
}
214229

230+
// Immediately call this after construction
231+
// if you want to pass the key log to something
232+
// note that you must close the file descriptor afterwards
233+
public setKeylog(path) {
234+
this.conn.setKeylog(path);
235+
}
236+
215237
public get remoteHost() {
216238
return this._remoteHost;
217239
}
@@ -290,6 +312,8 @@ class QUICConnection extends EventTarget {
290312
*/
291313
@ready(new errors.ErrorQUICConnectionDestroyed(), false, ['destroying'])
292314
public async recv(data: Uint8Array, remoteInfo: RemoteInfo) {
315+
console.log('RECV CALLED');
316+
293317
try {
294318
if (this.conn.isClosed()) {
295319
if (this.resolveCloseP != null) this.resolveCloseP();
@@ -372,6 +396,8 @@ class QUICConnection extends EventTarget {
372396
this.conn.isDraining()
373397
)
374398
) {
399+
400+
console.log('CALLING DESTROY 2');
375401
await this.destroy();
376402
}
377403
}
@@ -396,10 +422,12 @@ class QUICConnection extends EventTarget {
396422
*/
397423
@ready(new errors.ErrorQUICConnectionDestroyed(), false, ['destroying'])
398424
public async send(): Promise<void> {
425+
426+
console.log('SEND CALLED');
427+
399428
try {
400429
if (this.conn.isClosed()) {
401430

402-
// console.log('I AM RESOLVING THE CLOSE');
403431

404432
if (this.resolveCloseP != null) this.resolveCloseP();
405433
return;
@@ -443,6 +471,7 @@ class QUICConnection extends EventTarget {
443471
try {
444472

445473
// console.log('ATTEMPTING SEND', sendBuffer, 0, sendLength, sendInfo.to.port, sendInfo.to.host);
474+
console.log('SEND UDP PACKET');
446475

447476
await this.socket.send(
448477
sendBuffer,
@@ -465,6 +494,9 @@ class QUICConnection extends EventTarget {
465494
this[status] !== 'destroying' &&
466495
(this.conn.isClosed() || this.conn.isDraining())
467496
) {
497+
498+
console.log('CALLING DESTROY');
499+
468500
await this.destroy();
469501
} else if (
470502
this[status] === 'destroying' &&
@@ -550,6 +582,13 @@ class QUICConnection extends EventTarget {
550582
// During construction, this ends up being null
551583
const time = this.conn.timeout();
552584

585+
586+
// I think...
587+
// if we have a null timeout here
588+
// AND we are also closed or is draining
589+
// then we can emit a timeout error
590+
591+
553592
// On the receive
554593
// this is called again
555594
// the result is 5000 ms
@@ -599,19 +638,25 @@ class QUICConnection extends EventTarget {
599638
// console.log('resumed', this.conn.isResumed());
600639

601640
// Trigger a send, this will also set the timeout again at the end
641+
642+
console.log('TIMEOUT TRIGGER SEND');
643+
602644
await this.send();
603645
},
604646
time
605647
);
606648
} else {
607-
608649
// console.log('Clearing the timeout');
609-
610650
clearTimeout(this.timer);
611651
delete this.timer;
652+
if (this.conn.isClosed() || this.conn.isDraining()) {
653+
this.dispatchEvent(
654+
new events.QUICConnectionErrorEvent({
655+
detail: new errors.ErrorQUICConnectionTimeout()
656+
})
657+
);
658+
}
612659
}
613-
614-
// console.groupEnd();
615660
}
616661
}
617662

0 commit comments

Comments
 (0)