]>
Commit | Line | Data |
---|---|---|
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 | ||
33 | namespace seastar { | |
34 | ||
35 | using namespace std::literals::chrono_literals; | |
36 | ||
37 | class net::dhcp::impl : public ip_packet_filter { | |
38 | public: | |
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 | ||
430 | private: | |
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 | ||
440 | const 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 | ||
444 | const net::dhcp::impl::magic_tag net::dhcp::impl::options_magic = { { 0x63, 0x82, 0x53, | |
445 | 0x63 } }; | |
446 | ||
447 | const uint16_t net::dhcp::impl::client_port; | |
448 | const uint16_t net::dhcp::impl::server_port; | |
449 | ||
450 | const steady_clock_type::duration net::dhcp::default_timeout = std::chrono::duration_cast<steady_clock_type::duration>(std::chrono::seconds(30)); | |
451 | ||
452 | net::dhcp::dhcp(ipv4 & ip) | |
453 | : _impl(std::make_unique<impl>(ip)) | |
454 | {} | |
455 | ||
456 | net::dhcp::dhcp(dhcp && v) | |
457 | : _impl(std::move(v._impl)) | |
458 | {} | |
459 | ||
460 | net::dhcp::~dhcp() | |
461 | {} | |
462 | ||
463 | net::dhcp::result_type net::dhcp::discover(const steady_clock_type::duration & timeout) { | |
464 | return _impl->run(lease(), timeout); | |
465 | } | |
466 | ||
467 | net::dhcp::result_type net::dhcp::renew(const lease & l, const steady_clock_type::duration & timeout) { | |
468 | return _impl->run(l, timeout); | |
469 | } | |
470 | ||
471 | net::ip_packet_filter* net::dhcp::get_ipv4_filter() { | |
472 | return _impl.get(); | |
473 | } | |
474 | ||
475 | } |