respondd_client.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. #!/usr/bin/env python3
  2. import socket
  3. import select
  4. import struct
  5. import json
  6. import zlib
  7. import time
  8. import re
  9. import lib.helper
  10. from lib.ratelimit import rateLimit
  11. from lib.nodeinfo import Nodeinfo
  12. from lib.neighbours import Neighbours
  13. from lib.statistics import Statistics
  14. class ResponddClient:
  15. def __init__(self, config):
  16. self._config = config
  17. if 'rate_limit' in self._config:
  18. if 'rate_limit_burst' not in self._config:
  19. self._config['rate_limit_burst'] = 10
  20. self.__RateLimit = rateLimit(self._config['rate_limit'], self._config['rate_limit_burst'])
  21. else:
  22. self.__RateLimit = None
  23. self._nodeinfo = Nodeinfo(self._config)
  24. self._neighbours = Neighbours(self._config)
  25. self._statistics = Statistics(self._config)
  26. self._sock = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
  27. @staticmethod
  28. def joinMCAST(sock, addr, ifname):
  29. group = socket.inet_pton(socket.AF_INET6, addr)
  30. if_idx = socket.if_nametoindex(ifname)
  31. sock.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, group + struct.pack('I', if_idx))
  32. def start(self):
  33. self._sock.setsockopt(socket.SOL_SOCKET, socket.SO_BINDTODEVICE, bytes(self._config['bridge'], 'UTF-8'))
  34. self._sock.bind(('::', self._config['port']))
  35. lines = lib.helper.call(['batctl', '-m', self._config['batman'], 'if'])
  36. for line in lines:
  37. lineMatch = re.match(r'^([^:]*)', line)
  38. self.joinMCAST(self._sock, self._config['addr'], lineMatch.group(1))
  39. self.joinMCAST(self._sock, self._config['addr'], self._config['bridge'])
  40. while True:
  41. msg, sourceAddress = self._sock.recvfrom(2048)
  42. msgSplit = str(msg, 'UTF-8').split(' ')
  43. responseStruct = {}
  44. if msgSplit[0] == 'GET': # multi_request
  45. for request in msgSplit[1:]:
  46. responseStruct[request] = self.buildStruct(request)
  47. self.sendStruct(sourceAddress, responseStruct, True)
  48. else: # single_request
  49. responseStruct = self.buildStruct(msgSplit[0])
  50. self.sendStruct(sourceAddress, responseStruct, False)
  51. def buildStruct(self, responseType):
  52. if self.__RateLimit is not None and not self.__RateLimit.limit():
  53. print('rate limit reached!')
  54. return
  55. responseClass = None
  56. if responseType == 'statistics':
  57. responseClass = self._statistics
  58. elif responseType == 'nodeinfo':
  59. responseClass = self._nodeinfo
  60. elif responseType == 'neighbours':
  61. responseClass = self._neighbours
  62. else:
  63. print('unknown command: ' + responseType)
  64. return
  65. return responseClass.getStruct()
  66. def sendStruct(self, destAddress, responseStruct, withCompression):
  67. if self._config['verbose'] or self._config['dry_run']:
  68. print('%14.3f %35s %5d: ' % (time.time(), destAddress[0], destAddress[1]), end='')
  69. print(json.dumps(responseStruct, sort_keys=True, indent=4))
  70. responseData = bytes(json.dumps(responseStruct, separators=(',', ':')), 'UTF-8')
  71. if withCompression:
  72. encoder = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) # The data may be decompressed using zlib and many zlib bindings using -15 as the window size parameter.
  73. responseData = encoder.compress(responseData)
  74. responseData += encoder.flush()
  75. # return compress(str.encode(json.dumps(ret)))[2:-4] # bug? (mesh-announce strip here)
  76. if not self._config['dry_run']:
  77. self._sock.sendto(responseData, destAddress)