mirror of
https://github.com/tomaae/homeassistant-mikrotik_router.git
synced 2025-07-10 09:24:31 +02:00
reverted back to customized fork of librouteros
This commit is contained in:
parent
1010a8aa3a
commit
0a58db40cd
8 changed files with 621 additions and 29 deletions
|
@ -0,0 +1,60 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from socket import create_connection
|
||||||
|
from collections import ChainMap
|
||||||
|
|
||||||
|
from .exceptions import (
|
||||||
|
ConnectionClosed,
|
||||||
|
FatalError,
|
||||||
|
)
|
||||||
|
from .connections import SocketTransport
|
||||||
|
from .protocol import ApiProtocol
|
||||||
|
from .login import (
|
||||||
|
plain,
|
||||||
|
token,
|
||||||
|
)
|
||||||
|
from .api import Api
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
'timeout': 10,
|
||||||
|
'port': 8728,
|
||||||
|
'saddr': '',
|
||||||
|
'subclass': Api,
|
||||||
|
'encoding': 'ASCII',
|
||||||
|
'ssl_wrapper': lambda sock: sock,
|
||||||
|
'login_method': plain,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def connect(host, username, password, **kwargs):
|
||||||
|
"""
|
||||||
|
Connect and login to routeros device.
|
||||||
|
Upon success return a Api class.
|
||||||
|
|
||||||
|
:param host: Hostname to connecto to. May be ipv4,ipv6,FQDN.
|
||||||
|
:param username: Username to login with.
|
||||||
|
:param password: Password to login with. Only ASCII characters allowed.
|
||||||
|
:param timeout: Socket timeout. Defaults to 10.
|
||||||
|
:param port: Destination port to be used. Defaults to 8728.
|
||||||
|
:param saddr: Source address to bind to.
|
||||||
|
:param subclass: Subclass of Api class. Defaults to Api class from library.
|
||||||
|
:param ssl_wrapper: Callable (e.g. ssl.SSLContext instance) to wrap socket with.
|
||||||
|
:param login_method: Callable with login method.
|
||||||
|
"""
|
||||||
|
arguments = ChainMap(kwargs, DEFAULTS)
|
||||||
|
transport = create_transport(host, **arguments)
|
||||||
|
protocol = ApiProtocol(transport=transport, encoding=arguments['encoding'])
|
||||||
|
api = arguments['subclass'](protocol=protocol)
|
||||||
|
|
||||||
|
try:
|
||||||
|
arguments['login_method'](api=api, username=username, password=password)
|
||||||
|
return api
|
||||||
|
except (ConnectionClosed, FatalError):
|
||||||
|
transport.close()
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def create_transport(host, **kwargs):
|
||||||
|
sock = create_connection((host, kwargs['port']), kwargs['timeout'], (kwargs['saddr'], 0))
|
||||||
|
sock = kwargs['ssl_wrapper'](sock)
|
||||||
|
return SocketTransport(sock=sock)
|
136
custom_components/mikrotik_router/librouteros_custom/api.py
Normal file
136
custom_components/mikrotik_router/librouteros_custom/api.py
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from posixpath import join as pjoin
|
||||||
|
|
||||||
|
from .exceptions import TrapError, MultiTrapError
|
||||||
|
from .protocol import (
|
||||||
|
compose_word,
|
||||||
|
parse_word,
|
||||||
|
)
|
||||||
|
from .query import Query
|
||||||
|
|
||||||
|
|
||||||
|
class Api:
|
||||||
|
|
||||||
|
def __init__(self, protocol):
|
||||||
|
self.protocol = protocol
|
||||||
|
|
||||||
|
def __call__(self, cmd, **kwargs):
|
||||||
|
"""
|
||||||
|
Call Api with given command.
|
||||||
|
Yield each row.
|
||||||
|
|
||||||
|
:param cmd: Command word. eg. /ip/address/print
|
||||||
|
:param kwargs: Dictionary with optional arguments.
|
||||||
|
"""
|
||||||
|
words = (compose_word(key, value) for key, value in kwargs.items())
|
||||||
|
self.protocol.writeSentence(cmd, *words)
|
||||||
|
yield from self.readResponse()
|
||||||
|
|
||||||
|
def rawCmd(self, cmd, *words):
|
||||||
|
"""
|
||||||
|
Call Api with given command and raw words.
|
||||||
|
End user is responsible to properly format each api word argument.
|
||||||
|
:param cmd: Command word. eg. /ip/address/print
|
||||||
|
:param args: Iterable with optional plain api arguments.
|
||||||
|
"""
|
||||||
|
self.protocol.writeSentence(cmd, *words)
|
||||||
|
yield from self.readResponse()
|
||||||
|
|
||||||
|
def readSentence(self):
|
||||||
|
"""
|
||||||
|
Read one sentence and parse words.
|
||||||
|
|
||||||
|
:returns: Reply word, dict with attribute words.
|
||||||
|
"""
|
||||||
|
reply_word, words = self.protocol.readSentence()
|
||||||
|
words = dict(parse_word(word) for word in words)
|
||||||
|
return reply_word, words
|
||||||
|
|
||||||
|
def readResponse(self):
|
||||||
|
"""
|
||||||
|
Yield each sentence untill !done is received.
|
||||||
|
|
||||||
|
:throws TrapError: If one !trap is received.
|
||||||
|
:throws MultiTrapError: If > 1 !trap is received.
|
||||||
|
"""
|
||||||
|
traps = []
|
||||||
|
reply_word = None
|
||||||
|
while reply_word != '!done':
|
||||||
|
reply_word, words = self.readSentence()
|
||||||
|
if reply_word == '!trap':
|
||||||
|
traps.append(TrapError(**words))
|
||||||
|
elif reply_word in ('!re', '!done') and words:
|
||||||
|
yield words
|
||||||
|
|
||||||
|
if len(traps) > 1:
|
||||||
|
raise MultiTrapError(*traps)
|
||||||
|
if len(traps) == 1:
|
||||||
|
raise traps[0]
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.protocol.close()
|
||||||
|
|
||||||
|
def path(self, *path):
|
||||||
|
return Path(
|
||||||
|
path='',
|
||||||
|
api=self,
|
||||||
|
).join(*path)
|
||||||
|
|
||||||
|
|
||||||
|
class Path:
|
||||||
|
"""Represents absolute command path."""
|
||||||
|
|
||||||
|
def __init__(self, path, api):
|
||||||
|
self.path = path
|
||||||
|
self.api = api
|
||||||
|
|
||||||
|
def select(self, key, *other):
|
||||||
|
keys = (key, ) + other
|
||||||
|
return Query(path=self, keys=keys, api=self.api)
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.path
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<{module}.{cls} {path!r}>".format(
|
||||||
|
module=self.__class__.__module__,
|
||||||
|
cls=self.__class__.__name__,
|
||||||
|
path=self.path,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
yield from self('print')
|
||||||
|
|
||||||
|
def __call__(self, cmd, **kwargs):
|
||||||
|
yield from self.api(
|
||||||
|
self.join(cmd).path,
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
|
||||||
|
def join(self, *path):
|
||||||
|
"""Join current path with one or more path strings."""
|
||||||
|
return Path(
|
||||||
|
api=self.api,
|
||||||
|
path=pjoin('/', self.path, *path).rstrip('/'),
|
||||||
|
)
|
||||||
|
|
||||||
|
def remove(self, *ids):
|
||||||
|
ids = ','.join(ids)
|
||||||
|
tuple(self(
|
||||||
|
'remove',
|
||||||
|
**{'.id': ids},
|
||||||
|
))
|
||||||
|
|
||||||
|
def add(self, **kwargs):
|
||||||
|
ret = self(
|
||||||
|
'add',
|
||||||
|
**kwargs,
|
||||||
|
)
|
||||||
|
return tuple(ret)[0]['ret']
|
||||||
|
|
||||||
|
def update(self, **kwargs):
|
||||||
|
tuple(self(
|
||||||
|
'set',
|
||||||
|
**kwargs,
|
||||||
|
))
|
|
@ -0,0 +1,37 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from .exceptions import ConnectionClosed
|
||||||
|
|
||||||
|
|
||||||
|
class SocketTransport:
|
||||||
|
|
||||||
|
def __init__(self, sock):
|
||||||
|
self.sock = sock
|
||||||
|
|
||||||
|
def write(self, data):
|
||||||
|
"""
|
||||||
|
Write given bytes to socket. Loop as long as every byte in
|
||||||
|
string is written unless exception is raised.
|
||||||
|
"""
|
||||||
|
self.sock.sendall(data)
|
||||||
|
|
||||||
|
def read(self, length):
|
||||||
|
"""
|
||||||
|
Read as many bytes from socket as specified in length.
|
||||||
|
Loop as long as every byte is read unless exception is raised.
|
||||||
|
"""
|
||||||
|
data = bytearray()
|
||||||
|
while len(data) != length:
|
||||||
|
tmp = None
|
||||||
|
try:
|
||||||
|
tmp = self.sock.recv((length - len(data)))
|
||||||
|
except:
|
||||||
|
raise ConnectionClosed('Socket recv failed.')
|
||||||
|
|
||||||
|
data += tmp
|
||||||
|
if not data:
|
||||||
|
raise ConnectionClosed('Connection unexpectedly closed.')
|
||||||
|
return data
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.sock.close()
|
|
@ -0,0 +1,52 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class LibRouterosError(Exception):
|
||||||
|
"""Base exception for all other."""
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionClosed(LibRouterosError):
|
||||||
|
"""Raised when connection have been closed."""
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolError(LibRouterosError):
|
||||||
|
"""Raised when e.g. encoding/decoding fails."""
|
||||||
|
|
||||||
|
|
||||||
|
class FatalError(ProtocolError):
|
||||||
|
"""Exception raised when !fatal is received."""
|
||||||
|
|
||||||
|
|
||||||
|
class TrapError(ProtocolError):
|
||||||
|
"""
|
||||||
|
Exception raised when !trap is received.
|
||||||
|
|
||||||
|
:param int category: Optional integer representing category.
|
||||||
|
:param str message: Error message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, message, category=None):
|
||||||
|
self.category = category
|
||||||
|
self.message = message
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.message.replace('\r\n', ','))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{}({!r})'.format(self.__class__.__name__, str(self))
|
||||||
|
|
||||||
|
|
||||||
|
class MultiTrapError(ProtocolError):
|
||||||
|
"""
|
||||||
|
Exception raised when multiple !trap words have been received in one response.
|
||||||
|
|
||||||
|
:param traps: TrapError instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *traps):
|
||||||
|
self.traps = traps
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return ', '.join(str(trap) for trap in self.traps)
|
|
@ -0,0 +1,26 @@
|
||||||
|
from binascii import unhexlify, hexlify
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
|
|
||||||
|
def encode_password(token, password):
|
||||||
|
#pylint: disable=redefined-outer-name
|
||||||
|
token = token.encode('ascii', 'strict')
|
||||||
|
token = unhexlify(token)
|
||||||
|
password = password.encode('ascii', 'strict')
|
||||||
|
hasher = md5()
|
||||||
|
hasher.update(b'\x00' + password + token)
|
||||||
|
password = hexlify(hasher.digest())
|
||||||
|
return '00' + password.decode('ascii', 'strict')
|
||||||
|
|
||||||
|
|
||||||
|
def token(api, username, password):
|
||||||
|
"""Login using pre routeros 6.43 authorization method."""
|
||||||
|
sentence = api('/login')
|
||||||
|
tok = tuple(sentence)[0]['ret']
|
||||||
|
encoded = encode_password(tok, password)
|
||||||
|
tuple(api('/login', **{'name': username, 'response': encoded}))
|
||||||
|
|
||||||
|
|
||||||
|
def plain(api, username, password):
|
||||||
|
"""Login using post routeros 6.43 authorization method."""
|
||||||
|
tuple(api('/login', **{'name': username, 'password': password}))
|
209
custom_components/mikrotik_router/librouteros_custom/protocol.py
Normal file
209
custom_components/mikrotik_router/librouteros_custom/protocol.py
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
|
||||||
|
from struct import pack, unpack
|
||||||
|
from logging import getLogger, NullHandler
|
||||||
|
|
||||||
|
from .exceptions import (
|
||||||
|
ProtocolError,
|
||||||
|
FatalError,
|
||||||
|
)
|
||||||
|
|
||||||
|
LOGGER = getLogger('librouteros')
|
||||||
|
LOGGER.addHandler(NullHandler())
|
||||||
|
|
||||||
|
|
||||||
|
def parse_word(word):
|
||||||
|
"""
|
||||||
|
Split given attribute word to key, value pair.
|
||||||
|
|
||||||
|
Values are casted to python equivalents.
|
||||||
|
|
||||||
|
:param word: API word.
|
||||||
|
:returns: Key, value pair.
|
||||||
|
"""
|
||||||
|
mapping = {'yes': True, 'true': True, 'no': False, 'false': False}
|
||||||
|
_, key, value = word.split('=', 2)
|
||||||
|
try:
|
||||||
|
value = int(value)
|
||||||
|
except ValueError:
|
||||||
|
value = mapping.get(value, value)
|
||||||
|
return (key, value)
|
||||||
|
|
||||||
|
|
||||||
|
def cast_to_api(value):
|
||||||
|
"""Cast python equivalent to API."""
|
||||||
|
mapping = {True: 'yes', False: 'no'}
|
||||||
|
# this is necesary because 1 == True, 0 == False
|
||||||
|
if type(value) == int:
|
||||||
|
value = str(value)
|
||||||
|
else:
|
||||||
|
value = mapping.get(value, str(value))
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
def compose_word(key, value):
|
||||||
|
"""
|
||||||
|
Create a attribute word from key, value pair.
|
||||||
|
Values are casted to api equivalents.
|
||||||
|
"""
|
||||||
|
return '={}={}'.format(key, cast_to_api(value))
|
||||||
|
|
||||||
|
|
||||||
|
class Encoder:
|
||||||
|
|
||||||
|
def encodeSentence(self, *words):
|
||||||
|
"""
|
||||||
|
Encode given sentence in API format.
|
||||||
|
|
||||||
|
:param words: Words to endoce.
|
||||||
|
:returns: Encoded sentence.
|
||||||
|
"""
|
||||||
|
encoded = map(self.encodeWord, words)
|
||||||
|
encoded = b''.join(encoded)
|
||||||
|
# append EOS (end of sentence) byte
|
||||||
|
encoded += b'\x00'
|
||||||
|
return encoded
|
||||||
|
|
||||||
|
def encodeWord(self, word):
|
||||||
|
"""
|
||||||
|
Encode word in API format.
|
||||||
|
|
||||||
|
:param word: Word to encode.
|
||||||
|
:returns: Encoded word.
|
||||||
|
"""
|
||||||
|
#pylint: disable=no-member
|
||||||
|
encoded_word = word.encode(encoding=self.encoding, errors='strict')
|
||||||
|
return Encoder.encodeLength(len(word)) + encoded_word
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encodeLength(length):
|
||||||
|
"""
|
||||||
|
Encode given length in mikrotik format.
|
||||||
|
|
||||||
|
:param length: Integer < 268435456.
|
||||||
|
:returns: Encoded length.
|
||||||
|
"""
|
||||||
|
if length < 128:
|
||||||
|
ored_length = length
|
||||||
|
offset = -1
|
||||||
|
elif length < 16384:
|
||||||
|
ored_length = length | 0x8000
|
||||||
|
offset = -2
|
||||||
|
elif length < 2097152:
|
||||||
|
ored_length = length | 0xC00000
|
||||||
|
offset = -3
|
||||||
|
elif length < 268435456:
|
||||||
|
ored_length = length | 0xE0000000
|
||||||
|
offset = -4
|
||||||
|
else:
|
||||||
|
raise ProtocolError('Unable to encode length of {}'.format(length))
|
||||||
|
|
||||||
|
return pack('!I', ored_length)[offset:]
|
||||||
|
|
||||||
|
|
||||||
|
class Decoder:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def determineLength(length):
|
||||||
|
"""
|
||||||
|
Given first read byte, determine how many more bytes
|
||||||
|
needs to be known in order to get fully encoded length.
|
||||||
|
|
||||||
|
:param length: First read byte.
|
||||||
|
:return: How many bytes to read.
|
||||||
|
"""
|
||||||
|
integer = ord(length)
|
||||||
|
|
||||||
|
#pylint: disable=no-else-return
|
||||||
|
if integer < 128:
|
||||||
|
return 0
|
||||||
|
elif integer < 192:
|
||||||
|
return 1
|
||||||
|
elif integer < 224:
|
||||||
|
return 2
|
||||||
|
elif integer < 240:
|
||||||
|
return 3
|
||||||
|
|
||||||
|
raise ProtocolError('Unknown controll byte {}'.format(length))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def decodeLength(length):
|
||||||
|
"""
|
||||||
|
Decode length based on given bytes.
|
||||||
|
|
||||||
|
:param length: Bytes string to decode.
|
||||||
|
:return: Decoded length.
|
||||||
|
"""
|
||||||
|
bytes_length = len(length)
|
||||||
|
|
||||||
|
if bytes_length < 2:
|
||||||
|
offset = b'\x00\x00\x00'
|
||||||
|
xor = 0
|
||||||
|
elif bytes_length < 3:
|
||||||
|
offset = b'\x00\x00'
|
||||||
|
xor = 0x8000
|
||||||
|
elif bytes_length < 4:
|
||||||
|
offset = b'\x00'
|
||||||
|
xor = 0xC00000
|
||||||
|
elif bytes_length < 5:
|
||||||
|
offset = b''
|
||||||
|
xor = 0xE0000000
|
||||||
|
else:
|
||||||
|
raise ProtocolError('Unable to decode length of {}'.format(length))
|
||||||
|
|
||||||
|
decoded = unpack('!I', (offset + length))[0]
|
||||||
|
decoded ^= xor
|
||||||
|
return decoded
|
||||||
|
|
||||||
|
|
||||||
|
class ApiProtocol(Encoder, Decoder):
|
||||||
|
|
||||||
|
def __init__(self, transport, encoding):
|
||||||
|
self.transport = transport
|
||||||
|
self.encoding = encoding
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def log(direction_string, *sentence):
|
||||||
|
for word in sentence:
|
||||||
|
LOGGER.debug('{0} {1!r}'.format(direction_string, word))
|
||||||
|
|
||||||
|
LOGGER.debug('{0} EOS'.format(direction_string))
|
||||||
|
|
||||||
|
def writeSentence(self, cmd, *words):
|
||||||
|
"""
|
||||||
|
Write encoded sentence.
|
||||||
|
|
||||||
|
:param cmd: Command word.
|
||||||
|
:param words: Aditional words.
|
||||||
|
"""
|
||||||
|
encoded = self.encodeSentence(cmd, *words)
|
||||||
|
self.log('<---', cmd, *words)
|
||||||
|
self.transport.write(encoded)
|
||||||
|
|
||||||
|
def readSentence(self):
|
||||||
|
"""
|
||||||
|
Read every word untill empty word (NULL byte) is received.
|
||||||
|
|
||||||
|
:return: Reply word, tuple with read words.
|
||||||
|
"""
|
||||||
|
sentence = tuple(word for word in iter(self.readWord, ''))
|
||||||
|
self.log('--->', *sentence)
|
||||||
|
reply_word, words = sentence[0], sentence[1:]
|
||||||
|
if reply_word == '!fatal':
|
||||||
|
self.transport.close()
|
||||||
|
raise FatalError(words[0])
|
||||||
|
return reply_word, words
|
||||||
|
|
||||||
|
def readWord(self):
|
||||||
|
byte = self.transport.read(1)
|
||||||
|
# Early return check for null byte
|
||||||
|
if byte == b'\x00':
|
||||||
|
return ''
|
||||||
|
to_read = self.determineLength(byte)
|
||||||
|
byte += self.transport.read(to_read)
|
||||||
|
length = self.decodeLength(byte)
|
||||||
|
word = self.transport.read(length)
|
||||||
|
return word.decode(encoding=self.encoding, errors='strict')
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.transport.close()
|
|
@ -0,0 +1,64 @@
|
||||||
|
# -*- coding: UTF-8 -*-
|
||||||
|
from itertools import chain
|
||||||
|
from .protocol import (
|
||||||
|
cast_to_api,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Key:
|
||||||
|
|
||||||
|
def __init__(self, name):
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
yield '?={}={}'.format(self, cast_to_api(other))
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
yield from self == other
|
||||||
|
yield '?#!'
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
yield '?<{}={}'.format(self, cast_to_api(other))
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
yield '?>{}={}'.format(self, cast_to_api(other))
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.name)
|
||||||
|
|
||||||
|
|
||||||
|
class Query:
|
||||||
|
|
||||||
|
def __init__(self, path, keys, api):
|
||||||
|
self.path = path
|
||||||
|
self.keys = keys
|
||||||
|
self.api = api
|
||||||
|
self.query = tuple()
|
||||||
|
|
||||||
|
def where(self, *args):
|
||||||
|
self.query = tuple(chain.from_iterable(args))
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
keys = ','.join(str(key) for key in self.keys)
|
||||||
|
keys = '=.proplist={}'.format(keys)
|
||||||
|
cmd = str(self.path.join('print'))
|
||||||
|
return iter(self.api.rawCmd(cmd, keys, *self.query))
|
||||||
|
|
||||||
|
|
||||||
|
def And(left, right, *rest):
|
||||||
|
#pylint: disable=invalid-name
|
||||||
|
yield from left
|
||||||
|
yield from right
|
||||||
|
yield from chain.from_iterable(rest)
|
||||||
|
yield '?#&'
|
||||||
|
yield from ('?#&', ) * len(rest)
|
||||||
|
|
||||||
|
|
||||||
|
def Or(left, right, *rest):
|
||||||
|
#pylint: disable=invalid-name
|
||||||
|
yield from left
|
||||||
|
yield from right
|
||||||
|
yield from chain.from_iterable(rest)
|
||||||
|
yield '?#|'
|
||||||
|
yield from ('?#|', ) * len(rest)
|
|
@ -10,7 +10,15 @@ from .const import (
|
||||||
DEFAULT_ENCODING,
|
DEFAULT_ENCODING,
|
||||||
)
|
)
|
||||||
|
|
||||||
import librouteros
|
import os
|
||||||
|
import sys
|
||||||
|
import importlib
|
||||||
|
MODULE_PATH = os.path.join(os.path.dirname(__file__), "librouteros_custom", "__init__.py")
|
||||||
|
MODULE_NAME = "librouteros_custom"
|
||||||
|
spec = importlib.util.spec_from_file_location(MODULE_NAME, MODULE_PATH)
|
||||||
|
librouteros_custom = importlib.util.module_from_spec(spec)
|
||||||
|
sys.modules[spec.name] = librouteros_custom
|
||||||
|
spec.loader.exec_module(librouteros_custom)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -86,15 +94,15 @@ class MikrotikAPI:
|
||||||
kwargs["ssl_wrapper"] = self._ssl_wrapper
|
kwargs["ssl_wrapper"] = self._ssl_wrapper
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
try:
|
try:
|
||||||
self._connection = librouteros.connect(
|
self._connection = librouteros_custom.connect(
|
||||||
self._host, self._username, self._password, **kwargs
|
self._host, self._username, self._password, **kwargs
|
||||||
)
|
)
|
||||||
except (
|
except (
|
||||||
librouteros.exceptions.TrapError,
|
librouteros_custom.exceptions.TrapError,
|
||||||
librouteros.exceptions.MultiTrapError,
|
librouteros_custom.exceptions.MultiTrapError,
|
||||||
librouteros.exceptions.ConnectionClosed,
|
librouteros_custom.exceptions.ConnectionClosed,
|
||||||
librouteros.exceptions.ProtocolError,
|
librouteros_custom.exceptions.ProtocolError,
|
||||||
librouteros.exceptions.FatalError,
|
librouteros_custom.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -166,7 +174,7 @@ class MikrotikAPI:
|
||||||
try:
|
try:
|
||||||
response = self._connection.path(path)
|
response = self._connection.path(path)
|
||||||
_LOGGER.debug("API response (%s): %s", path, response)
|
_LOGGER.debug("API response (%s): %s", path, response)
|
||||||
except librouteros.exceptions.ConnectionClosed:
|
except librouteros_custom.exceptions.ConnectionClosed:
|
||||||
if not self.connection_error_reported:
|
if not self.connection_error_reported:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.connection_error_reported = True
|
self.connection_error_reported = True
|
||||||
|
@ -175,10 +183,10 @@ class MikrotikAPI:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return None
|
return None
|
||||||
except (
|
except (
|
||||||
librouteros.exceptions.TrapError,
|
librouteros_custom.exceptions.TrapError,
|
||||||
librouteros.exceptions.MultiTrapError,
|
librouteros_custom.exceptions.MultiTrapError,
|
||||||
librouteros.exceptions.ProtocolError,
|
librouteros_custom.exceptions.ProtocolError,
|
||||||
librouteros.exceptions.FatalError,
|
librouteros_custom.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -202,7 +210,7 @@ class MikrotikAPI:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tuple(response)
|
tuple(response)
|
||||||
except librouteros.exceptions.ConnectionClosed as api_error:
|
except librouteros_custom.exceptions.ConnectionClosed as api_error:
|
||||||
if not self.connection_error_reported:
|
if not self.connection_error_reported:
|
||||||
_LOGGER.error("Mikrotik %s error while path %s", self._host, api_error)
|
_LOGGER.error("Mikrotik %s error while path %s", self._host, api_error)
|
||||||
self.connection_error_reported = True
|
self.connection_error_reported = True
|
||||||
|
@ -252,7 +260,7 @@ class MikrotikAPI:
|
||||||
self.lock.acquire()
|
self.lock.acquire()
|
||||||
try:
|
try:
|
||||||
response.update(**params)
|
response.update(**params)
|
||||||
except librouteros.exceptions.ConnectionClosed:
|
except librouteros_custom.exceptions.ConnectionClosed:
|
||||||
if not self.connection_error_reported:
|
if not self.connection_error_reported:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.connection_error_reported = True
|
self.connection_error_reported = True
|
||||||
|
@ -261,10 +269,10 @@ class MikrotikAPI:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return False
|
return False
|
||||||
except (
|
except (
|
||||||
librouteros.exceptions.TrapError,
|
librouteros_custom.exceptions.TrapError,
|
||||||
librouteros.exceptions.MultiTrapError,
|
librouteros_custom.exceptions.MultiTrapError,
|
||||||
librouteros.exceptions.ProtocolError,
|
librouteros_custom.exceptions.ProtocolError,
|
||||||
librouteros.exceptions.FatalError,
|
librouteros_custom.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -326,7 +334,7 @@ class MikrotikAPI:
|
||||||
try:
|
try:
|
||||||
run = response("run", **{".id": tmp[".id"]})
|
run = response("run", **{".id": tmp[".id"]})
|
||||||
tuple(run)
|
tuple(run)
|
||||||
except librouteros.exceptions.ConnectionClosed:
|
except librouteros_custom.exceptions.ConnectionClosed:
|
||||||
if not self.connection_error_reported:
|
if not self.connection_error_reported:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.connection_error_reported = True
|
self.connection_error_reported = True
|
||||||
|
@ -335,10 +343,10 @@ class MikrotikAPI:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return False
|
return False
|
||||||
except (
|
except (
|
||||||
librouteros.exceptions.TrapError,
|
librouteros_custom.exceptions.TrapError,
|
||||||
librouteros.exceptions.MultiTrapError,
|
librouteros_custom.exceptions.MultiTrapError,
|
||||||
librouteros.exceptions.ProtocolError,
|
librouteros_custom.exceptions.ProtocolError,
|
||||||
librouteros.exceptions.FatalError,
|
librouteros_custom.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -395,7 +403,7 @@ class MikrotikAPI:
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"API response (%s): %s", "/interface/monitor-traffic", traffic
|
"API response (%s): %s", "/interface/monitor-traffic", traffic
|
||||||
)
|
)
|
||||||
except librouteros.exceptions.ConnectionClosed:
|
except librouteros_custom.exceptions.ConnectionClosed:
|
||||||
if not self.connection_error_reported:
|
if not self.connection_error_reported:
|
||||||
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
_LOGGER.error("Mikrotik %s connection closed", self._host)
|
||||||
self.connection_error_reported = True
|
self.connection_error_reported = True
|
||||||
|
@ -404,10 +412,10 @@ class MikrotikAPI:
|
||||||
self.lock.release()
|
self.lock.release()
|
||||||
return None
|
return None
|
||||||
except (
|
except (
|
||||||
librouteros.exceptions.TrapError,
|
librouteros_custom.exceptions.TrapError,
|
||||||
librouteros.exceptions.MultiTrapError,
|
librouteros_custom.exceptions.MultiTrapError,
|
||||||
librouteros.exceptions.ProtocolError,
|
librouteros_custom.exceptions.ProtocolError,
|
||||||
librouteros.exceptions.FatalError,
|
librouteros_custom.exceptions.FatalError,
|
||||||
ssl.SSLError,
|
ssl.SSLError,
|
||||||
BrokenPipeError,
|
BrokenPipeError,
|
||||||
OSError,
|
OSError,
|
||||||
|
@ -435,7 +443,7 @@ class MikrotikAPI:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
tuple(response)
|
tuple(response)
|
||||||
except librouteros.exceptions.ConnectionClosed as api_error:
|
except librouteros_custom.exceptions.ConnectionClosed as api_error:
|
||||||
if not self.connection_error_reported:
|
if not self.connection_error_reported:
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Mikrotik %s error while get_traffic %s", self._host, api_error
|
"Mikrotik %s error while get_traffic %s", self._host, api_error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue