diff --git a/Makefile.dep b/Makefile.dep
index 4cb063dc9c472336a2e392c597813a9589e2ca35..bff528ba0b400dd64f30113d2821fd77db981660 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -594,6 +594,10 @@ ifneq (,$(filter spiffs,$(USEMODULE)))
   USEMODULE += mtd
 endif
 
+ifneq (,$(filter l2filter_%,$(USEMODULE)))
+  USEMODULE += l2filter
+endif
+
 # include package dependencies
 -include $(USEPKG:%=$(RIOTPKG)/%/Makefile.dep)
 
diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk
index d922c62116584ba9c25d3248254289d380bd503e..990b5eda6a4f1e7d86e922903541401b8a82253f 100644
--- a/makefiles/pseudomodules.inc.mk
+++ b/makefiles/pseudomodules.inc.mk
@@ -17,6 +17,8 @@ PSEUDOMODULES += gnrc_sixlowpan_router
 PSEUDOMODULES += gnrc_sixlowpan_router_default
 PSEUDOMODULES += gnrc_sock_check_reuse
 PSEUDOMODULES += gnrc_txtsnd
+PSEUDOMODULES += l2filter_blacklist
+PSEUDOMODULES += l2filter_whitelist
 PSEUDOMODULES += log
 PSEUDOMODULES += log_printfnoformat
 PSEUDOMODULES += lwip_arp
diff --git a/sys/Makefile b/sys/Makefile
index 206985f5c170ff189afdf4ade8d961ed1d297ebe..143b65e2b177853e53f0be4426b6699666510172 100644
--- a/sys/Makefile
+++ b/sys/Makefile
@@ -125,6 +125,9 @@ endif
 ifneq (,$(filter devfs,$(USEMODULE)))
     DIRS += fs/devfs
 endif
+ifneq (,$(filter l2filter,$(USEMODULE)))
+    DIRS += net/link_layer/l2filter
+endif
 
 DIRS += $(dir $(wildcard $(addsuffix /Makefile, ${USEMODULE})))
 
diff --git a/sys/include/net/l2filter.h b/sys/include/net/l2filter.h
new file mode 100644
index 0000000000000000000000000000000000000000..3e6403ebc8321c3d0ee2b641425cd685eec6f4f0
--- /dev/null
+++ b/sys/include/net/l2filter.h
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+/**
+ * @defgroup    net_l2filter Link layer address filter
+ * @ingroup     net
+ * @brief       Module for filtering (black- or white-listing) link layer
+ *              addresses
+ *
+ * This module is used as a base for filtering link layer addresses. It allows
+ * to be (globally) configured in either blacklist or in whitelist mode. This
+ * configuration is done via RIOTs module system, for whitelist mode simply
+ * include the module `L2FILTER_WHITELIST`, for blacklist mode include
+ * `L2FILTER_BLACKLIST`.
+ *
+ * The actual memory for the filter lists should be allocated for every network
+ * device. This is done centrally in netdev_t type.
+ *
+ * @{
+ * @file
+ * @brief       Link layer address filter interface definition
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ */
+
+#ifndef L2FILTER_H
+#define L2FILTER_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Maximal length of addresses that can be stored in the filter list
+ */
+#ifndef L2FILTER_ADDR_MAXLEN
+#define L2FILTER_ADDR_MAXLEN            (8U)
+#endif
+
+/**
+ * @brief   Number of slots in each filter list (filter entries per device)
+ */
+#ifndef L2FILTER_LISTSIZE
+#define L2FILTER_LISTSIZE               (8U)
+#endif
+
+/**
+ * @brief   Filter list entries
+ *
+ * The filter list supports address entries with differing length. This is
+ * useful e.g. for IEEE802.15.4, where the list can store short and long
+ * addresses at the same time.
+ */
+typedef struct {
+    uint8_t addr[L2FILTER_ADDR_MAXLEN];     /**< link layer address */
+    size_t addr_len;                        /**< address length in byte */
+} l2filter_t;
+
+/**
+ * @brief   Add an entry to a devices filter list
+ *
+ * @param[in,out] list  pointer to the filter list
+ * @param[in] addr      address to be added to list
+ * @param[in] addr_len  size of @p addr [in byte]
+ *
+ * @pre     @p list != NULL
+ * @pre     @p addr != NULL
+ * @pre     @p addr_maxlen <= @ref L2FILTER_ADDR_MAXLEN
+ *
+ * @return  0 on success
+ * @return  -ENOMEM if no empty slot left in list
+ */
+int l2filter_add(l2filter_t *list, const void *addr, size_t addr_len);
+
+/**
+ * @brief   Remove an entry from the given filter list
+ *
+ * @param[in,out] list  pointer to the filter list
+ * @param[in] addr      address to remove from the list
+ * @param[in] addr_len  length of @p addr [in byte]
+ *
+ * @pre     @p list != NULL
+ * @pre     @p addr != NULL
+ * @pre     @p addr_maxlen <= @ref L2FILTER_ADDR_MAXLEN
+ *
+ * @return  0 on success
+ * @return  -ENOENT if @p addr was not found in @p list
+ */
+int l2filter_rm(l2filter_t *list, const void *addr, size_t addr_len);
+
+/**
+ * @brief   Check if the given address passes the set filters
+ *
+ * Independent if the l2filter module is used in blacklist or in whitelist mode,
+ * this function returns whether the given address passes the filter. In
+ * whitelist mode, this means that the given address has to be in the list, in
+ * blacklist mode this means that the given address is not in the list.
+ *
+ * @param[in] list      list with black-/whitelisted addresses
+ * @param[in] addr      address to check against the entries in @p list
+ * @param[in] addr_len  length of @p addr [in byte]
+ *
+ * @pre     @p list != NULL
+ * @pre     @p addr != NULL
+ * @pre     @p addr_maxlen <= @ref L2FILTER_ADDR_MAXLEN
+ *
+ * @return  in whitelist mode: true if @p addr is in @p list
+ * @return  in whitelist mode: false if @p addr is not in @p list
+ * @return  in blacklist mode: true if @p addr is not in @p list
+ * @return  in blacklist mode: false if @p addr is in @p list
+ */
+bool l2filter_pass(const l2filter_t *list, const void *addr, size_t addr_len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* L2FILTER_H */
+/** @} */
diff --git a/sys/net/link_layer/l2filter/Makefile b/sys/net/link_layer/l2filter/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..48422e909a47d7cd428d10fa73825060ccc8d8c2
--- /dev/null
+++ b/sys/net/link_layer/l2filter/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/link_layer/l2filter/l2filter.c b/sys/net/link_layer/l2filter/l2filter.c
new file mode 100644
index 0000000000000000000000000000000000000000..b9a50e3d5510ef986edd7d65202883a7b81d6149
--- /dev/null
+++ b/sys/net/link_layer/l2filter/l2filter.c
@@ -0,0 +1,107 @@
+/*
+ * 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     net_l2filter
+ * @{
+ *
+ * @file
+ * @brief       Link layer address filter implementation
+ *
+ * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
+ *
+ * @}
+ */
+
+#include <string.h>
+
+#include "assert.h"
+#include "net/l2filter.h"
+
+#define ENABLE_DEBUG    (0)
+#include "debug.h"
+
+static inline bool match(const l2filter_t *filter,
+                         const void *addr, size_t addr_len)
+{
+    return ((filter->addr_len == addr_len) &&
+            (memcmp(filter->addr, addr, addr_len) == 0));
+}
+
+void l2filter_init(l2filter_t *list)
+{
+    assert(list);
+
+    for (unsigned i = 0; i < L2FILTER_LISTSIZE; i++) {
+        list[i].addr_len = 0;
+    }
+}
+
+int l2filter_add(l2filter_t *list, const void *addr, size_t addr_len)
+{
+    assert(list && addr && (addr_len <= L2FILTER_ADDR_MAXLEN));
+
+    int res = -ENOMEM;
+
+    for (unsigned i = 0; i < L2FILTER_LISTSIZE; i++) {
+        if (list[i].addr_len == 0) {
+            list[i].addr_len = addr_len;
+            memcpy(list[i].addr, addr, addr_len);
+            res = 0;
+            break;
+        }
+    }
+
+    return res;
+}
+
+int l2filter_rm(l2filter_t *list, const void *addr, size_t addr_len)
+{
+    assert(list && addr && (addr_len <= L2FILTER_ADDR_MAXLEN));
+
+    int res = -ENOENT;
+
+    for (unsigned i = 0; i < L2FILTER_LISTSIZE; i++) {
+        if (match(&list[i], addr, addr_len)) {
+            list[i].addr_len = 0;
+            res = 0;
+            break;
+        }
+    }
+
+    return res;
+}
+
+bool l2filter_pass(const l2filter_t *list, const void *addr, size_t addr_len)
+{
+    assert(list && addr && (addr_len <= L2FILTER_ADDR_MAXLEN));
+
+#ifdef MODULE_L2FILTER_WHITELIST
+    bool res = false;
+    for (unsigned i = 0; i < L2FILTER_LISTSIZE; i++) {
+        if (match(&list[i], addr, addr_len)) {
+            DEBUG("[l2filter] whitelist: address match -> packet passes\n");
+            res = true;
+            break;
+        }
+    }
+    DEBUG("[l2filter] whitelist: no match -> packet dropped\n");
+#else
+    bool res = true;
+    for (unsigned i = 0; i < L2FILTER_LISTSIZE; i++) {
+        if (match(&list[i], addr, addr_len)) {
+            DEBUG("[l2filter] blacklist: address match -> packet dropped\n");
+            res = false;
+            break;
+        }
+    }
+    DEBUG("[l2fitler] blacklist: no match -> packet passes\n");
+#endif
+
+    return res;
+}