Skip to content

Commit

Permalink
Refactor bin/kamstrup_prober (#517)
Browse files Browse the repository at this point in the history
* refactored for readability
* added new def layout to reduce complexity
  • Loading branch information
BusinessFawn authored Oct 27, 2020
1 parent 48c7137 commit 165f632
Showing 1 changed file with 89 additions and 75 deletions.
164 changes: 89 additions & 75 deletions bin/kamstrup_prober.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

import logging
import socket
import json
from datetime import datetime
Expand All @@ -25,6 +26,12 @@
import xml.dom.minidom
from conpot.protocols.kamstrup.meter_protocol import kamstrup_constants

logger = logging.getLogger(__name__)

port_start_range = 1
port_end_range = 65535
default_comm_port = 63


class KamstrupRegisterCopier(object):
def __init__(self, ip_address, port, comm_address):
Expand All @@ -35,7 +42,7 @@ def __init__(self, ip_address, port, comm_address):
self._connect()

def _connect(self):
print("Connecting to {0}:{1}".format(self.ip_address, self.port))
logger.info("Connecting to {0}:{1}".format(self.ip_address, self.port))
if self._sock is not None:
self._sock.close()
time.sleep(1)
Expand All @@ -44,7 +51,7 @@ def _connect(self):
try:
self._sock.connect((self.ip_address, self.port))
except socket.error as socket_err:
print("Error while connecting: {0}".format(str(socket_err)))
logger.exception("Error while connecting: {0}".format(str(socket_err)))
self._connect()

def get_register(self, register):
Expand Down Expand Up @@ -77,7 +84,7 @@ def get_register(self, register):
received_data = self._sock.recv(1024)
received_data = bytearray(received_data)
except socket.error as socket_err:
print("Error while communicating: {0}".format(str(socket_err)))
logger.exception("Error while communicating: {0}".format(str(socket_err)))
self._connect()
data_length = len(received_data)

Expand All @@ -93,74 +100,71 @@ def get_register(self, register):
return received_data


def json_default(obj):
if isinstance(obj, datetime):
return obj.isoformat()
def find_registers_in_candidates(args):
if args.registerfile:
candidate_registers_values = []
with open(args.registerfile, "r") as register_file:
old_register_values = json.load(register_file)
for value in old_register_values.iterkeys():
candidate_registers_values.append(int(value))
else:
return None
candidate_registers_values = range(port_start_range, port_end_range)

found_registers = registers_from_candidates(candidate_registers_values, args)

parser = argparse.ArgumentParser(description="Probes kamstrup_meter meter registers.")
parser.add_argument("host", help="Hostname or IP or Kamstrup meter")
parser.add_argument("port", type=int, help="TCP port")
parser.add_argument(
"--registerfile",
dest="registerfile",
help="Reads registers from previous dumps files instead of"
"bruteforcing the meter.",
)
parser.add_argument("--comaddress", dest="communication_address", default=0x3F)
args = parser.parse_args()

if args.registerfile:
candidate_registers_values = []
with open(args.registerfile, "r") as register_file:
old_register_values = json.load(register_file)
for value in old_register_values.iterkeys():
candidate_registers_values.append(int(value))
else:
candidate_registers_values = range(0x01, 0xFFFF)

kamstrupRegisterCopier = KamstrupRegisterCopier(
args.host, args.port, int(args.communication_address)
)
found_registers = {}
not_found_counts = 0
scanned = 0
dumpfile = "kamstrup_dump_{0}.json".format(
calendar.timegm(datetime.utcnow().utctimetuple())
)

for x in candidate_registers_values:
result = kamstrupRegisterCopier.get_register(x)
if len(result) > 12:
units = result[5]
length = result[6]
unknown = result[7]

register_value = 0
for p in range(length):
register_value += result[8 + p] << (8 * ((length - p) - 1))

found_registers[x] = {
"timestamp": datetime.utcnow(),
"units": units,
"value": register_value,
"value_length": length,
"unknown": unknown,
}
print("Found register value at {0}:{1}".format(hex(x), register_value))
with open(dumpfile, "w") as json_file:
json_file.write(json.dumps(found_registers, indent=4, default=json_default))
else:
not_found_counts += 1
if not_found_counts % 10 == 0:
print(
"Hang on, still scanning, so far scanned {0} and found {1} registers".format(
scanned, len(found_registers)
logger.info(
"Scanned {0} registers, found {1}.".format(
len(candidate_registers_values), len(found_registers)
)
)
# with open('kamstrup_dump_{0}.json'.format(calendar.timegm(datetime.utcnow().utctimetuple())), 'w') as json_file:
# json_file.write(json.dumps(found_registers, indent=4, default=json_default))
logger.info("""*** Sample Conpot configuration from this scrape:""")
logger.info(generate_conpot_config(found_registers))


def registers_from_candidates(candidate_registers_values, args):
kamstrupRegisterCopier = KamstrupRegisterCopier(
args.host, args.port, int(args.communication_address)
)
found_registers = {}
not_found_counts = 0
scanned = 0
dumpfile = "kamstrup_dump_{0}.json".format(
calendar.timegm(datetime.utcnow().utctimetuple())
)
for register_id in candidate_registers_values:
result = kamstrupRegisterCopier.get_register(register_id)
if len(result) > 12:
units = result[5]
length = result[6]
unknown = result[7]

register_value = 0
for p in range(length):
register_value += result[8 + p] << (8 * ((length - p) - 1))

found_registers[register_id] = {
"timestamp": datetime.utcnow(),
"units": units,
"value": register_value,
"value_length": length,
"unknown": unknown,
}
logger.info("Found register value at {0}:{1}".format(hex(register_id), register_value))
with open(dumpfile, "w") as json_file:
json_file.write(json.dumps(found_registers, indent=4, default=json_default))
else:
not_found_counts += 1
if not_found_counts % 10 == 0:
logger.info(
"Hang on, still scanning, so far scanned {0} and found {1} registers".format(
scanned, len(found_registers)
)
)
)
scanned += 1
scanned += 1

return found_registers


def generate_conpot_config(result_list):
Expand All @@ -185,12 +189,22 @@ def generate_conpot_config(result_list):
return pretty_xml


print(
"Scanned {0} registers, found {1}.".format(
len(candidate_registers_values), len(found_registers)
def json_default(obj):
if isinstance(obj, datetime):
return obj.isoformat()
else:
return None


if __name__ == '__main__':
parser = argparse.ArgumentParser(description="Probes kamstrup_meter meter registers.")
parser.add_argument("host", help="Hostname or IP or Kamstrup meter")
parser.add_argument("port", type=int, help="TCP port")
parser.add_argument(
"--registerfile",
dest="registerfile",
help="Reads registers from previous dumps files instead of bruteforcing the meter.",
)
)
# with open('kamstrup_dump_{0}.json'.format(calendar.timegm(datetime.utcnow().utctimetuple())), 'w') as json_file:
# json_file.write(json.dumps(found_registers, indent=4, default=json_default))
print("""*** Sample Conpot configuration from this scrape:""")
print(generate_conpot_config(found_registers))
parser.add_argument("--comm-addr", dest="communication_address", default=default_comm_port)

find_registers_in_candidates(parser.parse_args())

0 comments on commit 165f632

Please sign in to comment.