Skip to content
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ playwright-report
.parcel-cache
.envrc
.tool-versions
db
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ⚠️ IMPORTANT ⚠️

# Please do not create a Pull Request for this repository

The contents of this repository are automatically synced from the parent [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples) so any changes made to the standalone repository will be lost after the next sync.

Please open a PR against [js-libp2p Examples](https://github.com/libp2p/js-libp2p-examples) instead.

## Contributing

Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are **greatly appreciated**.

1. Fork the [js-libp2p Examples Project](https://github.com/libp2p/js-libp2p-examples)
2. Create your Feature Branch (`git checkout -b feature/amazing-example`)
3. Commit your Changes (`git commit -a -m 'feat: add some amazing example'`)
4. Push to the Branch (`git push origin feature/amazing-example`)
5. Open a Pull Request
19 changes: 19 additions & 0 deletions examples/js-libp2p-example-auto-tls/.github/workflows/sync.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: pull

on:
workflow_dispatch

jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Pull from another repository
uses: ipfs-examples/actions-pull-directory-from-repo@main
with:
source-repo: libp2p/js-libp2p-examples
source-folder-path: examples/${{ github.event.repository.name }}
source-branch: main
target-branch: main
git-username: github-actions
git-email: github-actions@github.com
4 changes: 4 additions & 0 deletions examples/js-libp2p-example-auto-tls/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This project is dual licensed under MIT and Apache-2.0.

MIT: https://www.opensource.org/licenses/mit
Apache-2.0: https://www.apache.org/licenses/license-2.0
5 changes: 5 additions & 0 deletions examples/js-libp2p-example-auto-tls/LICENSE-APACHE
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
19 changes: 19 additions & 0 deletions examples/js-libp2p-example-auto-tls/LICENSE-MIT
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
The MIT License (MIT)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
295 changes: 295 additions & 0 deletions examples/js-libp2p-example-auto-tls/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,295 @@
# @libp2p/example-auto-tls

[![libp2p.io](https://img.shields.io/badge/project-libp2p-yellow.svg?style=flat-square)](http://libp2p.io/)
[![Discuss](https://img.shields.io/discourse/https/discuss.libp2p.io/posts.svg?style=flat-square)](https://discuss.libp2p.io)
[![codecov](https://img.shields.io/codecov/c/github/libp2p/js-libp2p-examples.svg?style=flat-square)](https://codecov.io/gh/libp2p/js-libp2p-examples)
[![CI](https://img.shields.io/github/actions/workflow/status/libp2p/js-libp2p-examples/ci.yml?branch=main\&style=flat-square)](https://github.com/libp2p/js-libp2p-examples/actions/workflows/ci.yml?query=branch%3Amain)

> How to get a TLS certificate automatically

[Interplanetary Shipyard](https://ipshipyard.com/) operates a public-good DNS
service that will answer [Acme DNS-01 Challenges](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge)
on behalf of any Internet user.

This means that we can use a service such as [Let's Encrypt](https://letsencrypt.org/)
to generate certificates we can use to upgrade our WebSocket transport listeners
to a Secure WebSocket version automatically.

## AutoTLS flow

The steps for obtaining a TLS certificate are:

1. Have a publicly routable listening address
2. Claim the `*.<peerID>.libp2p.direct` domain name by `POST`ing a message to [register.libp2p.direct](https://github.com/ipshipyard/p2p-forge?tab=readme-ov-file#submitting-challenge-records)
3. Contact an ACME provider to perform the [DNS-01](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) challenge and generate a certificate
4. Add HTTPS listeners to any relevant transports


## Setup

1. Clone this repo then install the dependencies of this example with `npm install`

## Instructions

The requirements for using `libp2p.direct` are:

1. A publicly routable socket address
2. A WebSocket or TCP listener
3. The [identify](https://www.npmjs.com/package/@libp2p/identify) protocol

ACME services normally have quite restrictive rate limits, so we'll configure a
a persistent datastore and a keychain so we can reuse any generated
certificates, their private keys and the PeerID it's tied to.

Let's configure the relevant modules:

```js
import { noise } from '@chainsafe/libp2p-noise'
import { yamux } from '@chainsafe/libp2p-yamux'
import { webSockets } from '@libp2p/websockets'
import { createLibp2p } from 'libp2p'
import { autoTLS } from '@libp2p/auto-tls'
import { identify, identifyPush } from '@libp2p/identify'
import { keychain } from '@libp2p/keychain'
import { LevelDatastore } from 'datastore-level'
import { loadOrCreateSelfKey } from '@libp2p/config'

const datastore = new LevelDatastore('./db')
await datastore.open()

const privateKey = await loadOrCreateSelfKey(datastore)

const libp2p = await createLibp2p({
datastore,
privateKey,
addresses: {
listen: [
'/ip4/0.0.0.0/tcp/0/ws',
'/ip6/::/tcp/0/ws'
]
},
transports: [
webSockets()
],
connectionEncrypters: [
noise()
],
streamMuxers: [
yamux()
],
services: {
autoTLS: autoTLS(),
identify: identify(),
identifyPush: identifyPush(),
keychain: keychain()
}
})
```

> [!TIP]
> If you want to experiment without hitting ACME service rate limits, you can
> set the `acmeDirectory` to a staging address, though be aware that any
> certificates generated will be self-signed:
>
> ```TypeScript
> const libp2p = await createLibp2p({
> // other config
> services: {
> autoTLS: autoTLS({
> acmeDirectory: 'https://acme-staging-v02.api.letsencrypt.org/directory'
> }),
> // other config
> }
> })
> ```

## Getting a publicly routable address

If we start the node now, we'll notice that nothing happens. The reason is that
we don't have a publicly routable address.

### Manual configuration

If you know you have a publicly routable address, for example if you are
deploying your app on a server or you have manually configured port forwarding
on your router then you can add your transport addresses to the `appendAnnounce`
config key:

> [!IMPORTANT]
> Multiaddrs added to `announce` or `appendAnnounce` will automatically be
> verified as publicly dialable where possible, though note that the domain name
> allocated by `libp2p.direct` will still need to be verified via AutoNAT or
> auto-confirmation - see the next section on confirming dialable addresses for
> more

```js
const libp2p = await createLibp2p({
addresses: {
appendAnnounce: [
'/ip4/123.123.123.123/tcp/1234/ws'
],
// other config
},
// other config
})
```

### Automatic configuration via UPnP

If you a home user and your router supports configuring port forwarding via
[UPnP](https://en.wikipedia.org/wiki/Universal_Plug_and_Play), you can use the
[@libp2p/upnp-nat](https://www.npmjs.com/package/@libp2p/upnp-nat) module to
automatically configure port forwarding for IPv4 and IPv6 networks:

> [!IMPORTANT]
> This will only work if your router supports configuring port forwarding via
> UPnP and also has it enabled.
>
> ISP provided routers sometimes do not and most ship with UPnP disabled by
> default - please check your router documentation for more information

```TypeScript
import { uPnPNAT } from '@libp2p/upnp-nat'

const libp2p = await createLibp2p({
services: {
upnp: uPnPNAT()
// other config
}
})
```

### Confirming dialable addresses

Designing distributed systems typically involves trying to trust other system
components as little as possible. Systems with a lot of trust tend to not be
[Byztantine fault](https://en.wikipedia.org/wiki/Byzantine_fault)-tolerant.

So far we've introduced two components that we are implicitly trusting. One is
`libp2p.direct` - we trust that it will configure the DNS records to answer the
ACME DNS-01 challenge correctly, and we trust that the external address reported
by our UPnP router is correct.

By default libp2p will not broadcast any public address until it has been
confirmed to be dialable.

We can skip this and explicitly trust `libp2p.direct` and our router by
auto-confirming the DNS mapping and the public IP address:

```TypeScript
import { uPnPNAT } from '@libp2p/upnp-nat'

const libp2p = await createLibp2p({
services: {
autoTLS: autoTLS({
// automatically mark *.<peerID>.libp2p.direct as routable
autoConfirmAddress: true
}),
upnp: uPnPNAT({
// automatically mark any detected socket address as routable
autoConfirmAddress: true
})
// other config
}
})
```

To not trust these actors and instead require confirmation from multiple peers
in different network segments that the addresses are, in fact dialable, we need
to configure [@libp2p/autonat](https://www.npmjs.com/package/@libp2p/autonat).

#### AutoNAT

This requires a few more system components. The rough flow here is:

1. Acquire network peers that speak the `/libp2p/autonat/1.0.0` protocol
2. Ask peers from a range of networks to dial us back on a specific address
3. Mark the address as reachable/not reachable after enough responses are received

To find network peers we need a peer routing system such as [@libp2p/kad-dht](https://www.npmjs.com/package/@libp2p/kad-dht) (One day we may be able to use a [lightweight HTTP alternative](https://github.com/ipfs/specs/pull/476)
but that day is not today).

So far we've only configured a WebSocket listener, but if we use the Amino
flavour of KAD-DHT (e.g. the public IPFS network), we have to be aware of
[the spread of supported transports](https://probelab.io/ipfs/amino/#dht-transport-distribution).

Because the WebSockets transport is not common, we need to add the [@libp2p/tcp](https://www.npmjs.com/package/@libp2p/tcp) transport to increase our likelihood of being able to
communicate with network peers.

Finally we need to also use the [@libp2p/bootstrap](https://www.npmjs.com/package/@libp2p/bootstrap)
module to connect to an initial set of peers that will let us start to fill our
routing table and perform queries:

```js
import { autoNAT } from '@libp2p/autonat'
import { bootstrap } from '@libp2p/bootstrap'
import { kadDHT, removePrivateAddressesMapper } from '@libp2p/kad-dht'
import { tcp } from '@libp2p/tcp'

const libp2p = await createLibp2p({
// other config
transports: [
// other config
tcp()
],
services: {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How did you decide which services to keep for each of these sections?

For example, shouldn't we also include identify here which is necessary for most of the other services to work?

Copy link
Member Author

@achingbrain achingbrain Dec 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was meant to be an additive thing, not replacing the existing config blocks.

It says // other config everywhere but I guess that's not obvious enough - do you think using a formatted diff instead of a js code block would be clearer?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I think adding a diff block would be better, runnable code is included, so should be a problem

autoNAT: autoNAT(),
aminoDHT: kadDHT({
protocol: '/ipfs/kad/1.0.0',
peerInfoMapper: removePrivateAddressesMapper
}),
bootstrap: bootstrap({
list: [
'/dnsaddr/bootstrap.libp2p.io/p2p/QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb',
'/dnsaddr/bootstrap.libp2p.io/p2p/QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt',
'/dnsaddr/va1.bootstrap.libp2p.io/p2p/12D3KooWKnDdG3iXw9eTFijk3EWSunZcFi54Zka4wmtqtt6rPxc8',
'/ip4/104.131.131.82/tcp/4001/p2p/QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ'
]
})
// other config
}
})
```

If you are running on your own network which has better support for varied
transports you may not need to add `@libp2p/tcp`.

## Putting it all together

Phew, well done making it this far.

If you are happy trusting the IP address assigned by your ISP and router, and
that `libp2p.direct` has configured DNS for you correctly, you can run the
working example in [./auto-confirm.js](./auto-confirm.js).

If you want to go the full trust-free route, please see the example in
[./trust-free.js](./trust-free.js).

After a little while you should see multiaddr(s) that include the
[Server name indication](https://en.wikipedia.org/wiki/Server_Name_Indication)
tuple:

```console
$ node ./trust-free.js
/ip4/[ip-address]/tcp/[port]/tls/sni/ip-address.base32-peer-id.libp2p.direct/ws/p2p/12D3Foo
```

## Need help?

- Read the [js-libp2p documentation](https://github.com/libp2p/js-libp2p/tree/main/doc)
- Check out the [js-libp2p API docs](https://libp2p.github.io/js-libp2p/)
- Check out the [general libp2p documentation](https://docs.libp2p.io) for tips, how-tos and more
- Read the [libp2p specs](https://github.com/libp2p/specs)
- Ask a question on the [js-libp2p discussion board](https://github.com/libp2p/js-libp2p/discussions)

## License

Licensed under either of

- Apache 2.0, ([LICENSE-APACHE](LICENSE-APACHE) / <http://www.apache.org/licenses/LICENSE-2.0>)
- MIT ([LICENSE-MIT](LICENSE-MIT) / <http://opensource.org/licenses/MIT>)

## Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Loading
Loading