smartdns-lite.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. /*************************************************************************
  2. *
  3. * Copyright (C) 2018-2024 Ruilin Peng (Nick) <[email protected]>.
  4. *
  5. * smartdns is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * smartdns is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. 'use strict';
  19. 'require fs';
  20. 'require uci';
  21. 'require form';
  22. 'require view';
  23. 'require poll';
  24. 'require rpc';
  25. 'require ui';
  26. var conf = 'smartdns';
  27. var callServiceList = rpc.declare({
  28. object: 'service',
  29. method: 'list',
  30. params: ['name'],
  31. expect: { '': {} }
  32. });
  33. var pollAdded = false;
  34. function getServiceStatus() {
  35. return L.resolveDefault(callServiceList(conf), {})
  36. .then(function (res) {
  37. var is_running = false;
  38. try {
  39. is_running = res[conf]['instances']['smartdns']['running'];
  40. } catch (e) {
  41. }
  42. return is_running;
  43. })
  44. }
  45. function smartdnsServiceStatus() {
  46. return Promise.all([
  47. getServiceStatus()
  48. ]);
  49. }
  50. function smartdnsRenderStatus(res) {
  51. var renderHTML = "";
  52. var isRunning = res[0];
  53. var autoSetDnsmasq = uci.get_first('smartdns', 'smartdns', 'auto_set_dnsmasq');
  54. var smartdnsPort = uci.get_first('smartdns', 'smartdns', 'port');
  55. var smartdnsEnable = uci.get_first('smartdns', 'smartdns', 'enabled');
  56. var dnsmasqServer = uci.get_first('dhcp', 'dnsmasq', 'server');
  57. if (isRunning) {
  58. renderHTML += "<span style=\"color:green;font-weight:bold\">SmartDNS - " + _("RUNNING") + "</span>";
  59. } else {
  60. renderHTML += "<span style=\"color:red;font-weight:bold\">SmartDNS - " + _("NOT RUNNING") + "</span>";
  61. if (smartdnsEnable === '1') {
  62. renderHTML += "<br /><span style=\"color:red;font-weight:bold\">" + _("Please check the system logs and check if the configuration is valid.");
  63. renderHTML += "</span>";
  64. }
  65. return renderHTML;
  66. }
  67. if (autoSetDnsmasq === '1' && smartdnsPort != '53') {
  68. var matchLine = "127.0.0.1#" + smartdnsPort;
  69. uci.unload('dhcp');
  70. uci.load('dhcp');
  71. if (dnsmasqServer == undefined || dnsmasqServer.indexOf(matchLine) < 0) {
  72. renderHTML += "<br /><span style=\"color:red;font-weight:bold\">" + _("Dnsmasq Forwarded To Smartdns Failure") + "</span>";
  73. }
  74. }
  75. return renderHTML;
  76. }
  77. return view.extend({
  78. load: function () {
  79. return Promise.all([
  80. uci.load('dhcp'),
  81. uci.load('smartdns'),
  82. uci.load('smartdns-lite'),
  83. ]);
  84. },
  85. render: function (stats) {
  86. var m, s, o;
  87. m = new form.Map('smartdns-lite', _('SmartDNS Lite'));
  88. m.title = _("SmartDNS Lite");
  89. m.description = _("A local SmartDNS server for lite users.");
  90. s = m.section(form.NamedSection, '_status');
  91. s.anonymous = true;
  92. s.render = function (section_id) {
  93. var renderStatus = function () {
  94. return L.resolveDefault(smartdnsServiceStatus()).then(function (res) {
  95. var view = document.getElementById("service_status");
  96. if (view == null) {
  97. return;
  98. }
  99. view.innerHTML = smartdnsRenderStatus(res);
  100. });
  101. }
  102. if (pollAdded == false) {
  103. poll.add(renderStatus, 1);
  104. pollAdded = true;
  105. }
  106. return E('div', { class: 'cbi-section' }, [
  107. E('div', { id: 'service_status' },
  108. _('Collecting data ...'))
  109. ]);
  110. }
  111. ////////////////
  112. // Basic;
  113. ////////////////
  114. s = m.section(form.TypedSection, "smartdns-lite", _("Settings"));
  115. s.anonymous = true;
  116. s.tab("settings", _("Basic Settings"));
  117. s.tab("parental", _("Parental Control Settings"));
  118. s.tab("rules", _("Domain Rules Settings"));
  119. s.tab("cloudflare", _("CloudFlare CDN IP Settings"), _("Set the IP addresses for accelerating CloudFlare CDN."));
  120. s.tab("custom", _("Custom Settings"));
  121. o = s.taboption("settings", form.Flag, "enabled", _("Enable"), _("Enable or disable smartdns server"));
  122. o.rmempty = false;
  123. o.default = o.disabled;
  124. o = s.taboption("settings", form.DynamicList, "servers", _("Upstream Server"),
  125. _("Upstream servers, format: [udp://|tcp://|tls://|https://][ip]."));
  126. o.rempty = true
  127. o.rmempty = true;
  128. o.validate = function (section_id, value) {
  129. if (value == "") {
  130. return true;
  131. }
  132. var values = value.split(/\s+/);
  133. for (var i = 0; i < values.length; i++) {
  134. if (!values[i].match(/^(https?|udp|tcp|tls|quic):\/\/[0-9a-zA-Z\.\[\]:]+(\/[^\s]*)?$/)) {
  135. return _('Invalid server address: %s').format(values[i]);
  136. }
  137. }
  138. return true;
  139. };
  140. o = s.taboption("settings", form.FileUpload, "ad_block_file", _("AD Block Domain List File"),
  141. _("Set the file for blocking ad domain names."));
  142. o.rmempty = true
  143. o.datatype = "file"
  144. o.rempty = true
  145. o.editable = true
  146. o.root_directory = "/etc/smartdns/domain-set"
  147. o = s.taboption("settings", form.ListValue, "server_mode", _("DNS Server Mode"), _("Smartdns server mode."));
  148. o.rmempty = false;
  149. o.value("main", _("Main DNS Server"));
  150. o.value("upstream", _("Upstream DNS Server"));
  151. o.value("dnsmasq_upstream", _("Dnsmasq Upstream Server"));
  152. o = s.taboption("settings", form.Value, "port", _("DNS Server Port"), _("Smartdns server port."));
  153. o.rmempty = true
  154. o.default = 6053;
  155. o.datatype = "port";
  156. o.depends("server_mode", "upstream");
  157. o.depends("server_mode", "dnsmasq_upstream");
  158. o = s.taboption("parental", form.Flag, "pc_enabled", _("Enable"), _("Enable or disable smartdns server"));
  159. o.rmempty = false;
  160. o.default = o.disabled;
  161. o.validate = function (section_id, value) {
  162. var v = this.map.lookupOption('pc_enabled', section_id)[0];
  163. if (v.formvalue(section_id) == 0) {
  164. return true;
  165. }
  166. var server_mode = this.map.lookupOption('server_mode', section_id)[0];
  167. if (server_mode.formvalue(section_id) != "main") {
  168. return _("Parental control feature is only available in Main DNS mode.");
  169. }
  170. return true;
  171. }
  172. o = s.taboption("parental", form.DynamicList, "pc_client_addr", _("Client Address"),
  173. _("If a client address is specified, only that client will apply this rule. You can enter an IP address, such as 1.2.3.4, or a MAC address, such as aa:bb:cc:dd:ee:ff."));
  174. o.rempty = true
  175. o.rmempty = true;
  176. o.validate = function (section_id, value) {
  177. if (value == "") {
  178. return true;
  179. }
  180. if (value.match(/^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[1-2][0-9]|3[0-2]))?$/)) {
  181. return true;
  182. }
  183. if (value.match(/^([a-fA-F0-9]*:){1,7}[a-fA-F0-9]*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/)) {
  184. return true;
  185. }
  186. if (value.match(/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/)) {
  187. return true;
  188. }
  189. return _("Client address format error, please input ip adress or mac address.");
  190. }
  191. o = s.taboption("parental", form.DynamicList, "pc_servers", _("Parental Control Upstream Server"),
  192. _("Upstream server with parental control feature. If not specified, the default server will be used."));
  193. o.rempty = true
  194. o.rmempty = true;
  195. o.validate = function (section_id, value) {
  196. if (value == "") {
  197. return true;
  198. }
  199. var values = value.split(/\s+/);
  200. for (var i = 0; i < values.length; i++) {
  201. if (!values[i].match(/^(https?|udp|tcp|tls|quic):\/\/[0-9a-zA-Z\.\[\]:]+(\/[^\s]*)?$/)) {
  202. return _('Invalid server address: %s').format(values[i]);
  203. }
  204. }
  205. return true;
  206. };
  207. o = s.taboption("parental", form.FileUpload, "pc_block_file", _("Parental Control Domain File"),
  208. _("Block Domain List File for Parental Control."));
  209. o.rmempty = true
  210. o.datatype = "file"
  211. o.rempty = true
  212. o.editable = true
  213. o.root_directory = "/etc/smartdns/domain-set"
  214. o = s.taboption("rules", form.Flag, "rules_enabled", _("Enable"), _("Enable or disable domain rules."));
  215. o.rmempty = false;
  216. o.default = o.disabled;
  217. o = s.taboption("rules", form.FileUpload, "rules_domain_file", _("Domain List File"),
  218. _("Upload domain list file for matching these rules, if not specified, the rules will be applied to all domains."));
  219. o.rmempty = true
  220. o.datatype = "file"
  221. o.rempty = true
  222. o.editable = true
  223. o.root_directory = "/etc/smartdns/domain-set"
  224. o = s.taboption("rules", form.DynamicList, "rules_servers", _("Upstream Server"),
  225. _("Upstream server for specific domain. If not specified, the default server will be used."));
  226. o.rempty = true
  227. o.rmempty = true;
  228. o.validate = function (section_id, value) {
  229. if (value == "") {
  230. return true;
  231. }
  232. var values = value.split(/\s+/);
  233. for (var i = 0; i < values.length; i++) {
  234. if (!values[i].match(/^(https?|udp|tcp|tls|quic):\/\/[0-9a-zA-Z\.\[\]:]+(\/[^\s]*)?$/)) {
  235. return _('Invalid server address: %s').format(values[i]);
  236. }
  237. }
  238. return true;
  239. };
  240. o = s.taboption("rules", form.Value, "rules_speed_check_mode", _("Speed Check Mode"), _("Speed check mode for matching domains."));
  241. o.rmempty = true;
  242. o.placeholder = _("None");
  243. o.default = "none";
  244. o.value("none", _("None"));
  245. o.value("ping,tcp:80,tcp:443");
  246. o.value("ping,tcp:443,tcp:80");
  247. o.value("tcp:80,tcp:443,ping");
  248. o.value("tcp:443,tcp:80,ping");
  249. o.validate = function (section_id, value) {
  250. if (value == "") {
  251. return true;
  252. }
  253. if (value == "none") {
  254. return true;
  255. }
  256. var check_mode = value.split(",")
  257. for (var i = 0; i < check_mode.length; i++) {
  258. if (check_mode[i] == "ping") {
  259. continue;
  260. }
  261. if (check_mode[i].indexOf("tcp:") == 0) {
  262. var port = check_mode[i].split(":")[1];
  263. if (port == "") {
  264. return _("TCP port is empty");
  265. }
  266. continue;
  267. }
  268. return _("Speed check mode is invalid.");
  269. }
  270. return true;
  271. }
  272. // Force AAAA SOA
  273. o = s.taboption("rules", form.Flag, "rules_force_aaaa_soa", _("Force AAAA SOA"), _("Force AAAA SOA."));
  274. o.rmempty = true;
  275. o.default = o.disabled;
  276. // Force HTTPS SOA
  277. o = s.taboption("rules", form.Flag, "rules_force_https_soa", _("Force HTTPS SOA"), _("Force HTTPS SOA."));
  278. o.rmempty = true;
  279. o.default = o.enabled;
  280. o = s.taboption("rules", form.Flag, "use_internal_rules", _("Use Internal IP Rules"),
  281. _("Use internal IP rules to forward data to TPROXY service when the domain matches, avoiding the need to configure IP rules."));
  282. o.rmempty = true;
  283. o.default = o.disabled;
  284. o = s.taboption("rules", form.Value, "rules_ipset_name", _("IPset Name"), _("IPset name."));
  285. o.rmempty = true;
  286. o.datatype = "string";
  287. o.rempty = true;
  288. o.validate = function (section_id, value) {
  289. if (value == "") {
  290. return true;
  291. }
  292. var ipset = value.split(",")
  293. for (var i = 0; i < ipset.length; i++) {
  294. if (!ipset[i].match(/^(#[4|6]:)?[a-zA-Z0-9\-_]+$/)) {
  295. return _("ipset name format error, format: [#[4|6]:]ipsetname");
  296. }
  297. }
  298. return true;
  299. }
  300. o.depends("use_internal_rules", "0");
  301. o = s.taboption("rules", form.Value, "rules_nftset_name", _("NFTset Name"), _("NFTset name, format: [#[4|6]:[family#table#set]]"));
  302. o.rmempty = true;
  303. o.datatype = "string";
  304. o.rempty = true;
  305. o.validate = function (section_id, value) {
  306. if (value == "") {
  307. return true;
  308. }
  309. var nftset = value.split(",")
  310. for (var i = 0; i < nftset.length; i++) {
  311. if (!nftset[i].match(/^#[4|6]:[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+#[a-zA-Z0-9\-_]+$/)) {
  312. return _("NFTset name format error, format: [#[4|6]:[family#table#set]]");
  313. }
  314. }
  315. return true;
  316. }
  317. o.depends("use_internal_rules", "0");
  318. o = s.taboption("rules", form.Value, "tproxy_server_port", _("TPROXY Server Port"),
  319. _("TPROXY server port used for forwarding data requests, please make sure this port has enabled TPROXY service."));
  320. o.rmempty = false;
  321. o.datatype = "port";
  322. o.rempty = false;
  323. o.depends("use_internal_rules", "1");
  324. o = s.taboption("cloudflare", form.Flag, "cloudflare_enabled", _("Enable"),
  325. _("Enable or disable cloudflare cdn ip accelerating."));
  326. o.rmempty = false;
  327. o.default = o.disabled;
  328. o = s.taboption("cloudflare", form.FileUpload, "cloudflare_cdn_ip_file", _("CloudFlare CDN IP File"),
  329. _("Upload CloudFlare cdn ip list file, please refer to https://www.cloudflare.com/ips"));
  330. o.rmempty = true
  331. o.datatype = "file"
  332. o.rempty = true
  333. o.modalonly = true;
  334. o.root_directory = "/etc/smartdns/ip-set"
  335. o = s.taboption("cloudflare", form.DynamicList, "cloudflare_ip_alias", _("IP alias"),
  336. _("IP Address Mapping, mapping all CloudFlare CDN IPs to the specified IP, can be used to accelerate CloudFlare's CDN websites."));
  337. o.rmempty = true;
  338. o.datatype = 'ipaddr("nomask")';
  339. o.modalonly = true;
  340. ///////////////////////////////////////
  341. // custom settings;
  342. ///////////////////////////////////////
  343. o = s.taboption("custom", form.TextValue, "custom_conf",
  344. "", _("smartdns custom settings"));
  345. o.rows = 20;
  346. o.cfgvalue = function (section_id) {
  347. return fs.trimmed('/etc/smartdns/custom.conf');
  348. };
  349. o.write = function (section_id, formvalue) {
  350. return this.cfgvalue(section_id).then(function (value) {
  351. if (value == formvalue) {
  352. return
  353. }
  354. return fs.write('/etc/smartdns/custom.conf', formvalue.trim().replace(/\r\n/g, '\n') + '\n');
  355. });
  356. };
  357. o = s.taboption("custom", form.Button, "web");
  358. o.title = _("SmartDNS official website");
  359. o.inputtitle = _("open website");
  360. o.inputstyle = "apply";
  361. o.onclick = function () {
  362. window.open("https://pymumu.github.io/smartdns", '_blank');
  363. };
  364. o = s.taboption("custom", form.DummyValue, "_restart", _("Restart Service"));
  365. o.renderWidget = function () {
  366. return E('button', {
  367. 'class': 'btn cbi-button cbi-button-apply',
  368. 'id': 'btn_restart',
  369. 'click': ui.createHandlerFn(this, function () {
  370. return fs.exec('/etc/init.d/smartdns-lite', ['restart'])
  371. .catch(function (e) { ui.addNotification(null, E('p', e.message), 'error') });
  372. })
  373. }, [_("Restart")]);
  374. }
  375. return m.render();
  376. }
  377. });