10 'require tools.widgets as widgets';
12 var callHostHints
, callDUIDHints
, callDHCPLeases
, CBILeaseStatus
, CBILease6Status
;
14 callHostHints
= rpc
.declare({
16 method
: 'getHostHints',
20 callDUIDHints
= rpc
.declare({
22 method
: 'getDUIDHints',
26 callDHCPLeases
= rpc
.declare({
28 method
: 'getDHCPLeases',
32 CBILeaseStatus
= form
.DummyValue
.extend({
33 renderWidget: function(section_id
, option_id
, cfgvalue
) {
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'))
43 E('tr', { 'class': 'tr placeholder' }, [
44 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
51 CBILease6Status
= form
.DummyValue
.extend({
52 renderWidget: function(section_id
, option_id
, cfgvalue
) {
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'))
62 E('tr', { 'class': 'tr placeholder' }, [
63 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
70 function calculateNetwork(addr
, mask
) {
71 addr
= validation
.parseIPv4(String(addr
));
74 mask
= validation
.parseIPv4(network
.prefixToMask(+mask
));
76 mask
= validation
.parseIPv4(String(mask
));
78 if (addr
== null || mask
== null)
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)
92 function generateDnsmasqInstanceEntry(data
) {
93 const nameValueMap
= new Map(Object
.entries(data
));
94 let formatString
= nameValueMap
.get('.index') + ' (' + _('Name') + (nameValueMap
.get('.anonymous') ? ': dnsmasq[' + nameValueMap
.get('.index') + ']': ': ' + nameValueMap
.get('.name'));
97 formatString
+= ', ' + _('Domain') + ': ' + data
.domain
;
100 formatString
+= ', ' + _('Local') + ': ' + data
.local
;
104 return nameValueMap
.get('.name'), formatString
;
107 function getDHCPPools() {
108 return uci
.load('dhcp').then(function() {
109 let sections
= uci
.sections('dhcp', 'dhcp'),
110 tasks
= [], pools
= [];
112 for (var i
= 0; i
< sections
.length
; i
++) {
113 if (sections
[i
].ignore
== '1' || !sections
[i
].interface)
116 tasks
.push(network
.getNetwork(sections
[i
].interface).then(L
.bind(function(section_id
, net
) {
117 var cidr
= net
? (net
.getIPAddrs()[0] || '').split('/') : null;
119 if (cidr
&& cidr
.length
== 2) {
120 var net_mask
= calculateNetwork(cidr
[0], cidr
[1]);
123 section_id
: section_id
,
124 network
: net_mask
[0],
128 }, null, sections
[i
]['.name'])));
131 return Promise
.all(tasks
).then(function() {
137 function validateHostname(sid
, s
) {
138 if (s
== null || s
== '')
142 return _('Expecting: %s').format(_('valid hostname'));
144 var labels
= s
.replace(/^\*?\.?|\.$/g, '').split(/\./);
146 for (var i
= 0; i
< labels
.length
; i
++)
147 if (!labels
[i
].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
148 return _('Expecting: %s').format(_('valid hostname'));
153 function validateAddressList(sid
, s
) {
154 if (s
== null || s
== '')
157 var m
= s
.match(/^\/(.+)\/$/),
158 names
= m
? m
[1].split(/\//) : [ s
];
160 for (var i
= 0; i
< names
.length
; i
++) {
161 var res
= validateHostname(sid
, names
[i
]);
170 function validateServerSpec(sid
, s
) {
171 if (s
== null || s
== '')
174 var m
= s
.match(/^(\/.*\/)?(.*)$/);
176 return _('Expecting: %s').format(_('valid hostname'));
178 if (m
[1] != '//' && m
[1] != '/#/') {
179 var res
= validateAddressList(sid
, m
[1]);
184 if (m
[2] == '' || m
[2] == '#')
187 // ipaddr%scopeid#srvport@source@interface#srcport
189 m
= m
[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
192 return _('Expecting: %s').format(_('valid IP address'));
194 if (validation
.parseIPv4(m
[1])) {
195 if (m
[3] != null && !validation
.parseIPv4(m
[3]))
196 return _('Expecting: %s').format(_('valid IPv4 address'));
198 else if (validation
.parseIPv6(m
[1])) {
199 if (m
[3] != null && !validation
.parseIPv6(m
[3]))
200 return _('Expecting: %s').format(_('valid IPv6 address'));
203 return _('Expecting: %s').format(_('valid IP address'));
206 if ((m
[2] != null && +m
[2] > 65535) || (m
[4] != null && +m
[4] > 65535))
207 return _('Expecting: %s').format(_('valid port value'));
212 function expandAndFormatMAC(macs
) {
215 macs
.forEach(mac
=> {
216 if (isValidMAC(mac
)) {
217 const expandedMac
= mac
.split(':').map(part
=> {
218 return (part
.length
=== 1 && part
!== '*') ? '0' + part
: part
;
219 }).join(':').toUpperCase();
220 result
.push(expandedMac
);
224 return result
.length
? result
.join(' ') : null;
227 function isValidMAC(sid
, s
) {
231 let macaddrs
= L
.toArray(s
);
233 for (var i
= 0; i
< macaddrs
.length
; i
++)
234 if (!macaddrs
[i
].match(/^(([0-9a-f]{1,2}|\*)[:-]){5}([0-9a-f]{1,2}|\*)$/i))
235 return _('Expecting a valid MAC address, optionally including wildcards') + _('; invalid MAC: ') + macaddrs
[i
];
240 function validateMACAddr(pools
, sid
, s
) {
241 if (s
== null || s
== '')
244 var leases
= uci
.sections('dhcp', 'host'),
245 this_macs
= L
.toArray(s
).map(function(m
) { return m
.toUpperCase() });
247 for (var i
= 0; i
< pools
.length
; i
++) {
248 var this_net_mask
= calculateNetwork(this.section
.formvalue(sid
, 'ip'), pools
[i
].netmask
);
253 for (var j
= 0; j
< leases
.length
; j
++) {
254 if (leases
[j
]['.name'] == sid
|| !leases
[j
].ip
)
257 var lease_net_mask
= calculateNetwork(leases
[j
].ip
, pools
[i
].netmask
);
259 if (!lease_net_mask
|| this_net_mask
[0] != lease_net_mask
[0])
262 var lease_macs
= L
.toArray(leases
[j
].mac
).map(function(m
) { return m
.toUpperCase() });
264 for (var k
= 0; k
< lease_macs
.length
; k
++)
265 for (var l
= 0; l
< this_macs
.length
; l
++)
266 if (lease_macs
[k
] == this_macs
[l
])
267 return _('The MAC address %h is already used by another static lease in the same DHCP pool').format(this_macs
[l
]);
271 return isValidMAC(sid
, s
);
280 network
.getNetworks()
284 render: function(hosts_duids_pools
) {
285 var has_dhcpv6
= L
.hasSystemFeature('dnsmasq', 'dhcpv6') || L
.hasSystemFeature('odhcpd'),
286 hosts
= hosts_duids_pools
[0],
287 duids
= hosts_duids_pools
[1],
288 pools
= hosts_duids_pools
[2],
289 networks
= hosts_duids_pools
[3],
292 let noi18nstrings
= {
293 etc_hosts
: '<code>/etc/hosts</code>',
294 etc_ethers
: '<code>/etc/ethers</code>',
295 localhost_v6
: '<code>::1</code>',
296 loopback_slash_8_v4
: '<code>127.0.0.0/8</code>',
297 not_found
: '<code>Not found</code>',
298 nxdomain
: '<code>NXDOMAIN</code>',
299 rfc_1918_link
: '<a href="https://www.rfc-editor.org/rfc/rfc1918">RFC1918</a>',
300 rfc_4193_link
: '<a href="https://www.rfc-editor.org/rfc/rfc4193">RFC4193</a>',
301 rfc_4291_link
: '<a href="https://www.rfc-editor.org/rfc/rfc4291">RFC4291</a>',
302 rfc_6303_link
: '<a href="https://www.rfc-editor.org/rfc/rfc6303">RFC6303</a>',
303 reverse_arpa
: '<code>*.IN-ADDR.ARPA,*.IP6.ARPA</code>',
304 servers_file_entry01
: '<code>server=1.2.3.4</code>',
305 servers_file_entry02
: '<code>server=/domain/1.2.3.4</code>',
309 function customi18n(template
, values
) {
311 values
= noi18nstrings
;
312 return template
.replace(/\{(\w+)\}/g, (match
, key
) => values
[key
] || match
);
315 m
= new form
.Map('dhcp', _('DHCP and DNS'),
316 _('Dnsmasq is a lightweight <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> server and <abbr title="Domain Name System">DNS</abbr> forwarder.'));
318 s
= m
.section(form
.TypedSection
, 'dnsmasq');
324 s
.tab('general', _('General'));
325 s
.tab('devices', _('Devices & Ports'));
326 s
.tab('dnssecopt', _('DNSSEC'));
327 s
.tab('filteropts', _('Filter'));
328 s
.tab('forward', _('Forwards'));
329 s
.tab('limits', _('Limits'));
330 s
.tab('logging', _('Log'));
331 s
.tab('files', _('Resolv & Hosts Files'));
332 s
.tab('leases', _('Static Leases'));
333 s
.tab('hosts', _('Hostnames'));
334 s
.tab('ipsets', _('IP Sets'));
335 s
.tab('relay', _('Relay'));
336 s
.tab('srvhosts', _('SRV'));
337 s
.tab('mxhosts', _('MX'));
338 s
.tab('cnamehosts', _('CNAME'));
339 s
.tab('pxe_tftp', _('PXE/TFTP'));
341 s
.taboption('filteropts', form
.Flag
, 'domainneeded',
342 _('Domain required'),
343 _('Never forward DNS queries which lack dots or domain parts.') + '<br />' +
344 customi18n(_('Names not in {etc_hosts} are answered {not_found}.') )
346 s
.taboption('general', form
.Flag
, 'authoritative',
348 _('This is the only DHCP server in the local network.'));
350 o
= s
.taboption('general', form
.Value
, 'local',
351 _('Resolve these locally'),
352 _('Never forward these matching domains or subdomains; resolve from DHCP or hosts files only.'));
353 o
.placeholder
= '/internal.example.com/private.example.com/example.org';
355 s
.taboption('general', form
.Value
, 'domain',
357 _('Local domain suffix appended to DHCP names and hosts file entries.'));
359 s
.taboption('general', form
.Flag
, 'expandhosts',
361 _('Add local domain suffix to names served from hosts files.'));
363 o
= s
.taboption('logging', form
.Flag
, 'logqueries',
365 _('Write received DNS queries to syslog.') + ' ' + _('Dump cache on SIGUSR1, include requesting IP.'));
368 o
= s
.taboption('logging', form
.Flag
, 'logdhcp',
369 _('Extra DHCP logging'),
370 _('Log all options sent to DHCP clients and the tags used to determine them.'));
373 o
= s
.taboption('logging', form
.Value
, 'logfacility',
375 _('Set log class/facility for syslog entries.'));
394 o
.value('-', _('stderr'));
396 o
= s
.taboption('forward', form
.DynamicList
, 'server',
398 _('Forward specific domain queries to specific upstream servers.'));
400 o
.placeholder
= '/*.example.org/10.1.2.3';
401 o
.validate
= validateServerSpec
;
403 o
= s
.taboption('general', form
.DynamicList
, 'address',
405 _('Resolve specified FQDNs to an IP.') + '<br />' +
406 customi18n(_('Syntax: {code_syntax}.'),
407 {code_syntax
: '<code>/fqdn[/fqdn…]/[ipaddr]</code>'}) + '<br />' +
408 customi18n(_('{example_nx} returns {nxdomain}.',
409 'hint: <code>/example.com/</code> returns <code>NXDOMAIN</code>.'),
410 {example_nx
: '<code>/example.com/</code>', nxdomain
: '<code>NXDOMAIN</code>'}) + '<br />' +
411 customi18n(_('{any_domain} matches any domain (and returns {nxdomain}).',
412 'hint: <code>/#/</code> matches any domain (and returns NXDOMAIN).'),
413 {any_domain
:'<code>/#/</code>', nxdomain
: '<code>NXDOMAIN</code>'}) + '<br />' +
415 _('{example_null} returns {null_addr} addresses ({null_ipv4}, {null_ipv6}) for {example_com} and its subdomains.',
416 'hint: <code>/example.com/#</code> returns NULL addresses (<code>0.0.0.0</code>, <code>::</code>) for example.com and its subdomains.'),
417 { example_null
: '<code>/example.com/#</code>',
418 null_addr
: '<code>NULL</code>',
419 null_ipv4
: '<code>0.0.0.0</code>',
420 null_ipv6
: '<code>::</code>',
421 example_com
: '<code>example.com</code>',
426 o
.placeholder
= '/router.local/router.lan/192.168.0.1';
428 o
= s
.taboption('general', form
.DynamicList
, 'ipset',
430 _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.'));
432 o
.placeholder
= '/example.org/ipset,ipset6';
434 o
= s
.taboption('filteropts', form
.Flag
, 'rebind_protection',
435 _('Rebind protection'),
436 customi18n(_('Discard upstream responses containing {rfc_1918_link} addresses.') ) + '<br />' +
437 customi18n(_('Discard also upstream responses containing {rfc_4193_link}, Link-Local and private IPv4-Mapped {rfc_4291_link} IPv6 Addresses.') )
441 o
= s
.taboption('filteropts', form
.Flag
, 'rebind_localhost',
442 _('Allow localhost'),
444 _('Exempt {loopback_slash_8_v4} and {localhost_v6} from rebinding checks, e.g. for <abbr title="Real-time Block List">RBL</abbr> services.')
447 o
.depends('rebind_protection', '1');
449 o
= s
.taboption('filteropts', form
.DynamicList
, 'rebind_domain',
450 _('Domain whitelist'),
451 customi18n(_('List of domains to allow {rfc_1918_link} responses for.') )
453 o
.depends('rebind_protection', '1');
455 o
.placeholder
= 'ihost.netflix.com';
456 o
.validate
= validateAddressList
;
458 o
= s
.taboption('filteropts', form
.Flag
, 'localservice',
459 _('Local service only'),
460 _('Accept DNS queries only from hosts whose address is on a local subnet.'));
464 o
= s
.taboption('devices', form
.Flag
, 'nonwildcard',
466 _('Bind only to configured interface addresses, instead of the wildcard address.'));
467 o
.default = o
.enabled
;
471 o
= s
.taboption('devices', widgets
.NetworkSelect
, 'interface',
472 _('Listen interfaces'),
473 _('Listen only on the specified interfaces, and loopback if not excluded explicitly.'));
477 o
= s
.taboption('devices', widgets
.NetworkSelect
, 'notinterface',
478 _('Exclude interfaces'),
479 _('Do not listen on the specified interfaces.'));
484 o
= s
.taboption('relay', form
.SectionValue
, '__relays__', form
.TableSection
, 'relay', null,
485 _('Relay DHCP requests elsewhere. OK: v4↔v4, v6↔v6. Not OK: v4↔v6, v6↔v4.')
486 + '<br />' + _('Note: you may also need a DHCP Proxy (currently unavailable) when specifying a non-standard Relay To port(<code>addr#port</code>).')
487 + '<br />' + _('You may add multiple unique Relay To on the same Listen addr.'));
495 ss
.nodescriptions
= true;
497 so
= ss
.option(form
.Value
, 'local_addr', _('Relay from'));
499 so
.datatype
= 'ipaddr';
501 for (var family
= 4; family
<= 6; family
+= 2) {
502 for (var i
= 0; i
< networks
.length
; i
++) {
503 if (networks
[i
].getName() != 'loopback') {
504 var addrs
= (family
== 6) ? networks
[i
].getIP6Addrs() : networks
[i
].getIPAddrs();
505 for (var j
= 0; j
< addrs
.length
; j
++) {
506 var addr
= addrs
[j
].split('/')[0];
507 so
.value(addr
, E([], [
509 widgets
.NetworkSelect
.prototype.renderIfaceBadge(networks
[i
]),
517 so
= ss
.option(form
.Value
, 'server_addr', _('Relay to address'));
520 so
.placeholder
= '192.168.10.1#535';
522 so
.validate = function(section
, value
) {
523 var m
= this.section
.formvalue(section
, 'local_addr'),
524 n
= this.section
.formvalue(section
, 'server_addr'),
528 return _('Both "Relay from" and "Relay to address" must be specified.');
532 if (p
.length
> 1 && !/^[0-9]+$/.test(p
[1]))
533 return _('Expected port number.');
537 if ((validation
.parseIPv6(m
) && validation
.parseIPv6(n
)) ||
538 validation
.parseIPv4(m
) && validation
.parseIPv4(n
))
541 return _('Address families of "Relay from" and "Relay to address" must match.')
547 so
= ss
.option(widgets
.NetworkSelect
, 'interface', _('Only accept replies via'));
550 so
.placeholder
= 'lan';
552 s
.taboption('files', form
.Flag
, 'readethers',
553 customi18n(_('Use {etc_ethers}') ),
554 customi18n(_('Read {etc_ethers} to configure the DHCP server.') )
557 s
.taboption('files', form
.Value
, 'leasefile',
559 _('File to store DHCP lease information.'));
561 o
= s
.taboption('files', form
.Flag
, 'noresolv',
562 _('Ignore resolv file'));
565 o
= s
.taboption('files', form
.Value
, 'resolvfile',
567 _('File with upstream resolvers.'));
568 o
.depends('noresolv', '0');
569 o
.placeholder
= '/tmp/resolv.conf.d/resolv.conf.auto';
572 o
= s
.taboption('files', form
.Flag
, 'strictorder',
574 _('Query upstream resolvers in the order they appear in the resolv file.'));
577 o
= s
.taboption('files', form
.Flag
, 'nohosts',
578 customi18n(_('Ignore {etc_hosts}') )
582 o
= s
.taboption('files', form
.DynamicList
, 'addnhosts',
583 _('Additional hosts files'));
585 o
.placeholder
= '/etc/dnsmasq.hosts';
587 o
= s
.taboption('logging', form
.Flag
, 'quietdhcp',
588 _('Suppress logging'),
589 _('Suppress logging of the routine operation for the DHCP protocol.'));
591 o
.depends('logdhcp', '0');
593 o
= s
.taboption('general', form
.Flag
, 'sequential_ip',
594 _('Allocate IPs sequentially'),
595 _('Allocate IP addresses sequentially, starting from the lowest available address.'));
598 o
= s
.taboption('filteropts', form
.Flag
, 'boguspriv',
601 _('Reject reverse lookups to {rfc_6303_link} IP ranges ({reverse_arpa}) not in {etc_hosts}.') )
603 o
.default = o
.enabled
;
605 s
.taboption('filteropts', form
.Flag
, 'filterwin2k',
606 _('Filter SRV/SOA service discovery'),
607 _('Filters SRV/SOA service discovery, to avoid triggering dial-on-demand links.') + '<br />' +
608 _('May prevent VoIP or other services from working.'));
610 o
= s
.taboption('filteropts', form
.Flag
, 'filter_aaaa',
611 _('Filter IPv6 AAAA records'),
612 _('Remove IPv6 addresses from the results and only return IPv4 addresses.') + '<br />' +
613 _('Can be useful if ISP has IPv6 nameservers but does not provide IPv6 routing.'));
616 o
= s
.taboption('filteropts', form
.Flag
, 'filter_a',
617 _('Filter IPv4 A records'),
618 _('Remove IPv4 addresses from the results and only return IPv6 addresses.'));
621 s
.taboption('filteropts', form
.Flag
, 'localise_queries',
622 _('Localise queries'),
623 customi18n(_('Limit response records (from {etc_hosts}) to those that fall within the subnet of the querying interface.') ) + '<br />' +
624 _('This prevents unreachable IPs in subnets not accessible to you.') + '<br />' +
625 _('Note: IPv4 only.'));
627 if (L
.hasSystemFeature('dnsmasq', 'dnssec')) {
628 o
= s
.taboption('dnssecopt', form
.Flag
, 'dnssec',
630 _('Validate DNS replies and cache DNSSEC data, requires upstream to support DNSSEC.'));
633 o
= s
.taboption('dnssecopt', form
.Flag
, 'dnsseccheckunsigned',
634 _('DNSSEC check unsigned'),
635 _('Verify unsigned domain responses really come from unsigned domains.'));
636 o
.default = o
.enabled
;
640 s
.taboption('filteropts', form
.Flag
, 'nonegcache',
641 _('No negative cache'),
642 _('Do not cache negative replies, e.g. for non-existent domains.'));
644 o
= s
.taboption('forward', form
.Value
, 'serversfile',
645 _('Additional servers file'),
646 customi18n(_('File listing upstream resolvers, optionally domain-specific, e.g. {servers_file_entry01}, {servers_file_entry02}.') )
648 o
.placeholder
= '/etc/dnsmasq.servers';
650 o
= s
.taboption('general', form
.Flag
, 'allservers',
652 _('Query all available upstream resolvers.') + ' ' + _('First answer wins.'));
655 o
= s
.taboption('filteropts', form
.DynamicList
, 'bogusnxdomain',
656 customi18n(_('IPs to override with {nxdomain}') ),
657 customi18n(_('Transform replies which contain the specified addresses or subnets into {nxdomain} responses.') )
660 o
.placeholder
= '64.94.110.11';
662 o
= s
.taboption('devices', form
.Value
, 'port',
663 _('DNS server port'),
664 _('Listening port for inbound DNS queries.'));
669 o
= s
.taboption('devices', form
.Value
, 'queryport',
671 _('Fixed source port for outbound DNS queries.'));
674 o
.placeholder
= _('any');
676 o
= s
.taboption('devices', form
.Value
, 'minport',
677 _('Minimum source port #'),
678 _('Min valid value %s.').format('<code>1024</code>') + ' ' + _('Useful for systems behind firewalls.'));
681 o
.placeholder
= 1024;
682 o
.depends('queryport', '');
684 o
= s
.taboption('devices', form
.Value
, 'maxport',
685 _('Maximum source port #'),
686 _('Max valid value %s.').format('<code>65535</code>') + ' ' + _('Useful for systems behind firewalls.'));
689 o
.placeholder
= 50000;
690 o
.depends('queryport', '');
692 o
= s
.taboption('limits', form
.Value
, 'dhcpleasemax',
693 _('Max. DHCP leases'),
694 _('Maximum allowed number of active DHCP leases.'));
696 o
.datatype
= 'uinteger';
699 o
= s
.taboption('limits', form
.Value
, 'ednspacket_max',
700 _('Max. EDNS0 packet size'),
701 _('Maximum allowed size of EDNS0 UDP packets.'));
703 o
.datatype
= 'uinteger';
704 o
.placeholder
= 1280;
706 o
= s
.taboption('limits', form
.Value
, 'dnsforwardmax',
707 _('Max. concurrent queries'),
708 _('Maximum allowed number of concurrent DNS queries.'));
710 o
.datatype
= 'uinteger';
713 o
= s
.taboption('limits', form
.Value
, 'cachesize',
714 _('Size of DNS query cache'),
715 _('Number of cached DNS entries, 10000 is maximum, 0 is no caching.'));
717 o
.datatype
= 'range(0,10000)';
718 o
.placeholder
= 1000;
720 o
= s
.taboption('limits', form
.Value
, 'min_cache_ttl',
722 _('Extend short TTL values to the seconds value given when caching them. Use with caution.') +
723 _(' (Max 1h == 3600)'));
727 o
= s
.taboption('limits', form
.Value
, 'max_cache_ttl',
729 _('Set a maximum seconds TTL value for entries in the cache.'));
731 o
.placeholder
= 3600;
733 o
= s
.taboption('pxe_tftp', form
.Flag
, 'enable_tftp',
734 _('Enable TFTP server'),
735 _('Enable the built-in single-instance TFTP server.'));
738 o
= s
.taboption('pxe_tftp', form
.Value
, 'tftp_root',
739 _('TFTP server root'),
740 _('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>.'));
741 o
.depends('enable_tftp', '1');
745 o
= s
.taboption('pxe_tftp', form
.Value
, 'dhcp_boot',
746 _('Network boot image'),
747 _('Filename of the boot image advertised to clients.'));
748 o
.depends('enable_tftp', '1');
750 o
.placeholder
= 'pxelinux.0';
752 /* PXE - https://openwrt.org/docs/guide-user/base-system/dhcp#booting_options */
753 o = s.taboption('pxe_tftp', form.SectionValue, '__pxe__', form.GridSection, 'boot', null,
754 _('Special <abbr title="Preboot eXecution Environment">PXE</abbr> boot options for Dnsmasq.'));
758 ss.modaltitle = _('Edit PXE/TFTP/BOOTP Host');
759 ss.nodescriptions = true;
761 so = ss.option(form.Value, 'filename',
763 _('Host requests this filename from the boot server.'));
765 so.placeholder = 'pxelinux.0';
767 so = ss.option(form.Value, 'servername',
769 _('The hostname of the boot server'));
771 so.placeholder = 'myNAS';
773 so = ss.option(form.Value, 'serveraddress',
775 _('The IP address of the boot server'));
777 so.placeholder = '192.168.1.2';
779 so = ss.option(form.DynamicList, 'dhcp_option',
781 _('Additional options to send to the below match tags.') + '<br />' +
782 _('%s means "the address of the system running dnsmasq".').format('<code>0.0.0.0</code>'));
784 so.placeholder = 'option:root-path,192.168.1.2:/data/netboot/root';
786 so = ss.option(form.Value, 'networkid',
788 _('Only DHCP Clients with this tag are sent this boot option.'));
792 so = ss.option(form.Flag, 'force',
794 _('Always send the chosen DHCP options. Sometimes needed, with e.g. PXELinux.'));
797 so = ss.option(form.Value, 'instance',
799 _('Dnsmasq instance to which this boot section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
802 Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
803 so.value(generateDnsmasqInstanceEntry(val));
806 o = s.taboption('srvhosts', form.SectionValue, '__srvhosts__', form.TableSection, 'srvhost', null,
807 _('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')
808 + '<br />' + _('_service: _sip, _ldap, _imap, _stun, _xmpp-client, … . (Note: while _http is possible, no browsers support SRV records.)')
809 + '<br />' + _('_proto: _tcp, _udp, _sctp, _quic, … .')
810 + '<br />' + _('You may add multiple records for the same Target.')
811 + '<br />' + _('Larger weights (of the same prio) are given a proportionately higher probability of being selected.'));
820 so = ss.option(form.Value, 'srv', _('SRV'), _('Syntax:') + ' ' + '<code>_service._proto.example.com.</code>');
822 so.datatype = 'hostname';
823 so.placeholder = '_sip._tcp.example.com.';
825 so = ss.option(form.Value, 'target', _('Target'), _('CNAME or fqdn'));
827 so.datatype = 'hostname';
828 so.placeholder = 'sip.example.com.';
830 so = ss.option(form.Value, 'port', _('Port'));
832 so.datatype = 'port';
833 so.placeholder = '5060';
835 so = ss.option(form.Value, 'class', _('Priority'), _('Ordinal: lower comes first.'));
837 so.datatype = 'range(0,65535)';
838 so.placeholder = '10';
840 so = ss.option(form.Value, 'weight', _('Weight'));
842 so.datatype = 'range(0,65535)';
843 so.placeholder = '50';
845 o = s.taboption('mxhosts', form.SectionValue, '__mxhosts__', form.TableSection, 'mxhost', null,
846 _('Bind service records to a domain name: specify the location of services.')
847 + '<br />' + _('You may add multiple records for the same domain.'));
855 ss.nodescriptions = true;
857 so = ss.option(form.Value, 'domain', _('Domain'));
859 so.datatype = 'hostname';
860 so.placeholder = 'example.com.';
862 so = ss.option(form.Value, 'relay', _('Relay'));
864 so.datatype = 'hostname';
865 so.placeholder = 'relay.example.com.';
867 so = ss.option(form.Value, 'pref', _('Priority'), _('Ordinal: lower comes first.'));
869 so.datatype = 'range(0,65535)';
870 so.placeholder = '0';
872 o = s.taboption('cnamehosts', form.SectionValue, '__cname__', form.TableSection, 'cname', null,
873 _('Set an alias for a hostname.'));
881 ss.nodescriptions = true;
883 so = ss.option(form.Value, 'cname', _('Domain'));
885 so.datatype = 'hostname';
886 so.placeholder = 'www.example.com.';
888 so = ss.option(form.Value, 'target', _('Target'));
890 so.datatype = 'hostname';
891 so.placeholder = 'example.com.';
893 o = s.taboption('hosts', form.SectionValue, '__hosts__', form.GridSection, 'domain', null,
894 _('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.'));
902 so = ss.option(form.Value, 'name', _('Hostname'));
904 so.datatype = 'hostname';
906 so = ss.option(form.Value, 'ip', _('IP address'));
908 so.datatype = 'ipaddr';
912 Object.keys(hosts).forEach(function(mac) {
913 var addrs = L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4);
915 for (var i = 0; i < addrs.length; i++)
916 ipaddrs[addrs[i]] = hosts[mac].name || mac;
919 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
920 so.value(ipv4, '%s (%s)'.format(ipv4, ipaddrs[ipv4]));
923 o = s.taboption('ipsets', form.SectionValue, '__ipsets__', form.GridSection, 'ipset', null,
924 _('List of IP sets to populate with the IPs of DNS lookup results of the FQDNs also specified here.') + '<br />' +
925 _('The netfilter components below are only regarded when running fw4.'));
933 ss.nodescriptions = true;
934 ss.modaltitle = _('Edit IP set');
936 so = ss.option(form.DynamicList, 'name', _('Name of the set'));
939 so.datatype = 'string';
941 so = ss.option(form.DynamicList, 'domain', _('FQDN'));
944 so.datatype = 'hostname';
946 so = ss.option(form.Value, 'table', _('Netfilter table name'), _('Defaults to fw4.'));
948 so.placeholder = 'fw4';
951 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 />' +
952 _('Adding an IPv6 to an IPv4 set and vice-versa silently fails.'));
955 so.value('inet', _('IPv4+6'));
956 so.value('ip', _('IPv4'));
957 so.value('ip6', _('IPv6'));
959 o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
960 _('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 /><br />' +
961 _('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.') + '<br /><br />' +
962 _('The tag construct filters which host directives are used; more than one tag can be provided, in this case the request must match all of them. Tagged directives are used in preference to untagged ones. Note that one of mac, duid or hostname still needs to be specified (can be a wildcard).'));
969 ss.nodescriptions = true;
971 ss.modaltitle = _('Edit static lease');
973 so = ss.option(form.Value, 'name',
975 _('Optional hostname to assign'));
976 so.validate = validateHostname;
978 so.write = function(section, value) {
979 uci.set('dhcp', section, 'name', value);
980 uci.set('dhcp', section, 'dns', '1');
982 so.remove = function(section) {
983 uci.unset('dhcp', section, 'name');
984 uci.unset('dhcp', section, 'dns');
987 //this can be a .DynamicList or a .Value with a widget and dnsmasq handles multimac OK.
988 so = ss.option(form.DynamicList, 'mac',
989 _('MAC address(es)'),
990 _('The hardware address(es) of this entry/host.') + '<br /><br />' +
991 _('In DHCPv4, it is possible to include more than one mac address. This allows an IP address to be associated with multiple macaddrs, and dnsmasq abandons a DHCP lease to one of the macaddrs when another asks for a lease. It only works reliably if only one of the macaddrs is active at any time.'));
992 //As a special case, in DHCPv4, it is possible to include more than one hardware address. eg: --dhcp-host=11:22:33:44:55:66,12:34:56:78:90:12,192.168.0.2 This allows an IP address to be associated with multiple hardware addresses, and gives dnsmasq permission to abandon a DHCP lease to one of the hardware addresses when another one asks for a lease
994 so.cfgvalue = function(section) {
995 var macs = L.toArray(uci.get('dhcp', section, 'mac'));
996 return expandAndFormatMAC(macs);
998 //removed jows renderwidget function which hindered multi-mac entry
999 so.validate = validateMACAddr.bind(so, pools);
1000 Object.keys(hosts).forEach(function(mac) {
1001 var hint = hosts[mac].name || L.toArray(hosts[mac].ipaddrs || hosts[mac].ipv4)[0];
1002 so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
1005 so = ss.option(form.Value, 'ip', _('IPv4 address'), _('The IP address to be used for this host, or <em>ignore</em> to ignore any DHCP request from this host.'));
1006 so.value('ignore', _('Ignore'));
1007 so.datatype = 'or(ip4addr,"ignore")';
1008 so.validate = function(section, value) {
1009 var m = this.section.formvalue(section, 'mac'),
1010 n = this.section.formvalue(section, 'name');
1012 if ((m && !m.length > 0) && !n)
1013 return _('One of hostname or MAC address must be specified!');
1015 if (!value || value == 'ignore')
1018 var leases = uci.sections('dhcp', 'host');
1020 for (var i = 0; i < leases.length; i++)
1021 if (leases[i]['.name'] != section && leases[i].ip == value)
1022 return _('The IP address %h is already used by another static lease').format(value);
1024 for (var i = 0; i < pools.length; i++) {
1025 var net_mask = calculateNetwork(value, pools[i].netmask);
1027 if (net_mask && net_mask[0] == pools[i].network)
1031 return _('The IP address is outside of any DHCP pool address range');
1034 L.sortedKeys(ipaddrs, null, 'addr').forEach(function(ipv4) {
1035 so.value(ipv4, ipaddrs[ipv4] ? '%s (%s)'.format(ipv4, ipaddrs[ipv4]) : ipv4);
1038 so = ss.option(form.Value, 'leasetime',
1040 _('Host-specific lease time, e.g. <code>5m</code>, <code>3h</code>, <code>7d</code>.'));
1042 so.value('5m', _('5m (5 minutes)'));
1043 so.value('3h', _('3h (3 hours)'));
1044 so.value('12h', _('12h (12 hours - default)'));
1045 so.value('7d', _('7d (7 days)'));
1046 so.value('infinite', _('infinite (lease does not expire)'));
1048 so = ss.option(form.Value, 'duid',
1050 _('The DHCPv6-DUID (DHCP unique identifier) of this host.'));
1051 so.datatype = 'and(rangelength(20,36),hexstring)';
1052 Object.keys(duids).forEach(function(duid) {
1053 so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
1056 so = ss.option(form.Value, 'hostid',
1057 _('IPv6-Suffix (hex)'),
1058 _('The IPv6 interface identifier (address suffix) as hexadecimal number (max. 16 chars).'));
1059 so.datatype = 'and(rangelength(0,16),hexstring)';
1061 so = ss.option(form.DynamicList, 'tag',
1063 _('Assign new, freeform tags to this entry.'));
1065 so = ss.option(form.DynamicList, 'match_tag',
1067 _('When a host matches an entry then the special tag %s is set. Use %s to match all known hosts.').format('<code>known</code>', '<code>known</code>') + '<br /><br />' +
1068 _('Ignore requests from unknown machines using %s.').format('<code>!known</code>') + '<br /><br />' +
1069 _('If a host matches an entry which cannot be used because it specifies an address on a different subnet, the tag %s is set.').format('<code>known-othernet</code>'));
1070 so.value('known', _('known'));
1071 so.value('!known', _('!known (not known)'));
1072 so.value('known-othernet', _('known-othernet (on different subnet)'));
1075 so = ss.option(form.Value, 'instance',
1077 _('Dnsmasq instance to which this DHCP host section is bound. If unspecified, the section is valid for all dnsmasq instances.'));
1080 Object.values(L.uci.sections('dhcp', 'dnsmasq')).forEach(function(val, index) {
1081 so.value(generateDnsmasqInstanceEntry(val));
1085 so = ss.option(form.Flag, 'broadcast',
1087 _('Force broadcast DHCP response.'));
1089 so = ss.option(form.Flag, 'dns',
1090 _('Forward/reverse DNS'),
1091 _('Add static forward and reverse DNS entries for this host.'));
1093 o = s.taboption('leases', CBILeaseStatus, '__status__');
1096 o = s.taboption('leases', CBILease6Status, '__status6__');
1098 return m.render().then(function(mapEl) {
1099 poll.add(function() {
1100 return callDHCPLeases().then(function(leaseinfo) {
1101 var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
1102 leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
1104 cbi_update_table(mapEl.querySelector('#lease_status_table'),
1105 leases.map(function(lease) {
1108 if (lease.expires === false)
1109 exp = E('em', _('unlimited'));
1110 else if (lease.expires <= 0)
1111 exp = E('em', _('expired'));
1113 exp = '%t'.format(lease.expires);
1115 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
1116 name = hint ? hint.name : null,
1119 if (name && lease.hostname && lease.hostname != name)
1120 host = '%s (%s)'.format(lease.hostname, name);
1121 else if (lease.hostname)
1122 host = lease.hostname;
1131 E('em', _('There are no active leases')));
1134 cbi_update_table(mapEl.querySelector('#lease6_status_table'),
1135 leases6.map(function(lease) {
1138 if (lease.expires === false)
1139 exp = E('em', _('unlimited'));
1140 else if (lease.expires <= 0)
1141 exp = E('em', _('expired'));
1143 exp = '%t'.format(lease.expires);
1145 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
1146 name = hint ? (hint.name || L.toArray(hint.ipaddrs || hint.ipv4)[0] || L.toArray(hint.ip6addrs || hint.ipv6)[0]) : null,
1149 if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
1150 host = '%s (%s)'.format(lease.hostname, name);
1151 else if (lease.hostname)
1152 host = lease.hostname;
1158 lease.ip6addrs ? lease.ip6addrs.join('<br />') : lease.ip6addr,
1163 E('em', _('There are no active leases')));