DHCP: use nameservers from config
[snf-nfdhcpd] / nfdhcpd
1 #!/usr/bin/env python
2 #
3
4 # nfdcpd: A promiscuous, NFQUEUE-based DHCP server for virtual machine hosting
5 # Copyright (c) 2010 GRNET SA
6 #
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.
11 #
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.
16 #
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.
20 #
21
22 import os
23 import re
24 import glob
25 import time
26 import logging
27 import logging.handlers
28 import threading
29 import subprocess
30
31 import daemon
32 import nfqueue
33 import pyinotify
34
35 import IPy
36 from select import select
37 from socket import AF_INET, AF_INET6
38
39 from scapy.layers.l2 import Ether
40 from scapy.layers.inet import IP, UDP
41 from scapy.layers.inet6 import *
42 from scapy.layers.dhcp import BOOTP, DHCP
43 from scapy.sendrecv import sendp
44
45 DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf"
46 DEFAULT_PATH = "/var/run/ganeti-dhcpd"
47 DEFAULT_USER = "nobody"
48 DEFAULT_LEASE_LIFETIME = 604800 # 1 week
49 DEFAULT_LEASE_RENEWAL = 600  # 10 min
50 DEFAULT_RA_PERIOD = 300 # seconds
51 DHCP_DUMMY_SERVER_IP = "1.2.3.4"
52
53 LOG_FILENAME = "nfdhcpd.log"
54
55 SYSFS_NET = "/sys/class/net"
56
57 LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s"
58
59 # Configuration file specification (see configobj documentation)
60 CONFIG_SPEC = """
61 [general]
62 pidfile = string()
63 datapath = string()
64 logdir = string()
65 user = string()
66
67 [dhcp]
68 enable_dhcp = boolean(default=True)
69 lease_lifetime = integer(min=0, max=4294967295)
70 lease_renewal = integer(min=0, max=4294967295)
71 server_ip = ip_addr()
72 dhcp_queue = integer(min=0, max=65535)
73 nameservers = ip_addr_list(family=4)
74
75 [ipv6]
76 enable_ipv6 = boolean(default=True)
77 ra_period = integer(min=1, max=4294967295)
78 rs_queue = integer(min=0, max=65535)
79 ns_queue = integer(min=0, max=65535)
80 nameservers = ip_addr_list(family=6)
81 """
82
83
84 DHCPDISCOVER = 1
85 DHCPOFFER = 2
86 DHCPREQUEST = 3
87 DHCPDECLINE = 4
88 DHCPACK = 5
89 DHCPNAK = 6
90 DHCPRELEASE = 7
91 DHCPINFORM = 8
92
93 DHCP_TYPES = {
94     DHCPDISCOVER: "DHCPDISCOVER",
95     DHCPOFFER: "DHCPOFFER",
96     DHCPREQUEST: "DHCPREQUEST",
97     DHCPDECLINE: "DHCPDECLINE",
98     DHCPACK: "DHCPACK",
99     DHCPNAK: "DHCPNAK",
100     DHCPRELEASE: "DHCPRELEASE",
101     DHCPINFORM: "DHCPINFORM",
102 }
103
104 DHCP_REQRESP = {
105     DHCPDISCOVER: DHCPOFFER,
106     DHCPREQUEST: DHCPACK,
107     DHCPINFORM: DHCPACK,
108     }
109
110
111 class ClientFileHandler(pyinotify.ProcessEvent):
112     def __init__(self, server):
113         pyinotify.ProcessEvent.__init__(self)
114         self.server = server
115
116     def process_IN_DELETE(self, event):
117         self.server.remove_iface(event.name)
118
119     def process_IN_CLOSE_WRITE(self, event):
120         self.server.add_iface(os.path.join(event.path, event.name))
121
122
123 class Client(object):
124     def __init__(self, mac=None, ips=None, link=None, hostname=None):
125         self.mac = mac
126         self.ips = ips
127         self.hostname = hostname
128         self.link = link
129         self.iface = None
130
131     @property
132     def ip(self):
133         return self.ips[0]
134
135     def is_valid(self):
136         return self.mac is not None and self.ips is not None\
137                and self.hostname is not None
138
139
140 class Subnet(object):
141     def __init__(self, net=None, gw=None, dev=None):
142         if isinstance(net, str):
143             self.net = IPy.IP(net)
144         else:
145             self.net = net
146         self.gw = gw
147         self.dev = dev
148
149     @property
150     def netmask(self):
151         return str(self.net.netmask())
152
153     @property
154     def broadcast(self):
155         return str(self.net.broadcast())
156
157     @property
158     def prefix(self):
159         return self.net.net()
160
161     @property
162     def prefixlen(self):
163         return self.net.prefixlen()
164
165     @staticmethod
166     def _make_eui64(net, mac):
167         """ Compute an EUI-64 address from an EUI-48 (MAC) address
168
169         """
170         comp = mac.split(":")
171         prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
172         eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
173         eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
174         for l in range(0, len(eui64), 2):
175             prefix += ["".join(eui64[l:l+2])]
176         return IPy.IP(":".join(prefix))
177
178     def make_eui64(self, mac):
179         return self._make_eui64(self.net, mac)
180
181     def make_ll64(self, mac):
182         return self._make_eui64("fe80::", mac)
183
184
185 class VMNetProxy(object):
186     def __init__(self, data_path, dhcp_queue_num=None,
187                  rs_queue_num=None, ns_queue_num=None,
188                  dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
189                  dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
190                  dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers = [],
191                  ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers = []):
192
193         self.data_path = data_path
194         self.lease_lifetime = dhcp_lease_lifetime
195         self.lease_renewal = dhcp_lease_renewal
196         self.dhcp_server_ip = dhcp_server_ip
197         self.ra_period = ra_period
198         self.dhcp_nameservers = dhcp_nameservers
199         self.ipv6_nameservers = ipv6_nameservers
200         self.ipv6_enabled = False
201
202         self.clients = {}
203         self.subnets = {}
204         self.ifaces = {}
205         self.v6nets = {}
206         self.nfq = {}
207
208         # Inotify setup
209         self.wm = pyinotify.WatchManager()
210         mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
211         mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
212         handler = ClientFileHandler(self)
213         self.notifier = pyinotify.Notifier(self.wm, handler)
214         self.wm.add_watch(self.data_path, mask, rec=True)
215
216         # NFQUEUE setup
217         if dhcp_queue_num is not None:
218             self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
219
220         if rs_queue_num is not None:
221             self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
222             self.ipv6_enabled = True
223
224         if ns_queue_num is not None:
225             self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
226             self.ipv6_enabled = True
227
228     def _setup_nfqueue(self, queue_num, family, callback):
229         logging.debug("Setting up NFQUEUE for queue %d, AF %s" %
230                       (queue_num, family))
231         q = nfqueue.queue()
232         q.set_callback(callback)
233         q.fast_open(queue_num, family)
234         q.set_queue_maxlen(5000)
235         # This is mandatory for the queue to operate
236         q.set_mode(nfqueue.NFQNL_COPY_PACKET)
237         self.nfq[q.get_fd()] = q
238
239     def build_config(self):
240         self.clients.clear()
241         self.subnets.clear()
242
243         for file in glob.glob(os.path.join(self.data_path, "*")):
244             self.add_iface(file)
245
246     def get_ifindex(self, iface):
247         """ Get the interface index from sysfs
248
249         """
250         file = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
251         if not file.startswith(SYSFS_NET):
252             return None
253
254         ifindex = None
255
256         try:
257             f = open(file, 'r')
258             ifindex = int(f.readline().strip())
259             f.close()
260         except IOError:
261             logging.debug("%s is down, removing" % iface)
262             self.remove_iface(iface)
263
264         return ifindex
265
266
267     def get_iface_hw_addr(self, iface):
268         """ Get the interface hardware address from sysfs
269
270         """
271         file = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
272         if not file.startswith(SYSFS_NET):
273             return None
274
275         addr = None
276         try:
277             f = open(file, 'r')
278             addr = f.readline().strip()
279             f.close()
280         except IOError:
281             logging.debug("%s is down, removing" % iface)
282             self.remove_iface(iface)
283
284         return addr
285
286     def parse_routing_table(self, table="main", family=4):
287         """ Parse the given routing table to get connected route, gateway and
288         default device.
289
290         """
291         ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls",
292                                  "table", table], stdout=subprocess.PIPE)
293         routes = ipro.stdout.readlines()
294
295         def_gw = None
296         def_dev = None
297         def_net = None
298
299         for route in routes:
300             match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route)
301             if match:
302                 def_gw, def_dev = match.groups()
303                 break
304
305         for route in routes:
306             # Find the least-specific connected route
307             try:
308                 def_net = re.match("^([^\\s]+) dev %s" %
309                                    def_dev, route).groups()[0]
310                 def_net = IPy.IP(def_net)
311             except:
312                 pass
313
314         return Subnet(net=def_net, gw=def_gw, dev=def_dev)
315
316     def parse_binding_file(self, path):
317         """ Read a client configuration from a tap file
318
319         """
320         try:
321             iffile = open(path, 'r')
322         except:
323             return (None, None, None, None)
324         mac = None
325         ips = None
326         link = None
327         hostname = None
328
329         for line in iffile:
330             if line.startswith("IP="):
331                 ip = line.strip().split("=")[1]
332                 ips = ip.split()
333             elif line.startswith("MAC="):
334                 mac = line.strip().split("=")[1]
335             elif line.startswith("LINK="):
336                 link = line.strip().split("=")[1]
337             elif line.startswith("HOSTNAME="):
338                 hostname = line.strip().split("=")[1]
339
340         return Client(mac=mac, ips=ips, link=link, hostname=hostname)
341
342     def add_iface(self, path):
343         """ Add an interface to monitor
344
345         """
346         iface = os.path.basename(path)
347
348         logging.debug("Updating configuration for %s" % iface)
349         binding = self.parse_binding_file(path)
350         ifindex = self.get_ifindex(iface)
351
352         if ifindex is None:
353             logging.warn("Stale configuration for %s found" % iface)
354         else:
355             if binding.is_valid():
356                 binding.iface = iface
357                 self.clients[binding.mac] = binding
358                 self.subnets[binding.link] = self.parse_routing_table(
359                                                 binding.link)
360                 logging.debug("Added client %s on %s" %
361                               (binding.hostname, iface))
362                 self.ifaces[ifindex] = iface
363                 self.v6nets[iface] = self.parse_routing_table(binding.link, 6)
364
365     def remove_iface(self, iface):
366         """ Cleanup clients on a removed interface
367
368         """
369         if iface in self.v6nets:
370             del self.v6nets[iface]
371
372         for mac in self.clients.keys():
373             if self.clients[mac].iface == iface:
374                 del self.clients[mac]
375
376         for ifindex in self.ifaces.keys():
377             if self.ifaces[ifindex] == iface:
378                 del self.ifaces[ifindex]
379
380         logging.debug("Removed interface %s" % iface)
381
382     def dhcp_response(self, i, payload):
383         """ Generate a reply to a BOOTP/DHCP request
384
385         """
386         # Decode the response - NFQUEUE relays IP packets
387         pkt = IP(payload.get_data())
388
389         # Get the actual interface from the ifindex
390         iface = self.ifaces[payload.get_indev()]
391
392         # Signal the kernel that it shouldn't further process the packet
393         payload.set_verdict(nfqueue.NF_DROP)
394
395         # Get the client MAC address
396         resp = pkt.getlayer(BOOTP).copy()
397         hlen = resp.hlen
398         mac = resp.chaddr[:hlen].encode("hex")
399         mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
400
401         # Server responses are always BOOTREPLYs
402         resp.op = "BOOTREPLY"
403         del resp.payload
404
405         try:
406             binding = self.clients[mac]
407         except KeyError:
408             logging.warn("Invalid client %s on %s" % (mac, iface))
409             return
410
411         if iface != binding.iface:
412             logging.warn("Received spoofed DHCP request for %s from interface"
413                          " %s instead of %s" %
414                          (mac, iface, binding.iface))
415             return
416
417         resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\
418                IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
419                UDP(sport=pkt.dport, dport=pkt.sport)/resp
420         subnet = self.subnets[binding.link]
421
422         if not DHCP in pkt:
423             logging.warn("Invalid request from %s on %s, no DHCP"
424                          " payload found" % (binding.mac, iface))
425             return
426
427         dhcp_options = []
428         requested_addr = binding.ip
429         for opt in pkt[DHCP].options:
430             if type(opt) is tuple and opt[0] == "message-type":
431                 req_type = opt[1]
432             if type(opt) is tuple and opt[0] == "requested_addr":
433                 requested_addr = opt[1]
434
435         logging.info("%s from %s on %s" %
436                     (DHCP_TYPES.get(req_type, "UNKNOWN"), binding.mac, iface))
437
438         if req_type == DHCPREQUEST and requested_addr != binding.ip:
439             resp_type = DHCPNAK
440             logging.info("Sending DHCPNAK to %s on %s: requested %s"
441                          " instead of %s" %
442                          (binding.mac, iface, requested_addr, binding.ip))
443
444         elif req_type in (DHCPDISCOVER, DHCPREQUEST):
445             resp_type = DHCP_REQRESP[req_type]
446             resp.yiaddr = self.clients[mac].ip
447             dhcp_options += [
448                  ("hostname", binding.hostname),
449                  ("domain", binding.hostname.split('.', 1)[-1]),
450                  ("router", subnet.gw),
451                  ("broadcast_address", str(subnet.broadcast)),
452                  ("subnet_mask", str(subnet.netmask)),
453                  ("renewal_time", self.lease_renewal),
454                  ("lease_time", self.lease_lifetime),
455             ]
456             dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
457
458         elif req_type == DHCPINFORM:
459             resp_type = DHCP_REQRESP[req_type]
460             dhcp_options += [
461                  ("hostname", binding.hostname),
462                  ("domain", binding.hostname.split('.', 1)[-1]),
463             ]
464             dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
465
466         elif req_type == DHCPRELEASE:
467             # Log and ignore
468             logging.info("DHCPRELEASE from %s on %s" %
469                          (binding.mac, iface))
470             return
471
472         # Finally, always add the server identifier and end options
473         dhcp_options += [
474             ("message-type", resp_type),
475             ("server_id", DHCP_DUMMY_SERVER_IP),
476             "end"
477         ]
478         resp /= DHCP(options=dhcp_options)
479
480         logging.info("%s to %s (%s) on %s" %
481                       (DHCP_TYPES[resp_type], mac, binding.ip, iface))
482         sendp(resp, iface=iface, verbose=False)
483
484     def rs_response(self, i, payload):
485         """ Generate a reply to a BOOTP/DHCP request
486
487         """
488         # Get the actual interface from the ifindex
489         iface = self.ifaces[payload.get_indev()]
490         ifmac = self.get_iface_hw_addr(iface)
491         subnet = self.v6nets[iface]
492         ifll = subnet.make_ll64(ifmac)
493
494         # Signal the kernel that it shouldn't further process the packet
495         payload.set_verdict(nfqueue.NF_DROP)
496
497         resp = Ether(src=self.get_iface_hw_addr(iface))/\
498                IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
499                ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
500                                      prefixlen=subnet.prefixlen)
501
502         logging.info("RA on %s for %s" % (iface, subnet.net))
503         sendp(resp, iface=iface, verbose=False)
504
505     def ns_response(self, i, payload):
506         """ Generate a reply to an ICMPv6 neighbor solicitation
507
508         """
509         # Get the actual interface from the ifindex
510         iface = self.ifaces[payload.get_indev()]
511         ifmac = self.get_iface_hw_addr(iface)
512         subnet = self.v6nets[iface]
513         ifll = subnet.make_ll64(ifmac)
514
515         ns = IPv6(payload.get_data())
516
517         if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
518             logging.debug("Received NS for a non-routable IP (%s)" % ns.tgt)
519             payload.set_verdict(nfqueue.NF_ACCEPT)
520             return 1
521
522         payload.set_verdict(nfqueue.NF_DROP)
523
524         try:
525             client_lladdr = ns.lladdr
526         except AttributeError:
527             return 1
528
529         resp = Ether(src=ifmac, dst=client_lladdr)/\
530                IPv6(src=str(ifll), dst=ns.src)/\
531                ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
532                ICMPv6NDOptDstLLAddr(lladdr=ifmac)
533
534         logging.info("NA on %s for %s" % (iface, ns.tgt))
535         sendp(resp, iface=iface, verbose=False)
536         return 1
537
538     def send_periodic_ra(self):
539         # Use a separate thread as this may take a _long_ time with
540         # many interfaces and we want to be responsive in the mean time
541         threading.Thread(target=self._send_periodic_ra).start()
542
543     def _send_periodic_ra(self):
544         logging.debug("Sending out periodic RAs")
545         start = time.time()
546         i = 0
547         for client in self.clients.values():
548             iface = client.iface
549             ifmac = self.get_iface_hw_addr(iface)
550             if not ifmac:
551                 continue
552
553             subnet = self.v6nets[iface]
554             ifll = subnet.make_ll64(ifmac)
555             resp = Ether(src=ifmac)/\
556                    IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
557                    ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
558                                          prefixlen=subnet.prefixlen)
559             try:
560                 sendp(resp, iface=iface, verbose=False)
561             except:
562                 logging.debug("Periodic RA on %s failed" % iface)
563             i += 1
564         logging.debug("Sent %d RAs in %.2f seconds" % (i, time.time() - start))
565
566     def serve(self):
567         """ Loop forever, serving DHCP requests
568
569         """
570         self.build_config()
571
572         iwfd = self.notifier._fd
573
574         start = time.time()
575         if self.ipv6_enabled:
576             timeout = self.ra_period
577             self.send_periodic_ra()
578         else:
579             timeout = None
580
581         while True:
582             rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
583             if xlist:
584                 logging.warn("Warning: Exception on %s" %
585                              ", ".join([ str(fd) for fd in xlist]))
586
587             if rlist:
588                 if iwfd in rlist:
589                 # First check if there are any inotify (= configuration change)
590                 # events
591                     self.notifier.read_events()
592                     self.notifier.process_events()
593                     rlist.remove(iwfd)
594
595                 for fd in rlist:
596                     try:
597                         self.nfq[fd].process_pending()
598                     except e, msg:
599                         logging.warn("Error processing fd %d: %s" % (fd, e))
600
601             if self.ipv6_enabled:
602                 # Calculate the new timeout
603                 timeout = self.ra_period - (time.time() - start)
604
605                 if timeout <= 0:
606                     start = time.time()
607                     self.send_periodic_ra()
608                     timeout = self.ra_period - (time.time() - start)
609
610
611 if __name__ == "__main__":
612     import optparse
613     from cStringIO import StringIO
614     from capng import *
615     from pwd import getpwnam, getpwuid
616     from configobj import ConfigObj, flatten_errors
617
618     import validate
619
620     validator = validate.Validator()
621
622     def is_ip_list(value, family=4):
623         try:
624             family = int(family)
625         except ValueError:
626             raise vaildate.VdtParamError(family)
627         if isinstance(value, (str, unicode)):
628             value = [value]
629         if not isinstance(value, list):
630             raise validate.VdtTypeError(value)
631
632         for entry in value:
633             try:
634                 ip = IPy.IP(entry)
635             except ValueError:
636                 raise validate.VdtValueError(entry)
637
638             if ip.version() != family:
639                 raise validate.VdtValueError(entry)
640         return value
641
642     validator.functions["ip_addr_list"] = is_ip_list
643     config_spec = StringIO(CONFIG_SPEC)
644
645
646     parser = optparse.OptionParser()
647     parser.add_option("-c", "--config", dest="config_file",
648                       help="The location of the data files", metavar="FILE",
649                       default=DEFAULT_CONFIG)
650     parser.add_option("-d", "--debug", action="store_true", dest="debug",
651                       help="Turn on debugging messages")
652     parser.add_option("-f", "--foreground", action="store_false", dest="daemonize",
653                       default=True, help="Do not daemonize, stay in the foreground")
654
655
656     opts, args = parser.parse_args()
657
658     if opts.daemonize:
659         d = daemon.DaemonContext()
660         d.open()
661
662     try:
663         config = ConfigObj(opts.config_file, configspec=config_spec)
664     except:
665         sys.stderr.write("Failed to parse config file %s" % opts.config_file)
666         sys.exit(1)
667
668     results = config.validate(validator)
669     if results != True:
670         logging.fatal("Configuration file validation failed! See errors below:")
671         for (section_list, key, _) in flatten_errors(config, results):
672             if key is not None:
673                 logging.fatal(" '%s' in section '%s' failed validation" % (key, ", ".join(section_list)))
674             else:
675                 logging.fatal(" Section '%s' is missing" % ", ".join(section_list))
676         sys.exit(1)
677
678     pidfile = open(config["general"]["pidfile"], "w")
679     pidfile.write("%s" % os.getpid())
680     pidfile.close()
681
682     logger = logging.getLogger()
683     if opts.debug:
684         logger.setLevel(logging.DEBUG)
685     else:
686         logger.setLevel(logging.INFO)
687
688     if opts.daemonize:
689         logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
690         handler = logging.handlers.RotatingFileHandler(logfile,
691                                                        maxBytes=2097152)
692     else:
693         handler = logging.StreamHandler()
694
695     handler.setFormatter(logging.Formatter(LOG_FORMAT))
696     logger.addHandler(handler)
697
698     logging.info("Starting up")
699
700     proxy_opts = {}
701     if config["dhcp"].as_bool("enable_dhcp"):
702         proxy_opts.update({
703             "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
704             "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
705             "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
706             "dhcp_server_ip": config["dhcp"]["server_ip"],
707             "dhcp_nameservers": config["dhcp"]["nameservers"],
708         })
709
710     if config["ipv6"].as_bool("enable_ipv6"):
711         proxy_opts.update({
712             "rs_queue_num": config["ipv6"].as_int("rs_queue"),
713             "ns_queue_num": config["ipv6"].as_int("ns_queue"),
714             "ra_period": config["ipv6"].as_int("ra_period"),
715             "ipv6_nameservers": config["ipv6"]["nameservers"],
716         })
717
718     proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
719
720     # Drop all capabilities except CAP_NET_RAW and change uid
721     try:
722         uid = getpwuid(config["general"].as_int("user"))
723     except ValueError:
724         uid = getpwnam(config["general"]["user"])
725
726     logging.info("Setting capabilities and changing uid")
727     logging.debug("User: %s, uid: %d, gid: %d" %
728                   (config["general"]["user"], uid.pw_uid, uid.pw_gid))
729     capng_clear(CAPNG_SELECT_BOTH)
730     capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_NET_RAW)
731     capng_change_id(uid.pw_uid, uid.pw_gid,
732                     CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)
733     logging.info("Ready to serve requests")
734     proxy.serve()
735
736
737 # vim: set ts=4 sts=4 sw=4 et :