[dhcp] Add preliminary support for PXE Boot Servers
authorMichael Brown <mcb30@etherboot.org>
Wed, 21 Jan 2009 03:43:26 +0000 (03:43 +0000)
committerMichael Brown <mcb30@etherboot.org>
Wed, 21 Jan 2009 03:43:26 +0000 (03:43 +0000)
Some PXE configurations require us to perform a third DHCP transaction
(in addition to the real DHCP transaction and the ProxyDHCP
transaction) in order to retrieve information from a "Boot Server".

This is an experimental implementation, since the actual behaviour is
not well specified in the PXE spec.

src/include/gpxe/dhcp.h
src/net/fakedhcp.c
src/net/udp/dhcp.c

index cd3964c..a0a0941 100644 (file)
@@ -81,6 +81,9 @@ struct dhcp_packet;
 /** Vendor encapsulated options */
 #define DHCP_VENDOR_ENCAP 43
 
+/** PXE boot server multicast address */
+#define DHCP_PXE_BOOT_SERVER_MCAST DHCP_ENCAP_OPT ( DHCP_VENDOR_ENCAP, 7 )
+
 /** Requested IP address */
 #define DHCP_REQUESTED_ADDRESS 50
 
@@ -480,6 +483,9 @@ struct dhcphdr {
 /** Settings block name used for ProxyDHCP responses */
 #define PROXYDHCP_SETTINGS_NAME "proxydhcp"
 
+/** Setting block name used for BootServerDHCP responses */
+#define BSDHCP_SETTINGS_NAME "bs"
+
 extern int dhcp_create_packet ( struct dhcp_packet *dhcppkt,
                                struct net_device *netdev, uint8_t msgtype,
                                struct dhcp_options *options, 
index 6026475..d9ae36c 100644 (file)
@@ -176,6 +176,7 @@ int create_fakeproxydhcpack ( struct net_device *netdev,
                              void *data, size_t max_len ) {
        struct dhcp_packet dhcppkt;
        struct settings *settings;
+       struct settings *bs_settings;
        int rc;
 
        /* Identify ProxyDHCP settings */
@@ -200,5 +201,15 @@ int create_fakeproxydhcpack ( struct net_device *netdev,
                return rc;
        }
 
+       /* Merge in BootServerDHCP options, if present */
+       bs_settings = find_settings ( BSDHCP_SETTINGS_NAME );
+       if ( bs_settings ) {
+               if ( ( rc = copy_settings ( &dhcppkt, bs_settings ) ) != 0 ) {
+                       DBG ( "Could not set BootServerDHCPACK settings: "
+                             "%s\n", strerror ( rc ) );
+                       return rc;
+               }
+       }
+
        return 0;
 }
index dd3763b..90987c7 100644 (file)
@@ -287,6 +287,8 @@ enum dhcp_session_state {
        DHCP_STATE_REQUEST,
        /** Sending ProxyDHCPREQUESTs, waiting for ProxyDHCPACK */
        DHCP_STATE_PROXYREQUEST,
+       /** Sending BootServerDHCPREQUESTs, waiting for BootServerDHCPACK */
+       DHCP_STATE_BSREQUEST,
 };
 
 /**
@@ -300,6 +302,7 @@ static inline const char * dhcp_state_name ( enum dhcp_session_state state ) {
        case DHCP_STATE_DISCOVER:       return "DHCPDISCOVER";
        case DHCP_STATE_REQUEST:        return "DHCPREQUEST";
        case DHCP_STATE_PROXYREQUEST:   return "ProxyDHCPREQUEST";
+       case DHCP_STATE_BSREQUEST:      return "BootServerREQUEST";
        default:                        return "<invalid>";
        }
 }
@@ -326,6 +329,12 @@ struct dhcp_session {
        struct dhcp_settings *dhcpoffer;
        /** ProxyDHCPOFFER obtained during DHCPDISCOVER */
        struct dhcp_settings *proxydhcpoffer;
+       /** DHCPACK obtained during DHCPREQUEST */
+       struct dhcp_settings *dhcpack;
+       /** ProxyDHCPACK obtained during ProxyDHCPREQUEST */
+       struct dhcp_settings *proxydhcpack;
+       /** BootServerDHCPACK obtained during BootServerDHCPREQUEST */
+       struct dhcp_settings *bsdhcpack;
        /** Retransmission timer */
        struct retry_timer timer;
        /** Start time of the current state (in ticks) */
@@ -344,6 +353,9 @@ static void dhcp_free ( struct refcnt *refcnt ) {
        netdev_put ( dhcp->netdev );
        dhcpset_put ( dhcp->dhcpoffer );
        dhcpset_put ( dhcp->proxydhcpoffer );
+       dhcpset_put ( dhcp->dhcpack );
+       dhcpset_put ( dhcp->proxydhcpack );
+       dhcpset_put ( dhcp->bsdhcpack );
        free ( dhcp );
 }
 
@@ -555,6 +567,10 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
                .sin_family = AF_INET,
                .sin_port = htons ( PROXYDHCP_PORT ),
        };
+       static struct sockaddr_in client = {
+               .sin_family = AF_INET,
+               .sin_port = htons ( BOOTPC_PORT ),
+       };
        struct xfer_metadata meta = {
                .netdev = dhcp->netdev,
        };
@@ -584,6 +600,7 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
                DBGC ( dhcp, "DHCP %p transmitting ProxyDHCPREQUEST\n", dhcp );
                assert ( dhcp->dhcpoffer );
                assert ( dhcp->proxydhcpoffer );
+               assert ( dhcp->dhcpack );
                offer = &dhcp->proxydhcpoffer->dhcppkt;
                ciaddr = dhcp->dhcpoffer->dhcppkt.dhcphdr->yiaddr;
                check_len = dhcppkt_fetch ( offer, DHCP_SERVER_IDENTIFIER,
@@ -594,6 +611,24 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
                assert ( proxydhcp_server.sin_addr.s_addr != 0 );
                assert ( check_len == sizeof ( proxydhcp_server.sin_addr ) );
                break;
+       case DHCP_STATE_BSREQUEST:
+               DBGC ( dhcp, "DHCP %p transmitting BootServerREQUEST\n",
+                      dhcp );
+               assert ( dhcp->dhcpoffer );
+               assert ( dhcp->proxydhcpoffer );
+               assert ( dhcp->dhcpack );
+               assert ( dhcp->proxydhcpack );
+               offer = &dhcp->proxydhcpoffer->dhcppkt;
+               ciaddr = dhcp->dhcpoffer->dhcppkt.dhcphdr->yiaddr;
+               check_len = dhcppkt_fetch ( &dhcp->proxydhcpack->dhcppkt,
+                                           DHCP_PXE_BOOT_SERVER_MCAST,
+                                           &proxydhcp_server.sin_addr,
+                                           sizeof(proxydhcp_server.sin_addr));
+               meta.dest = ( struct sockaddr * ) &proxydhcp_server;
+               assert ( ciaddr.s_addr != 0 );
+               assert ( proxydhcp_server.sin_addr.s_addr != 0 );
+               assert ( check_len == sizeof ( proxydhcp_server.sin_addr ) );
+               break;
        default:
                assert ( 0 );
                break;
@@ -613,6 +648,12 @@ static int dhcp_tx ( struct dhcp_session *dhcp ) {
                goto done;
        }
 
+       /* Explicitly specify source address, if available. */
+       if ( ciaddr.s_addr ) {
+               client.sin_addr = ciaddr;
+               meta.src = ( struct sockaddr * ) &client;
+       }
+
        /* Transmit the packet */
        iob_put ( iobuf, dhcppkt.len );
        rc = xfer_deliver_iob_meta ( &dhcp->xfer, iobuf, &meta );
@@ -650,6 +691,7 @@ static void dhcp_set_state ( struct dhcp_session *dhcp,
  * @v dhcp             DHCP session
  */
 static void dhcp_next_state ( struct dhcp_session *dhcp ) {
+       struct in_addr bs_mcast = { 0 };
 
        switch ( dhcp->state ) {
        case DHCP_STATE_DISCOVER:
@@ -662,6 +704,17 @@ static void dhcp_next_state ( struct dhcp_session *dhcp ) {
                }
                /* Fall through */
        case DHCP_STATE_PROXYREQUEST:
+               if ( dhcp->proxydhcpack ) {
+                       dhcppkt_fetch ( &dhcp->proxydhcpack->dhcppkt,
+                                       DHCP_PXE_BOOT_SERVER_MCAST,
+                                       &bs_mcast, sizeof ( bs_mcast ) );
+                       if ( bs_mcast.s_addr ) {
+                               dhcp_set_state ( dhcp, DHCP_STATE_BSREQUEST );
+                               break;
+                       }
+               }
+               /* Fall through */
+       case DHCP_STATE_BSREQUEST:
                dhcp_finished ( dhcp, 0 );
                break;
        default:
@@ -845,9 +898,13 @@ static void dhcp_rx_dhcpack ( struct dhcp_session *dhcp,
                return;
        }
 
+       /* Record DHCPACK */
+       assert ( dhcp->dhcpack == NULL );
+       dhcp->dhcpack = dhcpset_get ( dhcpack );
+
        /* Register settings */
        parent = netdev_settings ( dhcp->netdev );
-       if ( ( rc = dhcp_store_dhcpack ( dhcp, dhcpack, parent ) ) !=0 )
+       if ( ( rc = dhcp_store_dhcpack ( dhcp, dhcpack, parent ) ) != 0 )
                return;
 
        /* Transition to next state */
@@ -885,6 +942,10 @@ static void dhcp_rx_proxydhcpack ( struct dhcp_session *dhcp,
        /* Rename settings */
        proxydhcpack->settings.name = PROXYDHCP_SETTINGS_NAME;
 
+       /* Record ProxyDHCPACK */
+       assert ( dhcp->proxydhcpack == NULL );
+       dhcp->proxydhcpack = dhcpset_get ( proxydhcpack );
+
        /* Register settings */
        if ( ( rc = dhcp_store_dhcpack ( dhcp, proxydhcpack, NULL ) ) != 0 )
                return;
@@ -893,6 +954,31 @@ static void dhcp_rx_proxydhcpack ( struct dhcp_session *dhcp,
        dhcp_next_state ( dhcp );
 }
 
+/**
+ * Handle received BootServerDHCPACK
+ *
+ * @v dhcp             DHCP session
+ * @v bsdhcpack        Received BootServerDHCPACK
+ */
+static void dhcp_rx_bsdhcpack ( struct dhcp_session *dhcp,
+                               struct dhcp_settings *bsdhcpack ) {
+       int rc;
+
+       /* Rename settings */
+       bsdhcpack->settings.name = BSDHCP_SETTINGS_NAME;
+
+       /* Record ProxyDHCPACK */
+       assert ( dhcp->bsdhcpack == NULL );
+       dhcp->bsdhcpack = dhcpset_get ( bsdhcpack );
+
+       /* Register settings */
+       if ( ( rc = dhcp_store_dhcpack ( dhcp, bsdhcpack, NULL ) ) != 0 )
+               return;
+
+       /* Transition to next state */
+       dhcp_next_state ( dhcp );
+}
+
 /**
  * Receive new data
  *
@@ -969,6 +1055,11 @@ static int dhcp_deliver_iob ( struct xfer_interface *xfer,
                     ( src_port == htons ( PROXYDHCP_PORT ) ) )
                        dhcp_rx_proxydhcpack ( dhcp, dhcpset );
                break;
+       case DHCP_STATE_BSREQUEST:
+               if ( ( msgtype == DHCPACK ) &&
+                    ( src_port == htons ( PROXYDHCP_PORT ) ) )
+                       dhcp_rx_bsdhcpack ( dhcp, dhcpset );
+               break;
        default:
                assert ( 0 );
                break;