Skip to content

Commit 929066f

Browse files
authored
Various layer fixes (#5014)
* dtp: fix missing iface= when sending AI-Assisted: no * dns: add example to dnssd AI-Assisted: no * llmnr: replace DestField by DestIPField AI-Assisted: no * dhcp: simplify commands AI-Assisted: no * ldap: ability to select site in dclocator AI-Assisted: no
1 parent 62b9487 commit 929066f

7 files changed

Lines changed: 245 additions & 57 deletions

File tree

.config/codespell_ignore.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
abd
22
aci
3+
addopt
34
ans
45
applikation
56
archtypes

scapy/contrib/dtp.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,4 @@ def negotiate_trunk(iface=conf.iface, mymac=str(RandMAC())):
101101
p = Dot3(src=mymac, dst="01:00:0c:cc:cc:cc") / LLC()
102102
p /= SNAP()
103103
p /= DTP(tlvlist=[DTPDomain(), DTPStatus(), DTPType(), DTPNeighbor(neighbor=mymac)]) # noqa: E501
104-
sendp(p)
104+
sendp(p, iface=iface)

scapy/layers/dhcp.py

Lines changed: 220 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
)
5858

5959
from scapy.arch import get_if_hwaddr
60-
from scapy.sendrecv import srp1
60+
from scapy.sendrecv import srp1, sendp
6161
from scapy.error import warning
6262
from scapy.config import conf
6363

@@ -542,67 +542,239 @@ def mysummary(self):
542542
return "DHCP %s" % DHCPTypes.get(id[1], "").capitalize()
543543
return super(DHCP, self).mysummary()
544544

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+
545560

546561
bind_layers(UDP, BOOTP, dport=67, sport=68)
547562
bind_layers(UDP, BOOTP, dport=68, sport=67)
548563
bind_bottom_up(UDP, BOOTP, dport=67, sport=67)
549564
bind_layers(BOOTP, DHCP, options=b'c\x82Sc')
550565

551566

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+
552667
@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+
):
560677
"""
561678
Send a DHCP discover request and return the answer.
562679
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+
563688
Usage::
564689
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+
... )
568695
"""
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,
606778
)
607779

608780

scapy/layers/dns.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2040,6 +2040,11 @@ def dnssd(service="_services._dns-sd._udp.local",
20402040
:param af: the transport to use. socket.AF_INET or socket.AF_INET6
20412041
:param qtype: the type to use in the mDNS. Either TXT, PTR or SRV.
20422042
:param iface: the interface to do this discovery on.
2043+
2044+
Examples::
2045+
2046+
>>> dnssd(service='_companion-link._tcp.local.').show()
2047+
>>> dnssd(service='_spotify-connect._tcp.local.').show()
20432048
"""
20442049
if af == socket.AF_INET:
20452050
pkt = IP(dst=ScopedIP("224.0.0.251", iface), ttl=255)

scapy/layers/ldap.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,12 +1476,20 @@ def make_mailslot_ping_reply(self, req):
14761476

14771477
@conf.commands.register
14781478
def dclocator(
1479-
realm, qtype="A", mode="ldap", port=None, timeout=1, NtVersion=None, debug=0
1479+
realm: str,
1480+
site: str = "",
1481+
qtype: str = "A",
1482+
mode: str = "ldap",
1483+
port: int = None,
1484+
timeout: int = 1,
1485+
NtVersion: int = None,
1486+
debug: int = 0,
14801487
):
14811488
"""
14821489
Perform a DC Locator as per [MS-ADTS] sect 6.3.6 or RFC4120.
14831490
14841491
:param realm: the kerberos realm to locate
1492+
:param site: if provided, a Site name
14851493
:param mode: Detect if a server is up and joinable thanks to one of:
14861494
14871495
- 'nocheck': Do not check that servers are online.
@@ -1504,12 +1512,15 @@ def dclocator(
15041512
| 0x20000000 # IP
15051513
)
15061514
# Check cache
1507-
cache_ident = ";".join([realm, qtype, mode, str(NtVersion)]).lower()
1515+
cache_ident = ";".join([realm, qtype, mode, str(NtVersion), site]).lower()
15081516
if cache_ident in _dclocatorcache:
15091517
return _dclocatorcache[cache_ident]
15101518
# Perform DNS-Based discovery (6.3.6.1)
15111519
# 1. SRV records
1512-
qname = "_kerberos._tcp.dc._msdcs.%s" % realm.lower()
1520+
if site:
1521+
qname = ("_kerberos._tcp.%s._sites.dc._msdcs.%s" % (site, realm)).lower()
1522+
else:
1523+
qname = "_kerberos._tcp.dc._msdcs.%s" % realm.lower()
15131524
if debug:
15141525
log_runtime.info("DC Locator: requesting SRV for '%s' ..." % qname)
15151526
try:

scapy/layers/llmnr.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,12 @@
1717
from scapy.fields import (
1818
BitEnumField,
1919
BitField,
20-
DestField,
2120
DestIP6Field,
2221
ShortField,
2322
)
2423
from scapy.packet import Packet, bind_layers, bind_bottom_up
2524
from scapy.compat import orb
26-
from scapy.layers.inet import UDP
25+
from scapy.layers.inet import UDP, DestIPField
2726
from scapy.layers.dns import (
2827
DNSCompressedPacket,
2928
DNS_am,
@@ -103,8 +102,8 @@ def dispatch_hook(cls, _pkt=None, *args, **kargs):
103102
bind_bottom_up(UDP, _LLMNR, sport=5355)
104103
bind_layers(UDP, _LLMNR, sport=5355, dport=5355)
105104

106-
DestField.bind_addr(LLMNRQuery, _LLMNR_IPv4_mcast_addr, dport=5355)
107-
DestField.bind_addr(LLMNRResponse, _LLMNR_IPv4_mcast_addr, dport=5355)
105+
DestIPField.bind_addr(LLMNRQuery, _LLMNR_IPv4_mcast_addr, dport=5355)
106+
DestIPField.bind_addr(LLMNRResponse, _LLMNR_IPv4_mcast_addr, dport=5355)
108107
DestIP6Field.bind_addr(LLMNRQuery, _LLMNR_IPv6_mcast_Addr, dport=5355)
109108
DestIP6Field.bind_addr(LLMNRResponse, _LLMNR_IPv6_mcast_Addr, dport=5355)
110109

test/contrib/dtp.uts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ assert pkt[DTP].tlvlist[3].status == b'\x03'
1818

1919
from unittest import mock
2020

21-
def test_pkt(pkt):
21+
def test_pkt(pkt, iface=None):
2222
pkt = Ether(raw(pkt))
2323
assert DTP in pkt
2424
assert len(pkt[DTP].tlvlist) == 4

0 commit comments

Comments
 (0)