Modern Out-of-Band Management for Network ResilienceSecure access · high-density console management · ZERO cloud visibility

SMS Based Cellular Connectivity Control

This article will provide a configuration for the the NR4400/Duckfone to disable the cellular interface based on incoming text messages via SMS.

Start with the main WebUI screen. Select “Automation” from the left menu.

Automation

Select “Add Automation” and give the new automation a name, “SmsHandler”, then select “Add Automation”:

Add Automation

Click on the new “SmsHandler” automation to edit its details:

SMSHandler Automation

Enter a label for the automation, “SMS Handler”. Delete the example script and paste the script from below into the “script” input box, then “Save configuration”.

Automation save

The NR4400/Duckfone will now respond to “Cellup” and “Celldown” commands via SMS. It is advisable to add a unique code to the “Cellup/Celldown” commands to make them unique to your organisation, for example “123Cellup”.

This can be done by changing the strings in the first few lines of the script. Similarly, SNMP can be enabled by entering an IP address into the “snmp_host” setting and checking the community and OID settings. Once filled, this script will send SNMP traps to the configured host using the supplied OIDs.

Script code to copy/paste:

// Check for incoming SMS messages and take actions
// Customize these values as needed
const whitelist = [];
//const whilelist = ['whole-or-part-number', '555123789', '+1555666777']

const cellular_up = "Cellup";
const cellular_down = "Celldown";
const cellular_path = "/network/cellular/connection/0/enable";

// SNMP SMS trap setup, non-empty values enable
const snmp_host = ""; // IP address of SNMP server
const snmp_community = "public";
const snmp_trap_oid = "1.3.6.1.4.1.9999.1";
const snmp_variable_oid = "1.3.6.1.4.1.9999.1.1"

// Customize below here at your own risk :-)

function parseSMS(rawData) {
    const pattern = /\+CMGL: \d+,"[^"]+","([^"]+)",(?:[^,]*),"[^"]*"\r?\n([\s\S]*?)(?=\r?\n\+CMGL:|\r?\n\r?\nOK|$)/g;

    let messages = [];
    let match;

    while ((match = pattern.exec(rawData)) !== null) {
        messages.push({
            phone: match[1],
            message: match[2].trim() // Remove trailing CR/LF from message body
        });
    }

    return messages;
}

// force shell access on Duckfone for snmptrap/logging/atcmd
try {
    const junk = await dry.capture(['plugin-hardware-ssh', 'cli', 'config', 'system', 'advanced', 'shell', 'true']);
} catch {};

while (true) {
    let result;
    try {
        result = await dry.capture(['plugin-hardware-ssh', 'atcmd', 'at', `'at+cmgf=1\;+cmgl=\"ALL\"\;+cmgd=1,4'`]);
    } catch {};

    const messages = parseSMS(result);

    for (const m of messages) {
        if (snmp_host.length > 0) {
            try {
                dry.run(["snmptrap", "-v", "2c", "-m", "", "-c", snmp_community, snmp_host, "", snmp_trap_oid, snmp_variable_oid, "s", `SMS from ${m.phone} is '${m.message}'`]);
            } catch {};
        }

        if (whitelist.length <= 0 || whitelist.some(str => str.includes(m.phone))) {
            if (m.message.includes(cellular_up)) {
                await dry.run(['logger', '-t', 'cellular', `CELL UP from ${m.phone}`]);
                try { await dry.run(['config', 'set', cellular_path, 'true']); } catch {};
                continue;
            } else if (m.message.includes(cellular_down)) {
                await dry.run(['logger', '-t', 'cellular', `CELL DOWN from ${m.phone}`]);
                try { await dry.run(['config', 'set', cellular_path, 'false']); } catch {};
                continue;
            }
        }
        await dry.run(['logger', '-t', 'cellular', `SMS from ${m.phone} is '${m.message}'.`]);
    }

    await dry.sleep(20);
}