mathewbishop / fiberglass

FiberGlass - ISC DHCP Server Interface
MIT License
7 stars 0 forks source link

(question) Is there a way to use this in a HA environment? #18

Closed yurividal closed 10 months ago

yurividal commented 1 year ago

Does this front-end support multiple ISC DHCP backends?

mathewbishop commented 1 year ago

Hey @yurividal, sorry for the late reply and that I missed this comment.

No, currently HA deployment is not supported. FiberGlass currently only supports running on a single server, using the flat files from ISC-DHCP as the "database".

Eventually we may try to work on supporting Kea DHCP's PostgreSQL option, which could allow for something akin to an HA deployment. But for now, it's just flat files on a single server.

wcarman1 commented 9 months ago

I know this is closed but here is a suggestion on the HA environment, at least as a short-term fix you could make the lease-parser.js skip the leases that are not true leases. Obviously, you still have to run an instance on each server so you can't get away from that but at least it cleans up the lease table page of all the "Undefined" entries. Not really a solution but as far as I can tell it works for me in a fail-over setup.

Here is updated lease-parser.js file:

/**
 * Created by cmiles on 8/9/2017.
 */

module.exports = {
  parse: function (input) {
    var lease_data = input.split('lease')
    for (i = 0; i < lease_data.length; i++) {
      ip_address = ''

      lines = lease_data[i].split('\n')
      for (l = 0; l < lines.length; l++) {
        /**
         * Trim whitespaces at each end of the line
         */
        lines[l] = lines[l].trim()

        /**
         * Break each newline into an array split into spaces
         * Ex: [ 'starts', '3', '2017/08/09', '04:50:53;' ]
         *
         * @type {string[]}
         */
        line_data_arg = lines[l].split(' ')

        if (
          /{/i.test(lines[l]) &&
          /\./i.test(lines[l]) &&
          !/uid/i.test(lines[l])
        ) {
          ip_address = line_data_arg[0].trim()
          if (typeof dhcp_lease_data[ip_address] === 'undefined') {
            dhcp_lease_data[ip_address] = {}
          }
          option_data = {}
        }
        if (ip_address !== '') {
          if (/start/i.test(lines[l])) {
            /*
             Make sure we force format as UTC because that is what the leases are formatted in
             */
            date =
              (line_data_arg[2] + ' ' + line_data_arg[3])
              .trim()
              .replace(/\//gi, '-')
              .replace(/;/i, '') + ' UTC'

            start_unix_time = Date.parse(date) / 1000
            dhcp_lease_data[ip_address].start = start_unix_time
          }
          if (/ends/i.test(lines[l])) {
            /*
             Make sure we force format as UTC because that is what the leases are formatted in
             */
            lease_end =
              (line_data_arg[2] + ' ' + line_data_arg[3])
              .trim()
              .replace(/\//gi, '-')
              .replace(/;/i, '') + ' UTC'

            now_unix_time = parseInt((new Date().getTime() / 1000).toFixed(0))
            end_unix_time = parseInt(
              (new Date(lease_end).getTime() / 1000).toFixed(0).toLocaleString()
            )

            /*
             console.log('now ' + now_unix_time);
             console.log('end ' + end_unix_time);

             console.log('now ' + new Date());
             console.log('end_raw ' + lease_end);
             console.log('end ' + new Date(lease_end));
             */

            if (end_unix_time <= now_unix_time) {
              delete dhcp_lease_data[ip_address]
              break
            }
            dhcp_lease_data[ip_address].end = end_unix_time
          }
          if (/ethernet/i.test(lines[l])) {
            if (typeof line_data_arg[2] !== 'undefined') {
              dhcp_lease_data[ip_address].mac = line_data_arg[2]
                .replace(/;/gi, '')
                .trim()

              if (
                dhcp_lease_data[ip_address].mac.split(':').join('').trim() ===
                ''
              )
                continue

              if (
                dhcp_lease_data[ip_address].mac
                .split(':')
                .join('')
                .toUpperCase()
                .trim() === ''
              )
                continue

              /* Mac OUI Lookup */
              var mac_oui = dhcp_lease_data[ip_address].mac
                .split(':')
                .join('')
                .toUpperCase()
                .slice(0, 6)

              dhcp_lease_data[ip_address].mac_oui_vendor = ''
              if (typeof oui_data[mac_oui] !== 'undefined') {
                dhcp_lease_data[ip_address].mac_oui_vendor = oui_data[mac_oui]
              }
            }
          }
          if (/hostname/i.test(lines[l])) {
            if (typeof line_data_arg[1] !== 'undefined')
              dhcp_lease_data[ip_address].host = line_data_arg[1]
                .replace(/;/gi, '')
                .replace(/"/gi, '')
                .trim()
          }
          if (/set/i.test(lines[l])) {
            set_data = lines[l]
              .replace(/;/gi, '')
              .replace(/"/gi, '')
              .replace(/ = /gi, ' ')
              .replace(/set/gi, '')
              .trim()
            set_data_split = set_data.split(' ')

            option_key = set_data_split[0].trim()
            option_value = set_data.replace(RegExp(option_key, 'g'), '').trim()

            option_data[option_key] = option_value

            if (typeof dhcp_lease_data[ip_address]['options'] === 'undefined')
              dhcp_lease_data[ip_address]['options'] = []
          }
          if (/binding state (backup|free);/i.test(lines[l])) {
            // Skip the lease if it contains "binding state backup;" or "binding state free;"
            delete dhcp_lease_data[ip_address];
            break;
          }
          if (/option/i.test(lines[l])) {
            set_data = lines[l]
              .replace(/;/gi, '')
              .replace(/"/gi, '')
              .replace(/ = /gi, ' ')
              .replace(/option/gi, '')
              .trim()
            set_data_split = set_data.split(' ')

            option_key = set_data_split[0].trim()
            option_value = set_data.replace(RegExp(option_key, 'g'), '').trim()

            option_data[option_key] = option_value

            if (typeof dhcp_lease_data[ip_address]['options'] === 'undefined')
              dhcp_lease_data[ip_address]['options'] = []
          }
          if (
            lines[l].charAt(0) === '}' &&
            typeof dhcp_lease_data[ip_address]['options'] !== 'undefined'
          ) {
            if (typeof option_data !== 'undefined') {
              dhcp_lease_data[ip_address]['options'] = option_data
            }

            option_data = []
          }
          /* End of Lease */                            
          if (lines[l].charAt(0) === '}') {
            if (debug_watch_lease_parse_stream) {
              console.log('[Glass Server] Lease Parse')
              console.log(JSON.stringify(dhcp_lease_data[ip_address], null, 2))
            }
          }
        }
      }
    }
    return
  },
  parseV6: function (input) {
    let leaseFileEntries = input.split('\n\n')
    leaseFileEntries.forEach((entry) => {
      // not every entry is guaranteed to be a lease block
      // some entries are a line that starts with "server-duid"
      const leaseBlock = entry.match(/iaaddr (?<v6_address>[^ ]+) {([\s\S]*?)}/)
      if (leaseBlock) {
        const v6_address = leaseBlock.groups.v6_address
        // the addr is the key in each lease data object
        // {
        //   "2600::0::0(you get the point)": {
        //     "start": "<date>",
        //     "end": "<date>"
        //   }
        // }
        v6_dhcp_lease_data[v6_address] = {}
        const leaseInfo = leaseBlock[0]

        const endMatch = leaseInfo.match(
          /ends (?<end_time>\d+ \d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2});/
        )
        if (endMatch) {
          const endDate = endMatch.groups.end_time
            .split(' ')[1]
            .trim()
            .split('/')
            .join('-')
          const endTime = endMatch.groups.end_time.split(' ')[2].trim()
          const endDateTime = `${endDate} ${endTime} UTC`
          const endUnixTime = Date.parse(endDateTime) / 1000
          v6_dhcp_lease_data[v6_address] = {
            end: endUnixTime,
          }
        }
      }
    })
    return
  },
  clean: function () {
    for (var key in dhcp_lease_data) {
      now_unix_time = parseInt((new Date().getTime() / 1000).toFixed(0))
      end_unix_time = dhcp_lease_data[key].end

      if (now_unix_time >= end_unix_time) {
        console.log(
          '[DHCP Lease Data] Lease ' + key + ' has expired - clearing'
        )
        delete dhcp_lease_data[key]
      }
    }
  },
}

or just add the following to line 139 if (/binding state (backup|free);/i.test(lines[l])) { // Skip the lease if it contains "binding state backup;" or "binding state free;" delete dhcp_lease_data[ip_address]; break; }

This probably isn't the end all but it will make having an HA setup a little cleaner at least in the meantime.