forked from Bananymous/banan-os
				
			
		
			
				
	
	
		
			386 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			386 lines
		
	
	
		
			8.9 KiB
		
	
	
	
		
			C++
		
	
	
	
| #include <BAN/Debug.h>
 | |
| #include <BAN/Endianness.h>
 | |
| #include <BAN/IPv4.h>
 | |
| #include <BAN/MAC.h>
 | |
| #include <BAN/Optional.h>
 | |
| #include <BAN/Vector.h>
 | |
| 
 | |
| #include <arpa/inet.h>
 | |
| #include <fcntl.h>
 | |
| #include <net/if.h>
 | |
| #include <netinet/in.h>
 | |
| #include <stdio.h>
 | |
| #include <stdlib.h>
 | |
| #include <string.h>
 | |
| #include <stropts.h>
 | |
| #include <sys/socket.h>
 | |
| 
 | |
| #define DEBUG_DHCP 1
 | |
| 
 | |
| struct DHCPPacket
 | |
| {
 | |
| 	uint8_t							op;
 | |
| 	uint8_t							htype	{ 0x01 };
 | |
| 	uint8_t							hlen	{ 0x06 };
 | |
| 	uint8_t							hops	{ 0x00 };
 | |
| 	BAN::NetworkEndian<uint32_t>	xid		{ 0x3903F326 };
 | |
| 	BAN::NetworkEndian<uint16_t>	secs	{ 0x0000 };
 | |
| 	BAN::NetworkEndian<uint16_t>	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<uint32_t>	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<sockaddr_in*>(&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<sockaddr_in*>(&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<sockaddr_in*>(&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<BAN::IPv4Address> routers;
 | |
| 	BAN::Vector<BAN::IPv4Address> 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<const uint32_t*>(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<const uint32_t*>(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<const uint32_t*>(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<const uint32_t*>(options);
 | |
| 				packet_info.server = BAN::IPv4Address(raw);
 | |
| 				break;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		options += length;
 | |
| 	}
 | |
| 
 | |
| 	return packet_info;
 | |
| }
 | |
| 
 | |
| BAN::Optional<DHCPPacketInfo> 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;
 | |
| }
 |