Skip to content

Commit

Permalink
feat: optionally allow inbound sessions with unverified ENRs
Browse files Browse the repository at this point in the history
  • Loading branch information
acolytec3 committed May 19, 2022
1 parent 0488a7b commit 413ce5c
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 32 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"lint": "eslint --color --ext .ts src/",
"test": "yarn test:unit && yarn test:e2e",
"test:unit": "mocha 'test/unit/**/*.test.ts'",
"test:e2e": "mocha 'test/unit/**/*.test.ts'"
"test:e2e": "mocha 'test/e2e/**/*.test.ts'"
},
"pre-push": [
"lint"
Expand Down
1 change: 1 addition & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ export const defaultConfig: IDiscv5Config = {
lookupTimeout: 60 * 1000,
pingInterval: 300 * 1000,
enrUpdate: true,
allowUnverifiedSessions: false,
};
8 changes: 6 additions & 2 deletions src/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,11 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
/**
* Send TALKRESP message to requesting node
*/
public async sendTalkResp(remote: ENR | Multiaddr, requestId: RequestId, payload: Uint8Array): Promise<void> {
public async sendTalkResp(
remote: ENR | Multiaddr | INodeAddress,
requestId: RequestId,
payload: Uint8Array
): Promise<void> {
const msg = createTalkResponseMessage(requestId, payload);
const nodeAddr = getNodeAddress(createNodeContact(remote));
this.sendRpcResponse(nodeAddr, msg);
Expand All @@ -402,7 +406,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
/**
* Sends a PING request to a node
*/
private sendPing(enr: ENR): void {
public sendPing(enr: ENR): void {
this.sendRpcRequest({ contact: createNodeContact(enr), request: createPingMessage(this.enr.seq) });
}

Expand Down
11 changes: 8 additions & 3 deletions src/session/nodeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export type NodeContact =
nodeAddress: INodeAddress;
};

export function createNodeContact(input: ENR | Multiaddr): NodeContact {
export function createNodeContact(input: ENR | Multiaddr | INodeAddress): NodeContact {
if (Multiaddr.isMultiaddr(input)) {
const options = input.toOptions();
if (options.transport !== "udp") {
Expand All @@ -66,12 +66,17 @@ export function createNodeContact(input: ENR | Multiaddr): NodeContact {
nodeId,
},
};
} else {
} else if (input instanceof ENR) {
return {
type: INodeContactType.ENR,
enr: input,
};
}
} else
return {
type: INodeContactType.Raw,
publicKey: {} as IKeypair,
nodeAddress: input,
};
}

export function getNodeId(contact: NodeContact): NodeId {
Expand Down
50 changes: 27 additions & 23 deletions src/session/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -460,13 +460,10 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
}

/**
* Verifies a Node ENR to its observed address.
* If it fails, any associated session is also considered failed.
* If it succeeds, we notify the application
* Compares the ENR multiaddr to its observed address.
* Returns true if they match
*/
private verifyEnr(enr: ENR, nodeAddr: INodeAddress): boolean {
// If the ENR does not match the observed IP addresses,
// we consider the session failed.
const enrMultiaddr = enr.getLocationMultiaddr("udp");
return enr.nodeId === nodeAddr.nodeId && (enrMultiaddr?.equals(nodeAddr.socketAddr) ?? true);
}
Expand Down Expand Up @@ -510,28 +507,35 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
);

// Receiving an AuthResponse must give us an up-to-date view of the node ENR.
// Verify the ENR is valid
if (this.verifyEnr(enr, nodeAddr)) {
// Session is valid
// Notify the application
// Verify the ENR endpoint matches observed node address
const verified = this.verifyEnr(enr, nodeAddr);

// Drop session if invalid ENR and session service not configured to allow unverified sessions
if (!verified && !this.config.allowUnverifiedSessions) {
log("ENR contains invalid socket address. Dropping session with %o", nodeAddr);
return;
}

if (verified) {
// If ENR is valid, notify application in order to add to routing table
// The session established here are from WHOAREYOU packets that we sent.
// This occurs when a node established a connection with us.
this.emit("established", enr, ConnectionDirection.Incoming);

this.newSession(nodeAddr, session);

// decrypt the message
this.handleMessage(src, {
maskingIv: packet.maskingIv,
header: createHeader(
PacketType.Message,
encodeMessageAuthdata({ srcId: nodeAddr.nodeId }),
packet.header.nonce
),
message: packet.message,
messageAd: encodeChallengeData(packet.maskingIv, packet.header),
});
}

this.newSession(nodeAddr, session);

// decrypt the message
this.handleMessage(src, {
maskingIv: packet.maskingIv,
header: createHeader(
PacketType.Message,
encodeMessageAuthdata({ srcId: nodeAddr.nodeId }),
packet.header.nonce
),
message: packet.message,
messageAd: encodeChallengeData(packet.maskingIv, packet.header),
});
} catch (e) {
if ((e as Error).name === ERR_INVALID_SIG) {
log("Authentication header contained invalid signature. Ignoring packet from: %o", nodeAddr);
Expand Down
5 changes: 5 additions & 0 deletions src/session/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ export interface ISessionConfig {
* The maximum number of established sessions to maintain
*/
sessionCacheCapacity: number;
/**
* Allow sessions with unverified ENRs (i.e. either have no UDP endpoint specified or else reported
* UDP endpoint does not match observed socket address)
*/
allowUnverifiedSessions: boolean;
}

export enum RequestErrorType {
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/connect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,17 +66,17 @@ describe("discv5 integration test", function () {

// test a TALKRESP with no response
try {
await node0.discv5.sendTalkReq(node1.enr.nodeId, Buffer.from([0, 1, 2, 3]), "foo");
await node0.discv5.sendTalkReq(node1.enr, Buffer.from([0, 1, 2, 3]), "foo");
expect.fail("TALKREQ response should throw when no response is given");
} catch (e) {
}

// test a TALKRESP with a response
const expectedResp = Buffer.from([4, 5, 6, 7]);
node1.discv5.on("talkReqReceived", (nodeAddr, enr, request) => {
node1.discv5.sendTalkResp(nodeAddr.nodeId, request.id, expectedResp);
node1.discv5.sendTalkResp(nodeAddr, request.id, expectedResp);
});
const resp = await node0.discv5.sendTalkReq(node1.enr.nodeId, Buffer.from([0, 1, 2, 3]), "foo");
const resp = await node0.discv5.sendTalkReq(node1.enr, Buffer.from([0, 1, 2, 3]), "foo");
expect(resp).to.deep.equal(expectedResp);

});
Expand Down

0 comments on commit 413ce5c

Please sign in to comment.