From 2138eeb87f0c018826f60a014f177f9253197ae9 Mon Sep 17 00:00:00 2001 From: Bananymous Date: Sat, 3 Feb 2024 02:41:06 +0200 Subject: [PATCH] Userspace: Implement super simple DHCP client --- userspace/CMakeLists.txt | 1 + userspace/dhcp-client/CMakeLists.txt | 16 ++ userspace/dhcp-client/main.cpp | 380 +++++++++++++++++++++++++++ userspace/init/main.cpp | 6 + 4 files changed, 403 insertions(+) create mode 100644 userspace/dhcp-client/CMakeLists.txt create mode 100644 userspace/dhcp-client/main.cpp diff --git a/userspace/CMakeLists.txt b/userspace/CMakeLists.txt index 52651552..85dbb0fd 100644 --- a/userspace/CMakeLists.txt +++ b/userspace/CMakeLists.txt @@ -8,6 +8,7 @@ set(USERSPACE_PROJECTS chmod cp dd + dhcp-client echo id init diff --git a/userspace/dhcp-client/CMakeLists.txt b/userspace/dhcp-client/CMakeLists.txt new file mode 100644 index 00000000..13446873 --- /dev/null +++ b/userspace/dhcp-client/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.26) + +project(dhcp-client CXX) + +set(SOURCES + main.cpp +) + +add_executable(dhcp-client ${SOURCES}) +target_compile_options(dhcp-client PUBLIC -O2 -g) +target_link_libraries(dhcp-client PUBLIC libc) + +add_custom_target(dhcp-client-install + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/dhcp-client ${BANAN_BIN}/ + DEPENDS dhcp-client +) diff --git a/userspace/dhcp-client/main.cpp b/userspace/dhcp-client/main.cpp new file mode 100644 index 00000000..b4b559dd --- /dev/null +++ b/userspace/dhcp-client/main.cpp @@ -0,0 +1,380 @@ +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_DHCP 0 + +struct DHCPPacket +{ + uint8_t op; + uint8_t htype { 0x01 }; + uint8_t hlen { 0x06 }; + uint8_t hops { 0x00 }; + BAN::NetworkEndian xid { 0x3903F326 }; + BAN::NetworkEndian secs { 0x0000 }; + BAN::NetworkEndian flags { 0x0000 }; + BAN::NetworkEndian ciaddr { 0 }; + BAN::NetworkEndian yiaddr { 0 }; + BAN::NetworkEndian siaddr { 0 }; + BAN::NetworkEndian giaddr { 0 }; + BAN::MACAddress chaddr; + uint8_t padding[10] {}; + uint8_t legacy[192] {}; + BAN::NetworkEndian magic_cookie { 0x63825363 }; + uint8_t options[0x100]; +}; +static_assert(offsetof(DHCPPacket, options) == 240); + +enum DHCPType +{ + SubnetMask = 1, + Router = 3, + DomainNameServer = 6, + RequestedIPv4Address= 50, + DHCPMessageType = 53, + ServerIdentifier = 54, + ParameterRequestList = 55, + End = 255, +}; + +enum DHCPMessageType +{ + INVALID = 0, + DHCPDISCOVER = 1, + DHCPOFFER = 2, + DHCPREQUEST = 3, + DHCPDECLINE = 4, + DHCPACK = 5, +}; + +BAN::MACAddress get_mac_address(int socket) +{ + ifreq ifreq; + if (ioctl(socket, SIOCGIFHWADDR, &ifreq) == -1) + { + perror("ioctl"); + exit(1); + } + + BAN::MACAddress mac_address; + memcpy(&mac_address, ifreq.ifr_ifru.ifru_hwaddr.sa_data, sizeof(mac_address)); + return mac_address; +} + +void update_ipv4_info(int socket, BAN::IPv4Address address, BAN::IPv4Address subnet) +{ + { + ifreq ifreq; + ifreq.ifr_ifru.ifru_addr.sa_family = AF_INET; + *(uint32_t*)ifreq.ifr_ifru.ifru_addr.sa_data = address.as_u32(); + if (ioctl(socket, SIOCSIFADDR, &ifreq) == -1) + { + perror("ioctl"); + exit(1); + } + } + + { + ifreq ifreq; + ifreq.ifr_ifru.ifru_netmask.sa_family = AF_INET; + *(uint32_t*)ifreq.ifr_ifru.ifru_netmask.sa_data = subnet.as_u32(); + if (ioctl(socket, SIOCSIFNETMASK, &ifreq) == -1) + { + perror("ioctl"); + exit(1); + } + } +} + +void send_dhcp_packet(int socket, const DHCPPacket& dhcp_packet, BAN::IPv4Address server_ipv4) +{ + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = 67; + server_addr.sin_addr.s_addr = server_ipv4.as_u32();; + + if (sendto(socket, &dhcp_packet, sizeof(DHCPPacket), 0, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) + { + perror("sendto"); + exit(1); + } +} + +void send_dhcp_discover(int socket, BAN::MACAddress mac_address) +{ + DHCPPacket dhcp_packet; + dhcp_packet.op = 0x01; + dhcp_packet.chaddr = mac_address; + + size_t idx = 0; + + dhcp_packet.options[idx++] = DHCPMessageType; + dhcp_packet.options[idx++] = 0x01; + dhcp_packet.options[idx++] = DHCPDISCOVER; + + dhcp_packet.options[idx++] = ParameterRequestList; + dhcp_packet.options[idx++] = 0x03; + dhcp_packet.options[idx++] = DomainNameServer; + dhcp_packet.options[idx++] = SubnetMask; + dhcp_packet.options[idx++] = Router; + + dhcp_packet.options[idx++] = 0xFF; + + send_dhcp_packet(socket, dhcp_packet, BAN::IPv4Address { 0xFFFFFFFF }); +} + +void send_dhcp_request(int socket, BAN::MACAddress mac_address, BAN::IPv4Address offered_ipv4, BAN::IPv4Address server_ipv4) +{ + DHCPPacket dhcp_packet; + dhcp_packet.op = 0x01; + dhcp_packet.siaddr = server_ipv4.as_u32(); + dhcp_packet.chaddr = mac_address; + + size_t idx = 0; + + dhcp_packet.options[idx++] = DHCPMessageType; + dhcp_packet.options[idx++] = 0x01; + dhcp_packet.options[idx++] = DHCPREQUEST; + + dhcp_packet.options[idx++] = RequestedIPv4Address; + dhcp_packet.options[idx++] = 0x04; + dhcp_packet.options[idx++] = offered_ipv4.address[0]; + dhcp_packet.options[idx++] = offered_ipv4.address[1]; + dhcp_packet.options[idx++] = offered_ipv4.address[2]; + dhcp_packet.options[idx++] = offered_ipv4.address[3]; + + dhcp_packet.options[idx++] = 0xFF; + + send_dhcp_packet(socket, dhcp_packet, BAN::IPv4Address { 0xFFFFFFFF }); +} + +struct DHCPPacketInfo +{ + enum DHCPMessageType message_type { INVALID }; + BAN::IPv4Address address { 0 }; + BAN::IPv4Address subnet { 0 }; + BAN::IPv4Address server { 0 }; + BAN::Vector routers; + BAN::Vector dns; +}; + +DHCPPacketInfo parse_dhcp_packet(const DHCPPacket& packet) +{ + DHCPPacketInfo packet_info; + packet_info.address = BAN::IPv4Address(packet.yiaddr); + + const uint8_t* options = packet.options; + while (*options != End) + { + uint8_t type = *options++; + uint8_t length = *options++; + + switch (type) + { + case SubnetMask: + { + if (length != 4) + { + fprintf(stderr, "Subnet mask with invalid length %hhu\n", length); + break; + } + uint32_t raw = *reinterpret_cast*>(options); + packet_info.subnet = BAN::IPv4Address(raw); + break; + } + case Router: + { + if (length % 4 != 0) + { + fprintf(stderr, "Router with invalid length %hhu\n", length); + break; + } + for (int i = 0; i < length; i += 4) + { + uint32_t raw = *reinterpret_cast*>(options + i); + MUST(packet_info.routers.emplace_back(raw)); + } + break; + } + case DomainNameServer: + { + if (length % 4 != 0) + { + fprintf(stderr, "DNS with invalid length %hhu\n", length); + break; + } + for (int i = 0; i < length; i += 4) + { + uint32_t raw = *reinterpret_cast*>(options + i); + MUST(packet_info.dns.emplace_back(raw)); + } + break; + } + case DHCPMessageType: + { + if (length != 1) + { + fprintf(stderr, "DHCP Message Type with invalid length %hhu\n", length); + break; + } + switch (*options) + { + case DHCPDISCOVER: packet_info.message_type = DHCPDISCOVER; break; + case DHCPOFFER: packet_info.message_type = DHCPOFFER; break; + case DHCPREQUEST: packet_info.message_type = DHCPREQUEST; break; + case DHCPDECLINE: packet_info.message_type = DHCPDECLINE; break; + case DHCPACK: packet_info.message_type = DHCPACK; break; + } + break; + } + case ServerIdentifier: + { + if (length != 4) + { + fprintf(stderr, "Server identifier with invalid length %hhu\n", length); + break; + } + uint32_t raw = *reinterpret_cast*>(options); + packet_info.server = BAN::IPv4Address(raw); + break; + } + } + + options += length; + } + + return packet_info; +} + +BAN::Optional read_dhcp_packet(int socket) +{ + DHCPPacket dhcp_packet; + + ssize_t nrecv = recvfrom(socket, &dhcp_packet, sizeof(dhcp_packet), 0, nullptr, nullptr); + if (nrecv == -1) + { + perror("revcfrom"); + return {}; + } + + if (nrecv <= (ssize_t)offsetof(DHCPPacket, options)) + { + fprintf(stderr, "invalid DHCP offer\n"); + return {}; + } + + if (dhcp_packet.magic_cookie != 0x63825363) + { + fprintf(stderr, "invalid DHCP offer\n"); + return {}; + } + + return parse_dhcp_packet(dhcp_packet); +} + +int main() +{ + int socket = ::socket(AF_INET, SOCK_DGRAM, 0); + if (socket == -1) + { + perror("socket"); + return 1; + } + + sockaddr_in client_addr; + client_addr.sin_family = AF_INET; + client_addr.sin_port = 68; + client_addr.sin_addr.s_addr = 0x00000000; + + if (bind(socket, (sockaddr*)&client_addr, sizeof(client_addr)) == -1) + { + perror("bind"); + return 1; + } + + auto mac_address = get_mac_address(socket); +#if DEBUG_DHCP + BAN::Formatter::println(putchar, "MAC: {}", mac_address); +#endif + + send_dhcp_discover(socket, mac_address); +#if DEBUG_DHCP + printf("DHCPDISCOVER sent\n"); +#endif + + auto dhcp_offer = read_dhcp_packet(socket); + if (!dhcp_offer.has_value()) + return 1; + if (dhcp_offer->message_type != DHCPOFFER) + { + fprintf(stderr, "DHCP server did not respond with DHCPOFFER\n"); + return 1; + } + +#if DEBUG_DHCP + BAN::Formatter::println(putchar, "DHCPOFFER"); + BAN::Formatter::println(putchar, " IP {}", dhcp_offer->address); + BAN::Formatter::println(putchar, " SUBNET {}", dhcp_offer->subnet); + BAN::Formatter::println(putchar, " SERVER {}", dhcp_offer->server); +#endif + + send_dhcp_request(socket, mac_address, dhcp_offer->address, dhcp_offer->server); +#if DEBUG_DHCP + printf("DHCPREQUEST sent\n"); +#endif + + auto dhcp_ack = read_dhcp_packet(socket); + if (!dhcp_ack.has_value()) + return 1; + if (dhcp_ack->message_type != DHCPACK) + { + fprintf(stderr, "DHCP server did not respond with DHCPACK\n"); + return 1; + } + +#if DEBUG_DHCP + BAN::Formatter::println(putchar, "DHCPACK"); + BAN::Formatter::println(putchar, " IP {}", dhcp_ack->address); + BAN::Formatter::println(putchar, " SUBNET {}", dhcp_ack->subnet); + BAN::Formatter::println(putchar, " SERVER {}", dhcp_ack->server); +#endif + + if (dhcp_offer->address != dhcp_ack->address) + { + fprintf(stderr, "DHCP server OFFER and ACK ips don't match\n"); + return 1; + } + + update_ipv4_info(socket, dhcp_ack->address, dhcp_ack->subnet); + + if (true) + { + uint32_t packet = 0x12345678; + + sockaddr_in server_addr; + server_addr.sin_family = AF_INET; + server_addr.sin_port = 67; + server_addr.sin_addr.s_addr = dhcp_ack->routers.front().as_u32(); + server_addr.sin_addr.s_addr = (192 << 24) | (168 << 16) | (1 << 8) | 203; + + if (sendto(socket, &packet, sizeof(packet), 0, (sockaddr*)&server_addr, sizeof(server_addr)) == -1) + { + perror("sendto"); + exit(1); + } + } + + close(socket); + + return 0; +} diff --git a/userspace/init/main.cpp b/userspace/init/main.cpp index b74cd2e5..4fdf9d41 100644 --- a/userspace/init/main.cpp +++ b/userspace/init/main.cpp @@ -29,6 +29,12 @@ int main() if (load_keymap("/usr/share/keymaps/fi.keymap") == -1) perror("load_keymap"); + if (fork() == 0) + { + execl("/bin/dhcp-client", "dhcp-client", NULL); + exit(1); + } + bool first = true; termios termios;