make wireguard peers exchange peer information, so that a mesh can automagically get created without central 3rd party service
Find a file
2025-10-14 18:54:30 +02:00
src add link-local listen test script 2025-10-08 18:42:56 +02:00
Readme.md readme: specify json + \0 over tcp as transport 2025-10-14 18:54:30 +02:00

wireguard dynamic mesh (without central/3rd party servers)

This project aims to create a specification and implementation of a "wg-dynamic-mesh" protocol.
Using the protocol peers supporting the protocol can exchange endpoint addresses and general peer reachability, so the peers could create a (full) mesh network without requiring a relay node (but another node could be used as relay when peers can't reach each other )

ideas:

  • protocol running over an ipv6 linklocal ( fe80:: ) addresses inside the tunnel (addresses may be automatically generated using e.g the wireguard pubkey like fe80::<last 64 bits of pubkey> )
  • push or pull (or both) based (TBD), peer to peer connection (inside tunnel)
  • should also work with peers with don't implement the protocol (mobile devices which don't have an "app" for this protocol yet)
  • in protocol message containing list of peers identified by pubkey with endpoint address and (optionally) allowed ips (allowedIPs could be dangerous as it could be used to install (arbitrary) routes onto the host)

future (version2):

  • make selected nodes act as relay for cases where one peer has only ipv4 and another only has ipv6 (or those 2 peers can't reach eachother for any reason)
  • make selected nodes be "authorative" for a given prefix (i.e. node1 and its direct peers owned/operated by person1 is authorative for 2001:db8:0001::/48 and node2 and its peers by person2 has 2001:db8:0002::/48 ) where the central-ish node(s) of the specific person can be trusted with the specific prefix(es) and any subnets
  • ...

(api) spec / protocol version 1.0:

The messages are to be send over plain TCP on port 51820 (by default).
As encoding JSON MUST be used with any message containing the messageType key to identify the type of the message with the other keys and values as described below for that messageType.
The JSON objects MUST be seperated by a NUL byte (as thats an invalid byte for JSON)

a program implementing the protocol SHOULD attempt to initiate this protocol with

  • all peers of the wireguard interface OR
  • only a sub- or superset of the peers configured in an additional (optional) config file

during startup or shortly after a (configured) peer successfully made a handshake

establishing a connection / handshake:

SYN:

To start the initiating peer sends a SYN message containing the protocol string: WG-DYNAMIC-MESH and 'version' string with major and minor number("1.0")
(TODO: decide on protocol string)

the version should be the highest version of the protocol the peer supports

when a SYN gets received by a peer and the protocol string matches, it should reply with a SYNACK message.

example:

{
    "messageType": "SYN",
    "protocol": "WG-DYNAMIC-MESH",
    "version": "1.0"
}

SYNACK:

send as a reply to the SYN message containing the protocol string and version, while the version is the minimum of the peers version received by the SYN and the own highest supported protocol version

example:

{
    "messageType": "SYNACK",
    "protocol": "WG-DYNAMIC-MESH",
    "version": "1.0"
}

NAK (in handshake):

may be send by either peer after SYN or SYNACK if a peer is not be backwards compatible with peer's highest protocol version (e.g. only supports 2.0 but not 1.0). Not implementing backwards compatibility should only be done if previous versions of the protocol had serious (security) issues.

attributes:

  • reason (optional, string): reason why the connection got closed (useful for debugging)

after sending or receiving this message the connection SHOULD get closed and may be reattempted at a later time (but without changes it is likely that another NAK will get received again on the new connection)

example:

{
    "messageType": "NAK",
    "reason": "invalid message"
}

session

SELFINFO:

send after the handshake or whenver one of the attributes changes(optional, the only thing that even can change is the addresses)

the message contains:

  • the senders wireguard pubkey (base64 encoded as string),
  • addresses: (list of strings) of the addresses on the interface (TBD),
  • listenport: (int) of its wireguard interface,
  • hostname: (string) optional hostname with A and/or AAAA records where the peer is supposed to be reachable at
  • readonly: (bool) true if (for some reason) the communicating process can't (or isn't configured to) write the required configs to the wireguard interface, but can still read the required data

the receiving peer MUST verify that the connection came from the peer with the pubkey that got specified, if not it should reply with a NAK

PEERREQUEST:

request a the peer to send a PEERINFO message

the receiving peer SHOULD then send a PEERINFO message

PEERINFO:

notifies the peer about the state of the own interface, may be send at any time if something changed or during the first exchange or on PEERREQUEST

attributes:

  • list of peers of the wireguard interface with
    • pubkey: (string) base64 encoded wireguard public key
    • endpoint: (string) containing <hostname>:<port>, may be an empty string or Null/Nil/None if not yet known
    • allowedIps: (list of strings) with <ip>/<subnet_length>
    • lastHandshake: (int) seconds since last handshake in unix epoch (e.g. output of wg show $interface latest-handshakes )
    • hostname: (string) optional either the hostname from the SELFINFO from that peer (SHOULD get verified) OR from initial wireguard config
    • wgDynamic: (string/enum) whether that peer supports this protocol,one of full (can change interface config), readonly (can only read interface config, but not write) or no (default, peer hasn't yet initiated a session with the sender peer using the protocol) full/readonly is set according to the readonly attribute from that peers SELFINFO
    • peers: (list of strings) containing base64 encoded public keys of that peer's peers (as send by that peer using this protocol), may be empty if that peer hasn't been communicated with yet

the peer receiving this message may attempt to initiate connections with the listed peers by configuring the wireguard interface accordingly (if enabled/supported by the platform)

depending on the implementation the program MAY add additional peers to the wireguard interface, (but note that that may be a security concern), or just update the endpoints of the existing peers where feasable
(e.g. if the local lastHandshake to the given peer is a while ago, and but the received lastHandshake attribute has a more recent timestamp(e.g. within the last ~5mins))
for updating the wireguard interface config other peer's PEERINFO may also be taken into account

FIN

no fields

send when one peer wants to close the session, normal socket.close() could also be used if TCP gets used. (only added for the case the protocol is over UDP, which doesn't have connection tracking)

after sending the sender MUST close the connection within a reasonable time, and incase any further packets get received those should be ignored

NAK (in session)

send in reply to invalid messages

attributes:

  • reason (optional, string): reason why the connection got closed (useful for debugging)

after sending or receiving this message the connection SHOULD get closed and may be reattempted at a later time (but without changes it is likely that another NAK will get received again on the new connection)

Inspitations: