[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/bsp/lk/lib/minip/arp.c b/src/bsp/lk/lib/minip/arp.c
new file mode 100644
index 0000000..26c61be
--- /dev/null
+++ b/src/bsp/lk/lib/minip/arp.c
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <list.h>
+#include <string.h>
+#include <malloc.h>
+#include <stdio.h>
+#include <kernel/thread.h>
+#include <kernel/mutex.h>
+#include <trace.h>
+
+typedef union {
+    uint32_t u;
+    uint8_t b[4];
+} ipv4_t;
+
+#define LOCAL_TRACE 0
+static struct list_node arp_list;
+typedef struct {
+    struct list_node node;
+    uint32_t addr;
+    uint8_t mac[6];
+} arp_entry_t;
+
+static mutex_t arp_mutex = MUTEX_INITIAL_VALUE(arp_mutex);
+
+void arp_cache_init(void)
+{
+    list_initialize(&arp_list);
+}
+
+static inline void mru_update(struct list_node *entry)
+{
+    if (arp_list.next == entry)
+        return;
+
+    list_delete(entry);
+    list_add_head(&arp_list, entry);
+}
+
+void arp_cache_update(uint32_t addr, const uint8_t mac[6])
+{
+    arp_entry_t *arp;
+    ipv4_t ip;
+    bool found = false;
+
+    ip.u = addr;
+
+    // Ignore 0.0.0.0 or x.x.x.255
+    if (ip.u == 0 || ip.b[3] == 0xFF) {
+        return;
+    }
+
+    /* If the entry is in the cache update the address and move
+     * it to head */
+    mutex_acquire(&arp_mutex);
+    list_for_every_entry(&arp_list, arp, arp_entry_t, node) {
+        if (arp->addr == addr) {
+            arp->addr = addr;
+            mru_update(&arp->node);
+            found = true;
+            break;
+        }
+    }
+
+    if (!found) {
+        LTRACEF("Adding %u.%u.%u.%u -> %02x:%02x:%02x:%02x:%02x:%02x to cache\n",
+            ip.b[0], ip.b[1], ip.b[2], ip.b[3],
+            mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+        arp = malloc(sizeof(arp_entry_t));
+        if (arp == NULL) {
+            goto err;
+        }
+
+        arp->addr = addr;
+        memcpy(arp->mac, mac, sizeof(arp->mac));
+        list_add_head(&arp_list, &arp->node);
+    }
+
+err:
+    mutex_release(&arp_mutex);
+    return;
+}
+
+/* Looks up and returns a MAC address based on the provided ip addr */
+uint8_t *arp_cache_lookup(uint32_t addr)
+{
+    arp_entry_t *arp = NULL;
+    uint8_t *ret = NULL;
+
+    /* If the entry is in the cache update the address and move
+     * it to head */
+    mutex_acquire(&arp_mutex);
+    list_for_every_entry(&arp_list, arp, arp_entry_t, node) {
+        if (arp->addr == addr) {
+            mru_update(&arp->node);
+            ret = arp->mac;
+            break;
+        }
+    }
+    mutex_release(&arp_mutex);
+
+    return ret;
+}
+
+void arp_cache_dump(void)
+{
+    int i = 0;
+    arp_entry_t *arp;
+
+    if (!list_is_empty(&arp_list)) {
+            list_for_every_entry(&arp_list, arp, arp_entry_t, node) {
+            ipv4_t ip;
+            ip.u = arp->addr;
+            printf("%2d: %u.%u.%u.%u -> %02x:%02x:%02x:%02x:%02x:%02x\n",
+                i++, ip.b[0], ip.b[1], ip.b[2], ip.b[3],
+                arp->mac[0], arp->mac[1], arp->mac[2], arp->mac[3], arp->mac[4], arp->mac[5]);
+            }
+    } else {
+        printf("The arp table is empty\n");
+    }
+}
+
+int arp_send_request(uint32_t addr)
+{
+    pktbuf_t *p;
+    struct eth_hdr *eth;
+    struct arp_pkt *arp;
+
+    if ((p = pktbuf_alloc()) == NULL) {
+        return -1;
+    }
+
+    eth = pktbuf_prepend(p, sizeof(struct eth_hdr));
+    arp = pktbuf_append(p, sizeof(struct arp_pkt));
+    minip_build_mac_hdr(eth, bcast_mac, ETH_TYPE_ARP);
+
+    arp->htype = htons(0x0001);
+    arp->ptype = htons(0x0800);
+    arp->hlen = 6;
+    arp->plen = 4;
+    arp->oper = htons(ARP_OPER_REQUEST);
+    arp->spa = minip_get_ipaddr();
+    arp->tpa = addr;
+    minip_get_macaddr(arp->sha);
+    mac_addr_copy(arp->tha, bcast_mac);
+
+    minip_tx_handler(p);
+    return 0;
+}
+
+static void handle_arp_timeout_cb(void *arg) {
+    *(bool *)arg = true;
+}
+
+const uint8_t *arp_get_dest_mac(uint32_t host)
+{
+    const uint8_t *dst_mac = NULL;
+    bool arp_timeout = false;
+    net_timer_t arp_timeout_timer;
+
+    if (host == IPV4_BCAST) {
+        return bcast_mac;
+    }
+
+    dst_mac = arp_cache_lookup(host);
+    if (dst_mac == NULL) {
+        arp_send_request(host);
+        memset(&arp_timeout_timer, 0, sizeof(arp_timeout_timer));
+        net_timer_set(&arp_timeout_timer, handle_arp_timeout_cb, &arp_timeout, 100);
+        while (!arp_timeout) {
+            dst_mac = arp_cache_lookup(host);
+            if (dst_mac) {
+                net_timer_cancel(&arp_timeout_timer);
+                break;
+            }
+        }
+    }
+
+    return dst_mac;
+}
+
+// vim: set ts=4 sw=4 expandtab:
+
diff --git a/src/bsp/lk/lib/minip/chksum.c b/src/bsp/lk/lib/minip/chksum.c
new file mode 100644
index 0000000..68f4dcb
--- /dev/null
+++ b/src/bsp/lk/lib/minip/chksum.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+/* XXX alternate implementation, merge */
+uint16_t ones_sum16(uint32_t sum, const void *_buf, int len)
+{
+    const uint16_t *buf = _buf;
+
+    while (len >= 2) {
+        sum += *buf++;
+        if(sum & 0x80000000)
+            sum = (sum & 0xffff) + (sum >> 16);
+        len -= 2;
+    }
+
+    if (len) {
+        uint16_t temp = htons((*(uint8_t *)buf) << 8);
+        sum += temp;
+    }
+
+    while (sum >> 16)
+        sum = (sum & 0xffff) + (sum >> 16);
+
+    return sum;
+}
+
+uint16_t rfc1701_chksum(const uint8_t *buf, size_t len)
+{
+    uint32_t total = 0;
+    uint16_t chksum = 0;
+    const uint16_t *p = (const uint16_t *) buf;
+
+    // Length is in bytes
+    for (size_t i = 0; i < len / 2; i++ ) {
+        total += p[i];
+    }
+
+    chksum = (total & 0xFFFF) + (total >> 16);
+    chksum = ~chksum;
+
+    return chksum;
+}
+
+#if MINIP_USE_UDP_CHECKSUM
+uint16_t rfc768_chksum(struct ipv4_hdr *ipv4, struct udp_hdr *udp)
+{
+    uint32_t total = 0;
+    uint16_t chksum = 0;
+    size_t len = ntohs(udp->len);
+    uint16_t *p;
+
+    p = (uint16_t *)ipv4->src_addr;
+    total += htons(p[0]);
+    total += htons(p[1]);
+
+    p = (uint16_t *)ipv4->dst_addr;
+    total += htons(p[0]);
+    total += htons(p[1]);
+
+    p = (const uint16_t *)udp->data;
+    for (size_t i = 0; i < len / 2; i++ ) {
+        total += p[i];
+    }
+
+    total += IP_PROTO_UDP;
+    total += udp->len;
+    total += udp->src_port;
+    total += udp->dst_port;
+    total += ipv4->len;
+
+    chksum = (total & 0xFFFF) + (total >> 16);
+    chksum = ~chksum;
+
+    return chksum;
+}
+#endif
+
+// vim: set ts=4 sw=4 expandtab:
diff --git a/src/bsp/lk/lib/minip/dhcp.c b/src/bsp/lk/lib/minip/dhcp.c
new file mode 100644
index 0000000..170691d
--- /dev/null
+++ b/src/bsp/lk/lib/minip/dhcp.c
@@ -0,0 +1,296 @@
+/*
+ * Copyright (c) 2014 Brian Swetland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <err.h>
+#include <platform.h>
+#include <stdio.h>
+#include <debug.h>
+#include <malloc.h>
+
+#include <kernel/thread.h>
+#include <sys/types.h>
+#include <endian.h>
+#include <string.h>
+
+#define TRACE_DHCP 0
+
+typedef struct dhcp_msg {
+	u8 opcode;
+	u8 hwtype;	// hw addr type
+	u8 hwalen;	// hw addr length
+	u8 hops;
+	u32 xid;	// txn id
+	u16 secs;	// seconds since dhcp process start
+	u16 flags;
+	u32 ciaddr;	// Client IP Address
+	u32 yiaddr;	// Your IP Address
+	u32 siaddr;	// Server IP Address
+	u32 giaddr;	// Gateway IP Address
+	u8 chaddr[16];	// Client HW Address
+	u8 sname[64];   // Server Hostname, AsciiZ
+	u8 file[128];	// Boot File Name, AsciiZ
+	u32 cookie;
+	u8 options[0];
+} dhcp_msg_t;
+
+udp_socket_t *dhcp_udp_handle;
+
+#define DHCP_FLAG_BROADCAST 0x8000
+
+#define DHCP_REQUEST	1
+#define DHCP_REPLY	2
+
+#define OP_DHCPDISCOVER	1	// Client: Broadcast to find Server
+#define OP_DHCPOFFER	2	// Server response to Discover
+#define OP_DHCPREQUEST	3	// Client accepts offer
+#define OP_DHCPDECLINE	4	// Client notifies address already in use
+#define OP_DHCPACK	5	// Server confirms accept
+#define OP_DHCPNAK	6	// Server disconfirms or lease expires
+#define OP_DHCPRELEASE	7	// Client releases address
+
+#define OPT_NET_MASK	1	// len 4, mask
+#define OPT_ROUTERS	3	// len 4n, gateway0, ...
+#define OPT_DNS		6	// len 4n, nameserver0, ...
+#define OPT_HOSTNAME	12
+#define OPT_REQUEST_IP	50	// len 4
+#define OPT_MSG_TYPE	53	// len 1, type same as op
+#define OPT_SERVER_ID	54	// len 4, server ident ipaddr
+#define OPT_DONE	255
+
+#define DHCP_CLIENT_PORT	68
+#define DHCP_SERVER_PORT	67
+
+#define HW_ETHERNET	1
+
+static u8 mac[6];
+
+static void printip(const char *name, u32 x) {
+	union {
+		u32 u;
+		u8 b[4];
+	} ip;
+	ip.u = x;
+	printf("%s %d.%d.%d.%d\n", name, ip.b[0], ip.b[1], ip.b[2], ip.b[3]);
+}
+
+static volatile int configured = 0;
+static int cfgstate = 0;
+
+static void dhcp_discover(u32 xid) {
+	struct {
+		dhcp_msg_t msg;
+		u8 opt[128];
+	} s;
+	u8 *opt = s.opt;
+	const char *hostname = minip_get_hostname();
+	memset(&s, 0, sizeof(s));
+	s.msg.opcode = DHCP_REQUEST;
+	s.msg.hwtype = HW_ETHERNET;
+	s.msg.hwalen = 6;
+	s.msg.xid = xid;
+	s.msg.cookie = 0x63538263;
+	minip_get_macaddr(s.msg.chaddr);
+
+	*opt++ = OPT_MSG_TYPE;
+	*opt++ = 1;
+	*opt++ = OP_DHCPDISCOVER;
+
+	if (hostname && hostname[0]) {
+		size_t len = strlen(hostname);
+		*opt++ = OPT_HOSTNAME;
+		*opt++ = len;
+		memcpy(opt, hostname, len);
+		opt += len;
+	}
+
+	*opt++ = OPT_DONE;
+
+	udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle);
+	status_t ret = udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle);
+	if (ret != NO_ERROR) {
+		printf("DHCP_DISCOVER failed: %d\n", ret);
+	}
+}
+
+static void dhcp_request(u32 xid, u32 server, u32 reqip) {
+	struct {
+		dhcp_msg_t msg;
+		u8 opt[128];
+	} s;
+	u8 *opt = s.opt;
+	const char *hostname = minip_get_hostname();
+	memset(&s, 0, sizeof(s));
+	s.msg.opcode = DHCP_REQUEST;
+	s.msg.hwtype = HW_ETHERNET;
+	s.msg.hwalen = 6;
+	s.msg.xid = xid;
+	s.msg.cookie = 0x63538263;
+	minip_get_macaddr(s.msg.chaddr);
+
+	*opt++ = OPT_MSG_TYPE;
+	*opt++ = 1;
+	*opt++ = OP_DHCPREQUEST;
+
+	*opt++ = OPT_SERVER_ID;
+	*opt++ = 4;
+	memcpy(opt, &server, 4);
+	opt += 4;
+
+	*opt++ = OPT_REQUEST_IP;
+	*opt++ = 4;
+	memcpy(opt, &reqip, 4);
+	opt += 4;
+
+	if (hostname && hostname[0]) {
+		size_t len = strlen(hostname);
+		*opt++ = OPT_HOSTNAME;
+		*opt++ = len;
+		memcpy(opt, hostname, len);
+		opt += len;
+	}
+
+	*opt++ = OPT_DONE;
+
+	status_t ret = udp_send(&s.msg, sizeof(dhcp_msg_t) + (opt - s.opt), dhcp_udp_handle);
+	if (ret != NO_ERROR) {
+		printf("DHCP_REQUEST failed: %d\n", ret);
+	}
+}
+
+static void dhcp_cb(void *data, size_t sz, uint32_t srcip, uint16_t srcport, void *arg) {
+	dhcp_msg_t *msg = data;
+	u8 *opt;
+	u32 netmask = 0;
+	u32 gateway = 0;
+	u32 dns = 0;
+	u32 server = 0;
+	int op = -1;
+
+	if (sz < sizeof(dhcp_msg_t)) return;
+
+	if (memcmp(msg->chaddr, mac, 6)) return;
+
+#if TRACE_DHCP
+	printf("dhcp op=%d len=%d from p=%d ip=", msg->opcode, sz, srcport);
+	printip("", srcip);
+#endif
+
+	if (configured) {
+		printf("already configured\n");
+		return;
+	}
+#if TRACE_DHCP
+	printip("ciaddr", msg->ciaddr);
+	printip("yiaddr", msg->yiaddr);
+	printip("siaddr", msg->siaddr);
+	printip("giaddr", msg->giaddr);
+	printf("chaddr %02x:%02x:%02x:%02x:%02x:%02x\n",
+		msg->chaddr[0], msg->chaddr[1], msg->chaddr[2],
+		msg->chaddr[3], msg->chaddr[4], msg->chaddr[5]);
+#endif
+	sz -= sizeof(dhcp_msg_t);
+	opt = msg->options;
+	while (sz >= 2) {
+		sz -= 2;
+		if (opt[1] > sz) {
+			break;
+		}
+#if TRACE_DHCP
+		printf("#%d (%d), ", opt[0], opt[1]);
+#endif
+		switch (opt[0]) {
+		case OPT_MSG_TYPE:
+			if (opt[1] == 1) op = opt[2];
+			break;	
+		case OPT_NET_MASK:
+			if (opt[1] == 4) memcpy(&netmask, opt + 2, 4);
+			break;
+		case OPT_ROUTERS:
+			if (opt[1] >= 4) memcpy(&gateway, opt + 2, 4);
+			break;
+		case OPT_DNS:
+			if (opt[1] >= 4) memcpy(&dns, opt + 2, 4);
+			break;
+		case OPT_SERVER_ID:
+			if (opt[1] == 4) memcpy(&server, opt + 2, 4);
+			break;
+		case OPT_DONE:
+			goto done;
+		}
+		opt += opt[1] + 2;
+		sz -= opt[1];
+	}
+done:
+#if TRACE_DHCP
+	printf("\n");
+	if (server) printip("server", server);
+	if (netmask) printip("netmask", netmask);
+	if (gateway) printip("gateway", gateway);
+	if (dns) printip("dns", dns);
+#endif
+	if (cfgstate == 0) {
+		if (op == OP_DHCPOFFER) {
+			printip("dhcp: offer:", msg->yiaddr);
+			if (server) {
+				dhcp_request(0xaabbccdd, server, msg->yiaddr);
+				cfgstate = 1;
+			}
+		}
+	} else if (cfgstate == 1) {
+		if (op == OP_DHCPACK) {
+			printip("dhcp: ack:", msg->yiaddr);
+			minip_set_ipaddr(msg->yiaddr);
+			configured = 1;
+		}
+	}
+}
+
+static int dhcp_thread(void *arg) {
+	for (;;) {
+		if (configured) break;
+		thread_sleep(500);
+		if (configured) break;
+		dhcp_discover(0xaabbccdd);
+	}
+	return 0;
+}
+
+static thread_t *dhcp_thr;
+
+void minip_init_dhcp(tx_func_t tx_func, void *tx_arg) {
+	minip_get_macaddr(mac);
+
+	minip_init(tx_func, tx_arg, IPV4_NONE, IPV4_NONE, IPV4_NONE);
+
+	int ret = udp_open(IPV4_BCAST, DHCP_CLIENT_PORT, DHCP_SERVER_PORT, &dhcp_udp_handle);
+	printf("dhcp opened udp: %d\n", ret);
+
+	udp_listen(DHCP_CLIENT_PORT, dhcp_cb, NULL);
+
+	dhcp_thr = thread_create("dhcp", dhcp_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE);
+	thread_detach_and_resume(dhcp_thr);
+}
+
+// vim: set noexpandtab:
diff --git a/src/bsp/lk/lib/minip/include/lib/minip.h b/src/bsp/lk/lib/minip/include/lib/minip.h
new file mode 100644
index 0000000..71cacca
--- /dev/null
+++ b/src/bsp/lk/lib/minip/include/lib/minip.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ * Copyright (c) 2014 Brian Swetland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#pragma once
+
+#include <endian.h>
+#include <list.h>
+#include <stdint.h>
+#include <sys/types.h>
+
+#include <lib/pktbuf.h>
+
+#define IPV4(a,b,c,d) (((a)&0xFF)|(((b)&0xFF)<<8)|(((c)&0xFF)<<16)|(((d)&0xFF)<<24))
+#define IPV4_SPLIT(a) (a & 0xFF), ((a >> 8) & 0xFF), ((a >> 16) & 0xFF), ((a >> 24) & 0xFF)
+#define IPV4_PACK(a) (a[3] << 24 | a[2] << 16 | a[1] << 8 | a[0])
+#define IPV4_BCAST (0xFFFFFFFF)
+#define IPV4_NONE (0)
+
+typedef int (*tx_func_t)(pktbuf_t *p);
+typedef void (*udp_callback_t)(void *data, size_t len,
+    uint32_t srcaddr, uint16_t srcport, void *arg);
+
+/* initialize minip with static configuration */
+void minip_init(tx_func_t tx_func, void *tx_arg,
+    uint32_t ip, uint32_t netmask, uint32_t gateway);
+
+/* initialize minip with DHCP configuration */
+void minip_init_dhcp(tx_func_t tx_func, void *tx_arg);
+
+/* packet rx hook to hand to ethernet driver */
+void minip_rx_driver_callback(pktbuf_t *p);
+
+/* global configuration state */
+void minip_get_macaddr(uint8_t *addr);
+void minip_set_macaddr(const uint8_t *addr);
+
+uint32_t minip_get_ipaddr(void);
+void minip_set_ipaddr(const uint32_t addr);
+
+void minip_set_hostname(const char *name);
+const char *minip_get_hostname(void);
+
+uint32_t minip_parse_ipaddr(const char *addr, size_t len);
+
+/* udp */
+typedef struct udp_socket udp_socket_t;
+
+int udp_listen(uint16_t port, udp_callback_t cb, void *arg);
+status_t udp_open(uint32_t host, uint16_t sport, uint16_t dport, udp_socket_t **handle);
+status_t udp_send(void *buf, size_t len, udp_socket_t *handle);
+status_t udp_close(udp_socket_t *handle);
+
+/* tcp */
+typedef struct tcp_socket tcp_socket_t;
+
+status_t tcp_open_listen(tcp_socket_t **handle, uint16_t port);
+status_t tcp_accept_timeout(tcp_socket_t *listen_socket, tcp_socket_t **accept_socket, lk_time_t timeout);
+status_t tcp_close(tcp_socket_t *socket);
+ssize_t tcp_read(tcp_socket_t *socket, void *buf, size_t len);
+ssize_t tcp_write(tcp_socket_t *socket, const void *buf, size_t len);
+
+static inline status_t tcp_accept(tcp_socket_t *listen_socket, tcp_socket_t **accept_socket)
+{
+    return tcp_accept_timeout(listen_socket, accept_socket, INFINITE_TIME);
+}
+
+/* utilities */
+void gen_random_mac_address(uint8_t *mac_addr);
+
+// vim: set ts=4 sw=4 expandtab:
diff --git a/src/bsp/lk/lib/minip/include/lib/pktbuf.h b/src/bsp/lk/lib/minip/include/lib/pktbuf.h
new file mode 100644
index 0000000..29e1551
--- /dev/null
+++ b/src/bsp/lk/lib/minip/include/lib/pktbuf.h
@@ -0,0 +1,126 @@
+/*
+ * Copyright (c) 2014 Brian Swetland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef _NET_PKTBUF_H_
+#define _NET_PKTBUF_H_
+
+#include <sys/types.h>
+#include <list.h>
+
+/* PAGE_SIZE minus 16 bytes of metadata in pktbuf_buf */
+#ifndef PKTBUF_POOL_SIZE
+#define PKTBUF_POOL_SIZE 256
+#endif
+
+#ifndef PKTBUF_SIZE
+#define	PKTBUF_SIZE		1536
+#endif
+
+/* How much space pktbuf_alloc should save for IP headers in the front of the buffer */
+#define PKTBUF_MAX_HDR  64
+/* The remaining space in the buffer */
+#define PKTBUF_MAX_DATA (PKTBUF_SIZE - PKTBUF_MAX_HDR)
+
+typedef void (*pktbuf_free_callback)(void *buf, void *arg);
+typedef struct pktbuf {
+	u8 *data;
+	u32 blen;
+	u32 dlen;
+	paddr_t phys_base;
+	struct list_node list;
+	u32 flags;
+	pktbuf_free_callback cb;
+	void *cb_args;
+	u8 *buffer;
+} pktbuf_t;
+
+typedef struct pktbuf_pool_object {
+	union {
+		pktbuf_t p;
+		uint8_t b[PKTBUF_SIZE];
+	};
+} pktbuf_pool_object_t;
+
+#define PKTBUF_FLAG_CKSUM_IP_GOOD  (1<<0)
+#define PKTBUF_FLAG_CKSUM_TCP_GOOD (1<<1)
+#define PKTBUF_FLAG_CKSUM_UDP_GOOD (1<<2)
+#define PKTBUF_FLAG_EOF			   (1<<3)
+#define PKTBUF_FLAG_CACHED		   (1<<4)
+
+/* Return the physical address offset of data in the packet */
+static inline u32 pktbuf_data_phys(pktbuf_t *p) {
+	return p->phys_base + (p->data - p->buffer);
+}
+
+// number of bytes available for _prepend
+static inline u32 pktbuf_avail_head(pktbuf_t *p) {
+	return p->data - p->buffer;
+}
+
+// number of bytes available for _append or _append_data
+static inline u32 pktbuf_avail_tail(pktbuf_t *p) {
+	return p->blen - (p->data - p->buffer) - p->dlen;
+}
+
+// allocate packet buffer from buffer pool
+pktbuf_t *pktbuf_alloc(void);
+pktbuf_t *pktbuf_alloc_empty(void);
+
+/* Add a buffer to an existing packet buffer */
+void pktbuf_add_buffer(pktbuf_t *p, u8 *buf, u32 len, uint32_t header_sz,
+		uint32_t flags, pktbuf_free_callback cb, void *cb_args);
+// return packet buffer to buffer pool
+// returns number of threads woken up
+int pktbuf_free(pktbuf_t *p, bool reschedule);
+
+// extend buffer by sz bytes, copied from data
+void pktbuf_append_data(pktbuf_t *p, const void *data, size_t sz);
+
+// extend buffer by sz bytes, returning a pointer to the
+// start of the newly appended region
+void *pktbuf_append(pktbuf_t *p, size_t sz);
+
+// grow the front of the buffer and return a pointer
+// to the new start of packet
+void *pktbuf_prepend(pktbuf_t *p, size_t sz);
+
+// shrink the buffer by discarding the first sz bytes
+// returning a pointer to the discarded bytes (which
+// will remain untouched until the next _prepend),
+// or NULL if there were not enough bytes to consume
+void *pktbuf_consume(pktbuf_t *p, size_t sz);
+
+// remove sz bytes from the end of the pktbuf
+void pktbuf_consume_tail(pktbuf_t *p, size_t sz);
+
+// create a new packet buffer from raw memory and add
+// it to the free pool
+void pktbuf_create(void *ptr, size_t size);
+
+// Create buffers for pktbufs of size PKTBUF_BUF_SIZE out of size
+void pktbuf_create_bufs(void *ptr, size_t size);
+
+void pktbuf_dump(pktbuf_t *p);
+#endif
+
+// vim: set noexpandtab:
diff --git a/src/bsp/lk/lib/minip/lk_console.c b/src/bsp/lk/lib/minip/lk_console.c
new file mode 100644
index 0000000..76ac156
--- /dev/null
+++ b/src/bsp/lk/lib/minip/lk_console.c
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <lib/console.h>
+#include <kernel/thread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <platform.h>
+#include <kernel/timer.h>
+#include <err.h>
+
+#if WITH_LIB_CONSOLE
+
+uint32_t str_ip_to_int(const char *s, size_t len)
+{
+    uint8_t ip[4] = { 0, 0, 0, 0 };
+    uint8_t pos = 0, i = 0;
+
+    while (pos < len) {
+        char c = s[pos];
+        if (c == '.') {
+            i++;
+        } else {
+            ip[i] *= 10;
+            ip[i] += c - '0';
+        }
+        pos++;
+    }
+
+    return IPV4_PACK(ip);
+}
+
+void arp_usage(void) {
+    printf("arp list                        print arp table\n");
+    printf("arp query <ipv4 address>        query arp address\n");
+}
+
+static int cmd_arp(int argc, const cmd_args *argv)
+{
+    const char *cmd;
+
+    if (argc == 1) {
+        arp_usage();
+        return -1;
+    }
+
+    cmd = argv[1].str;
+    if (argc == 2 && strncmp(cmd, "list", sizeof("list")) == 0) {
+        arp_cache_dump();
+    } else if (argc == 3 && strncmp(cmd, "query", sizeof("query")) == 0) {
+        const char *addr_s = argv[2].str;
+        uint32_t addr = str_ip_to_int(addr_s, strlen(addr_s));
+
+        arp_send_request(addr);
+    } else {
+        arp_usage();
+    }
+
+    return 0;
+}
+
+static int cmd_minip(int argc, const cmd_args *argv)
+{
+    if (argc == 1) {
+minip_usage:
+        printf("minip commands\n");
+        printf("mi [a]rp                        dump arp table\n");
+        printf("mi [s]tatus                     print ip status\n");
+        printf("mi [t]est [dest] [port] [cnt]   send <cnt> test packets to the dest:port\n");
+    } else {
+        switch(argv[1].str[0]) {
+
+            case 'a':
+                arp_cache_dump();
+                break;
+
+            case 's': {
+                uint32_t ipaddr = minip_get_ipaddr();
+
+                printf("hostname: %s\n", minip_get_hostname());
+                printf("ip: %u.%u.%u.%u\n", IPV4_SPLIT(ipaddr));
+            }
+            break;
+            case 't': {
+                uint32_t count = 1;
+                uint32_t host = 0x0100000A; // 10.0.0.1
+                uint32_t port = 1025;
+                udp_socket_t *handle;
+
+                switch (argc) {
+                    case 5:
+                        count = argv[4].u;
+                    case 4:
+                        port = argv[3].u;
+                    case 3:
+                        host = str_ip_to_int(argv[2].str, strlen(argv[2].str));
+                        break;
+                }
+
+                if (udp_open(host, port, port, &handle) != NO_ERROR) {
+                    printf("udp_open to %u.%u.%u.%u:%u failed\n", IPV4_SPLIT(host), port);
+                    return -1;
+                }
+
+#define BUFSIZE 1470
+                uint8_t *buf;
+
+                buf = malloc(BUFSIZE);
+                if (!buf) {
+                    udp_close(handle);
+                    return -1;
+                }
+
+                memset(buf, 0x00, BUFSIZE);
+                printf("sending %u packet(s) to %u.%u.%u.%u:%u\n", count, IPV4_SPLIT(host), port);
+
+                lk_time_t t = current_time();
+                uint32_t failures = 0;
+                for (uint32_t i = 0; i < count; i++) {
+                    if (udp_send(buf, BUFSIZE, handle) != 0) {
+                        failures++;
+                    }
+                    buf[128]++;
+                }
+                t = current_time() - t;
+                printf("%d pkts failed\n", failures);
+                uint64_t total_count = (uint64_t)count * BUFSIZE;
+                printf("wrote %llu bytes in %u msecs (%llu bytes/sec)\n",
+                    total_count, (uint32_t)t, total_count * 1000 / t);
+
+                free(buf);
+                udp_close(handle);
+#undef BUFSIZE
+            }
+            break;
+        default:
+            goto minip_usage;
+        }
+    }
+
+    return 0;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("arp", "arp commands", &cmd_arp)
+STATIC_COMMAND("mi", "minip commands", &cmd_minip)
+STATIC_COMMAND_END(minip);
+#endif
+
+// vim: set ts=4 sw=4 expandtab:
diff --git a/src/bsp/lk/lib/minip/minip-internal.h b/src/bsp/lk/lib/minip/minip-internal.h
new file mode 100644
index 0000000..fe1acb4
--- /dev/null
+++ b/src/bsp/lk/lib/minip/minip-internal.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ * Copyright (c) 2014 Brian Swetland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#pragma once
+
+#include <lib/minip.h>
+
+#include <compiler.h>
+#include <endian.h>
+#include <list.h>
+#include <stdint.h>
+#include <string.h>
+
+/* Lib configuration */
+#define MINIP_USE_UDP_CHECKSUM    0
+#define MINIP_MTU_SIZE            1536
+#define MINIP_USE_ARP             1
+
+#pragma pack(push, 1)
+struct arp_pkt {
+    uint16_t htype;
+    uint16_t ptype;
+    uint8_t  hlen;
+    uint8_t  plen;
+    uint16_t oper;
+    uint8_t  sha[6];
+    uint32_t spa;
+    uint8_t  tha[6];
+    uint32_t tpa;
+};
+
+struct ipv4_hdr {
+    uint8_t  ver_ihl;
+    uint8_t  dscp_ecn;
+    uint16_t len;
+    uint16_t id;
+    uint16_t flags_frags;
+    uint8_t  ttl;
+    uint8_t  proto;
+    uint16_t chksum;
+    uint32_t src_addr;
+    uint32_t dst_addr;
+    uint8_t  data[];
+};
+
+struct icmp_pkt {
+    uint8_t  type;
+    uint8_t  code;
+    uint16_t chksum;
+    uint8_t  hdr_data[4];
+    uint8_t  data[];
+};
+
+struct eth_hdr {
+    uint8_t dst_mac[6];
+    uint8_t src_mac[6];
+    uint16_t type;
+};
+
+#pragma pack(pop)
+
+enum {
+    ICMP_ECHO_REPLY   = 0,
+    ICMP_ECHO_REQUEST = 8,
+};
+
+enum {
+    IP_PROTO_ICMP = 0x1,
+    IP_PROTO_TCP  = 0x6,
+    IP_PROTO_UDP  = 0x11,
+};
+
+enum {
+    ETH_TYPE_IPV4 = 0x0800,
+    ETH_TYPE_ARP  = 0x0806,
+};
+
+enum {
+    ARP_OPER_REQUEST = 0x0001,
+    ARP_OPER_REPLY   = 0x0002,
+};
+
+extern tx_func_t minip_tx_handler;
+typedef struct udp_hdr udp_hdr_t;
+static const uint8_t bcast_mac[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+
+void arp_cache_init(void);
+void arp_cache_update(uint32_t addr, const uint8_t mac[6]);
+uint8_t *arp_cache_lookup(uint32_t addr);
+void arp_cache_dump(void);
+int arp_send_request(uint32_t addr);
+const uint8_t *arp_get_dest_mac(uint32_t host);
+
+uint16_t rfc1701_chksum(const uint8_t *buf, size_t len);
+uint16_t rfc768_chksum(struct ipv4_hdr *ipv4, udp_hdr_t *udp);
+uint16_t ones_sum16(uint32_t sum, const void *_buf, int len);
+
+/* Helper methods for building headers */
+void minip_build_mac_hdr(struct eth_hdr *pkt, const uint8_t *dst, uint16_t type);
+void minip_build_ipv4_hdr(struct ipv4_hdr *ipv4, uint32_t dst, uint8_t proto, uint16_t len);
+
+status_t minip_ipv4_send(pktbuf_t *p, uint32_t dest_addr, uint8_t proto);
+
+void tcp_input(pktbuf_t *p, uint32_t src_ip, uint32_t dst_ip);
+void udp_input(pktbuf_t *p, uint32_t src_ip);
+
+const uint8_t *get_dest_mac(uint32_t host);
+
+// timers
+typedef void (*net_timer_callback_t)(void *);
+
+typedef struct net_timer {
+    struct list_node node;
+
+    lk_time_t sched_time;
+
+    net_timer_callback_t cb;
+    void *arg;
+} net_timer_t;
+
+/* set a net timer. returns true if the timer was not set before and is now */
+bool net_timer_set(net_timer_t *, net_timer_callback_t, void *callback_args, lk_time_t delay) __NONNULL((1));
+
+/* cancels a net timer. returns true if it was previously set and is not now */
+bool net_timer_cancel(net_timer_t *) __NONNULL();
+
+void net_timer_init(void);
+
+static inline void mac_addr_copy(uint8_t *dest, const uint8_t *src) {
+    *(uint32_t *)dest = *(const uint32_t *)src;
+    *(uint16_t *)(dest + 4) = *(const uint16_t *)(src + 4);
+}
+
+// vim: set ts=4 sw=4 expandtab:
+
diff --git a/src/bsp/lk/lib/minip/minip.c b/src/bsp/lk/lib/minip/minip.c
new file mode 100644
index 0000000..dca27df
--- /dev/null
+++ b/src/bsp/lk/lib/minip/minip.c
@@ -0,0 +1,496 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ * Copyright (c) 2014 Brian Swetland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <err.h>
+#include <stdio.h>
+#include <debug.h>
+#include <endian.h>
+#include <errno.h>
+#include <iovec.h>
+#include <stdlib.h>
+#include <string.h>
+#include <trace.h>
+#include <malloc.h>
+#include <list.h>
+#include <kernel/thread.h>
+
+static struct list_node arp_list = LIST_INITIAL_VALUE(arp_list);
+
+// TODO
+// 1. Tear endian code out into something that flips words before/after tx/rx calls
+
+#define LOCAL_TRACE 0
+static uint32_t minip_ip      = IPV4_NONE;
+static uint32_t minip_netmask = IPV4_NONE;
+static uint32_t minip_broadcast = IPV4_BCAST;
+static uint32_t minip_gateway = IPV4_NONE;
+
+static const uint8_t broadcast_mac[6] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+static uint8_t minip_mac[6] = {0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC};
+
+static char minip_hostname[32] = "";
+
+static void dump_mac_address(const uint8_t *mac);
+static void dump_ipv4_addr(uint32_t addr);
+
+void minip_set_hostname(const char *name) {
+    strlcpy(minip_hostname, name, sizeof(minip_hostname));
+}
+
+const char *minip_get_hostname(void) {
+   return minip_hostname;
+}
+
+static void compute_broadcast_address(void)
+{
+    minip_broadcast = (minip_ip & minip_netmask) | (IPV4_BCAST & ~minip_netmask);
+}
+
+void minip_get_macaddr(uint8_t *addr) {
+    mac_addr_copy(addr, minip_mac);
+}
+
+void minip_set_macaddr(const uint8_t *addr) {
+    mac_addr_copy(minip_mac, addr);
+}
+
+uint32_t minip_get_ipaddr(void) {
+    return minip_ip;
+}
+
+void minip_set_ipaddr(const uint32_t addr) {
+    minip_ip = addr;
+    compute_broadcast_address();
+}
+
+void gen_random_mac_address(uint8_t *mac_addr)
+{
+    for (size_t i = 0; i < 6; i++) {
+        mac_addr[i] = rand() & 0xff;
+    }
+    /* unicast and locally administered */
+    mac_addr[0] &= ~(1<<0);
+    mac_addr[0] |= (1<<1);
+}
+
+/* This function is called by minip to send packets */
+tx_func_t minip_tx_handler;
+void *minip_tx_arg;
+
+void minip_init(tx_func_t tx_handler, void *tx_arg,
+    uint32_t ip, uint32_t mask, uint32_t gateway)
+{
+    minip_tx_handler = tx_handler;
+    minip_tx_arg = tx_arg;
+
+    minip_ip = ip;
+    minip_netmask = mask;
+    minip_gateway = gateway;
+    compute_broadcast_address();
+
+    arp_cache_init();
+    net_timer_init();
+}
+
+uint16_t ipv4_payload_len(struct ipv4_hdr *pkt)
+{
+    return (pkt->len - ((pkt->ver_ihl >> 4) * 5));
+}
+
+void minip_build_mac_hdr(struct eth_hdr *pkt, const uint8_t *dst, uint16_t type)
+{
+    mac_addr_copy(pkt->dst_mac, dst);
+    mac_addr_copy(pkt->src_mac, minip_mac);
+    pkt->type = htons(type);
+}
+
+void minip_build_ipv4_hdr(struct ipv4_hdr *ipv4, uint32_t dst, uint8_t proto, uint16_t len)
+{
+    ipv4->ver_ihl       = 0x45;
+    ipv4->dscp_ecn      = 0;
+    ipv4->len           = htons(20 + len); // 5 * 4 from ihl, plus payload length
+    ipv4->id            = 0;
+    ipv4->flags_frags   = 0x40; // no offset, no fragments
+    ipv4->ttl           = 64;
+    ipv4->proto         = proto;
+    ipv4->dst_addr      = dst;
+    ipv4->src_addr      = minip_ip;
+
+    /* This may be unnecessary if the controller supports checksum offloading */
+    ipv4->chksum = 0;
+    ipv4->chksum = rfc1701_chksum((uint8_t *) ipv4, sizeof(struct ipv4_hdr));
+}
+
+int send_arp_request(uint32_t addr)
+{
+    pktbuf_t *p;
+    struct eth_hdr *eth;
+    struct arp_pkt *arp;
+
+    if ((p = pktbuf_alloc()) == NULL) {
+        return -1;
+    }
+
+    eth = pktbuf_prepend(p, sizeof(struct eth_hdr));
+    arp = pktbuf_append(p, sizeof(struct arp_pkt));
+    minip_build_mac_hdr(eth, bcast_mac, ETH_TYPE_ARP);
+
+    arp->htype = htons(0x0001);
+    arp->ptype = htons(0x0800);
+    arp->hlen = 6;
+    arp->plen = 4;
+    arp->oper = htons(ARP_OPER_REQUEST);
+    arp->spa = minip_ip;
+    arp->tpa = addr;
+    mac_addr_copy(arp->sha, minip_mac);
+    mac_addr_copy(arp->tha, bcast_mac);
+
+    minip_tx_handler(p);
+    return 0;
+}
+
+static void handle_arp_timeout_cb(void *arg) {
+    *(bool *)arg = true;
+}
+
+const uint8_t *get_dest_mac(uint32_t host)
+{
+    uint8_t *dst_mac = NULL;
+    bool arp_timeout = false;
+    net_timer_t arp_timeout_timer;
+
+    if (host == IPV4_BCAST) {
+        return bcast_mac;
+    }
+
+    dst_mac = arp_cache_lookup(host);
+    if (dst_mac == NULL) {
+        send_arp_request(host);
+        memset(&arp_timeout_timer, 0, sizeof(arp_timeout_timer));
+        net_timer_set(&arp_timeout_timer, handle_arp_timeout_cb, &arp_timeout, 100);
+        while (!arp_timeout) {
+            dst_mac = arp_cache_lookup(host);
+            if (dst_mac) {
+                net_timer_cancel(&arp_timeout_timer);
+                break;
+            }
+        }
+    }
+
+    return dst_mac;
+}
+
+status_t minip_ipv4_send(pktbuf_t *p, uint32_t dest_addr, uint8_t proto)
+{
+    status_t ret = 0;
+    size_t data_len = p->dlen;
+    const uint8_t *dst_mac;
+
+    struct ipv4_hdr *ip = pktbuf_prepend(p, sizeof(struct ipv4_hdr));
+    struct eth_hdr *eth = pktbuf_prepend(p, sizeof(struct eth_hdr));
+
+
+    if (dest_addr == IPV4_BCAST || dest_addr == minip_broadcast) {
+        dst_mac = bcast_mac;
+        goto ready;
+    }
+
+    dst_mac = get_dest_mac(dest_addr);
+    if (!dst_mac) {
+        pktbuf_free(p, true);
+        ret = -EHOSTUNREACH;
+        goto err;
+    }
+
+ready:
+    minip_build_mac_hdr(eth, dst_mac, ETH_TYPE_IPV4);
+    minip_build_ipv4_hdr(ip, dest_addr, proto, data_len);
+
+    minip_tx_handler(p);
+
+err:
+    return ret;
+}
+
+/* Swap the dst/src ip addresses and send an ICMP ECHO REPLY with the same payload.
+ * According to spec the data portion doesn't matter, but ping itself validates that
+ * the payload is identical
+ */
+void send_ping_reply(uint32_t ipaddr, struct icmp_pkt *req, size_t reqdatalen)
+{
+    pktbuf_t *p;
+    size_t len;
+    struct eth_hdr *eth;
+    struct ipv4_hdr *ip;
+    struct icmp_pkt *icmp;
+
+    if ((p = pktbuf_alloc()) == NULL) {
+        return;
+    }
+
+    icmp = pktbuf_prepend(p, sizeof(struct icmp_pkt));
+    ip = pktbuf_prepend(p, sizeof(struct ipv4_hdr));
+    eth = pktbuf_prepend(p, sizeof(struct eth_hdr));
+    pktbuf_append_data(p, req->data, reqdatalen);
+
+    len = sizeof(struct icmp_pkt) + reqdatalen;
+
+    minip_build_mac_hdr(eth, arp_cache_lookup(ipaddr), ETH_TYPE_IPV4);
+    minip_build_ipv4_hdr(ip, ipaddr, IP_PROTO_ICMP, len);
+
+    icmp->type = ICMP_ECHO_REPLY;
+    icmp->code = 0;
+    memcpy(icmp->hdr_data, req->hdr_data, sizeof(icmp->hdr_data));
+    icmp->chksum = 0;
+    icmp->chksum = rfc1701_chksum((uint8_t *) icmp, len);
+
+    minip_tx_handler(p);
+}
+
+static void dump_ipv4_addr(uint32_t addr)
+{
+    const uint8_t *a = (void *)&addr;
+
+    printf("%hhu.%hhu.%hhu.%hhu", a[0], a[1], a[2], a[3]);
+}
+
+static void dump_ipv4_packet(const struct ipv4_hdr *ip)
+{
+    printf("IP ");
+    dump_ipv4_addr(ip->src_addr);
+    printf(" -> ");
+    dump_ipv4_addr(ip->dst_addr);
+    printf(" hlen 0x%x, prot 0x%x, cksum 0x%x, len 0x%x, ident 0x%x, frag offset 0x%x\n",
+        (ip->ver_ihl & 0xf) * 4, ip->proto, ntohs(ip->chksum), ntohs(ip->len), ntohs(ip->id), ntohs(ip->flags_frags) & 0x1fff);
+}
+
+__NO_INLINE static void handle_ipv4_packet(pktbuf_t *p, const uint8_t *src_mac)
+{
+    struct ipv4_hdr *ip;
+
+    ip = (struct ipv4_hdr *)p->data;
+    if (p->dlen < sizeof(struct ipv4_hdr))
+        return;
+
+    /* print packets for us */
+    if (LOCAL_TRACE) {
+        dump_ipv4_packet(ip);
+    }
+
+    /* reject bad packets */
+    if (((ip->ver_ihl >> 4) & 0xf) != 4) {
+        /* not version 4 */
+        LTRACEF("REJECT: not version 4\n");
+        return;
+    }
+
+    /* do we have enough buffer to hold the full header + options? */
+    size_t header_len = (ip->ver_ihl & 0xf) * 4;
+    if (p->dlen < header_len) {
+        LTRACEF("REJECT: not enough buffer to hold header\n");
+        return;
+    }
+
+    /* compute checksum */
+    if (rfc1701_chksum((void *)ip, header_len) != 0) {
+        /* bad checksum */
+        LTRACEF("REJECT: bad checksum\n");
+        return;
+    }
+
+    /* is the pkt_buf large enough to hold the length the header says the packet is? */
+    if (htons(ip->len) > p->dlen) {
+        LTRACEF("REJECT: packet exceeds size of buffer (header %d, dlen %d)\n", htons(ip->len), p->dlen);
+        return;
+    }
+
+    /* trim any excess bytes at the end of the packet */
+    if (p->dlen > htons(ip->len)) {
+        pktbuf_consume_tail(p, p->dlen - htons(ip->len));
+    }
+
+    /* remove the header from the front of the packet_buf  */
+    if (pktbuf_consume(p, header_len) == NULL) {
+        return;
+    }
+
+    /* the packet is good, we can use it to populate our arp cache */
+    arp_cache_update(ip->src_addr, src_mac);
+
+    /* see if it's for us */
+    if (ip->dst_addr != IPV4_BCAST) {
+        if (minip_ip != IPV4_NONE && ip->dst_addr != minip_ip && ip->dst_addr != minip_broadcast) {
+            LTRACEF("REJECT: for another host\n");
+            return;
+        }
+    }
+
+    /* We only handle UDP and ECHO REQUEST */
+    switch (ip->proto) {
+        case IP_PROTO_ICMP: {
+            struct icmp_pkt *icmp;
+            if ((icmp = pktbuf_consume(p, sizeof(struct icmp_pkt))) == NULL) {
+                break;
+            }
+            if (icmp->type == ICMP_ECHO_REQUEST) {
+                send_ping_reply(ip->src_addr, icmp, p->dlen);
+            }
+        }
+        break;
+
+        case IP_PROTO_UDP:
+            udp_input(p, ip->src_addr);
+        break;
+
+        case IP_PROTO_TCP:
+            tcp_input(p, ip->src_addr, ip->dst_addr);
+        break;
+    }
+}
+
+__NO_INLINE static int handle_arp_pkt(pktbuf_t *p)
+{
+    struct eth_hdr *eth;
+    struct arp_pkt *arp;
+
+    eth = (void*) (p->data - sizeof(struct eth_hdr));
+
+    if ((arp = pktbuf_consume(p, sizeof(struct arp_pkt))) == NULL) {
+        return -1;
+    }
+
+    switch(ntohs(arp->oper)) {
+        case ARP_OPER_REQUEST: {
+            pktbuf_t *rp;
+            struct eth_hdr *reth;
+            struct arp_pkt *rarp;
+
+            if (memcmp(&arp->tpa, &minip_ip, sizeof(minip_ip)) == 0) {
+                if ((rp = pktbuf_alloc()) == NULL) {
+                    break;
+                }
+
+                reth = pktbuf_prepend(rp, sizeof(struct eth_hdr));
+                rarp = pktbuf_append(rp, sizeof(struct arp_pkt));
+
+                // Eth header
+                minip_build_mac_hdr(reth, eth->src_mac, ETH_TYPE_ARP);
+
+                // ARP packet
+                rarp->oper = htons(ARP_OPER_REPLY);
+                rarp->htype = htons(0x0001);
+                rarp->ptype = htons(0x0800);
+                rarp->hlen = 6;
+                rarp->plen = 4;
+                mac_addr_copy(rarp->sha, minip_mac);
+                rarp->spa = minip_ip;
+                mac_addr_copy(rarp->tha, arp->sha);
+                rarp->tpa = arp->spa;
+
+                minip_tx_handler(rp);
+            }
+        }
+        break;
+
+        case ARP_OPER_REPLY: {
+            uint32_t addr;
+            memcpy(&addr, &arp->spa, sizeof(addr)); // unaligned word
+            arp_cache_update(addr, arp->sha);
+        }
+        break;
+    }
+
+    return 0;
+}
+
+static void dump_mac_address(const uint8_t *mac)
+{
+    printf("%02x:%02x:%02x:%02x:%02x:%02x",
+        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+}
+
+static void dump_eth_packet(const struct eth_hdr *eth)
+{
+    printf("ETH src ");
+    dump_mac_address(eth->src_mac);
+    printf(" dst ");
+    dump_mac_address(eth->dst_mac);
+    printf(" type 0x%hx\n", htons(eth->type));
+}
+
+void minip_rx_driver_callback(pktbuf_t *p)
+{
+    struct eth_hdr *eth;
+
+    if ((eth = (void*) pktbuf_consume(p, sizeof(struct eth_hdr))) == NULL) {
+        return;
+    }
+
+    if (LOCAL_TRACE) {
+        dump_eth_packet(eth);
+    }
+
+    if (memcmp(eth->dst_mac, minip_mac, 6) != 0 &&
+        memcmp(eth->dst_mac, broadcast_mac, 6) != 0) {
+        /* not for us */
+        return;
+    }
+
+    switch(htons(eth->type)) {
+        case ETH_TYPE_IPV4:
+            LTRACEF("ipv4 pkt\n");
+            handle_ipv4_packet(p, eth->src_mac);
+            break;
+
+        case ETH_TYPE_ARP:
+            LTRACEF("arp pkt\n");
+            handle_arp_pkt(p);
+            break;
+    }
+}
+
+uint32_t minip_parse_ipaddr(const char* ipaddr_str, size_t len)
+{
+    uint8_t ip[4] = { 0, 0, 0, 0 };
+    uint8_t pos = 0, i = 0;
+
+    while (pos < len) {
+        char c = ipaddr_str[pos];
+        if (c == '.') {
+            i++;
+        } else if (c == '\0') {
+            break;
+        } else {
+            ip[i] *= 10;
+            ip[i] += c - '0';
+        }
+        pos++;
+    }
+
+    return IPV4_PACK(ip);
+}
+
+// vim: set ts=4 sw=4 expandtab:
diff --git a/src/bsp/lk/lib/minip/net_timer.c b/src/bsp/lk/lib/minip/net_timer.c
new file mode 100644
index 0000000..f2fee7f
--- /dev/null
+++ b/src/bsp/lk/lib/minip/net_timer.c
@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <trace.h>
+#include <debug.h>
+#include <compiler.h>
+#include <stdlib.h>
+#include <list.h>
+#include <err.h>
+#include <sys/types.h>
+#include <kernel/thread.h>
+#include <kernel/event.h>
+#include <kernel/mutex.h>
+#include <platform.h>
+
+#define LOCAL_TRACE 0
+
+static struct list_node net_timer_list = LIST_INITIAL_VALUE(net_timer_list);
+static event_t net_timer_event = EVENT_INITIAL_VALUE(net_timer_event, false, 0);
+static mutex_t net_timer_lock = MUTEX_INITIAL_VALUE(net_timer_lock);
+
+static void add_to_queue(net_timer_t *t)
+{
+    net_timer_t *e;
+    list_for_every_entry(&net_timer_list, e, net_timer_t, node) {
+        if (TIME_GT(e->sched_time, t->sched_time)) {
+            list_add_before(&e->node, &t->node);
+            return;
+        }
+    }
+
+    list_add_tail(&net_timer_list, &t->node);
+}
+
+bool net_timer_set(net_timer_t *t, net_timer_callback_t cb, void *callback_args, lk_time_t delay)
+{
+    bool newly_queued = true;
+
+    lk_time_t now = current_time();
+
+    mutex_acquire(&net_timer_lock);
+
+    if (list_in_list(&t->node)) {
+        list_delete(&t->node);
+        newly_queued = false;
+    }
+
+    t->cb = cb;
+    t->arg = callback_args;
+    t->sched_time = now + delay;
+
+    add_to_queue(t);
+
+    mutex_release(&net_timer_lock);
+
+    event_signal(&net_timer_event, true);
+
+    return newly_queued;
+}
+
+bool net_timer_cancel(net_timer_t *t)
+{
+    bool was_queued = false;
+
+    mutex_acquire(&net_timer_lock);
+
+    if (list_in_list(&t->node)) {
+        list_delete(&t->node);
+        was_queued = true;
+    }
+
+    mutex_release(&net_timer_lock);
+
+    return was_queued;
+}
+
+/* returns the delay to the next event */
+static lk_time_t net_timer_work_routine(void)
+{
+    lk_time_t now = current_time();
+    lk_time_t delay = INFINITE_TIME;
+
+    mutex_acquire(&net_timer_lock);
+
+    for (;;) {
+        net_timer_t *e;
+        e = list_peek_head_type(&net_timer_list, net_timer_t, node);
+        if (!e) {
+            delay = INFINITE_TIME;
+            goto done;
+        }
+
+        if (TIME_GT(e->sched_time, now)) {
+            delay = e->sched_time - now;
+            goto done;
+        }
+
+        list_delete(&e->node);
+
+        mutex_release(&net_timer_lock);
+
+        LTRACEF("firing timer %p, cb %p, arg %p\n", e, e->cb, e->arg);
+        e->cb(e->arg);
+
+        mutex_acquire(&net_timer_lock);
+    }
+
+done:
+    if (delay == INFINITE_TIME)
+        event_unsignal(&net_timer_event);
+
+    mutex_release(&net_timer_lock);
+
+    return delay;
+}
+
+int net_timer_work_thread(void *args)
+{
+    for (;;) {
+        event_wait(&net_timer_event);
+
+        lk_time_t delay = net_timer_work_routine();
+        if (delay != INFINITE_TIME) {
+            thread_sleep(MIN(delay, 100));
+        }
+    }
+
+    return 0;
+}
+
+void net_timer_init(void)
+{
+    thread_detach_and_resume(thread_create("net timer", &net_timer_work_thread, NULL, DEFAULT_PRIORITY, DEFAULT_STACK_SIZE));
+}
+
+
diff --git a/src/bsp/lk/lib/minip/pktbuf.c b/src/bsp/lk/lib/minip/pktbuf.c
new file mode 100644
index 0000000..2ffd254
--- /dev/null
+++ b/src/bsp/lk/lib/minip/pktbuf.c
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2014 Brian Swetland
+ * Copyright (c) 2014-2015 Christopher Anderson
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include <assert.h>
+#include <debug.h>
+#include <trace.h>
+#include <printf.h>
+#include <string.h>
+#include <malloc.h>
+
+#include <kernel/thread.h>
+#include <kernel/semaphore.h>
+#include <kernel/spinlock.h>
+#include <lib/pktbuf.h>
+#include <lib/pool.h>
+#include <lk/init.h>
+
+#if WITH_KERNEL_VM
+#include <kernel/vm.h>
+#endif
+
+#define LOCAL_TRACE 0
+
+static pool_t pktbuf_pool;
+static semaphore_t pktbuf_sem;
+static spin_lock_t lock;
+
+
+/* Take an object from the pool of pktbuf objects to act as a header or buffer.  */
+static void *get_pool_object(void) {
+	pool_t *entry;
+	spin_lock_saved_state_t state;
+
+	sem_wait(&pktbuf_sem);
+	spin_lock_irqsave(&lock, state);
+	entry = pool_alloc(&pktbuf_pool);
+	spin_unlock_irqrestore(&lock, state);
+
+	return (pktbuf_pool_object_t *) entry;
+
+}
+
+/* Return an object to thje pktbuf object pool. */
+static void free_pool_object(pktbuf_pool_object_t *entry, bool reschedule) {
+	DEBUG_ASSERT(entry);
+	spin_lock_saved_state_t state;
+
+	spin_lock_irqsave(&lock, state);
+	pool_free(&pktbuf_pool, entry);
+	spin_unlock_irqrestore(&lock, state);
+	sem_post(&pktbuf_sem, reschedule);
+}
+
+/* Callback used internally to place a pktbuf_pool_object back in the pool after
+ * it was used as a buffer for another pktbuf
+ */
+static void free_pktbuf_buf_cb(void *buf, void *arg) {
+	free_pool_object((pktbuf_pool_object_t *)buf, true);
+}
+
+/* Add a buffer to a pktbuf. Header space for prepending data is adjusted based on
+ * header_sz. cb is called when the pktbuf is freed / released by the driver level
+ * and should handle proper management / freeing of the buffer pointed to by the iovec.
+ *
+ * It's important to note that there is a flag to note that the buffer is cached and should
+ * be properly handled via the appropriate driver when it's time to deal with buffer
+ * descriptiors.
+ */
+void pktbuf_add_buffer(pktbuf_t *p, u8 *buf, u32 len, uint32_t header_sz, uint32_t flags,
+					   pktbuf_free_callback cb, void *cb_args) {
+	DEBUG_ASSERT(p);
+	DEBUG_ASSERT(buf);
+	DEBUG_ASSERT(header_sz < len);
+
+	p->buffer = buf;
+	p->blen = len;
+	p->data = p->buffer + header_sz;
+	p->dlen = 0;
+	p->flags = PKTBUF_FLAG_EOF | flags;
+	p->cb = cb;
+	p->cb_args = cb_args;
+
+	/* If we're using a VM then this may be a virtual address, look up to see
+	 * if there is an associated physical address we can store. If not, then
+	 * stick with the address as presented to us.
+	 */
+#if WITH_KERNEL_VM
+	p->phys_base = kvaddr_to_paddr(buf) | (uintptr_t) buf % PAGE_SIZE;
+#else
+	p->phys_base = (uintptr_t) buf;
+#endif
+}
+
+pktbuf_t *pktbuf_alloc(void) {
+	pktbuf_t *p = NULL;
+	void *buf = NULL;
+
+	p = get_pool_object();
+	if (!p) {
+		return NULL;
+	}
+
+	buf = get_pool_object();
+	if (!buf) {
+		free_pool_object((pktbuf_pool_object_t *)p, false);
+		return NULL;
+	}
+
+	memset(p, 0, sizeof(pktbuf_t));
+	pktbuf_add_buffer(p, buf, PKTBUF_SIZE, PKTBUF_MAX_HDR, 0, free_pktbuf_buf_cb, NULL);
+	return p;
+}
+
+pktbuf_t *pktbuf_alloc_empty(void) {
+	pktbuf_t *p = (pktbuf_t *) get_pool_object();
+
+	p->flags = PKTBUF_FLAG_EOF;
+	return p;
+}
+
+int pktbuf_free(pktbuf_t *p, bool reschedule) {
+	DEBUG_ASSERT(p);
+
+	if (p->cb) {
+		p->cb(p->buffer, p->cb_args);
+	}
+	free_pool_object((pktbuf_pool_object_t *)p, false);
+
+	return 1;
+}
+
+void pktbuf_append_data(pktbuf_t *p, const void *data, size_t sz) {
+	if (pktbuf_avail_tail(p) < sz) {
+		panic("pktbuf_append_data: overflow");
+	}
+
+	memcpy(p->data + p->dlen, data, sz);
+	p->dlen += sz;
+}
+
+void *pktbuf_append(pktbuf_t *p, size_t sz) {
+	if (pktbuf_avail_tail(p) < sz) {
+		panic("pktbuf_append: overflow");
+	}
+
+	void *data = p->data + p->dlen;
+	p->dlen += sz;
+
+	return data;
+}
+
+void *pktbuf_prepend(pktbuf_t *p, size_t sz) {
+	if (pktbuf_avail_head(p) < sz) {
+		panic("pktbuf_prepend: not enough space");
+	}
+
+	p->dlen += sz;
+	p->data -= sz;
+
+	return p->data;
+}
+
+void *pktbuf_consume(pktbuf_t *p, size_t sz) {
+	void *data = p->data;
+
+	if (sz > p->dlen) {
+		return NULL;
+	}
+
+	p->data += sz;
+	p->dlen -= sz;
+
+	return data;
+}
+
+void pktbuf_consume_tail(pktbuf_t *p, size_t sz) {
+	if (sz > p->dlen) {
+		p->dlen = 0;
+		return;
+	}
+
+	p->dlen -= sz;
+}
+
+void pktbuf_dump(pktbuf_t *p) {
+	printf("pktbuf data %p, buffer %p, dlen %u, data offset %lu, phys_base %p\n",
+			p->data, p->buffer, p->dlen, (uintptr_t) p->data - (uintptr_t) p->buffer,
+			(void *)p->phys_base);
+}
+
+static void pktbuf_init(uint level)
+{
+	void *slab;
+
+#if LK_DEBUGLEVEL > 0
+	printf("pktbuf: creating %u pktbuf entries of size %zu (total %zu)\n",
+		PKTBUF_POOL_SIZE, sizeof(struct pktbuf_pool_object),
+		PKTBUF_POOL_SIZE * sizeof(struct pktbuf_pool_object));
+#endif
+
+#if WITH_KERNEL_VM
+	if (vmm_alloc_contiguous(vmm_get_kernel_aspace(), "pktbuf",
+			PKTBUF_POOL_SIZE * sizeof(struct pktbuf_pool_object), 
+			&slab, 0, 0, ARCH_MMU_FLAG_CACHED) < 0) {
+		printf("Failed to initialize pktbuf hdr slab\n");
+		return;
+	}
+#else
+	slab = memalign(CACHE_LINE, PKTBUF_POOL_SIZE * sizeof(pktbuf_pool_object_t));
+#endif
+
+	pool_init(&pktbuf_pool, sizeof(struct pktbuf_pool_object), CACHE_LINE, PKTBUF_POOL_SIZE, slab);
+	sem_init(&pktbuf_sem, PKTBUF_POOL_SIZE);
+}
+
+LK_INIT_HOOK(pktbuf, pktbuf_init, LK_INIT_LEVEL_THREADING);
+
+// vim: set noexpandtab:
diff --git a/src/bsp/lk/lib/minip/rules.mk b/src/bsp/lk/lib/minip/rules.mk
new file mode 100644
index 0000000..a2c3dc6
--- /dev/null
+++ b/src/bsp/lk/lib/minip/rules.mk
@@ -0,0 +1,21 @@
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_DEPS := \
+	lib/cbuf \
+	lib/iovec \
+	lib/pool
+
+MODULE_SRCS += \
+	$(LOCAL_DIR)/arp.c \
+	$(LOCAL_DIR)/chksum.c \
+	$(LOCAL_DIR)/dhcp.c \
+	$(LOCAL_DIR)/lk_console.c \
+	$(LOCAL_DIR)/minip.c \
+	$(LOCAL_DIR)/net_timer.c \
+	$(LOCAL_DIR)/pktbuf.c \
+	$(LOCAL_DIR)/tcp.c \
+	$(LOCAL_DIR)/udp.c
+
+include make/module.mk
diff --git a/src/bsp/lk/lib/minip/tcp.c b/src/bsp/lk/lib/minip/tcp.c
new file mode 100644
index 0000000..6b367d6
--- /dev/null
+++ b/src/bsp/lk/lib/minip/tcp.c
@@ -0,0 +1,1304 @@
+/*
+ * Copyright (c) 2014 Travis Geiselbrecht
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <trace.h>
+#include <assert.h>
+#include <compiler.h>
+#include <stdlib.h>
+#include <err.h>
+#include <string.h>
+#include <sys/types.h>
+#include <lib/console.h>
+#include <lib/cbuf.h>
+#include <kernel/mutex.h>
+#include <kernel/semaphore.h>
+#include <arch/ops.h>
+#include <platform.h>
+
+#define LOCAL_TRACE 0
+
+typedef uint32_t ipv4_addr;
+
+typedef struct tcp_header {
+    uint16_t source_port;
+    uint16_t dest_port;
+    uint32_t seq_num;
+    uint32_t ack_num;
+    uint16_t length_flags;
+    uint16_t win_size;
+    uint16_t checksum;
+    uint16_t urg_pointer;
+} __PACKED tcp_header_t;
+
+typedef struct tcp_pseudo_header {
+    ipv4_addr source_addr;
+    ipv4_addr dest_addr;
+    uint8_t zero;
+    uint8_t protocol;
+    uint16_t tcp_length;
+} __PACKED tcp_pseudo_header_t;
+
+typedef struct tcp_mss_option {
+    uint8_t kind; /* 0x2 */
+    uint8_t len;  /* 0x4 */
+    uint16_t mss;
+} __PACKED tcp_mss_option_t;
+
+typedef enum tcp_state {
+    STATE_CLOSED,
+    STATE_LISTEN,
+    STATE_SYN_SENT,
+    STATE_SYN_RCVD,
+    STATE_ESTABLISHED,
+    STATE_CLOSE_WAIT,
+    STATE_LAST_ACK,
+    STATE_CLOSING,
+    STATE_FIN_WAIT_1,
+    STATE_FIN_WAIT_2,
+    STATE_TIME_WAIT
+} tcp_state_t;
+
+typedef enum tcp_flags {
+    PKT_FIN = 1,
+    PKT_SYN = 2,
+    PKT_RST = 4,
+    PKT_PSH = 8,
+    PKT_ACK = 16,
+    PKT_URG = 32
+} tcp_flags_t;
+
+typedef struct tcp_socket {
+    struct list_node node;
+
+    mutex_t lock;
+    volatile int ref;
+
+    tcp_state_t state;
+    ipv4_addr local_ip;
+    ipv4_addr remote_ip;
+    uint16_t local_port;
+    uint16_t remote_port;
+
+    uint32_t mss;
+
+    /* rx */
+    uint32_t rx_win_size;
+    uint32_t rx_win_low;
+    uint32_t rx_win_high;
+    uint8_t  *rx_buffer_raw;
+    cbuf_t   rx_buffer;
+    event_t  rx_event;
+    int      rx_full_mss_count; // number of packets we have received in a row with a full mss
+    net_timer_t ack_delay_timer;
+
+    /* tx */
+    uint32_t tx_win_low;  // low side of the acked window
+    uint32_t tx_win_high; // tx_win_low + their advertised window size
+    uint32_t tx_highest_seq; // highest sequence we have txed them
+    uint8_t  *tx_buffer;  // our outgoing buffer
+    uint32_t tx_buffer_size; // size of tx_buffer
+    uint32_t tx_buffer_offset; // offset into the buffer to append new data to
+    event_t  tx_event;
+    net_timer_t retransmit_timer;
+
+    /* listen accept */
+    semaphore_t accept_sem;
+    struct tcp_socket *accepted;
+
+    net_timer_t time_wait_timer;
+} tcp_socket_t;
+
+#define DEFAULT_MSS (1460)
+#define DEFAULT_RX_WINDOW_SIZE (8192)
+#define DEFAULT_TX_BUFFER_SIZE (8192)
+
+#define RETRANSMIT_TIMEOUT (50)
+#define DELAYED_ACK_TIMEOUT (50)
+#define TIME_WAIT_TIMEOUT (60000) // 1 minute
+
+#define FORCE_TCP_CHECKSUM (false)
+
+#define SEQUENCE_GTE(a, b) ((int32_t)((a) - (b)) >= 0)
+#define SEQUENCE_LTE(a, b) ((int32_t)((a) - (b)) <= 0)
+#define SEQUENCE_GT(a, b) ((int32_t)((a) - (b)) > 0)
+#define SEQUENCE_LT(a, b) ((int32_t)((a) - (b)) < 0)
+
+static mutex_t tcp_socket_list_lock = MUTEX_INITIAL_VALUE(tcp_socket_list_lock);
+static struct list_node tcp_socket_list = LIST_INITIAL_VALUE(tcp_socket_list);
+
+static bool tcp_debug = false;
+
+/* local routines */
+static tcp_socket_t *lookup_socket(ipv4_addr remote_ip, ipv4_addr local_ip, uint16_t remote_port, uint16_t local_port);
+static void add_socket_to_list(tcp_socket_t *s);
+static void remove_socket_from_list(tcp_socket_t *s);
+static tcp_socket_t *create_tcp_socket(bool alloc_buffers);
+static status_t tcp_send(ipv4_addr dest_ip, uint16_t dest_port, ipv4_addr src_ip, uint16_t src_port, const void *buf,
+    size_t len, tcp_flags_t flags, const void *options, size_t options_length, uint32_t ack, uint32_t sequence, uint16_t window_size);
+static status_t tcp_socket_send(tcp_socket_t *s, const void *data, size_t len, tcp_flags_t flags, const void *options, size_t options_length, uint32_t sequence);
+static void handle_data(tcp_socket_t *s, const void *data, size_t len, uint32_t sequence);
+static void send_ack(tcp_socket_t *s);
+static void handle_ack(tcp_socket_t *s, uint32_t sequence, uint32_t win_size);
+static void handle_retransmit_timeout(void *_s);
+static void handle_time_wait_timeout(void *_s);
+static void handle_delayed_ack_timeout(void *_s);
+static void tcp_remote_close(tcp_socket_t *s);
+static void tcp_wakeup_waiters(tcp_socket_t *s);
+static void inc_socket_ref(tcp_socket_t *s);
+static bool dec_socket_ref(tcp_socket_t *s);
+
+static uint16_t cksum_pheader(const tcp_pseudo_header_t *pheader, const void *buf, size_t len)
+{
+    uint16_t checksum = ones_sum16(0, pheader, sizeof(*pheader));
+    return ~ones_sum16(checksum, buf, len);
+}
+
+__NO_INLINE static void dump_tcp_header(const tcp_header_t *header)
+{
+    printf("TCP: src_port %u, dest_port %u, seq %u, ack %u, win %u, flags %c%c%c%c%c%c\n",
+        ntohs(header->source_port), ntohs(header->dest_port), ntohl(header->seq_num), ntohl(header->ack_num),
+        ntohs(header->win_size),
+        (ntohs(header->length_flags) & PKT_FIN) ? 'F' : ' ',
+        (ntohs(header->length_flags) & PKT_SYN) ? 'S' : ' ',
+        (ntohs(header->length_flags) & PKT_RST) ? 'R' : ' ',
+        (ntohs(header->length_flags) & PKT_PSH) ? 'P' : ' ',
+        (ntohs(header->length_flags) & PKT_ACK) ? 'A' : ' ',
+        (ntohs(header->length_flags) & PKT_URG) ? 'U' : ' ');
+}
+
+static const char *tcp_state_to_string(tcp_state_t state)
+{
+    switch (state) {
+        default:
+        case STATE_CLOSED: return "CLOSED";
+        case STATE_LISTEN: return "LISTEN";
+        case STATE_SYN_SENT: return "SYN_SENT";
+        case STATE_SYN_RCVD: return "SYN_RCVD";
+        case STATE_ESTABLISHED: return "ESTABLISHED";
+        case STATE_CLOSE_WAIT: return "CLOSE_WAIT";
+        case STATE_LAST_ACK: return "LAST_ACK";
+        case STATE_CLOSING: return "CLOSING";
+        case STATE_FIN_WAIT_1: return "FIN_WAIT_1";
+        case STATE_FIN_WAIT_2: return "FIN_WAIT_2";
+        case STATE_TIME_WAIT: return "TIME_WAIT";
+    }
+}
+
+static void dump_socket(tcp_socket_t *s)
+{
+    printf("socket %p: state %d (%s), local 0x%x:%hu, remote 0x%x:%hu, ref %d\n",
+            s, s->state, tcp_state_to_string(s->state),
+            s->local_ip, s->local_port, s->remote_ip, s->remote_port, s->ref);
+    if (s->state == STATE_ESTABLISHED || s->state == STATE_CLOSE_WAIT) {
+        printf("\trx: wsize %u wlo %u whi %u (%u)\n",
+                s->rx_win_size, s->rx_win_low, s->rx_win_high,
+                s->rx_win_high - s->rx_win_low);
+        printf("\ttx: wlo %u whi %u (%u) highest_seq %u (%u) bufsize %u bufoff %u\n",
+                s->tx_win_low, s->tx_win_high, s->tx_win_high - s->tx_win_low,
+                s->tx_highest_seq, s->tx_highest_seq - s->tx_win_low,
+                s->tx_buffer_size, s->tx_buffer_offset);
+    }
+}
+
+static tcp_socket_t *lookup_socket(ipv4_addr remote_ip, ipv4_addr local_ip, uint16_t remote_port, uint16_t local_port)
+{
+    LTRACEF("remote ip 0x%x local ip 0x%x remote port %u local port %u\n", remote_ip, local_ip, remote_port, local_port);
+
+    mutex_acquire(&tcp_socket_list_lock);
+
+    /* XXX replace with something faster, like a hash table */
+    tcp_socket_t *s = NULL;
+    list_for_every_entry(&tcp_socket_list, s, tcp_socket_t, node) {
+        if (s->state == STATE_CLOSED || s->state == STATE_LISTEN) {
+            continue;
+        } else {
+            /* full check */
+            if (s->remote_ip == remote_ip &&
+                s->local_ip == local_ip &&
+                s->remote_port == remote_port &&
+                s->local_port == local_port) {
+                goto out;
+            }
+        }
+    }
+
+    /* walk the list again, looking only for listen matches */
+    list_for_every_entry(&tcp_socket_list, s, tcp_socket_t, node) {
+        if (s->state == STATE_LISTEN) {
+            /* sockets in listen state only care about local port */
+            if (s->local_port == local_port) {
+                goto out;
+            }
+        }
+    }
+
+    /* fall through case returns null */
+    s = NULL;
+
+out:
+    /* bump the ref before returning it */
+    if (s)
+        inc_socket_ref(s);
+
+    mutex_release(&tcp_socket_list_lock);
+
+    return s;
+}
+
+static void add_socket_to_list(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(s->ref > 0); // we should have implicitly bumped the ref when creating the socket
+
+    mutex_acquire(&tcp_socket_list_lock);
+
+    list_add_head(&tcp_socket_list, &s->node);
+
+    mutex_release(&tcp_socket_list_lock);
+}
+
+static void remove_socket_from_list(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(s->ref > 0);
+
+    mutex_acquire(&tcp_socket_list_lock);
+
+    DEBUG_ASSERT(list_in_list(&s->node));
+    list_delete(&s->node);
+
+    mutex_release(&tcp_socket_list_lock);
+}
+
+static void inc_socket_ref(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+
+    __UNUSED int oldval = atomic_add(&s->ref, 1);
+    LTRACEF("caller %p, thread %p, socket %p, ref now %d\n", __GET_CALLER(), get_current_thread(), s, oldval + 1);
+    DEBUG_ASSERT(oldval > 0);
+}
+
+static bool dec_socket_ref(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+
+    int oldval = atomic_add(&s->ref, -1);
+    LTRACEF("caller %p, thread %p, socket %p, ref now %d\n", __GET_CALLER(), get_current_thread(), s, oldval - 1);
+
+    if (oldval == 1) {
+        LTRACEF("destroying socket\n");
+        event_destroy(&s->tx_event);
+        event_destroy(&s->rx_event);
+
+        free(s->rx_buffer_raw);
+        free(s->tx_buffer);
+
+        free(s);
+    }
+    return (oldval == 1);
+}
+
+static void tcp_timer_set(tcp_socket_t *s, net_timer_t *timer, net_timer_callback_t cb, lk_time_t delay)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(timer);
+
+    if (net_timer_set(timer, cb, s, delay))
+        inc_socket_ref(s);
+}
+
+static void tcp_timer_cancel(tcp_socket_t *s, net_timer_t *timer)
+{
+
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(timer);
+
+    if (net_timer_cancel(timer))
+        dec_socket_ref(s);
+}
+
+void tcp_input(pktbuf_t *p, uint32_t src_ip, uint32_t dst_ip)
+{
+    if (unlikely(tcp_debug))
+        TRACEF("p %p (len %u), src_ip 0x%x, dst_ip 0x%x\n", p, p->dlen, src_ip, dst_ip);
+
+    tcp_header_t *header = (tcp_header_t *)p->data;
+
+    /* reject if too small */
+    if (p->dlen < sizeof(tcp_header_t))
+        return;
+
+    if (unlikely(tcp_debug) || LOCAL_TRACE) {
+        dump_tcp_header(header);
+    }
+
+    /* compute the actual header length (+ options) */
+    size_t header_len = ((ntohs(header->length_flags) >> 12) & 0xf) * 4;
+    if (p->dlen < header_len) {
+        TRACEF("REJECT: packet too large for buffer\n");
+        return;
+    }
+
+    /* checksum */
+    if (FORCE_TCP_CHECKSUM || (p->flags & PKTBUF_FLAG_CKSUM_TCP_GOOD) == 0) {
+        tcp_pseudo_header_t pheader;
+
+        // set up the pseudo header for checksum purposes
+        pheader.source_addr = src_ip;
+        pheader.dest_addr = dst_ip;
+        pheader.zero = 0;
+        pheader.protocol = IP_PROTO_TCP;
+        pheader.tcp_length = htons(p->dlen);
+
+        uint16_t checksum = cksum_pheader(&pheader, p->data, p->dlen);
+        if(checksum != 0) {
+            TRACEF("REJECT: failed checksum, header says 0x%x, we got 0x%x\n", header->checksum, checksum);
+            return;
+        }
+    }
+
+    /* byte swap header in place */
+    header->source_port = ntohs(header->source_port);
+    header->dest_port = ntohs(header->dest_port);
+    header->seq_num = ntohl(header->seq_num);
+    header->ack_num = ntohl(header->ack_num);
+    header->length_flags = ntohs(header->length_flags);
+    header->win_size = ntohs(header->win_size);
+    header->urg_pointer = ntohs(header->urg_pointer);
+
+    /* get some data from the packet */
+    uint8_t packet_flags = header->length_flags & 0x3f;
+    size_t data_len = p->dlen - header_len;
+    uint32_t highest_sequence = header->seq_num + ((data_len > 0) ? (data_len - 1) : 0);
+
+    /* see if it matches a socket we have */
+    tcp_socket_t *s = lookup_socket(src_ip, dst_ip, header->source_port, header->dest_port);
+    if (!s) {
+        /* send a RST packet */
+        goto send_reset;
+    }
+
+    if (unlikely(tcp_debug))
+        TRACEF("got socket %p, state %d (%s), ref %d\n", s, s->state, tcp_state_to_string(s->state), s->ref);
+
+    /* remove the header */
+    pktbuf_consume(p, header_len);
+
+    mutex_acquire(&s->lock);
+
+    /* check to see if they're resetting us */
+    if (packet_flags & PKT_RST) {
+        if (s->state != STATE_CLOSED && s->state != STATE_LISTEN) {
+            tcp_remote_close(s);
+        }
+        goto done;
+    }
+
+    switch (s->state) {
+        case STATE_CLOSED:
+            /* socket closed, send RST */
+            goto send_reset;
+
+            /* passive connect states */
+        case STATE_LISTEN: {
+            /* we're in listen and they want to talk to us */
+            if (!(packet_flags & PKT_SYN)) {
+                /* not a SYN, send RST */
+                goto send_reset;
+            }
+
+            /* see if we have a slot to accept */
+            if (s->accepted != NULL)
+                goto done;
+
+            /* make a new accept socket */
+            tcp_socket_t *accept_socket = create_tcp_socket(true);
+            if (!accept_socket)
+                goto done;
+
+            /* set it up */
+            accept_socket->local_ip = minip_get_ipaddr();
+            accept_socket->local_port = s->local_port;
+            accept_socket->remote_ip = src_ip;
+            accept_socket->remote_port = header->source_port;
+            accept_socket->state = STATE_SYN_RCVD;
+
+            mutex_acquire(&accept_socket->lock);
+
+            add_socket_to_list(accept_socket);
+
+            /* remember their sequence */
+            accept_socket->rx_win_low = header->seq_num + 1;
+            accept_socket->rx_win_high = accept_socket->rx_win_low + accept_socket->rx_win_size - 1;
+
+            /* save this socket and wake anyone up that is waiting to accept */
+            s->accepted = accept_socket;
+            sem_post(&s->accept_sem, true);
+
+            /* set up a mss option for sending back */
+            tcp_mss_option_t mss_option;
+            mss_option.kind = 0x2;
+            mss_option.len = 0x4;
+            mss_option.mss = ntohs(s->mss); // XXX make sure we fit in their mss
+
+            /* send a response */
+            tcp_socket_send(accept_socket, NULL, 0, PKT_ACK|PKT_SYN, &mss_option, sizeof(mss_option),
+                accept_socket->tx_win_low);
+
+            /* SYN consumed a sequence */
+            accept_socket->tx_win_low++;
+
+            mutex_release(&accept_socket->lock);
+            break;
+        }
+        case STATE_SYN_RCVD:
+            if (packet_flags & PKT_SYN) {
+                /* they must have not seen our ack of their original syn, retransmit */
+                // XXX implement
+                goto send_reset;
+            }
+
+            /* if they ack our SYN, we can move on to ESTABLISHED */
+            if (packet_flags & PKT_ACK) {
+                if (header->ack_num != s->tx_win_low) {
+                    goto send_reset;
+                }
+
+                s->tx_win_high = s->tx_win_low + header->win_size;
+                s->tx_highest_seq = s->tx_win_low;
+
+                s->state = STATE_ESTABLISHED;
+            } else {
+                goto send_reset;
+            }
+
+            break;
+
+        case STATE_ESTABLISHED:
+            if (packet_flags & PKT_ACK) {
+                /* they're acking us */
+                handle_ack(s, header->ack_num, header->win_size);
+            }
+
+            if (data_len > 0) {
+                LTRACEF("new data, len %zu\n", data_len);
+                handle_data(s, p->data, p->dlen, header->seq_num);
+            }
+
+            if ((packet_flags & PKT_FIN) && SEQUENCE_GTE(s->rx_win_low, highest_sequence)) {
+                /* they're closing with us, and there's no outstanding data */
+
+                /* FIN consumed a sequence */
+                s->rx_win_low++;
+
+                /* ack them and transition to new state */
+                send_ack(s);
+                s->state = STATE_CLOSE_WAIT;
+
+                /* wake up any read waiters */
+                event_signal(&s->rx_event, true);
+            }
+            break;
+
+        case STATE_CLOSE_WAIT:
+            if (packet_flags & PKT_ACK) {
+                /* they're acking us */
+                handle_ack(s, header->ack_num, header->win_size);
+            }
+            if (packet_flags & PKT_FIN) {
+                /* they must have missed our ack, ack them again */
+                send_ack(s);
+            }
+            break;
+        case STATE_LAST_ACK:
+            if (packet_flags & PKT_ACK) {
+                /* they're acking our FIN, probably */
+                tcp_remote_close(s);
+
+                /* tcp_close() was already called on us, remove us from the list and drop the ref */
+                remove_socket_from_list(s);
+                dec_socket_ref(s);
+            }
+            break;
+        case STATE_FIN_WAIT_1:
+            if (packet_flags & PKT_ACK) {
+                /* they're acking our FIN, probably */
+                s->state = STATE_FIN_WAIT_2;
+                /* drop into fin_wait_2 state logic, in case they were FINning us too */
+                goto fin_wait_2;
+            } else if (packet_flags & PKT_FIN) {
+                /* simultaneous close. they finned us without acking our fin */
+                s->rx_win_low++;
+                send_ack(s);
+                s->state = STATE_CLOSING;
+            }
+            break;
+        case STATE_FIN_WAIT_2:
+fin_wait_2:
+            if (packet_flags & PKT_FIN) {
+                /* they're FINning us, ack them */
+                s->rx_win_low++;
+                send_ack(s);
+                s->state = STATE_TIME_WAIT;
+
+                /* set timed wait timer */
+                tcp_timer_set(s, &s->time_wait_timer, &handle_time_wait_timeout, TIME_WAIT_TIMEOUT);
+            }
+            break;
+        case STATE_CLOSING:
+            if (packet_flags & PKT_ACK) {
+                /* they're acking our FIN, probably */
+                s->state = STATE_TIME_WAIT;
+
+                /* set timed wait timer */
+                tcp_timer_set(s, &s->time_wait_timer, &handle_time_wait_timeout, TIME_WAIT_TIMEOUT);
+            }
+            break;
+        case STATE_TIME_WAIT:
+            /* /dev/null of packets */
+            break;
+
+        case STATE_SYN_SENT:
+            PANIC_UNIMPLEMENTED;
+    }
+
+done:
+    mutex_release(&s->lock);
+    dec_socket_ref(s);
+    return;
+
+send_reset:
+    if (s) {
+        mutex_release(&s->lock);
+        dec_socket_ref(s);
+    }
+
+    LTRACEF("SEND RST\n");
+    if (!(packet_flags & PKT_RST)) {
+        tcp_send(src_ip, header->source_port, dst_ip, header->dest_port,
+            NULL, 0, PKT_RST, NULL, 0, 0, header->ack_num, 0);
+    }
+}
+
+static void handle_data(tcp_socket_t *s, const void *data, size_t len, uint32_t sequence)
+{
+    if (unlikely(tcp_debug))
+        TRACEF("data %p, len %zu, sequence %u\n", data, len, sequence);
+
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+    DEBUG_ASSERT(data);
+    DEBUG_ASSERT(len > 0);
+
+    /* see if it matches our current window */
+    uint32_t sequence_top = sequence + len - 1;
+    if (SEQUENCE_LTE(sequence, s->rx_win_low) && SEQUENCE_GTE(sequence_top, s->rx_win_low)) {
+        /* it intersects the bottom of our window, so it's in order */
+
+        /* copy the data we need to our cbuf */
+        size_t offset = sequence - s->rx_win_low;
+        size_t copy_len = MIN(s->rx_win_high - s->rx_win_low, len - offset);
+
+        DEBUG_ASSERT(offset < len);
+
+        LTRACEF("copying from offset %zu, len %zu\n", offset, copy_len);
+
+        s->rx_win_low += copy_len;
+
+        cbuf_write(&s->rx_buffer, (uint8_t *)data + offset, copy_len, false);
+        event_signal(&s->rx_event, true);
+
+        /* keep a counter if they've been sending a full mss */
+        if (copy_len >= s->mss) {
+            s->rx_full_mss_count++;
+        } else {
+            s->rx_full_mss_count = 0;
+        }
+
+        /* immediately ack if we're more than halfway into our buffer or they've sent 2 or more full packets */
+        if (s->rx_full_mss_count >= 2 ||
+            (int)(s->rx_win_low + s->rx_win_size - s->rx_win_high) > (int)s->rx_win_size / 2) {
+            send_ack(s);
+            s->rx_full_mss_count = 0;
+        } else {
+            tcp_timer_set(s, &s->ack_delay_timer, &handle_delayed_ack_timeout, DELAYED_ACK_TIMEOUT);
+        }
+    } else {
+        // either out of order or completely out of our window, drop
+        // duplicately ack the last thing we really got
+        send_ack(s);
+    }
+}
+
+static status_t tcp_socket_send(tcp_socket_t *s, const void *data, size_t len, tcp_flags_t flags, 
+    const void *options, size_t options_length, uint32_t sequence)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+    DEBUG_ASSERT(len == 0 || data);
+    DEBUG_ASSERT(options_length == 0 || options);
+    DEBUG_ASSERT((options_length % 4) == 0);
+
+    // calculate the new right edge of the rx window
+    uint32_t rx_win_high = s->rx_win_low + s->rx_win_size - cbuf_space_used(&s->rx_buffer) - 1;
+
+    LTRACEF("rx_win_low %u rx_win_size %u read_buf_len %zu, new win high %u\n",
+        s->rx_win_low, s->rx_win_size, cbuf_space_used(&s->rx_buffer), rx_win_high);
+
+    uint16_t win_size;
+    if (SEQUENCE_GTE(rx_win_high, s->rx_win_high)) {
+        s->rx_win_high = rx_win_high;
+        win_size = rx_win_high - s->rx_win_low;
+    } else {
+        // the window size has shrunk, but we can't move the
+        // right edge of the window backwards
+        win_size = s->rx_win_high - s->rx_win_low;
+    }
+
+    // we are piggybacking a pending ACK, so clear the delayed ACK timer
+    if (flags & PKT_ACK) {
+        tcp_timer_cancel(s, &s->ack_delay_timer);
+    }
+
+    status_t err = tcp_send(s->remote_ip, s->remote_port, s->local_ip, s->local_port, data, len, flags,
+            options, options_length, (flags & PKT_ACK) ? s->rx_win_low : 0, sequence, win_size);
+
+    return err;
+}
+
+static void send_ack(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+
+    if (s->state != STATE_ESTABLISHED && s->state != STATE_CLOSE_WAIT && s->state != STATE_FIN_WAIT_2)
+        return;
+
+    tcp_socket_send(s, NULL, 0, PKT_ACK, NULL, 0, s->tx_win_low);
+}
+
+static status_t tcp_send(ipv4_addr dest_ip, uint16_t dest_port, ipv4_addr src_ip, uint16_t src_port, const void *buf,
+    size_t len, tcp_flags_t flags, const void *options, size_t options_length, uint32_t ack, uint32_t sequence, uint16_t window_size)
+{
+    DEBUG_ASSERT(len == 0 || buf);
+    DEBUG_ASSERT(options_length == 0 || options);
+    DEBUG_ASSERT((options_length % 4) == 0);
+
+    pktbuf_t *p = pktbuf_alloc();
+    if (!p)
+        return ERR_NO_MEMORY;
+
+    tcp_header_t *header = pktbuf_prepend(p, sizeof(tcp_header_t) + options_length);
+    DEBUG_ASSERT(header);
+
+    /* fill in the header */
+    header->source_port = htons(src_port);
+    header->dest_port = htons(dest_port);
+    header->seq_num = htonl(sequence);
+    header->ack_num = htonl(ack);
+    header->length_flags = htons(((sizeof(tcp_header_t) + options_length) / 4) << 12 | flags);
+    header->win_size = htons(window_size);
+    header->checksum = 0;
+    header->urg_pointer = 0;
+    if (options)
+        memcpy(header + 1, options, options_length);
+
+    /* append the data */
+    if (len > 0)
+        pktbuf_append_data(p, buf, len);
+
+    /* compute the checksum */
+    /* XXX get the tx ckecksum capability from the nic */
+    if (FORCE_TCP_CHECKSUM || true) {
+        tcp_pseudo_header_t pheader;
+        pheader.source_addr = src_ip;
+        pheader.dest_addr = dest_ip;
+        pheader.zero = 0;
+        pheader.protocol = IP_PROTO_TCP;
+        pheader.tcp_length = htons(p->dlen);
+
+        header->checksum = cksum_pheader(&pheader, p->data, p->dlen);
+    }
+
+    if (LOCAL_TRACE) {
+        printf("sending ");
+        dump_tcp_header(header);
+    }
+
+    status_t err = minip_ipv4_send(p, dest_ip, IP_PROTO_TCP);
+
+    return err;
+}
+
+static void handle_ack(tcp_socket_t *s, uint32_t sequence, uint32_t win_size)
+{
+    LTRACEF("socket %p ack sequence %u, win_size %u\n", s, sequence, win_size);
+
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+
+    LTRACEF("s %p, tx_win_low %u tx_win_high %u tx_highest_seq %u bufsize %u offset %u\n",
+            s, s->tx_win_low, s->tx_win_high, s->tx_highest_seq, s->tx_buffer_size, s->tx_buffer_offset);
+    if (SEQUENCE_LTE(sequence, s->tx_win_low)) {
+        /* they're acking stuff we've already received an ack for */
+        return;
+    } else if (SEQUENCE_GT(sequence, s->tx_highest_seq)) {
+        /* they're acking stuff we haven't sent */
+        return;
+    } else {
+        /* their ack is somewhere in our window */
+        uint32_t acked_len;
+
+        acked_len = (sequence - s->tx_win_low);
+
+        LTRACEF("acked len %u\n", acked_len);
+
+        DEBUG_ASSERT(acked_len <= s->tx_buffer_size);
+        DEBUG_ASSERT(acked_len <= s->tx_buffer_offset);
+
+        memmove(s->tx_buffer, s->tx_buffer + acked_len, s->tx_buffer_offset - acked_len);
+
+        s->tx_buffer_offset -= acked_len;
+        s->tx_win_low += acked_len;
+        s->tx_win_high = s->tx_win_low + win_size;
+
+        /* cancel or reset our retransmit timer */
+        if (s->tx_win_low == s->tx_highest_seq) {
+            tcp_timer_cancel(s, &s->retransmit_timer);
+        } else {
+            tcp_timer_set(s, &s->retransmit_timer, &handle_retransmit_timeout, RETRANSMIT_TIMEOUT);
+        }
+
+        /* we have opened the transmit buffer */
+        event_signal(&s->tx_event, true);
+    }
+}
+
+static ssize_t tcp_write_pending_data(tcp_socket_t *s)
+{
+    LTRACEF("s %p, tx_win_low %u tx_win_high %u tx_highest_seq %u bufsize %u offset %u\n",
+            s, s->tx_win_low, s->tx_win_high, s->tx_highest_seq, s->tx_buffer_size, s->tx_buffer_offset);
+
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+    DEBUG_ASSERT(s->tx_buffer_size > 0);
+    DEBUG_ASSERT(s->tx_buffer_offset <= s->tx_buffer_size);
+
+    /* do we have any new data to send? */
+    uint32_t outstanding = (s->tx_highest_seq - s->tx_win_low);
+    uint32_t pending = s->tx_buffer_offset - outstanding;
+    LTRACEF("outstanding %u, pending %u\n", outstanding, pending);
+
+    /* send packets that cover the pending area of the window */
+    uint32_t offset = 0;
+    while (offset < pending) {
+        uint32_t tosend = MIN(s->mss, pending - offset);
+
+        tcp_socket_send(s, s->tx_buffer + outstanding + offset, tosend, PKT_ACK|PKT_PSH, NULL, 0, s->tx_highest_seq);
+        s->tx_highest_seq += tosend;
+        offset += tosend;
+    }
+
+    /* reset the retransmit timer if we sent anything */
+    if (offset > 0) {
+        tcp_timer_set(s, &s->retransmit_timer, &handle_retransmit_timeout, RETRANSMIT_TIMEOUT);
+    }
+
+    return offset;
+}
+
+static ssize_t tcp_retransmit(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+
+    if (s->state != STATE_ESTABLISHED && s->state != STATE_CLOSE_WAIT)
+        return 0;
+
+    /* how much data have we sent but not gotten an ack for? */
+    uint32_t outstanding = (s->tx_highest_seq - s->tx_win_low);
+    if (outstanding == 0)
+        return 0;
+
+    uint32_t tosend = MIN(s->mss, outstanding);
+
+    LTRACEF("s %p, tosend %u seq %u\n", s, tosend, s->tx_win_low);
+    tcp_socket_send(s, s->tx_buffer, tosend, PKT_ACK|PKT_PSH, NULL, 0, s->tx_win_low);
+
+    return tosend;
+}
+
+static void handle_retransmit_timeout(void *_s)
+{
+    tcp_socket_t *s = _s;
+
+    LTRACEF("s %p\n", s);
+
+    DEBUG_ASSERT(s);
+
+    mutex_acquire(&s->lock);
+
+    if (tcp_retransmit(s) == 0)
+        goto done;
+
+    tcp_timer_set(s, &s->retransmit_timer, &handle_retransmit_timeout, RETRANSMIT_TIMEOUT);
+
+done:
+    mutex_release(&s->lock);
+    dec_socket_ref(s);
+}
+
+static void handle_delayed_ack_timeout(void *_s)
+{
+    tcp_socket_t *s = _s;
+
+    LTRACEF("s %p\n", s);
+
+    DEBUG_ASSERT(s);
+
+    mutex_acquire(&s->lock);
+    send_ack(s);
+    mutex_release(&s->lock);
+    dec_socket_ref(s);
+}
+
+static void handle_time_wait_timeout(void *_s)
+{
+    tcp_socket_t *s = _s;
+
+    LTRACEF("s %p\n", s);
+
+    DEBUG_ASSERT(s);
+
+    mutex_acquire(&s->lock);
+
+    DEBUG_ASSERT(s->state == STATE_TIME_WAIT);
+
+    /* remove us from the list and drop the last ref */
+    remove_socket_from_list(s);
+    dec_socket_ref(s);
+
+    mutex_release(&s->lock);
+    dec_socket_ref(s);
+}
+
+static void tcp_wakeup_waiters(tcp_socket_t *s)
+{
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+
+    // wake up any waiters
+    event_signal(&s->rx_event, true);
+    event_signal(&s->tx_event, true);
+}
+
+static void tcp_remote_close(tcp_socket_t *s)
+{
+    LTRACEF("s %p, ref %d\n", s, s->ref);
+
+    DEBUG_ASSERT(s);
+    DEBUG_ASSERT(is_mutex_held(&s->lock));
+    DEBUG_ASSERT(s->ref > 0);
+
+    if (s->state == STATE_CLOSED)
+        return;
+
+    s->state = STATE_CLOSED;
+
+    tcp_timer_cancel(s, &s->retransmit_timer);
+    tcp_timer_cancel(s, &s->ack_delay_timer);
+
+    tcp_wakeup_waiters(s);
+}
+
+static tcp_socket_t *create_tcp_socket(bool alloc_buffers)
+{
+    tcp_socket_t *s;
+
+    s = calloc(1, sizeof(tcp_socket_t));
+    if (!s)
+        return NULL;
+
+    mutex_init(&s->lock);
+    s->ref = 1; // start with the ref already bumped
+
+    s->state = STATE_CLOSED;
+    s->rx_win_size = DEFAULT_RX_WINDOW_SIZE;
+    event_init(&s->rx_event, false, 0);
+
+    s->mss = DEFAULT_MSS;
+
+    s->tx_win_low = rand();
+    s->tx_win_high = s->tx_win_low;
+    s->tx_highest_seq = s->tx_win_low;
+    event_init(&s->tx_event, true, 0);
+
+    if (alloc_buffers) {
+        // XXX check for error
+        s->rx_buffer_raw = malloc(s->rx_win_size);
+        cbuf_initialize_etc(&s->rx_buffer, s->rx_win_size, s->rx_buffer_raw);
+
+        s->tx_buffer_size = DEFAULT_TX_BUFFER_SIZE;
+        s->tx_buffer = malloc(s->tx_buffer_size);
+    }
+
+    sem_init(&s->accept_sem, 0);
+
+    return s;
+}
+
+/* user api */
+
+status_t tcp_open_listen(tcp_socket_t **handle, uint16_t port)
+{
+    tcp_socket_t *s;
+
+    if (!handle)
+        return ERR_INVALID_ARGS;
+
+    s = create_tcp_socket(false);
+    if (!s)
+        return ERR_NO_MEMORY;
+
+    // XXX see if there's another listen socket already on this port
+
+    s->local_port = port;
+
+    /* go to listen state */
+    s->state = STATE_LISTEN;
+
+    add_socket_to_list(s);
+
+    *handle = s;
+
+    return NO_ERROR;
+}
+
+status_t tcp_accept_timeout(tcp_socket_t *listen_socket, tcp_socket_t **accept_socket, lk_time_t timeout)
+{
+    if (!listen_socket || !accept_socket)
+        return ERR_INVALID_ARGS;
+
+    tcp_socket_t *s = listen_socket;
+    inc_socket_ref(s);
+
+    /* block to accept a socket for an amount of time */
+    if (sem_timedwait(&s->accept_sem, timeout) == ERR_TIMED_OUT) {
+        dec_socket_ref(s);
+        return ERR_TIMED_OUT;
+    }
+
+    mutex_acquire(&s->lock);
+
+    /* we got here, grab the accepted socket and return */
+    DEBUG_ASSERT(s->accepted);
+    *accept_socket = s->accepted;
+    s->accepted = NULL;
+
+    mutex_release(&s->lock);
+    dec_socket_ref(s);
+
+    return NO_ERROR;
+}
+
+ssize_t tcp_read(tcp_socket_t *socket, void *buf, size_t len)
+{
+    LTRACEF("socket %p, buf %p, len %zu\n", socket, buf, len);
+    if (!socket)
+        return ERR_INVALID_ARGS;
+    if (len == 0)
+        return 0;
+    if (!buf)
+        return ERR_INVALID_ARGS;
+
+    tcp_socket_t *s = socket;
+    inc_socket_ref(s);
+
+    ssize_t ret = 0;
+retry:
+    /* block on available data */
+    event_wait(&s->rx_event);
+
+    mutex_acquire(&s->lock);
+
+    /* try to read some data from the receive buffer, even if we're closed */
+    ret = cbuf_read(&s->rx_buffer, buf, len, false);
+    if (ret == 0) {
+        /* check to see if we've closed */
+        if (s->state != STATE_ESTABLISHED) {
+            ret = ERR_CHANNEL_CLOSED;
+            goto out;
+        }
+
+        /* we must have raced with another thread */
+        event_unsignal(&s->rx_event);
+        mutex_release(&s->lock);
+        goto retry;
+    }
+
+    /* if we've used up the last byte in the read buffer, unsignal the read event */
+    size_t remaining_bytes = cbuf_space_used(&s->rx_buffer);
+    if (s->state == STATE_ESTABLISHED && remaining_bytes == 0) {
+        event_unsignal(&s->rx_event);
+    }
+
+    /* we've read something, make sure the other end knows that our window is opening */
+    uint32_t new_rx_win_size = s->rx_win_size - remaining_bytes;
+
+    /* if we've opened it enough, send an ack */
+    if (new_rx_win_size >= s->mss && s->rx_win_high - s->rx_win_low < s->mss)
+        send_ack(s);
+
+out:
+    mutex_release(&s->lock);
+    dec_socket_ref(s);
+
+    return ret;
+}
+
+ssize_t tcp_write(tcp_socket_t *socket, const void *buf, size_t len)
+{
+    LTRACEF("socket %p, buf %p, len %zu\n", socket, buf, len);
+    if (!socket)
+        return ERR_INVALID_ARGS;
+    if (len == 0)
+        return 0;
+    if (!buf)
+        return ERR_INVALID_ARGS;
+
+    tcp_socket_t *s = socket;
+    inc_socket_ref(s);
+
+    size_t off = 0;
+    while (off < len) {
+        LTRACEF("off %zu, len %zu\n", off, len);
+
+        /* wait for the tx buffer to open up */
+        event_wait(&s->tx_event);
+        LTRACEF("after event_wait\n");
+
+        mutex_acquire(&s->lock);
+
+        /* check to see if we've closed */
+        if (s->state != STATE_ESTABLISHED && s->state != STATE_CLOSE_WAIT) {
+            mutex_release(&s->lock);
+            dec_socket_ref(s);
+            return ERR_CHANNEL_CLOSED;
+        }
+
+        DEBUG_ASSERT(s->tx_buffer_size > 0);
+        DEBUG_ASSERT(s->tx_buffer_offset <= s->tx_buffer_size);
+
+        /* figure out how much data to copy in */
+        size_t to_copy = MIN(s->tx_buffer_size - s->tx_buffer_offset, len - off);
+        if (to_copy == 0) {
+            mutex_release(&s->lock);
+            continue;
+        }
+
+        memcpy(s->tx_buffer + s->tx_buffer_offset, (uint8_t *)buf + off, to_copy);
+        s->tx_buffer_offset += to_copy;
+
+        /* if this has completely filled it, unsignal the event */
+        DEBUG_ASSERT(s->tx_buffer_offset <= s->tx_buffer_size);
+        if (s->tx_buffer_offset == s->tx_buffer_size) {
+            event_unsignal(&s->tx_event);
+        }
+
+        /* send as much data as we can */
+        tcp_write_pending_data(s);
+
+        off += to_copy;
+
+        mutex_release(&s->lock);
+    }
+
+    dec_socket_ref(s);
+    return len;
+}
+
+status_t tcp_close(tcp_socket_t *socket)
+{
+    if (!socket)
+        return ERR_INVALID_ARGS;
+
+    tcp_socket_t *s = socket;
+
+    inc_socket_ref(s);
+    mutex_acquire(&s->lock);
+
+    LTRACEF("socket %p, state %d (%s), ref %d\n", s, s->state, tcp_state_to_string(s->state), s->ref);
+
+    status_t err;
+    switch (s->state) {
+        case STATE_CLOSED:
+        case STATE_LISTEN:
+            /* we can directly remove this socket */
+            remove_socket_from_list(s);
+
+            /* drop any timers that may be pending on this */
+            tcp_timer_cancel(s, &s->ack_delay_timer);
+            tcp_timer_cancel(s, &s->retransmit_timer);
+
+            s->state = STATE_CLOSED;
+
+            /* drop the extra ref that was held when the socket was created */
+            dec_socket_ref(s);
+            break;
+        case STATE_SYN_RCVD:
+        case STATE_ESTABLISHED:
+            s->state = STATE_FIN_WAIT_1;
+            tcp_socket_send(s, NULL, 0, PKT_ACK|PKT_FIN, NULL, 0, s->tx_win_low);
+            s->tx_win_low++;
+
+            /* stick around and wait for them to FIN us */
+            break;
+        case STATE_CLOSE_WAIT:
+            s->state = STATE_LAST_ACK;
+            tcp_socket_send(s, NULL, 0, PKT_ACK|PKT_FIN, NULL, 0, s->tx_win_low);
+            s->tx_win_low++;
+
+            // XXX set up fin retransmit timer here
+            break;
+        case STATE_FIN_WAIT_1:
+        case STATE_FIN_WAIT_2:
+        case STATE_CLOSING:
+        case STATE_TIME_WAIT:
+        case STATE_LAST_ACK:
+            /* these states are all post tcp_close(), so it's invalid to call it here */
+            err = ERR_CHANNEL_CLOSED;
+            goto out;
+        default:
+            PANIC_UNIMPLEMENTED;
+    }
+
+    /* make sure anyone blocked on this wakes up */
+    tcp_wakeup_waiters(s);
+
+    mutex_release(&s->lock);
+
+    err = NO_ERROR;
+
+out:
+    /* if this was the last ref, it should destroy the socket */
+    dec_socket_ref(s);
+
+    return err;
+}
+
+/* debug stuff */
+static int cmd_tcp(int argc, const cmd_args *argv)
+{
+    status_t err;
+
+    if (argc < 2) {
+notenoughargs:
+        printf("ERROR not enough arguments\n");
+usage:
+        printf("usage: %s sockets\n", argv[0].str);
+        printf("usage: %s listenclose <port>\n", argv[0].str);
+        printf("usage: %s listen <port>\n", argv[0].str);
+        printf("usage: %s debug\n", argv[0].str);
+        return ERR_INVALID_ARGS;
+    }
+
+    if (!strcmp(argv[1].str, "sockets")) {
+
+        mutex_acquire(&tcp_socket_list_lock);
+        tcp_socket_t *s = NULL;
+        list_for_every_entry(&tcp_socket_list, s, tcp_socket_t, node) {
+            dump_socket(s);
+        }
+        mutex_release(&tcp_socket_list_lock);
+    } else if (!strcmp(argv[1].str, "listenclose")) {
+        /* listen for a connection, accept it, then immediately close it */
+        if (argc < 3) goto notenoughargs;
+
+        tcp_socket_t *handle;
+
+        err = tcp_open_listen(&handle, argv[2].u);
+        printf("tcp_open_listen returns %d, handle %p\n", err, handle);
+
+        tcp_socket_t *accepted;
+        err = tcp_accept(handle, &accepted);
+        printf("tcp_accept returns returns %d, handle %p\n", err, accepted);
+
+        err = tcp_close(accepted);
+        printf("tcp_close returns %d\n", err);
+
+        err = tcp_close(handle);
+        printf("tcp_close returns %d\n", err);
+    } else if (!strcmp(argv[1].str, "listen")) {
+        if (argc < 3) goto notenoughargs;
+
+        tcp_socket_t *handle;
+
+        err = tcp_open_listen(&handle, argv[2].u);
+        printf("tcp_open_listen returns %d, handle %p\n", err, handle);
+
+        tcp_socket_t *accepted;
+        err = tcp_accept(handle, &accepted);
+        printf("tcp_accept returns returns %d, handle %p\n", err, accepted);
+
+        for (;;) {
+            uint8_t buf[512];
+
+            ssize_t err = tcp_read(accepted, buf, sizeof(buf));
+            printf("tcp_read returns %ld\n", err);
+            if (err < 0)
+                break;
+            if (err > 0) {
+                hexdump8(buf, err);
+            }
+
+            err = tcp_write(accepted, buf, err);
+            printf("tcp_write returns %ld\n", err);
+            if (err < 0)
+                break;
+        }
+
+        err = tcp_close(accepted);
+        printf("tcp_close returns %d\n", err);
+
+        err = tcp_close(handle);
+        printf("tcp_close returns %d\n", err);
+    } else if (!strcmp(argv[1].str, "debug")) {
+        tcp_debug = !tcp_debug;
+        printf("tcp debug now %u\n", tcp_debug);
+    } else {
+        printf("ERROR unknown command\n");
+        goto usage;
+    }
+
+    return NO_ERROR;
+}
+
+STATIC_COMMAND_START
+STATIC_COMMAND("tcp", "tcp commands", &cmd_tcp)
+STATIC_COMMAND_END(tcp);
+
+
+// vim: set ts=4 sw=4 expandtab:
diff --git a/src/bsp/lk/lib/minip/udp.c b/src/bsp/lk/lib/minip/udp.c
new file mode 100644
index 0000000..08e10b3
--- /dev/null
+++ b/src/bsp/lk/lib/minip/udp.c
@@ -0,0 +1,208 @@
+/*
+ * Copyright (c) 2014 Chris Anderson
+ * Copyright (c) 2014 Brian Swetland
+ * Copyright (c) 2015 Nathaniel Quillin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files
+ * (the "Software"), to deal in the Software without restriction,
+ * including without limitation the rights to use, copy, modify, merge,
+ * publish, distribute, sublicense, and/or sell copies of the Software,
+ * and to permit persons to whom the Software is furnished to do so,
+ * subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#include "minip-internal.h"
+
+#include <err.h>
+#include <errno.h>
+#include <iovec.h>
+#include <list.h>
+#include <malloc.h>
+#include <stdint.h>
+#include <trace.h>
+
+#define LOCAL_TRACE 0
+
+static struct list_node udp_list = LIST_INITIAL_VALUE(udp_list);
+
+struct udp_listener {
+    struct list_node list;
+    uint16_t port;
+    udp_callback_t callback;
+    void *arg;
+};
+
+typedef struct udp_socket {
+    uint32_t host;
+    uint16_t sport;
+    uint16_t dport;
+    const uint8_t *mac;
+} udp_socket_t;
+
+typedef struct udp_hdr {
+    uint16_t src_port;
+    uint16_t dst_port;
+    uint16_t len;
+    uint16_t chksum;
+} __PACKED udp_hdr_t;
+
+
+int udp_listen(uint16_t port, udp_callback_t cb, void *arg) {
+    struct udp_listener *entry, *temp;
+
+    list_for_every_entry_safe(&udp_list, entry, temp, struct udp_listener, list) {
+        if (entry->port == port) {
+            if (cb == NULL) {
+                list_delete(&entry->list);
+                return 0;
+            }
+            return -1;
+        }
+    }
+
+    if ((entry = malloc(sizeof(struct udp_listener))) == NULL) {
+        return -1;
+    }
+
+    entry->port = port;
+    entry->callback = cb;
+    entry->arg = arg;
+
+    list_add_tail(&udp_list, &entry->list);
+
+    return 0;
+}
+
+status_t udp_open(uint32_t host, uint16_t sport, uint16_t dport, udp_socket_t **handle)
+{
+    LTRACEF("host %u.%u.%u.%u sport %u dport %u handle %p\n",
+           IPV4_SPLIT(host), sport, dport, handle);
+    udp_socket_t *socket;
+    const uint8_t *dst_mac;
+
+    if (handle == NULL) {
+        return -EINVAL;
+    }
+
+    socket = (udp_socket_t *) malloc(sizeof(udp_socket_t));
+    if (!socket) {
+        return -ENOMEM;
+    }
+
+    dst_mac = arp_get_dest_mac(host);
+    if (dst_mac == NULL) {
+        free(socket);
+        return -EHOSTUNREACH;
+    }
+
+    socket->host = host;
+    socket->sport = sport;
+    socket->dport = dport;
+    socket->mac = dst_mac;
+
+    *handle = socket;
+
+    return NO_ERROR;
+}
+
+status_t udp_close(udp_socket_t *handle)
+{
+    if (handle == NULL) {
+        return -EINVAL;
+    }
+
+    free(handle);
+    return NO_ERROR;
+}
+
+status_t udp_send_iovec(const iovec_t *iov, uint iov_count, udp_socket_t *handle)
+{
+    pktbuf_t *p;
+    struct eth_hdr *eth;
+    struct ipv4_hdr *ip;
+    udp_hdr_t *udp;
+    status_t ret = NO_ERROR;
+    void *buf;
+    ssize_t len;
+
+    if (handle == NULL || iov == NULL || iov_count == 0) {
+        return -EINVAL;
+    }
+
+    if ((p = pktbuf_alloc()) == NULL) {
+        return -ENOMEM;
+    }
+
+    len = iovec_size(iov, iov_count);
+
+    buf = pktbuf_append(p, len);
+    udp = pktbuf_prepend(p, sizeof(udp_hdr_t));
+    ip = pktbuf_prepend(p, sizeof(struct ipv4_hdr));
+    eth = pktbuf_prepend(p, sizeof(struct eth_hdr));
+
+    iovec_to_membuf(buf, len, iov, iov_count, 0);
+
+    udp->src_port   = htons(handle->sport);
+    udp->dst_port   = htons(handle->dport);
+    udp->len        = htons(sizeof(udp_hdr_t) + len);
+    udp->chksum     = 0;
+
+    minip_build_mac_hdr(eth, handle->mac, ETH_TYPE_IPV4);
+    minip_build_ipv4_hdr(ip, handle->host, IP_PROTO_UDP, len + sizeof(udp_hdr_t));
+
+#if (MINIP_USE_UDP_CHECKSUM != 0)
+    udp->chksum = rfc768_chksum(ip, udp);
+#endif
+
+    minip_tx_handler(p);
+
+    return ret;
+}
+
+status_t udp_send(void *buf, size_t len, udp_socket_t *handle)
+{
+    iovec_t iov;
+
+    LTRACEF("buf %p, len %zu, handle %p\n", buf, len, handle);
+
+    if (buf == NULL || len == 0) {
+        return -EINVAL;
+    }
+
+    iov.iov_base = buf;
+    iov.iov_len = len;
+
+    return udp_send_iovec(&iov, 1, handle);
+}
+
+void udp_input(pktbuf_t *p, uint32_t src_ip)
+{
+    udp_hdr_t *udp;
+    struct udp_listener *e;
+    uint16_t port;
+
+    if ((udp = pktbuf_consume(p, sizeof(udp_hdr_t))) == NULL) {
+        return;
+    }
+
+    port = ntohs(udp->dst_port);
+
+    list_for_every_entry(&udp_list, e, struct udp_listener, list) {
+        if (e->port == port) {
+            e->callback(p->data, p->dlen, src_ip, ntohs(udp->src_port), e->arg);
+            return;
+        }
+    }
+}