Implement IPv6 RDNSS
[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         if self.ipv6_nameservers:
503             resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
504                                      lifetime=self.ra_period * 3)
505
506         logging.info("RA on %s for %s" % (iface, subnet.net))
507         sendp(resp, iface=iface, verbose=False)
508
509     def ns_response(self, i, payload):
510         """ Generate a reply to an ICMPv6 neighbor solicitation
511
512         """
513         # Get the actual interface from the ifindex
514         iface = self.ifaces[payload.get_indev()]
515         ifmac = self.get_iface_hw_addr(iface)
516         subnet = self.v6nets[iface]
517         ifll = subnet.make_ll64(ifmac)
518
519         ns = IPv6(payload.get_data())
520
521         if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
522             logging.debug("Received NS for a non-routable IP (%s)" % ns.tgt)
523             payload.set_verdict(nfqueue.NF_ACCEPT)
524             return 1
525
526         payload.set_verdict(nfqueue.NF_DROP)
527
528         try:
529             client_lladdr = ns.lladdr
530         except AttributeError:
531             return 1
532
533         resp = Ether(src=ifmac, dst=client_lladdr)/\
534                IPv6(src=str(ifll), dst=ns.src)/\
535                ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
536                ICMPv6NDOptDstLLAddr(lladdr=ifmac)
537
538         logging.info("NA on %s for %s" % (iface, ns.tgt))
539         sendp(resp, iface=iface, verbose=False)
540         return 1
541
542     def send_periodic_ra(self):
543         # Use a separate thread as this may take a _long_ time with
544         # many interfaces and we want to be responsive in the mean time
545         threading.Thread(target=self._send_periodic_ra).start()
546
547     def _send_periodic_ra(self):
548         logging.debug("Sending out periodic RAs")
549         start = time.time()
550         i = 0
551         for client in self.clients.values():
552             iface = client.iface
553             ifmac = self.get_iface_hw_addr(iface)
554             if not ifmac:
555                 continue
556
557             subnet = self.v6nets[iface]
558             ifll = subnet.make_ll64(ifmac)
559             resp = Ether(src=ifmac)/\
560                    IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
561                    ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
562                                          prefixlen=subnet.prefixlen)
563             if self.ipv6_nameservers:
564                 resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
565                                          lifetime=self.ra_period * 3)
566             try:
567                 sendp(resp, iface=iface, verbose=False)
568             except:
569                 logging.debug("Periodic RA on %s failed" % iface)
570             i += 1
571         logging.debug("Sent %d RAs in %.2f seconds" % (i, time.time() - start))
572
573     def serve(self):
574         """ Loop forever, serving DHCP requests
575
576         """
577         self.build_config()
578
579         iwfd = self.notifier._fd
580
581         start = time.time()
582         if self.ipv6_enabled:
583             timeout = self.ra_period
584             self.send_periodic_ra()
585         else:
586             timeout = None
587
588         while True:
589             rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
590             if xlist:
591                 logging.warn("Warning: Exception on %s" %
592                              ", ".join([ str(fd) for fd in xlist]))
593
594             if rlist:
595                 if iwfd in rlist:
596                 # First check if there are any inotify (= configuration change)
597                 # events
598                     self.notifier.read_events()
599                     self.notifier.process_events()
600                     rlist.remove(iwfd)
601
602                 for fd in rlist:
603                     try:
604                         self.nfq[fd].process_pending()
605                     except e, msg:
606                         logging.warn("Error processing fd %d: %s" % (fd, e))
607
608             if self.ipv6_enabled:
609                 # Calculate the new timeout
610                 timeout = self.ra_period - (time.time() - start)
611
612                 if timeout <= 0:
613                     start = time.time()
614                     self.send_periodic_ra()
615                     timeout = self.ra_period - (time.time() - start)
616
617
618 if __name__ == "__main__":
619     import optparse
620     from cStringIO import StringIO
621     from capng import *
622     from pwd import getpwnam, getpwuid
623     from configobj import ConfigObj, flatten_errors
624
625     import validate
626
627     validator = validate.Validator()
628
629     def is_ip_list(value, family=4):
630         try:
631             family = int(family)
632         except ValueError:
633             raise vaildate.VdtParamError(family)
634         if isinstance(value, (str, unicode)):
635             value = [value]
636         if not isinstance(value, list):
637             raise validate.VdtTypeError(value)
638
639         for entry in value:
640             try:
641                 ip = IPy.IP(entry)
642             except ValueError:
643                 raise validate.VdtValueError(entry)
644
645             if ip.version() != family:
646                 raise validate.VdtValueError(entry)
647         return value
648
649     validator.functions["ip_addr_list"] = is_ip_list
650     config_spec = StringIO(CONFIG_SPEC)
651
652
653     parser = optparse.OptionParser()
654     parser.add_option("-c", "--config", dest="config_file",
655                       help="The location of the data files", metavar="FILE",
656                       default=DEFAULT_CONFIG)
657     parser.add_option("-d", "--debug", action="store_true", dest="debug",
658                       help="Turn on debugging messages")
659     parser.add_option("-f", "--foreground", action="store_false", dest="daemonize",
660                       default=True, help="Do not daemonize, stay in the foreground")
661
662
663     opts, args = parser.parse_args()
664
665     if opts.daemonize:
666         d = daemon.DaemonContext()
667         d.open()
668
669     try:
670         config = ConfigObj(opts.config_file, configspec=config_spec)
671     except:
672         sys.stderr.write("Failed to parse config file %s" % opts.config_file)
673         sys.exit(1)
674
675     results = config.validate(validator)
676     if results != True:
677         logging.fatal("Configuration file validation failed! See errors below:")
678         for (section_list, key, _) in flatten_errors(config, results):
679             if key is not None:
680                 logging.fatal(" '%s' in section '%s' failed validation" % (key, ", ".join(section_list)))
681             else:
682                 logging.fatal(" Section '%s' is missing" % ", ".join(section_list))
683         sys.exit(1)
684
685     pidfile = open(config["general"]["pidfile"], "w")
686     pidfile.write("%s" % os.getpid())
687     pidfile.close()
688
689     logger = logging.getLogger()
690     if opts.debug:
691         logger.setLevel(logging.DEBUG)
692     else:
693         logger.setLevel(logging.INFO)
694
695     if opts.daemonize:
696         logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
697         handler = logging.handlers.RotatingFileHandler(logfile,
698                                                        maxBytes=2097152)
699     else:
700         handler = logging.StreamHandler()
701
702     handler.setFormatter(logging.Formatter(LOG_FORMAT))
703     logger.addHandler(handler)
704
705     logging.info("Starting up")
706
707     proxy_opts = {}
708     if config["dhcp"].as_bool("enable_dhcp"):
709         proxy_opts.update({
710             "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
711             "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
712             "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
713             "dhcp_server_ip": config["dhcp"]["server_ip"],
714             "dhcp_nameservers": config["dhcp"]["nameservers"],
715         })
716
717     if config["ipv6"].as_bool("enable_ipv6"):
718         proxy_opts.update({
719             "rs_queue_num": config["ipv6"].as_int("rs_queue"),
720             "ns_queue_num": config["ipv6"].as_int("ns_queue"),
721             "ra_period": config["ipv6"].as_int("ra_period"),
722             "ipv6_nameservers": config["ipv6"]["nameservers"],
723         })
724
725     proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
726
727     # Drop all capabilities except CAP_NET_RAW and change uid
728     try:
729         uid = getpwuid(config["general"].as_int("user"))
730     except ValueError:
731         uid = getpwnam(config["general"]["user"])
732
733     logging.info("Setting capabilities and changing uid")
734     logging.debug("User: %s, uid: %d, gid: %d" %
735                   (config["general"]["user"], uid.pw_uid, uid.pw_gid))
736     capng_clear(CAPNG_SELECT_BOTH)
737     capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_NET_RAW)
738     capng_change_id(uid.pw_uid, uid.pw_gid,
739                     CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING)
740     logging.info("Ready to serve requests")
741     proxy.serve()
742
743
744 # vim: set ts=4 sts=4 sw=4 et :