def _logInvalidValue(name, value, subnet, serial): logging.writeLog("Invalid value for %(subnet)s:%(serial)i:%(name)s: %(value)s" % { 'subnet': subnet, 'serial': serial, 'name': name, 'value': value, })
def _logDHCPAccess(self, mac): """ Increments the number of times the given MAC address has accessed this server. If the value exceeds the policy threshold, the MAC is ignored as potentially belonging to a malicious user. @type mac: basestring @param mac: The MAC being evaluated. @rtype: bool @return: True if the MAC's request should be processed. """ if conf.ENABLE_SUSPEND: self._stats_lock.acquire() try: assignments = self._dhcp_assignments.get(mac) if not assignments: self._dhcp_assignments[mac] = 1 else: self._dhcp_assignments[mac] = assignments + 1 if assignments + 1 > conf.SUSPEND_THRESHOLD: logging.writeLog('%(mac)s issuing too many requests; ignoring for %(time)i seconds' % { 'mac': mac, 'time': conf.MISBEHAVING_CLIENT_TIMEOUT, }) self._ignored_addresses.append([mac, conf.MISBEHAVING_CLIENT_TIMEOUT]) return False finally: self._stats_lock.release() return True
def _doResponse(self): """ Renders the current state of the memory-log as HTML for consumption by the client. """ try: self.send_response(200) self.send_header('Content-type', 'text/html') self.send_header('Last-modified', time.strftime('%a, %d %b %Y %H:%M:%S %Z')) self.end_headers() self.wfile.write('<html><head><title>%(name)s log</title></head><body>' % {'name': conf.SYSTEM_NAME,}) self.wfile.write('<div style="width: 950px; margin-left: auto; margin-right: auto; border: 1px solid black;">') self.wfile.write('<div>Statistics:<div style="text-size: 0.9em; margin-left: 20px;">') for (timestamp, packets, discarded, time_taken, ignored_macs) in logging.readPollRecords(): if packets: turnaround = time_taken / packets else: turnaround = 0.0 self.wfile.write("%(time)s : received: %(received)i; discarded: %(discarded)i; turnaround: %(turnaround)fs/pkt; ignored MACs: %(ignored)i<br/>" % { 'time': time.ctime(timestamp), 'received': packets, 'discarded': discarded, 'turnaround': turnaround, 'ignored': ignored_macs, }) self.wfile.write("</div></div><br/>") self.wfile.write('<div>Events:<div style="text-size: 0.9em; margin-left: 20px;">') for (timestamp, line) in logging.readLog(): self.wfile.write("%(time)s : %(line)s<br/>" % { 'time': time.ctime(timestamp), 'line': cgi.escape(line), }) self.wfile.write("</div></div><br/>") self.wfile.write('<div style="text-align: center;">') self.wfile.write('<small>Summary generated %(time)s</small><br/>' % { 'time': time.asctime(), }) self.wfile.write('<small>%(server)s:%(port)i | PID: %(pid)i | v%(core_version)s | <a href="http://uguu.ca/" onclick="window.open(this.href); return false;">uguu.ca</a></small><br/>' % { 'pid': os.getpid(), 'server': conf.DHCP_SERVER_IP, 'port': conf.DHCP_SERVER_PORT, 'core_version': conf.VERSION, }) self.wfile.write('<form action="/" method="post"><div style="display: inline;">') self.wfile.write('<label for="key">Key: </label><input type="password" name="key" id="key"/>') if conf.USE_CACHE: self.wfile.write('<input type="submit" value="Flush cache and write log to disk"/>') else: self.wfile.write('<input type="submit" value="Write log to disk"/>') self.wfile.write('</div></form>') self.wfile.write('</div>') self.wfile.write("</div></body></html>") except Exception, e: logging.writeLog("Problem while serving response in Web module: %(error)s" % {'error': str(e),})
def _handleDHCPLeaseQuery(self, packet, source_address, pxe): """ Evaluates a DHCPLEASEQUERY request from a relay and determines whether a DHCPLEASEACTIVE or DHCPLEASEUNKNOWN should be sent. The logic here is to make sure the MAC isn't ignored or acting maliciously, then check the database to see whether it has an assigned IP. If it does, DHCPLEASEACTIVE is sent. Otherwise, DHCPLEASEUNKNOWN is sent. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The DHCPREQUEST to be evaluated. @type source_address: tuple @param source_address: The address (host, port) from which the request was received. @type pxe: bool @param pxe: True if the packet was received on the PXE port. """ if not self._evaluateRelay(packet): return start_time = time.time() mac = None #noinspection PyBroadException try: mac = packet.getHardwareAddress() except: pass if not mac: #IP/client-ID-based lookup; not supported. self._logDiscardedPacket() return vlan = packet.getVlanNum() if not [None for (ignored_mac, timeout) in self._ignored_addresses if mac == ignored_mac]: if not self._logDHCPAccess(mac): self._logDiscardedPacket() return logging.writeLog('DHCPLEASEQUERY for %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) try: result = self._sql_broker.lookupMAC(mac) if result: packet.transformToDHCPLeaseActivePacket() if packet.setOption('yiaddr', ipToList(result[0])): self._sendDHCPPacket(packet, source_address, 'LEASEACTIVE', mac, result[0]) else: _logInvalidValue('ip', result[0], result[-2], result[-1]) else: packet.transformToDHCPLeaseUnknownPacket() self._sendDHCPPacket(packet, source_address, 'LEASEUNKNOWN', mac, '?.?.?.?') except Exception, e: logging.sendErrorReport('Unable to respond for %(mac)s' % {'mac': mac,}, e)
def _sendDHCPPacket(self, packet, address, response_type, mac, client_ip): #noinspection PyUnresolvedReferences """ Sends the given packet to the right destination based on its properties. If the request originated from a host that knows its own IP, the packet is transmitted via unicast; in the event of a relayed request, it is sent to the 'server port', rather than the 'client port', per RFC 2131. If it was picked up as a broadcast packet, it is sent to the local subnet via the same mechanism, but to the 'client port'. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The packet to be transmitted. @type address: tuple @param address: The address from which the packet was received: (host, port) @type response_type: basestring @param response_type: The DHCP subtype of this response: 'OFFER', 'ACK', 'NAK' @type mac: basestring @param mac: The MAC of the client for which this packet is destined. @type client_ip: basestring @param client_ip: The IP being assigned to the client. @rtype: int @return: The number of bytes transmitted. """ #ip = port = None if address[0] not in ('255.255.255.255', '0.0.0.0', ''): #Unicast. giaddr = packet.getOption("giaddr") if giaddr and not giaddr == [0,0,0,0]: #Relayed request. ip = '.'.join(map(str, giaddr)) port = self._server_port else: #Request directly from client, routed or otherwise. ip = address[0] port = self._client_port else: #Broadcast. ip = '255.255.255.255' port = self._client_port packet.setOption('server_identifier', ipToList(self._server_address)) bytes = self._sendDHCPPacketTo(packet, ip, port) logging.writeLog('DHCP%(type)s sent to %(mac)s for %(client)s via %(ip)s:%(port)i [%(bytes)i bytes]' % { 'type': response_type, 'mac': mac, 'client': client_ip, 'bytes': bytes, 'ip': ip, 'port': port, }) return bytes
def run(): #Ensure that pre-setup tasks are taken care of. conf.init() #Start Web server. if conf.WEB_ENABLED: web_thread = web.WebService() web_thread.start() #Start DHCP server. dhcp_thread = dhcp.DHCPService() dhcp_thread.start() #Record PID. #noinspection PyBroadException try: pidfile = open(conf.PID_FILE, 'w') pidfile.write(str(os.getpid()) + '\n') pidfile.close() os.chown(conf.PID_FILE, conf.UID, conf.GID) except: logging.writeLog("Unable to write pidfile: %(file)s" % {'file': conf.PID_FILE,}) #Touch logfile. #noinspection PyBroadException try: open(conf.LOG_FILE, 'a').close() os.chown(conf.LOG_FILE, conf.UID, conf.GID) except: logging.writeLog("Unable to write pidfile: %(file)s" % {'file': conf.PID_FILE,}) #Set signal-handlers. signal.signal(signal.SIGHUP, _logHandler) signal.signal(signal.SIGTERM, _quitHandler) #Set proper permissions for execution os.setregid(conf.GID, conf.GID) os.setreuid(conf.UID, conf.UID) #Serve until interrupted. tick = 0 while True: time.sleep(1) tick += 1 if tick >= conf.POLLING_INTERVAL: #Perform periodic cleanup. dhcp_thread.pollStats() logging.emailTimeoutCooldown() tick = 0
def _handleDHCPDecline(self, packet, source_address, pxe): """ Informs the operator of a potential IP collision on the network. This function checks to make sure the MAC isn't ignored or acting maliciously, then checks the database to see whether it has an assigned IP. If it does, and the IP it thinks it has a right to matches this IP, then a benign message is logged and the operator is informed; if not, the decline is flagged as a malicious act. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The DHCPDISCOVER to be evaluated. @type source_address: tuple @param source_address: The address (host, port) from which the request was received. @type pxe: bool @param pxe: True if the packet was received on the PXE port. """ if not self._evaluateRelay(packet): return start_time = time.time() mac = packet.getHardwareAddress() if not [None for (ignored_mac, timeout) in self._ignored_addresses if mac == ignored_mac]: if not self._logDHCPAccess(mac): self._logDiscardedPacket() return if '.'.join(map(str, packet.getOption("server_identifier"))) == self._server_address: #Rejected! ip = '.'.join(map(str, packet.getOption("requested_ip_address"))) result = self._sql_broker.lookupMAC(mac) if result and result[0] == ip: #Known client. logging.writeLog('DHCPDECLINE from %(mac)s for %(ip)s on (%(subnet)s, %(serial)i)' % { 'ip': ip, 'mac': mac, 'subnet': result[9], 'serial': result[10], }) logging.sendDeclineReport(mac, ip, result[9], result[10]) else: logging.writeLog('Misconfigured client %(mac)s sent DHCPDECLINE for %(ip)s' % { 'ip': ip, 'mac': mac, }) else: self._logDiscardedPacket() else: self._logDiscardedPacket() self._logTimeTaken(time.time() - start_time)
def run(self): """ Runs the DHCP server indefinitely. In the event of an unexpected error, e-mail will be sent and processing will continue with the next request. """ logging.writeLog('Running DHCP server') while True: try: self._dhcp_server.getNextDHCPPacket() except select.error: logging.writeLog('Suppressed non-fatal select() error in DHCP module') except Exception, e: logging.sendErrorReport('Unhandled exception', e)
def _logHandler(signum, frame): """ Flushes DHCP cache and writes log to disk upon receipt of a SIGHUP. @type signum: int @param signum: The kill-signal constant received. This will always be SIGHUP. @type frame: int @param frame: The stack-frame in which the kill-signal was received. This is not used. """ dhcp.flushCache() if not logging.logToDisk(): logging.writeLog("Unable to write logfile: %(log)s" % {'log': conf.LOG_FILE,}) else: logging.writeLog("Wrote log to '%(log)s'" % {'log': conf.LOG_FILE,})
def do_HEAD(self): """ Handles all HTTP HEAD requests. This involves lying about the existence of files and telling the browser to always pull a fresh copy. """ if not self.path in self._allowed_pages: self.send_response(404) return try: self.send_response(200) self.send_header('Content-type', 'text/html') self.send_header('Last-modified', time.strftime('%a, %d %b %Y %H:%M:%S %Z')) self.end_headers() except Exception, e: logging.writeLog("Problem while processing HEAD in Web module: %(error)s" % {'error': str(e),})
def __init__(self): """ Sets up the DHCP server. @raise Exception: If a problem occurs while binding the sockets needed to handle DHCP traffic. """ threading.Thread.__init__(self) self.daemon = True self._dhcp_server = _DHCPServer( '.'.join([str(int(o)) for o in conf.DHCP_SERVER_IP.split('.')]), int(conf.DHCP_SERVER_PORT), int(conf.DHCP_CLIENT_PORT), conf.PXE_PORT and int(conf.PXE_PORT) ) _dhcp_servers.append(self._dhcp_server) #Add this server to the global list. logging.writeLog('Configured DHCP server')
def __init__(self): """ Sets up the Web server. @raise Exception: If a problem occurs while binding the sockets needed to handle HTTP traffic. """ threading.Thread.__init__(self) self.daemon = True self._web_server = BaseHTTPServer.HTTPServer( ( '.'.join([str(int(o)) for o in conf.WEB_IP.split('.')]), int(conf.WEB_PORT) ), _WebServer ) logging.writeLog('Configured Web server')
def _evaluateRelay(self, packet): """ Determines whether the received packet belongs to a relayed request or not and decides whether it should be allowed based on policy. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The packet to be evaluated. """ giaddr = packet.getOption("giaddr") if not giaddr == [0,0,0,0]: #Relayed request. if not conf.ALLOW_DHCP_RELAYS: #Ignore it. return False elif conf.ALLOWED_DHCP_RELAYS and not '.'.join(map(str, giaddr)) in conf.ALLOWED_DHCP_RELAYS: logging.writeLog('Relayed request from unauthorized relay %(ip)s ignored' % { 'ip': '.'.join(map(str, giaddr)), }) return False elif not conf.ALLOW_LOCAL_DHCP: #Local request, but denied. return False return True
def do_POST(self): """ Handles all HTTP POST requests. This checks to see if the user entered the flush key and, if so, flushes the cache and writes the memory-log to disk. """ try: (ctype, pdict) = cgi.parse_header(self.headers.getheader('content-type')) if ctype == 'application/x-www-form-urlencoded': query = parse_qs(self.rfile.read(int(self.headers.getheader('content-length')))) key = query.get('key') if key: if hashlib.md5(key[0]).hexdigest() == conf.WEB_RELOAD_KEY: dhcp.flushCache() if logging.logToDisk(): logging.writeLog("Wrote log to '%(log)s'" % {'log': conf.LOG_FILE,}) else: logging.writeLog("Unable to write log to '%(log)s'" % {'log': conf.LOG_FILE,}) else: logging.writeLog("Invalid Web-access-key provided") except Exception, e: logging.writeLog("Problem while processing POST in Web module: %(error)s" % {'error': str(e),})
def _handleDHCPDiscover(self, packet, source_address, pxe): """ Evaluates a DHCPDISCOVER request from a client and determines whether a DHCPOFFER should be sent. The logic here is to make sure the MAC isn't ignored or acting maliciously, then check the database to see whether it has an assigned IP. If it does, that IP is offered, along with all relevant options; if not, the MAC is ignored to mitigate spam from follow-up DHCPDISCOVERS. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The DHCPDISCOVER to be evaluated. @type source_address: tuple @param source_address: The address (host, port) from which the request was received. @type pxe: bool @param pxe: True if the packet was received on the PXE port. """ #assert False from conf import PKT_CAPT PKT_CAPT.append((packet, source_address, pxe)) if not self._evaluateRelay(packet): return start_time = time.time() mac = packet.getHardwareAddress() vlan = packet.getVlanNum() if not [None for (ignored_mac, timeout) in self._ignored_addresses if mac == ignored_mac]: if not self._logDHCPAccess(mac): self._logDiscardedPacket() return logging.writeLog('DHCPDISCOVER from %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) try: result = self._sql_broker.lookupMAC(mac) if result: rapid_commit = not packet.getOption('rapid_commit') is None if rapid_commit: packet.transformToDHCPAckPacket() packet.forceOption('rapid_commit', []) else: packet.transformToDHCPOfferPacket() vendor_options = packet.extractVendorOptions() self._loadDHCPPacket(packet, result) giaddr = packet.getOption("giaddr") if not giaddr or giaddr == [0,0,0,0]: giaddr = None else: giaddr = tuple(giaddr) if conf.loadDHCPPacket( packet, mac, tuple(ipToList(result[0])), giaddr, result[9], result[10], pxe, vendor_options ): if rapid_commit: self._sendDHCPPacket(packet, source_address, 'ACK-rapid', mac, result[0]) else: self._sendDHCPPacket(packet, source_address, 'OFFER', mac, result[0]) else: logging.writeLog('Ignoring %(mac)s per loadDHCPPacket()' % { 'mac': mac, }) self._logDiscardedPacket() else: if conf.AUTHORITATIVE: packet.transformToDHCPNackPacket() self._sendDHCPPacket(packet, source_address, 'NAK', mac, '?.?.?.?') else: logging.writeLog('%(mac)s unknown; ignoring for %(time)i seconds' % { 'mac': mac, 'time': conf.UNAUTHORIZED_CLIENT_TIMEOUT, }) self._stats_lock.acquire() self._ignored_addresses.append([mac, conf.UNAUTHORIZED_CLIENT_TIMEOUT]) self._stats_lock.release() except Exception, e: logging.sendErrorReport('Unable to respond to %(mac)s' % {'mac': mac,}, e)
def _handleDHCPInform(self, packet, source_address, pxe): """ Evaluates a DHCPINFORM request from a client and determines whether a DHCPACK should be sent. The logic here is to make sure the MAC isn't ignored or acting maliciously, then check the database to see whether it has an assigned IP. If it does, and the IP it thinks it has a right to matches this IP, then an ACK is sent, along with all relevant options; if not, the request is ignored. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The DHCPREQUEST to be evaluated. @type source_address: tuple @param source_address: The address (host, port) from which the request was received. @type pxe: bool @param pxe: True if the packet was received on the PXE port. """ if not self._evaluateRelay(packet): return start_time = time.time() mac = packet.getHardwareAddress() if not [None for (ignored_mac, timeout) in self._ignored_addresses if mac == ignored_mac]: if not self._logDHCPAccess(mac): self._logDiscardedPacket() return ciaddr = packet.getOption("ciaddr") giaddr = packet.getOption("giaddr") s_ciaddr = '.'.join(map(str, ciaddr)) if not ciaddr or ciaddr == [0,0,0,0]: ciaddr = None if not giaddr or giaddr == [0,0,0,0]: giaddr = None else: giaddr = tuple(giaddr) vlan = packet.getVlanNum() logging.writeLog('DHCPINFORM from %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) if not ciaddr: logging.writeLog('%(mac)s sent malformed packet; ignoring for %(time)i seconds' % { 'mac': mac, 'time': conf.UNAUTHORIZED_CLIENT_TIMEOUT, }) self._stats_lock.acquire() self._ignored_addresses.append([mac, conf.UNAUTHORIZED_CLIENT_TIMEOUT]) self._stats_lock.release() self._logDiscardedPacket() return try: result = self._sql_broker.lookupMAC(mac) if result: packet.transformToDHCPAckPacket() vendor_options = packet.extractVendorOptions() self._loadDHCPPacket(packet, result, True) if conf.loadDHCPPacket( packet, mac, tuple(ipToList(result[0])), giaddr, result[9], result[10], pxe, vendor_options ): self._sendDHCPPacket(packet, source_address, 'ACK', mac, s_ciaddr) else: logging.writeLog('Ignoring %(mac)s per loadDHCPPacket()' % { 'mac': mac, }) self._logDiscardedPacket() else: logging.writeLog('%(mac)s unknown; ignoring for %(time)i seconds' % { 'mac': mac, 'time': conf.UNAUTHORIZED_CLIENT_TIMEOUT, }) self._stats_lock.acquire() self._ignored_addresses.append([mac, conf.UNAUTHORIZED_CLIENT_TIMEOUT]) self._stats_lock.release() self._logDiscardedPacket() except Exception, e: logging.sendErrorReport('Unable to respond to %(mac)s' % {'mac': mac,}, e)
def _handleDHCPRequest(self, packet, source_address, pxe): """ Evaluates a DHCPREQUEST request from a client and determines whether a DHCPACK should be sent. The logic here is to make sure the MAC isn't ignored or acting maliciously, then check the database to see whether it has an assigned IP. If it does, and the IP it thinks it has a right to matches this IP, then an ACK is sent, along with all relevant options; if not, a DHCPNAK is sent to inform the client that it is not allowed to use the requested IP, forcing it to DISCOVER a new one. If policy forbids RENEW and REBIND operations, perhaps to prepare for a new configuration rollout, all such requests are NAKed immediately. @type packet: L{libpydhcpserver.dhcp_packet.DHCPPacket} @param packet: The DHCPREQUEST to be evaluated. @type source_address: tuple @param source_address: The address (host, port) from which the request was received. @type pxe: bool @param pxe: True if the packet was received on the PXE port. """ if not self._evaluateRelay(packet): return start_time = time.time() mac = packet.getHardwareAddress() if not [None for (ignored_mac, timeout) in self._ignored_addresses if mac == ignored_mac]: if not self._logDHCPAccess(mac): self._logDiscardedPacket() return ip = packet.getOption("requested_ip_address") sid = packet.getOption("server_identifier") ciaddr = packet.getOption("ciaddr") giaddr = packet.getOption("giaddr") s_ip = ip and '.'.join(map(str, ip)) s_sid = sid and '.'.join(map(str, sid)) s_ciaddr = ciaddr and '.'.join(map(str, ciaddr)) if not ip or ip == [0,0,0,0]: ip = None if not sid or sid == [0,0,0,0]: sid = None if not ciaddr or ciaddr == [0,0,0,0]: ciaddr = None if not giaddr or giaddr == [0,0,0,0]: giaddr = None else: giaddr = tuple(giaddr) vlan = packet.getVlanNum() if sid and not ciaddr: #SELECTING if s_sid == self._server_address: #Chosen! logging.writeLog('DHCPREQUEST:SELECTING from %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) try: result = self._sql_broker.lookupMAC(mac) if result and (not ip or result[0] == s_ip): packet.transformToDHCPAckPacket() vendor_options = packet.extractVendorOptions() self._loadDHCPPacket(packet, result) if conf.loadDHCPPacket( packet, mac, tuple(ipToList(result[0])), giaddr, result[9], result[10], pxe, vendor_options ): self._sendDHCPPacket(packet, source_address, 'ACK', mac, s_ip) else: logging.writeLog('Ignoring %(mac)s per loadDHCPPacket()' % { 'mac': mac, }) self._logDiscardedPacket() else: packet.transformToDHCPNackPacket() self._sendDHCPPacket(packet, source_address, 'NAK', mac, 'NO-MATCH') except Exception, e: logging.sendErrorReport('Unable to respond to %(mac)s' % {'mac': mac,}, e) else: self._logDiscardedPacket() elif not sid and not ciaddr and ip: #INIT-REBOOT logging.writeLog('DHCPREQUEST:INIT-REBOOT from %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) try: result = self._sql_broker.lookupMAC(mac) if result and result[0] == s_ip: packet.transformToDHCPAckPacket() vendor_options = packet.extractVendorOptions() self._loadDHCPPacket(packet, result) if conf.loadDHCPPacket( packet, mac, tuple(ip), giaddr, result[9], result[10], pxe, vendor_options ): self._sendDHCPPacket(packet, source_address, 'ACK', mac, s_ip) else: logging.writeLog('Ignoring %(mac)s per loadDHCPPacket()' % { 'mac': mac, }) self._logDiscardedPacket() else: packet.transformToDHCPNackPacket() self._sendDHCPPacket(packet, source_address, 'NAK', mac, s_ip) except Exception, e: logging.sendErrorReport('Unable to respond to %(mac)s' % {'mac': mac,}, e)
}) self._logDiscardedPacket() else: packet.transformToDHCPNackPacket() self._sendDHCPPacket(packet, source_address, 'NAK', mac, s_ip) except Exception, e: logging.sendErrorReport('Unable to respond to %(mac)s' % {'mac': mac,}, e) elif not sid and ciaddr and not ip: #RENEWING or REBINDING if conf.NAK_RENEWALS: packet.transformToDHCPNackPacket() self._sendDHCPPacket(packet, source_address, 'NAK', mac, 'NAK_RENEWALS') else: renew = source_address[0] not in ('255.255.255.255', '0.0.0.0', '') if renew: logging.writeLog('DHCPREQUEST:RENEW from %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) else: logging.writeLog('DHCPREQUEST:REBIND from %(mac)s on vlan %(vlan)s' % { 'mac': mac, 'vlan': vlan }) try: result = self._sql_broker.lookupMAC(mac) if result and result[0] == s_ciaddr: packet.transformToDHCPAckPacket() vendor_options = packet.extractVendorOptions() packet.setOption('yiaddr', ciaddr) self._loadDHCPPacket(packet, result) if conf.loadDHCPPacket( packet,