Skip to content

Commit fe4612a

Browse files
fix: allow dialling ip6 webtransport addresses (libp2p#60)
use valid rfc3986 ipv6 host syntax --------- Co-authored-by: achingbrain <[email protected]>
1 parent bae7952 commit fe4612a

File tree

5 files changed

+88
-30
lines changed

5 files changed

+88
-30
lines changed

.aegir.js

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,58 @@
1-
import { spawn, exec } from "child_process";
2-
import { existsSync } from "fs";
1+
import { spawn, exec } from 'child_process'
2+
import { existsSync } from 'fs'
3+
import defer from 'p-defer'
34

45
/** @type {import('aegir/types').PartialOptions} */
56
export default {
67
test: {
78
async before() {
8-
if (!existsSync("./go-libp2p-webtransport-server/main")) {
9+
if (!existsSync('./go-libp2p-webtransport-server/main')) {
910
await new Promise((resolve, reject) => {
1011
exec('go build -o main main.go',
11-
{ cwd: "./go-libp2p-webtransport-server" },
12+
{ cwd: './go-libp2p-webtransport-server' },
1213
(error, stdout, stderr) => {
1314
if (error) {
1415
reject(error)
15-
console.error(`exec error: ${error}`);
16-
return;
16+
console.error(`exec error: ${error}`)
17+
return
1718
}
1819
resolve()
19-
});
20+
})
2021
})
2122
}
2223

23-
const server = spawn('./main', [], { cwd: "./go-libp2p-webtransport-server", killSignal: "SIGINT" });
24+
const server = spawn('./main', [], { cwd: './go-libp2p-webtransport-server', killSignal: 'SIGINT' })
2425
server.stderr.on('data', (data) => {
25-
console.log(`stderr: ${data}`, typeof data);
26+
console.log('stderr:', data.toString())
27+
})
28+
const serverAddr = defer()
29+
const serverAddr6 = defer()
30+
31+
server.stdout.on('data', (buf) => {
32+
const data = buf.toString()
33+
34+
console.log('stdout:', data);
35+
if (data.includes('addr=/ip4')) {
36+
// Parse the addr out
37+
serverAddr.resolve(`/ip4${data.match(/addr=\/ip4(.*)/)[1]}`)
38+
}
39+
40+
if (data.includes('addr=/ip6')) {
41+
// Parse the addr out
42+
serverAddr6.resolve(`/ip6${data.match(/addr=\/ip6(.*)/)[1]}`)
43+
}
2644
})
27-
const serverAddr = await (new Promise((resolve => {
28-
server.stdout.on('data', (data) => {
29-
console.log(`stdout: ${data}`, typeof data);
30-
if (data.includes("addr=")) {
31-
// Parse the addr out
32-
resolve((data + "").match(/addr=([^\s]*)/)[1])
33-
}
34-
});
35-
})))
3645

3746
return {
3847
server,
3948
env: {
40-
serverAddr
49+
serverAddr: await serverAddr.promise,
50+
serverAddr6: await serverAddr6.promise
4151
}
4252
}
4353
},
4454
async after(_, { server }) {
45-
server.kill("SIGINT")
55+
server.kill('SIGINT')
4656
}
4757
},
4858
build: {

go-libp2p-webtransport-server/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ func main() {
2424
panic(err)
2525
}
2626

27+
err = h.Network().Listen(multiaddr.StringCast("/ip6/::1/udp/0/quic-v1/webtransport"))
28+
if err != nil {
29+
panic(err)
30+
}
31+
2732
h.SetStreamHandler("echo", func(s network.Stream) {
2833
io.Copy(s, s)
2934
s.Close()

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,8 @@
169169
},
170170
"devDependencies": {
171171
"aegir": "^38.1.7",
172-
"libp2p": "^0.43.2"
172+
"libp2p": "^0.43.2",
173+
"p-defer": "^4.0.0"
173174
},
174175
"browser": {
175176
"./dist/src/listener.js": "./dist/src/listener.browser.js"

src/index.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,23 @@ function parseMultiaddr (ma: Multiaddr): { url: string, certhashes: MultihashDig
186186
// eslint-disable-next-line complexity
187187
const { url, certhashes, remotePeer } = parts.reduce((state: { url: string, certhashes: MultihashDigest[], seenHost: boolean, seenPort: boolean, remotePeer?: PeerId }, [proto, value]) => {
188188
switch (proto) {
189-
case protocols('ip4').code:
190189
case protocols('ip6').code:
191-
case protocols('dns4').code:
190+
// @ts-expect-error - ts error on switch fallthrough
192191
case protocols('dns6').code:
192+
if (value?.includes(':') === true) {
193+
/**
194+
* This resolves cases where `new globalThis.WebTransport` fails to construct because of an invalid URL being passed.
195+
*
196+
* `new URL('https://::1:4001/blah')` will throw a `TypeError: Failed to construct 'URL': Invalid URL`
197+
* `new URL('https://[::1]:4001/blah')` is valid and will not.
198+
*
199+
* @see https://datatracker.ietf.org/doc/html/rfc3986#section-3.2.2
200+
*/
201+
value = `[${value}]`
202+
}
203+
// eslint-disable-next-line no-fallthrough
204+
case protocols('ip4').code:
205+
case protocols('dns4').code:
193206
if (state.seenHost || state.seenPort) {
194207
throw new Error('Invalid multiaddr, saw host and already saw the host or port')
195208
}

test/browser.ts

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,11 @@ declare global {
1515

1616
describe('libp2p-webtransport', () => {
1717
it('webtransport connects to go-libp2p', async () => {
18-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
19-
const maStr: string = process.env.serverAddr!
18+
if (process.env.serverAddr == null) {
19+
throw new Error('serverAddr not found')
20+
}
21+
22+
const maStr: string = process.env.serverAddr
2023
const ma = multiaddr(maStr)
2124
const node = await createLibp2p({
2225
transports: [webTransport()],
@@ -71,8 +74,11 @@ describe('libp2p-webtransport', () => {
7174
})
7275

7376
it('fails to connect without certhashes', async () => {
74-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
75-
const maStr: string = process.env.serverAddr!
77+
if (process.env.serverAddr == null) {
78+
throw new Error('serverAddr not found')
79+
}
80+
81+
const maStr: string = process.env.serverAddr
7682
const maStrNoCerthash: string = maStr.split('/certhash')[0]
7783
const maStrP2p = maStr.split('/p2p/')[1]
7884
const ma = multiaddr(maStrNoCerthash + '/p2p/' + maStrP2p)
@@ -89,10 +95,33 @@ describe('libp2p-webtransport', () => {
8995
await node.stop()
9096
})
9197

98+
it('connects to ipv6 addresses', async () => {
99+
if (process.env.serverAddr6 == null) {
100+
throw new Error('serverAddr6 not found')
101+
}
102+
103+
const ma = multiaddr(process.env.serverAddr6)
104+
const node = await createLibp2p({
105+
transports: [webTransport()],
106+
connectionEncryption: [noise()]
107+
})
108+
109+
await node.start()
110+
111+
// the address is unreachable but we can parse it correctly
112+
const stream = await node.dialProtocol(ma, '/ipfs/ping/1.0.0')
113+
stream.close()
114+
115+
await node.stop()
116+
})
117+
92118
it('Closes writes of streams after they have sunk a source', async () => {
93-
// This is the behavor of stream muxers: (see mplex, yamux and compliance tests: https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-stream-muxer-compliance-tests/src/close-test.ts)
94-
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
95-
const maStr: string = process.env.serverAddr!
119+
// This is the behavior of stream muxers: (see mplex, yamux and compliance tests: https://github.com/libp2p/js-libp2p-interfaces/blob/master/packages/interface-stream-muxer-compliance-tests/src/close-test.ts)
120+
if (process.env.serverAddr == null) {
121+
throw new Error('serverAddr not found')
122+
}
123+
124+
const maStr: string = process.env.serverAddr
96125
const ma = multiaddr(maStr)
97126
const node = await createLibp2p({
98127
transports: [webTransport()],

0 commit comments

Comments
 (0)