Skip to content

Commit

Permalink
feat!: add IPv6 support (#245)
Browse files Browse the repository at this point in the history
  • Loading branch information
wemeetagain committed Jul 10, 2023
1 parent eca1613 commit 4ccc10f
Show file tree
Hide file tree
Showing 13 changed files with 383 additions and 101 deletions.
32 changes: 18 additions & 14 deletions src/libp2p/discv5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import {
} from "@libp2p/interface-peer-discovery";
import { PeerInfo } from "@libp2p/interface-peer-info";
import { CustomEvent, EventEmitter } from "@libp2p/interfaces/events";
import { multiaddr } from "@multiformats/multiaddr";
import { Multiaddr, multiaddr } from "@multiformats/multiaddr";

import { Discv5, ENRInput, SignableENRInput } from "../service/index.js";
import { ENR } from "../enr/index.js";
import { IDiscv5Config } from "../config/index.js";
import { MetricsRegister } from "../metrics.js";
import { BindAddrs } from "../transport/types.js";

// Default to 0ms between automatic searches
// 0ms is 'backwards compatible' with the prior behavior (always be searching)
Expand All @@ -24,11 +25,14 @@ export interface IDiscv5DiscoveryInputOptions extends Partial<IDiscv5Config> {
*/
enr: SignableENRInput;
/**
* The bind multiaddr for the discv5 UDP server
* The bind multiaddrs for the discv5 UDP server
*
* NOTE: This MUST be a udp multiaddr
* NOTE: These MUST be a udp multiaddrs
*/
bindAddr: string;
bindAddrs: {
ip4?: string;
ip6?: string;
};
/**
* Remote ENRs used to bootstrap the network
*/
Expand Down Expand Up @@ -73,7 +77,10 @@ export class Discv5Discovery extends EventEmitter<PeerDiscoveryEvents> implement
this.discv5 = Discv5.create({
enr: options.enr,
peerId: options.peerId,
multiaddr: multiaddr(options.bindAddr),
bindAddrs: {
ip4: options.bindAddrs.ip4 ? multiaddr(options.bindAddrs.ip4) : undefined,
ip6: options.bindAddrs.ip6 ? multiaddr(options.bindAddrs.ip6) : undefined,
} as BindAddrs,
config: options,
metricsRegistry: options.metricsRegistry,
});
Expand Down Expand Up @@ -126,19 +133,16 @@ export class Discv5Discovery extends EventEmitter<PeerDiscoveryEvents> implement
}

handleEnr = async (enr: ENR): Promise<void> => {
const multiaddrTCP = enr.getLocationMultiaddr("tcp");
if (!multiaddrTCP) {
return;
}
const multiaddrTCP4 = enr.getLocationMultiaddr("tcp4");
const multiaddrTCP6 = enr.getLocationMultiaddr("tcp6");
const multiaddrs: Multiaddr[] = [];
if (multiaddrTCP4) multiaddrs.push(multiaddrTCP4);
if (multiaddrTCP6) multiaddrs.push(multiaddrTCP6);
this.dispatchEvent(
new CustomEvent<PeerInfo>("peer", {
detail: {
id: await enr.peerId(),
multiaddrs: [
// TODO fix whatever type issue is happening here :(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
multiaddrTCP as any,
],
multiaddrs,
protocols: [],
},
})
Expand Down
28 changes: 16 additions & 12 deletions src/service/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { randomBytes } from "@libp2p/crypto";
import { Multiaddr } from "@multiformats/multiaddr";
import { PeerId } from "@libp2p/interface-peer-id";

import { ITransportService, UDPTransportService } from "../transport/index.js";
import { BindAddrs, IPMode, ITransportService, UDPTransportService } from "../transport/index.js";
import { MAX_PACKET_SIZE } from "../packet/index.js";
import { ConnectionDirection, RequestErrorType, SessionService } from "../session/index.js";
import { ENR, NodeId, MAX_RECORD_SIZE, createNodeId, SignableENR } from "../enr/index.js";
Expand Down Expand Up @@ -52,11 +52,12 @@ import {
} from "./types.js";
import { RateLimiter, RateLimiterOpts } from "../rateLimit/index.js";
import {
getSocketAddressOnENR,
multiaddrFromSocketAddress,
isEqualSocketAddress,
multiaddrToSocketAddress,
setSocketAddressOnENR,
getSocketAddressOnENR,
getSocketAddressMultiaddrOnENR,
} from "../util/ip.js";
import { createDiscv5Metrics, IDiscv5Metrics, MetricsRegister } from "../metrics.js";

Expand All @@ -80,7 +81,7 @@ const log = debug("discv5:service");
export interface IDiscv5CreateOptions {
enr: SignableENRInput;
peerId: PeerId;
multiaddr: Multiaddr;
bindAddrs: BindAddrs;
config?: Partial<IDiscv5Config>;
metricsRegistry?: MetricsRegister | null;
transport?: ITransportService;
Expand Down Expand Up @@ -164,6 +165,8 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {

private metrics?: IDiscv5Metrics;

private ipMode: IPMode;

/**
* Default constructor.
* @param sessionService the service managing sessions underneath.
Expand All @@ -188,6 +191,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
metrics.activeSessionCount.collect = () => metrics.activeSessionCount.set(discv5.sessionService.sessionsSize());
metrics.lookupCount.collect = () => metrics.lookupCount.set(this.nextLookupId - 1);
}
this.ipMode = this.sessionService.transport.ipMode;
}

/**
Expand All @@ -198,7 +202,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
* @param multiaddr The multiaddr which contains the network interface and port to which the UDP server binds
*/
public static create(opts: IDiscv5CreateOptions): Discv5 {
const { enr, peerId, multiaddr, config = {}, metricsRegistry, transport } = opts;
const { enr, peerId, bindAddrs, config = {}, metricsRegistry, transport } = opts;
const fullConfig = { ...defaultConfig, ...config };
const metrics = metricsRegistry ? createDiscv5Metrics(metricsRegistry) : undefined;
const keypair = createKeypairFromPeerId(peerId);
Expand All @@ -208,7 +212,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
fullConfig,
decodedEnr,
keypair,
transport ?? new UDPTransportService(multiaddr, decodedEnr.nodeId, rateLimiter)
transport ?? new UDPTransportService({ bindAddrs, nodeId: decodedEnr.nodeId, rateLimiter })
);
return new Discv5(fullConfig, sessionService, metrics);
}
Expand Down Expand Up @@ -287,8 +291,8 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
}
}

public get bindAddress(): Multiaddr {
return this.sessionService.transport.multiaddr;
public get bindAddrs(): Multiaddr[] {
return this.sessionService.transport.bindAddrs;
}

public get keypair(): IKeypair {
Expand Down Expand Up @@ -491,7 +495,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
*/
private sendLookup(lookupId: number, peer: NodeId, request: RequestMessage): void {
const enr = this.findEnr(peer);
if (!enr || !enr.getLocationMultiaddr("udp")) {
if (!enr || !getSocketAddressMultiaddrOnENR(enr, this.ipMode)) {
log("Lookup %s requested an unknown ENR or ENR w/o UDP", lookupId);
this.activeLookups.get(lookupId)?.onFailure(peer);
return;
Expand All @@ -513,7 +517,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
private sendRpcRequest(activeRequest: IActiveRequest): void {
this.activeRequests.set(activeRequest.request.id, activeRequest);

const nodeAddr = getNodeAddress(activeRequest.contact);
const nodeAddr = getNodeAddress(activeRequest.contact, this.ipMode);
log("Sending %s to node: %o", MessageType[activeRequest.request.type], nodeAddr);
try {
this.sessionService.sendRequest(activeRequest.contact, activeRequest.request);
Expand Down Expand Up @@ -707,7 +711,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
verified: boolean
): void => {
// Ignore sessions with unverified or non-contactable ENRs
if (!verified || !enr.getLocationMultiaddr("udp")) {
if (!verified || !getSocketAddressOnENR(enr, this.ipMode)) {
return;
}

Expand Down Expand Up @@ -853,7 +857,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
this.activeRequests.delete(response.id);

// Check that the responder matches the expected request
const requestNodeAddr = getNodeAddress(activeRequest.contact);
const requestNodeAddr = getNodeAddress(activeRequest.contact, this.ipMode);
if (requestNodeAddr.nodeId !== nodeAddr.nodeId || !requestNodeAddr.socketAddr.equals(nodeAddr.socketAddr)) {
log(
"Received a response from an unexpected address. Expected %o, received %o, request id: %s",
Expand Down Expand Up @@ -894,7 +898,7 @@ export class Discv5 extends (EventEmitter as { new (): Discv5EventEmitter }) {
const isWinningVote = this.addrVotes.addVote(nodeAddr.nodeId, message.addr);

if (isWinningVote) {
const currentAddr = getSocketAddressOnENR(this.enr);
const currentAddr = getSocketAddressOnENR(this.enr, this.ipMode);
const winningAddr = message.addr;
if (!currentAddr || !isEqualSocketAddress(currentAddr, winningAddr)) {
log("Local ENR (IP & UDP) updated: %s", isWinningVote);
Expand Down
6 changes: 4 additions & 2 deletions src/session/nodeInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Multiaddr, isMultiaddr } from "@multiformats/multiaddr";
import { peerIdFromString } from "@libp2p/peer-id";
import { createKeypairFromPeerId, IKeypair } from "../keypair/index.js";
import { ENR, NodeId, v4 } from "../enr/index.js";
import { IPMode } from "../transport/types.js";
import { getSocketAddressMultiaddrOnENR } from "../util/ip.js";

/** A representation of an unsigned contactable node. */
export interface INodeAddress {
Expand Down Expand Up @@ -88,10 +90,10 @@ export function getNodeId(contact: NodeContact): NodeId {
}
}

export function getNodeAddress(contact: NodeContact): INodeAddress {
export function getNodeAddress(contact: NodeContact, ipMode: IPMode): INodeAddress {
switch (contact.type) {
case INodeContactType.ENR: {
const socketAddr = contact.enr.getLocationMultiaddr("udp");
const socketAddr = getSocketAddressMultiaddrOnENR(contact.enr, ipMode);
if (!socketAddr) {
throw new Error("ENR has no udp multiaddr");
}
Expand Down
24 changes: 16 additions & 8 deletions src/session/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import StrictEventEmitter from "strict-event-emitter-types";
import debug from "debug";
import { Multiaddr } from "@multiformats/multiaddr";

import { ITransportService } from "../transport/index.js";
import { IPMode, ITransportService } from "../transport/index.js";
import {
PacketType,
IPacket,
Expand Down Expand Up @@ -42,6 +42,7 @@ import { getNodeAddress, INodeAddress, INodeContactType, nodeAddressToString, No
import LRUCache from "lru-cache";
import { TimeoutMap } from "../util/index.js";
import { IDiscv5Metrics } from "../metrics.js";
import { getSocketAddressMultiaddrOnENR } from "../util/ip.js";

const log = debug("discv5:sessionService");

Expand Down Expand Up @@ -134,6 +135,8 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
*/
private sessions: LRUCache<NodeAddressString, Session>;

private ipMode: IPMode;

constructor(config: ISessionConfig, enr: SignableENR, keypair: IKeypair, transport: ITransportService) {
super();

Expand All @@ -147,12 +150,13 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
this.transport = transport;

this.activeRequests = new TimeoutMap(config.requestTimeout, (k, v) =>
this.handleRequestTimeout(getNodeAddress(v.contact), v)
this.handleRequestTimeout(getNodeAddress(v.contact, this.ipMode), v)
);
this.activeRequestsNonceMapping = new Map();
this.pendingRequests = new Map();
this.activeChallenges = new LRUCache({ maxAge: config.requestTimeout * 2 });
this.sessions = new LRUCache({ maxAge: config.sessionTimeout, max: config.sessionCacheCapacity });
this.ipMode = this.transport.ipMode;
}

/**
Expand Down Expand Up @@ -186,10 +190,10 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
* Sends an RequestMessage to a node.
*/
public sendRequest(contact: NodeContact, request: RequestMessage): void {
const nodeAddr = getNodeAddress(contact);
const nodeAddr = getNodeAddress(contact, this.ipMode);
const nodeAddrStr = nodeAddressToString(nodeAddr);

if (nodeAddr.socketAddr.equals(this.transport.multiaddr)) {
if (this.transport.bindAddrs.some((bindAddr) => nodeAddr.socketAddr.equals(bindAddr))) {
log("Filtered request to self");
return;
}
Expand Down Expand Up @@ -482,8 +486,12 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
* Returns true if they match
*/
private verifyEnr(enr: ENR, nodeAddr: INodeAddress): boolean {
const enrMultiaddr = enr.getLocationMultiaddr("udp");
return enr.nodeId === nodeAddr.nodeId && (enrMultiaddr?.equals(nodeAddr.socketAddr) ?? true);
const enrMultiaddrIP4 = getSocketAddressMultiaddrOnENR(enr, { ...this.ipMode, ip6: false } as IPMode);
const enrMultiaddrIP6 = getSocketAddressMultiaddrOnENR(enr, { ...this.ipMode, ip4: false } as IPMode);
return (
enr.nodeId === nodeAddr.nodeId &&
(enrMultiaddrIP4?.equals(nodeAddr.socketAddr) ?? enrMultiaddrIP6?.equals(nodeAddr.socketAddr) ?? true)
);
}

/** Handle a message that contains an authentication header */
Expand Down Expand Up @@ -737,7 +745,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
* Inserts a request and associated authTag mapping
*/
private insertActiveRequest(requestCall: IRequestCall): void {
const nodeAddr = getNodeAddress(requestCall.contact);
const nodeAddr = getNodeAddress(requestCall.contact, this.ipMode);
const nodeAddrStr = nodeAddressToString(nodeAddr);
this.activeRequestsNonceMapping.set(requestCall.packet.header.nonce.toString("hex"), nodeAddr);
this.activeRequests.set(nodeAddrStr, requestCall);
Expand Down Expand Up @@ -815,7 +823,7 @@ export class SessionService extends (EventEmitter as { new (): StrictEventEmitte
// Fail the current request
this.emit("requestFailed", requestCall.request.id, error);

const nodeAddr = getNodeAddress(requestCall.contact);
const nodeAddr = getNodeAddress(requestCall.contact, this.ipMode);
this.failSession(nodeAddr, error, removeSession);
}

Expand Down
36 changes: 35 additions & 1 deletion src/transport/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import StrictEventEmitter from "strict-event-emitter-types";
import { Multiaddr } from "@multiformats/multiaddr";

import { IPacket } from "../packet/index.js";
import { BaseENR } from "../enr/enr.js";
import { SocketAddress } from "../util/ip.js";

export interface ISocketAddr {
port: number;
Expand All @@ -24,12 +26,44 @@ export interface ITransportEvents {
}
export type TransportEventEmitter = StrictEventEmitter<EventEmitter, ITransportEvents>;

export type IPMode =
| {
ip4: true;
ip6: false;
}
| {
ip4: false;
ip6: true;
}
| {
ip4: true;
ip6: true;
};

export type BindAddrs =
| {
ip4: Multiaddr;
ip6?: undefined;
}
| {
ip4?: undefined;
ip6: Multiaddr;
}
| {
ip4: Multiaddr;
ip6: Multiaddr;
};

export interface ITransportService extends TransportEventEmitter {
multiaddr: Multiaddr;
bindAddrs: Multiaddr[];
ipMode: IPMode;

start(): Promise<void>;
stop(): Promise<void>;
send(to: Multiaddr, toId: string, packet: IPacket): Promise<void>;

getContactableAddr(enr: BaseENR): SocketAddress | undefined;

/** Add 1 expected response of unknown length from IP for rate limiter */
addExpectedResponse?(ipAddress: string): void;
/** Remove 1 expected response from IP added with addExpectedResponse */
Expand Down
Loading

0 comments on commit 4ccc10f

Please sign in to comment.