├── LICENSE
├── README.md
├── apiros.py
├── bgp_peer_field.sh
├── bgp_peer_names.sh
├── vrrp_state.sh
└── zbx_routeros_bgp.xml
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 MrCirca
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Monitoring Mikrotik BGP peer state and uptime on Zabbix
2 |  
3 | Using external script method on Zabbix, i made two scripts, getting values through [Mikrotik API](https://wiki.mikrotik.com/wiki/Manual:API_Python3).
4 | [Mikrotik API(Python 3)](https://wiki.mikrotik.com/wiki/Manual:API_Python3) is interactive. So I modified it to print the values I demand and then it disconnects automatically.
5 |
6 | Files you need to copy to externalscripts path of zabbix server:
7 |
8 | 1) bgp_peer_field.sh Calls the API and prints state or uptime of peer name.
9 | 2) bgp_peer_names.sh Calls the API and prints peer names.
10 | 3) apriros.py Is the Mikrotik API that is not interactive and can get the output you want.
11 |
12 | *Make sure that the files above are executable by zabbix user.*
13 |
14 | You should also import **zbx_routeros_bgp.xml** zabbix template on zabbix server.
15 |
16 |
17 | **Simple script test**:
18 |
19 | ```shell
20 | bgp_peer_filed.sh
21 | ```
22 |
23 | **Zabbix Discovery Key**:
24 |
25 |
26 | bgp_peer_names.sh["{HOST.CONN}","{$ROUTEROS_USERNAME}","{$ROUTEROS_PASSWORD}"]
27 |
28 | *You should define these macros: "{$ROUTEROS_USERNAME}" and "{$ROUTEROS_PASSWORD}"
29 |
30 |
31 | *The reason I created the bgp_peer_names.sh is so that zabbix can discover the peer names and create items for each peer name.*
32 | 
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/apiros.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import sys, posix, time, hashlib, binascii, socket, select, re
4 |
5 | class ApiRos:
6 | "Routeros api"
7 | def __init__(self, sk):
8 | self.sk = sk
9 | self.currenttag = 0
10 |
11 | def login(self, username, pwd):
12 | for repl, attrs in self.talk(["/login"]):
13 | chal = binascii.unhexlify(attrs['=ret'])
14 | md = hashlib.md5()
15 | md.update(b'\x00')
16 | md.update(bytes(pwd, 'utf-8'))
17 | md.update(chal)
18 | self.talk(["/login", "=name=" + username,
19 | "=response=00" + binascii.hexlify(md.digest()).decode('utf-8')])
20 |
21 | def talk(self, words):
22 | if self.writeSentence(words) == 0: return
23 | r = []
24 | while 1:
25 | i = self.readSentence();
26 | if len(i) == 0: continue
27 | reply = i[0]
28 | attrs = {}
29 | for w in i[1:]:
30 | j = w.find('=', 1)
31 | if (j == -1):
32 | attrs[w] = ''
33 | else:
34 | attrs[w[:j]] = w[j+1:]
35 | r.append((reply, attrs))
36 | if reply == '!done': return r
37 |
38 | def writeSentence(self, words):
39 | ret = 0
40 | for w in words:
41 | self.writeWord(w)
42 | ret += 1
43 | self.writeWord('')
44 | return ret
45 |
46 | def readSentence(self):
47 | r = []
48 | while 1:
49 | w = self.readWord()
50 | if w == '': return r
51 | r.append(w)
52 |
53 | def writeWord(self, w):
54 | #print(w)
55 | b = bytes(w, "utf-8")
56 | self.writeLen(len(b))
57 | self.writeBytes(b)
58 |
59 | def readWord(self):
60 | ret = self.readBytes(self.readLen()).decode('utf-8')
61 | if ret != '!done' and not re.match("^=ret=\S*[a-z]\S*", ret) and ret != '!re':
62 | print("%s" % ret)
63 | return ret
64 |
65 | def writeLen(self, l):
66 | if l < 0x80:
67 | self.writeBytes(bytes([l]))
68 | elif l < 0x4000:
69 | l |= 0x8000
70 | self.writeBytes(bytes([(l >> 8) & 0xff, l & 0xff]))
71 | elif l < 0x200000:
72 | l |= 0xC00000
73 | self.writeBytes(bytes([(l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
74 | elif l < 0x10000000:
75 | l |= 0xE0000000
76 | self.writeBytes(bytes([(l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
77 | else:
78 | self.writeBytes(bytes([0xf0, (l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff]))
79 |
80 | def readLen(self):
81 | c = self.readBytes(1)[0]
82 | if (c & 0x80) == 0x00:
83 | pass
84 | elif (c & 0xC0) == 0x80:
85 | c &= ~0xC0
86 | c <<= 8
87 | c += self.readBytes(1)[0]
88 | elif (c & 0xE0) == 0xC0:
89 | c &= ~0xE0
90 | c <<= 8
91 | c += self.readBytes(1)[0]
92 | c <<= 8
93 | c += self.readBytes(1)[0]
94 | elif (c & 0xF0) == 0xE0:
95 | c &= ~0xF0
96 | c <<= 8
97 | c += self.readBytes(1)[0]
98 | c <<= 8
99 | c += self.readBytes(1)[0]
100 | c <<= 8
101 | c += self.readBytes(1)[0]
102 | elif (c & 0xF8) == 0xF0:
103 | c = self.readBytes(1)[0]
104 | c <<= 8
105 | c += self.readBytes(1)[0]
106 | c <<= 8
107 | c += self.readBytes(1)[0]
108 | c <<= 8
109 | c += self.readBytes(1)[0]
110 | return c
111 |
112 | def writeBytes(self, str):
113 | n = 0;
114 | while n < len(str):
115 | r = self.sk.send(str[n:])
116 | if r == 0: raise RuntimeError("connection closed by remote end")
117 | n += r
118 |
119 | def readBytes(self, length):
120 | ret = b''
121 | while len(ret) < length:
122 | s = self.sk.recv(length - len(ret))
123 | if len(s) == 0: raise RuntimeError("connection closed by remote end")
124 | ret += s
125 | return ret
126 |
127 | def main():
128 | s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
129 | s.connect((sys.argv[1], 8728))
130 | apiros = ApiRos(s);
131 | apiros.login(sys.argv[2], sys.argv[3]);
132 |
133 | inputsentence = [line[:-1] for line in sys.stdin.readlines()]
134 | apiros.writeSentence(inputsentence)
135 | r = select.select([s], [], [], None)
136 | if s in r[0]:
137 | while 1:
138 | x = apiros.readSentence()
139 | if '!done' in x:
140 | break
141 | #if s in r[0]:
142 | # something to read in socket, read sentence
143 | # x = apiros.readSentence()
144 |
145 | #if sys.stdin in r[0]:
146 | # read line from input and strip off newline
147 | # l = sys.stdin.readline()
148 | # l = l[:-1]
149 | # if empty line, send sentence and start with new
150 | # otherwise append to input sentence
151 | # if l == '':
152 | # apiros.writeSentence(inputsentence)
153 | # inputsentence = []
154 | # else:
155 | # inputsentence.append(l)
156 |
157 | if __name__ == '__main__':
158 | main()
159 |
160 |
--------------------------------------------------------------------------------
/bgp_peer_field.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | HOST=$1
3 | USERNAME=$2
4 | PASSWORD=$3
5 | PEER_NAME=$4
6 | PEER_FIELD_NAME=$5
7 | API_CALL="/routing/bgp/peer/print\n?name=$PEER_NAME\n"
8 | API_ROS_COMMAND=$(cat /etc/zabbix/zabbix_server.conf | grep "^ExternalScripts=" | cut -d "=" -f 2)/apiros.py
9 | PEER_FIELD=$(echo -e "$API_CALL" | "$API_ROS_COMMAND" "$HOST" "$USERNAME" "$PASSWORD" | grep "$PEER_FIELD_NAME" | cut -d "=" -f 3)
10 |
11 | if [ "$PEER_FIELD_NAME" == "state" ]; then
12 | if [ "$PEER_FIELD" == "established" ]; then
13 | echo 4
14 |
15 | elif [ "$PEER_FIELD" == "active" ]; then
16 | echo 3
17 |
18 | elif [ "$PEER_FIELD" == "opensent" ] || [ "$PEER_FIELD" == "openconfirm" ]; then
19 | echo 2
20 |
21 | elif [ "$PEER_FIELD" == "idle" ]; then
22 | echo 1
23 | else
24 | echo 0
25 | fi
26 |
27 | elif [ "$PEER_FIELD_NAME" == "uptime" ]; then
28 | if [ "$PEER_FIELD" == "" ]; then
29 | echo 0
30 | else
31 | WEEKS=$(echo -e "$PEER_FIELD" | egrep -o '[0-9]+w' | tr -d 'w')
32 | WEEKS=${WEEKS:-0}
33 |
34 | DAYS=$(echo -e "$PEER_FIELD" | egrep -o '[0-9]+d' | tr -d 'd')
35 | DAYS=${DAYS:-0}
36 |
37 | HOURS=$(echo -e "$PEER_FIELD" | egrep -o '[0-9]+h' | tr -d 'h')
38 | HOURS=${HOURS:-0}
39 |
40 | MINUTES=$(echo -e "$PEER_FIELD" | egrep -o '[0-9]+m' | tr -d 'm')
41 | MINUTES=${MINUTES:-0}
42 |
43 | SECONDS=$(echo -e "$PEER_FIELD" | egrep -o '[0-9]+s' | tr -d 's')
44 | SECONDS=${SECONDS:-0}
45 |
46 | TOTAL_SECONDS=$(echo "$WEEKS * 604800 + $DAYS * 86400 + $HOURS * 3600 + $MINUTES * 60 + $SECONDS" | bc)
47 |
48 | echo $TOTAL_SECONDS
49 | fi
50 | else
51 | echo "$FIELD_NAME is not supported"
52 | fi
53 |
--------------------------------------------------------------------------------
/bgp_peer_names.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | HOST=$1
3 | USERNAME=$2
4 | PASSWORD=$3
5 |
6 | API_CALL="/routing/bgp/peer/print\n=status=\n"
7 | API_ROS_COMMAND=$(cat /etc/zabbix/zabbix_server.conf | grep "^ExternalScripts=" | cut -d "=" -f 2)/apiros.py
8 | PEERS_STATUS=$(echo -e "$API_CALL" | "$API_ROS_COMMAND" "$HOST" "$USERNAME" "$PASSWORD")
9 | PEER_NAMES=$(echo -e "$PEERS_STATUS" | tr -t '\n' ' ' | sed "s,=.id=*,\n,g" | grep "=state=" | grep -E -o "=name=[^ ]+" | cut -d "=" -f 3 | tr -t '\n' ' ')
10 |
11 | PEER_NAMES_JSON="{\n \"data\":["
12 |
13 | for PEER_NAME in $PEER_NAMES
14 | do
15 | PEER_NAMES_JSON="$PEER_NAMES_JSON\n { \"{#BGP_PEER_NAME}\":\"$PEER_NAME\" },"
16 | done
17 |
18 | PEER_NAMES_JSON=$(echo -n "$PEER_NAMES_JSON" | head -c '-1')
19 |
20 | PEER_NAMES_JSON="$PEER_NAMES_JSON\n ]\n}"
21 |
22 | echo -e "$PEER_NAMES_JSON"
23 |
--------------------------------------------------------------------------------
/vrrp_state.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | HOST=$1
3 | USERNAME=$2
4 | PASSWORD=$3
5 |
6 | API_VRRP_STATE_CALL="/interface/vrrp/print"
7 | API_ROS_COMMAND="/tmp/zabbix-routeros-bgp/apiros.py"
8 | VRRP_STATE_CONN=$(echo -e "$API_VRRP_STATE_CALL" | "$API_ROS_COMMAND" "$HOST" "$USERNAME" "$PASSWORD")
9 |
10 | VRRP_STATE=$(echo -e "$VRRP_STATE_CONN" | grep "=master" | cut -d "=" -f 3)
11 |
12 | if [ "$VRRP_STATE" == "true" ]; then
13 | echo 1
14 | else
15 | echo 0
16 | fi
17 |
--------------------------------------------------------------------------------
/zbx_routeros_bgp.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 3.0
4 | 2017-07-31T12:56:30Z
5 |
6 |
7 | Templates
8 |
9 |
10 |
11 |
12 | Template RouterOS BGP
13 | Template RouterOS BGP
14 |
15 |
16 |
17 | Templates
18 |
19 |
20 |
21 |
22 |
23 |
24 | BGP peer names
25 | 10
26 |
27 |
28 | bgp_peer_names.sh["{HOST.CONN}","{$ROUTEROS_USERNAME}","{$ROUTEROS_PASSWORD}"]
29 | 10
30 | 0
31 |
32 |
33 |
34 | 0
35 | 0
36 |
37 | 0
38 |
39 |
40 |
41 |
42 | 0
43 |
44 |
45 |
46 |
47 |
48 |
49 | 0
50 |
51 |
52 |
53 | 30
54 |
55 |
56 |
57 | BGP peer {#BGP_PEER_NAME} state
58 | 10
59 |
60 | 0
61 |
62 | bgp_peer_field.sh["{HOST.CONN}", "{$ROUTEROS_USERNAME}", "{$ROUTEROS_PASSWORD}", {#BGP_PEER_NAME}, "state"]
63 | 5
64 | 90
65 | 365
66 | 0
67 | 3
68 |
69 |
70 | 0
71 |
72 |
73 | 0
74 | 0
75 |
76 | 0
77 |
78 | 1
79 |
80 |
81 |
82 | 0
83 | 0
84 |
85 |
86 |
87 |
88 |
89 |
90 | 0
91 |
92 |
93 | RouterOS BGP value mapping
94 |
95 |
96 |
97 |
98 |
99 | BGP peer {#BGP_PEER_NAME} uptime
100 | 10
101 |
102 | 0
103 |
104 | bgp_peer_field.sh["{HOST.CONN}", "{$ROUTEROS_USERNAME}", "{$ROUTEROS_PASSWORD}", {#BGP_PEER_NAME}, "uptime"]
105 | 5
106 | 90
107 | 365
108 | 0
109 | 3
110 |
111 |
112 | 0
113 |
114 |
115 | 0
116 | 0
117 |
118 | 0
119 |
120 | 1
121 |
122 |
123 |
124 | 0
125 | 0
126 |
127 |
128 |
129 |
130 |
131 |
132 | 0
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 | {Template RouterOS BGP:bgp_peer_field.sh["{HOST.CONN}", "{$ROUTEROS_USERNAME}", "{$ROUTEROS_PASSWORD}", {#BGP_PEER_NAME}, "state"].last(#5)}<4 and {Template RouterOS BGP:bgp_peer_field.sh["{HOST.CONN}", "{$ROUTEROS_USERNAME}", "{$ROUTEROS_PASSWORD}", {#BGP_PEER_NAME}, "state"].last(#5)}>0
142 | BGP peer {#BGP_PEER_NAME} state is not established
143 |
144 | 0
145 | 2
146 |
147 | 0
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 | {$ROUTEROS_PASSWORD}
158 | zabbix_discovery
159 |
160 |
161 | {$ROUTEROS_USERNAME}
162 | zabbix
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | RouterOS BGP value mapping
172 |
173 |
174 | 0
175 | disabled
176 |
177 |
178 | 1
179 | idle
180 |
181 |
182 | 2
183 | openconfirm - opensent
184 |
185 |
186 | 3
187 | active
188 |
189 |
190 | 4
191 | established
192 |
193 |
194 |
195 |
196 |
197 |
--------------------------------------------------------------------------------