Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Customizing Client Hostnames in OpenWrt DHCP Interface

Tech May 17 2

OpenWrt's default DHCP list doesn't always display client hostnames properly, particularly for iOS devices. This can be inconvenient, so let's add a custom hostname column that alows users to set and modify aliases for connected devices.

The enhancement includes:

  • A new "Customized Hostname" column showing the custom hostname (alias)
  • A "Set Alias" button to input custom hostnames

To implement this, modify the file feeds/luci/modules/luci-mod-status/htdocs/luci-static/resources/view/status/include/40_dhcp.js with the following changes:

// Add this method to handle setting custom hostnames
handleSetAlias: function(lease, alias, ev) {
    ev.currentTarget.classList.add('spinning');
    ev.currentTarget.disabled = true;
    ev.currentTarget.blur();

    var sections = lease.macaddr.toUpperCase().replace(/:/g, '');
    if (!uci.get('client', sections, 'alias'))
        uci.add('client', 'client', sections);

    var say = 'Enter a new alias for client(%s)'.format(lease.macaddr.toUpperCase());
    var new_alias = prompt(say, alias);
    uci.set('client', sections, 'alias', new_alias || alias);

    return uci.save()
            .then(L.bind(L.ui.changes.init, L.ui.changes))
            .then(L.bind(L.ui.changes.displayChanges, L.ui.changes));
},

// Add this column header in the table
E('th', { 'class': 'th' }, _('Customized Hostname')),

// Add this code to retrieve and display the custom hostname
var alias = null;
if (lease.macaddr) {
    var mac = lease.macaddr.toUpperCase().replace(/:/g, '');
    alias = uci.get('client', mac, 'alias');
}

if (!alias)
    alias = 'Network Device';

// Add the alias to the table row
alias,

// Add the Set Alias button
rows.push(E('button', {
    'class': 'cbi-button cbi-button-apply',
    'click': L.bind(this.handleSetAlias, this, lease, alias)
}, [ _('Set Alias') ]));

Here's the complete modified source code:

'use strict';
'require baseclass';
'require rpc';
'require uci';
'require network';
'require validation';

var callLuciDHCPLeases = rpc.declare({
        object: 'luci-rpc',
        method: 'getDHCPLeases',
        expect: { '': {} }
});

return baseclass.extend({
        title: '',

        isMACStatic: {},
        isDUIDStatic: {},

        load: function() {
                return Promise.all([
                        callLuciDHCPLeases(),
                        network.getHostHints(),
            L.resolveDefault(uci.load('client')),
                        L.resolveDefault(uci.load('dhcp'))
                ]);
        },

    handleSetAlias: function(lease, alias, ev) {
                ev.currentTarget.classList.add('spinning');
                ev.currentTarget.disabled = true;
                ev.currentTarget.blur();

        var sections = lease.macaddr.toUpperCase().replace(/:/g, '');
        if (!uci.get('client', sections, 'alias'))
            uci.add('client', 'client', sections);

        var say = 'Enter a new alias for client(%s)'.format(lease.macaddr.toUpperCase());
        var new_alias = prompt(say, alias);
                uci.set('client', sections, 'alias', new_alias || alias);

                return uci.save()
                        .then(L.bind(L.ui.changes.init, L.ui.changes))
                        .then(L.bind(L.ui.changes.displayChanges, L.ui.changes));
        },

        handleCreateStaticLease: function(lease, ev) {
                ev.currentTarget.classList.add('spinning');
                ev.currentTarget.disabled = true;
                ev.currentTarget.blur();

                var cfg = uci.add('dhcp', 'host');
                uci.set('dhcp', cfg, 'name', lease.hostname);
                uci.set('dhcp', cfg, 'ip', lease.ipaddr);
                uci.set('dhcp', cfg, 'mac', lease.macaddr.toUpperCase());

                return uci.save()
                        .then(L.bind(L.ui.changes.init, L.ui.changes))
                        .then(L.bind(L.ui.changes.displayChanges, L.ui.changes));
        },

        handleCreateStaticLease6: function(lease, ev) {
                ev.currentTarget.classList.add('spinning');
                ev.currentTarget.disabled = true;
                ev.currentTarget.blur();

                var cfg = uci.add('dhcp', 'host'),
                    ip6arr = lease.ip6addrs[0] ? validation.parseIPv6(lease.ip6addrs[0]) : null;

                uci.set('dhcp', cfg, 'name', lease.hostname);
                uci.set('dhcp', cfg, 'duid', lease.duid.toUpperCase());
                uci.set('dhcp', cfg, 'mac', lease.macaddr);
                if (ip6arr)
                        uci.set('dhcp', cfg, 'hostid', (ip6arr[6] * 0xFFFF + ip6arr[7]).toString(16));

                return uci.save()
                        .then(L.bind(L.ui.changes.init, L.ui.changes))
                        .then(L.bind(L.ui.changes.displayChanges, L.ui.changes));
        },

        renderLeases: function(data) {
                var leases = Array.isArray(data[0].dhcp_leases) ? data[0].dhcp_leases : [],
                    leases6 = Array.isArray(data[0].dhcp6_leases) ? data[0].dhcp6_leases : [],
                    machints = data[1].getMACHints(false),
                    hosts = uci.sections('dhcp', 'host'),
                    isReadonlyView = !L.hasViewPermission();

                for (var i = 0; i < hosts.length; i++) {
                        var host = hosts[i];

                        if (host.mac) {
                                var macs = L.toArray(host.mac);
                                for (var j = 0; j < macs.length; j++) {
                                        var mac = macs[j].toUpperCase();
                                        this.isMACStatic[mac] = true;
                                }
                        }
                        if (host.duid) {
                                var duid = host.duid.toUpperCase();
                                this.isDUIDStatic[duid] = true;
                        }
                };

                var table = E('table', { 'class': 'table lases' }, [
                        E('tr', { 'class': 'tr table-titles' }, [
                                E('th', { 'class': 'th' }, _('Hostname')),
                                E('th', { 'class': 'th' }, _('Customized Hostname')),
                                E('th', { 'class': 'th' }, _('IPv4 address')),
                                E('th', { 'class': 'th' }, _('MAC address')),
                                E('th', { 'class': 'th' }, _('Lease time remaining')),
                                E('th', { 'class': 'th cbi-section-actions' }, _('Static Alias')),
                                isReadonlyView ? E([]) : E('th', { 'class': 'th cbi-section-actions' }, _('Static Lease'))
                        ])
                ]);

                cbi_update_table(table, leases.map(L.bind(function(lease) {
                        var exp, rows;

                        if (lease.expires === false)
                                exp = E('em', _('unlimited'));
                        else if (lease.expires <= 0)
                                exp = E('em', _('expired'));
                        else
                                exp = '%t'.format(lease.expires);

                        var hint = lease.macaddr ? machints.filter(function(h) { return h[0] == lease.macaddr })[0] : null,
                            host = null;

                        if (hint && lease.hostname && lease.hostname != hint[1])
                                host = '%s (%s)'.format(lease.hostname, hint[1]);
                        else if (lease.hostname)
                                host = lease.hostname;

            var alias = null;
            if (lease.macaddr)
                var mac = lease.macaddr.toUpperCase().replace(/:/g, '');
                alias = uci.get('client', mac, 'alias');

            if (!alias)
                alias = 'Network Device';

                        rows = [
                                host || '-',
                alias,
                                lease.ipaddr,
                                lease.macaddr,
                                exp
                        ];

            rows.push(E('button', {
                'class': 'cbi-button cbi-button-apply',
                'click': L.bind(this.handleSetAlias, this, lease, alias)
            }, [ _('Set Alias') ]));

                        if (!isReadonlyView && lease.macaddr != null) {
                                var mac = lease.macaddr.toUpperCase();
                                rows.push(E('button', {
                                        'class': 'cbi-button cbi-button-apply',
                                        'click': L.bind(this.handleCreateStaticLease, this, lease),
                                        'disabled': this.isMACStatic[mac]
                                }, [ _('Set Static') ]));
                        }

                        return rows;
                }, this)), E('em', _('There are no active leases')));

                var table6 = E('table', { 'class': 'table leases6' }, [
                        E('tr', { 'class': 'tr table-titles' }, [
                                E('th', { 'class': 'th' }, _('Host')),
                                E('th', { 'class': 'th' }, _('IPv6 address')),
                                E('th', { 'class': 'th' }, _('DUID')),
                                E('th', { 'class': 'th' }, _('Lease time remaining')),
                                isReadonlyView ? E([]) : E('th', { 'class': 'th cbi-section-actions' }, _('Static Lease'))
                        ])
                ]);

                cbi_update_table(table6, leases6.map(L.bind(function(lease) {
                        var exp, rows;

                        if (lease.expires === false)
                                exp = E('em', _('unlimited'));
                        else if (lease.expires <= 0)
                                exp = E('em', _('expired'));
                        else
                                exp = '%t'.format(lease.expires);

                        var hint = lease.macaddr ? machints.filter(function(h) { return h[0] == lease.macaddr })[0] : null,
                            host = null;

                        if (hint && lease.hostname && lease.hostname != hint[1] && lease.ip6addr != hint[1])
                                host = '%s (%s)'.format(lease.hostname, hint[1]);
                        else if (lease.hostname)
                                host = lease.hostname;
                        else if (hint)
                                host = hint[1];

                        rows = [
                                host || '-',
                                lease.ip6addrs ? lease.ip6addrs.join(' ') : lease.ip6addr,
                                lease.duid,
                                exp
                        ];

                        if (!isReadonlyView && lease.duid != null) {
                                var duid = lease.duid.toUpperCase();
                                rows.push(E('button', {
                                        'class': 'cbi-button cbi-button-apply',
                                        'click': L.bind(this.handleCreateStaticLease6, this, lease),
                                        'disabled': this.isDUIDStatic[duid]
                                }, [ _('Set Static') ]));
                        }

                        return rows;
                }, this)), E('em', _('There are no active leases')));

                return E([
                        E('h3', _('Active DHCP Leases')),
                        table,
                        E('h3', _('Active DHCPv6 Leases')),
                        table6
                ]);
        },

        render: function(data) {
                if (L.hasSystemFeature('dnsmasq') || L.hasSystemFeature('odhcpd'))
                        return this.renderLeases(data);

                return E([]);
        }
});

Replace the file in your OpenWrt system at /www/luci-static/resources/view/status/include/40_dhcp.js.

Create the configuration file:

touch /etc/config/client

Clear the LuCI cache:

rm -rf /tmp/luci-indexcache*

Refresh your browser to see the changes. Note that browser caching might affect the display, so try using a different browser or incognito mode if the changes don't appear immediately.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.