diff --git a/common.mk b/common.mk
index fcfaeee..4e8ee3a 100644
--- a/common.mk
+++ b/common.mk
@@ -48,6 +48,13 @@ ESPTOOL ?= esptool.py
 ESPPORT ?= /dev/ttyUSB0
 ESPBAUD ?= 115200
 
+# set this to 0 if you don't need floating point support in printf/scanf
+# this will save approx 14.5KB flash space and 448 bytes of statically allocated
+# data RAM
+#
+# NB: Setting the value to 0 requires a recent esptool.py (Feb 2016 / commit ebf02c9)
+PRINTF_SCANF_FLOAT_SUPPORT ?= 1
+
 # Set OTA to 1 to build an image that supports rBoot OTA bootloader
 #
 # Currently only works with 16mbit or more flash sizes, with 8mbit
@@ -109,7 +116,7 @@ CXXFLAGS	?= $(C_CXX_FLAGS) -fno-exceptions -fno-rtti $(EXTRA_CXXFLAGS)
 # these aren't technically preprocesor args, but used by all 3 of C, C++, assembler
 CPPFLAGS	+= -mlongcalls -mtext-section-literals
 
-LDFLAGS		= -nostdlib -Wl,--no-check-sections -L$(BUILD_DIR)sdklib -L$(ROOT)lib -u $(ENTRY_SYMBOL) -Wl,-static -Wl,-Map=$(BUILD_DIR)$(PROGRAM).map  $(EXTRA_LDFLAGS)
+LDFLAGS		= -nostdlib -Wl,--no-check-sections -L$(BUILD_DIR)sdklib -L$(ROOT)lib -u $(ENTRY_SYMBOL) -Wl,-static -Wl,-Map=$(BUILD_DIR)$(PROGRAM).map $(EXTRA_LDFLAGS)
 
 ifeq ($(SPLIT_SECTIONS),1)
   C_CXX_FLAGS += -ffunction-sections -fdata-sections
@@ -202,6 +209,9 @@ INC_DIRS      = $(PROGRAM_DIR) $(PROGRAM_DIR)include $(ROOT)include
 ifeq ($(OWN_LIBC),1)
     INC_DIRS += $(ROOT)libc/xtensa-lx106-elf/include
     LDFLAGS += -L$(ROOT)libc/xtensa-lx106-elf/lib
+   ifeq ($(PRINTF_SCANF_FLOAT_SUPPORT),1)
+     LDFLAGS += -u _printf_float -u _scanf_float
+   endif
 endif
 
 ifeq ("$(V)","1")
diff --git a/core/app_main.c b/core/app_main.c
index a710c77..7a9bf8d 100644
--- a/core/app_main.c
+++ b/core/app_main.c
@@ -250,7 +250,7 @@ void IRAM sdk_user_start(void) {
 
 // .text+0x3a8
 void IRAM vApplicationStackOverflowHook(xTaskHandle task, char *task_name) {
-    printf("\"%s\"(stack_size = %lu) overflow the heap_size.\n", task_name, uxTaskGetStackHighWaterMark(task));
+    printf("Task stack overflow (high water mark=%lu name=\"%s\")\n", uxTaskGetStackHighWaterMark(task), task_name);
 }
 
 // .text+0x3d8
diff --git a/core/esp_gpio.c b/core/esp_gpio.c
index 23ddf22..8d12be1 100644
--- a/core/esp_gpio.c
+++ b/core/esp_gpio.c
@@ -8,32 +8,34 @@
 
 void gpio_enable(const uint8_t gpio_num, const gpio_direction_t direction)
 {
-    uint32_t iomux_flags;
-
-    switch(direction) {
+    switch (direction) {
     case GPIO_INPUT:
-        iomux_flags = 0;
+        GPIO.ENABLE_OUT_CLEAR = BIT(gpio_num);
+        iomux_set_gpio_function(gpio_num, false);
         break;
     case GPIO_OUTPUT:
-        iomux_flags = IOMUX_PIN_OUTPUT_ENABLE;
+        GPIO.CONF[gpio_num] &= ~GPIO_CONF_OPEN_DRAIN;
+        GPIO.ENABLE_OUT_SET = BIT(gpio_num);
+        iomux_set_gpio_function(gpio_num, true);
         break;
     case GPIO_OUT_OPEN_DRAIN:
-        iomux_flags = IOMUX_PIN_OUTPUT_ENABLE;
-        break;
-    case GPIO_INPUT_PULLUP:
-        iomux_flags = IOMUX_PIN_PULLUP;
-        break;
-    default:
-        return; /* Invalid direction flag */
-    }
-    iomux_set_gpio_function(gpio_num, iomux_flags);
-    if(direction == GPIO_OUT_OPEN_DRAIN)
         GPIO.CONF[gpio_num] |= GPIO_CONF_OPEN_DRAIN;
-    else
-        GPIO.CONF[gpio_num] &= ~GPIO_CONF_OPEN_DRAIN;
-    if (iomux_flags & IOMUX_PIN_OUTPUT_ENABLE)
         GPIO.ENABLE_OUT_SET = BIT(gpio_num);
-    else
-        GPIO.ENABLE_OUT_CLEAR = BIT(gpio_num);
+        iomux_set_gpio_function(gpio_num, true);
+        break;
+    }
+}
+
+void gpio_set_pullup(uint8_t gpio_num, bool enabled, bool enabled_during_sleep)
+{
+    uint32_t flags = 0;
+
+    if (enabled) {
+        flags |= IOMUX_PIN_PULLUP;
+    }
+    if (enabled_during_sleep) {
+        flags |= IOMUX_PIN_PULLUP_SLEEP;
+    }
+    iomux_set_pullup_flags(gpio_to_iomux(gpio_num), flags);
 }
 
diff --git a/core/include/esp/gpio.h b/core/include/esp/gpio.h
index b5efcaf..01fe144 100644
--- a/core/include/esp/gpio.h
+++ b/core/include/esp/gpio.h
@@ -17,14 +17,24 @@ typedef enum {
     GPIO_INPUT,
     GPIO_OUTPUT,         /* "Standard" push-pull output */
     GPIO_OUT_OPEN_DRAIN, /* Open drain output */
-    GPIO_INPUT_PULLUP,
 } gpio_direction_t;
 
-/* Enable GPIO on the specified pin, and set it to input/output/ with
- *  pullup as needed
+/* Enable GPIO on the specified pin, and set it to input or output mode
  */
 void gpio_enable(const uint8_t gpio_num, const gpio_direction_t direction);
 
+/* Enable/disable internal pullup resistor for a particular GPIO
+ *
+ * Note: According to Espressif, pullup resistor values are between 30K and
+ * 100K ohms (see http://bbs.espressif.com/viewtopic.php?t=1079#p4097)
+ * However, measured values suggest that the actual value is likely to be close
+ * to 47K in reality.
+ *
+ * NOTE: The enabled_during_sleep setting is currently untested (please send
+ * feedback if you give it a try)
+ */
+void gpio_set_pullup(uint8_t gpio_num, bool enabled, bool enabled_during_sleep);
+
 /* Disable GPIO on the specified pin, and set it Hi-Z.
  *
  * If later muxing this pin to a different function, make sure to set
@@ -36,13 +46,37 @@ static inline void gpio_disable(const uint8_t gpio_num)
     *gpio_iomux_reg(gpio_num) &= ~IOMUX_PIN_OUTPUT_ENABLE;
 }
 
+/* Set whether the specified pin continues to drive its output when the ESP8266
+ * goes into sleep mode.  Note that this setting is reset to off whenever
+ * gpio_enable is called, so this must be called after calling that function.
+ *
+ * NOTE: This functionality is currently untested (please send feedback if you
+ * give it a try)
+ */
+static inline void gpio_set_output_on_sleep(const uint8_t gpio_num, bool enabled)
+{
+    if (enabled) {
+        IOMUX.PIN[gpio_to_iomux(gpio_num)] |= IOMUX_PIN_OUTPUT_ENABLE_SLEEP;
+    } else {
+        IOMUX.PIN[gpio_to_iomux(gpio_num)] &= ~IOMUX_PIN_OUTPUT_ENABLE_SLEEP;
+    }
+}
+
 /* Set output of a pin high or low.
  *
- * Only works if pin has been set to GPIO_OUTPUT via gpio_enable()
+ * Only works if pin has been set to GPIO_OUTPUT or GPIO_OUT_OPEN_DRAIN via
+ * gpio_enable()
+ *
+ * If the mode is GPIO_OUT_OPEN_DRAIN, setting it low (false) will pull the pin
+ * down to ground, but setting it high (true) will allow it to float.  Note
+ * that even in GPIO_OUT_OPEN_DRAIN mode, the input gates are still physically
+ * connected to the pin, and can be damaged if the voltage is not in either the
+ * "low" or "high" range.  Make sure there is some sort of pull-up resistor on
+ * the line to avoid floating logic lines!
  */
 static inline void gpio_write(const uint8_t gpio_num, const bool set)
 {
-    if(set)
+    if (set)
         GPIO.OUT_SET = BIT(gpio_num);
     else
         GPIO.OUT_CLEAR = BIT(gpio_num);
@@ -50,7 +84,10 @@ static inline void gpio_write(const uint8_t gpio_num, const bool set)
 
 /* Toggle output of a pin
  *
- * Only works if pin has been set to GPIO_OUTPUT via gpio_enable()
+ * Only works if pin has been set to GPIO_OUTPUT or GPIO_OUT_OPEN_DRAIN via
+ * gpio_enable()
+ *
+ * See notes in gpio_write() about GPIO_OUT_OPEN_DRAIN mode.
  */
 static inline void gpio_toggle(const uint8_t gpio_num)
 {
@@ -68,8 +105,12 @@ static inline void gpio_toggle(const uint8_t gpio_num)
 
 /* Read input value of a GPIO pin.
  *
- * If pin is set as an input, this reads the value on the pin.
- * If pin is set as an output, this reads the last value written to the pin.
+ * If pin is set GPIO_INPUT, this reads the level on the pin.
+ * If pin is set GPIO_OUTPUT, this reads the level at which the pin is
+ * currently being driven (i.e. the last value written).
+ * If pin is set GPIO_OUT_OPEN_DRAIN, when the pin is written low, this will
+ * return low (false), when the pin is written high, this will behave like
+ * GPIO_INPUT.
  */
 static inline bool gpio_read(const uint8_t gpio_num)
 {
@@ -80,7 +121,8 @@ extern void gpio_interrupt_handler(void);
 
 /* Set the interrupt type for a given pin
  *
- * If int_type is not GPIO_INTTYPE_NONE, the gpio_interrupt_handler will be attached and unmasked.
+ * If int_type is not GPIO_INTTYPE_NONE, the gpio_interrupt_handler will be
+ * attached and unmasked.
  */
 static inline void gpio_set_interrupt(const uint8_t gpio_num, const gpio_inttype_t int_type)
 {
diff --git a/core/include/esp/iomux.h b/core/include/esp/iomux.h
index 214c9e8..9948784 100644
--- a/core/include/esp/iomux.h
+++ b/core/include/esp/iomux.h
@@ -43,25 +43,41 @@ inline static esp_reg_t gpio_iomux_reg(const uint8_t gpio_number)
     return &(IOMUX.PIN[gpio_to_iomux(gpio_number)]);
 }
 
+inline static void iomux_set_function(uint8_t iomux_num, uint32_t func)
+{
+    uint32_t prev = IOMUX.PIN[iomux_num] & ~IOMUX_PIN_FUNC_MASK;
+    IOMUX.PIN[iomux_num] = IOMUX_FUNC(func) | prev;
+}
+
+inline static void iomux_set_direction_flags(uint8_t iomux_num, uint32_t dir_flags)
+{
+    uint32_t mask = IOMUX_PIN_OUTPUT_ENABLE | IOMUX_PIN_OUTPUT_ENABLE_SLEEP;
+    uint32_t prev = IOMUX.PIN[iomux_num] & ~mask;
+    IOMUX.PIN[iomux_num] = dir_flags | prev;
+}
+
+inline static void iomux_set_pullup_flags(uint8_t iomux_num, uint32_t pullup_flags)
+{
+    uint32_t mask = IOMUX_PIN_PULLUP | IOMUX_PIN_PULLDOWN | IOMUX_PIN_PULLUP_SLEEP | IOMUX_PIN_PULLDOWN_SLEEP;
+    uint32_t prev = IOMUX.PIN[iomux_num] & ~mask;
+    IOMUX.PIN[iomux_num] = pullup_flags | prev;
+}
+
 /**
  * Set a pin to the GPIO function.
  *
  * This allows you to set pins to GPIO without knowing in advance the
  * exact register masks to use.
  *
- * flags can be any of IOMUX_PIN_OUTPUT_ENABLE, IOMUX_PIN_PULLUP, IOMUX_PIN_PULLDOWN, etc. Any other flags will be cleared.
- *
- * Equivalent to a direct register operation if gpio_number is known at compile time.
- * ie the following are equivalent:
- *
- * iomux_set_gpio_function(12, IOMUX_PIN_OUTPUT_ENABLE);
- * IOMUX_GPIO12 = IOMUX_GPIO12_FUNC_GPIO | IOMUX_PIN_OUTPUT_ENABLE;
+ * Sets the function and direction, but leaves the pullup configuration the
+ * same as before.
  */
-inline static void iomux_set_gpio_function(const uint8_t gpio_number, const uint32_t flags)
+inline static void iomux_set_gpio_function(uint8_t gpio_number, bool output_enable)
 {
-    const uint8_t reg_idx = gpio_to_iomux(gpio_number);
-    const uint32_t func = (reg_idx > 11 ? IOMUX_FUNC(0) : IOMUX_FUNC(3)) | flags;
-    IOMUX.PIN[reg_idx] = func | flags;
+    const uint8_t iomux_num = gpio_to_iomux(gpio_number);
+    const uint32_t func = iomux_num > 11 ? 0 : 3;
+    iomux_set_function(iomux_num, func);
+    iomux_set_direction_flags(iomux_num, output_enable ? IOMUX_PIN_OUTPUT_ENABLE : 0);
 }
 
 #ifdef	__cplusplus
diff --git a/core/include/esp/types.h b/core/include/esp/types.h
index 3f0560a..cb816da 100644
--- a/core/include/esp/types.h
+++ b/core/include/esp/types.h
@@ -2,6 +2,7 @@
 #define _ESP_TYPES_H
 
 #include <stdint.h>
+#include <stdbool.h>
 
 typedef volatile uint32_t *esp_reg_t;
 
diff --git a/examples/access_point/Makefile b/examples/access_point/Makefile
new file mode 100644
index 0000000..5a6bc6b
--- /dev/null
+++ b/examples/access_point/Makefile
@@ -0,0 +1,5 @@
+# Makefile for access_point example
+PROGRAM=access_point
+EXTRA_COMPONENTS=extras/dhcpserver
+
+include ../../common.mk
diff --git a/examples/access_point/access_point.c b/examples/access_point/access_point.c
new file mode 100644
index 0000000..5a41156
--- /dev/null
+++ b/examples/access_point/access_point.c
@@ -0,0 +1,97 @@
+/* Very basic example showing usage of access point mode and the DHCP server.
+
+   The ESP in the example runs a telnet server on 172.16.0.1 (port 23) that
+   outputs some status information if you connect to it, then closes
+   the connection.
+
+   This example code is in the public domain.
+*/
+#include <string.h>
+
+#include <espressif/esp_common.h>
+#include <esp/uart.h>
+#include <FreeRTOS.h>
+#include <task.h>
+#include <queue.h>
+#include <dhcpserver.h>
+
+#include <lwip/api.h>
+
+#define AP_SSID "esp-open-rtos AP"
+#define AP_PSK "esp-open-rtos"
+
+#define TELNET_PORT 23
+
+static void telnetTask(void *pvParameters);
+
+void user_init(void)
+{
+    uart_set_baud(0, 115200);
+    printf("SDK version:%s\n", sdk_system_get_sdk_version());
+
+    sdk_wifi_set_opmode(SOFTAP_MODE);
+    struct ip_info ap_ip;
+    IP4_ADDR(&ap_ip.ip, 172, 16, 0, 1);
+    IP4_ADDR(&ap_ip.gw, 0, 0, 0, 0);
+    IP4_ADDR(&ap_ip.netmask, 255, 255, 0, 0);
+    sdk_wifi_set_ip_info(1, &ap_ip);
+
+    struct sdk_softap_config ap_config = {
+        .ssid = AP_SSID,
+        .ssid_hidden = 0,
+        .channel = 3,
+        .ssid_len = strlen(AP_SSID),
+        .authmode = AUTH_WPA_WPA2_PSK,
+        .password = AP_PSK,
+        .max_connection = 3,
+        .beacon_interval = 100,
+    };
+    sdk_wifi_softap_set_config(&ap_config);
+
+    ip_addr_t first_client_ip;
+    IP4_ADDR(&first_client_ip, 172, 16, 0, 2);
+    dhcpserver_start(&first_client_ip, 4);
+
+    xTaskCreate(telnetTask, (signed char *)"telnetTask", 512, NULL, 2, NULL);
+}
+
+/* Telnet task listens on port 23, returns some status information and then closes
+   the connection if you connect to it.
+*/
+static void telnetTask(void *pvParameters)
+{
+  struct netconn *nc = netconn_new (NETCONN_TCP);
+  if(!nc) {
+    printf("Status monitor: Failed to allocate socket.\r\n");
+    return;
+  }
+  netconn_bind(nc, IP_ADDR_ANY, TELNET_PORT);
+  netconn_listen(nc);
+
+  while(1) {
+    struct netconn *client = NULL;
+    err_t err = netconn_accept(nc, &client);
+
+    if ( err != ERR_OK ) {
+      if(client)
+	netconn_delete(client);
+      continue;
+    }
+
+    ip_addr_t client_addr;
+    uint16_t port_ignore;
+    netconn_peer(client, &client_addr, &port_ignore);
+
+    char buf[80];
+    snprintf(buf, sizeof(buf), "Uptime %d seconds\r\n",
+	     xTaskGetTickCount()*portTICK_RATE_MS/1000);
+    netconn_write(client, buf, strlen(buf), NETCONN_COPY);
+    snprintf(buf, sizeof(buf), "Free heap %d bytes\r\n", (int)xPortGetFreeHeapSize());
+    netconn_write(client, buf, strlen(buf), NETCONN_COPY);
+    snprintf(buf, sizeof(buf), "Your address is %d.%d.%d.%d\r\n\r\n",
+             ip4_addr1(&client_addr), ip4_addr2(&client_addr),
+             ip4_addr3(&client_addr), ip4_addr4(&client_addr));
+    netconn_write(client, buf, strlen(buf), NETCONN_COPY);
+    netconn_delete(client);
+  }
+}
diff --git a/extras/dhcpserver/component.mk b/extras/dhcpserver/component.mk
new file mode 100644
index 0000000..07da648
--- /dev/null
+++ b/extras/dhcpserver/component.mk
@@ -0,0 +1,9 @@
+# Component makefile for extras/dhcpserver
+
+INC_DIRS += $(dhcpserver_ROOT)include
+
+# args for passing into compile rule generation
+dhcpserver_INC_DIR =  $(dhcpserver_ROOT)
+dhcpserver_SRC_DIR =  $(dhcpserver_ROOT)
+
+$(eval $(call component_compile_rules,dhcpserver))
diff --git a/extras/dhcpserver/dhcpserver.c b/extras/dhcpserver/dhcpserver.c
new file mode 100644
index 0000000..269b297
--- /dev/null
+++ b/extras/dhcpserver/dhcpserver.c
@@ -0,0 +1,370 @@
+/* Very basic LWIP & FreeRTOS-based DHCP server
+ *
+ * Based on RFC2131 http://www.ietf.org/rfc/rfc2131.txt
+ * ... although not fully RFC compliant yet.
+ *
+ * TODO
+ * * Allow binding on a single interface only (for mixed AP/client mode), lwip seems to make it hard to
+ *   listen for or send broadcasts on a specific interface only.
+ *
+ * * Probably allocates more memory than it should, it should be possible to reuse netbufs in most cases.
+ *
+ * Part of esp-open-rtos
+ * Copyright (C) 2015 Superhouse Automation Pty Ltd
+ * BSD Licensed as described in the file LICENSE
+ */
+#include <string.h>
+
+#include <FreeRTOS.h>
+#include <task.h>
+#include <lwip/netif.h>
+#include <lwip/api.h>
+
+/* Grow the size of the lwip dhcp_msg struct's options field, as LWIP
+   defaults to a 68 octet options field for its DHCP client, and most
+   full-sized clients send us more than this. */
+#define DHCP_OPTIONS_LEN 312
+
+#include <lwip/dhcp.h>
+
+_Static_assert(sizeof(struct dhcp_msg) == offsetof(struct dhcp_msg, options) + 312, "dhcp_msg_t should have extended options size");
+
+#include <lwip/netbuf.h>
+
+#include "dhcpserver.h"
+
+typedef struct {
+    uint8_t hwaddr[NETIF_MAX_HWADDR_LEN];
+    uint32_t expires;
+} dhcp_lease_t;
+
+typedef struct {
+    struct netconn *nc;
+    uint8_t max_leases;
+    ip_addr_t first_client_addr;
+    struct netif *server_if;
+    dhcp_lease_t *leases; /* length max_leases */
+} server_state_t;
+
+/* Only one DHCP server task can run at once, so we have global state
+   for it.
+*/
+static xTaskHandle dhcpserver_task_handle;
+static server_state_t *state;
+
+/* Handlers for various kinds of incoming DHCP messages */
+static void handle_dhcp_discover(struct dhcp_msg *received);
+static void handle_dhcp_request(struct dhcp_msg *dhcpmsg);
+static void handle_dhcp_release(struct dhcp_msg *dhcpmsg);
+
+static void send_dhcp_nak(struct dhcp_msg *dhcpmsg);
+
+static void dhcpserver_task(void *pxParameter);
+
+/* Utility functions */
+static uint8_t *find_dhcp_option(struct dhcp_msg *msg, uint8_t option_num, uint8_t min_length, uint8_t *length);
+static uint8_t *add_dhcp_option_byte(uint8_t *opt, uint8_t type, uint8_t value);
+static uint8_t *add_dhcp_option_bytes(uint8_t *opt, uint8_t type, void *value, uint8_t len);
+static dhcp_lease_t *find_lease_slot(uint8_t *hwaddr);
+
+/* Copy IP address as dotted decimal to 'dest', must be at least 16 bytes long */
+inline static void sprintf_ipaddr(const ip_addr_t *addr, char *dest)
+{
+    if(addr == NULL)
+        sprintf(dest, "NULL");
+    else
+        sprintf(dest, "%d.%d.%d.%d", ip4_addr1(addr),
+                ip4_addr2(addr), ip4_addr3(addr), ip4_addr4(addr));
+}
+
+void dhcpserver_start(const ip_addr_t *first_client_addr, uint8_t max_leases)
+{
+    /* Stop any existing running dhcpserver */
+    if(dhcpserver_task_handle)
+        dhcpserver_stop();
+
+    state = malloc(sizeof(server_state_t));
+    state->max_leases = max_leases;
+    state->leases = calloc(max_leases, sizeof(dhcp_lease_t));
+    // state->server_if is assigned once the task is running - see comment in dhcpserver_task()
+    ip_addr_copy(state->first_client_addr, *first_client_addr);
+
+    xTaskCreate(dhcpserver_task, (signed char *)"DHCPServer", 768, NULL, 8, &dhcpserver_task_handle);
+}
+
+void dhcpserver_stop(void)
+{
+    if(dhcpserver_task_handle) {
+        vTaskDelete(dhcpserver_task_handle);
+        free(state);
+        dhcpserver_task_handle = NULL;
+    }
+}
+
+static void dhcpserver_task(void *pxParameter)
+{
+    /* netif_list isn't assigned until after user_init completes, which is why we do it inside the task */
+    state->server_if = netif_list; /* TODO: Make this configurable */
+
+    state->nc = netconn_new (NETCONN_UDP);
+    if(!state->nc) {
+        printf("OTA TFTP: Failed to allocate socket.\r\n");
+        return;
+    }
+
+    netconn_bind(state->nc, IP_ADDR_ANY, DHCP_SERVER_PORT);
+
+    while(1)
+    {
+        struct netbuf *netbuf;
+        struct dhcp_msg received = { 0 };
+
+        /* Receive a DHCP packet */
+        err_t err = netconn_recv(state->nc, &netbuf);
+        if(err != ERR_OK) {
+            printf("DHCP Server Error: Failed to receive DHCP packet. err=%d\r\n", err);
+            continue;
+        }
+
+        /* expire any leases that have passed */
+        uint32_t now = xTaskGetTickCount();
+        for(int i = 0; i < state->max_leases; i++) {
+            uint32_t expires = state->leases[i].expires;
+            if(expires && expires < now)
+                state->leases[i].expires = 0;
+        }
+
+        ip_addr_t received_ip;
+        u16_t port;
+        netconn_addr(state->nc, &received_ip, &port);
+
+        if(netbuf_len(netbuf) < offsetof(struct dhcp_msg, options)) {
+            /* too short to be a valid DHCP client message */
+            netbuf_delete(netbuf);
+            continue;
+        }
+        if(netbuf_len(netbuf) >= sizeof(struct dhcp_msg)) {
+           printf("DHCP Server Warning: Client sent more options than we know how to parse. len=%d\r\n", netbuf_len(netbuf));
+        }
+
+        netbuf_copy(netbuf, &received, sizeof(struct dhcp_msg));
+        netbuf_delete(netbuf);
+
+        uint8_t *message_type = find_dhcp_option(&received, DHCP_OPTION_MESSAGE_TYPE,
+                                                 DHCP_OPTION_MESSAGE_TYPE_LEN, NULL);
+        if(!message_type) {
+            printf("DHCP Server Error: No message type field found");
+            continue;
+        }
+
+
+        printf("State dump. Message type %d\n", *message_type);
+        for(int i = 0; i < state->max_leases; i++) {
+            dhcp_lease_t *lease = &state->leases[i];
+            printf("lease slot %d expiry %d hwaddr %02x:%02x:%02x:%02x:%02x:%02x\r\n", i, lease->expires, lease->hwaddr[0],
+                   lease->hwaddr[1], lease->hwaddr[2], lease->hwaddr[3], lease->hwaddr[4],
+                   lease->hwaddr[5]);
+        }
+
+        switch(*message_type) {
+        case DHCP_DISCOVER:
+            handle_dhcp_discover(&received);
+            break;
+        case DHCP_REQUEST:
+            handle_dhcp_request(&received);
+            break;
+        case DHCP_RELEASE:
+            handle_dhcp_release(&received);
+        default:
+            printf("DHCP Server Error: Unsupported message type %d\r\n", *message_type);
+            break;
+        }
+    }
+}
+
+static void handle_dhcp_discover(struct dhcp_msg *dhcpmsg)
+{
+    if(dhcpmsg->htype != DHCP_HTYPE_ETH)
+        return;
+    if(dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN)
+        return;
+
+    dhcp_lease_t *freelease = find_lease_slot(dhcpmsg->chaddr);
+    if(!freelease) {
+        printf("DHCP Server: All leases taken.\r\n");
+        return; /* Nothing available, so do nothing */
+    }
+
+    /* Reuse the DISCOVER buffer for the OFFER response */
+    dhcpmsg->op = DHCP_BOOTREPLY;
+    bzero(dhcpmsg->options, DHCP_OPTIONS_LEN);
+
+    ip_addr_copy(dhcpmsg->yiaddr, state->first_client_addr);
+    ip4_addr4(&(dhcpmsg->yiaddr)) += (freelease - state->leases);
+
+    uint8_t *opt = (uint8_t *)&dhcpmsg->options;
+    opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_OFFER);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SUBNET_MASK, &state->server_if->netmask, 4);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0);
+
+    struct netbuf *netbuf = netbuf_new();
+    netbuf_alloc(netbuf, sizeof(struct dhcp_msg));
+    netbuf_take(netbuf, dhcpmsg, sizeof(struct dhcp_msg));
+    netconn_sendto(state->nc, netbuf, IP_ADDR_BROADCAST, 68);
+    netbuf_delete(netbuf);
+}
+
+static void handle_dhcp_request(struct dhcp_msg *dhcpmsg)
+{
+    static char ipbuf[16];
+    if(dhcpmsg->htype != DHCP_HTYPE_ETH)
+        return;
+    if(dhcpmsg->hlen > NETIF_MAX_HWADDR_LEN)
+        return;
+
+    ip_addr_t requested_ip;
+    uint8_t *requested_ip_opt = find_dhcp_option(dhcpmsg, DHCP_OPTION_REQUESTED_IP, 4, NULL);
+    if(requested_ip_opt) {
+            memcpy(&requested_ip.addr, requested_ip_opt, 4);
+    } else if(ip_addr_cmp(&requested_ip, IP_ADDR_ANY)) {
+        ip_addr_copy(requested_ip, dhcpmsg->ciaddr);
+    } else {
+        printf("DHCP Server Error: No requested IP\r\n");
+        send_dhcp_nak(dhcpmsg);
+        return;
+    }
+
+    /* Test the first 4 octets match */
+    if(ip4_addr1(&requested_ip) != ip4_addr1(&state->first_client_addr)
+       || ip4_addr2(&requested_ip) != ip4_addr2(&state->first_client_addr)
+       || ip4_addr3(&requested_ip) != ip4_addr3(&state->first_client_addr)) {
+        sprintf_ipaddr(&requested_ip, ipbuf);
+        printf("DHCP Server Error: %s not an allowed IP\r\n", ipbuf);
+        send_dhcp_nak(dhcpmsg);
+        return;
+    }
+    /* Test the last octet is in the MAXCLIENTS range */
+    int16_t octet_offs = ip4_addr4(&requested_ip) - ip4_addr4(&state->first_client_addr);
+    if(octet_offs < 0 || octet_offs >= state->max_leases) {
+        printf("DHCP Server Error: Address out of range\r\n");
+        send_dhcp_nak(dhcpmsg);
+        return;
+    }
+
+    dhcp_lease_t *requested_lease = state->leases + octet_offs;
+    if(requested_lease->expires != 0 && memcmp(requested_lease->hwaddr, dhcpmsg->chaddr,dhcpmsg->hlen))
+    {
+        printf("DHCP Server Error: Lease for address already taken\r\n");
+        send_dhcp_nak(dhcpmsg);
+        return;
+    }
+
+    memcpy(requested_lease->hwaddr, dhcpmsg->chaddr, dhcpmsg->hlen);
+    sprintf_ipaddr(&requested_ip, ipbuf);
+    printf("DHCP lease addr %s assigned to MAC %02x:%02x:%02x:%02x:%02x:%02x\r\n", ipbuf, requested_lease->hwaddr[0],
+           requested_lease->hwaddr[1], requested_lease->hwaddr[2], requested_lease->hwaddr[3], requested_lease->hwaddr[4],
+           requested_lease->hwaddr[5]);
+    requested_lease->expires = DHCPSERVER_LEASE_TIME * configTICK_RATE_HZ;
+
+    /* Reuse the REQUEST message as the ACK message */
+    dhcpmsg->op = DHCP_BOOTREPLY;
+    bzero(dhcpmsg->options, DHCP_OPTIONS_LEN);
+
+    ip_addr_copy(dhcpmsg->yiaddr, requested_ip);
+
+    uint8_t *opt = (uint8_t *)&dhcpmsg->options;
+    opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_ACK);
+    uint32_t expiry = htonl(DHCPSERVER_LEASE_TIME);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_LEASE_TIME, &expiry, 4);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SUBNET_MASK, &state->server_if->netmask, 4);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0);
+
+    struct netbuf *netbuf = netbuf_new();
+    netbuf_alloc(netbuf, sizeof(struct dhcp_msg));
+    netbuf_take(netbuf, dhcpmsg, sizeof(struct dhcp_msg));
+    netconn_sendto(state->nc, netbuf, IP_ADDR_BROADCAST, 68);
+    netbuf_delete(netbuf);
+}
+
+static void handle_dhcp_release(struct dhcp_msg *dhcpmsg)
+{
+    dhcp_lease_t *lease = find_lease_slot(dhcpmsg->chaddr);
+    if(lease) {
+        lease->expires = 0;
+    }
+}
+
+static void send_dhcp_nak(struct dhcp_msg *dhcpmsg)
+{
+    /* Reuse 'dhcpmsg' for the NAK */
+    dhcpmsg->op = DHCP_BOOTREPLY;
+    bzero(dhcpmsg->options, DHCP_OPTIONS_LEN);
+
+    uint8_t *opt = (uint8_t *)&dhcpmsg->options;
+    opt = add_dhcp_option_byte(opt, DHCP_OPTION_MESSAGE_TYPE, DHCP_NAK);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_SERVER_ID, &state->server_if->ip_addr, 4);
+    opt = add_dhcp_option_bytes(opt, DHCP_OPTION_END, NULL, 0);
+
+    struct netbuf *netbuf = netbuf_new();
+    netbuf_alloc(netbuf, sizeof(struct dhcp_msg));
+    netbuf_take(netbuf, dhcpmsg, sizeof(struct dhcp_msg));
+    netconn_sendto(state->nc, netbuf, IP_ADDR_BROADCAST, 68);
+    netbuf_delete(netbuf);
+}
+
+static uint8_t *find_dhcp_option(struct dhcp_msg *msg, uint8_t option_num, uint8_t min_length, uint8_t *length)
+{
+    uint8_t *start = (uint8_t *)&msg->options;
+    uint8_t *msg_end = (uint8_t *)msg + sizeof(struct dhcp_msg);
+
+    for(uint8_t *p = start; p < msg_end-2;) {
+        uint8_t type = *p++;
+        uint8_t len = *p++;
+        if(type == DHCP_OPTION_END)
+            return NULL;
+        if(p+len >= msg_end)
+            break; /* We've overrun our valid DHCP message size, or this isn't a valid option */
+        if(type == option_num) {
+            if(len < min_length)
+                break;
+            if(length)
+                *length = len;
+            return p; /* start of actual option data */
+        }
+        p += len;
+    }
+    return NULL; /* Not found */
+}
+
+static uint8_t *add_dhcp_option_byte(uint8_t *opt, uint8_t type, uint8_t value)
+{
+    *opt++ = type;
+    *opt++ = 1;
+    *opt++ = value;
+    return opt;
+}
+
+static uint8_t *add_dhcp_option_bytes(uint8_t *opt, uint8_t type, void *value, uint8_t len)
+{
+    *opt++ = type;
+    if(len) {
+        *opt++ = len;
+        memcpy(opt, value, len);
+    }
+    return opt+len;
+}
+
+/* Find a free DHCP lease, or a lease already assigned to 'hwaddr' */
+static dhcp_lease_t *find_lease_slot(uint8_t *hwaddr)
+{
+    dhcp_lease_t *empty_lease = NULL;
+    for(int i = 0; i < state->max_leases; i++) {
+        if(state->leases[i].expires == 0 && !empty_lease)
+            empty_lease = &state->leases[i];
+        else if (memcmp(hwaddr, state->leases[i].hwaddr, 6) == 0)
+            return &state->leases[i];
+    }
+    return empty_lease;
+}
diff --git a/extras/dhcpserver/include/dhcpserver.h b/extras/dhcpserver/include/dhcpserver.h
new file mode 100644
index 0000000..5548c19
--- /dev/null
+++ b/extras/dhcpserver/include/dhcpserver.h
@@ -0,0 +1,33 @@
+/* Very basic LWIP & FreeRTOS-based DHCP server
+ *
+ * Header file contains default configuration for the DHCP server.
+ *
+ *
+ * Part of esp-open-rtos
+ * Copyright (C) 2015 Superhouse Automation Pty Ltd
+ * BSD Licensed as described in the file LICENSE
+ */
+#ifndef _DHCPSERVER_H
+#define _DHCPSERVER_H
+
+#ifndef DHCPSERVER_LEASE_TIME
+#define DHCPSERVER_LEASE_TIME 3600
+#endif
+
+/* Start DHCP server.
+
+   Static IP of server should already be set and network interface enabled.
+
+   first_client_addr is the IP address of the first lease to be handed
+   to a client.  Subsequent lease addresses are calculated by
+   incrementing the final octet of the IPv4 address, up to max_leases.
+*/
+void dhcpserver_start(const ip_addr_t *first_client_addr, uint8_t max_leases);
+
+void dhcpserver_get_lease(const ip_addr_t *first_client_addr, uint8_t max_leases);
+
+/* Stop DHCP server.
+ */
+void dhcpserver_stop(void);
+
+#endif
diff --git a/libc/xtensa-lx106-elf/include/config.h b/libc/xtensa-lx106-elf/include/config.h
new file mode 100644
index 0000000..6159c6f
--- /dev/null
+++ b/libc/xtensa-lx106-elf/include/config.h
@@ -0,0 +1,43 @@
+#ifndef __SYS_CONFIG_H__
+#define __SYS_CONFIG_H__
+
+#include <machine/ieeefp.h>  /* floating point macros */
+#include <sys/features.h>	/* POSIX defs */
+
+/*************************************************************************
+ esp8266-specific xtensa stuff
+ *************************************************************************/
+
+#ifndef _REENT_SMALL
+#define _REENT_SMALL
+#endif
+
+/* esp8266 hardware FIFO buffers are 128 bytes */
+#define __BUFSIZ__ 128
+
+/*************************************************************************
+ end of esp8266-specific stuff
+ *************************************************************************/
+
+#ifndef __EXPORT
+#define __EXPORT
+#endif
+
+#ifndef __IMPORT
+#define __IMPORT
+#endif
+
+/* Define return type of read/write routines.  In POSIX, the return type
+   for read()/write() is "ssize_t" but legacy newlib code has been using
+   "int" for some time.  If not specified, "int" is defaulted.  */
+#ifndef _READ_WRITE_RETURN_TYPE
+#define _READ_WRITE_RETURN_TYPE int
+#endif
+/* Define `count' parameter of read/write routines.  In POSIX, the `count'
+   parameter is "size_t" but legacy newlib code has been using "int" for some
+   time.  If not specified, "int" is defaulted.  */
+#ifndef _READ_WRITE_BUFSIZE_TYPE
+#define _READ_WRITE_BUFSIZE_TYPE int
+#endif
+
+#endif /* __SYS_CONFIG_H__ */
diff --git a/libc/xtensa-lx106-elf/lib/libc.a b/libc/xtensa-lx106-elf/lib/libc.a
index a166b9e..6a256cd 100644
Binary files a/libc/xtensa-lx106-elf/lib/libc.a and b/libc/xtensa-lx106-elf/lib/libc.a differ
diff --git a/libc/xtensa-lx106-elf/lib/libg.a b/libc/xtensa-lx106-elf/lib/libg.a
index a166b9e..6a256cd 100644
Binary files a/libc/xtensa-lx106-elf/lib/libg.a and b/libc/xtensa-lx106-elf/lib/libg.a differ
diff --git a/libc/xtensa-lx106-elf/lib/libm.a b/libc/xtensa-lx106-elf/lib/libm.a
index be41436..7d96963 100644
Binary files a/libc/xtensa-lx106-elf/lib/libm.a and b/libc/xtensa-lx106-elf/lib/libm.a differ