#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEBUG_DHCP 1 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::IPv4Address ciaddr { 0 }; BAN::IPv4Address yiaddr { 0 }; BAN::IPv4Address siaddr { 0 }; BAN::IPv4Address 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 netmask, BAN::IPv4Address gateway) { { ifreq ifreq; auto& ifru_addr = *reinterpret_cast(&ifreq.ifr_ifru.ifru_addr); ifru_addr.sin_family = AF_INET; ifru_addr.sin_addr.s_addr = address.raw; if (ioctl(socket, SIOCSIFADDR, &ifreq) == -1) { perror("ioctl"); exit(1); } } { ifreq ifreq; auto& ifru_netmask = *reinterpret_cast(&ifreq.ifr_ifru.ifru_netmask); ifru_netmask.sin_family = AF_INET; ifru_netmask.sin_addr.s_addr = netmask.raw; if (ioctl(socket, SIOCSIFNETMASK, &ifreq) == -1) { perror("ioctl"); exit(1); } } if (gateway.raw) { ifreq ifreq; auto& ifru_gwaddr = *reinterpret_cast(&ifreq.ifr_ifru.ifru_gwaddr); ifru_gwaddr.sin_family = AF_INET; ifru_gwaddr.sin_addr.s_addr = gateway.raw; if (ioctl(socket, SIOCSIFGWADDR, &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 = htons(67); server_addr.sin_addr.s_addr = server_ipv4.raw; 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.raw; 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.octets[0]; dhcp_packet.options[idx++] = offered_ipv4.octets[1]; dhcp_packet.options[idx++] = offered_ipv4.octets[2]; dhcp_packet.options[idx++] = offered_ipv4.octets[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 = htons(68); client_addr.sin_addr.s_addr = INADDR_ANY; if (bind(socket, (sockaddr*)&client_addr, sizeof(client_addr)) == -1) { perror("bind"); return 1; } auto mac_address = get_mac_address(socket); #if DEBUG_DHCP dprintln("MAC: {}", mac_address); #endif send_dhcp_discover(socket, mac_address); #if DEBUG_DHCP dprintln("DHCPDISCOVER sent"); #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 dprintln("DHCPOFFER"); dprintln(" IP {}", dhcp_offer->address); dprintln(" SUBNET {}", dhcp_offer->subnet); dprintln(" SERVER {}", dhcp_offer->server); #endif send_dhcp_request(socket, mac_address, dhcp_offer->address, dhcp_offer->server); #if DEBUG_DHCP dprintln("DHCPREQUEST sent"); #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 dprintln("DHCPACK"); dprintln(" IP {}", dhcp_ack->address); dprintln(" SUBNET {}", dhcp_ack->subnet); dprintln(" 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; } BAN::IPv4Address gateway { 0 }; if (!dhcp_ack->routers.empty()) gateway = dhcp_ack->routers.front(); update_ipv4_info(socket, dhcp_ack->address, dhcp_ack->subnet, gateway); close(socket); return 0; }