import { REMOTE_INGRESS_MAIL_TAG } from '../../ts_interfaces/data/remoteingress.js'; import type { IRemoteIngress } from '../../ts_interfaces/data/remoteingress.js'; import type { EmailDomainDoc } from '../db/documents/classes.email-domain.doc.js'; import type { DcRouter } from '../classes.dcrouter.js'; import type { IEmailDomainEdgeIdentity } from '../../ts_interfaces/data/email-domain.js'; export const MAIL_EGRESS_IDENTITY_MAX_AGE_MS = 45_000; export interface IMailEgressIdentity { edgeId: string; family: 4 | 6; address: string; heloHostname: string; proof: 'sourceBound'; /** Edge-side time at which the configured source address was successfully bound. */ observedAt: number; /** Hub-side heartbeat receipt time. */ hubReceivedAt: number; } export interface IMailEgressIdentitySnapshot { supported: boolean; reason?: string; identities: IMailEgressIdentity[]; } /** Boundary implemented against a released RemoteIngress identity-proof API. */ export interface IEgressIdentitySource { getSnapshot(): Promise; } /** * Reads the versioned RemoteIngress identity capability through TunnelManager. * Older hubs do not expose `egressIdentityV1`; they remain explicitly * unsupported instead of falling back to configured or peer addresses. */ export class ReleasedRemoteIngressEgressIdentitySource implements IEgressIdentitySource { constructor(private dcRouterRef: DcRouter) {} public async getSnapshot(): Promise { const status = await this.dcRouterRef.tunnelManager?.getAuthoritativeHubStatus(); const connectedEdges = status?.connectedEdges ?? []; const capableEdges = connectedEdges.filter((edge) => ( Array.isArray(edge.capabilities) && edge.capabilities.includes('egressIdentityV1') )); if (capableEdges.length > 0) { const identities: IMailEgressIdentity[] = []; for (const edge of capableEdges) { const report = edge.egressIdentity; if (!report || report.proofMethod !== 'localSocketBind') continue; for (const [family, observation] of [[4, report.ipv4], [6, report.ipv6]] as const) { if (!observation || observation.stale === true) continue; if ( typeof observation.address !== 'string' || !Number.isFinite(observation.edgeObservedAtUnixMs) || !Number.isFinite(observation.hubReceivedAtUnixMs) ) continue; const configured = this.dcRouterRef.remoteIngressManager?.getEdge(String(edge.edgeId)); const heloHostname = configured?.mailHostname?.trim(); if (!heloHostname) continue; identities.push({ edgeId: String(edge.edgeId), family, address: observation.address, heloHostname, proof: 'sourceBound', observedAt: observation.edgeObservedAtUnixMs, hubReceivedAt: observation.hubReceivedAtUnixMs, }); } } return { supported: true, identities }; } return { supported: false, reason: 'RemoteIngress egress identity proof is unavailable until the capability release is consumed', identities: [], }; } } export interface IEligibleMailEdge { edge: IRemoteIngress; hostname: string; identities: IMailEgressIdentity[]; } export interface IMailEdgeEligibilityResult { supported: boolean; reason?: string; edges: IEligibleMailEdge[]; } /** Shared topology predicate used by DNS publication and outbound selection. */ export class MailEdgeEligibility { private lastUnscopedResult: IMailEdgeEligibilityResult = { supported: false, reason: 'mail edge eligibility has not been evaluated yet', edges: [], }; constructor( private dcRouterRef: DcRouter, private identitySource: IEgressIdentitySource, ) {} public async resolveForDomain(doc?: EmailDomainDoc): Promise { const snapshot = await this.identitySource.getSnapshot(); if (!snapshot.supported) { return this.recordUnscoped(doc, { supported: false, reason: snapshot.reason, edges: [] }); } const manager = this.dcRouterRef.remoteIngressManager; const tunnelManager = this.dcRouterRef.tunnelManager; if (!manager || !tunnelManager) { return this.recordUnscoped(doc, { supported: true, reason: 'RemoteIngress hub is not running', edges: [] }); } const pin = doc?.remoteIngress?.edgeFilter || []; const pinIds = pin.length > 0 ? new Set(manager.resolveEdgesByFilter(pin).map((edge) => edge.id)) : undefined; const identitiesByEdge = new Map(); const now = Date.now(); for (const identity of snapshot.identities) { if (identity.proof !== 'sourceBound') continue; if (now - identity.observedAt > MAIL_EGRESS_IDENTITY_MAX_AGE_MS) continue; if (now - identity.hubReceivedAt > MAIL_EGRESS_IDENTITY_MAX_AGE_MS) continue; const entries = identitiesByEdge.get(identity.edgeId) || []; entries.push(identity); identitiesByEdge.set(identity.edgeId, entries); } const edges: IEligibleMailEdge[] = []; for (const edge of manager.getAllEdges()) { if (!edge.enabled || !edge.tags?.includes(REMOTE_INGRESS_MAIL_TAG)) continue; // A domain pin narrows the mail-tagged pool; it never replaces the mail tag. if (pinIds && !pinIds.has(edge.id)) continue; if (!edge.egress?.enabled || !edge.egress.allowedPorts?.includes(25)) continue; const status = tunnelManager.getEdgeStatus(edge.id); if (!status?.connected || !status.lastHeartbeat) continue; if (now - status.lastHeartbeat > MAIL_EGRESS_IDENTITY_MAX_AGE_MS) continue; if (status.egressEnabled !== true || !status.capabilities?.includes('egressTcpV1')) continue; const nativeQuic = status.transportMode === 'quic' || (status.transportMode === 'quicWithFallback' && status.fallbackUsed === false); if (!nativeQuic) continue; const hostname = edge.mailHostname?.toLowerCase().replace(/\.$/, ''); if (!hostname) continue; const reported = (identitiesByEdge.get(edge.id) || []).filter((identity) => ( identity.heloHostname.toLowerCase().replace(/\.$/, '') === hostname )); const expected = [ ...(edge.publicIp ? [{ family: 4 as const, address: edge.publicIp }] : []), ...(edge.publicIpV6 ? [{ family: 6 as const, address: edge.publicIpV6 }] : []), ]; const identities = expected.flatMap((configured) => { const matches = reported.filter((identity) => ( identity.family === configured.family && identity.address === configured.address )); return matches.length === 1 ? matches : []; }); // Every configured source family must have one fresh source-bound proof. // A matching IPv4 observation never excuses an unproven configured IPv6. if (expected.length > 0 && identities.length === expected.length) { edges.push({ edge, hostname, identities }); } } edges.sort((left, right) => left.edge.id.localeCompare(right.edge.id)); return this.recordUnscoped(doc, { supported: true, ...(edges.length === 0 ? { reason: 'no live mail-tagged edge has a fresh source-bound identity' } : {}), edges, }); } public getLastUnscopedResult(): IMailEdgeEligibilityResult { return this.lastUnscopedResult; } private recordUnscoped( doc: EmailDomainDoc | undefined, result: IMailEdgeEligibilityResult, ): IMailEdgeEligibilityResult { if (!doc) this.lastUnscopedResult = result; return result; } public async resolveActiveForDomain(doc: EmailDomainDoc): Promise { const live = await this.resolveForDomain(doc); const reconciliation = doc.reconciliation; const active = reconciliation?.activeRevision; const slots = active?.mxSlots || []; const completeSlots = slots.length === 2 && slots[0]?.priority === 10 && slots[1]?.priority === 20 && slots[0].edgeId !== slots[1].edgeId && slots[0].hostname !== slots[1].hostname; if ( !live.supported || reconciliation?.lifecycleStatus !== 'active' || reconciliation.errors.length > 0 || !active || active.generation !== reconciliation.desiredGeneration || !completeSlots ) { return { supported: live.supported, reason: live.reason || 'email domain has no current complete validated DNS revision', edges: [], }; } const activeByEdge = new Map( active.edgeIdentities.map((identity) => [identity.edgeId, identity]), ); const liveByEdge = new Map(live.edges.map((candidate) => [candidate.edge.id, candidate])); const edges = slots.flatMap((slot) => { const candidate = liveByEdge.get(slot.edgeId); if (!candidate || candidate.hostname !== slot.hostname) return []; const activeIdentity = activeByEdge.get(candidate.edge.id); if (!activeIdentity || activeIdentity.hostname !== candidate.hostname) return []; const expectedFamilies = [ ...(activeIdentity.ipv4 ? [{ family: 4 as const, address: activeIdentity.ipv4 }] : []), ...(activeIdentity.ipv6 ? [{ family: 6 as const, address: activeIdentity.ipv6 }] : []), ]; const exact = candidate.identities.length === expectedFamilies.length && expectedFamilies.every((expected) => candidate.identities.some((identity) => ( identity.family === expected.family && identity.address === expected.address ))) && candidate.identities.every((identity) => ( (identity.family === 4 && identity.address === activeIdentity.ipv4) || (identity.family === 6 && identity.address === activeIdentity.ipv6) )); return exact ? [candidate] : []; }); return { supported: live.supported, ...(edges.length !== 2 ? { reason: 'both validated MX edges must be live with exact source-bound identities' } : {}), edges: edges.length === 2 ? edges : [], }; } }