mirror of
https://github.com/eblot/pybootd.git
synced 2024-09-11 22:17:44 +03:00
commit
dccf4bf96f
41
README.rst
41
README.rst
@ -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
|
||||
..................
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"""
|
||||
|
@ -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( \
|
||||
|
@ -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):
|
||||
|
2
setup.py
2
setup.py
@ -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)',
|
||||
|
Loading…
Reference in New Issue
Block a user