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

More mdns fixes #368

Merged
merged 2 commits into from
Jun 12, 2024
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
91 changes: 81 additions & 10 deletions libp2p/src/main/kotlin/io/libp2p/discovery/MDnsDiscovery.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
package io.libp2p.discovery

import io.libp2p.core.Discoverer
import io.libp2p.core.Host
import io.libp2p.core.PeerId
import io.libp2p.core.PeerInfo
import io.libp2p.core.PeerListener
import io.libp2p.core.*
import io.libp2p.core.multiformats.Multiaddr
import io.libp2p.core.multiformats.MultiaddrComponent
import io.libp2p.core.multiformats.Protocol
import io.libp2p.discovery.mdns.AnswerListener
import io.libp2p.discovery.mdns.JmDNS
import io.libp2p.discovery.mdns.ServiceInfo
import io.libp2p.discovery.mdns.impl.DNSRecord
import io.libp2p.discovery.mdns.impl.constants.DNSRecordType
import java.net.Inet4Address
import java.net.Inet6Address
import java.net.InetAddress
import java.net.*
import java.util.*
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.ForkJoinPool
import java.util.stream.Collectors
import java.util.stream.Stream

class MDnsDiscovery(
private val host: Host,
Expand Down Expand Up @@ -61,6 +59,10 @@ class MDnsDiscovery(
newPeerFoundListeners.forEach { it(peerInfo) }
}

fun addHandler(h: PeerListener) {
newPeerFoundListeners += h
}

private fun ipfsDiscoveryInfo(): ServiceInfo {
return ServiceInfo.create(
serviceTag,
Expand All @@ -87,11 +89,80 @@ class MDnsDiscovery(
return Integer.parseInt(str)
}

/* /ip6/::/tcp/4001 should expand to the following for example:
"/ip6/0:0:0:0:0:0:0:1/udp/4001/quic"
"/ip4/50.116.48.246/tcp/4001"
"/ip4/127.0.0.1/tcp/4001"
"/ip6/2600:3c03:0:0:f03c:92ff:fee7:bc1c/tcp/4001"
"/ip6/0:0:0:0:0:0:0:1/tcp/4001"
"/ip4/50.116.48.246/udp/4001/quic"
"/ip4/127.0.0.1/udp/4001/quic"
"/ip6/2600:3c03:0:0:f03c:92ff:fee7:bc1c/udp/4001/quic"
*/
fun expandWildcardAddresses(addr: Multiaddr): List<Multiaddr> {
// Do not include /p2p or /ipfs components which are superfluous here
if (!isWildcard(addr)) {
return java.util.List.of(
Multiaddr(
addr.components
.stream()
.filter { c: MultiaddrComponent ->
(
c.protocol !== Protocol.P2P &&
c.protocol !== Protocol.IPFS
)
}
.collect(Collectors.toList())
)
)
}
if (addr.has(Protocol.IP4)) return listNetworkAddresses(false, addr)
return if (addr.has(Protocol.IP6)) listNetworkAddresses(true, addr) else emptyList()
}

fun listNetworkAddresses(includeIp6: Boolean, addr: Multiaddr): List<Multiaddr> {
return try {
Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
.flatMap { net: NetworkInterface ->
net.interfaceAddresses.stream()
.map { obj: InterfaceAddress -> obj.address }
.filter { ip: InetAddress? -> includeIp6 || ip is Inet4Address }
}
.map { ip: InetAddress ->
Multiaddr(
Stream.concat(
Stream.of(
MultiaddrComponent(
if (ip is Inet4Address) Protocol.IP4 else Protocol.IP6,
ip.address
)
),
addr.components.stream()
.filter { c: MultiaddrComponent ->
c.protocol !== Protocol.IP4 && c.protocol !== Protocol.IP6 && c.protocol !== Protocol.P2P && c.protocol !== Protocol.IPFS
}
)
.collect(Collectors.toList())
)
}
.collect(Collectors.toList())
} catch (e: SocketException) {
throw RuntimeException(e)
}
}

fun isWildcard(addr: Multiaddr): Boolean {
val s = addr.toString()
return s.contains("/::/") || s.contains("/0:0:0:0/")
}

private fun ip4Addresses() = ipAddresses(Protocol.IP4, Inet4Address::class.java)
private fun ip6Addresses() = ipAddresses(Protocol.IP6, Inet6Address::class.java)

private fun <R> ipAddresses(protocol: Protocol, klass: Class<R>): List<R> {
return host.listenAddresses().map {
return host.listenAddresses().flatMap {
expandWildcardAddresses(it)
}.map {
it.getFirstComponent(protocol)
}.filterNotNull().map {
InetAddress.getByAddress(localhost.hostName, it.value)
Expand All @@ -112,7 +183,7 @@ class MDnsDiscovery(
val aRecords = answers.filter { DNSRecordType.TYPE_A.equals(it.recordType) }
val aaaaRecords = answers.filter { DNSRecordType.TYPE_AAAA.equals(it.recordType) }

if (txtRecord == null || srvRecord == null || aRecords.isEmpty()) {
if (txtRecord == null || srvRecord == null || (aRecords.isEmpty() && aaaaRecords.isEmpty())) {
return // incomplete answers
}

Expand Down
23 changes: 23 additions & 0 deletions libp2p/src/test/kotlin/io/libp2p/discovery/MDnsDiscoveryTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.libp2p.core.multiformats.Multiaddr
import io.libp2p.crypto.keys.generateEcdsaKeyPair
import io.libp2p.tools.NullHost
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import java.util.concurrent.TimeUnit

Expand Down Expand Up @@ -90,6 +91,28 @@ class MDnsDiscoveryTest {
assertEquals(host.listenAddresses().size, peerInfo?.addresses?.size)
}

@Test
fun `start discovery and listen for self ipv6`() {
var peerInfo: PeerInfo? = null
val discoverer = MDnsDiscovery(hostIpv6, testServiceTag)

discoverer.newPeerFoundListeners += {
peerInfo = it
}

discoverer.start().get(1, TimeUnit.SECONDS)
for (i in 0..50) {
if (peerInfo != null) {
break
}
TimeUnit.MILLISECONDS.sleep(100)
}
discoverer.stop().get(5, TimeUnit.SECONDS)

assertEquals(hostIpv6.peerId, peerInfo?.peerId)
assertTrue(hostIpv6.listenAddresses().size <= peerInfo?.addresses?.size!!)
}

@Test
fun `start discovery and listen for other`() {
var peerInfo: PeerInfo? = null
Expand Down
Loading