Statistics
| Branch: | Tag: | Revision:

root / nfdhcpd @ 651e531d

History | View | Annotate | Download (23.2 kB)

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 :