net: Switch to the "connection" interface and get rid of uip app callbacks.

This change also loosens up the criteria used to select a UDP connection to
service incoming data. Before it required that the sending port matched the
remote port on the server. When the server switches over to an ephemeral port
after accepting a connection, the remote port and the source ports will no
longer match. If the packet is thrown out, then the ipv4 code won't have a
chance to update its remote port to match the new source port.

Change-Id: I8d78aa4ecc03949148bc5a114da47687af4c208d
diff --git a/src/net/ipv4/ipv4.c b/src/net/ipv4/ipv4.c
index bbaadd2..93c997b 100644
--- a/src/net/ipv4/ipv4.c
+++ b/src/net/ipv4/ipv4.c
@@ -21,15 +21,163 @@
  */
 
 #include <assert.h>
+#include <endian.h>
 #include <string.h>
 
+#include "base/container_of.h"
+#include "base/die.h"
 #include "base/init_funcs.h"
 #include "base/list.h"
+#include "base/xalloc.h"
 #include "net/ethernet.h"
+#include "net/ipv4/ipv4.h"
 #include "net/ipv4/uip/arp.h"
+#include "net/ipv4/uip/udp/packet.h"
 #include "net/ipv4/uip/uip.h"
 #include "net/net.h"
 
+
+ListNode ipv4_udp_connections;
+
+
+static int ipv4_udp_con_init(Ipv4UdpCon *con)
+{
+	con->uip_con = uip_udp_new(&con->remote_ip, htonw(con->port));
+	if (!con->uip_con) {
+		printf("Failed to set up UDP connection.\n");
+		return 1;
+	}
+	list_insert_after(&con->list_node, &ipv4_udp_connections);
+	return 0;
+}
+
+static int ipv4_udp_bind(NetConOps *me, uint16_t port)
+{
+	Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
+	if (!con->uip_con && ipv4_udp_con_init(con))
+		return 1;
+	uip_udp_bind(con->uip_con, htonw(port));
+	return 0;
+}
+
+static int ipv4_udp_incoming(NetConOps *me, size_t *size)
+{
+	Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
+	if (!con->uip_con && ipv4_udp_con_init(con))
+		return 1;
+
+	*size = con->incoming_count;
+
+	// Poke the device to receive any data it has.
+	if (!*size) {
+		net_service_dev(con->dev);
+		*size = con->incoming_count;
+	}
+
+	return 0;
+}
+
+static int ipv4_udp_send(NetConOps *me, const void *data, size_t len)
+{
+	Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
+	if (!con->uip_con && ipv4_udp_con_init(con))
+		return 1;
+
+	uip_udp_packet_send(con->dev, con->uip_con, data, len);
+	return 0;
+}
+
+static int ipv4_udp_receive(NetConOps *me, void *data, size_t *len,
+			    size_t max_len)
+{
+	Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
+	if (!con->uip_con && ipv4_udp_con_init(con))
+		return 1;
+
+	size_t size;
+	if (me->incoming(me, &size))
+		return 1;
+
+	if (!size) {
+		printf("No data to receive.\n");
+		return 1;
+	} else if (size > max_len) {
+		printf("Insufficient space: putting %zd bytes in %zd bytes.\n",
+		       size, max_len);
+		return 1;
+	}
+
+	*len = size;
+	if (data)
+		memcpy(data, con->incoming, size);
+	con->incoming_count = 0;
+	return 0;
+}
+
+static int ipv4_udp_close(NetConOps *me)
+{
+	Ipv4UdpCon *con = container_of(me, Ipv4UdpCon, ops);
+	if (!con->uip_con && ipv4_udp_con_init(con))
+		return 1;
+	list_remove(&con->list_node);
+	uip_udp_remove(con->uip_con);
+	con->uip_con = NULL;
+	return 0;
+}
+
+Ipv4UdpCon *new_ipv4_udp_con(NetDevice *dev, uip_ipaddr_t remote_ip,
+			     uint16_t port)
+{
+	Ipv4UdpCon *con = xzalloc(sizeof(*con));
+
+	con->ops.bind = &ipv4_udp_bind;
+	con->ops.incoming = &ipv4_udp_incoming;
+	con->ops.send = &ipv4_udp_send;
+	con->ops.receive = &ipv4_udp_receive;
+	con->ops.close = &ipv4_udp_close;
+
+	con->remote_ip = remote_ip;
+	con->port = port;
+	con->dev = dev;
+
+	return con;
+}
+
+
+void ipv4_uip_udp_callback(void)
+{
+	// Extract the destination port.
+	size_t header_size = sizeof(EtherHdr) + sizeof(struct uip_udpip_hdr);
+	if (uip_len < header_size)
+		return;
+	struct uip_udpip_hdr *hdr = (void *)&uip_buf[sizeof(EtherHdr)];
+
+	// Figure out who's data this is.
+	Ipv4UdpCon *con;
+	list_for_each(con, ipv4_udp_connections, list_node) {
+		if (con->uip_con->lport == hdr->destport) {
+			con->uip_con->rport = hdr->srcport;
+			if (con->incoming_count) {
+				printf("Already have data. Dropping.\n");
+				return;
+			}
+			if (uip_datalen() > sizeof(con->incoming)) {
+				printf("Too much data. Dropping.\n");
+				return;
+			}
+			con->incoming_count = uip_datalen();
+			memcpy(con->incoming, &uip_buf[header_size],
+			       con->incoming_count);
+			return;
+		}
+	}
+}
+
+void ipv4_uip_tcp_callback(void)
+{
+	die("TCP connections aren't supported.\n");
+}
+
 int ipv4_ipv4_process(EthTypeOps *me, NetDevice *dev, void *data, size_t len)
 {
 	assert(len <= CONFIG_UIP_BUFSIZE);
diff --git a/src/net/ipv4/ipv4.h b/src/net/ipv4/ipv4.h
new file mode 100644
index 0000000..6fb0faa
--- /dev/null
+++ b/src/net/ipv4/ipv4.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * See file CREDITS for list of people who contributed to this
+ * project.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but without any warranty; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ */
+
+#ifndef __NET_IPV4_H__
+#define __NET_IPV4_H__
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "base/list.h"
+#include "drivers/net/net.h"
+#include "net/connection.h"
+#include "net/ipv4/uip/uip.h"
+
+enum {
+	Ipv4IncomingBufferSize = 8 * 1024,
+};
+
+typedef struct {
+	NetConOps ops;
+
+	uip_ipaddr_t remote_ip;
+	uint16_t port;
+	NetDevice *dev;
+
+	ListNode list_node;
+	struct uip_udp_conn *uip_con;
+
+	uint8_t incoming[Ipv4IncomingBufferSize];
+	size_t incoming_count;
+} Ipv4UdpCon;
+
+Ipv4UdpCon *new_ipv4_udp_con(NetDevice *dev, uip_ipaddr_t remote_ip,
+			     uint16_t port);
+
+void ipv4_uip_udp_callback(void);
+void ipv4_uip_tcp_callback(void);
+
+#endif /* __NET_IPV4_H__ */
diff --git a/src/net/ipv4/uip/opt.h b/src/net/ipv4/uip/opt.h
index 8a50915..9c59282 100644
--- a/src/net/ipv4/uip/opt.h
+++ b/src/net/ipv4/uip/opt.h
@@ -53,8 +53,6 @@
 
 #include <stdint.h>
 
-#include "net/net.h"
-
 typedef uint32_t uip_stats_t;
 
 #if CONFIG_UIP_MAX_TCP_MSS
@@ -90,77 +88,5 @@
 #define UIP_BYTE_ORDER     (UIP_LITTLE_ENDIAN)
 #endif
 
-/** @} */
-/*------------------------------------------------------------------------------*/
-
-/**
- * \defgroup uipoptapp Application specific configurations
- * @{
- *
- * An uIP application is implemented using a single application
- * function that is called by uIP whenever a TCP/IP event occurs. The
- * name of this function must be registered with uIP at compile time
- * using the UIP_APPCALL definition.
- *
- * uIP applications can store the application state within the
- * uip_conn structure by specifying the type of the application
- * structure by typedef:ing the type uip_tcp_appstate_t and uip_udp_appstate_t.
- *
- * The file containing the definitions must be included in the
- * uipopt.h file.
- *
- * The following example illustrates how this can look.
- \code
-
- void httpd_appcall(void);
- #define UIP_APPCALL     httpd_appcall
-
- struct httpd_state {
- uint8_t state;
- uint16_t count;
- char *dataptr;
- char *script;
- };
- typedef struct httpd_state uip_tcp_appstate_t
- \endcode
-*/
-
-/**
- * \var #define UIP_APPCALL
- *
- * The name of the application function that uIP should call in
- * response to TCP/IP events.
- *
- */
-
-#define UIP_APPCALL net_call_callback
-#define UIP_UDP_APPCALL net_call_callback
-
-/**
- * \var typedef uip_tcp_appstate_t
- *
- * The type of the application state that is to be stored in the
- * uip_conn structure. This usually is typedef:ed to a struct holding
- * application state information.
- */
-
-typedef struct uip_tcp_appstate_t
-{
-} uip_tcp_appstate_t;
-
-/**
- * \var typedef uip_udp_appstate_t
- *
- * The type of the application state that is to be stored in the
- * uip_conn structure. This usually is typedef:ed to a struct holding
- * application state information.
- */
-/** @} */
-
-typedef struct uip_udp_appstate_t
-{
-} uip_udp_appstate_t;
-
 #endif /* __NET_UIP_OPT_H__ */
 /** @} */
-/** @} */
diff --git a/src/net/ipv4/uip/uip.c b/src/net/ipv4/uip/uip.c
index 5815d5d..4491a2a 100644
--- a/src/net/ipv4/uip/uip.c
+++ b/src/net/ipv4/uip/uip.c
@@ -71,10 +71,13 @@
  * the packet back to the peer.
 */
 
+#include <endian.h>
+#include <stdio.h>
 #include <string.h>
 
 #include "base/algorithm.h"
 #include "net/ethernet.h"
+#include "net/ipv4/ipv4.h"
 #include "net/ipv4/uip/arch.h"
 #include "net/ipv4/uip/arp.h"
 #include "net/ipv4/uip/opt.h"
@@ -620,7 +623,7 @@
     if((uip_connr->tcpstateflags & UIP_TS_MASK) == UIP_ESTABLISHED &&
        !uip_outstanding(uip_connr)) {
 	uip_flags = UIP_POLL;
-	UIP_APPCALL();
+	ipv4_uip_tcp_callback();
 	goto appsend;
     } else if(CONFIG_UIP_ACTIVE_OPEN &&
               (uip_connr->tcpstateflags & UIP_TS_MASK) == UIP_SYN_SENT) {
@@ -671,11 +674,11 @@
 	      uip_connr->nrtx == CONFIG_UIP_MAXSYNRTX)) {
 	    uip_connr->tcpstateflags = UIP_CLOSED;
 
-	    /* We call UIP_APPCALL() with uip_flags set to
+	    /* We call ipv4_uip_tcp_callback() with uip_flags set to
 	       UIP_TIMEDOUT to inform the application that the
 	       connection has timed out. */
 	    uip_flags = UIP_TIMEDOUT;
-	    UIP_APPCALL();
+	    ipv4_uip_tcp_callback();
 
 	    /* We also send a reset packet to the remote host. */
 	    BUF->flags = TCP_RST | TCP_ACK;
@@ -712,7 +715,7 @@
                the code for sending out the packet (the apprexmit
                label). */
 	    uip_flags = UIP_REXMIT;
-	    UIP_APPCALL();
+	    ipv4_uip_tcp_callback();
 	    goto apprexmit;
 	    
 	  case UIP_FIN_WAIT_1:
@@ -727,7 +730,7 @@
 	/* If there was no need for a retransmission, we poll the
            application for new data. */
 	uip_flags = UIP_POLL;
-	UIP_APPCALL();
+	ipv4_uip_tcp_callback();
 	goto appsend;
       }
     }
@@ -741,7 +744,7 @@
           &uip_buf[CONFIG_UIP_LLH_LEN + UIP_IPUDPH_LEN];
         uip_len = uip_slen = 0;
         uip_flags = UIP_POLL;
-        UIP_UDP_APPCALL();
+        ipv4_uip_udp_callback();
         goto udp_send;
       } else {
         goto drop;
@@ -938,8 +941,6 @@
          address of the packet is checked. */
       if(uip_udp_conn->lport != 0 &&
          UDPBUF->destport == uip_udp_conn->lport &&
-         (uip_udp_conn->rport == 0 ||
-          UDPBUF->srcport == uip_udp_conn->rport) &&
          (uip_ipaddr_cmp(&uip_udp_conn->ripaddr, &uip_all_zeroes_addr) ||
 	  uip_ipaddr_cmp(&uip_udp_conn->ripaddr, &uip_broadcast_addr) ||
           uip_ipaddr_cmp(&BUF->srcipaddr, &uip_udp_conn->ripaddr))) {
@@ -954,7 +955,7 @@
     uip_flags = UIP_NEWDATA;
     uip_sappdata = uip_appdata = &uip_buf[CONFIG_UIP_LLH_LEN + UIP_IPUDPH_LEN];
     uip_slen = 0;
-    UIP_UDP_APPCALL();
+    ipv4_uip_udp_callback();
 
    udp_send:
     if(uip_slen == 0) {
@@ -1214,7 +1215,7 @@
     uip_connr->tcpstateflags = UIP_CLOSED;
     UIP_LOG("tcp: got reset, aborting connection.");
     uip_flags = UIP_ABORT;
-    UIP_APPCALL();
+    ipv4_uip_tcp_callback();
     goto drop;
   }
   /* Calculate the length of the data, if the application has sent
@@ -1306,7 +1307,7 @@
         uip_add_rcv_nxt(uip_len);
       }
       uip_slen = 0;
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
       goto appsend;
     }
     /* We need to retransmit the SYNACK */
@@ -1367,12 +1368,12 @@
       uip_connr->len = 0;
       uip_len = 0;
       uip_slen = 0;
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
       goto appsend;
     }
     /* Inform the application that the connection failed */
     uip_flags = UIP_ABORT;
-    UIP_APPCALL();
+    ipv4_uip_tcp_callback();
     /* The connection is closed after we send the RST */
     uip_conn->tcpstateflags = UIP_CLOSED;
     goto reset;
@@ -1398,7 +1399,7 @@
       if(uip_len > 0) {
 	uip_flags |= UIP_NEWDATA;
       }
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
       uip_connr->len = 1;
       uip_connr->tcpstateflags = UIP_LAST_ACK;
       uip_connr->nrtx = 0;
@@ -1461,7 +1462,7 @@
        send, uip_len must be set to 0. */
     if(uip_flags & (UIP_NEWDATA | UIP_ACKDATA)) {
       uip_slen = 0;
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
 
     appsend:
       
@@ -1542,7 +1543,7 @@
     if(uip_flags & UIP_ACKDATA) {
       uip_connr->tcpstateflags = UIP_CLOSED;
       uip_flags = UIP_CLOSE;
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
     }
     break;
     
@@ -1563,7 +1564,7 @@
       }
       uip_add_rcv_nxt(1);
       uip_flags = UIP_CLOSE;
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
       goto tcp_send_ack;
     } else if(uip_flags & UIP_ACKDATA) {
       uip_connr->tcpstateflags = UIP_FIN_WAIT_2;
@@ -1584,7 +1585,7 @@
       uip_connr->timer = 0;
       uip_add_rcv_nxt(1);
       uip_flags = UIP_CLOSE;
-      UIP_APPCALL();
+      ipv4_uip_tcp_callback();
       goto tcp_send_ack;
     }
     if(uip_len > 0) {
diff --git a/src/net/ipv4/uip/uip.h b/src/net/ipv4/uip/uip.h
index 0151ea7..f295f64 100644
--- a/src/net/ipv4/uip/uip.h
+++ b/src/net/ipv4/uip/uip.h
@@ -1213,11 +1213,7 @@
  * Representation of a uIP TCP connection.
  *
  * The uip_conn structure is used for identifying a connection. All
- * but one field in the structure are to be considered read-only by an
- * application. The only exception is the appstate field whose purpose
- * is to let the application store application-specific state (e.g.,
- * file pointers) for the connection. The type of this field is
- * configured in the "uipopt.h" header file.
+ * fields in the structure are to be considered read-only by an application.
  */
 struct uip_conn {
   uip_ipaddr_t ripaddr;   /**< The IP address of the remote host. */
@@ -1244,9 +1240,6 @@
   uint8_t timer;         /**< The retransmission timer. */
   uint8_t nrtx;          /**< The number of retransmissions for the last
 			 segment sent. */
-
-  /** The application state. */
-  uip_tcp_appstate_t appstate;
 };
 
 
@@ -1280,9 +1273,6 @@
   uint16_t lport;        /**< The local port number in network byte order. */
   uint16_t rport;        /**< The remote port number in network byte order. */
   uint8_t  ttl;          /**< Default time-to-live. */
-
-  /** The application state. */
-  uip_udp_appstate_t appstate;
 };
 
 /**
diff --git a/src/net/net.c b/src/net/net.c
index 429705d..2c23c7e 100644
--- a/src/net/net.c
+++ b/src/net/net.c
@@ -26,21 +26,6 @@
 #include "net/ethernet.h"
 #include "net/net.h"
 
-static NetCallback net_callback_func;
-
-void net_set_callback(NetCallback func)
-{
-	net_callback_func = func;
-}
-
-void net_call_callback(void)
-{
-	if (net_callback_func)
-		net_callback_func();
-	else
-		printf("No network callback installed.\n");
-}
-
 ListNode net_eth_types;
 
 void net_service_dev(NetDevice *dev)
diff --git a/src/net/net.h b/src/net/net.h
index 4257371..cddf2b8 100644
--- a/src/net/net.h
+++ b/src/net/net.h
@@ -29,12 +29,6 @@
 #include "base/list.h"
 #include "drivers/net/net.h"
 
-typedef void (*NetCallback)(void);
-
-void net_set_callback(NetCallback func);
-
-void net_call_callback(void);
-
 void net_service_dev(NetDevice *dev);
 
 typedef struct EthTypeOps {
diff --git a/src/net/netboot/dhcp.c b/src/net/netboot/dhcp.c
index 51a1df3..7f1cd72 100644
--- a/src/net/netboot/dhcp.c
+++ b/src/net/netboot/dhcp.c
@@ -27,10 +27,10 @@
 #include "base/time.h"
 #include "base/xalloc.h"
 #include "drivers/net/net.h"
+#include "net/connection.h"
+#include "net/ipv4/ipv4.h"
 #include "net/ipv4/uip/arp.h"
-#include "net/ipv4/uip/udp/packet.h"
 #include "net/ipv4/uip/uip.h"
-#include "net/net.h"
 #include "net/netboot/dhcp.h"
 
 typedef enum DhcpOpcode
@@ -181,11 +181,21 @@
 	OptionOverloadBoth = 3
 };
 
-// Used in dhcp_callback().
-static DhcpState dhcp_state = DhcpInit;
-static DhcpPacket *dhcp_in;
-static int dhcp_in_ready;
-static DhcpPacket *dhcp_out;
+static void uip_ipaddr_from_int(uip_ipaddr_t *ip, uint32_t num)
+{
+	uip_ipaddr(ip, num >> 0, num >> 8, num >> 16, num >> 24);
+}
+
+static void uip_ipaddr_from_array(uip_ipaddr_t *ip, uint8_t *array)
+{
+	uip_ipaddr(ip, array[0], array[1], array[2], array[3]);
+}
+
+static uint32_t uip_ipaddr_to_int(uip_ipaddr_t ip)
+{
+	return (uip_ipaddr1(&ip) << 0) | (uip_ipaddr2(&ip) << 8) |
+	       (uip_ipaddr2(&ip) << 16) | (uip_ipaddr3(&ip) << 24);
+}
 
 typedef int (*DhcpOptionFunc)(uint8_t tag, uint8_t length, uint8_t *value,
 			      void *data);
@@ -292,8 +302,7 @@
 			return 1;
 		} else {
 			uip_ipaddr_t netmask;
-			uip_ipaddr(&netmask, value[0], value[1],
-					     value[2], value[3]);
+			uip_ipaddr_from_array(&netmask, value);
 			uip_setnetmask(&netmask);
 			return 0;
 		}
@@ -303,8 +312,7 @@
 			return 1;
 		} else {
 			uip_ipaddr_t router;
-			uip_ipaddr(&router, value[0], value[1],
-					    value[2], value[3]);
+			uip_ipaddr_from_array(&router, value);
 			uip_setdraddr(&router);
 			return 0;
 		}
@@ -312,49 +320,49 @@
 	return 0;
 }
 
-static void dhcp_callback(void)
+int dhcp_check_for_reply(NetConOps *con, DhcpState state,
+			 DhcpPacket *in, DhcpPacket *out)
 {
-	// Check that it's the right port. If it isn't, some other connection
-	// is open and we got their packet, and that's a bug on our end.
-	assert(ntohw(uip_udp_conn->lport) == DhcpClientPort);
-
-	// If there isn't any data, ignore the packet.
-	if (!uip_newdata())
-		return;
+	size_t incoming;
+	if (netcon_incoming(con, &incoming) || !incoming)
+		return 0;
 
 	// If the packet is too big, ignore it.
-	if (uip_datalen() > sizeof(DhcpPacket))
-		return;
+	if (incoming > sizeof(*in)) {
+		netcon_receive(con, NULL, &incoming, incoming);
+		return 0;
+	}
 
-	// Copy the packet out to ensure alignment, etc.
-	memset(dhcp_in, 0, sizeof(DhcpPacket));
-	memcpy(dhcp_in, uip_appdata, uip_datalen());
+	// Receive the packet.
+	memset(in, 0, sizeof(*in));
+	if (netcon_receive(con, in, &incoming, sizeof(*in)))
+		return 0;
 
 	// Check for problems in the reply.
-	if (dhcp_in->opcode != DhcpReplyOp ||
-		dhcp_in->hw_length != dhcp_out->hw_length ||
-		memcmp(dhcp_in->client_hw_addr,
-		       dhcp_out->client_hw_addr,
-		       dhcp_out->hw_length) ||
-		(dhcp_in->transaction_id != dhcp_out->transaction_id))
-		return;
+	if (in->opcode != DhcpReplyOp ||
+		in->hw_length != out->hw_length ||
+		memcmp(in->client_hw_addr,
+		       out->client_hw_addr,
+		       out->hw_length) ||
+		(in->transaction_id != out->transaction_id)) {
+		return 0;
+	}
 
-	if (memcmp(&dhcp_in->cookie, DhcpCookie, sizeof(DhcpCookie)))
-		return;
+	if (memcmp(&in->cookie, DhcpCookie, sizeof(DhcpCookie)))
+		return 0;
 
 	DhcpMessageType type = DhcpNoMessageType;
-	if (dhcp_process_options(dhcp_in, OptionOverloadNone, &dhcp_get_type,
-				 &type))
-		return;
+	if (dhcp_process_options(in, OptionOverloadNone, &dhcp_get_type, &type))
+		return 0;
 
-	switch (dhcp_state) {
+	switch (state) {
 	case DhcpInit:
 		if (type != DhcpOffer)
-			return;
+			return 0;
 		break;
 	case DhcpRequesting:
 		if (type != DhcpAck && type != DhcpNak)
-			return;
+			return 0;
 		break;
 	case DhcpBound:
 		// We shouldn't get any more packets once we're bound.
@@ -362,38 +370,31 @@
 	}
 
 	// Everything checks out. We have a valid reply.
-	dhcp_in_ready = 1;
+	return 1;
 }
 
-static void dhcp_send_packet(NetDevice *dev, struct uip_udp_conn *conn,
-			     const char *name, DhcpPacket *out, DhcpPacket *in)
+static int dhcp_send_packet(NetConOps *con, DhcpState state, const char *name,
+			    DhcpPacket *out, DhcpPacket *in)
 {
 	// Send the outbound packet.
 	printf("Sending %s... ", name);
-	uip_udp_packet_send(dev, conn, out, sizeof(*out));
+	if (netcon_send(con, out, sizeof(*out)))
+		return 1;
 	printf("done.\n");
 
 	// Prepare for the reply.
 	printf("Waiting for reply... ");
-	dhcp_in = in;
-	dhcp_out = out;
-	dhcp_in_ready = 0;
-
-	// Poll network driver until we get a reply. Resend periodically.
-	net_set_callback(&dhcp_callback);
-	for (;;) {
-		uint64_t start = time_us(0);
-		do {
-			net_service_dev(dev);
-		} while (!dhcp_in_ready &&
-			 time_us(start) < DhcpRespTimeoutUs);
-		if (dhcp_in_ready)
-			break;
-		// No response, try again.
-		uip_udp_packet_send(dev, conn, out, sizeof(*out));
+	uint64_t start = time_us(0);
+	while (!dhcp_check_for_reply(con, state, in, out)) {
+		if (time_us(start) > DhcpRespTimeoutUs) {
+			if (netcon_send(con, out, sizeof(*out)))
+				return 1;
+			start = time_us(0);
+		}
 	}
-	net_set_callback(NULL);
 	printf("done.\n");
+
+	return 0;
 }
 
 static void dhcp_prep_packet(DhcpPacket *packet, uint32_t transaction_id)
@@ -424,8 +425,8 @@
 	*options += length + 2;
 }
 
-int dhcp_request(NetDevice *dev, uip_ipaddr_t *next_ip, uip_ipaddr_t *server_ip,
-		 const char **bootfile)
+static int dhcp_request_work(NetConOps *con, uip_ipaddr_t *next_ip,
+			     uip_ipaddr_t *server_ip, const char **bootfile)
 {
 	DhcpPacket out, in;
 	uint8_t byte;
@@ -438,16 +439,6 @@
 	client_id[0] = DhcpEthernet;
 	memcpy(client_id + 1, &uip_ethaddr, sizeof(uip_ethaddr));
 
-	// Set up the UDP connection.
-	uip_ipaddr_t addr;
-	uip_ipaddr(&addr, 255,255,255,255);
-	struct uip_udp_conn *conn = uip_udp_new(&addr, htonw(DhcpServerPort));
-	if (!conn) {
-		printf("Failed to set up UDP connection.\n");
-		return 1;
-	}
-	uip_udp_bind(conn, htonw(DhcpClientPort));
-
 	// Send a DHCP discover packet.
 	dhcp_prep_packet(&out, rand());
 	options = out.options;
@@ -462,7 +453,8 @@
 	dhcp_add_option(&options, DhcpTagMaximumDhcpMessageSize,
 			&max_size, sizeof(max_size), &remaining);
 	dhcp_add_option(&options, DhcpTagEndOfList, NULL, 0, &remaining);
-	dhcp_send_packet(dev, conn, "DHCP discover", &out, &in);
+	if (dhcp_send_packet(con, DhcpInit, "DHCP discover", &out, &in))
+		return 1;
 
 	// Extract the DHCP server id.
 	uint32_t server_id;
@@ -473,7 +465,6 @@
 	}
 
 	// We got an offer. Request it.
-	dhcp_state = DhcpRequesting;
 	dhcp_prep_packet(&out, rand());
 	options = out.options;
 	remaining = sizeof(out.options);
@@ -491,29 +482,25 @@
 	dhcp_add_option(&options, DhcpTagServerIdentifier,
 			&server_id, sizeof(server_id), &remaining);
 	dhcp_add_option(&options, DhcpTagEndOfList, NULL, 0, &remaining);
-	dhcp_send_packet(dev, conn, "DHCP request", &out, &in);
+	if (dhcp_send_packet(con, DhcpRequesting, "DHCP request", &out, &in))
+		return 1;
 
 	DhcpMessageType type;
 	if (dhcp_process_options(&in, OptionOverloadNone, &dhcp_get_type,
 				 &type)) {
 		printf("Failed to extract message type.\n");
-		dhcp_state = DhcpInit;
 		return 1;
 	}
 	if (type == DhcpNak) {
 		printf("DHCP request nak-ed by the server.\n");
-		dhcp_state = DhcpInit;
 		return 1;
 	}
 
 	// The server acked, completing the transaction.
-	dhcp_state = DhcpBound;
-	uip_udp_remove(conn);
 
 	// Apply the settings.
 	if (dhcp_process_options(&in, OptionOverloadNone,
 				 &dhcp_apply_options, NULL)) {
-		dhcp_state = DhcpInit;
 		return 1;
 	}
 
@@ -522,41 +509,42 @@
 	file[bootfile_size - 1] = 0;
 	memcpy(file, in.bootfile_name, sizeof(in.bootfile_name));
 	*bootfile = file;
-	uip_ipaddr(next_ip, in.server_ip >> 0, in.server_ip >> 8,
-			    in.server_ip >> 16, in.server_ip >> 24);
-
-	uip_ipaddr(server_ip, server_id >> 0, server_id >> 8,
-			      server_id >> 16, server_id >> 24);
+	uip_ipaddr_from_int(next_ip, in.server_ip);
+	uip_ipaddr_from_int(server_ip, server_id);
 
 	uip_ipaddr_t my_ip;
-	uip_ipaddr(&my_ip, in.your_ip >> 0, in.your_ip >> 8,
-			   in.your_ip >> 16, in.your_ip >> 24);
+	uip_ipaddr_from_int(&my_ip, in.your_ip);
 	uip_sethostaddr(&my_ip);
 
 	return 0;
 }
 
-int dhcp_release(NetDevice *dev, uip_ipaddr_t server_ip)
+int dhcp_request(NetDevice *dev, uip_ipaddr_t *next_ip,
+		 uip_ipaddr_t *server_ip, const char **bootfile)
+{
+	// Set up the UDP connection.
+	uip_ipaddr_t addr;
+	uip_ipaddr(&addr, 255,255,255,255);
+	Ipv4UdpCon *con = new_ipv4_udp_con(dev, addr, DhcpServerPort);
+	int ret = netcon_bind(&con->ops, DhcpClientPort);
+	// We shouldn't attempt to DHCP request if we failed to bind.
+	ret = ret || dhcp_request_work(&con->ops, next_ip, server_ip, bootfile);
+	// Failure should not prevent closing the connection.
+	ret = netcon_close(&con->ops) || ret;
+	free(con);
+	return ret;
+}
+
+static int dhcp_release_work(NetConOps *con)
 {
 	DhcpPacket release;
 
-	// Set up the UDP connection.
-	struct uip_udp_conn *conn =
-		uip_udp_new(&server_ip, htonw(DhcpServerPort));
-	if (!conn) {
-		printf("Failed to set up UDP connection.\n");
-		return 1;
-	}
-	uip_udp_bind(conn, htonw(DhcpClientPort));
-
 	// Prepare the DHCP release packet.
 	dhcp_prep_packet(&release, rand());
 	uip_ipaddr_t my_ip;
 	uip_gethostaddr(&my_ip);
-	release.client_ip = (uip_ipaddr1(&my_ip) << 0) |
-			    (uip_ipaddr2(&my_ip) << 8) |
-			    (uip_ipaddr3(&my_ip) << 16) |
-			    (uip_ipaddr4(&my_ip) << 24);
+	release.client_ip = uip_ipaddr_to_int(my_ip);
+
 	uint8_t *options = release.options;
 	int remaining = sizeof(release.options);
 	uint8_t byte = DhcpRelease;
@@ -564,11 +552,18 @@
 			&remaining);
 	dhcp_add_option(&options, DhcpTagEndOfList, NULL, 0, &remaining);
 
-	// Call uip_udp_packet_send directly since we won't get a reply.
-	uip_udp_packet_send(dev, conn, &release, sizeof(release));
+	return netcon_send(con, &release, sizeof(release));
+}
 
-	dhcp_state = DhcpInit;
-	uip_udp_remove(conn);
-
-	return 0;
+int dhcp_release(NetDevice *dev, uip_ipaddr_t server_ip)
+{
+	// Set up the UDP connection.
+	Ipv4UdpCon *con = new_ipv4_udp_con(dev, server_ip, DhcpServerPort);
+	int ret = netcon_bind(&con->ops, DhcpClientPort);
+	// We shouldn't attempt to DHCP release if we failed to bind.
+	ret = ret || dhcp_release_work(&con->ops);
+	// Failure should not prevent closing the connection.
+	ret = netcon_close(&con->ops) || ret;
+	free(con);
+	return ret;
 }
diff --git a/src/net/netboot/netboot.c b/src/net/netboot/netboot.c
index cd8243c..abc2d17 100644
--- a/src/net/netboot/netboot.c
+++ b/src/net/netboot/netboot.c
@@ -32,6 +32,7 @@
 #include "net/netboot/netboot.h"
 #include "net/netboot/params.h"
 #include "net/netboot/tftp.h"
+#include "net/ipv4/ipv4.h"
 #include "net/ipv4/uip/arp.h"
 #include "net/ipv4/uip/uip.h"
 
@@ -87,7 +88,6 @@
 	printf("\n");
 
 	// Download the bootfile.
-	uint32_t size;
 	if (!bootfile) {
 		bootfile = (char *)dhcp_bootfile;
 		printf("Bootfile supplied by DHCP server: %s\n", bootfile);
@@ -95,8 +95,13 @@
 		printf("Bootfile predefined by user: %s\n", bootfile);
 	}
 
-	if (tftp_read(dev, payload, tftp_ip, bootfile, &size, MaxPayloadSize)) {
+	Ipv4UdpCon *tftp = new_ipv4_udp_con(dev, *tftp_ip, TftpPort);
+
+	uint32_t size;
+	if (tftp_read(&tftp->ops, payload, bootfile, &size, MaxPayloadSize)) {
 		printf("Tftp failed.\n");
+		netcon_close(&tftp->ops);
+		free(tftp);
 		if (dhcp_release(dev, server_ip))
 			printf("Dhcp release failed.\n");
 		return;
@@ -104,8 +109,8 @@
 	printf("The bootfile was %d bytes long.\n", size);
 
 	// Try to download command line file via TFTP if argsfile is specified
-	if (argsfile && !(tftp_read(dev, cmd_line, tftp_ip, argsfile, &size,
-			sizeof(cmd_line) - 1))) {
+	if (argsfile && !(tftp_read(&tftp->ops, cmd_line, argsfile, &size,
+				    sizeof(cmd_line) - 1))) {
 		while (cmd_line[size - 1] <= ' ')  // strip trailing whitespace
 			if (!--size) break;	   // and control chars (\n, \r)
 		cmd_line[size] = '\0';
@@ -120,6 +125,9 @@
 		printf("Command line predefined by user.\n");
 	}
 
+	netcon_close(&tftp->ops);
+	free(tftp);
+
 	// We're done on the network, so release our IP.
 	if (dhcp_release(dev, server_ip)) {
 		printf("Dhcp release failed.\n");
@@ -159,9 +167,11 @@
 	NetDevice *dev = NULL;
 	while (1) {
 		dev = net_scan_for_link(dev);
-		if (dev)
-			netboot_attempt(dev, tftp_ip, bootfile, argsfile, args);
-		else
+		if (dev) {
+			netboot_attempt(dev, tftp_ip, bootfile,
+					argsfile, args);
+		} else {
 			net_find_devices();
+		}
 	}
 }
diff --git a/src/net/netboot/tftp.c b/src/net/netboot/tftp.c
index 98c1352..e75d2cd 100644
--- a/src/net/netboot/tftp.c
+++ b/src/net/netboot/tftp.c
@@ -38,17 +38,21 @@
 typedef enum TftpStatus
 {
 	TftpPending = 0,
-	TftpSuccess = 1,
-	TftpFailure = 2
+	TftpProgressing = 1,
+	TftpSuccess = 2,
+	TftpFailure = 3
 } TftpStatus;
 
-static TftpStatus tftp_status;
+typedef struct {
+	void *dest;
+	size_t total_size;
+	size_t max_size;
 
-static uint8_t *tftp_dest;
-static int tftp_got_response;
-static int tftp_blocknum;
-static uint32_t tftp_total_size;
-static uint32_t tftp_max_size;
+	int block_num;
+
+	void *buf;
+	size_t buf_size;
+} TftpTransfer;
 
 typedef struct TftpAckPacket
 {
@@ -105,95 +109,95 @@
 	}
 }
 
-static void tftp_callback(void)
+TftpStatus tftp_handle_response(NetConOps *con, TftpTransfer *transfer)
 {
-	// If there isn't at least an opcode, ignore the packet.
-	if (!uip_newdata())
-		return;
-	if (uip_datalen() < 2)
-		return;
+	size_t incoming;
+	if (netcon_incoming(con, &incoming))
+		return TftpFailure;
 
-	if (!uip_udp_conn->rport) {
-		int srcport_offset = offsetof(struct uip_udpip_hdr, srcport);
-		memcpy(&uip_udp_conn->rport,
-			&uip_buf[CONFIG_UIP_LLH_LEN + srcport_offset],
-			sizeof(uip_udp_conn->rport));
+	// If the packet is too small, ignore it.
+	if (incoming < 4)
+		return TftpPending;
+
+	// Expand the transfer's scratch buffer if necessary.
+	if (transfer->buf_size < incoming) {
+		free(transfer->buf);
+		transfer->buf = xmalloc(incoming);
+		transfer->buf_size = incoming;
 	}
+	if (netcon_receive(con, transfer->buf, &incoming, transfer->buf_size))
+		return TftpFailure;
 
 	// Extract the opcode.
 	uint16_t opcode;
-	memcpy(&opcode, uip_appdata, sizeof(opcode));
+	memcpy(&opcode, transfer->buf, sizeof(opcode));
 	opcode = ntohw(opcode);
 
 	// If there was an error, report it and stop the transfer.
 	if (opcode == TftpError) {
-		tftp_status = TftpFailure;
 		printf(" error!\n");
 		tftp_print_error_pkt();
-		return;
+		return TftpFailure;
 	}
 
-	// We should only get data packets. Those are at least 4 bytes long.
-	if (opcode != TftpData || uip_datalen() < 4)
-		return;
+	// We should only get data packets.
+	if (opcode != TftpData)
+		return TftpPending;
 
 	// Get the block number.
-	uint16_t blocknum;
-	memcpy(&blocknum, (uint8_t *)uip_appdata + 2, sizeof(blocknum));
-	blocknum = ntohw(blocknum);
+	uint16_t block_num;
+	memcpy(&block_num, (uint8_t *)transfer->buf + 2, sizeof(block_num));
+	block_num = ntohw(block_num);
 
 	// Ignore blocks which are duplicated or out of order.
-	if (blocknum != tftp_blocknum)
-		return;
+	if (block_num != transfer->block_num)
+		return TftpPending;
 
-	void *new_data = (uint8_t *)uip_appdata + 4;
-	int new_data_len = uip_datalen() - 4;
+	void *new_data = (uint8_t *)transfer->buf + 4;
+	int new_data_len = incoming - 4;
 
 	// If the block is too big, reject it.
 	if (new_data_len > TftpMaxBlockSize)
-		return;
+		return TftpPending;
 
 	// If we're out of space give up.
-	if (new_data_len > tftp_max_size - tftp_total_size) {
-		tftp_status = TftpFailure;
+	if (new_data_len > transfer->max_size - transfer->total_size) {
 		printf("TFTP transfer too large.\n");
-		return;
+		return TftpFailure;
 	}
 
 	// If there's any data, copy it in.
 	if (new_data_len) {
-		memcpy(tftp_dest, new_data, new_data_len);
-		tftp_dest += new_data_len;
+		memcpy(transfer->dest, new_data, new_data_len);
+		transfer->dest = (uint8_t *)transfer->dest + new_data_len;
+		transfer->total_size += new_data_len;
 	}
-	tftp_total_size += new_data_len;
 
 	// Prepare an ack.
 	TftpAckPacket ack = {
 		htonw(TftpAck),
-		htonw(tftp_blocknum)
+		htonw(block_num)
 	};
-	memcpy(uip_appdata, &ack, sizeof(ack));
-	uip_udp_send(sizeof(ack));
-
-	tftp_got_response = 1;
+	if (netcon_send(con, &ack, sizeof(ack)))
+		return TftpFailure;
 
 	// If this block was less than the maximum size, the transfer is done.
-	if (new_data_len < TftpMaxBlockSize) {
-		tftp_status = TftpSuccess;
-		return;
-	}
+	if (new_data_len < TftpMaxBlockSize)
+		return TftpSuccess;
 
 	// Move on to the next block.
-	tftp_blocknum++;
+	transfer->block_num++;
 
-	if (!(tftp_blocknum % 10)) {
+	if (!(transfer->block_num % 10)) {
 		// Give some feedback that something is happening.
 		printf("#");
 	}
+
+	return TftpProgressing;
 }
 
-int tftp_read(NetDevice *dev, void *dest, uip_ipaddr_t *server_ip,
-	      const char *bootfile, uint32_t *size, uint32_t max_size)
+int tftp_read(NetConOps *con, void *dest, const char *bootfile,
+	      uint32_t *size, uint32_t max_size)
 {
 	// Build the read request packet.
 	uint16_t opcode = htonw(TftpReadReq);
@@ -211,71 +215,61 @@
 	memcpy(read_req + opcode_len, bootfile, name_len);
 	memcpy(read_req + opcode_len + name_len, mode, mode_len);
 
-	// Set up the UDP connection.
-	struct uip_udp_conn *conn = uip_udp_new(server_ip, htonw(TftpPort));
-	if (!conn) {
-		printf("Failed to set up UDP connection.\n");
-		free(read_req);
-		return -1;
-	}
-
 	// Send the request.
 	printf("Sending tftp read request... ");
-	uip_udp_packet_send(dev, conn, read_req, read_req_len);
+	if (netcon_send(con, read_req, read_req_len))
+		return -1;
 	uint64_t last_sent = time_us(0);
-	conn->rport = 0;
 	printf("done.\n");
 
 	// Prepare for the transfer.
 	printf("Waiting for the transfer... ");
-	tftp_status = TftpPending;
-	tftp_dest = dest;
-	tftp_blocknum = 1;
-	tftp_total_size = 0;
-	tftp_max_size = max_size;
+
+	TftpTransfer transfer;
+	transfer.dest = dest;
+	transfer.total_size = 0;
+	transfer.max_size = max_size;
+	transfer.block_num = 1;
+	transfer.buf = xmalloc(1);
+	transfer.buf_size = 1;
 
 	// Poll the network driver until the transaction is done.
-
-	net_set_callback(&tftp_callback);
-	while (tftp_status == TftpPending) {
-		tftp_got_response = 0;
-		net_service_dev(dev);
-		if (tftp_got_response)
-			continue;
-
-		// No response, check for a timeout.
-		if (time_us(last_sent) > TftpReceiveTimeoutUs) {
-			// Timed out. Resend our last packet and try again.
-			if (tftp_blocknum == 1) {
-				// Resend the read request.
-				conn->rport = htonw(TftpPort);
-				uip_udp_packet_send(dev, conn, read_req,
-						    read_req_len);
-				conn->rport = 0;
-			} else {
-				// Resend the last ack.
-				TftpAckPacket ack = {
-					htonw(TftpAck),
-					htonw(tftp_blocknum - 1)
-				};
-				uip_udp_packet_send(dev, conn, &ack,
-						    sizeof(ack));
+	int ret = -1;
+	while (1) {
+		TftpStatus status = tftp_handle_response(con, &transfer);
+		if (status == TftpFailure)
+			break;
+		if (status == TftpSuccess) {
+			if (size)
+				*size = transfer.total_size;
+			printf(" done.\n");
+			ret = 0;
+			break;
+		}
+		if (status != TftpProgressing) {
+			if (time_us(last_sent) > TftpReceiveTimeoutUs) {
+				// Timed out. Resend our last packet.
+				if (transfer.block_num == 1) {
+					// Resend the read request.
+					if (netcon_send(con, read_req,
+							read_req_len)) {
+						break;
+					}
+				} else {
+					// Resend the last ack.
+					TftpAckPacket ack = {
+						htonw(TftpAck),
+						htonw(transfer.block_num - 1)
+					};
+					if (netcon_send(con, &ack, sizeof(ack)))
+						break;
+				}
+				last_sent = time_us(0);
 			}
-			last_sent = time_us(0);
 		}
 	}
-	uip_udp_remove(conn);
+	free(transfer.buf);
 	free(read_req);
-	net_set_callback(NULL);
+	return ret;
 
-	// See what happened.
-	if (tftp_status == TftpFailure) {
-		// The error was printed when it was received.
-		return -1;
-	} else {
-		if (size)
-			*size = tftp_total_size;
-		printf(" done.\n");
-		return 0;
-	}
 }
diff --git a/src/net/netboot/tftp.h b/src/net/netboot/tftp.h
index 90bd4de..c5ac9d8 100644
--- a/src/net/netboot/tftp.h
+++ b/src/net/netboot/tftp.h
@@ -23,7 +23,9 @@
 #ifndef __NETBOOT_TFTP_H__
 #define __NETBOOT_TFTP_H__
 
-#include "net/ipv4/uip/uip.h"
+#include <stdint.h>
+
+#include "net/connection.h"
 
 typedef enum TftpOpcode
 {
@@ -49,7 +51,7 @@
 static const uint16_t TftpPort = 69;
 static const int TftpMaxBlockSize = 512;
 
-int tftp_read(NetDevice *dev, void *dest, uip_ipaddr_t *server_ip,
-	      const char *bootfile, uint32_t *size, uint32_t max_size);
+int tftp_read(NetConOps *con, void *dest, const char *bootfile,
+	      uint32_t *size, uint32_t max_size);
 
 #endif /* __NETBOOT_TFTP_H__ */