Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 29 additions & 10 deletions scapy/layers/dhcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@
from scapy.error import warning
from scapy.config import conf

# Typing imports
from typing import (
List,
Optional,
Union,
)

dhcpmagic = b"c\x82Sc"


Expand Down Expand Up @@ -601,27 +608,33 @@ class BOOTP_am(AnsweringMachine):
filter = "udp and port 68 and port 67"

def parse_options(self,
pool=Net("192.168.1.128/25"),
network="192.168.1.0/24",
gw="192.168.1.1",
nameserver=None,
domain=None,
renewal_time=60,
lease_time=1800,
pool: Union[Net, List[str]] = Net("192.168.1.128/25"),
network: str = "192.168.1.0/24",
gw: str = "192.168.1.1",
nameserver: Union[str, List[str]] = None,
domain: Optional[str] = None,
renewal_time: int = 60,
lease_time: int = 1800,
**kwargs):
"""
:param pool: the range of addresses to distribute. Can be a Net,
a list of IPs or a string (always gives the same IP).
:param network: the subnet range
:param gw: the gateway IP (can be None)
:param nameserver: the DNS server IP (by default, same than gw)
:param nameserver: the DNS server IP (by default, same than gw).
This can also be a list.
:param domain: the domain to advertise (can be None)

Other DHCP parameters can be passed as kwargs. See DHCPOptions in dhcp.py.
For instance::

dhcpd(pool=Net("10.0.10.0/24"), network="10.0.0.0/8", gw="10.0.10.1",
classless_static_routes=["1.2.3.4/32:9.8.7.6"])

Other example with different options::

dhcpd(pool=Net("10.0.10.0/24"), network="10.0.0.0/8", gw="10.0.10.1",
nameserver=["8.8.8.8", "4.4.4.4"], domain="DOMAIN.LOCAL")
"""
self.domain = domain
netw, msk = (network.split("/") + ["32"])[:2]
Expand All @@ -630,7 +643,13 @@ def parse_options(self,
self.network = ltoa(atol(netw) & msk)
self.broadcast = ltoa(atol(self.network) | (0xffffffff & ~msk))
self.gw = gw
self.nameserver = nameserver or gw
if nameserver is None:
self.nameserver = (gw,)
elif isinstance(nameserver, str):
self.nameserver = (nameserver,)
else:
self.nameserver = tuple(nameserver)

if isinstance(pool, str):
pool = Net(pool)
if isinstance(pool, Iterable):
Expand Down Expand Up @@ -691,7 +710,7 @@ def make_reply(self, req):
("server_id", self.gw),
("domain", self.domain),
("router", self.gw),
("name_server", self.nameserver),
("name_server", *self.nameserver),
("broadcast_address", self.broadcast),
("subnet_mask", self.netmask),
("renewal_time", self.renewal_time),
Expand Down
11 changes: 11 additions & 0 deletions test/answering_machines.uts
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,23 @@ test_am(BOOTP_am,
def check_DHCP_am_reply(packet):
assert DHCP in packet and len(packet[DHCP].options)
assert ("domain", b"localnet") in packet[DHCP].options
assert ('name_server', '192.168.1.1') in packet[DHCP].options

def check_ns_DHCP_am_reply(packet):
assert DHCP in packet and len(packet[DHCP].options)
assert ("domain", b"localnet") in packet[DHCP].options
assert ('name_server', '1.1.1.1', '2.2.2.2') in packet[DHCP].options

test_am(DHCP_am,
Ether()/IP()/UDP()/BOOTP(op=1)/DHCP(options=[('message-type', 'request')]),
check_DHCP_am_reply,
domain="localnet")

test_am(DHCP_am,
Ether()/IP()/UDP()/BOOTP(op=1)/DHCP(options=[('message-type', 'request')]),
check_ns_DHCP_am_reply,
domain="localnet",
nameserver=["1.1.1.1", "2.2.2.2"])

= ARP_am
def check_ARP_am_reply(packet):
Expand Down
11 changes: 10 additions & 1 deletion test/scapy/layers/dhcp.uts
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,14 @@ assert DHCPRevOptions['static-routes'][0] == 33

assert dhcpd
import IPython
assert IPython.lib.pretty.pretty(dhcpd) == '<function scapy.ansmachine.dhcpd(self, pool=Net("192.168.1.128/25"), network=\'192.168.1.0/24\', gw=\'192.168.1.1\', nameserver=None, domain=None, renewal_time=60, lease_time=1800, **kwargs)>'

result = IPython.lib.pretty.pretty(dhcpd)
result

# 3 results depending on the Python version
assert result in [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it fails on Fedora Rawhide (with Python 3.14.0rc2):

###(006)=[failed] Check that the dhcpd alias is properly defined and documented

>>> assert dhcpd
>>> import IPython
>>> 
>>> result = IPython.lib.pretty.pretty(dhcpd)
>>> result
'<function scapy.ansmachine.dhcpd(self, pool: scapy.base_classes.Net | List[str] = Net("192.168.1.128/25"), network: str = \'192.168.1.0/24\', gw: str = \'192.168.1.1\', nameserver: str | List[str] = None, domain: str | None = None, renewal_time: int = 60, lease_time: int = 1800, **kwargs)>'
>>> 
>>> assert result in [
...     '<function scapy.ansmachine.dhcpd(self, pool: Union[scapy.base_classes.Net, List[str]] = Net("192.168.1.128/25"), network: str = \'192.168.1.0/24\', gw: str = \'192.168.1.1\', nameserver: Union[str, List[str]] = None, domain: Optional[str] = None, renewal_time: int = 60, lease_time: int = 1800, **kwargs)>',
...     '<function scapy.ansmachine.dhcpd(self, pool: Union[scapy.base_classes.Net, List[str]] = Net("192.168.1.128/25"), network: str = \'192.168.1.0/24\', gw: str = \'192.168.1.1\', nameserver: Union[str, List[str]] = None, domain: Union[str, NoneType] = None, renewal_time: int = 60, lease_time: int = 1800, **kwargs)>',
...     '<function scapy.ansmachine.dhcpd(self, pool=Net("192.168.1.128/25"), network=\'192.168.1.0/24\', gw=\'192.168.1.1\', nameserver=None, domain=None, renewal_time=60, lease_time=1800, **kwargs)>',
... ]
AssertionError

https://download.copr.fedorainfracloud.org/results/packit/evverx-scapy-2/fedora-rawhide-aarch64/09564744-scapy/builder-live.log.gz

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks ! See befe516

This varies a LOT depending on the Python version hehe ^^

'<function scapy.ansmachine.dhcpd(self, pool: Union[scapy.base_classes.Net, List[str]] = Net("192.168.1.128/25"), network: str = \'192.168.1.0/24\', gw: str = \'192.168.1.1\', nameserver: Union[str, List[str]] = None, domain: Optional[str] = None, renewal_time: int = 60, lease_time: int = 1800, **kwargs)>',
'<function scapy.ansmachine.dhcpd(self, pool: Union[scapy.base_classes.Net, List[str]] = Net("192.168.1.128/25"), network: str = \'192.168.1.0/24\', gw: str = \'192.168.1.1\', nameserver: Union[str, List[str]] = None, domain: Union[str, NoneType] = None, renewal_time: int = 60, lease_time: int = 1800, **kwargs)>',
'<function scapy.ansmachine.dhcpd(self, pool=Net("192.168.1.128/25"), network=\'192.168.1.0/24\', gw=\'192.168.1.1\', nameserver=None, domain=None, renewal_time=60, lease_time=1800, **kwargs)>',
]

Loading