swim protocol in ocaml interoperable with membership lib and serf cli
OCaml 79.6%
Go 11.7%
Shell 6.6%
Dune 0.6%
Other 1.5%
26 1 0

Clone this repository

https://tangled.org/gdiazlo.tngl.sh/swim
git@tangled.org:gdiazlo.tngl.sh/swim

For self-hosted knots, clone URLs may differ based on your setup.

README.md

swim#

An OCaml 5 implementation of the SWIM (Scalable Weakly-consistent Infection-style Process Group Membership) protocol for cluster membership and failure detection.

Overview#

This library provides:

  • Membership Management: Automatic discovery and tracking of cluster nodes
  • Failure Detection: Identifies unreachable nodes using periodic probes and indirect checks
  • Gossip Protocol: Propagates state changes (Alive/Suspect/Dead) across the cluster
  • Messaging: Cluster-wide broadcast (gossip-based) and direct point-to-point UDP messaging
  • Encryption: Optional AES-256-GCM encryption for all network traffic

Built on Eio for effect-based concurrency and Kcas for lock-free shared state.

Requirements#

  • OCaml >= 5.1
  • Dune >= 3.20

Installation#

opam install .

Or add to your dune-project:

(depends (swim (>= 0.1.0)))

Usage#

Basic Example#

open Swim.Types

let config = {
  default_config with
  bind_port = 7946;
  node_name = Some "node-1";
  secret_key = "your-32-byte-secret-key-here!!!"; (* 32 bytes for AES-256 *)
  encryption_enabled = true;
}

let () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run @@ fun sw ->
  let env_wrap = { stdenv = env; sw } in
  match Swim.Cluster.create ~sw ~env:env_wrap ~config with
  | Error `Invalid_key -> failwith "Invalid secret key"
  | Ok cluster ->
      Swim.Cluster.start cluster;

      (* Join an existing cluster *)
      let seed_nodes = ["192.168.1.10:7946"] in
      (match Swim.Cluster.join cluster ~seed_nodes with
       | Ok () -> Printf.printf "Joined cluster\n"
       | Error `No_seeds_reachable -> Printf.printf "Failed to join\n");

      (* Send a broadcast message to all nodes *)
      Swim.Cluster.broadcast cluster ~topic:"config" ~payload:"v2";

      (* Send a direct message to a specific node *)
      let target = node_id_of_string "node-2" in
      Swim.Cluster.send cluster ~target ~topic:"ping" ~payload:"hello";

      (* Handle incoming messages *)
      Swim.Cluster.on_message cluster (fun sender topic payload ->
        Printf.printf "From %s: [%s] %s\n"
          (node_id_to_string sender.id) topic payload);

      (* Listen for membership events *)
      Eio.Fiber.fork ~sw (fun () ->
        let stream = Swim.Cluster.events cluster in
        while true do
          match Eio.Stream.take stream with
          | Join node -> Printf.printf "Joined: %s\n" (node_id_to_string node.id)
          | Leave node -> Printf.printf "Left: %s\n" (node_id_to_string node.id)
          | Suspect_event node -> Printf.printf "Suspect: %s\n" (node_id_to_string node.id)
          | Alive_event node -> Printf.printf "Alive: %s\n" (node_id_to_string node.id)
          | Update _ -> ()
        done);

      Eio.Fiber.await_cancel ()

Configuration Options#

Field Default Description
bind_addr "0.0.0.0" Interface to bind listeners
bind_port 7946 Port for SWIM protocol
protocol_interval 1.0 Seconds between probe rounds
probe_timeout 0.5 Seconds to wait for Ack
indirect_checks 3 Peers to ask for indirect probes
secret_key (zeros) 32-byte key for AES-256-GCM
encryption_enabled false Enable encryption

Interoperability Testing#

The library includes interoperability tests with HashiCorp's memberlist (Go). This verifies protocol compatibility with the reference implementation.

Prerequisites#

  • Go >= 1.19
  • OCaml environment with dune

Running Interop Tests#

The interop test suite starts a Go memberlist node and an OCaml node, then verifies they can discover each other and exchange messages.

# Build the OCaml project
dune build

# Build the Go memberlist server
cd interop && go build -o memberlist-server main.go && cd ..

# Run the interop test
bash test/scripts/test_interop.sh

# Run with encryption enabled
bash test/scripts/test_interop_encrypted.sh

Manual Interop Testing#

Start the Go node:

cd interop
go run main.go -name go-node -bind 127.0.0.1 -port 7946

In another terminal, start the OCaml node:

dune exec swim-interop-test

The OCaml node will connect to the Go node and print membership statistics for 30 seconds.

Available Test Scripts#

Script Description
test/scripts/test_interop.sh Basic interop test
test/scripts/test_interop_encrypted.sh Interop with AES encryption
test/scripts/test_interop_udp_only.sh UDP-only communication test
test/scripts/test_interop_go_joins.sh Go node joining OCaml cluster

Debug Utilities#

# Test packet encoding/decoding
dune exec swim-debug-codec

# Receive and display incoming SWIM packets
dune exec swim-debug-recv

# Send manual ping to a target node
dune exec swim-debug-ping

Running Tests#

dune runtest

License#

ISC License. See LICENSE for details.