mirror of
https://github.com/genodelabs/genode.git
synced 2025-01-04 04:54:12 +00:00
980f3e9c5c
Instead of handing over the maximum available size to the packet data accessors, hand over a size guard that keeps track of the packets boundaries. This commit also moves the size-guard utilitiy header of Ping and NIC Router to the include/net directory making it a part of the net library. It applies the new approach to all net-lib users in the basic repositories. Ping looses its configurability regarding the ICMP data size as this would require an additional method in the size guard which would be used only by Ping. The size guard was also re-worked to fit the fact that a packet can bring a tail as well as a header (Ethernet). Issue #2788
262 lines
8.0 KiB
C++
262 lines
8.0 KiB
C++
/*
|
|
* \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 <dhcp_client.h>
|
|
#include <interface.h>
|
|
#include <domain.h>
|
|
#include <configuration.h>
|
|
|
|
enum { PKT_SIZE = 1024 };
|
|
|
|
using namespace Genode;
|
|
using namespace Net;
|
|
using Message_type = Dhcp_packet::Message_type;
|
|
using Drop_packet_inform = Net::Interface::Drop_packet_inform;
|
|
using Dhcp_options = Dhcp_packet::Options_aggregator<Size_guard>;
|
|
|
|
|
|
/***************
|
|
** 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<Dhcp_packet::Message_type_option>();
|
|
data.append_param_req<Dhcp_packet::Server_ipv4>();
|
|
data.append_param_req<Dhcp_packet::Ip_lease_time>();
|
|
data.append_param_req<Dhcp_packet::Dns_server_ipv4>();
|
|
data.append_param_req<Dhcp_packet::Subnet_mask>();
|
|
data.append_param_req<Dhcp_packet::Router_ipv4>();
|
|
});
|
|
}
|
|
|
|
|
|
/*****************
|
|
** 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 };
|
|
unsigned long timeout_sec = _lease_time_sec >> lease_time_div_log2;
|
|
|
|
if (timeout_sec > MAX_TIMEOUT_SEC) {
|
|
timeout_sec = MAX_TIMEOUT_SEC;
|
|
warning("Had to prune the state timeout of DHCP client");
|
|
}
|
|
return Microseconds(timeout_sec * 1000UL * 1000UL);
|
|
}
|
|
|
|
|
|
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_inform("DHCP client expects Ethernet targeting the router"); }
|
|
|
|
Ipv4_packet &ip = eth.data<Ipv4_packet>(size_guard);
|
|
if (ip.protocol() != Ipv4_packet::Protocol::UDP) {
|
|
throw Drop_packet_inform("DHCP client expects UDP packet"); }
|
|
|
|
Udp_packet &udp = ip.data<Udp_packet>(size_guard);
|
|
if (!Dhcp_packet::is_dhcp(&udp)) {
|
|
throw Drop_packet_inform("DHCP client expects DHCP packet"); }
|
|
|
|
Dhcp_packet &dhcp = udp.data<Dhcp_packet>(size_guard);
|
|
if (dhcp.op() != Dhcp_packet::REPLY) {
|
|
throw Drop_packet_inform("DHCP client expects DHCP reply"); }
|
|
|
|
if (dhcp.client_mac() != _interface.router_mac()) {
|
|
throw Drop_packet_inform("DHCP client expects DHCP targeting the router"); }
|
|
|
|
try { _handle_dhcp_reply(dhcp); }
|
|
catch (Dhcp_packet::Option_not_found) {
|
|
throw Drop_packet_inform("DHCP client misses DHCP option"); }
|
|
}
|
|
|
|
|
|
void Dhcp_client::_handle_dhcp_reply(Dhcp_packet &dhcp)
|
|
{
|
|
Message_type const msg_type =
|
|
dhcp.option<Dhcp_packet::Message_type_option>().value();
|
|
|
|
switch (_state) {
|
|
case State::SELECT:
|
|
|
|
if (msg_type != Message_type::OFFER) {
|
|
throw Drop_packet_inform("DHCP client expects an offer");
|
|
}
|
|
_set_state(State::REQUEST, _config().dhcp_request_timeout());
|
|
_send(Message_type::REQUEST, Ipv4_address(),
|
|
dhcp.option<Dhcp_packet::Server_ipv4>().value(),
|
|
dhcp.yiaddr());
|
|
break;
|
|
|
|
case State::REQUEST:
|
|
{
|
|
if (msg_type != Message_type::ACK) {
|
|
throw Drop_packet_inform("DHCP client expects an acknowledgement");
|
|
}
|
|
_lease_time_sec = dhcp.option<Dhcp_packet::Ip_lease_time>().value();
|
|
_set_state(State::BOUND, _rerequest_timeout(1));
|
|
Ipv4_address dns_server;
|
|
try { dns_server = dhcp.option<Dhcp_packet::Dns_server_ipv4>().value(); }
|
|
catch (Dhcp_packet::Option_not_found) { }
|
|
_domain().ip_config(dhcp.yiaddr(),
|
|
dhcp.option<Dhcp_packet::Subnet_mask>().value(),
|
|
dhcp.option<Dhcp_packet::Router_ipv4>().value(),
|
|
dns_server);
|
|
break;
|
|
}
|
|
case State::RENEW:
|
|
case State::REBIND:
|
|
|
|
if (msg_type != Message_type::ACK) {
|
|
throw Drop_packet_inform("DHCP client expects an acknowledgement");
|
|
}
|
|
_set_state(State::BOUND, _rerequest_timeout(1));
|
|
_lease_time_sec = dhcp.option<Dhcp_packet::Ip_lease_time>().value();
|
|
break;
|
|
|
|
default: throw Drop_packet_inform("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<Ipv4_packet>(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<Udp_packet>(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<Dhcp_packet>(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<Dhcp_packet::Message_type_option>(msg_type);
|
|
switch (msg_type) {
|
|
case Message_type::DISCOVER:
|
|
append_param_req_list(dhcp_opts);
|
|
dhcp_opts.append_option<Dhcp_packet::Client_id>(client_mac);
|
|
dhcp_opts.append_option<Dhcp_packet::Max_msg_size>(PKT_SIZE - dhcp_off);
|
|
break;
|
|
|
|
case Message_type::REQUEST:
|
|
append_param_req_list(dhcp_opts);
|
|
dhcp_opts.append_option<Dhcp_packet::Client_id>(client_mac);
|
|
dhcp_opts.append_option<Dhcp_packet::Max_msg_size>(PKT_SIZE - dhcp_off);
|
|
if (_state == State::REQUEST) {
|
|
dhcp_opts.append_option<Dhcp_packet::Requested_addr>(requested_ip);
|
|
dhcp_opts.append_option<Dhcp_packet::Server_ipv4>(server_ip);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
throw Interface::Bad_send_dhcp_args();
|
|
}
|
|
dhcp_opts.append_option<Dhcp_packet::Options_end>();
|
|
|
|
/* 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();
|
|
});
|
|
}
|