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
41 from select import select
42 from socket import AF_INET, AF_INET6
44 from scapy.data import ETH_P_ALL
45 from scapy.packet import BasePacket
46 from scapy.layers.l2 import Ether
47 from scapy.layers.inet import IP, UDP
48 from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \
49 ICMPv6NDOptDstLLAddr, \
50 ICMPv6NDOptPrefixInfo, \
52 from scapy.layers.dhcp import BOOTP, DHCP
54 DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf"
55 DEFAULT_PATH = "/var/run/ganeti-dhcpd"
56 DEFAULT_USER = "nobody"
57 DEFAULT_LEASE_LIFETIME = 604800 # 1 week
58 DEFAULT_LEASE_RENEWAL = 600 # 10 min
59 DEFAULT_RA_PERIOD = 300 # seconds
60 DHCP_DUMMY_SERVER_IP = "1.2.3.4"
62 LOG_FILENAME = "nfdhcpd.log"
64 SYSFS_NET = "/sys/class/net"
66 LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s"
68 # Configuration file specification (see configobj documentation)
77 enable_dhcp = boolean(default=True)
78 lease_lifetime = integer(min=0, max=4294967295)
79 lease_renewal = integer(min=0, max=4294967295)
81 dhcp_queue = integer(min=0, max=65535)
82 nameservers = ip_addr_list(family=4)
83 domain = string(default=None)
86 enable_ipv6 = boolean(default=True)
87 ra_period = integer(min=1, max=4294967295)
88 rs_queue = integer(min=0, max=65535)
89 ns_queue = integer(min=0, max=65535)
90 nameservers = ip_addr_list(family=6)
104 DHCPDISCOVER: "DHCPDISCOVER",
105 DHCPOFFER: "DHCPOFFER",
106 DHCPREQUEST: "DHCPREQUEST",
107 DHCPDECLINE: "DHCPDECLINE",
110 DHCPRELEASE: "DHCPRELEASE",
111 DHCPINFORM: "DHCPINFORM",
115 DHCPDISCOVER: DHCPOFFER,
116 DHCPREQUEST: DHCPACK,
121 def get_indev(payload):
123 indev_ifindex = payload.get_physindev()
124 logging.debug("get_physindev %s", indev_ifindex)
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)
138 def get_binding(proxy, ifindex, mac):
140 if proxy.mac_indexed_clients:
141 logging.debug("get_binding for mac %s", mac)
142 b = proxy.clients[mac]
144 logging.debug("get_binding for ifindex %s", ifindex)
145 b = proxy.clients[ifindex]
148 logging.debug("No client found for mac/ifindex %s/%s", mac, ifindex)
151 def parse_binding_file(path):
152 """ Read a client configuration from a tap file
156 iffile = open(path, 'r')
157 except EnvironmentError, e:
158 logging.warn("Unable to open binding file %s: %s", path, str(e))
161 tap = os.path.basename(path)
173 v = line.strip().split('=')[1]
179 if line.startswith("IP="):
181 elif line.startswith("MAC="):
182 mac = get_value(line)
183 elif line.startswith("HOSTNAME="):
184 hostname = get_value(line)
185 elif line.startswith("INDEV="):
186 indev = get_value(line)
187 elif line.startswith("SUBNET="):
188 subnet = get_value(line)
189 elif line.startswith("GATEWAY="):
190 gateway = get_value(line)
191 elif line.startswith("SUBNET6="):
192 subnet6 = get_value(line)
193 elif line.startswith("GATEWAY6="):
194 gateway6 = get_value(line)
195 elif line.startswith("EUI64="):
196 eui64 = get_value(line)
198 return Client(tap=tap, mac=mac, ip=ip,
199 hostname=hostname, indev=indev, subnet=subnet,
200 gateway=gateway, subnet6=subnet6, gateway6=gateway6, eui64=eui64 )
202 class ClientFileHandler(pyinotify.ProcessEvent):
203 def __init__(self, server):
204 pyinotify.ProcessEvent.__init__(self)
207 def process_IN_DELETE(self, event): # pylint: disable=C0103
208 """ Delete file handler
210 Currently this removes an interface from the watch list
213 self.server.remove_tap(event.name)
215 def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
218 Currently this adds an interface to the watch list
221 self.server.add_tap(os.path.join(event.path, event.name))
224 class Client(object):
225 def __init__(self, tap=None, indev=None, mac=None, ip=None, hostname=None,
226 subnet=None, gateway=None, subnet6=None, gateway6=None, eui64=None ):
229 self.hostname = hostname
233 self.gateway = gateway
234 self.net = Subnet(net=subnet, gw=gateway, dev=tap)
235 self.subnet6 = subnet6
236 self.gateway6 = gateway6
237 self.net6 = Subnet(net=subnet6, gw=gateway6, dev=tap)
241 return self.mac is not None and self.ip is not None\
242 and self.hostname is not None
245 class Subnet(object):
246 def __init__(self, net=None, gw=None, dev=None):
247 if isinstance(net, str):
248 self.net = IPy.IP(net)
256 """ Return the netmask in textual representation
259 return str(self.net.netmask())
263 """ Return the broadcast address in textual representation
266 return str(self.net.broadcast())
270 """ Return the network as an IPy.IP
273 return self.net.net()
277 """ Return the prefix length as an integer
280 return self.net.prefixlen()
283 def _make_eui64(net, mac):
284 """ Compute an EUI-64 address from an EUI-48 (MAC) address
289 comp = mac.split(":")
290 prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
291 eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
292 eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
293 for l in range(0, len(eui64), 2):
294 prefix += ["".join(eui64[l:l+2])]
295 return IPy.IP(":".join(prefix))
297 def make_eui64(self, mac):
298 """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
302 return self._make_eui64(self.net, mac)
304 def make_ll64(self, mac):
305 """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
308 return self._make_eui64("fe80::", mac)
311 class VMNetProxy(object): # pylint: disable=R0902
312 def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
313 rs_queue_num=None, ns_queue_num=None,
314 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
315 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
317 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
318 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
321 getattr(nfqueue.payload, 'get_physindev')
322 self.mac_indexed_clients = False
323 except AttributeError:
324 self.mac_indexed_clients = True
325 self.data_path = data_path
326 self.lease_lifetime = dhcp_lease_lifetime
327 self.lease_renewal = dhcp_lease_renewal
328 self.dhcp_domain = dhcp_domain
329 self.dhcp_server_ip = dhcp_server_ip
330 self.ra_period = ra_period
331 if dhcp_nameservers is None:
332 self.dhcp_nameserver = []
334 self.dhcp_nameservers = dhcp_nameservers
336 if ipv6_nameservers is None:
337 self.ipv6_nameservers = []
339 self.ipv6_nameservers = ipv6_nameservers
341 self.ipv6_enabled = False
348 self.l2socket = socket.socket(socket.AF_PACKET,
349 socket.SOCK_RAW, ETH_P_ALL)
350 self.l2socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
353 self.wm = pyinotify.WatchManager()
354 mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
355 mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
356 inotify_handler = ClientFileHandler(self)
357 self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
358 self.wm.add_watch(self.data_path, mask, rec=True)
361 if dhcp_queue_num is not None:
362 self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
364 if rs_queue_num is not None:
365 self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
366 self.ipv6_enabled = True
368 if ns_queue_num is not None:
369 self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
370 self.ipv6_enabled = True
373 """ Free all resources for a graceful exit
376 logging.info("Cleaning up")
378 logging.debug("Closing netfilter queues")
379 for q in self.nfq.values():
382 logging.debug("Closing socket")
383 self.l2socket.close()
385 logging.debug("Stopping inotify watches")
388 logging.info("Cleanup finished")
390 def _setup_nfqueue(self, queue_num, family, callback):
391 logging.debug("Setting up NFQUEUE for queue %d, AF %s",
394 q.set_callback(callback)
395 q.fast_open(queue_num, family)
396 q.set_queue_maxlen(5000)
397 # This is mandatory for the queue to operate
398 q.set_mode(nfqueue.NFQNL_COPY_PACKET)
399 self.nfq[q.get_fd()] = q
401 def sendp(self, data, dev):
402 """ Send a raw packet using a layer-2 socket
405 logging.debug("%s", data)
406 if isinstance(data, BasePacket):
409 self.l2socket.bind((dev, ETH_P_ALL))
410 count = self.l2socket.send(data)
413 logging.warn("Truncated send on %s (%d/%d bytes sent)",
416 def build_config(self):
419 for path in glob.glob(os.path.join(self.data_path, "*")):
422 logging.debug("\n\n\n\n\n")
423 logging.debug("%10s %20s %7s %15s", 'Client', 'MAC', 'TAP', 'IP')
424 for b in self.clients.values():
425 logging.debug("%10s %20s %7s %15s", b.hostname, b.mac, b.tap, b.ip)
427 def get_ifindex(self, iface):
428 """ Get the interface index from sysfs
431 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
432 if not path.startswith(SYSFS_NET):
439 except EnvironmentError:
440 logging.debug("%s is probably down, removing", iface)
441 self.remove_tap(iface)
446 ifindex = f.readline().strip()
448 ifindex = int(ifindex)
449 except ValueError, e:
450 logging.warn("Failed to get ifindex for %s, cannot parse sysfs"
451 " output '%s'", iface, ifindex)
452 except EnvironmentError, e:
453 logging.warn("Error reading %s's ifindex from sysfs: %s",
455 self.remove_tap(iface)
462 def get_iface_hw_addr(self, iface):
463 """ Get the interface hardware address from sysfs
466 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
467 if not path.startswith(SYSFS_NET):
473 except EnvironmentError:
474 logging.debug("%s is probably down, removing", iface)
475 self.remove_tap(iface)
479 addr = f.readline().strip()
480 except EnvironmentError, e:
481 logging.warn("Failed to read hw address for %s from sysfs: %s",
488 def add_tap(self, path):
489 """ Add an interface to monitor
492 tap = os.path.basename(path)
494 logging.debug("Updating configuration for %s", tap)
495 b = parse_binding_file(path)
498 ifindex = self.get_ifindex(b.tap)
501 logging.warn("Stale configuration for %s found", tap)
504 if self.mac_indexed_clients:
505 self.clients[b.mac] = b
507 self.clients[ifindex] = b
508 logging.debug("Added client:")
509 logging.debug("%5s: %10s %20s %7s %15s",
510 ifindex, b.hostname, b.mac, b.tap, b.ip)
512 def remove_tap(self, tap):
513 """ Cleanup clients on a removed interface
517 for k in self.clients.keys():
519 if self.clients[k].tap == tap:
520 logging.debug("Removing client on interface %s", tap)
521 logging.debug("%10s %20s %7s %15s",
522 b.hostname, b.mac, b.tap, b.ip)
525 logging.debug("Client on %s disappeared!!!", tap)
528 def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
529 """ Generate a reply to bnetfilter-queue-deva BOOTP/DHCP request
532 # Decode the response - NFQUEUE relays IP packets
533 pkt = IP(payload.get_data())
534 logging.debug("IN DHCP RESPONCE")
535 #logging.debug(pkt.show())
537 # Get the client MAC address
538 resp = pkt.getlayer(BOOTP).copy()
540 mac = resp.chaddr[:hlen].encode("hex")
541 mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
543 # Server responses are always BOOTREPLYs
544 resp.op = "BOOTREPLY"
547 indev = get_indev(payload)
549 binding = get_binding(self, indev, mac)
551 # We don't know anything about this interface, so accept the packet
553 logging.debug("Ignoring DHCP request on unknown iface %d", indev)
554 # We don't know what to do with this packet, so let the kernel
556 payload.set_verdict(nfqueue.NF_ACCEPT)
560 # Signal the kernel that it shouldn't further process the packet
561 payload.set_verdict(nfqueue.NF_DROP)
563 if mac != binding.mac:
564 logging.warn("Recieved spoofed DHCP request for mac %s from tap %s", mac, indev)
567 resp = Ether(dst=mac, src=self.get_iface_hw_addr(binding.indev))/\
568 IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
569 UDP(sport=pkt.dport, dport=pkt.sport)/resp
573 logging.warn("Invalid request from %s on %s, no DHCP"
574 " payload found", binding.mac, binding.tap)
578 requested_addr = binding.ip
579 for opt in pkt[DHCP].options:
580 if type(opt) is tuple and opt[0] == "message-type":
582 if type(opt) is tuple and opt[0] == "requested_addr":
583 requested_addr = opt[1]
585 logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
586 binding.mac, binding.tap)
589 domainname = self.dhcp_domain
591 domainname = binding.hostname.split('.', 1)[-1]
593 if req_type == DHCPREQUEST and requested_addr != binding.ip:
595 logging.info("Sending DHCPNAK to %s on %s: requested %s"
596 " instead of %s", binding.mac, binding.tap,
597 requested_addr, binding.ip)
599 elif req_type in (DHCPDISCOVER, DHCPREQUEST):
600 resp_type = DHCP_REQRESP[req_type]
601 resp.yiaddr = binding.ip
603 ("hostname", binding.hostname),
604 ("domain", domainname),
605 ("broadcast_address", str(subnet.broadcast)),
606 ("subnet_mask", str(subnet.netmask)),
607 ("renewal_time", self.lease_renewal),
608 ("lease_time", self.lease_lifetime),
611 dhcp_options += [("router", subnet.gw)]
612 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
614 elif req_type == DHCPINFORM:
615 resp_type = DHCP_REQRESP[req_type]
617 ("hostname", binding.hostname),
618 ("domain", domainname),
620 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
622 elif req_type == DHCPRELEASE:
624 logging.info("DHCPRELEASE from %s on %s", binding.mac, binding.tap )
627 # Finally, always add the server identifier and end options
629 ("message-type", resp_type),
630 ("server_id", DHCP_DUMMY_SERVER_IP),
633 resp /= DHCP(options=dhcp_options)
635 logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
636 binding.ip, binding.tap)
638 self.sendp(resp, binding.indev)
639 except socket.error, e:
640 logging.warn("DHCP response on %s failed: %s", binding.indev, str(e))
642 logging.warn("Unkown error during DHCP response on %s: %s",
643 binding.indev, str(e))
645 def rs_response(self, i, payload): # pylint: disable=W0613
646 """ Generate a reply to a BOOTP/DHCP request
649 pkt = IPv6(payload.get_data())
650 logging.debug("IN RS RESPONCE")
651 #logging.debug(pkt.show())
655 logging.debug("Cannot obtain lladdr in rs")
658 logging.debug("rs for mac %s", mac)
660 indev = get_indev(payload)
662 binding = get_binding(self, indev, mac)
664 # We don't know anything about this interface, so accept the packet
666 logging.debug("Ignoring router solicitation on for mac %s", mac)
667 # We don't know what to do with this packet, so let the kernel
669 payload.set_verdict(nfqueue.NF_ACCEPT)
672 # Signal the kernel that it shouldn't further process the packet
673 payload.set_verdict(nfqueue.NF_DROP)
675 if mac != binding.mac:
676 logging.warn("Recieved spoofed RS request for mac %s from tap %s", mac, tap)
679 subnet = binding.net6
681 if subnet.net is None:
682 logging.debug("No IPv6 network assigned for the interface")
685 indevmac = self.get_iface_hw_addr(binding.indev)
686 ifll = subnet.make_ll64(indevmac)
691 resp = Ether(src=indevmac)/\
692 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
693 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
694 prefixlen=subnet.prefixlen)
696 if self.ipv6_nameservers:
697 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
698 lifetime=self.ra_period * 3)
700 logging.info("RA on %s for %s", binding.indev, subnet.net)
702 self.sendp(resp, binding.indev)
703 except socket.error, e:
704 logging.warn("RA on %s failed: %s", binding.indev, str(e))
706 logging.warn("Unkown error during RA on %s: %s",
707 binding.indev, str(e))
709 def ns_response(self, i, payload): # pylint: disable=W0613
710 """ Generate a reply to an ICMPv6 neighbor solicitation
713 ns = IPv6(payload.get_data())
714 logging.debug("IN NS RESPONCE")
715 #logging.debug(ns.show())
719 logging.debug("Cannot obtain lladdr from ns")
722 logging.debug("dst %s tgt %s" , ns.dst, ns.tgt)
724 indev = get_indev(payload)
726 binding = get_binding(self, indev, mac)
728 # We don't know anything about this interface, so accept the packet
730 logging.debug("Ignoring neighbour solicitation for eui64 %s", ns.tgt)
731 # We don't know what to do with this packet, so let the kernel
733 payload.set_verdict(nfqueue.NF_ACCEPT)
736 payload.set_verdict(nfqueue.NF_DROP)
738 if mac != binding.mac:
739 logging.warn("Recieved spoofed NS request for mac %s from tap %s", mac, tap)
742 subnet = binding.net6
743 if subnet.net is None:
744 logging.debug("No IPv6 network assigned for the interface")
747 indevmac = self.get_iface_hw_addr(binding.indev)
749 ifll = subnet.make_ll64(indevmac)
753 if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
754 logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
755 payload.set_verdict(nfqueue.NF_ACCEPT)
758 logging.debug("na ether %s %s", binding.mac, ns.src)
759 resp = Ether(src=indevmac, dst=binding.mac)/\
760 IPv6(src=str(ifll), dst=ns.src)/\
761 ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
762 ICMPv6NDOptDstLLAddr(lladdr=indevmac)
764 logging.info("NA on %s for %s", binding.indev, ns.tgt)
766 self.sendp(resp, binding.indev)
767 except socket.error, e:
768 logging.warn("NA on %s failed: %s", binding.indev, str(e))
770 logging.warn("Unkown error during periodic NA on %s: %s",
771 binding.indev, str(e))
773 def send_periodic_ra(self):
774 # Use a separate thread as this may take a _long_ time with
775 # many interfaces and we want to be responsive in the mean time
776 threading.Thread(target=self._send_periodic_ra).start()
778 def _send_periodic_ra(self):
779 logging.debug("Sending out periodic RAs")
782 for binding in self.clients.values():
784 indev = binding.indev
786 subnet = binding.net6
787 if subnet.net is None:
788 logging.debug("Skipping periodic RA on interface %s,"
789 " as it is not IPv6-connected", tap)
791 indevmac = self.get_iface_hw_addr(indev)
792 ifll = subnet.make_ll64(indevmac)
795 resp = Ether(src=indevmac)/\
796 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
797 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
798 prefixlen=subnet.prefixlen)
799 if self.ipv6_nameservers:
800 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
801 lifetime=self.ra_period * 3)
803 self.sendp(resp, indev)
804 except socket.error, e:
805 logging.warn("Periodic RA on %s failed: %s", tap, str(e))
807 logging.warn("Unkown error during periodic RA on %s: %s",
810 logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
813 """ Safely perform the main loop, freeing all resources upon exit
822 """ Loop forever, serving DHCP requests
827 # Yes, we are accessing _fd directly, but it's the only way to have a
828 # single select() loop ;-)
829 iwfd = self.notifier._fd # pylint: disable=W0212
832 if self.ipv6_enabled:
833 timeout = self.ra_period
834 self.send_periodic_ra()
839 rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
841 logging.warn("Warning: Exception on %s",
842 ", ".join([ str(fd) for fd in xlist]))
846 # First check if there are any inotify (= configuration change)
848 self.notifier.read_events()
849 self.notifier.process_events()
854 self.nfq[fd].process_pending()
855 except RuntimeError, e:
856 logging.warn("Error processing fd %d: %s", fd, str(e))
858 logging.warn("Unknown error processing fd %d: %s",
861 if self.ipv6_enabled:
862 # Calculate the new timeout
863 timeout = self.ra_period - (time.time() - start)
867 self.send_periodic_ra()
868 timeout = self.ra_period - (time.time() - start)
871 if __name__ == "__main__":
874 from cStringIO import StringIO
875 from pwd import getpwnam, getpwuid
876 from configobj import ConfigObj, ConfigObjError, flatten_errors
880 validator = validate.Validator()
882 def is_ip_list(value, family=4):
886 raise validate.VdtParamError(family)
887 if isinstance(value, (str, unicode)):
889 if not isinstance(value, list):
890 raise validate.VdtTypeError(value)
896 raise validate.VdtValueError(entry)
898 if ip.version() != family:
899 raise validate.VdtValueError(entry)
902 validator.functions["ip_addr_list"] = is_ip_list
903 config_spec = StringIO(CONFIG_SPEC)
906 parser = optparse.OptionParser()
907 parser.add_option("-c", "--config", dest="config_file",
908 help="The location of the data files", metavar="FILE",
909 default=DEFAULT_CONFIG)
910 parser.add_option("-d", "--debug", action="store_true", dest="debug",
911 help="Turn on debugging messages")
912 parser.add_option("-f", "--foreground", action="store_false",
913 dest="daemonize", default=True,
914 help="Do not daemonize, stay in the foreground")
917 opts, args = parser.parse_args()
920 config = ConfigObj(opts.config_file, configspec=config_spec)
921 except ConfigObjError, err:
922 sys.stderr.write("Failed to parse config file %s: %s" %
923 (opts.config_file, str(err)))
926 results = config.validate(validator)
928 logging.fatal("Configuration file validation failed! See errors below:")
929 for (section_list, key, unused) in flatten_errors(config, results):
931 logging.fatal(" '%s' in section '%s' failed validation",
932 key, ", ".join(section_list))
934 logging.fatal(" Section '%s' is missing",
935 ", ".join(section_list))
938 logger = logging.getLogger()
940 logger.setLevel(logging.DEBUG)
942 logger.setLevel(logging.INFO)
945 logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
946 handler = logging.handlers.RotatingFileHandler(logfile,
949 handler = logging.StreamHandler()
951 handler.setFormatter(logging.Formatter(LOG_FORMAT))
952 logger.addHandler(handler)
955 pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
956 config["general"]["pidfile"], 10)
957 # Remove any stale PID files, left behind by previous invocations
958 if (daemon.runner.is_pidfile_stale(pidfile) or
959 pidfile.read_pid() is None):
960 logger.warning("Removing stale PID lock file %s", pidfile.path)
964 d = daemon.DaemonContext(pidfile=pidfile,
965 stdout=handler.stream,
966 stderr=handler.stream,
967 files_preserve=[handler.stream])
971 logging.info("Starting up")
974 if config["dhcp"].as_bool("enable_dhcp"):
976 "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
977 "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
978 "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
979 "dhcp_server_ip": config["dhcp"]["server_ip"],
980 "dhcp_nameservers": config["dhcp"]["nameservers"],
981 "dhcp_domain": config["dhcp"]["domain"],
984 if config["ipv6"].as_bool("enable_ipv6"):
986 "rs_queue_num": config["ipv6"].as_int("rs_queue"),
987 "ns_queue_num": config["ipv6"].as_int("ns_queue"),
988 "ra_period": config["ipv6"].as_int("ra_period"),
989 "ipv6_nameservers": config["ipv6"]["nameservers"],
992 # pylint: disable=W0142
993 proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
995 # Drop all capabilities except CAP_NET_RAW and change uid
997 uid = getpwuid(config["general"].as_int("user"))
999 uid = getpwnam(config["general"]["user"])
1001 logging.debug("Setting capabilities and changing uid")
1002 logging.debug("User: %s, uid: %d, gid: %d",
1003 config["general"]["user"], uid.pw_uid, uid.pw_gid)
1005 # Keep only the capabilities we need
1006 # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
1007 capng.capng_clear(capng.CAPNG_SELECT_BOTH)
1008 capng.capng_update(capng.CAPNG_ADD,
1009 capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED,
1010 capng.CAP_NET_ADMIN)
1011 capng.capng_change_id(uid.pw_uid, uid.pw_gid,
1012 capng.CAPNG_DROP_SUPP_GRP|capng.CAPNG_CLEAR_BOUNDING)
1014 logging.info("Ready to serve requests")
1019 exc = "".join(traceback.format_exception(*sys.exc_info()))
1020 logging.critical(exc)
1024 # vim: set ts=4 sts=4 sw=4 et :