/* This file is part of jpcsp. Jpcsp is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Jpcsp is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Jpcsp. If not, see <http://www.gnu.org/licenses/>. */ package jpcsp.network.protocols; import static jpcsp.network.accesspoint.AccessPoint.IP_ADDRESS_LENGTH; import static jpcsp.network.protocols.NetPacket.getIpAddressString; import static jpcsp.network.protocols.UDP.UDP_PORT_DHCP_CLIENT; import static jpcsp.network.protocols.UDP.UDP_PORT_DHCP_SERVER; import java.io.EOFException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import jpcsp.util.Utilities; public class DHCP { // See https://en.wikipedia.org/wiki/Dynamic_Host_Configuration_Protocol public static final byte[] nullIPAddress = new byte[] { (byte) 0, (byte) 0, (byte) 0, (byte) 0 }; public static final byte[] broadcastIPAddress = new byte[] { (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF }; public static final int DHCP_BOOT_REQUEST = 1; public static final int DHCP_BOOT_REPLY = 2; // See DHCP Options in https://tools.ietf.org/html/rfc1533 public static final int DHCP_OPTION_MAGIC_COOKIE = 0x63825363; public static final int DHCP_OPTION_PAD = 0; public static final int DHCP_OPTION_SUBNET_MASK = 1; public static final int DHCP_OPTION_ROUTER = 3; public static final int DHCP_OPTION_DNS = 6; public static final int DHCP_OPTION_DOMAIN_NAME = 15; public static final int DHCP_OPTION_BROADCAST_ADDRESS = 28; public static final int DHCP_OPTION_REQUESTED_IP_ADDRESS = 50; public static final int DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51; public static final int DHCP_OPTION_MESSAGE_TYPE = 53; public static final int DHCP_OPTION_SERVER_IDENTIFIER = 54; public static final int DHCP_OPTION_PARAMETER_REQUEST = 55; public static final int DHCP_OPTION_MAXIMUM_DHCP_MESSAGE = 57; public static final int DHCP_OPTION_CLIENT_IDENTIFIER = 61; public static final int DHCP_OPTION_END = 255; public static final String[] DHCP_OPTION_NAMES = new String[256]; // See DHCP Message type in https://tools.ietf.org/html/rfc1533, chapter "9.4. DHCP Message Type" public static final int DHCP_OPTION_MESSAGE_TYPE_DHCPDISCOVER = 1; public static final int DHCP_OPTION_MESSAGE_TYPE_DHCPOFFER = 2; public static final int DHCP_OPTION_MESSAGE_TYPE_DHCPREQUEST = 3; public static final int DHCP_OPTION_MESSAGE_TYPE_DHCPACK = 5; public int opcode; public int hardwareAddressType; public int hardwareAddressLength; public int hops; public int transactionID; public int seconds; public boolean flagBroadcast; public int flagsZero; public byte[] clientIPAddress; public byte[] yourIPAddress; public byte[] nextServerIPAddress; public byte[] relayAgentIPAddress; public byte[] clientHardwareAddress; public String serverHostName; public String bootFileName; public List<DHCPOption> options; static { DHCP_OPTION_NAMES[DHCP_OPTION_PAD] = "PAD"; DHCP_OPTION_NAMES[DHCP_OPTION_SUBNET_MASK] = "SUBNET_MASK"; DHCP_OPTION_NAMES[DHCP_OPTION_ROUTER] = "ROUTER"; DHCP_OPTION_NAMES[DHCP_OPTION_DNS] = "DNS"; DHCP_OPTION_NAMES[DHCP_OPTION_DOMAIN_NAME] = "DOMAIN_NAME"; DHCP_OPTION_NAMES[DHCP_OPTION_BROADCAST_ADDRESS] = "BROADCAST_ADDRESS"; DHCP_OPTION_NAMES[DHCP_OPTION_REQUESTED_IP_ADDRESS] = "REQUESTED_IP_ADDRESS"; DHCP_OPTION_NAMES[DHCP_OPTION_IP_ADDRESS_LEASE_TIME] = "IP_ADDRESS_LEASE_TIME"; DHCP_OPTION_NAMES[DHCP_OPTION_MESSAGE_TYPE] = "MESSAGE_TYPE"; DHCP_OPTION_NAMES[DHCP_OPTION_SERVER_IDENTIFIER] = "SERVER_IDENTIFIER"; DHCP_OPTION_NAMES[DHCP_OPTION_PARAMETER_REQUEST] = "PARAMETER_REQUEST"; DHCP_OPTION_NAMES[DHCP_OPTION_MAXIMUM_DHCP_MESSAGE] = "MAXIMUM_DHCP_MESSAGE"; DHCP_OPTION_NAMES[DHCP_OPTION_CLIENT_IDENTIFIER] = "CLIENT_IDENTIFIER"; DHCP_OPTION_NAMES[DHCP_OPTION_END] = "END"; } public static class DHCPOption { int tag; int length; byte[] data; public DHCPOption() { tag = DHCP_OPTION_END; } public DHCPOption(int tag) { this.tag = tag; } public DHCPOption(int tag, byte data) { this.tag = tag; this.length = 1; this.data = new byte[] { data }; } public DHCPOption(int tag, int data) { this.tag = tag; this.length = 4; this.data = new byte[4]; this.data[0] = (byte) (data >> 24); this.data[1] = (byte) (data >> 16); this.data[2] = (byte) (data >> 8); this.data[3] = (byte) data; } public DHCPOption(int tag, byte[] data) { this.tag = tag; this.length = data == null ? 0 : data.length; this.data = data; } public void read(NetPacket packet) throws EOFException { tag = packet.read8(); if (!isZeroLengthTag()) { length = packet.read8(); data = packet.readBytes(length); } } private boolean isZeroLengthTag() { // PAD and END tags have no length. return tag == DHCP_OPTION_PAD || tag == DHCP_OPTION_END; } public int sizeOf() { if (isZeroLengthTag()) { return 1; } return 2 + length; } public NetPacket write(NetPacket packet) throws EOFException { packet.write8(tag); if (!isZeroLengthTag()) { packet.write8(length); packet.writeBytes(data, 0, length); } return packet; } public int getDataAsInt() { int value = 0; switch (length) { case 1: value = data[0] & 0xFF; break; case 2: value = (data[0] & 0xFF) << 8; value |= data[1] & 0xFF; break; case 4: value = (data[0] & 0xFF) << 24; value |= (data[1] & 0xFF) << 16; value |= (data[2] & 0xFF) << 8; value |= data[3] & 0xFF; break; } return value; } private String getTagName() { if (tag >= 0 && tag < DHCP_OPTION_NAMES.length) { if (DHCP_OPTION_NAMES[tag] != null) { return DHCP_OPTION_NAMES[tag]; } } return String.format("tag=%d", tag); } @Override public String toString() { if (isZeroLengthTag()) { return getTagName(); } return String.format("%s, length=0x%X, data=%s", getTagName(), length, Utilities.getMemoryDump(data, 0, length)); } } public DHCP() { options = new LinkedList<DHCP.DHCPOption>(); } public DHCP(DHCP dhcp) { opcode = dhcp.opcode; hardwareAddressType = dhcp.hardwareAddressType; hardwareAddressLength = dhcp.hardwareAddressLength; hops = dhcp.hops; transactionID = dhcp.transactionID; seconds = dhcp.seconds; flagBroadcast = dhcp.flagBroadcast; flagsZero = dhcp.flagsZero; clientIPAddress = dhcp.clientIPAddress; yourIPAddress = dhcp.yourIPAddress; nextServerIPAddress = dhcp.nextServerIPAddress; relayAgentIPAddress = dhcp.relayAgentIPAddress; clientHardwareAddress = dhcp.clientHardwareAddress; serverHostName = dhcp.serverHostName; bootFileName = dhcp.bootFileName; options = dhcp.options; } public void read(NetPacket packet) throws EOFException { opcode = packet.read8(); hardwareAddressType = packet.read8(); hardwareAddressLength = packet.read8(); hops = packet.read8(); transactionID = packet.read32(); seconds = packet.read16(); flagBroadcast = packet.readBoolean(); flagsZero = packet.readBits(15); clientIPAddress = packet.readBytes(IP_ADDRESS_LENGTH); yourIPAddress = packet.readBytes(IP_ADDRESS_LENGTH); nextServerIPAddress = packet.readBytes(IP_ADDRESS_LENGTH); relayAgentIPAddress = packet.readBytes(IP_ADDRESS_LENGTH); clientHardwareAddress = packet.readBytes(16); serverHostName = packet.readStringNZ(64); bootFileName = packet.readStringNZ(128); int optionsLength = 312; int magicCookie = packet.read32(); optionsLength -= 4; if (magicCookie == DHCP_OPTION_MAGIC_COOKIE) { while (optionsLength > 0) { DHCPOption option = new DHCPOption(); option.read(packet); options.add(option); optionsLength -= option.sizeOf(); // END tag marks the end of the options if (option.tag == DHCP_OPTION_END) { break; } } } else { packet.skip8(optionsLength); } } public NetPacket write(NetPacket packet) throws EOFException { packet.write8(opcode); packet.write8(hardwareAddressType); packet.write8(hardwareAddressLength); packet.write8(hops); packet.write32(transactionID); packet.write16(seconds); packet.writeBoolean(flagBroadcast); packet.writeBits(flagsZero, 15); packet.writeBytes(clientIPAddress, 0, IP_ADDRESS_LENGTH); packet.writeBytes(yourIPAddress, 0, IP_ADDRESS_LENGTH); packet.writeBytes(nextServerIPAddress, 0, IP_ADDRESS_LENGTH); packet.writeBytes(relayAgentIPAddress, 0, IP_ADDRESS_LENGTH); packet.writeBytes(clientHardwareAddress, 0, 16); packet.writeStringNZ(serverHostName, 64); packet.writeStringNZ(bootFileName, 128); int optionsLength = 312; packet.write32(DHCP_OPTION_MAGIC_COOKIE); optionsLength -= 4; for (DHCPOption option : options) { if (optionsLength < option.sizeOf()) { break; } option.write(packet); } return packet; } public NetPacket write() throws EOFException { return write(new NetPacket(sizeOf())); } public int sizeOf() { return 548; } public boolean isMessageOfType(UDP udp, IPv4 ipv4, int messageType) { if (opcode != DHCP_BOOT_REQUEST) { return false; } if (udp.sourcePort != UDP_PORT_DHCP_CLIENT || udp.destinationPort != UDP_PORT_DHCP_SERVER) { return false; } if (!Arrays.equals(ipv4.sourceIPAddress, nullIPAddress)) { return false; } if (!Arrays.equals(ipv4.destinationIPAddress, broadcastIPAddress)) { return false; } DHCPOption option = getOptionByTag(DHCP_OPTION_MESSAGE_TYPE); if (option == null || option.length != 1) { return false; } int optionMessageType = option.getDataAsInt(); if (optionMessageType != messageType) { return false; } return true; } public boolean isDiscovery(UDP udp, IPv4 ipv4) { return isMessageOfType(udp, ipv4, DHCP_OPTION_MESSAGE_TYPE_DHCPDISCOVER); } public boolean isRequest(UDP udp, IPv4 ipv4, byte[] requestedIpAddress) { if (!isMessageOfType(udp, ipv4, DHCP_OPTION_MESSAGE_TYPE_DHCPREQUEST)) { return false; } // Verify that the requested IP address is matching // the one specified in the options. DHCPOption requestedIpAddressOption = getOptionByTag(DHCP_OPTION_REQUESTED_IP_ADDRESS); if (requestedIpAddressOption == null || requestedIpAddressOption.length != 4) { return false; } if (!Arrays.equals(requestedIpAddress, requestedIpAddressOption.data)) { return false; } return true; } public void addOption(DHCPOption option) { options.add(option); } public void clearOptions() { options.clear(); } public DHCPOption getOptionByTag(int tag) { for (DHCPOption option : options) { if (option.tag == tag) { return option; } } return null; } private String optionsToString() { StringBuilder s = new StringBuilder(); for (DHCPOption option : options) { if (s.length() > 0) { s.append(", "); } s.append(option.toString()); } return s.toString(); } @Override public String toString() { return String.format("opcode=0x%X, hardwareAddressType=0x%X, hardwareAddressLength=0x%X, hops=0x%X, transactionID=0x%08X, seconds=0x%X, flagBroadcast=%b, flags=0x%04X, clientIPAddress=%s, yourIPAddress=%s, nextServerIPAddress=%s, relayAgentIPAddress=%s, clientHardwareAddress=%s, serverHostName='%s', bootFileName='%s', options=%s", opcode, hardwareAddressType, hardwareAddressLength, hops, transactionID, seconds, flagBroadcast, flagsZero, getIpAddressString(clientIPAddress), getIpAddressString(yourIPAddress), getIpAddressString(nextServerIPAddress), getIpAddressString(relayAgentIPAddress), Utilities.getMemoryDump(clientHardwareAddress, 0, hardwareAddressLength), serverHostName, bootFileName, optionsToString()); } }