From c5318336aede5012f2884d1217911f9c78946bb2 Mon Sep 17 00:00:00 2001 From: Martine Lenders <mlenders@inf.fu-berlin.de> Date: Tue, 17 Mar 2015 14:42:35 +0100 Subject: [PATCH] net: intial import of the ZEP protocol --- Makefile.dep | 8 + sys/Makefile | 3 + sys/include/net/ng_zep.h | 241 +++++ sys/net/application_layer/ng_zep/Makefile | 1 + sys/net/application_layer/ng_zep/ng_zep.c | 1081 +++++++++++++++++++++ sys/shell/commands/Makefile | 3 + sys/shell/commands/sc_zep.c | 98 ++ sys/shell/commands/shell_commands.c | 11 + 8 files changed, 1446 insertions(+) create mode 100644 sys/include/net/ng_zep.h create mode 100644 sys/net/application_layer/ng_zep/Makefile create mode 100644 sys/net/application_layer/ng_zep/ng_zep.c create mode 100644 sys/shell/commands/sc_zep.c diff --git a/Makefile.dep b/Makefile.dep index dee1f81c7a..49d2a01644 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -73,6 +73,14 @@ ifneq (,$(filter xbee,$(USEMODULE))) USEMODULE += ng_ieee802154 endif +ifneq (,$(filter ng_zep,$(USEMODULE))) + USEMODULE += hashes + USEMODULE += ng_ieee802154 + USEMODULE += ng_udp + USEMODULE += random + USEMODULE += vtimer +endif + ifneq (,$(filter ng_ieee802154,$(USEMODULE))) ifneq (,$(filter ng_ipv6, $(USEMODULE))) USEMODULE += ng_sixlowpan diff --git a/sys/Makefile b/sys/Makefile index 4248eebd81..9707548590 100644 --- a/sys/Makefile +++ b/sys/Makefile @@ -113,6 +113,9 @@ endif ifneq (,$(filter ng_pktbuf,$(USEMODULE))) DIRS += net/crosslayer/ng_pktbuf endif +ifneq (,$(filter ng_zep,$(USEMODULE))) + DIRS += net/application_layer/ng_zep +endif ifneq (,$(filter ng_rpl_srh,$(USEMODULE))) DIRS += net/routing/ng_rpl/srh endif diff --git a/sys/include/net/ng_zep.h b/sys/include/net/ng_zep.h new file mode 100644 index 0000000000..b5d551fe12 --- /dev/null +++ b/sys/include/net/ng_zep.h @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2015 Martine Lenders <mlenders@inf.fu-berlin.de> + * + * 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_ng_zep Zigbee Encapsulation Protocol + * @ingroup net + * @brief Transports IEEE 802.15.4 frames over UDP (can be parsed by + * Wireshark) + * @see <a href="https://www.wireshark.org/docs/dfref/z/zep.html"> + * ZigBee Encapsulation Protocol in the Wireshark docs + * </a> + * @see <a href="https://code.wireshark.org/review/gitweb?p=wireshark.git;a=blob;f=epan/dissectors/packet-zep.c;h=3422eb7876f973f191da98cc4c02aecbd0dbaaeb;hb=HEAD"> + * ZEP packet definition in the Wireshark code base. + * </a> + * @{ + * + * @file + * @brief ZEP definitions + * + * @author Martine Lenders <mlenders@inf.fu-berlin.de> + */ + + +#ifndef NG_ZEP_H_ +#define NG_ZEP_H_ + +#include <inttypes.h> + +#include "byteorder.h" +#include "kernel_types.h" +#include "net/ng_ipv6/addr.h" +#include "net/ng_nettype.h" +#include "thread.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Maximum possible packet size in byte + */ +#define NG_ZEP_MAX_PKT_LENGTH (116) + +/** + * @brief Default stack size to use for the ZEP thread + */ +#ifndef NG_ZEP_STACK_SIZE +#define NG_ZEP_STACK_SIZE (THREAD_STACKSIZE_DEFAULT) +#endif + +/** + * @brief Default priority for the ZEP thread + */ +#ifndef NG_ZEP_PRIO +#define NG_ZEP_PRIO (THREAD_PRIORITY_MAIN - 5) +#endif + +/** + * @brief Default message queue size to use for the ZEP thread. + */ +#ifndef NG_ZEP_MSG_QUEUE_SIZE +#define NG_ZEP_MSG_QUEUE_SIZE (8U) +#endif + +/** + * @brief Default addresses if the CPUID module is not present + * @{ + */ +#define NG_ZEP_DEFAULT_ADDR_SHORT (0x98b0) +#define NG_ZEP_DEFAULT_ADDR_LONG (0x4a8a6377552b4249) +/** + * @} + */ + +/** + * @brief Channel configuration + * @{ + */ +#define NG_ZEP_MIN_CHANNEL (11U) +#define NG_ZEP_MAX_CHANNEL (26U) +#define NG_ZEP_DEFAULT_CHANNEL (17U) +/** + * @} + */ + +/** + * @brief Default PAN ID + * + * TODO: Read some global network stack specific configuration value + */ +#define NG_ZEP_DEFAULT_PANID (0x0023) + +/** + * @brief Option flags for the ZEP device + * @{ + */ +#define NG_ZEP_FLAGS_AUTOACK (0x0001) /**< auto ACKS active */ +#define NG_ZEP_FLAGS_SRC_ADDR_LONG (0x0002) /**< send data using long source address */ +#define NG_ZEP_FLAGS_DST_ADDR_LONG (0x0004) /**< send data using long destination address */ +#define NG_ZEP_FLAGS_USE_SRC_PAN (0x0008) /**< do not compress source PAN ID */ +/** + * @} + */ + +/** + * @brief Default UDP port for ZEP + */ +#define NG_ZEP_DEFAULT_PORT (17754) + +/** + * @brief Type == Data for ZEPv2 header + */ +#define NG_ZEP_V2_TYPE_DATA (1) + +/** + * @brief Type == Ack for ZEPv2 header + */ +#define NG_ZEP_V2_TYPE_ACK (2) + +/** + * @brief Mask for length field + */ +#define ZEP_LENGTH_MASK (0x7f) + +/** + * @brief ZEP header definition + */ +typedef struct __attribute__((packed)) { + char preamble[2]; /**< Preamble code (must be "EX") */ + uint8_t version; /**< Protocol Version (must be 1 or 2) */ +} ng_zep_hdr_t; + +/** + * @brief ZEPv1 header definition + * @extends ng_zep_hdr_t + */ +typedef struct __attribute__((packed)) { + char preamble[2]; /**< preamble code (must be "EX") */ + uint8_t version; /**< protocol Version (must be 1) */ + uint8_t chan; /**< channel ID */ + network_uint16_t dev; /**< device ID */ + uint8_t lqi_mode; /**< CRC/LQI Mode */ + uint8_t lqi_val; /**< LQI value */ + uint8_t resv[7]; /**< reserved field, must always be 0 */ + uint8_t length; /**< length of the frame */ +} ng_zep_v1_hdr_t; + +/** + * @brief ZEPv2 header definition (type == Data) + * @extends ng_zep_hdr_t + */ +typedef struct __attribute__((packed)) { + char preamble[2]; /**< preamble code (must be "EX") */ + uint8_t version; /**< protocol Version (must be 2) */ + uint8_t type; /**< type (must be 1/Data) */ + uint8_t chan; /**< channel ID */ + network_uint16_t dev; /**< device ID */ + uint8_t lqi_mode; /**< CRC/LQI Mode */ + uint8_t lqi_val; /**< LQI value */ + network_uint64_t time; /**< NTP timestamp */ + network_uint32_t seq; /**< Sequence number */ + uint8_t resv[10]; /**< reserved field, must always be 0 */ + uint8_t length; /**< length of the frame */ +} ng_zep_v2_data_hdr_t; + +/** + * @brief ZEPv2 header definition (type == Ack) + * @extends ng_zep_hdr_t + */ +typedef struct __attribute__((packed)) { + char preamble[2]; /**< preamble code (must be "EX") */ + uint8_t version; /**< protocol Version (must be 2) */ + uint8_t type; /**< type (must be 2/Ack) */ + network_uint32_t seq; /**< Sequence number */ +} ng_zep_v2_ack_hdr_t; + +/** + * @brief ZEP device descriptor. + * + * @extends ng_netdev_t + */ +typedef struct { + ng_netdev_driver_t *driver; /**< pointer to the device's interface */ + ng_netdev_event_cb_t event_cb; /**< netdev event callback */ + kernel_pid_t mac_pid; /**< the driver's thread's PID */ + /** + * @brief @ref ng_zep_t specific members + * @{ + */ + le_uint16_t addr; /**< the device's short address */ + le_uint64_t eui64; /**< the device's EUI-64 */ + le_uint16_t pan; /**< the device's PAN ID */ + uint16_t flags; /**< the device's option flags */ + uint32_t seq; /**< the current sequence number for frames */ + ng_ipv6_addr_t dst; /**< destination IPv6 address */ + uint16_t src_port; /**< source UDP port */ + uint16_t dst_port; /**< destination UDP port */ + ng_nettype_t proto; /**< the target protocol for received packets */ + uint8_t chan; /**< the device's channel */ + uint8_t version; /**< ZEP version to use (default 2) */ + uint8_t lqi_mode; /**< LQI mode for send packets (default 1) */ + /** + * @} + */ +} ng_zep_t; + +/** + * @brief Initializion of the ZEP thread and device. + * + * @param[in] dev Network device, will be initialized. + * @param[in] src_port Source port to use in UDP datagrams. Also the port + * @ref net_ng_zep registers to in @ref net_ng_netreg. + * @param[in] dst Destination address to use in IPv6 packets. + * @param[in] dst_port Destination port to use in UDP datagrams. + * + * @return PID of the ZEP thread on success. + * @return -EADDRINUSE, if @p src_port is already ready registered to + * @ref net_ng_netreg. + * @return -EEXIST, if ZEP thread was already created. + * @return -EINVAL, if @ref NG_ZEP_PRIO is greater than or equal to + * @ref SCHED_PRIO_LEVELS + * @return -ENODEV, if @p dev is NULL. + * @return -ENOTSUP, if @p dst is NULL or unspecified address (::). + * @return -EOVERFLOW, if there are too many threads running already + */ +kernel_pid_t ng_zep_init(ng_zep_t *dev, uint16_t src_port, ng_ipv6_addr_t *dst, + uint16_t dst_port); + +#ifdef __cplusplus +} +#endif + +#endif /* NG_ZEP_H_ */ +/** + * @} + */ diff --git a/sys/net/application_layer/ng_zep/Makefile b/sys/net/application_layer/ng_zep/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/net/application_layer/ng_zep/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/application_layer/ng_zep/ng_zep.c b/sys/net/application_layer/ng_zep/ng_zep.c new file mode 100644 index 0000000000..f313928912 --- /dev/null +++ b/sys/net/application_layer/ng_zep/ng_zep.c @@ -0,0 +1,1081 @@ +/* + * Copyright (C) 2015 Martine Lenders <mlenders@inf.fu-berlin.de> + * + * 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_ng_zep + * @{ + * + * @file + * + * @author Martine Lenders <mlenders@inf.fu-berlin.de> + */ + +#include <errno.h> +#include <string.h> +#include <time.h> + +#include "ringbuffer.h" +#include "hashes.h" +#include "kernel.h" +#include "msg.h" +#include "net/ng_ieee802154.h" +#include "net/ng_ipv6/addr.h" +#include "net/ng_ipv6/hdr.h" +#include "net/ng_netbase.h" +#include "net/ng_udp.h" +#include "periph/cpuid.h" +#include "random.h" +#include "vtimer.h" + +#include "net/ng_zep.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +#define _EVENT_RX_STARTED (1) +#define _EVENT_RX_COMPLETE (2) +#define _RX_BUF_SIZE (16U * sizeof(ng_pktsnip_t *)) +#define IEEE_802154_FCS_POLY (0x8408) /* x^16 + x^12 + x^5 + 1 for LSB first */ + +static kernel_pid_t _pid = KERNEL_PID_UNDEF; +static char _rx_stack[NG_ZEP_STACK_SIZE]; +static char _rx_buf_array[_RX_BUF_SIZE]; +static ringbuffer_t _rx_buf = RINGBUFFER_INIT(_rx_buf_array); + +/* ng_netdev driver definitions */ +static int _send(ng_netdev_t *dev, ng_pktsnip_t *pkt); +static int _add_cb(ng_netdev_t *dev, ng_netdev_event_cb_t cb); +static int _rem_cb(ng_netdev_t *dev, ng_netdev_event_cb_t cb); +static int _get(ng_netdev_t *dev, ng_netconf_opt_t opt, void *value, + size_t max_len); +static int _set(ng_netdev_t *dev, ng_netconf_opt_t opt, void *value, + size_t value_len); +static void _isr_event(ng_netdev_t *dev, uint32_t event_type); + +static const ng_netdev_driver_t _zep_driver = { + _send, + _add_cb, + _rem_cb, + _get, + _set, + _isr_event +}; + +/* Function for the ZEP thread */ +void *_event_loop(void *args); + +/* Builds (uninitialized) ZEP packet according to configured version */ +static ng_pktsnip_t *_zep_hdr_build(ng_zep_t *dev, size_t size, bool ack); + +/* Fills ZEP header according to ng_zep_t configuration */ +static size_t _zep_hdr_fill(ng_zep_t *dev, ng_zep_hdr_t *hdr, + size_t payload_len); + +/* Event handlers for ISR events */ +static void _rx_started_event(ng_zep_t *dev); + +/* IEEE 802.15.4 helper functions: TODO: generalize add to ng_ieee802154 */ +static size_t _make_data_frame_hdr(ng_zep_t *dev, uint8_t *buf, + ng_netif_hdr_t *hdr); +static size_t _get_frame_hdr_len(uint8_t *mhr); +ng_pktsnip_t *_make_netif_hdr(uint8_t *mhr); +static uint16_t _calc_fcs(uint16_t fcs, const uint8_t *frame, uint8_t frame_len); + +kernel_pid_t ng_zep_init(ng_zep_t *dev, uint16_t src_port, ng_ipv6_addr_t *dst, + uint16_t dst_port) +{ +#if CPUID_ID_LEN + uint8_t cpuid[CPUID_ID_LEN]; + uint32_t hash1, hash2; +#endif + + if (_pid != KERNEL_PID_UNDEF) { + DEBUG("zep: ZEP thread already running at pid=%" PRIkernel_pid "\n", _pid); + return -EEXIST; + } + + if (dev == NULL) { + DEBUG("zep: dev was NULL\n"); + return -ENODEV; + } + + if ((dst == NULL) || (ng_ipv6_addr_is_unspecified(dst))) { + DEBUG("zep: dst (%s) was NULL or unspecified\n", dst); + return -ENOTSUP; + } + + if (ng_netreg_lookup(NG_NETTYPE_UDP, src_port)) { + DEBUG("zep: port (%" PRIu16 ") already registered\n", src_port); + return -EADDRINUSE; + } + + dev->driver = (ng_netdev_driver_t *)&_zep_driver; + dev->chan = NG_ZEP_DEFAULT_CHANNEL; + dev->pan = byteorder_btols(byteorder_htons(NG_ZEP_DEFAULT_PANID)); + dev->flags = NG_ZEP_FLAGS_USE_SRC_PAN; +#if CPUID_ID_LEN + /* initialize dev->addr and dev->eui64 from cpuid if available */ + cpuid_get(cpuid); + + hash1 = djb2_hash(cpuid, CPUID_ID_LEN / 2); + dev->addr.u16 = (uint16_t)((hash1 >> 16) ^ (hash1 & 0xffff)); + + if (CPUID_ID_LEN % 2) { + hash2 = djb2_hash(cpuid + (CPUID_ID_LEN / 2), (CPUID_ID_LEN / 2) - 1); + } + else { + hash2 = djb2_hash(cpuid + (CPUID_ID_LEN / 2), CPUID_ID_LEN / 2); + } + + dev->eui64.u32[0] = hash1; + dev->eui64.u32[1] = hash2; + + dev->eui64.u8[7] &= 0xfe; /* set to unicast */ + dev->eui64.u8[7] |= 0x02; /* set to locally administered */ +#else + dev->addr = NG_ZEP_DEFAULT_ADDR_SHORT; + dev->eui64 = NG_ZEP_DEFAULT_ADDR_LONG; +#endif + DEBUG("zep: initialized radio parameters: chan: %" PRIu8 ", pan: 0x%04" PRIx16 + "addr: 0x%04" PRIx16 ", eui64: %016" PRIx64 "\n", dev->chan, + dev->pan, byteorder_ltobs(dev->addr).u16, + byteorder_ltobll(dev->eui64).u64); + +#ifdef MODULE_NG_SIXLOWPAN + dev->proto = NG_NETTYPE_SIXLOWPAN; +#else + dev->proto = NG_NETTYPE_UNDEF; +#endif + + dev->seq = genrand_uint32(); + dev->src_port = src_port; + dev->dst.u64[0] = dst->u64[0]; + dev->dst.u64[1] = dst->u64[1]; + dev->dst_port = dst_port; + dev->version = 2; + dev->lqi_mode = 1; + + _pid = thread_create(_rx_stack, NG_ZEP_STACK_SIZE, NG_ZEP_PRIO, + CREATE_STACKTEST, _event_loop, dev, "zep_app"); + + DEBUG("zep: started thread with PID %" PRIkernel_pid "\n", _pid); + + return _pid; +} + +/* helper functions for options to avoid type pruning */ +static inline void _set_uint16_ptr(uint16_t *ptr, uint16_t val) +{ + *ptr = val; +} + +static inline void _set_uint64_ptr(uint64_t *ptr, uint64_t val) +{ + *ptr = val; +} + +static inline void _set_flag_ptr(ng_netconf_enable_t *enable, + uint16_t flag_field, uint16_t flag) +{ + if (flag_field & flag) { + *enable = NETCONF_ENABLE; + } + else { + *enable = NETCONF_DISABLE; + } +} + +static inline uint16_t *_get_uint16_ptr(void *ptr) +{ + return ptr; +} + +static inline uint64_t *_get_uint64_ptr(void *ptr) +{ + return ptr; +} + +static int _send(ng_netdev_t *netdev, ng_pktsnip_t *pkt) +{ + ng_zep_t *dev = (ng_zep_t *)netdev; + ng_pktsnip_t *ptr, *new_pkt, *hdr; + ng_zep_hdr_t *zep; + size_t payload_len = ng_pkt_len(pkt->next), hdr_len, mhr_offset; + uint8_t mhr[NG_IEEE802154_MAX_HDR_LEN], *data; + uint16_t fcs = 0; + + if ((netdev == NULL) || (netdev->driver != &_zep_driver)) { + DEBUG("zep: wrong device on sending\n"); + ng_pktbuf_release(pkt); + return -ENODEV; + } + + /* create 802.15.4 header */ + hdr_len = _make_data_frame_hdr(dev, mhr, (ng_netif_hdr_t *)pkt->data); + + if (hdr_len == 0) { + DEBUG("zep: error on frame creation\n"); + ng_pktbuf_release(pkt); + return -ENOMSG; + } + + new_pkt = _zep_hdr_build(dev, hdr_len + payload_len + NG_IEEE802154_FCS_LEN, false); + + if (new_pkt == NULL) { + DEBUG("zep: could not allocate ZEP header in pktbuf\n"); + ng_pktbuf_release(pkt); + return -ENOBUFS; + } + + zep = new_pkt->data; + + hdr = ng_udp_hdr_build(new_pkt, (uint8_t *)(&(dev->src_port)), sizeof(uint16_t), + (uint8_t *)(&(dev->dst_port)), sizeof(uint16_t)); + + if (hdr == NULL) { + DEBUG("zep: could not allocate UDP header in pktbuf\n"); + ng_pktbuf_release(pkt); + ng_pktbuf_release(new_pkt); + return -ENOBUFS; + } + + new_pkt = hdr; + + hdr = ng_ipv6_hdr_build(new_pkt, NULL, 0, (uint8_t *) &(dev->dst), + sizeof(ng_ipv6_addr_t)); + + if (hdr == NULL) { + DEBUG("zep: could not allocate IPv6 header in pktbuf\n"); + ng_pktbuf_release(pkt); + ng_pktbuf_release(new_pkt); + return -ENOBUFS; + } + + new_pkt = hdr; + + mhr_offset = _zep_hdr_fill(dev, zep, payload_len + hdr_len + NG_IEEE802154_FCS_LEN); + + if (mhr_offset == 0) { + DEBUG("zep: error filling ZEP header\n"); + ng_pktbuf_release(pkt); + ng_pktbuf_release(new_pkt); + return -EINVAL; + } + + memcpy(((uint8_t *)zep) + mhr_offset, mhr, hdr_len); + + fcs = _calc_fcs(fcs, ((uint8_t *)zep) + mhr_offset, hdr_len); + data = ((uint8_t *)zep) + mhr_offset + hdr_len; + ptr = pkt->next; + + while (ptr != NULL) { + fcs = _calc_fcs(fcs, ptr->data, ptr->size); + memcpy(data, ptr->data, ptr->size); + data += ptr->size; + ptr = ptr->next; + } + + ng_pktbuf_release(pkt); + + DEBUG("zep: set frame FCS to 0x%04 " PRIx16 "\n", fcs); + _set_uint16_ptr((uint16_t *)data, byteorder_btols(byteorder_htons(fcs)).u16); + + if (!ng_netapi_dispatch_send(NG_NETTYPE_UDP, NG_NETREG_DEMUX_CTX_ALL, new_pkt)) { + DEBUG("zep: no UDP handler found: dropping packet\n"); + ng_pktbuf_release(new_pkt); + return -ENOENT; + } + + return payload_len + hdr_len + NG_IEEE802154_FCS_LEN; +} + +static int _add_cb(ng_netdev_t *dev, ng_netdev_event_cb_t cb) +{ + if ((dev == NULL) || (dev->driver != &_zep_driver)) { + return -ENODEV; + } + + if (dev->event_cb != NULL) { + return -ENOBUFS; + } + + dev->event_cb = cb; + + return 0; +} + +static int _rem_cb(ng_netdev_t *dev, ng_netdev_event_cb_t cb) +{ + if ((dev == NULL) || (dev->driver != &_zep_driver)) { + return -ENODEV; + } + + if (dev->event_cb != cb) { + return -ENOENT; + } + + dev->event_cb = NULL; + + return 0; +} + +static int _get(ng_netdev_t *netdev, ng_netconf_opt_t opt, void *value, + size_t max_len) +{ + ng_zep_t *dev = (ng_zep_t *)netdev; + + if (dev == NULL) { + return -ENODEV; + } + + switch (opt) { + case NETCONF_OPT_CHANNEL: + if (max_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + _set_uint16_ptr(value, (uint16_t)dev->chan); + return sizeof(uint16_t); + + case NETCONF_OPT_ADDRESS: + if (max_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + _set_uint16_ptr(value, byteorder_ltobs(dev->addr).u16); + return sizeof(uint16_t); + + case NETCONF_OPT_ADDRESS_LONG: + if (max_len < sizeof(uint64_t)) { + return -EOVERFLOW; + } + + _set_uint64_ptr(value, byteorder_ltobll(dev->eui64).u64); + return sizeof(uint64_t); + + case NETCONF_OPT_ADDR_LEN: + if (max_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + if (dev->flags & NG_ZEP_FLAGS_DST_ADDR_LONG) { + _set_uint16_ptr(value, 8); + } + else { + _set_uint16_ptr(value, 2); + } + + return sizeof(uint16_t); + + case NETCONF_OPT_SRC_LEN: + if (max_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + if (dev->flags & NG_ZEP_FLAGS_SRC_ADDR_LONG) { + _set_uint16_ptr(value, 8); + } + else { + _set_uint16_ptr(value, 2); + } + + return sizeof(uint16_t); + + case NETCONF_OPT_PROTO: + if (max_len < sizeof(ng_nettype_t)) { + return -EOVERFLOW; + } + + *((ng_nettype_t *)value) = dev->proto; + return sizeof(ng_nettype_t); + + case NETCONF_OPT_NID: + if (max_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + _set_uint16_ptr(value, byteorder_ltobs(dev->pan).u16); + return sizeof(uint16_t); + + case NETCONF_OPT_IPV6_IID: + if (max_len < sizeof(eui64_t)) { + return -EOVERFLOW; + } + if (dev->flags & NG_ZEP_FLAGS_SRC_ADDR_LONG) { + uint64_t addr = byteorder_ltobll(dev->eui64).u64; + ng_ieee802154_get_iid(value, (uint8_t *)&addr, 8); + } + else { + uint16_t addr = byteorder_ltobs(dev->addr).u16; + ng_ieee802154_get_iid(value, (uint8_t *)&addr, 2); + } + return sizeof(eui64_t); + + case NETCONF_OPT_MAX_PACKET_SIZE: + if (max_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + _set_uint16_ptr(value, NG_ZEP_MAX_PKT_LENGTH); + return sizeof(uint16_t); + + case NETCONF_OPT_AUTOACK: + if (max_len < sizeof(ng_netconf_enable_t)) { + return -EOVERFLOW; + } + + _set_flag_ptr(value, dev->flags, NG_ZEP_FLAGS_AUTOACK); + return sizeof(uint16_t); + + default: + return -ENOTSUP; + } +} + +static int _set(ng_netdev_t *netdev, ng_netconf_opt_t opt, void *value, + size_t value_len) +{ + ng_zep_t *dev = (ng_zep_t *)netdev; + + if (dev == NULL) { + return -ENODEV; + } + + switch (opt) { + case NETCONF_OPT_CHANNEL: + if (value_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + if ((*_get_uint16_ptr(value) < NG_ZEP_MIN_CHANNEL) || + (*_get_uint16_ptr(value)) > NG_ZEP_MAX_CHANNEL) { + return -ENOTSUP; + } + + dev->chan = *_get_uint16_ptr(value); + return sizeof(uint16_t); + + case NETCONF_OPT_ADDRESS: + if (value_len < sizeof(be_uint16_t)) { + return -EOVERFLOW; + } + else { + be_uint16_t *val = value; + + dev->addr = byteorder_btols(*val); + return sizeof(be_uint16_t); + } + + case NETCONF_OPT_ADDRESS_LONG: + if (value_len < sizeof(be_uint64_t)) { + return -EOVERFLOW; + } + else { + be_uint64_t *val = value; + + dev->eui64 = byteorder_btolll(*val); + return sizeof(be_uint64_t); + } + + case NETCONF_OPT_ADDR_LEN: + if (value_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + switch (*_get_uint16_ptr(value)) { + case 2: + dev->flags &= ~NG_ZEP_FLAGS_DST_ADDR_LONG; + break; + + case 8: + dev->flags |= NG_ZEP_FLAGS_DST_ADDR_LONG; + break; + + default: + return -ENOTSUP; + } + + return sizeof(uint16_t); + + case NETCONF_OPT_SRC_LEN: + if (value_len < sizeof(uint16_t)) { + return -EOVERFLOW; + } + + switch (*_get_uint16_ptr(value)) { + case 2: + dev->flags &= ~NG_ZEP_FLAGS_SRC_ADDR_LONG; + break; + + case 8: + dev->flags |= NG_ZEP_FLAGS_SRC_ADDR_LONG; + break; + + default: + return -ENOTSUP; + } + + return sizeof(uint16_t); + + case NETCONF_OPT_NID: + if (value_len < sizeof(be_uint16_t)) { + return -EOVERFLOW; + } + else { + be_uint16_t *val = value; + + dev->pan = byteorder_btols(*val); + return sizeof(be_uint16_t); + } + + case NETCONF_OPT_AUTOACK: + if (value_len < sizeof(ng_netconf_enable_t)) { + return -EOVERFLOW; + } + + _set_flag_ptr(value, dev->flags, NG_ZEP_FLAGS_AUTOACK); + return sizeof(uint16_t); + + default: + return -ENOTSUP; + } +} + +static void _isr_event(ng_netdev_t *dev, uint32_t event_type) +{ + switch (event_type) { + case _EVENT_RX_STARTED: + DEBUG("zep: ISR event: RX started\n"); + _rx_started_event((ng_zep_t *)dev); + break; + + default: + DEBUG("zep: event %" PRIu32 " not handled\n", event_type); + break; + } +} + +void *_event_loop(void *args) +{ + msg_t msg, ack, msg_q[NG_ZEP_MSG_QUEUE_SIZE]; + ng_netdev_t *dev = (ng_netdev_t *)args; + ng_netapi_opt_t *opt; + ng_netreg_entry_t my_reg = { NULL, ((ng_zep_t *)args)->src_port, + KERNEL_PID_UNDEF + }; + + if (msg_init_queue(msg_q, NG_ZEP_MSG_QUEUE_SIZE)) { + return NULL; + } + + my_reg.pid = thread_getpid(); + + ng_netreg_register(NG_NETTYPE_UDP, &my_reg); + + while (1) { + msg_receive(&msg); + + switch (msg.type) { + case NG_NETAPI_MSG_TYPE_RCV: + DEBUG("zep: NG_NETAPI_MSG_TYPE_RCV\n"); + ringbuffer_add(&_rx_buf, (char *)(&msg.content.ptr), + sizeof(ng_pktsnip_t *)); + ack.type = NG_NETDEV_MSG_TYPE_EVENT; + ack.content.value = _EVENT_RX_STARTED; + msg_send_int(&ack, dev->mac_pid); + break; + + case NG_NETAPI_MSG_TYPE_SND: + DEBUG("zep: NG_NETAPI_MSG_TYPE_SND\n"); + _send(dev, (ng_pktsnip_t *)msg.content.ptr); + break; + + case NG_NETAPI_MSG_TYPE_GET: + DEBUG("zep: NG_NETAPI_MSG_TYPE_GET\n"); + opt = (ng_netapi_opt_t *)msg.content.ptr; + ack.type = NG_NETAPI_MSG_TYPE_ACK; + ack.content.value = _get(dev, opt->opt, opt->data, opt->data_len); + msg_reply(&msg, &ack); + break; + + case NG_NETAPI_MSG_TYPE_SET: + DEBUG("zep: NG_NETAPI_MSG_TYPE_SET\n"); + opt = (ng_netapi_opt_t *)msg.content.ptr; + ack.type = NG_NETAPI_MSG_TYPE_ACK; + ack.content.value = _set(dev, opt->opt, opt->data, opt->data_len); + msg_reply(&msg, &ack); + break; + + default: + DEBUG("udp: received unidentified message 0x%04" PRIx16 "\n", + msg.type); + break; + } + } + + return NULL; +} + +static ng_pktsnip_t *_zep_hdr_build(ng_zep_t *dev, size_t size, bool ack) +{ + ng_pktsnip_t *zep; + + switch (dev->version) { + case 1: + DEBUG("zep: Build ZEPv1 data header in pktbuf\n"); + zep = ng_pktbuf_add(NULL, NULL, sizeof(ng_zep_v1_hdr_t) + size, + NG_NETTYPE_UNDEF); + break; + + case 2: + if (ack) { + DEBUG("zep: Build ZEPv2 ACK header in pktbuf\n"); + zep = ng_pktbuf_add(NULL, NULL, sizeof(ng_zep_v2_ack_hdr_t) + size, + NG_NETTYPE_UNDEF); + } + else { + DEBUG("zep: Build ZEPv2 data header in pktbuf\n"); + zep = ng_pktbuf_add(NULL, NULL, sizeof(ng_zep_v2_data_hdr_t) + size, + NG_NETTYPE_UNDEF); + } + + break; + + default: + DEBUG("zep: malconfigured version: %" PRIu8 "\n", dev->version); + return NULL; + } + + return zep; +} + +static inline size_t _zep_hdr_fill_v1(ng_zep_t *dev, ng_zep_v1_hdr_t *hdr, + size_t payload_len) +{ + hdr->version = 1; + hdr->chan = dev->chan - NG_ZEP_MIN_CHANNEL; + hdr->dev = byteorder_htons(1); + hdr->lqi_mode = dev->lqi_mode; + hdr->lqi_val = 0xff; /* TODO: set */ + memset(hdr->resv, 0, sizeof(hdr->resv)); + hdr->length = payload_len; + + return sizeof(ng_zep_v1_hdr_t); +} + +static size_t _zep_hdr_fill_v2_data(ng_zep_t *dev, ng_zep_v2_data_hdr_t *hdr, + size_t payload_len) +{ + uint32_t epoch_sec; + + epoch_sec = (uint32_t)time(NULL); + + hdr->version = 2; + hdr->type = NG_ZEP_V2_TYPE_DATA; + hdr->chan = dev->chan - NG_ZEP_MIN_CHANNEL; + hdr->dev = byteorder_htons(1); + hdr->lqi_mode = dev->lqi_mode; + hdr->lqi_val = 0xff; /* TODO: set */ + hdr->time.b32[0] = byteorder_htonl(epoch_sec); + hdr->time.u32[1] = 0; + hdr->seq = byteorder_htonl(dev->seq); + memset(hdr->resv, 0, sizeof(hdr->resv)); + hdr->length = payload_len; + + return sizeof(ng_zep_v2_data_hdr_t); +} + +static size_t _zep_hdr_fill(ng_zep_t *dev, ng_zep_hdr_t *hdr, + size_t payload_len) +{ + hdr->preamble[0] = 'E'; + hdr->preamble[1] = 'X'; + + switch (dev->version) { + case 1: + return _zep_hdr_fill_v1(dev, (ng_zep_v1_hdr_t *)hdr, payload_len); + + case 2: + return _zep_hdr_fill_v2_data(dev, (ng_zep_v2_data_hdr_t *)hdr, + payload_len); + break; + + default: + return 0; + } +} + +static ng_pktsnip_t *_create_received(ng_zep_t *dev, ng_pktsnip_t *pkt, + uint8_t lqi, uint8_t frame_len, + uint8_t version) +{ + ng_pktsnip_t *payload, *mhr, *netif; + size_t mhr_len; + + (void)version; + + if ((frame_len != pkt->size) || (_calc_fcs(0, pkt->data, pkt->size) != 0)) { + return NULL; + } + + payload = ng_pktbuf_add(pkt, pkt->data, pkt->size - 2, dev->proto); + + if (payload == NULL) { + return NULL; + } + + pkt = ng_pktbuf_remove_snip(pkt, pkt); /* remove FCS */ + + mhr_len = _get_frame_hdr_len(pkt->data); + + if (mhr_len == 0) { + return NULL; + } + + mhr = ng_pktbuf_add(pkt, pkt->data, mhr_len, NG_NETTYPE_UNDEF); + + /* TODO: send ACK */ + + netif = _make_netif_hdr(mhr->data); + + pkt = ng_pktbuf_remove_snip(pkt, mhr); + + ((ng_netif_hdr_t *)netif->data)->if_pid = dev->mac_pid; + ((ng_netif_hdr_t *)netif->data)->lqi = lqi; + ((ng_netif_hdr_t *)netif->data)->rssi = 0; + + LL_APPEND(pkt, netif); + + return pkt; +} + +static ng_pktsnip_t *_recv_v1(ng_zep_t *dev, ng_pktsnip_t *pkt) +{ + ng_pktsnip_t *zep; + ng_zep_v1_hdr_t *hdr = pkt->data; + uint8_t lqi, frame_len; + + if (pkt->size < sizeof(ng_zep_v1_hdr_t)) { + return NULL; + } + + lqi = hdr->lqi_val; + frame_len = hdr->length; + + if ((hdr->chan + NG_ZEP_MIN_CHANNEL) != dev->chan) { + return NULL; + } + + zep = ng_pktbuf_add(pkt, pkt->data, sizeof(ng_zep_v1_hdr_t), NG_NETTYPE_UNDEF); + + if (zep == NULL) { + return NULL; + } + + pkt = ng_pktbuf_remove_snip(pkt, zep); + + return _create_received(dev, pkt, lqi, frame_len, 2); +} + +static ng_pktsnip_t *_recv_v2(ng_zep_t *dev, ng_pktsnip_t *pkt) +{ + ng_zep_v2_data_hdr_t *hdr = pkt->data; + + if (pkt->size < sizeof(ng_zep_v2_ack_hdr_t)) { + return NULL; + } + + if (hdr->type == NG_ZEP_V2_TYPE_ACK) { + /* TODO handle correctly */ + } + else if ((hdr->type == NG_ZEP_V2_TYPE_DATA) && + (pkt->size >= sizeof(ng_zep_v2_data_hdr_t))) { + ng_pktsnip_t *zep; + uint8_t lqi = hdr->lqi_val, frame_len = hdr->length; + + if ((hdr->chan + NG_ZEP_MIN_CHANNEL) != dev->chan) { + return NULL; + } + + zep = ng_pktbuf_add(pkt, pkt->data, sizeof(ng_zep_v2_data_hdr_t), NG_NETTYPE_UNDEF); + + if (zep == NULL) { + return NULL; + } + + pkt = ng_pktbuf_remove_snip(pkt, zep); + + return _create_received(dev, pkt, lqi, frame_len, 2); + } + + return NULL; +} + +static void _rx_started_event(ng_zep_t *dev) +{ + ng_pktsnip_t *tmp, *pkt; + ng_zep_hdr_t *hdr; + + if (ringbuffer_get(&_rx_buf, (char *)(&pkt), + sizeof(ng_pktsnip_t *)) != sizeof(ng_pktsnip_t *)) { + return; + } + + tmp = ng_pktbuf_start_write(pkt); + + if (tmp == NULL) { + DEBUG("zep: Could not get write access to received packet\n"); + ng_pktbuf_release(pkt); + return; + } + + pkt = tmp; + + while (pkt->next) { + /* remove everything below UDP */ + ng_pktbuf_remove_snip(pkt, pkt->next); + } + + hdr = pkt->data; + + if ((pkt->size < 2) || (hdr->preamble[0] != 'E') || + (hdr->preamble[1] != 'X')) { + ng_pktbuf_release(pkt); + return; + } + + switch (hdr->version) { + case 1: + pkt = _recv_v1(dev, pkt); + break; + + case 2: + pkt = _recv_v2(dev, pkt); + break; + + default: + ng_pktbuf_release(pkt); + return; + } + + if (pkt != NULL && dev->event_cb != NULL) { + dev->event_cb(NETDEV_EVENT_RX_COMPLETE, pkt); + } + else if (pkt != NULL) { + ng_pktbuf_release(pkt); + } +} + +/* TODO: Generalize and move all below to ng_ieee802154 */ +static size_t _make_data_frame_hdr(ng_zep_t *dev, uint8_t *buf, + ng_netif_hdr_t *hdr) +{ + int pos = 0; + + /* we are building a data frame here */ + buf[0] = NG_IEEE802154_FCF_TYPE_DATA; + buf[1] = 0x88; /* use short src and dst addresses as starting point */ + + /* if AUTOACK is enabled, then we also expect ACKs for this packet */ + if (dev->flags & NG_ZEP_FLAGS_AUTOACK) { + buf[0] |= NG_IEEE802154_FCF_ACK_REQ; + } + + /* fill in destination PAN ID */ + pos = 3; + buf[pos++] = dev->pan.u8[0]; + buf[pos++] = dev->pan.u8[1]; + + /* fill in destination address */ + if (hdr->flags & + (NG_NETIF_HDR_FLAGS_BROADCAST | NG_NETIF_HDR_FLAGS_MULTICAST)) { + buf[pos++] = 0xff; + buf[pos++] = 0xff; + } + else if (hdr->dst_l2addr_len == 2) { + uint8_t *dst_addr = ng_netif_hdr_get_dst_addr(hdr); + buf[pos++] = dst_addr[1]; + buf[pos++] = dst_addr[0]; + } + else if (hdr->dst_l2addr_len == 8) { + buf[1] |= 0x04; + uint8_t *dst_addr = ng_netif_hdr_get_dst_addr(hdr); + + for (int i = 7; i >= 0; i--) { + buf[pos++] = dst_addr[i]; + } + } + else { + /* unsupported address length */ + return 0; + } + + /* fill in source PAN ID (if applicable) */ + if (dev->flags & NG_ZEP_FLAGS_USE_SRC_PAN) { + buf[pos++] = dev->pan.u8[0]; + buf[pos++] = dev->pan.u8[1]; + } + else { + buf[0] |= NG_IEEE802154_FCF_PAN_COMP; + } + + /* fill in source address */ + if (dev->flags & NG_ZEP_FLAGS_SRC_ADDR_LONG) { + buf[1] |= 0x40; + memcpy(&(buf[pos]), &dev->eui64, 8); + pos += 8; + } + else { + memcpy(&(buf[pos]), &dev->addr, 2); + pos += 2; + } + + /* set sequence number */ + buf[2] = dev->seq++; + /* return actual header length */ + return pos; +} + +static size_t _get_frame_hdr_len(uint8_t *mhr) +{ + uint8_t tmp; + size_t len = 3; + + /* figure out address sizes */ + tmp = (mhr[1] & NG_IEEE802154_FCF_DST_ADDR_MASK); + + if (tmp == NG_IEEE802154_FCF_DST_ADDR_SHORT) { + len += 4; + } + else if (tmp == NG_IEEE802154_FCF_DST_ADDR_LONG) { + len += 10; + } + else if (tmp != NG_IEEE802154_FCF_DST_ADDR_VOID) { + return 0; + } + + tmp = (mhr[1] & NG_IEEE802154_FCF_SRC_ADDR_MASK); + + if (tmp == NG_IEEE802154_FCF_SRC_ADDR_VOID) { + return len; + } + else { + if (!(mhr[0] & NG_IEEE802154_FCF_PAN_COMP)) { + len += 2; + } + + if (tmp == NG_IEEE802154_FCF_SRC_ADDR_SHORT) { + return (len + 2); + } + else if (tmp == NG_IEEE802154_FCF_SRC_ADDR_LONG) { + return (len + 8); + } + } + + return 0; +} + +ng_pktsnip_t *_make_netif_hdr(uint8_t *mhr) +{ + uint8_t tmp; + uint8_t *addr; + uint8_t src_len, dst_len; + ng_pktsnip_t *snip; + ng_netif_hdr_t *hdr; + + /* figure out address sizes */ + tmp = mhr[1] & NG_IEEE802154_FCF_SRC_ADDR_MASK; + + if (tmp == NG_IEEE802154_FCF_SRC_ADDR_SHORT) { + src_len = 2; + } + else if (tmp == NG_IEEE802154_FCF_SRC_ADDR_LONG) { + src_len = 8; + } + else if (tmp == 0) { + src_len = 0; + } + else { + return NULL; + } + + tmp = mhr[1] & NG_IEEE802154_FCF_DST_ADDR_MASK; + + if (tmp == NG_IEEE802154_FCF_DST_ADDR_SHORT) { + dst_len = 2; + } + else if (tmp == NG_IEEE802154_FCF_DST_ADDR_LONG) { + dst_len = 8; + } + else if (tmp == 0) { + dst_len = 0; + } + else { + return NULL; + } + + /* allocate space for header */ + snip = ng_pktbuf_add(NULL, NULL, sizeof(ng_netif_hdr_t) + src_len + dst_len, + NG_NETTYPE_NETIF); + + if (snip == NULL) { + return NULL; + } + + /* fill header */ + hdr = (ng_netif_hdr_t *)snip->data; + ng_netif_hdr_init(hdr, src_len, dst_len); + + if (dst_len > 0) { + tmp = 5 + dst_len; + addr = ng_netif_hdr_get_dst_addr(hdr); + + for (int i = 0; i < dst_len; i++) { + addr[i] = mhr[5 + (dst_len - i) - 1]; + } + } + else { + tmp = 3; + } + + if (!(mhr[0] & NG_IEEE802154_FCF_PAN_COMP)) { + tmp += 2; + } + + if (src_len > 0) { + addr = ng_netif_hdr_get_src_addr(hdr); + + for (int i = 0; i < src_len; i++) { + addr[i] = mhr[tmp + (src_len - i) - 1]; + } + } + + return snip; +} + +static uint16_t _calc_fcs(uint16_t fcs, const uint8_t *frame, uint8_t frame_len) +{ + for (uint8_t byte = 0; byte < frame_len; ++byte) { + fcs ^= frame[byte]; + + for (uint8_t bit = 8; bit > 0; --bit) { + if (fcs & 0x0001) { + fcs = (fcs >> 1) ^ IEEE_802154_FCS_POLY; + } + else { + fcs = (fcs >> 1); + } + } + } + + return fcs; +} + +/** + * @} + */ diff --git a/sys/shell/commands/Makefile b/sys/shell/commands/Makefile index b64d70c46c..02c420bb62 100644 --- a/sys/shell/commands/Makefile +++ b/sys/shell/commands/Makefile @@ -67,6 +67,9 @@ endif ifneq (,$(filter ng_icmpv6_echo vtimer,$(USEMODULE))) SRC += sc_icmpv6_echo.c endif +ifneq (,$(filter ng_zep ng_ipv6_addr,$(USEMODULE))) + SRC += sc_zep.c +endif # TODO # Conditional building not possible at the moment due to diff --git a/sys/shell/commands/sc_zep.c b/sys/shell/commands/sc_zep.c new file mode 100644 index 0000000000..f5005b4f49 --- /dev/null +++ b/sys/shell/commands/sc_zep.c @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2015 Martine Lenders <mlenders@inf.fu-berlin.de> + * + * 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. + */ + +/** + * @{ + * + * @file + */ + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> + +#include "net/ng_ipv6/addr.h" +#include "net/ng_ipv6/netif.h" +#include "net/ng_nomac.h" +#include "net/ng_zep.h" +#include "thread.h" + +static ng_zep_t zep; +static char zep_stack[THREAD_STACKSIZE_DEFAULT]; + +int _zep_init(int argc, char **argv) +{ + uint16_t src_port = NG_ZEP_DEFAULT_PORT; + uint16_t dst_port = NG_ZEP_DEFAULT_PORT; + ng_ipv6_addr_t dst_addr; + int res; + + if (argc < 2) { + printf("usage: %s dst_addr [src_port [dst_port]]\n", argv[0]); + return 1; + } + + if (argc > 2) { + src_port = (uint16_t)atoi(argv[2]); + } + + if (argc > 3) { + dst_port = (uint16_t)atoi(argv[3]); + } + + ng_ipv6_addr_from_str(&dst_addr, argv[1]); + + if ((res = ng_zep_init(&zep, src_port, &dst_addr, dst_port)) < 0) { + switch (res) { + case -EADDRINUSE: + printf("error: Source port %" PRIu16 " already in use\n", src_port); + break; + + case -EEXIST: + puts("error: ZEP already intialized"); + break; + + case -ENOTSUP: + printf("error: dst_addr (%s) invalid\n", argv[1]); + break; + + case -EOVERFLOW: + puts("error: too many threads running"); + break; + + default: + puts("unexpected error"); + break; + } + + return 1; + } + + if ((res = ng_nomac_init(zep_stack, sizeof(zep_stack), THREAD_PRIORITY_MAIN - 3, + "zep_l2", (ng_netdev_t *)&zep)) < 0) { + switch (res) { + case -EOVERFLOW: + puts("error: too many threads running"); + break; + + default: + puts("unexpected error"); + break; + } + + return 1; + } + +#ifdef MODULE_NG_IPV6_NETIF + ng_ipv6_netif_init_by_dev(); +#endif + + return 0; +} + +/** @} */ diff --git a/sys/shell/commands/shell_commands.c b/sys/shell/commands/shell_commands.c index 5adad5e291..9d1a58810b 100644 --- a/sys/shell/commands/shell_commands.c +++ b/sys/shell/commands/shell_commands.c @@ -174,6 +174,12 @@ extern int _ipv6_nc_manage(int argc, char **argv); extern int _ipv6_nc_routers(int argc, char **argv); #endif +#ifdef MODULE_NG_ZEP +#ifdef MODULE_NG_IPV6_ADDR +extern int _zep_init(int argc, char **argv); +#endif +#endif + const shell_command_t _shell_command_list[] = { {"reboot", "Reboot the node", _reboot_handler}, #ifdef MODULE_CONFIG @@ -286,6 +292,11 @@ const shell_command_t _shell_command_list[] = { #ifdef MODULE_NG_IPV6_NC {"ncache", "manage neighbor cache by hand", _ipv6_nc_manage }, {"routers", "IPv6 default router list", _ipv6_nc_routers }, +#endif +#ifdef MODULE_NG_ZEP +#ifdef MODULE_NG_IPV6_ADDR + {"zep_init", "initializes ZEP (Zigbee Encapsulation Protocol)", _zep_init }, +#endif #endif {NULL, NULL, NULL} }; -- GitLab