view mod_s2s_v6mesh/README.md @ 6478:bd785f524fd2

mod_s2s_v6mesh: Fix various bugs, s2s auth policies now work as expected
author Matthew Wild <mwild1@gmail.com>
date Wed, 18 Mar 2026 18:54:30 +0000
parents 9f43226c7de1
children 771944f2a7c7
line wrap: on
line source

---
labels:
- Stage-Alpha
summary: "Federate using stable IPv6 addresses encoded as hostnames, e.g. in mesh networks"
rockspec:
  build:
    modules:
      mod_s2s_v6mesh.base32: base32.lib.lua
---

## Overview

There are various virtual network overlay projects which provide peers with
stable IPv6 addresses, including [Yggdrasil](https://yggdrasil-network.github.io/),
[CJDNS](https://github.com/cjdelisle/cjdns/), and others.

One problem is that these systems are generally peer-to-peer, and there is
typically no standard DNS root for the private networks. Public DNS can be
used, but that adds a dependency on the public internet which can cause
problems, e.g. if the public internet becomes inaccessible.

The "obvious" solution is to skip domain names, and just federate between IP
addresses directly. Unfortunately this brings a lot of problems, such as
the inability for IP addresses to have subdomains, which are used extensively
in XMPP to allow hosting multiple services on a single IP address.

This module provides an alternative solution. For every possible IPv6 address,
it can generate an encoded hostname, which supports subdomains, and can be
resolved to an IPv6 address without any external DNS server infrastructure.

The domain names end with `.v6.alt`. The `.alt` TLD was defined by [RFC 9476](https://www.rfc-editor.org/rfc/rfc9476.html)
specifically for non-DNS use cases such as this. `.v6.alt` is our own scheme.

## Usage

For this module to be useful for federation:

- Both servers must have this module loaded
- Both servers must have stable IPv6 addresses (if the IP address changes, the
  hostname changes, and your XMPP identity changes - a new hostname is like
  starting fresh).

Although this module was designed with virtual/mesh networks in mind, it is
possible to use it between any two servers with IPv6 addresses (check the
section on connection security below if you are using it over unencrypted
networks such as the public internet).

Discover your hostname with the prosodyctl command (our example will use the
IP address `2a00:1098:3a0::1` but you should replace it with your own):

```
$ prosodyctl shell v6alt get_domain 2a00:1098:3a0::1
OK: Encoded hostname: fiabbgadu-e.v6.alt
```

You can then use this domain name in your config file, for example:

```lua
VirtualHost "fiabbgadu-e.v6.alt" -- Main host, for user accounts

Component "groups.fiabbgadu-e.v6.alt" "muc" -- MUC on a subdomain
```

### Connection security

By default, Prosody requires TLS for server-to-server connections. However, it
is not possible to obtain CA-issued certificates for `.v6.alt` domains. This
module disables certificate validation (it treats all certificates as trusted)
for all `v6.alt` domains where the IP address matches the domain name that the
remote server is claiming to be (the actual certificate contents are
irrelevant and never checked, so self-signed certificates are fine).

Additionally, if the IP is within one of the CIDR ranges listed in the
`s2s_v6mesh_secure_ranges` configuration option, then the module will tell
Prosody to disable TLS encryption for the connection.

**Warning:** Prosody will treat connections within any configured ranges as
already totally secure, and **TLS will be disabled**. Be *very* sure you trust
the underlying network's security layer before adding an IP range to this
list, and never add IPs that use the public internet. If you want similar
behaviour for other (non-`v6.alt`) domains, look at [mod_secure_interfaces](https://modules.prosody.im/mod_secure_interfaces)
and configure it with your mesh interface's address. If in doubt, just don't
set any secure ranges, and use self-signed certificates with TLS instead.

Finally, because of the above mechanisms in place, it is strongly recommended
to keep Prosody's default settings of `s2s_require_encryption = true` and
`s2s_secure_auth = true` so that connections not covered by this module are
always encrypted and authenticated properly.

### Connecting from clients

Note that although Prosody supports `.v6.alt` domains for federation when this
module is loaded, this domain scheme is not (currently?) supported by clients,
and you'll need to specify the connection IP manually in order to connect.

Your JID should always be entered as `<username>@<domain.v6.alt>`, and you
should specify the server's IPv6 address in your client's advanced connection
configuration screen.

### HTTP

Services that use HTTP such as file sharing, web clients, etc. will need to
use the IP address in the URL, as clients and browsers won't be able to
resolve the `.v6.alt` domain names. You will need to [configure Prosody's HTTP
server](https://prosody.im/doc/http) appropriately for your deployment, e.g.

```lua
http_external_url = "https://[2a00:1098:3a0::1]:5281"
```

## Configuration

  | Name                     | Default| Description
  |--------------------------|--------|-------------------------------------------------------------------------
  | s2s_v6mesh_secure_ranges | `{}`   | A list of IP ranges to treat as secure (bypassing TLS)

## Compatibility

  |Prosody-Version|Status
  |---------------|----------------------
  |trunk          | Works
  |13.0           | Works
  |0.12           | Does not work


## Developers

This section is for developers who want to encode/decode v6.alt addresses
in their own code.

### Encoding scheme

1. Take an IPv6 address as raw packed bytes
2. Encode it using base32 (RFC 4648), remove `====` padding from the end.
3. Lowercase the output
4. Find the first longest sequence of 'a' characters that is:
     1. Not at the start of the string (start scanning from second position)
     2. Two or more characters in length
     3. Not including the end character

     If found, replace this sequence with a hyphen (`-`).

5. Append `.v6.alt`

### Decoding scheme

1. Take a hostname ending with `.v6.alt`
2. Remove `.v6.alt`
3. Remove any '.' characters and all content before them (`foo.bar` -> `bar`)
4. If the string contains a hyphen, replace that hyphen with sufficient 'a' characters to make the string 26 characters in length. Strings with multiple hyphen characters are invalid and must be rejected.
5. Uppercase the string
6. Decode using base32

### Test vectors

| IP                                        | Domain                             |
|-------------------------------------------|------------------------------------|
| `2001:db8::1`                             | `eaaq3o-e.v6.alt`                  |
| `0200::1`                                 | `ai-e.v6.alt`                      |
| `200:cab8:deb4:ce60:4362:4492:e539:4b5`   | `aiamvog6wthgaq3cisjokoiewu.v6.alt`|
| `::1`                                     | `a-e.v6.alt`                       |
| `::`                                      | `a-a.v6.alt`                       | 
| `fe80::1`                                 | `72-e.v6.alt`                      |
| `ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff` | `77777777777777777777777774.v6.alt`|
| `1111:0000:0000:1111::1111`               | `ceiqaaaaaairc-rce.v6.alt`         |
| `1111:0000:0000:0000:1111::1111`          | `ceiq-eiraaaaaaarce.v6.alt`        |