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)
199 client = Client(tap=tap, mac=mac, ip=ip,
200 hostname=hostname, indev=indev, subnet=subnet,
201 gateway=gateway, subnet6=subnet6, gateway6=gateway6, eui64=eui64 )
206 class ClientFileHandler(pyinotify.ProcessEvent):
207 def __init__(self, server):
208 pyinotify.ProcessEvent.__init__(self)
211 def process_IN_DELETE(self, event): # pylint: disable=C0103
212 """ Delete file handler
214 Currently this removes an interface from the watch list
217 self.server.remove_tap(event.name)
219 def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
222 Currently this adds an interface to the watch list
225 self.server.add_tap(os.path.join(event.path, event.name))
228 class Client(object):
229 def __init__(self, tap=None, indev=None, mac=None, ip=None, hostname=None,
230 subnet=None, gateway=None, subnet6=None, gateway6=None, eui64=None ):
233 self.hostname = hostname
237 self.gateway = gateway
238 self.net = Subnet(net=subnet, gw=gateway, dev=tap)
239 self.subnet6 = subnet6
240 self.gateway6 = gateway6
241 self.net6 = Subnet(net=subnet6, gw=gateway6, dev=tap)
245 return self.mac is not None and self.ip is not None\
246 and self.hostname is not None
249 class Subnet(object):
250 def __init__(self, net=None, gw=None, dev=None):
251 if isinstance(net, str):
253 self.net = IPy.IP(net)
263 """ Return the netmask in textual representation
266 return str(self.net.netmask())
270 """ Return the broadcast address in textual representation
273 return str(self.net.broadcast())
277 """ Return the network as an IPy.IP
280 return self.net.net()
284 """ Return the prefix length as an integer
287 return self.net.prefixlen()
290 def _make_eui64(net, mac):
291 """ Compute an EUI-64 address from an EUI-48 (MAC) address
296 comp = mac.split(":")
297 prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
298 eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
299 eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
300 for l in range(0, len(eui64), 2):
301 prefix += ["".join(eui64[l:l+2])]
302 return IPy.IP(":".join(prefix))
304 def make_eui64(self, mac):
305 """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
309 return self._make_eui64(self.net, mac)
311 def make_ll64(self, mac):
312 """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
315 return self._make_eui64("fe80::", mac)
318 class VMNetProxy(object): # pylint: disable=R0902
319 def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
320 rs_queue_num=None, ns_queue_num=None,
321 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
322 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
324 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
325 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
328 getattr(nfqueue.payload, 'get_physindev')
329 self.mac_indexed_clients = False
330 except AttributeError:
331 self.mac_indexed_clients = True
332 self.data_path = data_path
333 self.lease_lifetime = dhcp_lease_lifetime
334 self.lease_renewal = dhcp_lease_renewal
335 self.dhcp_domain = dhcp_domain
336 self.dhcp_server_ip = dhcp_server_ip
337 self.ra_period = ra_period
338 if dhcp_nameservers is None:
339 self.dhcp_nameserver = []
341 self.dhcp_nameservers = dhcp_nameservers
343 if ipv6_nameservers is None:
344 self.ipv6_nameservers = []
346 self.ipv6_nameservers = ipv6_nameservers
348 self.ipv6_enabled = False
355 self.l2socket = socket.socket(socket.AF_PACKET,
356 socket.SOCK_RAW, ETH_P_ALL)
357 self.l2socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
360 self.wm = pyinotify.WatchManager()
361 mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
362 mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
363 inotify_handler = ClientFileHandler(self)
364 self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
365 self.wm.add_watch(self.data_path, mask, rec=True)
368 if dhcp_queue_num is not None:
369 self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
371 if rs_queue_num is not None:
372 self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
373 self.ipv6_enabled = True
375 if ns_queue_num is not None:
376 self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
377 self.ipv6_enabled = True
380 """ Free all resources for a graceful exit
383 logging.info("Cleaning up")
385 logging.debug("Closing netfilter queues")
386 for q in self.nfq.values():
389 logging.debug("Closing socket")
390 self.l2socket.close()
392 logging.debug("Stopping inotify watches")
395 logging.info("Cleanup finished")
397 def _setup_nfqueue(self, queue_num, family, callback):
398 logging.debug("Setting up NFQUEUE for queue %d, AF %s",
401 q.set_callback(callback)
402 q.fast_open(queue_num, family)
403 q.set_queue_maxlen(5000)
404 # This is mandatory for the queue to operate
405 q.set_mode(nfqueue.NFQNL_COPY_PACKET)
406 self.nfq[q.get_fd()] = q
408 def sendp(self, data, dev):
409 """ Send a raw packet using a layer-2 socket
412 logging.debug("%s", data)
413 if isinstance(data, BasePacket):
416 self.l2socket.bind((dev, ETH_P_ALL))
417 count = self.l2socket.send(data)
420 logging.warn("Truncated send on %s (%d/%d bytes sent)",
423 def build_config(self):
426 for path in glob.glob(os.path.join(self.data_path, "*")):
429 logging.debug("\n\n\n\n\n")
430 logging.debug("%10s %20s %7s %15s", 'Client', 'MAC', 'TAP', 'IP')
431 for b in self.clients.values():
432 logging.debug("%10s %20s %7s %15s", b.hostname, b.mac, b.tap, b.ip)
434 def get_ifindex(self, iface):
435 """ Get the interface index from sysfs
438 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
439 if not path.startswith(SYSFS_NET):
446 except EnvironmentError:
447 logging.debug("%s is probably down, removing", iface)
448 self.remove_tap(iface)
453 ifindex = f.readline().strip()
455 ifindex = int(ifindex)
456 except ValueError, e:
457 logging.warn("Failed to get ifindex for %s, cannot parse sysfs"
458 " output '%s'", iface, ifindex)
459 except EnvironmentError, e:
460 logging.warn("Error reading %s's ifindex from sysfs: %s",
462 self.remove_tap(iface)
469 def get_iface_hw_addr(self, iface):
470 """ Get the interface hardware address from sysfs
473 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
474 if not path.startswith(SYSFS_NET):
480 except EnvironmentError:
481 logging.debug("%s is probably down, removing", iface)
482 self.remove_tap(iface)
486 addr = f.readline().strip()
487 except EnvironmentError, e:
488 logging.warn("Failed to read hw address for %s from sysfs: %s",
495 def add_tap(self, path):
496 """ Add an interface to monitor
499 tap = os.path.basename(path)
501 logging.debug("Updating configuration for %s", tap)
502 b = parse_binding_file(path)
505 ifindex = self.get_ifindex(b.tap)
508 logging.warn("Stale configuration for %s found", tap)
511 if self.mac_indexed_clients:
512 self.clients[b.mac] = b
514 self.clients[ifindex] = b
515 logging.debug("Added client:")
516 logging.debug("%5s: %10s %20s %7s %15s",
517 ifindex, b.hostname, b.mac, b.tap, b.ip)
519 def remove_tap(self, tap):
520 """ Cleanup clients on a removed interface
524 for k in self.clients.keys():
526 if self.clients[k].tap == tap:
527 logging.debug("Removing client on interface %s", tap)
528 logging.debug("%10s %20s %7s %15s",
529 b.hostname, b.mac, b.tap, b.ip)
532 logging.debug("Client on %s disappeared!!!", tap)
535 def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
536 """ Generate a reply to bnetfilter-queue-deva BOOTP/DHCP request
539 # Decode the response - NFQUEUE relays IP packets
540 pkt = IP(payload.get_data())
541 logging.debug("IN DHCP RESPONCE")
542 #logging.debug(pkt.show())
544 # Get the client MAC address
545 resp = pkt.getlayer(BOOTP).copy()
547 mac = resp.chaddr[:hlen].encode("hex")
548 mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
550 # Server responses are always BOOTREPLYs
551 resp.op = "BOOTREPLY"
554 indev = get_indev(payload)
556 binding = get_binding(self, indev, mac)
558 # We don't know anything about this interface, so accept the packet
560 logging.debug("Ignoring DHCP request on unknown iface %d", indev)
561 # We don't know what to do with this packet, so let the kernel
563 payload.set_verdict(nfqueue.NF_ACCEPT)
567 # Signal the kernel that it shouldn't further process the packet
568 payload.set_verdict(nfqueue.NF_DROP)
570 if mac != binding.mac:
571 logging.warn("Recieved spoofed DHCP request for mac %s from tap %s", mac, indev)
574 resp = Ether(dst=mac, src=self.get_iface_hw_addr(binding.indev))/\
575 IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
576 UDP(sport=pkt.dport, dport=pkt.sport)/resp
580 logging.warn("Invalid request from %s on %s, no DHCP"
581 " payload found", binding.mac, binding.tap)
585 requested_addr = binding.ip
586 for opt in pkt[DHCP].options:
587 if type(opt) is tuple and opt[0] == "message-type":
589 if type(opt) is tuple and opt[0] == "requested_addr":
590 requested_addr = opt[1]
592 logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
593 binding.mac, binding.tap)
596 domainname = self.dhcp_domain
598 domainname = binding.hostname.split('.', 1)[-1]
600 if req_type == DHCPREQUEST and requested_addr != binding.ip:
602 logging.info("Sending DHCPNAK to %s on %s: requested %s"
603 " instead of %s", binding.mac, binding.tap,
604 requested_addr, binding.ip)
606 elif req_type in (DHCPDISCOVER, DHCPREQUEST):
607 resp_type = DHCP_REQRESP[req_type]
608 resp.yiaddr = binding.ip
610 ("hostname", binding.hostname),
611 ("domain", domainname),
612 ("broadcast_address", str(subnet.broadcast)),
613 ("subnet_mask", str(subnet.netmask)),
614 ("renewal_time", self.lease_renewal),
615 ("lease_time", self.lease_lifetime),
618 dhcp_options += [("router", subnet.gw)]
619 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
621 elif req_type == DHCPINFORM:
622 resp_type = DHCP_REQRESP[req_type]
624 ("hostname", binding.hostname),
625 ("domain", domainname),
627 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
629 elif req_type == DHCPRELEASE:
631 logging.info("DHCPRELEASE from %s on %s", binding.mac, binding.tap )
634 # Finally, always add the server identifier and end options
636 ("message-type", resp_type),
637 ("server_id", DHCP_DUMMY_SERVER_IP),
640 resp /= DHCP(options=dhcp_options)
642 logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
643 binding.ip, binding.tap)
645 self.sendp(resp, binding.indev)
646 except socket.error, e:
647 logging.warn("DHCP response on %s failed: %s", binding.indev, str(e))
649 logging.warn("Unkown error during DHCP response on %s: %s",
650 binding.indev, str(e))
652 def rs_response(self, i, payload): # pylint: disable=W0613
653 """ Generate a reply to a BOOTP/DHCP request
656 pkt = IPv6(payload.get_data())
657 logging.debug("IN RS RESPONCE")
658 #logging.debug(pkt.show())
662 logging.debug("Cannot obtain lladdr in rs")
665 logging.debug("rs for mac %s", mac)
667 indev = get_indev(payload)
669 binding = get_binding(self, indev, mac)
671 # We don't know anything about this interface, so accept the packet
673 logging.debug("Ignoring router solicitation on for mac %s", mac)
674 # We don't know what to do with this packet, so let the kernel
676 payload.set_verdict(nfqueue.NF_ACCEPT)
679 # Signal the kernel that it shouldn't further process the packet
680 payload.set_verdict(nfqueue.NF_DROP)
682 if mac != binding.mac:
683 logging.warn("Recieved spoofed RS request for mac %s from tap %s", mac, tap)
686 subnet = binding.net6
688 if subnet.net is None:
689 logging.debug("No IPv6 network assigned for the interface")
692 indevmac = self.get_iface_hw_addr(binding.indev)
693 ifll = subnet.make_ll64(indevmac)
698 resp = Ether(src=indevmac)/\
699 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
700 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
701 prefixlen=subnet.prefixlen)
703 if self.ipv6_nameservers:
704 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
705 lifetime=self.ra_period * 3)
707 logging.info("RA on %s for %s", binding.indev, subnet.net)
709 self.sendp(resp, binding.indev)
710 except socket.error, e:
711 logging.warn("RA on %s failed: %s", binding.indev, str(e))
713 logging.warn("Unkown error during RA on %s: %s",
714 binding.indev, str(e))
716 def ns_response(self, i, payload): # pylint: disable=W0613
717 """ Generate a reply to an ICMPv6 neighbor solicitation
720 ns = IPv6(payload.get_data())
721 logging.debug("IN NS RESPONCE")
722 #logging.debug(ns.show())
726 logging.debug("Cannot obtain lladdr from ns")
729 logging.debug("dst %s tgt %s" , ns.dst, ns.tgt)
731 indev = get_indev(payload)
733 binding = get_binding(self, indev, mac)
735 # We don't know anything about this interface, so accept the packet
737 logging.debug("Ignoring neighbour solicitation for eui64 %s", ns.tgt)
738 # We don't know what to do with this packet, so let the kernel
740 payload.set_verdict(nfqueue.NF_ACCEPT)
743 payload.set_verdict(nfqueue.NF_DROP)
745 if mac != binding.mac:
746 logging.warn("Recieved spoofed NS request for mac %s from tap %s", mac, tap)
749 subnet = binding.net6
750 if subnet.net is None:
751 logging.debug("No IPv6 network assigned for the interface")
754 indevmac = self.get_iface_hw_addr(binding.indev)
756 ifll = subnet.make_ll64(indevmac)
760 if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
761 logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
762 payload.set_verdict(nfqueue.NF_ACCEPT)
765 logging.debug("na ether %s %s", binding.mac, ns.src)
766 resp = Ether(src=indevmac, dst=binding.mac)/\
767 IPv6(src=str(ifll), dst=ns.src)/\
768 ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
769 ICMPv6NDOptDstLLAddr(lladdr=indevmac)
771 logging.info("NA on %s for %s", binding.indev, ns.tgt)
773 self.sendp(resp, binding.indev)
774 except socket.error, e:
775 logging.warn("NA on %s failed: %s", binding.indev, str(e))
777 logging.warn("Unkown error during periodic NA on %s: %s",
778 binding.indev, str(e))
780 def send_periodic_ra(self):
781 # Use a separate thread as this may take a _long_ time with
782 # many interfaces and we want to be responsive in the mean time
783 threading.Thread(target=self._send_periodic_ra).start()
785 def _send_periodic_ra(self):
786 logging.debug("Sending out periodic RAs")
789 for binding in self.clients.values():
791 indev = binding.indev
793 subnet = binding.net6
794 if subnet.net is None:
795 logging.debug("Skipping periodic RA on interface %s,"
796 " as it is not IPv6-connected", tap)
798 indevmac = self.get_iface_hw_addr(indev)
799 ifll = subnet.make_ll64(indevmac)
802 resp = Ether(src=indevmac)/\
803 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
804 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
805 prefixlen=subnet.prefixlen)
806 if self.ipv6_nameservers:
807 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
808 lifetime=self.ra_period * 3)
810 self.sendp(resp, indev)
811 except socket.error, e:
812 logging.warn("Periodic RA on %s failed: %s", tap, str(e))
814 logging.warn("Unkown error during periodic RA on %s: %s",
817 logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
820 """ Safely perform the main loop, freeing all resources upon exit
829 """ Loop forever, serving DHCP requests
834 # Yes, we are accessing _fd directly, but it's the only way to have a
835 # single select() loop ;-)
836 iwfd = self.notifier._fd # pylint: disable=W0212
839 if self.ipv6_enabled:
840 timeout = self.ra_period
841 self.send_periodic_ra()
846 rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
848 logging.warn("Warning: Exception on %s",
849 ", ".join([ str(fd) for fd in xlist]))
853 # First check if there are any inotify (= configuration change)
855 self.notifier.read_events()
856 self.notifier.process_events()
861 self.nfq[fd].process_pending()
862 except RuntimeError, e:
863 logging.warn("Error processing fd %d: %s", fd, str(e))
865 logging.warn("Unknown error processing fd %d: %s",
868 if self.ipv6_enabled:
869 # Calculate the new timeout
870 timeout = self.ra_period - (time.time() - start)
874 self.send_periodic_ra()
875 timeout = self.ra_period - (time.time() - start)
878 if __name__ == "__main__":
881 from cStringIO import StringIO
882 from pwd import getpwnam, getpwuid
883 from configobj import ConfigObj, ConfigObjError, flatten_errors
887 validator = validate.Validator()
889 def is_ip_list(value, family=4):
893 raise validate.VdtParamError(family)
894 if isinstance(value, (str, unicode)):
896 if not isinstance(value, list):
897 raise validate.VdtTypeError(value)
903 raise validate.VdtValueError(entry)
905 if ip.version() != family:
906 raise validate.VdtValueError(entry)
909 validator.functions["ip_addr_list"] = is_ip_list
910 config_spec = StringIO(CONFIG_SPEC)
913 parser = optparse.OptionParser()
914 parser.add_option("-c", "--config", dest="config_file",
915 help="The location of the data files", metavar="FILE",
916 default=DEFAULT_CONFIG)
917 parser.add_option("-d", "--debug", action="store_true", dest="debug",
918 help="Turn on debugging messages")
919 parser.add_option("-f", "--foreground", action="store_false",
920 dest="daemonize", default=True,
921 help="Do not daemonize, stay in the foreground")
924 opts, args = parser.parse_args()
927 config = ConfigObj(opts.config_file, configspec=config_spec)
928 except ConfigObjError, err:
929 sys.stderr.write("Failed to parse config file %s: %s" %
930 (opts.config_file, str(err)))
933 results = config.validate(validator)
935 logging.fatal("Configuration file validation failed! See errors below:")
936 for (section_list, key, unused) in flatten_errors(config, results):
938 logging.fatal(" '%s' in section '%s' failed validation",
939 key, ", ".join(section_list))
941 logging.fatal(" Section '%s' is missing",
942 ", ".join(section_list))
945 logger = logging.getLogger()
947 logger.setLevel(logging.DEBUG)
949 logger.setLevel(logging.INFO)
952 logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
953 handler = logging.handlers.RotatingFileHandler(logfile,
956 handler = logging.StreamHandler()
958 handler.setFormatter(logging.Formatter(LOG_FORMAT))
959 logger.addHandler(handler)
962 pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
963 config["general"]["pidfile"], 10)
964 # Remove any stale PID files, left behind by previous invocations
965 if daemon.runner.is_pidfile_stale(pidfile):
966 logger.warning("Removing stale PID lock file %s", pidfile.path)
970 d = daemon.DaemonContext(pidfile=pidfile,
971 stdout=handler.stream,
972 stderr=handler.stream,
973 files_preserve=[handler.stream])
977 logging.info("Starting up")
980 if config["dhcp"].as_bool("enable_dhcp"):
982 "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
983 "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
984 "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
985 "dhcp_server_ip": config["dhcp"]["server_ip"],
986 "dhcp_nameservers": config["dhcp"]["nameservers"],
987 "dhcp_domain": config["dhcp"]["domain"],
990 if config["ipv6"].as_bool("enable_ipv6"):
992 "rs_queue_num": config["ipv6"].as_int("rs_queue"),
993 "ns_queue_num": config["ipv6"].as_int("ns_queue"),
994 "ra_period": config["ipv6"].as_int("ra_period"),
995 "ipv6_nameservers": config["ipv6"]["nameservers"],
998 # pylint: disable=W0142
999 proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
1001 # Drop all capabilities except CAP_NET_RAW and change uid
1003 uid = getpwuid(config["general"].as_int("user"))
1005 uid = getpwnam(config["general"]["user"])
1007 logging.debug("Setting capabilities and changing uid")
1008 logging.debug("User: %s, uid: %d, gid: %d",
1009 config["general"]["user"], uid.pw_uid, uid.pw_gid)
1011 # Keep only the capabilities we need
1012 # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
1013 capng.capng_clear(capng.CAPNG_SELECT_BOTH)
1014 capng.capng_update(capng.CAPNG_ADD,
1015 capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED,
1016 capng.CAP_NET_ADMIN)
1017 capng.capng_change_id(uid.pw_uid, uid.pw_gid,
1018 capng.CAPNG_DROP_SUPP_GRP|capng.CAPNG_CLEAR_BOUNDING)
1020 logging.info("Ready to serve requests")
1025 exc = "".join(traceback.format_exception(*sys.exc_info()))
1026 logging.critical(exc)
1030 # vim: set ts=4 sts=4 sw=4 et :