Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: add IPv6 support #245

Merged
merged 6 commits into from
Jul 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 18 additions & 14 deletions src/libp2p/discv5.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { PeerId } from "@libp2p/interface-peer-id";
import { PeerDiscovery, PeerDiscoveryEvents, symbol as peerDiscoverySymbol } 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 @@ -20,11 +21,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 @@ -69,7 +73,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 @@ -122,19 +129,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