Browse Source

Initial commit

Sebastian Lohff 5 years ago
commit
12dfa7f3b4

+ 5
- 0
.gitignore View File

@@ -0,0 +1,5 @@
1
+*.pyc
2
+*.pyo
3
+.*.swp
4
+.*.swo
5
+db.sqlite3

+ 0
- 0
bgpdata/__init__.py View File


+ 12
- 0
bgpdata/admin.py View File

@@ -0,0 +1,12 @@
1
+from django.contrib import admin
2
+from bgpdata.models import ConfigHost, CrawlRun, CrawlLog, AS, BorderRouter, Announcement, Peering, BorderRouterPair
3
+
4
+# Register your models here.
5
+admin.site.register(ConfigHost)
6
+admin.site.register(CrawlRun)
7
+admin.site.register(CrawlLog)
8
+admin.site.register(AS)
9
+admin.site.register(BorderRouter)
10
+admin.site.register(Announcement)
11
+admin.site.register(Peering)
12
+admin.site.register(BorderRouterPair)

+ 97
- 0
bgpdata/migrations/0001_initial.py View File

@@ -0,0 +1,97 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+    ]
11
+
12
+    operations = [
13
+        migrations.CreateModel(
14
+            name='Announcement',
15
+            fields=[
16
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17
+                ('ip', models.GenericIPAddressField()),
18
+                ('prefix', models.IntegerField()),
19
+                ('ASPath', models.CharField(max_length=512)),
20
+                ('nextHop', models.GenericIPAddressField()),
21
+            ],
22
+            options={
23
+            },
24
+            bases=(models.Model,),
25
+        ),
26
+        migrations.CreateModel(
27
+            name='AS',
28
+            fields=[
29
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
30
+                ('number', models.IntegerField()),
31
+            ],
32
+            options={
33
+            },
34
+            bases=(models.Model,),
35
+        ),
36
+        migrations.CreateModel(
37
+            name='BorderRouter',
38
+            fields=[
39
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
40
+                ('pingable', models.BooleanField(default=False)),
41
+                ('reachable', models.BooleanField(default=False)),
42
+                ('AS', models.ForeignKey(to='bgpdata.AS')),
43
+            ],
44
+            options={
45
+            },
46
+            bases=(models.Model,),
47
+        ),
48
+        migrations.CreateModel(
49
+            name='ConfigHost',
50
+            fields=[
51
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
52
+                ('number', models.IntegerField()),
53
+                ('ip', models.GenericIPAddressField()),
54
+                ('checkMethod', models.CharField(max_length=4, choices=[(b'CMK', b'Check MK'), (b'PLAIN', b'Plain')])),
55
+            ],
56
+            options={
57
+            },
58
+            bases=(models.Model,),
59
+        ),
60
+        migrations.CreateModel(
61
+            name='CrawlRun',
62
+            fields=[
63
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
64
+                ('startTime', models.DateTimeField()),
65
+                ('endTime', models.DateTimeField(blank=True)),
66
+            ],
67
+            options={
68
+            },
69
+            bases=(models.Model,),
70
+        ),
71
+        migrations.CreateModel(
72
+            name='Peering',
73
+            fields=[
74
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
75
+                ('origin', models.CharField(max_length=10, choices=[(b'path', b'BGP Path'), (b'direct', b'Direct Connection')])),
76
+                ('as1', models.ForeignKey(related_name='peering1', to='bgpdata.AS')),
77
+                ('as2', models.ForeignKey(related_name='peering2', to='bgpdata.AS')),
78
+                ('router1', models.ForeignKey(related_name='peering1', default=None, to='bgpdata.BorderRouter', null=True)),
79
+                ('router2', models.ForeignKey(related_name='peering2', default=None, to='bgpdata.BorderRouter', null=True)),
80
+            ],
81
+            options={
82
+            },
83
+            bases=(models.Model,),
84
+        ),
85
+        migrations.AddField(
86
+            model_name='as',
87
+            name='crawl',
88
+            field=models.ForeignKey(to='bgpdata.CrawlRun'),
89
+            preserve_default=True,
90
+        ),
91
+        migrations.AddField(
92
+            model_name='announcement',
93
+            name='originAS',
94
+            field=models.ForeignKey(to='bgpdata.AS'),
95
+            preserve_default=True,
96
+        ),
97
+    ]

+ 20
- 0
bgpdata/migrations/0002_confighost_name.py View File

@@ -0,0 +1,20 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0001_initial'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='confighost',
16
+            name='name',
17
+            field=models.CharField(default='<undef>', max_length=50),
18
+            preserve_default=False,
19
+        ),
20
+    ]

+ 28
- 0
bgpdata/migrations/0003_crawllog.py View File

@@ -0,0 +1,28 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0002_confighost_name'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.CreateModel(
15
+            name='CrawlLog',
16
+            fields=[
17
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
+                ('logtime', models.DateTimeField(auto_now_add=True)),
19
+                ('severity', models.CharField(max_length=10, choices=[(b'INFO', b'info'), (b'ERROR', b'error'), (b'DEBUG', b'debug'), (b'WARN', b'warning')])),
20
+                ('message', models.TextField()),
21
+                ('crawl', models.ForeignKey(to='bgpdata.CrawlRun')),
22
+                ('host', models.ForeignKey(to='bgpdata.ConfigHost')),
23
+            ],
24
+            options={
25
+            },
26
+            bases=(models.Model,),
27
+        ),
28
+    ]

+ 20
- 0
bgpdata/migrations/0004_auto_20150321_1607.py View File

@@ -0,0 +1,20 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0003_crawllog'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AlterField(
15
+            model_name='crawlrun',
16
+            name='endTime',
17
+            field=models.DateTimeField(null=True, blank=True),
18
+            preserve_default=True,
19
+        ),
20
+    ]

+ 20
- 0
bgpdata/migrations/0005_auto_20150321_1608.py View File

@@ -0,0 +1,20 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0004_auto_20150321_1607'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AlterField(
15
+            model_name='crawllog',
16
+            name='host',
17
+            field=models.ForeignKey(to='bgpdata.ConfigHost', null=True),
18
+            preserve_default=True,
19
+        ),
20
+    ]

+ 26
- 0
bgpdata/migrations/0006_auto_20150321_1845.py View File

@@ -0,0 +1,26 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0005_auto_20150321_1608'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.AddField(
15
+            model_name='borderrouter',
16
+            name='routerID',
17
+            field=models.GenericIPAddressField(default=None),
18
+            preserve_default=False,
19
+        ),
20
+        migrations.AlterField(
21
+            model_name='crawllog',
22
+            name='host',
23
+            field=models.ForeignKey(blank=True, to='bgpdata.ConfigHost', null=True),
24
+            preserve_default=True,
25
+        ),
26
+    ]

+ 45
- 0
bgpdata/migrations/0007_auto_20150322_1828.py View File

@@ -0,0 +1,45 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0006_auto_20150321_1845'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.CreateModel(
15
+            name='BorderRouterPair',
16
+            fields=[
17
+                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
18
+                ('router1', models.ForeignKey(related_name='routerpair1', default=None, blank=True, to='bgpdata.BorderRouter', null=True)),
19
+                ('router2', models.ForeignKey(related_name='routerpair2', default=None, blank=True, to='bgpdata.BorderRouter', null=True)),
20
+            ],
21
+            options={
22
+            },
23
+            bases=(models.Model,),
24
+        ),
25
+        migrations.RemoveField(
26
+            model_name='peering',
27
+            name='router1',
28
+        ),
29
+        migrations.RemoveField(
30
+            model_name='peering',
31
+            name='router2',
32
+        ),
33
+        migrations.AddField(
34
+            model_name='announcement',
35
+            name='router',
36
+            field=models.ForeignKey(default=None, to='bgpdata.BorderRouter'),
37
+            preserve_default=False,
38
+        ),
39
+        migrations.AddField(
40
+            model_name='peering',
41
+            name='routers',
42
+            field=models.ManyToManyField(default=None, to='bgpdata.BorderRouterPair', null=True, blank=True),
43
+            preserve_default=True,
44
+        ),
45
+    ]

+ 24
- 0
bgpdata/migrations/0008_auto_20150322_1906.py View File

@@ -0,0 +1,24 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.db import models, migrations
5
+
6
+
7
+class Migration(migrations.Migration):
8
+
9
+    dependencies = [
10
+        ('bgpdata', '0007_auto_20150322_1828'),
11
+    ]
12
+
13
+    operations = [
14
+        migrations.RemoveField(
15
+            model_name='peering',
16
+            name='routers',
17
+        ),
18
+        migrations.AddField(
19
+            model_name='borderrouterpair',
20
+            name='peering',
21
+            field=models.ForeignKey(default=None, to='bgpdata.Peering'),
22
+            preserve_default=False,
23
+        ),
24
+    ]

+ 0
- 0
bgpdata/migrations/__init__.py View File


+ 142
- 0
bgpdata/models.py View File

@@ -0,0 +1,142 @@
1
+from django.db import models
2
+from django.db.models import Q
3
+
4
+
5
+# Create your models here.
6
+class ConfigHost(models.Model):
7
+	CHECK_CHOICES = (
8
+		('CMK', "Check MK"),
9
+		('PLAIN', "Plain"),
10
+	)
11
+
12
+	# asno, ip, check method,
13
+	name = models.CharField(max_length=50)
14
+	number = models.IntegerField()
15
+	ip = models.GenericIPAddressField()
16
+	checkMethod = models.CharField(max_length=4, choices=CHECK_CHOICES)
17
+
18
+	def __unicode__(self):
19
+		return u"%s (%s / %s)" % (self.name, self.number, self.ip)
20
+
21
+class CrawlRun(models.Model):
22
+	# time start, time end, 
23
+	startTime = models.DateTimeField()
24
+	endTime = models.DateTimeField(null=True, blank=True)
25
+
26
+	def __unicode__(self):
27
+		return u"Run %d - %s to %s" % (self.pk, self.startTime, self.endTime if self.endTime else "?")
28
+
29
+class CrawlLog(models.Model):
30
+	INFO = 'INFO'
31
+	ERROR = 'ERROR'
32
+	DEBUG = 'DEBUG'
33
+	WARN = 'WARN'
34
+	SEVERITY = (
35
+		(INFO, 'info'),
36
+		(ERROR, 'error'),
37
+		(DEBUG, 'debug'),
38
+		(WARN, 'warning'),
39
+	)
40
+
41
+	crawl = models.ForeignKey(CrawlRun)
42
+	host = models.ForeignKey(ConfigHost, null=True, blank=True)
43
+	logtime = models.DateTimeField(auto_now_add=True)
44
+	severity = models.CharField(max_length=10, choices=SEVERITY)
45
+	message = models.TextField()
46
+
47
+	@staticmethod
48
+	def log(crawl, msg, severity=None, host=None):
49
+		if not severity:
50
+			severity = CrawlLog.ERROR
51
+
52
+		log = CrawlLog()
53
+		log.crawl = crawl
54
+		log.message = msg
55
+		log.severity = severity
56
+		log.host = host
57
+		log.save()
58
+
59
+	def __unicode__(self):
60
+		host = "host %s - " % self.host.name if self.host else ""
61
+		return u"Log %s %s: %s%s" % (self.get_severity_display(), self.logtime, host, self.message)
62
+
63
+class AS(models.Model):
64
+	# asno
65
+	crawl = models.ForeignKey(CrawlRun)
66
+	number = models.IntegerField()
67
+
68
+	def __unicode__(self):
69
+		return u"AS %s (crawl %d)" % (self.number, self.crawl.pk)
70
+
71
+	def getPeerings(self):
72
+		return Peering.objects.filter(Q(as1=self)|Q(as2=self))
73
+
74
+class BorderRouter(models.Model):
75
+	# as id, ip, check method, pingable, reachable
76
+	# unique: (crawl_id, asno, as id)
77
+	AS = models.ForeignKey(AS)
78
+	routerID = models.GenericIPAddressField()
79
+	
80
+	pingable = models.BooleanField(default=False)
81
+	reachable = models.BooleanField(default=False)
82
+
83
+	def __unicode__(self):
84
+		p = "p" if self.pingable else "!p"
85
+		r = "r" if self.reachable else "!r"
86
+		return u"Router %s (AS %s, %s%s)" % (self.routerID, self.AS.number, p, r)
87
+
88
+class Announcement(models.Model):
89
+	router = models.ForeignKey(BorderRouter)
90
+
91
+	ip = models.GenericIPAddressField()
92
+	prefix = models.IntegerField()
93
+
94
+	# NOTE: increase length for longer pathes (currently supports a length of ~85)
95
+	ASPath = models.CharField(max_length=512)
96
+	nextHop = models.GenericIPAddressField()
97
+	originAS = models.ForeignKey(AS)
98
+
99
+	def __unicode__(self):
100
+		return u"%s/%s via %s (crawl %s)" % (self.ip, self.prefix, self.ASPath, self.router.AS.crawl.pk)
101
+
102
+class Peering(models.Model):
103
+	DIRECT = 'direct'
104
+	PATH = 'path'
105
+
106
+	ORIGIN = (
107
+		(PATH, 'BGP Path'),
108
+		(DIRECT, 'Direct Connection'),
109
+	)
110
+
111
+	as1 =  models.ForeignKey(AS, related_name='peering1')
112
+	as2 = models.ForeignKey(AS, related_name='peering2')
113
+	origin = models.CharField(max_length=10, choices=ORIGIN)
114
+
115
+	def __unicode__(self):
116
+		return u"AS %s <--> AS %s (%s, crawl %s)" % (self.as1.number, self.as2.number, self.get_origin_display(), self.as1.crawl.pk)
117
+
118
+	def containsAS(self, AS):
119
+		return AS in (self.as1, self.as2)
120
+
121
+	@staticmethod
122
+	def getPeering(as1, as2):
123
+		""" Find matching peering """
124
+		try:
125
+			return Peering.objects.get(as1=as1, as2=as2)
126
+		except Peering.DoesNotExist:
127
+			return Peering.objects.get(as1=as2, as2=as1)
128
+
129
+class BorderRouterPair(models.Model):
130
+	peering = models.ForeignKey(Peering)
131
+	router1 = models.ForeignKey(BorderRouter, default=None, blank=True, null=True, related_name='routerpair1')
132
+	router2 = models.ForeignKey(BorderRouter, default=None, blank=True, null=True, related_name='routerpair2')
133
+
134
+	def __unicode__(self):
135
+		return u"%s <--> %s (crawl %d)" % (self.router1, self.router2, self.router1.AS.crawl.pk)
136
+
137
+	@staticmethod
138
+	def getPairing(peering, router1, router2):
139
+		try:
140
+			return BorderRouterPair.objects.get(peering=peering, router1=router1, router2=router2)
141
+		except BorderRouterPair.DoesNotExist:
142
+			return BorderRouterPair.objects.get(peering=peering, router1=router2, router2=router1)

+ 3
- 0
bgpdata/tests.py View File

@@ -0,0 +1,3 @@
1
+from django.test import TestCase
2
+
3
+# Create your tests here.

+ 3
- 0
bgpdata/views.py View File

@@ -0,0 +1,3 @@
1
+from django.shortcuts import render
2
+
3
+# Create your views here.

+ 155
- 0
bin/crawl.py View File

@@ -0,0 +1,155 @@
1
+#!/usr/bin/env python2
2
+from __future__ import print_function
3
+
4
+# prepare environment
5
+import sys
6
+sys.path.append("..")
7
+import os
8
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnmapper.settings")
9
+import django
10
+django.setup()
11
+
12
+from django.utils import timezone
13
+from django.db.models import Q
14
+
15
+from bgpdata.models import ConfigHost, CrawlRun, CrawlLog, AS, BorderRouter, Announcement, Peering, BorderRouterPair
16
+from routerparsers import getBGPData, RouterParserException
17
+
18
+
19
+def getOrCreateAS(crawl, number):
20
+	currAS = None
21
+	try:
22
+		currAS = AS.objects.get(crawl=crawl, number=number)
23
+	except AS.DoesNotExist:
24
+		currAS = AS(crawl=crawl, number=number)
25
+		currAS.save()
26
+
27
+	return currAS
28
+
29
+def main():
30
+	# 1. create crawl run
31
+	crawl = CrawlRun()
32
+	crawl.startTime = timezone.now()
33
+	crawl.save()
34
+
35
+	CrawlLog.log(crawl, "Starting crawl run!", severity=CrawlLog.INFO)
36
+
37
+	# 2. get data from all hosts, put it in the database
38
+	for host in ConfigHost.objects.all():
39
+		data = None
40
+		print(" -- Getting data for host %s" % host)
41
+		try:
42
+			if host.checkMethod == 'CMK':
43
+				data = getBGPData(host.ip, host.number)
44
+			else:
45
+				CrawlLog.log(crawl, "Method %s is not currently supported, skipping host" % host.checkMethod, host=host, severity=CrawlLog.ERROR)
46
+				continue
47
+		except RouterParserException as e:
48
+			msg = "Could not parse data for host: %s" % str(e)
49
+			print("%s: %s" % (host, msg))
50
+			CrawlLog.log(crawl, msg, host=host, severity=CrawlLog.ERROR)
51
+			continue
52
+
53
+		print(" -- parsing...")
54
+
55
+		currASno = int(data["local_as"])
56
+		currAS = getOrCreateAS(crawl, currASno)
57
+
58
+		currRouter = None
59
+		try:
60
+			currRouter = BorderRouter.objects.get(AS=currAS, routerID=data["local_id"])
61
+			currRouter.pingable = True
62
+			currRouter.reachable = True
63
+			currRouter.save()
64
+		except BorderRouter.DoesNotExist:
65
+			currRouter = BorderRouter(AS=currAS, routerID=data["local_id"], pingable=True, reachable=True)
66
+			currRouter.save()
67
+
68
+		print(" --> peers")
69
+		for peer in data["peers"]:
70
+			# peerings
71
+			# data: BGP{state, neighbor_id, neighbor_as}, description
72
+
73
+			# a) find/create neighbor
74
+			print(" ----> Peer:", int(peer["BGP"]["neighbor_as"]))
75
+			neighAS = None
76
+			try:
77
+				neighAS = AS.objects.get(crawl=crawl, number=int(peer["BGP"]["neighbor_as"]))
78
+			except AS.DoesNotExist:
79
+				neighAS = AS(crawl=crawl, number=int(peer["BGP"]["neighbor_as"]))
80
+				neighAS.save()
81
+
82
+			# b) find out if a peering already exists (maybe where we only need to add our router id?)
83
+			peering = None
84
+			try:
85
+				peering = Peering.getPeering(currAS, neighAS)
86
+			except Peering.DoesNotExist:
87
+				peering = Peering(as1=currAS, as2=neighAS, origin=Peering.DIRECT)
88
+				peering.save()
89
+
90
+			# c) look for router/peering pairs
91
+			if peer["BGP"]["neighbor_id"]:
92
+				try:
93
+					neighRouter = BorderRouter.objects.get(AS=neighAS, routerID=peer["BGP"]["neighbor_id"])
94
+				except BorderRouter.DoesNotExist:
95
+					neighRouter = BorderRouter(AS=currAS, routerID=peer["BGP"]["neighbor_id"], pingable=False, reachable=False)
96
+					neighRouter.save()
97
+				try:
98
+					BorderRouterPair.getPairing(peering, currRouter, neighRouter)
99
+				except BorderRouterPair.DoesNotExist:
100
+					pairs = BorderRouterPair.objects.filter(Q(peering=peering) & (Q(router1=neighRouter, router2=None)|Q(router1=None, router2=neighRouter)))
101
+					if pairs.count() > 0:
102
+						pair = pairs[0]
103
+						if pair.router1 == None:
104
+							pair.router1 = currRouter
105
+						else:
106
+							pair.router2 = currRouter
107
+						pair.save()
108
+					else:
109
+						pair = BorderRouterPair(peering=peering, router1=currRouter, router2=neighRouter)
110
+						pair.save()
111
+
112
+		print(" --> Announcements")
113
+		if "routes" in data and data["routes"]:
114
+			for route in data["routes"]:
115
+				print(" ---->", route["prefix"])
116
+				if "/" not in route["prefix"]:
117
+					continue
118
+				ip, prefix = route["prefix"].split("/")
119
+				a = Announcement(router=currRouter, ip=ip, prefix=prefix,
120
+									ASPath=" ".join(route["path"]), nextHop=route["nexthop"],
121
+									originAS=currAS)
122
+				a.save()
123
+		else:
124
+			print(" !! No routes found in host output")
125
+			CrawlLog.log(crawl, "No routes found in host output (no bgp feed included?)", host=host, severity=CrawlLog.WARN)
126
+
127
+	# 3. calculate missing data
128
+	print(" -- Adding extra data from announcements...")
129
+	# 3.1. use announcement data to find hidden peerings
130
+	for announcement in Announcement.objects.filter(router__AS__crawl=crawl):
131
+		path = announcement.ASPath.split(" ")
132
+		if len(path) > 1:
133
+			firstASno = path.pop(0)
134
+			firstAS = getOrCreateAS(crawl, firstASno)
135
+			while len(path) > 0:
136
+				secondASno = path.pop(0)
137
+				secondAS = getOrCreateAS(crawl, secondASno)
138
+
139
+				try:
140
+					Peering.getPeering(firstAS, secondAS)
141
+				except Peering.DoesNotExist:
142
+					peering = Peering(as1=firstAS, as2=secondAS, origin=Peering.PATH)
143
+					peering.save()
144
+
145
+				firstAS = secondAS
146
+
147
+	# 4. end crawl run
148
+	crawl.endTime = timezone.now()
149
+	crawl.save()
150
+
151
+	print(" !! Done")
152
+	CrawlLog.log(crawl, "Crawl completed", severity=CrawlLog.INFO)
153
+
154
+if __name__ == '__main__':
155
+	main()

+ 340
- 0
bin/routerparsers.py View File

@@ -0,0 +1,340 @@
1
+#!/usr/bin/env python
2
+
3
+from __future__ import print_function
4
+
5
+import re
6
+import socket
7
+
8
+from collections import OrderedDict
9
+
10
+class RouterParserException(Exception):
11
+	pass
12
+
13
+def err(msg):
14
+	raise RouterParserException(msg)
15
+
16
+
17
+def getBGPData(ip, asno):
18
+	rawData = getDataFromHost(ip)
19
+	if not rawData:
20
+		err("Could not get data from host (empty response)")
21
+
22
+	router = parseBGPData(rawData, asno)
23
+
24
+	router["ip"] = ip
25
+
26
+	return router
27
+
28
+def getDataFromHost(ip):
29
+	socket.setdefaulttimeout(5)
30
+	x = socket.socket()
31
+	x.connect((ip, 6556))
32
+	f = x.makefile()
33
+	data = f.read()
34
+	x.close()
35
+
36
+	return data
37
+
38
+def parseBGPData(raw, asno):
39
+	d = re.search(r"(?:^|\n)<<<(quagga|bird)>>>\n(.*?)(?:$|<<<[^\n]+>>>)", raw, re.DOTALL)
40
+
41
+	if not d:
42
+		err("Data not found in check mk output")
43
+
44
+	# mkify
45
+	raw = d.group(2).split("\n")
46
+	arr = filter(lambda _z: _z, map(lambda _y: filter(lambda _x: _x, re.split(r"\s+", _y)), raw))
47
+
48
+	# parse for bird/quagga
49
+	result = None
50
+
51
+	if d.group(1) == "quagga":
52
+		result = parseQuagga(arr, raw, asno)
53
+	else:
54
+		result = parseBird(arr, raw, asno)
55
+
56
+	return result
57
+
58
+
59
+def parseQuagga(data, raw, asno):
60
+	status = _quaggaFindCommand(data, "show ip bgp sum")
61
+	if status[0][0:3] != ['BGP', 'router', 'identifier']:
62
+		print(status)
63
+		err("Couldn't find router id in quagga output")
64
+
65
+	peers = _quaggaFindNeighbors(data)
66
+	if asno and int(asno) != int(status[0][7]):
67
+		err("AS number (%s) does not match as number from quagga (%s)" % (asno, status[0][7]))
68
+
69
+	routes = _quaggaFindRoutes(raw)
70
+
71
+	return {"local_id": status[0][3].strip(","), "local_as": int(status[0][7]), "peers": peers, "routes": routes}
72
+
73
+def parseBird(data, raw, asno):
74
+	status = _birdFindTable(data, "show status")
75
+	if status[2][0] != "1011-Router":
76
+		err("Couldn't find router id in bird output")
77
+	peers = filter(lambda _x: _x["type"] == "BGP", _birdMakeProtocols(data))
78
+
79
+	if asno == None:
80
+		err("Host is bird")
81
+		# FIXME
82
+
83
+	routes = _birdFindRoutes(data)
84
+
85
+	return {"local_id": status[2][3], "local_as": int(asno), "peers": peers, "routes": routes}
86
+
87
+def _birdFindTable(info, command):
88
+	""" find command output of a bird command, e.g. "show bgp neighbors" """
89
+	command = ["bird>"] + command.split(" ")
90
+	commandInfo = []
91
+	editNextLine = False
92
+	for line in info:
93
+		if not commandInfo:
94
+			if line == command:
95
+				commandInfo.append(line)
96
+				editNextLine = True
97
+		else:
98
+			if editNextLine:
99
+				editNextLine = False
100
+				commandInfo.append(line[1:])
101
+			elif line[0] == "bird>":
102
+				return commandInfo
103
+			else:
104
+				commandInfo.append(line)
105
+	return []
106
+
107
+def _birdFindProtocols(info):
108
+	""" return a list of tuples (protoname, protoinfo) """
109
+	protocolTable = _birdFindTable(info, "show protocols all")
110
+	protocols = OrderedDict()
111
+	currProto = None
112
+	for line in protocolTable[2:]:
113
+		if line[0][0:4] == "1002":
114
+			currProto = line[0][5:]
115
+			protocols[currProto] = [[currProto] + line[1:]]
116
+		elif currProto == None:
117
+			err("No proto selected, couldn't parse line:", line)
118
+		else:
119
+			protocols[currProto].append(line)
120
+
121
+	return protocols
122
+
123
+def _birdMakeProtocols(info):
124
+	""" Parse birds show protocols all output """
125
+	# proto: name, type, description, state (up/down?), up-since
126
+	#		routes imported, exported, preferred
127
+	#		also: routing stats (
128
+	# bgp special stuff: state, neighbor (address, as, id) (id not available when down)
129
+	#   state (established, active)
130
+	#   if error, last error is avilable
131
+	protocols = []
132
+	for proto, data in _birdFindProtocols(info).iteritems():
133
+		protoInfo = {
134
+			"name": proto,
135
+			"type": data[0][1],
136
+			"table": data[0][2],
137
+			"state": data[0][3],
138
+			"last_change": data[0][4],
139
+			"info": " ".join(data[0][5:]),
140
+			"description": " ".join(data[1][2:]),
141
+			"routes": {
142
+				"imported": data[5][1],
143
+				"exported": data[5][3],
144
+				"preferred": data[5][5],
145
+			}
146
+		}
147
+		if protoInfo["type"] == "BGP":
148
+			found = False
149
+			for n, line in enumerate(data):
150
+				if line[0:2] == ["BGP", "state:"]:
151
+					found = True
152
+					protoInfo["BGP"] = {
153
+						"state": data[n][2],
154
+						"neighbor_address": data[n+1][2],
155
+						"neighbor_as": int(data[n+2][2]),
156
+						"neighbor_id": data[n+3][2] if data[n+3][0:2] == ["Neighbor", "ID:"] else None,
157
+						"last_error": " ".join(data[n+3][2:]) if data[n+3][0:2] == ["Last", "error:"] else None,
158
+					}
159
+
160
+			if not found:
161
+				protoInfo["BGP"] = None
162
+
163
+		protocols.append(protoInfo)
164
+
165
+	return protocols
166
+
167
+
168
+def _birdFindRoutes(info):
169
+	output = _birdFindTable(info, "show route all")
170
+	if len(output) < 1:
171
+		# no data found
172
+		return None
173
+
174
+	def handleCandidate(routes, candidate):
175
+		if candidate:
176
+			# path, nexthop, network
177
+			for key in ["path", "nexthop", "network", "iBGP"]:
178
+				if key not in candidate:
179
+					return
180
+			route = {"prefix": candidate["network"], "nexthop": candidate["nexthop"], "path": candidate["path"], "iBGP": candidate["iBGP"]}
181
+			routes.append(route)
182
+			pass
183
+
184
+	routes = []
185
+	candidate = None
186
+	lastIP = None
187
+	for line in output:
188
+		if line[0].startswith("1007-"):
189
+			# new route!
190
+			handleCandidate(routes, candidate)
191
+			if line[0] != "1007-":
192
+				# line has a network, use it!
193
+				lastIP = line[0][5:]
194
+			candidate = {"network": lastIP, "iBGP": None}
195
+
196
+		elif candidate is not None:
197
+			# search bgp attributes
198
+			if line[0] == "1012-":
199
+				pass
200
+				k, v = line[1], line[2:]
201
+			else:
202
+				k, v = line[0], line[1:]
203
+
204
+			k = k.rstrip(":")
205
+			if k == "BGP.next_hop":
206
+				candidate["nexthop"] = v[0]
207
+			elif k == "BGP.as_path":
208
+				candidate["path"] = v
209
+
210
+
211
+	handleCandidate(routes, candidate)
212
+
213
+	return routes
214
+
215
+
216
+def _quaggaFindCommand(info, cmd):
217
+	# ['core-frunde#', 'show', 'ip', 'bgp', 'sum']
218
+	# ['core-frunde#', 'show', 'ip', 'bgp', 'neighbors']
219
+	output = []
220
+	cmd = cmd.split(" ")
221
+	prompt = None
222
+	for line in info:
223
+		if line[1:] == cmd:
224
+			prompt = line[0]
225
+		elif line[0] == prompt:
226
+			# done
227
+			return output
228
+		elif prompt != None:
229
+			output.append(line)
230
+
231
+	err("Could not find command '%s' in output" % " ".join(cmd))
232
+
233
+def _quaggaFindNeighbors(info):
234
+	#['BGP', 'neighbor', 'is', '10.50.1.2,', 'remote', 'AS', '65001,', 'local', 'AS', '65001,', 'internal', 'link']
235
+	output = _quaggaFindCommand(info, "show ip bgp neighbors")
236
+	start = ["BGP", "neighbor", "is"]
237
+
238
+	curr = None
239
+	rawNeighbors = []
240
+	for line in output:
241
+		if line[0:3] == start:
242
+			if curr:
243
+				rawNeighbors.append(curr)
244
+			curr = [line]
245
+		elif curr:
246
+			curr.append(line)
247
+		else:
248
+			err("Could not find start of neighbors")
249
+
250
+	if curr:
251
+		rawNeighbors.append(curr)
252
+		curr = None
253
+
254
+	neighbors = []
255
+	neighborDict = OrderedDict()
256
+	for raw in rawNeighbors:
257
+		descrIdx = 1 if raw[1][0] == "Description:" else 0
258
+		peerdict = {
259
+			"neighbor_address": raw[0][3].rstrip(","),
260
+			"neighbor_as": int(raw[0][6].rstrip(",")),
261
+			"local_as": int(raw[0][9].rstrip(",")),
262
+			"description": " ".join(raw[1][1:]) if descrIdx else "No description",
263
+			"neighbor_id": raw[1+descrIdx][6].strip(","),
264
+			"state": raw[2+descrIdx][3].strip(","),
265
+			"routes": {
266
+				"imported": 0,
267
+			},
268
+			"BGP": {
269
+				"state": raw[2+descrIdx][3].strip(","),
270
+				"neighbor_id": raw[1+descrIdx][6].strip(","),
271
+				"neighbor_address": raw[0][3].rstrip(","),
272
+				"neighbor_as": int(raw[0][6].rstrip(",")),
273
+				"state": raw[2+descrIdx][3].strip(","),
274
+			},
275
+		}
276
+
277
+		for line in raw:
278
+			if line[1:3] == ["accepted", "prefixes"]:
279
+				# woooo
280
+				peerdict["routes"]["imported"] = int(line[0])
281
+				break
282
+
283
+		neighbors.append(peerdict)
284
+		neighborDict[peerdict["neighbor_address"]] = peerdict
285
+
286
+	return neighbors
287
+
288
+def _quaggaFindRoutes(raw):
289
+	# from # show ip bgp to Total number of prefixes XX
290
+	# BGP table version is 0, local router ID is 10.50.0.1
291
+	# *> 10.3.14.0/27     10.75.0.22                             0 65002 65112 i
292
+	cmdre = re.compile(r"^([^\s#]+#) show ip bgp$")
293
+	routere = re.compile(r"^(?P<status>.)(?P<status2>.)(?P<origin>.)(?P<network>[0-9./]+)?\s+(?P<nexthop>[0-9./]+)[\s0-9i]+$")
294
+
295
+	# find output
296
+	output = []
297
+	prompt = None
298
+	for line in raw:
299
+		if not prompt:
300
+			m = cmdre.match(line)
301
+			if m:
302
+				prompt = m.group(1) + " "
303
+		else:
304
+			if line.startswith(prompt):
305
+				break
306
+			else:
307
+				output.append(line)
308
+
309
+	if len(output) < 1:
310
+		# no data found
311
+		return None
312
+
313
+	routes = []
314
+	foundTable = False
315
+	lastIP = None
316
+	for line in output:
317
+		if not foundTable:
318
+			if line.endswith("Metric LocPrf Weight Path"):
319
+				foundTable = True
320
+		else:
321
+			if line != '':
322
+				if line.startswith("Total number of prefixes"):
323
+					break
324
+				else:
325
+					# parse one route line
326
+					#print(line)
327
+					m = routere.match(line)
328
+					d = m.groupdict()
329
+					if d["network"]:
330
+						lastIP = d["network"]
331
+					else:
332
+						d["network"] = lastIP
333
+
334
+					# "parse" path (everything after 61 chars, but no i)
335
+					path = filter(lambda _x: _x not in ('', 'i'), line[61:].split(" "))
336
+
337
+					route = {"prefix": d["network"], "nexthop": d["nexthop"], "path": path, "iBGP": d["origin"] == "i"}
338
+					routes.append(route)
339
+
340
+	return routes

+ 0
- 0
dnmapper/__init__.py View File


+ 84
- 0
dnmapper/settings.py View File

@@ -0,0 +1,84 @@
1
+"""
2
+Django settings for dnmapper project.
3
+
4
+For more information on this file, see
5
+https://docs.djangoproject.com/en/1.7/topics/settings/
6
+
7
+For the full list of settings and their values, see
8
+https://docs.djangoproject.com/en/1.7/ref/settings/
9
+"""
10
+
11
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
12
+import os
13
+BASE_DIR = os.path.dirname(os.path.dirname(__file__))
14
+
15
+
16
+# Quick-start development settings - unsuitable for production
17
+# See https://docs.djangoproject.com/en/1.7/howto/deployment/checklist/
18
+
19
+# SECURITY WARNING: keep the secret key used in production secret!
20
+SECRET_KEY = 'crv*hfx2pkxvq1s!)dbz*hdu+r7u2$y4djf6_#6mm)shk9e!58'
21
+
22
+# SECURITY WARNING: don't run with debug turned on in production!
23
+DEBUG = True
24
+
25
+TEMPLATE_DEBUG = True
26
+
27
+ALLOWED_HOSTS = []
28
+
29
+
30
+# Application definition
31
+
32
+INSTALLED_APPS = (
33
+    'django.contrib.admin',
34
+    'django.contrib.auth',
35
+    'django.contrib.contenttypes',
36
+    'django.contrib.sessions',
37
+    'django.contrib.messages',
38
+    'django.contrib.staticfiles',
39
+	'bgpdata',
40
+)
41
+
42
+MIDDLEWARE_CLASSES = (
43
+    'django.contrib.sessions.middleware.SessionMiddleware',
44
+    'django.middleware.common.CommonMiddleware',
45
+    'django.middleware.csrf.CsrfViewMiddleware',
46
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
47
+    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
48
+    'django.contrib.messages.middleware.MessageMiddleware',
49
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
50
+)
51
+
52
+ROOT_URLCONF = 'dnmapper.urls'
53
+
54
+WSGI_APPLICATION = 'dnmapper.wsgi.application'
55
+
56
+
57
+# Database
58
+# https://docs.djangoproject.com/en/1.7/ref/settings/#databases
59
+
60
+DATABASES = {
61
+    'default': {
62
+        'ENGINE': 'django.db.backends.sqlite3',
63
+        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
64
+    }
65
+}
66
+
67
+# Internationalization
68
+# https://docs.djangoproject.com/en/1.7/topics/i18n/
69
+
70
+LANGUAGE_CODE = 'de-de'
71
+
72
+TIME_ZONE = 'CET'
73
+
74
+USE_I18N = True
75
+
76
+USE_L10N = True
77
+
78
+USE_TZ = True
79
+
80
+
81
+# Static files (CSS, JavaScript, Images)
82
+# https://docs.djangoproject.com/en/1.7/howto/static-files/
83
+
84
+STATIC_URL = '/static/'

+ 10
- 0
dnmapper/urls.py View File

@@ -0,0 +1,10 @@
1
+from django.conf.urls import patterns, include, url
2
+from django.contrib import admin
3
+
4
+urlpatterns = patterns('',
5
+    # Examples:
6
+    # url(r'^$', 'dnmapper.views.home', name='home'),
7
+    # url(r'^blog/', include('blog.urls')),
8
+
9
+    url(r'^admin/', include(admin.site.urls)),
10
+)

+ 14
- 0
dnmapper/wsgi.py View File

@@ -0,0 +1,14 @@
1
+"""
2
+WSGI config for dnmapper project.
3
+
4
+It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+For more information on this file, see
7
+https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
8
+"""
9
+
10
+import os
11
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnmapper.settings")
12
+
13
+from django.core.wsgi import get_wsgi_application
14
+application = get_wsgi_application()

+ 10
- 0
manage.py View File

@@ -0,0 +1,10 @@
1
+#!/usr/bin/env python
2
+import os
3
+import sys
4
+
5
+if __name__ == "__main__":
6
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "dnmapper.settings")
7
+
8
+    from django.core.management import execute_from_command_line
9
+
10
+    execute_from_command_line(sys.argv)

Loading…
Cancel
Save