126 |
126 |
logging.warn("Unable to open binding file %s: %s", path, str(e))
|
127 |
127 |
return None
|
128 |
128 |
|
129 |
|
ifname = os.path.basename(path)
|
|
129 |
tap = os.path.basename(path)
|
|
130 |
indev = None
|
130 |
131 |
mac = None
|
131 |
132 |
ips = None
|
132 |
|
link = None
|
133 |
133 |
hostname = None
|
134 |
134 |
subnet = None
|
135 |
135 |
gateway = None
|
... | ... | |
144 |
144 |
mac = line.strip().split("=")[1]
|
145 |
145 |
elif line.startswith("HOSTNAME="):
|
146 |
146 |
hostname = line.strip().split("=")[1]
|
147 |
|
elif line.startswith("IFACE="):
|
148 |
|
iface = line.strip().split("=")[1]
|
|
147 |
elif line.startswith("INDEV="):
|
|
148 |
indev = line.strip().split("=")[1]
|
149 |
149 |
elif line.startswith("SUBNET="):
|
150 |
150 |
subnet = line.strip().split("=")[1]
|
151 |
151 |
elif line.startswith("GATEWAY="):
|
... | ... | |
155 |
155 |
elif line.startswith("GATEWAY6="):
|
156 |
156 |
gateway6 = line.strip().split("=")[1]
|
157 |
157 |
|
158 |
|
return Client(ifname=ifname, mac=mac, ips=ips, link=link,
|
159 |
|
hostname=hostname, iface=iface, subnet=subnet,
|
|
158 |
return Client(tap=tap, mac=mac, ips=ips,
|
|
159 |
hostname=hostname, indev=indev, subnet=subnet,
|
160 |
160 |
gateway=gateway, subnet6=subnet6, gateway6=gateway6 )
|
161 |
161 |
|
162 |
162 |
class ClientFileHandler(pyinotify.ProcessEvent):
|
... | ... | |
170 |
170 |
Currently this removes an interface from the watch list
|
171 |
171 |
|
172 |
172 |
"""
|
173 |
|
self.server.remove_iface(event.name)
|
|
173 |
self.server.remove_tap(event.name)
|
174 |
174 |
|
175 |
175 |
def process_IN_CLOSE_WRITE(self, event): # pylint: disable=C0103
|
176 |
176 |
""" Add file handler
|
... | ... | |
178 |
178 |
Currently this adds an interface to the watch list
|
179 |
179 |
|
180 |
180 |
"""
|
181 |
|
self.server.add_iface(os.path.join(event.path, event.name))
|
|
181 |
self.server.add_tap(os.path.join(event.path, event.name))
|
182 |
182 |
|
183 |
183 |
|
184 |
184 |
class Client(object):
|
185 |
|
def __init__(self, ifname=None, mac=None, ips=None, link=None,
|
186 |
|
hostname=None, iface=None, subnet=None, gateway=None,
|
187 |
|
subnet6=None, gateway6=None ):
|
|
185 |
def __init__(self, tap=None, indev=None, mac=None, ips=None, hostname=None,
|
|
186 |
subnet=None, gateway=None, subnet6=None, gateway6=None ):
|
188 |
187 |
self.mac = mac
|
189 |
188 |
self.ips = ips
|
190 |
189 |
self.hostname = hostname
|
191 |
|
self.link = link
|
192 |
|
self.iface = iface
|
193 |
|
self.ifname = ifname
|
|
190 |
self.indev = indev
|
|
191 |
self.tap = tap
|
194 |
192 |
self.subnet = subnet
|
195 |
193 |
self.gateway = gateway
|
196 |
|
self.net = Subnet(net=subnet, gw=gateway, dev=ifname)
|
|
194 |
self.net = Subnet(net=subnet, gw=gateway, dev=tap)
|
197 |
195 |
self.subnet6 = subnet6
|
198 |
196 |
self.gateway6 = gateway6
|
199 |
|
self.net6 = Subnet(net=subnet6, gw=gateway6, dev=ifname)
|
|
197 |
self.net6 = Subnet(net=subnet6, gw=gateway6, dev=tap)
|
200 |
198 |
|
201 |
199 |
@property
|
202 |
200 |
def ip(self):
|
... | ... | |
354 |
352 |
q.set_mode(nfqueue.NFQNL_COPY_PACKET)
|
355 |
353 |
self.nfq[q.get_fd()] = q
|
356 |
354 |
|
357 |
|
def sendp(self, data, iface):
|
|
355 |
def sendp(self, data, dev):
|
358 |
356 |
""" Send a raw packet using a layer-2 socket
|
359 |
357 |
|
360 |
358 |
"""
|
... | ... | |
362 |
360 |
if isinstance(data, BasePacket):
|
363 |
361 |
data = str(data)
|
364 |
362 |
|
365 |
|
self.l2socket.bind((iface, ETH_P_ALL))
|
|
363 |
self.l2socket.bind((dev, ETH_P_ALL))
|
366 |
364 |
count = self.l2socket.send(data)
|
367 |
365 |
ldata = len(data)
|
368 |
366 |
if count != ldata:
|
369 |
367 |
logging.warn("Truncated send on %s (%d/%d bytes sent)",
|
370 |
|
iface, count, ldata)
|
|
368 |
dev, count, ldata)
|
371 |
369 |
|
372 |
370 |
def build_config(self):
|
373 |
371 |
self.clients.clear()
|
374 |
372 |
|
375 |
373 |
for path in glob.glob(os.path.join(self.data_path, "*")):
|
376 |
|
self.add_iface(path)
|
|
374 |
self.add_tap(path)
|
377 |
375 |
|
378 |
376 |
def get_ifindex(self, iface):
|
379 |
377 |
""" Get the interface index from sysfs
|
... | ... | |
389 |
387 |
f = open(path, 'r')
|
390 |
388 |
except EnvironmentError:
|
391 |
389 |
logging.debug("%s is probably down, removing", iface)
|
392 |
|
self.remove_iface(iface)
|
|
390 |
self.remove_tap(iface)
|
393 |
391 |
|
394 |
392 |
return ifindex
|
395 |
393 |
|
... | ... | |
403 |
401 |
except EnvironmentError, e:
|
404 |
402 |
logging.warn("Error reading %s's ifindex from sysfs: %s",
|
405 |
403 |
iface, str(e))
|
406 |
|
self.remove_iface(iface)
|
|
404 |
self.remove_tap(iface)
|
407 |
405 |
finally:
|
408 |
406 |
f.close()
|
409 |
407 |
|
... | ... | |
423 |
421 |
f = open(path, 'r')
|
424 |
422 |
except EnvironmentError:
|
425 |
423 |
logging.debug("%s is probably down, removing", iface)
|
426 |
|
self.remove_iface(iface)
|
|
424 |
self.remove_tap(iface)
|
427 |
425 |
return addr
|
428 |
426 |
|
429 |
427 |
try:
|
... | ... | |
436 |
434 |
|
437 |
435 |
return addr
|
438 |
436 |
|
439 |
|
def add_iface(self, path):
|
|
437 |
def add_tap(self, path):
|
440 |
438 |
""" Add an interface to monitor
|
441 |
439 |
|
442 |
440 |
"""
|
443 |
|
iface = os.path.basename(path)
|
|
441 |
tap = os.path.basename(path)
|
444 |
442 |
|
445 |
|
logging.debug("Updating configuration for %s", iface)
|
|
443 |
logging.debug("Updating configuration for %s", tap)
|
446 |
444 |
binding = parse_binding_file(path)
|
447 |
445 |
if binding is None:
|
448 |
446 |
return
|
449 |
|
ifindex = self.get_ifindex(binding.iface)
|
|
447 |
ifindex = self.get_ifindex(binding.indev)
|
450 |
448 |
|
451 |
449 |
if ifindex is None:
|
452 |
|
logging.warn("Stale configuration for %s found", iface)
|
|
450 |
logging.warn("Stale configuration for %s found", tap)
|
453 |
451 |
else:
|
454 |
452 |
if binding.is_valid():
|
455 |
453 |
self.clients[binding.mac] = binding
|
456 |
|
logging.debug("Added client %s on %s", binding.hostname, iface)
|
457 |
|
self.ifaces[ifindex] = binding.iface
|
|
454 |
logging.debug("Added client %s on %s", binding.hostname, tap)
|
|
455 |
self.ifaces[ifindex] = binding.indev
|
458 |
456 |
|
459 |
|
def remove_iface(self, ifname):
|
|
457 |
def remove_tap(self, tap):
|
460 |
458 |
""" Cleanup clients on a removed interface
|
461 |
459 |
|
462 |
460 |
"""
|
463 |
461 |
for mac in self.clients.keys():
|
464 |
|
if self.clients[mac].ifname == ifname:
|
465 |
|
iface = self.clients[mac].iface
|
|
462 |
logging.debug("mac %s", mac)
|
|
463 |
logging.debug("tap %s", self.clients[mac].tap)
|
|
464 |
logging.debug("indev %s", self.clients[mac].indev)
|
|
465 |
if self.clients[mac].tap == tap:
|
|
466 |
indev = self.clients[mac].indev
|
466 |
467 |
del self.clients[mac]
|
467 |
468 |
|
468 |
469 |
for ifindex in self.ifaces.keys():
|
469 |
|
if self.ifaces[ifindex] == ifname == iface:
|
|
470 |
if self.ifaces[ifindex] == indev == tap:
|
470 |
471 |
del self.ifaces[ifindex]
|
471 |
472 |
|
472 |
|
logging.debug("Removed interface %s", ifname)
|
|
473 |
logging.debug("Removed interface %s", tap)
|
473 |
474 |
|
474 |
475 |
def dhcp_response(self, i, payload): # pylint: disable=W0613,R0914
|
475 |
|
""" Generate a reply to a BOOTP/DHCP request
|
|
476 |
""" Generate a reply to bnetfilter-queue-deva BOOTP/DHCP request
|
476 |
477 |
|
477 |
478 |
"""
|
478 |
|
logging.info("%s",payload)
|
479 |
|
indev = payload.get_indev()
|
|
479 |
indevidx = payload.get_indev()
|
480 |
480 |
try:
|
481 |
481 |
# Get the actual interface from the ifindex
|
482 |
|
iface = self.ifaces[indev]
|
|
482 |
indev = self.ifaces[indevidx]
|
483 |
483 |
except KeyError:
|
484 |
484 |
# We don't know anything about this interface, so accept the packet
|
485 |
485 |
# and return
|
... | ... | |
508 |
508 |
try:
|
509 |
509 |
binding = self.clients[mac]
|
510 |
510 |
except KeyError:
|
511 |
|
logging.warn("Invalid client %s on %s", mac, iface)
|
|
511 |
logging.warn("Invalid client %s on %s", mac, indev)
|
512 |
512 |
return
|
513 |
513 |
|
514 |
|
if iface != binding.iface:
|
|
514 |
if indev != binding.indev:
|
515 |
515 |
logging.warn("Received spoofed DHCP request for %s from interface"
|
516 |
|
" %s instead of %s", mac, iface, binding.iface)
|
|
516 |
" %s instead of %s", mac, indev, binding.indev)
|
517 |
517 |
return
|
518 |
518 |
|
519 |
|
resp = Ether(dst=mac, src=self.get_iface_hw_addr(iface))/\
|
|
519 |
resp = Ether(dst=mac, src=self.get_iface_hw_addr(indev))/\
|
520 |
520 |
IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
|
521 |
521 |
UDP(sport=pkt.dport, dport=pkt.sport)/resp
|
522 |
522 |
subnet = binding.net
|
523 |
523 |
|
524 |
524 |
if not DHCP in pkt:
|
525 |
525 |
logging.warn("Invalid request from %s on %s, no DHCP"
|
526 |
|
" payload found", binding.mac, iface)
|
|
526 |
" payload found", binding.mac, binding.tap)
|
527 |
527 |
return
|
528 |
528 |
|
529 |
529 |
dhcp_options = []
|
... | ... | |
535 |
535 |
requested_addr = opt[1]
|
536 |
536 |
|
537 |
537 |
logging.info("%s from %s on %s", DHCP_TYPES.get(req_type, "UNKNOWN"),
|
538 |
|
binding.mac, iface)
|
|
538 |
binding.mac, binding.tap)
|
539 |
539 |
|
540 |
540 |
if req_type == DHCPREQUEST and requested_addr != binding.ip:
|
541 |
541 |
resp_type = DHCPNAK
|
542 |
542 |
logging.info("Sending DHCPNAK to %s on %s: requested %s"
|
543 |
|
" instead of %s", binding.mac, iface, requested_addr,
|
|
543 |
" instead of %s", binding.mac, binding.tap, requested_addr,
|
544 |
544 |
binding.ip)
|
545 |
545 |
|
546 |
546 |
elif req_type in (DHCPDISCOVER, DHCPREQUEST):
|
... | ... | |
568 |
568 |
|
569 |
569 |
elif req_type == DHCPRELEASE:
|
570 |
570 |
# Log and ignore
|
571 |
|
logging.info("DHCPRELEASE from %s on %s", binding.mac, iface)
|
|
571 |
logging.info("DHCPRELEASE from %s on %s", binding.mac, binding.tap )
|
572 |
572 |
return
|
573 |
573 |
|
574 |
574 |
# Finally, always add the server identifier and end options
|
... | ... | |
580 |
580 |
resp /= DHCP(options=dhcp_options)
|
581 |
581 |
|
582 |
582 |
logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
|
583 |
|
binding.ip, iface)
|
584 |
|
self.sendp(resp, iface)
|
|
583 |
binding.ip, binding.tap)
|
|
584 |
self.sendp(resp, indev )
|
585 |
585 |
|
586 |
586 |
def rs_response(self, i, payload): # pylint: disable=W0613
|
587 |
587 |
""" Generate a reply to a BOOTP/DHCP request
|
588 |
588 |
|
589 |
589 |
"""
|
590 |
|
indev = payload.get_indev()
|
|
590 |
indevidx = payload.get_indev()
|
591 |
591 |
try:
|
592 |
592 |
# Get the actual interface from the ifindex
|
593 |
|
iface = self.ifaces[indev]
|
|
593 |
indev = self.ifaces[indevidx]
|
594 |
594 |
except KeyError:
|
595 |
595 |
logging.debug("Ignoring router solicitation on"
|
596 |
596 |
" unknown interface %d", indev)
|
... | ... | |
599 |
599 |
payload.set_verdict(nfqueue.NF_ACCEPT)
|
600 |
600 |
return
|
601 |
601 |
|
602 |
|
ifmac = self.get_iface_hw_addr(iface)
|
603 |
|
binding = [ b for b in self.clients.values() if b.ifname == iface ]
|
|
602 |
binding = [ b for b in self.clients.values() if b.tap == indev ]
|
604 |
603 |
subnet = binding[0].net6
|
605 |
604 |
if subnet.net is None:
|
606 |
605 |
logging.debug("No IPv6 network assigned for the interface")
|
... | ... | |
610 |
609 |
# Signal the kernel that it shouldn't further process the packet
|
611 |
610 |
payload.set_verdict(nfqueue.NF_DROP)
|
612 |
611 |
|
613 |
|
resp = Ether(src=self.get_iface_hw_addr(iface))/\
|
|
612 |
resp = Ether(src=self.get_iface_hw_addr(indev))/\
|
614 |
613 |
IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
|
615 |
614 |
ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
|
616 |
615 |
prefixlen=subnet.prefixlen)
|
... | ... | |
619 |
618 |
resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
|
620 |
619 |
lifetime=self.ra_period * 3)
|
621 |
620 |
|
622 |
|
logging.info("RA on %s for %s", iface, subnet.net)
|
623 |
|
self.sendp(resp, iface)
|
|
621 |
logging.info("RA on %s for %s", indev, subnet.net)
|
|
622 |
self.sendp(resp, indev)
|
624 |
623 |
|
625 |
624 |
def ns_response(self, i, payload): # pylint: disable=W0613
|
626 |
625 |
""" Generate a reply to an ICMPv6 neighbor solicitation
|
627 |
626 |
|
628 |
627 |
"""
|
629 |
|
indev = payload.get_indev()
|
|
628 |
indevidx = payload.get_indev()
|
630 |
629 |
try:
|
631 |
630 |
# Get the actual interface from the ifindex
|
632 |
|
iface = self.ifaces[indev]
|
|
631 |
indev = self.ifaces[indevidx]
|
633 |
632 |
except KeyError:
|
634 |
633 |
logging.debug("Ignoring neighbour solicitation on"
|
635 |
634 |
" unknown interface %d", indev)
|
... | ... | |
638 |
637 |
payload.set_verdict(nfqueue.NF_ACCEPT)
|
639 |
638 |
return
|
640 |
639 |
|
641 |
|
ifmac = self.get_iface_hw_addr(iface)
|
642 |
|
binding = [ b for b in self.clients.values() if b.ifname == iface ]
|
|
640 |
binding = [ b for b in self.clients.values() if b.tap == indev ]
|
643 |
641 |
subnet = binding[0].net6
|
644 |
642 |
if subnet.net is None:
|
645 |
643 |
logging.debug("No IPv6 network assigned for the interface")
|
646 |
644 |
return
|
647 |
645 |
|
648 |
|
ifll = subnet.make_ll64(ifmac)
|
|
646 |
indevmac = self.get_iface_hw_addr(indev)
|
|
647 |
|
|
648 |
ifll = subnet.make_ll64(indevmac)
|
649 |
649 |
|
650 |
650 |
ns = IPv6(payload.get_data())
|
651 |
651 |
|
... | ... | |
660 |
660 |
client_lladdr = ns.lladdr
|
661 |
661 |
except AttributeError:
|
662 |
662 |
return 1
|
663 |
|
|
664 |
|
resp = Ether(src=ifmac, dst=client_lladdr)/\
|
|
663 |
|
|
664 |
resp = Ether(src=indevmac, dst=client_lladdr)/\
|
665 |
665 |
IPv6(src=str(ifll), dst=ns.src)/\
|
666 |
666 |
ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
|
667 |
|
ICMPv6NDOptDstLLAddr(lladdr=ifmac)
|
|
667 |
ICMPv6NDOptDstLLAddr(lladdr=indevmac)
|
668 |
668 |
|
669 |
|
logging.info("NA on %s for %s", iface, ns.tgt)
|
670 |
|
self.sendp(resp, iface)
|
|
669 |
logging.info("NA on %s for %s", indev, ns.tgt)
|
|
670 |
self.sendp(resp, indev)
|
671 |
671 |
return 1
|
672 |
672 |
|
673 |
673 |
def send_periodic_ra(self):
|
... | ... | |
680 |
680 |
start = time.time()
|
681 |
681 |
i = 0
|
682 |
682 |
for binding in self.clients.values():
|
683 |
|
iface = binding.ifname
|
684 |
|
ifmac = binding.mac
|
|
683 |
tap = binding.tap
|
|
684 |
indev = binding.indev
|
|
685 |
mac = binding.mac
|
685 |
686 |
subnet = binding.net6
|
686 |
687 |
if subnet.net is None:
|
687 |
688 |
logging.debug("Skipping periodic RA on interface %s,"
|
688 |
|
" as it is not IPv6-connected", iface)
|
|
689 |
" as it is not IPv6-connected", tap)
|
689 |
690 |
continue
|
690 |
|
|
691 |
|
ifll = subnet.make_ll64(ifmac)
|
692 |
|
resp = Ether(src=ifmac)/\
|
|
691 |
indevmac = self.get_iface_hw_addr(indev)
|
|
692 |
ifll = subnet.make_ll64(indevmac)
|
|
693 |
resp = Ether(src=indevmac)/\
|
693 |
694 |
IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
|
694 |
695 |
ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
|
695 |
696 |
prefixlen=subnet.prefixlen)
|
... | ... | |
697 |
698 |
resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
|
698 |
699 |
lifetime=self.ra_period * 3)
|
699 |
700 |
try:
|
700 |
|
self.sendp(resp, iface)
|
|
701 |
self.sendp(resp, indev)
|
701 |
702 |
except socket.error, e:
|
702 |
|
logging.warn("Periodic RA on %s failed: %s", iface, str(e))
|
|
703 |
logging.warn("Periodic RA on %s failed: %s", tap, str(e))
|
703 |
704 |
except Exception, e:
|
704 |
705 |
logging.warn("Unkown error during periodic RA on %s: %s",
|
705 |
|
iface, str(e))
|
|
706 |
tap, str(e))
|
706 |
707 |
i += 1
|
707 |
708 |
logging.debug("Sent %d RAs in %.2f seconds", i, time.time() - start)
|
708 |
709 |
|