Skip to content

Add IPv6 support for mesh addressing and gossip endpoints#97

Merged
igorls merged 6 commits into
mainfrom
copilot/add-ipv6-support
Apr 28, 2026
Merged

Add IPv6 support for mesh addressing and gossip endpoints#97
igorls merged 6 commits into
mainfrom
copilot/add-ipv6-support

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 27, 2026

MeshGuard’s control and data-plane setup was IPv4-only: mesh addresses, peer endpoints, TUN configuration, and SWIM gossip all assumed 4-byte addresses. This PR adds IPv6 support while keeping existing IPv4 behavior compatible.

  • Mesh addressing

    • Derives deterministic IPv6 mesh addresses from Ed25519 public keys.
    • Adds default ULA mesh prefix fd99:6d67::/64.
    • Keeps existing 10.99.0.0/16 IPv4 derivation intact.
  • Endpoint model and wire codec

    • Extends Endpoint to represent IPv4 or IPv6.
    • Updates SWIM gossip codec to encode endpoint address family plus 16-byte address storage.
    • Adds IPv6 round-trip coverage for gossip endpoints.
  • UDP gossip transport

    • Adds IPv6 UDP bind, send, and receive paths.

    • Routes SWIM sends through endpoint-aware helpers instead of assuming IPv4.

    • Supports IPv6 seed syntax:

      meshguard up --seed [fd99:6d67::1]:51821
      
  • TUN/interface configuration

    • Configures derived IPv6 mesh addresses on Linux, Windows, and macOS userspace interfaces.
    • Adds IPv6 mesh routes for fd99:6d67::/64.
  • FFI/mobile API

    • Adds IPv6 initialization and join entry points for embedders.
    • Preserves existing IPv4 FFI APIs.

Example IPv6 endpoint handling:

const ep = messages.Endpoint.initV6(
    .{ 0xfd, 0x99, 0x6d, 0x67, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
    51821,
);

_ = socket.sendToEndpoint(packet, ep);

Copilot AI linked an issue Apr 27, 2026 that may be closed by this pull request
Copilot AI and others added 5 commits April 27, 2026 23:36
Agent-Logs-Url: https://github.com/igorls/meshguard/sessions/3a78a423-ddd3-41a5-9cb3-ae76a9114919

Co-authored-by: igorls <4753812+igorls@users.noreply.github.com>
Agent-Logs-Url: https://github.com/igorls/meshguard/sessions/3a78a423-ddd3-41a5-9cb3-ae76a9114919

Co-authored-by: igorls <4753812+igorls@users.noreply.github.com>
Agent-Logs-Url: https://github.com/igorls/meshguard/sessions/3a78a423-ddd3-41a5-9cb3-ae76a9114919

Co-authored-by: igorls <4753812+igorls@users.noreply.github.com>
Agent-Logs-Url: https://github.com/igorls/meshguard/sessions/3a78a423-ddd3-41a5-9cb3-ae76a9114919

Co-authored-by: igorls <4753812+igorls@users.noreply.github.com>
Copilot AI changed the title [WIP] Add IPv6 support for mesh addressing and peer endpoints Add IPv6 support for mesh addressing and gossip endpoints Apr 27, 2026
Copilot AI requested a review from igorls April 27, 2026 23:44
@igorls igorls marked this pull request as ready for review April 28, 2026 03:39
Copilot AI review requested due to automatic review settings April 28, 2026 03:39
@igorls igorls merged commit f3657d4 into main Apr 28, 2026
4 checks passed
@igorls igorls deleted the copilot/add-ipv6-support branch April 28, 2026 03:39
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds IPv6 support across MeshGuard’s mesh addressing, endpoint representation, gossip wire format, and platform networking setup while preserving existing IPv4 behavior paths.

Changes:

  • Introduce deterministic IPv6 mesh addressing (fd99:6d67::/64) and configure it on Linux/Windows/macOS TUN interfaces plus routes.
  • Extend the Endpoint model + SWIM gossip codec to support IPv4/IPv6 endpoints and add IPv6 round-trip tests.
  • Add IPv6-capable UDP bind/send/recv paths and thread endpoint-aware sending through SWIM/holepunch/CLI/FFI.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
src/wireguard/rtnetlink.zig Adds IPv6 address/route netlink operations and improves route attributes.
src/wireguard/ip.zig Adds IPv6 mesh prefix + deterministic IPv6 derivation and formatting helpers/tests.
src/wireguard/device.zig Stores mesh IPv6 + supports IPv6 endpoints in peer model.
src/protocol/messages.zig Extends Endpoint to represent IPv4 or IPv6 and updates formatting/equality.
src/protocol/codec.zig Updates gossip/holepunch encoding to carry endpoint family + 16-byte addresses; adds IPv6 round-trip test.
src/net/wincfg.zig Adds netsh-based IPv6 address/route configuration helpers.
src/net/udp.zig Adds IPv6 UDP bind/send/recv and an endpoint-aware send helper.
src/net/darwincfg.zig Adds ifconfig/route-based IPv6 interface and route configuration helpers.
src/nat/holepunch.zig Sends probes via endpoint-aware UDP helper (works for IPv4/IPv6).
src/meshguard_ffi.zig Adds IPv6 init/join entry points for embedders while keeping IPv4 APIs.
src/main.zig Wires IPv6 mesh config/routes and IPv6 announce/bind behavior into CLI up.
src/discovery/swim.zig Switches SWIM to endpoint-based send/recv, derives/stores mesh IPv6, and gossips endpoints with family.
src/discovery/seed.zig Adds IPv6 endpoint parsing and bracketed host:port splitting support + tests.
src/discovery/membership.zig Stores deterministic mesh IPv6 per peer.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// RTA_OIF = 4 (output interface)
b.addAttr(RTA_DST, &dst);
var oif_bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &oif_bytes, ifindex, .little);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Netlink integer attributes are encoded in native endianness. Writing ifindex as little-endian here will break on big-endian Linux architectures. Use .native (or write via std.mem.nativeToBytes) for the RTA_OIF payload.

Suggested change
std.mem.writeInt(u32, &oif_bytes, ifindex, .little);
std.mem.writeInt(u32, &oif_bytes, ifindex, .native);

Copilot uses AI. Check for mistakes.
Comment thread src/protocol/codec.zig
Comment on lines +464 to +472
const port = std.mem.readInt(u16, data[18..][0..2], .little);
if (data[1] == 6) {
var addr6: [16]u8 = undefined;
@memcpy(&addr6, data[2..][0..16]);
return messages.Endpoint.initV6(addr6, port);
}
var addr: [4]u8 = undefined;
@memcpy(&addr, data[2..][0..4]);
return messages.Endpoint.initV4(addr, port);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

decodeEndpoint treats any family byte other than 6 as IPv4. That means corrupted/invalid inputs (e.g. family=0/5) will be accepted as IPv4 endpoints, potentially leading to misrouting or confusing peer endpoint state. Consider validating data[1] is exactly 4 or 6 when has_ep is set; otherwise return null (or a decode error).

Suggested change
const port = std.mem.readInt(u16, data[18..][0..2], .little);
if (data[1] == 6) {
var addr6: [16]u8 = undefined;
@memcpy(&addr6, data[2..][0..16]);
return messages.Endpoint.initV6(addr6, port);
}
var addr: [4]u8 = undefined;
@memcpy(&addr, data[2..][0..4]);
return messages.Endpoint.initV4(addr, port);
const family = data[1];
const port = std.mem.readInt(u16, data[18..][0..2], .little);
if (family == 6) {
var addr6: [16]u8 = undefined;
@memcpy(&addr6, data[2..][0..16]);
return messages.Endpoint.initV6(addr6, port);
}
if (family == 4) {
var addr: [4]u8 = undefined;
@memcpy(&addr, data[2..][0..4]);
return messages.Endpoint.initV4(addr, port);
}
return null;

Copilot uses AI. Check for mistakes.
Comment thread src/discovery/swim.zig
Comment on lines 459 to 465
if (data.len >= 4) {
const msg_type = std.mem.readInt(u32, data[0..4], .little);
if (msg_type >= 1 and msg_type <= 4) {
if (self.handler) |h| {
if (h.onWgPacket) |cb| {
cb(h.ctx, data, sender_addr, sender_port);
cb(h.ctx, data, sender_endpoint.addr, sender_endpoint.port);
}
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

When receiving WireGuard packets on an IPv6-bound socket, sender_endpoint will be IPv6 and sender_endpoint.addr will be the default 0.0.0.0, so onWgPacket gets an invalid IPv4 sender address. This breaks WG packet dispatch over IPv6 (and can also break endpoint tracking if the callback uses the sender address). Consider changing the callback to accept messages.Endpoint (or add a parallel onWgPacketEndpoint) and pass the full endpoint through.

Copilot uses AI. Check for mistakes.
Comment thread src/discovery/seed.zig
Comment on lines +99 to +100
const colon_idx = std.mem.lastIndexOfScalar(u8, s, ':') orelse return null;
return .{ .host = s[0..colon_idx], .port = s[colon_idx + 1 ..] };
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

splitHostPort falls back to lastIndexOf(':') for non-bracketed inputs. For a bare IPv6 address without an explicit port (e.g. fd99:6d67::1), this will be mis-parsed as host fd99:6d67:: with port 1, silently producing an incorrect endpoint. Since the documented IPv6 seed syntax is bracketed, consider rejecting non-bracketed hosts that contain ':' (or otherwise requiring brackets for IPv6) so missing ports can’t be mistaken for the last hextet.

Suggested change
const colon_idx = std.mem.lastIndexOfScalar(u8, s, ':') orelse return null;
return .{ .host = s[0..colon_idx], .port = s[colon_idx + 1 ..] };
const first_colon = std.mem.indexOfScalar(u8, s, ':') orelse return null;
if (std.mem.indexOfScalar(u8, s[first_colon + 1 ..], ':') != null) return null;
return .{ .host = s[0..first_colon], .port = s[first_colon + 1 ..] };

Copilot uses AI. Check for mistakes.
b.addAttr(RTA_DST, &dst);

var oif_bytes: [4]u8 = undefined;
std.mem.writeInt(u32, &oif_bytes, ifindex, .little);
Copy link

Copilot AI Apr 28, 2026

Choose a reason for hiding this comment

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

Netlink integer attributes are encoded in native endianness. Writing ifindex as little-endian here will break on big-endian Linux architectures. Use .native (or write via std.mem.nativeToBytes) for the RTA_OIF payload.

Suggested change
std.mem.writeInt(u32, &oif_bytes, ifindex, .little);
std.mem.writeInt(u32, &oif_bytes, ifindex, .native);

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IPv6 support

3 participants