/* * \brief DHCP client state model * \author Martin Stein * \date 2016-08-24 */ /* * Copyright (C) 2016-2017 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU Affero General Public License version 3. */ /* local includes */ #include #include #include #include enum { PKT_SIZE = 1024 }; using namespace Genode; using namespace Net; using Message_type = Dhcp_packet::Message_type; using Drop_packet = Net::Interface::Drop_packet; using Dhcp_options = Dhcp_packet::Options_aggregator; /*************** ** Utilities ** ***************/ void append_param_req_list(Dhcp_options &dhcp_opts) { dhcp_opts.append_param_req_list([&] (Dhcp_options::Parameter_request_list_data &data) { data.append_param_req(); data.append_param_req(); data.append_param_req(); data.append_param_req(); data.append_param_req(); data.append_param_req(); }); } /***************** ** Dhcp_client ** *****************/ Configuration &Dhcp_client::_config() { return _domain().config(); }; Domain &Dhcp_client::_domain() { return _interface.domain(); } Dhcp_client::Dhcp_client(Genode::Allocator &alloc, Timer::Connection &timer, Interface &interface) : _alloc(alloc), _interface(interface), _timeout(timer, *this, &Dhcp_client::_handle_timeout) { } void Dhcp_client::discover() { _set_state(State::SELECT, _config().dhcp_discover_timeout()); _send(Message_type::DISCOVER, Ipv4_address(), Ipv4_address(), Ipv4_address()); } void Dhcp_client::_rerequest(State next_state) { _set_state(next_state, _rerequest_timeout(2)); Ipv4_address const client_ip = _domain().ip_config().interface.address; _send(Message_type::REQUEST, client_ip, Ipv4_address(), client_ip); } void Dhcp_client::_set_state(State state, Microseconds timeout) { _state = state; _timeout.schedule(timeout); } Microseconds Dhcp_client::_rerequest_timeout(unsigned lease_time_div_log2) { /* FIXME limit the time because of shortcomings in timeout framework */ enum { MAX_TIMEOUT_SEC = 3600 }; uint64_t timeout_sec = _lease_time_sec >> lease_time_div_log2; if (timeout_sec > MAX_TIMEOUT_SEC) { timeout_sec = MAX_TIMEOUT_SEC; if (_interface.config().verbose()) { try { log("[", _interface.domain(), "] prune re-request timeout of " "DHCP client"); } catch (Pointer::Invalid) { log("[?] prune re-request timeout of DHCP client"); } } } return Microseconds(timeout_sec * 1000 * 1000); } void Dhcp_client::_handle_timeout(Duration) { switch (_state) { case State::BOUND: _rerequest(State::RENEW); break; case State::RENEW: _rerequest(State::REBIND); break; case State::REBIND: _domain().discard_ip_config(); default: discover(); } } void Dhcp_client::handle_ip(Ethernet_frame ð, Size_guard &size_guard) { if (eth.dst() != _interface.router_mac() && eth.dst() != Mac_address(0xff)) { throw Drop_packet("DHCP client expects Ethernet targeting the router"); } Ipv4_packet &ip = eth.data(size_guard); if (ip.protocol() != Ipv4_packet::Protocol::UDP) { throw Drop_packet("DHCP client expects UDP packet"); } Udp_packet &udp = ip.data(size_guard); if (!Dhcp_packet::is_dhcp(&udp)) { throw Drop_packet("DHCP client expects DHCP packet"); } Dhcp_packet &dhcp = udp.data(size_guard); if (dhcp.op() != Dhcp_packet::REPLY) { throw Drop_packet("DHCP client expects DHCP reply"); } if (dhcp.client_mac() != _interface.router_mac()) { throw Drop_packet("DHCP client expects DHCP targeting the router"); } try { _handle_dhcp_reply(dhcp); } catch (Dhcp_packet::Option_not_found) { throw Drop_packet("DHCP client misses DHCP option"); } } void Dhcp_client::_handle_dhcp_reply(Dhcp_packet &dhcp) { Message_type const msg_type = dhcp.option().value(); switch (_state) { case State::SELECT: if (msg_type != Message_type::OFFER) { throw Drop_packet("DHCP client expects an offer"); } _set_state(State::REQUEST, _config().dhcp_request_timeout()); _send(Message_type::REQUEST, Ipv4_address(), dhcp.option().value(), dhcp.yiaddr()); break; case State::REQUEST: { if (msg_type != Message_type::ACK) { throw Drop_packet("DHCP client expects an acknowledgement"); } _lease_time_sec = dhcp.option().value(); _set_state(State::BOUND, _rerequest_timeout(1)); Ipv4_address dns_server; try { dns_server = dhcp.option().value(); } catch (Dhcp_packet::Option_not_found) { } _domain().ip_config(dhcp.yiaddr(), dhcp.option().value(), dhcp.option().value(), dns_server); break; } case State::RENEW: case State::REBIND: if (msg_type != Message_type::ACK) { throw Drop_packet("DHCP client expects an acknowledgement"); } _set_state(State::BOUND, _rerequest_timeout(1)); _lease_time_sec = dhcp.option().value(); break; default: throw Drop_packet("DHCP client doesn't expect a packet"); } } void Dhcp_client::_send(Message_type msg_type, Ipv4_address client_ip, Ipv4_address server_ip, Ipv4_address requested_ip) { Mac_address client_mac = _interface.router_mac(); _interface.send(PKT_SIZE, [&] (void *pkt_base, Size_guard &size_guard) { /* create ETH header of the request */ Ethernet_frame ð = Ethernet_frame::construct_at(pkt_base, size_guard); eth.dst(Mac_address(0xff)); eth.src(client_mac); eth.type(Ethernet_frame::Type::IPV4); /* create IP header of the request */ enum { IPV4_TIME_TO_LIVE = 64 }; size_t const ip_off = size_guard.head_size(); Ipv4_packet &ip = eth.construct_at_data(size_guard); ip.header_length(sizeof(Ipv4_packet) / 4); ip.version(4); ip.time_to_live(IPV4_TIME_TO_LIVE); ip.protocol(Ipv4_packet::Protocol::UDP); ip.src(client_ip); ip.dst(Ipv4_address(0xff)); /* create UDP header of the request */ size_t const udp_off = size_guard.head_size(); Udp_packet &udp = ip.construct_at_data(size_guard); udp.src_port(Port(Dhcp_packet::BOOTPC)); udp.dst_port(Port(Dhcp_packet::BOOTPS)); /* create mandatory DHCP fields of the request */ size_t const dhcp_off = size_guard.head_size(); Dhcp_packet &dhcp = udp.construct_at_data(size_guard); dhcp.op(Dhcp_packet::REQUEST); dhcp.htype(Dhcp_packet::Htype::ETH); dhcp.hlen(sizeof(Mac_address)); dhcp.ciaddr(client_ip); dhcp.client_mac(client_mac); dhcp.default_magic_cookie(); /* append DHCP option fields to the request */ Dhcp_options dhcp_opts(dhcp, size_guard); dhcp_opts.append_option(msg_type); switch (msg_type) { case Message_type::DISCOVER: append_param_req_list(dhcp_opts); dhcp_opts.append_option(client_mac); dhcp_opts.append_option(PKT_SIZE - dhcp_off); break; case Message_type::REQUEST: append_param_req_list(dhcp_opts); dhcp_opts.append_option(client_mac); dhcp_opts.append_option(PKT_SIZE - dhcp_off); if (_state == State::REQUEST) { dhcp_opts.append_option(requested_ip); dhcp_opts.append_option(server_ip); } break; default: throw Interface::Bad_send_dhcp_args(); } dhcp_opts.append_option(); /* fill in header values that need the packet to be complete already */ udp.length(size_guard.head_size() - udp_off); udp.update_checksum(ip.src(), ip.dst()); ip.total_length(size_guard.head_size() - ip_off); ip.update_checksum(); }); }