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
34 import daemon.pidlockfile
40 from select import select
41 from socket import AF_INET, AF_INET6
43 from scapy.data import ETH_P_ALL
44 from scapy.packet import BasePacket
45 from scapy.layers.l2 import Ether
46 from scapy.layers.inet import IP, UDP
47 from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \
48 ICMPv6NDOptDstLLAddr, \
49 ICMPv6NDOptPrefixInfo, \
51 from scapy.layers.dhcp import BOOTP, DHCP
53 DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf"
54 DEFAULT_PATH = "/var/run/ganeti-dhcpd"
55 DEFAULT_USER = "nobody"
56 DEFAULT_LEASE_LIFETIME = 604800 # 1 week
57 DEFAULT_LEASE_RENEWAL = 600 # 10 min
58 DEFAULT_RA_PERIOD = 300 # seconds
59 DHCP_DUMMY_SERVER_IP = "1.2.3.4"
61 LOG_FILENAME = "nfdhcpd.log"
63 SYSFS_NET = "/sys/class/net"
65 LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s"
67 # Configuration file specification (see configobj documentation)
76 enable_dhcp = boolean(default=True)
77 lease_lifetime = integer(min=0, max=4294967295)
78 lease_renewal = integer(min=0, max=4294967295)
80 dhcp_queue = integer(min=0, max=65535)
81 nameservers = ip_addr_list(family=4)
84 enable_ipv6 = boolean(default=True)
85 ra_period = integer(min=1, max=4294967295)
86 rs_queue = integer(min=0, max=65535)
87 ns_queue = integer(min=0, max=65535)
88 nameservers = ip_addr_list(family=6)
102 DHCPDISCOVER: "DHCPDISCOVER",
103 DHCPOFFER: "DHCPOFFER",
104 DHCPREQUEST: "DHCPREQUEST",
105 DHCPDECLINE: "DHCPDECLINE",
108 DHCPRELEASE: "DHCPRELEASE",
109 DHCPINFORM: "DHCPINFORM",
113 DHCPDISCOVER: DHCPOFFER,
114 DHCPREQUEST: DHCPACK,
119 def parse_routing_table(table="main", family=4):
120 """ Parse the given routing table to get connected route, gateway and
124 ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls",
125 "table", table], stdout=subprocess.PIPE)
126 routes = ipro.stdout.readlines()
133 # Find the least-specific connected route
134 m = re.match("^([\S]+/[\S]+) dev ([\S]+)", route)
138 if family == 6 and m.group(1).startswith("fe80:"):
139 # Skip link-local declarations in "main" table
142 def_net, def_dev = m.groups()
145 def_net = IPy.IP(def_net)
146 except ValueError, e:
147 logging.warn("Unable to parse default route entry %s: %s",
151 match = re.match(r'^default.*via ([\S]+).*dev ([\S]+)', route)
153 def_gw, def_dev = match.groups()
156 return Subnet(net=def_net, gw=def_gw, dev=def_dev)
159 def parse_binding_file(path):
160 """ Read a client configuration from a tap file
164 iffile = open(path, 'r')
165 except EnvironmentError, e:
166 logging.warn("Unable to open binding file %s: %s", path, str(e))
169 ifname = os.path.basename(path)
176 if line.startswith("IP="):
177 ip = line.strip().split("=")[1]
179 elif line.startswith("MAC="):
180 mac = line.strip().split("=")[1]
181 elif line.startswith("LINK="):
182 link = line.strip().split("=")[1]
183 elif line.startswith("HOSTNAME="):
184 hostname = line.strip().split("=")[1]
185 elif line.startswith("IFACE="):
186 iface = line.strip().split("=")[1]
188 return Client(ifname=ifname, mac=mac, ips=ips, link=link, hostname=hostname, iface=iface)
191 class ClientFileHandler(pyinotify.ProcessEvent):
192 def __init__(self, server):
193 pyinotify.ProcessEvent.__init__(self)
196 def process_IN_DELETE(self, event): # pylint: disable=C0103
197 """ Delete file handler
199 Currently this removes an interface from the watch list
202 self.server.remove_iface(event.name)
204 def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
207 Currently this adds an interface to the watch list
210 self.server.add_iface(os.path.join(event.path, event.name))
213 class Client(object):
214 def __init__(self, ifname=None, mac=None, ips=None, link=None, hostname=None, iface=None):
217 self.hostname = hostname
227 return self.mac is not None and self.ips is not None\
228 and self.hostname is not None
231 class Subnet(object):
232 def __init__(self, net=None, gw=None, dev=None):
233 if isinstance(net, str):
234 self.net = IPy.IP(net)
242 """ Return the netmask in textual representation
245 return str(self.net.netmask())
249 """ Return the broadcast address in textual representation
252 return str(self.net.broadcast())
256 """ Return the network as an IPy.IP
259 return self.net.net()
263 """ Return the prefix length as an integer
266 return self.net.prefixlen()
269 def _make_eui64(net, mac):
270 """ Compute an EUI-64 address from an EUI-48 (MAC) address
273 comp = mac.split(":")
274 prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
275 eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
276 eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
277 for l in range(0, len(eui64), 2):
278 prefix += ["".join(eui64[l:l+2])]
279 return IPy.IP(":".join(prefix))
281 def make_eui64(self, mac):
282 """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
286 return self._make_eui64(self.net, mac)
288 def make_ll64(self, mac):
289 """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
292 return self._make_eui64("fe80::", mac)
295 class VMNetProxy(object): # pylint: disable=R0902
296 def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
297 rs_queue_num=None, ns_queue_num=None,
298 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
299 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
300 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
301 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
303 self.data_path = data_path
304 self.lease_lifetime = dhcp_lease_lifetime
305 self.lease_renewal = dhcp_lease_renewal
306 self.dhcp_server_ip = dhcp_server_ip
307 self.ra_period = ra_period
308 if dhcp_nameservers is None:
309 self.dhcp_nameserver = []
311 self.dhcp_nameservers = dhcp_nameservers
313 if ipv6_nameservers is None:
314 self.ipv6_nameservers = []
316 self.ipv6_nameservers = ipv6_nameservers
318 self.ipv6_enabled = False
325 self.l2socket = socket.socket(socket.AF_PACKET,
326 socket.SOCK_RAW, ETH_P_ALL)
327 self.l2socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
330 self.wm = pyinotify.WatchManager()
331 mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
332 mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
333 inotify_handler = ClientFileHandler(self)
334 self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
335 self.wm.add_watch(self.data_path, mask, rec=True)
338 if dhcp_queue_num is not None:
339 self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
341 if rs_queue_num is not None:
342 self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
343 self.ipv6_enabled = True
345 if ns_queue_num is not None:
346 self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
347 self.ipv6_enabled = True
350 """ Free all resources for a graceful exit
353 logging.info("Cleaning up")
355 logging.debug("Closing netfilter queues")
356 for q in self.nfq.values():
359 logging.debug("Closing socket")
360 self.l2socket.close()
362 logging.debug("Stopping inotify watches")
365 logging.info("Cleanup finished")
367 def _setup_nfqueue(self, queue_num, family, callback):
368 logging.debug("Setting up NFQUEUE for queue %d, AF %s",
371 q.set_callback(callback)
372 q.fast_open(queue_num, family)
373 q.set_queue_maxlen(5000)
374 # This is mandatory for the queue to operate
375 q.set_mode(nfqueue.NFQNL_COPY_PACKET)
376 self.nfq[q.get_fd()] = q
378 def sendp(self, data, iface):
379 """ Send a raw packet using a layer-2 socket
382 logging.debug("%s", data)
383 if isinstance(data, BasePacket):
386 self.l2socket.bind((iface, ETH_P_ALL))
387 count = self.l2socket.send(data)
390 logging.warn("Truncated send on %s (%d/%d bytes sent)",
393 def build_config(self):
397 for path in glob.glob(os.path.join(self.data_path, "*")):
400 def get_ifindex(self, iface):
401 """ Get the interface index from sysfs
404 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
405 if not path.startswith(SYSFS_NET):
412 except EnvironmentError:
413 logging.debug("%s is probably down, removing", iface)
414 self.remove_iface(iface)
419 ifindex = f.readline().strip()
421 ifindex = int(ifindex)
422 except ValueError, e:
423 logging.warn("Failed to get ifindex for %s, cannot parse sysfs"
424 " output '%s'", iface, ifindex)
425 except EnvironmentError, e:
426 logging.warn("Error reading %s's ifindex from sysfs: %s",
428 self.remove_iface(iface)
435 def get_iface_hw_addr(self, iface):
436 """ Get the interface hardware address from sysfs
439 path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
440 if not path.startswith(SYSFS_NET):
446 except EnvironmentError:
447 logging.debug("%s is probably down, removing", iface)
448 self.remove_iface(iface)
452 addr = f.readline().strip()
453 except EnvironmentError, e:
454 logging.warn("Failed to read hw address for %s from sysfs: %s",
461 def add_iface(self, path):
462 """ Add an interface to monitor
465 iface = os.path.basename(path)
467 logging.debug("Updating configuration for %s", iface)
468 binding = parse_binding_file(path)
471 ifindex = self.get_ifindex(binding.iface)
474 logging.warn("Stale configuration for %s found", iface)
476 if binding.is_valid():
477 self.clients[binding.mac] = binding
478 self.subnets[binding.link] = parse_routing_table(binding.link)
479 logging.debug("Added client %s on %s", binding.hostname, iface)
480 self.ifaces[ifindex] = binding.iface
481 self.v6nets[iface] = parse_routing_table(binding.link, 6)
483 def remove_iface(self, ifname):
484 """ Cleanup clients on a removed interface
487 if ifname in self.v6nets:
488 del self.v6nets[ifname]
490 for mac in self.clients.keys():
491 if self.clients[mac].ifname == ifname:
492 iface = self.client[mac].iface
493 del self.clients[mac]
495 for ifindex in self.ifaces.keys():
496 if self.ifaces[ifindex] == ifname == iface:
497 del self.ifaces[ifindex]
499 logging.debug("Removed interface %s", ifname)
501 def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
502 """ Generate a reply to a BOOTP/DHCP request
505 logging.info("%s",payload)
506 indev = payload.get_indev()
508 # Get the actual interface from the ifindex
509 iface = self.ifaces[indev]
511 # We don't know anything about this interface, so accept the packet
513 logging.debug("Ignoring DHCP request on unknown iface %d", indev)
514 # We don't know what to do with this packet, so let the kernel
516 payload.set_verdict(nfqueue.NF_ACCEPT)
519 # Decode the response - NFQUEUE relays IP packets
520 pkt = IP(payload.get_data())
522 # Signal the kernel that it shouldn't further process the packet
523 payload.set_verdict(nfqueue.NF_DROP)
525 # Get the client MAC address
526 resp = pkt.getlayer(BOOTP).copy()
528 mac = resp.chaddr[:hlen].encode("hex")
529 mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
531 # Server responses are always BOOTREPLYs
532 resp.op = "BOOTREPLY"
536 binding = self.clients[mac]
538 logging.warn("Invalid client %s on %s", mac, iface)
541 if iface != binding.iface:
542 logging.warn("Received spoofed DHCP request for %s from interface"
543 " %s instead of %s", mac, iface, binding.iface)
546 resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\
547 IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
548 UDP(sport=pkt.dport, dport=pkt.sport)/resp
549 subnet = self.subnets[binding.link]
552 logging.warn("Invalid request from %s on %s, no DHCP"
553 " payload found", binding.mac, iface)
557 requested_addr = binding.ip
558 for opt in pkt[DHCP].options:
559 if type(opt) is tuple and opt[0] == "message-type":
561 if type(opt) is tuple and opt[0] == "requested_addr":
562 requested_addr = opt[1]
564 logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
567 if req_type == DHCPREQUEST and requested_addr != binding.ip:
569 logging.info("Sending DHCPNAK to %s on %s: requested %s"
570 " instead of %s", binding.mac, iface, requested_addr,
573 elif req_type in (DHCPDISCOVER, DHCPREQUEST):
574 resp_type = DHCP_REQRESP[req_type]
575 resp.yiaddr = self.clients[mac].ip
577 ("hostname", binding.hostname),
578 ("domain", binding.hostname.split('.', 1)[-1]),
579 ("broadcast_address", str(subnet.broadcast)),
580 ("subnet_mask", str(subnet.netmask)),
581 ("renewal_time", self.lease_renewal),
582 ("lease_time", self.lease_lifetime),
585 dhcp_options += [("router", subnet.gw)]
586 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
588 elif req_type == DHCPINFORM:
589 resp_type = DHCP_REQRESP[req_type]
591 ("hostname", binding.hostname),
592 ("domain", binding.hostname.split('.', 1)[-1]),
594 dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
596 elif req_type == DHCPRELEASE:
598 logging.info("DHCPRELEASE from %s on %s", binding.mac, iface)
601 # Finally, always add the server identifier and end options
603 ("message-type", resp_type),
604 ("server_id", DHCP_DUMMY_SERVER_IP),
607 resp /= DHCP(options=dhcp_options)
609 logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
611 self.sendp(resp, iface)
613 def rs_response(self, i, payload): # pylint: disable=W0613
614 """ Generate a reply to a BOOTP/DHCP request
617 indev = payload.get_indev()
619 # Get the actual interface from the ifindex
620 iface = self.ifaces[indev]
622 logging.debug("Ignoring router solicitation on"
623 " unknown interface %d", indev)
624 # We don't know what to do with this packet, so let the kernel
626 payload.set_verdict(nfqueue.NF_ACCEPT)
629 ifmac = self.get_iface_hw_addr(iface)
630 subnet = self.v6nets[iface]
631 ifll = subnet.make_ll64(ifmac)
633 # Signal the kernel that it shouldn't further process the packet
634 payload.set_verdict(nfqueue.NF_DROP)
636 resp = Ether(src=self.get_iface_hw_addr(iface))/\
637 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
638 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
639 prefixlen=subnet.prefixlen)
641 if self.ipv6_nameservers:
642 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
643 lifetime=self.ra_period * 3)
645 logging.info("RA on %s for %s", iface, subnet.net)
646 self.sendp(resp, iface)
648 def ns_response(self, i, payload): # pylint: disable=W0613
649 """ Generate a reply to an ICMPv6 neighbor solicitation
652 indev = payload.get_indev()
654 # Get the actual interface from the ifindex
655 iface = self.ifaces[indev]
657 logging.debug("Ignoring neighbour solicitation on"
658 " unknown interface %d", indev)
659 # We don't know what to do with this packet, so let the kernel
661 payload.set_verdict(nfqueue.NF_ACCEPT)
664 ifmac = self.get_iface_hw_addr(iface)
665 subnet = self.v6nets[iface]
666 ifll = subnet.make_ll64(ifmac)
668 ns = IPv6(payload.get_data())
670 if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
671 logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
672 payload.set_verdict(nfqueue.NF_ACCEPT)
675 payload.set_verdict(nfqueue.NF_DROP)
678 client_lladdr = ns.lladdr
679 except AttributeError:
682 resp = Ether(src=ifmac, dst=client_lladdr)/\
683 IPv6(src=str(ifll), dst=ns.src)/\
684 ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
685 ICMPv6NDOptDstLLAddr(lladdr=ifmac)
687 logging.info("NA on %s for %s", iface, ns.tgt)
688 self.sendp(resp, iface)
691 def send_periodic_ra(self):
692 # Use a separate thread as this may take a _long_ time with
693 # many interfaces and we want to be responsive in the mean time
694 threading.Thread(target=self._send_periodic_ra).start()
696 def _send_periodic_ra(self):
697 logging.debug("Sending out periodic RAs")
700 for client in self.clients.values():
702 ifmac = self.get_iface_hw_addr(iface)
706 subnet = self.v6nets[iface]
707 if subnet.net is None:
708 logging.debug("Skipping periodic RA on interface %s,"
709 " as it is not IPv6-connected", iface)
712 ifll = subnet.make_ll64(ifmac)
713 resp = Ether(src=ifmac)/\
714 IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
715 ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
716 prefixlen=subnet.prefixlen)
717 if self.ipv6_nameservers:
718 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
719 lifetime=self.ra_period * 3)
721 self.sendp(resp, iface)
722 except socket.error, e:
723 logging.warn("Periodic RA on %s failed: %s", iface, str(e))
725 logging.warn("Unkown error during periodic RA on %s: %s",
728 logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
731 """ Safely perform the main loop, freeing all resources upon exit
740 """ Loop forever, serving DHCP requests
745 # Yes, we are accessing _fd directly, but it's the only way to have a
746 # single select() loop ;-)
747 iwfd = self.notifier._fd # pylint: disable=W0212
750 if self.ipv6_enabled:
751 timeout = self.ra_period
752 self.send_periodic_ra()
757 rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
759 logging.warn("Warning: Exception on %s",
760 ", ".join([ str(fd) for fd in xlist]))
764 # First check if there are any inotify (= configuration change)
766 self.notifier.read_events()
767 self.notifier.process_events()
772 self.nfq[fd].process_pending()
773 except RuntimeError, e:
774 logging.warn("Error processing fd %d: %s", fd, str(e))
776 logging.warn("Unknown error processing fd %d: %s",
779 if self.ipv6_enabled:
780 # Calculate the new timeout
781 timeout = self.ra_period - (time.time() - start)
785 self.send_periodic_ra()
786 timeout = self.ra_period - (time.time() - start)
789 if __name__ == "__main__":
792 from cStringIO import StringIO
793 from pwd import getpwnam, getpwuid
794 from configobj import ConfigObj, ConfigObjError, flatten_errors
798 validator = validate.Validator()
800 def is_ip_list(value, family=4):
804 raise validate.VdtParamError(family)
805 if isinstance(value, (str, unicode)):
807 if not isinstance(value, list):
808 raise validate.VdtTypeError(value)
814 raise validate.VdtValueError(entry)
816 if ip.version() != family:
817 raise validate.VdtValueError(entry)
820 validator.functions["ip_addr_list"] = is_ip_list
821 config_spec = StringIO(CONFIG_SPEC)
824 parser = optparse.OptionParser()
825 parser.add_option("-c", "--config", dest="config_file",
826 help="The location of the data files", metavar="FILE",
827 default=DEFAULT_CONFIG)
828 parser.add_option("-d", "--debug", action="store_true", dest="debug",
829 help="Turn on debugging messages")
830 parser.add_option("-f", "--foreground", action="store_false",
831 dest="daemonize", default=True,
832 help="Do not daemonize, stay in the foreground")
835 opts, args = parser.parse_args()
838 config = ConfigObj(opts.config_file, configspec=config_spec)
839 except ConfigObjError, err:
840 sys.stderr.write("Failed to parse config file %s: %s" %
841 (opts.config_file, str(err)))
844 results = config.validate(validator)
846 logging.fatal("Configuration file validation failed! See errors below:")
847 for (section_list, key, unused) in flatten_errors(config, results):
849 logging.fatal(" '%s' in section '%s' failed validation",
850 key, ", ".join(section_list))
852 logging.fatal(" Section '%s' is missing",
853 ", ".join(section_list))
856 logger = logging.getLogger()
858 logger.setLevel(logging.DEBUG)
860 logger.setLevel(logging.INFO)
863 logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
864 handler = logging.handlers.RotatingFileHandler(logfile,
867 handler = logging.StreamHandler()
869 handler.setFormatter(logging.Formatter(LOG_FORMAT))
870 logger.addHandler(handler)
873 pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
874 config["general"]["pidfile"], 10)
876 d = daemon.DaemonContext(pidfile=pidfile,
877 stdout=handler.stream,
878 stderr=handler.stream,
879 files_preserve=[handler.stream])
883 logging.info("Starting up")
886 if config["dhcp"].as_bool("enable_dhcp"):
888 "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
889 "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
890 "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
891 "dhcp_server_ip": config["dhcp"]["server_ip"],
892 "dhcp_nameservers": config["dhcp"]["nameservers"],
895 if config["ipv6"].as_bool("enable_ipv6"):
897 "rs_queue_num": config["ipv6"].as_int("rs_queue"),
898 "ns_queue_num": config["ipv6"].as_int("ns_queue"),
899 "ra_period": config["ipv6"].as_int("ra_period"),
900 "ipv6_nameservers": config["ipv6"]["nameservers"],
903 # pylint: disable=W0142
904 proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
906 # Drop all capabilities except CAP_NET_RAW and change uid
908 uid = getpwuid(config["general"].as_int("user"))
910 uid = getpwnam(config["general"]["user"])
912 logging.debug("Setting capabilities and changing uid")
913 logging.debug("User: %s, uid: %d, gid: %d",
914 config["general"]["user"], uid.pw_uid, uid.pw_gid)
916 # Keep only the capabilities we need
917 # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
918 capng.capng_clear(capng.CAPNG_SELECT_BOTH)
919 capng.capng_update(capng.CAPNG_ADD,
920 capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED,
922 capng.capng_change_id(uid.pw_uid, uid.pw_gid,
923 capng.CAPNG_DROP_SUPP_GRP|capng.CAPNG_CLEAR_BOUNDING)
925 logging.info("Ready to serve requests")
930 exc = "".join(traceback.format_exception(*sys.exc_info()))
931 logging.critical(exc)
935 # vim: set ts=4 sts=4 sw=4 et :