Skip to content

Commit

Permalink
Merge branch 'master' of git.cygnusnet.de:pypureomapi
Browse files Browse the repository at this point in the history
  • Loading branch information
cygnusb committed Feb 12, 2019
2 parents 8c9e396 + df1057b commit 5e6951f
Show file tree
Hide file tree
Showing 11 changed files with 395 additions and 278 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.idea
*.pyc
atlassian-ide-plugin.xml
__pycache__
build
test_live_omapi.py
264 changes: 89 additions & 175 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,30 @@
[![Build Status](https://travis-ci.org/CygnusNetworks/pypureomapi.svg?branch=master)](https://travis-ci.org/CygnusNetworks/pypureomapi)
[![Latest Version](https://img.shields.io/pypi/v/pypureomapi.svg)](https://pypi.python.org/pypi/pypureomapi) [![Downloads](https://img.shields.io/pypi/dm/pypureomapi.svg)](https://pypi.python.org/pypi/pypureomapi) [![Downloads PyPi](https://img.shields.io/pypi/dw/pypureomapi.svg)](https://pypi.python.org/pypi/pypureomapi)
[![Latest Version](https://img.shields.io/pypi/v/pypureomapi.svg)](https://pypi.python.org/pypi/pypureomapi)
[![PyPi Status](https://img.shields.io/pypi/status/pypureomapi.svg)](https://pypi.python.org/pypi/pypureomapi) [![PyPi Versions](https://img.shields.io/pypi/pyversions/pypureomapi.svg)](https://pypi.python.org/pypi/pypureomapi)

pypureomapi
===========

pypureomapi is a Python implementation of the DHCP OMAPI protocol used in the most popular Linux DHCP server from ISC. It can be used to query and modify leases and other objects exported by an ISC DHCP server. The interaction can be authenticated using HMAC-MD5. Besides basic ready to use operations, custom interaction can be implemented with limited effort. It can be used as a drop-in replacement for pyomapic, but provides error checking and extensibility beyond pyomapic.
pypureomapi is a Python implementation of the DHCP OMAPI protocol used in the most popular Linux DHCP server from ISC.
It can be used to query and modify leases and other objects exported by an ISC DHCP server.
The interaction can be authenticated using HMAC-MD5. Besides basic ready to use operations, custom interaction can be implemented with limited effort.
It can be used as a drop-in replacement for pyomapic, but provides error checking and extensibility beyond pyomapic.

#Example omapi lookup

```
import pypureomapi
KEYNAME="defomapi"
BASE64_ENCODED_KEY="+bFQtBCta6j2vWkjPkNFtgA=="
lease_ip = "192.168.0.250" # ip of some host with a dhcp lease on your dhcp server
dhcp_server_ip="127.0.0.1"
port = 7911 # Port of the omapi service
try:
o = pypureomapi.Omapi(dhcp_server_ip,port, KEYNAME, BASE64_ENCODED_KEY)
mac = o.lookup_mac(lease_ip)
print "%s is currently assigned to mac %s" % (lease_ip, mac)
except pypureomapi.OmapiErrorNotFound:
print "%s is currently not assigned" % (lease_ip,)
except pypureomapi.OmapiError, err:
print "an error occured: %r" % (err,)
```

#Server side configugration for ISC DHCP3
## Server side configugration for ISC DHCP3

To allow a OMAPI access to your ISC DHCP3 DHCP Server you should define the following in your dhcpd.conf config file:

```
key defomapi {
algorithm hmac-md5;
secret +bFQtBCta6j2vWkjPkNFtgA==;
secret +bFQtBCta6j2vWkjPkNFtgA==; # FIXME: replace by your own dnssec key (see below)!!!
};
omapi-key defomapi;
omapi-port 7911;
```

Replace the given secret by a key created on your own!
**Replace the given secret by a key created on your own!**

To generate a key use the following command:

Expand All @@ -58,182 +39,115 @@ is possible to generate the key value for the config file directly:
dd if=/dev/urandom bs=16 count=1 2>/dev/null | openssl enc -e -base64
```

#Create Group
## Example omapi lookup

This is a short example, of how to use basic lookup functions **lookup_mac** and **lookup_ip** to quickly query a DHCP lease on a ISC DHCP Server.

A group needs at least one statement. See UseCaseSupersedeHostname for example statements.
```
def add_group(omapi, groupname, statements):
"""
@type omapi: Omapi
@type groupname: bytes
@type statements: str
"""
msg = OmapiMessage.open("group")
msg.message.append(("create", struct.pack("!I", 1)))
msg.obj.append(("name", groupname))
msg.obj.append(("statements", statements))
response = self.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiError("add group failed")
```

And with that, to attach a new host to a group:
```
def add_host_with_group(omapi, ip, mac, groupname):
msg = OmapiMessage.open("host")
msg.message.append(("create", struct.pack("!I", 1)))
msg.message.append(("exclusive", struct.pack("!I", 1)))
msg.obj.append(("hardware-address", pack_mac(mac)))
msg.obj.append(("hardware-type", struct.pack("!I", 1)))
msg.obj.append(("ip-address", pack_ip(ip)))
msg.obj.append(("group", groupname))
response = omapi.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiError("add failed")
```
from __future__ import print_function
import pypureomapi
#Supersede Hostname
KEYNAME="defomapi"
BASE64_ENCODED_KEY="+bFQtBCta6j2vWkjPkNFtgA==" # FIXME: be sure to replace this by your own key!!!
See http://jpmens.net/2011/07/20/dynamically-add-static-leases-to-dhcpd/ for the original idea.
dhcp_server_ip="127.0.0.1"
port = 7911 # Port of the omapi service
```
def add_host_supersede_name(omapi, ip, mac, name):
"""Add a host with a fixed-address and override its hostname with the given name.
@type omapi: Omapi
@type ip: str
@type mac: str
@type name: str
@raises ValueError:
@raises OmapiError:
@raises socket.error:
"""
msg = OmapiMessage.open("host")
msg.message.append(("create", struct.pack("!I", 1)))
msg.message.append(("exclusive", struct.pack("!I", 1)))
msg.obj.append(("hardware-address", pack_mac(mac)))
msg.obj.append(("hardware-type", struct.pack("!I", 1)))
msg.obj.append(("ip-address", pack_ip(ip)))
msg.obj.append(("name", name))
msg.obj.append(("statement", "supersede host-name %s;" % name))
response = omapi.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiError("add failed")
omapi = pypureomapi.Omapi(dhcp_server_ip, port, KEYNAME, BASE64_ENCODED_KEY)
mac = omapi.lookup_mac("192.168.0.250")
print("%s is currently assigned to mac %s" % (lease_ip, mac))
ip = omapi.lookup_ip(mac)
print("%s mac currently has ip %s assigned" % (mac, ip))
```

Similarly the router can be superseded.
If you need full lease information, you can also query the full lease directly by using **lookup_by_lease**, which gives you the full lease details as output:

#add host declaration without static ip
```
def add_host_without_ip(self, mac):
"""Create a host object with given mac address without assigning a static ip address.
@type ip: str
@type mac: str
@raises ValueError:
@raises OmapiError:
@raises socket.error:
"""
msg = OmapiMessage.open(b"host")
msg.message.append((b"create", struct.pack("!I", 1)))
msg.message.append((b"exclusive", struct.pack("!I", 1)))
msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
response = self.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiError("add failed")
lease = omapi.lookup_by_lease(mac="24:79:2a:0a:13:c0")
for k, v in res.items():
print("%s: %s" % (k, v))
```

#lookup hostname based on ip address
Output:
```
def lookup_hostname(self, ip):
"""Look up a lease object with given ip address and return the associated client hostname.
@type ip: str
@rtype: str or None
@raises ValueError:
@raises OmapiError:
@raises OmapiErrorNotFound: if no lease object with the given ip
address could be found or the object lacks a hostname
@raises socket.error:
"""
msg = OmapiMessage.open(b"lease")
msg.obj.append((b"ip-address", pack_ip(ip)))
response = self.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiErrorNotFound()
try:
return (dict(response.obj)[b"client-hostname"])
except KeyError: # client hostname
raise OmapiErrorNotFound()
state: 2
ip-address: 192.168.10.167
dhcp-client-identifier: b'\x01$y*\x06U\xc0'
subnet: 6126
pool: 6127
hardware-address: 24:79:2a:0a:13:c0
hardware-type: 1
ends: 1549885690
starts: 1549885390
tstp: 1549885840
tsfp: 1549885840
atsfp: 1549885840
cltt: 1549885390
flags: 0
clientip: b'192.168.10.167'
clientmac: b'24:79:2a:0a:13:c0'
clientmac_hostname: b'24792a0a13c0'
vendor-class-identifier: b'Ruckus CPE'
agent.circuit-id: b'\x00\x04\x00\x12\x00-'
agent.remote-id: b'\x00\x06\x00\x12\xf2\x8e!\x00'
agent.subscriber-id: b'wifi-basement'
```

#Get a lease

Original idea from Josh West.
To check if a lease is still valid, you should check ends and state:

```
def get_lease(omapi, ip):
"""
@type omapi: Omapi
@type ip: str
@rtype: OmapiMessage
@raises OmapiErrorNotFound:
@raises socket.error:
"""
msg = OmapiMessage.open("lease")
msg.obj.append(("ip-address", pack_ip(ip)))
response = omapi.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiErrorNotFound()
return response
if lease["ends"] < time.time() or lease["state"] != 2:
print("Lease is not valid")
```

#Get an IP from a host MAC address
Most attributes will be decoded directly into the corresponding human readable values.
Converted attributes are ip-address, hardware-address and all 32 bit and 8 bit integer values. If you need raw values, you can add a raw option to the lookup:

```
def lookup_ip_host(self, mac):
"""Lookup a host object with with given mac address.
@type mac: str
@raises ValueError:
@raises OmapiError:
@raises socket.error:
"""
msg = OmapiMessage.open(b"host")
msg.obj.append((b"hardware-address", pack_mac(mac)))
msg.obj.append((b"hardware-type", struct.pack("!I", 1)))
response = self.query_server(msg)
if response.opcode != OMAPI_OP_UPDATE:
raise OmapiErrorNotFound()
try:
return unpack_ip(dict(response.obj)[b"ip-address"])
except KeyError: # ip-address
raise OmapiErrorNotFound()
lease = omapi.lookup_by_lease(mac="24:79:2a:0a:13:c0", raw=True)
for k, v in res.items():
print("%s: %s" % (k, v))
```

#Change Group
Output:

```
def change_group(omapi, name, group):
"""Change the group of a host given the name of the host.
@type omapi: Omapi
@type name: str
@type group: str
"""
m1 = OmapiMessage.open("host")
m1.update_object(dict(name=name))
r1 = omapi.query_server(m1)
if r1.opcode != OMAPI_OP_UPDATE:
raise OmapiError("opening host %s failed" % name)
m2 = OmapiMessage.update(r.handle)
m2.update_object(dict(group=group))
r2 = omapi.query_server(m2)
if r2.opcode != OMAPI_OP_UPDATE:
raise OmapiError("changing group of host %s to %s failed" % (name, group))
b'state': b'\x00\x00\x00\x02'
b'ip-address': b'\xc0\xa8\n\xa7'
...
```

#Custom Integration
The following lookup functions are implemented, allowing directly querying the different types:

* lookup_ip_host(mac) - lookups up a host object (static defined host) by mac
* lookup_ip(mac) - lookups a lease object by mac and returns the ip
* lookup_host(name) - lookups a host object by name and returns the ip, mac and hostname
* lookup_host_host(mac) - lookups a host object by mac and returns the ip, mac and name
* lookup_hostname(ip) - lookups a lease object by ip and returns the client-hostname

These special functions use:

* lookup_by_host - generic lookup function for host objects
* lookup_by_lease - generic lookup function for lease objects

which provide full access to complete lease data.

## Add and delete host objects

For adding and deleting host objects (static DHCP leases), there are multiple functions:

* add_host(ip, mac)
* add_host_supersede_name(ip, mac, name)
* add_host_without_ip(mac)
* add_host_supersede(ip, mac, name, hostname=None, router=None, domain=None)
* add_group(groupname, statements)
* add_host_with_group(ip, mac, groupname))

See http://jpmens.net/2011/07/20/dynamically-add-static-leases-to-dhcpd/ for original idea (which is now merged) and detailed explanation.

# Custom Integration

Assuming there already is a connection named `o` (i.e. a `Omapi` instance, see [Example]).
To craft your own communication with the server you need to create an `OmapiMessage`, send it, receive a response and evaluate that response being an `OmapiMessage` as well. So here we go and create our first message.
Expand Down
16 changes: 15 additions & 1 deletion debian/changelog
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
pypureomapi (0.8) UNRELEASED; urgency=low

* Implement more generic lookup functions
* move group examples to source
* improve documentation

-- Dr. Torge Szczepanek <[email protected]> Mon, 11 Feb 2019 13:29:22 +0100

pypureomapi (0.7) unstable; urgency=low

* add new method add_host_supersede

-- Dr. Torge Szczepanek <[email protected]> Fri, 21 Sep 2018 15:14:59 +0200

pypureomapi (0.6) unstable; urgency=low

* add optional timeout parameter
Expand All @@ -20,7 +34,7 @@ pypureomapi (0.4) unstable; urgency=low
* Use new-style classes
* Disabled doctests - to be included again later in next upstream release
* Change project source from Google Code to Github after upstream migration
* Change maintainer to Torge Szczepanek and remove
* Change maintainer to Torge Szczepanek and remove
Helmut Grohne from uploaders
[ Helmut Grohne ]
* Bump python dependency to 2.6 since we need it.
Expand Down
2 changes: 1 addition & 1 deletion debian/compat
Original file line number Diff line number Diff line change
@@ -1 +1 @@
9
10
12 changes: 10 additions & 2 deletions debian/control
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
Source: pypureomapi
Maintainer: Dr. Torge Szczepanek <[email protected]>
Standards-Version: 3.9.6
Standards-Version: 4.3.0
Section: python
Priority: extra
Homepage: https://github.com/CygnusNetworks/pypureomapi
Build-Depends: debhelper (>= 9), python (>= 2.6), python-all, python3, python3-all, dh-python
Rules-Requires-Root: no
Build-Depends: debhelper (>= 10), python (>= 2.6), python-all, python3, python3-all, dh-python
X-Python-Version: >= 2.6
X-Python3-Version: >= 3.3

Expand Down Expand Up @@ -33,3 +34,10 @@ Description: ISC DHCP OMAPI protocol implementation in Python3
a pure Python implementation for omapi, pypureomapi was born. It can mostly
be used as a drop-in replacement for pyomapic.
This is the py3 version of the module.

Package: python-pypureomapi-doc
Architecture: all
Section: doc
Depends: ${misc:Depends}
Description: ISC DHCP OMAPI protocol implementation (common documentation)
This package provides usage documentation for the Python OMAPI implementation
2 changes: 1 addition & 1 deletion debian/copyright
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Upstream-Contact: Dr. Torge Szczepanek <[email protected]>
Source: https://github.com/CygnusNetworks/pypureomapi

Files: *
Copyright: 2010-2015, Cygnus Networks GmbH <[email protected]>
Copyright: 2010-2019, Cygnus Networks GmbH <[email protected]>
License: Apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
File renamed without changes.
1 change: 0 additions & 1 deletion debian/python3-pypureomapi.docs

This file was deleted.

Loading

0 comments on commit 5e6951f

Please sign in to comment.