#!/opt/lnxall_app/bin/lua

-- Created by jiaqiang.ye@lnxall.com
-- UCI network with NetworkManager backend module
-- 2023/11/03

local invoker = require 'invoker'

local nmcli = {}

nmcli.BRPFX = 'lxbr_'
nmcli.IPV6M = 'disabled'

-- for rk3568 gateway, nmcli is so old that it does not support `ipv6.method disabled
local nmok, nmver = invoker.invoke(invoker.OUTPUT, "nmcli", "--version")
if nmok == 0 and type(nmver) == "string" and
	(string.find(nmver, "1.14.6", 1, true) or string.find(nmver, "1.10.6", 1, true)) then
	nmcli.IPV6M = 'ignore' -- ipv6.method should be 'ignore' on rk3568 gateway
end
nmok, nmver = nil, nil

local function del_con_name(conn)
	local conlen = 0
	if type(conn) == "string" then
		conlen = string.len(conn)
	end

	if conlen <= 0 then return false end
	if conlen >= 4 and conn:sub(1, 3) == "br-" then
		return false
	end

	if conlen >= 7 and conn:sub(1, 6) == "net4g_" then
		return false
	end

	if conn == "lo" then
		return false
	end

	if conn == "docker0" then
		return false
	end

	if conn == "docker1" then
		return false
	end

	if conn == "virtbr0" then
		return false
	end

	return true
end

-- function to delete all configured connections
nmcli["deleteall"] = function ()
	local fmt = string.format
	local okay, output = invoker.invoke(invoker.OUTPUT + invoker.CLOSEFD,
		"nmcli", "-m", "tabular", "-t", "con", "show")
	if okay ~= 0 then
		io.stderr:write("Error, failed to show NetworkManager connections\n")
		io.stderr:flush()
		return false
	end
	if type(output) ~= "string" or #output == 0 then return true end

	for line in string.gmatch(output, "[^\r\n]+") do
		local con = string.match(line, "^([^:]+)")
		if del_con_name(con) then
			io.stdout:write(fmt("Deleting connection: %s...\n", con))
			io.stdout:flush()
			invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "down", con)
			local res = invoker.invoke(0, "nmcli", "con", "delete", con)
			if res ~= 0 then
				io.stderr:write(fmt("Error delete connection: %s.\n", con))
				io.stderr:flush()
			end
		end
	end
	return true
end

local function netdev_exists(name)
	if type(name) ~= "string" or #name == 0 then return false end
	local okay = invoker.invoke(invoker.NOSTDIO, "ip", "link", "show", "dev", name)
	return okay == 0
end
local proto_map = { ["auto"] = "dhcp", ["manual"] = "static" }

local function read_wifi_passwd(conn)
	local fmt = string.format
	local format = "/etc/NetworkManager/system-connections/%s.nmconnection"
	local conc = invoker.readfile(fmt(format, conn))
	if type(conc) ~= "string" or string.len(conc) == 0 then
		format = "/etc/sysconfig/network-scripts/ifcfg-%s"
		conc = invoker.readfile(fmt(format, conn))
		if type(conc) ~= "string" or string.len(conc) == 0 then
			io.stderr:write(fmt("Error, failed to locate connection profile: %s\n", conn))
			io.stderr:flush()
			return nil
		end
	end

	-- process line by line, I think it better
	for pline in string.gmatch(conc, "[^\r\n]+") do
		local passwd = string.match(pline, "^psk=(.+)$")
		if passwd then return passwd end
	end
	io.stderr:write(fmt("Warning, no WiFi password found for '%s'\n", conn))
	io.stderr:flush()
	return nil
end

local function nmcli_coninfo(name)
	local fmt, mch = string.format, string.match
	local iflags = invoker.OUTPUT + 0x70000
	local okay, output = invoker.invoke(iflags, "nmcli", "con", "show", name)
	if okay ~= 0 then
		io.stderr:write(fmt("Error, failed to show NetworkManager connection: %s\n", name))
		io.stderr:flush()
		return nil
	end
	if type(output) ~= "string" or string.len(output) == 0 then
		io.stderr:write(fmt("Error, no information about connection: %s\n", name))
		io.stderr:flush()
		return nil
	end

	local regex = "%s:[ \t]+([^ \t\r\n][^\r\n]*)"
	local netdev = mch(output, fmt(regex, 'connection.interface%-name'))
	if not netdev_exists(netdev) then
		io.stderr:write(fmt("Error, no device found for connection: %s\n", name))
		io.stderr:flush()
		nmcli.delete(name)
		return nil
	end

	-- get network type
	local ntype = mch(output, fmt(regex, "connection.type"))
	if type(ntype) ~= "string" then
		io.stderr:write(fmt("Error, network type not found for %s\n", name))
		io.stderr:flush()
		return nil
	end

	local proto = mch(output, fmt(regex, 'ipv4.method'))
	if proto == nil then proto = 'auto' end
	if proto ~= "auto" and proto ~= "manual" then
		if type(proto) ~= "string" then proto = "unknown" end
		io.stderr:write(fmt("Error, invalid ipv4.method for %s: %s\n", name, proto))
		io.stderr:flush()
		return nil
	end
	proto = proto_map[proto]

	local is_dhcpv6 = false
	local proto_v6 = mch(output, fmt(regex, 'ipv6.method'))
	if proto_v6 == nil then proto_v6 = nmcli.IPV6M end
	if proto_v6 == "auto" then is_dhcpv6 = true end
	if proto_v6 ~= "auto" and proto_v6 ~= "manual" and proto_v6 ~= "disabled" and proto_v6 ~= "ignore" then
		if type(proto_v6) ~= "string" then proto_v6 = "unknown" end
		io.stderr:write(fmt("Error, invalid ipv6.method for %s: %s\n", name, proto_v6))
		io.stderr:flush()
		return nil
	end

	local metric = mch(output, fmt(regex, 'ipv4.route%-metric'))
	if type(metric) == "string" then metric = tonumber(metric) end
	if type(metric) ~= "number" then metric = -1 end

	local macaddr = mch(output, fmt(regex, "cloned%-mac%-address"))
	if type(macaddr) == "string" then macaddr = macaddr:lower() end
	if type(macaddr) ~= "string" or macaddr == "--" or
		macaddr == 'permanent' or macaddr == 'preserve' then macaddr = nil end

	local disabled = true
	local autocon = mch(output, fmt(regex, "connection.autoconnect"))
	if autocon == "yes" then disabled = false end

	-- get configured ipv4 static address, if any
	local ipmask = mch(output, fmt(regex, "ipv4.addresses"))
	if ipmask == "--" then ipmask = nil end

	local dns1, dns2 = nil, nil
	-- get configured DNS address
	local dnses = mch(output, fmt(regex, "ipv4.dns"))
	if type(dnses) == "string" and dnses ~= "--" then
		if string.find(dnses, ",", 1, true) then
			dns1, dns2 = mch(dnses, "([%d%.]+),([%d%.]+)")
		else
			dns1 = dnses
		end
	else
		dnses = nil
	end

	local w_ssid, w_pass, w_enc = nil, nil, nil
	-- for wifi connection, get the SSID/password/encryption
	if string.find(ntype, 'wireless', 1, true) then
		w_pass = read_wifi_passwd(name)
		w_enc = mch(output, fmt(regex, "key%-mgmt"))
		if w_enc == nil then w_enc = "none" end
		w_ssid = mch(output, fmt(regex, "wireless.ssid"))
		if type(w_ssid) ~= "string" then
			w_ssid, w_pass, w_enc = nil, nil, nil
			io.stderr:write(fmt("Error, cannot determine SSID for ''\n", name))
			io.stderr:flush()
		end
	end

	local ninfo = { ["iface"] = name, ["netdev"] = netdev, ["macaddr"] = macaddr,
		proto = proto, disabled = disabled, metric = metric, dns1 = dns1, dns2 = dns2,
		_ipmask = ipmask, _dnses = dnses, _ntype = ntype, ["dhcpv6"] = is_dhcpv6,
		["ssid"] = w_ssid, ["passwd"] = w_pass, ["wifienc"] = w_enc }
	if proto == "dhcp" then return ninfo end

	local ipaddr, mask = nil, nil
	if type(ipmask) == "string" then
		ipaddr, mask = mch(ipmask, "([^/]+)/(%d+)")
		if type(mask) == "string" then mask = tonumber(mask) end
	end
	if not invoker.isipv4(ipaddr) or not mask then
		io.stderr:write(fmt("Error, invalid IPv4 address found for %s/%s", name, netdev))
		io.stderr:flush()
		ipaddr, mask = nil, nil
	end

	-- get default gateway
	local gateway = mch(output, fmt(regex, 'ipv4.gateway'))
	if not invoker.isipv4(gateway) then gateway = nil end

	ninfo["ipaddr"] = ipaddr
	ninfo["netmask"] = mask
	ninfo["gateway"] = gateway
	return ninfo
end

local function wan_new(name, ucfg)
	local ndev = ucfg["netdev"]
	invoker.invoke(0, "ip", "addr", "flush", "dev", ndev)
	local okay = invoker.invoke(0, "nmcli", "con", "add", "con-name",
		name, "ifname", ndev, "type", "ethernet")

	if okay ~= 0 then
		io.stderr:write(string.format("Error, failed add new NM connection: %s\n", name))
		io.stderr:flush()
		return false
	end

	local is_dhcpv6 = ucfg["dhcpv6"]
	local is_dhcp = ucfg["proto"] == "dhcp"
	local cmds = { "nmcli", "con", "mod", name,
		"ipv4.method", is_dhcp and 'auto' or 'manual', "ipv6.method", is_dhcpv6 and 'auto' or nmcli.IPV6M }
	local pinfo = invoker.readfile("/proc/cpuinfo")
	if type(pinfo) ~= "string" then pinfo = "what the fuck" end
	if is_dhcp and not string.find(pinfo, "Freescale i.MX6", 1, true) then
		cmds[#cmds + 1] = "ipv4.dhcp-timeout"
		cmds[#cmds + 1] = "2147483647"
	end

	local tmpval = ucfg["macaddr"]
	if type(tmpval) == "string" then
		cmds[#cmds + 1] = "ethernet.cloned-mac-address"
		cmds[#cmds + 1] = tmpval
	end

	tmpval = ucfg["metric"]
	if type(tmpval) == "number" and tmpval >= 0 then
		cmds[#cmds + 1] = "ipv4.route-metric"
		cmds[#cmds + 1] = tostring(tmpval)
	end

	tmpval = ucfg["disabled"]
	cmds[#cmds + 1] = "autoconnect"
	cmds[#cmds + 1] = tmpval and "no" or "yes"

	if not is_dhcp then
		if not ucfg["ipaddr"] or not ucfg["netmask"] then
			io.stderr:write(string.format("Error, no static ipaddr/mask found for %s\n", name))
			io.stderr:flush()
			invoker.invoke(0, "nmcli", "con", "delete", name)
			return false
		end
		cmds[#cmds + 1] = "ipv4.addr"
		cmds[#cmds + 1] = string.format("%s/%d", ucfg.ipaddr, ucfg.netmask)

		tmpval = ucfg["gateway"]
		if type(tmpval) == "string" and #tmpval > 0 then
			cmds[#cmds + 1] = "ipv4.gateway"
			cmds[#cmds + 1] = tmpval
		end
	end

	tmpval = ucfg["dns1"]
	if type(tmpval) == "string" then
		cmds[#cmds + 1] = "+ipv4.dns"
		cmds[#cmds + 1] = tmpval
	end

	tmpval = ucfg["dns2"]
	if type(tmpval) == "string" then
		cmds[#cmds + 1] = "+ipv4.dns"
		cmds[#cmds + 1] = tmpval
	end

	okay = invoker.invoke(0, cmds)
	if okay ~= 0 then
		io.stderr:write(string.format("Error, failed to add new connection: %s\n", name))
		io.stderr:write(table.concat(cmds, "\n\t"))
		io.stderr:flush()
		return false
	end

	local updown = ucfg["disabled"] and "down" or "up"
	invoker.invoke(invoker.NOSTDIO, "nmcli", "-w", "6", "con", updown, name)
	return true
end

local function lan_new(lancfg)
	local lfmt = string.format
	local okay = invoker.invoke(0, 'nmcli', 'con', 'add', 'type',
		'bridge', 'con-name', 'lan', 'ifname', 'br-lan')
	if not okay then
		io.stderr:write("Error, failed to create br-lan connection\n")
		io.stderr:flush()
		return false
	end

	local brmac = lancfg['macaddr'];
	if type(brmac) ~= "string" or #brmac == 0 then brmac = nil end
	for _, iface in ipairs(lancfg["netdevs"]) do
		okay = invoker.invoke(0, 'nmcli', 'con', 'add', 'type', 'bridge-slave',
			'con-name', nmcli.BRPFX .. iface, 'ifname', iface,
			'autoconnect', 'yes', 'master', 'br-lan',
			brmac and "ethernet.cloned-mac-address" or nil, brmac)
		if okay ~= 0 then
			io.stderr:write(lfmt("Error, failed to bridge network device: %s\n", iface))
			io.stderr:flush()
			return false
		end
	end

	-- disable STP for bridge
	-- invoker.invoke(0, "nmcli", "con", "modify", "lan", "bridge.stp", "no")

	local cmds = { "nmcli", "con", "modify", "lan" }
	cmds[#cmds + 1] = "bridge.stp"
	cmds[#cmds + 1] = "no"
	cmds[#cmds + 1] = "ipv4.addr"
	cmds[#cmds + 1] = lfmt("%s/%d", lancfg["ipaddr"], lancfg["netmask"])
	if invoker.isipv4(lancfg['gateway']) then
		cmds[#cmds + 1] = "ipv4.gateway"
		cmds[#cmds + 1] = lancfg['gateway']
		cmds[#cmds + 1] = "ipv4.route-metric"
		cmds[#cmds + 1] = tostring(lancfg['metric'])
	end
	if type(brmac) == "string" then
		cmds[#cmds + 1] = "ethernet.cloned-mac-address"
		cmds[#cmds + 1] = lancfg['macaddr']
	end
	cmds[#cmds + 1] = "ipv4.method"
	cmds[#cmds + 1] = "manual"
	cmds[#cmds + 1] = "ipv6.method"
	cmds[#cmds + 1] = nmcli.IPV6M
	cmds[#cmds + 1] = "autoconnect"
	cmds[#cmds + 1] = "yes"

	okay = invoker.invoke(0, cmds)
	if okay ~= 0 then
		io.stderr:write("Error, failed to modify br-lan connection\n")
		io.stderr:flush()
		return false
	end
	invoker.invoke(invoker.NOSTDIO, "nmcli", "-w", "6", "con", "up", "lan")
	return true
end

nmcli["delete"] = function (name)
	if del_con_name(name) then
		io.stdout:write(string.format("Deleting interface: %s ...\n", name))
		io.stdout:flush()
		invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "down", name)
		local okay = invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "delete", name)
		return okay == 0
	end
	return false
end

nmcli["update"] = function (ucicfg)
	local con = ucicfg["iface"]
	if type(con) ~= "string" or #con == 0 then
		io.stderr:write("Error, invalid NM connection name found.\n")
		io.stderr:flush()
		return false
	end

	invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "delete", con)
	return wan_new(con, ucicfg)
end

local function bridged_delete(nmcfgs)
	local dlist = {}
	for what, _ in pairs(nmcfgs) do
		if type(what) == "string" and #what > 0x5 and what:sub(1, 5) == nmcli.BRPFX then
			nmcli.delete(what)
			dlist[#dlist + 1] = what
		end
	end

	for _, name in ipairs(dlist) do
		nmcfgs[name] = nil
	end
	return true
end

local function bridged_ifaces(ccfgs)
	local blist = {}
	if type(ccfgs) ~= "table" then return blist end
	for cn, _ in pairs(ccfgs) do
		if type(cn) == "string" and #cn > 0x5 and cn:sub(1, 5) == nmcli.BRPFX then
			local ndev = cn:sub(6)
			blist[ndev] = true
		end
	end
	return blist
end

nmcli["updatelan"] = function (lancfg, nmcfgs)
	local nmlan = nmcfgs['lan']
	if type(nmlan) ~= "table" or lancfg.macaddr ~= nmlan.macaddr then
		nmcli.delete('lan')
		bridged_delete(nmcfgs)
		nmcfgs['lan'] = nil -- avoid lan interface deletion
		return lan_new(lancfg), true
	end

	local changed = false
	local iflist = bridged_ifaces(nmcfgs)
	for _, iface in ipairs(lancfg["netdevs"]) do
		if not iflist[iface] then
			io.stdout:write(string.format("INFO: bridge interface: %s...\n", iface))
			io.stdout:flush()
			changed = true
			local brn = nmcli.BRPFX .. iface
			invoker.invoke(0, "nmcli", "con", "add", "type", "bridge-slave",
				"con-name", brn, "ifname", iface, "autoconnect", "yes", "master", "br-lan")
			invoker.invoke(0, "nothup", "nmcli", "-w", "6", "con", "up", brn)
		end
		iflist[iface] = nil
		nmcfgs[nmcli.BRPFX .. iface] = nil -- avoid deletion
	end

	local okay = 0
	if lancfg["ipaddr"] ~= nmlan["ipaddr"] or lancfg["netmask"] ~= nmlan["netmask"] or
		lancfg["gateway"] ~= nmlan["gateway"] or (invoker.isipv4(nmlan["gateway"]) and
		lancfg["metric"] ~= nmlan["metric"]) then
		local ipv4 = string.format("%s/%d", lancfg["ipaddr"], lancfg["netmask"])
		local lancmd = { "nmcli", "con", "modify", "lan", "ipv4.addr", ipv4, }
		if invoker.isipv4(lancfg["gateway"]) then
			lancmd[#lancmd + 1] = "ipv4.gateway"
			lancmd[#lancmd + 1] = lancfg["gateway"]
			lancmd[#lancmd + 1] = "ipv4.route-metric"
			lancmd[#lancmd + 1] = tostring(lancfg["metric"])
		elseif invoker.isipv4(nmlan["gateway"]) then
			lancmd[#lancmd + 1] = "-ipv4.gateway"
			lancmd[#lancmd + 1] = ""
		end
		if type(lancfg['macaddr']) == "string" then
			lancmd[#lancmd + 1] = "ethernet.cloned-mac-address"
			lancmd[#lancmd + 1] = lancfg['macaddr']
		elseif type(nmlan['macaddr']) == "string" then
			lancmd[#lancmd + 1] = "-ethernet.cloned-mac-address"
			lancmd[#lancmd + 1] = ""
		end
		lancmd[#lancmd + 1] = "ipv4.route-metric"
		lancmd[#lancmd + 1] = tostring(lancfg["metric"])

		-- print(table.concat(lancmd, " "))
		okay = invoker.invoke(0, lancmd)
		io.stdout:write(string.format("Modify br-lan IPv4 address as %s: %d\n", ipv4, okay))
		io.stdout:flush()
		changed = true
	end

	if changed then okay = invoker.invoke(invoker.NOSTDIO, "nmcli", "-w", "6", "con", "up", "lan") end
	nmcfgs['lan'] = nil -- avoid deletion
	return okay == 0, changed
end

local function wifi_keymgmt(encryption)
	if type(encryption) ~= "string" then return "none" end
	if encryption == 'none' then return 'none' end
	if encryption == 'sae' then return 'sae' end -- WPA3 Personal only
	if encryption == 'owe' then return 'owe' end -- Opportunistic Wireless Encryption
	if encryption == 'wpa3' then return "wpa-eap-suite-b-192" end -- WPA3 enterprise only
	if string.find(encryption, "wpa", 1, true) then return "wpa-eap" end -- WPA3/WPA2 Enterprise
	if string.find(encryption, "wep", 1, true) then return "ieee8021x" end -- Dynamic WEP
	return "wpa-psk" -- default, WPA2/WPA3 Personal
end

local function wifiwan_new(wcfg)
	local okay = invoker.invoke(0, "nmcli", "con", "add", "type", "wifi", "con-name",
		"wifiwan", "autoconnect", "no", "ipv4.method", "auto", "ipv6.method", nmcli.IPV6M,
		"ipv4.route-metric", tostring(wcfg['metric']), "ifname", wcfg['netdev'],
		"ssid", wcfg['ssid'])
	if okay ~= 0 then
		io.stderr:write("Error, failed to create wifiwan connection.\n")
		io.stderr:flush()
		return false
	end

	local enc = wifi_keymgmt(wcfg['wifienc'])
	local cmds = { "nmcli", "con", "modify", "wifiwan", "wifi.hidden", "true" }
	if enc ~= "none" then
		-- cmds[#cmds + 1] = "802-11-wireless-security.auth-alg"; cmds[#cmds + 1] = "open"
		cmds[#cmds + 1] = "802-11-wireless-security.key-mgmt"; cmds[#cmds + 1] = enc
		cmds[#cmds + 1] = "802-11-wireless-security.psk"; cmds[#cmds + 1] = wcfg['passwd']
	end
	cmds[#cmds + 1] = "autoconnect"; cmds[#cmds + 1] = "yes"
	okay = invoker.invoke(0, cmds)
	if okay ~= 0 then
		io.stderr:write("Error, failed to modify wifiwan connection.\n")
		io.stderr:flush()
		return false
	end
	-- invoke nothup instead of nmcli to avoid hang:
	invoker.invoke(0, "nothup", "nmcli", "-w", "6", "con", "up", "wifiwan")
	return true
end

nmcli["wifiwan"] = function(wcfg, nmcfgs)
	local wifi = nmcfgs['wifiwan']
	if type(wifi) ~= "table" then
		nmcli.delete('wifiwan')
		return wifiwan_new(wcfg)
	end

	nmcfgs['wifiwan'] = nil -- set to nil to avoid deletion
	local cmds, changed = { "nmcli", "con", "modify", "wifiwan" }, false
	if wcfg.metric ~= wifi.metric then
		changed = true
		cmds[#cmds + 1] = "ipv4.route-metric"
		cmds[#cmds + 1] = tostring(wcfg.metric)
	end

	if wcfg.ssid ~= wifi.ssid then
		changed = true
		cmds[#cmds + 1] = "802-11-wireless.ssid"
		cmds[#cmds + 1] = wcfg.ssid
	end

	local enc = wifi_keymgmt(wcfg['wifienc'])
	if enc ~= wifi['wifienc'] then
		changed = true
		if enc == "none" then
			nmcli.delete('wifiwan')
			return wifiwan_new(wcfg)
		end
		cmds[#cmds + 1] = "802-11-wireless-security.key-mgmt"
		cmds[#cmds + 1] = enc
	end

	if wcfg.passwd ~= wifi['passwd'] then
		changed = true
		cmds[#cmds + 1] = "802-11-wireless-security.psk"
		cmds[#cmds + 1] = wcfg.passwd
	end

	if changed then
		io.stdout:write("INFO: configuration for wifiwan has changed.\n")
		io.stdout:flush()
		invoker.invoke(0, cmds) -- carry out the modifications
		invoker.invoke(0, "nothup", "nmcli", "-w", "6", "con", "up", "wifiwan")
	end
	return true
end

nmcli['wlan'] = function (conname, lcfg, nmcfgs)
	local lancfg = nmcfgs[conname]
	if type(lancfg) ~= "table" then
		invoker.invoke(0, "nmcli", "con", "add", "type", "bridge-slave",
			"con-name", conname, "ifname", lcfg["netdev"],
			"autoconnect", "yes", "master", "br-lan")
		return true
	end

	nmcfgs[conname] = nil -- remove connection to avoid deletion
	if lcfg['netdev'] ~= lancfg['netdev'] then
		invoker.invoke(0, "nmcli", "con", "modify", conname, "ifname", lcfg['netdev'])
		invoker.invoke(0, "nmcli", "-w", "6", "con", "up", conname)
	end
	return true
end

-- if the connection does not exist, add it
nmcli["getall"] = function ()
	local okay, output = invoker.invoke(invoker.OUTPUT,
		"nmcli", "-m", "tabular", "-t", "con", "show")
	if okay ~= 0 then
		io.stderr:write("Error, failed to show NetworkManager connections\n")
		io.stderr:flush()
		return nil
	end
	if type(output) ~= "string" or #output == 0 then return nil end

	local netinfo, ncount, rerun = {}, 0, 0
	for line in string.gmatch(output, "[^\r\n]+") do
		local con = string.match(line, "^([^:]+)")
		if con and #con > 0 then
			local info = nmcli_coninfo(con)
			if netinfo[con] then
				local uuid = string.match(line, "^[^:]+:([^:]+)")
				if uuid and #uuid > 0 then
					rerun = rerun + 1
					io.stderr:write("Warning: DELETE DUPLICATED CONNECTION: " .. con .. "\n")
					io.stderr:flush()
					invoker.invoke(invoker.NOSTDIO, "nmcli", "con", "del", uuid)
				end
			else
				if info then ncount = ncount + 1 end
				netinfo[con] = info
			end
		end
	end
	if rerun >= 1 then
		invoker.invoke(0, "nothup", "/bin/sh", "-c", "sleep 15 ; netuci")
		io.stderr:write("Restarting netuci in 15 seconds due to duplicated connections\n")
		io.stderr:flush()
	end
	if ncount > 0 then return netinfo end
	return nil
end

return nmcli
