diff --git a/Makefile.dep b/Makefile.dep index f3cda9959b..3533f0a155 100644 --- a/Makefile.dep +++ b/Makefile.dep @@ -300,6 +300,10 @@ ifneq (,$(filter gnrc_ipv6_ext_frag,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter gnrc_ipv6_ext_opt,$(USEMODULE))) + USEMODULE += gnrc_ipv6_ext +endif + ifneq (,$(filter gnrc_ipv6_ext_rh,$(USEMODULE))) USEMODULE += gnrc_ipv6_ext endif diff --git a/sys/include/net/gnrc/ipv6/ext/opt.h b/sys/include/net/gnrc/ipv6/ext/opt.h new file mode 100644 index 0000000000..02fbcc2c43 --- /dev/null +++ b/sys/include/net/gnrc/ipv6/ext/opt.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2020 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_gnrc_ipv6_ext_opt Support for IPv6 option extension headers + * @ingroup net_gnrc_ipv6_ext + * @brief GNRC implementation of IPv6 hop-by-hop and destination option + * header extension + * @{ + * + * @file + * @brief GNRC hop-by-hop and destination option header definitions. + * + * @author Martine Lenders + */ +#ifndef NET_GNRC_IPV6_EXT_OPT_H +#define NET_GNRC_IPV6_EXT_OPT_H + +#include + +#include "net/gnrc/pkt.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Processes all options within an IPv6 option header + * + * @pre `pkt != NULL` + * @pre `(protnum == PROTNUM_IPV6_EXT_HOPOPT) || (protnum == PROTNUM_IPV6_EXT_DST)` + * + * @param[in] pkt The packet containing the option header. The option + * must be contained in the first snip, with all + * preceding headers marked (in receive order). + * Must not be NULL. + * @param[in] protnum The protocol number of the option header. Must be + * @ref PROTNUM_IPV6_EXT_HOPOPT or @ref + * PROTNUM_IPV6_EXT_DST + * + * @return @p pkt with the option header marked on success. + * @return NULL, if the packet was consumed by the option handling. + * @return NULL, on error. @p pkt is released with EINVAL in that case and if + * necessary and [`gnrc_icmpv6_error`](@ref net_gnrc_icmpv6_error) is + * used, the according ICMPv6 error message is sent. + */ +gnrc_pktsnip_t *gnrc_ipv6_ext_opt_process(gnrc_pktsnip_t *pkt, + uint8_t protnum); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_IPV6_EXT_OPT_H */ +/** @} */ diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index 991b41069e..0146f20b79 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -19,6 +19,9 @@ endif ifneq (,$(filter gnrc_ipv6_ext_frag,$(USEMODULE))) DIRS += network_layer/ipv6/ext/frag endif +ifneq (,$(filter gnrc_ipv6_ext_opt,$(USEMODULE))) + DIRS += network_layer/ipv6/ext/opt +endif ifneq (,$(filter gnrc_ipv6_ext_rh,$(USEMODULE))) DIRS += network_layer/ipv6/ext/rh endif diff --git a/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c b/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c index 3aaaa90f23..82fdfc18ad 100644 --- a/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c +++ b/sys/net/gnrc/network_layer/ipv6/ext/gnrc_ipv6_ext.c @@ -24,6 +24,7 @@ #include "net/gnrc/icmpv6/error.h" #include "net/gnrc/ipv6.h" #include "net/gnrc/ipv6/ext/frag.h" +#include "net/gnrc/ipv6/ext/opt.h" #include "net/gnrc/ipv6/ext/rh.h" #if defined(MODULE_GNRC_SIXLOWPAN_IPHC_NHC) && \ defined(MODULE_GNRC_IPV6_EXT_FRAG) @@ -311,6 +312,10 @@ static gnrc_pktsnip_t *_demux(gnrc_pktsnip_t *pkt, unsigned protnum) #endif /* MODULE_GNRC_IPV6_EXT_FRAG */ case PROTNUM_IPV6_EXT_HOPOPT: case PROTNUM_IPV6_EXT_DST: + if (IS_USED(MODULE_GNRC_IPV6_EXT_OPT)) { + return gnrc_ipv6_ext_opt_process(pkt, protnum); + } + /* Intentionally falls through */ case PROTNUM_IPV6_EXT_AH: case PROTNUM_IPV6_EXT_ESP: case PROTNUM_IPV6_EXT_MOB: diff --git a/sys/net/gnrc/network_layer/ipv6/ext/opt/Makefile b/sys/net/gnrc/network_layer/ipv6/ext/opt/Makefile new file mode 100644 index 0000000000..4df77e0662 --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/ext/opt/Makefile @@ -0,0 +1,3 @@ +MODULE := gnrc_ipv6_ext_opt + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/network_layer/ipv6/ext/opt/gnrc_ipv6_ext_opt.c b/sys/net/gnrc/network_layer/ipv6/ext/opt/gnrc_ipv6_ext_opt.c new file mode 100644 index 0000000000..13fd4fbcb1 --- /dev/null +++ b/sys/net/gnrc/network_layer/ipv6/ext/opt/gnrc_ipv6_ext_opt.c @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2020 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. + */ + +/** + * @{ + * + * @file + * @author Martine Lenders + */ + +#include "net/ipv6.h" +#include "net/ipv6/ext.h" +#include "net/ipv6/ext/opt.h" +#include "net/gnrc/icmpv6/error.h" +#include "net/gnrc/pktbuf.h" + +#include "net/gnrc/ipv6/ext/opt.h" + +#define ENABLE_DEBUG (0) +#include "debug.h" + +/** + * @brief Determine what action to do, when option is not recognized + * + * @see https://tools.ietf.org/html/rfc8200#section-4.2 + * + * @param[in] type Type of the option + */ +static inline uint8_t _unrec_action(uint8_t type) +{ + return (type & IPV6_EXT_OPT_ACTION_MASK); +} + +static bool _multicast_dst(gnrc_pktsnip_t *pkt) +{ + gnrc_pktsnip_t *ipv6 = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_IPV6); + ipv6_hdr_t *ipv6_hdr; + + assert(ipv6 != NULL); + ipv6_hdr = ipv6->data; + return ipv6_addr_is_multicast(&ipv6_hdr->dst); +} + +gnrc_pktsnip_t *gnrc_ipv6_ext_opt_process(gnrc_pktsnip_t *pkt, + uint8_t protnum) +{ + assert(pkt != NULL); + assert((protnum == PROTNUM_IPV6_EXT_HOPOPT) || + (protnum == PROTNUM_IPV6_EXT_DST)); + gnrc_pktsnip_t *hdr; + ipv6_ext_t *opt_hdr = pkt->data; + uint8_t *opts; + size_t hdr_len; + + if (pkt->size < sizeof(ipv6_ext_t)) { + DEBUG("gnrc_ipv6_ext_opt: packet of invalid size\n"); + goto error; + } + hdr_len = ((opt_hdr->len * IPV6_EXT_LEN_UNIT) + IPV6_EXT_LEN_UNIT); + hdr = gnrc_pktbuf_mark(pkt, hdr_len, GNRC_NETTYPE_IPV6_EXT); + if (hdr == NULL) { + DEBUG("gnrc_ipv6_ext_opt: unable to mark option header\n"); + goto error; + } + opts = hdr->data; + for (unsigned offset = sizeof(ipv6_ext_t); offset < hdr_len;) { + uint8_t opt_type = opts[offset++]; + uint8_t opt_len; + + if (opt_type == IPV6_EXT_OPT_PAD1) { + /* nothing more to do */ + continue; + } + opt_len = opts[offset++]; + if (opt_len > (hdr_len - offset)) { + DEBUG("gnrc_ipv6_ext_opt: invalid option size\n"); + goto error; + } + switch (opt_type) { + /* IPV6_EXT_OPT_PAD1 already handled before length check due + * to special format */ + case IPV6_EXT_OPT_PADN: + /* nothing to do, offset will be progressed below */ + break; + default: { + bool send_error = false; + + switch (_unrec_action(opt_type)) { + case IPV6_EXT_OPT_ACTION_SKIP: + DEBUG("gnrc_ipv6_ext_opt: skipping unknown " + "option %02x\n", opt_type); + /* skip here already, as we don't reach the + * incrementation of offset below */ + offset += opt_len; + continue; + case IPV6_EXT_OPT_ACTION_DISC: + break; + case IPV6_EXT_OPT_ACTION_DISC_ERR_MCAST: + send_error = IS_USED(MODULE_GNRC_ICMPV6_ERROR); + break; + case IPV6_EXT_OPT_ACTION_DISC_ERR: + send_error = IS_USED(MODULE_GNRC_ICMPV6_ERROR) && + !_multicast_dst(pkt); + break; + } + DEBUG("gnrc_ipv6_ext_opt: discarding packet with unknown " + "option %02x\n", opt_type); + if (send_error) { + DEBUG("gnrc_ipv6_ext_opt: reporting parameter problem " + "for option %02x (pos at %02x)\n", opts[offset - 2U], + opt_type); + gnrc_icmpv6_error_param_prob_send( + ICMPV6_ERROR_PARAM_PROB_OPT, + /* offset was already progressed to opt data*/ + &opts[offset - 2U], pkt); + } + goto error; + } + } + offset += opt_len; + } + return pkt; +error: + gnrc_pktbuf_release_error(pkt, EINVAL); + return NULL; +} + +/** @} */