10 var callHostHints
, callDUIDHints
, callDHCPLeases
, CBILeaseStatus
, CBILease6Status
;
12 callHostHints
= rpc
.declare({
14 method
: 'getHostHints',
18 callDUIDHints
= rpc
.declare({
20 method
: 'getDUIDHints',
24 callDHCPLeases
= rpc
.declare({
26 method
: 'getDHCPLeases',
30 CBILeaseStatus
= form
.DummyValue
.extend({
31 renderWidget: function(section_id
, option_id
, cfgvalue
) {
33 E('h4', _('Active DHCP Leases')),
34 E('table', { 'id': 'lease_status_table', 'class': 'table' }, [
35 E('tr', { 'class': 'tr table-titles' }, [
36 E('th', { 'class': 'th' }, _('Hostname')),
37 E('th', { 'class': 'th' }, _('IPv4-Address')),
38 E('th', { 'class': 'th' }, _('MAC-Address')),
39 E('th', { 'class': 'th' }, _('Lease time remaining'))
41 E('tr', { 'class': 'tr placeholder' }, [
42 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
49 CBILease6Status
= form
.DummyValue
.extend({
50 renderWidget: function(section_id
, option_id
, cfgvalue
) {
52 E('h4', _('Active DHCPv6 Leases')),
53 E('table', { 'id': 'lease6_status_table', 'class': 'table' }, [
54 E('tr', { 'class': 'tr table-titles' }, [
55 E('th', { 'class': 'th' }, _('Host')),
56 E('th', { 'class': 'th' }, _('IPv6-Address')),
57 E('th', { 'class': 'th' }, _('DUID')),
58 E('th', { 'class': 'th' }, _('Lease time remaining'))
60 E('tr', { 'class': 'tr placeholder' }, [
61 E('td', { 'class': 'td' }, E('em', _('Collecting data...')))
68 function validateHostname(sid
, s
) {
69 if (s
== null || s
== '')
73 return _('Expecting: %s').format(_('valid hostname'));
75 var labels
= s
.replace(/^\.+|\.$/g, '').split(/\./);
77 for (var i
= 0; i
< labels
.length
; i
++)
78 if (!labels
[i
].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
79 return _('Expecting: %s').format(_('valid hostname'));
84 function validateAddressList(sid
, s
) {
85 if (s
== null || s
== '')
88 var m
= s
.match(/^\/(.+)\/$/),
89 names
= m
? m
[1].split(/\//) : [ s
];
91 for (var i
= 0; i
< names
.length
; i
++) {
92 var res
= validateHostname(sid
, names
[i
]);
101 function validateServerSpec(sid
, s
) {
102 if (s
== null || s
== '')
105 var m
= s
.match(/^(?:\/(.+)\/)?(.*)$/);
107 return _('Expecting: %s').format(_('valid hostname'));
109 var res
= validateAddressList(sid
, m
[1]);
113 if (m
[2] == '' || m
[2] == '#')
116 // ipaddr%scopeid#srvport@source@interface#srcport
118 m
= m
[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
121 return _('Expecting: %s').format(_('valid IP address'));
123 if (validation
.parseIPv4(m
[1])) {
124 if (m
[3] != null && !validation
.parseIPv4(m
[3]))
125 return _('Expecting: %s').format(_('valid IPv4 address'));
127 else if (validation
.parseIPv6(m
[1])) {
128 if (m
[3] != null && !validation
.parseIPv6(m
[3]))
129 return _('Expecting: %s').format(_('valid IPv6 address'));
132 return _('Expecting: %s').format(_('valid IP address'));
135 if ((m
[2] != null && +m
[2] > 65535) || (m
[4] != null && +m
[4] > 65535))
136 return _('Expecting: %s').format(_('valid port value'));
149 render: function(hosts_duids
) {
150 var has_dhcpv6
= L
.hasSystemFeature('dnsmasq', 'dhcpv6') || L
.hasSystemFeature('odhcpd'),
151 hosts
= hosts_duids
[0],
152 duids
= hosts_duids
[1],
155 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'));
157 s
= m
.section(form
.TypedSection
, 'dnsmasq', _('Server Settings'));
161 s
.tab('general', _('General Settings'));
162 s
.tab('files', _('Resolv and Hosts Files'));
163 s
.tab('tftp', _('TFTP Settings'));
164 s
.tab('advanced', _('Advanced Settings'));
165 s
.tab('leases', _('Static Leases'));
167 s
.taboption('general', form
.Flag
, 'domainneeded',
168 _('Domain required'),
169 _('Don\'t forward <abbr title="Domain Name System">DNS</abbr>-Requests without <abbr title="Domain Name System">DNS</abbr>-Name'));
171 s
.taboption('general', form
.Flag
, 'authoritative',
173 _('This is the only <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> in the local network'));
176 s
.taboption('files', form
.Flag
, 'readethers',
177 _('Use <code>/etc/ethers</code>'),
178 _('Read <code>/etc/ethers</code> to configure the <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server'));
180 s
.taboption('files', form
.Value
, 'leasefile',
182 _('file where given <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-leases will be stored'));
184 s
.taboption('files', form
.Flag
, 'noresolv',
185 _('Ignore resolve file')).optional
= true;
187 o
= s
.taboption('files', form
.Value
, 'resolvfile',
189 _('local <abbr title="Domain Name System">DNS</abbr> file'));
191 o
.depends('noresolv', '0');
192 o
.placeholder
= '/tmp/resolv.conf.d/resolv.conf.auto';
196 s
.taboption('files', form
.Flag
, 'nohosts',
197 _('Ignore <code>/etc/hosts</code>')).optional
= true;
199 s
.taboption('files', form
.DynamicList
, 'addnhosts',
200 _('Additional Hosts files')).optional
= true;
202 o
= s
.taboption('advanced', form
.Flag
, 'quietdhcp',
203 _('Suppress logging'),
204 _('Suppress logging of the routine operation of these protocols'));
207 o
= s
.taboption('advanced', form
.Flag
, 'sequential_ip',
208 _('Allocate IP sequentially'),
209 _('Allocate IP addresses sequentially, starting from the lowest available address'));
212 o
= s
.taboption('advanced', form
.Flag
, 'boguspriv',
214 _('Do not forward reverse lookups for local networks'));
215 o
.default = o
.enabled
;
217 s
.taboption('advanced', form
.Flag
, 'filterwin2k',
219 _('Do not forward requests that cannot be answered by public name servers'));
222 s
.taboption('advanced', form
.Flag
, 'localise_queries',
223 _('Localise queries'),
224 _('Localise hostname depending on the requesting subnet if multiple IPs are available'));
226 if (L
.hasSystemFeature('dnsmasq', 'dnssec')) {
227 o
= s
.taboption('advanced', form
.Flag
, 'dnssec',
231 o
= s
.taboption('advanced', form
.Flag
, 'dnsseccheckunsigned',
232 _('DNSSEC check unsigned'),
233 _('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
234 o
.default = o
.enabled
;
238 s
.taboption('general', form
.Value
, 'local',
240 _('Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only'));
242 s
.taboption('general', form
.Value
, 'domain',
244 _('Local domain suffix appended to DHCP names and hosts file entries'));
246 s
.taboption('advanced', form
.Flag
, 'expandhosts',
248 _('Add local domain suffix to names served from hosts files'));
250 s
.taboption('advanced', form
.Flag
, 'nonegcache',
251 _('No negative cache'),
252 _('Do not cache negative replies, e.g. for not existing domains'));
254 s
.taboption('advanced', form
.Value
, 'serversfile',
255 _('Additional servers file'),
256 _('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.'));
258 s
.taboption('advanced', form
.Flag
, 'strictorder',
260 _('<abbr title="Domain Name System">DNS</abbr> servers will be queried in the order of the resolvfile')).optional
= true;
262 s
.taboption('advanced', form
.Flag
, 'allservers',
264 _('Query all available upstream <abbr title="Domain Name System">DNS</abbr> servers')).optional
= true;
266 o
= s
.taboption('advanced', form
.DynamicList
, 'bogusnxdomain', _('Bogus NX Domain Override'),
267 _('List of hosts that supply bogus NX domain results'));
270 o
.placeholder
= '67.215.65.132';
273 s
.taboption('general', form
.Flag
, 'logqueries',
275 _('Write received DNS requests to syslog')).optional
= true;
277 o
= s
.taboption('general', form
.DynamicList
, 'server', _('DNS forwardings'),
278 _('List of <abbr title="Domain Name System">DNS</abbr> servers to forward requests to'));
281 o
.placeholder
= '/example.org/10.1.2.3';
282 o
.validate
= validateServerSpec
;
285 o
= s
.taboption('general', form
.DynamicList
, 'address', _('Addresses'),
286 _('List of domains to force to an IP address.'));
289 o
.placeholder
= '/router.local/192.168.0.1';
292 o
= s
.taboption('general', form
.Flag
, 'rebind_protection',
293 _('Rebind protection'),
294 _('Discard upstream RFC1918 responses'));
299 o
= s
.taboption('general', form
.Flag
, 'rebind_localhost',
300 _('Allow localhost'),
301 _('Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services'));
303 o
.depends('rebind_protection', '1');
306 o
= s
.taboption('general', form
.DynamicList
, 'rebind_domain',
307 _('Domain whitelist'),
308 _('List of domains to allow RFC1918 responses for'));
311 o
.depends('rebind_protection', '1');
312 o
.placeholder
= 'ihost.netflix.com';
313 o
.validate
= validateAddressList
;
316 o
= s
.taboption('advanced', form
.Value
, 'port',
317 _('<abbr title="Domain Name System">DNS</abbr> server port'),
318 _('Listening port for inbound DNS queries'));
325 o
= s
.taboption('advanced', form
.Value
, 'queryport',
326 _('<abbr title="Domain Name System">DNS</abbr> query port'),
327 _('Fixed source port for outbound DNS queries'));
331 o
.placeholder
= _('any');
334 o
= s
.taboption('advanced', form
.Value
, 'dhcpleasemax',
335 _('<abbr title="maximal">Max.</abbr> <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> leases'),
336 _('Maximum allowed number of active DHCP leases'));
339 o
.datatype
= 'uinteger';
340 o
.placeholder
= _('unlimited');
343 o
= s
.taboption('advanced', form
.Value
, 'ednspacket_max',
344 _('<abbr title="maximal">Max.</abbr> <abbr title="Extension Mechanisms for Domain Name System">EDNS0</abbr> packet size'),
345 _('Maximum allowed size of EDNS.0 UDP packets'));
348 o
.datatype
= 'uinteger';
349 o
.placeholder
= 1280;
352 o
= s
.taboption('advanced', form
.Value
, 'dnsforwardmax',
353 _('<abbr title="maximal">Max.</abbr> concurrent queries'),
354 _('Maximum allowed number of concurrent DNS queries'));
357 o
.datatype
= 'uinteger';
360 o
= s
.taboption('advanced', form
.Value
, 'cachesize',
361 _('Size of DNS query cache'),
362 _('Number of cached DNS entries (max is 10000, 0 is no caching)'));
364 o
.datatype
= 'range(0,10000)';
367 s
.taboption('tftp', form
.Flag
, 'enable_tftp',
368 _('Enable TFTP server')).optional
= true;
370 o
= s
.taboption('tftp', form
.Value
, 'tftp_root',
371 _('TFTP server root'),
372 _('Root directory for files served via TFTP'));
375 o
.depends('enable_tftp', '1');
379 o
= s
.taboption('tftp', form
.Value
, 'dhcp_boot',
380 _('Network boot image'),
381 _('Filename of the boot image advertised to clients'));
384 o
.depends('enable_tftp', '1');
385 o
.placeholder
= 'pxelinux.0';
387 o
= s
.taboption('general', form
.Flag
, 'localservice',
388 _('Local Service Only'),
389 _('Limit DNS service to subnets interfaces on which we are serving DNS.'));
393 o
= s
.taboption('general', form
.Flag
, 'nonwildcard',
395 _('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
396 o
.default = o
.enabled
;
400 o
= s
.taboption('general', form
.DynamicList
, 'interface',
401 _('Listen Interfaces'),
402 _('Limit listening to these interfaces, and loopback.'));
405 o
= s
.taboption('general', form
.DynamicList
, 'notinterface',
406 _('Exclude interfaces'),
407 _('Prevent listening on these interfaces.'));
410 o
= s
.taboption('leases', form
.SectionValue
, '__leases__', form
.GridSection
, 'host', null,
411 _('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 />' +
412 _('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.'));
419 so
= ss
.option(form
.Value
, 'name', _('Hostname'));
420 so
.validate
= validateHostname
;
422 so
.write = function(section
, value
) {
423 uci
.set('dhcp', section
, 'name', value
);
424 uci
.set('dhcp', section
, 'dns', '1');
426 so
.remove = function(section
) {
427 uci
.unset('dhcp', section
, 'name');
428 uci
.unset('dhcp', section
, 'dns');
431 so
= ss
.option(form
.Value
, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
432 so
.datatype
= 'list(unique(macaddr))';
434 so
.cfgvalue = function(section
) {
435 var macs
= L
.toArray(uci
.get('dhcp', section
, 'mac')),
438 for (var i
= 0, mac
; (mac
= macs
[i
]) != null; i
++)
439 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
))
440 result
.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
441 parseInt(RegExp
.$1, 16), parseInt(RegExp
.$2, 16),
442 parseInt(RegExp
.$3, 16), parseInt(RegExp
.$4, 16),
443 parseInt(RegExp
.$5, 16), parseInt(RegExp
.$6, 16)));
445 return result
.length
? result
.join(' ') : null;
447 so
.renderWidget = function(section_id
, option_index
, cfgvalue
) {
448 var node
= form
.Value
.prototype.renderWidget
.apply(this, [section_id
, option_index
, cfgvalue
]),
449 ipopt
= this.section
.children
.filter(function(o
) { return o
.option
== 'ip' })[0];
451 node
.addEventListener('cbi-dropdown-change', L
.bind(function(ipopt
, section_id
, ev
) {
452 var mac
= ev
.detail
.value
.value
;
453 if (mac
== null || mac
== '' || !hosts
[mac
] || !hosts
[mac
].ipv4
)
456 var ip
= ipopt
.formvalue(section_id
);
457 if (ip
!= null && ip
!= '')
460 var node
= ipopt
.map
.findElement('id', ipopt
.cbid(section_id
));
462 dom
.callClassMethod(node
, 'setValue', hosts
[mac
].ipv4
);
463 }, this, ipopt
, section_id
));
467 Object
.keys(hosts
).forEach(function(mac
) {
468 var hint
= hosts
[mac
].name
|| hosts
[mac
].ipv4
;
469 so
.value(mac
, hint
? '%s (%s)'.format(mac
, hint
) : mac
);
472 so
.write = function(section
, value
) {
473 var ip
= this.map
.lookupOption('ip', section
)[0].formvalue(section
);
474 var hosts
= uci
.sections('dhcp', 'host');
475 var section_removed
= false;
477 for (var i
= 0; i
< hosts
.length
; i
++) {
478 if (ip
== hosts
[i
].ip
) {
479 uci
.set('dhcp', hosts
[i
]['.name'], 'mac', [hosts
[i
].mac
, value
].join(' '));
480 uci
.remove('dhcp', section
);
481 section_removed
= true;
486 if (!section_removed
) {
487 uci
.set('dhcp', section
, 'mac', value
);
491 so
= ss
.option(form
.Value
, 'ip', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Address'));
492 so
.datatype
= 'or(ip4addr,"ignore")';
493 so
.validate = function(section
, value
) {
494 var mac
= this.map
.lookupOption('mac', section
),
495 name
= this.map
.lookupOption('name', section
),
496 m
= mac
? mac
[0].formvalue(section
) : null,
497 n
= name
? name
[0].formvalue(section
) : null;
499 if ((m
== null || m
== '') && (n
== null || n
== ''))
500 return _('One of hostname or mac address must be specified!');
504 Object
.keys(hosts
).forEach(function(mac
) {
505 if (hosts
[mac
].ipv4
) {
506 var hint
= hosts
[mac
].name
;
507 so
.value(hosts
[mac
].ipv4
, hint
? '%s (%s)'.format(hosts
[mac
].ipv4
, hint
) : hosts
[mac
].ipv4
);
511 so
= ss
.option(form
.Value
, 'leasetime', _('Lease time'));
514 so
= ss
.option(form
.Value
, 'duid', _('<abbr title="The DHCP Unique Identifier">DUID</abbr>'));
515 so
.datatype
= 'and(rangelength(20,36),hexstring)';
516 Object
.keys(duids
).forEach(function(duid
) {
517 so
.value(duid
, '%s (%s)'.format(duid
, duids
[duid
].hostname
|| duids
[duid
].macaddr
|| duids
[duid
].ip6addr
|| '?'));
520 so
= ss
.option(form
.Value
, 'hostid', _('<abbr title="Internet Protocol Version 6">IPv6</abbr>-Suffix (hex)'));
522 o
= s
.taboption('leases', CBILeaseStatus
, '__status__');
525 o
= s
.taboption('leases', CBILease6Status
, '__status6__');
527 return m
.render().then(function(mapEl
) {
528 poll
.add(function() {
529 return callDHCPLeases().then(function(leaseinfo
) {
530 var leases
= Array
.isArray(leaseinfo
.dhcp_leases
) ? leaseinfo
.dhcp_leases
: [],
531 leases6
= Array
.isArray(leaseinfo
.dhcp6_leases
) ? leaseinfo
.dhcp6_leases
: [];
533 cbi_update_table(mapEl
.querySelector('#lease_status_table'),
534 leases
.map(function(lease
) {
537 if (lease
.expires
=== false)
538 exp
= E('em', _('unlimited'));
539 else if (lease
.expires
<= 0)
540 exp
= E('em', _('expired'));
542 exp
= '%t'.format(lease
.expires
);
545 lease
.hostname
|| '?',
551 E('em', _('There are no active leases')));
554 cbi_update_table(mapEl
.querySelector('#lease6_status_table'),
555 leases6
.map(function(lease
) {
558 if (lease
.expires
=== false)
559 exp
= E('em', _('unlimited'));
560 else if (lease
.expires
<= 0)
561 exp
= E('em', _('expired'));
563 exp
= '%t'.format(lease
.expires
);
565 var hint
= lease
.macaddr
? hosts
[lease
.macaddr
] : null,
566 name
= hint
? (hint
.name
|| hint
.ipv4
|| hint
.ipv6
) : null,
569 if (name
&& lease
.hostname
&& lease
.hostname
!= name
&& lease
.ip6addr
!= name
)
570 host
= '%s (%s)'.format(lease
.hostname
, name
);
571 else if (lease
.hostname
)
572 host
= lease
.hostname
;
578 lease
.ip6addrs
? lease
.ip6addrs
.join(' ') : lease
.ip6addr
,
583 E('em', _('There are no active leases')));