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", mac, ifindex)
153 def parse_binding_file(path):
154 """ Read a client configuration from a tap file
157 logging.info("Parsing binding file %s", path)
159 iffile = open(path, 'r')
160 except EnvironmentError, e:
161 logging.warn("Unable to open binding file %s: %s", path, str(e))
164 tap = os.path.basename(path)
176 v = line.strip().split('=')[1]
182 if line.startswith("IP="):
184 elif line.startswith("MAC="):
185 mac = get_value(line)
186 elif line.startswith("HOSTNAME="):
187 hostname = get_value(line)
188 elif line.startswith("INDEV="):
189 indev = get_value(line)
190 elif line.startswith("SUBNET="):
191 subnet = get_value(line)
192 elif line.startswith("GATEWAY="):
193 gateway = get_value(line)
194 elif line.startswith("SUBNET6="):
195 subnet6 = get_value(line)
196 elif line.startswith("GATEWAY6="):
197 gateway6 = get_value(line)
198 elif line.startswith("EUI64="):
199 eui64 = get_value(line)
202 return Client(tap=tap, mac=mac, ip=ip, hostname=hostname,
203 indev=indev, subnet=subnet, gateway=gateway,
204 subnet6=subnet6, gateway6=gateway6, eui64=eui64 )
206 logging.warning("Cannot add client for host %s and IP %s on tap %s",
211 class ClientFileHandler(pyinotify.ProcessEvent):
212 def __init__(self, server):
213 pyinotify.ProcessEvent.__init__(self)
216 def process_IN_DELETE(self, event): # pylint: disable=C0103
217 """ Delete file handler
219 Currently this removes an interface from the watch list
222 self.server.remove_tap(event.name)
224 def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
227 Currently this adds an interface to the watch list
230 self.server.add_tap(os.path.join(event.path, event.name))
233 class Client(object):
234 def __init__(self, tap=None, indev=None, mac=None, ip=None, hostname=None,
235 subnet=None, gateway=None, subnet6=None, gateway6=None, eui64=None ):
238 self.hostname = hostname
242 self.gateway = gateway
243 self.net = Subnet(net=subnet, gw=gateway, dev=tap)
244 self.subnet6 = subnet6
245 self.gateway6 = gateway6
246 self.net6 = Subnet(net=subnet6, gw=gateway6, dev=tap)
250 return self.mac is not None and self.ip is not None\
251 and self.hostname is not None
254 class Subnet(object):
255 def __init__(self, net=None, gw=None, dev=None):
256 if isinstance(net, str):
258 self.net = IPy.IP(net)
259 except ValueError, e:
260 logging.warning("IPy error: %s", e)
269 """ Return the netmask in textual representation
272 return str(self.net.netmask())
276 """ Return the broadcast address in textual representation
279 return str(self.net.broadcast())
283 """ Return the network as an IPy.IP
286 return self.net.net()
290 """ Return the prefix length as an integer
293 return self.net.prefixlen()
296 def _make_eui64(net, mac):
297 """ Compute an EUI-64 address from an EUI-48 (MAC) address
302 comp = mac.split(":")
303 prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
304 eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
305 eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
306 for l in range(0, len(eui64), 2):
307 prefix += ["".join(eui64[l:l+2])]
308 return IPy.IP(":".join(prefix))
310 def make_eui64(self, mac):
311 """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
315 return self._make_eui64(self.net, mac)
317 def make_ll64(self, mac):
318 """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
321 return self._make_eui64("fe80::", mac)
324 class VMNetProxy(object): # pylint: disable=R0902
325 def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
326 rs_queue_num=None, ns_queue_num=None,
327 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
328 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
330 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
331 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
334 getattr(nfqueue.payload, 'get_physindev')
335 self.mac_indexed_clients = False
336 except AttributeError:
337 self.mac_indexed_clients = True
338 self.data_path = data_path
339 self.lease_lifetime = dhcp_lease_lifetime
340 self.lease_renewal = dhcp_lease_renewal
341 self.dhcp_domain = dhcp_domain
342 self.dhcp_server_ip = dhcp_server_ip
343 self.ra_period = ra_period
344 if dhcp_nameservers is None:
345 self.dhcp_nameserver = []
347 self.dhcp_nameservers = dhcp_nameservers
349 if ipv6_nameservers is None:
350 self.ipv6_nameservers = []
352 self.ipv6_nameservers = ipv6_nameservers
354 self.ipv6_enabled = False
361 self.l2socket = self._socket()
364 self.wm = pyinotify.WatchManager()
365 mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
366 mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
367 inotify_handler = ClientFileHandler(self)
368 self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
369 self.wm.add_watch(self.data_path, mask, rec=True)
372 if dhcp_queue_num is not None:
373 self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
375 if rs_queue_num is not None:
376 self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
377 self.ipv6_enabled = True
379 if ns_queue_num is not None:
380 self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
381 self.ipv6_enabled = True
384 logging.info("Opening L2 socket")
386 s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, ETH_P_ALL)
387 s.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
388 except socket.error, e:
389 logging.warning("Cannot open socket %s", e)
393 """ Free all resources for a graceful exit
396 logging.info("Cleaning up")
398 logging.debug("Closing netfilter queues")
399 for q in self.nfq.values():
402 logging.debug("Closing socket")
403 self.l2socket.close()
405 logging.debug("Stopping inotify watches")
408 logging.info("Cleanup finished")
410 def _setup_nfqueue(self, queue_num, family, callback):
411 logging.info("Setting up NFQUEUE for queue %d, AF %s",
414 q.set_callback(callback)
415 q.fast_open(queue_num, family)
416 q.set_queue_maxlen(5000)
417 # This is mandatory for the queue to operate
418 q.set_mode(nfqueue.NFQNL_COPY_PACKET)
419 self.nfq[q.get_fd()] = q
420 logging.debug("Successfully set up NFQUEUE %d", queue_num)
422 def sendp(self, data, dev):
423 """ Send a raw packet using a layer-2 socket
426 if isinstance(data, BasePacket):
429 logging.debug("Sending raw packet %r", data)
431 self.l2socket.bind((dev, ETH_P_ALL))
433 count = self.l2socket.send(data, socket.MSG_DONTWAIT)
434 except socket.error, e:
435 logging.warn("Send with MSG_DONTWAIT failed: %s", str(e))
436 self.l2socket.close()
437 self.l2socket = self._socket()
441 logging.debug("Sent %d bytes to device %s", count, dev)
443 logging.warn("Truncated send on %s (%d/%d bytes sent)",
446 def build_config(self):
449 for path in glob.glob(os.path.join(self.data_path, "*")):
452 logging.debug("%15s %20s %7s %15s", 'Client', 'MAC', 'TAP', 'IP')
453 for b in self.clients.values():
454 logging.debug("%15s %20s %7s %15s", b.hostname, b.mac, b.tap, b.ip)
456 def get_ifindex(self, iface):
457 """ Get the interface index from sysfs
460 logging.debug("Getting ifindex for interface %s from sysfs", iface)
462 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
463 if not path.startswith(SYSFS_NET):
470 except EnvironmentError:
471 logging.debug("%s is probably down, removing", iface)
472 self.remove_tap(iface)
477 ifindex = f.readline().strip()
479 ifindex = int(ifindex)
480 except ValueError, e:
481 logging.warn("Failed to get ifindex for %s, cannot parse sysfs"
482 " output '%s'", iface, ifindex)
483 except EnvironmentError, e:
484 logging.warn("Error reading %s's ifindex from sysfs: %s",
486 self.remove_tap(iface)
492 def get_iface_hw_addr(self, iface):
493 """ Get the interface hardware address from sysfs
496 logging.debug("Getting mac for iface %s", iface)
497 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
498 if not path.startswith(SYSFS_NET):
504 except EnvironmentError:
505 logging.debug("%s is probably down, removing", iface)
506 self.remove_tap(iface)
510 addr = f.readline().strip()
511 except EnvironmentError, e:
512 logging.warn("Failed to read hw address for %s from sysfs: %s",
519 def add_tap(self, path):
520 """ Add an interface to monitor
523 tap = os.path.basename(path)
525 logging.debug("Updating configuration for %s", tap)
526 b = parse_binding_file(path)
529 ifindex = self.get_ifindex(b.tap)
532 logging.warn("Stale configuration for %s found", tap)
535 if self.mac_indexed_clients:
536 self.clients[b.mac] = b
538 self.clients[ifindex] = b
539 logging.debug("Added client:")
540 logging.debug("%5s: %10s %20s %7s %15s",
541 ifindex, b.hostname, b.mac, b.tap, b.ip)
543 def remove_tap(self, tap):
544 """ Cleanup clients on a removed interface
548 for k in self.clients.keys():
550 if self.clients[k].tap == tap:
551 logging.debug("Removing client on interface %s", tap)
552 logging.debug("%10s %20s %7s %15s",
553 b.hostname, b.mac, b.tap, b.ip)
556 logging.debug("Client on %s disappeared!!!", tap)
558 def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
559 """ Generate a reply to bnetfilter-queue-deva BOOTP/DHCP request
562 # Decode the response - NFQUEUE relays IP packets
563 pkt = IP(payload.get_data())
564 logging.debug("Generating DHCP response")
565 #logging.debug(pkt.show())
567 # Get the client MAC address
568 resp = pkt.getlayer(BOOTP).copy()
570 mac = resp.chaddr[:hlen].encode("hex")
571 mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen - 1)
573 # Server responses are always BOOTREPLYs
574 resp.op = "BOOTREPLY"
577 indev = get_indev(payload)
579 binding = get_binding(self, indev, mac)
581 # We don't know anything about this interface, so accept the packet
583 logging.debug("Ignoring DHCP request on unknown iface %d", indev)
584 # We don't know what to do with this packet, so let the kernel
586 payload.set_verdict(nfqueue.NF_ACCEPT)
589 # Signal the kernel that it shouldn't further process the packet
590 payload.set_verdict(nfqueue.NF_DROP)
592 if mac != binding.mac:
593 logging.warn("Recieved spoofed DHCP request for mac %s from tap %s", mac, indev)
596 resp = Ether(dst=mac, src=self.get_iface_hw_addr(binding.indev))/\
597 IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
598 UDP(sport=pkt.dport, dport=pkt.sport)/resp
602 logging.warn("Invalid request from %s on %s, no DHCP"
603 " payload found", binding.mac, binding.tap)
607 requested_addr = binding.ip
608 for opt in pkt[DHCP].options:
609 if type(opt) is tuple and opt[0] == "message-type":
611 if type(opt) is tuple and opt[0] == "requested_addr":
612 requested_addr = opt[1]
614 logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
615 binding.mac, binding.tap)
618 domainname = self.dhcp_domain
620 domainname = binding.hostname.split('.', 1)[-1]
622 if req_type == DHCPREQUEST and requested_addr != binding.ip:
624 logging.info("Sending DHCPNAK to %s on %s: requested %s"
625 " instead of %s", binding.mac, binding.tap,
626 requested_addr, binding.ip)
628 elif req_type in (DHCPDISCOVER, DHCPREQUEST):
629 resp_type = DHCP_REQRESP[req_type]
630 resp.yiaddr = binding.ip
632 ("hostname", binding.hostname),
633 ("domain", domainname),
634 ("broadcast_address", str(subnet.broadcast)),
635 ("subnet_mask", str(subnet.netmask)),
636 ("renewal_time", self.lease_renewal),
637 ("lease_time", self.lease_lifetime),
640 dhcp_options += [("router", subnet.gw)]
641 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
643 elif req_type == DHCPINFORM:
644 resp_type = DHCP_REQRESP[req_type]
646 ("hostname", binding.hostname),
647 ("domain", domainname),
649 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
651 elif req_type == DHCPRELEASE:
653 logging.info("DHCPRELEASE from %s on %s", binding.mac, binding.tap)
656 # Finally, always add the server identifier and end options
658 ("message-type", resp_type),
659 ("server_id", DHCP_DUMMY_SERVER_IP),
662 resp /= DHCP(options=dhcp_options)
664 logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
665 binding.ip, binding.tap)
667 self.sendp(resp, binding.indev)
668 except socket.error, e:
669 logging.warn("DHCP response on %s failed: %s", binding.indev, str(e))
671 logging.warn("Unkown error during DHCP response on %s: %s",
672 binding.indev, str(e))
674 def rs_response(self, i, payload): # pylint: disable=W0613
675 """ Generate a reply to a BOOTP/DHCP request
679 pkt = IPv6(payload.get_data())
680 #logging.debug(pkt.show())
684 logging.debug("Cannot obtain lladdr in rs")
687 logging.debug("Generating an rs response for mac %s", mac)
689 indev = get_indev(payload)
691 binding = get_binding(self, indev, mac)
693 # We don't know anything about this interface, so accept the packet
695 logging.debug("Ignoring router solicitation on for mac %s", mac)
696 # We don't know what to do with this packet, so let the kernel
698 payload.set_verdict(nfqueue.NF_ACCEPT)
701 # Signal the kernel that it shouldn't further process the packet
702 payload.set_verdict(nfqueue.NF_DROP)
704 if mac != binding.mac:
705 logging.warn("Received spoofed RS request for mac %s from tap %s",
709 subnet = binding.net6
711 if subnet.net is None:
712 logging.debug("No IPv6 network assigned for the interface")
715 indevmac = self.get_iface_hw_addr(binding.indev)
716 ifll = subnet.make_ll64(indevmac)
720 resp = Ether(src=indevmac)/\
721 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
722 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
723 prefixlen=subnet.prefixlen)
725 if self.ipv6_nameservers:
726 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
727 lifetime=self.ra_period * 3)
729 logging.info("RA on %s for %s", binding.indev, subnet.net)
731 self.sendp(resp, binding.indev)
732 except socket.error, e:
733 logging.warn("RA on %s failed: %s", binding.indev, str(e))
735 logging.warn("Unkown error during RA on %s: %s",
736 binding.indev, str(e))
738 def ns_response(self, i, payload): # pylint: disable=W0613
739 """ Generate a reply to an ICMPv6 neighbor solicitation
743 ns = IPv6(payload.get_data())
744 #logging.debug(ns.show())
748 logging.debug("Cannot obtain lladdr from ns")
751 logging.debug("Generating ns response, dst: %s tgt: %s", ns.dst, ns.tgt)
753 indev = get_indev(payload)
755 binding = get_binding(self, indev, mac)
757 # We don't know anything about this interface, so accept the packet
759 logging.debug("Ignoring neighbour solicitation for eui64 %s", ns.tgt)
760 # We don't know what to do with this packet, so let the kernel
762 payload.set_verdict(nfqueue.NF_ACCEPT)
765 payload.set_verdict(nfqueue.NF_DROP)
767 if mac != binding.mac:
768 logging.warn("Received spoofed NS request for mac %s from tap %s",
772 subnet = binding.net6
773 if subnet.net is None:
774 logging.debug("No IPv6 network assigned for the interface")
777 indevmac = self.get_iface_hw_addr(binding.indev)
779 ifll = subnet.make_ll64(indevmac)
783 if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
784 logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
785 payload.set_verdict(nfqueue.NF_ACCEPT)
788 logging.debug("Sending NA response to client %s", binding.mac)
789 resp = Ether(src=indevmac, dst=binding.mac)/\
790 IPv6(src=str(ifll), dst=ns.src)/\
791 ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
792 ICMPv6NDOptDstLLAddr(lladdr=indevmac)
794 logging.info("NA on %s for %s", binding.indev, ns.tgt)
796 self.sendp(resp, binding.indev)
797 except socket.error, e:
798 logging.warn("NA on %s failed: %s", binding.indev, str(e))
800 logging.warn("Unkown error during periodic NA on %s: %s",
801 binding.indev, str(e))
803 def send_periodic_ra(self):
804 # Use a separate thread as this may take a _long_ time with
805 # many interfaces and we want to be responsive in the mean time
806 threading.Thread(target=self._send_periodic_ra).start()
808 def _send_periodic_ra(self):
809 logging.debug("Sending out periodic RAs")
812 for binding in self.clients.values():
814 indev = binding.indev
816 subnet = binding.net6
817 if subnet.net is None:
818 logging.debug("Skipping periodic RA on interface %s,"
819 " as it is not IPv6-connected", tap)
821 indevmac = self.get_iface_hw_addr(indev)
822 ifll = subnet.make_ll64(indevmac)
825 resp = Ether(src=indevmac)/\
826 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
827 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
828 prefixlen=subnet.prefixlen)
829 if self.ipv6_nameservers:
830 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
831 lifetime=self.ra_period * 3)
833 self.sendp(resp, indev)
834 except socket.error, e:
835 logging.warn("Periodic RA on %s failed: %s", tap, str(e))
837 logging.warn("Unkown error during periodic RA on %s: %s",
840 logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
843 """ Safely perform the main loop, freeing all resources upon exit
852 """ Loop forever, serving DHCP requests
857 # Yes, we are accessing _fd directly, but it's the only way to have a
858 # single select() loop ;-)
859 iwfd = self.notifier._fd # pylint: disable=W0212
862 if self.ipv6_enabled:
863 timeout = self.ra_period
864 self.send_periodic_ra()
869 rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
871 logging.warn("Warning: Exception on %s",
872 ", ".join([str(fd) for fd in xlist]))
876 # First check if there are any inotify (= configuration change)
878 self.notifier.read_events()
879 self.notifier.process_events()
884 self.nfq[fd].process_pending()
885 except RuntimeError, e:
886 logging.warn("Error processing fd %d: %s", fd, str(e))
888 logging.warn("Unknown error processing fd %d: %s",
891 if self.ipv6_enabled:
892 # Calculate the new timeout
893 timeout = self.ra_period - (time.time() - start)
897 self.send_periodic_ra()
898 timeout = self.ra_period - (time.time() - start)
901 if __name__ == "__main__":
904 from cStringIO import StringIO
905 from pwd import getpwnam, getpwuid
906 from configobj import ConfigObj, ConfigObjError, flatten_errors
910 validator = validate.Validator()
912 def is_ip_list(value, family=4):
916 raise validate.VdtParamError(family)
917 if isinstance(value, (str, unicode)):
919 if not isinstance(value, list):
920 raise validate.VdtTypeError(value)
926 raise validate.VdtValueError(entry)
928 if ip.version() != family:
929 raise validate.VdtValueError(entry)
932 validator.functions["ip_addr_list"] = is_ip_list
933 config_spec = StringIO(CONFIG_SPEC)
935 parser = optparse.OptionParser()
936 parser.add_option("-c", "--config", dest="config_file",
937 help="The location of the data files", metavar="FILE",
938 default=DEFAULT_CONFIG)
939 parser.add_option("-d", "--debug", action="store_true", dest="debug",
940 help="Turn on debugging messages")
941 parser.add_option("-f", "--foreground", action="store_false",
942 dest="daemonize", default=True,
943 help="Do not daemonize, stay in the foreground")
945 opts, args = parser.parse_args()
948 config = ConfigObj(opts.config_file, configspec=config_spec)
949 except ConfigObjError, err:
950 sys.stderr.write("Failed to parse config file %s: %s" %
951 (opts.config_file, str(err)))
954 results = config.validate(validator)
956 logging.fatal("Configuration file validation failed! See errors below:")
957 for (section_list, key, unused) in flatten_errors(config, results):
959 logging.fatal(" '%s' in section '%s' failed validation",
960 key, ", ".join(section_list))
962 logging.fatal(" Section '%s' is missing",
963 ", ".join(section_list))
966 logger = logging.getLogger()
968 logger.setLevel(logging.DEBUG)
970 logger.setLevel(logging.INFO)
973 logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
974 handler = logging.handlers.RotatingFileHandler(logfile,
977 handler = logging.StreamHandler()
979 handler.setFormatter(logging.Formatter(LOG_FORMAT))
980 logger.addHandler(handler)
983 pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
984 config["general"]["pidfile"], 10)
985 # Remove any stale PID files, left behind by previous invocations
986 if daemon.runner.is_pidfile_stale(pidfile):
987 logger.warning("Removing stale PID lock file %s", pidfile.path)
990 d = daemon.DaemonContext(pidfile=pidfile,
992 stdout=handler.stream,
993 stderr=handler.stream,
994 files_preserve=[handler.stream])
997 except (daemon.pidlockfile.AlreadyLocked, LockTimeout):
998 logger.critical("Failed to lock pidfile %s,"
999 " another instance running?", pidfile.path)
1002 logging.info("Starting up")
1005 if config["dhcp"].as_bool("enable_dhcp"):
1007 "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
1008 "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
1009 "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
1010 "dhcp_server_ip": config["dhcp"]["server_ip"],
1011 "dhcp_nameservers": config["dhcp"]["nameservers"],
1012 "dhcp_domain": config["dhcp"]["domain"],
1015 if config["ipv6"].as_bool("enable_ipv6"):
1017 "rs_queue_num": config["ipv6"].as_int("rs_queue"),
1018 "ns_queue_num": config["ipv6"].as_int("ns_queue"),
1019 "ra_period": config["ipv6"].as_int("ra_period"),
1020 "ipv6_nameservers": config["ipv6"]["nameservers"],
1023 # pylint: disable=W0142
1024 proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
1026 # Drop all capabilities except CAP_NET_RAW and change uid
1028 uid = getpwuid(config["general"].as_int("user"))
1030 uid = getpwnam(config["general"]["user"])
1032 logging.debug("Setting capabilities and changing uid")
1033 logging.debug("User: %s, uid: %d, gid: %d",
1034 config["general"]["user"], uid.pw_uid, uid.pw_gid)
1036 # Keep only the capabilities we need
1037 # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
1038 # CAP_NET_RAW: we need to reopen socket in case the buffer gets full
1039 capng.capng_clear(capng.CAPNG_SELECT_BOTH)
1040 capng.capng_update(capng.CAPNG_ADD,
1041 capng.CAPNG_EFFECTIVE | capng.CAPNG_PERMITTED,
1042 capng.CAP_NET_ADMIN | capng.CAP_NET_RAW)
1043 capng.capng_change_id(uid.pw_uid, uid.pw_gid,
1044 capng.CAPNG_DROP_SUPP_GRP | capng.CAPNG_CLEAR_BOUNDING)
1046 logging.info("Ready to serve requests")
1051 exc = "".join(traceback.format_exception(*sys.exc_info()))
1052 logging.critical(exc)
1056 # vim: set ts=4 sts=4 sw=4 et :