From 94f2e579d1a90cc17780aee99f20841c8f94ab17 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 10 Sep 2026 14:13:58 -0400 Subject: [PATCH] Add license checks to JobScheduler and AuthoritativeDNSServer --- server/private/lib/certificates/scheduler.ts | 7 ++++ server/private/lib/dns/server.ts | 34 ++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/server/private/lib/certificates/scheduler.ts b/server/private/lib/certificates/scheduler.ts index 44d3d5b73..ee9111a6b 100644 --- a/server/private/lib/certificates/scheduler.ts +++ b/server/private/lib/certificates/scheduler.ts @@ -17,6 +17,7 @@ import { certificateService } from "./certificate-service"; import { privateConfig as config } from "#private/lib/config"; import { dnsValidator } from "./dns-validator"; import { domainReverifier } from "./domain-reverifier"; +import license from "#private/license/license"; // Backstop for runExclusive: no single job's own internal timeouts (e.g. // certificate-service's per-cert issuance timeout) are relied on here. This @@ -44,6 +45,12 @@ export class JobScheduler { label: string ): () => Promise { return async () => { + if (!(await license.isUnlocked())) { + logger.debug( + `Skipping ${label} tick - license is not subscribed` + ); + return; + } if (state.active) { logger.debug( `Skipping ${label} tick - previous run still in progress` diff --git a/server/private/lib/dns/server.ts b/server/private/lib/dns/server.ts index 5055d34fe..2e9167ebb 100644 --- a/server/private/lib/dns/server.ts +++ b/server/private/lib/dns/server.ts @@ -33,6 +33,7 @@ import * as dnsResolver from "dns"; import logger from "@server/logger"; import { listExitNodes } from "../exitNodes"; import { rateLimitService } from "../rateLimit"; +import license from "#private/license/license"; type DNSRecord = { id: number; @@ -57,6 +58,13 @@ export class AuthoritativeDNSServer { private allDomains: Set = new Set(); private domainRefreshInterval: NodeJS.Timeout | null = null; + // Cached license/subscription status. license.isUnlocked() does a DB + // round-trip on every call, so it can't be checked per-query on a UDP + // server that may see very high query volume - instead it's polled on + // the same cadence as the domain set refresh and read from memory here. + private isLicensed: boolean = false; + private licenseRefreshInterval: NodeJS.Timeout | null = null; + // Cache for per-queryName zone resolution and SOA records private authoritativeDomainCache: NodeCache = new NodeCache({ stdTTL: 300, @@ -121,6 +129,15 @@ export class AuthoritativeDNSServer { return; } + if (!this.isLicensed) { + logger.debug( + "Refusing DNS query - license is not subscribed" + ); + // REFUSED (rcode=5) indicates a policy refusal by this nameserver. + this.sendResponse(packet, [], rinfo, false, 5, []); + return; + } + const question = packet.questions[0]; const dnsRateLimit = config.getRawConfig().dns!.rate_limit; @@ -1027,6 +1044,15 @@ export class AuthoritativeDNSServer { } } + private async refreshLicenseStatus(): Promise { + try { + this.isLicensed = await license.isUnlocked(); + } catch (error) { + logger.error("Failed to refresh license status:", error); + this.isLicensed = false; + } + } + public async start(): Promise { await this.loadAllDomains(); this.domainRefreshInterval = setInterval(() => { @@ -1035,6 +1061,11 @@ export class AuthoritativeDNSServer { ); }, 60_000); + await this.refreshLicenseStatus(); + this.licenseRefreshInterval = setInterval(() => { + this.refreshLicenseStatus(); + }, 60_000); + return new Promise((resolve, reject) => { this.server.bind(this.port, (err?: Error) => { if (err) { @@ -1050,6 +1081,9 @@ export class AuthoritativeDNSServer { if (this.domainRefreshInterval) { clearInterval(this.domainRefreshInterval); } + if (this.licenseRefreshInterval) { + clearInterval(this.licenseRefreshInterval); + } return new Promise((resolve) => { this.server.close(() => { logger.info("DNS server stopped");