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('div', { 'id': 'lease_status_table', 'class': 'table' }, [
35 E('div', { 'class': 'tr table-titles' }, [
36 E('div', { 'class': 'th' }, _('Hostname')),
37 E('div', { 'class': 'th' }, _('IPv4-Address')),
38 E('div', { 'class': 'th' }, _('MAC-Address')),
39 E('div', { 'class': 'th' }, _('Lease time remaining'))
41 E('div', { 'class': 'tr placeholder' }, [
42 E('div', { '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('div', { 'id': 'lease6_status_table', 'class': 'table' }, [
54 E('div', { 'class': 'tr table-titles' }, [
55 E('div', { 'class': 'th' }, _('Host')),
56 E('div', { 'class': 'th' }, _('IPv6-Address')),
57 E('div', { 'class': 'th' }, _('DUID')),
58 E('div', { 'class': 'th' }, _('Leasetime remaining'))
60 E('div', { 'class': 'tr placeholder' }, [
61 E('div', { 'class': 'td' }, E('em', _('Collecting data...')))
68 function validateHostname(sid, s) {
70 return _('Expecting: %s').format(_('valid hostname'));
72 var labels = s.replace(/^\.+|\.$/g, '').split(/\./);
74 for (var i = 0; i < labels.length; i++)
75 if (!labels[i].match(/^[a-z0-9_](?:[a-z0-9-]{0,61}[a-z0-9])?$/i))
76 return _('Expecting: %s').format(_('valid hostname'));
81 function validateAddressList(sid, s) {
82 if (s == null || s == '')
85 var m = s.match(/^\/(.+)\/$/),
86 names = m ? m[1].split(/\//) : [ s ];
88 for (var i = 0; i < names.length; i++) {
89 var res = validateHostname(sid, names[i]);
98 function validateServerSpec(sid, s) {
99 if (s == null || s == '')
102 var m = s.match(/^(?:\/(.+)\/)?(.*)$/);
104 return _('Expecting: %s').format(_('valid hostname'));
106 var res = validateAddressList(sid, m[1]);
110 if (m[2] == '' || m[2] == '#')
113 // ipaddr%scopeid#srvport@source@interface#srcport
115 m = m[2].match(/^([0-9a-f:.]+)(?:%[^#@]+)?(?:#(\d+))?(?:@([0-9a-f:.]+)(?:@[^#]+)?(?:#(\d+))?)?$/);
118 return _('Expecting: %s').format(_('valid IP address'));
120 if (validation.parseIPv4(m[1])) {
121 if (m[3] != null && !validation.parseIPv4(m[3]))
122 return _('Expecting: %s').format(_('valid IPv4 address'));
124 else if (validation.parseIPv6(m[1])) {
125 if (m[3] != null && !validation.parseIPv6(m[3]))
126 return _('Expecting: %s').format(_('valid IPv6 address'));
129 return _('Expecting: %s').format(_('valid IP address'));
132 if ((m[2] != null && +m[2] > 65535) || (m[4] != null && +m[4] > 65535))
133 return _('Expecting: %s').format(_('valid port value'));
146 render: function(hosts_duids) {
147 var has_dhcpv6 = L.hasSystemFeature('dnsmasq', 'dhcpv6') || L.hasSystemFeature('odhcpd'),
148 hosts = hosts_duids[0],
149 duids = hosts_duids[1],
152 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'));
154 s = m.section(form.TypedSection, 'dnsmasq', _('Server Settings'));
158 s.tab('general', _('General Settings'));
159 s.tab('files', _('Resolv and Hosts Files'));
160 s.tab('tftp', _('TFTP Settings'));
161 s.tab('advanced', _('Advanced Settings'));
162 s.tab('leases', _('Static Leases'));
164 s.taboption('general', form.Flag, 'domainneeded',
165 _('Domain required'),
166 _('Don\'t forward <abbr title="Domain Name System">DNS</abbr>-Requests without <abbr title="Domain Name System">DNS</abbr>-Name'));
168 s.taboption('general', form.Flag, 'authoritative',
170 _('This is the only <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> in the local network'));
173 s.taboption('files', form.Flag, 'readethers',
174 _('Use <code>/etc/ethers</code>'),
175 _('Read <code>/etc/ethers</code> to configure the <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-Server'));
177 s.taboption('files', form.Value, 'leasefile',
179 _('file where given <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr>-leases will be stored'));
181 s.taboption('files', form.Flag, 'noresolv',
182 _('Ignore resolve file')).optional = true;
184 o = s.taboption('files', form.Value, 'resolvfile',
186 _('local <abbr title="Domain Name System">DNS</abbr> file'));
188 o.depends('noresolv', '0');
189 o.placeholder = '/tmp/resolv.conf.auto';
193 s.taboption('files', form.Flag, 'nohosts',
194 _('Ignore <code>/etc/hosts</code>')).optional = true;
196 s.taboption('files', form.DynamicList, 'addnhosts',
197 _('Additional Hosts files')).optional = true;
199 o = s.taboption('advanced', form.Flag, 'quietdhcp',
200 _('Suppress logging'),
201 _('Suppress logging of the routine operation of these protocols'));
204 o = s.taboption('advanced', form.Flag, 'sequential_ip',
205 _('Allocate IP sequentially'),
206 _('Allocate IP addresses sequentially, starting from the lowest available address'));
209 o = s.taboption('advanced', form.Flag, 'boguspriv',
211 _('Do not forward reverse lookups for local networks'));
212 o.default = o.enabled;
214 s.taboption('advanced', form.Flag, 'filterwin2k',
216 _('Do not forward requests that cannot be answered by public name servers'));
219 s.taboption('advanced', form.Flag, 'localise_queries',
220 _('Localise queries'),
221 _('Localise hostname depending on the requesting subnet if multiple IPs are available'));
223 if (L.hasSystemFeature('dnsmasq', 'dnssec')) {
224 o = s.taboption('advanced', form.Flag, 'dnssec',
228 o = s.taboption('advanced', form.Flag, 'dnsseccheckunsigned',
229 _('DNSSEC check unsigned'),
230 _('Requires upstream supports DNSSEC; verify unsigned domain responses really come from unsigned domains'));
231 o.default = o.enabled;
235 s.taboption('general', form.Value, 'local',
237 _('Local domain specification. Names matching this domain are never forwarded and are resolved from DHCP or hosts files only'));
239 s.taboption('general', form.Value, 'domain',
241 _('Local domain suffix appended to DHCP names and hosts file entries'));
243 s.taboption('advanced', form.Flag, 'expandhosts',
245 _('Add local domain suffix to names served from hosts files'));
247 s.taboption('advanced', form.Flag, 'nonegcache',
248 _('No negative cache'),
249 _('Do not cache negative replies, e.g. for not existing domains'));
251 s.taboption('advanced', form.Value, 'serversfile',
252 _('Additional servers file'),
253 _('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.'));
255 s.taboption('advanced', form.Flag, 'strictorder',
257 _('<abbr title="Domain Name System">DNS</abbr> servers will be queried in the order of the resolvfile')).optional = true;
259 s.taboption('advanced', form.Flag, 'allservers',
261 _('Query all available upstream <abbr title="Domain Name System">DNS</abbr> servers')).optional = true;
263 o = s.taboption('advanced', form.DynamicList, 'bogusnxdomain', _('Bogus NX Domain Override'),
264 _('List of hosts that supply bogus NX domain results'));
267 o.placeholder = '67.215.65.132';
270 s.taboption('general', form.Flag, 'logqueries',
272 _('Write received DNS requests to syslog')).optional = true;
274 o = s.taboption('general', form.DynamicList, 'server', _('DNS forwardings'),
275 _('List of <abbr title="Domain Name System">DNS</abbr> servers to forward requests to'));
278 o.placeholder = '/example.org/10.1.2.3';
279 o.validate = validateServerSpec;
282 o = s.taboption('general', form.Flag, 'rebind_protection',
283 _('Rebind protection'),
284 _('Discard upstream RFC1918 responses'));
289 o = s.taboption('general', form.Flag, 'rebind_localhost',
290 _('Allow localhost'),
291 _('Allow upstream responses in the 127.0.0.0/8 range, e.g. for RBL services'));
293 o.depends('rebind_protection', '1');
296 o = s.taboption('general', form.DynamicList, 'rebind_domain',
297 _('Domain whitelist'),
298 _('List of domains to allow RFC1918 responses for'));
301 o.depends('rebind_protection', '1');
302 o.placeholder = 'ihost.netflix.com';
303 o.validate = validateAddressList;
306 o = s.taboption('advanced', form.Value, 'port',
307 _('<abbr title="Domain Name System">DNS</abbr> server port'),
308 _('Listening port for inbound DNS queries'));
315 o = s.taboption('advanced', form.Value, 'queryport',
316 _('<abbr title="Domain Name System">DNS</abbr> query port'),
317 _('Fixed source port for outbound DNS queries'));
321 o.placeholder = _('any');
324 o = s.taboption('advanced', form.Value, 'dhcpleasemax',
325 _('<abbr title="maximal">Max.</abbr> <abbr title="Dynamic Host Configuration Protocol">DHCP</abbr> leases'),
326 _('Maximum allowed number of active DHCP leases'));
329 o.datatype = 'uinteger';
330 o.placeholder = _('unlimited');
333 o = s.taboption('advanced', form.Value, 'ednspacket_max',
334 _('<abbr title="maximal">Max.</abbr> <abbr title="Extension Mechanisms for Domain Name System">EDNS0</abbr> packet size'),
335 _('Maximum allowed size of EDNS.0 UDP packets'));
338 o.datatype = 'uinteger';
339 o.placeholder = 1280;
342 o = s.taboption('advanced', form.Value, 'dnsforwardmax',
343 _('<abbr title="maximal">Max.</abbr> concurrent queries'),
344 _('Maximum allowed number of concurrent DNS queries'));
347 o.datatype = 'uinteger';
350 o = s.taboption('advanced', form.Value, 'cachesize',
351 _('Size of DNS query cache'),
352 _('Number of cached DNS entries (max is 10000, 0 is no caching)'));
354 o.datatype = 'range(0,10000)';
357 s.taboption('tftp', form.Flag, 'enable_tftp',
358 _('Enable TFTP server')).optional = true;
360 o = s.taboption('tftp', form.Value, 'tftp_root',
361 _('TFTP server root'),
362 _('Root directory for files served via TFTP'));
365 o.depends('enable_tftp', '1');
369 o = s.taboption('tftp', form.Value, 'dhcp_boot',
370 _('Network boot image'),
371 _('Filename of the boot image advertised to clients'));
374 o.depends('enable_tftp', '1');
375 o.placeholder = 'pxelinux.0';
377 o = s.taboption('general', form.Flag, 'localservice',
378 _('Local Service Only'),
379 _('Limit DNS service to subnets interfaces on which we are serving DNS.'));
383 o = s.taboption('general', form.Flag, 'nonwildcard',
385 _('Bind dynamically to interfaces rather than wildcard address (recommended as linux default)'));
386 o.default = o.enabled;
390 o = s.taboption('general', form.DynamicList, 'interface',
391 _('Listen Interfaces'),
392 _('Limit listening to these interfaces, and loopback.'));
395 o = s.taboption('general', form.DynamicList, 'notinterface',
396 _('Exclude interfaces'),
397 _('Prevent listening on these interfaces.'));
400 o = s.taboption('leases', form.SectionValue, '__leases__', form.GridSection, 'host', null,
401 _('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 />' +
402 _('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.'));
409 so = ss.option(form.Value, 'name', _('Hostname'));
410 so.validate = validateHostname;
412 so.write = function(section, value) {
413 uci.set('dhcp', section, 'name', value);
414 uci.set('dhcp', section, 'dns', '1');
416 so.remove = function(section) {
417 uci.unset('dhcp', section, 'name');
418 uci.unset('dhcp', section, 'dns');
421 so = ss.option(form.Value, 'mac', _('<abbr title="Media Access Control">MAC</abbr>-Address'));
422 so.datatype = 'list(unique(macaddr))';
424 so.cfgvalue = function(section) {
425 var macs = uci.get('dhcp', section, 'mac'),
428 if (!Array.isArray(macs))
429 macs = (macs != null && macs != '') ? macs.split(/\ss+/) : [];
431 for (var i = 0, mac; (mac = macs[i]) != null; i++)
432 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))
433 result.push('%02X:%02X:%02X:%02X:%02X:%02X'.format(
434 parseInt(RegExp.$1, 16), parseInt(RegExp.$2, 16),
435 parseInt(RegExp.$3, 16), parseInt(RegExp.$4, 16),
436 parseInt(RegExp.$5, 16), parseInt(RegExp.$6, 16)));
438 return result.length ? result.join(' ') : null;
440 so.renderWidget = function(section_id, option_index, cfgvalue) {
441 var node = form.Value.prototype.renderWidget.apply(this, [section_id, option_index, cfgvalue]),
442 ipopt = this.section.children.filter(function(o) { return o.option == 'ip' })[0];
444 node.addEventListener('cbi-dropdown-change', L.bind(function(ipopt, section_id, ev) {
445 var mac = ev.detail.value.value;
446 if (mac == null || mac == '' || !hosts[mac] || !hosts[mac].ipv4)
449 var ip = ipopt.formvalue(section_id);
450 if (ip != null && ip != '')
453 var node = ipopt.map.findElement('id', ipopt.cbid(section_id));
455 dom.callClassMethod(node, 'setValue', hosts[mac].ipv4);
456 }, this, ipopt, section_id));
460 Object.keys(hosts).forEach(function(mac) {
461 var hint = hosts[mac].name || hosts[mac].ipv4;
462 so.value(mac, hint ? '%s (%s)'.format(mac, hint) : mac);
465 so = ss.option(form.Value, 'ip', _('<abbr title="Internet Protocol Version 4">IPv4</abbr>-Address'));
466 so.datatype = 'or(ip4addr,"ignore")';
467 so.validate = function(section, value) {
468 var mac = this.map.lookupOption('mac', section),
469 name = this.map.lookupOption('name', section),
470 m = mac ? mac[0].formvalue(section) : null,
471 n = name ? name[0].formvalue(section) : null;
473 if ((m == null || m == '') && (n == null || n == ''))
474 return _('One of hostname or mac address must be specified!');
478 Object.keys(hosts).forEach(function(mac) {
479 if (hosts[mac].ipv4) {
480 var hint = hosts[mac].name;
481 so.value(hosts[mac].ipv4, hint ? '%s (%s)'.format(hosts[mac].ipv4, hint) : hosts[mac].ipv4);
485 so = ss.option(form.Value, 'leasetime', _('Lease time'));
488 so = ss.option(form.Value, 'duid', _('<abbr title="The DHCP Unique Identifier">DUID</abbr>'));
489 so.datatype = 'and(rangelength(20,36),hexstring)';
490 Object.keys(duids).forEach(function(duid) {
491 so.value(duid, '%s (%s)'.format(duid, duids[duid].hostname || duids[duid].macaddr || duids[duid].ip6addr || '?'));
494 so = ss.option(form.Value, 'hostid', _('<abbr title="Internet Protocol Version 6">IPv6</abbr>-Suffix (hex)'));
496 o = s.taboption('leases', CBILeaseStatus, '__status__');
499 o = s.taboption('leases', CBILease6Status, '__status6__');
501 return m.render().then(function(mapEl) {
502 poll.add(function() {
503 return callDHCPLeases().then(function(leaseinfo) {
504 var leases = Array.isArray(leaseinfo.dhcp_leases) ? leaseinfo.dhcp_leases : [],
505 leases6 = Array.isArray(leaseinfo.dhcp6_leases) ? leaseinfo.dhcp6_leases : [];
507 cbi_update_table(mapEl.querySelector('#lease_status_table'),
508 leases.map(function(lease) {
511 if (lease.expires === false)
512 exp = E('em', _('unlimited'));
513 else if (lease.expires <= 0)
514 exp = E('em', _('expired'));
516 exp = '%t'.format(lease.expires);
519 lease.hostname || '?',
525 E('em', _('There are no active leases')));
528 cbi_update_table(mapEl.querySelector('#lease6_status_table'),
529 leases6.map(function(lease) {
532 if (lease.expires === false)
533 exp = E('em', _('unlimited'));
534 else if (lease.expires <= 0)
535 exp = E('em', _('expired'));
537 exp = '%t'.format(lease.expires);
539 var hint = lease.macaddr ? hosts[lease.macaddr] : null,
540 name = hint ? (hint.name || hint.ipv4 || hint.ipv6) : null,
543 if (name && lease.hostname && lease.hostname != name && lease.ip6addr != name)
544 host = '%s (%s)'.format(lease.hostname, name);
545 else if (lease.hostname)
546 host = lease.hostname;
552 lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
557 E('em', _('There are no active leases')));