Skip to content

Commit

Permalink
Merged master with develop
Browse files Browse the repository at this point in the history
  • Loading branch information
takeshixx committed Sep 4, 2016
2 parents 33a44dd + a42b0df commit cd5d391
Show file tree
Hide file tree
Showing 21 changed files with 2,281 additions and 1,492 deletions.
69 changes: 55 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# KNXmap

A tool for scanning and auditing KNXnet/IP gateways on IP driven networks. In addition to search and identify gateways KNXmap allows to scan for devices on the KNX bus via KNXnet/IP gateways.
A tool for scanning and auditing KNXnet/IP gateways on IP driven networks. KNXnet/IP defines Ethernet as physical communication media for KNX (EN 50090, ISO/IEC 14543). KNXmap also allows to scan for devices on the KNX bus via KNXnet/IP gateways. In addition to scanning, KNXmap supports other modes to interact with KNX gateways like monitor bus messages or write arbitrary values to group addresses.

## Compatibility

KNXmap is based on the [asyncio](https://docs.python.org/3/library/asyncio.html) module which is available for Python 3.3 and newer. Users of Python 3.3 must install `asyncio` from [PyPI](https://pypi.python.org/pypi), Python 3.4 ships it in the standard library by default. Therefore KNXmap requires Python 3.3 or any newer version of Python.
KNXmap is based on the [asyncio](https://docs.python.org/3/library/asyncio.html) module which is available for Python 3.3 and newer. Users of Python 3.3 must install `asyncio` from [PyPI](https://pypi.python.org/pypi), Python 3.4 ships it in the standard library.

<<<<<<< HEAD
## KNX

KNX is a standardized (EN 50090, ISO/IEC 14543), OSI-based network communications protocol for building automation. KNX is the successor to, and convergence of, three previous standards: the European Home Systems Protocol (EHS), BatiBUS, and the European Installation Bus (EIB or Instabus). The KNX standard is administered by the [KNX Association](https://www.knx.org/knx-en/index.php). ([Source](https://en.wikipedia.org/wiki/KNX_\(standard\)))
Expand All @@ -15,6 +16,23 @@ KNX is a standardized (EN 50090, ISO/IEC 14543), OSI-based network communication
KNXnet/IP defines Ethernet as physical communication media. It basically allows administrators to manage KNX bus devices via IP driven networks.

**Note**: Unfourtunately the standard is proprietary which makes it impossible to be included in this repository.
=======
## Usage

Install and run KNXmap:

```
python setup.py install
knxmap.py --help
```

Or just invoke the script locally:

```
chmod +x knxmap.py
./knxmap.py --help
```
>>>>>>> develop
## Scanning Modes

Expand All @@ -29,26 +47,28 @@ KNXmap supports three different scanning modes:
This is the default mode of KNXmap. It sends KNX description request to the supplied targets in order to chceck if they are KNXnet/IP gateways.

```
knxmap.py 192.168.1.100
knxmap.py scan 192.168.1.100
```

KNXmap supports to scan multiple targets at once by supplying multiple IP addresses separated by a space. Targets can also be defined as networks in CIDR notation:

```
knxmap.py 192.168.1.100 192.168.1.110 192.168.2.0/24
knxmap.py scan 192.168.1.100 192.168.1.110 192.168.2.0/24
```

**Note**: Many KNXnet/IP gateways fail to properly handle subsequent discovery requests. As a consequence, discovering such devices can be quite unreliable!

### Bus Mode

In addition to the discovery mode, KNXmap also supports to scan for devices on the KNX bus.

```
knxmap.py --bus-targets 1.0.0-1.1.255 192.168.1.100
knxmap.py scan 192.168.1.100 --bus-targets 1.1.5
```

**Note**: Currently only target ranges are allowed, so at least two devices must be scanned because e.g. 1.1.1-1.1.1 is not a valid target definition.
KNXmap also supports bus address ranges:

```
knxmap.py scan 192.168.1.100 --bus-targets 1.0.0-1.1.255
```

The default mode is to only check if sending messages to a address returns an error or not. This helps to identify potential devices and alive targets.

Expand All @@ -57,15 +77,15 @@ The default mode is to only check if sending messages to a address returns an er
In addition to the default bus scanning KNXmap can also extract basic information from devices for further identification by supplying the `--bus-info` argument:

```
knxmap.py --bus-targets 1.0.0-1.1.255 --bus-info 192.168.1.100
knxmap.py scan 192.168.1.100 --bus-targets 1.1.5 --bus-info
```

### Search Mode

KNX supports finding devices by sending multicast packets that should be answered by any KNXnet/IP gateway. KNXmap supports gateway searching via the `--search` flag. It requires the `-i`/`--interface` and superuser privileges:

```
sudo knxmap.py --search --interface eth1
sudo knxmap.py --interface eth1 search
```

**Note**: Packet filtering rules might block the response packets. If there are no KNXnet/IP gateways answering their packets might be dropped by netfilter/iptables rules.
Expand All @@ -74,21 +94,42 @@ sudo knxmap.py --search --interface eth1

KNXmap supports two different monitoring modes:

* Bus monitoring (`--bus-monitor`) prints the raw messages received from the KNX bus.
* Group monitoring (`--group-monitor`) prints all group messages received from the KNX bus.
* Bus monitoring: prints the raw messages received from the KNX bus.

<<<<<<< HEAD
These monitoring modes can be useful for debugging communication on the bus. Additionally, they can be used for passive information gathering which allows to identify bus devices without sending messages to any individual or group address. Especially motion sensors or other devices that frequently send messages to the bus can easily be identified via bus monitoring.

## TODO

* Implement KNXnet/IP Routing (bus.py)
* KNXnet/IP router device required (not available yet)
* Implement [KNX ObjectServer protocol](http://www.weinzierl.de/images/download/products/770/KNX_BAOS_Protocol.pdf) (objectserver.py)
=======
```
knxmap.py monitor 192.168.1.100
```

* Group monitoring: prints all group messages received from the KNX bus.

```
knxmap.py monitor 192.168.1.100 --group-monitor
```

These monitoring modes can be useful for debugging communication on the bus. Additionally, they can be used for passive information gathering which allows to identify bus devices without sending messages to any individual or group address. Especially motion sensors or other devices that frequently send messages to the bus can easily be identified via bus monitoring.

## Group Write

KNXmap allows one to write arbitrary values to any group address on the bus. The following example writes the value `1` to the group address `0/0/1`:

```
knxmap.py write 192.168.1.100 0/0/1 1
```
>>>>>>> develop
## Hacking

Enable full debugging and verbosity for development:

```
PYTHONASYNCIODEBUG=1 knxmap.py 192.168.178.20 --bus-targets 1.1.0-1.1.6 --bus-info -v
```
PYTHONASYNCIODEBUG=1 knxmap.py -v scan 192.168.178.20 --bus-targets 1.1.0-1.1.6 --bus-info
```
163 changes: 102 additions & 61 deletions knxmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import argparse
import logging

from libknx import KnxScanner, Targets, KnxTargets
from libknxmap import KnxMap, Targets, KnxTargets

# asyncio requires at least Python 3.3
if sys.version_info.major < 3 or \
(sys.version_info.major > 2 and
sys.version_info.minor < 3):
(sys.version_info.major > 2 and
sys.version_info.minor < 3):
print('At least Python version 3.3 is required to run this script!')
sys.exit(1)
try:
Expand All @@ -21,93 +21,134 @@
sys.exit(1)

LOGGER = logging.getLogger(__name__)
ARGS = argparse.ArgumentParser(
description='KNXnet/IP network and bus mapper',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
SUBARGS = ARGS.add_subparsers(dest='cmd')

# TODO: create proper arguments
# TODO: add subcommands for scanning modes?
# TODO: add dump-file argument for monitoring modes
# TODO: implement key bruteforcing for authorization request PINs
ARGS = argparse.ArgumentParser(description="KNXnet/IP Scanner")
# General options
ARGS.add_argument(
'targets', nargs='*',
default=[], help='Target hostnames/IP addresses')
'-v', '--verbose', action='count', dest='level',
default=2, help='Verbose logging (repeat for more verbose)')
ARGS.add_argument(
'-q', '--quiet', action='store_const', const=0, dest='level',
default=2, help='Only log errors')
ARGS.add_argument(
'-p', '--port', action='store', dest='port', type=int,
'-p', action='store', dest='port', type=int,
default=3671, help='UDP port to be scanned')
ARGS.add_argument(
'-i', action='store', dest='iface',
default=None, help='Interface to be used')
ARGS.add_argument(
'--workers', action='store', type=int, metavar='N',
default=30, help='Limit concurrent workers')
ARGS.add_argument(
'-i', '--interface', action='store', dest='iface',
default=None, help='Interface to be used')
'--key', action='store', dest='auth_key', type=int,
default=0xffffffff, help='Authorize key for System 2 and System 7 devices')
ARGS.add_argument(
'--search', action='store_true', dest='search_mode',
default=False, help='Find local KNX gateways via search requests')
ARGS.add_argument(
'--search-timeout', action='store', dest='search_timeout', type=int,
default=5, help='Timeout in seconds for multicast responses')
'--timeout', action='store', dest='timeout', type=int,
default=2, help='Timeout in seconds for unicast description responses')
ARGS.add_argument(
'--retries', action='store', dest='retries', type=int,
default=3, help='Count of retries for description requests')

pscan = SUBARGS.add_parser('scan', help='Scan KNXnet/IP gateways and attached bus devices')
pscan.add_argument(
'targets', help='KNXnet/IP gateway', metavar='gateway')
pscan.add_argument(
'--bus-targets', action='store', dest='bus_targets',
default=None, help='Bus target range')
ARGS.add_argument(
default=None, help='Bus target range (e.g. 1.1.0-1.1.10)')
pscan.add_argument(
'--bus-info', action='store_true', dest='bus_info',
default=False, help='Try to extract information from bus devices')
ARGS.add_argument(
'--bus-monitor', action='store_true', dest='bus_monitor_mode',
default=False, help='Monitor all bus messages via KNXnet/IP gateway')
ARGS.add_argument(
default=False, help='Try to extract information from alive bus devices')

psearch = SUBARGS.add_parser('search',
help='Search for KNXnet/IP gateways on the local network')
psearch.add_argument(
'--search-timeout', action='store', dest='search_timeout', type=int,
default=5, help='Timeout in seconds for multicast responses')

pwrite = SUBARGS.add_parser('write', help='Write a value to a group address')
pwrite.add_argument(
'targets', help='KNXnet/IP gateway', metavar='gateway')
pwrite.add_argument(
'group_write_address', help='A KNX group address to write to')
pwrite.add_argument(
'group_write_value', default=0, help='Value to write to the group address')
pwrite.add_argument(
'--routing', action='store_true', dest='routing',
default=False, help='Use Routing instead of Tunnelling')

pbrute = SUBARGS.add_parser('brute', help='Bruteforce authentication key')
pbrute.add_argument(
'targets', help='KNXnet/IP gateway', metavar='gateway')
pbrute.add_argument(
'bus_target', help='Individual address of bus device')

pmonitor = SUBARGS.add_parser('monitor', help='Monitor bus and group messages')
pmonitor.add_argument(
'targets', help='KNXnet/IP gateway', metavar='gateway')
pmonitor.add_argument(
'--group-monitor', action='store_true', dest='group_monitor_mode',
default=False, help='Monitor group bus messages via KNXnet/IP gateway')
ARGS.add_argument(
'-v', '--verbose', action='count', dest='level',
default=2, help='Verbose logging (repeat for more verbose)')
ARGS.add_argument(
'-q', '--quiet', action='store_const', const=0, dest='level',
default=2, help='Only log errors')
default=False, help='Monitor group instead of messages via KNXnet/IP gateway')


def main():
args = ARGS.parse_args()
if not args.targets and not args.search_mode:
ARGS.print_help()
sys.exit()

targets = Targets(args.targets, args.port)
bus_targets = KnxTargets(args.bus_targets)
levels = [logging.ERROR, logging.WARN, logging.INFO, logging.DEBUG]
format = '[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s' if args.level > 2 else '%(message)s'
logging.basicConfig(level=levels[min(args.level, len(levels)-1)], format=format)
logging.basicConfig(level=levels[min(args.level, len(levels) - 1)], format=format)
loop = asyncio.get_event_loop()

if args.search_mode:
if not args.iface:
LOGGER.error('--search option requires -i/--interface argument')
sys.exit(1)

if os.geteuid() != 0:
LOGGER.error('-i/--interface option requires superuser privileges')
sys.exit(1)
if hasattr(args, 'targets'):
targets = Targets(args.targets, args.port)
knxmap = KnxMap(targets=targets.targets, max_workers=args.workers)
else:
LOGGER.info('Scanning {} target(s)'.format(len(targets.targets)))

scanner = KnxScanner(targets=targets.targets, max_workers=args.workers)
knxmap = KnxMap(max_workers=args.workers)

try:
loop.run_until_complete(scanner.scan(
search_mode=args.search_mode,
search_timeout=args.search_timeout,
bus_targets=bus_targets.targets,
bus_info=args.bus_info,
bus_monitor_mode=args.bus_monitor_mode,
group_monitor_mode=args.group_monitor_mode,
iface=args.iface))
if args.cmd == 'search':
if not args.iface:
LOGGER.error('--search option requires -i/--interface argument')
sys.exit(1)
if os.geteuid() != 0:
LOGGER.error('-i/--interface option requires superuser privileges')
sys.exit(1)
loop.run_until_complete(knxmap.search(
search_timeout=args.search_timeout,
iface=args.iface))
elif args.cmd == 'write':
loop.run_until_complete(knxmap.group_writer(
target=args.group_write_address,
value=args.group_write_value,
routing=args.routing,
desc_timeout=args.timeout,
desc_retries=args.retries,
iface=args.iface))
elif args.cmd == 'monitor':
loop.run_until_complete(knxmap.monitor(
group_monitor_mode=args.group_monitor_mode))
elif args.cmd == 'brute':
loop.run_until_complete(knxmap.brute(
bus_target=KnxTargets(args.bus_target)))
elif args.cmd == 'scan':
LOGGER.info('Scanning {} target(s)'.format(len(targets.targets)))
bus_targets = KnxTargets(args.bus_targets)
loop.run_until_complete(knxmap.scan(
desc_timeout=args.timeout,
desc_retries=args.retries,
bus_targets=bus_targets.targets,
bus_info=args.bus_info,
auth_key=args.auth_key))
except KeyboardInterrupt:
for t in asyncio.Task.all_tasks():
t.cancel()
loop.run_forever()

if scanner.bus_protocols:
# Make sure to send a DISCONNECT_REQUEST when the bus monitor will be closed
for p in scanner.bus_protocols:
if knxmap.bus_protocols:
# Make sure to send a DISCONNECT_REQUEST
# when the bus monitor will be closed.
for p in knxmap.bus_protocols:
p.knx_tunnel_disconnect()
finally:
loop.close()
Expand Down
Loading

0 comments on commit cd5d391

Please sign in to comment.