├── .gitignore
├── bgpdiscovery.py
├── bgpmon.py
├── README.md
├── zbx_export_templates.xml
└── RosAPI.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by .ignore support plugin (hsz.mobi)
2 | .idea/
--------------------------------------------------------------------------------
/bgpdiscovery.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from RosAPI import Core
3 | from sys import argv
4 | import json
5 |
6 | try:
7 | a = Core(argv[1])
8 | except:
9 | print("No connection")
10 |
11 | a.login("bgpmonlogin", "bgpmonpass")
12 | peers = a.response_handler(a.talk(["/routing/bgp/peer/print"]))
13 | out = {'data': []}
14 | if len(peers) > 0:
15 | for peer in peers:
16 | out['data'].append({"{#BGPPEER}": peer['remote-address']})
17 |
18 | print(json.dumps(out, indent=4, sort_keys=True))
19 |
--------------------------------------------------------------------------------
/bgpmon.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | from RosAPI import Core
3 | from sys import argv
4 |
5 | try:
6 | a = Core(argv[1])
7 | except:
8 | print("No connection")
9 | a.login("bgpmonlogin", "bgpmonpass")
10 | peers = a.response_handler(a.talk(["/routing/bgp/peer/print"]))
11 | peers_dict = {}
12 | if len(peers) > 0:
13 | for peer in peers:
14 | # print("{} {} {}".format(peer['remote-address'], peer['remote-as'], peer['established']))
15 | peers_dict[peer['remote-address']] = peer['established']
16 |
17 | if argv[2] in peers_dict:
18 | if peers_dict[argv[2]] == 'true':
19 | print(1)
20 | else:
21 | print(0)
22 | else:
23 | print(0)
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Description
2 |
3 | Two scripts and template for monitoring bgp sessions in zabbix.
4 |
5 | Import template to your zabbix instalation.
6 |
7 | Place scripts in zabbix external scripts folder.
8 |
9 |
10 | # Files
11 |
12 | * bgpdiscovery.py - used for returning peers list to zabbix
13 | * bgpmon.py - get bgp session state and return 0 or 1.
14 | * RosAPI - RouterOS API library for python (thx David Jelić and Luka Blašković)
15 |
16 | # Configs
17 |
18 | For change connection credentials you should replace placeholders in function a.login().
19 |
20 | In bgpmon it will be line 9 and in bgpdiscovery it will be line 11.
21 |
22 | # Authors
23 |
24 | * bsod (t1bur1an)
25 |
26 |
--------------------------------------------------------------------------------
/zbx_export_templates.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 2.0
4 | 2016-01-12T14:43:55Z
5 |
6 |
7 | RouterOS
8 |
9 |
10 |
11 |
12 | RouterOS BGP
13 | RouterOS BGP
14 | Template for auto-discovery bgp peers and monitor they.
15 |
16 |
17 | RouterOS
18 |
19 |
20 |
21 |
22 | BGP
23 |
24 |
25 |
26 |
27 |
28 | Bgp discovery
29 | 10
30 |
31 |
32 | bgpdiscovery.py[{HOST.IP}]
33 | 3600
34 | 0
35 |
36 |
37 |
38 | 0
39 | 0
40 |
41 | 0
42 |
43 |
44 |
45 |
46 | 0
47 |
48 |
49 |
50 |
51 |
52 |
53 | 0
54 |
55 |
56 |
57 | 1
58 |
59 |
60 |
61 | Peer {#BGPPEER}
62 | 10
63 |
64 | 0
65 |
66 | bgpmon.py[{HOST.IP}, {#BGPPEER}]
67 | 60
68 | 90
69 | 365
70 | 0
71 | 3
72 |
73 | state
74 | 0
75 |
76 |
77 | 0
78 | 0
79 |
80 | 0
81 |
82 | 1
83 |
84 |
85 |
86 | 0
87 | 0
88 |
89 |
90 |
91 |
92 |
93 |
94 | 0
95 |
96 |
97 | BGP
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | {RouterOS BGP:bgpmon.py[{HOST.IP}, {#BGPPEER}].last()}=0
107 | Bgp sessions {HOST.NAME} with {#BGPPEER} is down
108 |
109 | 0
110 | 2
111 | Bgp session is down
112 | 0
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
--------------------------------------------------------------------------------
/RosAPI.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 | #
4 | # RosAPI.py
5 | #
6 | # Copyright 2010 David Jelić
7 | # Copyright 2010 Luka Blašković
8 | #
9 |
10 | """Python binding for Mikrotik RouterOS API"""
11 | __all__ = ["RosAPICore", "Networking"]
12 |
13 | class Core:
14 | """Core part of Router OS API
15 |
16 | It contains methods necessary to extract raw data from the router.
17 | If object is instanced with DEBUG = True parameter, it runs in verbosity mode.
18 |
19 | Core part is taken mostly from http://wiki.mikrotik.com/wiki/Manual:API#Example_client."""
20 |
21 | def __init__(self, hostname, port=8728, DEBUG=False):
22 | import socket
23 | self.DEBUG = DEBUG
24 | self.hostname = hostname
25 | self.port = port
26 | self.currenttag = 0
27 | self.sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
28 | self.sk.connect((self.hostname, self.port))
29 |
30 | def login(self, username, pwd):
31 | import binascii
32 | from hashlib import md5
33 |
34 | for repl, attrs in self.talk(["/login"]):
35 | chal = binascii.unhexlify(attrs['=ret'])
36 | md = md5()
37 | md.update('\x00')
38 | md.update(pwd)
39 | md.update(chal)
40 | self.talk(["/login", "=name=" + username, "=response=00" + binascii.hexlify(md.digest())])
41 |
42 | def talk(self, words):
43 | if self.writeSentence(words) == 0: return
44 | r = []
45 | while 1:
46 | i = self.readSentence();
47 | if len(i) == 0: continue
48 | reply = i[0]
49 | attrs = {}
50 | for w in i[1:]:
51 | j = w.find('=', 1)
52 | if (j == -1):
53 | attrs[w] = ''
54 | else:
55 | attrs[w[:j]] = w[j+1:]
56 | r.append((reply, attrs))
57 | if reply == '!done': return r
58 |
59 | def writeSentence(self, words):
60 | ret = 0
61 | for w in words:
62 | self.writeWord(w)
63 | ret += 1
64 | self.writeWord('')
65 | return ret
66 |
67 | def readSentence(self):
68 | r = []
69 | while 1:
70 | w = self.readWord()
71 | if w == '': return r
72 | r.append(w)
73 |
74 | def writeWord(self, w):
75 | if self.DEBUG:
76 | print "<<< " + w
77 | self.writeLen(len(w))
78 | self.writeStr(w)
79 |
80 | def readWord(self):
81 | ret = self.readStr(self.readLen())
82 | if self.DEBUG:
83 | print ">>> " + ret
84 | return ret
85 |
86 | def writeLen(self, l):
87 | if l < 0x80:
88 | self.writeStr(chr(l))
89 | elif l < 0x4000:
90 | l |= 0x8000
91 | self.writeStr(chr((l >> 8) & 0xFF))
92 | self.writeStr(chr(l & 0xFF))
93 | elif l < 0x200000:
94 | l |= 0xC00000
95 | self.writeStr(chr((l >> 16) & 0xFF))
96 | self.writeStr(chr((l >> 8) & 0xFF))
97 | self.writeStr(chr(l & 0xFF))
98 | elif l < 0x10000000:
99 | l |= 0xE0000000
100 | self.writeStr(chr((l >> 24) & 0xFF))
101 | self.writeStr(chr((l >> 16) & 0xFF))
102 | self.writeStr(chr((l >> 8) & 0xFF))
103 | self.writeStr(chr(l & 0xFF))
104 | else:
105 | self.writeStr(chr(0xF0))
106 | self.writeStr(chr((l >> 24) & 0xFF))
107 | self.writeStr(chr((l >> 16) & 0xFF))
108 | self.writeStr(chr((l >> 8) & 0xFF))
109 | self.writeStr(chr(l & 0xFF))
110 |
111 | def readLen(self):
112 | c = ord(self.readStr(1))
113 | if (c & 0x80) == 0x00:
114 | pass
115 | elif (c & 0xC0) == 0x80:
116 | c &= ~0xC0
117 | c <<= 8
118 | c += ord(self.readStr(1))
119 | elif (c & 0xE0) == 0xC0:
120 | c &= ~0xE0
121 | c <<= 8
122 | c += ord(self.readStr(1))
123 | c <<= 8
124 | c += ord(self.readStr(1))
125 | elif (c & 0xF0) == 0xE0:
126 | c &= ~0xF0
127 | c <<= 8
128 | c += ord(self.readStr(1))
129 | c <<= 8
130 | c += ord(self.readStr(1))
131 | c <<= 8
132 | c += ord(self.readStr(1))
133 | elif (c & 0xF8) == 0xF0:
134 | c = ord(self.readStr(1))
135 | c <<= 8
136 | c += ord(self.readStr(1))
137 | c <<= 8
138 | c += ord(self.readStr(1))
139 | c <<= 8
140 | c += ord(self.readStr(1))
141 | return c
142 |
143 | def writeStr(self, str):
144 | n = 0;
145 | while n < len(str):
146 | r = self.sk.send(str[n:])
147 | if r == 0: raise RuntimeError, "connection closed by remote end"
148 | n += r
149 |
150 | def readStr(self, length):
151 | ret = ''
152 | while len(ret) < length:
153 | s = self.sk.recv(length - len(ret))
154 | if s == '': raise RuntimeError, "connection closed by remote end"
155 | ret += s
156 | return ret
157 |
158 | def response_handler(self, response):
159 | """Handles API response and remove unnessesary data"""
160 |
161 | # if respons end up successfully
162 | if response[-1][0] == "!done":
163 | r = []
164 | # for each returned element
165 | for elem in response[:-1]:
166 | # if response is valid Mikrotik returns !re, if error !trap
167 | # before each valid element, there is !re
168 | if elem[0] == "!re":
169 | # take whole dictionary of single element
170 | element = elem[1]
171 | # with this loop we strip equals in front of each keyword
172 | for att in element.keys():
173 | element[att[1:]] = element[att]
174 | element.pop(att)
175 | # collect modified data in new array
176 | r.append(element)
177 | return r
178 |
179 | def run_interpreter(self):
180 | import select, sys
181 | inputsentence = []
182 |
183 | while 1:
184 | r = select.select([self.sk, sys.stdin], [], [], None)
185 | if self.sk in r[0]:
186 | # something to read in socket, read sentence
187 | x = self.readSentence()
188 |
189 | if sys.stdin in r[0]:
190 | # read line from input and strip off newline
191 | l = sys.stdin.readline()
192 | l = l[:-1]
193 |
194 | # if empty line, send sentence and start with new
195 | # otherwise append to input sentence
196 | if l == '':
197 | self.writeSentence(inputsentence)
198 | inputsentence = []
199 | else:
200 | inputsentence.append(l)
201 | return 0
202 |
203 | class Networking(Core):
204 | """Handles network part of Mikrotik Router OS
205 |
206 | Contains functions for pulling informations about interfaces,
207 | routes, wireless registrations, etc."""
208 |
209 | def get_all_interfaces(self):
210 | """Pulls out all available data related to network interfaces"""
211 |
212 | word = ["/interface/print"]
213 | response = Core.talk(self, word)
214 | response = Core.response_handler(self, response)
215 | return response
216 |
217 | def test():
218 | tik = Core("172.16.1.1", DEBUG=True)
219 | tik.login("admin", "")
220 | tik.run_interpreter()
221 |
222 | if __name__ == "__main__":
223 | test()
224 |
--------------------------------------------------------------------------------