#!/usr/bin/lua -- Created by jiaqiang.ye@lnxall.com -- 2023/12/08 -- Simple /etc/resolv.conf monitor local uci = require "uci" local bit32 = require "bit32" local invoker = require "invoker" local gfmt = string.format local g_dns = { [1] = "8.8.8.8", [2] = "114.114.114.114" } local g_outtime = 12 local g_renum = 3 local function check_resolv() local has_sr = "" if invoker.statfile('/lib/systemd/systemd-resolved') then local conts, restart = {}, false local syscfg = '/etc/systemd/resolved.conf' conts[#conts + 1] = "# Generated by DNSWATCHER\n[Resolve]\n" conts[#conts + 1] = gfmt("DNS=%s %s\n", g_dns[1], g_dns[2]) conts[#conts + 1] = "DNSSEC=no\n" conts[#conts + 1] = "Cache=yes\n" conts[#conts + 1] = "LLMNR=false\n" conts = table.concat(conts) local sdat = invoker.readfile(syscfg) if sdat ~= conts then restart = true local sfile = io.open(syscfg, "wb") if sfile then sfile:write(conts) sfile:close(); sfile = nil end end if not restart then local _, output = invoker.invoke(invoker.OUTPUT, "systemctl", "status", "systemd-resolved") if type(output) ~= "string" then output = "" end if not string.find(output, "Active: active (running)", 1, true) then restart = true end end if restart then io.stdout:write("Restarting systemd-resolved service...\n") io.stdout:flush() invoker.invoke(0, "systemctl", "enable", "systemd-resolved") invoker.invoke(0, "systemctl", "restart", "systemd-resolved") end has_sr = "nameserver 127.0.0.53\n" end local content = gfmt("# Generated by DNSWATCHER\n%snameserver %s\nnameserver %s\noptions timeout:%s attempts:%s\n", has_sr, g_dns[1], g_dns[2], g_outtime, g_renum) local rfile = "/etc/resolv.conf" local cont = invoker.readfile(rfile) if cont == content then return true end io.stdout:write(gfmt("Warning: Updating DNS settings: %s ...\n", rfile)) io.stdout:flush() local resolv = io.open(rfile, "wb") if resolv then resolv:write(content) resolv:close(); resolv = nil invoker.sync() return true end return false end local function watch_resolv(delay) local flags = bit32.bor(invoker.IN_CREATE, invoker.IN_DELETE_SELF, invoker.IN_MODIFY, invoker.IN_CLOSE_WRITE) local rfile = '/etc/resolv.conf' local rpath = invoker.realpath(rfile) if type(rpath) == "string" then rfile = rpath end rpath = nil local changed, errmsg = invoker.watchfile(flags, delay * 1000, nil, rfile) if changed == nil then if type(errmsg) ~= "string" then errmsg = "unknown watchfile error" end io.stderr:write(errmsg); io.stderr:write("\n"); io.stderr:flush() invoker.waitsec(delay) return false end if changed and changed > 0 then invoker.msleep(1500) -- delay 1.5 second to avoid modifying by too many processes check_resolv() end return true end local function mainloop() invoker.setname("DNSWATCHER") local ucidns = uci.cursor('/etc/config') if ucidns then local dns1 = ucidns:get('dnsconfig', "@lnxdnscfg[0]", "dns1") local dns2 = ucidns:get('dnsconfig', "@lnxdnscfg[0]", "dns2") local outtime = ucidns:get('dnsconfig', "@lnxdnscfg[0]", "outtime") local renum = ucidns:get('dnsconfig', "@lnxdnscfg[0]", "attempts") ucidns:close(); ucidns = nil if invoker.isipv4(dns1) then g_dns[1] = dns1 end if invoker.isipv4(dns2) then g_dns[2] = dns2 end if outtime then outtime = tonumber(outtime) if outtime and outtime > 1 and outtime < 13 then g_outtime = outtime end end if attempts then attempts = tonumber(attempts) if attempts and attempts > 0 and attempts < 6 then g_renum = attempts end end end io.stdout:write(gfmt("DNS servers: %s, %s timeout:%s attempts:%s\n", g_dns[1], g_dns[2], g_outtime, g_renum)) io.stdout:flush() -- delay 45 seconds before continue for fresh boot: local now = invoker.uptime() if now < 45 then invoker.waitsec(45 - now) now = invoker.uptime() end local nex = now + 900 while true do check_resolv() now = invoker.uptime() while now < nex do watch_resolv(nex - now) now = invoker.uptime() end nex = nex + 900 end end mainloop() os.exit(1)