diff --git a/Makefile.dep b/Makefile.dep
index 4c55e2e275e8de5234096ae957276bdc1e3d158f..e083323e1b0e67d82efda6c7a4107840c9560b60 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -768,6 +768,21 @@ ifneq (,$(filter rdcli_simple,$(USEMODULE)))
   USEMODULE += fmt
 endif
 
+ifneq (,$(filter rdcli_standalone,$(USEMODULE)))
+  USEMODULE += rdcli
+  USEMODULE += xtimer
+endif
+
+ifneq (,$(filter rdcli,$(USEMODULE)))
+  USEMODULE += rdcli_common
+  USEMODULE += core_thread_flags
+  USEMODULE += gcoap
+  USEMODULE += fmt
+  ifneq (,$(filter shell_commands,$(USEMODULE)))
+    USEMODULE += sock_util
+  endif
+endif
+
 ifneq (,$(filter rdcli_common,$(USEMODULE)))
   USEMODULE += fmt
   USEMODULE += gcoap
diff --git a/examples/rdcli/Makefile b/examples/rdcli/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..cf1007f7ecaae243354cb6609d289958d8a5db32
--- /dev/null
+++ b/examples/rdcli/Makefile
@@ -0,0 +1,40 @@
+# name of your application
+APPLICATION = rdcli
+
+# If no BOARD is found in the environment, use this default:
+BOARD ?= native
+
+# This has to be the absolute path to the RIOT base directory:
+RIOTBASE ?= $(CURDIR)/../..
+
+BOARD_INSUFFICIENT_MEMORY := arduino-duemilanove arduino-mega2560 arduino-uno \
+                             chronos hifive1 mega-xplained msb-430 msb-430h  \
+                             nucleo-f030r8 nucleo-l053r8 nucleo-f031k6 \
+                             nucleo-f042k6 nucleo-f303k8 nucleo-f334r8 \
+                             nucleo-l031k6 stm32f0discovery telosb waspmote-pro \
+                             wsn430-v1_3b wsn430-v1_4 z1
+
+USEMODULE += gnrc_netdev_default
+USEMODULE += auto_init_gnrc_netif
+USEMODULE += gnrc_ipv6_default
+USEMODULE += gnrc_icmpv6_echo
+
+USEMODULE += rdcli_standalone
+
+USEMODULE += shell
+USEMODULE += shell_commands
+USEMODULE += ps
+USEMODULE += fmt
+
+# Comment this out to disable code in RIOT that does safety checking
+# which is not needed in a production environment but helps in the
+# development process:
+CFLAGS += -DDEVELHELP
+
+# For debugging and demonstration purposes, we limit the lifetime to 60s
+CFLAGS += -DRDCLI_LT=60
+
+# Change this to 0 show compiler invocation lines by default:
+QUIET ?= 1
+
+include $(RIOTBASE)/Makefile.include
diff --git a/examples/rdcli/README.md b/examples/rdcli/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..315e6087e6488a836545c084e2302c282336f790
--- /dev/null
+++ b/examples/rdcli/README.md
@@ -0,0 +1,20 @@
+CoRE Resource Directory Client Example
+======================================
+This example application demonstrates the usage of RIOT's Resource Directory
+(RD) client module, called `rdcli`. This module supports the registration,
+update, and removal processes as defined in the
+[Resource Directory Draft](https://tools.ietf.org/html/draft-ietf-core-resource-directory-14).
+
+Usage
+=====
+The examples includes a shell command that you can use to interact with a given
+RD server, called `rdcli`. Simply use that shell command without parameters for
+more information on its usage.
+
+Some connection parameters are configured statically during compile time,
+namely the lifetime (`RDCLI_LT`) and the node's endpoint name (`RDCLI_EP`). You
+can change these values during command line by overriding these values in the
+application's Makefile, e.g. add
+```
+CFLAGS += "-DRDCLI_EP=\"MyNewEpName\""
+```
diff --git a/examples/rdcli/main.c b/examples/rdcli/main.c
new file mode 100644
index 0000000000000000000000000000000000000000..f60f794a96fcd7eec2bc3628616258480b807dab
--- /dev/null
+++ b/examples/rdcli/main.c
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2017-2018 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     examples
+ * @{
+ *
+ * @file
+ * @brief       CoRE Resource Directory client (rdcli) example application
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+
+#include "fmt.h"
+#include "shell.h"
+#include "net/ipv6/addr.h"
+#include "net/gcoap.h"
+#include "net/rdcli_common.h"
+#include "net/rdcli_standalone.h"
+
+#define MAIN_QUEUE_SIZE     (8)
+static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];
+
+/* we will use a custom event handler for dumping rdcli_standalone events */
+static void _on_rdcli_event(rdcli_standalone_event_t event)
+{
+    switch (event) {
+        case RDCLI_REGISTERED:
+            puts("rdcli event: now registered with a RD");
+            break;
+        case RDCLI_DEREGISTERED:
+            puts("rdcli event: dropped client registration");
+            break;
+        case RDCLI_UPDATED:
+            puts("rdcli event: successfully updated client registration");
+            break;
+    }
+}
+
+/* define some dummy CoAP resources */
+static ssize_t _handler_dummy(coap_pkt_t *pdu,
+                              uint8_t *buf, size_t len, void *ctx)
+{
+    (void)ctx;
+
+    /* get random data */
+    int16_t val = 23;
+
+    gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
+    size_t plen = fmt_s16_dec((char *)pdu->payload, val);
+    return gcoap_finish(pdu, plen, COAP_FORMAT_TEXT);
+}
+
+static ssize_t _handler_info(coap_pkt_t *pdu,
+                             uint8_t *buf, size_t len, void *ctx)
+{
+    (void)ctx;
+
+    gcoap_resp_init(pdu, buf, len, COAP_CODE_CONTENT);
+    size_t slen = sizeof("SOME NODE INFOMRATION");
+    memcpy(pdu->payload, "SOME NODE INFOMRATION", slen);
+    return gcoap_finish(pdu, slen, COAP_FORMAT_TEXT);
+}
+
+static const coap_resource_t _resources[] = {
+    { "/node/info",  COAP_GET, _handler_info, NULL },
+    { "/sense/hum",  COAP_GET, _handler_dummy, NULL },
+    { "/sense/temp", COAP_GET, _handler_dummy, NULL }
+};
+
+static gcoap_listener_t _listener = {
+    .resources     = (coap_resource_t *)&_resources[0],
+    .resources_len = sizeof(_resources) / sizeof(_resources[0]),
+    .next          = NULL
+};
+
+int main(void)
+{
+    /* we need a message queue for the thread running the shell in order to
+     * receive potentially fast incoming networking packets */
+    msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
+
+    puts("CoRE RD client example!\n");
+
+    /* setup CoAP resources */
+    gcoap_register_listener(&_listener);
+
+    /* register event callback with rdcli_standalone */
+    rdcli_standalone_reg_cb(_on_rdcli_event);
+
+    puts("Client information:");
+    printf("  ep: %s\n", rdcli_common_get_ep());
+    printf("  lt: %is\n", (int)RDCLI_LT);
+
+    char line_buf[SHELL_DEFAULT_BUFSIZE];
+    shell_run(NULL, line_buf, SHELL_DEFAULT_BUFSIZE);
+
+    return 0;
+}
diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk
index c57bff7d0dda09d8f5dd3226bf75b646eb7231bd..c5e37daaefed1663c5a830e0bec86284fa812465 100644
--- a/makefiles/pseudomodules.inc.mk
+++ b/makefiles/pseudomodules.inc.mk
@@ -65,6 +65,7 @@ PSEUDOMODULES += pktqueue
 PSEUDOMODULES += printf_float
 PSEUDOMODULES += prng
 PSEUDOMODULES += prng_%
+PSEUDOMODULES += rdcli_standalone
 PSEUDOMODULES += rdcli_simple_standalone
 PSEUDOMODULES += saul_adc
 PSEUDOMODULES += saul_default
diff --git a/sys/Makefile b/sys/Makefile
index 77e8832dc65ddbcea6df64ba3955b7a572c163b9..9d63db742c4f4c5108e8b7cff43d02e15120a2c9 100644
--- a/sys/Makefile
+++ b/sys/Makefile
@@ -133,6 +133,10 @@ endif
 ifneq (,$(filter rdcli_simple,$(USEMODULE)))
     DIRS += net/application_layer/rdcli_simple
 endif
+ifneq (,$(filter rdcli,$(USEMODULE)))
+    DIRS += net/application_layer/rdcli
+endif
+
 
 DIRS += $(dir $(wildcard $(addsuffix /Makefile, $(USEMODULE))))
 
diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c
index 9abb26f47bbc1eef5ae48f31f0649a74ecca6b96..755f5f0ec1e4b0c37f8ae7711d80cfeebd181981 100644
--- a/sys/auto_init/auto_init.c
+++ b/sys/auto_init/auto_init.c
@@ -163,6 +163,11 @@ void auto_init(void)
     extern void rdcli_common_init(void);
     rdcli_common_init();
 #endif
+#ifdef MODULE_RDCLI_STANDALONE
+    DEBUG("Auto init rdcli_standalone\n");
+    extern void rdcli_standalone_run(void);
+    rdcli_standalone_run();
+#endif
 #ifdef MODULE_RDCLI_SIMPLE_STANDALONE
     DEBUG("Auto init rdcli_simple module\n");
     extern void rdcli_simple_run(void);
diff --git a/sys/include/net/rdcli.h b/sys/include/net/rdcli.h
new file mode 100644
index 0000000000000000000000000000000000000000..f9b2a049f52e246c46637b0fbae174a04169c7c4
--- /dev/null
+++ b/sys/include/net/rdcli.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2017-2018 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    net_rdcli CoRE RD Endpoint Library
+ * @ingroup     net
+ * @brief       Library for using RIOT as CoRE Resource Directory endpoint
+ *
+ * This module implements a CoRE Resource Directory endpoint library, that
+ * allows RIOT nodes to register themselves with resource directories.
+ * It implements the standard endpoint functionality as defined in
+ * draft-ietf-core-resource-directory-15.
+ * @see https://tools.ietf.org/html/draft-ietf-core-resource-directory-15
+ *
+ * @note        As the name of this library (`rdcli`) can be misleading in
+ *              context of the RD draft (endpoint vs client), this library
+ *              will most likely undergo a name change in the near future...
+ *
+ * # Design Decisions
+ * - all operations provided by this module are fully synchronous, meaning that
+ *   the functions will block until an operation is successful or will time out
+ * - the implementation limits the client to be registered with a single RD at
+ *   any point in time
+ *
+ * @{
+ *
+ * @file
+ * @brief       CoRE Resource Directory endpoint interface
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ */
+
+#ifndef NET_RDCLI_H
+#define NET_RDCLI_H
+
+#include "net/sock/udp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Return values and error codes used by this module
+ */
+enum {
+    RDCLI_OK        =  0,   /**< everything went as expected */
+    RDCLI_TIMEOUT   = -1,   /**< no response from the network */
+    RDCLI_ERR       = -2,   /**< internal error or invalid reply */
+    RDCLI_NORD      = -3,   /**< not connected to an RD */
+    RDCLI_OVERFLOW  = -4,   /**< internal buffers can not handle input */
+};
+
+/**
+ * @brief   Discover the registration interface resource of a RD
+ *
+ * @param[in] remote    remote endpoint of the target RD
+ * @param[out] regif    the registration interface is written to this buffer
+ * @param[in] maxlen    size of @p regif
+ *
+ * @return  RDCLI_OK on success
+ * @return  RDCLI_TIMEOUT if the discovery request times out
+ * @return  RDCLI_NORD if addressed endpoint is not a RD
+ * @return  RDCLI_ERR on any other internal error
+ */
+int rdcli_discover_regif(const sock_udp_ep_t *remote,
+                         char *regif, size_t maxlen);
+
+/**
+ * @brief   Initiate the node registration by sending an empty push
+ *
+ * - if registration fails (e.g. timeout), we are not associated with any RD
+ *   anymore (even if we have been before we called rdcli_register)
+ *
+ * @note    In case a multicast address is given, the @p regif parameter MUST be
+ *          NULL. The first RD responding to the request will be chosen and all
+ *          replies from other RD servers are ignored.
+ *
+ * @param[in] remote    remote endpoint of the target RD
+ * @param[in] regif     registration interface resource of the RD, it will be
+ *                      discovered automatically when set to NULL
+ *
+ * @return  RDCLI_OK on success
+ * @return  RDCLI_TIMEOUT on registration timeout
+ * @return  RDCLI_NORD if addressed endpoint is not a RD
+ * @return  RDCLI_OVERFLOW if @p regif does not fit into internal buffer
+ * @return  RDCLI_ERR on any other internal error
+ */
+int rdcli_register(const sock_udp_ep_t *remote, const char *regif);
+
+/**
+ * @brief   Update our current entry at the RD
+ *
+ * @return  RDCLI_OK on success
+ * @return  RDCLI_TIMEOUT if the update request times out
+ * @return  RDCLI_ERR on any other internal error
+ */
+int rdcli_update(void);
+
+/**
+ * @brief   Unregister from a given RD server
+ *
+ * @return  RDCLI_OK on success
+ * @return  RDCLI_TIMEOUT if the remove request times out
+ * @return  RDCLI_ERR on any other internal error
+ */
+int rdcli_remove(void);
+
+/**
+ * @brief   Dump the current RD connection status to STDIO (for debugging)
+ */
+void rdcli_dump_status(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NET_RDCLI_H */
+/** @} */
diff --git a/sys/include/net/rdcli_config.h b/sys/include/net/rdcli_config.h
index b63638cd1d074f1d545d245a7cc80b7c6f6a8b36..078261859b0c02bab53a2e7a11ad701ebadf53ae 100644
--- a/sys/include/net/rdcli_config.h
+++ b/sys/include/net/rdcli_config.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 Freie Universität Berlin
+ * Copyright (C) 2017-2018 Freie Universität Berlin
  *
  * This file is subject to the terms and conditions of the GNU Lesser
  * General Public License v2.1. See the file LICENSE in the top level
@@ -13,7 +13,7 @@
  * @{
  *
  * @file
- * @brief
+ * @brief       CoRE RD Client static configuration default values
  *
  * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
  */
@@ -42,10 +42,10 @@ extern "C" {
 #endif
 
 /**
- * @brief   Default client update interval (default is half the lifetime)
+ * @brief   Default client update interval (default is 3/4 the lifetime)
  */
 #ifndef RDCLI_UPDATE_INTERVAL
-#define RDCLI_UPDATE_INTERVAL   (RDCLI_LT / 2)
+#define RDCLI_UPDATE_INTERVAL   ((RDCLI_LT / 4) * 3)
 #endif
 
 /**
diff --git a/sys/include/net/rdcli_standalone.h b/sys/include/net/rdcli_standalone.h
new file mode 100644
index 0000000000000000000000000000000000000000..a76041b882c0de1a0f57dc1fd50a241618aceb6e
--- /dev/null
+++ b/sys/include/net/rdcli_standalone.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017-2018 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    net_rdcli_standalone CoRE RD Standalone Extension
+ * @ingroup     net_rdcli
+ * @brief       Run CoRE Resource Directory client in standalone configuration
+ *
+ * This sub-module enables the CoRE RD client to manage is registration state
+ * with a server autonomously by periodically running the update procedure. This
+ * is implemented by running a dedicated thread.
+ *
+ * @{
+ *
+ * @file
+ * @brief       CoRE Resource Directory client standalone extension
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ */
+
+#ifndef NET_RDCLI_STANDALONE_H
+#define NET_RDCLI_STANDALONE_H
+
+#include "net/sock/udp.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Possible types of events triggered by the standalone rdcli module
+ */
+typedef enum {
+    RDCLI_REGISTERED,
+    RDCLI_DEREGISTERED,
+    RDCLI_UPDATED,
+} rdcli_standalone_event_t;
+
+/**
+ * @brief   Callback function signature for RD client state synchronization
+ *
+ * The registered callback function is executed in the context of the dedicated
+ * standalone RD client's thread.
+ *
+ * @param[in] t         type of event
+ */
+typedef void(*rdcli_standalone_cb_t)(rdcli_standalone_event_t event);
+
+/**
+ * @brief   Spawn a new thread that takes care of sending periodic updates to an
+ *          active RD entry
+ *
+ * @note    This function must only be called once (typically during system
+ *          initialization)
+ */
+void rdcli_standalone_run(void);
+
+/**
+ * @brief   Register a callback to be notified about RD client state changes
+ *
+ * Only a single callback can be active at any point in time, so setting a new
+ * callback will override the existing one.
+ *
+ * @pre                     @p cb != NULL
+ *
+ * @param[in] cb            callback to execute on RD client state changes
+ */
+void rdcli_standalone_reg_cb(rdcli_standalone_cb_t cb);
+
+/**
+ * @brief   Signal the rdcli thread about connection status change
+ *
+ * @note    This function should not be called by a user, but it is called from
+ *          withing the rdcli implementation
+ *
+ * @param[in] connected     set to true if we are connected to a RD
+ */
+void rdcli_standalone_signal(bool connected);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* NET_RDCLI_STANDALONE_H */
+/** @} */
diff --git a/sys/net/application_layer/rdcli/Makefile b/sys/net/application_layer/rdcli/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..3b2e7e88b2c7863e573a6540af83ffafe93aedcb
--- /dev/null
+++ b/sys/net/application_layer/rdcli/Makefile
@@ -0,0 +1,7 @@
+SRC = rdcli.c
+
+ifneq (,$(filter rdcli_standalone,$(USEMODULE)))
+  SRC += rdcli_standalone.c
+endif
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/application_layer/rdcli/rdcli.c b/sys/net/application_layer/rdcli/rdcli.c
new file mode 100644
index 0000000000000000000000000000000000000000..0742ebcc1c7e56d02a500f78830e920060a1fb8e
--- /dev/null
+++ b/sys/net/application_layer/rdcli/rdcli.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2017-2018 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     net_rdcli
+ * @{
+ *
+ * @file
+ * @brief       CoRE Resource Directory client implementation
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <string.h>
+
+#include "fmt.h"
+#include "mutex.h"
+#include "thread_flags.h"
+
+#include "net/gcoap.h"
+#include "net/ipv6/addr.h"
+#include "net/rdcli.h"
+#include "net/rdcli_common.h"
+#include "net/rdcli_config.h"
+
+#ifdef MODULE_RDCLI_STANDALONE
+#include "net/rdcli_standalone.h"
+#endif
+
+#define ENABLE_DEBUG        (0)
+#include "debug.h"
+
+#define FLAG_SUCCESS        (0x0001)
+#define FLAG_TIMEOUT        (0x0002)
+#define FLAG_ERR            (0x0004)
+#define FLAG_OVERFLOW       (0x0008)
+#define FLAG_MASK           (0x000f)
+
+#define BUFSIZE             (512U)
+
+static char *_regif_buf;
+static size_t _regif_buf_len;
+
+static char _rd_loc[NANOCOAP_URI_MAX];
+static char _rd_regif[NANOCOAP_URI_MAX];
+static sock_udp_ep_t _rd_remote;
+
+static mutex_t _mutex = MUTEX_INIT;
+static volatile thread_t *_waiter;
+
+static uint8_t buf[BUFSIZE];
+
+static void _lock(void)
+{
+    mutex_lock(&_mutex);
+    _waiter = sched_active_thread;
+}
+
+static int _sync(void)
+{
+    thread_flags_t flags = thread_flags_wait_any(FLAG_MASK);
+
+    if (flags & FLAG_ERR) {
+        return RDCLI_ERR;
+    }
+    else if (flags & FLAG_TIMEOUT) {
+        return RDCLI_TIMEOUT;
+    }
+    else if (flags & FLAG_OVERFLOW) {
+        return RDCLI_OVERFLOW;
+    }
+    else {
+        return RDCLI_OK;
+    }
+}
+
+static void _on_register(unsigned req_state, coap_pkt_t* pdu,
+                        sock_udp_ep_t *remote)
+{
+    thread_flags_t flag = FLAG_ERR;
+
+    if ((req_state == GCOAP_MEMO_RESP) &&
+        (pdu->hdr->code == COAP_CODE_CREATED)) {
+        /* read the location header and save the RD details on success */
+        if (coap_get_location_path(pdu, (uint8_t *)_rd_loc,
+                                   sizeof(_rd_loc)) > 0) {
+            memcpy(&_rd_remote, remote, sizeof(_rd_remote));
+            flag = FLAG_SUCCESS;
+        }
+        else {
+            /* reset RD entry */
+            flag = FLAG_OVERFLOW;
+        }
+    }
+    else if (req_state == GCOAP_MEMO_TIMEOUT) {
+        flag = FLAG_TIMEOUT;
+    }
+
+    thread_flags_set((thread_t *)_waiter, flag);
+}
+
+static void _on_update_remove(unsigned req_state, coap_pkt_t *pdu, uint8_t code)
+{
+    thread_flags_t flag = FLAG_ERR;
+
+    if ((req_state == GCOAP_MEMO_RESP) && (pdu->hdr->code == code)) {
+        flag = FLAG_SUCCESS;
+    }
+    else if (req_state == GCOAP_MEMO_TIMEOUT) {
+        flag = FLAG_TIMEOUT;
+    }
+
+    thread_flags_set((thread_t *)_waiter, flag);
+}
+
+static void _on_update(unsigned req_state, coap_pkt_t *pdu, sock_udp_ep_t *remote)
+{
+    (void)remote;
+    _on_update_remove(req_state, pdu, COAP_CODE_CHANGED);
+}
+
+static void _on_remove(unsigned req_state, coap_pkt_t *pdu, sock_udp_ep_t *remote)
+{
+    (void)remote;
+    _on_update_remove(req_state, pdu, COAP_CODE_DELETED);
+}
+
+static int _update_remove(unsigned code, gcoap_resp_handler_t handle)
+{
+    coap_pkt_t pkt;
+
+    if (_rd_loc[0] == 0) {
+        return RDCLI_NORD;
+    }
+
+    /* build CoAP request packet */
+    int res = gcoap_req_init(&pkt, buf, sizeof(buf), code, _rd_loc);
+    if (res < 0) {
+        return RDCLI_ERR;
+    }
+    coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON);
+    ssize_t pkt_len = gcoap_finish(&pkt, 0, COAP_FORMAT_NONE);
+
+    /* send request */
+    gcoap_req_send2(buf, pkt_len, &_rd_remote, handle);
+
+    /* synchronize response */
+    return _sync();
+}
+
+static void _on_discover(unsigned req_state, coap_pkt_t *pdu,
+                         sock_udp_ep_t *remote)
+{
+    thread_flags_t flag = RDCLI_NORD;
+    (void)remote;
+
+    if (req_state == GCOAP_MEMO_RESP) {
+        unsigned ct = coap_get_content_type(pdu);
+        if (ct != COAP_FORMAT_LINK) {
+            goto end;
+        }
+        if (pdu->payload_len == 0) {
+            goto end;
+        }
+        /* do simplified parsing of registration interface location */
+        char *start = (char *)pdu->payload;
+        char *end;
+        char *limit = (char *)(pdu->payload + pdu->payload_len);
+        while ((*start != '<') && (start < limit)) {
+            start++;
+        }
+        if (*start != '<') {
+            goto end;
+        }
+        end = ++start;
+        while ((*end != '>') && (end < limit)) {
+            end++;
+        }
+        if (*end != '>') {
+            goto end;
+        }
+        /* TODO: verify size of interface resource identifier */
+        size_t uri_len = (size_t)(end - start);
+        if (uri_len >= _regif_buf_len) {
+            goto end;
+        }
+        memcpy(_regif_buf, start, uri_len);
+        memset((_regif_buf + uri_len), 0, (_regif_buf_len - uri_len));
+        flag = FLAG_SUCCESS;
+    }
+    else if (req_state == GCOAP_MEMO_TIMEOUT) {
+        flag = FLAG_TIMEOUT;
+    }
+
+end:
+    thread_flags_set((thread_t *)_waiter, flag);
+}
+
+static int _discover_internal(const sock_udp_ep_t *remote,
+                              char *regif, size_t maxlen)
+{
+    coap_pkt_t pkt;
+
+    /* save pointer to result buffer */
+    _regif_buf = regif;
+    _regif_buf_len = maxlen;
+
+    /* do URI discovery for the registration interface */
+    int res = gcoap_req_init(&pkt, buf, sizeof(buf), COAP_METHOD_GET,
+                             "/.well-known/core");
+    if (res < 0) {
+        return RDCLI_ERR;
+    }
+    coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON);
+    gcoap_add_qstring(&pkt, "rt", "core.rd");
+    size_t pkt_len = gcoap_finish(&pkt, 0, COAP_FORMAT_NONE);
+    res = gcoap_req_send2(buf, pkt_len, remote, _on_discover);
+    if (res < 0) {
+        return RDCLI_ERR;
+    }
+    return _sync();
+}
+
+int rdcli_discover_regif(const sock_udp_ep_t *remote, char *regif, size_t maxlen)
+{
+    assert(remote && regif);
+
+    _lock();
+    int res = _discover_internal(remote, regif, maxlen);
+    mutex_unlock(&_mutex);
+    return res;
+}
+
+int rdcli_register(const sock_udp_ep_t *remote, const char *regif)
+{
+    assert(remote);
+
+    int res;
+    ssize_t pkt_len;
+    int retval;
+    coap_pkt_t pkt;
+
+    _lock();
+
+    /* if no registration interface is given, we will need to trigger a URI
+     * discovery for it first (see section 5.2) */
+    if (regif == NULL) {
+        retval = _discover_internal(remote, _rd_regif, sizeof(_rd_regif));
+        if (retval != RDCLI_OK) {
+            goto end;
+        }
+    }
+    else {
+        if (strlen(_rd_regif) >= sizeof(_rd_regif)) {
+            retval = RDCLI_OVERFLOW;
+            goto end;
+        }
+        strncpy(_rd_regif, regif, sizeof(_rd_regif));
+    }
+
+    /* build and send CoAP POST request to the RD's registration interface */
+    res = gcoap_req_init(&pkt, buf, sizeof(buf), COAP_METHOD_POST, _rd_regif);
+    if (res < 0) {
+        retval = RDCLI_ERR;
+        goto end;
+    }
+    /* set some packet options and write query string */
+    coap_hdr_set_type(pkt.hdr, COAP_TYPE_CON);
+    rdcli_common_add_qstring(&pkt);
+
+    /* add the resource description as payload */
+    res = gcoap_get_resource_list(pkt.payload, pkt.payload_len,
+                                  COAP_FORMAT_LINK);
+    if (res < 0) {
+        retval = RDCLI_ERR;
+        goto end;
+    }
+
+    /* finish up the packet */
+    pkt_len = gcoap_finish(&pkt, res, COAP_FORMAT_LINK);
+
+    /* send out the request */
+    res = gcoap_req_send2(buf, pkt_len, remote, _on_register);
+    if (res < 0) {
+        retval = RDCLI_ERR;
+        goto end;
+    }
+    retval = _sync();
+
+end:
+    /* if we encountered any error, we mark the client as not connected */
+    if (retval != RDCLI_OK) {
+        _rd_loc[0] = '\0';
+    }
+#ifdef MODULE_RDCLI_STANDALONE
+    else {
+        rdcli_standalone_signal(true);
+    }
+#endif
+
+    mutex_unlock(&_mutex);
+    return retval;
+}
+
+int rdcli_update(void)
+{
+    _lock();
+    int res = _update_remove(COAP_METHOD_POST, _on_update);
+    if (res != RDCLI_OK) {
+        /* in case we are not able to reach the RD, we drop the association */
+#ifdef MODULE_RDCLI_STANDALONE
+        rdcli_standalone_signal(false);
+#endif
+        _rd_loc[0] = '\0';
+    }
+    mutex_unlock(&_mutex);
+    return res;
+}
+
+int rdcli_remove(void)
+{
+    _lock();
+    if (_rd_loc[0] == '\0') {
+        mutex_unlock(&_mutex);
+        return RDCLI_NORD;
+    }
+#ifdef MODULE_RDCLI_STANDALONE
+    rdcli_standalone_signal(false);
+#endif
+    _update_remove(COAP_METHOD_DELETE, _on_remove);
+    /* we actually do not care about the result, we drop the RD local RD entry
+     * in any case */
+    _rd_loc[0] = '\0';
+    mutex_unlock(&_mutex);
+    return RDCLI_OK;
+}
+
+void rdcli_dump_status(void)
+{
+    puts("CoAP RD connection status:");
+
+    if (_rd_loc[0] == 0) {
+        puts("  --- not registered with any RD ---");
+    }
+    else {
+        /* get address string */
+        char addr[IPV6_ADDR_MAX_STR_LEN];
+        ipv6_addr_to_str(addr, (ipv6_addr_t *)&_rd_remote.addr, sizeof(addr));
+
+        printf("RD address: coap://[%s]:%i\n", addr, (int)_rd_remote.port);
+        printf("   ep name: %s\n", rdcli_common_get_ep());
+        printf("  lifetime: %is\n", (int)RDCLI_LT);
+        printf("    reg if: %s\n", _rd_regif);
+        printf("  location: %s\n", _rd_loc);
+    }
+}
diff --git a/sys/net/application_layer/rdcli/rdcli_standalone.c b/sys/net/application_layer/rdcli/rdcli_standalone.c
new file mode 100644
index 0000000000000000000000000000000000000000..73026d90c9794716e231cd96a594879a44f172a0
--- /dev/null
+++ b/sys/net/application_layer/rdcli/rdcli_standalone.c
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017-2018 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     net_rdcli_simple
+ * @{
+ *
+ * @file
+ * @brief       Standalone extension for the simple RD registration client
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <stdint.h>
+
+#include "log.h"
+#include "assert.h"
+#include "thread.h"
+#include "xtimer.h"
+#include "net/rdcli.h"
+#include "net/rdcli_config.h"
+#include "net/rdcli_standalone.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+/* stack configuration */
+#define STACKSIZE           (THREAD_STACKSIZE_DEFAULT)
+#define PRIO                (THREAD_PRIORITY_MAIN - 1)
+#define TNAME               "rdcli"
+
+#define UPDATE_TIMEOUT      (0xe537)
+
+#define TIMEOUT_US          ((uint64_t)(RDCLI_UPDATE_INTERVAL * US_PER_SEC))
+
+static char _stack[STACKSIZE];
+
+static xtimer_t _timer;
+static kernel_pid_t _runner_pid;
+static msg_t _msg;
+
+static rdcli_standalone_cb_t _cb = NULL;
+
+static void _set_timer(void)
+{
+    xtimer_set_msg64(&_timer, TIMEOUT_US, &_msg, _runner_pid);
+}
+
+static void _notify(rdcli_standalone_event_t event)
+{
+    if (_cb) {
+        _cb(event);
+    }
+}
+
+static void *_reg_runner(void *arg)
+{
+    (void)arg;
+    msg_t in;
+
+    /* prepare context and message */
+    _runner_pid = thread_getpid();
+    _msg.type = UPDATE_TIMEOUT;
+
+    while (1) {
+        DEBUG("rd stand: waiting for message\n");
+        msg_receive(&in);
+        if (in.type == UPDATE_TIMEOUT) {
+            if (rdcli_update() == RDCLI_OK) {
+                DEBUG("rd stand: update ok\n");
+                _set_timer();
+                _notify(RDCLI_UPDATED);
+            }
+            else {
+                _notify(RDCLI_DEREGISTERED);
+            }
+        }
+    }
+
+    return NULL;    /* should never be reached */
+}
+
+void rdcli_standalone_run(void)
+{
+    thread_create(_stack, sizeof(_stack), PRIO, 0, _reg_runner, NULL, TNAME);
+}
+
+void rdcli_standalone_signal(bool connected)
+{
+    /* clear timer in any case */
+    xtimer_remove(&_timer);
+    /* reset the update timer in case a connection was established or updated */
+    if (connected) {
+        _set_timer();
+        _notify(RDCLI_REGISTERED);
+    } else {
+        _notify(RDCLI_DEREGISTERED);
+    }
+}
+
+void rdcli_standalone_reg_cb(rdcli_standalone_cb_t cb)
+{
+    /* Note: we do not allow re-setting the callback (via passing cb := NULL),
+     *       as this would mean additional complexity for synchronizing the
+     *       value of `_cb` to prevent concurrency issues... */
+    assert(cb);
+    _cb = cb;
+}
diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile
index 5d1908f2d13e9c4d903f37aab68495b514688e7d..bcffa1c43e50463c236f5b429a336352ce006153 100644
--- a/sys/shell/commands/Makefile
+++ b/sys/shell/commands/Makefile
@@ -66,6 +66,9 @@ endif
 ifneq (,$(filter conn_can,$(USEMODULE)))
   SRC += sc_can.c
 endif
+ifneq (,$(filter rdcli,$(USEMODULE)))
+  SRC += sc_rdcli.c
+endif
 
 ifneq (,$(filter periph_rtc,$(FEATURES_PROVIDED)))
   SRC += sc_rtc.c
diff --git a/sys/shell/commands/sc_rdcli.c b/sys/shell/commands/sc_rdcli.c
new file mode 100644
index 0000000000000000000000000000000000000000..c67b79016dfcb253b883cb1824981d6bbb563d56
--- /dev/null
+++ b/sys/shell/commands/sc_rdcli.c
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2017 Freie Universität Berlin
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     sys_shell_commands
+ * @{
+ *
+ * @file
+ * @brief       Shell commands for the rdcli module
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "net/rdcli.h"
+#include "net/nanocoap.h"
+#include "net/sock/util.h"
+#include "net/rdcli_config.h"
+#include "net/rdcli_common.h"
+
+static int make_sock_ep(sock_udp_ep_t *ep, const char *addr)
+{
+    ep->port = 0;
+    if (sock_udp_str2ep(ep, addr) < 0) {
+        return -1;
+    }
+    ep->family  = AF_INET6;
+    ep->netif   = SOCK_ADDR_ANY_NETIF;
+    if (ep->port == 0) {
+        ep->port = RDCLI_SERVER_PORT;
+    }
+    return 0;
+}
+
+int _rdcli_handler(int argc, char **argv)
+{
+    int res;
+
+    if ((argc > 1) && (strcmp(argv[1], "register") == 0)) {
+        char *regif = NULL;
+        if (argc < 3) {
+            printf("usage: %s register <server address> [registration interface]\n",
+                   argv[0]);
+            return 1;
+        }
+        sock_udp_ep_t remote;
+        if (make_sock_ep(&remote, argv[2]) < 0) {
+            printf("error: unable to parse address\n");
+            return 1;
+        }
+        if (argc > 3) {
+            regif = argv[3];
+        }
+        puts("Registering with RD now, this may take a short while...");
+        if (rdcli_register(&remote, regif) != RDCLI_OK) {
+            puts("error: registration failed");
+        }
+        else {
+            puts("registration successful\n");
+            rdcli_dump_status();
+        }
+    }
+    else if ((argc > 1) && (strcmp(argv[1], "discover") == 0)) {
+        if (argc < 3) {
+            printf("usage: %s discover <server address>\n", argv[0]);
+            return 1;
+        }
+        char regif[NANOCOAP_URI_MAX];
+        sock_udp_ep_t remote;
+        if (make_sock_ep(&remote, argv[2]) < 0) {
+            printf("error: unable to parse address\n");
+            return 1;
+        }
+        if (rdcli_discover_regif(&remote, regif, sizeof(regif)) == RDCLI_OK) {
+            printf("the registration interface is '%s'\n", regif);
+        }
+        else {
+            printf("error: unable to discover registration interface\n");
+        }
+    }
+    else if ((argc > 1) && (strcmp(argv[1], "update") == 0)) {
+        res = rdcli_update();
+        if (res == RDCLI_OK) {
+            puts("RD update successful");
+        }
+        else if (res == RDCLI_NORD) {
+            puts("error: not associated with any RD");
+        }
+        else if (res == RDCLI_TIMEOUT) {
+            puts("error: unable to reach RD - dropped association");
+        }
+        else {
+            puts("error: RD update failed");
+        }
+    }
+    else if ((argc > 1) && (strcmp(argv[1], "remove") == 0)) {
+        res = rdcli_remove();
+        if (res == RDCLI_OK) {
+            puts("node successfully removed from RD");
+        }
+        else if (res == RDCLI_NORD) {
+            puts("error: not associated with any RD");
+        }
+        else if (res == RDCLI_TIMEOUT) {
+            puts("error: unable to reach RD - remove association only locally");
+        }
+        else {
+            puts("error: unable to remove node from RD");
+        }
+    }
+    else if ((argc > 1) && (strcmp(argv[1], "info") == 0)) {
+        rdcli_dump_status();
+    }
+    else {
+        printf("usage: %s <register|discover|update|remove|info>\n",
+               argv[0]);
+        return 1;
+    }
+
+    return 0;
+}
diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c
index a9871517d3e64da7b5cdbd5642db37827d1f3e27..673ef75eb400870a4890a476a43ed4f133b4c2ed 100644
--- a/sys/shell/commands/shell_commands.c
+++ b/sys/shell/commands/shell_commands.c
@@ -142,6 +142,10 @@ extern int _ls_handler(int argc, char **argv);
 extern int _can_handler(int argc, char **argv);
 #endif
 
+#ifdef MODULE_RDCLI
+extern int _rdcli_handler(int argc, char **argv);
+#endif
+
 const shell_command_t _shell_command_list[] = {
     {"reboot", "Reboot the node", _reboot_handler},
 #ifdef MODULE_CONFIG
@@ -232,6 +236,9 @@ const shell_command_t _shell_command_list[] = {
 #endif
 #ifdef MODULE_CONN_CAN
     {"can", "CAN commands", _can_handler},
+#endif
+#ifdef MODULE_RDCLI
+    {"rdcli", "CoAP RD client commands", _rdcli_handler },
 #endif
     {NULL, NULL, NULL}
 };