81c2055a1c02a1483662c549aea8cbd25c4553e1
[project/luci.git] / modules / luci-mod-network / htdocs / luci-static / resources / view / network / dhcp.js
1 'use strict';
2 'require view';
3 'require dom';
4 'require poll';
5 'require rpc';
6 'require uci';
7 'require form';
8 'require network';
9 'require validation';
10 'require tools.widgets as widgets';
11
12 var callHostHints, callDUIDHints, callDHCPLeases, CBILeaseStatus, CBILease6Status;
13
14 callHostHints = rpc.declare({
15 object: 'luci-rpc',
16 method: 'getHostHints',
17 expect: { '': {} }
18 });
19
20 callDUIDHints = rpc.declare({
21 object: 'luci-rpc',
22 method: 'getDUIDHints',
23 expect: { '': {} }
24 });
25
26 callDHCPLeases = rpc.declare({
27 object: 'luci-rpc',
28 method: 'getDHCPLeases',
29 expect: { '': {} }
30 });
31
32 CBILeaseStatus = form.DummyValue.extend({
33 renderWidget: function(section_id, option_id, cfgvalue) {
34 return E([
35 E('h4', _('Active DHCP Leases')),
36 E('table', { 'id': 'lease_status_table', 'class': 'table' }, [
37 E('tr', { 'class': 'tr table-titles' }, [
38 E('th', { 'class': 'th' }, _('Hostname')),
39 E('th', { 'class': 'th' }, _('IPv4 address')),
40 E('th', { 'class': 'th' }, _('MAC address')),
41 E('th', { 'class': 'th' }, _('Lease time remaining'))
42 ]),
43 E('tr', { 'class': 'tr placeholder' }, [
44 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
45 ])
46 ])
47 ]);
48 }
49 });
50
51 CBILease6Status = form.DummyValue.extend({
52 renderWidget: function(section_id, option_id, cfgvalue) {
53 return E([
54 E('h4', _('Active DHCPv6 Leases')),
55 E('table', { 'id': 'lease6_status_table', 'class': 'table' }, [
56 E('tr', { 'class': 'tr table-titles' }, [
57 E('th', { 'class': 'th' }, _('Host')),
58 E('th', { 'class': 'th' }, _('IPv6 address')),
59 E('th', { 'class': 'th' }, _('DUID')),
60 E('th', { 'class': 'th' }, _('Lease time remaining'))
61 ]),
62 E('tr', { 'class': 'tr placeholder' }, [
63 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
64 ])
65 ])
66 ]);
67 }
68 });
69
70 function calculateNetwork(addr, mask) {
71 addr = validation.parseIPv4(String(addr));
72
73 if (!isNaN(mask))
74 mask = validation.parseIPv4(network.prefixToMask(+mask));
75 else
76 mask = validation.parseIPv4(String(mask));
77
78 if (addr == null || mask == null)
79 return null;
80
81 return [
82 [
83 addr[0] & (mask[0] >>> 0 & 255),
84 addr[1] & (mask[1] >>> 0 & 255),
85 addr[2] & (mask[2] >>> 0 & 255),
86 addr[3] & (mask[3] >>> 0 & 255)
87 ].join('.'),
88 mask.join('.')
89 ];
90 }
91
92 function getDHCPPools() {
93 return uci.load('dhcp').then(function() {
94 let sections = uci.sections('dhcp', 'dhcp'),
95 tasks = [], pools = [];
96
97 for (var i = 0; i < sections.length; i++) {
98 if (sections[i].ignore == '1' || !sections[i].interface)
99 continue;
100
101 tasks.push(network.getNetwork(sections[i].interface).then(L.bind(function(section_id, net) {
102 var cidr = net ? (net.getIPAddrs()[0] || '').split('/') : null;
103
104 if (cidr && cidr.length == 2) {
105 var net_mask = calculateNetwork(cidr[0], cidr[1]);
106
107 pools.push({
108 section_id: section_id,
109 network: net_mask[0],
110 netmask: net_mask[1]
111 });
112 }
113 }, null, sections[i]['.name'])));
114 }
115
116 return Promise.all(tasks).then(function() {
117 return pools;
118 });
119 });
120 }
121
122 function validateHostname(sid, s) {
123 if (s == null || s == '')
124 return true;
125
126 if (s.length > 256)
127 return _('Expecting: %s').format(_('valid hostname'));
128
129 var labels = s.replace(/^\*?\.?|\.$/g, '').split(/\./);
130
131 for (var i = 0; i < labels.length; i++)
132 if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
133 return _('Expecting: %s').format(_('valid hostname'));
134
135 return true;
136 }
137
138 function validateAddressList(sid, s) {
139 if (s == null || s == '')
140 return true;
141
142 var m = s.match(/^\/(.+)\/$/),
143 names = m ? m[1].split(/\//) : [ s ];
144
145 for (var i = 0; i < names.length; i++) {
146 var res = validateHostname(sid, names[i]);
147
148 if (res !== true)
149 return res;
150 }
151
152 return true;
153 }
154
155 function validateServerSpec(sid, s) {
156 if (s == null || s == '')
157 return true;
158
159 var m = s.match(/^(\/.*\/)?(.*)$/);
160 if (!m)
161 return _('Expecting: %s').format(_('valid hostname'));
162
163 if (m[1] != '//' && m[1] != '/#/') {
164 var res = validateAddressList(sid, m[1]);
165 if (res !== true)
166 return res;
167 }
168
169 if (m[2] == '' || m[2] == '#')
170 return true;
171
172 // ipaddr%scopeid#srvport@source@interface#srcport
173
174 m = m[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
175
176 if (!m)
177 return _('Expecting: %s').format(_('valid IP address'));
178
179 if (validation.parseIPv4(m[1])) {
180 if (m[3] != null && !validation.parseIPv4(m[3]))
181 return _('Expecting: %s').format(_('valid IPv4 address'));
182 }
183 else if (validation.parseIPv6(m[1])) {
184 if (m[3] != null && !validation.parseIPv6(m[3]))
185 return _('Expecting: %s').format(_('valid IPv6 address'));
186 }
187 else {
188 return _('Expecting: %s').format(_('valid IP address'));
189 }
190
191 if ((m[2] != null && +m[2] > 65535) || (m[4] != null && +m[4] > 65535))
192 return _('Expecting: %s').format(_('valid port value'));
193
194 return true;
195 }
196
197 function validateMACAddr(pools, sid, s) {
198 if (s == null || s == '')
199 return true;
200
201 var leases = uci.sections('dhcp', 'host'),
202 this_macs = L.toArray(s).map(function(m) { return m.toUpperCase() });
203
204 for (var i = 0; i < pools.length; i++) {
205 var this_net_mask = calculateNetwork(this.section.formvalue(sid, 'ip'), pools[i].netmask);
206
207 if (!this_net_mask)
208 continue;
209
210 for (var j = 0; j < leases.length; j++) {
211 if (leases[j]['.name'] == sid || !leases[j].ip)
212 continue;
213
214 var lease_net_mask = calculateNetwork(leases[j].ip, pools[i].netmask);
215
216 if (!lease_net_mask || this_net_mask[0] != lease_net_mask[0])
217 continue;
218
219 var lease_macs = L.toArray(leases[j].mac).map(function(m) { return m.toUpperCase() });
220
221 for (var k = 0; k < lease_macs.length; k++)
222 for (var l = 0; l < this_macs.length; l++)
223 if (lease_macs[k] == this_macs[l])
224 return _('The MAC address %h is already used by another static lease in the same DHCP pool').format(this_macs[l]);
225 }
226 }
227
228 return true;
229 }
230
231 return view.extend({
232 load: function() {
233 return Promise.all([
234 callHostHints(),
235 callDUIDHints(),
236 getDHCPPools(),
237 network.getNetworks()
238 ]);
239 },
240
241 render: function(hosts_duids_pools) {
242 var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
243 hosts = hosts_duids_pools[0],
244 duids = hosts_duids_pools[1],
245 pools = hosts_duids_pools[2],
246 networks = hosts_duids_pools[3],
247 m, s, o, ss, so;
248
249 m = new form.Map('dhcp', _('DHCP and DNS'),
250 _('Dnsmasq is a lightweight <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> server and <abbr title="Domain Name System">DNS</abbr> forwarder.'));
251
252 s = m.section(form.TypedSection, 'dnsmasq');
253 s.anonymous = false;
254 s.addremove = true;
255
256
257
258 s.tab('general', _('General'));
259 s.tab('devices', _('Devices &amp; Ports'));
260 s.tab('dnssecopt', _('DNSSEC'));
261 s.tab('filteropts', _('Filter'));
262 s.tab('forward', _('Forwards'));
263 s.tab('limits', _('Limits'));
264 s.tab('logging', _('Log'));
265 s.tab('files', _('Resolv &amp; Hosts Files'));
266 s.tab('leases', _('Static Leases'));
267 s.tab('hosts', _('Hostnames'));
268 s.tab('ipsets', _('IP Sets'));
269 s.tab('relay', _('Relay'));
270 s.tab('srvhosts', _('SRV'));
271 s.tab('mxhosts', _('MX'));
272 s.tab('pxe_tftp', _('PXE/TFTP'));
273
274 s.taboption('filteropts', form.Flag, 'domainneeded',
275 _('Domain required'),
276 _('Do not forward DNS queries without dots or domain parts.'));
277
278 s.taboption('general', form.Flag, 'authoritative',
279 _('Authoritative'),
280 _('This is the only DHCP server in the local network.'));
281
282 s.taboption('general', form.Value, 'local',
283 _('Local server'),
284 _('Never forward matching domains and subdomains, resolve from DHCP or hosts files only.'));
285
286 s.taboption('general', form.Value, 'domain',
287 _('Local domain'),
288 _('Local domain suffix appended to DHCP names and hosts file entries.'));
289
290 s.taboption('general', form.Flag, 'expandhosts',
291 _('Expand hosts'),
292 _('Add local domain suffix to names served from hosts files.'));
293
294 o = s.taboption('logging', form.Flag, 'logqueries',
295 _('Log queries'),
296 _('Write received DNS queries to syslog.'));
297 o.optional = true;
298
299 o = s.taboption('logging', form.Flag, 'logdhcp',
300 _('Extra DHCP logging'),
301 _('Log all options sent to DHCP clients and the tags used to determine them.'));
302 o.optional = true;
303
304 o = s.taboption('logging', form.Value, 'logfacility',
305 _('Log facility'),
306 _('Set log class/facility for syslog entries.'));
307 o.optional = true;
308 o.value('KERN');
309 o.value('USER');
310 o.value('MAIL');
311 o.value('DAEMON');
312 o.value('AUTH');
313 o.value('LPR');
314 o.value('NEWS');
315 o.value('UUCP');
316 o.value('CRON');
317 o.value('LOCAL0');
318 o.value('LOCAL1');
319 o.value('LOCAL2');
320 o.value('LOCAL3');
321 o.value('LOCAL4');
322 o.value('LOCAL5');
323 o.value('LOCAL6');
324 o.value('LOCAL7');
325 o.value('-', _('stderr'));
326
327 o = s.taboption('forward', form.DynamicList, 'server',
328 _('DNS forwardings'),
329 _('List of upstream resolvers to forward queries to.'));
330 o.optional = true;
331 o.placeholder = '/example.org/10.1.2.3';
332 o.validate = validateServerSpec;
333
334 o = s.taboption('general', form.DynamicList, 'address',
335 _('Addresses'),
336 _('Resolve specified FQDNs to an IP.') + '<br />' +
337 _('Syntax: <code>/fqdn[/fqdn…]/[ipaddr]</code>.') + '<br />' +
338 _('<code>/#/</code> matches any domain. <code>/example.com/</code> returns NXDOMAIN.') + '<br />' +
339 _('<code>/example.com/#</code> returns NULL addresses (<code>0.0.0.0</code> and <code>::</code>) for example.com and its subdomains.'));
340 o.optional = true;
341 o.placeholder = '/router.local/router.lan/192.168.0.1';
342
343 o = s.taboption('general', form.DynamicList, 'ipset',
344 _('IP sets'),
345 _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
346 o.optional = true;
347 o.placeholder = '/example.org/ipset,ipset6';
348
349 o = s.taboption('filteropts', form.Flag, 'rebind_protection',
350 _('Rebind protection'),
351 _('Discard upstream responses containing <a href="%s">RFC1918</a> addresses.').format('https://www.rfc-editor.org/rfc/rfc1918') + '<br />' +
352 _('Discard also upstream responses containing <a href="%s">RFC4193</a>, Link-Local and private IPv4-Mapped <a href="%s">RFC4291</a> IPv6 Addresses.').format('https://www.rfc-editor.org/rfc/rfc4193', 'https://www.rfc-editor.org/rfc/rfc4291'));
353 o.rmempty = false;
354
355 o = s.taboption('filteropts', form.Flag, 'rebind_localhost',
356 _('Allow localhost'),
357 _('Exempt <code>127.0.0.0/8</code> and <code>::1</code> from rebinding checks, e.g. for RBL services.'));
358 o.depends('rebind_protection', '1');
359
360 o = s.taboption('filteropts', form.DynamicList, 'rebind_domain',
361 _('Domain whitelist'),
362 _('List of domains to allow RFC1918 responses for.'));
363 o.depends('rebind_protection', '1');
364 o.optional = true;
365 o.placeholder = 'ihost.netflix.com';
366 o.validate = validateAddressList;
367
368 o = s.taboption('filteropts', form.Flag, 'localservice',
369 _('Local service only'),
370 _('Accept DNS queries only from hosts whose address is on a local subnet.'));
371 o.optional = false;
372 o.rmempty = false;
373
374 o = s.taboption('devices', form.Flag, 'nonwildcard',
375 _('Non-wildcard'),
376 _('Bind dynamically to interfaces rather than wildcard address.'));
377 o.default = o.enabled;
378 o.optional = false;
379 o.rmempty = true;
380
381 o = s.taboption('devices', widgets.NetworkSelect, 'interface',
382 _('Listen interfaces'),
383 _('Listen only on the specified interfaces, and loopback if not excluded explicitly.'));
384 o.multiple = true;
385 o.nocreate = true;
386
387 o = s.taboption('devices', widgets.NetworkSelect, 'notinterface',
388 _('Exclude interfaces'),
389 _('Do not listen on the specified interfaces.'));
390 o.loopback = true;
391 o.multiple = true;
392 o.nocreate = true;
393
394 o = s.taboption('relay', form.SectionValue, '__relays__', form.TableSection, 'relay', null,
395 _('Relay DHCP requests elsewhere. OK: v4↔v4, v6↔v6. Not OK: v4↔v6, v6↔v4.')
396 + '<br />' + _('Note: you may also need a DHCP Proxy (currently unavailable) when specifying a non-standard Relay To port(<code>addr#port</code>).')
397 + '<br />' + _('You may add multiple unique Relay To on the same Listen addr.'));
398
399 ss = o.subsection;
400
401 ss.addremove = true;
402 ss.anonymous = true;
403 ss.sortable = true;
404 ss.rowcolors = true;
405 ss.nodescriptions = true;
406
407 so = ss.option(form.Value, 'local_addr', _('Relay from'));
408 so.rmempty = false;
409 so.datatype = 'ipaddr';
410
411 for (var family = 4; family <= 6; family += 2) {
412 for (var i = 0; i < networks.length; i++) {
413 if (networks[i].getName() != 'loopback') {
414 var addrs = (family == 6) ? networks[i].getIP6Addrs() : networks[i].getIPAddrs();
415 for (var j = 0; j < addrs.length; j++) {
416 var addr = addrs[j].split('/')[0];
417 so.value(addr, E([], [
418 addr, ' (',
419 widgets.NetworkSelect.prototype.renderIfaceBadge(networks[i]),
420 ')'
421 ]));
422 }
423 }
424 }
425 }
426
427 so = ss.option(form.Value, 'server_addr', _('Relay to address'));
428 so.rmempty = false;
429 so.optional = false;
430 so.placeholder = '192.168.10.1#535';
431
432 so.validate = function(section, value) {
433 var m = this.section.formvalue(section, 'local_addr'),
434 n = this.section.formvalue(section, 'server_addr'),
435 p;
436
437 if (!m || !n) {
438 return _('Both "Relay from" and "Relay to address" must be specified.');
439 }
440 else {
441 p = n.split('#');
442 if (p.length > 1 && !/^[0-9]+$/.test(p[1]))
443 return _('Expected port number.');
444 else
445 n = p[0];
446
447 if ((validation.parseIPv6(m) && validation.parseIPv6(n)) ||
448 validation.parseIPv4(m) && validation.parseIPv4(n))
449 return true;
450 else
451 return _('Address families of "Relay from" and "Relay to address" must match.')
452 }
453 return true;
454 };
455
456 so = ss.option(widgets.NetworkSelect, 'interface', _('Only accept replies via'));
457 so.optional = true;
458 so.rmempty = false;
459 so.placeholder = 'lan';
460
461 s.taboption('files', form.Flag, 'readethers',
462 _('Use <code>/etc/ethers</code>'),
463 _('Read <code>/etc/ethers</code> to configure the DHCP server.'));
464
465 s.taboption('files', form.Value, 'leasefile',
466 _('Lease file'),
467 _('File to store DHCP lease information.'));
468
469 o = s.taboption('files', form.Flag, 'noresolv',
470 _('Ignore resolv file'));
471 o.optional = true;
472
473 o = s.taboption('files', form.Value, 'resolvfile',
474 _('Resolv file'),
475 _('File with upstream resolvers.'));
476 o.depends('noresolv', '0');
477 o.placeholder = '/tmp/resolv.conf.d/resolv.conf.auto';
478 o.optional = true;
479
480 o = s.taboption('files', form.Flag, 'strictorder',
481 _('Strict order'),
482 _('Query upstream resolvers in the order they appear in the resolv file.'));
483 o.optional = true;
484
485 o = s.taboption('files', form.Flag, 'nohosts',
486 _('Ignore <code>/etc/hosts</code>'));
487 o.optional = true;
488
489 o = s.taboption('files', form.DynamicList, 'addnhosts',
490 _('Additional hosts files'));
491 o.optional = true;
492 o.placeholder = '/etc/dnsmasq.hosts';
493
494 o = s.taboption('logging', form.Flag, 'quietdhcp',
495 _('Suppress logging'),
496 _('Suppress logging of the routine operation for the DHCP protocol.'));
497 o.optional = true;
498 o.depends('logdhcp', '0');
499
500 o = s.taboption('general', form.Flag, 'sequential_ip',
501 _('Allocate IPs sequentially'),
502 _('Allocate IP addresses sequentially, starting from the lowest available address.'));
503 o.optional = true;
504
505 o = s.taboption('filteropts', form.Flag, 'boguspriv',
506 _('Filter private'),
507 _('Do not forward reverse lookups for local networks.'));
508 o.default = o.enabled;
509
510 s.taboption('filteropts', form.Flag, 'filterwin2k',
511 _('Filter SRV/SOA service discovery'),
512 _('Filters SRV/SOA service discovery, to avoid triggering dial-on-demand links.') + '<br />' +
513 _('May prevent VoIP or other services from working.'));
514
515 o = s.taboption('filteropts', form.Flag, 'filter_aaaa',
516 _('Filter IPv6 AAAA records'),
517 _('Remove IPv6 addresses from the results and only return IPv4 addresses.') + '<br />' +
518 _('Can be useful if ISP has IPv6 nameservers but does not provide IPv6 routing.'));
519 o.optional = true;
520
521 o = s.taboption('filteropts', form.Flag, 'filter_a',
522 _('Filter IPv4 A records'),
523 _('Remove IPv4 addresses from the results and only return IPv6 addresses.'));
524 o.optional = true;
525
526 s.taboption('filteropts', form.Flag, 'localise_queries',
527 _('Localise queries'),
528 _('Return answers to DNS queries matching the subnet from which the query was received if multiple IPs are available.'));
529
530 if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
531 o = s.taboption('dnssecopt', form.Flag, 'dnssec',
532 _('DNSSEC'),
533 _('Validate DNS replies and cache DNSSEC data, requires upstream to support DNSSEC.'));
534 o.optional = true;
535
536 o = s.taboption('dnssecopt', form.Flag, 'dnsseccheckunsigned',
537 _('DNSSEC check unsigned'),
538 _('Verify unsigned domain responses really come from unsigned domains.'));
539 o.default = o.enabled;
540 o.optional = true;
541 }
542
543 s.taboption('filteropts', form.Flag, 'nonegcache',
544 _('No negative cache'),
545 _('Do not cache negative replies, e.g. for non-existent domains.'));
546
547 o = s.taboption('forward', form.Value, 'serversfile',
548 _('Additional servers file'),
549 _('File listing upstream resolvers, optionally domain-specific, e.g. <code>server=1.2.3.4</code>, <code>server=/domain/1.2.3.4</code>.'));
550 o.placeholder = '/etc/dnsmasq.servers';
551
552 o = s.taboption('general', form.Flag, 'allservers',
553 _('All servers'),
554 _('Query all available upstream resolvers.') + ' ' + _('First answer wins.'));
555 o.optional = true;
556
557 o = s.taboption('filteropts', form.DynamicList, 'bogusnxdomain',
558 _('IPs to override with NXDOMAIN'),
559 _('List of IP addresses to convert into NXDOMAIN responses.'));
560 o.optional = true;
561 o.placeholder = '64.94.110.11';
562
563 o = s.taboption('devices', form.Value, 'port',
564 _('DNS server port'),
565 _('Listening port for inbound DNS queries.'));
566 o.optional = true;
567 o.datatype = 'port';
568 o.placeholder = 53;
569
570 o = s.taboption('devices', form.Value, 'queryport',
571 _('DNS query port'),
572 _('Fixed source port for outbound DNS queries.'));
573 o.optional = true;
574 o.datatype = 'port';
575 o.placeholder = _('any');
576
577 o = s.taboption('devices', form.Value, 'minport',
578 _('Minimum source port #'),
579 _('Min valid value %s.').format('<code>1024</code>') + ' ' + _('Useful for systems behind firewalls.'));
580 o.optional = true;
581 o.datatype = 'port';
582 o.placeholder = 1024;
583 o.depends('queryport', '');
584
585 o = s.taboption('devices', form.Value, 'maxport',
586 _('Maximum source port #'),
587 _('Max valid value %s.').format('<code>65535</code>') + ' ' + _('Useful for systems behind firewalls.'));
588 o.optional = true;
589 o.datatype = 'port';
590 o.placeholder = 50000;
591 o.depends('queryport', '');
592
593 o = s.taboption('limits', form.Value, 'dhcpleasemax',
594 _('Max. DHCP leases'),
595 _('Maximum allowed number of active DHCP leases.'));
596 o.optional = true;
597 o.datatype = 'uinteger';
598 o.placeholder = 150;
599
600 o = s.taboption('limits', form.Value, 'ednspacket_max',
601 _('Max. EDNS0 packet size'),
602 _('Maximum allowed size of EDNS0 UDP packets.'));
603 o.optional = true;
604 o.datatype = 'uinteger';
605 o.placeholder = 1280;
606
607 o = s.taboption('limits', form.Value, 'dnsforwardmax',
608 _('Max. concurrent queries'),
609 _('Maximum allowed number of concurrent DNS queries.'));
610 o.optional = true;
611 o.datatype = 'uinteger';
612 o.placeholder = 150;
613
614 o = s.taboption('limits', form.Value, 'cachesize',
615 _('Size of DNS query cache'),
616 _('Number of cached DNS entries, 10000 is maximum, 0 is no caching.'));
617 o.optional = true;
618 o.datatype = 'range(0,10000)';
619 o.placeholder = 150;
620
621 o = s.taboption('limits', form.Value, 'min_cache_ttl',
622 _('Min cache TTL'),
623 _('Extend short TTL values to the seconds value given when caching them. Use with caution.') +
624 _(' (Max 1h == 3600)'));
625 o.optional = true;
626 o.placeholder = 60;
627
628 o = s.taboption('limits', form.Value, 'max_cache_ttl',
629 _('Max cache TTL'),
630 _('Set a maximum seconds TTL value for entries in the cache.'));
631 o.optional = true;
632 o.placeholder = 3600;
633
634 o = s.taboption('pxe_tftp', form.Flag, 'enable_tftp',
635 _('Enable TFTP server'),
636 _('Enable the built-in single-instance TFTP server.'));
637 o.optional = true;
638
639 o = s.taboption('pxe_tftp', form.Value, 'tftp_root',
640 _('TFTP server root'),
641 _('Root directory for files served via TFTP. <em>Enable TFTP server</em> and <em>TFTP server root</em> turn on the TFTP server and serve files from <em>TFTP server root</em>.'));
642 o.depends('enable_tftp', '1');
643 o.optional = true;
644 o.placeholder = '/';
645
646 o = s.taboption('pxe_tftp', form.Value, 'dhcp_boot',
647 _('Network boot image'),
648 _('Filename of the boot image advertised to clients.'));
649 o.depends('enable_tftp', '1');
650 o.optional = true;
651 o.placeholder = 'pxelinux.0';
652
653 /* PXE - https://openwrt.org/docs/guide-user/base-system/dhcp#booting_options */
654 o = s.taboption('pxe_tftp', form.SectionValue, '__pxe__', form.GridSection, 'boot', null,
655 _('Special <abbr title="Preboot eXecution Environment">PXE</abbr> boot options for Dnsmasq.'));
656 ss = o.subsection;
657 ss.addremove = true;
658 ss.anonymous = true;
659 ss.modaltitle = _('Edit PXE/TFTP/BOOTP Host');
660 ss.nodescriptions = true;
661
662 so = ss.option(form.Value, 'filename',
663 _('Filename'),
664 _('Host requests this filename from the boot server.'));
665 so.optional = false;
666 so.placeholder = 'pxelinux.0';
667
668 so = ss.option(form.Value, 'servername',
669 _('Server name'),
670 _('The hostname of the boot server'));
671 so.optional = false;
672 so.placeholder = 'myNAS';
673
674 so = ss.option(form.Value, 'serveraddress',
675 _('Server address'),
676 _('The IP address of the boot server'));
677 so.optional = false;
678 so.placeholder = '192.168.1.2';
679
680 so = ss.option(form.DynamicList, 'dhcp_option',
681 _('DHCP Options'),
682 _('Additional options to send to the below match tags.') + '<br />' +
683 _('%s means "the address of the system running dnsmasq".').format('<code>0.0.0.0</code>'));
684 so.optional = true;
685 so.placeholder = 'option:root-path,192.168.1.2:/data/netboot/root';
686
687 so = ss.option(form.Value, 'networkid',
688 _('Match this Tag'),
689 _('Only DHCP Clients with this tag are sent this boot option.'));
690 so.optional = true;
691 so.noaliases = true;
692
693 so = ss.option(form.Flag, 'force',
694 _('Force'),
695 _('Always send the chosen DHCP options. Sometimes needed, with e.g. PXELinux.'));
696 so.optional = true;
697
698 so = ss.option(form.Value, 'instance',
699 _('Instance'),
700 _('Dnsmasq instance to which this boot section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
701 so.optional = true;
702
703 Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
704 so.value(index, '%s (Domain: %s, Local: %s)'.format(index, val.domain || '?', val.local || '?'));
705 });
706
707 o = s.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
708 _('Bind service records to a domain name: specify the location of services. See <a href="%s">RFC2782</a>.').format('https://datatracker.ietf.org/doc/html/rfc2782')
709 + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
710 + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
711 + '<br />' + _('You may add multiple records for the same Target.')
712 + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
713
714 ss = o.subsection;
715
716 ss.addremove = true;
717 ss.anonymous = true;
718 ss.sortable = true;
719 ss.rowcolors = true;
720
721 so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax: <code>_service._proto.example.com</code>.'));
722 so.rmempty = false;
723 so.datatype = 'hostname';
724 so.placeholder = '_sip._tcp.example.com';
725
726 so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
727 so.rmempty = false;
728 so.datatype = 'hostname';
729 so.placeholder = 'sip.example.com';
730
731 so = ss.option(form.Value, 'port', _('Port'));
732 so.rmempty = false;
733 so.datatype = 'port';
734 so.placeholder = '5060';
735
736 so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
737 so.rmempty = true;
738 so.datatype = 'range(0,65535)';
739 so.placeholder = '10';
740
741 so = ss.option(form.Value, 'weight', _('Weight'));
742 so.rmempty = true;
743 so.datatype = 'range(0,65535)';
744 so.placeholder = '50';
745
746 o = s.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
747 _('Bind service records to a domain name: specify the location of services.')
748 + '<br />' + _('You may add multiple records for the same domain.'));
749
750 ss = o.subsection;
751
752 ss.addremove = true;
753 ss.anonymous = true;
754 ss.sortable = true;
755 ss.rowcolors = true;
756 ss.nodescriptions = true;
757
758 so = ss.option(form.Value, 'domain', _('Domain'));
759 so.rmempty = false;
760 so.datatype = 'hostname';
761 so.placeholder = 'example.com';
762
763 so = ss.option(form.Value, 'relay', _('Relay'));
764 so.rmempty = false;
765 so.datatype = 'hostname';
766 so.placeholder = 'relay.example.com';
767
768 so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
769 so.rmempty = true;
770 so.datatype = 'range(0,65535)';
771 so.placeholder = '0';
772
773 o = s.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
774 _('Hostnames are used to bind a domain name to an IP address. This setting is redundant for hostnames already configured with static leases, but it can be useful to rebind an FQDN.'));
775
776 ss = o.subsection;
777
778 ss.addremove = true;
779 ss.anonymous = true;
780 ss.sortable = true;
781
782 so = ss.option(form.Value, 'name', _('Hostname'));
783 so.rmempty = false;
784 so.datatype = 'hostname';
785
786 so = ss.option(form.Value, 'ip', _('IP address'));
787 so.rmempty = false;
788 so.datatype = 'ipaddr';
789
790 var ipaddrs = {};
791
792 Object.keys(hosts).forEach(function(mac) {
793 var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
794
795 for (var i = 0; i < addrs.length; i++)
796 ipaddrs[addrs[i]] = hosts[mac].name || mac;
797 });
798
799 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
800 so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
801 });
802
803 o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
804 _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.') + '<br />' +
805 _('The netfilter components below are only regarded when running fw4.'));
806
807 ss = o.subsection;
808
809 ss.addremove = true;
810 ss.anonymous = true;
811 ss.sortable = true;
812 ss.rowcolors = true;
813 ss.nodescriptions = true;
814 ss.modaltitle = _('Edit IP set');
815
816 so = ss.option(form.DynamicList, 'name', _('Name of the set'));
817 so.rmempty = false;
818 so.editable = true;
819 so.datatype = 'string';
820
821 so = ss.option(form.DynamicList, 'domain', _('FQDN'));
822 so.rmempty = false;
823 so.editable = true;
824 so.datatype = 'hostname';
825
826 so = ss.option(form.Value, 'table', _('Netfilter table name'), _('Defaults to fw4.'));
827 so.editable = true;
828 so.placeholder = 'fw4';
829 so.rmempty = true;
830
831 so = ss.option(form.ListValue, 'table_family', _('Table IP family'), _('Defaults to IPv4+6.') + ' ' + _('Can be hinted by adding 4 or 6 to the name.') + '<br />' +
832 _('Adding an IPv6 to an IPv4 set and vice-versa silently fails.'));
833 so.editable = true;
834 so.rmempty = true;
835 so.value('inet', _('IPv4+6'));
836 so.value('ip', _('IPv4'));
837 so.value('ip6', _('IPv6'));
838
839 o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
840 _('Static leases are used to assign fixed IP addresses and symbolic hostnames to DHCP clients. They are also required for non-dynamic interface configurations where only hosts with a corresponding lease are served.') + '<br />' +
841 _('Use the <em>Add</em> Button to add a new lease entry. The <em>MAC address</em> identifies the host, the <em>IPv4 address</em> specifies the fixed address to use, and the <em>Hostname</em> is assigned as a symbolic name to the requesting host. The optional <em>Lease time</em> can be used to set non-standard host-specific lease time, e.g. 12h, 3d or infinite.'));
842
843 ss = o.subsection;
844
845 ss.addremove = true;
846 ss.anonymous = true;
847 ss.sortable = true;
848
849 so = ss.option(form.Value, 'name', _('Hostname'));
850 so.validate = validateHostname;
851 so.rmempty = true;
852 so.write = function(section, value) {
853 uci.set('dhcp', section, 'name', value);
854 uci.set('dhcp', section, 'dns', '1');
855 };
856 so.remove = function(section) {
857 uci.unset('dhcp', section, 'name');
858 uci.unset('dhcp', section, 'dns');
859 };
860
861 so = ss.option(form.Value, 'mac', _('MAC address'));
862 so.datatype = 'list(macaddr)';
863 so.rmempty = true;
864 so.cfgvalue = function(section) {
865 var macs = L.toArray(uci.get('dhcp', section, 'mac')),
866 result = [];
867
868 for (var i = 0, mac; (mac = macs[i]) != null; i++)
869 if (/^([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2}):([0-9a-fA-F]{1,2})$/.test(mac))
870 result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
871 parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
872 parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
873 parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
874
875 return result.length ? result.join(' ') : null;
876 };
877 so.renderWidget = function(section_id, option_index, cfgvalue) {
878 var node = form.Value.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]),
879 ipopt = this.section.children.filter(function(o) { return o.option == 'ip' })[0];
880
881 node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) {
882 var mac = ev.detail.value.value;
883 if (mac == null || mac == '' || !hosts[mac])
884 return;
885
886 var iphint = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
887 if (iphint == null)
888 return;
889
890 var ip = ipopt.formvalue(section_id);
891 if (ip != null && ip != '')
892 return;
893
894 var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
895 if (node)
896 dom.callClassMethod(node, 'setValue', iphint);
897 }, this, ipopt, section_id));
898
899 return node;
900 };
901 so.validate = validateMACAddr.bind(so, pools);
902 Object.keys(hosts).forEach(function(mac) {
903 var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
904 so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
905 });
906
907 so = ss.option(form.Value, 'ip', _('IPv4 address'));
908 so.datatype = 'or(ip4addr,"ignore")';
909 so.validate = function(section, value) {
910 var m = this.section.formvalue(section, 'mac'),
911 n = this.section.formvalue(section, 'name');
912
913 if ((m && !m.length > 0) && !n)
914 return _('One of hostname or MAC address must be specified!');
915
916 if (!value || value == 'ignore')
917 return true;
918
919 var leases = uci.sections('dhcp', 'host');
920
921 for (var i = 0; i < leases.length; i++)
922 if (leases[i]['.name'] != section && leases[i].ip == value)
923 return _('The IP address %h is already used by another static lease').format(value);
924
925 for (var i = 0; i < pools.length; i++) {
926 var net_mask = calculateNetwork(value, pools[i].netmask);
927
928 if (net_mask && net_mask[0] == pools[i].network)
929 return true;
930 }
931
932 return _('The IP address is outside of any DHCP pool address range');
933 };
934
935 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
936 so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4);
937 });
938
939 so = ss.option(form.Value, 'leasetime', _('Lease time'));
940 so.rmempty = true;
941
942 so = ss.option(form.Value, 'duid', _('DUID'));
943 so.datatype = 'and(rangelength(20,36),hexstring)';
944 Object.keys(duids).forEach(function(duid) {
945 so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
946 });
947
948 so = ss.option(form.Value, 'hostid', _('IPv6 suffix (hex)'));
949
950 o = s.taboption('leases', CBILeaseStatus, '__status__');
951
952 if (has_dhcpv6)
953 o = s.taboption('leases', CBILease6Status, '__status6__');
954
955 return m.render().then(function(mapEl) {
956 poll.add(function() {
957 return callDHCPLeases().then(function(leaseinfo) {
958 var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
959 leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
960
961 cbi_update_table(mapEl.querySelector('#lease_status_table'),
962 leases.map(function(lease) {
963 var exp;
964
965 if (lease.expires === false)
966 exp = E('em', _('unlimited'));
967 else if (lease.expires <= 0)
968 exp = E('em', _('expired'));
969 else
970 exp = '%t'.format(lease.expires);
971
972 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
973 name = hint ? hint.name : null,
974 host = null;
975
976 if (name && lease.hostname && lease.hostname != name)
977 host = '%s (%s)'.format(lease.hostname, name);
978 else if (lease.hostname)
979 host = lease.hostname;
980
981 return [
982 host || '-',
983 lease.ipaddr,
984 lease.macaddr,
985 exp
986 ];
987 }),
988 E('em', _('There are no active leases')));
989
990 if (has_dhcpv6) {
991 cbi_update_table(mapEl.querySelector('#lease6_status_table'),
992 leases6.map(function(lease) {
993 var exp;
994
995 if (lease.expires === false)
996 exp = E('em', _('unlimited'));
997 else if (lease.expires <= 0)
998 exp = E('em', _('expired'));
999 else
1000 exp = '%t'.format(lease.expires);
1001
1002 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
1003 name = hint ? (hint.name || L.toArray(hint.ipaddrs || hint.ipv4)[0] || L.toArray(hint.ip6addrs || hint.ipv6)[0]) : null,
1004 host = null;
1005
1006 if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
1007 host = '%s (%s)'.format(lease.hostname, name);
1008 else if (lease.hostname)
1009 host = lease.hostname;
1010 else if (name)
1011 host = name;
1012
1013 return [
1014 host || '-',
1015 lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
1016 lease.duid,
1017 exp
1018 ];
1019 }),
1020 E('em', _('There are no active leases')));
1021 }
1022 });
1023 });
1024
1025 return mapEl;
1026 });
1027 }
1028 });