1
1
mirror of https://github.com/eblot/pybootd.git synced 2024-09-11 22:17:44 +03:00

Merge pull request #9 from DavidDecotigny/decot-master

Decot master
This commit is contained in:
Emmanuel Blot 2016-10-05 11:14:46 +02:00 committed by GitHub
commit dccf4bf96f
6 changed files with 112 additions and 31 deletions

View File

@ -22,10 +22,13 @@ Python
------
- Python_ 2.6 or above is required. Python_ 3.x is not yet supported.
- Netifaces_ Python module
- Optional: Netifaces_ Python module; In its absence, iproute2_ is expected
- Optional: python-pkg-resources_ Python module
.. _Python: http://python.org/
.. _Netifaces: http://alastairs-place.net/netifaces/
.. _iproute2: http://www.linuxfoundation.org/collaborate/workgroups/networking/iproute2
.. _python-pkg-resources: http://pythonhosted.org/distribute/pkg_resources.html
Permissions
-----------
@ -73,9 +76,13 @@ Common errors
-------------
``pybootd.pxed.BootpError: Unable to detect network configuration``
This error is often triggered when the ``pool_start`` address is not part of
a valid network. Double check the network configuration and fix up the
``[bootp]`` section so that it matches the actual network.
This error is often triggered when the ``pool_start`` address is not
part of a valid network. Double check the network configuration and
fix up the ``[bootp]`` section so that it matches the actual
network. If you don't want to allocate addresses dynamically from
the pool (with ``pool_count = 0``), you still need to specify
``pool_start`` to some address in the local network you want to
serve (*eg.* the address of your local server).
Configuration
-------------
@ -172,17 +179,26 @@ client requests at least an IP address twice:
Domain part of the client FQDN, that is the network's domain name.
``dns``
IP address of the DNS server. The server only accepts a single address.
IP addresses of DNS servers. Multiple addresses are separated with
semicolon. Specify ``auto`` to re-use DNS addresses used by the
server. Note that most DHCP clients will only consider the first
DNS address if multiple are provided.
``lease_time``
Validity in seconds of a DHCP lease. Please note that the BOOTP daemon does
not manage lease expiration; this value has therefore little meaning.
``pool_start``
First address to allocate for a BOOT client.
First address to allocate for a BOOT client. This has to be an
address in the local network you want to serve, even if
``pool_count`` is set to 0, in which case the address of the DHCP
server is a good choice.
``pool_count``
The maximum number of clients that can be served.
The maximum number of IP addresses that can be dynamically
allocated from the pool to BOOTP/DHCP clients. Set it to 0 to
prevent server from dynamically allocating IP addresses from the
pool and see ``static_dhcp`` below.
``notify``
When defined, the IP address and port (using a column separator:
@ -209,6 +225,17 @@ client requests at least an IP address twice:
AA-BB-CC-DD-EE-FF = enable
``[static_dhcp]`` section
.........................
The ``[static_dhcp]`` section contains one entry for each MAC
address to associate with a specific IP address. The IP address can be
any IPv4 address in dotted notation, *i.e.*:
AA-BB-CC-DD-EE-FF = 192.168.1.2
The MAC addresses specified here will automatically be allowed,
unless ``[mac]`` section specifies otherwise.
``[uuid]`` section
..................

View File

@ -21,8 +21,12 @@ import os
import sys
def _get_package_name(default='', version='1.5.0'):
from pkg_resources import WorkingSet
ws = WorkingSet()
try:
from pkg_resources import WorkingSet
except ImportError:
ws = []
else:
ws = WorkingSet()
_path, _ = os.path.split(os.path.dirname( \
sys.modules['pybootd'].__file__))
_path = os.path.normpath(_path)
@ -48,9 +52,12 @@ def pybootd_path(path):
else:
try:
from pkg_resources import Requirement, resource_filename
from pkg_resources import get_distribution
except ImportError:
raise IOError('pkg_resources module not available')
try:
newpath = resource_filename(Requirement.parse(PRODUCT_NAME), path)
if not newpath:
from pkg_resources import get_distribution
localpath = get_distribution(PRODUCT_NAME).location
newpath = os.path.join(localpath, path)
except KeyError:
@ -58,4 +65,3 @@ def pybootd_path(path):
if not os.path.isfile(newpath) and not os.path.isdir(newpath):
raise IOError('No such file or directory (local)')
return newpath

View File

@ -215,6 +215,13 @@ class BootpServer:
for entry in self.config.options(access):
self.acl[entry.upper()] = \
to_bool(self.config.get(access, entry))
# pre-fill ippool if specified
if self.config.has_section('static_dhcp'):
for mac_str, ip_str in config.items('static_dhcp'):
mac_key = mac_str.upper()
self.ippool[mac_key] = ip_str
if access == 'mac' and mac_str not in self.acl:
self.acl[mac_key] = True
self.access = access
# Private
@ -274,9 +281,9 @@ class BootpServer:
self.log.debug(" option %d: '%s', size:%d %s" % \
(tag, option, length, hexline(value)))
except KeyError:
self.log.error(' unknown option %d, size:%d %s:' % \
(tag, length, hexline(value)))
return None
self.log.debug(' unknown option %d, size:%d %s:' % \
(tag, length, hexline(value)))
continue
dhcp_tags[tag] = value
def build_pxe_options(self, options, server):
@ -383,6 +390,9 @@ class BootpServer:
to_bool(self.config.get(self.bootp_section, sdhcp))
if not simple_dhcp:
return
if not dhcp_msg_type:
# Legacy DHCP: assuming discover by default
dhcp_msg_type = DHCP_DISCOVER
# if access control is enable
if self.access:
@ -549,9 +559,12 @@ class BootpServer:
'dns', None)
if dns:
if dns.lower() == 'auto':
dns = self.get_dns_server() or socket.inet_ntoa(server)
dns = socket.inet_aton(dns)
pkt += struct.pack('!BB4s', DHCP_IP_DNS, 4, dns)
dns_list = self.get_dns_servers() or [socket.inet_ntoa(server)]
else:
dns_list = dns.split(';')
for dns_str in dns_list:
dns_ip = socket.inet_aton(dns_str)
pkt += struct.pack('!BB4s', DHCP_IP_DNS, 4, dns_ip)
pkt += struct.pack('!BBI', DHCP_LEASE_TIME, 4,
int(self.config.get(self.bootp_section, 'lease_time',
str(24*3600))))
@ -579,20 +592,22 @@ class BootpServer:
(currentstate, newstate))
self.states[mac_str] = newstate
def get_dns_server(self):
def get_dns_servers(self):
nscre = re.compile('nameserver\s+(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s')
result = []
try:
with open('/etc/resolv.conf', 'r') as resolv:
for line in resolv:
mo = nscre.match(line)
if mo:
dns = mo.group(1)
self.log.info('Found primary nameserver: %s' % dns)
return dns
self.log.info('Found nameserver: %s' % dns)
result.append(dns)
except Exception, e:
pass
self.log.info('No nameserver found')
return None
if not result:
self.log.info('No nameserver found')
return result
def get_filename(self, ip):
"""Returns the filename defined for a host"""

View File

@ -134,7 +134,7 @@ class TftpConnection(object):
else:
try:
resource = pybootd_path(resource)
except IOError:
except Exception:
if not self.server.genfilecre.match(resource):
if resource.startswith('^%s' % os.sep):
resource = os.path.join( \

View File

@ -18,12 +18,18 @@
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
from ConfigParser import SafeConfigParser
import commands
import logging
import re
import socket
import struct
import sys
try:
import netifaces
except ImportError:
netifaces = None
# String values evaluated as a true boolean values
TRUE_BOOLEANS = ['on','true','enable','enabled','yes','high','ok','1']
# String values evaluated as a false boolean values
@ -122,13 +128,7 @@ def iptoint(ipstr):
def inttoip(ipval):
return socket.inet_ntoa(struct.pack('!I', ipval))
def get_iface_config(address):
if not address:
return None
try:
import netifaces
except ImportError:
raise AssertionError("netifaces module is not installed")
def _netifaces_get_iface_config(address):
pool = iptoint(address)
for iface in netifaces.interfaces():
ifinfo = netifaces.ifaddresses(iface)
@ -148,6 +148,39 @@ def get_iface_config(address):
return config
return None
def _iproute_get_iface_config(address):
pool = iptoint(address)
iplines=(line.strip()
for line in commands.getoutput("ip address show").split('\n'))
iface = None
for l in iplines:
items = l.split()
if not items:
continue
if items[0].endswith(':'):
iface = items[1][:-1]
elif items[0] == 'inet':
saddr, smasklen = items[1].split('/', 1)
addr = iptoint(saddr)
masklen = int(smasklen)
mask = ((1 << masklen) - 1) << (32 - masklen)
ip = addr & mask
ip_client = pool & mask
delta = ip ^ ip_client
if not delta:
return { 'ifname': iface,
'server': inttoip(addr),
'net': inttoip(ip),
'mask': inttoip(mask) }
return None
def get_iface_config(address):
if not address:
return None
if netifaces:
return _iproute_get_iface_config(address)
return _netifaces_get_iface_config(address)
class EasyConfigParser(SafeConfigParser):
"ConfigParser extension to support default config values"
def get(self, section, option, default=None):

View File

@ -36,7 +36,7 @@ setup(
url='http://github.com/eblot/pybootd',
download_url='https://github.com/eblot/pybootd/tarball/master',
packages=['pybootd'],
requires=['netifaces (>= 0.5)'],
# requires=['netifaces (>= 0.5)'],
classifiers=[
'Development Status :: 4 - Beta',
'Environment :: No Input/Output (Daemon)',