468 lines
14 KiB
C++
468 lines
14 KiB
C++
/*
|
|
* This file is open source software, licensed to you under the terms
|
|
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
|
|
* distributed with this work for additional information regarding copyright
|
|
* ownership. You may not use this file except in compliance with the License.
|
|
*
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
/*
|
|
* Copyright 2014 Cloudius Systems
|
|
*/
|
|
|
|
#include <chrono>
|
|
#include <unordered_map>
|
|
#include <array>
|
|
#include <random>
|
|
|
|
#include "dhcp.hh"
|
|
#include "ip.hh"
|
|
#include "udp.hh"
|
|
|
|
using namespace std::literals::chrono_literals;
|
|
|
|
class net::dhcp::impl : public ip_packet_filter {
|
|
public:
|
|
|
|
decltype(std::cout) & log() {
|
|
return std::cout << "DHCP ";
|
|
}
|
|
|
|
enum class state {
|
|
NONE,
|
|
DISCOVER,
|
|
REQUEST,
|
|
DONE,
|
|
FAIL,
|
|
};
|
|
|
|
enum class m_type : uint8_t {
|
|
BOOTREQUEST = 1,
|
|
BOOTREPLY = 2
|
|
};
|
|
|
|
enum class htype : uint8_t {
|
|
ETHERNET = 1
|
|
};
|
|
|
|
enum class opt_type : uint8_t {
|
|
PAD = 0,
|
|
SUBNET_MASK = 1,
|
|
ROUTER = 3,
|
|
DOMAIN_NAME_SERVERS = 6,
|
|
INTERFACE_MTU = 26,
|
|
BROADCAST_ADDRESS = 28,
|
|
REQUESTED_ADDRESS = 50,
|
|
LEASE_TIME = 51,
|
|
MESSAGE_TYPE = 53,
|
|
DHCP_SERVER = 54,
|
|
PARAMETER_REQUEST_LIST = 55,
|
|
RENEWAL_TIME = 58,
|
|
REBINDING_TIME = 59,
|
|
CLASSLESS_ROUTE = 121,
|
|
END = 255
|
|
};
|
|
|
|
enum class msg_type : uint8_t {
|
|
DISCOVER = 1,
|
|
OFFER = 2,
|
|
REQUEST = 3,
|
|
DECLINE = 4,
|
|
ACK = 5,
|
|
NAK = 6,
|
|
RELEASE = 7,
|
|
INFORM = 8,
|
|
LEASEQUERY = 10,
|
|
LEASEUNASSIGNED = 11,
|
|
LEASEUNKNOWN = 12,
|
|
LEASEACTIVE = 13,
|
|
INVALID = 255
|
|
};
|
|
|
|
struct dhcp_header {
|
|
m_type op = m_type::BOOTREQUEST; // Message op code / message type.
|
|
htype type = htype::ETHERNET; // Hardware address type
|
|
uint8_t hlen = 6; // Hardware address length
|
|
uint8_t hops = 0; // Client sets to zero, used by relay agents
|
|
packed<uint32_t> xid = 0; // Client sets Transaction ID, a random number
|
|
packed<uint16_t> secs = 0; // Client sets seconds elapsed since op start
|
|
packed<uint16_t> flags = 0; // Flags
|
|
ipv4_address ciaddr; // Client IP address
|
|
ipv4_address yiaddr; // 'your' (client) IP address.
|
|
ipv4_address siaddr; // IP address of next server to use in bootstrap
|
|
ipv4_address giaddr; // Relay agent IP address
|
|
uint8_t chaddr[16] = { 0, }; // Client hardware address.
|
|
char sname[64] = { 0, }; // unused
|
|
char file[128] = { 0, }; // unused
|
|
|
|
template <typename Adjuster>
|
|
auto adjust_endianness(Adjuster a) {
|
|
return a(xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr);
|
|
}
|
|
} __attribute__((packed));
|
|
|
|
typedef std::array<opt_type, 5> req_opt_type;
|
|
|
|
static const req_opt_type requested_options;
|
|
|
|
struct option_mark {
|
|
option_mark(opt_type t = opt_type::END) : type(t) {};
|
|
opt_type type;
|
|
} __attribute__((packed));
|
|
|
|
struct option : public option_mark {
|
|
option(opt_type t, uint8_t l = 1) : option_mark(t), len(l) {};
|
|
uint8_t len;
|
|
} __attribute__((packed));
|
|
|
|
struct type_option : public option {
|
|
type_option(msg_type t) : option(opt_type::MESSAGE_TYPE), type(t) {}
|
|
msg_type type;
|
|
} __attribute__((packed));
|
|
|
|
struct mtu_option : public option {
|
|
mtu_option(uint16_t v) : option(opt_type::INTERFACE_MTU, 2), mtu((::htons)(v)) {}
|
|
packed<uint16_t> mtu;
|
|
} __attribute__((packed));
|
|
|
|
struct ip_option : public option {
|
|
ip_option(opt_type t = opt_type::BROADCAST_ADDRESS, const ipv4_address & ip = ipv4_address()) : option(t, sizeof(uint32_t)), ip(::htonl(ip.ip)) {}
|
|
packed<uint32_t> ip;
|
|
} __attribute__((packed));
|
|
|
|
struct time_option : public option {
|
|
time_option(opt_type t, uint32_t v) : option(t, sizeof(uint32_t)), time(::htonl(v)) {}
|
|
packed<uint32_t> time;
|
|
} __attribute__((packed));
|
|
|
|
|
|
struct requested_option: public option {
|
|
requested_option()
|
|
: option(opt_type::PARAMETER_REQUEST_LIST,
|
|
uint8_t(requested_options.size())), req(
|
|
requested_options) {
|
|
}
|
|
req_opt_type req;
|
|
}__attribute__((packed));
|
|
|
|
static const uint16_t client_port = 68;
|
|
static const uint16_t server_port = 67;
|
|
|
|
typedef std::array<uint8_t, 4> magic_tag;
|
|
|
|
static const magic_tag options_magic;
|
|
|
|
struct dhcp_payload {
|
|
dhcp_header bootp;
|
|
magic_tag magic = options_magic;
|
|
|
|
template <typename Adjuster>
|
|
auto adjust_endianness(Adjuster a) {
|
|
return a(bootp);
|
|
}
|
|
} __attribute__((packed));
|
|
|
|
struct dhcp_packet_base {
|
|
dhcp_payload dhp;
|
|
|
|
template <typename Adjuster>
|
|
auto adjust_endianness(Adjuster a) {
|
|
return a(dhp);
|
|
}
|
|
} __attribute__((packed));
|
|
|
|
struct ip_info : public lease {
|
|
msg_type type = msg_type();
|
|
|
|
void set(opt_type type, const ipv4_address & ip) {
|
|
switch (type) {
|
|
case opt_type::SUBNET_MASK: netmask = ip; break;
|
|
case opt_type::ROUTER: gateway = ip; break;
|
|
case opt_type::BROADCAST_ADDRESS: broadcast = ip; break;
|
|
case opt_type::DHCP_SERVER: dhcp_server = ip; break;
|
|
case opt_type::DOMAIN_NAME_SERVERS:
|
|
name_servers.emplace_back(ip);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void set(opt_type type, std::chrono::seconds s) {
|
|
switch (type) {
|
|
case opt_type::LEASE_TIME: lease_time = s; break;
|
|
case opt_type::RENEWAL_TIME: renew_time = s; break;
|
|
case opt_type::REBINDING_TIME: rebind_time = s; break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void parse_options(packet & p, size_t off) {
|
|
for (;;) {
|
|
auto * m = p.get_header<option_mark>(off);
|
|
if (m == nullptr || m->type == opt_type::END) {
|
|
break;
|
|
}
|
|
auto * o = p.get_header<option>(off);
|
|
if (o == nullptr) {
|
|
// TODO: report broken packet?
|
|
break;
|
|
}
|
|
|
|
switch (o->type) {
|
|
case opt_type::SUBNET_MASK:
|
|
case opt_type::ROUTER:
|
|
case opt_type::BROADCAST_ADDRESS:
|
|
case opt_type::DHCP_SERVER:
|
|
case opt_type::DOMAIN_NAME_SERVERS:
|
|
{
|
|
auto ipo = p.get_header<ip_option>(off);
|
|
if (ipo != nullptr) {
|
|
set(o->type, ipv4_address(::ntohl(ipo->ip)));
|
|
}
|
|
}
|
|
break;
|
|
case opt_type::MESSAGE_TYPE:
|
|
{
|
|
auto to = p.get_header<type_option>(off);
|
|
if (to != nullptr) {
|
|
type = to->type;
|
|
}
|
|
}
|
|
break;
|
|
case opt_type::INTERFACE_MTU:
|
|
{
|
|
auto mo = p.get_header<mtu_option>(off);
|
|
if (mo != nullptr) {
|
|
mtu = (::ntohs)(uint16_t(mo->mtu));
|
|
}
|
|
}
|
|
break;
|
|
case opt_type::LEASE_TIME:
|
|
case opt_type::RENEWAL_TIME:
|
|
case opt_type::REBINDING_TIME:
|
|
{
|
|
auto to = p.get_header<time_option>(off);
|
|
if (to != nullptr) {
|
|
set(o->type, std::chrono::seconds(::ntohl(to->time)));
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
off += sizeof(*o) + o->len;
|
|
}
|
|
}
|
|
};
|
|
|
|
impl(ipv4 & stack)
|
|
: _stack(stack)
|
|
{
|
|
_sock = _stack.get_udp().make_channel({0, client_port});
|
|
}
|
|
|
|
future<> process_packet(packet p, dhcp_payload* dhp, size_t opt_off) {
|
|
_retry_timer.cancel();
|
|
|
|
auto h = ntoh(*dhp);
|
|
|
|
ip_info info;
|
|
|
|
info.ip = h.bootp.yiaddr;
|
|
info.parse_options(p, opt_off);
|
|
|
|
switch (_state) {
|
|
case state::DISCOVER:
|
|
if (info.type != msg_type::OFFER) {
|
|
// TODO: log?
|
|
break;
|
|
}
|
|
log() << "Got offer for " << info.ip << std::endl;
|
|
// TODO, check for minimum valid/required fields sent back?
|
|
return send_request(info);
|
|
case state::REQUEST:
|
|
if (info.type == msg_type::NAK) {
|
|
log() << "Got nak on request" << std::endl;
|
|
_state = state::NONE;
|
|
return send_discover();
|
|
}
|
|
if (info.type != msg_type::ACK) {
|
|
break;
|
|
}
|
|
log() << "Got ack on request" << std::endl;
|
|
log() << " ip: " << info.ip << std::endl;
|
|
log() << " nm: " << info.netmask << std::endl;
|
|
log() << " gw: " << info.gateway << std::endl;
|
|
_state = state::DONE;
|
|
_result.set_value(true, info);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
future<> handle(packet& p, ip_hdr* iph, ethernet_address from, bool & handled) override {
|
|
if (_state == state::NONE || p.len() < sizeof(dhcp_packet_base)) {
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
auto ipl = iph->ihl * 4;
|
|
auto udp = p.get_header<udp_hdr>(ipl);
|
|
auto dhp = p.get_header<dhcp_payload>(ipl + sizeof(*udp));
|
|
|
|
const auto opt_off = ipl + sizeof(*udp) + sizeof(dhcp_payload);
|
|
|
|
if (udp == nullptr || dhp == nullptr
|
|
|| iph->ip_proto != uint8_t(ip_protocol_num::udp)
|
|
|| (::ntohs)(udp->dst_port) != client_port
|
|
|| iph->len < (opt_off + sizeof(option_mark))
|
|
|| dhp->magic != options_magic) {
|
|
return make_ready_future<>();
|
|
}
|
|
handled = true;
|
|
auto src_cpu = engine().cpu_id();
|
|
if (src_cpu == 0) {
|
|
return process_packet(std::move(p), dhp, opt_off);
|
|
}
|
|
smp::submit_to(0, [this, p = std::move(p), src_cpu, dhp, opt_off]() mutable {
|
|
process_packet(p.free_on_cpu(src_cpu), dhp, opt_off);
|
|
});
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
future<bool, lease> run(const lease & l,
|
|
const clock_type::duration & timeout) {
|
|
|
|
_state = state::NONE;
|
|
_timer.set_callback([this]() {
|
|
_state = state::FAIL;
|
|
log() << "timeout" << std::endl;
|
|
_retry_timer.cancel();
|
|
_result.set_value(false, lease());
|
|
});
|
|
|
|
send_discover(l.ip); // FIXME: ignoring return
|
|
if (timeout.count()) {
|
|
_timer.arm(timeout);
|
|
}
|
|
_retry_timer.set_callback([this, l] {
|
|
send_discover(l.ip);
|
|
});
|
|
_retry_timer.arm_periodic(1s);
|
|
return _result.get_future();
|
|
}
|
|
|
|
template<typename T>
|
|
future<> send(T && pkt) {
|
|
pkt.dhp.bootp.xid = _xid;
|
|
auto ipf = _stack.netif();
|
|
auto mac = ipf->hw_address().mac;
|
|
std::copy(mac.begin(), mac.end(), std::begin(pkt.dhp.bootp.chaddr));
|
|
|
|
pkt = hton(pkt);
|
|
|
|
_sock.send({0xffffffff, server_port}, packet(reinterpret_cast<char *>(&pkt), sizeof(pkt)));
|
|
|
|
return make_ready_future<>();
|
|
}
|
|
|
|
future<> send_discover(const ipv4_address & ip = ipv4_address()) {
|
|
struct discover : public dhcp_packet_base {
|
|
type_option type = type_option(msg_type::DISCOVER);
|
|
ip_option requested_ip;
|
|
requested_option req;
|
|
option_mark end;
|
|
} __attribute__((packed));
|
|
|
|
discover d;
|
|
|
|
d.requested_ip = ip_option(opt_type::REQUESTED_ADDRESS, ip);
|
|
|
|
log() << "sending discover" << std::endl;
|
|
|
|
std::random_device rd;
|
|
std::default_random_engine e1(rd());
|
|
std::uniform_int_distribution<uint32_t> xid_dist{};
|
|
|
|
_xid = xid_dist(e1);
|
|
_state = state::DISCOVER;
|
|
return send(d);
|
|
}
|
|
|
|
future<> send_request(const lease & info) {
|
|
struct request : public dhcp_packet_base {
|
|
type_option type = type_option(msg_type::REQUEST);
|
|
ip_option dhcp_server;
|
|
ip_option requested_ip;
|
|
requested_option req;
|
|
option_mark end;
|
|
} __attribute__((packed));
|
|
|
|
request d;
|
|
|
|
d.dhcp_server = ip_option(opt_type::DHCP_SERVER, info.dhcp_server);
|
|
d.requested_ip = ip_option(opt_type::REQUESTED_ADDRESS, info.ip);
|
|
|
|
log() << "sending request for " << info.ip << std::endl;
|
|
_state = state::REQUEST;
|
|
return send(d);
|
|
}
|
|
|
|
private:
|
|
promise<bool, lease> _result;
|
|
state _state = state::NONE;
|
|
timer<> _timer;
|
|
timer<> _retry_timer;
|
|
ipv4 & _stack;
|
|
udp_channel _sock;
|
|
uint32_t _xid = 0;
|
|
};
|
|
|
|
const net::dhcp::impl::req_opt_type net::dhcp::impl::requested_options = { {
|
|
opt_type::SUBNET_MASK, opt_type::ROUTER, opt_type::DOMAIN_NAME_SERVERS,
|
|
opt_type::INTERFACE_MTU, opt_type::BROADCAST_ADDRESS } };
|
|
|
|
const net::dhcp::impl::magic_tag net::dhcp::impl::options_magic = { { 0x63, 0x82, 0x53,
|
|
0x63 } };
|
|
|
|
const uint16_t net::dhcp::impl::client_port;
|
|
const uint16_t net::dhcp::impl::server_port;
|
|
|
|
const clock_type::duration net::dhcp::default_timeout = std::chrono::duration_cast<clock_type::duration>(std::chrono::seconds(30));
|
|
|
|
net::dhcp::dhcp(ipv4 & ip)
|
|
: _impl(std::make_unique<impl>(ip))
|
|
{}
|
|
|
|
net::dhcp::dhcp(dhcp && v)
|
|
: _impl(std::move(v._impl))
|
|
{}
|
|
|
|
net::dhcp::~dhcp()
|
|
{}
|
|
|
|
net::dhcp::result_type net::dhcp::discover(const clock_type::duration & timeout) {
|
|
return _impl->run(lease(), timeout);
|
|
}
|
|
|
|
net::dhcp::result_type net::dhcp::renew(const lease & l, const clock_type::duration & timeout) {
|
|
return _impl->run(l, timeout);
|
|
}
|
|
|
|
net::ip_packet_filter* net::dhcp::get_ipv4_filter() {
|
|
return _impl.get();
|
|
}
|