Skip to content

Commit

Permalink
Ping service
Browse files Browse the repository at this point in the history
  • Loading branch information
erwin-kok committed Aug 12, 2023
1 parent 3edd4d1 commit 52e6449
Show file tree
Hide file tree
Showing 19 changed files with 305 additions and 153 deletions.
20 changes: 0 additions & 20 deletions .run/ApplicationKt.run.xml

This file was deleted.

151 changes: 76 additions & 75 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@ to each other. For this to work a few challenges have to be solved:
- How do the nodes know and find each other? In a client-server communication, the client established the connection to
the server, and the server is known by some pre-defined ip address (or addresses). In a P2P mesh network how to know
the ip address of the client? Potentially, the client is roaming which means its ip address can change over time.
**In libp2p this is solved by mDNS, DHT/Kademlia, and more**.
**In libp2p this is solved by mDNS, DHT/Kademlia, and more**.

- How can a node directly connect to another node? In client-server communication, the client initiates a connection to
the server. This is an outbound connection. However, in a P2P connection, a node connects directly to another node,
which is an inbound connection. Inbound connections are not always possible. Think of security (the router rejects
inbound connection attempts), but also think of NAT devices (what is the correct ip address? A node might know a
different ip address of itself than the outside world is able to connect to) **In libp2p this is solved by autonat,
holepunching, relay-service, and more**.
different ip address of itself than the outside world is able to connect to) **In libp2p this is solved by autonat,
holepunching, relay-service, and more**.

- Which wire protocol to use? In a client-server network the used protocol is obvious: the server dictates the protocol
to be used, or else it rejects the connection. A server can easily support the current protocol version while also
Expand All @@ -49,44 +49,44 @@ See for an in-depth description of libp2p, please see: https://libp2p.io/
## Features

- Multiformats. See my other repo: https://github.com/erwin-kok/multiformat
- [X] multiaddr
- [X] multibase
- [X] multicodec
- [X] multihash
- [X] multistream-select

- Crypto
- [X] ED25519
- [X] ECDSA
- [X] SECp256k1
- [X] RSA
- [X] multiaddr
- [X] multibase
- [X] multicodec
- [X] multihash
- [X] multistream-select

- Crypto
- [X] ED25519
- [X] ECDSA
- [X] SECp256k1
- [X] RSA

- Transports
- [X] Tcp
- [ ] Quic (planned)
- [X] Tcp
- [ ] Quic (planned)

- Muxers
- [X] Mplex
- [ ] Yamux (planned)
- [ ] Quic (planned)
- [X] Mplex
- [ ] Yamux (planned)
- [ ] Quic (planned)

- Security
- [X] Noise
- [ ] Tls (planned)
- [ ] Quic (planned)
- [X] Noise
- [ ] Tls (planned)
- [ ] Quic (planned)

- Protocols
- [ ] Identify (planned)
- [ ] Ping (planned)
- [ ] DHT/Kademlia (planned)
- [ ] pubsub (planned)
- [ ] Identify (planned)
- [ ] Ping (planned)
- [ ] DHT/Kademlia (planned)
- [ ] pubsub (planned)

- Peer discovery
- [ ] mDNS (planned)
- [ ] DHT/Kademlia (planned)
- [ ] mDNS (planned)
- [ ] DHT/Kademlia (planned)

- Datastore
- [X] RocksDB
- [X] RocksDB

## Getting started

Expand All @@ -104,78 +104,79 @@ dependencies {
}
```

`libp2p-core` is mandatory, other dependencies are optional depending on your needs. For example, if you need tcp
`libp2p-core` is mandatory, other dependencies are optional depending on your needs. For example, if you need tcp
transport, include `libp2p-transport-tcp`, or if you want the mplex muxer include `libp2p-muxer-plex`.

In your code, first create a host:

```kotlin
val hostBuilder = host {
identity(localIdentity)
muxers {
mplex()
}
securityTransport {
noise()
}
transports {
tcp()
}
peerstore {
gcInterval = 1.hours
keyStore {
password = "APasswordThatIsAtLeast20CharactersLong"
dek {
salt = "W/SC6fnZfBIWdeAD3l+ClLpQtfICEtn+KYTUhfKq6d7l"
}
}
}
swarm {
dialTimeout = 10.minutes
listenAddresses {
multiAddress("/ip4/0.0.0.0/tcp/10333")
}
identity(localIdentity)
muxers {
mplex()
}
securityTransport {
noise()
}
transports {
tcp()
}
peerstore {
gcInterval = 1.hours
keyStore {
password = "APasswordThatIsAtLeast20CharactersLong"
dek {
salt = "W/SC6fnZfBIWdeAD3l+ClLpQtfICEtn+KYTUhfKq6d7l"
}
datastore(datastore)
}
}
swarm {
dialTimeout = 10.minutes
listenAddresses {
multiAddress("/ip4/0.0.0.0/tcp/10333")
}
}
datastore(datastore)
}

val host = hostBuilder.build(scope)
.getOrElse {
logger.error { "The following errors occurred while creating the host: ${errorMessage(it)}" }
return@runBlocking
}
val host = hostBuilder.build(scope)
.getOrElse {
logger.error { "The following errors occurred while creating the host: ${errorMessage(it)}" }
return@runBlocking
}
```

The layout is hopefully clear: for example, the code above will use `tcp` as a transport, `noise` for security and
The layout is hopefully clear: for example, the code above will use `tcp` as a transport, `noise` for security and
`mplex` as a muxer.

Then you can add a handler:

```kotlin
host.setStreamHandler(ProtocolId.of("/chat/1.0.0")) {
chatHandler(it)
}
chatHandler(it)
}
```
This means that if a peer connects and requests the `/chat/1.0.0` protocol, the corresponding handler will be called.

This means that if a peer connects and requests the `/chat/1.0.0` protocol, the corresponding handler will be called.

To call a peer and open a new stream, use the following code:

```kotlin
val stream = host.newStream(aPeerId, ProtocolId.of("/chat/1.0.0"))
.getOrElse {
logger.error { "Could not open chat stream with peer: ${errorMessage(it)}" }
return@runBlocking
}
chatHandler(stream)
.getOrElse {
logger.error { "Could not open chat stream with peer: ${errorMessage(it)}" }
return@runBlocking
}
chatHandler(stream)
```

This tries to connect to peer `aPeerId` and tries to open a new stream for protocol `/chat/1.0.0`. If it fails, it
This tries to connect to peer `aPeerId` and tries to open a new stream for protocol `/chat/1.0.0`. If it fails, it
returns if it succeeds it progresses to the chatHandler.

See also the example application in `app`.
See also the example application in `examples/chat`.

To use this sample app, start the application. It will create a new random LocalIdentity (key-pair) and logs the adres
on which it listens on the output:
To use this sample application, start the application. It will create a new random LocalIdentity (key-pair) and logs the
adres on which it listens on the output:

```shell
[main] o.e.l.a.ApplicationKt$main$1: Local addresses the Host listens on: /ip4/0.0.0.0/tcp/10333/p2p/12D3KooWDfaEJxpmjbFLb9wUakCd6Lo6LRntaV3drb4EaYZRtYuY
Expand All @@ -188,11 +189,11 @@ by using:
./chat -d /ip4/0.0.0.0/tcp/10333/p2p/12D3KooWDfaEJxpmjbFLb9wUakCd6Lo6LRntaV3drb4EaYZRtYuY
```

On both sides it should mention that a connection is established.
On both sides it should mention that a connection is established.

## Contact

If you want to contact me, please write an e-mail to: [erwin[email protected]](mailto:erwin-kok@gmx.com)
If you want to contact me, please write an e-mail to: "erwin (DOT) kok (AT) protonmail (DOT) com"

## Acknowledgements

Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Copyright (c) 2023 Erwin Kok. BSD-3-Clause license. See LICENSE file for more details.
package org.erwinkok.libp2p.app
package org.erwinkok.libp2p.examples.chat

import io.ktor.utils.io.writeFully
import kotlinx.coroutines.CoroutineExceptionHandler
Expand Down Expand Up @@ -90,15 +90,16 @@ fun main() {
chatHandler(it)
}

val localAddress = addPeerAddress(host, "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWBazTG3XWMMjZ86tHSt4H1dYUVF7za8EGbUu7SenFxUkC")
host.connect(localAddress)

val localAddress = addPeerAddress(host, "/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWRCFBtg6AGX9hLFvhtaUzDQGVJ6SK7fQ6VakuAEH6Bn1v")
val stream = host.newStream(localAddress.peerId, ProtocolId.of("/chat/1.0.0"))
.getOrElse {
logger.error { "Could not open chat stream with peer: ${errorMessage(it)}" }
host.close()
host.awaitClosed()
return@runBlocking
}
chatHandler(stream)
stream.close()

host.close()
host.awaitClosed()
Expand Down Expand Up @@ -128,6 +129,9 @@ private suspend fun chatHandler(stream: Stream) {
val message = String(bytes, 0, size).trim('\n')
logger.info { message }
stream.output.writeFully(bytes)
if (message == "/quit") {
break
}
}
}
}
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import mu.KotlinLogging
import org.erwinkok.libp2p.core.base.AwaitableClosable
import org.erwinkok.libp2p.core.event.EventBus
import org.erwinkok.libp2p.core.event.EvtLocalProtocolsUpdated
import org.erwinkok.libp2p.core.host.builder.HostConfig
import org.erwinkok.libp2p.core.network.Connectedness
import org.erwinkok.libp2p.core.network.InetMultiaddress
import org.erwinkok.libp2p.core.network.Network
import org.erwinkok.libp2p.core.network.Stream
import org.erwinkok.libp2p.core.peerstore.Peerstore
import org.erwinkok.libp2p.core.peerstore.Peerstore.Companion.TempAddrTTL
import org.erwinkok.libp2p.core.protocol.ping.PingService
import org.erwinkok.libp2p.core.record.AddressInfo
import org.erwinkok.multiformat.multistream.MultistreamMuxer
import org.erwinkok.multiformat.multistream.ProtocolId
Expand All @@ -31,19 +33,30 @@ private val logger = KotlinLogging.logger {}
class BasicHost(
val scope: CoroutineScope,
val localIdentity: LocalIdentity,
val hostConfig: HostConfig,
override val network: Network,
override val peerstore: Peerstore,
override val multistreamMuxer: MultistreamMuxer<Stream>,
override val eventBus: EventBus,
) : AwaitableClosable, Host {
private val _context = SupervisorJob(scope.coroutineContext[Job])

val pingService: PingService?

override val jobContext: Job
get() = _context

override val id: PeerId
get() = localIdentity.peerId

init {
pingService = if (hostConfig.enablePing) {
PingService(scope, this)
} else {
null
}
}

override fun setStreamHandler(protocolId: ProtocolId, handler: StreamHandler) {
multistreamMuxer.addHandler(protocolId) { protocol, stream ->
stream.setProtocol(protocol)
Expand All @@ -68,8 +81,6 @@ class BasicHost(
}

override suspend fun newStream(peerId: PeerId, protocols: Set<ProtocolId>): Result<Stream> {
connect(AddressInfo.fromPeerId(peerId))
.onFailure { return Err(it) }
val stream = network.newStream(peerId)
.getOrElse { return Err(it) }
val preferredProtocol = peerstore.firstSupportedProtocol(peerId, protocols)
Expand Down Expand Up @@ -113,8 +124,10 @@ class BasicHost(
}

override fun close() {
pingService?.close()
eventBus.close()
peerstore.close()
_context.cancel()
network.close()
_context.complete()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,12 @@ annotation class HostDsl

@HostDsl
class HostBuilder {
val reflections = Reflections("org.erwinkok")
private val reflections = Reflections("org.erwinkok")
val errors = CombinedError()
val config = HostConfig()

var enablePing by config::enablePing

@HostDsl
fun identity(localIdentity: LocalIdentity) {
if (config.localIdentity != null) {
Expand Down Expand Up @@ -166,6 +168,7 @@ class HostBuilder {
val host = BasicHost(
coroutineScope,
localIdentity,
config,
swarm,
peerstore,
multistreamMuxer,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class SwarmConfig {

class HostConfig {
var insecure = false
var enablePing = false
var localIdentity: LocalIdentity? = null
val peerstoreConfig = PeerstoreConfig()
val swarmConfig = SwarmConfig()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@

package org.erwinkok.libp2p.core.network

import org.erwinkok.libp2p.core.base.AwaitableClosable
import org.erwinkok.libp2p.core.host.PeerId
import org.erwinkok.libp2p.core.network.transport.Transport
import org.erwinkok.libp2p.core.peerstore.Peerstore
import org.erwinkok.libp2p.core.resourcemanager.ResourceManager
import org.erwinkok.result.Result

interface Network {
interface Network : AwaitableClosable {
val peerstore: Peerstore
val localPeerId: PeerId
val resourceManager: ResourceManager?
Expand Down
Loading

0 comments on commit 52e6449

Please sign in to comment.