11 var callHostHints
, callDUIDHints
, callDHCPLeases
, CBILeaseStatus
, CBILease6Status
;
13 callHostHints
= rpc
.declare({
15 method
: 'getHostHints',
19 callDUIDHints
= rpc
.declare({
21 method
: 'getDUIDHints',
25 callDHCPLeases
= rpc
.declare({
27 method
: 'getDHCPLeases',
31 CBILeaseStatus
= form
.DummyValue
.extend({
32 renderWidget: function(section_id
, option_id
, cfgvalue
) {
34 E('h4', _('Active DHCP Leases')),
35 E('table', { 'id': 'lease_status_table', 'class': 'table' }, [
36 E('tr', { 'class': 'tr table-titles' }, [
37 E('th', { 'class': 'th' }, _('Hostname')),
38 E('th', { 'class': 'th' }, _('IPv4 address')),
39 E('th', { 'class': 'th' }, _('MAC address')),
40 E('th', { 'class': 'th' }, _('Lease time remaining'))
42 E('tr', { 'class': 'tr placeholder' }, [
43 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
50 CBILease6Status
= form
.DummyValue
.extend({
51 renderWidget: function(section_id
, option_id
, cfgvalue
) {
53 E('h4', _('Active DHCPv6 Leases')),
54 E('table', { 'id': 'lease6_status_table', 'class': 'table' }, [
55 E('tr', { 'class': 'tr table-titles' }, [
56 E('th', { 'class': 'th' }, _('Host')),
57 E('th', { 'class': 'th' }, _('IPv6 address')),
58 E('th', { 'class': 'th' }, _('DUID')),
59 E('th', { 'class': 'th' }, _('Lease time remaining'))
61 E('tr', { 'class': 'tr placeholder' }, [
62 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
69 function calculateNetwork(addr
, mask
) {
70 addr
= validation
.parseIPv4(String(addr
));
73 mask
= validation
.parseIPv4(network
.prefixToMask(+mask
));
75 mask
= validation
.parseIPv4(String(mask
));
77 if (addr
== null || mask
== null)
82 addr
[0] & (mask
[0] >>> 0 & 255),
83 addr
[1] & (mask
[1] >>> 0 & 255),
84 addr
[2] & (mask
[2] >>> 0 & 255),
85 addr
[3] & (mask
[3] >>> 0 & 255)
91 function getDHCPPools() {
92 return uci
.load('dhcp').then(function() {
93 let sections
= uci
.sections('dhcp', 'dhcp'),
94 tasks
= [], pools
= [];
96 for (var i
= 0; i
< sections
.length
; i
++) {
97 if (sections
[i
].ignore
== '1' || !sections
[i
].interface)
100 tasks
.push(network
.getNetwork(sections
[i
].interface).then(L
.bind(function(section_id
, net
) {
101 var cidr
= net
? (net
.getIPAddrs()[0] || '').split('/') : null;
103 if (cidr
&& cidr
.length
== 2) {
104 var net_mask
= calculateNetwork(cidr
[0], cidr
[1]);
107 section_id
: section_id
,
108 network
: net_mask
[0],
112 }, null, sections
[i
]['.name'])));
115 return Promise
.all(tasks
).then(function() {
121 function validateHostname(sid
, s
) {
122 if (s
== null || s
== '')
126 return _('Expecting: %s').format(_('valid hostname'));
128 var labels
= s
.replace(/^\.+|\.$/g, '').split(/\./);
130 for (var i
= 0; i
< labels
.length
; i
++)
131 if (!labels
[i
].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
132 return _('Expecting: %s').format(_('valid hostname'));
137 function validateAddressList(sid
, s
) {
138 if (s
== null || s
== '')
141 var m
= s
.match(/^\/(.+)\/$/),
142 names
= m
? m
[1].split(/\//) : [ s
];
144 for (var i
= 0; i
< names
.length
; i
++) {
145 var res
= validateHostname(sid
, names
[i
]);
154 function validateServerSpec(sid
, s
) {
155 if (s
== null || s
== '')
158 var m
= s
.match(/^(?:\/(.+)\/)?(.*)$/);
160 return _('Expecting: %s').format(_('valid hostname'));
162 var res
= validateAddressList(sid
, m
[1]);
166 if (m
[2] == '' || m
[2] == '#')
169 // ipaddr%scopeid#srvport@source@interface#srcport
171 m
= m
[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
174 return _('Expecting: %s').format(_('valid IP address'));
176 if (validation
.parseIPv4(m
[1])) {
177 if (m
[3] != null && !validation
.parseIPv4(m
[3]))
178 return _('Expecting: %s').format(_('valid IPv4 address'));
180 else if (validation
.parseIPv6(m
[1])) {
181 if (m
[3] != null && !validation
.parseIPv6(m
[3]))
182 return _('Expecting: %s').format(_('valid IPv6 address'));
185 return _('Expecting: %s').format(_('valid IP address'));
188 if ((m
[2] != null && +m
[2] > 65535) || (m
[4] != null && +m
[4] > 65535))
189 return _('Expecting: %s').format(_('valid port value'));
194 function validateMACAddr(pools
, sid
, s
) {
195 if (s
== null || s
== '')
198 var leases
= uci
.sections('dhcp', 'host'),
199 this_macs
= L
.toArray(s
).map(function(m
) { return m
.toUpperCase() });
201 for (var i
= 0; i
< pools
.length
; i
++) {
202 var this_net_mask
= calculateNetwork(this.section
.formvalue(sid
, 'ip'), pools
[i
].netmask
);
207 for (var j
= 0; j
< leases
.length
; j
++) {
208 if (leases
[j
]['.name'] == sid
|| !leases
[j
].ip
)
211 var lease_net_mask
= calculateNetwork(leases
[j
].ip
, pools
[i
].netmask
);
213 if (!lease_net_mask
|| this_net_mask
[0] != lease_net_mask
[0])
216 var lease_macs
= L
.toArray(leases
[j
].mac
).map(function(m
) { return m
.toUpperCase() });
218 for (var k
= 0; k
< lease_macs
.length
; k
++)
219 for (var l
= 0; l
< this_macs
.length
; l
++)
220 if (lease_macs
[k
] == this_macs
[l
])
221 return _('The MAC address %h is already used by another static lease in the same DHCP pool').format(this_macs
[l
]);
237 render: function(hosts_duids_pools
) {
238 var has_dhcpv6
= L
.hasSystemFeature('dnsmasq', 'dhcpv6') || L
.hasSystemFeature('odhcpd'),
239 hosts
= hosts_duids_pools
[0],
240 duids
= hosts_duids_pools
[1],
241 pools
= hosts_duids_pools
[2],
244 m
= new form
.Map('dhcp', _('DHCP and DNS'), _('Dnsmasq is a combined <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server and <abbr title="Domain Name System">DNS</abbr>-Forwarder for <abbr title="Network Address Translation">NAT</abbr> firewalls'));
246 s
= m
.section(form
.TypedSection
, 'dnsmasq', _('Server Settings'));
250 s
.tab('general', _('General Settings'));
251 s
.tab('files', _('Resolv and Hosts Files'));
252 s
.tab('tftp', _('TFTP Settings'));
253 s
.tab('advanced', _('Advanced Settings'));
254 s
.tab('leases', _('Static Leases'));
256 s
.taboption('general', form
.Flag
, 'domainneeded',
257 _('Domain required'),
258 _('Don\'t forward <abbr title="Domain Name System">DNS</abbr>-Requests without <abbr title="Domain Name System">DNS</abbr>-Name'));
260 s
.taboption('general', form
.Flag
, 'authoritative',
262 _('This is the only <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> in the local network'));
265 s
.taboption('files', form
.Flag
, 'readethers',
266 _('Use <code>/etc/ethers</code>'),
267 _('Read <code>/etc/ethers</code> to configure the <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server'));
269 s
.taboption('files', form
.Value
, 'leasefile',
271 _('file where given <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-leases will be stored'));
273 s
.taboption('files', form
.Flag
, 'noresolv',
274 _('Ignore resolve file')).optional
= true;
276 o
= s
.taboption('files', form
.Value
, 'resolvfile',
278 _('local <abbr title="Domain Name System">DNS</abbr> file'));
280 o
.depends('noresolv', '0');
281 o
.placeholder
= '/tmp/resolv.conf.d/resolv.conf.auto';
285 s
.taboption('files', form
.Flag
, 'nohosts',
286 _('Ignore <code>/etc/hosts</code>')).optional
= true;
288 s
.taboption('files', form
.DynamicList
, 'addnhosts',
289 _('Additional Hosts files')).optional
= true;
291 o
= s
.taboption('advanced', form
.Flag
, 'quietdhcp',
292 _('Suppress logging'),
293 _('Suppress logging of the routine operation of these protocols'));
296 o
= s
.taboption('advanced', form
.Flag
, 'sequential_ip',
297 _('Allocate IP sequentially'),
298 _('Allocate IP addresses sequentially, starting from the lowest available address'));
301 o
= s
.taboption('advanced', form
.Flag
, 'boguspriv',
303 _('Do not forward reverse lookups for local networks'));
304 o
.default = o
.enabled
;
306 s
.taboption('advanced', form
.Flag
, 'filterwin2k',
308 _('Do not forward requests that cannot be answered by public name servers'));
311 s
.taboption('advanced', form
.Flag
, 'localise_queries',
312 _('Localise queries'),
313 _('Localise hostname depending on the requesting subnet if multiple IPs are available'));
315 if (L
.hasSystemFeature('dnsmasq', 'dnssec')) {
316 o
= s
.taboption('advanced', form
.Flag
, 'dnssec',
320 o
= s
.taboption('advanced', form
.Flag
, 'dnsseccheckunsigned',
321 _('DNSSEC check unsigned'),
322 _('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
323 o
.default = o
.enabled
;
327 s
.taboption('general', form
.Value
, 'local',
329 _('Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only'));
331 s
.taboption('general', form
.Value
, 'domain',
333 _('Local domain suffix appended to DHCP names and hosts file entries'));
335 s
.taboption('advanced', form
.Flag
, 'expandhosts',
337 _('Add local domain suffix to names served from hosts files'));
339 s
.taboption('advanced', form
.Flag
, 'nonegcache',
340 _('No negative cache'),
341 _('Do not cache negative replies, e.g. for not existing domains'));
343 s
.taboption('advanced', form
.Value
, 'serversfile',
344 _('Additional servers file'),
345 _('This file may contain lines like \'server=/domain/1.2.3.4\' or \'server=1.2.3.4\' for domain-specific or full upstream <abbr title="Domain Name System">DNS</abbr> servers.'));
347 s
.taboption('advanced', form
.Flag
, 'strictorder',
349 _('<abbr title="Domain Name System">DNS</abbr> servers will be queried in the order of the resolvfile')).optional
= true;
351 s
.taboption('advanced', form
.Flag
, 'allservers',
353 _('Query all available upstream <abbr title="Domain Name System">DNS</abbr> servers')).optional
= true;
355 o
= s
.taboption('advanced', form
.DynamicList
, 'bogusnxdomain', _('Bogus NX Domain Override'),
356 _('List of hosts that supply bogus NX domain results'));
359 o
.placeholder
= '67.215.65.132';
362 s
.taboption('general', form
.Flag
, 'logqueries',
364 _('Write received DNS requests to syslog')).optional
= true;
366 o
= s
.taboption('general', form
.DynamicList
, 'server', _('DNS forwardings'),
367 _('List of <abbr title="Domain Name System">DNS</abbr> servers to forward requests to'));
370 o
.placeholder
= '/example.org/10.1.2.3';
371 o
.validate
= validateServerSpec
;
374 o
= s
.taboption('general', form
.DynamicList
, 'address', _('Addresses'),
375 _('List of domains to force to an IP address.'));
378 o
.placeholder
= '/router.local/192.168.0.1';
381 o
= s
.taboption('general', form
.Flag
, 'rebind_protection',
382 _('Rebind protection'),
383 _('Discard upstream RFC1918 responses'));
388 o
= s
.taboption('general', form
.Flag
, 'rebind_localhost',
389 _('Allow localhost'),
390 _('Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services'));
392 o
.depends('rebind_protection', '1');
395 o
= s
.taboption('general', form
.DynamicList
, 'rebind_domain',
396 _('Domain whitelist'),
397 _('List of domains to allow RFC1918 responses for'));
400 o
.depends('rebind_protection', '1');
401 o
.placeholder
= 'ihost.netflix.com';
402 o
.validate
= validateAddressList
;
405 o
= s
.taboption('advanced', form
.Value
, 'port',
406 _('<abbr title="Domain Name System">DNS</abbr> server port'),
407 _('Listening port for inbound DNS queries'));
414 o
= s
.taboption('advanced', form
.Value
, 'queryport',
415 _('<abbr title="Domain Name System">DNS</abbr> query port'),
416 _('Fixed source port for outbound DNS queries'));
420 o
.placeholder
= _('any');
423 o
= s
.taboption('advanced', form
.Value
, 'dhcpleasemax',
424 _('<abbr title="maximal">Max.</abbr> <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> leases'),
425 _('Maximum allowed number of active DHCP leases'));
428 o
.datatype
= 'uinteger';
429 o
.placeholder
= _('unlimited');
432 o
= s
.taboption('advanced', form
.Value
, 'ednspacket_max',
433 _('<abbr title="maximal">Max.</abbr> <abbr title="Extension Mechanisms for Domain Name System">EDNS0</abbr> packet size'),
434 _('Maximum allowed size of EDNS.0 UDP packets'));
437 o
.datatype
= 'uinteger';
438 o
.placeholder
= 1280;
441 o
= s
.taboption('advanced', form
.Value
, 'dnsforwardmax',
442 _('<abbr title="maximal">Max.</abbr> concurrent queries'),
443 _('Maximum allowed number of concurrent DNS queries'));
446 o
.datatype
= 'uinteger';
449 o
= s
.taboption('advanced', form
.Value
, 'cachesize',
450 _('Size of DNS query cache'),
451 _('Number of cached DNS entries (max is 10000, 0 is no caching)'));
453 o
.datatype
= 'range(0,10000)';
456 s
.taboption('tftp', form
.Flag
, 'enable_tftp',
457 _('Enable TFTP server')).optional
= true;
459 o
= s
.taboption('tftp', form
.Value
, 'tftp_root',
460 _('TFTP server root'),
461 _('Root directory for files served via TFTP'));
464 o
.depends('enable_tftp', '1');
468 o
= s
.taboption('tftp', form
.Value
, 'dhcp_boot',
469 _('Network boot image'),
470 _('Filename of the boot image advertised to clients'));
473 o
.depends('enable_tftp', '1');
474 o
.placeholder
= 'pxelinux.0';
476 o
= s
.taboption('general', form
.Flag
, 'localservice',
477 _('Local Service Only'),
478 _('Limit DNS service to subnets interfaces on which we are serving DNS.'));
482 o
= s
.taboption('general', form
.Flag
, 'nonwildcard',
484 _('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
485 o
.default = o
.enabled
;
489 o
= s
.taboption('general', form
.DynamicList
, 'interface',
490 _('Listen Interfaces'),
491 _('Limit listening to these interfaces, and loopback.'));
494 o
= s
.taboption('general', form
.DynamicList
, 'notinterface',
495 _('Exclude interfaces'),
496 _('Prevent listening on these interfaces.'));
499 o
= s
.taboption('leases', form
.SectionValue
, '__leases__', form
.GridSection
, 'host', null,
500 _('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 />' +
501 _('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.'));
509 so
= ss
.option(form
.Value
, 'name', _('Hostname'));
510 so
.validate
= validateHostname
;
512 so
.write = function(section
, value
) {
513 uci
.set('dhcp', section
, 'name', value
);
514 uci
.set('dhcp', section
, 'dns', '1');
516 so
.remove = function(section
) {
517 uci
.unset('dhcp', section
, 'name');
518 uci
.unset('dhcp', section
, 'dns');
521 so
= ss
.option(form
.Value
, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
522 so
.datatype
= 'list(macaddr)';
524 so
.cfgvalue = function(section
) {
525 var macs
= L
.toArray(uci
.get('dhcp', section
, 'mac')),
528 for (var i
= 0, mac
; (mac
= macs
[i
]) != null; i
++)
529 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
))
530 result
.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
531 parseInt(RegExp
.$1, 16), parseInt(RegExp
.$2, 16),
532 parseInt(RegExp
.$3, 16), parseInt(RegExp
.$4, 16),
533 parseInt(RegExp
.$5, 16), parseInt(RegExp
.$6, 16)));
535 return result
.length
? result
.join(' ') : null;
537 so
.renderWidget = function(section_id
, option_index
, cfgvalue
) {
538 var node
= form
.Value
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]),
539 ipopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'ip' })[0];
541 node
.addEventListener('cbi-dropdown-change', L
.bind(function(ipopt
, section_id
, ev
) {
542 var mac
= ev
.detail
.value
.value
;
543 if (mac
== null || mac
== '' || !hosts
[mac
])
546 var iphint
= L
.toArray(hosts
[mac
].ipaddrs
|| hosts
[mac
].ipv4
)[0];
550 var ip
= ipopt
.formvalue(section_id
);
551 if (ip
!= null && ip
!= '')
554 var node
= ipopt
.map
.findElement('id', ipopt
.cbid(section_id
));
556 dom
.callClassMethod(node
, 'setValue', iphint
);
557 }, this, ipopt
, section_id
));
561 so
.validate
= validateMACAddr
.bind(so
, pools
);
562 Object
.keys(hosts
).forEach(function(mac
) {
563 var hint
= hosts
[mac
].name
|| L
.toArray(hosts
[mac
].ipaddrs
|| hosts
[mac
].ipv4
)[0];
564 so
.value(mac
, hint
? '%s (%s)'.format(mac
, hint
) : mac
);
567 so
= ss
.option(form
.Value
, 'ip', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Address'));
568 so
.datatype
= 'or(ip4addr,"ignore")';
569 so
.validate = function(section
, value
) {
570 var m
= this.section
.formvalue(section
, 'mac'),
571 n
= this.section
.formvalue(section
, 'name');
573 if ((m
== null || m
== '') && (n
== null || n
== ''))
574 return _('One of hostname or mac address must be specified!');
576 if (value
== null || value
== '' || value
== 'ignore')
579 var leases
= uci
.sections('dhcp', 'host');
581 for (var i
= 0; i
< leases
.length
; i
++)
582 if (leases
[i
]['.name'] != section
&& leases
[i
].ip
== value
)
583 return _('The IP address %h is already used by another static lease').format(value
);
586 for (var i
= 0; i
< pools
.length
; i
++) {
587 var net_mask
= calculateNetwork(value
, pools
[i
].netmask
);
589 if (net_mask
&& net_mask
[0] == pools
[i
].network
)
593 return _('The IP address is outside of any DHCP pool address range');
598 Object
.keys(hosts
).forEach(function(mac
) {
599 var addrs
= L
.toArray(hosts
[mac
].ipaddrs
|| hosts
[mac
].ipv4
);
601 for (var i
= 0; i
< addrs
.length
; i
++)
602 ipaddrs
[addrs
[i
]] = hosts
[mac
].name
;
605 L
.sortedKeys(ipaddrs
, null, 'addr').forEach(function(ipv4
) {
606 so
.value(ipv4
, ipaddrs
[ipv4
] ? '%s (%s)'.format(ipv4
, ipaddrs
[ipv4
]) : ipv4
);
609 so
= ss
.option(form
.Value
, 'leasetime', _('Lease time'));
612 so
= ss
.option(form
.Value
, 'duid', _('<abbr title="The DHCP Unique Identifier">DUID</abbr>'));
613 so
.datatype
= 'and(rangelength(20,36),hexstring)';
614 Object
.keys(duids
).forEach(function(duid
) {
615 so
.value(duid
, '%s (%s)'.format(duid
, duids
[duid
].hostname
|| duids
[duid
].macaddr
|| duids
[duid
].ip6addr
|| '?'));
618 so
= ss
.option(form
.Value
, 'hostid', _('<abbr title="Internet Protocol Version 6">IPv6</abbr>-Suffix (hex)'));
620 o
= s
.taboption('leases', CBILeaseStatus
, '__status__');
623 o
= s
.taboption('leases', CBILease6Status
, '__status6__');
625 return m
.render().then(function(mapEl
) {
626 poll
.add(function() {
627 return callDHCPLeases().then(function(leaseinfo
) {
628 var leases
= Array
.isArray(leaseinfo
.dhcp_leases
) ? leaseinfo
.dhcp_leases
: [],
629 leases6
= Array
.isArray(leaseinfo
.dhcp6_leases
) ? leaseinfo
.dhcp6_leases
: [];
631 cbi_update_table(mapEl
.querySelector('#lease_status_table'),
632 leases
.map(function(lease
) {
635 if (lease
.expires
=== false)
636 exp
= E('em', _('unlimited'));
637 else if (lease
.expires
<= 0)
638 exp
= E('em', _('expired'));
640 exp
= '%t'.format(lease
.expires
);
642 var hint
= lease
.macaddr
? hosts
[lease
.macaddr
] : null,
643 name
= hint
? hint
.name
: null,
646 if (name
&& lease
.hostname
&& lease
.hostname
!= name
)
647 host
= '%s (%s)'.format(lease
.hostname
, name
);
648 else if (lease
.hostname
)
649 host
= lease
.hostname
;
658 E('em', _('There are no active leases')));
661 cbi_update_table(mapEl
.querySelector('#lease6_status_table'),
662 leases6
.map(function(lease
) {
665 if (lease
.expires
=== false)
666 exp
= E('em', _('unlimited'));
667 else if (lease
.expires
<= 0)
668 exp
= E('em', _('expired'));
670 exp
= '%t'.format(lease
.expires
);
672 var hint
= lease
.macaddr
? hosts
[lease
.macaddr
] : null,
673 name
= hint
? (hint
.name
|| L
.toArray(hint
.ipaddrs
|| hint
.ipv4
)[0] || L
.toArray(hint
.ip6addrs
|| hint
.ipv6
)[0]) : null,
676 if (name
&& lease
.hostname
&& lease
.hostname
!= name
&& lease
.ip6addr
!= name
)
677 host
= '%s (%s)'.format(lease
.hostname
, name
);
678 else if (lease
.hostname
)
679 host
= lease
.hostname
;
685 lease
.ip6addrs
? lease
.ip6addrs
.join(' ') : lease
.ip6addr
,
690 E('em', _('There are no active leases')));