Index: uspace/app/init/init.c
===================================================================
--- uspace/app/init/init.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/app/init/init.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -509,10 +509,9 @@
 	srv_start("/srv/net/loopip");
 	srv_start("/srv/net/ethip");
+	srv_start("/srv/net/dhcp");
 	srv_start("/srv/net/inetsrv");
 	srv_start("/srv/net/tcp");
 	srv_start("/srv/net/udp");
 	srv_start("/srv/net/dnsrsrv");
-	srv_start("/srv/net/dhcp");
-	srv_start("/srv/net/nconfsrv");
 
 	srv_start("/srv/clipboard");
Index: uspace/srv/meson.build
===================================================================
--- uspace/srv/meson.build	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/meson.build	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -61,5 +61,4 @@
 	'net/inetsrv',
 	'net/loopip',
-	'net/nconfsrv',
 	'net/slip',
 	'net/tcp',
Index: uspace/srv/net/dhcp/dhcp.c
===================================================================
--- uspace/srv/net/dhcp/dhcp.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/dhcp/dhcp.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -71,4 +71,6 @@
 static list_t dhcp_links;
 
+bool inetcfg_inited = false;
+
 static void dhcpsrv_discover_timeout(void *);
 static void dhcpsrv_request_timeout(void *);
@@ -468,4 +470,15 @@
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "dhcpsrv_link_add(%zu)", link_id);
 
+	if (!inetcfg_inited) {
+		rc = inetcfg_init();
+		if (rc != EOK) {
+			log_msg(LOG_DEFAULT, LVL_ERROR, "Error contacting "
+			    "inet configuration service.\n");
+			return EIO;
+		}
+
+		inetcfg_inited = true;
+	}
+
 	if (dhcpsrv_link_find(link_id) != NULL) {
 		log_msg(LOG_DEFAULT, LVL_NOTE, "Link %zu already added",
Index: uspace/srv/net/dhcp/main.c
===================================================================
--- uspace/srv/net/dhcp/main.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/dhcp/main.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,5 +38,4 @@
 #include <str_error.h>
 #include <io/log.h>
-#include <inet/inetcfg.h>
 #include <ipc/dhcp.h>
 #include <ipc/services.h>
@@ -60,10 +59,4 @@
 
 	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_fallback_port_handler(dhcp_client_conn, NULL);
Index: uspace/srv/net/doc/doxygroups.h
===================================================================
--- uspace/srv/net/doc/doxygroups.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/doc/doxygroups.h	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -20,9 +20,4 @@
 /**    @addtogroup slip slip
  *     @brief SLIP service
- *     @ingroup net
- */
-
-/**    @addtogroup nconfsrv nconfsrv
- *     @brief Network configuration service
  *     @ingroup net
  */
Index: uspace/srv/net/inetsrv/addrobj.c
===================================================================
--- uspace/srv/net/inetsrv/addrobj.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/addrobj.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,4 +41,6 @@
 #include <io/log.h>
 #include <ipc/loc.h>
+#include <sif.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <str.h>
@@ -211,4 +213,27 @@
 }
 
+/** Count number of non-temporary address objects configured for link.
+ *
+ * @param ilink Inet link
+ * @return Number of address objects configured for this link
+ */
+unsigned inet_addrobj_cnt_by_link(inet_link_t *ilink)
+{
+	unsigned cnt;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_addrobj_cnt_by_link()");
+
+	fibril_mutex_lock(&addr_list_lock);
+
+	cnt = 0;
+	list_foreach(addr_list, addr_list, inet_addrobj_t, naddr) {
+		if (naddr->ilink == ilink && naddr->temp == false)
+			++cnt;
+	}
+
+	fibril_mutex_unlock(&addr_list_lock);
+	return cnt;
+}
+
 /** Send datagram from address object */
 errno_t inet_addrobj_send_dgram(inet_addrobj_t *addr, inet_addr_t *ldest,
@@ -282,4 +307,206 @@
 }
 
+/** Load address object from SIF node.
+ *
+ * @param anode SIF node to load address object from
+ * @return EOK on success or an error code
+ */
+static errno_t inet_addrobj_load(sif_node_t *anode)
+{
+	errno_t rc;
+	const char *sid;
+	const char *snaddr;
+	const char *slink;
+	const char *name;
+	char *endptr;
+	int id;
+	inet_naddr_t naddr;
+	inet_addrobj_t *addr;
+	inet_link_t *link;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_addrobj_load()");
+
+	sid = sif_node_get_attr(anode, "id");
+	if (sid == NULL)
+		return EIO;
+
+	snaddr = sif_node_get_attr(anode, "naddr");
+	if (snaddr == NULL)
+		return EIO;
+
+	slink = sif_node_get_attr(anode, "link");
+	if (slink == NULL)
+		return EIO;
+
+	name = sif_node_get_attr(anode, "name");
+	if (name == NULL)
+		return EIO;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_addrobj_load(): id='%s' "
+	    "naddr='%s' link='%s' name='%s'", sid, snaddr, slink, name);
+
+	id = strtoul(sid, &endptr, 10);
+	if (*endptr != '\0')
+		return EIO;
+
+	rc = inet_naddr_parse(snaddr, &naddr, NULL);
+	if (rc != EOK)
+		return EIO;
+
+	link = inet_link_get_by_svc_name(slink);
+	if (link == NULL) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Link '%s' not found",
+		    slink);
+		return EIO;
+	}
+
+	addr = inet_addrobj_new();
+	if (addr == NULL)
+		return ENOMEM;
+
+	addr->id = id;
+	addr->naddr = naddr;
+	addr->ilink = link;
+	addr->name = str_dup(name);
+
+	if (addr->name == NULL) {
+		inet_addrobj_delete(addr);
+		return ENOMEM;
+	}
+
+	inet_addrobj_add(addr);
+	return EOK;
+}
+
+/** Load address objects from SIF node.
+ *
+ * @param naddrs SIF node to load address objects from
+ * @return EOK on success or an error code
+ */
+errno_t inet_addrobjs_load(sif_node_t *naddrs)
+{
+	sif_node_t *naddr;
+	const char *ntype;
+	errno_t rc;
+
+	naddr = sif_node_first_child(naddrs);
+	while (naddr != NULL) {
+		ntype = sif_node_get_type(naddr);
+		if (str_cmp(ntype, "address") != 0) {
+			rc = EIO;
+			goto error;
+		}
+
+		rc = inet_addrobj_load(naddr);
+		if (rc != EOK)
+			goto error;
+
+		naddr = sif_node_next_child(naddr);
+	}
+
+	return EOK;
+error:
+	return rc;
+
+}
+
+/** Save address object to SIF node.
+ *
+ * @param addr Address object
+ * @param naddr SIF node to save addres to
+ * @return EOK on success or an error code
+ */
+static errno_t inet_addrobj_save(inet_addrobj_t *addr, sif_node_t *naddr)
+{
+	char *str = NULL;
+	errno_t rc;
+	int rv;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_addrobj_save(%p, %p)",
+	    addr, naddr);
+
+	/* id */
+
+	rv = asprintf(&str, "%lu", addr->id);
+	if (rv < 0) {
+		str = NULL;
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = sif_node_set_attr(naddr, "id", str);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+	str = NULL;
+
+	/* dest */
+
+	rc = inet_naddr_format(&addr->naddr, &str);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(naddr, "naddr", str);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+	str = NULL;
+
+	/* link */
+
+	rc = sif_node_set_attr(naddr, "link", addr->ilink->svc_name);
+	if (rc != EOK)
+		goto error;
+
+	/* name */
+
+	rc = sif_node_set_attr(naddr, "name", addr->name);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+
+	return rc;
+error:
+	if (str != NULL)
+		free(str);
+	return rc;
+}
+
+/** Save address objects to SIF node.
+ *
+ * @param cnode SIF node to save address objects to
+ * @return EOK on success or an error code
+ */
+errno_t inet_addrobjs_save(sif_node_t *cnode)
+{
+	sif_node_t *naddr;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_addrobjs_save()");
+
+	fibril_mutex_lock(&addr_list_lock);
+
+	list_foreach(addr_list, addr_list, inet_addrobj_t, addr) {
+		if (addr->temp == false) {
+			rc = sif_node_append_child(cnode, "address", &naddr);
+			if (rc != EOK)
+				goto error;
+
+			rc = inet_addrobj_save(addr, naddr);
+			if (rc != EOK)
+				goto error;
+		}
+	}
+
+	fibril_mutex_unlock(&addr_list_lock);
+	return EOK;
+error:
+	fibril_mutex_unlock(&addr_list_lock);
+	return rc;
+}
+
 /** @}
  */
Index: uspace/srv/net/inetsrv/addrobj.h
===================================================================
--- uspace/srv/net/inetsrv/addrobj.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/addrobj.h	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,4 +38,5 @@
 #define INET_ADDROBJ_H_
 
+#include <sif.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -56,7 +57,10 @@
 extern inet_addrobj_t *inet_addrobj_find_by_name(const char *, inet_link_t *);
 extern inet_addrobj_t *inet_addrobj_get_by_id(sysarg_t);
+extern unsigned inet_addrobj_cnt_by_link(inet_link_t *);
 extern errno_t inet_addrobj_send_dgram(inet_addrobj_t *, inet_addr_t *,
     inet_dgram_t *, uint8_t, uint8_t, int);
 extern errno_t inet_addrobj_get_id_list(sysarg_t **, size_t *);
+extern errno_t inet_addrobjs_load(sif_node_t *);
+extern errno_t inet_addrobjs_save(sif_node_t *);
 
 #endif
Index: uspace/srv/net/inetsrv/inet_link.c
===================================================================
--- uspace/srv/net/inetsrv/inet_link.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/inet_link.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,4 +37,5 @@
 #include <errno.h>
 #include <fibril_synch.h>
+#include <inet/dhcp.h>
 #include <inet/eth_addr.h>
 #include <inet/iplink.h>
@@ -164,5 +165,10 @@
 }
 
-errno_t inet_link_open(service_id_t sid)
+/** Open new IP link while inet_links_lock is held.
+ *
+ * @param sid IP link service ID
+ * @return EOK on success or an error code
+ */
+static errno_t inet_link_open_locked(service_id_t sid)
 {
 	inet_link_t *ilink;
@@ -170,5 +176,7 @@
 	errno_t rc;
 
-	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_link_open()");
+	assert(fibril_mutex_is_locked(&inet_links_lock));
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_link_open_locked()");
 	ilink = inet_link_new();
 	if (ilink == NULL)
@@ -213,8 +221,5 @@
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "Opened IP link '%s'", ilink->svc_name);
 
-	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);
@@ -224,5 +229,4 @@
 
 	list_append(&ilink->link_list, &inet_links);
-	fibril_mutex_unlock(&inet_links_lock);
 
 	inet_addrobj_t *addr = NULL;
@@ -239,4 +243,5 @@
 		addr->ilink = ilink;
 		addr->name = str_dup("v4a");
+		addr->temp = true;
 
 		rc = inet_addrobj_add(addr);
@@ -275,4 +280,5 @@
 		addr6->ilink = ilink;
 		addr6->name = str_dup("v6a");
+		addr6->temp = true;
 
 		rc = inet_addrobj_add(addr6);
@@ -303,4 +309,20 @@
 }
 
+/** Open new IP link..
+ *
+ * @param sid IP link service ID
+ * @return EOK on success or an error code
+ */
+errno_t inet_link_open(service_id_t sid)
+{
+	errno_t rc;
+
+	fibril_mutex_lock(&inet_links_lock);
+	rc = inet_link_open_locked(sid);
+	fibril_mutex_unlock(&inet_links_lock);
+
+	return rc;
+}
+
 /** Send IPv4 datagram over Internet link
  *
@@ -476,4 +498,37 @@
 }
 
+/** Find link by service name while inet_links_lock is held.
+ *
+ * @param svc_name Service name
+ * @return Link or @c NULL if not found
+ */
+static inet_link_t *inet_link_get_by_svc_name_locked(const char *svc_name)
+{
+	assert(fibril_mutex_is_locked(&inet_links_lock));
+
+	list_foreach(inet_links, link_list, inet_link_t, ilink) {
+		if (str_cmp(ilink->svc_name, svc_name) == 0)
+			return ilink;
+	}
+
+	return NULL;
+}
+
+/** Find link by service name.
+ *
+ * @param svc_name Service name
+ * @return Link or @c NULL if not found
+ */
+inet_link_t *inet_link_get_by_svc_name(const char *svc_name)
+{
+	inet_link_t *ilink;
+
+	fibril_mutex_lock(&inet_links_lock);
+	ilink = inet_link_get_by_svc_name_locked(svc_name);
+	fibril_mutex_unlock(&inet_links_lock);
+
+	return ilink;
+}
+
 /** Get IDs of all links. */
 errno_t inet_link_get_id_list(sysarg_t **rid_list, size_t *rcount)
@@ -504,4 +559,212 @@
 }
 
+/** Check for new IP links.
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t inet_link_check_new(void)
+{
+	bool already_known;
+	category_id_t iplink_cat;
+	service_id_t *svcs;
+	inet_link_cfg_info_t info;
+	size_t count, i;
+	errno_t rc;
+
+	fibril_mutex_lock(&inet_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(&inet_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(&inet_links_lock);
+		return EIO;
+	}
+
+	for (i = 0; i < count; i++) {
+		already_known = false;
+
+		list_foreach(inet_links, link_list, inet_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 = inet_link_open_locked(svcs[i]);
+			if (rc != EOK) {
+				log_msg(LOG_DEFAULT, LVL_ERROR, "Could not "
+				    "add IP link.");
+			}
+		} else {
+			/* Clear so it won't be autoconfigured below */
+			svcs[i] = 0;
+		}
+	}
+
+	fibril_mutex_unlock(&inet_links_lock);
+
+	/*
+	 * Auto-configure new links. Note that newly discovered links
+	 * cannot have any configured address objects, because we only
+	 * retain configuration for present links.
+	 */
+	for (i = 0; i < count; i++) {
+		if (svcs[i] != 0) {
+			info.svc_id = svcs[i];
+			rc = loc_service_get_name(info.svc_id, &info.svc_name);
+			if (rc != EOK) {
+				log_msg(LOG_DEFAULT, LVL_ERROR, "Failed "
+				    "getting service name.");
+				return rc;
+			}
+
+			inet_link_autoconf_link(&info);
+			free(info.svc_name);
+			info.svc_name = NULL;
+		}
+	}
+
+	return EOK;
+}
+
+/** IP link category change callback.
+ *
+ * @param arg Not used
+ */
+static void inet_link_cat_change_cb(void *arg)
+{
+	(void) inet_link_check_new();
+}
+
+/** Start IP link discovery.
+ *
+ * @return EOK on success or an error code
+ */
+errno_t inet_link_discovery_start(void)
+{
+	errno_t rc;
+
+	rc = loc_register_cat_change_cb(inet_link_cat_change_cb, NULL);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering callback "
+		    "for IP link discovery: %s.", str_error(rc));
+		return rc;
+	}
+
+	return inet_link_check_new();
+}
+
+/** Start DHCP autoconfiguration on IP link.
+ *
+ * @param info Link information
+ */
+void inet_link_autoconf_link(inet_link_cfg_info_t *info)
+{
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf_link");
+
+	if (str_lcmp(info->svc_name, "net/eth", str_length("net/eth")) == 0) {
+		log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf_link : dhcp link add for link '%s' (%u)",
+		    info->svc_name, (unsigned)info->svc_id);
+		rc = dhcp_link_add(info->svc_id);
+		log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf_link : dhcp link add for link '%s' (%u) DONE",
+		    info->svc_name, (unsigned)info->svc_id);
+		if (rc != EOK) {
+			log_msg(LOG_DEFAULT, LVL_WARN, "Failed configuring "
+			    "DHCP on  link '%s'.\n", info->svc_name);
+		}
+	}
+}
+
+/** Start DHCP autoconfiguration on IP links. */
+errno_t inet_link_autoconf(void)
+{
+	inet_link_cfg_info_t *link_info;
+	size_t link_cnt;
+	size_t acnt;
+	size_t i;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf : initialize DHCP");
+	rc = dhcp_init();
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_WARN, "Failed initializing DHCP "
+		    "service.");
+		return rc;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf : initialize DHCP done");
+
+	fibril_mutex_lock(&inet_links_lock);
+	link_cnt = list_count(&inet_links);
+
+	link_info = calloc(link_cnt, sizeof(inet_link_cfg_info_t));
+	if (link_info == NULL) {
+		fibril_mutex_unlock(&inet_links_lock);
+		return ENOMEM;
+	}
+
+	i = 0;
+	list_foreach(inet_links, link_list, inet_link_t, ilink) {
+		assert(i < link_cnt);
+
+		acnt = inet_addrobj_cnt_by_link(ilink);
+		if (acnt != 0) {
+			/*
+			 * No autoconfiguration if link has configured
+			 * addresses.
+			 */
+			continue;
+		}
+
+		link_info[i].svc_id = ilink->svc_id;
+		link_info[i].svc_name = str_dup(ilink->svc_name);
+		if (link_info[i].svc_name == NULL) {
+			fibril_mutex_unlock(&inet_links_lock);
+			goto error;
+		}
+
+		++i;
+	}
+
+	fibril_mutex_unlock(&inet_links_lock);
+
+	/* Update link_cnt to include only links slated for autoconfig. */
+	link_cnt = i;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf : autoconf links...");
+
+	for (i = 0; i < link_cnt; i++)
+		inet_link_autoconf_link(&link_info[i]);
+
+	for (i = 0; i < link_cnt; i++) {
+		if (link_info[i].svc_name != NULL)
+			free(link_info[i].svc_name);
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_link_autoconf : autoconf links done");
+	return EOK;
+error:
+	for (i = 0; i < link_cnt; i++) {
+		if (link_info[i].svc_name != NULL)
+			free(link_info[i].svc_name);
+	}
+	free(link_info);
+	return ENOMEM;
+}
+
 /** @}
  */
Index: uspace/srv/net/inetsrv/inet_link.h
===================================================================
--- uspace/srv/net/inetsrv/inet_link.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/inet_link.h	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -49,5 +49,9 @@
     uint8_t, uint8_t, int);
 extern inet_link_t *inet_link_get_by_id(sysarg_t);
+extern inet_link_t *inet_link_get_by_svc_name(const char *);
 extern errno_t inet_link_get_id_list(sysarg_t **, size_t *);
+extern errno_t inet_link_discovery_start(void);
+extern errno_t inet_link_autoconf(void);
+extern void inet_link_autoconf_link(inet_link_cfg_info_t *);
 
 #endif
Index: uspace/srv/net/inetsrv/inetcfg.c
===================================================================
--- uspace/srv/net/inetsrv/inetcfg.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/inetcfg.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -92,4 +92,10 @@
 	}
 
+	rc = inet_cfg_sync(cfg);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error saving configuration.");
+		return rc;
+	}
+
 	return EOK;
 }
@@ -98,4 +104,10 @@
 {
 	inet_addrobj_t *addr;
+	inet_link_cfg_info_t info;
+	unsigned acnt;
+	inet_link_t *ilink;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inetcfg_addr_delete()");
 
 	addr = inet_addrobj_get_by_id(addr_id);
@@ -103,7 +115,38 @@
 		return ENOENT;
 
+	info.svc_id = addr->ilink->svc_id;
+	info.svc_name = str_dup(addr->ilink->svc_name);
+	if (info.svc_name == NULL)
+		return ENOMEM;
+
 	inet_addrobj_remove(addr);
 	inet_addrobj_delete(addr);
 
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inetcfg_addr_delete(): sync");
+
+	rc = inet_cfg_sync(cfg);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error saving configuration.");
+		free(info.svc_name);
+		return rc;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inetcfg_addr_delete(): get link by ID");
+
+	ilink = inet_link_get_by_id(info.svc_id);
+	if (ilink == NULL) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error finding link.");
+		return ENOENT;
+	}
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inetcfg_addr_delete(): check addrobj count");
+
+	/* If there are no configured addresses left, autoconfigure link */
+	acnt = inet_addrobj_cnt_by_link(ilink);
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inetcfg_addr_delete(): acnt=%u", acnt);
+	if (acnt == 0)
+		inet_link_autoconf_link(&info);
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inetcfg_addr_delete(): DONE");
 	return EOK;
 }
@@ -193,4 +236,5 @@
     inet_addr_t *router, sysarg_t *sroute_id)
 {
+	errno_t rc;
 	inet_sroute_t *sroute;
 
@@ -207,4 +251,11 @@
 
 	*sroute_id = sroute->id;
+
+	rc = inet_cfg_sync(cfg);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error saving configuration.");
+		return rc;
+	}
+
 	return EOK;
 }
@@ -212,4 +263,5 @@
 static errno_t inetcfg_sroute_delete(sysarg_t sroute_id)
 {
+	errno_t rc;
 	inet_sroute_t *sroute;
 
@@ -220,4 +272,10 @@
 	inet_sroute_remove(sroute);
 	inet_sroute_delete(sroute);
+
+	rc = inet_cfg_sync(cfg);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_ERROR, "Error saving configuration.");
+		return rc;
+	}
 
 	return EOK;
@@ -805,4 +863,143 @@
 }
 
+static errno_t inet_cfg_load(const char *cfg_path)
+{
+	sif_doc_t *doc = NULL;
+	sif_node_t *rnode;
+	sif_node_t *naddrs;
+	sif_node_t *nroutes;
+	const char *ntype;
+	errno_t rc;
+
+	rc = sif_load(cfg_path, &doc);
+	if (rc != EOK)
+		goto error;
+
+	rnode = sif_get_root(doc);
+	naddrs = sif_node_first_child(rnode);
+	ntype = sif_node_get_type(naddrs);
+	if (str_cmp(ntype, "addresses") != 0) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = inet_addrobjs_load(naddrs);
+	if (rc != EOK)
+		goto error;
+
+	nroutes = sif_node_next_child(naddrs);
+	ntype = sif_node_get_type(nroutes);
+	if (str_cmp(ntype, "static-routes") != 0) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = inet_sroutes_load(nroutes);
+	if (rc != EOK)
+		goto error;
+
+	sif_delete(doc);
+	return EOK;
+error:
+	if (doc != NULL)
+		sif_delete(doc);
+	return rc;
+
+}
+
+static errno_t inet_cfg_save(const char *cfg_path)
+{
+	sif_doc_t *doc = NULL;
+	sif_node_t *rnode;
+	sif_node_t *nsroutes;
+	sif_node_t *naddrobjs;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_cfg_save(%s)", cfg_path);
+
+	rc = sif_new(&doc);
+	if (rc != EOK)
+		goto error;
+
+	rnode = sif_get_root(doc);
+
+	/* Address objects */
+
+	rc = sif_node_append_child(rnode, "addresses", &naddrobjs);
+	if (rc != EOK)
+		goto error;
+
+	rc = inet_addrobjs_save(naddrobjs);
+	if (rc != EOK)
+		goto error;
+
+	/* Static routes */
+
+	rc = sif_node_append_child(rnode, "static-routes", &nsroutes);
+	if (rc != EOK)
+		goto error;
+
+	rc = inet_sroutes_save(nsroutes);
+	if (rc != EOK)
+		goto error;
+
+	/* Save */
+
+	rc = sif_save(doc, cfg_path);
+	if (rc != EOK)
+		goto error;
+
+	sif_delete(doc);
+	return EOK;
+error:
+	if (doc != NULL)
+		sif_delete(doc);
+	return rc;
+}
+
+/** Open internet server configuration.
+ *
+ * @param cfg_path Configuration file path
+ * @param rcfg Place to store pointer to configuration object
+ * @return EOK on success or an error code
+ */
+errno_t inet_cfg_open(const char *cfg_path, inet_cfg_t **rcfg)
+{
+	inet_cfg_t *cfg;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_cfg_open(%s)", cfg_path);
+
+	rc = inet_cfg_load(cfg_path);
+	if (rc != EOK) {
+		log_msg(LOG_DEFAULT, LVL_WARN, "inet_cfg_open(%s) :"
+		    "could not load configuration.", cfg_path);
+	}
+
+	cfg = calloc(1, sizeof(inet_cfg_t));
+	if (cfg == NULL)
+		return ENOMEM;
+
+	cfg->cfg_path = str_dup(cfg_path);
+	if (cfg->cfg_path == NULL) {
+		free(cfg);
+		return ENOMEM;
+	}
+
+	*rcfg = cfg;
+	return EOK;
+}
+
+errno_t inet_cfg_sync(inet_cfg_t *cfg)
+{
+	log_msg(LOG_DEFAULT, LVL_NOTE, "inet_cfg_sync(cfg=%p)", cfg);
+	return inet_cfg_save(cfg->cfg_path);
+}
+
+void inet_cfg_close(inet_cfg_t *cfg)
+{
+	free(cfg);
+}
+
 /** @}
  */
Index: uspace/srv/net/inetsrv/inetcfg.h
===================================================================
--- uspace/srv/net/inetsrv/inetcfg.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/inetcfg.h	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,5 +38,11 @@
 #define INETCFG_H_
 
+#include <errno.h>
+#include "inetsrv.h"
+
 extern void inet_cfg_conn(ipc_call_t *, void *);
+extern errno_t inet_cfg_open(const char *, inet_cfg_t **);
+extern errno_t inet_cfg_sync(inet_cfg_t *);
+extern void inet_cfg_close(inet_cfg_t *);
 
 #endif
Index: uspace/srv/net/inetsrv/inetsrv.c
===================================================================
--- uspace/srv/net/inetsrv/inetsrv.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/inetsrv.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -79,6 +79,9 @@
 };
 
+static const char *inet_cfg_path = "/w/cfg/inetsrv.sif";
+
 static FIBRIL_MUTEX_INITIALIZE(client_list_lock);
 static LIST_INITIALIZE(client_list);
+inet_cfg_t *cfg;
 
 static void inet_default_conn(ipc_call_t *, void *);
@@ -86,10 +89,19 @@
 static errno_t inet_init(void)
 {
+	port_id_t port;
+	errno_t rc;
 	loc_srv_t *srv;
 
 	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_init()");
 
-	port_id_t port;
-	errno_t rc = async_create_port(INTERFACE_INET,
+	rc = inet_link_discovery_start();
+	if (rc != EOK)
+		return rc;
+
+	rc = inet_cfg_open(inet_cfg_path, &cfg);
+	if (rc != EOK)
+		return rc;
+
+	rc = async_create_port(INTERFACE_INET,
 	    inet_default_conn, NULL, &port);
 	if (rc != EOK)
@@ -556,5 +568,8 @@
 
 	printf(NAME ": Accepting connections.\n");
+
 	task_retval(0);
+
+	(void)inet_link_autoconf();
 	async_manager();
 
Index: uspace/srv/net/inetsrv/inetsrv.h
===================================================================
--- uspace/srv/net/inetsrv/inetsrv.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/inetsrv.h	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -44,4 +44,5 @@
 #include <inet/iplink.h>
 #include <ipc/loc.h>
+#include <sif.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -114,10 +115,24 @@
 } inet_link_t;
 
+/** Link information needed for autoconfiguration */
 typedef struct {
+	service_id_t svc_id;
+	char *svc_name;
+} inet_link_cfg_info_t;
+
+/** Address object */
+typedef struct {
+	/** Link to list of addresses */
 	link_t addr_list;
+	/** Address object ID */
 	sysarg_t id;
+	/** Network address */
 	inet_naddr_t naddr;
+	/** Underlying IP link */
 	inet_link_t *ilink;
+	/** Address name */
 	char *name;
+	/** Temporary object */
+	bool temp;
 } inet_addrobj_t;
 
@@ -125,4 +140,5 @@
 typedef struct {
 	link_t sroute_list;
+	/** ID */
 	sysarg_t id;
 	/** Destination network */
@@ -130,5 +146,8 @@
 	/** Router via which to route packets */
 	inet_addr_t router;
+	/** Route name */
 	char *name;
+	/** Temporary route */
+	bool temp;
 } inet_sroute_t;
 
@@ -152,4 +171,12 @@
 } inet_dir_t;
 
+/** Internet server configuration */
+typedef struct {
+	/** Configuration file path */
+	char *cfg_path;
+} inet_cfg_t;
+
+extern inet_cfg_t *cfg;
+
 extern errno_t inet_ev_recv(inet_client_t *, inet_dgram_t *);
 extern errno_t inet_recv_packet(inet_packet_t *);
Index: uspace/srv/net/inetsrv/meson.build
===================================================================
--- uspace/srv/net/inetsrv/meson.build	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/meson.build	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2021 Jiri Svoboda
+# Copyright (c) 2024 Jiri Svoboda
 # All rights reserved.
 #
@@ -27,5 +27,5 @@
 #
 
-deps = [ 'inet' ]
+deps = [ 'inet', 'sif' ]
 src = files(
 	'addrobj.c',
Index: uspace/srv/net/inetsrv/sroute.c
===================================================================
--- uspace/srv/net/inetsrv/sroute.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/sroute.c	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -40,4 +40,6 @@
 #include <io/log.h>
 #include <ipc/loc.h>
+#include <sif.h>
+#include <stdio.h>
 #include <stdlib.h>
 #include <str.h>
@@ -210,4 +212,201 @@
 }
 
+/** Load static route from SIF node.
+ *
+ * @param nroute SIF node to load static route from
+ * @return EOK on success or an error code
+ */
+static errno_t inet_sroute_load(sif_node_t *nroute)
+{
+	errno_t rc;
+	const char *sid;
+	const char *sdest;
+	const char *srouter;
+	const char *name;
+	char *endptr;
+	int id;
+	inet_naddr_t dest;
+	inet_addr_t router;
+	inet_sroute_t *sroute;
+
+	sid = sif_node_get_attr(nroute, "id");
+	if (sid == NULL)
+		return EIO;
+
+	sdest = sif_node_get_attr(nroute, "dest");
+	if (sdest == NULL)
+		return EIO;
+
+	srouter = sif_node_get_attr(nroute, "router");
+	if (srouter == NULL)
+		return EIO;
+
+	name = sif_node_get_attr(nroute, "name");
+	if (name == NULL)
+		return EIO;
+
+	id = strtoul(sid, &endptr, 10);
+	if (*endptr != '\0')
+		return EIO;
+
+	rc = inet_naddr_parse(sdest, &dest, NULL);
+	if (rc != EOK)
+		return EIO;
+
+	rc = inet_addr_parse(srouter, &router, NULL);
+	if (rc != EOK)
+		return EIO;
+
+	sroute = inet_sroute_new();
+	if (sroute == NULL)
+		return ENOMEM;
+
+	sroute->id = id;
+	sroute->dest = dest;
+	sroute->router = router;
+	sroute->name = str_dup(name);
+
+	if (sroute->name == NULL) {
+		inet_sroute_delete(sroute);
+		return ENOMEM;
+	}
+
+	inet_sroute_add(sroute);
+	return EOK;
+}
+
+/** Load static routes from SIF node.
+ *
+ * @param nroutes SIF node to load static routes from
+ * @return EOK on success or an error code
+ */
+errno_t inet_sroutes_load(sif_node_t *nroutes)
+{
+	sif_node_t *nroute;
+	const char *ntype;
+	errno_t rc;
+
+	nroute = sif_node_first_child(nroutes);
+	while (nroute != NULL) {
+		ntype = sif_node_get_type(nroute);
+		if (str_cmp(ntype, "route") != 0) {
+			rc = EIO;
+			goto error;
+		}
+
+		rc = inet_sroute_load(nroute);
+		if (rc != EOK)
+			goto error;
+
+		nroute = sif_node_next_child(nroute);
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
+/** Save static route to SIF node.
+ *
+ * @param sroute Static route
+ * @param nroute SIF node to save static route to
+ * @return EOK on success or an error code
+ */
+static errno_t inet_sroute_save(inet_sroute_t *sroute, sif_node_t *nroute)
+{
+	char *str = NULL;
+	errno_t rc;
+	int rv;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_sroute_save(%p, %p)",
+	    sroute, nroute);
+
+	/* id */
+
+	rv = asprintf(&str, "%lu", sroute->id);
+	if (rv < 0) {
+		str = NULL;
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = sif_node_set_attr(nroute, "id", str);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+	str = NULL;
+
+	/* dest */
+
+	rc = inet_naddr_format(&sroute->dest, &str);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(nroute, "dest", str);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+	str = NULL;
+
+	/* router */
+
+	rc = inet_addr_format(&sroute->router, &str);
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_node_set_attr(nroute, "router", str);
+	if (rc != EOK)
+		goto error;
+
+	/* name */
+
+	rc = sif_node_set_attr(nroute, "name", sroute->name);
+	if (rc != EOK)
+		goto error;
+
+	free(str);
+
+	return rc;
+error:
+	if (str != NULL)
+		free(str);
+	return rc;
+}
+
+/** Save static routes to SIF node.
+ *
+ * @param nroutes SIF node to save static routes to
+ * @return EOK on success or an error code
+ */
+errno_t inet_sroutes_save(sif_node_t *nroutes)
+{
+	sif_node_t *nroute;
+	errno_t rc;
+
+	log_msg(LOG_DEFAULT, LVL_DEBUG, "inet_sroutes_save()");
+
+	fibril_mutex_lock(&sroute_list_lock);
+
+	list_foreach(sroute_list, sroute_list, inet_sroute_t, sroute) {
+		if (sroute->temp == false) {
+			rc = sif_node_append_child(nroutes, "route", &nroute);
+			if (rc != EOK)
+				goto error;
+
+			rc = inet_sroute_save(sroute, nroute);
+			if (rc != EOK)
+				goto error;
+		}
+	}
+
+	fibril_mutex_unlock(&sroute_list_lock);
+	return EOK;
+error:
+	fibril_mutex_unlock(&sroute_list_lock);
+	return rc;
+}
+
 /** @}
  */
Index: uspace/srv/net/inetsrv/sroute.h
===================================================================
--- uspace/srv/net/inetsrv/sroute.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ uspace/srv/net/inetsrv/sroute.h	(revision 1bbc6dc64db723b798344b36c45a156bdd6ccc13)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2012 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -38,4 +38,5 @@
 #define INET_SROUTE_H_
 
+#include <sif.h>
 #include <stddef.h>
 #include <stdint.h>
@@ -52,4 +53,6 @@
     inet_dgram_t *, uint8_t, uint8_t, int);
 extern errno_t inet_sroute_get_id_list(sysarg_t **, size_t *);
+extern errno_t inet_sroutes_load(sif_node_t *);
+extern errno_t inet_sroutes_save(sif_node_t *);
 
 #endif
Index: pace/srv/net/nconfsrv/iplink.c
===================================================================
--- uspace/srv/net/nconfsrv/iplink.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ 	(revision )
@@ -1,237 +1,0 @@
-/*
- * 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 <str_error.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 errno_t ncs_link_add(service_id_t);
-
-static LIST_INITIALIZE(ncs_links);
-static FIBRIL_MUTEX_INITIALIZE(ncs_links_lock);
-
-static errno_t ncs_link_check_new(void)
-{
-	bool already_known;
-	category_id_t iplink_cat;
-	service_id_t *svcs;
-	size_t count, i;
-	errno_t 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 errno_t ncs_link_add(service_id_t sid)
-{
-	ncs_link_t *nlink;
-	errno_t 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 *arg)
-{
-	(void) ncs_link_check_new();
-}
-
-errno_t ncs_link_discovery_start(void)
-{
-	errno_t rc;
-
-	rc = loc_register_cat_change_cb(ncs_link_cat_change_cb, NULL);
-	if (rc != EOK) {
-		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering callback for IP link "
-		    "discovery: %s.", str_error(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. */
-errno_t 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: pace/srv/net/nconfsrv/iplink.h
===================================================================
--- uspace/srv/net/nconfsrv/iplink.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ 	(revision )
@@ -1,50 +1,0 @@
-/*
- * 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 <stddef.h>
-#include "nconfsrv.h"
-
-extern errno_t ncs_link_discovery_start(void);
-extern ncs_link_t *ncs_link_get_by_id(sysarg_t);
-extern errno_t ncs_link_get_id_list(sysarg_t **, size_t *);
-
-#endif
-
-/** @}
- */
Index: pace/srv/net/nconfsrv/meson.build
===================================================================
--- uspace/srv/net/nconfsrv/meson.build	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ 	(revision )
@@ -1,30 +1,0 @@
-#
-# Copyright (c) 2021 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.
-#
-
-deps = [ 'inet' ]
-src = files('iplink.c', 'nconfsrv.c')
Index: pace/srv/net/nconfsrv/nconfsrv.c
===================================================================
--- uspace/srv/net/nconfsrv/nconfsrv.c	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ 	(revision )
@@ -1,134 +1,0 @@
-/*
- * Copyright (c) 2023 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 <str_error.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 <task.h>
-#include "iplink.h"
-#include "nconfsrv.h"
-
-#define NAME "nconfsrv"
-
-static void ncs_client_conn(ipc_call_t *icall, void *arg);
-
-static errno_t ncs_init(void)
-{
-	service_id_t sid;
-	loc_srv_t *srv;
-	errno_t 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_fallback_port_handler(ncs_client_conn, NULL);
-
-	rc = loc_server_register(NAME, &srv);
-	if (rc != EOK) {
-		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering server: %s.", str_error(rc));
-		return EEXIST;
-	}
-
-	rc = loc_service_register(srv, SERVICE_NAME_NETCONF, &sid);
-	if (rc != EOK) {
-		loc_server_unregister(srv);
-		log_msg(LOG_DEFAULT, LVL_ERROR, "Failed registering service: %s.", str_error(rc));
-		return EEXIST;
-	}
-
-	rc = ncs_link_discovery_start();
-	if (rc != EOK) {
-		loc_service_unregister(srv, sid);
-		loc_server_unregister(srv);
-		return EEXIST;
-	}
-
-	return EOK;
-}
-
-static void ncs_client_conn(ipc_call_t *icall, void *arg)
-{
-	async_answer_0(icall, ENOTSUP);
-}
-
-int main(int argc, char *argv[])
-{
-	errno_t 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: pace/srv/net/nconfsrv/nconfsrv.h
===================================================================
--- uspace/srv/net/nconfsrv/nconfsrv.h	(revision 096c0786530d26ecc60b3c63b61e396df267c704)
+++ 	(revision )
@@ -1,52 +1,0 @@
-/*
- * 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
-
-/** @}
- */
