Add configurationf file validation
[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
201         self.clients = {}
202         self.subnets = {}
203         self.ifaces = {}
204         self.v6nets = {}
205         self.nfq = {}
206
207         # Inotify setup
208         self.wm = pyinotify.WatchManager()
209         mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
210         mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
211         handler = ClientFileHandler(self)
212         self.notifier = pyinotify.Notifier(self.wm, handler)
213         self.wm.add_watch(self.data_path, mask, rec=True)
214
215         # NFQUEUE setup
216         if dhcp_queue_num is not None:
217             self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
218
219         if rs_queue_num is not None:
220             self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
221
222         if ns_queue_num is not None:
223             self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
224
225     def _setup_nfqueue(self, queue_num, family, callback):
226         logging.debug("Setting up NFQUEUE for queue %d, AF %s" %
227                       (queue_num, family))
228         q = nfqueue.queue()
229         q.set_callback(callback)
230         q.fast_open(queue_num, family)
231         q.set_queue_maxlen(5000)
232         # This is mandatory for the queue to operate
233         q.set_mode(nfqueue.NFQNL_COPY_PACKET)
234         self.nfq[q.get_fd()] = q
235
236     def build_config(self):
237         self.clients.clear()
238         self.subnets.clear()
239
240         for file in glob.glob(os.path.join(self.data_path, "*")):
241             self.add_iface(file)
242
243     def get_ifindex(self, iface):
244         """ Get the interface index from sysfs
245
246         """
247         file = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
248         if not file.startswith(SYSFS_NET):
249             return None
250
251         ifindex = None
252
253         try:
254             f = open(file, 'r')
255             ifindex = int(f.readline().strip())
256             f.close()
257         except IOError:
258             logging.debug("%s is down, removing" % iface)
259             self.remove_iface(iface)
260
261         return ifindex
262
263
264     def get_iface_hw_addr(self, iface):
265         """ Get the interface hardware address from sysfs
266
267         """
268         file = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
269         if not file.startswith(SYSFS_NET):
270             return None
271
272         addr = None
273         try:
274             f = open(file, 'r')
275             addr = f.readline().strip()
276             f.close()
277         except IOError:
278             logging.debug("%s is down, removing" % iface)
279             self.remove_iface(iface)
280
281         return addr
282
283     def parse_routing_table(self, table="main", family=4):
284         """ Parse the given routing table to get connected route, gateway and
285         default device.
286
287         """
288         ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls",
289                                  "table", table], stdout=subprocess.PIPE)
290         routes = ipro.stdout.readlines()
291
292         def_gw = None
293         def_dev = None
294         def_net = None
295
296         for route in routes:
297             match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route)
298             if match:
299                 def_gw, def_dev = match.groups()
300                 break
301
302         for route in routes:
303             # Find the least-specific connected route
304             try:
305                 def_net = re.match("^([^\\s]+) dev %s" %
306                                    def_dev, route).groups()[0]
307                 def_net = IPy.IP(def_net)
308             except:
309                 pass
310
311         return Subnet(net=def_net, gw=def_gw, dev=def_dev)
312
313     def parse_binding_file(self, path):
314         """ Read a client configuration from a tap file
315
316         """
317         try:
318             iffile = open(path, 'r')
319         except:
320             return (None, None, None, None)
321         mac = None
322         ips = None
323         link = None
324         hostname = None
325
326         for line in iffile:
327             if line.startswith("IP="):
328                 ip = line.strip().split("=")[1]
329                 ips = ip.split()
330             elif line.startswith("MAC="):
331                 mac = line.strip().split("=")[1]
332             elif line.startswith("LINK="):
333                 link = line.strip().split("=")[1]
334             elif line.startswith("HOSTNAME="):
335                 hostname = line.strip().split("=")[1]
336
337         return Client(mac=mac, ips=ips, link=link, hostname=hostname)
338
339     def add_iface(self, path):
340         """ Add an interface to monitor
341
342         """
343         iface = os.path.basename(path)
344
345         logging.debug("Updating configuration for %s" % iface)
346         binding = self.parse_binding_file(path)
347         ifindex = self.get_ifindex(iface)
348
349         if ifindex is None:
350             logging.warn("Stale configuration for %s found" % iface)
351         else:
352             if binding.is_valid():
353                 binding.iface = iface
354                 self.clients[binding.mac] = binding
355                 self.subnets[binding.link] = self.parse_routing_table(
356                                                 binding.link)
357                 logging.debug("Added client %s on %s" %
358                               (binding.hostname, iface))
359                 self.ifaces[ifindex] = iface
360                 self.v6nets[iface] = self.parse_routing_table(binding.link, 6)
361
362     def remove_iface(self, iface):
363         """ Cleanup clients on a removed interface
364
365         """
366         if iface in self.v6nets:
367             del self.v6nets[iface]
368
369         for mac in self.clients.keys():
370             if self.clients[mac].iface == iface:
371                 del self.clients[mac]
372
373         for ifindex in self.ifaces.keys():
374             if self.ifaces[ifindex] == iface:
375                 del self.ifaces[ifindex]
376
377         logging.debug("Removed interface %s" % iface)
378
379     def dhcp_response(self, i, payload):
380         """ Generate a reply to a BOOTP/DHCP request
381
382         """
383         # Decode the response - NFQUEUE relays IP packets
384         pkt = IP(payload.get_data())
385
386         # Get the actual interface from the ifindex
387         iface = self.ifaces[payload.get_indev()]
388
389         # Signal the kernel that it shouldn't further process the packet
390         payload.set_verdict(nfqueue.NF_DROP)
391
392         # Get the client MAC address
393         resp = pkt.getlayer(BOOTP).copy()
394         hlen = resp.hlen
395         mac = resp.chaddr[:hlen].encode("hex")
396         mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
397
398         # Server responses are always BOOTREPLYs
399         resp.op = "BOOTREPLY"
400         del resp.payload
401
402         try:
403             binding = self.clients[mac]
404         except KeyError:
405             logging.warn("Invalid client %s on %s" % (mac, iface))
406             return
407
408         if iface != binding.iface:
409             logging.warn("Received spoofed DHCP request for %s from interface"
410                          " %s instead of %s" %
411                          (mac, iface, binding.iface))
412             return
413
414         resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\
415                IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
416                UDP(sport=pkt.dport, dport=pkt.sport)/resp
417         subnet = self.subnets[binding.link]
418
419         if not DHCP in pkt:
420             logging.warn("Invalid request from %s on %s, no DHCP"
421                          " payload found" % (binding.mac, iface))
422             return
423
424         dhcp_options = []
425         requested_addr = binding.ip
426         for opt in pkt[DHCP].options:
427             if type(opt) is tuple and opt[0] == "message-type":
428                 req_type = opt[1]
429             if type(opt) is tuple and opt[0] == "requested_addr":
430                 requested_addr = opt[1]
431
432         logging.info("%s from %s on %s" %
433                     (DHCP_TYPES.get(req_type, "UNKNOWN"), binding.mac, iface))
434
435         if req_type == DHCPREQUEST and requested_addr != binding.ip:
436             resp_type = DHCPNAK
437             logging.info("Sending DHCPNAK to %s on %s: requested %s"
438                          " instead of %s" %
439                          (binding.mac, iface, requested_addr, binding.ip))
440
441         elif req_type in (DHCPDISCOVER, DHCPREQUEST):
442             resp_type = DHCP_REQRESP[req_type]
443             resp.yiaddr = self.clients[mac].ip
444             dhcp_options += [
445                  ("hostname", binding.hostname),
446                  ("domain", binding.hostname.split('.', 1)[-1]),
447                  ("router", subnet.gw),
448                  ("name_server", "194.177.210.10"),
449                  ("name_server", "194.177.210.211"),
450                  ("broadcast_address", str(subnet.broadcast)),
451                  ("subnet_mask", str(subnet.netmask)),
452                  ("renewal_time", self.lease_renewal),
453                  ("lease_time", self.lease_lifetime),
454             ]
455
456         elif req_type == DHCPINFORM:
457             resp_type = DHCP_REQRESP[req_type]
458             dhcp_options += [
459                  ("hostname", binding.hostname),
460                  ("domain", binding.hostname.split('.', 1)[-1]),
461                  ("name_server", "194.177.210.10"),
462                  ("name_server", "194.177.210.211"),
463             ]
464
465         elif req_type == DHCPRELEASE:
466             # Log and ignore
467             logging.info("DHCPRELEASE from %s on %s" %
468                          (binding.mac, iface))
469             return
470
471         # Finally, always add the server identifier and end options
472         dhcp_options += [
473             ("message-type", resp_type),
474             ("server_id", DHCP_DUMMY_SERVER_IP),
475             "end"
476         ]
477         resp /= DHCP(options=dhcp_options)
478
479         logging.info("%s to %s (%s) on %s" %
480                       (DHCP_TYPES[resp_type], mac, binding.ip, iface))
481         sendp(resp, iface=iface, verbose=False)
482
483     def rs_response(self, i, payload):
484         """ Generate a reply to a BOOTP/DHCP request
485
486         """
487         # Get the actual interface from the ifindex
488         iface = self.ifaces[payload.get_indev()]
489         ifmac = self.get_iface_hw_addr(iface)
490         subnet = self.v6nets[iface]
491         ifll = subnet.make_ll64(ifmac)
492
493         # Signal the kernel that it shouldn't further process the packet
494         payload.set_verdict(nfqueue.NF_DROP)
495
496         resp = Ether(src=self.get_iface_hw_addr(iface))/\
497                IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
498                ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
499                                      prefixlen=subnet.prefixlen)
500
501         logging.info("RA on %s for %s" % (iface, subnet.net))
502         sendp(resp, iface=iface, verbose=False)
503
504     def ns_response(self, i, payload):
505         """ Generate a reply to an ICMPv6 neighbor solicitation
506
507         """
508         # Get the actual interface from the ifindex
509         iface = self.ifaces[payload.get_indev()]
510         ifmac = self.get_iface_hw_addr(iface)
511         subnet = self.v6nets[iface]
512         ifll = subnet.make_ll64(ifmac)
513
514         ns = IPv6(payload.get_data())
515
516         if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
517             logging.debug("Received NS for a non-routable IP (%s)" % ns.tgt)
518             payload.set_verdict(nfqueue.NF_ACCEPT)
519             return 1
520
521         payload.set_verdict(nfqueue.NF_DROP)
522
523         try:
524             client_lladdr = ns.lladdr
525         except AttributeError:
526             return 1
527
528         resp = Ether(src=ifmac, dst=client_lladdr)/\
529                IPv6(src=str(ifll), dst=ns.src)/\
530                ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
531                ICMPv6NDOptDstLLAddr(lladdr=ifmac)
532
533         logging.info("NA on %s for %s" % (iface, ns.tgt))
534         sendp(resp, iface=iface, verbose=False)
535         return 1
536
537     def send_periodic_ra(self):
538         # Use a separate thread as this may take a _long_ time with
539         # many interfaces and we want to be responsive in the mean time
540         threading.Thread(target=self._send_periodic_ra).start()
541
542     def _send_periodic_ra(self):
543         logging.debug("Sending out periodic RAs")
544         start = time.time()
545         i = 0
546         for client in self.clients.values():
547             iface = client.iface
548             ifmac = self.get_iface_hw_addr(iface)
549             if not ifmac:
550                 continue
551
552             subnet = self.v6nets[iface]
553             ifll = subnet.make_ll64(ifmac)
554             resp = Ether(src=ifmac)/\
555                    IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
556                    ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
557                                          prefixlen=subnet.prefixlen)
558             try:
559                 sendp(resp, iface=iface, verbose=False)
560             except:
561                 logging.debug("Periodic RA on %s failed" % iface)
562             i += 1
563         logging.debug("Sent %d RAs in %.2f seconds" % (i, time.time() - start))
564
565     def serve(self):
566         """ Loop forever, serving DHCP requests
567
568         """
569         self.build_config()
570
571         iwfd = self.notifier._fd
572
573         start = time.time()
574         timeout = self.ra_period
575         self.send_periodic_ra()
576
577         while True:
578             rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
579             if xlist:
580                 logging.warn("Warning: Exception on %s" %
581                              ", ".join([ str(fd) for fd in xlist]))
582
583             if rlist:
584                 if iwfd in rlist:
585                 # First check if there are any inotify (= configuration change)
586                 # events
587                     self.notifier.read_events()
588                     self.notifier.process_events()
589                     rlist.remove(iwfd)
590
591                 for fd in rlist:
592                     try:
593                         self.nfq[fd].process_pending()
594                     except e, msg:
595                         logging.warn("Error processing fd %d: %s" % (fd, e))
596
597             # Calculate the new timeout
598             timeout = self.ra_period - (time.time() - start)
599
600             if timeout <= 0:
601                 start = time.time()
602                 self.send_periodic_ra()
603                 timeout = self.ra_period - (time.time() - start)
604
605
606
607 if __name__ == "__main__":
608     import optparse
609     from cStringIO import StringIO
610     from capng import *
611     from pwd import getpwnam, getpwuid
612     from configobj import ConfigObj, flatten_errors
613
614     import validate
615
616     validator = validate.Validator()
617
618     def is_ip_list(value, family=4):
619         try:
620             family = int(family)
621         except ValueError:
622             raise vaildate.VdtParamError(family)
623         if isinstance(value, (str, unicode)):
624             value = [value]
625         if not isinstance(value, list):
626             raise validate.VdtTypeError(value)
627
628         for entry in value:
629             try:
630                 ip = IPy.IP(entry)
631             except ValueError:
632                 raise validate.VdtValueError(entry)
633
634             if ip.version() != family:
635                 raise validate.VdtValueError(entry)
636         return value
637
638     validator.functions["ip_addr_list"] = is_ip_list
639     config_spec = StringIO(CONFIG_SPEC)
640
641
642     parser = optparse.OptionParser()
643     parser.add_option("-c", "--config", dest="config_file",
644                       help="The location of the data files", metavar="FILE",
645                       default=DEFAULT_CONFIG)
646     parser.add_option("-d", "--debug", action="store_true", dest="debug",
647                       help="Turn on debugging messages")
648     parser.add_option("-f", "--foreground", action="store_false", dest="daemonize",
649                       default=True, help="Do not daemonize, stay in the foreground")
650
651
652     opts, args = parser.parse_args()
653
654     if opts.daemonize:
655         d = daemon.DaemonContext()
656         d.open()
657
658     try:
659         config = ConfigObj(opts.config_file, configspec=config_spec)
660     except:
661         sys.stderr.write("Failed to parse config file %s" % opts.config_file)
662         sys.exit(1)
663
664     results = config.validate(validator)
665     if results != True:
666         logging.fatal("Configuration file validation failed! See errors below:")
667         for (section_list, key, _) in flatten_errors(config, results):
668             if key is not None:
669                 logging.fatal(" '%s' in section '%s' failed validation" % (key, ", ".join(section_list)))
670             else:
671                 logging.fatal(" Section '%s' is missing" % ", ".join(section_list))
672         sys.exit(1)
673
674     pidfile = open(config["general"]["pidfile"], "w")
675     pidfile.write("%s" % os.getpid())
676     pidfile.close()
677
678     logger = logging.getLogger()
679     if opts.debug:
680         logger.setLevel(logging.DEBUG)
681     else:
682         logger.setLevel(logging.INFO)
683
684     if opts.daemonize:
685         logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
686         handler = logging.handlers.RotatingFileHandler(logfile,
687                                                        maxBytes=2097152)
688     else:
689         handler = logging.StreamHandler()
690
691     handler.setFormatter(logging.Formatter(LOG_FORMAT))
692     logger.addHandler(handler)
693
694     logging.info("Starting up")
695
696     proxy_opts = {}
697     if config["dhcp"].as_bool("enable_dhcp"):
698         proxy_opts.update({
699             "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
700             "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
701             "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
702             "dhcp_server_ip": config["dhcp"]["server_ip"],
703             "dhcp_nameservers": config["dhcp"]["nameservers"],
704         })
705
706     if config["ipv6"].as_bool("enable_ipv6"):
707         proxy_opts.update({
708             "rs_queue_num": config["ipv6"].as_int("rs_queue"),
709             "ns_queue_num": config["ipv6"].as_int("ns_queue"),
710             "ra_period": config["ipv6"].as_int("ra_period"),
711             "ipv6_nameservers": config["ipv6"]["nameservers"],
712         })
713
714     proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
715
716     # Drop all capabilities except CAP_NET_RAW and change uid
717     try:
718         uid = getpwuid(config["general"].as_int("user"))
719     except ValueError:
720         uid = getpwnam(config["general"]["user"])
721
722     logging.info("Setting capabilities and changing uid")
723     logging.debug("User: %s, uid: %d, gid: %d" %
724                   (config["general"]["user"], uid.pw_uid, uid.pw_gid))
725     capng_clear(CAPNG_SELECT_BOTH)
726     capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_NET_RAW)
727     capng_change_id(uid.pw_uid, uid.pw_gid,
728                     CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)
729     logging.info("Ready to serve requests")
730     proxy.serve()
731
732
733 # vim: set ts=4 sts=4 sw=4 et :