Revision 6765a36f
b/nfdhcpd | ||
---|---|---|
21 | 21 |
|
22 | 22 |
import os |
23 | 23 |
import re |
24 |
import sys |
|
24 | 25 |
import glob |
25 | 26 |
import time |
26 | 27 |
import logging |
... | ... | |
39 | 40 |
|
40 | 41 |
from scapy.layers.l2 import Ether |
41 | 42 |
from scapy.layers.inet import IP, UDP |
42 |
from scapy.layers.inet6 import * |
|
43 |
from scapy.layers.inet6 import IPv6, ICMPv6ND_RA, ICMPv6ND_NA, \ |
|
44 |
ICMPv6NDOptDstLLAddr, \ |
|
45 |
ICMPv6NDOptPrefixInfo, \ |
|
46 |
ICMPv6NDOptRDNSS |
|
43 | 47 |
from scapy.layers.dhcp import BOOTP, DHCP |
44 | 48 |
from scapy.sendrecv import sendp |
45 | 49 |
|
... | ... | |
109 | 113 |
} |
110 | 114 |
|
111 | 115 |
|
116 |
def parse_routing_table(table="main", family=4): |
|
117 |
""" Parse the given routing table to get connected route, gateway and |
|
118 |
default device. |
|
119 |
|
|
120 |
""" |
|
121 |
ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls", |
|
122 |
"table", table], stdout=subprocess.PIPE) |
|
123 |
routes = ipro.stdout.readlines() |
|
124 |
|
|
125 |
def_gw = None |
|
126 |
def_dev = None |
|
127 |
def_net = None |
|
128 |
|
|
129 |
for route in routes: |
|
130 |
match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route) |
|
131 |
if match: |
|
132 |
def_gw, def_dev = match.groups() |
|
133 |
break |
|
134 |
|
|
135 |
for route in routes: |
|
136 |
# Find the least-specific connected route |
|
137 |
m = re.match("^([^\\s]+) dev %s" % def_dev, route) |
|
138 |
if not m: |
|
139 |
continue |
|
140 |
def_net = m.groups(1) |
|
141 |
|
|
142 |
try: |
|
143 |
def_net = IPy.IP(def_net) |
|
144 |
except ValueError, e: |
|
145 |
logging.warn("Unable to parse default route entry %s: %s", |
|
146 |
def_net, str(e)) |
|
147 |
|
|
148 |
return Subnet(net=def_net, gw=def_gw, dev=def_dev) |
|
149 |
|
|
150 |
|
|
151 |
def parse_binding_file(path): |
|
152 |
""" Read a client configuration from a tap file |
|
153 |
|
|
154 |
""" |
|
155 |
try: |
|
156 |
iffile = open(path, 'r') |
|
157 |
except EnvironmentError, e: |
|
158 |
logging.warn("Unable to open binding file %s: %s", path, str(e)) |
|
159 |
return (None, None, None, None) |
|
160 |
|
|
161 |
mac = None |
|
162 |
ips = None |
|
163 |
link = None |
|
164 |
hostname = None |
|
165 |
|
|
166 |
for line in iffile: |
|
167 |
if line.startswith("IP="): |
|
168 |
ip = line.strip().split("=")[1] |
|
169 |
ips = ip.split() |
|
170 |
elif line.startswith("MAC="): |
|
171 |
mac = line.strip().split("=")[1] |
|
172 |
elif line.startswith("LINK="): |
|
173 |
link = line.strip().split("=")[1] |
|
174 |
elif line.startswith("HOSTNAME="): |
|
175 |
hostname = line.strip().split("=")[1] |
|
176 |
|
|
177 |
return Client(mac=mac, ips=ips, link=link, hostname=hostname) |
|
178 |
|
|
179 |
|
|
112 | 180 |
class ClientFileHandler(pyinotify.ProcessEvent): |
113 | 181 |
def __init__(self, server): |
114 | 182 |
pyinotify.ProcessEvent.__init__(self) |
115 | 183 |
self.server = server |
116 | 184 |
|
117 | 185 |
def process_IN_DELETE(self, event): |
186 |
""" Delete file handler |
|
187 |
|
|
188 |
Currently this removes an interface from the watch list |
|
189 |
|
|
190 |
""" |
|
118 | 191 |
self.server.remove_iface(event.name) |
119 | 192 |
|
120 | 193 |
def process_IN_CLOSE_WRITE(self, event): |
194 |
""" Add file handler |
|
195 |
|
|
196 |
Currently this adds an interface to the watch list |
|
197 |
|
|
198 |
""" |
|
121 | 199 |
self.server.add_iface(os.path.join(event.path, event.name)) |
122 | 200 |
|
123 | 201 |
|
... | ... | |
149 | 227 |
|
150 | 228 |
@property |
151 | 229 |
def netmask(self): |
230 |
""" Return the netmask in textual representation |
|
231 |
|
|
232 |
""" |
|
152 | 233 |
return str(self.net.netmask()) |
153 | 234 |
|
154 | 235 |
@property |
155 | 236 |
def broadcast(self): |
237 |
""" Return the broadcast address in textual representation |
|
238 |
|
|
239 |
""" |
|
156 | 240 |
return str(self.net.broadcast()) |
157 | 241 |
|
158 | 242 |
@property |
159 | 243 |
def prefix(self): |
244 |
""" Return the network as an IPy.IP |
|
245 |
|
|
246 |
""" |
|
160 | 247 |
return self.net.net() |
161 | 248 |
|
162 | 249 |
@property |
163 | 250 |
def prefixlen(self): |
251 |
""" Return the prefix length as an integer |
|
252 |
|
|
253 |
""" |
|
164 | 254 |
return self.net.prefixlen() |
165 | 255 |
|
166 | 256 |
@staticmethod |
... | ... | |
177 | 267 |
return IPy.IP(":".join(prefix)) |
178 | 268 |
|
179 | 269 |
def make_eui64(self, mac): |
270 |
""" Compute an EUI-64 address from an EUI-48 (MAC) address in this |
|
271 |
subnet. |
|
272 |
|
|
273 |
""" |
|
180 | 274 |
return self._make_eui64(self.net, mac) |
181 | 275 |
|
182 | 276 |
def make_ll64(self, mac): |
277 |
""" Compute an IPv6 Link-local address from an EUI-48 (MAC) address |
|
278 |
|
|
279 |
""" |
|
183 | 280 |
return self._make_eui64("fe80::", mac) |
184 | 281 |
|
185 | 282 |
|
186 |
class VMNetProxy(object): |
|
187 |
def __init__(self, data_path, dhcp_queue_num=None, |
|
283 |
class VMNetProxy(object): # pylint: disable=R0902
|
|
284 |
def __init__(self, data_path, dhcp_queue_num=None, # pylint: disable=R0913
|
|
188 | 285 |
rs_queue_num=None, ns_queue_num=None, |
189 | 286 |
dhcp_lease_lifetime=DEFAULT_LEASE_LIFETIME, |
190 | 287 |
dhcp_lease_renewal=DEFAULT_LEASE_RENEWAL, |
191 |
dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers = [],
|
|
192 |
ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers = []):
|
|
288 |
dhcp_server_ip=DHCP_DUMMY_SERVER_IP, dhcp_nameservers=None,
|
|
289 |
ra_period=DEFAULT_RA_PERIOD, ipv6_nameservers=None):
|
|
193 | 290 |
|
194 | 291 |
self.data_path = data_path |
195 | 292 |
self.lease_lifetime = dhcp_lease_lifetime |
196 | 293 |
self.lease_renewal = dhcp_lease_renewal |
197 | 294 |
self.dhcp_server_ip = dhcp_server_ip |
198 | 295 |
self.ra_period = ra_period |
199 |
self.dhcp_nameservers = dhcp_nameservers |
|
200 |
self.ipv6_nameservers = ipv6_nameservers |
|
296 |
if dhcp_nameservers is None: |
|
297 |
self.dhcp_nameserver = [] |
|
298 |
else: |
|
299 |
self.dhcp_nameservers = dhcp_nameservers |
|
300 |
|
|
301 |
if ipv6_nameservers is None: |
|
302 |
self.ipv6_nameservers = [] |
|
303 |
else: |
|
304 |
self.ipv6_nameservers = ipv6_nameservers |
|
305 |
|
|
201 | 306 |
self.ipv6_enabled = False |
202 | 307 |
|
203 | 308 |
self.clients = {} |
... | ... | |
210 | 315 |
self.wm = pyinotify.WatchManager() |
211 | 316 |
mask = pyinotify.EventsCodes.ALL_FLAGS["IN_DELETE"] |
212 | 317 |
mask |= pyinotify.EventsCodes.ALL_FLAGS["IN_CLOSE_WRITE"] |
213 |
handler = ClientFileHandler(self) |
|
214 |
self.notifier = pyinotify.Notifier(self.wm, handler) |
|
318 |
inotify_handler = ClientFileHandler(self)
|
|
319 |
self.notifier = pyinotify.Notifier(self.wm, inotify_handler)
|
|
215 | 320 |
self.wm.add_watch(self.data_path, mask, rec=True) |
216 | 321 |
|
217 | 322 |
# NFQUEUE setup |
... | ... | |
227 | 332 |
self.ipv6_enabled = True |
228 | 333 |
|
229 | 334 |
def _setup_nfqueue(self, queue_num, family, callback): |
230 |
logging.debug("Setting up NFQUEUE for queue %d, AF %s" %
|
|
231 |
(queue_num, family))
|
|
335 |
logging.debug("Setting up NFQUEUE for queue %d, AF %s",
|
|
336 |
queue_num, family)
|
|
232 | 337 |
q = nfqueue.queue() |
233 | 338 |
q.set_callback(callback) |
234 | 339 |
q.fast_open(queue_num, family) |
... | ... | |
241 | 346 |
self.clients.clear() |
242 | 347 |
self.subnets.clear() |
243 | 348 |
|
244 |
for file in glob.glob(os.path.join(self.data_path, "*")):
|
|
245 |
self.add_iface(file)
|
|
349 |
for path in glob.glob(os.path.join(self.data_path, "*")):
|
|
350 |
self.add_iface(path)
|
|
246 | 351 |
|
247 | 352 |
def get_ifindex(self, iface): |
248 | 353 |
""" Get the interface index from sysfs |
249 | 354 |
|
250 | 355 |
""" |
251 |
file = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
|
|
252 |
if not file.startswith(SYSFS_NET):
|
|
356 |
path = os.path.abspath(os.path.join(SYSFS_NET, iface, "ifindex"))
|
|
357 |
if not path.startswith(SYSFS_NET):
|
|
253 | 358 |
return None |
254 | 359 |
|
255 | 360 |
ifindex = None |
256 | 361 |
|
257 | 362 |
try: |
258 |
f = open(file, 'r')
|
|
363 |
f = open(path, 'r')
|
|
259 | 364 |
except EnvironmentError: |
260 |
logging.debug("%s is probably down, removing" % iface)
|
|
365 |
logging.debug("%s is probably down, removing", iface)
|
|
261 | 366 |
self.remove_iface(iface) |
262 | 367 |
|
263 | 368 |
return ifindex |
... | ... | |
268 | 373 |
ifindex = int(ifindex) |
269 | 374 |
except ValueError, e: |
270 | 375 |
logging.warn("Failed to get ifindex for %s, cannot parse sysfs" |
271 |
" output '%s'" % (iface, ifindex))
|
|
376 |
" output '%s'", iface, ifindex)
|
|
272 | 377 |
except EnvironmentError, e: |
273 |
logging.warn("Error reading %s's ifindex from sysfs: %s" %
|
|
274 |
(iface, str(e)))
|
|
378 |
logging.warn("Error reading %s's ifindex from sysfs: %s",
|
|
379 |
iface, str(e))
|
|
275 | 380 |
self.remove_iface(iface) |
276 | 381 |
finally: |
277 | 382 |
f.close() |
... | ... | |
283 | 388 |
""" Get the interface hardware address from sysfs |
284 | 389 |
|
285 | 390 |
""" |
286 |
file = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
|
|
287 |
if not file.startswith(SYSFS_NET):
|
|
391 |
path = os.path.abspath(os.path.join(SYSFS_NET, iface, "address"))
|
|
392 |
if not path.startswith(SYSFS_NET):
|
|
288 | 393 |
return None |
289 | 394 |
|
290 | 395 |
addr = None |
291 | 396 |
try: |
292 |
f = open(file, 'r')
|
|
397 |
f = open(path, 'r')
|
|
293 | 398 |
except EnvironmentError: |
294 |
logging.debug("%s is probably down, removing" % iface)
|
|
399 |
logging.debug("%s is probably down, removing", iface)
|
|
295 | 400 |
self.remove_iface(iface) |
296 | 401 |
return addr |
297 | 402 |
|
298 | 403 |
try: |
299 | 404 |
addr = f.readline().strip() |
300 | 405 |
except EnvironmentError, e: |
301 |
logging.warn("Failed to read hw address for %s from sysfs: %s" %
|
|
302 |
(iface, str(e)))
|
|
406 |
logging.warn("Failed to read hw address for %s from sysfs: %s",
|
|
407 |
iface, str(e))
|
|
303 | 408 |
finally: |
304 | 409 |
f.close() |
305 | 410 |
|
306 | 411 |
return addr |
307 | 412 |
|
308 |
def parse_routing_table(self, table="main", family=4): |
|
309 |
""" Parse the given routing table to get connected route, gateway and |
|
310 |
default device. |
|
311 |
|
|
312 |
""" |
|
313 |
ipro = subprocess.Popen(["ip", "-%d" % family, "ro", "ls", |
|
314 |
"table", table], stdout=subprocess.PIPE) |
|
315 |
routes = ipro.stdout.readlines() |
|
316 |
|
|
317 |
def_gw = None |
|
318 |
def_dev = None |
|
319 |
def_net = None |
|
320 |
|
|
321 |
for route in routes: |
|
322 |
match = re.match(r'^default.*via ([^\s]+).*dev ([^\s]+)', route) |
|
323 |
if match: |
|
324 |
def_gw, def_dev = match.groups() |
|
325 |
break |
|
326 |
|
|
327 |
for route in routes: |
|
328 |
# Find the least-specific connected route |
|
329 |
m = re.match("^([^\\s]+) dev %s" % def_dev, route) |
|
330 |
if not m: |
|
331 |
continue |
|
332 |
def_net = m.groups(1) |
|
333 |
|
|
334 |
try: |
|
335 |
def_net = IPy.IP(def_net) |
|
336 |
except ValueError, e: |
|
337 |
logging.warn("Unable to parse default route entry %s: %s" % |
|
338 |
(def_net, str(e))) |
|
339 |
|
|
340 |
return Subnet(net=def_net, gw=def_gw, dev=def_dev) |
|
341 |
|
|
342 |
def parse_binding_file(self, path): |
|
343 |
""" Read a client configuration from a tap file |
|
344 |
|
|
345 |
""" |
|
346 |
try: |
|
347 |
iffile = open(path, 'r') |
|
348 |
except EnvironmentError, e: |
|
349 |
logging.warn("Unable to open binding file %s: %s" % (path, str(e))) |
|
350 |
return (None, None, None, None) |
|
351 |
|
|
352 |
mac = None |
|
353 |
ips = None |
|
354 |
link = None |
|
355 |
hostname = None |
|
356 |
|
|
357 |
for line in iffile: |
|
358 |
if line.startswith("IP="): |
|
359 |
ip = line.strip().split("=")[1] |
|
360 |
ips = ip.split() |
|
361 |
elif line.startswith("MAC="): |
|
362 |
mac = line.strip().split("=")[1] |
|
363 |
elif line.startswith("LINK="): |
|
364 |
link = line.strip().split("=")[1] |
|
365 |
elif line.startswith("HOSTNAME="): |
|
366 |
hostname = line.strip().split("=")[1] |
|
367 |
|
|
368 |
return Client(mac=mac, ips=ips, link=link, hostname=hostname) |
|
369 |
|
|
370 | 413 |
def add_iface(self, path): |
371 | 414 |
""" Add an interface to monitor |
372 | 415 |
|
373 | 416 |
""" |
374 | 417 |
iface = os.path.basename(path) |
375 | 418 |
|
376 |
logging.debug("Updating configuration for %s" % iface)
|
|
377 |
binding = self.parse_binding_file(path)
|
|
419 |
logging.debug("Updating configuration for %s", iface)
|
|
420 |
binding = parse_binding_file(path) |
|
378 | 421 |
ifindex = self.get_ifindex(iface) |
379 | 422 |
|
380 | 423 |
if ifindex is None: |
381 |
logging.warn("Stale configuration for %s found" % iface)
|
|
424 |
logging.warn("Stale configuration for %s found", iface)
|
|
382 | 425 |
else: |
383 | 426 |
if binding.is_valid(): |
384 | 427 |
binding.iface = iface |
385 | 428 |
self.clients[binding.mac] = binding |
386 |
self.subnets[binding.link] = self.parse_routing_table( |
|
387 |
binding.link) |
|
388 |
logging.debug("Added client %s on %s" % |
|
389 |
(binding.hostname, iface)) |
|
429 |
self.subnets[binding.link] = parse_routing_table(binding.link) |
|
430 |
logging.debug("Added client %s on %s", binding.hostname, iface) |
|
390 | 431 |
self.ifaces[ifindex] = iface |
391 |
self.v6nets[iface] = self.parse_routing_table(binding.link, 6)
|
|
432 |
self.v6nets[iface] = parse_routing_table(binding.link, 6) |
|
392 | 433 |
|
393 | 434 |
def remove_iface(self, iface): |
394 | 435 |
""" Cleanup clients on a removed interface |
... | ... | |
405 | 446 |
if self.ifaces[ifindex] == iface: |
406 | 447 |
del self.ifaces[ifindex] |
407 | 448 |
|
408 |
logging.debug("Removed interface %s" % iface)
|
|
449 |
logging.debug("Removed interface %s", iface)
|
|
409 | 450 |
|
410 |
def dhcp_response(self, i, payload): |
|
451 |
def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
|
|
411 | 452 |
""" Generate a reply to a BOOTP/DHCP request |
412 | 453 |
|
413 | 454 |
""" |
... | ... | |
433 | 474 |
try: |
434 | 475 |
binding = self.clients[mac] |
435 | 476 |
except KeyError: |
436 |
logging.warn("Invalid client %s on %s" % (mac, iface))
|
|
477 |
logging.warn("Invalid client %s on %s", mac, iface)
|
|
437 | 478 |
return |
438 | 479 |
|
439 | 480 |
if iface != binding.iface: |
440 | 481 |
logging.warn("Received spoofed DHCP request for %s from interface" |
441 |
" %s instead of %s" % |
|
442 |
(mac, iface, binding.iface)) |
|
482 |
" %s instead of %s", mac, iface, binding.iface) |
|
443 | 483 |
return |
444 | 484 |
|
445 | 485 |
resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\ |
... | ... | |
449 | 489 |
|
450 | 490 |
if not DHCP in pkt: |
451 | 491 |
logging.warn("Invalid request from %s on %s, no DHCP" |
452 |
" payload found" % (binding.mac, iface))
|
|
492 |
" payload found", binding.mac, iface)
|
|
453 | 493 |
return |
454 | 494 |
|
455 | 495 |
dhcp_options = [] |
... | ... | |
460 | 500 |
if type(opt) is tuple and opt[0] == "requested_addr": |
461 | 501 |
requested_addr = opt[1] |
462 | 502 |
|
463 |
logging.info("%s from %s on %s" %
|
|
464 |
(DHCP_TYPES.get(req_type, "UNKNOWN"), binding.mac, iface))
|
|
503 |
logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
|
|
504 |
binding.mac, iface)
|
|
465 | 505 |
|
466 | 506 |
if req_type == DHCPREQUEST and requested_addr != binding.ip: |
467 | 507 |
resp_type = DHCPNAK |
468 | 508 |
logging.info("Sending DHCPNAK to %s on %s: requested %s" |
469 |
" instead of %s" %
|
|
470 |
(binding.mac, iface, requested_addr, binding.ip))
|
|
509 |
" instead of %s", binding.mac, iface, requested_addr,
|
|
510 |
binding.ip)
|
|
471 | 511 |
|
472 | 512 |
elif req_type in (DHCPDISCOVER, DHCPREQUEST): |
473 | 513 |
resp_type = DHCP_REQRESP[req_type] |
... | ... | |
493 | 533 |
|
494 | 534 |
elif req_type == DHCPRELEASE: |
495 | 535 |
# Log and ignore |
496 |
logging.info("DHCPRELEASE from %s on %s" % |
|
497 |
(binding.mac, iface)) |
|
536 |
logging.info("DHCPRELEASE from %s on %s", binding.mac, iface) |
|
498 | 537 |
return |
499 | 538 |
|
500 | 539 |
# Finally, always add the server identifier and end options |
... | ... | |
505 | 544 |
] |
506 | 545 |
resp /= DHCP(options=dhcp_options) |
507 | 546 |
|
508 |
logging.info("%s to %s (%s) on %s" %
|
|
509 |
(DHCP_TYPES[resp_type], mac, binding.ip, iface))
|
|
547 |
logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
|
|
548 |
binding.ip, iface)
|
|
510 | 549 |
sendp(resp, iface=iface, verbose=False) |
511 | 550 |
|
512 |
def rs_response(self, i, payload): |
|
551 |
def rs_response(self, i, payload): # pylint: disable=W0613
|
|
513 | 552 |
""" Generate a reply to a BOOTP/DHCP request |
514 | 553 |
|
515 | 554 |
""" |
... | ... | |
531 | 570 |
resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers, |
532 | 571 |
lifetime=self.ra_period * 3) |
533 | 572 |
|
534 |
logging.info("RA on %s for %s" % (iface, subnet.net))
|
|
573 |
logging.info("RA on %s for %s", iface, subnet.net)
|
|
535 | 574 |
sendp(resp, iface=iface, verbose=False) |
536 | 575 |
|
537 |
def ns_response(self, i, payload): |
|
576 |
def ns_response(self, i, payload): # pylint: disable=W0613
|
|
538 | 577 |
""" Generate a reply to an ICMPv6 neighbor solicitation |
539 | 578 |
|
540 | 579 |
""" |
... | ... | |
547 | 586 |
ns = IPv6(payload.get_data()) |
548 | 587 |
|
549 | 588 |
if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)): |
550 |
logging.debug("Received NS for a non-routable IP (%s)" % ns.tgt)
|
|
589 |
logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
|
|
551 | 590 |
payload.set_verdict(nfqueue.NF_ACCEPT) |
552 | 591 |
return 1 |
553 | 592 |
|
... | ... | |
563 | 602 |
ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\ |
564 | 603 |
ICMPv6NDOptDstLLAddr(lladdr=ifmac) |
565 | 604 |
|
566 |
logging.info("NA on %s for %s" % (iface, ns.tgt))
|
|
605 |
logging.info("NA on %s for %s", iface, ns.tgt)
|
|
567 | 606 |
sendp(resp, iface=iface, verbose=False) |
568 | 607 |
return 1 |
569 | 608 |
|
... | ... | |
594 | 633 |
try: |
595 | 634 |
sendp(resp, iface=iface, verbose=False) |
596 | 635 |
except socket.error, e: |
597 |
logging.warn("Periodic RA on %s failed: %s" % (iface, str(e)))
|
|
636 |
logging.warn("Periodic RA on %s failed: %s", iface, str(e))
|
|
598 | 637 |
except Exception, e: |
599 |
logging.warn("Unkown error during periodic RA on %s: %s" %
|
|
600 |
(iface, str(e)))
|
|
638 |
logging.warn("Unkown error during periodic RA on %s: %s",
|
|
639 |
iface, str(e))
|
|
601 | 640 |
i += 1 |
602 |
logging.debug("Sent %d RAs in %.2f seconds" % (i, time.time() - start))
|
|
641 |
logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
|
|
603 | 642 |
|
604 | 643 |
def serve(self): |
605 | 644 |
""" Loop forever, serving DHCP requests |
... | ... | |
607 | 646 |
""" |
608 | 647 |
self.build_config() |
609 | 648 |
|
610 |
iwfd = self.notifier._fd |
|
649 |
# Yes, we are accessing _fd directly, but it's the only way to have a |
|
650 |
# single select() loop ;-) |
|
651 |
iwfd = self.notifier._fd # pylint: disable=W0212 |
|
611 | 652 |
|
612 | 653 |
start = time.time() |
613 | 654 |
if self.ipv6_enabled: |
... | ... | |
619 | 660 |
while True: |
620 | 661 |
rlist, _, xlist = select(self.nfq.keys() + [iwfd], [], [], timeout) |
621 | 662 |
if xlist: |
622 |
logging.warn("Warning: Exception on %s" %
|
|
663 |
logging.warn("Warning: Exception on %s",
|
|
623 | 664 |
", ".join([ str(fd) for fd in xlist])) |
624 | 665 |
|
625 | 666 |
if rlist: |
... | ... | |
633 | 674 |
for fd in rlist: |
634 | 675 |
try: |
635 | 676 |
self.nfq[fd].process_pending() |
677 |
except RuntimeError, e: |
|
678 |
logging.warn("Error processing fd %d: %s", fd, str(e)) |
|
636 | 679 |
except Exception, e: |
637 |
logging.warn("Error processing fd %d: %s" %
|
|
638 |
(fd, str(e)))
|
|
680 |
logging.warn("Unknown error processing fd %d: %s",
|
|
681 |
fd, str(e))
|
|
639 | 682 |
|
640 | 683 |
if self.ipv6_enabled: |
641 | 684 |
# Calculate the new timeout |
... | ... | |
648 | 691 |
|
649 | 692 |
|
650 | 693 |
if __name__ == "__main__": |
694 |
import capng |
|
651 | 695 |
import optparse |
652 | 696 |
from cStringIO import StringIO |
653 |
from capng import * |
|
654 | 697 |
from pwd import getpwnam, getpwuid |
655 | 698 |
from configobj import ConfigObj, ConfigObjError, flatten_errors |
656 | 699 |
|
... | ... | |
702 | 745 |
|
703 | 746 |
try: |
704 | 747 |
config = ConfigObj(opts.config_file, configspec=config_spec) |
705 |
except ConfigObjError, e: |
|
748 |
except ConfigObjError, err:
|
|
706 | 749 |
sys.stderr.write("Failed to parse config file %s: %s" % |
707 |
(opts.config_file, str(e))) |
|
750 |
(opts.config_file, str(err)))
|
|
708 | 751 |
sys.exit(1) |
709 | 752 |
|
710 | 753 |
results = config.validate(validator) |
711 | 754 |
if results != True: |
712 | 755 |
logging.fatal("Configuration file validation failed! See errors below:") |
713 |
for (section_list, key, _) in flatten_errors(config, results):
|
|
756 |
for (section_list, key, unused) in flatten_errors(config, results):
|
|
714 | 757 |
if key is not None: |
715 |
logging.fatal(" '%s' in section '%s' failed validation" %
|
|
716 |
(key, ", ".join(section_list)))
|
|
758 |
logging.fatal(" '%s' in section '%s' failed validation",
|
|
759 |
key, ", ".join(section_list))
|
|
717 | 760 |
else: |
718 |
logging.fatal(" Section '%s' is missing" %
|
|
761 |
logging.fatal(" Section '%s' is missing",
|
|
719 | 762 |
", ".join(section_list)) |
720 | 763 |
sys.exit(1) |
721 | 764 |
|
... | ... | |
749 | 792 |
"ipv6_nameservers": config["ipv6"]["nameservers"], |
750 | 793 |
}) |
751 | 794 |
|
795 |
# pylint: disable=W0142 |
|
752 | 796 |
proxy = VMNetProxy(data_path=config["general"]["datapath"], **proxy_opts) |
753 | 797 |
|
754 | 798 |
# Drop all capabilities except CAP_NET_RAW and change uid |
... | ... | |
758 | 802 |
uid = getpwnam(config["general"]["user"]) |
759 | 803 |
|
760 | 804 |
logging.debug("Setting capabilities and changing uid") |
761 |
logging.debug("User: %s, uid: %d, gid: %d" % |
|
762 |
(config["general"]["user"], uid.pw_uid, uid.pw_gid)) |
|
763 |
capng_clear(CAPNG_SELECT_BOTH) |
|
764 |
capng_update(CAPNG_ADD, CAPNG_EFFECTIVE|CAPNG_PERMITTED, CAP_NET_RAW) |
|
765 |
capng_change_id(uid.pw_uid, uid.pw_gid, |
|
766 |
CAPNG_DROP_SUPP_GRP | CAPNG_CLEAR_BOUNDING) |
|
805 |
logging.debug("User: %s, uid: %d, gid: %d", |
|
806 |
config["general"]["user"], uid.pw_uid, uid.pw_gid) |
|
807 |
capng.capng_clear(capng.CAPNG_SELECT_BOTH) |
|
808 |
capng.capng_update(capng.CAPNG_ADD, |
|
809 |
capng.CAPNG_EFFECTIVE|capng.CAPNG_PERMITTED, |
|
810 |
capng.CAP_NET_RAW) |
|
811 |
capng.capng_change_id(uid.pw_uid, uid.pw_gid, |
|
812 |
capng.CAPNG_DROP_SUPP_GRP|capng.CAPNG_CLEAR_BOUNDING) |
|
767 | 813 |
|
768 | 814 |
if opts.daemonize: |
769 | 815 |
logfile = os.path.join(config["general"]["logdir"], LOG_FILENAME) |
Also available in: Unified diff