This is part of the Router Log Monitor. And this part is responsible for reading & parsing log files, and writing the parsed entries to database tables.
The logging provided by the Netgear N6400 is not the best, or the worst. And I'm sure that when the original engineer designed it they had no intent of it being parsed.
Here is a sample of some typical log entries:
[DHCP IP: (10.100.0.3)] to MAC address BE:EF:D0:0D:00:12, Friday, May 07,2021 00:21:49
[LAN access from remote] from 11.22.33.123:40216 to 10.100.0.100:59018, Friday, May 07,2021 00:37:22
[Dynamic DNS] host name someserver.com registration successful, Friday, May 07,2021 16:42:41
[Internet connected] IP address: 1.22.33.123, Friday, May 07,2021 17:04:30
[Time synchronized with NTP server] Friday, May 07,2021 17:04:31
[WLAN access rejected: incorrect security] from MAC 0D:DB:EE:F0:F7:52, Saturday, May 08,2021 08:40:17
[Admin login] from source 10.100.0.3, Tuesday, Apr 27,2021 04:12:23
[DoS attack: FIN Scan] (1) attack packets in last 20 sec from ip [151.101.186.133], Monday, Apr 26,2021 11:20:24
[DoS attack: STORM] (1) attack packets in last 20 sec from ip [68.86.184.146], Wednesday, Jan 01,2020 00:07:27
From those log entries it's possible to extract the pieces needed for the database. I wanted to be sure that there was sufficient details so that meaningful queries could be ran on the data.
Here are the parts I needed to parse from a log entry:
- Time stamp of the entry
- Which type action is the entry describing?
- Other pieces of information that describe the action
First I needed to categorize the types of actions that the router could take:
Description | Category Code |
---|---|
Network Access | NA |
Router Actions | RA |
Router Invasion | RI |
Router LAN Activity | RL |
Router Updates | RU |
Router Protection | RP |
(Table 1 - Action Categories)
NOTE: The category Router Protection and its actions have not been implemented.
Then I looked at the possible actions, listed them and assigned them to a category:
Category Code | Action Description |
---|---|
NA | UPnP set event |
NA | LAN access from remote |
------------- | --------------------------------- |
RA | Admin login |
RA | Dynamic DNS |
RA | Internet connected |
RA | Internet disconnected |
RA | Time synchronized with NTP server |
------------- | --------------------------------- |
RI | DoS attack |
RI | WLAN access rejected |
------------- | --------------------------------- |
RL | DHCP IP |
------------- | --------------------------------- |
RU | Initialized, firmware version |
------------- | --------------------------------- |
(Table 2 - Router Actions)
NOTE: Router Protection has not been implemented yet.
This is what has been implemented so far:
Action Description | Implemented? | Category Code |
---|---|---|
Admin login | Y | RA |
DHCP IP | Y | RL |
DoS attack | Y | RI |
Dynamic DNS | Y | RA |
email failed | RA | |
email sent to | RA | |
Initialized | Y | RU |
Internet connected | Y | RA |
Internet disconnected | Y | RA |
LAN access from remote | Y | NA |
Log Cleared | RA | |
Self2WAN ICMP type b Detected! | RI | |
Service blocked | RP | |
Site allowed | RP | |
Site blocked | RP | |
Time synchronized with NTP server | Y | RA |
UPnP set event | Y | NA |
USB device attached | RA | |
USB device detached | RA | |
WLAN access rejected | Y | RI |
(Table 3 - Implemented Actions)
This application uses two event emitters. One for the events needed for log file watching & reading, and another for synchronizing other application operations like:
- The database connection is opened or closed
- Resources and data ready for use
- Data saved to database
Actually, "static" is just the term I'm using to describe data that is retrieved from the database and does not change during the application's run-time. The only exception is the MAC vendor database which can be updated during run-time.
The majority of that data is used in the parsing of log entries. It's also used when generating the report tables for MAC lookups and event classifications.
Here are the "static" data tables in the rlmonitor
database:
actioncats
- "Action Categories" as described above in "Table 1"actions
- "Router Actions" as described in "Table 2"ipcats
- This table contains IP address categories that I have assigned to the equipment on my network. Each "IP Category" is assigned an ID, description, and the IP range.known
- All of the "known" devices attached to my network, this includes a description, MAC, and "IP Category".attacktypes
- Different types of DOS attacks.macvendors
- This table contains MAC vendor information that was obtained via an API.
For detailed information regarding the layout of the tables see THISDOCHERE.
logentry
- all parsed log entrieslogentry_bad
- any log entry that could not be parsed
After a log file has been parsed, the following tables are populated with log entries that match the tables' criteria:
attacks
- all DOS attackslanaccess
- all LAN access from outside the local networkdhcpip
- all DHCP eventswlanrejects
- Wireless LAN access failures
runlogopt.js
This configures the run-time logging. See the NPM package simple-text-log for details.
'use strict';
module.exports = {
logfile:'./logs/logwatcher.log',
// 10 MiB file size, then roll over & archive
logsize:10485760
};
watchopt.js
Option settings for the logwatcher
applciation:
See File Contents
'use strict';
// options and settings for logwatch.js and logread.js
module.exports = {
path : './../logoutput/',
// log file names are: YYYYMMDD-HHMMSS-net.log
// NOTE: the "net.log" portion must match the
// 'outfile' setting in ../logcollector/appoptions.json
nameregexp: /^\d{8}-\d{6}-net\.log/g,
copybad : true,
mintstamp: 1523771032000,
delbad: true,
// used in logread.js, readdel has
// priority over readmov and readren,
// and readmov has priority over readren
readdel: false,
readmov: true,
// NOTE: this is relative to "path", so things
// like "../folder/" will work. If the path does
// not exist it will be created. This will be
// added to 'path'.
movpath: 'oldlogs/',
// rename the file(s), renchar can be a string
// instead of a single character. It is added
// to the start of the file name.
readren: false,
renchar: '_',
// only used in logread.js, leave set to 'true'
// the 'false' setting is for debug purposes
readexit: true
};
For handling unparsable ("bad") log entries the following options are available:
copybad
- whentrue
bad entries are copied into a separate database table for later examinationdelbad
- whentrue
bad entries are deleted from the log entry table.
Bad Log Entry Disposition | copybad | delbad |
---|---|---|
Copy to "bad entry" table | true | false |
Copy & Delete | true | true |
Leave "bad entry" in table | false | false |
(Table 4 - Bad Log Entry Disposition)
NOTE: Each time a log entry is parsed an "entry sequence number" is created and saved with that entry, including "bad" log entries. This means that if bad entries are deleted then there will be a gap in the sequence numbers found in the log entry table. This is intentional, and can be used for detecting router logging problems.
After the log file is read and parsed there are options to determine how that file will be handled further:
Operation | readdel | readmov | readren |
---|---|---|---|
Leave file "as is" | false | false | false |
Delete File | true | N/A | N/A |
Move file into movpath |
false | true | N/A |
Rename file | false | false | true |
(Table 5 - Log File Handling Options)
- "Move file into
movpath
" - The destination path ispath
+movpath
- "Rename file" - The filename is prepended with the character (or string) in
rechar
and the log file is left in its current location
macinfocfg.js
Configuration for looking up MAC addresses and retrieving vendor information via an API:
See File Contents
// macinfocfg.js - configuration for MAC manufacturer
// look ups.
//
// NOTE: You will need to get your own API key, place
// in a file - ./keys/_maclookupapp.key
//
module.exports = {
// https://maclookup.app/
hostname: 'api.maclookup.app',
// for developer refernce, as named on maclookup.app
apikeyName: 'rlmonitor',
// API key
apikey: `${require('fs').readFileSync('./keys/_maclookupapp.key')}`,
// part of the URL
urlparts : [
'/v2/macs/',
'?apiKey='
],
// Let's be "nice"...
useragent: 'rlmonitor application https://github.com/jxmot/router-log-monitor',
headeraccept: 'application/json',
// save when the API returns?
savemac: true
};
If savemac
is true
then any MAC found via the API will be saved in the macvendors
table.
example_dbcfg.js
See File Contents
'use strict';
/* ************************************************************************ */
/*
MySQL Connection Settings & Record Definition
For information on the contents of 'parms' see -
https://www.npmjs.com/package/mysql#connection-options
*/
module.exports = {
// mysql
parms: {
host : 'localhost',
database : 'rlmonitor',
// add your info and save this file as
// "_dbcfg.js"
user : 'db_user_name',
password : 'db_user_password'
},
// the tables in our database, use tables[] and
// the indices below to retrieve the table names
tables : [
// static data tables
'actions',
'actioncats',
'ipcats',
'known',
'attacktypes',
'macvendors',
// dynamic data tables
'logentry',
'logentry_bad',
'ipstats',
'invasions',
'attacks',
'wlanrejects',
'dhcpip'
],
// static data table indices
TABLE_STATIC_BEGIN: 0,
TABLE_ACTIONS_IDX: 0,
TABLE_ACTIONCATS_IDX: 1,
TABLE_IPCATS_IDX: 2,
TABLE_KNOWN_IDX: 3,
TABLE_ATYPES_IDX: 4,
TABLE_MACVEND_IDX: 5,
TABLE_STATIC_END: 5,
// dynamic data table indices
TABLE_LOGENTRY_IDX: 6,
TABLE_LOGENTRYBAD_IDX: 7,
TABLE_IPSTATS_IDX: 8,
TABLE_LANACCESS_IDX: 9,
TABLE_ATTACKS_IDX: 10,
TABLE_WLANREJS_IDX: 11,
TABLE_DHCPIP_IDX: 12
};
The parms
object is passed to mysql
, and the rest is a list table names and usable indices.
There are SQL statment files in the /sql
folder and sub-folders. They should be use to create the database and tables, and create & seed the "static" data tables.
-
/sql
:rlmonitor.sql
- creates the schema
-
/sql/dyndata
:logentry.sql
- create tableattacks.sql
- create tablelanaccess.sql
-create tabledhcpip.sql
- create tablewlanrejects.sql
- create table
-
/sql/staticdata
:actions.sql
- create table and seed dataactioncats.sql
- create table and seed dataipcats.sql
- create table and seed dataknown.sql
- create table and seed with dummy dataattacktypes.sql
- create table and seed datamacvendors.sql
- create table and seed with dummy data
There are two script files, one for starting and the other for stopping the application. However, since my target platform runs Busybox the "stop" script is written for it.
run.sh
This script will run the Log Watcher application in the background using nohup
:
#!/bin/sh
rm nohup.out
# "watch" mode
nohup node rlmonitor.js&
echo "$(ps -ef | grep "[0-9] node rlmonitor.js")"
exit 0
sand.sh
(Search And Destroy)
#
# NOTE: On the AS1002T NAS Busybox is used, so things
# are a little diffenent from regular bash.
# https://www.busybox.net/
#
# We will grep the output of the ps command and look
# for strings that match the node instance we want.
# In this case it is the one running rlmonitor.js. And
# the output from the command will be placed into
# separate variables ($1,$2, etc..). The one we want
# is $1. It contains the PID.
#
# The positional parameters are set to the arguments,
# which means that the output -
# 26213 admin 0:04 node rlmonitor.js
# is delimited by spaces and each part is loaded into
# an argument.
set -- $(ps -ef | grep "[0-9] node rlmonitor.js")
kill -9 "$1"
exit 0
By default, the Log Watcher will be in "watch" mode. But it can be run in "read" mode just by adding an argument:
node rlmonitor.js read