1
0
mirror of https://github.com/nttgin/BGPalerter.git synced 2024-05-19 06:50:08 +00:00

introduced monitoring of neighbors in AS path

This commit is contained in:
Massimo Candela
2021-04-27 19:00:11 +02:00
parent 9a1a782c87
commit d85df19dbd
16 changed files with 482 additions and 56 deletions

View File

@@ -11,6 +11,8 @@ Self-configuring BGP monitoring tool, which allows you to monitor in **real-time
* ROAs covering your prefixes are no longer reachable (e.g., TA malfunction);
* a ROA involving any of your prefixes or ASes was deleted/added/edited;
* your AS is announcing a new prefix that was never announced before;
* an unexpected upstream (left-side) AS appears in an AS path (possible path poisoning);
* an unexpected downstream (right-side) AS appears in an AS path;
* one of the AS paths used to reach your prefix matches a specific condition defined by you.
You just run it. You don't need to provide any data source or connect it to anything in your network since it connects to [public repos](docs/datasets.md).

View File

@@ -54,6 +54,12 @@ monitors:
channel: rpki
name: rpki-diff
- file: monitorPathPoisoning
channel: hijack
name: path-poisoning
params:
thresholdMinPeers: 3
reports:
- file: reportFile
channels:

View File

@@ -266,7 +266,7 @@ This is useful if you want to be alerted in case your AS starts announcing somet
>
> If AS58302 starts announcing 45.230.23.0/24 an alert will be triggered. This happens because such prefix is not already monitored (it's not a sub prefix of 50.82.0.0/20).
You can generate the options block in the prefixes list automatically. Refer to the options `-s` and `-m` in the [auto genere prefixes documentation](prefixes.md#generate).
You can generate the options block in the prefixes list automatically. Refer to the options `-s` and `-m` of the [auto configuration](prefixes.md#generate).
Example of alert:
@@ -355,6 +355,12 @@ Example of alerts:
> ROAs change detected: removed <1.2.3.4/24, 1234, 25, apnic>
#### monitorPathPoisoning
The component `monitorPathPoisoning` allows to monitor for unexpected neighbor ASes in AS paths. The list of neighbors can be specified in `prefixes.yml` inside the `monitorASns` sections.
Refer to the [documentation for this monitor](path-poisoning.md).
### Reports

68
docs/path-poisoning.md Normal file
View File

@@ -0,0 +1,68 @@
# Path poisoning / upstream and downstream AS monitoring
The component `monitorPathPoisoning` allows to monitor for unexpected neighbor ASes in AS paths. The list of neighbors can be specified in `prefixes.yml` inside the `monitorASns` sections.
> For example, imagine AS100 has two upstreams, AS99 and AS98, and one downstream, AS101. You can express the following rule in 'prefixes.yml'
>
> ```yaml
> options:
> monitorASns:
> '2914':
> group: noc
> upstreams:
> - 99
> - 98
> downstreams:
> - 101
> ```
Every time an AS path is detected with a different upstream/downstream AS, an alert will be generated.
**You can generate the upstream/downstream lists automatically. Refer to the options `-u` and `-n` of the [auto configuration](prefixes.md#generate).**
According to the above configuration,
* the AS path [10, 20, 30, 100, 101] will generate an alert since AS30 is not an upstream of AS100;
* the AS path [10, 20, 30, 100] will generate an alert since AS30 is not an upstream of AS100;
* the AS path [10, 20, 99, 100, 101] will not generate an alert since AS99 is an upstream of AS100;
* the AS path [10, 20, 99, 100, 104] will generate an alert since AS104 is not a downstream of AS100;
* the AS path [100, 104] will generate an alert since AS104 is not a downstream of AS100.
You can disable the monitoring by removing the upstreams and downstreams lists or by commenting the `monitorPathPoisoning` block in `config.yml`.
If you delete only one of the upstreams and downstreams lists, the monitoring will continue on the remaining one.
> E.g., the config below monitors only for upstreams
>
> ```yaml
> options:
> monitorASns:
> '2914':
> group: noc
> upstreams:
> - 99
> - 98
> ```
If you provide empty lists, the monitoring will be performed and you will receive an alert for every upstream/downstream.
> E.g., the config below monitors only for downstreams and expects to never see any downstream AS (stub network)
>
> ```yaml
> options:
> monitorASns:
> '2914':
> group: noc
> downstreams:
> ```
Example of alert:
> A new upstream of AS100 has been detected: AS30
Parameters for this monitor module:
|Parameter| Description|
|---|---|
|thresholdMinPeers| Minimum number of peers that need to see the BGP update before to trigger an alert. |
|maxDataSamples| Maximum number of collected BGP messages for each alert which doesn't reach yet the `thresholdMinPeers`. Default to 1000. As soon as the `thresholdMinPeers` is reached, the collected BGP messages are flushed, independently from the value of `maxDataSamples`.|

View File

@@ -27,6 +27,8 @@ Below the list of possible parameters. **Remember to prepend them with a `--` in
| -D | Enable debug mode. All queries executed in background will be shown. | Nothing | | No |
| -H | Use historical visibility data for generating prefix list (prefixes visible in the last week). Useful in case the prefix generation process returns an empty dataset. | Nothing | | No |
| -g | The name of the user group that will be assigned to all the generated rules. See [here](usergroups.md). | A string | noc | No |
| -u | Calculate all upstream ASes and enable path poisoning monitoring. See [here](path-poisoning.md). | Nothing | | No |
| -n | Calculate all downstream ASes and enable detection of new customer ASes. See [here](path-poisoning.md). | Nothing | | No |
## <a name="prefixes-fields"></a>Prefixes list fields

View File

@@ -114,6 +114,14 @@ const params = yargs
.nargs('H', 0)
.describe('H', 'Use historical visibility data for generating prefix list (prefixes visible in the last week).')
.alias('u', 'upstreams')
.nargs('u', 0)
.describe('u', 'Detect a list of allowed upstream ASes, useful to monitor for path poisoning.')
.alias('n', 'downstreams')
.nargs('n', 0)
.describe('n', 'Detect a list of allowed downstream ASes, useful to monitor for path poisoning.')
.demandOption(['o']);
})
.example('$0 generate -a 2914 -o prefixes.yml', 'Generate prefixes for AS2914')
@@ -166,6 +174,8 @@ switch(params._[0]) {
group: params.g || null,
append: !!params.A,
logger: null,
upstreams: !!params.u,
downstreams: !!params.n,
getCurrentPrefixesList: () => {
return Promise.resolve(yaml.load(fs.readFileSync(params.o, "utf8")));
}

44
package-lock.json generated
View File

@@ -19,7 +19,7 @@
"https-proxy-agent": "^5.0.0",
"inquirer": "^8.0.0",
"ip-address": "7.1.0",
"ip-sub": "^1.0.21",
"ip-sub": "^1.0.22",
"js-yaml": "^4.1.0",
"kafkajs": "^1.15.0",
"md5": "^2.3.0",
@@ -46,7 +46,7 @@
"chai": "^4.3.4",
"chai-subset": "^1.6.0",
"mocha": "^8.3.2",
"pkg": "^5.0.0",
"pkg": "^5.1.0",
"read-last-lines": "^1.8.0",
"syslogd": "^1.1.2"
},
@@ -3914,9 +3914,9 @@
}
},
"node_modules/ip-sub": {
"version": "1.0.21",
"resolved": "https://registry.npmjs.org/ip-sub/-/ip-sub-1.0.21.tgz",
"integrity": "sha512-EBjB3RGTPxLoszOVk8XB6B5eec8EsWV4Z+4K9EKe7QmGqC3CmHBlpTbIqcdK2QTIb6eJUR7Y9N9kCHT63Go1ew==",
"version": "1.0.22",
"resolved": "https://registry.npmjs.org/ip-sub/-/ip-sub-1.0.22.tgz",
"integrity": "sha512-CsgRTLRmknJqn+/LvRcpLtozYb4CvRvXr1lronZjtuVuEUu9CeAyrTtL5Fi9jOxC6UNBfzCvRrsEhnoV8aIQnQ==",
"dependencies": {
"ip-address": "^7.1.0"
}
@@ -5342,9 +5342,9 @@
}
},
"node_modules/pkg": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.0.0.tgz",
"integrity": "sha512-B/bZZp51wUP00XHme4qoA/VHsRDIL1ldN+oEdg/L8E7mE9B8Oz/mximyQWbKFrMu85Uh3H9jy55ln5Ms6jspwg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.1.0.tgz",
"integrity": "sha512-rWTwvLJakQnEVg03s97KNtGkhM3pPfxk7XinjR7H1bToMZQMNpBTwahrAPoFHdQyfn6odI76DP6vX3Br9VubNQ==",
"dev": true,
"dependencies": {
"@babel/parser": "7.13.13",
@@ -5356,7 +5356,7 @@
"into-stream": "^6.0.0",
"minimist": "^1.2.5",
"multistream": "^4.1.0",
"pkg-fetch": "3.0.3",
"pkg-fetch": "3.0.4",
"prebuild-install": "6.0.1",
"progress": "^2.0.3",
"resolve": "^1.20.0",
@@ -5446,9 +5446,9 @@
}
},
"node_modules/pkg-fetch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.0.3.tgz",
"integrity": "sha512-w8Nn7EZZFtTcdeERUH5IcMRAbrn4xL55X05dtjIaBlrkkNzMvbgAyTAwEDGmK3cxBU1d/Eh+uZVueeUzIG0AEA==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.0.4.tgz",
"integrity": "sha512-XgMXcco5hy0/Q7OXfQ/FbBnPvS4e7gWB9BCcUWUgaHYo3JretihmJjr62EZWmxAjvodoWLGMZ3E7XHf8Q2LfBg==",
"dev": true,
"dependencies": {
"axios": "^0.21.1",
@@ -10728,9 +10728,9 @@
}
},
"ip-sub": {
"version": "1.0.21",
"resolved": "https://registry.npmjs.org/ip-sub/-/ip-sub-1.0.21.tgz",
"integrity": "sha512-EBjB3RGTPxLoszOVk8XB6B5eec8EsWV4Z+4K9EKe7QmGqC3CmHBlpTbIqcdK2QTIb6eJUR7Y9N9kCHT63Go1ew==",
"version": "1.0.22",
"resolved": "https://registry.npmjs.org/ip-sub/-/ip-sub-1.0.22.tgz",
"integrity": "sha512-CsgRTLRmknJqn+/LvRcpLtozYb4CvRvXr1lronZjtuVuEUu9CeAyrTtL5Fi9jOxC6UNBfzCvRrsEhnoV8aIQnQ==",
"requires": {
"ip-address": "^7.1.0"
}
@@ -11851,9 +11851,9 @@
}
},
"pkg": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.0.0.tgz",
"integrity": "sha512-B/bZZp51wUP00XHme4qoA/VHsRDIL1ldN+oEdg/L8E7mE9B8Oz/mximyQWbKFrMu85Uh3H9jy55ln5Ms6jspwg==",
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/pkg/-/pkg-5.1.0.tgz",
"integrity": "sha512-rWTwvLJakQnEVg03s97KNtGkhM3pPfxk7XinjR7H1bToMZQMNpBTwahrAPoFHdQyfn6odI76DP6vX3Br9VubNQ==",
"dev": true,
"requires": {
"@babel/parser": "7.13.13",
@@ -11865,7 +11865,7 @@
"into-stream": "^6.0.0",
"minimist": "^1.2.5",
"multistream": "^4.1.0",
"pkg-fetch": "3.0.3",
"pkg-fetch": "3.0.4",
"prebuild-install": "6.0.1",
"progress": "^2.0.3",
"resolve": "^1.20.0",
@@ -12002,9 +12002,9 @@
}
},
"pkg-fetch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.0.3.tgz",
"integrity": "sha512-w8Nn7EZZFtTcdeERUH5IcMRAbrn4xL55X05dtjIaBlrkkNzMvbgAyTAwEDGmK3cxBU1d/Eh+uZVueeUzIG0AEA==",
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pkg-fetch/-/pkg-fetch-3.0.4.tgz",
"integrity": "sha512-XgMXcco5hy0/Q7OXfQ/FbBnPvS4e7gWB9BCcUWUgaHYo3JretihmJjr62EZWmxAjvodoWLGMZ3E7XHf8Q2LfBg==",
"dev": true,
"requires": {
"axios": "^0.21.1",

View File

@@ -51,7 +51,7 @@
"chai": "^4.3.4",
"chai-subset": "^1.6.0",
"mocha": "^8.3.2",
"pkg": "^5.0.0",
"pkg": "^5.1.0",
"read-last-lines": "^1.8.0",
"syslogd": "^1.1.2"
},
@@ -66,7 +66,7 @@
"https-proxy-agent": "^5.0.0",
"inquirer": "^8.0.0",
"ip-address": "7.1.0",
"ip-sub": "^1.0.21",
"ip-sub": "^1.0.22",
"js-yaml": "^4.1.0",
"kafkajs": "^1.15.0",
"md5": "^2.3.0",

79
prefixes.yml.bak Normal file
View File

@@ -0,0 +1,79 @@
193.0.22.0/23:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
193.0.0.0/21:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
path:
- match: .*2194,1234$
notMatch: .*5054.*
matchDescription: detected scrubbing center
- match: .*123$
notMatch: .*5056.*
matchDescription: other match
2001:67c:2e8::/48:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
193.0.10.0/23:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
193.0.12.0/23:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
193.0.18.0/23:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
193.0.20.0/23:
group: noc
ignore: false
description: No description provided
asn: 3333
ignoreMorespecifics: false
options:
monitorASns:
'18747':
group: noc
upstreams:
- 1103
- 1257
- 1273
- 12859
downstreams:
- 12654
'3333':
group: noc
upstreams:
- 1103
- 1257
- 1273
- 12859
downstreams:
- 12654
generate:
asnList:
- '3333'
exclude: []
excludeDelegated: true
prefixes: null
monitoredASes:
- '3333'
historical: false
group: noc

View File

@@ -78,6 +78,14 @@ export default class Config {
channel: "rpki",
name: "rpki-diff",
params: {}
},
{
file: "monitorPathPoisoning",
channel: "hijack",
name: "path-poisoning",
params: {
thresholdMinPeers: 3
}
}
],
reports: [

View File

@@ -176,16 +176,17 @@ export default class ConnectorRIS extends Connector {
};
_subscribeToASns = (input) => {
const monitoredASns = input.getMonitoredASns().map(i => i.asn);
const monitoredASns = input.getMonitoredASns();
const params = JSON.parse(JSON.stringify(this.params.subscription));
for (let asn of monitoredASns){
const asnString = asn.getValue();
const asnString = asn.asn.getValue();
if (!this.subscribed[asnString]) {
console.log(`Monitoring AS${asnString}`);
this.subscribed[asnString] = true;
}
params.path = `${asnString}\$`;
this.ws.send(JSON.stringify({

View File

@@ -26,7 +26,9 @@ module.exports = function generatePrefixes(inputParameters) {
append,
logger,
getCurrentPrefixesList,
enriched
enriched,
upstreams,
downstreams
} = inputParameters;
exclude = exclude || [];
@@ -69,6 +71,56 @@ module.exports = function generatePrefixes(inputParameters) {
}
}
const getNeighbors = (asn) => {
const url = brembo.build("https://stat.ripe.net", {
path: ["data", "asn-neighbours", "data.json"],
params: {
client: clientId,
resource: asn
}
});
if (debug) {
logger("Query", url)
}
return axios({
url,
method: 'GET',
responseType: 'json',
timeout: apiTimeout
})
.then(data => {
let neighbors = [];
if (data.data && data.data.data && data.data.data.neighbours){
const items = data.data.data.neighbours;
for (let item of items) {
if (item.type === "left" || item.type === "right") {
neighbors.push({asn: item.asn, type: item.type});
}
}
}
const out = {
asn,
upstreams: neighbors.filter(i => i.type === "left").map(i => i.asn),
downstreams: neighbors.filter(i => i.type === "right").map(i => i.asn),
};
logger(`Detected upstreams for ${out.asn}: ${out.upstreams.join(", ")}`);
logger(`Detected downstreams for ${out.asn}: ${out.downstreams.join(", ")}`);
return out;
})
.catch((error) => {
logger(error);
logger(`RIPEstat asn-neighbours query failed: cannot retrieve information for ${asn}`);
});
};
const getMultipleOrigins = (prefix) => {
const url = brembo.build("https://stat.ripe.net", {
path: ["data", "prefix-overview", "data.json"],
@@ -303,26 +355,36 @@ module.exports = function generatePrefixes(inputParameters) {
})
.then(() => { // Add the options for monitorASns
const generateMonitoredAsObject = function (list) {
const generateMonitoredAsObject = function (list, asnNeighbors) {
generateList.options = generateList.options || {};
generateList.options.monitorASns = generateList.options.monitorASns || {};
for (let monitoredAs of list) {
logger(`Generating generic monitoring rule for AS${monitoredAs}`);
const neighbors = asnNeighbors.filter(i => i.asn.toString() === monitoredAs.toString());
generateList.options.monitorASns[monitoredAs] = {
group: group
group: group,
upstreams: upstreams && neighbors.length ? neighbors[0].upstreams : null,
downstreams: downstreams && neighbors.length ? neighbors[0].downstreams : null
};
}
};
let createASesRules = [];
if (monitoredASes === true) {
generateMonitoredAsObject(asnList);
createASesRules = asnList;
} else if (monitoredASes.length) {
generateMonitoredAsObject(monitoredASes);
createASesRules = monitoredASes;
}
return batchPromises(1, asnList, getNeighbors)
.then(asnNeighbors => {
generateMonitoredAsObject(createASesRules, asnNeighbors);
})
// Otherwise nothing
})
.then(() => {
if (someNotValidatedPrefixes) {
logger("WARNING: the generated configuration is a snapshot of what is currently announced. Some of the prefixes don't have ROA objects associated or are RPKI invalid. Please, verify the config file by hand!");
logger("WARNING: the generated configuration is a snapshot of what is currently announced. Some of the prefixes don't have ROA objects associated. Please, verify the config file by hand!");
}
})
.then(() => {

View File

@@ -1,4 +1,3 @@
/*
* BSD 3-Clause License
*
@@ -42,7 +41,8 @@ export default class Input {
this.asns = [];
this.cache = {
af: {},
binaries: {}
binaries: {},
matched: {}
};
this.config = env.config;
this.storage = env.storage;
@@ -50,6 +50,14 @@ export default class Input {
this.callbacks = [];
this.prefixListDiffFailThreshold = 50;
// This implements a fast basic fixed space cache, other approaches lru-like use too much cpu
setInterval(() => {
if (Object.keys(this.cache.matched).length > 10000) {
delete this.cache.matched;
}
}, 10000);
// This is to load the prefixes after the application is booted
setTimeout(() => {
this.loadPrefixes()
.then(() => {
@@ -120,26 +128,36 @@ export default class Input {
};
getMoreSpecificMatch = (prefix, includeIgnoredMorespecifics) => {
const key = `${prefix}-${includeIgnoredMorespecifics}`;
const cached = this.cache.matched[key];
for (let p of this.prefixes) {
if (ipUtils._isEqualPrefix(p.prefix, prefix)) {
return p;
} else {
if (cached !== undefined) {
return cached;
} else {
for (let p of this.prefixes) {
if (ipUtils._isEqualPrefix(p.prefix, prefix)) {
this.cache.matched[key] = p;
return p;
} else {
if (!this.cache.af[p.prefix] || !this.cache.binaries[p.prefix]) {
this.cache.af[p.prefix] = ipUtils.getAddressFamily(p.prefix);
this.cache.binaries[p.prefix] = ipUtils.getNetmask(p.prefix, this.cache.af[p.prefix]);
}
const prefixAf = ipUtils.getAddressFamily(prefix);
// if (!this.cache.af[p.prefix] || !this.cache.binaries[p.prefix]) {
if (!this.cache.af[p.prefix]) {
this.cache.af[p.prefix] = ipUtils.getAddressFamily(p.prefix);
this.cache.binaries[p.prefix] = ipUtils.getNetmask(p.prefix, this.cache.af[p.prefix]);
}
const prefixAf = ipUtils.getAddressFamily(prefix);
if (prefixAf === this.cache.af[p.prefix]) {
if (prefixAf === this.cache.af[p.prefix]) {
const prefixBinary = ipUtils.getNetmask(prefix, prefixAf);
if (ipUtils.isSubnetBinary(this.cache.binaries[p.prefix], prefixBinary)) {
if (includeIgnoredMorespecifics || !p.ignoreMorespecifics) {
return p;
} else {
return null;
const prefixBinary = ipUtils.getNetmask(prefix, prefixAf);
if (ipUtils.isSubnetBinary(this.cache.binaries[p.prefix], prefixBinary)) {
if (includeIgnoredMorespecifics || !p.ignoreMorespecifics) {
this.cache.matched[key] = p;
return p;
} else {
this.cache.matched[key] = null;
return null;
}
}
}
}
@@ -195,6 +213,19 @@ export default class Input {
name: 'm',
message: "Do you want to be notified when your AS is announcing a new prefix?",
default: true
},
{
type: 'confirm',
name: 'upstreams',
message: "Do you want to be notified when a new upstream AS appears in a BGP path?",
default: true
},
{
type: 'confirm',
name: 'downstreams',
message: "Do you want to be notified when a new downstream AS appears in a BGP path?",
default: true
}
])
.then((answer) => {
@@ -212,6 +243,8 @@ export default class Input {
group: null,
append: false,
logger: null,
upstreams: !!answer.upstreams,
downstreams: !!answer.downstreams,
getCurrentPrefixesList: () => {
return this.retrieve();
}
@@ -255,7 +288,6 @@ export default class Input {
}
inputParameters.httpProxy = this.config.httpProxy || null;
inputParameters.logger = (message) => {
// Nothing, ignore logs in this case (too many otherwise)
};
@@ -263,6 +295,8 @@ export default class Input {
return generatePrefixes(inputParameters)
.then(newPrefixList => {
newPrefixList.options.monitorASns = oldPrefixList.options.monitorASns;
if (Object.keys(newPrefixList).length <= (Object.keys(oldPrefixList).length / 100) * this.prefixListDiffFailThreshold) {
throw new Error("Prefix list generation failed.");
}

View File

@@ -128,10 +128,15 @@ export default class InputYml extends Input {
return;
}
uniqueAsns[asn] = true;
return Object.assign({
const item = Object.assign({
asn: new AS(asn),
group: 'default'
}, monitoredPrefixesFile.options.monitorASns[asn]);
if (item.upstreams) item.upstreams = new AS(item.upstreams);
if (item.downstreams) item.downstreams = new AS(item.downstreams);
return item;
});
this.asns = this.asns.concat(newAsnSet);
@@ -330,7 +335,9 @@ export default class InputYml extends Input {
for (let asnRule of this.asns) {
monitorASns[asnRule.asn.getValue()] = {
group: asnRule.group
group: asnRule.group,
upstreams: asnRule.upstreams ? asnRule.upstreams.numbers : null,
downstreams: asnRule.downstreams ? asnRule.downstreams.numbers : null,
};
}

View File

@@ -21,7 +21,28 @@ export class Path {
toJSON () {
return this.getValues();
}
};
getNeighbors (asn) {
const path = this.value;
const length = path.length - 1
for (let n=0; n < length; n++) {
const current = path[n] || null;
if (current.getId() === asn.getId()) {
const left = path[n - 1] || null;
const right = path[n + 1] || null;
return [left, current, right];
}
}
return [null, null, null];
};
includes (asn) {
console.log(this.value);
return this.value.some(i => i.includes(asn));
};
}
@@ -109,5 +130,5 @@ export class AS {
toJSON () {
return this.numbers;
}
};
}

View File

@@ -0,0 +1,120 @@
/*
* BSD 3-Clause License
*
* Copyright (c) 2019, NTT Ltd.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of the copyright holder nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import Monitor from "./monitor";
export default class MonitorPathPoisoning extends Monitor {
constructor(name, channel, params, env, input){
super(name, channel, params, env, input);
this.thresholdMinPeers = (params && params.thresholdMinPeers != null) ? params.thresholdMinPeers : 0;
this.updateMonitoredResources();
};
updateMonitoredResources = () => {
this.monitored = this.input.getMonitoredASns();
};
filter = (message) => {
return message.type === 'announcement';
};
squashAlerts = (alerts) => {
const peers = [...new Set(alerts.map(alert => alert.matchedMessage.peer))].length;
if (peers >= this.thresholdMinPeers) {
const matchedRule = alerts[0].matchedRule;
const extra = alerts[0].extra;
const asnText = matchedRule.asn;
return `A new ${extra.side} of ${asnText} has been detected: AS${extra.neighbor}`;
}
return false;
};
monitor = (message) =>
new Promise((resolve, reject) => {
const path = message.path;
for (let monitoredAs of this.monitored) {
if (monitoredAs.upstreams || monitoredAs.downstreams) {
const [left, _, right] = path.getNeighbors(monitoredAs.asn);
if (!!left || !!right) {
let match = false;
let side = null;
let id = null;
if (left) {
if (monitoredAs.upstreams === null) {
side = "upstream";
id = left.getId();
match = true;
} else if (monitoredAs.upstreams && !monitoredAs.upstreams.includes(left)) {
side = "upstream";
id = left.getId();
match = true;
}
}
if (right) {
if (monitoredAs.downstreams === null) {
side = "downstream";
id = right.getId();
match = true;
} else if (monitoredAs.downstreams && !monitoredAs.downstreams.includes(right)) {
side = "downstream";
id = right.getId();
match = true;
}
}
if (match) {
const monitoredId = monitoredAs.asn.getId();
this.publishAlert([monitoredId, id].join("-"),
monitoredId,
monitoredAs,
message,
{side, neighbor: id});
}
}
}
}
resolve(true);
});
}