GithubHelp home page GithubHelp logo

pypureomapi's Introduction

Codacy Badge Build Status Latest Version PyPi Status PyPi Versions

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 provides error checking and extensibility.

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==; # 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!

To generate a key use the following command:

/usr/sbin/dnssec-keygen -a HMAC-MD5 -b 128 -n USER defomapi

which will create two files containing a HMAC MD5 key. Alternatively, it 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

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.

Python 3 example:

import pypureomapi

KEYNAME=b"defomapi"
BASE64_ENCODED_KEY=b"+bFQtBCta6j2vWkjPkNFtgA=="  # FIXME: be sure to replace this by your own key!!!

dhcp_server_ip="127.0.0.1"
port = 7911 # Port of the omapi service

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))

Python 2 example:

from __future__ import print_function
import pypureomapi

KEYNAME="defomapi"
BASE64_ENCODED_KEY="+bFQtBCta6j2vWkjPkNFtgA=="  # FIXME: be sure to replace this by your own key!!!

dhcp_server_ip="127.0.0.1"
port = 7911 # Port of the omapi service

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))

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:

lease = omapi.lookup_by_lease(mac="24:79:2a:0a:13:c0")
for k, v in lease.items():
	print("%s: %s" % (k, v))

Output:

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'

To check if a lease is still valid, you should check ends and state:

if lease["ends"] < time.time() or lease["state"] != 2:
    print("Lease is not valid")

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:

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))

Output:

b'state': b'\x00\x00\x00\x02'
b'ip-address': b'\xc0\xa8\n\xa7'
...

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.

m1 = OmapiMessage.open("host")

We are using a named constructor (OmapiMessage.open). It fills in the opcode (as OMAPI_OP_OPEN), generates a random transaction id, and uses the parameter for the type field. This is the thing you want almost all the time. In this case we are going to open a host object, but we did not specify which host to open. For example we can select a host by its name.

m1.update_object(dict(name="foo"))

The next step is to interact with the DHCP server. The easiest way to do so is using the query_server method. It takes an OmapiMessageand returns another.

r1 = o.query_server(m1)

The returned OmapiMessage contains the parsed response from the server. Since opening can fail, we need to check the opcode attribute. In case of success its value is OMAPI_OP_UPDATE. As with files on unix we now have a descriptor called r1.handle. So now we are to modify some attribute about this host. Say we want to set its group. To do so we construct a new message and reference the opened host object via its handle.

m2 = OmapiMessage.update(r1.handle)

Again OmapiMessage.update is a named constructor. It fills in the opcode (as OMAPI_OP_UPDATE), generates a random transaction id and fills in the handle. So now we need to add the actual modification to the message and send the message to the server.

m2.update_object(dict(group="bar"))
r2 = o.query_server(m2)

We receive a new message and need to check the returned opcode which should be OMAPI_OP_UPDATE again. Now we have a complete sequence.

As can be seen, the OMAPI protocol permits flexible interaction and it would be unreasonable to include every possibility as library functions. Instead you are encouraged to subclass the Omapi class and define your own methods. If they prove useful in multiple locations, please submit them to the issue tracker.

pypureomapi's People

Contributors

anlaksh88 avatar codacy-badger avatar cygnusb avatar edwardbetts avatar harlowja avatar jacobweinstock avatar jagter avatar jonjacs avatar mrd000 avatar nageshlop avatar neirbowj avatar sagarun avatar terricain avatar vatson112 avatar wmturner avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pypureomapi's Issues

Is it possible to add new lease with mac address?

For a number of reasons, I don't want to create object with type host.
My attempts to assign a new ip address to the hardware-address have failed.
The basis of the code is taken from dev change_group

  1. I open a new lease, do an IP search (10.10.0.213) and request
  2. Through handle I try to update/add mac and reservation flag.
    m2.update_object({b'hardware-address':pack_mac('56:6F:60:B1:01:02'),b'flags':b'0x04'})
    As a result, the following is added to dhcpd.leases
    lease 10.10.0.213 {
    binding state free;
    reserved;
    set remote-handle = %2;
    }
    And I can set and delete this flag reserved, but I can't manage the mac address.

Is it possible to retrieve all the leases with a MAC matching a regex (specific vendor) or all the leases with the same hostname?

Hi guys

Thanks for this module it is exactly what I needed
Is it possible to retrieve all the leases with a MAC matching a regex (specific vendor) or all the leases with the same hostname?

Concrete example: I have virtual appliances that boot up and get a DHCP IP. Since they are vanilla images they all have the same hostname but they get their own IP (diff MACs)
I would like to be able to get a list of these appliances I just deployed and that got an IP from ISD DHCP. Is it possible to get the lease for all the objects having a certain vendor prefix for the MAC address or all the leases here the hostname has a certain string as value ( in my case "ROUTER")

TypeError when creating initial omapi instance

import pypureomapi

KEYNAME="defomapi"
KEY="wGYh1n9uC9Regws6n111zw=="

dhcp_server_ip="my.server.ip.address"
port = 7911 # Port of the omapi service

omapi = pypureomapi.Omapi(dhcp_server_ip, port, KEYNAME, KEY)

Yields:

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 980, in __init__
    self.initialize_authenticator(newauth)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 1064, in initialize_authenticator
    response = self.query_server(msg)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 1053, in query_server
    self.send_message(message)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 1044, in send_message
    self.protocol.send_message(message, sign)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 944, in send_message
    message.sign(self.authenticators[self.defauth])
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 466, in sign
    self.signature = authenticator.sign(self.as_string(forsigning=True))
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 457, in as_string
    self.serialize(ret, forsigning)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 443, in serialize
    outbuffer.add_bindict(self.obj)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 212, in add_bindict
    self.add_net16string(key).add_net32string(value)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 183, in add_net32string
    return self.add_net32int(len(string)).add(string)
  File "/home/<myuser>/dhcpapi/venv/lib/python3.6/site-packages/pypureomapi.py", line 145, in add
    self.buff.write(data)
TypeError: a bytes-like object is required, not 'str'

Relevant portion of ISC-DHCP config:

key defomapi {
    algorithm hmac-md5;
    secret wGYh1n9uC9Regws6n111zw==;
};

omapi-key defomapi;
omapi-port 7911;

Python 3.6.7 on 18.04.2
Any ideas would be very welcome. Thank you!

check_connected() unreliable.

Killing the dhcpd process after creating the omapi object does not result in check_connected() raising an exception. A more reliable approach should employ some ping-pong test to the isc-dhcp-server, such as is done in the wpa_supplicant control api for example.

NameError: global name 'OmapiMessage' is not defined

I'm well versed in dhcpd and omapi, not so much python, so this is a noob question, sorry.

In readme.md --

When I try to use the examples (e.g. add_host_supercede_name()), I get:

"NameError: global name 'OmapiMessage' is not defined" [on msg = OmapiMessage.open("host")]

How do I define/initialize OmapiMessage correctly to get these examples to work?

update pypureompi in pypi

Can python package index(pypi) be updated with latest version of pypureomapi so that pip install would install recent changes?

Switch to using ASLv2 license?

Hi folks @ CygnusNetworks,

A request that you may or may not be ok with (but I and others would prefer it),

OpenStack (http://openstack.org/) would really like to use this code in a ongoing set of work in the networking project (Neutron http://wiki.openstack.org/wiki/Neutron) but yet there are license incompatibilities with doing this and there is resistance to including a library that is GPLv3 licensed (due to license differences with the apache software license).

For some of the conversation/discussions/bugs about this:

There is still desire (from spandhe and others) to use this library as it seems to be the best out there right now, and it would be really cool if this could switch to being ASLv2 (http://www.apache.org/licenses/LICENSE-2.0) so that this could happen.

What are the thoughts about doing this? Would such a conversion be possible (seeing that the number of contributors are currently small, it would likely be easy at this stage)?

P.S. It would also have other benefits as well, more users, more eyes on this code, greater usage (and bug fixes...).

Unable to update a host object: "can't update object"

I am using following wrapper method to update a host object:

 def update():
      try:
          o = pypureomapi.Omapi(dhcp_server_ip, port)
          m1 = pypureomapi.OmapiMessage.open("host")
          m1.update_object(dict(name=<name>))
          r1 = o.query_server(m1)
          m2 = pypureomapi.OmapiMessage.update(r1.handle))
          m2.update_object(dict(group="bar"))
          r2 = o.query_server(m2) <<<< fails here
     except pypureomapi.OmapiErrorNotFound:
          print "%s is currently not assigned" % (name,)
     except pypureomapi.OmapiError, err:
          print "an error occured: %r" % (err,)

leases database has entry:

  host <name> {
       dynamic;
       hardware ethernet <mac>;
       fixed-address <ip>;
              supersede host-name = "<hostname>";

Error log:
message: [('result', '\x00\x00\x00\x17'), ('message', "can't update object")]
opcode: 5

Add travisci

It'd be great to have a automatic CI running the unittests of this library (assuming some exist). Perhaps travis ci or other similar system?

vendor specific routes

I can add classless static routes but not vendor specific ones like "ms-classless-static-routes".
Not sure if it is not supported or I'm not able to find a way to make it work.

bytes-like object is required, not 'str'

So I can connect using the Example with an omapi object, passing in the byted secret and key, even do things like:

omapi..lookup_host('example1.com')

which returns a dict just dandy with the ip, mac, and hostname.

What I need to do is find a host record (not lease) by IP which doesn't seem to be part of the default library options (as I'm trying to find stale host records that are holding on to specific IPs and delete them) so was following the example for custom integration. When I run the:

r1 = omapi.query_server(m1)

I get the ... File "/usr/local/lib/python3.8/site-packages/pypureomapi.py", line 183, in add_net32string return self.add_net32int(len(string)).add(string) File "/usr/local/lib/python3.8/site-packages/pypureomapi.py", line 145, in add self.buff.write(data)

Stack trace. I don't understand why doing it this way is giving me this error (implying my secrets are not bytes)when I can use the same omapi with the lookup functions just fine.

This is version 0.8 on python3

Update SOA Serial

Hello,

I'm splitting hairs here, but per RFC 1033 we should be updating the SOA record serial number when we make changes to the zone file. Happy to make the change if you agree.

Best,
-William

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.