Statistics
| Branch: | Tag: | Revision:

root / nfdhcpd / nfdhcpd @ cf51ea5b

History | View | Annotate | Download (29.8 kB)

1 cf51ea5b Dimitris Aragiorgis
#!/usr/bin/env python
2 cf51ea5b Dimitris Aragiorgis
#
3 cf51ea5b Dimitris Aragiorgis
4 cf51ea5b Dimitris Aragiorgis
# nfdcpd: A promiscuous, NFQUEUE-based DHCP server for virtual machine hosting
5 cf51ea5b Dimitris Aragiorgis
# Copyright (c) 2010 GRNET SA
6 cf51ea5b Dimitris Aragiorgis
#
7 cf51ea5b Dimitris Aragiorgis
#    This program is free software; you can redistribute it and/or modify
8 cf51ea5b Dimitris Aragiorgis
#    it under the terms of the GNU General Public License as published by
9 cf51ea5b Dimitris Aragiorgis
#    the Free Software Foundation; either version 2 of the License, or
10 cf51ea5b Dimitris Aragiorgis
#    (at your option) any later version.
11 cf51ea5b Dimitris Aragiorgis
#
12 cf51ea5b Dimitris Aragiorgis
#    This program is distributed in the hope that it will be useful,
13 cf51ea5b Dimitris Aragiorgis
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
14 cf51ea5b Dimitris Aragiorgis
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 cf51ea5b Dimitris Aragiorgis
#    GNU General Public License for more details.
16 cf51ea5b Dimitris Aragiorgis
#
17 cf51ea5b Dimitris Aragiorgis
#    You should have received a copy of the GNU General Public License along
18 cf51ea5b Dimitris Aragiorgis
#    with this program; if not, write to the Free Software Foundation, Inc.,
19 cf51ea5b Dimitris Aragiorgis
#    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 cf51ea5b Dimitris Aragiorgis
#
21 cf51ea5b Dimitris Aragiorgis
22 cf51ea5b Dimitris Aragiorgis
import os
23 cf51ea5b Dimitris Aragiorgis
import re
24 cf51ea5b Dimitris Aragiorgis
import sys
25 cf51ea5b Dimitris Aragiorgis
import glob
26 cf51ea5b Dimitris Aragiorgis
import time
27 cf51ea5b Dimitris Aragiorgis
import logging
28 cf51ea5b Dimitris Aragiorgis
import logging.handlers
29 cf51ea5b Dimitris Aragiorgis
import threading
30 cf51ea5b Dimitris Aragiorgis
import traceback
31 cf51ea5b Dimitris Aragiorgis
import subprocess
32 cf51ea5b Dimitris Aragiorgis
33 cf51ea5b Dimitris Aragiorgis
import daemon
34 cf51ea5b Dimitris Aragiorgis
import daemon.pidlockfile
35 cf51ea5b Dimitris Aragiorgis
import nfqueue
36 cf51ea5b Dimitris Aragiorgis
import pyinotify
37 cf51ea5b Dimitris Aragiorgis
38 cf51ea5b Dimitris Aragiorgis
import IPy
39 cf51ea5b Dimitris Aragiorgis
import socket
40 cf51ea5b Dimitris Aragiorgis
from select import select
41 cf51ea5b Dimitris Aragiorgis
from socket import AF_INET, AF_INET6
42 cf51ea5b Dimitris Aragiorgis
43 cf51ea5b Dimitris Aragiorgis
from scapy.data import ETH_P_ALL
44 cf51ea5b Dimitris Aragiorgis
from scapy.packet import BasePacket
45 cf51ea5b Dimitris Aragiorgis
from scapy.layers.l2 import Ether
46 cf51ea5b Dimitris Aragiorgis
from scapy.layers.inet import IP, UDP
47 cf51ea5b Dimitris Aragiorgis
from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \
48 cf51ea5b Dimitris Aragiorgis
                               ICMPv6NDOptDstLLAddr, \
49 cf51ea5b Dimitris Aragiorgis
                               ICMPv6NDOptPrefixInfo, \
50 cf51ea5b Dimitris Aragiorgis
                               ICMPv6NDOptRDNSS
51 cf51ea5b Dimitris Aragiorgis
from scapy.layers.dhcp import BOOTP, DHCP
52 cf51ea5b Dimitris Aragiorgis
53 cf51ea5b Dimitris Aragiorgis
DEFAULT_CONFIG = "/etc/nfdhcpd/nfdhcpd.conf"
54 cf51ea5b Dimitris Aragiorgis
DEFAULT_PATH = "/var/run/ganeti-dhcpd"
55 cf51ea5b Dimitris Aragiorgis
DEFAULT_USER = "nobody"
56 cf51ea5b Dimitris Aragiorgis
DEFAULT_LEASE_LIFETIME = 604800 # 1 week
57 cf51ea5b Dimitris Aragiorgis
DEFAULT_LEASE_RENEWAL = 600  # 10 min
58 cf51ea5b Dimitris Aragiorgis
DEFAULT_RA_PERIOD = 300 # seconds
59 cf51ea5b Dimitris Aragiorgis
DHCP_DUMMY_SERVER_IP = "1.2.3.4"
60 cf51ea5b Dimitris Aragiorgis
61 cf51ea5b Dimitris Aragiorgis
LOG_FILENAME = "nfdhcpd.log"
62 cf51ea5b Dimitris Aragiorgis
63 cf51ea5b Dimitris Aragiorgis
SYSFS_NET = "/sys/class/net"
64 cf51ea5b Dimitris Aragiorgis
65 cf51ea5b Dimitris Aragiorgis
LOG_FORMAT = "%(asctime)-15s %(levelname)-6s %(message)s"
66 cf51ea5b Dimitris Aragiorgis
67 cf51ea5b Dimitris Aragiorgis
# Configuration file specification (see configobj documentation)
68 cf51ea5b Dimitris Aragiorgis
CONFIG_SPEC = """
69 cf51ea5b Dimitris Aragiorgis
[general]
70 cf51ea5b Dimitris Aragiorgis
pidfile = string()
71 cf51ea5b Dimitris Aragiorgis
datapath = string()
72 cf51ea5b Dimitris Aragiorgis
logdir = string()
73 cf51ea5b Dimitris Aragiorgis
user = string()
74 cf51ea5b Dimitris Aragiorgis
75 cf51ea5b Dimitris Aragiorgis
[dhcp]
76 cf51ea5b Dimitris Aragiorgis
enable_dhcp = boolean(default=True)
77 cf51ea5b Dimitris Aragiorgis
lease_lifetime = integer(min=0, max=4294967295)
78 cf51ea5b Dimitris Aragiorgis
lease_renewal = integer(min=0, max=4294967295)
79 cf51ea5b Dimitris Aragiorgis
server_ip = ip_addr()
80 cf51ea5b Dimitris Aragiorgis
dhcp_queue = integer(min=0, max=65535)
81 cf51ea5b Dimitris Aragiorgis
nameservers = ip_addr_list(family=4)
82 cf51ea5b Dimitris Aragiorgis
83 cf51ea5b Dimitris Aragiorgis
[ipv6]
84 cf51ea5b Dimitris Aragiorgis
enable_ipv6 = boolean(default=True)
85 cf51ea5b Dimitris Aragiorgis
ra_period = integer(min=1, max=4294967295)
86 cf51ea5b Dimitris Aragiorgis
rs_queue = integer(min=0, max=65535)
87 cf51ea5b Dimitris Aragiorgis
ns_queue = integer(min=0, max=65535)
88 cf51ea5b Dimitris Aragiorgis
nameservers = ip_addr_list(family=6)
89 cf51ea5b Dimitris Aragiorgis
"""
90 cf51ea5b Dimitris Aragiorgis
91 cf51ea5b Dimitris Aragiorgis
92 cf51ea5b Dimitris Aragiorgis
DHCPDISCOVER = 1
93 cf51ea5b Dimitris Aragiorgis
DHCPOFFER = 2
94 cf51ea5b Dimitris Aragiorgis
DHCPREQUEST = 3
95 cf51ea5b Dimitris Aragiorgis
DHCPDECLINE = 4
96 cf51ea5b Dimitris Aragiorgis
DHCPACK = 5
97 cf51ea5b Dimitris Aragiorgis
DHCPNAK = 6
98 cf51ea5b Dimitris Aragiorgis
DHCPRELEASE = 7
99 cf51ea5b Dimitris Aragiorgis
DHCPINFORM = 8
100 cf51ea5b Dimitris Aragiorgis
101 cf51ea5b Dimitris Aragiorgis
DHCP_TYPES = {
102 cf51ea5b Dimitris Aragiorgis
    DHCPDISCOVER: "DHCPDISCOVER",
103 cf51ea5b Dimitris Aragiorgis
    DHCPOFFER: "DHCPOFFER",
104 cf51ea5b Dimitris Aragiorgis
    DHCPREQUEST: "DHCPREQUEST",
105 cf51ea5b Dimitris Aragiorgis
    DHCPDECLINE: "DHCPDECLINE",
106 cf51ea5b Dimitris Aragiorgis
    DHCPACK: "DHCPACK",
107 cf51ea5b Dimitris Aragiorgis
    DHCPNAK: "DHCPNAK",
108 cf51ea5b Dimitris Aragiorgis
    DHCPRELEASE: "DHCPRELEASE",
109 cf51ea5b Dimitris Aragiorgis
    DHCPINFORM: "DHCPINFORM",
110 cf51ea5b Dimitris Aragiorgis
}
111 cf51ea5b Dimitris Aragiorgis
112 cf51ea5b Dimitris Aragiorgis
DHCP_REQRESP = {
113 cf51ea5b Dimitris Aragiorgis
    DHCPDISCOVER: DHCPOFFER,
114 cf51ea5b Dimitris Aragiorgis
    DHCPREQUEST: DHCPACK,
115 cf51ea5b Dimitris Aragiorgis
    DHCPINFORM: DHCPACK,
116 cf51ea5b Dimitris Aragiorgis
    }
117 cf51ea5b Dimitris Aragiorgis
118 cf51ea5b Dimitris Aragiorgis
119 cf51ea5b Dimitris Aragiorgis
def parse_routing_table(table="main", family=4):
120 cf51ea5b Dimitris Aragiorgis
    """ Parse the given routing table to get connected route, gateway and
121 cf51ea5b Dimitris Aragiorgis
    default device.
122 cf51ea5b Dimitris Aragiorgis
123 cf51ea5b Dimitris Aragiorgis
    """
124 cf51ea5b Dimitris Aragiorgis
    ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls",
125 cf51ea5b Dimitris Aragiorgis
                             "table", table], stdout=subprocess.PIPE)
126 cf51ea5b Dimitris Aragiorgis
    routes = ipro.stdout.readlines()
127 cf51ea5b Dimitris Aragiorgis
128 cf51ea5b Dimitris Aragiorgis
    def_gw = None
129 cf51ea5b Dimitris Aragiorgis
    def_dev = None
130 cf51ea5b Dimitris Aragiorgis
    def_net = None
131 cf51ea5b Dimitris Aragiorgis
132 cf51ea5b Dimitris Aragiorgis
    for route in routes:
133 cf51ea5b Dimitris Aragiorgis
        match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route)
134 cf51ea5b Dimitris Aragiorgis
        if match:
135 cf51ea5b Dimitris Aragiorgis
            def_gw, def_dev = match.groups()
136 cf51ea5b Dimitris Aragiorgis
            break
137 cf51ea5b Dimitris Aragiorgis
138 cf51ea5b Dimitris Aragiorgis
    for route in routes:
139 cf51ea5b Dimitris Aragiorgis
        # Find the least-specific connected route
140 cf51ea5b Dimitris Aragiorgis
        m = re.match("^([^\\s]+) dev %s" % def_dev, route)
141 cf51ea5b Dimitris Aragiorgis
        if not m:
142 cf51ea5b Dimitris Aragiorgis
            continue
143 cf51ea5b Dimitris Aragiorgis
144 cf51ea5b Dimitris Aragiorgis
        if family == 6 and m.group(1).startswith("fe80:"):
145 cf51ea5b Dimitris Aragiorgis
            # Skip link-local declarations in "main" table
146 cf51ea5b Dimitris Aragiorgis
            continue
147 cf51ea5b Dimitris Aragiorgis
148 cf51ea5b Dimitris Aragiorgis
        def_net = m.group(1)
149 cf51ea5b Dimitris Aragiorgis
150 cf51ea5b Dimitris Aragiorgis
        try:
151 cf51ea5b Dimitris Aragiorgis
            def_net = IPy.IP(def_net)
152 cf51ea5b Dimitris Aragiorgis
        except ValueError, e:
153 cf51ea5b Dimitris Aragiorgis
            logging.warn("Unable to parse default route entry %s: %s",
154 cf51ea5b Dimitris Aragiorgis
                         def_net, str(e))
155 cf51ea5b Dimitris Aragiorgis
156 cf51ea5b Dimitris Aragiorgis
    return Subnet(net=def_net, gw=def_gw, dev=def_dev)
157 cf51ea5b Dimitris Aragiorgis
158 cf51ea5b Dimitris Aragiorgis
159 cf51ea5b Dimitris Aragiorgis
def parse_binding_file(path):
160 cf51ea5b Dimitris Aragiorgis
    """ Read a client configuration from a tap file
161 cf51ea5b Dimitris Aragiorgis
162 cf51ea5b Dimitris Aragiorgis
    """
163 cf51ea5b Dimitris Aragiorgis
    try:
164 cf51ea5b Dimitris Aragiorgis
        iffile = open(path, 'r')
165 cf51ea5b Dimitris Aragiorgis
    except EnvironmentError, e:
166 cf51ea5b Dimitris Aragiorgis
        logging.warn("Unable to open binding file %s: %s", path, str(e))
167 cf51ea5b Dimitris Aragiorgis
        return None
168 cf51ea5b Dimitris Aragiorgis
169 cf51ea5b Dimitris Aragiorgis
    ifname = os.path.basename(path)
170 cf51ea5b Dimitris Aragiorgis
    mac = None
171 cf51ea5b Dimitris Aragiorgis
    ips = None
172 cf51ea5b Dimitris Aragiorgis
    link = None
173 cf51ea5b Dimitris Aragiorgis
    hostname = None
174 cf51ea5b Dimitris Aragiorgis
175 cf51ea5b Dimitris Aragiorgis
    for line in iffile:
176 cf51ea5b Dimitris Aragiorgis
        if line.startswith("IP="):
177 cf51ea5b Dimitris Aragiorgis
            ip = line.strip().split("=")[1]
178 cf51ea5b Dimitris Aragiorgis
            ips = ip.split()
179 cf51ea5b Dimitris Aragiorgis
        elif line.startswith("MAC="):
180 cf51ea5b Dimitris Aragiorgis
            mac = line.strip().split("=")[1]
181 cf51ea5b Dimitris Aragiorgis
        elif line.startswith("LINK="):
182 cf51ea5b Dimitris Aragiorgis
            link = line.strip().split("=")[1]
183 cf51ea5b Dimitris Aragiorgis
        elif line.startswith("HOSTNAME="):
184 cf51ea5b Dimitris Aragiorgis
            hostname = line.strip().split("=")[1]
185 cf51ea5b Dimitris Aragiorgis
        elif line.startswith("IFACE="):
186 cf51ea5b Dimitris Aragiorgis
            iface = line.strip().split("=")[1]
187 cf51ea5b Dimitris Aragiorgis
188 cf51ea5b Dimitris Aragiorgis
    return Client(ifname=ifname, mac=mac, ips=ips, link=link, hostname=hostname, iface=iface)
189 cf51ea5b Dimitris Aragiorgis
190 cf51ea5b Dimitris Aragiorgis
191 cf51ea5b Dimitris Aragiorgis
class ClientFileHandler(pyinotify.ProcessEvent):
192 cf51ea5b Dimitris Aragiorgis
    def __init__(self, server):
193 cf51ea5b Dimitris Aragiorgis
        pyinotify.ProcessEvent.__init__(self)
194 cf51ea5b Dimitris Aragiorgis
        self.server = server
195 cf51ea5b Dimitris Aragiorgis
196 cf51ea5b Dimitris Aragiorgis
    def process_IN_DELETE(self, event): # pylint: disable=C0103
197 cf51ea5b Dimitris Aragiorgis
        """ Delete file handler
198 cf51ea5b Dimitris Aragiorgis
199 cf51ea5b Dimitris Aragiorgis
        Currently this removes an interface from the watch list
200 cf51ea5b Dimitris Aragiorgis
201 cf51ea5b Dimitris Aragiorgis
        """
202 cf51ea5b Dimitris Aragiorgis
        self.server.remove_iface(event.name)
203 cf51ea5b Dimitris Aragiorgis
204 cf51ea5b Dimitris Aragiorgis
    def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
205 cf51ea5b Dimitris Aragiorgis
        """ Add file handler
206 cf51ea5b Dimitris Aragiorgis
207 cf51ea5b Dimitris Aragiorgis
        Currently this adds an interface to the watch list
208 cf51ea5b Dimitris Aragiorgis
209 cf51ea5b Dimitris Aragiorgis
        """
210 cf51ea5b Dimitris Aragiorgis
        self.server.add_iface(os.path.join(event.path, event.name))
211 cf51ea5b Dimitris Aragiorgis
212 cf51ea5b Dimitris Aragiorgis
213 cf51ea5b Dimitris Aragiorgis
class Client(object):
214 cf51ea5b Dimitris Aragiorgis
    def __init__(self, ifname=None, mac=None, ips=None, link=None, hostname=None, iface=None):
215 cf51ea5b Dimitris Aragiorgis
        self.mac = mac
216 cf51ea5b Dimitris Aragiorgis
        self.ips = ips
217 cf51ea5b Dimitris Aragiorgis
        self.hostname = hostname
218 cf51ea5b Dimitris Aragiorgis
        self.link = link
219 cf51ea5b Dimitris Aragiorgis
        self.iface = iface
220 cf51ea5b Dimitris Aragiorgis
        self.ifname = ifname
221 cf51ea5b Dimitris Aragiorgis
222 cf51ea5b Dimitris Aragiorgis
    @property
223 cf51ea5b Dimitris Aragiorgis
    def ip(self):
224 cf51ea5b Dimitris Aragiorgis
        return self.ips[0]
225 cf51ea5b Dimitris Aragiorgis
226 cf51ea5b Dimitris Aragiorgis
    def is_valid(self):
227 cf51ea5b Dimitris Aragiorgis
        return self.mac is not None and self.ips is not None\
228 cf51ea5b Dimitris Aragiorgis
               and self.hostname is not None
229 cf51ea5b Dimitris Aragiorgis
230 cf51ea5b Dimitris Aragiorgis
231 cf51ea5b Dimitris Aragiorgis
class Subnet(object):
232 cf51ea5b Dimitris Aragiorgis
    def __init__(self, net=None, gw=None, dev=None):
233 cf51ea5b Dimitris Aragiorgis
        if isinstance(net, str):
234 cf51ea5b Dimitris Aragiorgis
            self.net = IPy.IP(net)
235 cf51ea5b Dimitris Aragiorgis
        else:
236 cf51ea5b Dimitris Aragiorgis
            self.net = net
237 cf51ea5b Dimitris Aragiorgis
        self.gw = gw
238 cf51ea5b Dimitris Aragiorgis
        self.dev = dev
239 cf51ea5b Dimitris Aragiorgis
240 cf51ea5b Dimitris Aragiorgis
    @property
241 cf51ea5b Dimitris Aragiorgis
    def netmask(self):
242 cf51ea5b Dimitris Aragiorgis
        """ Return the netmask in textual representation
243 cf51ea5b Dimitris Aragiorgis
244 cf51ea5b Dimitris Aragiorgis
        """
245 cf51ea5b Dimitris Aragiorgis
        return str(self.net.netmask())
246 cf51ea5b Dimitris Aragiorgis
247 cf51ea5b Dimitris Aragiorgis
    @property
248 cf51ea5b Dimitris Aragiorgis
    def broadcast(self):
249 cf51ea5b Dimitris Aragiorgis
        """ Return the broadcast address in textual representation
250 cf51ea5b Dimitris Aragiorgis
251 cf51ea5b Dimitris Aragiorgis
        """
252 cf51ea5b Dimitris Aragiorgis
        return str(self.net.broadcast())
253 cf51ea5b Dimitris Aragiorgis
254 cf51ea5b Dimitris Aragiorgis
    @property
255 cf51ea5b Dimitris Aragiorgis
    def prefix(self):
256 cf51ea5b Dimitris Aragiorgis
        """ Return the network as an IPy.IP
257 cf51ea5b Dimitris Aragiorgis
258 cf51ea5b Dimitris Aragiorgis
        """
259 cf51ea5b Dimitris Aragiorgis
        return self.net.net()
260 cf51ea5b Dimitris Aragiorgis
261 cf51ea5b Dimitris Aragiorgis
    @property
262 cf51ea5b Dimitris Aragiorgis
    def prefixlen(self):
263 cf51ea5b Dimitris Aragiorgis
        """ Return the prefix length as an integer
264 cf51ea5b Dimitris Aragiorgis
265 cf51ea5b Dimitris Aragiorgis
        """
266 cf51ea5b Dimitris Aragiorgis
        return self.net.prefixlen()
267 cf51ea5b Dimitris Aragiorgis
268 cf51ea5b Dimitris Aragiorgis
    @staticmethod
269 cf51ea5b Dimitris Aragiorgis
    def _make_eui64(net, mac):
270 cf51ea5b Dimitris Aragiorgis
        """ Compute an EUI-64 address from an EUI-48 (MAC) address
271 cf51ea5b Dimitris Aragiorgis
272 cf51ea5b Dimitris Aragiorgis
        """
273 cf51ea5b Dimitris Aragiorgis
        comp = mac.split(":")
274 cf51ea5b Dimitris Aragiorgis
        prefix = IPy.IP(net).net().strFullsize().split(":")[:4]
275 cf51ea5b Dimitris Aragiorgis
        eui64 = comp[:3] + ["ff", "fe"] + comp[3:]
276 cf51ea5b Dimitris Aragiorgis
        eui64[0] = "%02x" % (int(eui64[0], 16) ^ 0x02)
277 cf51ea5b Dimitris Aragiorgis
        for l in range(0, len(eui64), 2):
278 cf51ea5b Dimitris Aragiorgis
            prefix += ["".join(eui64[l:l+2])]
279 cf51ea5b Dimitris Aragiorgis
        return IPy.IP(":".join(prefix))
280 cf51ea5b Dimitris Aragiorgis
281 cf51ea5b Dimitris Aragiorgis
    def make_eui64(self, mac):
282 cf51ea5b Dimitris Aragiorgis
        """ Compute an EUI-64 address from an EUI-48 (MAC) address in this
283 cf51ea5b Dimitris Aragiorgis
        subnet.
284 cf51ea5b Dimitris Aragiorgis
285 cf51ea5b Dimitris Aragiorgis
        """
286 cf51ea5b Dimitris Aragiorgis
        return self._make_eui64(self.net, mac)
287 cf51ea5b Dimitris Aragiorgis
288 cf51ea5b Dimitris Aragiorgis
    def make_ll64(self, mac):
289 cf51ea5b Dimitris Aragiorgis
        """ Compute an IPv6 Link-local address from an EUI-48 (MAC) address
290 cf51ea5b Dimitris Aragiorgis
291 cf51ea5b Dimitris Aragiorgis
        """
292 cf51ea5b Dimitris Aragiorgis
        return self._make_eui64("fe80::", mac)
293 cf51ea5b Dimitris Aragiorgis
294 cf51ea5b Dimitris Aragiorgis
295 cf51ea5b Dimitris Aragiorgis
class VMNetProxy(object): # pylint: disable=R0902
296 cf51ea5b Dimitris Aragiorgis
    def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
297 cf51ea5b Dimitris Aragiorgis
                 rs_queue_num=None, ns_queue_num=None,
298 cf51ea5b Dimitris Aragiorgis
                 dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME,
299 cf51ea5b Dimitris Aragiorgis
                 dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL,
300 cf51ea5b Dimitris Aragiorgis
                 dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
301 cf51ea5b Dimitris Aragiorgis
                 ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
302 cf51ea5b Dimitris Aragiorgis
303 cf51ea5b Dimitris Aragiorgis
        self.data_path = data_path
304 cf51ea5b Dimitris Aragiorgis
        self.lease_lifetime = dhcp_lease_lifetime
305 cf51ea5b Dimitris Aragiorgis
        self.lease_renewal = dhcp_lease_renewal
306 cf51ea5b Dimitris Aragiorgis
        self.dhcp_server_ip = dhcp_server_ip
307 cf51ea5b Dimitris Aragiorgis
        self.ra_period = ra_period
308 cf51ea5b Dimitris Aragiorgis
        if dhcp_nameservers is None:
309 cf51ea5b Dimitris Aragiorgis
            self.dhcp_nameserver = []
310 cf51ea5b Dimitris Aragiorgis
        else:
311 cf51ea5b Dimitris Aragiorgis
            self.dhcp_nameservers = dhcp_nameservers
312 cf51ea5b Dimitris Aragiorgis
313 cf51ea5b Dimitris Aragiorgis
        if ipv6_nameservers is None:
314 cf51ea5b Dimitris Aragiorgis
            self.ipv6_nameservers = []
315 cf51ea5b Dimitris Aragiorgis
        else:
316 cf51ea5b Dimitris Aragiorgis
            self.ipv6_nameservers = ipv6_nameservers
317 cf51ea5b Dimitris Aragiorgis
318 cf51ea5b Dimitris Aragiorgis
        self.ipv6_enabled = False
319 cf51ea5b Dimitris Aragiorgis
320 cf51ea5b Dimitris Aragiorgis
        self.clients = {}
321 cf51ea5b Dimitris Aragiorgis
        self.subnets = {}
322 cf51ea5b Dimitris Aragiorgis
        self.ifaces = {}
323 cf51ea5b Dimitris Aragiorgis
        self.v6nets = {}
324 cf51ea5b Dimitris Aragiorgis
        self.nfq = {}
325 cf51ea5b Dimitris Aragiorgis
        self.l2socket = socket.socket(socket.AF_PACKET,
326 cf51ea5b Dimitris Aragiorgis
                                      socket.SOCK_RAW, ETH_P_ALL)
327 cf51ea5b Dimitris Aragiorgis
        self.l2socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 0)
328 cf51ea5b Dimitris Aragiorgis
329 cf51ea5b Dimitris Aragiorgis
        # Inotify setup
330 cf51ea5b Dimitris Aragiorgis
        self.wm = pyinotify.WatchManager()
331 cf51ea5b Dimitris Aragiorgis
        mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"]
332 cf51ea5b Dimitris Aragiorgis
        mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"]
333 cf51ea5b Dimitris Aragiorgis
        inotify_handler = ClientFileHandler(self)
334 cf51ea5b Dimitris Aragiorgis
        self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
335 cf51ea5b Dimitris Aragiorgis
        self.wm.add_watch(self.data_path, mask, rec=True)
336 cf51ea5b Dimitris Aragiorgis
337 cf51ea5b Dimitris Aragiorgis
        # NFQUEUE setup
338 cf51ea5b Dimitris Aragiorgis
        if dhcp_queue_num is not None:
339 cf51ea5b Dimitris Aragiorgis
            self._setup_nfqueue(dhcp_queue_num, AF_INET, self.dhcp_response)
340 cf51ea5b Dimitris Aragiorgis
341 cf51ea5b Dimitris Aragiorgis
        if rs_queue_num is not None:
342 cf51ea5b Dimitris Aragiorgis
            self._setup_nfqueue(rs_queue_num, AF_INET6, self.rs_response)
343 cf51ea5b Dimitris Aragiorgis
            self.ipv6_enabled = True
344 cf51ea5b Dimitris Aragiorgis
345 cf51ea5b Dimitris Aragiorgis
        if ns_queue_num is not None:
346 cf51ea5b Dimitris Aragiorgis
            self._setup_nfqueue(ns_queue_num, AF_INET6, self.ns_response)
347 cf51ea5b Dimitris Aragiorgis
            self.ipv6_enabled = True
348 cf51ea5b Dimitris Aragiorgis
349 cf51ea5b Dimitris Aragiorgis
    def _cleanup(self):
350 cf51ea5b Dimitris Aragiorgis
        """ Free all resources for a graceful exit
351 cf51ea5b Dimitris Aragiorgis
352 cf51ea5b Dimitris Aragiorgis
        """
353 cf51ea5b Dimitris Aragiorgis
        logging.info("Cleaning up")
354 cf51ea5b Dimitris Aragiorgis
355 cf51ea5b Dimitris Aragiorgis
        logging.debug("Closing netfilter queues")
356 cf51ea5b Dimitris Aragiorgis
        for q in self.nfq.values():
357 cf51ea5b Dimitris Aragiorgis
            q.close()
358 cf51ea5b Dimitris Aragiorgis
359 cf51ea5b Dimitris Aragiorgis
        logging.debug("Closing socket")
360 cf51ea5b Dimitris Aragiorgis
        self.l2socket.close()
361 cf51ea5b Dimitris Aragiorgis
362 cf51ea5b Dimitris Aragiorgis
        logging.debug("Stopping inotify watches")
363 cf51ea5b Dimitris Aragiorgis
        self.notifier.stop()
364 cf51ea5b Dimitris Aragiorgis
365 cf51ea5b Dimitris Aragiorgis
        logging.info("Cleanup finished")
366 cf51ea5b Dimitris Aragiorgis
367 cf51ea5b Dimitris Aragiorgis
    def _setup_nfqueue(self, queue_num, family, callback):
368 cf51ea5b Dimitris Aragiorgis
        logging.debug("Setting up NFQUEUE for queue %d, AF %s",
369 cf51ea5b Dimitris Aragiorgis
                      queue_num, family)
370 cf51ea5b Dimitris Aragiorgis
        q = nfqueue.queue()
371 cf51ea5b Dimitris Aragiorgis
        q.set_callback(callback)
372 cf51ea5b Dimitris Aragiorgis
        q.fast_open(queue_num, family)
373 cf51ea5b Dimitris Aragiorgis
        q.set_queue_maxlen(5000)
374 cf51ea5b Dimitris Aragiorgis
        # This is mandatory for the queue to operate
375 cf51ea5b Dimitris Aragiorgis
        q.set_mode(nfqueue.NFQNL_COPY_PACKET)
376 cf51ea5b Dimitris Aragiorgis
        self.nfq[q.get_fd()] = q
377 cf51ea5b Dimitris Aragiorgis
378 cf51ea5b Dimitris Aragiorgis
    def sendp(self, data, iface):
379 cf51ea5b Dimitris Aragiorgis
        """ Send a raw packet using a layer-2 socket
380 cf51ea5b Dimitris Aragiorgis
381 cf51ea5b Dimitris Aragiorgis
        """
382 cf51ea5b Dimitris Aragiorgis
        if isinstance(data, BasePacket):
383 cf51ea5b Dimitris Aragiorgis
            data = str(data)
384 cf51ea5b Dimitris Aragiorgis
385 cf51ea5b Dimitris Aragiorgis
        self.l2socket.bind((iface, ETH_P_ALL))
386 cf51ea5b Dimitris Aragiorgis
        count = self.l2socket.send(data)
387 cf51ea5b Dimitris Aragiorgis
        ldata = len(data)
388 cf51ea5b Dimitris Aragiorgis
        if count != ldata:
389 cf51ea5b Dimitris Aragiorgis
            logging.warn("Truncated send on %s (%d/%d bytes sent)",
390 cf51ea5b Dimitris Aragiorgis
                         iface, count, ldata)
391 cf51ea5b Dimitris Aragiorgis
392 cf51ea5b Dimitris Aragiorgis
    def build_config(self):
393 cf51ea5b Dimitris Aragiorgis
        self.clients.clear()
394 cf51ea5b Dimitris Aragiorgis
        self.subnets.clear()
395 cf51ea5b Dimitris Aragiorgis
396 cf51ea5b Dimitris Aragiorgis
        for path in glob.glob(os.path.join(self.data_path, "*")):
397 cf51ea5b Dimitris Aragiorgis
            self.add_iface(path)
398 cf51ea5b Dimitris Aragiorgis
399 cf51ea5b Dimitris Aragiorgis
    def get_ifindex(self, iface):
400 cf51ea5b Dimitris Aragiorgis
        """ Get the interface index from sysfs
401 cf51ea5b Dimitris Aragiorgis
402 cf51ea5b Dimitris Aragiorgis
        """
403 cf51ea5b Dimitris Aragiorgis
        path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
404 cf51ea5b Dimitris Aragiorgis
        if not path.startswith(SYSFS_NET):
405 cf51ea5b Dimitris Aragiorgis
            return None
406 cf51ea5b Dimitris Aragiorgis
407 cf51ea5b Dimitris Aragiorgis
        ifindex = None
408 cf51ea5b Dimitris Aragiorgis
409 cf51ea5b Dimitris Aragiorgis
        try:
410 cf51ea5b Dimitris Aragiorgis
            f = open(path, 'r')
411 cf51ea5b Dimitris Aragiorgis
        except EnvironmentError:
412 cf51ea5b Dimitris Aragiorgis
            logging.debug("%s is probably down, removing", iface)
413 cf51ea5b Dimitris Aragiorgis
            self.remove_iface(iface)
414 cf51ea5b Dimitris Aragiorgis
415 cf51ea5b Dimitris Aragiorgis
            return ifindex
416 cf51ea5b Dimitris Aragiorgis
417 cf51ea5b Dimitris Aragiorgis
        try:
418 cf51ea5b Dimitris Aragiorgis
            ifindex = f.readline().strip()
419 cf51ea5b Dimitris Aragiorgis
            try:
420 cf51ea5b Dimitris Aragiorgis
                ifindex = int(ifindex)
421 cf51ea5b Dimitris Aragiorgis
            except ValueError, e:
422 cf51ea5b Dimitris Aragiorgis
                logging.warn("Failed to get ifindex for %s, cannot parse sysfs"
423 cf51ea5b Dimitris Aragiorgis
                             " output '%s'", iface, ifindex)
424 cf51ea5b Dimitris Aragiorgis
        except EnvironmentError, e:
425 cf51ea5b Dimitris Aragiorgis
            logging.warn("Error reading %s's ifindex from sysfs: %s",
426 cf51ea5b Dimitris Aragiorgis
                         iface, str(e))
427 cf51ea5b Dimitris Aragiorgis
            self.remove_iface(iface)
428 cf51ea5b Dimitris Aragiorgis
        finally:
429 cf51ea5b Dimitris Aragiorgis
            f.close()
430 cf51ea5b Dimitris Aragiorgis
431 cf51ea5b Dimitris Aragiorgis
        return ifindex
432 cf51ea5b Dimitris Aragiorgis
433 cf51ea5b Dimitris Aragiorgis
434 cf51ea5b Dimitris Aragiorgis
    def get_iface_hw_addr(self, iface):
435 cf51ea5b Dimitris Aragiorgis
        """ Get the interface hardware address from sysfs
436 cf51ea5b Dimitris Aragiorgis
437 cf51ea5b Dimitris Aragiorgis
        """
438 cf51ea5b Dimitris Aragiorgis
        path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
439 cf51ea5b Dimitris Aragiorgis
        if not path.startswith(SYSFS_NET):
440 cf51ea5b Dimitris Aragiorgis
            return None
441 cf51ea5b Dimitris Aragiorgis
442 cf51ea5b Dimitris Aragiorgis
        addr = None
443 cf51ea5b Dimitris Aragiorgis
        try:
444 cf51ea5b Dimitris Aragiorgis
            f = open(path, 'r')
445 cf51ea5b Dimitris Aragiorgis
        except EnvironmentError:
446 cf51ea5b Dimitris Aragiorgis
            logging.debug("%s is probably down, removing", iface)
447 cf51ea5b Dimitris Aragiorgis
            self.remove_iface(iface)
448 cf51ea5b Dimitris Aragiorgis
            return addr
449 cf51ea5b Dimitris Aragiorgis
450 cf51ea5b Dimitris Aragiorgis
        try:
451 cf51ea5b Dimitris Aragiorgis
            addr = f.readline().strip()
452 cf51ea5b Dimitris Aragiorgis
        except EnvironmentError, e:
453 cf51ea5b Dimitris Aragiorgis
            logging.warn("Failed to read hw address for %s from sysfs: %s",
454 cf51ea5b Dimitris Aragiorgis
                         iface, str(e))
455 cf51ea5b Dimitris Aragiorgis
        finally:
456 cf51ea5b Dimitris Aragiorgis
            f.close()
457 cf51ea5b Dimitris Aragiorgis
458 cf51ea5b Dimitris Aragiorgis
        return addr
459 cf51ea5b Dimitris Aragiorgis
460 cf51ea5b Dimitris Aragiorgis
    def add_iface(self, path):
461 cf51ea5b Dimitris Aragiorgis
        """ Add an interface to monitor
462 cf51ea5b Dimitris Aragiorgis
463 cf51ea5b Dimitris Aragiorgis
        """
464 cf51ea5b Dimitris Aragiorgis
        iface = os.path.basename(path)
465 cf51ea5b Dimitris Aragiorgis
466 cf51ea5b Dimitris Aragiorgis
        logging.debug("Updating configuration for %s", iface)
467 cf51ea5b Dimitris Aragiorgis
        binding = parse_binding_file(path)
468 cf51ea5b Dimitris Aragiorgis
        if binding is None:
469 cf51ea5b Dimitris Aragiorgis
            return
470 cf51ea5b Dimitris Aragiorgis
        ifindex = self.get_ifindex(binding.iface)
471 cf51ea5b Dimitris Aragiorgis
472 cf51ea5b Dimitris Aragiorgis
        if ifindex is None:
473 cf51ea5b Dimitris Aragiorgis
            logging.warn("Stale configuration for %s found", iface)
474 cf51ea5b Dimitris Aragiorgis
        else:
475 cf51ea5b Dimitris Aragiorgis
            if binding.is_valid():
476 cf51ea5b Dimitris Aragiorgis
                self.clients[binding.mac] = binding
477 cf51ea5b Dimitris Aragiorgis
                self.subnets[binding.link] = parse_routing_table(binding.link)
478 cf51ea5b Dimitris Aragiorgis
                logging.debug("Added client %s on %s", binding.hostname, iface)
479 cf51ea5b Dimitris Aragiorgis
                self.ifaces[ifindex] = binding.iface
480 cf51ea5b Dimitris Aragiorgis
                self.v6nets[iface] = parse_routing_table(binding.link, 6)
481 cf51ea5b Dimitris Aragiorgis
482 cf51ea5b Dimitris Aragiorgis
    def remove_iface(self, ifname):
483 cf51ea5b Dimitris Aragiorgis
        """ Cleanup clients on a removed interface
484 cf51ea5b Dimitris Aragiorgis
485 cf51ea5b Dimitris Aragiorgis
        """
486 cf51ea5b Dimitris Aragiorgis
        if ifname in self.v6nets:
487 cf51ea5b Dimitris Aragiorgis
            del self.v6nets[ifname]
488 cf51ea5b Dimitris Aragiorgis
489 cf51ea5b Dimitris Aragiorgis
        for mac in self.clients.keys():
490 cf51ea5b Dimitris Aragiorgis
            if self.clients[mac].ifname == ifname:
491 cf51ea5b Dimitris Aragiorgis
                iface = self.client[mac].iface
492 cf51ea5b Dimitris Aragiorgis
                del self.clients[mac]
493 cf51ea5b Dimitris Aragiorgis
494 cf51ea5b Dimitris Aragiorgis
        for ifindex in self.ifaces.keys():
495 cf51ea5b Dimitris Aragiorgis
            if self.ifaces[ifindex] == ifname == iface:
496 cf51ea5b Dimitris Aragiorgis
                del self.ifaces[ifindex]
497 cf51ea5b Dimitris Aragiorgis
498 cf51ea5b Dimitris Aragiorgis
        logging.debug("Removed interface %s", ifname)
499 cf51ea5b Dimitris Aragiorgis
500 cf51ea5b Dimitris Aragiorgis
    def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
501 cf51ea5b Dimitris Aragiorgis
        """ Generate a reply to a BOOTP/DHCP request
502 cf51ea5b Dimitris Aragiorgis
503 cf51ea5b Dimitris Aragiorgis
        """
504 cf51ea5b Dimitris Aragiorgis
        logging.info("%s",payload)
505 cf51ea5b Dimitris Aragiorgis
        indev = payload.get_indev()
506 cf51ea5b Dimitris Aragiorgis
        try:
507 cf51ea5b Dimitris Aragiorgis
            # Get the actual interface from the ifindex
508 cf51ea5b Dimitris Aragiorgis
            iface = self.ifaces[indev]
509 cf51ea5b Dimitris Aragiorgis
        except KeyError:
510 cf51ea5b Dimitris Aragiorgis
            # We don't know anything about this interface, so accept the packet
511 cf51ea5b Dimitris Aragiorgis
            # and return
512 cf51ea5b Dimitris Aragiorgis
            logging.debug("Ignoring DHCP request on unknown iface %d", indev)
513 cf51ea5b Dimitris Aragiorgis
            # We don't know what to do with this packet, so let the kernel
514 cf51ea5b Dimitris Aragiorgis
            # handle it
515 cf51ea5b Dimitris Aragiorgis
            payload.set_verdict(nfqueue.NF_ACCEPT)
516 cf51ea5b Dimitris Aragiorgis
            return
517 cf51ea5b Dimitris Aragiorgis
518 cf51ea5b Dimitris Aragiorgis
        # Decode the response - NFQUEUE relays IP packets
519 cf51ea5b Dimitris Aragiorgis
        pkt = IP(payload.get_data())
520 cf51ea5b Dimitris Aragiorgis
521 cf51ea5b Dimitris Aragiorgis
        # Signal the kernel that it shouldn't further process the packet
522 cf51ea5b Dimitris Aragiorgis
        payload.set_verdict(nfqueue.NF_DROP)
523 cf51ea5b Dimitris Aragiorgis
524 cf51ea5b Dimitris Aragiorgis
        # Get the client MAC address
525 cf51ea5b Dimitris Aragiorgis
        resp = pkt.getlayer(BOOTP).copy()
526 cf51ea5b Dimitris Aragiorgis
        hlen = resp.hlen
527 cf51ea5b Dimitris Aragiorgis
        mac = resp.chaddr[:hlen].encode("hex")
528 cf51ea5b Dimitris Aragiorgis
        mac, _ = re.subn(r'([0-9a-fA-F]{2})', r'\1:', mac, hlen-1)
529 cf51ea5b Dimitris Aragiorgis
530 cf51ea5b Dimitris Aragiorgis
	logging.info("%s %s %s ", resp, hlen, mac)
531 cf51ea5b Dimitris Aragiorgis
        # Server responses are always BOOTREPLYs
532 cf51ea5b Dimitris Aragiorgis
        resp.op = "BOOTREPLY"
533 cf51ea5b Dimitris Aragiorgis
        del resp.payload
534 cf51ea5b Dimitris Aragiorgis
535 cf51ea5b Dimitris Aragiorgis
        try:
536 cf51ea5b Dimitris Aragiorgis
            binding = self.clients[mac]
537 cf51ea5b Dimitris Aragiorgis
        except KeyError:
538 cf51ea5b Dimitris Aragiorgis
            logging.warn("Invalid client %s on %s", mac, iface)
539 cf51ea5b Dimitris Aragiorgis
            return
540 cf51ea5b Dimitris Aragiorgis
541 cf51ea5b Dimitris Aragiorgis
        if iface != binding.iface:
542 cf51ea5b Dimitris Aragiorgis
            logging.warn("Received spoofed DHCP request for %s from interface"
543 cf51ea5b Dimitris Aragiorgis
                         " %s instead of %s", mac, iface, binding.iface)
544 cf51ea5b Dimitris Aragiorgis
            return
545 cf51ea5b Dimitris Aragiorgis
546 cf51ea5b Dimitris Aragiorgis
        resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\
547 cf51ea5b Dimitris Aragiorgis
               IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
548 cf51ea5b Dimitris Aragiorgis
               UDP(sport=pkt.dport, dport=pkt.sport)/resp
549 cf51ea5b Dimitris Aragiorgis
        subnet = self.subnets[binding.link]
550 cf51ea5b Dimitris Aragiorgis
551 cf51ea5b Dimitris Aragiorgis
        if not DHCP in pkt:
552 cf51ea5b Dimitris Aragiorgis
            logging.warn("Invalid request from %s on %s, no DHCP"
553 cf51ea5b Dimitris Aragiorgis
                         " payload found", binding.mac, iface)
554 cf51ea5b Dimitris Aragiorgis
            return
555 cf51ea5b Dimitris Aragiorgis
556 cf51ea5b Dimitris Aragiorgis
        dhcp_options = []
557 cf51ea5b Dimitris Aragiorgis
        requested_addr = binding.ip
558 cf51ea5b Dimitris Aragiorgis
        for opt in pkt[DHCP].options:
559 cf51ea5b Dimitris Aragiorgis
            if type(opt) is tuple and opt[0] == "message-type":
560 cf51ea5b Dimitris Aragiorgis
                req_type = opt[1]
561 cf51ea5b Dimitris Aragiorgis
            if type(opt) is tuple and opt[0] == "requested_addr":
562 cf51ea5b Dimitris Aragiorgis
                requested_addr = opt[1]
563 cf51ea5b Dimitris Aragiorgis
564 cf51ea5b Dimitris Aragiorgis
        logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
565 cf51ea5b Dimitris Aragiorgis
                     binding.mac, iface)
566 cf51ea5b Dimitris Aragiorgis
567 cf51ea5b Dimitris Aragiorgis
        if req_type == DHCPREQUEST and requested_addr != binding.ip:
568 cf51ea5b Dimitris Aragiorgis
            resp_type = DHCPNAK
569 cf51ea5b Dimitris Aragiorgis
            logging.info("Sending DHCPNAK to %s on %s: requested %s"
570 cf51ea5b Dimitris Aragiorgis
                         " instead of %s", binding.mac, iface, requested_addr,
571 cf51ea5b Dimitris Aragiorgis
                         binding.ip)
572 cf51ea5b Dimitris Aragiorgis
573 cf51ea5b Dimitris Aragiorgis
        elif req_type in (DHCPDISCOVER, DHCPREQUEST):
574 cf51ea5b Dimitris Aragiorgis
            resp_type = DHCP_REQRESP[req_type]
575 cf51ea5b Dimitris Aragiorgis
            resp.yiaddr = self.clients[mac].ip
576 cf51ea5b Dimitris Aragiorgis
            dhcp_options += [
577 cf51ea5b Dimitris Aragiorgis
                 ("hostname", binding.hostname),
578 cf51ea5b Dimitris Aragiorgis
                 ("domain", binding.hostname.split('.', 1)[-1]),
579 cf51ea5b Dimitris Aragiorgis
                 ("router", subnet.gw),
580 cf51ea5b Dimitris Aragiorgis
                 ("broadcast_address", str(subnet.broadcast)),
581 cf51ea5b Dimitris Aragiorgis
                 ("subnet_mask", str(subnet.netmask)),
582 cf51ea5b Dimitris Aragiorgis
                 ("renewal_time", self.lease_renewal),
583 cf51ea5b Dimitris Aragiorgis
                 ("lease_time", self.lease_lifetime),
584 cf51ea5b Dimitris Aragiorgis
            ]
585 cf51ea5b Dimitris Aragiorgis
            dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
586 cf51ea5b Dimitris Aragiorgis
587 cf51ea5b Dimitris Aragiorgis
        elif req_type == DHCPINFORM:
588 cf51ea5b Dimitris Aragiorgis
            resp_type = DHCP_REQRESP[req_type]
589 cf51ea5b Dimitris Aragiorgis
            dhcp_options += [
590 cf51ea5b Dimitris Aragiorgis
                 ("hostname", binding.hostname),
591 cf51ea5b Dimitris Aragiorgis
                 ("domain", binding.hostname.split('.', 1)[-1]),
592 cf51ea5b Dimitris Aragiorgis
            ]
593 cf51ea5b Dimitris Aragiorgis
            dhcp_options += [("name_server", x) for x in self.dhcp_nameservers]
594 cf51ea5b Dimitris Aragiorgis
595 cf51ea5b Dimitris Aragiorgis
        elif req_type == DHCPRELEASE:
596 cf51ea5b Dimitris Aragiorgis
            # Log and ignore
597 cf51ea5b Dimitris Aragiorgis
            logging.info("DHCPRELEASE from %s on %s", binding.mac, iface)
598 cf51ea5b Dimitris Aragiorgis
            return
599 cf51ea5b Dimitris Aragiorgis
600 cf51ea5b Dimitris Aragiorgis
        # Finally, always add the server identifier and end options
601 cf51ea5b Dimitris Aragiorgis
        dhcp_options += [
602 cf51ea5b Dimitris Aragiorgis
            ("message-type", resp_type),
603 cf51ea5b Dimitris Aragiorgis
            ("server_id", DHCP_DUMMY_SERVER_IP),
604 cf51ea5b Dimitris Aragiorgis
            "end"
605 cf51ea5b Dimitris Aragiorgis
        ]
606 cf51ea5b Dimitris Aragiorgis
        resp /= DHCP(options=dhcp_options)
607 cf51ea5b Dimitris Aragiorgis
608 cf51ea5b Dimitris Aragiorgis
        logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
609 cf51ea5b Dimitris Aragiorgis
                     binding.ip, iface)
610 cf51ea5b Dimitris Aragiorgis
        self.sendp(resp, iface)
611 cf51ea5b Dimitris Aragiorgis
612 cf51ea5b Dimitris Aragiorgis
    def rs_response(self, i, payload): # pylint: disable=W0613
613 cf51ea5b Dimitris Aragiorgis
        """ Generate a reply to a BOOTP/DHCP request
614 cf51ea5b Dimitris Aragiorgis
615 cf51ea5b Dimitris Aragiorgis
        """
616 cf51ea5b Dimitris Aragiorgis
        indev = payload.get_indev()
617 cf51ea5b Dimitris Aragiorgis
        try:
618 cf51ea5b Dimitris Aragiorgis
            # Get the actual interface from the ifindex
619 cf51ea5b Dimitris Aragiorgis
            iface = self.ifaces[indev]
620 cf51ea5b Dimitris Aragiorgis
        except KeyError:
621 cf51ea5b Dimitris Aragiorgis
            logging.debug("Ignoring router solicitation on"
622 cf51ea5b Dimitris Aragiorgis
                          " unknown interface %d", indev)
623 cf51ea5b Dimitris Aragiorgis
            # We don't know what to do with this packet, so let the kernel
624 cf51ea5b Dimitris Aragiorgis
            # handle it
625 cf51ea5b Dimitris Aragiorgis
            payload.set_verdict(nfqueue.NF_ACCEPT)
626 cf51ea5b Dimitris Aragiorgis
            return
627 cf51ea5b Dimitris Aragiorgis
628 cf51ea5b Dimitris Aragiorgis
        ifmac = self.get_iface_hw_addr(iface)
629 cf51ea5b Dimitris Aragiorgis
        subnet = self.v6nets[iface]
630 cf51ea5b Dimitris Aragiorgis
        ifll = subnet.make_ll64(ifmac)
631 cf51ea5b Dimitris Aragiorgis
632 cf51ea5b Dimitris Aragiorgis
        # Signal the kernel that it shouldn't further process the packet
633 cf51ea5b Dimitris Aragiorgis
        payload.set_verdict(nfqueue.NF_DROP)
634 cf51ea5b Dimitris Aragiorgis
635 cf51ea5b Dimitris Aragiorgis
        resp = Ether(src=self.get_iface_hw_addr(iface))/\
636 cf51ea5b Dimitris Aragiorgis
               IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
637 cf51ea5b Dimitris Aragiorgis
               ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
638 cf51ea5b Dimitris Aragiorgis
                                     prefixlen=subnet.prefixlen)
639 cf51ea5b Dimitris Aragiorgis
640 cf51ea5b Dimitris Aragiorgis
        if self.ipv6_nameservers:
641 cf51ea5b Dimitris Aragiorgis
            resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
642 cf51ea5b Dimitris Aragiorgis
                                     lifetime=self.ra_period * 3)
643 cf51ea5b Dimitris Aragiorgis
644 cf51ea5b Dimitris Aragiorgis
        logging.info("RA on %s for %s", iface, subnet.net)
645 cf51ea5b Dimitris Aragiorgis
        self.sendp(resp, iface)
646 cf51ea5b Dimitris Aragiorgis
647 cf51ea5b Dimitris Aragiorgis
    def ns_response(self, i, payload): # pylint: disable=W0613
648 cf51ea5b Dimitris Aragiorgis
        """ Generate a reply to an ICMPv6 neighbor solicitation
649 cf51ea5b Dimitris Aragiorgis
650 cf51ea5b Dimitris Aragiorgis
        """
651 cf51ea5b Dimitris Aragiorgis
        indev = payload.get_indev()
652 cf51ea5b Dimitris Aragiorgis
        try:
653 cf51ea5b Dimitris Aragiorgis
            # Get the actual interface from the ifindex
654 cf51ea5b Dimitris Aragiorgis
            iface = self.ifaces[indev]
655 cf51ea5b Dimitris Aragiorgis
        except KeyError:
656 cf51ea5b Dimitris Aragiorgis
            logging.debug("Ignoring neighbour solicitation on"
657 cf51ea5b Dimitris Aragiorgis
                          " unknown interface %d", indev)
658 cf51ea5b Dimitris Aragiorgis
            # We don't know what to do with this packet, so let the kernel
659 cf51ea5b Dimitris Aragiorgis
            # handle it
660 cf51ea5b Dimitris Aragiorgis
            payload.set_verdict(nfqueue.NF_ACCEPT)
661 cf51ea5b Dimitris Aragiorgis
            return
662 cf51ea5b Dimitris Aragiorgis
663 cf51ea5b Dimitris Aragiorgis
        ifmac = self.get_iface_hw_addr(iface)
664 cf51ea5b Dimitris Aragiorgis
        subnet = self.v6nets[iface]
665 cf51ea5b Dimitris Aragiorgis
        ifll = subnet.make_ll64(ifmac)
666 cf51ea5b Dimitris Aragiorgis
667 cf51ea5b Dimitris Aragiorgis
        ns = IPv6(payload.get_data())
668 cf51ea5b Dimitris Aragiorgis
669 cf51ea5b Dimitris Aragiorgis
        if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
670 cf51ea5b Dimitris Aragiorgis
            logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
671 cf51ea5b Dimitris Aragiorgis
            payload.set_verdict(nfqueue.NF_ACCEPT)
672 cf51ea5b Dimitris Aragiorgis
            return 1
673 cf51ea5b Dimitris Aragiorgis
674 cf51ea5b Dimitris Aragiorgis
        payload.set_verdict(nfqueue.NF_DROP)
675 cf51ea5b Dimitris Aragiorgis
676 cf51ea5b Dimitris Aragiorgis
        try:
677 cf51ea5b Dimitris Aragiorgis
            client_lladdr = ns.lladdr
678 cf51ea5b Dimitris Aragiorgis
        except AttributeError:
679 cf51ea5b Dimitris Aragiorgis
            return 1
680 cf51ea5b Dimitris Aragiorgis
681 cf51ea5b Dimitris Aragiorgis
        resp = Ether(src=ifmac, dst=client_lladdr)/\
682 cf51ea5b Dimitris Aragiorgis
               IPv6(src=str(ifll), dst=ns.src)/\
683 cf51ea5b Dimitris Aragiorgis
               ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
684 cf51ea5b Dimitris Aragiorgis
               ICMPv6NDOptDstLLAddr(lladdr=ifmac)
685 cf51ea5b Dimitris Aragiorgis
686 cf51ea5b Dimitris Aragiorgis
        logging.info("NA on %s for %s", iface, ns.tgt)
687 cf51ea5b Dimitris Aragiorgis
        self.sendp(resp, iface)
688 cf51ea5b Dimitris Aragiorgis
        return 1
689 cf51ea5b Dimitris Aragiorgis
690 cf51ea5b Dimitris Aragiorgis
    def send_periodic_ra(self):
691 cf51ea5b Dimitris Aragiorgis
        # Use a separate thread as this may take a _long_ time with
692 cf51ea5b Dimitris Aragiorgis
        # many interfaces and we want to be responsive in the mean time
693 cf51ea5b Dimitris Aragiorgis
        threading.Thread(target=self._send_periodic_ra).start()
694 cf51ea5b Dimitris Aragiorgis
695 cf51ea5b Dimitris Aragiorgis
    def _send_periodic_ra(self):
696 cf51ea5b Dimitris Aragiorgis
        logging.debug("Sending out periodic RAs")
697 cf51ea5b Dimitris Aragiorgis
        start = time.time()
698 cf51ea5b Dimitris Aragiorgis
        i = 0
699 cf51ea5b Dimitris Aragiorgis
        for client in self.clients.values():
700 cf51ea5b Dimitris Aragiorgis
            iface = client.iface
701 cf51ea5b Dimitris Aragiorgis
            ifmac = self.get_iface_hw_addr(iface)
702 cf51ea5b Dimitris Aragiorgis
            if not ifmac:
703 cf51ea5b Dimitris Aragiorgis
                continue
704 cf51ea5b Dimitris Aragiorgis
705 cf51ea5b Dimitris Aragiorgis
            subnet = self.v6nets[iface]
706 cf51ea5b Dimitris Aragiorgis
            if subnet.net is None:
707 cf51ea5b Dimitris Aragiorgis
                logging.debug("Skipping periodic RA on interface %s,"
708 cf51ea5b Dimitris Aragiorgis
                              " as it is not IPv6-connected", iface)
709 cf51ea5b Dimitris Aragiorgis
                continue
710 cf51ea5b Dimitris Aragiorgis
711 cf51ea5b Dimitris Aragiorgis
            ifll = subnet.make_ll64(ifmac)
712 cf51ea5b Dimitris Aragiorgis
            resp = Ether(src=ifmac)/\
713 cf51ea5b Dimitris Aragiorgis
                   IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
714 cf51ea5b Dimitris Aragiorgis
                   ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
715 cf51ea5b Dimitris Aragiorgis
                                         prefixlen=subnet.prefixlen)
716 cf51ea5b Dimitris Aragiorgis
            if self.ipv6_nameservers:
717 cf51ea5b Dimitris Aragiorgis
                resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
718 cf51ea5b Dimitris Aragiorgis
                                         lifetime=self.ra_period * 3)
719 cf51ea5b Dimitris Aragiorgis
            try:
720 cf51ea5b Dimitris Aragiorgis
                self.sendp(resp, iface)
721 cf51ea5b Dimitris Aragiorgis
            except socket.error, e:
722 cf51ea5b Dimitris Aragiorgis
                logging.warn("Periodic RA on %s failed: %s", iface, str(e))
723 cf51ea5b Dimitris Aragiorgis
            except Exception, e:
724 cf51ea5b Dimitris Aragiorgis
                logging.warn("Unkown error during periodic RA on %s: %s",
725 cf51ea5b Dimitris Aragiorgis
                             iface, str(e))
726 cf51ea5b Dimitris Aragiorgis
            i += 1
727 cf51ea5b Dimitris Aragiorgis
        logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
728 cf51ea5b Dimitris Aragiorgis
729 cf51ea5b Dimitris Aragiorgis
    def serve(self):
730 cf51ea5b Dimitris Aragiorgis
        """ Safely perform the main loop, freeing all resources upon exit
731 cf51ea5b Dimitris Aragiorgis
732 cf51ea5b Dimitris Aragiorgis
        """
733 cf51ea5b Dimitris Aragiorgis
        try:
734 cf51ea5b Dimitris Aragiorgis
            self._serve()
735 cf51ea5b Dimitris Aragiorgis
        finally:
736 cf51ea5b Dimitris Aragiorgis
            self._cleanup()
737 cf51ea5b Dimitris Aragiorgis
738 cf51ea5b Dimitris Aragiorgis
    def _serve(self):
739 cf51ea5b Dimitris Aragiorgis
        """ Loop forever, serving DHCP requests
740 cf51ea5b Dimitris Aragiorgis
741 cf51ea5b Dimitris Aragiorgis
        """
742 cf51ea5b Dimitris Aragiorgis
        self.build_config()
743 cf51ea5b Dimitris Aragiorgis
744 cf51ea5b Dimitris Aragiorgis
        # Yes, we are accessing _fd directly, but it's the only way to have a
745 cf51ea5b Dimitris Aragiorgis
        # single select() loop ;-)
746 cf51ea5b Dimitris Aragiorgis
        iwfd = self.notifier._fd # pylint: disable=W0212
747 cf51ea5b Dimitris Aragiorgis
748 cf51ea5b Dimitris Aragiorgis
        start = time.time()
749 cf51ea5b Dimitris Aragiorgis
        if self.ipv6_enabled:
750 cf51ea5b Dimitris Aragiorgis
            timeout = self.ra_period
751 cf51ea5b Dimitris Aragiorgis
            self.send_periodic_ra()
752 cf51ea5b Dimitris Aragiorgis
        else:
753 cf51ea5b Dimitris Aragiorgis
            timeout = None
754 cf51ea5b Dimitris Aragiorgis
755 cf51ea5b Dimitris Aragiorgis
        while True:
756 cf51ea5b Dimitris Aragiorgis
            rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout)
757 cf51ea5b Dimitris Aragiorgis
            if xlist:
758 cf51ea5b Dimitris Aragiorgis
                logging.warn("Warning: Exception on %s",
759 cf51ea5b Dimitris Aragiorgis
                             ", ".join([ str(fd) for fd in xlist]))
760 cf51ea5b Dimitris Aragiorgis
761 cf51ea5b Dimitris Aragiorgis
            if rlist:
762 cf51ea5b Dimitris Aragiorgis
                if iwfd in rlist:
763 cf51ea5b Dimitris Aragiorgis
                # First check if there are any inotify (= configuration change)
764 cf51ea5b Dimitris Aragiorgis
                # events
765 cf51ea5b Dimitris Aragiorgis
                    self.notifier.read_events()
766 cf51ea5b Dimitris Aragiorgis
                    self.notifier.process_events()
767 cf51ea5b Dimitris Aragiorgis
                    rlist.remove(iwfd)
768 cf51ea5b Dimitris Aragiorgis
769 cf51ea5b Dimitris Aragiorgis
                for fd in rlist:
770 cf51ea5b Dimitris Aragiorgis
                    try:
771 cf51ea5b Dimitris Aragiorgis
                        self.nfq[fd].process_pending()
772 cf51ea5b Dimitris Aragiorgis
                    except RuntimeError, e:
773 cf51ea5b Dimitris Aragiorgis
                        logging.warn("Error processing fd %d: %s", fd, str(e))
774 cf51ea5b Dimitris Aragiorgis
                    except Exception, e:
775 cf51ea5b Dimitris Aragiorgis
                        logging.warn("Unknown error processing fd %d: %s",
776 cf51ea5b Dimitris Aragiorgis
                                     fd, str(e))
777 cf51ea5b Dimitris Aragiorgis
778 cf51ea5b Dimitris Aragiorgis
            if self.ipv6_enabled:
779 cf51ea5b Dimitris Aragiorgis
                # Calculate the new timeout
780 cf51ea5b Dimitris Aragiorgis
                timeout = self.ra_period - (time.time() - start)
781 cf51ea5b Dimitris Aragiorgis
782 cf51ea5b Dimitris Aragiorgis
                if timeout <= 0:
783 cf51ea5b Dimitris Aragiorgis
                    start = time.time()
784 cf51ea5b Dimitris Aragiorgis
                    self.send_periodic_ra()
785 cf51ea5b Dimitris Aragiorgis
                    timeout = self.ra_period - (time.time() - start)
786 cf51ea5b Dimitris Aragiorgis
787 cf51ea5b Dimitris Aragiorgis
788 cf51ea5b Dimitris Aragiorgis
if __name__ == "__main__":
789 cf51ea5b Dimitris Aragiorgis
    import capng
790 cf51ea5b Dimitris Aragiorgis
    import optparse
791 cf51ea5b Dimitris Aragiorgis
    from cStringIO import StringIO
792 cf51ea5b Dimitris Aragiorgis
    from pwd import getpwnam, getpwuid
793 cf51ea5b Dimitris Aragiorgis
    from configobj import ConfigObj, ConfigObjError, flatten_errors
794 cf51ea5b Dimitris Aragiorgis
795 cf51ea5b Dimitris Aragiorgis
    import validate
796 cf51ea5b Dimitris Aragiorgis
797 cf51ea5b Dimitris Aragiorgis
    validator = validate.Validator()
798 cf51ea5b Dimitris Aragiorgis
799 cf51ea5b Dimitris Aragiorgis
    def is_ip_list(value, family=4):
800 cf51ea5b Dimitris Aragiorgis
        try:
801 cf51ea5b Dimitris Aragiorgis
            family = int(family)
802 cf51ea5b Dimitris Aragiorgis
        except ValueError:
803 cf51ea5b Dimitris Aragiorgis
            raise validate.VdtParamError(family)
804 cf51ea5b Dimitris Aragiorgis
        if isinstance(value, (str, unicode)):
805 cf51ea5b Dimitris Aragiorgis
            value = [value]
806 cf51ea5b Dimitris Aragiorgis
        if not isinstance(value, list):
807 cf51ea5b Dimitris Aragiorgis
            raise validate.VdtTypeError(value)
808 cf51ea5b Dimitris Aragiorgis
809 cf51ea5b Dimitris Aragiorgis
        for entry in value:
810 cf51ea5b Dimitris Aragiorgis
            try:
811 cf51ea5b Dimitris Aragiorgis
                ip = IPy.IP(entry)
812 cf51ea5b Dimitris Aragiorgis
            except ValueError:
813 cf51ea5b Dimitris Aragiorgis
                raise validate.VdtValueError(entry)
814 cf51ea5b Dimitris Aragiorgis
815 cf51ea5b Dimitris Aragiorgis
            if ip.version() != family:
816 cf51ea5b Dimitris Aragiorgis
                raise validate.VdtValueError(entry)
817 cf51ea5b Dimitris Aragiorgis
        return value
818 cf51ea5b Dimitris Aragiorgis
819 cf51ea5b Dimitris Aragiorgis
    validator.functions["ip_addr_list"] = is_ip_list
820 cf51ea5b Dimitris Aragiorgis
    config_spec = StringIO(CONFIG_SPEC)
821 cf51ea5b Dimitris Aragiorgis
822 cf51ea5b Dimitris Aragiorgis
823 cf51ea5b Dimitris Aragiorgis
    parser = optparse.OptionParser()
824 cf51ea5b Dimitris Aragiorgis
    parser.add_option("-c", "--config", dest="config_file",
825 cf51ea5b Dimitris Aragiorgis
                      help="The location of the data files", metavar="FILE",
826 cf51ea5b Dimitris Aragiorgis
                      default=DEFAULT_CONFIG)
827 cf51ea5b Dimitris Aragiorgis
    parser.add_option("-d", "--debug", action="store_true", dest="debug",
828 cf51ea5b Dimitris Aragiorgis
                      help="Turn on debugging messages")
829 cf51ea5b Dimitris Aragiorgis
    parser.add_option("-f", "--foreground", action="store_false",
830 cf51ea5b Dimitris Aragiorgis
                      dest="daemonize", default=True,
831 cf51ea5b Dimitris Aragiorgis
                      help="Do not daemonize, stay in the foreground")
832 cf51ea5b Dimitris Aragiorgis
833 cf51ea5b Dimitris Aragiorgis
834 cf51ea5b Dimitris Aragiorgis
    opts, args = parser.parse_args()
835 cf51ea5b Dimitris Aragiorgis
836 cf51ea5b Dimitris Aragiorgis
    try:
837 cf51ea5b Dimitris Aragiorgis
        config = ConfigObj(opts.config_file, configspec=config_spec)
838 cf51ea5b Dimitris Aragiorgis
    except ConfigObjError, err:
839 cf51ea5b Dimitris Aragiorgis
        sys.stderr.write("Failed to parse config file %s: %s" %
840 cf51ea5b Dimitris Aragiorgis
                         (opts.config_file, str(err)))
841 cf51ea5b Dimitris Aragiorgis
        sys.exit(1)
842 cf51ea5b Dimitris Aragiorgis
843 cf51ea5b Dimitris Aragiorgis
    results = config.validate(validator)
844 cf51ea5b Dimitris Aragiorgis
    if results != True:
845 cf51ea5b Dimitris Aragiorgis
        logging.fatal("Configuration file validation failed! See errors below:")
846 cf51ea5b Dimitris Aragiorgis
        for (section_list, key, unused) in flatten_errors(config, results):
847 cf51ea5b Dimitris Aragiorgis
            if key is not None:
848 cf51ea5b Dimitris Aragiorgis
                logging.fatal(" '%s' in section '%s' failed validation",
849 cf51ea5b Dimitris Aragiorgis
                              key, ", ".join(section_list))
850 cf51ea5b Dimitris Aragiorgis
            else:
851 cf51ea5b Dimitris Aragiorgis
                logging.fatal(" Section '%s' is missing",
852 cf51ea5b Dimitris Aragiorgis
                              ", ".join(section_list))
853 cf51ea5b Dimitris Aragiorgis
        sys.exit(1)
854 cf51ea5b Dimitris Aragiorgis
855 cf51ea5b Dimitris Aragiorgis
    logger = logging.getLogger()
856 cf51ea5b Dimitris Aragiorgis
    if opts.debug:
857 cf51ea5b Dimitris Aragiorgis
        logger.setLevel(logging.DEBUG)
858 cf51ea5b Dimitris Aragiorgis
    else:
859 cf51ea5b Dimitris Aragiorgis
        logger.setLevel(logging.INFO)
860 cf51ea5b Dimitris Aragiorgis
861 cf51ea5b Dimitris Aragiorgis
    if opts.daemonize:
862 cf51ea5b Dimitris Aragiorgis
        logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME)
863 cf51ea5b Dimitris Aragiorgis
        handler = logging.handlers.RotatingFileHandler(logfile,
864 cf51ea5b Dimitris Aragiorgis
                                                       maxBytes=2097152)
865 cf51ea5b Dimitris Aragiorgis
    else:
866 cf51ea5b Dimitris Aragiorgis
        handler = logging.StreamHandler()
867 cf51ea5b Dimitris Aragiorgis
868 cf51ea5b Dimitris Aragiorgis
    handler.setFormatter(logging.Formatter(LOG_FORMAT))
869 cf51ea5b Dimitris Aragiorgis
    logger.addHandler(handler)
870 cf51ea5b Dimitris Aragiorgis
871 cf51ea5b Dimitris Aragiorgis
    if opts.daemonize:
872 cf51ea5b Dimitris Aragiorgis
        pidfile = daemon.pidlockfile.TimeoutPIDLockFile(
873 cf51ea5b Dimitris Aragiorgis
            config["general"]["pidfile"], 10)
874 cf51ea5b Dimitris Aragiorgis
875 cf51ea5b Dimitris Aragiorgis
        d = daemon.DaemonContext(pidfile=pidfile,
876 cf51ea5b Dimitris Aragiorgis
                                 stdout=handler.stream,
877 cf51ea5b Dimitris Aragiorgis
                                 stderr=handler.stream,
878 cf51ea5b Dimitris Aragiorgis
                                 files_preserve=[handler.stream])
879 cf51ea5b Dimitris Aragiorgis
        d.umask = 0022
880 cf51ea5b Dimitris Aragiorgis
        d.open()
881 cf51ea5b Dimitris Aragiorgis
882 cf51ea5b Dimitris Aragiorgis
    logging.info("Starting up")
883 cf51ea5b Dimitris Aragiorgis
884 cf51ea5b Dimitris Aragiorgis
    proxy_opts = {}
885 cf51ea5b Dimitris Aragiorgis
    if config["dhcp"].as_bool("enable_dhcp"):
886 cf51ea5b Dimitris Aragiorgis
        proxy_opts.update({
887 cf51ea5b Dimitris Aragiorgis
            "dhcp_queue_num": config["dhcp"].as_int("dhcp_queue"),
888 cf51ea5b Dimitris Aragiorgis
            "dhcp_lease_lifetime": config["dhcp"].as_int("lease_lifetime"),
889 cf51ea5b Dimitris Aragiorgis
            "dhcp_lease_renewal": config["dhcp"].as_int("lease_renewal"),
890 cf51ea5b Dimitris Aragiorgis
            "dhcp_server_ip": config["dhcp"]["server_ip"],
891 cf51ea5b Dimitris Aragiorgis
            "dhcp_nameservers": config["dhcp"]["nameservers"],
892 cf51ea5b Dimitris Aragiorgis
        })
893 cf51ea5b Dimitris Aragiorgis
894 cf51ea5b Dimitris Aragiorgis
    if config["ipv6"].as_bool("enable_ipv6"):
895 cf51ea5b Dimitris Aragiorgis
        proxy_opts.update({
896 cf51ea5b Dimitris Aragiorgis
            "rs_queue_num": config["ipv6"].as_int("rs_queue"),
897 cf51ea5b Dimitris Aragiorgis
            "ns_queue_num": config["ipv6"].as_int("ns_queue"),
898 cf51ea5b Dimitris Aragiorgis
            "ra_period": config["ipv6"].as_int("ra_period"),
899 cf51ea5b Dimitris Aragiorgis
            "ipv6_nameservers": config["ipv6"]["nameservers"],
900 cf51ea5b Dimitris Aragiorgis
        })
901 cf51ea5b Dimitris Aragiorgis
902 cf51ea5b Dimitris Aragiorgis
    # pylint: disable=W0142
903 cf51ea5b Dimitris Aragiorgis
    proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts)
904 cf51ea5b Dimitris Aragiorgis
905 cf51ea5b Dimitris Aragiorgis
    # Drop all capabilities except CAP_NET_RAW and change uid
906 cf51ea5b Dimitris Aragiorgis
    try:
907 cf51ea5b Dimitris Aragiorgis
        uid = getpwuid(config["general"].as_int("user"))
908 cf51ea5b Dimitris Aragiorgis
    except ValueError:
909 cf51ea5b Dimitris Aragiorgis
        uid = getpwnam(config["general"]["user"])
910 cf51ea5b Dimitris Aragiorgis
911 cf51ea5b Dimitris Aragiorgis
    logging.debug("Setting capabilities and changing uid")
912 cf51ea5b Dimitris Aragiorgis
    logging.debug("User: %s, uid: %d, gid: %d",
913 cf51ea5b Dimitris Aragiorgis
                  config["general"]["user"], uid.pw_uid, uid.pw_gid)
914 cf51ea5b Dimitris Aragiorgis
915 cf51ea5b Dimitris Aragiorgis
    # Keep only the capabilities we need
916 cf51ea5b Dimitris Aragiorgis
    # CAP_NET_ADMIN: we need to send nfqueue packet verdicts to a netlinkgroup
917 cf51ea5b Dimitris Aragiorgis
    capng.capng_clear(capng.CAPNG_SELECT_BOTH)
918 cf51ea5b Dimitris Aragiorgis
    capng.capng_update(capng.CAPNG_ADD,
919 cf51ea5b Dimitris Aragiorgis
                       capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED,
920 cf51ea5b Dimitris Aragiorgis
                       capng.CAP_NET_ADMIN)
921 cf51ea5b Dimitris Aragiorgis
    capng.capng_change_id(uid.pw_uid, uid.pw_gid,
922 cf51ea5b Dimitris Aragiorgis
                          capng.CAPNG_DROP_SUPP_GRP|capng.CAPNG_CLEAR_BOUNDING)
923 cf51ea5b Dimitris Aragiorgis
924 cf51ea5b Dimitris Aragiorgis
    logging.info("Ready to serve requests")
925 cf51ea5b Dimitris Aragiorgis
    try:
926 cf51ea5b Dimitris Aragiorgis
        proxy.serve()
927 cf51ea5b Dimitris Aragiorgis
    except Exception:
928 cf51ea5b Dimitris Aragiorgis
        if opts.daemonize:
929 cf51ea5b Dimitris Aragiorgis
            exc = "".join(traceback.format_exception(*sys.exc_info()))
930 cf51ea5b Dimitris Aragiorgis
            logging.critical(exc)
931 cf51ea5b Dimitris Aragiorgis
        raise
932 cf51ea5b Dimitris Aragiorgis
933 cf51ea5b Dimitris Aragiorgis
934 cf51ea5b Dimitris Aragiorgis
# vim: set ts=4 sts=4 sw=4 et :