4 # nfdcpd: A promiscuous, NFQUEUE-based DHCP server for virtual machine hosting
5 # Copyright (c) 2010 GRNET SA
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License along
18 # with this program; if not, write to the Free Software Foundation, Inc.,
19 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
28 import logging.handlers
35 import daemon.pidlockfile
38 from lockfile import LockTimeout
42 from select import select
43 from socket import AF_INET, AF_INET6
45 from scapy.data import ETH_P_ALL
46 from scapy.packet import BasePacket
47 from scapy.layers.l2 import Ether
48 from scapy.layers.inet import IP, UDP
49 from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \
50 ICMPv6NDOptDstLLAddr, \
51 ICMPv6NDOptPrefixInfo, \
53 from scapy.layers.dhcp import BOOTP, DHCP
55 DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf"
56 DEFAULT_PATH = "/var/run/ganeti-dhcpd"
57 DEFAULT_USER = "nobody"
58 DEFAULT_LEASE_LIFETIME = 604800 # 1 week
59 DEFAULT_LEASE_RENEWAL = 600 # 10 min
60 DEFAULT_RA_PERIOD = 300 # seconds
61 DHCP_DUMMY_SERVER_IP = "1.2.3.4"
63 LOG_FILENAME = "nfdhcpd.log"
65 SYSFS_NET = "/sys/class/net"
67 LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s"
69 # Configuration file specification (see configobj documentation)
78 enable_dhcp = boolean(default=True)
79 lease_lifetime = integer(min=0, max=4294967295)
80 lease_renewal = integer(min=0, max=4294967295)
82 dhcp_queue = integer(min=0, max=65535)
83 nameservers = ip_addr_list(family=4)
84 domain = string(default=None)
87 enable_ipv6 = boolean(default=True)
88 ra_period = integer(min=1, max=4294967295)
89 rs_queue = integer(min=0, max=65535)
90 ns_queue = integer(min=0, max=65535)
91 nameservers = ip_addr_list(family=6)
105 DHCPDISCOVER: "DHCPDISCOVER",
106 DHCPOFFER: "DHCPOFFER",
107 DHCPREQUEST: "DHCPREQUEST",
108 DHCPDECLINE: "DHCPDECLINE",
111 DHCPRELEASE: "DHCPRELEASE",
112 DHCPINFORM: "DHCPINFORM",
116 DHCPDISCOVER: DHCPOFFER,
117 DHCPREQUEST: DHCPACK,
122 def get_indev(payload):
124 indev_ifindex = payload.get_physindev()
126 logging.debug(" - Incomming packet from bridge %s", indev_ifindex)
128 except AttributeError:
129 #TODO: return error value
130 logging.debug("No get_physindev() supported")
133 indev_ifindex = payload.get_indev()
134 logging.debug(" - Incomming packet from tap %s", indev_ifindex)
139 def get_binding(proxy, ifindex, mac):
141 if proxy.mac_indexed_clients:
142 logging.debug(" - Getting binding for mac %s", mac)
143 b = proxy.clients[mac]
145 logging.debug(" - Getting binding for ifindex %s", ifindex)
146 b = proxy.clients[ifindex]
149 logging.debug(" - No client found for mac / ifindex %s / %s",
154 def parse_binding_file(path):
155 """ Read a client configuration from a tap file
158 logging.info("Parsing binding file %s", path)
160 iffile = open(path, 'r')
161 except EnvironmentError, e:
162 logging.warn(" - Unable to open binding file %s: %s", path, str(e))
165 tap = os.path.basename(path)
177 v = line.strip().split('=')[1]
183 if line.startswith("IP="):
185 elif line.startswith("MAC="):
186 mac = get_value(line)
187 elif line.startswith("HOSTNAME="):
188 hostname = get_value(line)
189 elif line.startswith("INDEV="):
190 indev = get_value(line)
191 elif line.startswith("SUBNET="):
192 subnet = get_value(line)
193 elif line.startswith("GATEWAY="):
194 gateway = get_value(line)
195 elif line.startswith("SUBNET6="):
196 subnet6 = get_value(line)
197 elif line.startswith("GATEWAY6="):
198 gateway6 = get_value(line)
199 elif line.startswith("EUI64="):
200 eui64 = get_value(line)
203 return Client(tap=tap, mac=mac, ip=ip, hostname=hostname,
204 indev=indev, subnet=subnet, gateway=gateway,
205 subnet6=subnet6, gateway6=gateway6, eui64=eui64 )
207 logging.warning(" - Cannot add client for host %s and IP %s on tap %s",
212 class ClientFileHandler(pyinotify.ProcessEvent):
213 def __init__(self, server):
214 pyinotify.ProcessEvent.__init__(self)
217 def process_IN_DELETE(self, event): # pylint: disable=C0103
218 """ Delete file handler
220 Currently this removes an interface from the watch list
223 self.server.remove_tap(event.name)
225 def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
228 Currently this adds an interface to the watch list
231 self.server.add_tap(os.path.join(event.path, event.name))
234 class Client(object):
235 def __init__(self, tap=None, indev=None, mac=None, ip=None, hostname=None,
236 subnet=None, gateway=None, subnet6=None, gateway6=None, eui64=None ):
239 self.hostname = hostname
243 self.gateway = gateway
244 self.net = Subnet(net=subnet, gw=gateway, dev=tap)
245 self.subnet6 = subnet6
246 self.gateway6 = gateway6
247 self.net6 = Subnet(net=subnet6, gw=gateway6, dev=tap)
251 return self.mac is not None and self.ip is not None\
252 and self.hostname is not None
255 class Subnet(object):
256 def __init__(self, net=None, gw=None, dev=None):
257 if isinstance(net, str):
259 self.net = IPy.IP(net)
260 except ValueError, e:
261 logging.warning(" - IPy error: %s", e)
270 """ Return the netmask in textual representation
273 return str(self.net.netmask())
277 """ Return the broadcast address in textual representation
280 return str(self.net.broadcast())
284 """ Return the network as an IPy.IP
287 return self.net.net()
291 """ Return the prefix length as an integer
294 return self.net.prefixlen()
297 def _make_eui64(net, mac):
298 """ Compute an EUI-64 address from an EUI-48 (MAC) address
303 comp = mac.split(":")
304 prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
305 eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
306 eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
307 for l in range(0, len(eui64), 2):
308 prefix += ["".join(eui64[l:l+2])]
309 return IPy.IP(":".join(prefix))
311 def make_eui64(self, mac):
312 """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
316 return self._make_eui64(self.net, mac)
318 def make_ll64(self, mac):
319 """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
322 return self._make_eui64("fe80::", mac)
325 class VMNetProxy(object): # pylint: disable=R0902
326 def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
327 rs_queue_num=None, ns_queue_num=None,
328 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
329 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
331 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
332 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
335 getattr(nfqueue.payload, 'get_physindev')
336 self.mac_indexed_clients = False
337 except AttributeError:
338 self.mac_indexed_clients = True
339 self.data_path = data_path
340 self.lease_lifetime = dhcp_lease_lifetime
341 self.lease_renewal = dhcp_lease_renewal
342 self.dhcp_domain = dhcp_domain
343 self.dhcp_server_ip = dhcp_server_ip
344 self.ra_period = ra_period
345 if dhcp_nameservers is None:
346 self.dhcp_nameserver = []
348 self.dhcp_nameservers = dhcp_nameservers
350 if ipv6_nameservers is None:
351 self.ipv6_nameservers = []
353 self.ipv6_nameservers = ipv6_nameservers
355 self.ipv6_enabled = False
362 self.l2socket = self._socket()
365 self.wm = pyinotify.WatchManager()
366 mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
367 mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
368 inotify_handler = ClientFileHandler(self)
369 self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
370 self.wm.add_watch(self.data_path, mask, rec=True)
373 if dhcp_queue_num is not None:
374 self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
376 if rs_queue_num is not None:
377 self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
378 self.ipv6_enabled = True
380 if ns_queue_num is not None:
381 self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
382 self.ipv6_enabled = True
385 logging.info("Opening L2 socket")
387 s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ETH_P_ALL)
388 s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
389 except socket.error, e:
390 logging.warning(" - Cannot open socket %s", e)
394 """ Free all resources for a graceful exit
397 logging.info("Cleaning up")
399 logging.debug(" - Closing netfilter queues")
400 for q in self.nfq.values():
403 logging.debug(" - Closing socket")
404 self.l2socket.close()
406 logging.debug(" - Stopping inotify watches")
409 logging.info(" - Cleanup finished")
411 def _setup_nfqueue(self, queue_num, family, callback):
412 logging.info("Setting up NFQUEUE for queue %d, AF %s",
415 q.set_callback(callback)
416 q.fast_open(queue_num, family)
417 q.set_queue_maxlen(30)
418 # This is mandatory for the queue to operate
419 q.set_mode(nfqueue.NFQNL_COPY_PACKET)
420 self.nfq[q.get_fd()] = q
421 logging.debug(" - Successfully set up NFQUEUE %d", queue_num)
423 def sendp(self, data, dev):
424 """ Send a raw packet using a layer-2 socket
427 if isinstance(data, BasePacket):
430 logging.debug(" - Sending raw packet %s", data)
432 self.l2socket.bind((dev, ETH_P_ALL))
434 count = self.l2socket.send(data, socket.MSG_DONTWAIT)
435 except socket.error, e:
436 logging.warn(" - Send with MSG_DONTWAIT failed: %s", str(e))
437 self.l2socket.close()
438 self.l2socket = self._socket()
442 logging.debug(" - Sent %d bytes to device %s", count, dev)
444 logging.warn(" - Truncated send on %s (%d/%d bytes sent)",
447 def build_config(self):
450 for path in glob.glob(os.path.join(self.data_path, "*")):
453 logging.debug("%15s %20s %7s %15s", 'Client', 'MAC', 'TAP', 'IP')
454 for b in self.clients.values():
455 logging.debug("%15s %20s %7s %15s", b.hostname, b.mac, b.tap, b.ip)
457 def get_ifindex(self, iface):
458 """ Get the interface index from sysfs
461 logging.debug(" - Getting ifindex for interface %s from sysfs", iface)
463 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
464 if not path.startswith(SYSFS_NET):
471 except EnvironmentError:
472 logging.debug(" - %s is probably down, removing", iface)
473 self.remove_tap(iface)
478 ifindex = f.readline().strip()
480 ifindex = int(ifindex)
481 except ValueError, e:
482 logging.warn(" - Failed to get ifindex for %s, cannot parse"
483 " sysfs output '%s'", iface, ifindex)
484 except EnvironmentError, e:
485 logging.warn(" - Error reading %s's ifindex from sysfs: %s",
487 self.remove_tap(iface)
493 def get_iface_hw_addr(self, iface):
494 """ Get the interface hardware address from sysfs
497 logging.debug(" - Getting mac for iface %s", iface)
498 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
499 if not path.startswith(SYSFS_NET):
505 except EnvironmentError:
506 logging.debug(" - %s is probably down, removing", iface)
507 self.remove_tap(iface)
511 addr = f.readline().strip()
512 except EnvironmentError, e:
513 logging.warn(" - Failed to read hw address for %s from sysfs: %s",
520 def add_tap(self, path):
521 """ Add an interface to monitor
524 tap = os.path.basename(path)
526 logging.debug("Updating configuration for %s", tap)
527 b = parse_binding_file(path)
530 ifindex = self.get_ifindex(b.tap)
533 logging.warn(" - Stale configuration for %s found", tap)
536 if self.mac_indexed_clients:
537 self.clients[b.mac] = b
539 self.clients[ifindex] = b
540 logging.debug(" - Added client:")
541 logging.debug(" + %5s: %10s %20s %7s %15s",
542 ifindex, b.hostname, b.mac, b.tap, b.ip)
544 def remove_tap(self, tap):
545 """ Cleanup clients on a removed interface
549 for k in self.clients.keys():
551 if self.clients[k].tap == tap:
552 logging.debug("Removing client on interface %s", tap)
553 logging.debug(" - %5s: %10s %20s %7s %15s",
554 k, b.hostname, b.mac, b.tap, b.ip)
557 logging.debug("Client on %s disappeared!!!", tap)
559 def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
560 """ Generate a reply to bnetfilter-queue-deva BOOTP/DHCP request
563 logging.debug(" * Processing pending DHCP request (NFQUEUE %d)", i)
564 # Decode the response - NFQUEUE relays IP packets
565 pkt = IP(payload.get_data())
566 #logging.debug(pkt.show())
568 # Get the client MAC address
569 resp = pkt.getlayer(BOOTP).copy()
571 mac = resp.chaddr[:hlen].encode("hex")
572 mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen - 1)
574 # Server responses are always BOOTREPLYs
575 resp.op = "BOOTREPLY"
578 indev = get_indev(payload)
580 binding = get_binding(self, indev, mac)
582 # We don't know anything about this interface, so accept the packet
584 logging.debug(" - Ignoring DHCP request on unknown iface %s", indev)
585 # We don't know what to do with this packet, so let the kernel
587 payload.set_verdict(nfqueue.NF_ACCEPT)
590 # Signal the kernel that it shouldn't further process the packet
591 payload.set_verdict(nfqueue.NF_DROP)
593 if mac != binding.mac:
594 logging.warn(" - Recieved spoofed DHCP request for mac %s from tap %s", mac, indev)
597 logging.debug(" - Generating DHCP response for host %s (mac %s) on tap %s",
598 binding.hostname, mac, binding.tap)
601 resp = Ether(dst=mac, src=self.get_iface_hw_addr(binding.indev))/\
602 IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
603 UDP(sport=pkt.dport, dport=pkt.sport)/resp
607 logging.warn(" - Invalid request from %s on %s, no DHCP"
608 " payload found", binding.mac, binding.tap)
612 requested_addr = binding.ip
613 for opt in pkt[DHCP].options:
614 if type(opt) is tuple and opt[0] == "message-type":
616 if type(opt) is tuple and opt[0] == "requested_addr":
617 requested_addr = opt[1]
619 logging.info(" - %s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
620 binding.mac, binding.tap)
623 domainname = self.dhcp_domain
625 domainname = binding.hostname.split('.', 1)[-1]
627 if req_type == DHCPREQUEST and requested_addr != binding.ip:
629 logging.info(" - Sending DHCPNAK to %s on %s: requested %s"
630 " instead of %s", binding.mac, binding.tap,
631 requested_addr, binding.ip)
633 elif req_type in (DHCPDISCOVER, DHCPREQUEST):
634 resp_type = DHCP_REQRESP[req_type]
635 resp.yiaddr = binding.ip
637 ("hostname", binding.hostname),
638 ("domain", domainname),
639 ("broadcast_address", str(subnet.broadcast)),
640 ("subnet_mask", str(subnet.netmask)),
641 ("renewal_time", self.lease_renewal),
642 ("lease_time", self.lease_lifetime),
645 dhcp_options += [("router", subnet.gw)]
646 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
648 elif req_type == DHCPINFORM:
649 resp_type = DHCP_REQRESP[req_type]
651 ("hostname", binding.hostname),
652 ("domain", domainname),
654 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
656 elif req_type == DHCPRELEASE:
658 logging.info(" - DHCPRELEASE from %s on %s", binding.mac, binding.tap)
661 # Finally, always add the server identifier and end options
663 ("message-type", resp_type),
664 ("server_id", DHCP_DUMMY_SERVER_IP),
667 resp /= DHCP(options=dhcp_options)
669 logging.info(" - %s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
670 binding.ip, binding.tap)
672 self.sendp(resp, binding.indev)
673 except socket.error, e:
674 logging.warn(" - DHCP response on %s failed: %s", binding.indev, str(e))
676 logging.warn(" - Unkown error during DHCP response on %s: %s",
677 binding.indev, str(e))
679 def rs_response(self, i, payload): # pylint: disable=W0613
680 """ Generate a reply to a BOOTP/DHCP request
683 logging.debug(" * Processing pending RS request (NFQUEUE %d)", i)
684 pkt = IPv6(payload.get_data())
685 #logging.debug(pkt.show())
689 logging.debug(" - Cannot obtain lladdr in rs")
692 indev = get_indev(payload)
694 binding = get_binding(self, indev, mac)
696 # We don't know anything about this interface, so accept the packet
698 logging.debug(" - Ignoring router solicitation on for mac %s", mac)
699 # We don't know what to do with this packet, so let the kernel
701 payload.set_verdict(nfqueue.NF_ACCEPT)
704 # Signal the kernel that it shouldn't further process the packet
705 payload.set_verdict(nfqueue.NF_DROP)
707 if mac != binding.mac:
708 logging.warn(" - Received spoofed RS request for mac %s from tap %s",
712 subnet = binding.net6
714 if subnet.net is None:
715 logging.debug(" - No IPv6 network assigned for tap %s", binding.tap)
718 indevmac = self.get_iface_hw_addr(binding.indev)
719 ifll = subnet.make_ll64(indevmac)
723 logging.debug(" - Generating RA for host %s (mac %s) on tap %s",
724 binding.hostname, mac, binding.tap)
726 resp = Ether(src=indevmac)/\
727 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
728 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
729 prefixlen=subnet.prefixlen)
731 if self.ipv6_nameservers:
732 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
733 lifetime=self.ra_period * 3)
736 self.sendp(resp, binding.indev)
737 except socket.error, e:
738 logging.warn(" - RA on %s failed: %s", binding.indev, str(e))
740 logging.warn(" - Unkown error during RA on %s: %s",
741 binding.indev, str(e))
743 def ns_response(self, i, payload): # pylint: disable=W0613
744 """ Generate a reply to an ICMPv6 neighbor solicitation
748 logging.debug(" * Processing pending NS request (NFQuEUE %d)", i)
750 ns = IPv6(payload.get_data())
751 #logging.debug(ns.show())
755 logging.debug(" - Cannot obtain lladdr from ns")
759 indev = get_indev(payload)
761 binding = get_binding(self, indev, mac)
763 # We don't know anything about this interface, so accept the packet
765 logging.debug(" - Ignoring neighbour solicitation for eui64 %s",
767 # We don't know what to do with this packet, so let the kernel
769 payload.set_verdict(nfqueue.NF_ACCEPT)
772 payload.set_verdict(nfqueue.NF_DROP)
774 if mac != binding.mac:
775 logging.warn(" - Received spoofed NS request"
776 " for mac %s from tap %s", mac, binding.tap)
779 subnet = binding.net6
780 if subnet.net is None:
781 logging.debug(" - No IPv6 network assigned for the interface")
784 indevmac = self.get_iface_hw_addr(binding.indev)
786 ifll = subnet.make_ll64(indevmac)
790 if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
791 logging.debug(" - Received NS for a non-routable IP (%s)", ns.tgt)
794 logging.debug(" - Generating NA for host %s (mac %s) on tap %s",
795 binding.hostname, mac, binding.tap)
797 resp = Ether(src=indevmac, dst=binding.mac)/\
798 IPv6(src=str(ifll), dst=ns.src)/\
799 ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
800 ICMPv6NDOptDstLLAddr(lladdr=indevmac)
803 self.sendp(resp, binding.indev)
804 except socket.error, e:
805 logging.warn(" - NA on %s failed: %s", binding.indev, str(e))
807 logging.warn(" - Unkown error during periodic NA on %s: %s",
808 binding.indev, str(e))
810 def send_periodic_ra(self):
811 # Use a separate thread as this may take a _long_ time with
812 # many interfaces and we want to be responsive in the mean time
813 threading.Thread(target=self._send_periodic_ra).start()
815 def _send_periodic_ra(self):
816 logging.debug("Sending out periodic RAs")
819 for binding in self.clients.values():
821 indev = binding.indev
823 subnet = binding.net6
824 if subnet.net is None:
825 logging.debug(" - Skipping periodic RA on interface %s,"
826 " as it is not IPv6-connected", tap)
828 indevmac = self.get_iface_hw_addr(indev)
829 ifll = subnet.make_ll64(indevmac)
832 resp = Ether(src=indevmac)/\
833 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
834 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
835 prefixlen=subnet.prefixlen)
836 if self.ipv6_nameservers:
837 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
838 lifetime=self.ra_period * 3)
840 self.sendp(resp, indev)
841 except socket.error, e:
842 logging.warn(" - Periodic RA on %s failed: %s", tap, str(e))
844 logging.warn(" - Unkown error during periodic RA on %s: %s",
847 logging.debug(" - Sent %d RAs in %.2f seconds", i, time.time() - start)
850 """ Safely perform the main loop, freeing all resources upon exit
859 """ Loop forever, serving DHCP requests
864 # Yes, we are accessing _fd directly, but it's the only way to have a
865 # single select() loop ;-)
866 iwfd = self.notifier._fd # pylint: disable=W0212
869 if self.ipv6_enabled:
870 timeout = self.ra_period
871 self.send_periodic_ra()
876 rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
878 logging.warn("Warning: Exception on %s",
879 ", ".join([str(fd) for fd in xlist]))
883 # First check if there are any inotify (= configuration change)
885 self.notifier.read_events()
886 self.notifier.process_events()
889 logging.debug("Pending requests on fds %s", rlist)
893 cnt = self.nfq[fd].process_pending()
894 logging.debug(" * Processed %d requests on NFQUEUE"
895 " with fd %d", cnt, fd)
896 except RuntimeError, e:
897 logging.warn("Error processing fd %d: %s", fd, str(e))
899 logging.warn("Unknown error processing fd %d: %s",
902 if self.ipv6_enabled:
903 # Calculate the new timeout
904 timeout = self.ra_period - (time.time() - start)
908 self.send_periodic_ra()
909 timeout = self.ra_period - (time.time() - start)
912 if __name__ == "__main__":
915 from cStringIO import StringIO
916 from pwd import getpwnam, getpwuid
917 from configobj import ConfigObj, ConfigObjError, flatten_errors
921 validator = validate.Validator()
923 def is_ip_list(value, family=4):
927 raise validate.VdtParamError(family)
928 if isinstance(value, (str, unicode)):
930 if not isinstance(value, list):
931 raise validate.VdtTypeError(value)
937 raise validate.VdtValueError(entry)
939 if ip.version() != family:
940 raise validate.VdtValueError(entry)
943 validator.functions["ip_addr_list"] = is_ip_list
944 config_spec = StringIO(CONFIG_SPEC)
946 parser = optparse.OptionParser()
947 parser.add_option("-c", "--config", dest="config_file",
948 help="The location of the data files", metavar="FILE",
949 default=DEFAULT_CONFIG)
950 parser.add_option("-d", "--debug", action="store_true", dest="debug",
951 help="Turn on debugging messages")
952 parser.add_option("-f", "--foreground", action="store_false",
953 dest="daemonize", default=True,
954 help="Do not daemonize, stay in the foreground")
956 opts, args = parser.parse_args()
959 config = ConfigObj(opts.config_file, configspec=config_spec)
960 except ConfigObjError, err:
961 sys.stderr.write("Failed to parse config file %s: %s" %
962 (opts.config_file, str(err)))
965 results = config.validate(validator)
967 logging.fatal("Configuration file validation failed! See errors below:")
968 for (section_list, key, unused) in flatten_errors(config, results):
970 logging.fatal(" '%s' in section '%s' failed validation",
971 key, ", ".join(section_list))
973 logging.fatal(" Section '%s' is missing",
974 ", ".join(section_list))
977 logger = logging.getLogger()
979 logger.setLevel(logging.DEBUG)
981 logger.setLevel(logging.INFO)
984 logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
985 handler = logging.handlers.RotatingFileHandler(logfile,
988 handler = logging.StreamHandler()
990 handler.setFormatter(logging.Formatter(LOG_FORMAT))
991 logger.addHandler(handler)
994 pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
995 config["general"]["pidfile"], 10)
996 # Remove any stale PID files, left behind by previous invocations
997 if daemon.runner.is_pidfile_stale(pidfile):
998 logger.warning("Removing stale PID lock file %s", pidfile.path)
1001 d = daemon.DaemonContext(pidfile=pidfile,
1003 stdout=handler.stream,
1004 stderr=handler.stream,
1005 files_preserve=[handler.stream])
1008 except (daemon.pidlockfile.AlreadyLocked, LockTimeout):
1009 logger.critical("Failed to lock pidfile %s,"
1010 " another instance running?", pidfile.path)
1013 logging.info("Starting up")
1016 if config["dhcp"].as_bool("enable_dhcp"):
1018 "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
1019 "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
1020 "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
1021 "dhcp_server_ip": config["dhcp"]["server_ip"],
1022 "dhcp_nameservers": config["dhcp"]["nameservers"],
1023 "dhcp_domain": config["dhcp"]["domain"],
1026 if config["ipv6"].as_bool("enable_ipv6"):
1028 "rs_queue_num": config["ipv6"].as_int("rs_queue"),
1029 "ns_queue_num": config["ipv6"].as_int("ns_queue"),
1030 "ra_period": config["ipv6"].as_int("ra_period"),
1031 "ipv6_nameservers": config["ipv6"]["nameservers"],
1034 # pylint: disable=W0142
1035 proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
1037 # Drop all capabilities except CAP_NET_RAW and change uid
1039 uid = getpwuid(config["general"].as_int("user"))
1041 uid = getpwnam(config["general"]["user"])
1043 logging.debug("Setting capabilities and changing uid")
1044 logging.debug("User: %s, uid: %d, gid: %d",
1045 config["general"]["user"], uid.pw_uid, uid.pw_gid)
1047 # Keep only the capabilities we need
1048 # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
1049 # CAP_NET_RAW: we need to reopen socket in case the buffer gets full
1050 capng.capng_clear(capng.CAPNG_SELECT_BOTH)
1051 capng.capng_update(capng.CAPNG_ADD,
1052 capng.CAPNG_EFFECTIVE | capng.CAPNG_PERMITTED,
1053 capng.CAP_NET_ADMIN | capng.CAP_NET_RAW)
1054 capng.capng_change_id(uid.pw_uid, uid.pw_gid,
1055 capng.CAPNG_DROP_SUPP_GRP | capng.CAPNG_CLEAR_BOUNDING)
1057 logging.info("Ready to serve requests")
1062 exc = "".join(traceback.format_exception(*sys.exc_info()))
1063 logging.critical(exc)
1067 # vim: set ts=4 sts=4 sw=4 et :