def test__returns_server_identifier_if_included(self): server_ip = factory.make_ip_address(ipv6=False) packet = factory.make_dhcp_packet( include_server_identifier=True, server_ip=server_ip) dhcp = DHCP(packet) self.assertThat(dhcp.is_valid(), Equals(True)) self.assertThat(dhcp.server_identifier, Equals(IPAddress(server_ip)))
def test_is_valid_returns_false_for_truncated_option_value(self): packet = factory.make_dhcp_packet(truncated_option_value=True) dhcp = DHCP(packet) self.assertThat(dhcp.is_valid(), Equals(False)) self.assertThat( dhcp.invalid_reason, DocTestMatches("Truncated DHCP option value.") )
def test_is_valid_returns_false_for_invalid_cookie(self): packet = factory.make_dhcp_packet(bad_cookie=True) dhcp = DHCP(packet) self.assertThat(dhcp.is_valid(), Equals(False)) self.assertThat( dhcp.invalid_reason, DocTestMatches("Invalid DHCP cookie.") )
def send_requests_and_await_replies(self): """Sends out DHCP requests and waits for their replies. This method is intended to run under `deferToThread()`. Calls the reactor using `blockingCallFromThread` to queue the request packets. Blocks for ~10 seconds while checking for DHCP offers. :returns: `set` of `DHCPServer` objects. """ # Since deferToThread() might be delayed until the next thread is # available, it's better to kick off the DHCP requests from the # spawned thread rather than hoping the thread starts running after # we kick off the requests. blockingCallFromThread(self.clock, self.deferDHCPRequests) servers = set() # Convert the transaction_id to an integer so we can test it against # what the parsed DHCP packet will return. xid = int.from_bytes(self.transaction_id, byteorder="big") with udp_socket() as sock: sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # Note: the empty string is the equivalent of INADDR_ANY. sock.bind(("", BOOTP_CLIENT_PORT)) # The timeout has to be relatively small, since we wake up every # timeout interval to check the elapsed time. sock.settimeout(0.5) runtime = 0 # Use a monotonic clock to ensure leaping backward in time won't # cause an infinite loop. start_time = time.monotonic() while runtime < REPLY_TIMEOUT: try: # Use recvfrom() to check the source IP for the request. # It could be interesting in cases where DHCP relay is in # use. data, (address, port) = sock.recvfrom(2048) except socket.timeout: continue else: offer = DHCP(data) if not offer.is_valid(): log.info( "Invalid DHCP response received from {address} " "on '{ifname}': {reason}", address=address, ifname=self.ifname, reason=offer.invalid_reason, ) elif offer.packet.xid == xid: # Offer matches our transaction ID, so check if it has # a Server Identifier option. server = offer.server_identifier if server is not None: servers.add(DHCPServer(server, address)) finally: runtime = time.monotonic() - start_time return servers
def test__is_valid_returns_false_for_truncated_option_length(self): packet = factory.make_dhcp_packet(truncated_option_length=True) dhcp = DHCP(packet) self.assertThat(dhcp.is_valid(), Equals(False)) self.assertThat( dhcp.invalid_reason, DocTestMatches("Truncated length field in DHCP option."), )
def test__server_identifier_none_if_not_included(self): packet = factory.make_dhcp_packet( include_server_identifier=False) dhcp = DHCP(packet) self.assertThat(dhcp.is_valid(), Equals(True)) self.assertThat(dhcp.server_identifier, Is(None))
def test__is_valid_return_true_for_valid_packet(self): packet = factory.make_dhcp_packet() dhcp = DHCP(packet) self.assertThat(dhcp.is_valid(), Equals(True))