All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jvnet.hudson.proxy_dhcp.ProxyDhcpService Maven / Gradle / Ivy

package org.jvnet.hudson.proxy_dhcp;

import static org.jvnet.hudson.proxy_dhcp.DHCPMessageType.DHCPOFFER;
import static org.jvnet.hudson.proxy_dhcp.DHCPOption.OPTION_DHCP_SERVER_IDENTIFIER;
import static org.jvnet.hudson.proxy_dhcp.DHCPPacket.OP_BOOTREQUEST;

import java.io.IOException;
import java.io.Closeable;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.logging.ConsoleHandler;
import java.util.logging.Level;
import static java.util.logging.Level.INFO;
import java.util.logging.Logger;

/**
 * Proxy DHCP service that works in conjunction with a real DHCP server to point PXEclients to the TFTP boot coordinates.
 *
 * @author Kohsuke Kawaguchi
 */
public class ProxyDhcpService implements Runnable, Closeable {

    public final Inet4Address tftpServer;
    public final String bootFileName;
    private final DatagramSocket server;
    private final DatagramSocket replySocket;

    public ProxyDhcpService(Inet4Address tftpServer, String bootFileName) throws SocketException {
        this(tftpServer,bootFileName,null);
    }
    
    public ProxyDhcpService(Inet4Address tftpServer, String bootFileName, InetAddress interfaceToListen) throws SocketException {
        this.tftpServer = tftpServer;
        this.bootFileName = bootFileName;

        server = new DatagramSocket(DHCP_SERVER_PORT,interfaceToListen);
        server.setBroadcast(true);

        if(interfaceToListen==null) {
            replySocket = server;
        } else {
            replySocket = new DatagramSocket(0,tftpServer);
            replySocket.setBroadcast(true);
        }

        LOGGER.info("TFTP server: "+tftpServer.getHostAddress());
        LOGGER.info("Boot file: "+bootFileName);
    }

    public void run() {
        try {
            execute();
        } catch (IOException e) {
            LOGGER.log(Level.WARNING,"IO exception in proxy DHCP service",e);
        }
    }

    public void execute() throws IOException {
        DatagramPacket datagram = new DatagramPacket(new byte[8192],8192);
        while(true) {
            server.receive(datagram);
            LOGGER.fine("Got a packet from "+datagram.getSocketAddress());
            try {
                handle(server, datagram);
            } catch (IOException e) {
                LOGGER.log(INFO,"Failed to handle a DHCP packet",e);
            }
        }
    }

    public void close() {
        server.close();
    }

    private void handle(DatagramSocket server, DatagramPacket datagram) throws IOException {
        DHCPPacket packet = new DHCPPacket(datagram);
        if(packet.op!=OP_BOOTREQUEST) {
            LOGGER.fine("Not a BOOT request: "+packet.op);
            return;
        }

        if(!packet.is(DHCPMessageType.DHCPDISCOVER)) {
            LOGGER.fine("Not a DHCPDISCOVER");
            return;
        }

        String vendorClass = packet.getVendorClassIdentifier();
        if(vendorClass==null || !vendorClass.startsWith("PXEClient")) {
            LOGGER.fine("Not a PXEClient: "+vendorClass);
            return;
        }

        if (!shallWeRespond(datagram,packet)) {
            LOGGER.fine("Ignoring this request");
            return;
        }

        // at this point we think someone is PXE booting and we need to tell it where the boot image is
        DHCPPacket reply = packet.createResponse();
        reply.siaddr = tftpServer;
        reply.chaddr = packet.chaddr;
        reply.file = bootFileName;
        reply.options.add(DHCPOFFER.createOption()); // DHCP offer
        reply.options.add(new DHCPOption(OPTION_DHCP_SERVER_IDENTIFIER,tftpServer)); // set server identifier. not sure what to set to.
        reply.options.add(DHCPOption.createVendorClassIdentifier("PXEClient")); // set vendor class
        reply.options.add(PXEOptions.createDiscoveryControl());

        // send back the response
        datagram = reply.pack();
        datagram.setAddress(InetAddress.getByName("255.255.255.255"));
        datagram.setPort(DHCP_CLIENT_PORT);
        replySocket.send(datagram);
        LOGGER.fine("responded");
    }

    /**
     * Subtypes can override this method to selectively respond to the boot requests.
     *
     * By default, this method always return true, meaning it responds to every request.
     *
     * @param datagram
     *      The received DHCP DISCOVER packet.
     * @param packet
     *      Interpreted packet.
     */
    protected boolean shallWeRespond(DatagramPacket datagram, DHCPPacket packet) {
        return true;
    }

    public static final int DHCP_CLIENT_PORT = 68;
    public static final int DHCP_SERVER_PORT = 67;

    private static final Logger LOGGER = Logger.getLogger(ProxyDhcpService.class.getName());

    public static void main(String[] args) throws IOException {
        // take logging into our own hands
        LOGGER.setLevel(Level.ALL);
        ConsoleHandler h = new ConsoleHandler();
        h.setLevel(Level.ALL);
        LOGGER.addHandler(h);
        LOGGER.setUseParentHandlers(false);

        new ProxyDhcpService((Inet4Address) InetAddress.getByName(args[0]),args[1]).run();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy