Example of a Provisioning Flow - genieacs/genieacs GitHub Wiki
Thanks to Dan for the scripts. This is how he implemented the flow. He posted it on the mailing list on 6th September 2017.
This script checks for a "Provisioned" tag on the CPE, if the CPE has the Provisioned tag, then the script returns. At the end of the script, the Provisioned tag is set. This script is kicked off by a preset which has the following preconditions: Tags != Provisioned, Tags == Routed. Our CPEs can be in one of two states, Routed or Bridged.
Data is returned to the provisioning script from an external script (line 18). The external script interfaces with our subscriber management system which returns the username and password for the given CPE, as well as Wi-Fi and DHCP settings (we don't allow customers access to the CPE and require them to do all configuration through our portal which stores the Wi-Fi and DHCP settings (with the passphrase stored encrypted).
Provisioning script
const now = Date.now();
let provisioned = declare("Tags.Provisioned", {value: 1});
if (provisioned.value !== undefined) {
log('CPE is (allegedly) provisioned, returning');
return;
}
let model = declare("InternetGatewayDevice.DeviceInfo.ModelName", {value: 1}).value[0];
let serialNumber = declare("DeviceID.SerialNumber", {value: 1}).value[0];
let productClass = declare("DeviceID.ProductClass", {value: 1}).value[0];
let oui = declare("DeviceID.OUI", {value: 1}).value[0];
let args = {serial: serialNumber, productClass: productClass, oui: oui};
//Get the PPPoE creds
let config = ext('cpe-config', 'resetPppoe', JSON.stringify(args));
if (!config) {
log('No config returned from API');
return;
}
//Disable the WANIPConnection instance on the ATM interface
log('Disabling WANIPConnection');
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANIPConnection.*.Enable", {path: now, value: now}, {value: false});
refreshWlan();
setupBaseWanPppConnection();
setAccountSpecificSettings(config);
setConnectionServicesAndDns();
//Refresh the mac and external ip
declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.1.WANPPPConnection.*.MACAddress", {value: now});
updateTags(config);
bouncePppoeConnection();
return; //Not explicitly needed, but I want to prevent any extranious code at the bottom from executing...
function updateTags(config) {
if (config.tags) {
if (config.tags.add && config.tags.add.length) {
log('Adding tags: ' + config.tags.add.join(', '));
for (let [index, tag] of Object.entries(config.tags.add)) {
log('Tag: ' + tag);
declare("Tags." + tag, null, {value: true});
}
}
if (config.tags.remove && config.tags.remove.length) {
log('Removing tags: ' + config.tags.remove.join(', '));
for (let [index, tag] of Object.entries(config.tags.remove)) {
log('Tag: ' + tag);
declare("Tags." + tag, null, {value: false});
}
}
}
log('Done configuring. Setting provisioned tag');
declare("Tags.Provisioned", null, {value: true});
}
function refreshWlan() {
//Refresh the WLAN config
log('Refreshing WLAN');
declare("InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.*", {path: now});
declare("InternetGatewayDevice.LANDevice.*.WLANConfiguration.*.SSID", {value: now});
declare("InternetGatewayDevice.LANDevice.1.WLANConfiguration.*.Enable", {value: now}, {value: true});
declare("InternetGatewayDevice.LANDevice.1.WLANConfiguration.*.X_SMARTRG_COM_BaseMacAsDefaultPassPhrase", {value: now}, {value: false});
}
function setupBaseWanPppConnection() {
//Ensure we have a WANPPPConnection instance
log('Creating WANPPPConnection (if necessary)');
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*", null, {path: 1});
log('Setting up WANPPPConnection');
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.*", {path: now}); //Refresh the node...
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.Name", {value: now}, {value: "Internet"});
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.ConnectionType", {value: now}, {value: "IP_Routed"});
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.X_BROADCOM_COM_IfName", {value: now}, {value: "ppp0.1"});
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.NATEnabled", {value: now}, {value: true});
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.X_BROADCOM_COM_FirewallEnabled", {value: now}, {value: true});
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.Enable", {value: now}, {value: true});
declare("InternetGatewayDevice.WANDevice.1.WANConnectionDevice.1.WANPPPConnection.*.PPPoEServiceName", {value: now}, {value: "broadband"});
}
function setAccountSpecificSettings(config) {
//{value: now} forces GenieACS to update the value of the username/password if the value hasn't been updated before now
log('Setting un: ' + config.username + ', pw: ' + config.password);
declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANPPPConnection.*.Username", {value: now}, {value: config.username});
declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.*.WANPPPConnection.*.Password", {value: now}, {value: config.password});
//Refresh the vParams
declare("VirtualParameters.pppoeUsername", {value: now});
if (config.settings) {
log('Setting wifi/dhcp config');
for (let [key, value] of Object.entries(config.settings)) {
log('KVP', {key: key, value: value });
declare(key, {value: now}, {value: value});
}
}
}
function setConnectionServicesAndDns() {
log('Setting connection services');
let hasWanPort = declare("Tags.WanPort", {value: 1}).value !== undefined;
let connServices = 'ppp0.1';
if (hasWanPort) {
connServices = 'ppp0.1,ppp1.1';
}
declare("InternetGatewayDevice.Layer3Forwarding.*", {value: now});
declare("InternetGatewayDevice.Layer3Forwarding.X_BROADCOM_COM_DefaultConnectionServices", {value: now}, {value: connServices});
declare("InternetGatewayDevice.X_BROADCOM_COM_NetworkConfig.DNSIfName", {value: now}, {value: connServices});
}
function bouncePppoeConnection() {
//Bounce the PPPoE connection
switch (model) {
case 'SR515ac':
log('Rebooting, because the CPE is dumb', {model: model});
declare("Reboot", null, {value: Date.now()});
break;
case 'SR510N':
default:
log('Bouncing the WANPPPConnection instances');
declare("InternetGatewayDevice.WANDevice.*.WANConnectionDevice.1.WANPPPConnection.*.Reset", {value: now}, {value: true});
}
}
External script
//genieacs/config/ext/cpe-config.js
const API_URL = process.env.API_URL || 'https://your-host.com/api';
const url = require("url");
const http = require(API_URL.split(":", 1)[0]);
function resetPppoe(args, callback) {
let params = JSON.parse(args[0]);
const uri = API_URL + "/ResetPPPoECreds?serial=" + params.serial + '&productClass=' + params.productClass + '&oui=' + params.oui;
console.log({ uri: uri, serial: params.serial });
let options = url.parse(uri);
options.headers = {
accept: 'application/json',
"content-type": 'application/json'
};
let request = http.get(options, function (response) {
if (response.statusCode == 404) {
return callback(null, null);
}
if (response.statusCode >= 400) {
return callback(new Error("Unexpected error resetting PPPoE credentials. Response Code: " +
response.statusCode + '. Status Message: ' + response.statusMessage + '. t: ' + typeof response.statusCode));
}
let data = "";
response.on("data", function (d) {
data = data + d.toString();
});
response.on("end", function () {
let result = JSON.parse(data);
console.log('Returning credentials to client', result);
return callback(null, result);
});
});
request.on("error", function (err) {
console.log('args');
console.log(arguments);
callback(err);
});
}
exports.resetPppoe = resetPppoe;