Refine routing defaults for global and device fallbacks
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 17s
All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 17s
This commit is contained in:
@@ -12,6 +12,11 @@ import {
|
||||
readSingboxConfig,
|
||||
removeSingboxConfig,
|
||||
} from "./singbox.js";
|
||||
import {
|
||||
legacyDeviceRulesFromProfiles,
|
||||
readDeviceProfiles,
|
||||
writeDeviceProfiles,
|
||||
} from "./devices.js";
|
||||
import { matchRoute, detectRuleConflicts } from "./routeMatcher.js";
|
||||
import { tcpPing, resolveHost } from "./ping.js";
|
||||
|
||||
@@ -24,10 +29,11 @@ const SINGBOX_PID_FILE = path.join(settings.dataDir, "singbox.pid");
|
||||
// ─── Direct bypass cache (ipset) ────────────────────────────────────────────
|
||||
const DIRECT_BYPASS_SET = process.env.DIRECT_BYPASS_SET || "vpn_direct_bypass";
|
||||
const DIRECT_BYPASS_TTL = process.env.DIRECT_BYPASS_TTL || "3600";
|
||||
const DIRECT_BYPASS_CACHE = process.env.DIRECT_BYPASS_CACHE === "true";
|
||||
const IPSET_AVAILABLE = (() => {
|
||||
try {
|
||||
spawnSync("ipset", ["version"], { timeout: 1000 });
|
||||
return true;
|
||||
const result = spawnSync("ipset", ["version"], { timeout: 1000 });
|
||||
return !result.error && result.status === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -38,7 +44,7 @@ const IP_RE = /^\d{1,3}(?:\.\d{1,3}){3}$/;
|
||||
let directBypassCount = 0;
|
||||
|
||||
function addToDirectBypass(ip) {
|
||||
if (!IPSET_AVAILABLE || !IP_RE.test(ip)) return;
|
||||
if (!DIRECT_BYPASS_CACHE || !IPSET_AVAILABLE || !IP_RE.test(ip)) return;
|
||||
try {
|
||||
spawnSync(
|
||||
"ipset",
|
||||
@@ -60,7 +66,7 @@ function flushDirectBypass() {
|
||||
}
|
||||
|
||||
function listDirectBypass() {
|
||||
if (!IPSET_AVAILABLE) return [];
|
||||
if (!DIRECT_BYPASS_CACHE || !IPSET_AVAILABLE) return [];
|
||||
try {
|
||||
const result = spawnSync(
|
||||
"ipset",
|
||||
@@ -478,7 +484,7 @@ async function startSingbox() {
|
||||
function publicState() {
|
||||
const state = readJson(settings.statePath, {});
|
||||
const customRules = readJson(settings.customRulesPath, []);
|
||||
const deviceRules = readJson(settings.deviceRulesPath, []);
|
||||
const deviceProfiles = readDeviceProfiles();
|
||||
const { subscriptionUrl, ...rest } = state;
|
||||
return {
|
||||
mode: "gateway",
|
||||
@@ -492,12 +498,16 @@ function publicState() {
|
||||
subscriptionHost: maskSubscriptionUrl(subscriptionUrl),
|
||||
hasSubscription: Boolean(subscriptionUrl),
|
||||
customRules,
|
||||
deviceRules,
|
||||
devicesConfig: deviceProfiles,
|
||||
devices: deviceProfiles.devices,
|
||||
deviceRules: legacyDeviceRulesFromProfiles(deviceProfiles),
|
||||
appliedHistory: state.appliedHistory || [],
|
||||
rulesUpdatedAt: state.rulesUpdatedAt || null,
|
||||
devicesUpdatedAt: state.devicesUpdatedAt || null,
|
||||
rulesAppliedAt: state.rulesAppliedAt || null,
|
||||
bypassMode: Boolean(state.bypassMode),
|
||||
directBypassCount,
|
||||
directBypassEnabled: DIRECT_BYPASS_CACHE,
|
||||
directBypassAvailable: IPSET_AVAILABLE,
|
||||
...rest,
|
||||
};
|
||||
@@ -739,17 +749,69 @@ async function handleApi(req, res) {
|
||||
}
|
||||
|
||||
if (req.method === "GET" && req.url === "/api/device-rules") {
|
||||
const deviceProfiles = readDeviceProfiles();
|
||||
return sendJson(res, 200, {
|
||||
success: true,
|
||||
deviceRules: readJson(settings.deviceRulesPath, []),
|
||||
deviceRules: legacyDeviceRulesFromProfiles(deviceProfiles),
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === "PUT" && req.url === "/api/device-rules") {
|
||||
const body = await readBody(req);
|
||||
const rules = normalizeDeviceRules(body.deviceRules);
|
||||
writeJson(settings.deviceRulesPath, rules);
|
||||
return sendJson(res, 200, { success: true, deviceRules: rules });
|
||||
const devices = [];
|
||||
for (const rule of rules) {
|
||||
rule.sourceIps.forEach((ip, index) => {
|
||||
devices.push({
|
||||
id: `${rule.id}-${index}`,
|
||||
name: rule.name,
|
||||
enabled: rule.enabled,
|
||||
ip,
|
||||
mode: rule.outbound,
|
||||
});
|
||||
});
|
||||
}
|
||||
const profiles = writeDeviceProfiles({
|
||||
defaultTransparentMode: "direct",
|
||||
proxyDefaultMode: "vpn",
|
||||
devices,
|
||||
});
|
||||
const prevState = readJson(settings.statePath, {});
|
||||
writeJson(settings.statePath, {
|
||||
...prevState,
|
||||
devicesUpdatedAt: new Date().toISOString(),
|
||||
});
|
||||
return sendJson(res, 200, {
|
||||
success: true,
|
||||
...profiles,
|
||||
deviceRules: legacyDeviceRulesFromProfiles(profiles),
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === "GET" && req.url === "/api/devices") {
|
||||
const profiles = readDeviceProfiles();
|
||||
return sendJson(res, 200, { success: true, ...profiles });
|
||||
}
|
||||
|
||||
if (req.method === "PUT" && req.url === "/api/devices") {
|
||||
const body = await readBody(req);
|
||||
const input = body.devicesConfig || {
|
||||
defaultTransparentMode: body.defaultTransparentMode || body.defaultMode,
|
||||
proxyDefaultMode: body.proxyDefaultMode,
|
||||
devices: body.devices,
|
||||
};
|
||||
const profiles = writeDeviceProfiles(input);
|
||||
const prevState = readJson(settings.statePath, {});
|
||||
const devicesUpdatedAt = new Date().toISOString();
|
||||
writeJson(settings.statePath, {
|
||||
...prevState,
|
||||
devicesUpdatedAt,
|
||||
});
|
||||
return sendJson(res, 200, {
|
||||
success: true,
|
||||
...profiles,
|
||||
devicesUpdatedAt,
|
||||
});
|
||||
}
|
||||
|
||||
if (req.method === "GET" && req.url === "/api/rule-sets") {
|
||||
@@ -954,6 +1016,8 @@ async function handleApi(req, res) {
|
||||
? Number(body.port)
|
||||
: undefined;
|
||||
const network = String(body.network || "").trim() || undefined;
|
||||
const sourceIp = String(body.sourceIp || "").trim() || undefined;
|
||||
const inbound = String(body.inbound || "").trim() || undefined;
|
||||
|
||||
if (!host && !ip) {
|
||||
return sendJson(res, 400, {
|
||||
@@ -972,12 +1036,12 @@ async function handleApi(req, res) {
|
||||
}
|
||||
|
||||
const rules = readJson(settings.customRulesPath, []);
|
||||
const cached = readJson(settings.subscriptionCachePath, null);
|
||||
const state = readJson(settings.statePath, {});
|
||||
const vpnTag = state.selectedTag || "vpn-out";
|
||||
const result = matchRoute({ host, ip, port, network }, rules, {
|
||||
const result = matchRoute({ host, ip, port, network, sourceIp, inbound }, rules, {
|
||||
routingRuDirect: settings.routingRuDirect,
|
||||
vpnTag,
|
||||
deviceProfiles: readDeviceProfiles(),
|
||||
});
|
||||
|
||||
return sendJson(res, 200, {
|
||||
|
||||
Reference in New Issue
Block a user