├── 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 | ![Logo of ZABBIX](http://www.zabbix.com/img/logo/zabbix_logo_150x39.png) ![Logo of Mikrotik](https://www.mikrotik.com/logo/files/logo_spacing.jpg)
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 | ![Zabbix BGP Monitoring on Mikrotik](https://i.imgur.com/5I8vzrm.png) 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 | 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 | --------------------------------------------------------------------------------