<commit_before>/*    Copyright 2016 - 2022 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);

const Sensor = require('./Sensor.js').Sensor;

const extensionManager = require('./ExtensionManager.js')
const f = require('../net2/Firewalla.js');
const VPNClient = require('../extension/vpnclient/VPNClient.js');
const _ = require('lodash');
const fs = require('fs');
const Promise = require('bluebird');
Promise.promisifyAll(fs);
const MESH_PROFILE_ID = "FW_MESH";
const pclient = require('../util/redis_manager.js').getPublishClient();
const Message = require('../net2/Message.js');
const sem = require('../sensor/SensorEventManager.js').getInstance();


const featureName = "mesh_vpn";

class MeshVPNPlugin extends Sensor {
  async run() {
    this.featureSwitch = false;
    this.localConfig = {};
    this.remoteConfig = {};
    this.hookFeature(featureName);

    if (f.isMain()) {
      sem.on("MESH_LOCAL_CONFIG_UPDATE", (event) => {
        const oldConfig = event.oldConfig;
        this.applyMeshVPN(oldConfig).catch((err) => {
          log.error(`Failed to apply mesh VPN`, err.message);
        });
      });
    }
  }

  async apiRun() {
    this.localConfig = {};
    this.remoteConfig = {};
    extensionManager.onCmd("mesh:fetchRemoteConfig", async (msg, data) => {
      await this.loadLocalConfig();
      if (this.localConfig.id && this.localConfig.instanceURL) {
        const configId = this.localConfig.id;
        const newConfig = await this.fetchRemoteConfig(this.localConfig.instanceURL, configId).catch((err) => {
          log.error(`Failed to fetch remote config of ${configId}`);
          return null;
        });
        if (!newConfig)
          return;
        await this.loadRemoteConfig(configId);
        const oldConfig = this.remoteConfig[configId];
        this.remoteConfig[configId] = newConfig;
        await this.saveRemoteConfig(configId);
        if (!_.isEqual(oldConfig, newConfig)) {
          log.info(`Mesh VPN remote config of ${configId} is changed, will generate new settings ...`);
          await this.generateVPNSettings();
          if (_.isEqual(oldConfig.cert, newConfig.cert)) // only need to update routes if cert is not changed
            await pclient.publishAsync(Message.MSG_NEBULA_VPN_ROUTE_UPDATE, MESH_PROFILE_ID);
          else // need to restart if cert is changed
            sem.sendEventToFireMain({
              type: "MESH_LOCAL_CONFIG_UPDATE",
              oldConfig: this.localConfig
            })
        }
      } else {
        log.error(`id is not defined in local config`)
      }
    });

    extensionManager.onCmd("mesh:updateLocalConfig", async (msg, data) => {
      const newConfig = {
        state: data.state || false,
        type: data.type || "nebula",
        id: data.id,
        instanceURL: data.instanceURL,
        auth: data.auth
      };
      await this.loadLocalConfig();
      const oldConfig = this.localConfig;
      this.localConfig = newConfig;
      await this.saveLocalConfig();
      if (!_.isEqual(oldConfig, newConfig)) {
        // firemain will maintain the VPN connection
        sem.sendEventToFireMain({
          type: "MESH_LOCAL_CONFIG_UPDATE",
          oldConfig: oldConfig
        });
      }
    });
  }

  async job() {
    await this.applyMeshVPN().catch((err) => {
      log.error(`Failed to apply mesh VPN`, err.message);
    });
  }

  async applyMeshVPN(oldLocalConfig = null) {
    if (oldLocalConfig) {
      // stop old vpn client according to old config if present
      if (oldLocalConfig.state) {
        const vpnType = oldLocalConfig.type;
        const c = VPNClient.getClass(vpnType);
        if (!c) {
          log.error(`Mesh VPN type ${vpnType} is not supported in old config`);
        } else {
          log.info(`Stop old ${vpnType} mesh VPN`);
          const vpnClient = new c({profileId: MESH_PROFILE_ID});
          await vpnClient.stop().catch((err) => {
            log.error(`Failed to stop ${vpnType} mesh VPN in old config`, err.message);
          });
        }
      }
    }
    await this.loadLocalConfig();
    const instanceURL = this.localConfig && this.localConfig.instanceURL;
    const id = this.localConfig && this.localConfig.id;
    if (!instanceURL || !id)
      return;<commit_msg>add interval task in MeshVPNPlugin.js to sync remote config<commit_after>/*    Copyright 2016 - 2022 Firewalla Inc
 *
 *    This program is free software: you can redistribute it and/or  modify
 *    it under the terms of the GNU Affero General Public License, version 3,
 *    as published by the Free Software Foundation.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
'use strict';

const log = require('../net2/logger.js')(__filename);

const Sensor = require('./Sensor.js').Sensor;

const extensionManager = require('./ExtensionManager.js')
const f = require('../net2/Firewalla.js');
const VPNClient = require('../extension/vpnclient/VPNClient.js');
const _ = require('lodash');
const fs = require('fs');
const Promise = require('bluebird');
Promise.promisifyAll(fs);
const MESH_PROFILE_ID = "FW_MESH";
const pclient = require('../util/redis_manager.js').getPublishClient();
const Message = require('../net2/Message.js');
const sem = require('../sensor/SensorEventManager.js').getInstance();


const featureName = "mesh_vpn";

class MeshVPNPlugin extends Sensor {
  async run() {
    this.featureSwitch = false;
    this.localConfig = {};
    this.remoteConfig = {};
    this.hookFeature(featureName);

    if (f.isMain()) {
      sem.on("MESH_LOCAL_CONFIG_UPDATE", (event) => {
        const oldConfig = event.oldConfig;
        this.applyMeshVPN(oldConfig).catch((err) => {
          log.error(`Failed to apply mesh VPN`, err.message);
        });
      });

      setInterval(async () => {
        this.syncAndApplyRemoteConfig().catch((err) => {
          log.error(`Failed to sync and apply remote config`, err.message);
        }); // sync remote config once every two hours
      }, 7200 * 1000);
    }
  }

  async apiRun() {
    this.localConfig = {};
    this.remoteConfig = {};
    extensionManager.onCmd("mesh:fetchRemoteConfig", async (msg, data) => {
      await this.syncAndApplyRemoteConfig();
    });

    extensionManager.onCmd("mesh:updateLocalConfig", async (msg, data) => {
      const newConfig = {
        state: data.state || false,
        type: data.type || "nebula",
        id: data.id,
        instanceURL: data.instanceURL,
        auth: data.auth
      };
      await this.loadLocalConfig();
      const oldConfig = this.localConfig;
      this.localConfig = newConfig;
      await this.saveLocalConfig();
      if (!_.isEqual(oldConfig, newConfig)) {
        // firemain will maintain the VPN connection
        sem.sendEventToFireMain({
          type: "MESH_LOCAL_CONFIG_UPDATE",
          oldConfig: oldConfig
        });
      }
    });
  }

  async job() {
    await this.applyMeshVPN().catch((err) => {
      log.error(`Failed to apply mesh VPN`, err.message);
    });
  }

  async syncAndApplyRemoteConfig() {
    await this.loadLocalConfig();
    if (this.localConfig.id && this.localConfig.instanceURL) {
      const configId = this.localConfig.id;
      const newConfig = await this.fetchRemoteConfig(this.localConfig.instanceURL, configId).catch((err) => {
        log.error(`Failed to fetch remote config of ${configId}`);
        return null;
      });
      if (!newConfig)
        return;
      await this.loadRemoteConfig(configId);
      const oldConfig = this.remoteConfig[configId];
      this.remoteConfig[configId] = newConfig;
      await this.saveRemoteConfig(configId);
      if (!_.isEqual(oldConfig, newConfig)) {
        log.info(`Mesh VPN remote config of ${configId} is changed, will generate new settings ...`);
        await this.generateVPNSettings();
        if (_.isEqual(oldConfig.cert, newConfig.cert)) // only need to update routes if cert is not changed
          await pclient.publishAsync(Message.MSG_NEBULA_VPN_ROUTE_UPDATE, MESH_PROFILE_ID);
        else // need to restart if cert is changed
          sem.sendEventToFireMain({
            type: "MESH_LOCAL_CONFIG_UPDATE",
            oldConfig: this.localConfig
          })
      } else {
        log.info("Mesh VPN remote config is not changed, no need to do anything");
      }
    } else {
      log.error(`id is not defined in local config`)
    }
  }

  async applyMeshVPN(oldLocalConfig = null) {
    if (oldLocalConfig) {
      // stop old vpn client according to old config if present
      if (oldLocalConfig.state) {
        const vpnType = oldLocalConfig.type;
        const c = VPNClient.getClass(vpnType);
        if (!c) {
          log.error(`Mesh VPN type ${vpnType} is not supported in old config`);
        } else {
          log.info(`Stop old ${vpnType} mesh VPN`);
          const vpnClient = new c({profileId: MESH_PROFILE_ID});
          await vpnClient.stop().catch((err) => {
            log.error(`Failed to stop ${vpnType} mesh VPN in old config`, err.message);
          });
        }
      }
    }
    await this.loadLocalConfig();
    const instanceURL = this.localConfig && this.localConfig.instanceURL;
    const id = this.localConfig && this.localConfig.id;
    if (!instanceURL || !id)
      return;
{'fraction_non_alphanumeric': 0.12085238882942492, 'fraction_numerical': 0.0031137491485842173, 'mean_word_length': 2.7252627763682495, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7284817', 'n_tokens_mistral': 2982, 'n_tokens_neox': 2957, 'n_words': 807}