Skip to content

feat: implement Cloudflare Worker connection pooling and management - #290

Merged
DanielLavrushin merged 10 commits into
mainfrom
1-75-0-fixes
Aug 8, 2026
Merged

feat: implement Cloudflare Worker connection pooling and management#290
DanielLavrushin merged 10 commits into
mainfrom
1-75-0-fixes

Conversation

@DanielLavrushin

Copy link
Copy Markdown
Owner
  • Added cfWorkerPool for managing pooled connections to Cloudflare Workers.
  • Introduced new transport plans for handling Worker connections.
  • Enhanced connection dialing logic to support Worker-specific configurations.
  • Implemented tests for Worker connection pooling and dialing behavior.
  • Updated existing code to integrate Worker pooling into the MTProto transport layer.

- Added cfWorkerPool for managing pooled connections to Cloudflare Workers.
- Introduced new transport plans for handling Worker connections.
- Enhanced connection dialing logic to support Worker-specific configurations.
- Implemented tests for Worker connection pooling and dialing behavior.
- Updated existing code to integrate Worker pooling into the MTProto transport layer.
- Refactor dnsTCPServer to support both IPv4 and IPv6 listeners.
- Add configuration options for enabling/disabling IPv4 and IPv6.
- Update connection handling to manage separate listeners for each IP family.
- Enhance error handling for listener startup failures.
- Modify writeDNSTCPMessage to accept a timeout parameter.
- Introduce tests for dual-stack functionality, ensuring proper blocking behavior for both IPv4 and IPv6.
- Update configuration defaults and validation to ensure DNS over TCP settings are correctly applied.
- Add UI components for DNS settings, allowing users to configure TCP options.
@DanielLavrushin DanielLavrushin self-assigned this Aug 8, 2026
Copilot AI lite review requested due to automatic review settings August 8, 2026 07:42
@codacy-production

codacy-production Bot commented Aug 8, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 2 critical · 2 high · 2 minor

Alerts:
⚠ 6 issues (≤ 0 issues of at least minor severity)

Results:
6 new issues

Category Results
ErrorProne 1 high
Security 2 critical
1 high
CodeStyle 2 minor

View in Codacy

🟢 Metrics 671 complexity

Metric Results
Complexity 671

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR expands b4’s transport and DNS subsystems by adding Cloudflare Worker connection pooling for MTProto/WebSocket dialing and introducing full DNS-over-TCP interception (listener + firewall redirects), plus UI/config support to manage DNS timeouts and listener port.

Changes:

  • Added a Cloudflare Worker warm-connection pool and updated MTProto dialing/planning to distinguish native Telegram WS edge vs Worker/CF-proxy routes.
  • Implemented DNS-over-TCP interception end-to-end: TCP listener, iptables/nftables redirect rules, monitoring, and runtime readiness wiring.
  • Added configuration schema/validation + Web UI settings/i18n for DNS TCP and DNS query timeouts, with tests and changelog updates.

Reviewed changes

Copilot reviewed 33 out of 33 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/tables/nftables.go Applies DNS-over-TCP nftables rules as part of nftables apply/clear lifecycle.
src/tables/monitor.go Extends firewall rule monitoring to include DNS-over-TCP redirect rule presence checks.
src/tables/iptables.go Adds DNS-over-TCP chains/rules into iptables manifest and teardown paths.
src/tables/dnstcp.go Implements iptables/nftables DNS-over-TCP redirect rule creation and cleanup logic.
src/nfq/types.go Adds DNS TCP server handle to NFQ pool state.
src/nfq/pool.go Starts/stops DNS-over-TCP listener and publishes readiness for firewall rule gating.
src/nfq/dnstcp.go Implements DNS-over-TCP listener, query handling, and passthrough forwarding behavior.
src/nfq/dnstcp_test.go Unit tests for DNS TCP message framing and original-destination helper behavior.
src/nfq/dnstcp_dualstack_test.go Integration-style tests for dual-stack DNS-over-TCP listener behavior and blocking.
src/nfq/dns.go Adds richer DNS trace event labeling and unifies endpoint extraction logic.
src/mtproto/workerprobe_test.go Adds an opt-in manual diagnostic test for Worker dial/idle lifetime behavior.
src/mtproto/transparent.go Switches transparent bridge dialing to use the Worker pool (when configured) and correct splitter selection.
src/mtproto/server.go Creates/tears down Worker pool alongside wsPool and routes dialer through both pools.
src/mtproto/obfuscated2.go Updates transport planning/dialing: native-vs-worker distinction, shuffle Worker domains, new dialInfo/dialPools.
src/mtproto/msgsplit.go Introduces splitter selection logic based on whether the transport is a Worker relay.
src/mtproto/cfworkerpool.go Implements warm spare WebSocket connections per Worker/DC key with max-age expiry.
src/mtproto/cfworkerpool_test.go Adds tests covering Worker-pool behaviors and updated transport-planning semantics.
src/main.go Wires NFQ DNS TCP readiness publisher into tables layer for rule gating.
src/http/ui/src/models/config.ts Adds DNS system config typing to the UI config model.
src/http/ui/src/i18n/ru.json Adds RU translations for the new DNS settings section.
src/http/ui/src/i18n/en.json Adds EN translations for the new DNS settings section.
src/http/ui/src/components/settings/Page.tsx Adds DNS settings section to the settings page layout.
src/http/ui/src/components/settings/Dns.tsx Implements UI controls for DNS-over-TCP enable/port and DNS timeout configuration.
src/http/ui/src/assets/doh.json Adds additional DoH presets to the UI asset list.
src/http/handler/preflight.go Adds preflight port probing when enabling/changing DNS TCP listener port.
src/http/handler/config.go Triggers soft-restart updates when DNS TCP settings change.
src/config/validation.go Adds DNS TCP port range validation and port collision reservation logic.
src/config/types.go Adds DNS system config types and default constants.
src/config/methods.go Adds computed getters for DNS TCP port/timeouts and interception enablement logic.
src/config/dns_test.go Adds tests for DNS system defaults, validation interactions, and config sparsification.
src/config/config.go Sets default DNS system config values in DefaultConfig.
changelog.md Documents DNS-over-TCP interception, Worker pooling changes, and related fixes in release notes.
changelog_ru.md RU changelog entries for the new DNS and Worker-related changes.
Suppressed comments (1)

src/mtproto/obfuscated2.go:454

  • workerPool can be nil (e.g., DialObfuscatedDC called with pools == nil), but warm() is called unconditionally for Worker plans after a successful fresh dial. This can panic even though dialing itself succeeded.
			if p.isWorker {
				workerPool.warm(p)
			}

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/mtproto/obfuscated2.go
Comment thread src/mtproto/obfuscated2.go
Comment thread src/nfq/dns.go Outdated
Comment thread changelog.md
Comment thread src/tables/dnstcp.go
Comment thread src/tables/monitor.go
Copilot AI review requested due to automatic review settings August 8, 2026 07:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 35 out of 35 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/nfq/dns.go:126

  • logDNSEvent always logs the protocol as "UDP". Since DNS-over-TCP events are reported via the tcp/ action prefix, trace output will incorrectly label TCP activity as UDP.
	if set != nil {
		setName = set.Name
	}
	log.LogConnection("UDP", setName, domain, clientIP.String(), clientPort, "", serverIP.String(), 53, srcMac, "", action)
}

src/tables/monitor.go:236

  • This iptables DNS-over-TCP rule check only fails when both -S PREROUTING and -S B4_DNSTCP succeed. If the chain is missing (or the command errors), the monitor will incorrectly report rules as present and won’t trigger a restore.
    src/tables/dnstcp.go:135
  • applyDNSTCPFamily unconditionally runs nft add table ... and returns an error if the table already exists. If the per-family DNS nat table is left behind (e.g., after a partial cleanup or crash), future applies will treat that as fatal and skip installing DNS-over-TCP redirect rules for that family.
    changelog.md:7
  • PR title/description focus on Cloudflare Worker pooling, but this change set also introduces a sizable DNS-over-TCP interception feature (firewall rules, NFQ TCP listener, config + UI + docs). Consider updating the PR title/description (or splitting) so reviewers can track both scopes explicitly.
## [1.75.0] - 2026-08-08

- FIXED: **DNS sent over TCP skipped the set's DNS server** - only requests over UDP were intercepted, so a device that switched to TCP reached the router's usual resolver instead.
- ADDED: **A DNS section in Settings** - the port used for DNS over TCP, how long to wait for an answer, an off switch, and a check against ports other services already use.
- ADDED: **DNS request outcomes in the trace** - a trace named the domain but never whether the request reached the set's DNS server or slipped past it.

- Added a switch for "any domain" and "any address" to simplify configuration.
- Fixed an issue where `0.0.0.0/0` was lost in the firewall due to `ipset` limitations.
- Enhanced DNS handling to capture TCP requests and added a DNS settings section.
- Introduced a new catch-all mechanism for domains and IPs, including normalization and entry notices.
- Updated UI components to support highlighting and displaying notices for catch-all entries.
- Implemented tests for new CIDR handling logic to ensure correct expansion of zero-prefix entries.
Copilot AI review requested due to automatic review settings August 8, 2026 09:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 50 out of 50 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/mtproto/obfuscated2.go:80

  • shuffledWorkerDomains uses the global math/rand without ever seeding it, so the “random” Worker order will be deterministic across process starts. This defeats the intended load spreading described in the comment.
	mrand.Shuffle(len(out), func(i, j int) { out[i], out[j] = out[j], out[i] })

src/tables/dnstcp.go:28

  • PR metadata describes Cloudflare Worker connection pooling changes, but this PR also introduces a substantial DNS-over-TCP interception feature (iptables/nftables rules, NFQ listener, UI/settings, config validation, changelog). Please either update the PR title/description to reflect the full scope, or split the DNS and routing/UI changes into separate PR(s) so review/risk is clearer.
    src/tables/monitor.go:344
  • If nft.runNft("list", "table", ...) returns an error, this check currently treats it as “rules present” and continues. That can mask missing DNS-over-TCP redirect rules (or permission/exec failures) and make the monitor incorrectly report a healthy ruleset.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 50 out of 50 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

src/tables/monitor.go:344

  • In the nftables monitor, an error from nft.runNft("list", "table", ...) is currently ignored, which can make the monitor report rules as present even when nftables queries are failing (false positive). Treat listing failures as missing rules so the monitor can trigger a restore.
    src/config/methods.go:287
  • DNSTCPInterceptEnabled() is gated on HasDNSRedirect() (a set with DNS.Enabled and a DoH/TargetDNS). But the DNS-over-TCP server also implements routing-mode blocking, which does not require set.DNS.Enabled. With the current gate, a block-only set will still be bypassed for DNS-over-TCP because the listener and redirect rules never start/install.
func (c *Config) DNSTCPInterceptEnabled() bool {
	return !c.System.DNS.TCPDisabled && c.HasDNSRedirect()
}

src/http/ui/src/models/config.ts:356

  • MarshalSparse can omit system.dns entirely when all DNS settings are defaults, so the UI model should treat this field as optional. Making it required here makes it easier to accidentally dereference config.system.dns elsewhere without a fallback, and it doesn't match the on-the-wire shape for existing configs.
export interface SystemConfig {
  logging: LoggingConfig;
  web_server: WebServerConfig;
  socks5: Socks5Config;
  mtproto: MTProtoConfig;
  tables: TableConfig;
  checker: DiscoveryConfig;
  geo: GeoConfig;
  api: ApiConfig;
  ai: AIConfig;
  dns: DnsSystemConfig;
  timezone: string;
  memory_limit?: string;

src/http/handler/preflight.go:53

  • DNS-over-TCP preflight probes only 0.0.0.0:<port>, but the DNS TCP listener can also bind tcp6 on [::]:<port> when IPv6 is enabled. This can miss an IPv6-only port collision (or incorrectly report success when the IPv6 bind will fail), leading to a config that passes preflight but can't start the TCP listener.
	if newCfg.DNSTCPInterceptEnabled() {
		oldPort := oldCfg.DNSTCPListenPort()
		newPort := newCfg.DNSTCPListenPort()
		if !oldCfg.DNSTCPInterceptEnabled() || oldPort != newPort {
			if f := probePort("system.dns.tcp_port", "0.0.0.0", newPort); f != nil {

src/config/validation.go:363

  • The DNS TCP port is currently added to the port-collision set whenever tcp_disabled is false, even if DNS-over-TCP interception is effectively inactive (e.g. no enabled sets with DNS redirect / block rules, so no listener or firewall redirect rules are started). This can produce unnecessary "port already in use" validation errors for services that would otherwise be able to use the port safely.
	if !c.System.DNS.TCPDisabled {
		if p := c.System.DNS.TCPPort; p != 0 && (p < 1 || p > 65535) {
			v.add("system.dns.tcp_port", "out_of_range", "port must be between 1 and 65535", portRangeParams)
		} else {
			refs = append(refs, portRef{"system.dns.tcp_port", c.DNSTCPListenPort()})
		}
	}

Comment thread src/nfq/dnstcp.go
Copilot AI review requested due to automatic review settings August 8, 2026 11:06

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/mtproto/obfuscated2.go:80

  • shuffledWorkerDomains uses math/rand.Shuffle but the codebase does not appear to seed the global PRNG. That makes the Worker order deterministic across process starts, which undermines the stated goal of spreading load/quota across workers.
    src/tables/monitor.go:344
  • In the nftables DNS-over-TCP check, an error from nft list table currently does not fail the monitor check (it only checks for missing "redirect" when err == nil). If nft returns an error here, the monitor can incorrectly report rules as present and skip restoration.
    src/tables/dnstcp.go:14
  • The PR title/description focuses on Cloudflare Worker connection pooling, but this PR also introduces DNS-over-TCP interception (iptables/nftables rules + UI/config). Please update the PR description to reflect this added scope, or split the DNS work into a separate PR for easier review/release notes.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated no new comments.

Suppressed comments (6)

src/mtproto/obfuscated2.go:80

  • shuffledWorkerDomains uses math/rand's global source without any explicit seeding in the codebase, so the initial worker order after each process start is deterministic. If the goal is to spread load/quota across workers immediately after restart, use a per-call RNG seeded with time (or seed the global RNG once) before shuffling.
    changelog.md:7
  • The PR title/description describe Cloudflare Worker connection pooling, but this release entry (and the diff) also includes multiple substantial, unrelated features (DNS-over-TCP interception + UI, routing/firewall fixes, matcher behavior changes, discovery payload changes). Please update the PR metadata to reflect the full scope, or consider splitting into focused PRs to make review/rollback safer.
## [1.75.0] - 2026-08-08

- ADDED: **A switch for matching every domain, and one for matching every address** - a set meant to catch everything had to be written by hand as `regexp:.*` or `0.0.0.0/0`, and neither form appears anywhere in the interface. The IPv6 half, `::/0`, was easy to leave out, so a catch-all set passed IPv6 traffic through untouched without saying so. Typing `*`, `any` or `all` into either field adds the same entries, and `*.example.com` is stored as `example.com`, which was taken as written and matched nothing.
- FIXED: **Two sets listing the same IP range fought over it unpredictably** - only one of them reached the matcher, and it was whichever was stored last rather than the one placed higher in the list. A set restricted to source devices lost the range outright to a general set below it, and the winner could change between restarts. Ranges of equal length are settled by set order, and a device-scoped set is offered the traffic first.
- FIXED: **`0.0.0.0/0` among a set's IP targets was dropped on the way to the firewall** - `ipset` cannot store a network with a zero prefix size, so on routers using iptables the routing set lost that entry, while per-set MSS clamping and duplication lost every entry alongside it, since those are loaded in one batch that fails whole. The matching engine had accepted the same entry all along, so a set matched traffic while its firewall side stood empty. Both catch-alls are written to the firewall as their two halves.

src/http/ui/src/models/config.ts:354

  • SystemConfig declares dns as required, but the UI already treats it as possibly absent (config.system.dns ?? {}) and the backend can omit the DNS section when it matches defaults. Marking this field optional avoids misleading typing and prevents other UI code from assuming it is always present.
  dns: DnsSystemConfig;

src/tables/monitor.go:344

  • If nft list table fails, the monitor currently treats DNS-over-TCP rules as present and continues. That can hide missing/failed DNS-over-TCP setup (e.g., permissions or table corruption) and prevent corrective re-apply logic from triggering. Treat list errors as a failure here.
    src/http/handler/preflight.go:53
  • DNS-over-TCP can bind both 0.0.0.0:<port> (tcp4) and [::]:<port> (tcp6), but preflight only probes 0.0.0.0. This can report the port as free even when an IPv6 listener would fail to bind, leaving IPv6 DNS-over-TCP silently disabled. Probe :: as well when IPv6 is enabled.
	if newCfg.DNSTCPInterceptEnabled() {
		oldPort := oldCfg.DNSTCPListenPort()
		newPort := newCfg.DNSTCPListenPort()
		if !oldCfg.DNSTCPInterceptEnabled() || oldPort != newPort {
			if f := probePort("system.dns.tcp_port", "0.0.0.0", newPort); f != nil {
				fields = append(fields, *f)
			}
		}
	}

src/nfq/dnstcp.go:335

  • passthrough explicitly clears read deadlines on both the client and upstream conns (SetReadDeadline(time.Time{})). That means the configured DNS-over-TCP timeouts (idle/io) are not enforced for unmatched/passthrough TCP sessions, which can keep idle redirected connections around indefinitely once interception is enabled.

Copilot AI review requested due to automatic review settings August 8, 2026 12:36
@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/tables/dnstcp.go:147

  • applyDNSTCPFamily unconditionally runs nft add table ..., which fails with “file exists” if the DNS nat table is left behind (e.g. a prior run crashed between creating the DNS table and clearing it). That would prevent DNS-over-TCP redirects from being installed for that family. Consider clearing/deleting an existing family table before creating it, or only creating it when missing.
    changelog.md:19
  • The PR title/description are scoped to Cloudflare Worker pooling/management, but this changelog entry (and the diff overall) includes additional user-facing features and behavioral changes (DNS-over-TCP interception + settings, set overlap handling, UI catch-all entries, discovery behavior changes, etc.). Please update the PR description/title to reflect the full scope, or split unrelated changes into separate PRs for easier review and safer rollout.
## [1.75.0] - 2026-08-08

- FIXED: **A domain was handled by a different strategy than the one Discovery found for it** - every result was published with all the domains of the run, so two applied results claimed the same domains and set order picked the winner.
- FIXED: **A set applied from Discovery had no effect until b4 was restarted** - it was saved and listed in the interface while the traffic it named kept going out under the previous settings.
- ADDED: **A warning when several enabled sets name the same domain** - only one of them handles that domain, chosen by its place in the list rather than by which strategy works.
- ADDED: **A switch for matching every domain, and one for matching every address** - a set meant to catch everything had to be written by hand as `regexp:.*` or `0.0.0.0/0`, and neither form appears anywhere in the interface. The IPv6 half, `::/0`, was easy to leave out, so a catch-all set passed IPv6 traffic through untouched without saying so. Typing `*`, `any` or `all` into either field adds the same entries, and `*.example.com` is stored as `example.com`, which was taken as written and matched nothing.
- FIXED: **Two sets listing the same IP range fought over it unpredictably** - only one of them reached the matcher, and it was whichever was stored last rather than the one placed higher in the list. A set restricted to source devices lost the range outright to a general set below it, and the winner could change between restarts. Ranges of equal length are settled by set order, and a device-scoped set is offered the traffic first.
- FIXED: **`0.0.0.0/0` among a set's IP targets was dropped on the way to the firewall** - `ipset` cannot store a network with a zero prefix size, so on routers using iptables the routing set lost that entry, while per-set MSS clamping and duplication lost every entry alongside it, since those are loaded in one batch that fails whole. The matching engine had accepted the same entry all along, so a set matched traffic while its firewall side stood empty. Both catch-alls are written to the firewall as their two halves.
- FIXED: **A set routed through a TCP-only upstream proxy left no firewall rules at all on routers using iptables** - each sync of the set failed on the name of its QUIC-refusing chain, one character over the length iptables accepts.
- FIXED: **DNS sent over TCP skipped the set's DNS server** - only requests over UDP were intercepted, so a device that switched to TCP reached the router's usual resolver instead.
- ADDED: **A DNS section in Settings** - the port used for DNS over TCP, how long to wait for an answer, an off switch, and a check against ports other services already use.
- ADDED: **DNS request outcomes in the trace** - a trace named the domain but never whether the request reached the set's DNS server or slipped past it.
- FIXED: **One refusal from Telegram's WebSocket edge switched off a working Cloudflare Worker** - the refusal was treated as a verdict on every WebSocket route to that data center, including the relay the user runs themselves.
- FIXED: **A paused Telegram data center address was answered with an instant failure** - Telegram read that as grounds to reconnect immediately, filling the log with hundreds of identical lines a second.
- ADDED: **A warm spare connection to your Cloudflare Worker** - one ready connection per Worker and data center, in place of the 65-90 ms handshake every new Telegram connection paid for.
- FIXED: **Traffic to a Cloudflare Worker was cut into one small write per Telegram message** - that framing was only ever required by Telegram's own WebSocket edge.
- CHANGED: **Several Worker addresses are tried in a random order** - they were tried strictly as written, so the first absorbed all the traffic and all the rate limiting.

src/nfq/dnstcp.go:199

  • writeDNSTCPMessage writes the framed DNS message with a single Write call. net.Conn.Write is allowed to return a short write without an error, which could truncate DNS responses under load or when using non-TCP transports in tests. Loop until the whole buffer is written (or return an explicit short-write error).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/mtproto/obfuscated2.go:80

  • shuffledWorkerDomains uses math/rand's global PRNG but the codebase doesn't seed it anywhere, so the shuffle order will be deterministic across process starts (and therefore won’t reliably spread load the way the comment describes). Consider using a locally-seeded rand.Rand (or crypto/rand) for the shuffle.
    src/tables/cidr.go:62
  • expandZeroPrefix dedupes only the generated /1 halves, but it can still produce duplicate elements when the input already contains one of the halves (e.g. 0.0.0.0/1 plus 0.0.0.0/0). This is risky for the nftables path because NFTablesManager.addSetElements uses nft add element (no "-exist" equivalent) and will fail the whole batch if a duplicate is present. Consider deduping all emitted entries (while preserving order).
    src/http/handler/preflight.go:53
  • DNS-over-TCP preflight currently probes only 0.0.0.0:<port> regardless of which IP families are enabled. This can (1) miss IPv6-only port collisions (so the v6 listener later fails to bind) and (2) incorrectly block config changes when IPv4 is disabled but the port is busy on v4 (even though b4 won’t bind tcp4). Probe per enabled family (0.0.0.0 for IPv4, :: for IPv6).
			if f := probePort("system.dns.tcp_port", "0.0.0.0", newPort); f != nil {

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 63 out of 63 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/mtproto/obfuscated2.go:81

  • shuffledWorkerDomains uses math/rand without any seeding in the repo, so the shuffle sequence is deterministic across process starts (default seed = 1). That makes Worker selection predictable and can undermine the intended load/request-quota spreading after restarts; consider using a per-call RNG seeded with time/crypto, or seeding the global RNG at startup.
    changelog.md:7
  • The PR title/description focus on Cloudflare Worker pooling, but this PR also includes a broad set of unrelated changes (DNS-over-TCP interception + settings UI, discovery/domain overlap handling, catch-all target UX, etc., as reflected in this changelog entry). Please update the PR description/title to match the actual scope, or consider splitting into separate PRs for easier review and rollback.
## [1.75.0] - 2026-08-08

- FIXED: **A domain was handled by a different strategy than the one Discovery found for it** - every result was published with all the domains of the run, so two applied results claimed the same domains and set order picked the winner.
- FIXED: **A set applied from Discovery had no effect until b4 was restarted** - it was saved and listed in the interface while the traffic it named kept going out under the previous settings.
- ADDED: **A warning when several enabled sets name the same domain** - only one of them handles that domain, chosen by its place in the list rather than by which strategy works.

src/tables/monitor.go:344

  • If nft.runNft("list", "table", ...) fails, this check currently ignores the error and may incorrectly treat DNS-over-TCP redirect rules as present. The monitor should treat list errors as a failed verification (return false), so rule restoration can run when nftables output isn't readable.

@DanielLavrushin
DanielLavrushin merged commit a2b9c81 into main Aug 8, 2026
3 of 5 checks passed
@DanielLavrushin
DanielLavrushin deleted the 1-75-0-fixes branch August 8, 2026 13:00
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.

2 participants