Serenity Operating System
1/*
2 * Copyright (c) 2020, The SerenityOS developers.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "DHCPv4Client.h"
28#include <AK/ByteBuffer.h>
29#include <AK/Function.h>
30#include <LibCore/SocketAddress.h>
31#include <LibCore/Timer.h>
32#include <stdio.h>
33
34//#define DHCPV4CLIENT_DEBUG
35
36static void send(const InterfaceDescriptor& iface, const DHCPv4Packet& packet, Core::Object*)
37{
38 int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
39 if (fd < 0) {
40 dbg() << "ERROR: socket :: " << strerror(errno);
41 return;
42 }
43
44 if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface.m_ifname.characters(), IFNAMSIZ) < 0) {
45 dbg() << "ERROR: setsockopt(SO_BINDTODEVICE) :: " << strerror(errno);
46 return;
47 }
48
49 sockaddr_in dst;
50 memset(&dst, 0, sizeof(dst));
51 dst.sin_family = AF_INET;
52 dst.sin_port = htons(67);
53 dst.sin_addr.s_addr = IPv4Address { 255, 255, 255, 255 }.to_u32();
54 memset(&dst.sin_zero, 0, sizeof(dst.sin_zero));
55
56 auto rc = sendto(fd, &packet, sizeof(packet), 0, (sockaddr*)&dst, sizeof(dst));
57 if (rc < 0) {
58 dbg() << "sendto failed with " << strerror(errno);
59 // FIXME: what do we do here?
60 }
61}
62
63static void set_params(const InterfaceDescriptor& iface, const IPv4Address& ipv4_addr, const IPv4Address& netmask, const IPv4Address& gateway)
64{
65 int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
66 if (fd < 0) {
67 dbg() << "ERROR: socket :: " << strerror(errno);
68 return;
69 }
70
71 struct ifreq ifr;
72 memset(&ifr, 0, sizeof(ifr));
73 strncpy(ifr.ifr_name, iface.m_ifname.characters(), IFNAMSIZ);
74
75 // set the IP address
76 ifr.ifr_addr.sa_family = AF_INET;
77 ((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = ipv4_addr.to_in_addr_t();
78
79 if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) {
80 dbg() << "ERROR: ioctl(SIOCSIFADDR) :: " << strerror(errno);
81 }
82
83 // set the network mask
84 ((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = netmask.to_in_addr_t();
85
86 if (ioctl(fd, SIOCSIFNETMASK, &ifr) < 0) {
87 dbg() << "ERROR: ioctl(SIOCSIFNETMASK) :: " << strerror(errno);
88 }
89
90 // set the default gateway
91 struct rtentry rt;
92 memset(&rt, 0, sizeof(rt));
93
94 rt.rt_dev = const_cast<char*>(iface.m_ifname.characters());
95 rt.rt_gateway.sa_family = AF_INET;
96 ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.to_in_addr_t();
97 rt.rt_flags = RTF_UP | RTF_GATEWAY;
98
99 if (ioctl(fd, SIOCADDRT, &rt) < 0) {
100 dbg() << "Error: ioctl(SIOCADDRT) :: " << strerror(errno);
101 }
102}
103
104DHCPv4Client::DHCPv4Client(Vector<InterfaceDescriptor> ifnames)
105 : m_ifnames(ifnames)
106{
107 m_server = Core::UDPServer::construct(this);
108 m_server->on_ready_to_receive = [this] {
109 auto buffer = m_server->receive(sizeof(DHCPv4Packet));
110 dbg() << "Received " << buffer.size() << " bytes";
111 if (buffer.size() != sizeof(DHCPv4Packet)) {
112 dbg() << "we expected " << sizeof(DHCPv4Packet) << " bytes, this is a bad packet";
113 return;
114 }
115 auto& packet = *(DHCPv4Packet*)buffer.data();
116 process_incoming(packet);
117 };
118
119 if (!m_server->bind({}, 68)) {
120 dbg() << "The server we just created somehow came already bound, refusing to continue";
121 ASSERT_NOT_REACHED();
122 }
123
124 for (auto& iface : m_ifnames)
125 dhcp_discover(iface);
126}
127
128DHCPv4Client::~DHCPv4Client()
129{
130}
131
132void DHCPv4Client::handle_offer(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
133{
134 dbg() << "We were offered " << packet.yiaddr().to_string() << " for " << options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(0);
135 auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
136 if (!transaction) {
137 dbg() << "we're not looking for " << packet.xid();
138 return;
139 }
140 if (transaction->has_ip)
141 return;
142 if (transaction->accepted_offer) {
143 // we've accepted someone's offer, but they haven't given us an ack
144 // TODO: maybe record this offer?
145 return;
146 }
147 // TAKE IT...
148 transaction->offered_lease_time = options.get<u32>(DHCPOption::IPAddressLeaseTime).value();
149 dhcp_request(*transaction, packet);
150}
151
152void DHCPv4Client::handle_ack(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
153{
154#ifdef DHCPV4CLIENT_DEBUG
155 dbg() << "The DHCP server handed us " << packet.yiaddr().to_string();
156 dbg() << "Here are the options: " << options.to_string();
157#endif
158 auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
159 if (!transaction) {
160 dbg() << "we're not looking for " << packet.xid();
161 return;
162 }
163 transaction->has_ip = true;
164 auto& interface = transaction->interface;
165 auto new_ip = packet.yiaddr();
166 auto lease_time = convert_between_host_and_network(options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(transaction->offered_lease_time));
167 // set a timer for the duration of the lease, we shall renew if needed
168 Core::Timer::create_single_shot(
169 lease_time * 1000,
170 [this, transaction, interface = InterfaceDescriptor { interface }, new_ip] {
171 transaction->accepted_offer = false;
172 transaction->has_ip = false;
173 dhcp_discover(interface, new_ip);
174 },
175 this);
176 set_params(transaction->interface, new_ip, options.get<IPv4Address>(DHCPOption::SubnetMask).value(), options.get_many<IPv4Address>(DHCPOption::Router, 1).first());
177}
178
179void DHCPv4Client::handle_nak(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
180{
181 dbg() << "The DHCP server told us to go chase our own tail about " << packet.yiaddr().to_string();
182 dbg() << "Here are the options: " << options.to_string();
183 // make another request a bit later :shrug:
184 auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
185 if (!transaction) {
186 dbg() << "we're not looking for " << packet.xid();
187 return;
188 }
189 transaction->accepted_offer = false;
190 transaction->has_ip = false;
191 auto& iface = transaction->interface;
192 Core::Timer::create_single_shot(
193 10000,
194 [this, iface = InterfaceDescriptor { iface }] {
195 dhcp_discover(iface);
196 },
197 this);
198}
199
200void DHCPv4Client::process_incoming(const DHCPv4Packet& packet)
201{
202 auto options = packet.parse_options();
203#ifdef DHCPV4CLIENT_DEBUG
204 dbg() << "Here are the options: " << options.to_string();
205#endif
206 auto value = options.get<DHCPMessageType>(DHCPOption::DHCPMessageType).value();
207 switch (value) {
208 case DHCPMessageType::DHCPOffer:
209 handle_offer(packet, options);
210 break;
211 case DHCPMessageType::DHCPAck:
212 handle_ack(packet, options);
213 break;
214 case DHCPMessageType::DHCPNak:
215 handle_nak(packet, options);
216 break;
217 case DHCPMessageType::DHCPDiscover:
218 case DHCPMessageType::DHCPRequest:
219 case DHCPMessageType::DHCPRelease:
220 // These are not for us
221 // we're just getting them because there are other people on our subnet
222 // broadcasting stuff
223 break;
224 case DHCPMessageType::DHCPDecline:
225 default:
226 dbg() << "I dunno what to do with this " << (u8)value;
227 ASSERT_NOT_REACHED();
228 break;
229 }
230}
231
232void DHCPv4Client::dhcp_discover(const InterfaceDescriptor& iface, IPv4Address previous)
233{
234 auto transaction_id = rand();
235#ifdef DHCPV4CLIENT_DEBUG
236 dbg() << "Trying to lease an IP for " << iface.m_ifname << " with ID " << transaction_id;
237 if (!previous.is_zero())
238 dbg() << "going to request the server to hand us " << previous.to_string();
239#endif
240 DHCPv4PacketBuilder builder;
241
242 DHCPv4Packet& packet = builder.peek();
243 packet.set_op(DHCPv4Op::BootRequest);
244 packet.set_htype(1); // 10mb ethernet
245 packet.set_hlen(sizeof(MACAddress));
246 packet.set_xid(transaction_id);
247 packet.set_flags(DHCPv4Flags::Broadcast);
248 packet.ciaddr() = previous;
249 packet.set_chaddr(iface.m_mac_address);
250 packet.set_secs(65535); // we lie
251
252 // set packet options
253 builder.set_message_type(DHCPMessageType::DHCPDiscover);
254 auto& dhcp_packet = builder.build();
255
256 // broadcast the discover request
257 send(iface, dhcp_packet, this);
258 m_ongoing_transactions.set(transaction_id, make<DHCPv4Transaction>(iface));
259}
260
261void DHCPv4Client::dhcp_request(DHCPv4Transaction& transaction, const DHCPv4Packet& offer)
262{
263 auto& iface = transaction.interface;
264 dbg() << "Leasing the IP " << offer.yiaddr().to_string() << " for adapter " << iface.m_ifname;
265 DHCPv4PacketBuilder builder;
266
267 DHCPv4Packet& packet = builder.peek();
268 packet.set_op(DHCPv4Op::BootRequest);
269 packet.set_htype(1); // 10mb ethernet
270 packet.set_hlen(sizeof(MACAddress));
271 packet.set_xid(offer.xid());
272 packet.set_flags(DHCPv4Flags::Broadcast);
273 packet.set_chaddr(iface.m_mac_address);
274 packet.set_secs(65535); // we lie
275
276 // set packet options
277 builder.set_message_type(DHCPMessageType::DHCPRequest);
278 auto& dhcp_packet = builder.build();
279
280 // broadcast the "request" request
281 send(iface, dhcp_packet, this);
282 transaction.accepted_offer = true;
283}