]> git.proxmox.com Git - ceph.git/blame - ceph/src/seastar/src/net/dhcp.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / seastar / src / net / dhcp.cc
CommitLineData
11fdf7f2
TL
1/*
2 * This file is open source software, licensed to you under the terms
3 * of the Apache License, Version 2.0 (the "License"). See the NOTICE file
4 * distributed with this work for additional information regarding copyright
5 * ownership. You may not use this file except in compliance with the License.
6 *
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing,
12 * software distributed under the License is distributed on an
13 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14 * KIND, either express or implied. See the License for the
15 * specific language governing permissions and limitations
16 * under the License.
17 */
18/*
19 * Copyright 2014 Cloudius Systems
20 */
21
22#include <chrono>
23#include <unordered_map>
24#include <array>
25#include <random>
26#include <iostream>
27
28#include <seastar/net/dhcp.hh>
29#include <seastar/net/ip.hh>
30#include <seastar/net/udp.hh>
31#include <seastar/net/stack.hh>
32
33namespace seastar {
34
35using namespace std::literals::chrono_literals;
36
37class net::dhcp::impl : public ip_packet_filter {
38public:
39
40 decltype(std::cout) & log() {
41 return std::cout << "DHCP ";
42 }
43
44 enum class state {
45 NONE,
46 DISCOVER,
47 REQUEST,
48 DONE,
49 FAIL,
50 };
51
52 enum class m_type : uint8_t {
53 BOOTREQUEST = 1,
54 BOOTREPLY = 2
55 };
56
57 enum class htype : uint8_t {
58 ETHERNET = 1
59 };
60
61 enum class opt_type : uint8_t {
62 PAD = 0,
63 SUBNET_MASK = 1,
64 ROUTER = 3,
65 DOMAIN_NAME_SERVERS = 6,
66 INTERFACE_MTU = 26,
67 BROADCAST_ADDRESS = 28,
68 REQUESTED_ADDRESS = 50,
69 LEASE_TIME = 51,
70 MESSAGE_TYPE = 53,
71 DHCP_SERVER = 54,
72 PARAMETER_REQUEST_LIST = 55,
73 RENEWAL_TIME = 58,
74 REBINDING_TIME = 59,
75 CLASSLESS_ROUTE = 121,
76 END = 255
77 };
78
79 enum class msg_type : uint8_t {
80 DISCOVER = 1,
81 OFFER = 2,
82 REQUEST = 3,
83 DECLINE = 4,
84 ACK = 5,
85 NAK = 6,
86 RELEASE = 7,
87 INFORM = 8,
88 LEASEQUERY = 10,
89 LEASEUNASSIGNED = 11,
90 LEASEUNKNOWN = 12,
91 LEASEACTIVE = 13,
92 INVALID = 255
93 };
94
95 struct dhcp_header {
96 m_type op = m_type::BOOTREQUEST; // Message op code / message type.
97 htype type = htype::ETHERNET; // Hardware address type
98 uint8_t hlen = 6; // Hardware address length
99 uint8_t hops = 0; // Client sets to zero, used by relay agents
100 packed<uint32_t> xid = 0; // Client sets Transaction ID, a random number
101 packed<uint16_t> secs = 0; // Client sets seconds elapsed since op start
102 packed<uint16_t> flags = 0; // Flags
103 ipv4_address ciaddr; // Client IP address
104 ipv4_address yiaddr; // 'your' (client) IP address.
105 ipv4_address siaddr; // IP address of next server to use in bootstrap
106 ipv4_address giaddr; // Relay agent IP address
107 uint8_t chaddr[16] = { 0, }; // Client hardware address.
108 char sname[64] = { 0, }; // unused
109 char file[128] = { 0, }; // unused
110
111 template <typename Adjuster>
112 auto adjust_endianness(Adjuster a) {
113 return a(xid, secs, flags, ciaddr, yiaddr, siaddr, giaddr);
114 }
115 } __attribute__((packed));
116
117 typedef std::array<opt_type, 5> req_opt_type;
118
119 static const req_opt_type requested_options;
120
121 struct option_mark {
122 option_mark(opt_type t = opt_type::END) : type(t) {};
123 opt_type type;
124 } __attribute__((packed));
125
126 struct option : public option_mark {
127 option(opt_type t, uint8_t l = 1) : option_mark(t), len(l) {};
128 uint8_t len;
129 } __attribute__((packed));
130
131 struct type_option : public option {
132 type_option(msg_type t) : option(opt_type::MESSAGE_TYPE), type(t) {}
133 msg_type type;
134 } __attribute__((packed));
135
136 struct mtu_option : public option {
137 mtu_option(uint16_t v) : option(opt_type::INTERFACE_MTU, 2), mtu((::htons)(v)) {}
138 packed<uint16_t> mtu;
139 } __attribute__((packed));
140
141 struct ip_option : public option {
142 ip_option(opt_type t = opt_type::BROADCAST_ADDRESS, const ipv4_address & ip = ipv4_address()) : option(t, sizeof(uint32_t)), ip(htonl(ip.ip)) {}
143 packed<uint32_t> ip;
144 } __attribute__((packed));
145
146 struct time_option : public option {
147 time_option(opt_type t, uint32_t v) : option(t, sizeof(uint32_t)), time(htonl(v)) {}
148 packed<uint32_t> time;
149 } __attribute__((packed));
150
151
152 struct requested_option: public option {
153 requested_option()
154 : option(opt_type::PARAMETER_REQUEST_LIST,
155 uint8_t(requested_options.size())), req(
156 requested_options) {
157 }
158 req_opt_type req;
159 }__attribute__((packed));
160
161 static const uint16_t client_port = 68;
162 static const uint16_t server_port = 67;
163
164 typedef std::array<uint8_t, 4> magic_tag;
165
166 static const magic_tag options_magic;
167
168 struct dhcp_payload {
169 dhcp_header bootp;
170 magic_tag magic = options_magic;
171
172 template <typename Adjuster>
173 auto adjust_endianness(Adjuster a) {
174 return a(bootp);
175 }
176 } __attribute__((packed));
177
178 struct dhcp_packet_base {
179 dhcp_payload dhp;
180
181 template <typename Adjuster>
182 auto adjust_endianness(Adjuster a) {
183 return a(dhp);
184 }
185 } __attribute__((packed));
186
187 struct ip_info : public lease {
188 msg_type type = msg_type();
189
190 void set(opt_type type, const ipv4_address & ip) {
191 switch (type) {
192 case opt_type::SUBNET_MASK: netmask = ip; break;
193 case opt_type::ROUTER: gateway = ip; break;
194 case opt_type::BROADCAST_ADDRESS: broadcast = ip; break;
195 case opt_type::DHCP_SERVER: dhcp_server = ip; break;
196 case opt_type::DOMAIN_NAME_SERVERS:
197 name_servers.emplace_back(ip);
198 break;
199 default:
200 break;
201 }
202 }
203
204 void set(opt_type type, std::chrono::seconds s) {
205 switch (type) {
206 case opt_type::LEASE_TIME: lease_time = s; break;
207 case opt_type::RENEWAL_TIME: renew_time = s; break;
208 case opt_type::REBINDING_TIME: rebind_time = s; break;
209 default:
210 break;
211 }
212 }
213
214 void parse_options(packet & p, size_t off) {
215 for (;;) {
216 auto * m = p.get_header<option_mark>(off);
217 if (m == nullptr || m->type == opt_type::END) {
218 break;
219 }
220 auto * o = p.get_header<option>(off);
221 if (o == nullptr) {
222 // TODO: report broken packet?
223 break;
224 }
225
226 switch (o->type) {
227 case opt_type::SUBNET_MASK:
228 case opt_type::ROUTER:
229 case opt_type::BROADCAST_ADDRESS:
230 case opt_type::DHCP_SERVER:
231 case opt_type::DOMAIN_NAME_SERVERS:
232 {
233 auto ipo = p.get_header<ip_option>(off);
234 if (ipo != nullptr) {
235 set(o->type, ipv4_address(ntohl(ipo->ip)));
236 }
237 }
238 break;
239 case opt_type::MESSAGE_TYPE:
240 {
241 auto to = p.get_header<type_option>(off);
242 if (to != nullptr) {
243 type = to->type;
244 }
245 }
246 break;
247 case opt_type::INTERFACE_MTU:
248 {
249 auto mo = p.get_header<mtu_option>(off);
250 if (mo != nullptr) {
251 mtu = (::ntohs)(uint16_t(mo->mtu));
252 }
253 }
254 break;
255 case opt_type::LEASE_TIME:
256 case opt_type::RENEWAL_TIME:
257 case opt_type::REBINDING_TIME:
258 {
259 auto to = p.get_header<time_option>(off);
260 if (to != nullptr) {
261 set(o->type, std::chrono::seconds(ntohl(to->time)));
262 }
263 }
264 break;
265 default:
266 break;
267 }
268
269 off += sizeof(*o) + o->len;
270 }
271 }
272 };
273
274 impl(ipv4 & stack)
275 : _stack(stack)
276 {
277 _sock = _stack.get_udp().make_channel({0, client_port});
278 }
279
280 future<> process_packet(packet p, dhcp_payload* dhp, size_t opt_off) {
281 _retry_timer.cancel();
282
283 auto h = ntoh(*dhp);
284
285 ip_info info;
286
287 info.ip = h.bootp.yiaddr;
288 info.parse_options(p, opt_off);
289
290 switch (_state) {
291 case state::DISCOVER:
292 if (info.type != msg_type::OFFER) {
293 // TODO: log?
294 break;
295 }
296 log() << "Got offer for " << info.ip << std::endl;
297 // TODO, check for minimum valid/required fields sent back?
298 return send_request(info);
299 case state::REQUEST:
300 if (info.type == msg_type::NAK) {
301 log() << "Got nak on request" << std::endl;
302 _state = state::NONE;
303 return send_discover();
304 }
305 if (info.type != msg_type::ACK) {
306 break;
307 }
308 log() << "Got ack on request" << std::endl;
309 log() << " ip: " << info.ip << std::endl;
310 log() << " nm: " << info.netmask << std::endl;
311 log() << " gw: " << info.gateway << std::endl;
312 _state = state::DONE;
9f95a23c 313 _result.set_value(info);
11fdf7f2
TL
314 break;
315 default:
316 break;
317 }
318 return make_ready_future<>();
319 }
320
321 future<> handle(packet& p, ip_hdr* iph, ethernet_address from, bool & handled) override {
322 if (_state == state::NONE || p.len() < sizeof(dhcp_packet_base)) {
323 return make_ready_future<>();
324 }
325
326 auto ipl = iph->ihl * 4;
327 auto udp = p.get_header<udp_hdr>(ipl);
328 auto dhp = p.get_header<dhcp_payload>(ipl + sizeof(*udp));
329
330 const auto opt_off = ipl + sizeof(*udp) + sizeof(dhcp_payload);
331
332 if (udp == nullptr || dhp == nullptr
333 || iph->ip_proto != uint8_t(ip_protocol_num::udp)
334 || (::ntohs)(udp->dst_port) != client_port
335 || iph->len < (opt_off + sizeof(option_mark))
336 || dhp->magic != options_magic) {
337 return make_ready_future<>();
338 }
339 handled = true;
340 auto src_cpu = engine().cpu_id();
341 if (src_cpu == 0) {
342 return process_packet(std::move(p), dhp, opt_off);
343 }
9f95a23c
TL
344 // FIXME: future is discarded
345 (void)smp::submit_to(0, [this, p = std::move(p), src_cpu, dhp, opt_off]() mutable {
346 return process_packet(p.free_on_cpu(src_cpu), dhp, opt_off);
11fdf7f2
TL
347 });
348 return make_ready_future<>();
349 }
350
9f95a23c 351 future<compat::optional<lease>> run(const lease & l,
11fdf7f2
TL
352 const steady_clock_type::duration & timeout) {
353
354 _state = state::NONE;
355 _timer.set_callback([this]() {
356 _state = state::FAIL;
357 log() << "timeout" << std::endl;
358 _retry_timer.cancel();
9f95a23c 359 _result.set_value(compat::nullopt);
11fdf7f2
TL
360 });
361
362 log() << "sending discover" << std::endl;
9f95a23c 363 (void)send_discover(l.ip); // FIXME: ignoring return
11fdf7f2
TL
364 if (timeout.count()) {
365 _timer.arm(timeout);
366 }
367 _retry_timer.set_callback([this, l] {
9f95a23c
TL
368 // FIXME: ignoring return
369 (void)send_discover(l.ip);
11fdf7f2
TL
370 });
371 _retry_timer.arm_periodic(1s);
372 return _result.get_future();
373 }
374
375 template<typename T>
376 future<> send(T && pkt) {
377 pkt.dhp.bootp.xid = _xid;
378 auto ipf = _stack.netif();
379 auto mac = ipf->hw_address().mac;
380 std::copy(mac.begin(), mac.end(), std::begin(pkt.dhp.bootp.chaddr));
381
382 pkt = hton(pkt);
383
9f95a23c
TL
384 // FIXME: future is discarded
385 (void)_sock.send({0xffffffff, server_port}, packet(reinterpret_cast<char *>(&pkt), sizeof(pkt)));
11fdf7f2
TL
386
387 return make_ready_future<>();
388 }
389
390 future<> send_discover(const ipv4_address & ip = ipv4_address()) {
391 struct discover : public dhcp_packet_base {
392 type_option type = type_option(msg_type::DISCOVER);
393 ip_option requested_ip;
394 requested_option req;
395 option_mark end;
396 } __attribute__((packed));
397
398 discover d;
399
400 d.requested_ip = ip_option(opt_type::REQUESTED_ADDRESS, ip);
401
402 std::random_device rd;
403 std::default_random_engine e1(rd());
404 std::uniform_int_distribution<uint32_t> xid_dist{};
405
406 _xid = xid_dist(e1);
407 _state = state::DISCOVER;
408 return send(d);
409 }
410
411 future<> send_request(const lease & info) {
412 struct request : public dhcp_packet_base {
413 type_option type = type_option(msg_type::REQUEST);
414 ip_option dhcp_server;
415 ip_option requested_ip;
416 requested_option req;
417 option_mark end;
418 } __attribute__((packed));
419
420 request d;
421
422 d.dhcp_server = ip_option(opt_type::DHCP_SERVER, info.dhcp_server);
423 d.requested_ip = ip_option(opt_type::REQUESTED_ADDRESS, info.ip);
424
425 log() << "sending request for " << info.ip << std::endl;
426 _state = state::REQUEST;
427 return send(d);
428 }
429
430private:
9f95a23c 431 promise<compat::optional<lease>> _result;
11fdf7f2
TL
432 state _state = state::NONE;
433 timer<> _timer;
434 timer<> _retry_timer;
435 ipv4 & _stack;
436 udp_channel _sock;
437 uint32_t _xid = 0;
438};
439
440const net::dhcp::impl::req_opt_type net::dhcp::impl::requested_options = { {
441 opt_type::SUBNET_MASK, opt_type::ROUTER, opt_type::DOMAIN_NAME_SERVERS,
442 opt_type::INTERFACE_MTU, opt_type::BROADCAST_ADDRESS } };
443
444const net::dhcp::impl::magic_tag net::dhcp::impl::options_magic = { { 0x63, 0x82, 0x53,
445 0x63 } };
446
447const uint16_t net::dhcp::impl::client_port;
448const uint16_t net::dhcp::impl::server_port;
449
450const steady_clock_type::duration net::dhcp::default_timeout = std::chrono::duration_cast<steady_clock_type::duration>(std::chrono::seconds(30));
451
452net::dhcp::dhcp(ipv4 & ip)
453: _impl(std::make_unique<impl>(ip))
454{}
455
456net::dhcp::dhcp(dhcp && v)
457: _impl(std::move(v._impl))
458{}
459
460net::dhcp::~dhcp()
461{}
462
463net::dhcp::result_type net::dhcp::discover(const steady_clock_type::duration & timeout) {
464 return _impl->run(lease(), timeout);
465}
466
467net::dhcp::result_type net::dhcp::renew(const lease & l, const steady_clock_type::duration & timeout) {
468 return _impl->run(l, timeout);
469}
470
471net::ip_packet_filter* net::dhcp::get_ipv4_filter() {
472 return _impl.get();
473}
474
475}