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
13 changes: 13 additions & 0 deletions src/core/wire-mesh-transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ export class WireMeshTransport implements MeshTransport {
const identity = await this.identityReady;
const session = await acceptMeshSession(connection, identity, [DOMAIN], {
onFrame: async (conn, frame) => this.handleDataFrame(conn, frame),
addresses: this.advertisedAddresses,
});
if (this.isShuttingDown()) {
await session.close();
Expand Down Expand Up @@ -619,6 +620,7 @@ export class WireMeshTransport implements MeshTransport {
const identity = await this.identityReady;
const session = await acceptMeshSession(connection, identity, [DOMAIN], {
onFrame: async (conn, frame) => this.handleDataFrame(conn, frame),
addresses: this.advertisedAddresses,
});
this.coordinatorSession = session;
const coordinatorDeviceId = connection.peerDeviceId;
Expand Down Expand Up @@ -717,6 +719,7 @@ export class WireMeshTransport implements MeshTransport {
const identity = await this.identityReady;
const session = await acceptMeshSession(connection, identity, [DOMAIN], {
onFrame: async (conn, frame) => this.handleDataFrame(conn, frame),
addresses: this.advertisedAddresses,
});
if (this.isShuttingDown()) {
this.dataDials.delete(peer.id);
Expand Down Expand Up @@ -800,6 +803,7 @@ export class WireMeshTransport implements MeshTransport {
const identity = await this.identityReady;
const session = await acceptMeshSession(connection, identity, [DOMAIN], {
onFrame: async (conn, frame) => this.handleDataFrame(conn, frame),
addresses: this.advertisedAddresses,
});
const outcome = await session.sendManageRequest(
buildCommand({
Expand Down Expand Up @@ -913,6 +917,15 @@ export class WireMeshTransport implements MeshTransport {
}));
}

/**
* This side's own directly-reachable "host:port" candidates (wire-mesh#38), passed into every acceptMeshSession call's own self-advert. Deliberately excludes the default bootstrap coordinator listener -- it always binds COORDINATOR_HOST (127.0.0.1, hardcoded, never configurable), which is meaningless to advertise to a remote peer -- and includes only listeners an operator explicitly registered via addListener, which by construction represent a deliberate "make me reachable from elsewhere" declaration.
*/
private get advertisedAddresses(): string[] {
return [...this.coordinatorListeners.values()]
.filter((tracked) => !tracked.isDefault)
.map((tracked) => `${tracked.host}:${String(tracked.port)}`);
}

// -----------------------------------------------------------------------
// MeshTransport -- Shutdown / unref
// -----------------------------------------------------------------------
Expand Down
117 changes: 117 additions & 0 deletions src/test/gossip-listener-addresses.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/**
* WireMeshTransport's own gossip self-advert now carries this side's own genuinely-reachable listener addresses (wire-mesh#38's own fork 2, "populate addresses from a real listener's own bound address" -- agent-comms is exactly the "real listen-and-dial consumer" that issue's own text names as the missing prerequisite). The default bootstrap coordinator listener always binds COORDINATOR_HOST (127.0.0.1, hardcoded, never configurable) -- useless to advertise to a remote peer -- so only listeners an operator explicitly registered via addListener (mesh_listen), which by construction represent a deliberate "make me reachable from elsewhere" declaration, are included.
*/

import { test, describe, expect } from "vitest";
import { generateIdentity } from "../core/identity.js";
import { toIdentityPort } from "../core/wire-mesh-identity.js";
import { deviceIdToHex } from "wire-mesh-core/domain/device-id";
import { WireMeshTransport } from "../core/wire-mesh-transport.js";
import type { TransportEvents } from "../core/transport.js";
import { waitFor } from "./test-transport.js";

function inertEvents(): TransportEvents {
return {
onMessage: () => undefined,
onPeerConnected: () => undefined,
onPeerDisconnected: () => undefined,
onIntroduction: () => undefined,
onConnectionRequest: () => undefined,
onPeerList: () => undefined,
onPeerJoined: () => undefined,
onBecomeCoordinator: () => undefined,
onRevocationAnnounce: () => undefined,
onPresenceAdvert: () => undefined,
};
}

describe("WireMeshTransport listener-address gossip", () => {
test("an operator-registered listener's address reaches a peer's directory via gossip", async () => {
const identityA = generateIdentity();
const identityB = generateIdentity();
const peerIdA = deviceIdToHex(
await toIdentityPort(identityA).then((p) => p.deviceId),
);
const peerIdB = deviceIdToHex(
await toIdentityPort(identityB).then((p) => p.deviceId),
);

const transportA = new WireMeshTransport(inertEvents(), identityA);
const transportB = new WireMeshTransport(inertEvents(), identityB);

try {
await transportA.startDataServer();
await transportA.addListener("127.0.0.1", 0, "full");

await transportB.connectToPeer(
{
id: peerIdA,
port: transportA.dataPort,
startedAt: new Date().toISOString(),
},
peerIdB,
);

await waitFor(() => {
const advert = transportB
.listKnownDevices()
.find((entry) => entry.deviceId === peerIdA)?.advert;
return (advert?.addresses.length ?? 0) > 0;
}, "B observes A's advertised listener address");

const known = transportB
.listKnownDevices()
.find((entry) => entry.deviceId === peerIdA);
const [registered] = transportA.listListeners();
expect(known?.advert.addresses).toEqual([
`127.0.0.1:${String(registered?.port)}`,
]);
} finally {
await transportB.shutdown();
await transportA.shutdown();
}
});

test("the default bootstrap coordinator listener is never advertised (always loopback, useless to a remote peer)", async () => {
const identityA = generateIdentity();
const identityB = generateIdentity();
const peerIdA = deviceIdToHex(
await toIdentityPort(identityA).then((p) => p.deviceId),
);
const peerIdB = deviceIdToHex(
await toIdentityPort(identityB).then((p) => p.deviceId),
);

const transportA = new WireMeshTransport(inertEvents(), identityA);
const transportB = new WireMeshTransport(inertEvents(), identityB);

try {
await transportA.startDataServer();

await transportB.connectToPeer(
{
id: peerIdA,
port: transportA.dataPort,
startedAt: new Date().toISOString(),
},
peerIdB,
);

await waitFor(
() =>
transportB
.listKnownDevices()
.some((entry) => entry.deviceId === peerIdA),
"B's known-devices view includes A",
);

const known = transportB
.listKnownDevices()
.find((entry) => entry.deviceId === peerIdA);
expect(known?.advert.addresses).toEqual([]);
} finally {
await transportB.shutdown();
await transportA.shutdown();
}
});
});