|
57 | 57 | ) |
58 | 58 |
|
59 | 59 | from scapy.arch import get_if_hwaddr |
60 | | -from scapy.sendrecv import srp1 |
| 60 | +from scapy.sendrecv import srp1, sendp |
61 | 61 | from scapy.error import warning |
62 | 62 | from scapy.config import conf |
63 | 63 |
|
@@ -542,67 +542,239 @@ def mysummary(self): |
542 | 542 | return "DHCP %s" % DHCPTypes.get(id[1], "").capitalize() |
543 | 543 | return super(DHCP, self).mysummary() |
544 | 544 |
|
| 545 | + def getopt(self, name: str): |
| 546 | + try: |
| 547 | + if not self.options: |
| 548 | + raise StopIteration |
| 549 | + return next(x[1] for x in self.options if x[0] == name) |
| 550 | + except StopIteration: |
| 551 | + raise AttributeError("Option '%s' not found !" % name) |
| 552 | + |
| 553 | + def addopt(self, name: str, value): |
| 554 | + opts = self.options |
| 555 | + if not isinstance(opts, list): |
| 556 | + opts = [] |
| 557 | + opts.insert(len(opts) - 1, (name, value)) |
| 558 | + self.options = opts |
| 559 | + |
545 | 560 |
|
546 | 561 | bind_layers(UDP, BOOTP, dport=67, sport=68) |
547 | 562 | bind_layers(UDP, BOOTP, dport=68, sport=67) |
548 | 563 | bind_bottom_up(UDP, BOOTP, dport=67, sport=67) |
549 | 564 | bind_layers(BOOTP, DHCP, options=b'c\x82Sc') |
550 | 565 |
|
551 | 566 |
|
| 567 | +def _dhcp_query( |
| 568 | + req_type: str, |
| 569 | + hw=None, |
| 570 | + dstip=None, |
| 571 | + ciaddr=None, |
| 572 | + server_id=None, |
| 573 | + requested_addr=None, |
| 574 | + fixed_ether=False, |
| 575 | + hostname=None, |
| 576 | + iface=None, |
| 577 | + wait_resp=True, |
| 578 | + **kwargs, |
| 579 | +): |
| 580 | + # We need to set checkIPaddr to False for DHCP |
| 581 | + _old_checkIPaddr = conf.checkIPaddr |
| 582 | + conf.checkIPaddr = False |
| 583 | + |
| 584 | + try: |
| 585 | + if hw is None: |
| 586 | + if iface is None: |
| 587 | + iface = conf.iface |
| 588 | + hw = get_if_hwaddr(iface) |
| 589 | + |
| 590 | + # Build the DHCP options of the request |
| 591 | + dhcp_options = [ |
| 592 | + ('message-type', req_type), |
| 593 | + ('client_id', b'\x01' + mac2str(hw)), |
| 594 | + ] |
| 595 | + if requested_addr is not None: |
| 596 | + dhcp_options.append(('requested_addr', requested_addr)) |
| 597 | + if server_id is not None: |
| 598 | + dhcp_options.append(('server_id', server_id)) |
| 599 | + if hostname is not None: |
| 600 | + dhcp_options.extend([ |
| 601 | + ('hostname', hostname), |
| 602 | + ('client_FQDN', b'\x00\x00\x00' + bytes_encode(hostname)), |
| 603 | + ]) |
| 604 | + dhcp_options.extend([ |
| 605 | + ('vendor_class_id', b'MSFT 5.0'), |
| 606 | + ('param_req_list', [ |
| 607 | + 1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252 |
| 608 | + ]), |
| 609 | + 'end' |
| 610 | + ]) |
| 611 | + |
| 612 | + # Build the packet |
| 613 | + pkt = ( |
| 614 | + Ether(dst="ff:ff:ff:ff:ff:ff", src=None if fixed_ether else hw) / |
| 615 | + IP( |
| 616 | + src=ciaddr or "0.0.0.0", |
| 617 | + dst=dstip or "255.255.255.255", |
| 618 | + ) / |
| 619 | + UDP(sport=68, dport=67) / |
| 620 | + BOOTP( |
| 621 | + chaddr=hw, |
| 622 | + ciaddr=ciaddr or "0.0.0.0", |
| 623 | + xid=RandInt(), |
| 624 | + flags="B", |
| 625 | + ) / |
| 626 | + DHCP(options=dhcp_options) |
| 627 | + ) |
| 628 | + |
| 629 | + if wait_resp: |
| 630 | + return srp1(pkt, iface=iface, **kwargs) |
| 631 | + else: |
| 632 | + return sendp(pkt, iface=iface, **kwargs) |
| 633 | + finally: |
| 634 | + conf.checkIPaddr = _old_checkIPaddr |
| 635 | + |
| 636 | + |
| 637 | +@conf.commands.register |
| 638 | +def dhcp_discover( |
| 639 | + hw=None, |
| 640 | + requested_addr=None, |
| 641 | + hostname=None, |
| 642 | + iface=None, |
| 643 | + **kwargs, |
| 644 | +): |
| 645 | + """ |
| 646 | + Send a DHCP discover and return the answer |
| 647 | +
|
| 648 | + :param hw: (optional) the source MAC |
| 649 | + :param requested_addr: (optional) ask the DHCP server for a preferred IP |
| 650 | + :param hostname: the hostname of the client (default: not specified) |
| 651 | + :param iface: the interface to send this on. (default: conf.iface) |
| 652 | +
|
| 653 | + Usage:: |
| 654 | +
|
| 655 | + >>> dhcp_discover() |
| 656 | + """ |
| 657 | + return _dhcp_query( |
| 658 | + req_type="discover", |
| 659 | + hw=hw, |
| 660 | + requested_addr=requested_addr, |
| 661 | + hostname=hostname, |
| 662 | + iface=iface, |
| 663 | + **kwargs, |
| 664 | + ) |
| 665 | + |
| 666 | + |
552 | 667 | @conf.commands.register |
553 | | -def dhcp_request(hw=None, |
554 | | - req_type='discover', |
555 | | - server_id=None, |
556 | | - requested_addr=None, |
557 | | - hostname=None, |
558 | | - iface=None, |
559 | | - **kargs): |
| 668 | +def dhcp_request( |
| 669 | + requested_addr: str, |
| 670 | + hw=None, |
| 671 | + server_id=None, |
| 672 | + hostname=None, |
| 673 | + iface=None, |
| 674 | + fixed_ether=False, |
| 675 | + **kwargs, |
| 676 | +): |
560 | 677 | """ |
561 | 678 | Send a DHCP discover request and return the answer. |
562 | 679 |
|
| 680 | + :param server_id: the server id from the OFFER |
| 681 | + :param hw: (optional) the source MAC |
| 682 | + :param requested_addr: request an IP from the DHCP server |
| 683 | + :param hostname: the hostname of the client (default: not specified) |
| 684 | + :param iface: the interface to send this on. (default: conf.iface) |
| 685 | + :param fixed_ether: (optional) use the real local mac in the Ethernet layer, |
| 686 | + and only use 'hw' in the ARP layer. |
| 687 | +
|
563 | 688 | Usage:: |
564 | 689 |
|
565 | | - >>> dhcp_request() # send DHCP discover |
566 | | - >>> dhcp_request(req_type='request', |
567 | | - ... requested_addr='10.53.4.34') # send DHCP request |
| 690 | + >>> srv = dhcp_discover() |
| 691 | + >>> dhcp_request( |
| 692 | + ... requested_addr=srv.yiaddr, |
| 693 | + ... server_id=srv[DHCP].getopt("server_id") |
| 694 | + ... ) |
568 | 695 | """ |
569 | | - if conf.checkIPaddr: |
570 | | - warning( |
571 | | - "conf.checkIPaddr is enabled, may not be able to match the answer" |
572 | | - ) |
573 | | - if hw is None: |
574 | | - if iface is None: |
575 | | - iface = conf.iface |
576 | | - hw = get_if_hwaddr(iface) |
577 | | - dhcp_options = [ |
578 | | - ('message-type', req_type), |
579 | | - ('client_id', b'\x01' + mac2str(hw)), |
580 | | - ] |
581 | | - if requested_addr is not None: |
582 | | - dhcp_options.append(('requested_addr', requested_addr)) |
583 | | - elif req_type == 'request': |
584 | | - warning("DHCP Request without requested_addr will likely be ignored") |
585 | | - if server_id is not None: |
586 | | - dhcp_options.append(('server_id', server_id)) |
587 | | - if hostname is not None: |
588 | | - dhcp_options.extend([ |
589 | | - ('hostname', hostname), |
590 | | - ('client_FQDN', b'\x00\x00\x00' + bytes_encode(hostname)), |
591 | | - ]) |
592 | | - dhcp_options.extend([ |
593 | | - ('vendor_class_id', b'MSFT 5.0'), |
594 | | - ('param_req_list', [ |
595 | | - 1, 3, 6, 15, 31, 33, 43, 44, 46, 47, 119, 121, 249, 252 |
596 | | - ]), |
597 | | - 'end' |
598 | | - ]) |
599 | | - return srp1( |
600 | | - Ether(dst="ff:ff:ff:ff:ff:ff", src=hw) / |
601 | | - IP(src="0.0.0.0", dst="255.255.255.255") / |
602 | | - UDP(sport=68, dport=67) / |
603 | | - BOOTP(chaddr=hw, xid=RandInt(), flags="B") / |
604 | | - DHCP(options=dhcp_options), |
605 | | - iface=iface, **kargs |
| 696 | + return _dhcp_query( |
| 697 | + req_type="request", |
| 698 | + hw=hw, |
| 699 | + server_id=server_id, |
| 700 | + requested_addr=requested_addr, |
| 701 | + hostname=hostname, |
| 702 | + iface=iface, |
| 703 | + fixed_ether=fixed_ether, |
| 704 | + **kwargs, |
| 705 | + ) |
| 706 | + |
| 707 | + |
| 708 | +@conf.commands.register |
| 709 | +def dhcp_release( |
| 710 | + ciaddr: str, |
| 711 | + server_id: str, |
| 712 | + hw=None, |
| 713 | + hostname=None, |
| 714 | + iface=None, |
| 715 | + fixed_ether=False, |
| 716 | + **kwargs, |
| 717 | +): |
| 718 | + """ |
| 719 | + Send a DHCP release request |
| 720 | +
|
| 721 | + :param server_id: the server id from the OFFER |
| 722 | + :param ciaddr: the IP to release |
| 723 | + :param hw: (optional) the source MAC |
| 724 | + :param hostname: the hostname of the client (default: not specified) |
| 725 | + :param iface: the interface to send this on. (default: conf.iface) |
| 726 | + :param fixed_ether: (optional) use the real local mac in the Ethernet layer, |
| 727 | + and only use 'hw' in the ARP layer. |
| 728 | +
|
| 729 | + Usage:: |
| 730 | +
|
| 731 | + >>> dhcp_release(ciaddr="10.0.0.12", server_id="10.0.0.254") |
| 732 | + """ |
| 733 | + return _dhcp_query( |
| 734 | + req_type="release", |
| 735 | + hw=hw, |
| 736 | + dstip=server_id, |
| 737 | + server_id=server_id, |
| 738 | + ciaddr=ciaddr, |
| 739 | + hostname=hostname, |
| 740 | + iface=iface, |
| 741 | + fixed_ether=fixed_ether, |
| 742 | + wait_resp=False, |
| 743 | + **kwargs, |
| 744 | + ) |
| 745 | + |
| 746 | + |
| 747 | +@conf.commands.register |
| 748 | +def dhcp_inform( |
| 749 | + ciaddr: str, |
| 750 | + hw=None, |
| 751 | + hostname=None, |
| 752 | + iface=None, |
| 753 | + fixed_ether=False, |
| 754 | + **kwargs, |
| 755 | +): |
| 756 | + """ |
| 757 | + Send a DHCP inform request |
| 758 | +
|
| 759 | + :param ciaddr: the IP to inform |
| 760 | + :param hw: (optional) the source MAC |
| 761 | + :param hostname: the hostname of the client (default: not specified) |
| 762 | + :param iface: the interface to send this on. (default: conf.iface) |
| 763 | + :param fixed_ether: (optional) use the real local mac in the Ethernet layer, |
| 764 | + and only use 'hw' in the ARP layer. |
| 765 | +
|
| 766 | + Usage:: |
| 767 | +
|
| 768 | + >>> dhcp_inform(ciaddr="10.0.0.12") |
| 769 | + """ |
| 770 | + return _dhcp_query( |
| 771 | + req_type="inform", |
| 772 | + hw=hw, |
| 773 | + ciaddr=ciaddr, |
| 774 | + hostname=hostname, |
| 775 | + iface=iface, |
| 776 | + fixed_ether=fixed_ether, |
| 777 | + **kwargs, |
606 | 778 | ) |
607 | 779 |
|
608 | 780 |
|
|
0 commit comments