fix: zapret2 daemon privileges and lua loader, dnsmasq reload auto-repair (1.3.20)

This commit is contained in:
Dushnilin
2026-09-04 14:45:01 +03:00
parent e901f9d41a
commit 6bedef4c77
12 changed files with 4629 additions and 18 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "fe-app-tachyon",
"version": "1.3.19",
"version": "1.3.20",
"license": "MIT",
"type": "module",
"scripts": {
+2 -2
View File
@@ -205,9 +205,9 @@ function dnsmasq_configure_default_instance() {
if (!default_has_tachyon_dns) {
backup_dnsmasq_config_option("noresolv", "tachyon_noresolv");
backup_dnsmasq_config_option("cachesize", "tachyon_cachesize");
backup_dnsmasq_config_option("rebind_protection", "tachyon_rebind_protection");
backup_dnsmasq_config_option("localuse", "tachyon_localuse");
}
backup_dnsmasq_config_option("rebind_protection", "tachyon_rebind_protection");
backup_dnsmasq_config_option("localuse", "tachyon_localuse");
backup_dnsmasq_config_option("addn_hosts", "tachyon_addn_hosts");
uci_delete("dhcp.@dnsmasq[0].addn_hosts");
@@ -235,7 +235,13 @@ function live_pid_count(path) {
}
function ensure_runtime_dirs(cfg) {
return command_success_from_args([ "mkdir", "-p", cfg.state_dir, cfg.pid_dir, cfg.child_pid_dir, cfg.log_dir ]);
let ok = command_success_from_args([ "mkdir", "-p", cfg.state_dir, cfg.pid_dir, cfg.child_pid_dir, cfg.log_dir ]);
if (ok) {
command_status_from_args([ "chmod", "0755", cfg.state_dir, cfg.pid_dir, cfg.child_pid_dir, cfg.log_dir ]);
if (cfg.kind == "zapret2" && fs.stat("/opt/zapret2") != null)
command_status_from_args([ "chmod", "-R", "a+rX", "/opt/zapret2" ]);
}
return ok;
}
function uci_value_contains(value, needle) {
@@ -583,8 +589,25 @@ function status_json(cfg) {
message = "legacy zapret runtime paths are still present and should be migrated";
else if (running > expected || supervisors > expected)
message = "unexpected Tachyon-managed " + cfg.binary_name + " processes are running without matching action=" + cfg.action + " rules";
else if (configured && !ready)
else if (configured && !ready) {
message = "action=" + cfg.action + " is configured, but the Tachyon-managed " + cfg.binary_name + " runtime is not ready";
if (running < expected) {
for (let section in sections) {
let name = section_name(section);
let log_data = fs.readfile(cfg.log_dir + "/" + name + ".log");
if (log_data != null && log_data != "") {
let log_lines = split(trim(log_data), "\n");
if (length(log_lines) > 0) {
let last_line = trim(log_lines[length(log_lines) - 1]);
if (last_line != "") {
message += " (" + name + ": " + last_line + ")";
break;
}
}
}
}
}
}
else if (standalone_conflict)
message = "standalone " + cfg.status_label + " is active together with Tachyon action=" + cfg.action + "; queues are separate, but packet-level policy overlap is possible";
else if (!configured && !provider && pkg)
@@ -18,15 +18,23 @@ function config(ctx) {
let desync_mark = getenv("ZAPRET2_DESYNC_MARK") || runtime_constants.ZAPRET2_DESYNC_MARK;
let provider_lua_dir = getenv("ZAPRET2_PROVIDER_LUA_DIR") || runtime_constants.ZAPRET2_PROVIDER_LUA_DIR;
let passwd = fs.readfile("/etc/passwd");
let ws_user = (passwd != null && index(passwd, "daemon:") >= 0) ? "daemon" : "root";
let base_args = [
"--user=" + ws_user,
"--fwmark=" + desync_mark
];
let candidate_dirs = [
provider_lua_dir,
"/opt/zapret2/lua",
"/opt/zapret/lua",
"/usr/share/zapret2/lua",
"/usr/share/zapret/lua",
"/etc/zapret2/lua",
"/usr/lib/zapret2/lua"
"/etc/zapret/lua",
"/usr/lib/zapret2/lua",
"/usr/lib/zapret/lua",
lib_dir + "/providers/zapret2/lua"
];
let lua_scripts = [
"zapret-lib.lua",
@@ -39,14 +47,10 @@ function config(ctx) {
if (!dir)
continue;
let p = dir + "/" + script;
if (fs.stat(p) != null) {
if (fs.stat(p) != null || fs.stat(p + ".gz") != null) {
found = p;
break;
}
if (fs.stat(p + ".gz") != null) {
found = p + ".gz";
break;
}
}
if (found != null)
push(base_args, "--lua-init=@" + found);
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,576 @@
-- standard automation/orchestration code
-- this is related to making dynamic strategy decisions without rewriting or altering strategy function code
-- orchestrators can decide which instances to call or not to call or pass them dynamic arguments
-- failure and success detectors test potential block conditions for orchestrators
-- standard host key generator for per-host storage
-- arg: reqhost - require hostname, do not work with ip
-- arg: nld=N - cut hostname to N level domain. NLD=2 static.intranet.microsoft.com => microsoft.com
function standard_hostkey(desync)
local hostkey = desync.track and desync.track.hostname
if hostkey then
if desync.arg.nld and tonumber(desync.arg.nld)>0 and not (desync.track and desync.track.hostname_is_ip) then
-- dissect_nld returns nil if domain is invalid or does not have this NLD
-- fall back to original hostkey if it fails
local hktemp = dissect_nld(hostkey, tonumber(desync.arg.nld))
if hktemp then
hostkey = hktemp
end
end
elseif not desync.arg.reqhost then
hostkey = host_ip(desync)
end
return hostkey
end
-- per-host storage
-- arg: key - a string - table name inside autostate table. to allow multiple orchestrator instances to use single host storage
-- arg: hostkey - hostkey generator function name
function automate_host_record(desync)
local hostkey, hkf, askey
if desync.arg.hostkey then
if type(_G[desync.arg.hostkey])~="function" then
error("automate: invalid hostkey function '"..desync.arg.hostkey.."'")
end
hkf = _G[desync.arg.hostkey]
else
hkf = standard_hostkey
end
hostkey = hkf(desync)
if not hostkey then
DLOG("automate: host record key unavailable")
return nil
end
askey = (desync.arg.key and #desync.arg.key>0) and desync.arg.key or desync.func_instance
DLOG("automate: host record key 'autostate."..askey.."."..hostkey.."'")
if not autostate then
autostate = {}
end
if not autostate[askey] then
autostate[askey] = {}
end
if not autostate[askey][hostkey] then
autostate[askey][hostkey] = {}
end
return autostate[askey][hostkey]
end
-- per-connection storage
function automate_conn_record(desync)
if not desync.track then return nil end
if not desync.track.lua_state.automate then
desync.track.lua_state.automate = {}
end
return desync.track.lua_state.automate
end
-- counts failure, optionally (if crec is given) prevents dup failure counts in a single connection
-- if 'maxtime' between failures is exceeded then failure count is reset
-- return true if threshold ('fails') is reached
-- hres is host record. host or ip bound table
-- cres is connection record. connection bound table
function automate_failure_counter(hrec, crec, fails, maxtime)
if crec and crec.failure then
DLOG("automate: duplicate failure in the same connection. not counted")
else
if crec then crec.failure = true end
local tnow=os.time()
if not hrec.failure_time_last then
hrec.failure_time_last = tnow
end
if not hrec.failure_counter then
hrec.failure_counter = 0
elseif tnow>(hrec.failure_time_last + maxtime) then
DLOG("automate: failure counter reset because last failure was "..(tnow - hrec.failure_time_last).." seconds ago")
hrec.failure_counter = 0
end
hrec.failure_counter = hrec.failure_counter + 1
hrec.failure_time_last = tnow
if b_debug then DLOG("automate: failure counter "..hrec.failure_counter..(fails and ('/'..fails) or '')) end
if fails and hrec.failure_counter>=fails then
hrec.failure_counter = nil -- reset counter
return true
end
end
return false
end
-- resets failure counter if it has started counting
function automate_failure_counter_reset(hrec)
if hrec.failure_counter then
DLOG("automate: failure counter reset")
hrec.failure_counter = nil
end
end
-- location is url compatible with Location: header
-- hostname is original hostname
function is_dpi_redirect(hostname, location)
local ds = dissect_url(location)
if ds and ds.domain then
local sld1 = dissect_nld(hostname,2)
local sld2 = dissect_nld(ds.domain,2)
return sld2 and sld1~=sld2 and true or false
end
return false
end
function standard_detector_defaults(arg)
return {
inseq = tonumber(arg.inseq) or 4096,
retrans = tonumber(arg.retrans) or 3,
maxseq = tonumber(arg.maxseq) or 32768,
udp_in = tonumber(arg.udp_in) or 1,
udp_out = tonumber(arg.udp_out) or 4,
no_http_redirect = arg.no_http_redirect,
no_rst = arg.no_rst,
reset = arg.reset
}
end
-- standard failure detector
-- works with tcp and udp
-- detected failures:
-- incoming RST
-- incoming http redirection
-- outgoing retransmissions
-- udp too much out with too few in
-- arg: maxseq=<rseq> - tcp: test retransmissions only within this relative sequence. default is 32K
-- arg: retrans=N - tcp: retrans count threshold. default is 3
-- arg: reset - send RST to retransmitter to break long wait
-- arg: inseq=<rseq> - tcp: maximum relative sequence number to treat incoming RST as DPI reset. default is 4K
-- arg: no_http_redirect - tcp: disable http_reply dpi redirect trigger
-- arg: no_rst - tcp: disable incoming RST trigger
-- arg: udp_out - udp: >= outgoing udp packets. default is 4
-- arg: udp_in - udp: with <= incoming udp packets. default is 1
function standard_failure_detector(desync, crec)
local arg = standard_detector_defaults(desync.arg)
local trigger = false
if desync.dis.tcp then
local seq = pos_get(desync,'s')
if desync.outgoing then
if #desync.dis.payload>0 and arg.retrans and arg.maxseq>0 and seq<=arg.maxseq and (crec.retrans or 0)<arg.retrans then
if is_retransmission(desync) then
crec.retrans = crec.retrans and (crec.retrans+1) or 1
DLOG("standard_failure_detector: retransmission "..crec.retrans.."/"..arg.retrans)
trigger = crec.retrans>=arg.retrans
if trigger and arg.reset then
local dis = deepcopy(desync.dis)
dis.payload = nil
dis_reverse(dis)
dis.tcp.th_flags = TH_RST
dis.tcp.th_win = desync.track and desync.track.pos.reverse.tcp.winsize or 64
dis.tcp.options = nil
if dis.ip6 then
dis.ip6.ip6_flow = (desync.track and desync.track.pos.reverse.ip6_flow) and desync.track.pos.reverse.ip6_flow or 0x60000000;
end
DLOG("standard_failure_detector: sending RST to retransmitter")
rawsend_dissect(dis, {ifout = desync.ifin})
end
end
end
else
if not arg.no_rst and arg.inseq>0 and bitand(desync.dis.tcp.th_flags, TH_RST)~=0 and seq>=1 then
trigger = seq<=arg.inseq
if b_debug then
if trigger then
DLOG("standard_failure_detector: incoming RST s"..seq.." in range s"..arg.inseq)
else
DLOG("standard_failure_detector: not counting incoming RST s"..seq.." beyond s"..arg.inseq)
end
end
elseif not arg.no_http_redirect and desync.l7payload=="http_reply" and desync.track.hostname then
local hdis = http_dissect_reply(desync.dis.payload)
if hdis and (hdis.code==302 or hdis.code==307) then
local idx_loc = array_field_search(hdis.headers, "header_low", "location")
if idx_loc then
trigger = is_dpi_redirect(desync.track.hostname, hdis.headers[idx_loc].value)
if b_debug then
if trigger then
DLOG("standard_failure_detector: http redirect "..hdis.code.." to '"..hdis.headers[idx_loc].value.."'. looks like DPI redirect.")
else
DLOG("standard_failure_detector: http redirect "..hdis.code.." to '"..hdis.headers[idx_loc].value.."'. NOT a DPI redirect.")
end
end
end
end
end
end
elseif desync.dis.udp then
if desync.outgoing then
if arg.udp_out>0 then
local pos_out = pos_get(desync,'n',false)
local pos_in = pos_get(desync,'n',true)
trigger = pos_out>=arg.udp_out and pos_in<=arg.udp_in
if trigger then
if b_debug then
DLOG("standard_failure_detector: arg.udp_out "..pos_out..">="..arg.udp_out.." arg.udp_in "..pos_in.."<="..arg.udp_in)
end
end
end
end
end
return trigger
end
-- standard success detector
-- success means previous failures were temporary and counter should be reset
-- detected successes:
-- tcp: outgoing seq is beyond 'maxseq' and maxseq>0
-- tcp: incoming seq is beyond 'inseq' and inseq>0
-- udp: incoming packets count > `udp_in` and `udp_out`>0
-- arg: maxseq=<rseq> - tcp: success if outgoing relative sequence is beyond this value. default is 32K
-- arg: inseq=<rseq> - tcp: success if incoming relative sequence is beyond this value. default is 4K
-- arg: udp_out - udp : must be nil or >0 to test udp_in
-- arg: udp_in - udp: if number if incoming packets > udp_in it means success
function standard_success_detector(desync, crec)
local arg = standard_detector_defaults(desync.arg)
if desync.dis.tcp then
local seq = pos_get(desync,'s')
if desync.outgoing then
if arg.maxseq>0 and seq>arg.maxseq then
DLOG("standard_success_detector: outgoing s"..seq.." is beyond s"..arg.maxseq..". treating connection as successful")
return true
end
else
if arg.inseq>0 and seq>arg.inseq then
DLOG("standard_success_detector: incoming s"..seq.." is beyond s"..arg.inseq..". treating connection as successful")
return true
end
end
elseif desync.dis.udp then
if not desync.outgoing then
local pos = pos_get(desync,'n')
if arg.udp_out>0 and pos>arg.udp_in then
if b_debug then
DLOG("standard_success_detector: arg.udp_in "..pos..">"..arg.udp_in)
end
return true
end
end
end
return false
end
-- calls success and failure detectors
-- resets counter if success is detected
-- increases counter if failure is detected
-- returns true if failure counter exceeds threshold
function automate_failure_check(desync, hrec, crec)
if crec.nocheck then return false end
local failure_detector, success_detector
if desync.arg.failure_detector then
if type(_G[desync.arg.failure_detector])~="function" then
error("automate: invalid failure detector function '"..desync.arg.failure_detector.."'")
end
failure_detector = _G[desync.arg.failure_detector]
else
failure_detector = standard_failure_detector
end
if desync.arg.success_detector then
if type(_G[desync.arg.success_detector])~="function" then
error("automate: invalid success detector function '"..desync.arg.success_detector.."'")
end
success_detector = _G[desync.arg.success_detector]
else
success_detector = standard_success_detector
end
if success_detector(desync, crec) then
crec.nocheck = true
DLOG("automate: success detected")
automate_failure_counter_reset(hrec)
return false
end
if failure_detector(desync, crec) then
crec.nocheck = true
DLOG("automate: failure detected")
local fails = tonumber(desync.arg.fails) or 3
local maxtime = tonumber(desync.arg.time) or 60
return automate_failure_counter(hrec, crec, fails, maxtime)
end
return false
end
-- circularily change strategy numbers when failure count reaches threshold ('fails')
-- this orchestrator requires redirection of incoming traffic to cache RST and http replies !
-- each orchestrated instance must have strategy=N arg, where N starts from 1 and increment without gaps
-- if 'final' arg is present in an orchestrated instance it stops rotation
-- arg: fails=N - failture count threshold. default is 3
-- arg: time=<sec> - if last failure happened earlier than `maxtime` seconds ago - reset failure counter. default is 60.
-- arg: success_detector - success detector function name
-- arg: failure_detector - failure detector function name
-- arg: hostkey - hostkey generator function name
-- args for failure detector - see standard_failure_detector or your own detector
-- args for success detector - see standard_success_detector or your own detector
-- args for hostkey generator - see standard_hostkey or your own generator
-- test case: --in-range=-s34228 --lua-desync=circular --lua-desync=argdebug:strategy=1 --lua-desync=argdebug:strategy=2
function circular(ctx, desync)
local function count_strategies(hrec)
if not hrec.ctstrategy then
local uniq={}
local n
for i,instance in pairs(desync.plan) do
if instance.arg.strategy then
n = tonumber(instance.arg.strategy)
if not n or n<1 then
error("circular: strategy number '"..tostring(instance.arg.strategy).."' is invalid")
end
uniq[tonumber(instance.arg.strategy)] = true
if instance.arg.final then
hrec.final = n
end
end
end
n=0
for i,v in pairs(uniq) do
n=n+1
end
if n~=#uniq then
error("circular: strategies numbers must start from 1 and increment. gaps are not allowed.")
end
hrec.ctstrategy = n
end
end
-- take over execution. prevent further instance execution in case of error
orchestrate(ctx, desync)
if not desync.track then
DLOG_ERR("circular: conntrack is missing but required")
return
end
local hrec = automate_host_record(desync)
if not hrec then
DLOG("circular: passing with no tampering")
return
end
count_strategies(hrec)
if hrec.ctstrategy==0 then
error("circular: add strategy=N tag argument to each following instance ! N must start from 1 and increment")
end
if not hrec.nstrategy then
DLOG("circular: start from strategy 1")
hrec.nstrategy = 1
end
local verdict = VERDICT_PASS
if hrec.final~=hrec.nstrategy then
local crec = automate_conn_record(desync)
if automate_failure_check(desync, hrec, crec) then
hrec.nstrategy = (hrec.nstrategy % hrec.ctstrategy) + 1
DLOG("circular: rotate strategy to "..hrec.nstrategy)
if hrec.nstrategy == hrec.final then
DLOG("circular: final strategy "..hrec.final.." reached. will rotate no more.")
end
end
end
DLOG("circular: current strategy "..hrec.nstrategy)
while true do
local instance = plan_instance_pop(desync)
if not instance then break end
if instance.arg.strategy and tonumber(instance.arg.strategy)==hrec.nstrategy then
verdict = plan_instance_execute(desync, verdict, instance)
end
end
return verdict
end
-- test iff functions
function cond_true(desync)
return true
end
function cond_false(desync)
return false
end
-- arg: percent - of true . 50 by default
function cond_random(desync)
return math.random(0,99)<(tonumber(desync.arg.percent) or 50)
end
-- this iif function detects packets having 'arg.pattern' string in their payload
-- test case : --lua-desync=condition:iff=cond_payload_str:pattern=1234 --lua-desync=argdebug:testarg=1 --lua-desync=argdebug:testarg=2:morearg=xyz
-- test case (true) : echo aaz1234zzz | ncat -4u 1.1.1.1 443
-- test case (false) : echo aaze124zzz | ncat -4u 1.1.1.1 443
function cond_payload_str(desync)
if not desync.arg.pattern then
error("cond_payload_str: missing 'pattern'")
end
return desync.dis.payload and string.find(desync.dis.payload,desync.arg.pattern,1,true)
end
-- true if dissect is tcp and timestamp tcp option is present
function cond_tcp_has_ts(desync)
return desync.dis.tcp and find_tcp_option(desync.dis.tcp.options, TCP_KIND_TS)
end
-- exec lua code in "code" arg and return it's result
function cond_lua(desync)
if not desync.arg.cond_code then
error("cond_lua: no 'cond_code' parameter")
end
local fname = desync.func_instance.."_cond_cond_code"
if not _G[fname] then
local err
_G[fname], err = load(desync.arg.cond_code, fname)
if not _G[fname] then
error(err)
end
end
-- allow dynamic cond_code to access desync
_G.desync = desync
local res, v = pcall(_G[fname])
_G.desync = nil
if not res then
error(v);
end
return v
end
-- check iff function available. error if not
function require_iff(desync, name)
if not desync.arg.iff then
error(name..": missing 'iff' function")
end
if type(_G[desync.arg.iff])~="function" then
error(name..": invalid 'iff' function '"..desync.arg.iff.."'")
end
end
-- execute further desync instances only if user-provided 'iff' function returns true
-- for example, this can be used by custom protocol detectors
-- arg: iff - condition function. takes desync as arg and returns bool. (cant use 'if' because of reserved word)
-- arg: neg - invert condition function result
-- arg: instances - how many instances execute conditionally. all if not defined
-- test case : --lua-desync=condition:iff=cond_random --lua-desync=argdebug:testarg=1 --lua-desync=argdebug:testarg=2:morearg=xyz
function condition(ctx, desync)
require_iff(desync, "condition")
orchestrate(ctx, desync)
if logical_xor(_G[desync.arg.iff](desync), desync.arg.neg) then
DLOG("condition: true")
else
DLOG("condition: false")
plan_clear(desync, tonumber(desync.arg.instances))
if #desync.plan>0 then
DLOG("condition: executing remaining "..#desync.plan.." instance(s)")
end
end
return replay_execution_plan(desync)
end
-- execute further desync instances.
-- each instance must have "cond" arg and may optionally have "cond_neg" arg.
-- "cond" - condition function. "neg" - invert condition function result
-- arg: instances - how many instances execute conditionally. all if not defined
function per_instance_condition(ctx, desync)
orchestrate(ctx, desync)
local verdict = VERDICT_PASS
local n = 0
local max = tonumber(desync.arg.instances)
while not max or n<max do
local instance = plan_instance_pop(desync)
if not instance then break end
if instance.arg.cond then
if type(_G[instance.arg.cond])~="function" then
error("per_instance_condition: invalid 'iff' function '"..instance.arg.cond.."'")
end
-- preapply exec plan to feed cond function correct args
apply_execution_plan(desync, instance)
if logical_xor(_G[instance.arg.cond](desync), instance.arg.cond_neg) then
verdict = plan_instance_execute_preapplied(desync, verdict, instance)
else
DLOG("per_instance_condition: condition not satisfied. skipping '"..instance.func_instance.."'")
end
else
DLOG("per_instance_condition: no 'cond' arg in '"..instance.func_instance.."'. skipping")
end
n = n + 1
end
if #desync.plan>0 then
DLOG("per_instance_condition: executing remaining "..#desync.plan.." instance(s) unconditionally")
end
return verdict_aggregate(verdict, replay_execution_plan(desync))
end
-- clear execution plan if user provided 'iff' functions returns true
-- can be used with other orchestrators to stop execution conditionally
-- arg: iff - condition function. takes desync as arg and returns bool. (cant use 'if' because of reserved word)
-- arg: neg - invert condition function result
-- test case : --in-range=-s1 --lua-desync=circular --lua-desync=stopif:iff=cond_random:strategy=1 --lua-desync=argdebug:strategy=1 --lua-desync=argdebug:strategy=2
function stopif(ctx, desync)
require_iff(desync, "stopif")
orchestrate(ctx, desync)
if logical_xor(_G[desync.arg.iff](desync), desync.arg.neg) then
DLOG("stopif: true")
plan_clear(desync)
else
-- do not do anything. allow other orchestrator to finish the plan
DLOG("stopif: false")
end
end
-- repeat following 'instances' 'repeats' times, execute others with no tampering
-- arg: instances - number of following instances to be repeated. 1 by default
-- arg: repeats - number of repeats
-- arg: iff - condition function to continue execution. takes desync as arg and returns bool. (cant use 'if' because of reserved word)
-- arg: neg - invert condition function result
-- arg: stop - do not replay remaining execution plan after 'instances'
-- arg: clear - clear execution plan after 'instances'
-- test case : --lua-desync=repeater:repeats=2:instances=2 --lua-desync=argdebug:v=1 --lua-desync=argdebug:v=2 --lua-desync=argdebug:v=3
function repeater(ctx, desync)
local repeats = tonumber(desync.arg.repeats)
if not repeats then
error("repeater: missing 'repeats'")
end
local iff = desync.arg.iff or "cond_true"
if type(_G[iff])~="function" then
error("repeater: invalid 'iff' function '"..iff.."'")
end
orchestrate(ctx, desync)
if #desync.plan==0 then
DLOG("repeater: execution plan is empty - nothing to repeat")
return
end
local neg = desync.arg.neg
local stop = desync.arg.stop
local clear = desync.arg.clear
local verdict = VERDICT_PASS
local instances = tonumber(desync.arg.instances) or 1
local repinst = desync.func_instance
if instances>#desync.plan then
instances = #desync.plan
end
-- save plan copy
local plancopy = deepcopy(desync.plan)
for r=1,repeats do
if not logical_xor(_G[iff](desync), neg) then
DLOG("repeater: break by iff")
break
end
DLOG("repeater: "..repinst.." "..r.."/"..repeats)
-- nested orchestrators can also pop
local ct_end = #desync.plan - instances
repeat
local instance = plan_instance_pop(desync)
verdict = plan_instance_execute(desync, verdict, instance)
until #desync.plan <= ct_end
-- rollback desync plan
desync.plan = deepcopy(plancopy)
end
-- remove repeated instances from desync plan
for i=1,instances do
table.remove(desync.plan,1)
end
if clear then
plan_clear(desync)
return verdict
elseif stop then
return verdict
end
-- replay the rest
return verdict_aggregate(verdict, replay_execution_plan(desync))
end
File diff suppressed because it is too large Load Diff
+3 -1
View File
@@ -1261,6 +1261,7 @@ function reload(reason) {
}
let dnsmasq_managed_state = dnsmasq_has_tachyon_managed_state() ? 1 : 0;
let dnsmasq_default_complete = dns_apply_success([ "default-config-complete" ]) ? 1 : 0;
let list_update_sources = module_success(STATE_UC, [ "has-list-update-sources" ]) ? 1 : 0;
let nft_list_update_sources = module_success(STATE_UC, [ "has-nft-list-update-sources" ]) ? 1 : 0;
let runtime_cache_needs_rebuild = 0;
@@ -1276,7 +1277,8 @@ function reload(reason) {
as_string(dnsmasq_managed_state),
as_string(list_update_sources),
as_string(nft_list_update_sources),
as_string(runtime_cache_needs_rebuild)
as_string(runtime_cache_needs_rebuild),
as_string(dnsmasq_default_complete)
]);
remove_file(current_reload_state_file);
+9 -4
View File
@@ -114,6 +114,9 @@ function emit_reload_plan(previous, current, context) {
else if (context.dnsmasq_managed_state)
needs.dnsmasq_restore = true;
}
else if (!current.dont_touch_dhcp && context.dnsmasq_default_complete === false) {
needs.dnsmasq_configure = true;
}
if (changed.cron)
needs.cron_refresh = true;
@@ -226,7 +229,8 @@ function reload_plan() {
dnsmasq_managed_state: arg_bool(ARGV[28]),
has_list_update_sources: arg_bool(ARGV[29]),
has_nft_list_update_sources: arg_bool(ARGV[30]),
runtime_cache_needs_rebuild: arg_bool(ARGV[31])
runtime_cache_needs_rebuild: arg_bool(ARGV[31]),
dnsmasq_default_complete: length(ARGV) > 32 ? arg_bool(ARGV[32]) : true
};
emit_reload_plan(previous, current, context);
@@ -292,7 +296,8 @@ function plan_state_files() {
dnsmasq_managed_state: arg_bool(ARGV[4]),
has_list_update_sources: arg_bool(ARGV[5]),
has_nft_list_update_sources: arg_bool(ARGV[6]),
runtime_cache_needs_rebuild: arg_bool(ARGV[7])
runtime_cache_needs_rebuild: arg_bool(ARGV[7]),
dnsmasq_default_complete: length(ARGV) > 8 ? arg_bool(ARGV[8]) : true
};
emit_reload_plan(previous, current, context);
@@ -306,6 +311,6 @@ else if (mode == "plan-state-files")
plan_state_files();
else {
warn("Usage: service/reload.uc plan ...\n");
warn(" service/reload.uc plan-state-files <previous-state> <current-state> <force-reload> <dnsmasq-managed-state> <list-update-sources> <nft-list-update-sources> <runtime-cache-needs-rebuild>\n");
warn(" service/reload.uc plan-state-files <previous-state> <current-state> <force-reload> <dnsmasq-managed-state> <list-update-sources> <nft-list-update-sources> <runtime-cache-needs-rebuild> [dnsmasq-default-complete]\n");
exit(1);
}
}
+9 -1
View File
@@ -895,9 +895,13 @@ function dnsmasq_signature_body(settings, dnsmasq, legacy_dnsmasq_present) {
body = signature_add_value(body, "dhcp.@dnsmasq[0].server", option(dnsmasq, "server", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].noresolv", option(dnsmasq, "noresolv", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].cachesize", option(dnsmasq, "cachesize", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].rebind_protection", option(dnsmasq, "rebind_protection", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].localuse", option(dnsmasq, "localuse", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].tachyon_server", option(dnsmasq, "tachyon_server", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].tachyon_noresolv", option(dnsmasq, "tachyon_noresolv", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].tachyon_cachesize", option(dnsmasq, "tachyon_cachesize", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].tachyon_rebind_protection", option(dnsmasq, "tachyon_rebind_protection", ""));
body = signature_add_value(body, "dhcp.@dnsmasq[0].tachyon_localuse", option(dnsmasq, "tachyon_localuse", ""));
body = signature_add_value(body, "dhcp.tachyon.present", arg_bool(legacy_dnsmasq_present) ? "1" : "0");
return body;
@@ -1843,9 +1847,13 @@ function uci_dnsmasq() {
server: uci_get("dhcp.@dnsmasq[0].server"),
noresolv: uci_get("dhcp.@dnsmasq[0].noresolv"),
cachesize: uci_get("dhcp.@dnsmasq[0].cachesize"),
rebind_protection: uci_get("dhcp.@dnsmasq[0].rebind_protection"),
localuse: uci_get("dhcp.@dnsmasq[0].localuse"),
tachyon_server: uci_get("dhcp.@dnsmasq[0].tachyon_server"),
tachyon_noresolv: uci_get("dhcp.@dnsmasq[0].tachyon_noresolv"),
tachyon_cachesize: uci_get("dhcp.@dnsmasq[0].tachyon_cachesize")
tachyon_cachesize: uci_get("dhcp.@dnsmasq[0].tachyon_cachesize"),
tachyon_rebind_protection: uci_get("dhcp.@dnsmasq[0].tachyon_rebind_protection"),
tachyon_localuse: uci_get("dhcp.@dnsmasq[0].tachyon_localuse")
};
}
+13 -1
View File
@@ -294,9 +294,13 @@ cat >"$WORK_DIR/service-dns-state.json" <<'JSON'
"server": [ "1.1.1.1#53", "8.8.8.8" ],
"noresolv": "1",
"cachesize": "0",
"rebind_protection": "1",
"localuse": "0",
"tachyon_server": [ "127.0.0.42#53" ],
"tachyon_noresolv": "0",
"tachyon_cachesize": "1500"
"tachyon_cachesize": "1500",
"tachyon_rebind_protection": "0",
"tachyon_localuse": "1"
},
"legacy_dnsmasq_present": true
}
@@ -320,12 +324,20 @@ cat >"$WORK_DIR/dnsmasq-signature.expected" <<'EOF_DNSMASQ_SIG'
1
[dhcp.@dnsmasq[0].cachesize]
0
[dhcp.@dnsmasq[0].rebind_protection]
1
[dhcp.@dnsmasq[0].localuse]
0
[dhcp.@dnsmasq[0].tachyon_server]
127.0.0.42#53
[dhcp.@dnsmasq[0].tachyon_noresolv]
0
[dhcp.@dnsmasq[0].tachyon_cachesize]
1500
[dhcp.@dnsmasq[0].tachyon_rebind_protection]
0
[dhcp.@dnsmasq[0].tachyon_localuse]
1
[dhcp.tachyon.present]
1
EOF_DNSMASQ_SIG
+12
View File
@@ -162,6 +162,13 @@ assert_plan_value "$plan" changed_dnsmasq 1
assert_plan_value "$plan" needs_dnsmasq_configure 1
assert_plan_value "$plan" needs_dnsmasq_restore 0
args=("${base_args[@]}")
args[31]=0
plan="$(run_case dnsmasq_repair_incomplete "${args[@]}")"
assert_plan_value "$plan" changed_dnsmasq 0
assert_plan_value "$plan" needs_dnsmasq_configure 1
assert_plan_value "$plan" needs_dnsmasq_restore 0
args=("${base_args[@]}")
args[14]=dns2
args[25]=1
@@ -204,6 +211,11 @@ plan="$(run_state_case state_unchanged "$previous_state" "$current_state" 0 0 0
assert_plan_value "$plan" has_work 0
assert_plan_value "$plan" changed_sing_box 0
plan="$(run_state_case state_repair_incomplete "$previous_state" "$current_state" 0 0 0 0 0 0)"
assert_plan_value "$plan" has_work 1
assert_plan_value "$plan" changed_dnsmasq 0
assert_plan_value "$plan" needs_dnsmasq_configure 1
write_state "$current_state" service_trigger_signature svc dns sb nft zq zr z2q z2r br list cron "alpha beta gamma" 0
plan="$(run_state_case state_urltest_new "$previous_state" "$current_state" 0 0 0 0 0)"
assert_plan_value "$plan" urltest_new_enabled_sections "gamma"