luci-app-vpn-policy-routing: improve i18n
[project/luci.git] / applications / luci-app-vpn-policy-routing / luasrc / model / cbi / vpn-policy-routing.lua
1 local readmeURL = "https://github.com/openwrt/packages/tree/master/net/vpn-policy-routing/files/README.md"
2
3 local packageName = "vpn-policy-routing"
4 local uci = require "luci.model.uci".cursor()
5 local sys = require "luci.sys"
6 local util = require "luci.util"
7 local ip = require "luci.ip"
8 local fs = require "nixio.fs"
9 local jsonc = require "luci.jsonc"
10 local http = require "luci.http"
11 local nutil = require "nixio.util"
12 local dispatcher = require "luci.dispatcher"
13 local enabledFlag = uci:get(packageName, "config", "enabled")
14 local enc
15
16 local ubusStatus = util.ubus("service", "list", { name = packageName })
17 if ubusStatus and ubusStatus[packageName] and
18 ubusStatus[packageName]["instances"] and
19 ubusStatus[packageName]["instances"]["main"] and
20 ubusStatus[packageName]["instances"]["main"]["data"] and
21 ubusStatus[packageName]["instances"]["main"]["data"]["status"] and
22 ubusStatus[packageName]["instances"]["main"]["data"]["status"][1] then
23 serviceGateways = ubusStatus[packageName]["instances"]["main"]["data"]["status"][1]["gateway"]
24 serviceGateways = serviceGateways and serviceGateways:gsub('\\n', '\n')
25 serviceGateways = serviceGateways and serviceGateways:gsub('\\033%[0;32m%[\\xe2\\x9c\\x93%]\\033%[0m', '✓')
26 serviceErrors = ubusStatus[packageName]["instances"]["main"]["data"]["status"][1]["error"]
27 serviceErrors = serviceErrors and serviceErrors:gsub('\\n', '\n')
28 serviceErrors = serviceErrors and serviceErrors:gsub('\\033%[0;31mERROR\\033%[0m: ', '')
29 serviceWarnings = ubusStatus[packageName]["instances"]["main"]["data"]["status"][1]["warning"]
30 serviceWarnings = serviceWarnings and serviceWarnings:gsub('\\n', '\n')
31 serviceWarnings = serviceWarnings and serviceWarnings:gsub('\\033%[0;33mWARNING\\033%[0m: ', '')
32 serviceMode = ubusStatus[packageName]["instances"]["main"]["data"]["status"][1]["mode"]
33 end
34
35 local serviceRunning, statusText = false, nil
36 local packageVersion = tostring(util.trim(sys.exec("opkg list-installed " .. packageName .. " | awk '{print $3}'"))) or ""
37 if packageVersion == "" then
38 statusText = translatef("%s is not installed or not found", packageName)
39 end
40 if sys.call("iptables -t mangle -L | grep -q VPR_PREROUTING") == 0 then
41 serviceRunning = true
42 statusText = translate("Running")
43 if serviceMode and serviceMode == "strict" then
44 statusText = translatef("%s (strict mode)", statusText)
45 end
46 else
47 statusText = translate("Stopped")
48 if uci:get(packageName, "config", "enabled") ~= "1" then
49 statusText = translatef("%s (disabled)", statusText)
50 end
51 end
52
53 local t = uci:get("vpn-policy-routing", "config", "supported_interface")
54 if not t then
55 supportedIfaces = ""
56 elseif type(t) == "table" then
57 for key,value in pairs(t) do supportedIfaces = supportedIfaces and supportedIfaces .. ' ' .. value or value end
58 elseif type(t) == "string" then
59 supportedIfaces = t
60 end
61
62 t = uci:get("vpn-policy-routing", "config", "ignored_interface")
63 if not t then
64 ignoredIfaces = ""
65 elseif type(t) == "table" then
66 for key,value in pairs(t) do ignoredIfaces = ignoredIfaces and ignoredIfaces .. ' ' .. value or value end
67 elseif type(t) == "string" then
68 ignoredIfaces = t
69 end
70
71 local lanIPAddr = uci:get("network", "lan", "ipaddr")
72 local lanNetmask = uci:get("network", "lan", "netmask")
73 -- if multiple ip addresses on lan interface, will be returned as table of CIDR notations i.e. {"10.0.0.1/24","10.0.0.2/24"}
74 if (type(lanIPAddr) == "table") then
75 first = true
76 for i,line in ipairs(lanIPAddr) do
77 lanIPAddr = lanIPAddr[i]
78 break
79 end
80 lanIPAddr = lanIPAddr:match("[0-9.]+")
81 end
82 if lanIPAddr and lanNetmask then
83 laPlaceholder = ip.new(lanIPAddr .. "/" .. lanNetmask )
84 end
85
86 function is_wan(name)
87 return name:sub(1,3) == "wan" or name:sub(-3) == "wan"
88 end
89
90 function is_supported_interface(arg)
91 local name=arg['.name']
92 local proto=arg['proto']
93 local ifname=arg['ifname']
94
95 if name and is_wan(name) then return true end
96 if name and supportedIfaces:match('%f[%w]' .. name .. '%f[%W]') then return true end
97 if name and not ignoredIfaces:match('%f[%w]' .. name .. '%f[%W]') then
98 if type(ifname) == "table" then
99 for key,value in pairs(ifname) do
100 if value and value:sub(1,3) == "tun" then return true end
101 if value and value:sub(1,3) == "tap" then return true end
102 if value and value:sub(1,3) == "tor" then return true end
103 if value and fs.access("/sys/devices/virtual/net/" .. value .. "/tun_flags") then return true end
104 end
105 elseif type(ifname) == "string" then
106 if ifname and ifname:sub(1,3) == "tun" then return true end
107 if ifname and ifname:sub(1,3) == "tap" then return true end
108 if ifname and ifname:sub(1,3) == "tor" then return true end
109 if ifname and fs.access("/sys/devices/virtual/net/" .. ifname .. "/tun_flags") then return true end
110 end
111 if proto and proto:sub(1,11) == "openconnect" then return true end
112 if proto and proto:sub(1,4) == "pptp" then return true end
113 if proto and proto:sub(1,4) == "l2tp" then return true end
114 if proto and proto:sub(1,9) == "wireguard" then return true end
115 end
116 end
117
118 m = Map("vpn-policy-routing", translate("VPN and WAN Policy-Based Routing"))
119
120 h = m:section(NamedSection, "config", packageName, translatef("Service Status [%s %s]", packageName, packageVersion))
121 status = h:option(DummyValue, "_dummy", translate("Service Status"))
122 status.template = "vpn-policy-routing/status"
123 status.value = statusText
124 if serviceRunning and serviceGateways and serviceGateways ~= "" then
125 gateways = h:option(DummyValue, "_dummy", translate("Service Gateways"))
126 gateways.template = packageName .. "/status-gateways"
127 gateways.value = serviceGateways
128 end
129 if serviceErrors and serviceErrors ~= "" then
130 errors = h:option(DummyValue, "_dummy", translate("Service Errors"))
131 errors.template = packageName .. "/status-textarea"
132 errors.value = serviceErrors
133 end
134 if serviceWarnings and serviceWarnings ~= "" then
135 warnings = h:option(DummyValue, "_dummy", translate("Service Warnings"))
136 warnings.template = packageName .. "/status-textarea"
137 warnings.value = serviceWarnings
138 end
139 if packageVersion ~= "" then
140 buttons = h:option(DummyValue, "_dummy")
141 buttons.template = packageName .. "/buttons"
142 end
143
144 -- General Options
145 config = m:section(NamedSection, "config", "vpn-policy-routing", translate("Configuration"))
146 config.override_values = true
147 config.override_depends = true
148
149 -- Basic Options
150 config:tab("basic", translate("Basic Configuration"))
151
152 verb = config:taboption("basic", ListValue, "verbosity", translate("Output verbosity"), translate("Controls both system log and console output verbosity."))
153 verb:value("0", translate("Suppress/No output"))
154 verb:value("1", translate("Condensed output"))
155 verb:value("2", translate("Verbose output"))
156 verb.default = 2
157
158 se = config:taboption("basic", ListValue, "strict_enforcement", translate("Strict enforcement"),
159 translatef("See the <a href=\"%s\" target=\"_blank\">README</a> for details.", readmeURL .. "#strict-enforcement"))
160 se:value("0", translate("Do not enforce policies when their gateway is down"))
161 se:value("1", translate("Strictly enforce policies when their gateway is down"))
162 se.default = 1
163
164 dest_ipset = config:taboption("basic", ListValue, "dest_ipset", translate("The ipset option for remote policies"),
165 translatef("Please check the <a href=\"%s\" target=\"_blank\">README</a> before changing this option.", readmeURL .. "#service-configuration-settings"))
166 dest_ipset:value("", translate("Disabled"))
167 dest_ipset:value("ipset", translate("Use ipset command"))
168 dest_ipset:value("dnsmasq.ipset", translate("Use DNSMASQ ipset"))
169 dest_ipset.default = ""
170 dest_ipset.rmempty = true
171
172 src_ipset = config:taboption("basic", ListValue, "src_ipset", translate("The ipset option for local policies"),
173 translatef("Please check the <a href=\"%s\" target=\"_blank\">README</a> before changing this option.", readmeURL .. "#service-configuration-settings"))
174 src_ipset:value("0", translate("Disabled"))
175 src_ipset:value("1", translate("Use ipset command"))
176
177 ipv6 = config:taboption("basic", ListValue, "ipv6_enabled", translate("IPv6 Support"))
178 ipv6:value("0", translate("Disabled"))
179 ipv6:value("1", translate("Enabled"))
180
181 -- Advanced Options
182 config:tab("advanced", translate("Advanced Configuration"),
183 translatef("%sWARNING:%s Please make sure to check the <a href=\"%s\" target=\"_blank\">README</a> before changing anything in this section! Change any of the settings below with extreme caution!%s" , "<br/>&nbsp;&nbsp;&nbsp;&nbsp;<b>", "</b>", readmeURL .. "#service-configuration-settings", "<br/><br/>"))
184
185 supportedIface = config:taboption("advanced", DynamicList, "supported_interface", translate("Supported Interfaces"), translate("Allows to specify the list of interface names (in lower case) to be explicitly supported by the service. Can be useful if your OpenVPN tunnels have dev option other than tun* or tap*."))
186 supportedIface.optional = false
187
188 ignoredIface = config:taboption("advanced", DynamicList, "ignored_interface", translate("Ignored Interfaces"), translate("Allows to specify the list of interface names (in lower case) to be ignored by the service. Can be useful if running both VPN server and VPN client on the router."))
189 ignoredIface.optional = false
190
191 timeout = config:taboption("advanced", Value, "boot_timeout", translate("Boot Time-out"), translate("Time (in seconds) for service to wait for WAN gateway discovery on boot."))
192 timeout.optional = false
193 timeout.rmempty = true
194
195 insert = config:taboption("advanced", ListValue, "iptables_rule_option", translate("IPTables rule option"), translate("Select Append for -A and Insert for -I."))
196 insert:value("append", translate("Append"))
197 insert:value("insert", translate("Insert"))
198 insert.default = "append"
199
200 iprule = config:taboption("advanced", ListValue, "iprule_enabled", translate("IP Rules Support"), translate("Add an ip rule, not an iptables entry for policies with just the local address. Use with caution to manipulte policies priorities."))
201 iprule:value("0", translate("Disabled"))
202 iprule:value("1", translate("Enabled"))
203
204 icmp = config:taboption("advanced", ListValue, "icmp_interface", translate("Default ICMP Interface"), translate("Force the ICMP protocol interface."))
205 icmp:value("", translate("No Change"))
206 icmp:value("wan", translate("WAN"))
207 uci:foreach("network", "interface", function(s)
208 local name=s['.name']
209 if is_supported_interface(s) then icmp:value(name, name:upper()) end
210 end)
211 icmp.rmempty = true
212
213 append_local = config:taboption("advanced", Value, "append_src_rules", translate("Append local IP Tables rules"), translate("Special instructions to append iptables rules for local IPs/netmasks/devices."))
214 append_local.rmempty = true
215
216 append_remote = config:taboption("advanced", Value, "append_dest_rules", translate("Append remote IP Tables rules"), translate("Special instructions to append iptables rules for remote IPs/netmasks."))
217 append_remote.rmempty = true
218
219 wantid = config:taboption("advanced", Value, "wan_tid", translate("WAN Table ID"), translate("Starting (WAN) Table ID number for tables created by the service."))
220 wantid.rmempty = true
221 wantid.placeholder = "201"
222 wantid.datatype = 'and(uinteger, min(201))'
223
224 wanmark = config:taboption("advanced", Value, "wan_mark", translate("WAN Table FW Mark"), translate("Starting (WAN) FW Mark for marks used by the service. High starting mark is used to avoid conflict with SQM/QoS. Change with caution together with") .. " " .. translate("Service FW Mask") .. ".")
225 wanmark.rmempty = true
226 wanmark.placeholder = "0x010000"
227 wanmark.datatype = "hex(8)"
228
229 fwmask = config:taboption("advanced", Value, "fw_mask", translate("Service FW Mask"), translate("FW Mask used by the service. High mask is used to avoid conflict with SQM/QoS. Change with caution together with") .. " " .. translate("WAN Table FW Mark") .. ".")
230 fwmask.rmempty = true
231 fwmask.placeholder = "0xff0000"
232 fwmask.datatype = "hex(8)"
233
234 config:tab("webui", translate("Web UI Configuration"))
235
236 webui_enable_column = config:taboption("webui", ListValue, "webui_enable_column", translate("Show Enable Column"), translate("Shows the enable checkbox column for policies, allowing you to quickly enable/disable specific policy without deleting it."))
237 webui_enable_column:value("0", translate("Disabled"))
238 webui_enable_column:value("1", translate("Enabled"))
239
240 webui_protocol_column = config:taboption("webui", ListValue, "webui_protocol_column", translate("Show Protocol Column"), translate("Shows the protocol column for policies, allowing you to assign a specific protocol to a policy."))
241 webui_protocol_column:value("0", translate("Disabled"))
242 webui_protocol_column:value("1", translate("Enabled"))
243
244 webui_supported_protocol = config:taboption("webui", DynamicList, "webui_supported_protocol", translate("Supported Protocols"), translate("Display these protocols in protocol column in Web UI."))
245 webui_supported_protocol.optional = false
246
247 webui_chain_column = config:taboption("webui", ListValue, "webui_chain_column", translate("Show Chain Column"), translate("Shows the chain column for policies, allowing you to assign a PREROUTING, FORWARD, INPUT or OUTPUT chain to a policy."))
248 webui_chain_column:value("0", translate("Disabled"))
249 webui_chain_column:value("1", translate("Enabled"))
250
251 webui_sorting = config:taboption("webui", ListValue, "webui_sorting", translate("Show Up/Down Buttons"), translate("Shows the Up/Down buttons for policies, allowing you to move a policy up or down in the list."))
252 webui_sorting:value("0", translate("Disabled"))
253 webui_sorting:value("1", translate("Enabled"))
254 webui_sorting.default = "1"
255
256
257 -- Policies
258 p = m:section(TypedSection, "policy", translate("Policies"), translate("Comment, interface and at least one other field are required. Multiple local and remote addresses/devices/domains and ports can be space separated. Placeholders below represent just the format/syntax and will not be used if fields are left blank."))
259 p.template = "cbi/tblsection"
260 enc = tonumber(uci:get("vpn-policy-routing", "config", "webui_sorting"))
261 if not enc or enc ~= 0 then
262 p.sortable = true
263 end
264 p.anonymous = true
265 p.addremove = true
266
267 enc = tonumber(uci:get("vpn-policy-routing", "config", "webui_enable_column"))
268 if enc and enc ~= 0 then
269 le = p:option(Flag, "enabled", translate("Enabled"))
270 le.default = "1"
271 end
272
273 local comment = uci:get_first("vpn-policy-routing", "policy", "comment")
274 if comment then
275 p:option(Value, "comment", translate("Comment"))
276 else
277 p:option(Value, "name", translate("Name"))
278 end
279
280 la = p:option(Value, "src_addr", translate("Local addresses / devices"))
281 if laPlaceholder then
282 la.placeholder = laPlaceholder
283 end
284 la.rmempty = true
285 la.datatype = 'list(neg(or(host,network,macaddr)))'
286
287 lp = p:option(Value, "src_port", translate("Local ports"))
288 lp.datatype = 'list(neg(or(portrange, string)))'
289 lp.placeholder = "0-65535"
290 lp.rmempty = true
291
292 ra = p:option(Value, "dest_addr", translate("Remote addresses / domains"))
293 ra.datatype = 'list(neg(host))'
294 ra.placeholder = "0.0.0.0/0"
295 ra.rmempty = true
296
297 rp = p:option(Value, "dest_port", translate("Remote ports"))
298 rp.datatype = 'list(neg(or(portrange, string)))'
299 rp.placeholder = "0-65535"
300 rp.rmempty = true
301
302 enc = tonumber(uci:get("vpn-policy-routing", "config", "webui_protocol_column"))
303 if enc and enc ~= 0 then
304 proto = p:option(ListValue, "proto", translate("Protocol"))
305 proto:value("", "AUTO")
306 proto.default = ""
307 proto.rmempty = true
308 enc = uci:get_list("vpn-policy-routing", "config", "webui_supported_protocol")
309 local count = 0
310 for key, value in pairs(enc) do
311 count = count + 1
312 proto:value(value:lower(), value:gsub(" ", "/"):upper())
313 end
314 if count == 0 then
315 enc = { "tcp", "udp", "tcp udp", "icmp", "all" }
316 for key,value in pairs(enc) do
317 proto:value(value:lower(), value:gsub(" ", "/"):upper())
318 end
319 end
320 end
321
322 enc = tonumber(uci:get("vpn-policy-routing", "config", "webui_chain_column"))
323 if enc and enc ~= 0 then
324 chain = p:option(ListValue, "chain", translate("Chain"))
325 chain:value("", "PREROUTING")
326 chain:value("FORWARD", "FORWARD")
327 chain:value("INPUT", "INPUT")
328 chain:value("OUTPUT", "OUTPUT")
329 chain.default = ""
330 chain.rmempty = true
331 end
332
333 gw = p:option(ListValue, "interface", translate("Interface"))
334 gw.datatype = "network"
335 gw.rmempty = false
336 uci:foreach("network", "interface", function(s)
337 local name=s['.name']
338 if is_wan(name) then
339 gw:value(name, name:upper())
340 if not gw.default then gw.default = name end
341 elseif is_supported_interface(s) then
342 gw:value(name, name:upper())
343 end
344 end)
345
346 dscp = m:section(NamedSection, "config", "vpn-policy-routing", translate("DSCP Tagging"),
347 translatef("Set DSCP tags (in range between 1 and 63) for specific interfaces. See the <a href=\"%s\" target=\"_blank\">README</a> for details.", readmeURL .. "#dscp-tag-based-policies"))
348 uci:foreach("network", "interface", function(s)
349 local name=s['.name']
350 if is_supported_interface(s) then
351 local x = dscp:option(Value, name .. "_dscp", name:upper() .. " " .. translate("DSCP Tag"))
352 x.rmempty = true
353 x.datatype = "range(1,63)"
354 end
355 end)
356
357 -- Includes
358 inc = m:section(TypedSection, "include", translate("Custom User File Includes"),
359 translatef("Run the following user files after setting up but before restarting DNSMASQ. See the <a href=\"%s\" target=\"_blank\">README</a> for details.", readmeURL .. "#custom-user-files"))
360 inc.template = "cbi/tblsection"
361 inc.sortable = true
362 inc.anonymous = true
363 inc.addremove = true
364
365 finc = inc:option(Flag, "enabled", translate("Enabled"))
366 finc.optional = false
367 finc.default = "1"
368 inc:option(Value, "path", translate("Path")).optional = false
369
370 return m