Skip to content

Commit

Permalink
refactoring monitoring to multimatch
Browse files Browse the repository at this point in the history
  • Loading branch information
massimocandela committed Oct 19, 2023
1 parent 96958e9 commit a795f0c
Show file tree
Hide file tree
Showing 17 changed files with 170 additions and 138 deletions.
2 changes: 1 addition & 1 deletion config.yml.example
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ monitors:
channel: misconfiguration
name: asn-monitor
params:
skipPrefixMatchOnDifferentGroups: false
skipPrefixMatch: false
thresholdMinPeers: 3

- file: monitorRPKI
Expand Down
6 changes: 3 additions & 3 deletions docs/research.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ export default class myMonitor extends Monitor { // It MUST extend Monitor
* This is where the real analysis happens and when the alerts are generated. Place here your complex filtering/analysis.
* The 'filter' function described before is needed to avoid useless calls to the 'monitor' function, which is much more expensive in terms of memory. */
const matchedRule = this.getMoreSpecificMatch(message.prefix); //The method getMoreSpecificMatch is inherited from the super class, it provides the rule in prefixes.yml that matches the current BGP message.
const matchedRules = this.getMoreSpecificMatches(message.prefix); //The method getMoreSpecificMatches is inherited from the super class, it provides the rule in prefixes.yml that matches the current BGP message.
if (matchedRule) { // We matched something in prefixes.yml
for (let matchedRule of matchedRules) { // We matched something in prefixes.yml
const signature = message.originAS.getId() + "-" + message.prefix; // All messages with the same origin AS and prefix will be bundled together. Read above the squash method to understand why.
this.publishAlert(signature, // The method publishAlert is inherited from the super class.
message.prefix, // The monitored resource subject of the alert (it can be an AS or a prefix)
matchedRule, // The monitored rule that was matched (from prefixes.yml)
Expand Down
2 changes: 1 addition & 1 deletion src/config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export default class Config {
channel: "misconfiguration",
name: "asn-monitor",
params: {
skipPrefixMatchOnDifferentGroups: false,
skipPrefixMatch: false,
thresholdMinPeers: 3
}
},
Expand Down
28 changes: 18 additions & 10 deletions src/inputs/input.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ export default class Input {
// This is to load the prefixes after the application is booted
setTimeout(() => {
this.loadPrefixes()
.then(() => {
this._change();
})
.then(() => this._change())
.catch(error => {
this.logger.log({
level: 'error',
Expand Down Expand Up @@ -105,7 +103,12 @@ export default class Input {
};

_change = () => {
for (let item of this.asns) {
item.group = [item.group].flat();
}

for (let item of this.prefixes) {
item.group = [item.group].flat();
this.index.addPrefix(item.prefix, item);
}

Expand Down Expand Up @@ -150,15 +153,20 @@ export default class Input {
throw new Error('The method getMonitoredPrefixes MUST be implemented');
};

getMoreSpecificMatch = (prefix, includeIgnoredMorespecifics=false) => {
const matches = this.index.getMatch(prefix, false)
.filter(i => {
return includeIgnoredMorespecifics || !i.ignoreMorespecifics || ipUtils.isEqualPrefix(i.prefix, prefix); // last piece says "or it is not a more specific"
});

return matches.length ? matches[0] : null
};
_filterIgnoreMorespecifics = (prefix, includeIgnoredMorespecifics) => {

return i => {
return includeIgnoredMorespecifics
|| !i.ignoreMorespecifics
|| ipUtils._isEqualPrefix(i.prefix, prefix); // last piece says "or it is not a more specific"
}
}

getMoreSpecificMatches = (prefix, includeIgnoredMorespecifics=false) => {
return this.index.getMatch(prefix, false)
.filter(this._filterIgnoreMorespecifics(prefix, includeIgnoredMorespecifics));
}

getMonitoredASns = () => {
throw new Error('The method getMonitoredASns MUST be implemented');
Expand Down
10 changes: 4 additions & 6 deletions src/inputs/inputYml.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@ export default class InputYml extends Input {
this.prefixes = [];
this.asns = [];
this._loadPrefixes()
.then(() => {
return this._change();
})
.then(() => this._change())
.catch(error => {
this.logger.log({
level: 'error',
Expand Down Expand Up @@ -132,7 +130,7 @@ export default class InputYml extends Input {
uniqueAsns[asn] = true;
const item = Object.assign({
asn: new AS(asn),
group: 'default'
group: ['default']
}, monitoredPrefixesFile.options.monitorASns[asn]);

if (item.upstreams && item.upstreams.length) {
Expand Down Expand Up @@ -162,7 +160,7 @@ export default class InputYml extends Input {

return Object.assign({
prefix: i,
group: 'default',
group: ['default'],
ignore: false,
excludeMonitors: [],
includeMonitors: [],
Expand Down Expand Up @@ -351,7 +349,7 @@ export default class InputYml extends Input {

for (let asnRule of this.asns) {
monitorASns[asnRule.asn.getValue()] = {
group: asnRule.group,
group: [asnRule.group].flat(),
upstreams: asnRule.upstreams ? asnRule.upstreams.numbers : null,
downstreams: asnRule.downstreams ? asnRule.downstreams.numbers : null,
};
Expand Down
44 changes: 22 additions & 22 deletions src/monitors/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -257,31 +257,31 @@ export default class Monitor {
return null;
};

_included = (matched) => {
if (matched.includeMonitors.length > 0) {
return matched.includeMonitors.includes(this.name);
} else {
return !matched.excludeMonitors.includes(this.name);
}
};

getMoreSpecificMatch = (prefix, includeIgnoredMorespecifics, verbose=false) => {
const matched = this.input.getMoreSpecificMatch(prefix, includeIgnoredMorespecifics);

if (matched) {
const included = this._included(matched);

if (verbose) {
return {
matched,
included
};
} else if (included) {
return matched;
_filterMatched = (verbose) => {
return matched => {
if (matched) {
const included = matched.includeMonitors.length > 0
? matched.includeMonitors.includes(this.name)
: !matched.excludeMonitors.includes(this.name);

if (verbose) {
return {
matched,
included
};
} else if (included) {
return matched;
}
}

return null;
}
}

return null;
getMoreSpecificMatches = (prefix, includeIgnoredMorespecifics, verbose) => {
return this.input.getMoreSpecificMatches(prefix, includeIgnoredMorespecifics)
.map(this._filterMatched(verbose))
.filter(i => !!i);
};

}
23 changes: 20 additions & 3 deletions src/monitors/monitorAS.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export default class MonitorAS extends Monitor {
constructor(name, channel, params, env, input){
super(name, channel, params, env, input);
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 3;
this.skipPrefixMatchOnDifferentGroups = !!params?.skipPrefixMatchOnDifferentGroups;
this.skipPrefixMatch = !!params?.skipPrefixMatch;
this.updateMonitoredResources();
};

Expand Down Expand Up @@ -84,8 +84,25 @@ export default class MonitorAS extends Monitor {

if (matchedRule) {

const matchedPrefixRule = this.getMoreSpecificMatch(messagePrefix, true);
if (!matchedPrefixRule) {
const matchedPrefixRules = this.getMoreSpecificMatches(messagePrefix, true, false);

if (this.skipPrefixMatch) {
this.publishAlert(messageOrigin.getId().toString() + "-" + messagePrefix,
messageOrigin.getId(),
matchedRule,
message,
{});

for (let matchedPrefixRule of matchedPrefixRules) {
this.publishAlert(messageOrigin.getId().toString() + "-" + messagePrefix,
messageOrigin.getId(),
matchedPrefixRule,
message,
{});
}

} else if (!matchedPrefixRules.length) {

this.publishAlert(messageOrigin.getId().toString() + "-" + messagePrefix,
messageOrigin.getId(),
matchedRule,
Expand Down
16 changes: 9 additions & 7 deletions src/monitors/monitorHijack.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,17 @@ export default class MonitorHijack extends Monitor {
}
}

monitor = (message) =>
new Promise((resolve, reject) => {
const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
monitor = (message) =>{
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);

if (matchedRule && !matchedRule.ignore && !matchedRule.asn.includes(message.originAS)) {
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore && !matchedRule.asn.includes(message.originAS)) {
this.validate(message, matchedRule);
resolve(true);
}
});
}

return Promise.resolve(true);
}

}
15 changes: 8 additions & 7 deletions src/monitors/monitorNewPrefix.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,13 @@ export default class MonitorNewPrefix extends Monitor {
return false;
};

monitor = (message) =>
new Promise((resolve, reject) => {
monitor = (message) => {

const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);

if (matchedRule && !matchedRule.ignore &&
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore &&
matchedRule.asn.includes(message.originAS) &&
!ipUtils._isEqualPrefix(matchedRule.prefix, messagePrefix)) {

Expand All @@ -78,8 +78,9 @@ export default class MonitorNewPrefix extends Monitor {
message,
{});
}
}

resolve(true);
});
return Promise.resolve(true);
}

}
16 changes: 8 additions & 8 deletions src/monitors/monitorPath.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,20 +104,20 @@ export default class MonitorPath extends Monitor {
}
};

monitor = (message) =>
new Promise((resolve, reject) => {
monitor = (message) => {

const messagePrefix = message.prefix;
const matchedRule = this.getMoreSpecificMatch(messagePrefix, false);
const messagePrefix = message.prefix;
const matchedRules = this.getMoreSpecificMatches(messagePrefix, false);

if (matchedRule && !matchedRule.ignore && matchedRule.path) {
for (let matchedRule of matchedRules) {
if (!matchedRule.ignore && matchedRule.path) {
const pathRules = (matchedRule.path.length) ? matchedRule.path : [ matchedRule.path ];

pathRules.map((pathRule, position) => this.pathRuleCheck(pathRule, position, message, matchedRule));

}
}

resolve(true);
});
return Promise.resolve(true);
};

}
54 changes: 28 additions & 26 deletions src/monitors/monitorROAS.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,30 +210,31 @@ export default class MonitorROAS extends Monitor {
return Promise.all([...new Set(vrps.map(i => i.prefix))]
.map(prefix => {
const roas = vrps.filter(i => ipUtils.isEqualPrefix(i.prefix, prefix)); // Get only the ROAs for this prefix
const matchedRule = this.getMoreSpecificMatch(prefix, false); // Get the matching rule
if (matchedRule) {
return this._getExpiringItems(roas)
.then(extra => {
const alertsStrings = [...new Set(roas.map(this._roaToString))];
let message = "";

if (extra && extra.type === "chain") {
message = `The following ROAs will become invalid in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}.`
message += ` The reason is the expiration of the following parent components: ${extra.expiring.join(", ")}`;
} else {
message = `The following ROAs will expire in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}`;
}
alerts = alerts.concat(alertsStrings);

this.publishAlert(md5(message), // The hash will prevent alert duplications in case multiple ASes/prefixes are involved
matchedRule.prefix,
matchedRule,
message,
{...extra, vrps, roaExpirationHours: roaExpirationAlertHours, rpkiMetadata: metadata, subType: "roa-expire"});
});
} else {
return Promise.resolve();
}
const matchedRules = this.getMoreSpecificMatches(prefix, false); // Get the matching rule

return Promise
.all(matchedRules
.map(matchedRule => {
return this._getExpiringItems(roas)
.then(extra => {
const alertsStrings = [...new Set(roas.map(this._roaToString))];
let message = "";

if (extra && extra.type === "chain") {
message = `The following ROAs will become invalid in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}.`
message += ` The reason is the expiration of the following parent components: ${extra.expiring.join(", ")}`;
} else {
message = `The following ROAs will expire in less than ${roaExpirationAlertHours} hours: ${alertsStrings.join("; ")}`;
}
alerts = alerts.concat(alertsStrings);

this.publishAlert(md5(message), // The hash will prevent alert duplications in case multiple ASes/prefixes are involved
matchedRule.prefix,
matchedRule,
message,
{...extra, vrps, roaExpirationHours: roaExpirationAlertHours, rpkiMetadata: metadata, subType: "roa-expire"});
})
}))
}))
.then(() => alerts);
};
Expand Down Expand Up @@ -314,8 +315,9 @@ export default class MonitorROAS extends Monitor {
for (let prefix of [...new Set(roaDiff.map(i => i.prefix))]) {

const roas = roaDiff.filter(i => ipUtils.isEqualPrefix(i.prefix, prefix)); // Get only the ROAs for this prefix
const matchedRule = this.getMoreSpecificMatch(prefix, false); // Get the matching rule
if (matchedRule) {
const matchedRules = this.getMoreSpecificMatches(prefix, false); // Get the matching rule

for (let matchedRule of matchedRules) {
const alertsStrings = [...new Set(roas.map(this._roaToString))];
const message = alertsStrings.length <= 10 ?
`ROAs change detected: ${alertsStrings.join("; ")}` :
Expand Down
15 changes: 10 additions & 5 deletions src/monitors/monitorRPKI.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,18 +183,23 @@ export default class MonitorRPKI extends Monitor {
const messageOrigin = message.originAS;
const prefix = message.prefix;

const matchedPrefixRule = this.getMoreSpecificMatch(prefix, false, true);
const matchedPrefixRules = this.getMoreSpecificMatches(prefix, false, true);

if (matchedPrefixRule && matchedPrefixRule.matched) { // There is a prefix match
if (!matchedPrefixRule.matched.ignore && matchedPrefixRule.included) { // The prefix match is not excluded in any way
this.validate(message, matchedPrefixRule.matched);
if (matchedPrefixRules.length) {
for (let matchedPrefixRule of matchedPrefixRules) {
if (matchedPrefixRule.matched) { // There is a prefix match
if (!matchedPrefixRule.matched.ignore && matchedPrefixRule.included) { // The prefix match is not excluded in any way
this.validate(message, matchedPrefixRule.matched);
}
}
}
} else { // No prefix match
} else {
const matchedASRule = this.getMonitoredAsMatch(messageOrigin); // Try AS match
if (matchedASRule) {
this.validate(message, matchedASRule);
}
}

} catch (error) {
this.logger.log({
level: 'error',
Expand Down
Loading

0 comments on commit a795f0c

Please sign in to comment.