Skip to content

Commit fe2e35e

Browse files
authored
fix: allow IPv4 and IPv6 to bind to the same port in dual-stack mode (#318)
1 parent 13f4637 commit fe2e35e

File tree

2 files changed

+171
-51
lines changed

2 files changed

+171
-51
lines changed

packages/discv5/src/transport/udp.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ async function openSocket(opts: MultiaddrObject): Promise<dgram.Socket> {
154154
recvBufferSize: 16 * MAX_PACKET_SIZE,
155155
sendBufferSize: MAX_PACKET_SIZE,
156156
type: opts.family === 4 ? "udp4" : "udp6",
157+
ipv6Only: opts.family === 6,
157158
});
158159
await new Promise((resolve) => socket.bind(opts.port, opts.host, resolve as () => void));
159160
return socket;

packages/discv5/test/unit/transport/udp.test.ts

Lines changed: 170 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -101,63 +101,182 @@ describe("UDP6 transport", () => {
101101
});
102102

103103
describe("UDP4+6 transport", () => {
104-
const address4 = "127.0.0.1";
105-
const address6 = "::1";
106-
const nodeIdA = bytesToHex(new Uint8Array(32).fill(1));
107-
const portA = 49523;
108-
const multiaddr4A = multiaddr(`/ip4/${address4}/udp/${portA}`);
109-
const multiaddr6A = multiaddr(`/ip6/${address6}/udp/${portA}`);
110-
const a = new UDPTransportService({ bindAddrs: { ip4: multiaddr4A, ip6: multiaddr6A }, nodeId: nodeIdA });
104+
context("with loopback addresses", () => {
105+
const address4 = "127.0.0.1";
106+
const address6 = "::1";
107+
const nodeIdA = bytesToHex(new Uint8Array(32).fill(1));
108+
const portA = 49523;
109+
const multiaddr4A = multiaddr(`/ip4/${address4}/udp/${portA}`);
110+
const multiaddr6A = multiaddr(`/ip6/${address6}/udp/${portA}`);
111+
const a = new UDPTransportService({ bindAddrs: { ip4: multiaddr4A, ip6: multiaddr6A }, nodeId: nodeIdA });
111112

112-
const nodeIdB = bytesToHex(new Uint8Array(32).fill(2));
113-
const portB = portA + 1;
114-
const multiaddr4B = multiaddr(`/ip4/${address4}/udp/${portB}`);
115-
const multiaddr6B = multiaddr(`/ip6/${address6}/udp/${portB}`);
116-
const b = new UDPTransportService({ bindAddrs: { ip4: multiaddr4B, ip6: multiaddr6B }, nodeId: nodeIdB });
113+
const nodeIdB = bytesToHex(new Uint8Array(32).fill(2));
114+
const portB = portA + 1;
115+
const multiaddr4B = multiaddr(`/ip4/${address4}/udp/${portB}`);
116+
const multiaddr6B = multiaddr(`/ip6/${address6}/udp/${portB}`);
117+
const b = new UDPTransportService({ bindAddrs: { ip4: multiaddr4B, ip6: multiaddr6B }, nodeId: nodeIdB });
117118

118-
before(async () => {
119-
await a.start();
120-
await b.start();
121-
});
119+
before(async () => {
120+
await a.start();
121+
await b.start();
122+
});
122123

123-
after(async () => {
124-
await a.stop();
125-
await b.stop();
124+
after(async () => {
125+
await a.stop();
126+
await b.stop();
127+
});
128+
129+
it("should send and receive messages", async () => {
130+
const messagePacket: IPacket = {
131+
maskingIv: new Uint8Array(MASKING_IV_SIZE),
132+
header: {
133+
protocolId: "discv5",
134+
version: 1,
135+
flag: PacketType.Message,
136+
nonce: new Uint8Array(NONCE_SIZE),
137+
authdataSize: 32,
138+
authdata: new Uint8Array(32).fill(2),
139+
},
140+
message: new Uint8Array(44).fill(1),
141+
};
142+
async function send(multiaddr: Multiaddr, nodeId: string, packet: IPacket): Promise<[Multiaddr, IPacket]> {
143+
const received = new Promise<[Multiaddr, IPacket]>((resolve) =>
144+
a.once("packet", (sender, packet) => resolve([sender, packet]))
145+
);
146+
await b.send(multiaddr, nodeId, packet);
147+
return await received;
148+
}
149+
{
150+
const [rSender, rPacket] = await send(multiaddr6A, nodeIdA, messagePacket);
151+
expect(rSender.toString()).to.deep.equal(multiaddr6B.toString());
152+
expect(rPacket.maskingIv).to.deep.equal(messagePacket.maskingIv);
153+
expect(rPacket.header).to.deep.equal(messagePacket.header);
154+
expect(rPacket.message).to.deep.equal(messagePacket.message);
155+
}
156+
{
157+
const [rSender, rPacket] = await send(multiaddr4A, nodeIdA, messagePacket);
158+
expect(rSender.toString()).to.deep.equal(multiaddr4B.toString());
159+
expect(rPacket.maskingIv).to.deep.equal(messagePacket.maskingIv);
160+
expect(rPacket.header).to.deep.equal(messagePacket.header);
161+
expect(rPacket.message).to.deep.equal(messagePacket.message);
162+
}
163+
});
126164
});
127165

128-
it("should send and receive messages", async () => {
129-
const messagePacket: IPacket = {
130-
maskingIv: new Uint8Array(MASKING_IV_SIZE),
131-
header: {
132-
protocolId: "discv5",
133-
version: 1,
134-
flag: PacketType.Message,
135-
nonce: new Uint8Array(NONCE_SIZE),
136-
authdataSize: 32,
137-
authdata: new Uint8Array(32).fill(2),
138-
},
139-
message: new Uint8Array(44).fill(1),
140-
};
141-
async function send(multiaddr: Multiaddr, nodeId: string, packet: IPacket): Promise<[Multiaddr, IPacket]> {
142-
const received = new Promise<[Multiaddr, IPacket]>((resolve) =>
166+
context("with wildcard addresses", () => {
167+
it("should bind to the same port on both IPv4 and IPv6", async () => {
168+
const nodeId = bytesToHex(new Uint8Array(32).fill(3));
169+
const port = 49525;
170+
const multiaddr4 = multiaddr(`/ip4/0.0.0.0/udp/${port}`);
171+
const multiaddr6 = multiaddr(`/ip6/::/udp/${port}`);
172+
173+
const transport = new UDPTransportService({
174+
bindAddrs: { ip4: multiaddr4, ip6: multiaddr6 },
175+
nodeId,
176+
});
177+
178+
await transport.start();
179+
expect(transport.bindAddrs).to.have.lengthOf(2);
180+
expect(transport.bindAddrs[0].toString()).to.equal(multiaddr4.toString());
181+
expect(transport.bindAddrs[1].toString()).to.equal(multiaddr6.toString());
182+
await transport.stop();
183+
});
184+
185+
it("should successfully communicate between dual-stack nodes on same port", async () => {
186+
const nodeIdA = bytesToHex(new Uint8Array(32).fill(4));
187+
const nodeIdB = bytesToHex(new Uint8Array(32).fill(5));
188+
const portA = 49526;
189+
const portB = 49527;
190+
191+
const multiaddr4A = multiaddr(`/ip4/127.0.0.1/udp/${portA}`);
192+
const multiaddr6A = multiaddr(`/ip6/::1/udp/${portA}`);
193+
const a = new UDPTransportService({
194+
bindAddrs: { ip4: multiaddr4A, ip6: multiaddr6A },
195+
nodeId: nodeIdA,
196+
});
197+
198+
const multiaddr4B = multiaddr(`/ip4/127.0.0.1/udp/${portB}`);
199+
const multiaddr6B = multiaddr(`/ip6/::1/udp/${portB}`);
200+
const b = new UDPTransportService({
201+
bindAddrs: { ip4: multiaddr4B, ip6: multiaddr6B },
202+
nodeId: nodeIdB,
203+
});
204+
205+
await a.start();
206+
await b.start();
207+
208+
const messagePacket: IPacket = {
209+
maskingIv: new Uint8Array(MASKING_IV_SIZE),
210+
header: {
211+
protocolId: "discv5",
212+
version: 1,
213+
flag: PacketType.Message,
214+
nonce: new Uint8Array(NONCE_SIZE),
215+
authdataSize: 32,
216+
authdata: new Uint8Array(32).fill(2),
217+
},
218+
message: new Uint8Array(44).fill(1),
219+
};
220+
221+
const receivedIPv4 = new Promise<[Multiaddr, IPacket]>((resolve) =>
222+
a.once("packet", (sender, packet) => resolve([sender, packet]))
223+
);
224+
await b.send(multiaddr4A, nodeIdA, messagePacket);
225+
const [senderIPv4] = await receivedIPv4;
226+
expect(senderIPv4.toString()).to.equal(multiaddr4B.toString());
227+
228+
const receivedIPv6 = new Promise<[Multiaddr, IPacket]>((resolve) =>
143229
a.once("packet", (sender, packet) => resolve([sender, packet]))
144230
);
145-
await b.send(multiaddr, nodeId, packet);
146-
return await received;
147-
}
148-
{
149-
const [rSender, rPacket] = await send(multiaddr6A, nodeIdA, messagePacket);
150-
expect(rSender.toString()).to.deep.equal(multiaddr6B.toString());
151-
expect(rPacket.maskingIv).to.deep.equal(messagePacket.maskingIv);
152-
expect(rPacket.header).to.deep.equal(messagePacket.header);
153-
expect(rPacket.message).to.deep.equal(messagePacket.message);
154-
}
155-
{
156-
const [rSender, rPacket] = await send(multiaddr4A, nodeIdA, messagePacket);
157-
expect(rSender.toString()).to.deep.equal(multiaddr4B.toString());
158-
expect(rPacket.maskingIv).to.deep.equal(messagePacket.maskingIv);
159-
expect(rPacket.header).to.deep.equal(messagePacket.header);
160-
expect(rPacket.message).to.deep.equal(messagePacket.message);
161-
}
231+
await b.send(multiaddr6A, nodeIdA, messagePacket);
232+
const [senderIPv6] = await receivedIPv6;
233+
expect(senderIPv6.toString()).to.equal(multiaddr6B.toString());
234+
235+
await a.stop();
236+
await b.stop();
237+
});
238+
239+
it("should handle multiple dual-stack nodes on different ports", async () => {
240+
const ports = [49528, 49529, 49530];
241+
const transports: UDPTransportService[] = [];
242+
243+
for (let i = 0; i < ports.length; i++) {
244+
const nodeId = bytesToHex(new Uint8Array(32).fill(10 + i));
245+
const multiaddr4 = multiaddr(`/ip4/127.0.0.1/udp/${ports[i]}`);
246+
const multiaddr6 = multiaddr(`/ip6/::1/udp/${ports[i]}`);
247+
const transport = new UDPTransportService({
248+
bindAddrs: { ip4: multiaddr4, ip6: multiaddr6 },
249+
nodeId,
250+
});
251+
transports.push(transport);
252+
}
253+
254+
await Promise.all(transports.map((t) => t.start()));
255+
256+
const messagePacket: IPacket = {
257+
maskingIv: new Uint8Array(MASKING_IV_SIZE),
258+
header: {
259+
protocolId: "discv5",
260+
version: 1,
261+
flag: PacketType.Message,
262+
nonce: new Uint8Array(NONCE_SIZE),
263+
authdataSize: 32,
264+
authdata: new Uint8Array(32).fill(2),
265+
},
266+
message: new Uint8Array(44).fill(1),
267+
};
268+
269+
const received = new Promise<boolean>((resolve) => {
270+
transports[1].once("packet", () => resolve(true));
271+
});
272+
273+
const targetMultiaddr = multiaddr(`/ip4/127.0.0.1/udp/${ports[1]}`);
274+
const targetNodeId = bytesToHex(new Uint8Array(32).fill(11));
275+
await transports[0].send(targetMultiaddr, targetNodeId, messagePacket);
276+
277+
expect(await received).to.be.true;
278+
279+
await Promise.all(transports.map((t) => t.stop()));
280+
});
162281
});
163282
});

0 commit comments

Comments
 (0)