Files
scylladb/net/dhcp.cc
Avi Kivity 7f8d88371a Add LICENSE, NOTICE, and copyright headers to all source files.
The two files imported from the OSv project retain their original licenses.
2015-02-19 16:52:34 +02:00

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();
}