123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- /*
- * Copyright (c) 2020, The SerenityOS developers.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- #include "DHCPv4Client.h"
- #include <AK/ByteBuffer.h>
- #include <AK/Debug.h>
- #include <AK/Endian.h>
- #include <AK/Function.h>
- #include <LibCore/SocketAddress.h>
- #include <LibCore/Timer.h>
- #include <stdio.h>
- static void send(const InterfaceDescriptor& iface, const DHCPv4Packet& packet, Core::Object*)
- {
- int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
- if (fd < 0) {
- dbgln("ERROR: socket :: {}", strerror(errno));
- return;
- }
- if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface.m_ifname.characters(), IFNAMSIZ) < 0) {
- dbgln("ERROR: setsockopt(SO_BINDTODEVICE) :: {}", strerror(errno));
- return;
- }
- sockaddr_in dst;
- memset(&dst, 0, sizeof(dst));
- dst.sin_family = AF_INET;
- dst.sin_port = htons(67);
- dst.sin_addr.s_addr = IPv4Address { 255, 255, 255, 255 }.to_u32();
- memset(&dst.sin_zero, 0, sizeof(dst.sin_zero));
- auto rc = sendto(fd, &packet, sizeof(packet), 0, (sockaddr*)&dst, sizeof(dst));
- if (rc < 0) {
- dbgln("sendto failed with {}", strerror(errno));
- // FIXME: what do we do here?
- }
- }
- static void set_params(const InterfaceDescriptor& iface, const IPv4Address& ipv4_addr, const IPv4Address& netmask, const IPv4Address& gateway)
- {
- int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
- if (fd < 0) {
- dbgln("ERROR: socket :: {}", strerror(errno));
- return;
- }
- struct ifreq ifr;
- memset(&ifr, 0, sizeof(ifr));
- bool fits = iface.m_ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ);
- if (!fits) {
- dbgln("Interface name doesn't fit into IFNAMSIZ!");
- return;
- }
- // set the IP address
- ifr.ifr_addr.sa_family = AF_INET;
- ((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = ipv4_addr.to_in_addr_t();
- if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) {
- dbgln("ERROR: ioctl(SIOCSIFADDR) :: {}", strerror(errno));
- }
- // set the network mask
- ((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = netmask.to_in_addr_t();
- if (ioctl(fd, SIOCSIFNETMASK, &ifr) < 0) {
- dbgln("ERROR: ioctl(SIOCSIFNETMASK) :: {}", strerror(errno));
- }
- // set the default gateway
- struct rtentry rt;
- memset(&rt, 0, sizeof(rt));
- rt.rt_dev = const_cast<char*>(iface.m_ifname.characters());
- rt.rt_gateway.sa_family = AF_INET;
- ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.to_in_addr_t();
- rt.rt_flags = RTF_UP | RTF_GATEWAY;
- if (ioctl(fd, SIOCADDRT, &rt) < 0) {
- dbgln("Error: ioctl(SIOCADDRT) :: {}", strerror(errno));
- }
- }
- DHCPv4Client::DHCPv4Client(Vector<InterfaceDescriptor> ifnames)
- : m_ifnames(ifnames)
- {
- m_server = Core::UDPServer::construct(this);
- m_server->on_ready_to_receive = [this] {
- auto buffer = m_server->receive(sizeof(DHCPv4Packet));
- dbgln("Received {} bytes", buffer.size());
- if (buffer.size() != sizeof(DHCPv4Packet)) {
- dbgln("we expected {} bytes, this is a bad packet", sizeof(DHCPv4Packet));
- return;
- }
- auto& packet = *(DHCPv4Packet*)buffer.data();
- process_incoming(packet);
- };
- if (!m_server->bind({}, 68)) {
- dbgln("The server we just created somehow came already bound, refusing to continue");
- ASSERT_NOT_REACHED();
- }
- for (auto& iface : m_ifnames)
- dhcp_discover(iface);
- }
- DHCPv4Client::~DHCPv4Client()
- {
- }
- void DHCPv4Client::handle_offer(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
- {
- dbgln("We were offered {} for {}", packet.yiaddr().to_string(), options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(0));
- auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
- if (!transaction) {
- dbgln("we're not looking for {}", packet.xid());
- return;
- }
- if (transaction->has_ip)
- return;
- if (transaction->accepted_offer) {
- // we've accepted someone's offer, but they haven't given us an ack
- // TODO: maybe record this offer?
- return;
- }
- // TAKE IT...
- transaction->offered_lease_time = options.get<u32>(DHCPOption::IPAddressLeaseTime).value();
- dhcp_request(*transaction, packet);
- }
- void DHCPv4Client::handle_ack(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
- {
- if constexpr (debug_dhcpv4_client) {
- dbgln("The DHCP server handed us {}", packet.yiaddr().to_string());
- dbgln("Here are the options: {}", options.to_string());
- }
- auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
- if (!transaction) {
- dbgln("we're not looking for {}", packet.xid());
- return;
- }
- transaction->has_ip = true;
- auto& interface = transaction->interface;
- auto new_ip = packet.yiaddr();
- auto lease_time = AK::convert_between_host_and_network_endian(options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(transaction->offered_lease_time));
- // set a timer for the duration of the lease, we shall renew if needed
- Core::Timer::create_single_shot(
- lease_time * 1000,
- [this, transaction, interface = InterfaceDescriptor { interface }, new_ip] {
- transaction->accepted_offer = false;
- transaction->has_ip = false;
- dhcp_discover(interface, new_ip);
- },
- this);
- set_params(transaction->interface, new_ip, options.get<IPv4Address>(DHCPOption::SubnetMask).value(), options.get_many<IPv4Address>(DHCPOption::Router, 1).first());
- }
- void DHCPv4Client::handle_nak(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
- {
- dbgln("The DHCP server told us to go chase our own tail about {}", packet.yiaddr().to_string());
- dbgln("Here are the options: {}", options.to_string());
- // make another request a bit later :shrug:
- auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
- if (!transaction) {
- dbgln("we're not looking for {}", packet.xid());
- return;
- }
- transaction->accepted_offer = false;
- transaction->has_ip = false;
- auto& iface = transaction->interface;
- Core::Timer::create_single_shot(
- 10000,
- [this, iface = InterfaceDescriptor { iface }] {
- dhcp_discover(iface);
- },
- this);
- }
- void DHCPv4Client::process_incoming(const DHCPv4Packet& packet)
- {
- auto options = packet.parse_options();
- dbgln<debug_dhcpv4_client>("Here are the options: {}", options.to_string());
- auto value = options.get<DHCPMessageType>(DHCPOption::DHCPMessageType).value();
- switch (value) {
- case DHCPMessageType::DHCPOffer:
- handle_offer(packet, options);
- break;
- case DHCPMessageType::DHCPAck:
- handle_ack(packet, options);
- break;
- case DHCPMessageType::DHCPNak:
- handle_nak(packet, options);
- break;
- case DHCPMessageType::DHCPDiscover:
- case DHCPMessageType::DHCPRequest:
- case DHCPMessageType::DHCPRelease:
- // These are not for us
- // we're just getting them because there are other people on our subnet
- // broadcasting stuff
- break;
- case DHCPMessageType::DHCPDecline:
- default:
- dbgln("I dunno what to do with this {}", (u8)value);
- ASSERT_NOT_REACHED();
- break;
- }
- }
- void DHCPv4Client::dhcp_discover(const InterfaceDescriptor& iface, IPv4Address previous)
- {
- auto transaction_id = rand();
- if constexpr (debug_dhcpv4_client) {
- dbgln("Trying to lease an IP for {} with ID {}", iface.m_ifname, transaction_id);
- if (!previous.is_zero())
- dbgln("going to request the server to hand us {}", previous.to_string());
- }
- DHCPv4PacketBuilder builder;
- DHCPv4Packet& packet = builder.peek();
- packet.set_op(DHCPv4Op::BootRequest);
- packet.set_htype(1); // 10mb ethernet
- packet.set_hlen(sizeof(MACAddress));
- packet.set_xid(transaction_id);
- packet.set_flags(DHCPv4Flags::Broadcast);
- packet.ciaddr() = previous;
- packet.set_chaddr(iface.m_mac_address);
- packet.set_secs(65535); // we lie
- // set packet options
- builder.set_message_type(DHCPMessageType::DHCPDiscover);
- auto& dhcp_packet = builder.build();
- // broadcast the discover request
- send(iface, dhcp_packet, this);
- m_ongoing_transactions.set(transaction_id, make<DHCPv4Transaction>(iface));
- }
- void DHCPv4Client::dhcp_request(DHCPv4Transaction& transaction, const DHCPv4Packet& offer)
- {
- auto& iface = transaction.interface;
- dbgln("Leasing the IP {} for adapter {}", offer.yiaddr().to_string(), iface.m_ifname);
- DHCPv4PacketBuilder builder;
- DHCPv4Packet& packet = builder.peek();
- packet.set_op(DHCPv4Op::BootRequest);
- packet.set_htype(1); // 10mb ethernet
- packet.set_hlen(sizeof(MACAddress));
- packet.set_xid(offer.xid());
- packet.set_flags(DHCPv4Flags::Broadcast);
- packet.set_chaddr(iface.m_mac_address);
- packet.set_secs(65535); // we lie
- // set packet options
- builder.set_message_type(DHCPMessageType::DHCPRequest);
- auto& dhcp_packet = builder.build();
- // broadcast the "request" request
- send(iface, dhcp_packet, this);
- transaction.accepted_offer = true;
- }
|