135 |
135 |
gateway = None
|
136 |
136 |
subnet6 = None
|
137 |
137 |
gateway6 = None
|
|
138 |
eui64 = None
|
138 |
139 |
|
139 |
140 |
for line in iffile:
|
140 |
141 |
if line.startswith("IP="):
|
... | ... | |
154 |
155 |
subnet6 = line.strip().split("=")[1]
|
155 |
156 |
elif line.startswith("GATEWAY6="):
|
156 |
157 |
gateway6 = line.strip().split("=")[1]
|
|
158 |
elif line.startswith("EUI64="):
|
|
159 |
eui64 = line.strip().split("=")[1]
|
157 |
160 |
|
158 |
161 |
return Client(tap=tap, mac=mac, ips=ips,
|
159 |
162 |
hostname=hostname, indev=indev, subnet=subnet,
|
160 |
|
gateway=gateway, subnet6=subnet6, gateway6=gateway6 )
|
|
163 |
gateway=gateway, subnet6=subnet6, gateway6=gateway6, eui64=eui64 )
|
161 |
164 |
|
162 |
165 |
class ClientFileHandler(pyinotify.ProcessEvent):
|
163 |
166 |
def __init__(self, server):
|
... | ... | |
183 |
186 |
|
184 |
187 |
class Client(object):
|
185 |
188 |
def __init__(self, tap=None, indev=None, mac=None, ips=None, hostname=None,
|
186 |
|
subnet=None, gateway=None, subnet6=None, gateway6=None ):
|
|
189 |
subnet=None, gateway=None, subnet6=None, gateway6=None, eui64=None ):
|
187 |
190 |
self.mac = mac
|
188 |
191 |
self.ips = ips
|
189 |
192 |
self.hostname = hostname
|
... | ... | |
195 |
198 |
self.subnet6 = subnet6
|
196 |
199 |
self.gateway6 = gateway6
|
197 |
200 |
self.net6 = Subnet(net=subnet6, gw=gateway6, dev=tap)
|
|
201 |
self.eui64 = eui64
|
198 |
202 |
|
199 |
203 |
@property
|
200 |
204 |
def ip(self):
|
... | ... | |
296 |
300 |
|
297 |
301 |
self.clients = {}
|
298 |
302 |
#self.subnets = {}
|
299 |
|
self.ifaces = {}
|
|
303 |
#self.ifaces = {}
|
300 |
304 |
#self.v6nets = {}
|
301 |
305 |
self.nfq = {}
|
302 |
306 |
self.l2socket = socket.socket(socket.AF_PACKET,
|
... | ... | |
452 |
456 |
if binding.is_valid():
|
453 |
457 |
self.clients[binding.mac] = binding
|
454 |
458 |
logging.debug("Added client %s on %s", binding.hostname, tap)
|
455 |
|
self.ifaces[ifindex] = binding.indev
|
|
459 |
logging.debug("clients %s", self.clients.keys())
|
456 |
460 |
|
457 |
461 |
def remove_tap(self, tap):
|
458 |
462 |
""" Cleanup clients on a removed interface
|
459 |
463 |
|
460 |
464 |
"""
|
461 |
|
for mac in self.clients.keys():
|
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
|
467 |
|
del self.clients[mac]
|
468 |
|
|
469 |
|
for ifindex in self.ifaces.keys():
|
470 |
|
if self.ifaces[ifindex] == indev == tap:
|
471 |
|
del self.ifaces[ifindex]
|
|
465 |
for b in self.clients.values():
|
|
466 |
if b.tap == tap:
|
|
467 |
del b
|
472 |
468 |
|
473 |
469 |
logging.debug("Removed interface %s", tap)
|
474 |
470 |
|
... | ... | |
476 |
472 |
""" Generate a reply to bnetfilter-queue-deva BOOTP/DHCP request
|
477 |
473 |
|
478 |
474 |
"""
|
479 |
|
indevidx = payload.get_indev()
|
480 |
|
try:
|
481 |
|
# Get the actual interface from the ifindex
|
482 |
|
indev = self.ifaces[indevidx]
|
483 |
|
except KeyError:
|
484 |
|
# We don't know anything about this interface, so accept the packet
|
485 |
|
# and return
|
486 |
|
logging.debug("Ignoring DHCP request on unknown iface %d", indev)
|
487 |
|
# We don't know what to do with this packet, so let the kernel
|
488 |
|
# handle it
|
489 |
|
payload.set_verdict(nfqueue.NF_ACCEPT)
|
490 |
|
return
|
491 |
|
|
492 |
475 |
# Decode the response - NFQUEUE relays IP packets
|
493 |
476 |
pkt = IP(payload.get_data())
|
494 |
477 |
|
495 |
|
# Signal the kernel that it shouldn't further process the packet
|
496 |
|
payload.set_verdict(nfqueue.NF_DROP)
|
497 |
|
|
498 |
478 |
# Get the client MAC address
|
499 |
479 |
resp = pkt.getlayer(BOOTP).copy()
|
500 |
480 |
hlen = resp.hlen
|
... | ... | |
508 |
488 |
try:
|
509 |
489 |
binding = self.clients[mac]
|
510 |
490 |
except KeyError:
|
511 |
|
logging.warn("Invalid client %s on %s", mac, indev)
|
|
491 |
logging.warn("Invalid client for mac %s ", mac)
|
|
492 |
payload.set_verdict(nfqueue.NF_ACCEPT)
|
512 |
493 |
return
|
513 |
494 |
|
514 |
|
if indev != binding.indev:
|
515 |
|
logging.warn("Received spoofed DHCP request for %s from interface"
|
516 |
|
" %s instead of %s", mac, indev, binding.indev)
|
517 |
|
return
|
|
495 |
# Signal the kernel that it shouldn't further process the packet
|
|
496 |
payload.set_verdict(nfqueue.NF_DROP)
|
518 |
497 |
|
519 |
|
resp = Ether(dst=mac, src=self.get_iface_hw_addr(indev))/\
|
|
498 |
resp = Ether(dst=mac, src=self.get_iface_hw_addr(binding.indev))/\
|
520 |
499 |
IP(src=DHCP_DUMMY_SERVER_IP, dst=binding.ip)/\
|
521 |
500 |
UDP(sport=pkt.dport, dport=pkt.sport)/resp
|
522 |
501 |
subnet = binding.net
|
... | ... | |
579 |
558 |
]
|
580 |
559 |
resp /= DHCP(options=dhcp_options)
|
581 |
560 |
|
|
561 |
if payload.get_indev() != self.get_ifindex(binding.indev):
|
|
562 |
logging.warn("Received spoofed DHCP request for %s from interface"
|
|
563 |
" %s instead of %s", mac, payload.get_indev(), binding.indev)
|
|
564 |
return
|
|
565 |
|
582 |
566 |
logging.info("%s to %s (%s) on %s", DHCP_TYPES[resp_type], mac,
|
583 |
567 |
binding.ip, binding.tap)
|
584 |
|
self.sendp(resp, indev )
|
|
568 |
self.sendp(resp, binding.indev)
|
585 |
569 |
|
586 |
570 |
def rs_response(self, i, payload): # pylint: disable=W0613
|
587 |
571 |
""" Generate a reply to a BOOTP/DHCP request
|
588 |
572 |
|
589 |
573 |
"""
|
590 |
|
indevidx = payload.get_indev()
|
|
574 |
pkt = IPv6(payload.get_data())
|
|
575 |
mac = pkt.lladdr
|
|
576 |
logging.debug("rs for mac %s", mac)
|
591 |
577 |
try:
|
592 |
|
# Get the actual interface from the ifindex
|
593 |
|
indev = self.ifaces[indevidx]
|
|
578 |
binding = self.clients[mac]
|
594 |
579 |
except KeyError:
|
595 |
580 |
logging.debug("Ignoring router solicitation on"
|
596 |
|
" unknown interface %d", indev)
|
|
581 |
" for mac %s", mac)
|
597 |
582 |
# We don't know what to do with this packet, so let the kernel
|
598 |
583 |
# handle it
|
599 |
584 |
payload.set_verdict(nfqueue.NF_ACCEPT)
|
600 |
585 |
return
|
|
586 |
|
|
587 |
# Signal the kernel that it shouldn't further process the packet
|
|
588 |
payload.set_verdict(nfqueue.NF_DROP)
|
|
589 |
|
|
590 |
subnet = binding.net6
|
601 |
591 |
|
602 |
|
binding = [ b for b in self.clients.values() if b.tap == indev ]
|
603 |
|
subnet = binding[0].net6
|
604 |
592 |
if subnet.net is None:
|
605 |
593 |
logging.debug("No IPv6 network assigned for the interface")
|
606 |
594 |
return
|
|
595 |
|
|
596 |
ifmac = self.get_iface_hw_addr(binding.indev)
|
607 |
597 |
ifll = subnet.make_ll64(ifmac)
|
608 |
598 |
|
609 |
|
# Signal the kernel that it shouldn't further process the packet
|
610 |
|
payload.set_verdict(nfqueue.NF_DROP)
|
611 |
599 |
|
612 |
|
resp = Ether(src=self.get_iface_hw_addr(indev))/\
|
|
600 |
resp = Ether(src=ifmac)/\
|
613 |
601 |
IPv6(src=str(ifll))/ICMPv6ND_RA(routerlifetime=14400)/\
|
614 |
602 |
ICMPv6NDOptPrefixInfo(prefix=str(subnet.prefix),
|
615 |
603 |
prefixlen=subnet.prefixlen)
|
... | ... | |
618 |
606 |
resp /= ICMPv6NDOptRDNSS(dns=self.ipv6_nameservers,
|
619 |
607 |
lifetime=self.ra_period * 3)
|
620 |
608 |
|
621 |
|
logging.info("RA on %s for %s", indev, subnet.net)
|
622 |
|
self.sendp(resp, indev)
|
|
609 |
logging.info("RA on %s for %s", binding.indev, subnet.net)
|
|
610 |
self.sendp(resp, binding.indev)
|
623 |
611 |
|
624 |
612 |
def ns_response(self, i, payload): # pylint: disable=W0613
|
625 |
613 |
""" Generate a reply to an ICMPv6 neighbor solicitation
|
626 |
614 |
|
627 |
615 |
"""
|
628 |
|
indevidx = payload.get_indev()
|
629 |
|
try:
|
630 |
|
# Get the actual interface from the ifindex
|
631 |
|
indev = self.ifaces[indevidx]
|
632 |
|
except KeyError:
|
|
616 |
ns = IPv6(payload.get_data())
|
|
617 |
|
|
618 |
binding = None
|
|
619 |
for b in self.clients.values():
|
|
620 |
if b.eui64 == ns.tgt:
|
|
621 |
binding = b
|
|
622 |
break
|
|
623 |
|
|
624 |
if binding is None:
|
633 |
625 |
logging.debug("Ignoring neighbour solicitation on"
|
634 |
|
" unknown interface %d", indev)
|
|
626 |
" for eui64 %s", ns.tgt)
|
635 |
627 |
# We don't know what to do with this packet, so let the kernel
|
636 |
628 |
# handle it
|
637 |
629 |
payload.set_verdict(nfqueue.NF_ACCEPT)
|
638 |
630 |
return
|
639 |
631 |
|
640 |
|
binding = [ b for b in self.clients.values() if b.tap == indev ]
|
641 |
|
subnet = binding[0].net6
|
|
632 |
payload.set_verdict(nfqueue.NF_DROP)
|
|
633 |
|
|
634 |
subnet = binding.net6
|
642 |
635 |
if subnet.net is None:
|
643 |
636 |
logging.debug("No IPv6 network assigned for the interface")
|
644 |
637 |
return
|
645 |
638 |
|
646 |
|
indevmac = self.get_iface_hw_addr(indev)
|
|
639 |
indevmac = self.get_iface_hw_addr(binding.indev)
|
647 |
640 |
|
648 |
641 |
ifll = subnet.make_ll64(indevmac)
|
649 |
642 |
|
650 |
|
ns = IPv6(payload.get_data())
|
651 |
|
|
652 |
643 |
if not (subnet.net.overlaps(ns.tgt) or str(ns.tgt) == str(ifll)):
|
653 |
644 |
logging.debug("Received NS for a non-routable IP (%s)", ns.tgt)
|
654 |
645 |
payload.set_verdict(nfqueue.NF_ACCEPT)
|
655 |
646 |
return 1
|
656 |
647 |
|
657 |
|
payload.set_verdict(nfqueue.NF_DROP)
|
658 |
|
|
659 |
|
try:
|
660 |
|
client_lladdr = ns.lladdr
|
661 |
|
except AttributeError:
|
662 |
|
return 1
|
663 |
|
|
664 |
|
resp = Ether(src=indevmac, dst=client_lladdr)/\
|
|
648 |
logging.debug("na ether %s %s", binding.mac, ns.src)
|
|
649 |
resp = Ether(src=indevmac, dst=binding.mac)/\
|
665 |
650 |
IPv6(src=str(ifll), dst=ns.src)/\
|
666 |
651 |
ICMPv6ND_NA(R=1, O=0, S=1, tgt=ns.tgt)/\
|
667 |
652 |
ICMPv6NDOptDstLLAddr(lladdr=indevmac)
|
668 |
653 |
|
669 |
|
logging.info("NA on %s for %s", indev, ns.tgt)
|
670 |
|
self.sendp(resp, indev)
|
671 |
|
return 1
|
|
654 |
logging.info("NA on %s for %s", binding.indev, ns.tgt)
|
|
655 |
self.sendp(resp, binding.indev)
|
672 |
656 |
|
673 |
657 |
def send_periodic_ra(self):
|
674 |
658 |
# Use a separate thread as this may take a _long_ time with
|