Index: .bzrignore
===================================================================
--- .bzrignore	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ .bzrignore	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -269,4 +269,6 @@
 uspace/dist/srv/dhcp
 uspace/srv/net/dhcp/dhcp
+uspace/dist/srv/nconfsrv
+uspace/srv/net/nconfsrv/nconfsrv
 uspace/app/netspeed/netspeed
 uspace/dist/app/netspeed
Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ boot/Makefile.common	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -118,4 +118,5 @@
 	$(USPACE_PATH)/srv/net/inetsrv/inetsrv \
 	$(USPACE_PATH)/srv/net/loopip/loopip \
+	$(USPACE_PATH)/srv/net/nconfsrv/nconfsrv \
 	$(USPACE_PATH)/srv/net/slip/slip \
 	$(USPACE_PATH)/srv/net/tcp/tcp \
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/Makefile	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -97,4 +97,5 @@
 	srv/net/inetsrv \
 	srv/net/loopip \
+	srv/net/nconfsrv \
 	srv/net/slip \
 	srv/net/tcp \
Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/app/init/init.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -360,4 +360,6 @@
 	srv_start("/srv/udp");
 	srv_start("/srv/dnsrsrv");
+	srv_start("/srv/dhcp");
+	srv_start("/srv/nconfsrv");
 	
 	srv_start("/srv/clipboard");
Index: uspace/lib/c/Makefile
===================================================================
--- uspace/lib/c/Makefile	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/lib/c/Makefile	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -77,4 +77,5 @@
 	generic/device/pci.c \
 	generic/device/ahci.c \
+	generic/dhcp.c \
 	generic/dnsr.c \
 	generic/dlfcn.c \
Index: uspace/lib/c/generic/dhcp.c
===================================================================
--- uspace/lib/c/generic/dhcp.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/lib/c/generic/dhcp.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#include <async.h>
+#include <assert.h>
+#include <errno.h>
+#include <inet/dhcp.h>
+#include <ipc/dhcp.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <stdlib.h>
+
+static async_sess_t *dhcp_sess = NULL;
+
+int dhcp_init(void)
+{
+	service_id_t dhcp_svc;
+	int rc;
+
+	assert(dhcp_sess == NULL);
+
+	rc = loc_service_get_id(SERVICE_NAME_DHCP, &dhcp_svc,
+	    IPC_FLAG_BLOCKING);
+	if (rc != EOK)
+		return ENOENT;
+
+	dhcp_sess = loc_service_connect(EXCHANGE_SERIALIZE, dhcp_svc,
+	    IPC_FLAG_BLOCKING);
+	if (dhcp_sess == NULL)
+		return ENOENT;
+
+	return EOK;
+}
+
+int dhcp_link_add(sysarg_t link_id)
+{
+	async_exch_t *exch = async_exchange_begin(dhcp_sess);
+
+	int rc = async_req_1_0(exch, DHCP_LINK_ADD, link_id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+int dhcp_link_remove(sysarg_t link_id)
+{
+	async_exch_t *exch = async_exchange_begin(dhcp_sess);
+
+	int rc = async_req_1_0(exch, DHCP_LINK_REMOVE, link_id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/lib/c/generic/inetcfg.c
===================================================================
--- uspace/lib/c/generic/inetcfg.c	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/lib/c/generic/inetcfg.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -267,4 +267,14 @@
 }
 
+int inetcfg_link_add(sysarg_t link_id)
+{
+	async_exch_t *exch = async_exchange_begin(inetcfg_sess);
+
+	int rc = async_req_1_0(exch, INETCFG_LINK_ADD, link_id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
 int inetcfg_link_get(sysarg_t link_id, inet_link_info_t *linfo)
 {
@@ -305,4 +315,14 @@
 }
 
+int inetcfg_link_remove(sysarg_t link_id)
+{
+	async_exch_t *exch = async_exchange_begin(inetcfg_sess);
+
+	int rc = async_req_1_0(exch, INETCFG_LINK_REMOVE, link_id);
+	async_exchange_end(exch);
+
+	return rc;
+}
+
 int inetcfg_sroute_create(const char *name, inet_naddr_t *dest,
     inet_addr_t *router, sysarg_t *sroute_id)
Index: uspace/lib/c/include/inet/dhcp.h
===================================================================
--- uspace/lib/c/include/inet/dhcp.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/lib/c/include/inet/dhcp.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBC_INET_DHCP_H_
+#define LIBC_INET_DHCP_H_
+
+#include <sys/types.h>
+
+extern int dhcp_init(void);
+extern int dhcp_link_add(sysarg_t);
+extern int dhcp_link_remove(sysarg_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/c/include/inet/inetcfg.h
===================================================================
--- uspace/lib/c/include/inet/inetcfg.h	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/lib/c/include/inet/inetcfg.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -48,5 +48,7 @@
 extern int inetcfg_get_link_list(sysarg_t **, size_t *);
 extern int inetcfg_get_sroute_list(sysarg_t **, size_t *);
+extern int inetcfg_link_add(sysarg_t);
 extern int inetcfg_link_get(sysarg_t, inet_link_info_t *);
+extern int inetcfg_link_remove(sysarg_t);
 extern int inetcfg_sroute_get(sysarg_t, inet_sroute_info_t *);
 extern int inetcfg_sroute_get_id(const char *, sysarg_t *);
Index: uspace/lib/c/include/ipc/dhcp.h
===================================================================
--- uspace/lib/c/include/ipc/dhcp.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/lib/c/include/ipc/dhcp.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libcipc
+ * @{
+ */
+/** @file
+ */
+
+#ifndef LIBC_IPC_DHCP_H_
+#define LIBC_IPC_DHCP_H_
+
+#include <ipc/common.h>
+
+/** DHCP service requests */
+typedef enum {
+	DHCP_LINK_ADD = IPC_FIRST_USER_METHOD,
+	DHCP_LINK_REMOVE
+} dhcp_request_t;
+
+#endif
+
+/**
+ * @}
+ */
Index: uspace/lib/c/include/ipc/inet.h
===================================================================
--- uspace/lib/c/include/ipc/inet.h	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/lib/c/include/ipc/inet.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -72,5 +72,7 @@
 	INETCFG_GET_LINK_LIST,
 	INETCFG_GET_SROUTE_LIST,
+	INETCFG_LINK_ADD,
 	INETCFG_LINK_GET,
+	INETCFG_LINK_REMOVE,
 	INETCFG_SROUTE_CREATE,
 	INETCFG_SROUTE_DELETE,
Index: uspace/lib/c/include/ipc/services.h
===================================================================
--- uspace/lib/c/include/ipc/services.h	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/lib/c/include/ipc/services.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -54,4 +54,5 @@
 
 #define SERVICE_NAME_CORECFG	"corecfg"
+#define SERVICE_NAME_DHCP       "net/dhcp"
 #define SERVICE_NAME_DNSR       "net/dnsr"
 #define SERVICE_NAME_INET       "net/inet"
@@ -59,4 +60,5 @@
 #define SERVICE_NAME_INETPING   "net/inetping"
 #define SERVICE_NAME_INETPING6  "net/inetping6"
+#define SERVICE_NAME_NETCONF    "net/netconf"
 
 #endif
Index: uspace/srv/net/dhcp/Makefile
===================================================================
--- uspace/srv/net/dhcp/Makefile	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/srv/net/dhcp/Makefile	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -31,5 +31,7 @@
 
 SOURCES = \
-	dhcp.c 
+	dhcp.c \
+	main.c \
+	transport.c
 
 include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/net/dhcp/dhcp.c
===================================================================
--- uspace/srv/net/dhcp/dhcp.c	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/srv/net/dhcp/dhcp.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -35,8 +35,11 @@
  */
 
+#include <adt/list.h>
 #include <bitops.h>
+#include <fibril_synch.h>
 #include <inet/addr.h>
 #include <inet/dnsr.h>
 #include <inet/inetcfg.h>
+#include <io/log.h>
 #include <loc.h>
 #include <net/in.h>
@@ -46,13 +49,36 @@
 #include <stdlib.h>
 
+#include "dhcp.h"
 #include "dhcp_std.h"
-
-#define NAME "dhcp"
+#include "transport.h"
+
+enum {
+	/** In microseconds */
+	dhcp_discover_timeout_val = 5 * 1000 * 1000,
+	/** In microseconds */
+	dhcp_request_timeout_val = 1 * 1000 * 1000,
+	dhcp_discover_retries = 5,
+	dhcp_request_retries = 3
+};
 
 #define MAX_MSG_SIZE 1024
-
-static int transport_fd = -1;
-static inet_link_info_t link_info;
 static uint8_t msgbuf[MAX_MSG_SIZE];
+
+/** List of registered links (of dhcp_link_t) */
+static list_t dhcp_links;
+
+static void dhcpsrv_discover_timeout(void *);
+static void dhcpsrv_request_timeout(void *);
+
+typedef enum {
+	ds_bound,
+	ds_fail,
+	ds_init,
+	ds_init_reboot,
+	ds_rebinding,
+	ds_renewing,
+	ds_requesting,
+	ds_selecting
+} dhcp_state_t;
 
 typedef struct {
@@ -69,4 +95,25 @@
 } dhcp_offer_t;
 
+typedef struct {
+	/** Link to dhcp_links list */
+	link_t links;
+	/** Link service ID */
+	service_id_t link_id;
+	/** Link info */
+	inet_link_info_t link_info;
+	/** Transport */
+	dhcp_transport_t dt;
+	/** Transport timeout */
+	fibril_timer_t *timeout;
+	/** Number of retries */
+	int retries_left;
+	/** Link state */
+	dhcp_state_t state;
+	/** Last received offer */
+	dhcp_offer_t offer;
+} dhcp_link_t;
+
+static void dhcpsrv_recv(void *, void *, size_t);
+
 /** Decode subnet mask into subnet prefix length. */
 static int subnet_mask_decode(uint32_t mask, int *bits)
@@ -101,24 +148,5 @@
 }
 
-static int dhcp_send(void *msg, size_t size)
-{
-	struct sockaddr_in addr;
-	int rc;
-
-	addr.sin_family = AF_INET;
-	addr.sin_port = htons(dhcp_server_port);
-	addr.sin_addr.s_addr = htonl(addr32_broadcast_all_hosts);
-
-	rc = sendto(transport_fd, msg, size, 0,
-	    (struct sockaddr *)&addr, sizeof(addr));
-	if (rc != EOK) {
-		printf("Sending failed\n");
-		return rc;
-	}
-
-	return EOK;
-}
-
-static int dhcp_send_discover(void)
+static int dhcp_send_discover(dhcp_link_t *dlink)
 {
 	dhcp_hdr_t *hdr = (dhcp_hdr_t *)msgbuf;
@@ -132,5 +160,5 @@
 	hdr->flags = flag_broadcast;
 
-	addr48(link_info.mac_addr, hdr->chaddr);
+	addr48(dlink->link_info.mac_addr, hdr->chaddr);
 	hdr->opt_magic = host2uint32_t_be(dhcp_opt_magic);
 
@@ -140,30 +168,8 @@
 	opt[3] = opt_end;
 
-	return dhcp_send(msgbuf, sizeof(dhcp_hdr_t) + 4);
-}
-
-static int dhcp_recv_msg(void **rmsg, size_t *rsize)
-{
-	struct sockaddr_in src_addr;
-	socklen_t src_addr_size;
-	size_t recv_size;
-	int rc;
-
-	src_addr_size = sizeof(src_addr);
-	rc = recvfrom(transport_fd, msgbuf, MAX_MSG_SIZE, 0,
-	    (struct sockaddr *)&src_addr, &src_addr_size);
-	if (rc < 0) {
-		printf("recvfrom failed (%d)\n", rc);
-		return rc;
-	}
-
-	recv_size = (size_t)rc;
-	*rmsg = msgbuf;
-	*rsize = recv_size;
-
-	return EOK;
-}
-
-static int dhcp_send_request(dhcp_offer_t *offer)
+	return dhcp_send(&dlink->dt, msgbuf, sizeof(dhcp_hdr_t) + 4);
+}
+
+static int dhcp_send_request(dhcp_link_t *dlink, dhcp_offer_t *offer)
 {
 	dhcp_hdr_t *hdr = (dhcp_hdr_t *)msgbuf;
@@ -178,5 +184,5 @@
 	hdr->flags = flag_broadcast;
 	hdr->ciaddr = host2uint32_t_be(offer->oaddr.addr);
-	addr48(link_info.mac_addr, hdr->chaddr);
+	addr48(dlink->link_info.mac_addr, hdr->chaddr);
 	hdr->opt_magic = host2uint32_t_be(dhcp_opt_magic);
 
@@ -203,8 +209,8 @@
 	opt[i++] = opt_end;
 
-	return dhcp_send(msgbuf, sizeof(dhcp_hdr_t) + i);
-}
-
-static int dhcp_recv_reply(void *msg, size_t size, dhcp_offer_t *offer)
+	return dhcp_send(&dlink->dt, msgbuf, sizeof(dhcp_hdr_t) + i);
+}
+
+static int dhcp_parse_reply(void *msg, size_t size, dhcp_offer_t *offer)
 {
 	dhcp_hdr_t *hdr = (dhcp_hdr_t *)msg;
@@ -222,5 +228,5 @@
 	size_t i;
 
-	printf("Receive reply\n");
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Receive reply");
 	memset(offer, 0, sizeof(*offer));
 
@@ -231,5 +237,5 @@
 		return rc;
 
-	printf("Your IP address: %s\n", saddr);
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Your IP address: %s", saddr);
 	free(saddr);
 
@@ -240,5 +246,5 @@
 		return rc;
 
-	printf("Next server IP address: %s\n", saddr);
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Next server IP address: %s", saddr);
 	free(saddr);
 
@@ -249,5 +255,5 @@
 		return rc;
 
-	printf("Relay agent IP address: %s\n", saddr);
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Relay agent IP address: %s", saddr);
 	free(saddr);
 
@@ -320,10 +326,10 @@
 
 	if (!have_server_id) {
-		printf("Missing server ID option.\n");
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Missing server ID option.");
 		return rc;
 	}
 
 	if (!have_subnet_mask) {
-		printf("Missing subnet mask option.\n");
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Missing subnet mask option.");
 		return rc;
 	}
@@ -333,5 +339,5 @@
 		return rc;
 
-	printf("Offered network address: %s\n", saddr);
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Offered network address: %s", saddr);
 	free(saddr);
 
@@ -341,5 +347,5 @@
 			return rc;
 
-		printf("Router address: %s\n", saddr);
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Router address: %s", saddr);
 		free(saddr);
 	}
@@ -350,5 +356,5 @@
 			return rc;
 
-		printf("DNS server: %s\n", saddr);
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "DNS server: %s", saddr);
 		free(saddr);
 	}
@@ -367,5 +373,6 @@
 	    &addr_id);
 	if (rc != EOK) {
-		printf("Error creating IP address %s (%d)\n", "dhcp4a", rc);
+		log_msg(LOG_DEFAULT, LVL_ERROR,
+		    "Error creating IP address %s (%d)", "dhcp4a", rc);
 		return rc;
 	}
@@ -378,6 +385,6 @@
 		rc = inetcfg_sroute_create("dhcpdef", &defr, &offer->router, &sroute_id);
 		if (rc != EOK) {
-			printf("Error creating default route %s (%d).\n", "dhcpdef",
-			    rc);
+			log_msg(LOG_DEFAULT, LVL_ERROR, "Error creating "
+			    "default route %s (%d).", "dhcpdef", rc);
 			return rc;
 		}
@@ -387,6 +394,6 @@
 		rc = dnsr_set_srvaddr(&offer->dns_server);
 		if (rc != EOK) {
-			printf("%s: Error setting nameserver address (%d))\n",
-			    NAME, rc);
+			log_msg(LOG_DEFAULT, LVL_ERROR, "Error setting "
+			    "nameserver address (%d))", rc);
 			return rc;
 		}
@@ -396,93 +403,230 @@
 }
 
-int main(int argc, char *argv[])
-{
-	int fd;
-	struct sockaddr_in laddr;
-	void *msg;
-	service_id_t iplink;
-	size_t msg_size;
+void dhcpsrv_links_init(void)
+{
+	list_initialize(&dhcp_links);
+}
+
+static dhcp_link_t *dhcpsrv_link_find(service_id_t link_id)
+{
+	list_foreach(dhcp_links, links, dhcp_link_t, dlink) {
+		if (dlink->link_id == link_id)
+			return dlink;
+	}
+
+	return NULL;
+}
+
+static void dhcp_link_set_failed(dhcp_link_t *dlink)
+{
+	log_msg(LOG_DEFAULT, LVL_NOTE, "Giving up on link %s",
+	    dlink->link_info.name);
+	dlink->state = ds_fail;
+}
+
+int dhcpsrv_link_add(service_id_t link_id)
+{
+	dhcp_link_t *dlink;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcpsrv_link_add(%zu)", link_id);
+
+	if (dhcpsrv_link_find(link_id) != NULL) {
+		log_msg(LOG_DEFAULT, LVL_NOTE, "Link %zu already added",
+		    link_id);
+		return EEXIST;
+	}
+
+	dlink = calloc(1, sizeof(dhcp_link_t));
+	if (dlink == NULL)
+		return ENOMEM;
+
+	dlink->link_id = link_id;
+	dlink->timeout = fibril_timer_create();
+	if (dlink->timeout == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	/* Get link hardware address */
+	rc = inetcfg_link_get(link_id, &dlink->link_info);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error getting properties "
+		    "for link %zu.", link_id);
+		rc = EIO;
+		goto error;
+	}
+
+	rc = dhcp_transport_init(&dlink->dt, link_id, dhcpsrv_recv, dlink);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error initializing DHCP "
+		    "transport for link %s.", dlink->link_info.name);
+		rc = EIO;
+		goto error;
+	}
+
+	dlink->state = ds_selecting;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPDISCOVER");
+	rc = dhcp_send_discover(dlink);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error sending DHCPDISCOVER.");
+		dhcp_link_set_failed(dlink);
+		rc = EIO;
+		goto error;
+	}
+
+	dlink->retries_left = dhcp_discover_retries;
+	fibril_timer_set(dlink->timeout, dhcp_discover_timeout_val,
+	    dhcpsrv_discover_timeout, dlink);
+
+	list_append(&dlink->links, &dhcp_links);
+
+	return EOK;
+error:
+	if (dlink != NULL && dlink->timeout != NULL)
+		fibril_timer_destroy(dlink->timeout);
+	free(dlink);
+	return rc;
+}
+
+int dhcpsrv_link_remove(service_id_t link_id)
+{
+	return ENOTSUP;
+}
+
+static void dhcpsrv_recv_offer(dhcp_link_t *dlink, dhcp_offer_t *offer)
+{
+	int rc;
+
+	if (dlink->state != ds_selecting) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Received offer in state "
+		    " %d, ignoring.", (int)dlink->state);
+		return;
+	}
+
+	fibril_timer_clear(dlink->timeout);
+	dlink->offer = *offer;
+	dlink->state = ds_requesting;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPREQUEST");
+	rc = dhcp_send_request(dlink, offer);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Error sending request.");
+		return;
+	}
+
+	dlink->retries_left = dhcp_request_retries;
+	fibril_timer_set(dlink->timeout, dhcp_request_timeout_val,
+	    dhcpsrv_request_timeout, dlink);
+}
+
+static void dhcpsrv_recv_ack(dhcp_link_t *dlink, dhcp_offer_t *offer)
+{
+	int rc;
+
+	if (dlink->state != ds_requesting) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Received ack in state "
+		    " %d, ignoring.", (int)dlink->state);
+		return;
+	}
+
+	fibril_timer_clear(dlink->timeout);
+	dlink->offer = *offer;
+	dlink->state = ds_bound;
+
+	rc = dhcp_cfg_create(dlink->link_id, offer);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Error creating configuration.");
+		return;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "%s: Successfully configured.",
+	    dlink->link_info.name);
+}
+
+static void dhcpsrv_recv(void *arg, void *msg, size_t size)
+{
+	dhcp_link_t *dlink = (dhcp_link_t *)arg;
 	dhcp_offer_t offer;
 	int rc;
 
-	if (argc < 2) {
-		printf("syntax: %s <ip-link>\n", NAME);
-		return 1;
-	}
-
-	rc = inetcfg_init();
-	if (rc != EOK) {
-		printf("Error contacting inet configuration service.\n");
-		return 1;
-	}
-
-	rc = loc_service_get_id(argv[1], &iplink, 0);
-	if (rc != EOK) {
-		printf("Error resolving service '%s'.\n", argv[1]);
-		return 1;
-	}
-
-	/* Get link hardware address */
-	rc = inetcfg_link_get(iplink, &link_info);
-	if (rc != EOK) {
-		printf("Error getting properties for link '%s'.\n", argv[1]);
-		return 1;
-	}
-
-	laddr.sin_family = AF_INET;
-	laddr.sin_port = htons(dhcp_client_port);
-	laddr.sin_addr.s_addr = INADDR_ANY;
-
-	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
-	if (fd < 0)
-		return 1;
-
-	printf("Bind socket.\n");
-	rc = bind(fd, (struct sockaddr *)&laddr, sizeof(laddr));
-	if (rc != EOK)
-		return 1;
-
-	printf("Set socket options\n");
-	rc = setsockopt(fd, SOL_SOCKET, SO_IPLINK, &iplink, sizeof(iplink));
-	if (rc != EOK)
-		return 1;
-
-	transport_fd = fd;
-
-	printf("Send DHCPDISCOVER\n");
-	rc = dhcp_send_discover();
-	if (rc != EOK)
-		return 1;
-
-	rc = dhcp_recv_msg(&msg, &msg_size);
-	if (rc != EOK)
-		return 1;
-
-	printf("Received %zu bytes\n", msg_size);
-
-	rc = dhcp_recv_reply(msg, msg_size, &offer);
-	if (rc != EOK)
-		return 1;
-
-	rc = dhcp_send_request(&offer);
-	if (rc != EOK)
-		return 1;
-
-	rc = dhcp_recv_msg(&msg, &msg_size);
-	if (rc != EOK)
-		return 1;
-
-	printf("Received %zu bytes\n", msg_size);
-
-	rc = dhcp_recv_reply(msg, msg_size, &offer);
-	if (rc != EOK)
-		return 1;
-
-	rc = dhcp_cfg_create(iplink, &offer);
-	if (rc != EOK)
-		return 1;
-
-	closesocket(fd);
-	return 0;
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "%s: dhcpsrv_recv() %zu bytes",
+	    dlink->link_info.name, size);
+
+	rc = dhcp_parse_reply(msg, size, &offer);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Error parsing reply");
+		return;
+	}
+
+	switch (offer.msg_type) {
+	case msg_dhcpoffer:
+		dhcpsrv_recv_offer(dlink, &offer);
+		break;
+	case msg_dhcpack:
+		dhcpsrv_recv_ack(dlink, &offer);
+		break;
+	default:
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Received unexpected "
+		    "message type. %d", (int)offer.msg_type);
+		break;
+	}
+}
+
+static void dhcpsrv_discover_timeout(void *arg)
+{
+	dhcp_link_t *dlink = (dhcp_link_t *)arg;
+	int rc;
+
+	assert(dlink->state == ds_selecting);
+	log_msg(LOG_DEFAULT, LVL_NOTE, "%s: dcpsrv_discover_timeout",
+	    dlink->link_info.name);
+
+	if (dlink->retries_left == 0) {
+		log_msg(LOG_DEFAULT, LVL_NOTE, "Retries exhausted");
+		dhcp_link_set_failed(dlink);
+		return;
+	}
+	--dlink->retries_left;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPDISCOVER");
+	rc = dhcp_send_discover(dlink);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error sending DHCPDISCOVER");
+		dhcp_link_set_failed(dlink);
+		return;
+	}
+
+	fibril_timer_set(dlink->timeout, dhcp_discover_timeout_val,
+	    dhcpsrv_discover_timeout, dlink);
+}
+
+static void dhcpsrv_request_timeout(void *arg)
+{
+	dhcp_link_t *dlink = (dhcp_link_t *)arg;
+	int rc;
+
+	assert(dlink->state == ds_requesting);
+	log_msg(LOG_DEFAULT, LVL_NOTE, "%s: dcpsrv_request_timeout",
+	    dlink->link_info.name);
+
+	if (dlink->retries_left == 0) {
+		log_msg(LOG_DEFAULT, LVL_NOTE, "Retries exhausted");
+		dhcp_link_set_failed(dlink);
+		return;
+	}
+	--dlink->retries_left;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Send DHCPREQUEST");
+	rc = dhcp_send_request(dlink, &dlink->offer);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Error sending request.");
+		dhcp_link_set_failed(dlink);
+		return;
+	}
+
+	fibril_timer_set(dlink->timeout, dhcp_request_timeout_val,
+	    dhcpsrv_request_timeout, dlink);
 }
 
Index: uspace/srv/net/dhcp/dhcp.h
===================================================================
--- uspace/srv/net/dhcp/dhcp.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/dhcp/dhcp.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup dhcp
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#ifndef DHCP_H
+#define DHCP_H
+
+#include <ipc/loc.h>
+
+extern void dhcpsrv_links_init(void);
+extern int dhcpsrv_link_add(service_id_t);
+extern int dhcpsrv_link_remove(service_id_t);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/net/dhcp/main.c
===================================================================
--- uspace/srv/net/dhcp/main.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/dhcp/main.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup dhcp
+ * @{
+ */
+/**
+ * @file
+ */
+
+#include <async.h>
+#include <errno.h>
+#include <io/log.h>
+#include <inet/inetcfg.h>
+#include <ipc/dhcp.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <task.h>
+
+#include "dhcp.h"
+
+#define NAME  "dhcp"
+
+static void dhcp_client_conn(ipc_callid_t, ipc_call_t *, void *);
+
+static int dhcp_init(void)
+{
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcp_init()");
+
+	dhcpsrv_links_init();
+
+	rc = inetcfg_init();
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error contacting inet configuration service.\n");
+		return EIO;
+	}
+
+	async_set_client_connection(dhcp_client_conn);
+
+	rc = loc_server_register(NAME);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server (%d).", rc);
+		return EEXIST;
+	}
+
+	service_id_t sid;
+	rc = loc_service_register(SERVICE_NAME_DHCP, &sid);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service (%d).", rc);
+		return EEXIST;
+	}
+
+	return EOK;
+}
+
+static void dhcp_link_add_srv(ipc_callid_t callid, ipc_call_t *call)
+{
+	sysarg_t link_id;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcp_link_add_srv()");
+
+	link_id = IPC_GET_ARG1(*call);
+
+	rc = dhcpsrv_link_add(link_id);
+	async_answer_0(callid, rc);
+}
+
+static void dhcp_link_remove_srv(ipc_callid_t callid, ipc_call_t *call)
+{
+	sysarg_t link_id;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcp_link_remove_srv()");
+
+	link_id = IPC_GET_ARG1(*call);
+
+	rc = dhcpsrv_link_remove(link_id);
+	async_answer_0(callid, rc);
+}
+
+static void dhcp_client_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcp_client_conn()");
+
+	/* Accept the connection */
+	async_answer_0(iid, EOK);
+
+	while (true) {
+		ipc_call_t call;
+		ipc_callid_t callid = async_get_call(&call);
+		sysarg_t method = IPC_GET_IMETHOD(call);
+
+		if (!method) {
+			/* The other side has hung up */
+			async_answer_0(callid, EOK);
+			return;
+		}
+
+		switch (method) {
+		case DHCP_LINK_ADD:
+			dhcp_link_add_srv(callid, &call);
+			break;
+		case DHCP_LINK_REMOVE:
+			dhcp_link_remove_srv(callid, &call);
+			break;
+		default:
+			async_answer_0(callid, EINVAL);
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	int rc;
+
+	printf("%s: DHCP Service\n", NAME);
+
+	if (log_init(NAME) != EOK) {
+		printf(NAME ": Failed to initialize logging.\n");
+		return 1;
+	}
+
+	rc = dhcp_init();
+	if (rc != EOK)
+		return 1;
+
+	printf(NAME ": Accepting connections.\n");
+	task_retval(0);
+	async_manager();
+
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/srv/net/dhcp/transport.c
===================================================================
--- uspace/srv/net/dhcp/transport.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/dhcp/transport.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup dhcp
+ * @{
+ */
+/**
+ * @file
+ * @brief DHCP client
+ */
+
+#include <bitops.h>
+#include <inet/addr.h>
+#include <inet/dnsr.h>
+#include <inet/inetcfg.h>
+#include <io/log.h>
+#include <loc.h>
+#include <net/in.h>
+#include <net/inet.h>
+#include <net/socket.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "dhcp.h"
+#include "dhcp_std.h"
+#include "transport.h"
+
+#define MAX_MSG_SIZE 1024
+static uint8_t msgbuf[MAX_MSG_SIZE];
+
+typedef struct {
+	/** Message type */
+	enum dhcp_msg_type msg_type;
+	/** Offered address */
+	inet_naddr_t oaddr;
+	/** Server address */
+	inet_addr_t srv_addr;
+	/** Router address */
+	inet_addr_t router;
+	/** DNS server */
+	inet_addr_t dns_server;
+} dhcp_offer_t;
+
+static int dhcp_recv_fibril(void *);
+
+int dhcp_send(dhcp_transport_t *dt, void *msg, size_t size)
+{
+	struct sockaddr_in addr;
+	int rc;
+
+	addr.sin_family = AF_INET;
+	addr.sin_port = htons(dhcp_server_port);
+	addr.sin_addr.s_addr = htonl(addr32_broadcast_all_hosts);
+
+	rc = sendto(dt->fd, msg, size, 0,
+	    (struct sockaddr *)&addr, sizeof(addr));
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Sending failed");
+		return rc;
+	}
+
+	return EOK;
+}
+
+static int dhcp_recv_msg(dhcp_transport_t *dt, void **rmsg, size_t *rsize)
+{
+	struct sockaddr_in src_addr;
+	socklen_t src_addr_size;
+	size_t recv_size;
+	int rc;
+
+	src_addr_size = sizeof(src_addr);
+	rc = recvfrom(dt->fd, msgbuf, MAX_MSG_SIZE, 0,
+	    (struct sockaddr *)&src_addr, &src_addr_size);
+	if (rc < 0) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "recvfrom failed (%d)", rc);
+		return rc;
+	}
+
+	recv_size = (size_t)rc;
+	*rmsg = msgbuf;
+	*rsize = recv_size;
+
+	return EOK;
+}
+
+int dhcp_transport_init(dhcp_transport_t *dt, service_id_t link_id,
+    dhcp_recv_cb_t recv_cb, void *arg)
+{
+	int fd;
+	struct sockaddr_in laddr;
+	int fid;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcptransport_init()");
+
+	laddr.sin_family = AF_INET;
+	laddr.sin_port = htons(dhcp_client_port);
+	laddr.sin_addr.s_addr = INADDR_ANY;
+
+	fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+	if (fd < 0) {
+		rc = EIO;
+		goto error;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Bind socket.");
+	rc = bind(fd, (struct sockaddr *)&laddr, sizeof(laddr));
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Set socket options");
+	rc = setsockopt(fd, SOL_SOCKET, SO_IPLINK, &link_id, sizeof(link_id));
+	if (rc != EOK) {
+		rc = EIO;
+		goto error;
+	}
+
+	dt->fd = fd;
+	dt->recv_cb = recv_cb;
+	dt->cb_arg = arg;
+
+	fid = fibril_create(dhcp_recv_fibril, dt);
+	if (fid == 0) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	dt->recv_fid = fid;
+	fibril_add_ready(fid);
+
+	return EOK;
+error:
+	closesocket(fd);
+	return rc;
+}
+
+void dhcp_transport_fini(dhcp_transport_t *dt)
+{
+	closesocket(dt->fd);
+}
+
+static int dhcp_recv_fibril(void *arg)
+{
+	dhcp_transport_t *dt = (dhcp_transport_t *)arg;
+	void *msg;
+	size_t size;
+	int rc;
+
+	while (true) {
+		rc = dhcp_recv_msg(dt, &msg, &size);
+		if (rc != EOK)
+			break;
+
+		dt->recv_cb(dt->cb_arg, msg, size);
+	}
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/net/dhcp/transport.h
===================================================================
--- uspace/srv/net/dhcp/transport.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/dhcp/transport.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup dhcp
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#ifndef TRANSPORT_H
+#define TRANSPORT_H
+
+#include <ipc/loc.h>
+#include <sys/types.h>
+
+struct dhcp_transport;
+typedef struct dhcp_transport dhcp_transport_t;
+
+typedef void (*dhcp_recv_cb_t)(void *, void *, size_t);
+
+struct dhcp_transport {
+	/** Transport socket */
+	int fd;
+	/** Receive callback */
+	dhcp_recv_cb_t recv_cb;
+	/** Callback argument */
+	void *cb_arg;
+	/** Receive fibril ID */
+	int recv_fid;
+};
+
+extern int dhcp_transport_init(dhcp_transport_t *, service_id_t,
+    dhcp_recv_cb_t, void *);
+extern void dhcp_transport_fini(dhcp_transport_t *);
+extern int dhcp_send(dhcp_transport_t *dt, void *msg, size_t size);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/net/inetsrv/inet_link.c
===================================================================
--- uspace/srv/net/inetsrv/inet_link.c	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/srv/net/inetsrv/inet_link.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -55,6 +55,6 @@
 static uint16_t ip_ident = 0;
 
-static int inet_link_open(service_id_t);
 static int inet_iplink_recv(iplink_t *, iplink_recv_sdu_t *, uint16_t);
+static inet_link_t *inet_link_get_by_id_locked(sysarg_t);
 
 static iplink_ev_ops_t inet_iplink_ev_ops = {
@@ -62,6 +62,6 @@
 };
 
-static LIST_INITIALIZE(inet_link_list);
-static FIBRIL_MUTEX_INITIALIZE(inet_discovery_lock);
+static LIST_INITIALIZE(inet_links);
+static FIBRIL_MUTEX_INITIALIZE(inet_links_lock);
 
 static addr128_t link_local_node_ip =
@@ -113,51 +113,4 @@
 }
 
-static int inet_link_check_new(void)
-{
-	bool already_known;
-	category_id_t iplink_cat;
-	service_id_t *svcs;
-	size_t count, i;
-	int rc;
-
-	fibril_mutex_lock(&inet_discovery_lock);
-
-	rc = loc_category_get_id("iplink", &iplink_cat, IPC_FLAG_BLOCKING);
-	if (rc != EOK) {
-		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed resolving category 'iplink'.");
-		fibril_mutex_unlock(&inet_discovery_lock);
-		return ENOENT;
-	}
-
-	rc = loc_category_get_svcs(iplink_cat, &svcs, &count);
-	if (rc != EOK) {
-		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed getting list of IP links.");
-		fibril_mutex_unlock(&inet_discovery_lock);
-		return EIO;
-	}
-
-	for (i = 0; i < count; i++) {
-		already_known = false;
-
-		list_foreach(inet_link_list, link_list, inet_link_t, ilink) {
-			if (ilink->svc_id == svcs[i]) {
-				already_known = true;
-				break;
-			}
-		}
-
-		if (!already_known) {
-			log_msg(LOG_DEFAULT, LVL_DEBUG, "Found IP link '%lu'",
-			    (unsigned long) svcs[i]);
-			rc = inet_link_open(svcs[i]);
-			if (rc != EOK)
-				log_msg(LOG_DEFAULT, LVL_ERROR, "Could not open IP link.");
-		}
-	}
-
-	fibril_mutex_unlock(&inet_discovery_lock);
-	return EOK;
-}
-
 static inet_link_t *inet_link_new(void)
 {
@@ -183,5 +136,5 @@
 }
 
-static int inet_link_open(service_id_t sid)
+int inet_link_open(service_id_t sid)
 {
 	inet_link_t *ilink;
@@ -231,5 +184,17 @@
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "Opened IP link '%s'", ilink->svc_name);
-	list_append(&ilink->link_list, &inet_link_list);
+
+	fibril_mutex_lock(&inet_links_lock);
+
+	if (inet_link_get_by_id_locked(sid) != NULL) {
+		fibril_mutex_unlock(&inet_links_lock);
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "Link %zu already open",
+		    sid);
+		rc = EEXIST;
+		goto error;
+	}
+
+	list_append(&ilink->link_list, &inet_links);
+	fibril_mutex_unlock(&inet_links_lock);
 
 	inet_addrobj_t *addr = NULL;
@@ -299,4 +264,5 @@
 	}
 	
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "Configured link '%s'.", ilink->svc_name);
 	return EOK;
 	
@@ -307,23 +273,4 @@
 	inet_link_delete(ilink);
 	return rc;
-}
-
-static void inet_link_cat_change_cb(void)
-{
-	(void) inet_link_check_new();
-}
-
-int inet_link_discovery_start(void)
-{
-	int rc;
-
-	rc = loc_register_cat_change_cb(inet_link_cat_change_cb);
-	if (rc != EOK) {
-		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering callback for IP link "
-		    "discovery (%d).", rc);
-		return rc;
-	}
-
-	return inet_link_check_new();
 }
 
@@ -478,17 +425,25 @@
 }
 
+static inet_link_t *inet_link_get_by_id_locked(sysarg_t link_id)
+{
+	assert(fibril_mutex_is_locked(&inet_links_lock));
+
+	list_foreach(inet_links, link_list, inet_link_t, ilink) {
+		if (ilink->svc_id == link_id)
+			return ilink;
+	}
+
+	return NULL;
+}
+
 inet_link_t *inet_link_get_by_id(sysarg_t link_id)
 {
-	fibril_mutex_lock(&inet_discovery_lock);
-
-	list_foreach(inet_link_list, link_list, inet_link_t, ilink) {
-		if (ilink->svc_id == link_id) {
-			fibril_mutex_unlock(&inet_discovery_lock);
-			return ilink;
-		}
-	}
-
-	fibril_mutex_unlock(&inet_discovery_lock);
-	return NULL;
+	inet_link_t *ilink;
+
+	fibril_mutex_lock(&inet_links_lock);
+	ilink = inet_link_get_by_id_locked(link_id);
+	fibril_mutex_unlock(&inet_links_lock);
+
+	return ilink;
 }
 
@@ -499,20 +454,20 @@
 	size_t count, i;
 
-	fibril_mutex_lock(&inet_discovery_lock);
-	count = list_count(&inet_link_list);
+	fibril_mutex_lock(&inet_links_lock);
+	count = list_count(&inet_links);
 
 	id_list = calloc(count, sizeof(sysarg_t));
 	if (id_list == NULL) {
-		fibril_mutex_unlock(&inet_discovery_lock);
+		fibril_mutex_unlock(&inet_links_lock);
 		return ENOMEM;
 	}
 
 	i = 0;
-	list_foreach(inet_link_list, link_list, inet_link_t, ilink) {
+	list_foreach(inet_links, link_list, inet_link_t, ilink) {
 		id_list[i++] = ilink->svc_id;
 		log_msg(LOG_DEFAULT, LVL_NOTE, "add link to list");
 	}
 
-	fibril_mutex_unlock(&inet_discovery_lock);
+	fibril_mutex_unlock(&inet_links_lock);
 
 	log_msg(LOG_DEFAULT, LVL_NOTE, "return %zu links", count);
Index: uspace/srv/net/inetsrv/inet_link.h
===================================================================
--- uspace/srv/net/inetsrv/inet_link.h	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/srv/net/inetsrv/inet_link.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -41,5 +41,5 @@
 #include "inetsrv.h"
 
-extern int inet_link_discovery_start(void);
+extern int inet_link_open(service_id_t);
 extern int inet_link_send_dgram(inet_link_t *, addr32_t,
     addr32_t, inet_dgram_t *, uint8_t, uint8_t, int);
Index: uspace/srv/net/inetsrv/inetcfg.c
===================================================================
--- uspace/srv/net/inetsrv/inetcfg.c	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/srv/net/inetsrv/inetcfg.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2013 Jiri Svoboda
  * All rights reserved.
  *
@@ -160,4 +160,9 @@
 }
 
+static int inetcfg_link_add(sysarg_t link_id)
+{
+	return inet_link_open(link_id);
+}
+
 static int inetcfg_link_get(sysarg_t link_id, inet_link_info_t *linfo)
 {
@@ -180,4 +185,9 @@
 }
 
+static int inetcfg_link_remove(sysarg_t link_id)
+{
+	return ENOTSUP;
+}
+
 static int inetcfg_sroute_create(char *name, inet_naddr_t *dest,
     inet_addr_t *router, sysarg_t *sroute_id)
@@ -483,4 +493,17 @@
 }
 
+static void inetcfg_link_add_srv(ipc_callid_t callid, ipc_call_t *call)
+{
+	sysarg_t link_id;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inetcfg_link_add_srv()");
+
+	link_id = IPC_GET_ARG1(*call);
+
+	rc = inetcfg_link_add(link_id);
+	async_answer_0(callid, rc);
+}
+
 static void inetcfg_link_get_srv(ipc_callid_t callid, ipc_call_t *call)
 {
@@ -536,4 +559,17 @@
 }
 
+static void inetcfg_link_remove_srv(ipc_callid_t callid, ipc_call_t *call)
+{
+	sysarg_t link_id;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inetcfg_link_remove_srv()");
+
+	link_id = IPC_GET_ARG1(*call);
+
+	rc = inetcfg_link_remove(link_id);
+	async_answer_0(callid, rc);
+}
+
 static void inetcfg_sroute_create_srv(ipc_callid_t iid,
     ipc_call_t *icall)
@@ -714,4 +750,5 @@
 		sysarg_t method = IPC_GET_IMETHOD(call);
 
+		log_msg(LOG_DEFAULT, LVL_DEBUG, "method %d", (int)method);
 		if (!method) {
 			/* The other side has hung up */
@@ -742,6 +779,12 @@
 			inetcfg_get_sroute_list_srv(callid, &call);
 			break;
+		case INETCFG_LINK_ADD:
+			inetcfg_link_add_srv(callid, &call);
+			break;
 		case INETCFG_LINK_GET:
 			inetcfg_link_get_srv(callid, &call);
+			break;
+		case INETCFG_LINK_REMOVE:
+			inetcfg_link_remove_srv(callid, &call);
 			break;
 		case INETCFG_SROUTE_CREATE:
Index: uspace/srv/net/inetsrv/inetsrv.c
===================================================================
--- uspace/srv/net/inetsrv/inetsrv.c	(revision 884c56b66f8457da47e74e0de0dd74015a1df097)
+++ uspace/srv/net/inetsrv/inetsrv.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -124,8 +124,4 @@
 	}
 	
-	rc = inet_link_discovery_start();
-	if (rc != EOK)
-		return EEXIST;
-	
 	return EOK;
 }
Index: uspace/srv/net/nconfsrv/Makefile
===================================================================
--- uspace/srv/net/nconfsrv/Makefile	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/nconfsrv/Makefile	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,36 @@
+#
+# Copyright (c) 2013 Jiri Svoboda
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# - Redistributions of source code must retain the above copyright
+#   notice, this list of conditions and the following disclaimer.
+# - Redistributions in binary form must reproduce the above copyright
+#   notice, this list of conditions and the following disclaimer in the
+#   documentation and/or other materials provided with the distribution.
+# - The name of the author may not be used to endorse or promote products
+#   derived from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+#
+
+USPACE_PREFIX = ../../..
+BINARY = nconfsrv
+
+SOURCES = \
+	iplink.c \
+	nconfsrv.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/srv/net/nconfsrv/iplink.c
===================================================================
--- uspace/srv/net/nconfsrv/iplink.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/nconfsrv/iplink.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup nconfsrv
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#include <stdbool.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <inet/dhcp.h>
+#include <inet/inetcfg.h>
+#include <io/log.h>
+#include <loc.h>
+#include <stdlib.h>
+#include <str.h>
+
+#include "iplink.h"
+#include "nconfsrv.h"
+
+static int ncs_link_add(service_id_t);
+
+static LIST_INITIALIZE(ncs_links);
+static FIBRIL_MUTEX_INITIALIZE(ncs_links_lock);
+
+static int ncs_link_check_new(void)
+{
+	bool already_known;
+	category_id_t iplink_cat;
+	service_id_t *svcs;
+	size_t count, i;
+	int rc;
+
+	fibril_mutex_lock(&ncs_links_lock);
+
+	rc = loc_category_get_id("iplink", &iplink_cat, IPC_FLAG_BLOCKING);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed resolving category 'iplink'.");
+		fibril_mutex_unlock(&ncs_links_lock);
+		return ENOENT;
+	}
+
+	rc = loc_category_get_svcs(iplink_cat, &svcs, &count);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed getting list of IP links.");
+		fibril_mutex_unlock(&ncs_links_lock);
+		return EIO;
+	}
+
+	for (i = 0; i < count; i++) {
+		already_known = false;
+
+		list_foreach(ncs_links, link_list, ncs_link_t, ilink) {
+			if (ilink->svc_id == svcs[i]) {
+				already_known = true;
+				break;
+			}
+		}
+
+		if (!already_known) {
+			log_msg(LOG_DEFAULT, LVL_NOTE, "Found IP link '%lu'",
+			    (unsigned long) svcs[i]);
+			rc = ncs_link_add(svcs[i]);
+			if (rc != EOK)
+				log_msg(LOG_DEFAULT, LVL_ERROR, "Could not add IP link.");
+		}
+	}
+
+	fibril_mutex_unlock(&ncs_links_lock);
+	return EOK;
+}
+
+static ncs_link_t *ncs_link_new(void)
+{
+	ncs_link_t *nlink = calloc(1, sizeof(ncs_link_t));
+
+	if (nlink == NULL) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed allocating link structure. "
+		    "Out of memory.");
+		return NULL;
+	}
+
+	link_initialize(&nlink->link_list);
+
+	return nlink;
+}
+
+static void ncs_link_delete(ncs_link_t *nlink)
+{
+	if (nlink->svc_name != NULL)
+		free(nlink->svc_name);
+
+	free(nlink);
+}
+
+static int ncs_link_add(service_id_t sid)
+{
+	ncs_link_t *nlink;
+	int rc;
+
+	assert(fibril_mutex_is_locked(&ncs_links_lock));
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ncs_link_add()");
+	nlink = ncs_link_new();
+	if (nlink == NULL)
+		return ENOMEM;
+
+	nlink->svc_id = sid;
+
+	rc = loc_service_get_name(sid, &nlink->svc_name);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed getting service name.");
+		goto error;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "Configure link %s", nlink->svc_name);
+	rc = inetcfg_link_add(sid);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed configuring link "
+		    "'%s'.\n", nlink->svc_name);
+		goto error;
+	}
+
+	if (str_lcmp(nlink->svc_name, "net/eth", str_length("net/eth")) == 0) {
+		rc = dhcp_link_add(sid);
+		if (rc != EOK) {
+			log_msg(LOG_DEFAULT, LVL_ERROR, "Failed configuring DHCP on "
+			    " link '%s'.\n", nlink->svc_name);
+			goto error;
+		}
+	}
+
+	list_append(&nlink->link_list, &ncs_links);
+
+	return EOK;
+
+error:
+	ncs_link_delete(nlink);
+	return rc;
+}
+
+static void ncs_link_cat_change_cb(void)
+{
+	(void) ncs_link_check_new();
+}
+
+int ncs_link_discovery_start(void)
+{
+	int rc;
+
+	rc = loc_register_cat_change_cb(ncs_link_cat_change_cb);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering callback for IP link "
+		    "discovery (%d).", rc);
+		return rc;
+	}
+
+	return ncs_link_check_new();
+}
+
+ncs_link_t *ncs_link_get_by_id(sysarg_t link_id)
+{
+	fibril_mutex_lock(&ncs_links_lock);
+
+	list_foreach(ncs_links, link_list, ncs_link_t, nlink) {
+		if (nlink->svc_id == link_id) {
+			fibril_mutex_unlock(&ncs_links_lock);
+			return nlink;
+		}
+	}
+
+	fibril_mutex_unlock(&ncs_links_lock);
+	return NULL;
+}
+
+/** Get IDs of all links. */
+int ncs_link_get_id_list(sysarg_t **rid_list, size_t *rcount)
+{
+	sysarg_t *id_list;
+	size_t count, i;
+
+	fibril_mutex_lock(&ncs_links_lock);
+	count = list_count(&ncs_links);
+
+	id_list = calloc(count, sizeof(sysarg_t));
+	if (id_list == NULL) {
+		fibril_mutex_unlock(&ncs_links_lock);
+		return ENOMEM;
+	}
+
+	i = 0;
+	list_foreach(ncs_links, link_list, ncs_link_t, nlink) {
+		id_list[i++] = nlink->svc_id;
+		log_msg(LOG_DEFAULT, LVL_NOTE, "add link to list");
+	}
+
+	fibril_mutex_unlock(&ncs_links_lock);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "return %zu links", count);
+	*rid_list = id_list;
+	*rcount = count;
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/srv/net/nconfsrv/iplink.h
===================================================================
--- uspace/srv/net/nconfsrv/iplink.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/nconfsrv/iplink.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup nconfsrv
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#ifndef NCONFSRV_IPLINK_H_
+#define NCONFSRV_IPLINK_H_
+
+#include <sys/types.h>
+#include "nconfsrv.h"
+
+extern int ncs_link_discovery_start(void);
+extern ncs_link_t *ncs_link_get_by_id(sysarg_t);
+extern int ncs_link_get_id_list(sysarg_t **, size_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/srv/net/nconfsrv/nconfsrv.c
===================================================================
--- uspace/srv/net/nconfsrv/nconfsrv.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/nconfsrv/nconfsrv.c	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup nconfsrv
+ * @{
+ */
+/**
+ * @file
+ * @brief Network configuration Service
+ */
+
+#include <adt/list.h>
+#include <async.h>
+#include <errno.h>
+#include <fibril_synch.h>
+#include <inet/dhcp.h>
+#include <inet/inetcfg.h>
+#include <io/log.h>
+#include <ipc/inet.h>
+#include <ipc/services.h>
+#include <loc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <net/socket_codes.h>
+#include "iplink.h"
+#include "nconfsrv.h"
+
+#define NAME "nconfsrv"
+
+static void ncs_client_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg);
+
+static int ncs_init(void)
+{
+	service_id_t sid;
+	int rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "ncs_init()");
+
+	rc = inetcfg_init();
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error contacting inet "
+		    "configuration service.");
+		return EIO;
+	}
+
+	rc = dhcp_init();
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error contacting dhcp "
+		    "configuration service.");
+		return EIO;
+	}
+
+	async_set_client_connection(ncs_client_conn);
+
+	rc = loc_server_register(NAME);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server (%d).", rc);
+		return EEXIST;
+	}
+
+	rc = loc_service_register(SERVICE_NAME_NETCONF, &sid);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service (%d).", rc);
+		return EEXIST;
+	}
+
+	rc = ncs_link_discovery_start();
+	if (rc != EOK)
+		return EEXIST;
+
+	return EOK;
+}
+
+static void ncs_client_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg)
+{
+	async_answer_0(iid, ENOTSUP);
+}
+
+int main(int argc, char *argv[])
+{
+	int rc;
+
+	printf(NAME ": HelenOS Network configuration service\n");
+
+	if (log_init(NAME) != EOK) {
+		printf(NAME ": Failed to initialize logging.\n");
+		return 1;
+	}
+
+	rc = ncs_init();
+	if (rc != EOK)
+		return 1;
+
+	printf(NAME ": Accepting connections.\n");
+	task_retval(0);
+	async_manager();
+
+	/* Not reached */
+	return 0;
+}
+
+/** @}
+ */
Index: uspace/srv/net/nconfsrv/nconfsrv.h
===================================================================
--- uspace/srv/net/nconfsrv/nconfsrv.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
+++ uspace/srv/net/nconfsrv/nconfsrv.h	(revision 04ecc6df166749e3133297733e30b460612c119f)
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2013 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup inetsrv
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#ifndef INETSRV_H_
+#define INETSRV_H_
+
+#include <adt/list.h>
+#include <ipc/loc.h>
+
+typedef struct {
+	link_t link_list;
+	service_id_t svc_id;
+	char *svc_name;
+} ncs_link_t;
+
+#endif
+
+/** @}
+ */
