Skip to content

DHCP state machine error handling #80

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jan 9, 2023
151 changes: 80 additions & 71 deletions adafruit_wiznet5k/adafruit_wiznet5k_dhcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
DHCP_HLENETHERNET = const(0x06)
DHCP_HOPS = const(0x00)

MAGIC_COOKIE = const(0x63825363)
MAGIC_COOKIE = b"c\x82Sc" # Four bytes 99.130.83.99
MAX_DHCP_OPT = const(0x10)

# Default DHCP Server port
Expand Down Expand Up @@ -179,7 +179,7 @@ def send_dhcp_message(
# Transaction ID (xid)
self._initial_xid = htonl(self._transaction_id)
self._initial_xid = self._initial_xid.to_bytes(4, "big")
_BUFF[4:7] = self._initial_xid
_BUFF[4:8] = self._initial_xid

# seconds elapsed
_BUFF[8] = (int(time_elapsed) & 0xFF00) >> 8
Expand All @@ -195,18 +195,15 @@ def send_dhcp_message(
# as they're already set to 0.0.0.0
# Except when renewing, then fill in ciaddr
if renew:
_BUFF[12:15] = bytes(self.local_ip)
_BUFF[12:16] = bytes(self.local_ip)

# chaddr
_BUFF[28:34] = self._mac_address

# NOTE: 192 octets of 0's, BOOTP legacy

# Magic Cookie
_BUFF[236] = (MAGIC_COOKIE >> 24) & 0xFF
_BUFF[237] = (MAGIC_COOKIE >> 16) & 0xFF
_BUFF[238] = (MAGIC_COOKIE >> 8) & 0xFF
_BUFF[239] = MAGIC_COOKIE & 0xFF
_BUFF[236:240] = MAGIC_COOKIE

# Option - DHCP Message Type
_BUFF[240] = 53
Expand Down Expand Up @@ -262,10 +259,10 @@ def send_dhcp_message(
# pylint: disable=too-many-branches, too-many-statements
def parse_dhcp_response(
self,
) -> Union[Tuple[int, bytes], Tuple[int, int]]:
) -> Tuple[int, bytearray]:
"""Parse DHCP response from DHCP server.

:return Union[Tuple[int, bytes], Tuple[int, int]]: DHCP packet type.
:return Tuple[int, bytearray]: DHCP packet type and ID.
"""
# store packet in buffer
_BUFF = self._sock.recv()
Expand All @@ -274,22 +271,20 @@ def parse_dhcp_response(

# -- Parse Packet, FIXED -- #
# Validate OP
assert (
_BUFF[0] == DHCP_BOOT_REPLY
), "Malformed Packet - \
DHCP message OP is not expected BOOT Reply."
if _BUFF[0] != DHCP_BOOT_REPLY:
raise ValueError("DHCP message OP is not expected BOOTP Reply.")

xid = _BUFF[4:8]
if bytes(xid) < self._initial_xid:
print("f")
return 0, 0
if bytes(xid) != self._initial_xid:
raise ValueError("DHCP response ID mismatch.")

self.local_ip = tuple(_BUFF[16:20])
if _BUFF[28:34] == 0:
return 0, 0
# Check that there is a server ID.
if _BUFF[28:34] == b"\x00\x00\x00\x00\x00\x00":
raise ValueError("No DHCP server ID in the response.")

if int.from_bytes(_BUFF[235:240], "big") != MAGIC_COOKIE:
return 0, 0
if _BUFF[236:240] != MAGIC_COOKIE:
raise ValueError("No DHCP Magic Cookie in the response.")

# -- Parse Packet, VARIABLE -- #
ptr = 240
Expand Down Expand Up @@ -322,8 +317,8 @@ def parse_dhcp_response(
ptr += 1
opt_len = _BUFF[ptr]
ptr += 1
self.gateway_ip = tuple(_BUFF[ptr : ptr + opt_len])
ptr += opt_len
self.gateway_ip = tuple(_BUFF[ptr : ptr + 4])
ptr += opt_len # still increment even though we only read 1 addr.
elif _BUFF[ptr] == DNS_SERVERS:
ptr += 1
opt_len = _BUFF[ptr]
Expand Down Expand Up @@ -426,65 +421,79 @@ def _dhcp_state_machine(self) -> None:
if self._sock.available():
if self._debug:
print("* DHCP: Parsing OFFER")
msg_type, xid = self.parse_dhcp_response()
if msg_type == DHCP_OFFER:
# Check if transaction ID matches, otherwise it may be an offer
# for another device
if htonl(self._transaction_id) == int.from_bytes(xid, "big"):
if self._debug:
print(
"* DHCP: Send request to {}".format(self.dhcp_server_ip)
try:
msg_type, xid = self.parse_dhcp_response()
except ValueError as error:
if self._debug:
print(error)
else:
if msg_type == DHCP_OFFER:
# Check if transaction ID matches, otherwise it may be an offer
# for another device
if htonl(self._transaction_id) == int.from_bytes(xid, "big"):
if self._debug:
print(
"* DHCP: Send request to {}".format(
self.dhcp_server_ip
)
)
self._transaction_id = (
self._transaction_id + 1
) & 0x7FFFFFFF
self.send_dhcp_message(
DHCP_REQUEST, (time.monotonic() - self._start_time)
)
self._transaction_id = (self._transaction_id + 1) & 0x7FFFFFFF
self.send_dhcp_message(
DHCP_REQUEST, (time.monotonic() - self._start_time)
)
self._dhcp_state = STATE_DHCP_REQUEST
self._dhcp_state = STATE_DHCP_REQUEST
else:
if self._debug:
print("* DHCP: Received OFFER with non-matching xid")
else:
if self._debug:
print("* DHCP: Received OFFER with non-matching xid")
else:
if self._debug:
print("* DHCP: Received DHCP Message is not OFFER")
print("* DHCP: Received DHCP Message is not OFFER")

elif self._dhcp_state == STATE_DHCP_REQUEST:
if self._sock.available():
if self._debug:
print("* DHCP: Parsing ACK")
msg_type, xid = self.parse_dhcp_response()
# Check if transaction ID matches, otherwise it may be
# for another device
if htonl(self._transaction_id) == int.from_bytes(xid, "big"):
if msg_type == DHCP_ACK:
if self._debug:
print("* DHCP: Successful lease")
self._sock.close()
self._sock = None
self._dhcp_state = STATE_DHCP_LEASED
self._last_lease_time = self._start_time
if self._lease_time == 0:
self._lease_time = DEFAULT_LEASE_TIME
if self._t1 == 0:
# T1 is 50% of _lease_time
self._t1 = self._lease_time >> 1
if self._t2 == 0:
# T2 is 87.5% of _lease_time
self._t2 = self._lease_time - (self._lease_time >> 3)
self._renew_in_sec = self._t1
self._rebind_in_sec = self._t2
self._eth.ifconfig = (
self.local_ip,
self.subnet_mask,
self.gateway_ip,
self.dns_server_ip,
)
gc.collect()
try:
msg_type, xid = self.parse_dhcp_response()
except ValueError as error:
if self._debug:
print(error)
else:
# Check if transaction ID matches, otherwise it may be
# for another device
if htonl(self._transaction_id) == int.from_bytes(xid, "big"):
if msg_type == DHCP_ACK:
if self._debug:
print("* DHCP: Successful lease")
self._sock.close()
self._sock = None
self._dhcp_state = STATE_DHCP_LEASED
self._last_lease_time = self._start_time
if self._lease_time == 0:
self._lease_time = DEFAULT_LEASE_TIME
if self._t1 == 0:
# T1 is 50% of _lease_time
self._t1 = self._lease_time >> 1
if self._t2 == 0:
# T2 is 87.5% of _lease_time
self._t2 = self._lease_time - (self._lease_time >> 3)
self._renew_in_sec = self._t1
self._rebind_in_sec = self._t2
self._eth.ifconfig = (
self.local_ip,
self.subnet_mask,
self.gateway_ip,
self.dns_server_ip,
)
gc.collect()
else:
if self._debug:
print("* DHCP: Received DHCP Message is not ACK")
else:
if self._debug:
print("* DHCP: Received DHCP Message is not ACK")
else:
if self._debug:
print("* DHCP: Received non-matching xid")
print("* DHCP: Received non-matching xid")

elif self._dhcp_state == STATE_DHCP_WAIT:
if time.monotonic() > (self._start_time + DHCP_WAIT_TIME):
Expand Down
1 change: 1 addition & 0 deletions optional_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# SPDX-FileCopyrightText: 2022 Alec Delaney, for Adafruit Industries
#
# SPDX-License-Identifier: Unlicense
pytest
Loading