From patchwork Mon Apr 29 23:04:16 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092879 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="h9F/11Wh"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxG2f9fz9s9T for ; Tue, 30 Apr 2019 09:04:50 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729677AbfD2XEt (ORCPT ); Mon, 29 Apr 2019 19:04:49 -0400 Received: from mail-io1-f65.google.com ([209.85.166.65]:34152 "EHLO mail-io1-f65.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729603AbfD2XEt (ORCPT ); Mon, 29 Apr 2019 19:04:49 -0400 Received: by mail-io1-f65.google.com with SMTP id h26so10558457ioj.1 for ; Mon, 29 Apr 2019 16:04:48 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=hAj0kqmai0y0EXSiNLbJqJt1e4EmwfMuX+PW7YER1s8=; b=h9F/11WhikgGH06gPdS6ROy7zShW+Jr0kTrCAYS1jPes5dEC03jq7DAnu17zRSDov9 5BMEc4/ETA9x/0tCORkDxLfUk4nquXsF157UnGVAU/BJImAx1Rd2FQtfFhfVDF+lrQUo O6YPPeLszHWEbxJLH8Ufafzd32RTTL3PrH7Ff5gfmFtPHqowkXbVJK+aHANTqBysDWh6 Biv3ju/N1D0YOjj3thkA3mdHMKbq4g/PA7G2ChznBCSzLsIxoQ4yb5dRUk8eoZFKM7WY 7XJ0L9Nz51p4MeiEfmkgvybn1iuUzunhP9fiWp0BfT1lreAdhlpoHGlcs/LKUMxSVZqe Jk4A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=hAj0kqmai0y0EXSiNLbJqJt1e4EmwfMuX+PW7YER1s8=; b=abOuqo2UZtEP341++qaDNVAN//OwzMpNLdDn4ke2revS95xlL+NoyUJ7psNrqqOBiB BbUpQ/+hbZVrnWMEYQPALbRVznR5t2fZKA1QpnkEb2uxNUo31U6aq6VbgUF8zjZRG3za uugo5C5F2Ixl2Eqm/Il0L987aJLZexcfyl8mrVrAHqvvDT+A4wbQ4g828p/IunfadmMI reg7PQjYKdt9ZvI7VmIivRgHKaRBHpow/2NXdOgnyKLc/3fOcISTw7EHCIchqQLtqbU0 xVKZHJlX4o2cuJV5vjLwkDkBvxj4noBniLSNuX+vryq0QlvYfJhXGQC/4Hc/vs9Gb7DE eNag== X-Gm-Message-State: APjAAAXWSRuu6dMWDfIIfwJyw6a/FX6DkWsflAjZN8CcoanR9wRU2sFH SPHz8AfH7zpDVPrKZRXvY/9eRQ== X-Google-Smtp-Source: APXvYqzF3qShgWe7RpPSsoh8/tqV96m0ubeKMA9jJhQBAbaV4/23Ga99YYDKTdgQnW+YpZ1uF+5y9g== X-Received: by 2002:a6b:3b53:: with SMTP id i80mr1189136ioa.142.1556579087576; Mon, 29 Apr 2019 16:04:47 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.46 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:47 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 1/8] exthdrs: Move generic EH functions to exthdrs_common.c Date: Mon, 29 Apr 2019 16:04:16 -0700 Message-Id: <1556579063-1367-2-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Move generic functions in exthdrs.c to new exthdrs_common.c so that exthdrs.c only contains functions that are specific to IPv6 processing, and exthdrs_common.c contains functions that are generic. These functions include those that will be used with IPv4 extension headers. Signed-off-by: Tom Herbert --- net/ipv6/Kconfig | 4 ++ net/ipv6/Makefile | 1 + net/ipv6/exthdrs.c | 138 -------------------------------------------- net/ipv6/exthdrs_common.c | 144 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+), 138 deletions(-) create mode 100644 net/ipv6/exthdrs_common.c diff --git a/net/ipv6/Kconfig b/net/ipv6/Kconfig index 613282c..c88fc9b 100644 --- a/net/ipv6/Kconfig +++ b/net/ipv6/Kconfig @@ -2,9 +2,13 @@ # IPv6 configuration # +config EXTHDRS + bool + # IPv6 as module will cause a CRASH if you try to unload it menuconfig IPV6 tristate "The IPv6 protocol" + select EXTHDRS default y ---help--- Support for IP version 6 (IPv6). diff --git a/net/ipv6/Makefile b/net/ipv6/Makefile index 72bd775..22438ca 100644 --- a/net/ipv6/Makefile +++ b/net/ipv6/Makefile @@ -48,6 +48,7 @@ obj-$(CONFIG_IPV6_SIT) += sit.o obj-$(CONFIG_IPV6_TUNNEL) += ip6_tunnel.o obj-$(CONFIG_IPV6_GRE) += ip6_gre.o obj-$(CONFIG_IPV6_FOU) += fou6.o +obj-$(CONFIG_EXTHDRS) += exthdrs_common.o obj-y += addrconf_core.o exthdrs_core.o ip6_checksum.o ip6_icmp.o obj-$(CONFIG_INET) += output_core.o protocol.o $(ipv6-offload) diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 55ca778..6dbacf1 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -788,144 +788,6 @@ void ipv6_push_frag_opts(struct sk_buff *skb, struct ipv6_txoptions *opt, u8 *pr } EXPORT_SYMBOL(ipv6_push_frag_opts); -struct ipv6_txoptions * -ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt) -{ - struct ipv6_txoptions *opt2; - - opt2 = sock_kmalloc(sk, opt->tot_len, GFP_ATOMIC); - if (opt2) { - long dif = (char *)opt2 - (char *)opt; - memcpy(opt2, opt, opt->tot_len); - if (opt2->hopopt) - *((char **)&opt2->hopopt) += dif; - if (opt2->dst0opt) - *((char **)&opt2->dst0opt) += dif; - if (opt2->dst1opt) - *((char **)&opt2->dst1opt) += dif; - if (opt2->srcrt) - *((char **)&opt2->srcrt) += dif; - refcount_set(&opt2->refcnt, 1); - } - return opt2; -} -EXPORT_SYMBOL_GPL(ipv6_dup_options); - -static void ipv6_renew_option(int renewtype, - struct ipv6_opt_hdr **dest, - struct ipv6_opt_hdr *old, - struct ipv6_opt_hdr *new, - int newtype, char **p) -{ - struct ipv6_opt_hdr *src; - - src = (renewtype == newtype ? new : old); - if (!src) - return; - - memcpy(*p, src, ipv6_optlen(src)); - *dest = (struct ipv6_opt_hdr *)*p; - *p += CMSG_ALIGN(ipv6_optlen(*dest)); -} - -/** - * ipv6_renew_options - replace a specific ext hdr with a new one. - * - * @sk: sock from which to allocate memory - * @opt: original options - * @newtype: option type to replace in @opt - * @newopt: new option of type @newtype to replace (user-mem) - * @newoptlen: length of @newopt - * - * Returns a new set of options which is a copy of @opt with the - * option type @newtype replaced with @newopt. - * - * @opt may be NULL, in which case a new set of options is returned - * containing just @newopt. - * - * @newopt may be NULL, in which case the specified option type is - * not copied into the new set of options. - * - * The new set of options is allocated from the socket option memory - * buffer of @sk. - */ -struct ipv6_txoptions * -ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt, - int newtype, struct ipv6_opt_hdr *newopt) -{ - int tot_len = 0; - char *p; - struct ipv6_txoptions *opt2; - - if (opt) { - if (newtype != IPV6_HOPOPTS && opt->hopopt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->hopopt)); - if (newtype != IPV6_RTHDRDSTOPTS && opt->dst0opt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst0opt)); - if (newtype != IPV6_RTHDR && opt->srcrt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->srcrt)); - if (newtype != IPV6_DSTOPTS && opt->dst1opt) - tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt)); - } - - if (newopt) - tot_len += CMSG_ALIGN(ipv6_optlen(newopt)); - - if (!tot_len) - return NULL; - - tot_len += sizeof(*opt2); - opt2 = sock_kmalloc(sk, tot_len, GFP_ATOMIC); - if (!opt2) - return ERR_PTR(-ENOBUFS); - - memset(opt2, 0, tot_len); - refcount_set(&opt2->refcnt, 1); - opt2->tot_len = tot_len; - p = (char *)(opt2 + 1); - - ipv6_renew_option(IPV6_HOPOPTS, &opt2->hopopt, - (opt ? opt->hopopt : NULL), - newopt, newtype, &p); - ipv6_renew_option(IPV6_RTHDRDSTOPTS, &opt2->dst0opt, - (opt ? opt->dst0opt : NULL), - newopt, newtype, &p); - ipv6_renew_option(IPV6_RTHDR, - (struct ipv6_opt_hdr **)&opt2->srcrt, - (opt ? (struct ipv6_opt_hdr *)opt->srcrt : NULL), - newopt, newtype, &p); - ipv6_renew_option(IPV6_DSTOPTS, &opt2->dst1opt, - (opt ? opt->dst1opt : NULL), - newopt, newtype, &p); - - opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) + - (opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) + - (opt2->srcrt ? ipv6_optlen(opt2->srcrt) : 0); - opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0); - - return opt2; -} - -struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, - struct ipv6_txoptions *opt) -{ - /* - * ignore the dest before srcrt unless srcrt is being included. - * --yoshfuji - */ - if (opt && opt->dst0opt && !opt->srcrt) { - if (opt_space != opt) { - memcpy(opt_space, opt, sizeof(*opt_space)); - opt = opt_space; - } - opt->opt_nflen -= ipv6_optlen(opt->dst0opt); - opt->dst0opt = NULL; - } - - return opt; -} -EXPORT_SYMBOL_GPL(ipv6_fixup_options); - /** * fl6_update_dst - update flowi destination address with info given * by srcrt option, if any. diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c new file mode 100644 index 0000000..179861c --- /dev/null +++ b/net/ipv6/exthdrs_common.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* Extension header and TLV library code that is not specific to IPv6. */ +#include +#include + +struct ipv6_txoptions * +ipv6_dup_options(struct sock *sk, struct ipv6_txoptions *opt) +{ + struct ipv6_txoptions *opt2; + + opt2 = sock_kmalloc(sk, opt->tot_len, GFP_ATOMIC); + if (opt2) { + long dif = (char *)opt2 - (char *)opt; + + memcpy(opt2, opt, opt->tot_len); + if (opt2->hopopt) + *((char **)&opt2->hopopt) += dif; + if (opt2->dst0opt) + *((char **)&opt2->dst0opt) += dif; + if (opt2->dst1opt) + *((char **)&opt2->dst1opt) += dif; + if (opt2->srcrt) + *((char **)&opt2->srcrt) += dif; + refcount_set(&opt2->refcnt, 1); + } + return opt2; +} +EXPORT_SYMBOL_GPL(ipv6_dup_options); + +static void ipv6_renew_option(int renewtype, + struct ipv6_opt_hdr **dest, + struct ipv6_opt_hdr *old, + struct ipv6_opt_hdr *new, + int newtype, char **p) +{ + struct ipv6_opt_hdr *src; + + src = (renewtype == newtype ? new : old); + if (!src) + return; + + memcpy(*p, src, ipv6_optlen(src)); + *dest = (struct ipv6_opt_hdr *)*p; + *p += CMSG_ALIGN(ipv6_optlen(*dest)); +} + +/** + * ipv6_renew_options - replace a specific ext hdr with a new one. + * + * @sk: sock from which to allocate memory + * @opt: original options + * @newtype: option type to replace in @opt + * @newopt: new option of type @newtype to replace (user-mem) + * @newoptlen: length of @newopt + * + * Returns a new set of options which is a copy of @opt with the + * option type @newtype replaced with @newopt. + * + * @opt may be NULL, in which case a new set of options is returned + * containing just @newopt. + * + * @newopt may be NULL, in which case the specified option type is + * not copied into the new set of options. + * + * The new set of options is allocated from the socket option memory + * buffer of @sk. + */ +struct ipv6_txoptions * +ipv6_renew_options(struct sock *sk, struct ipv6_txoptions *opt, + int newtype, struct ipv6_opt_hdr *newopt) +{ + int tot_len = 0; + char *p; + struct ipv6_txoptions *opt2; + + if (opt) { + if (newtype != IPV6_HOPOPTS && opt->hopopt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->hopopt)); + if (newtype != IPV6_RTHDRDSTOPTS && opt->dst0opt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst0opt)); + if (newtype != IPV6_RTHDR && opt->srcrt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->srcrt)); + if (newtype != IPV6_DSTOPTS && opt->dst1opt) + tot_len += CMSG_ALIGN(ipv6_optlen(opt->dst1opt)); + } + + if (newopt) + tot_len += CMSG_ALIGN(ipv6_optlen(newopt)); + + if (!tot_len) + return NULL; + + tot_len += sizeof(*opt2); + opt2 = sock_kmalloc(sk, tot_len, GFP_ATOMIC); + if (!opt2) + return ERR_PTR(-ENOBUFS); + + memset(opt2, 0, tot_len); + refcount_set(&opt2->refcnt, 1); + opt2->tot_len = tot_len; + p = (char *)(opt2 + 1); + + ipv6_renew_option(IPV6_HOPOPTS, &opt2->hopopt, + (opt ? opt->hopopt : NULL), + newopt, newtype, &p); + ipv6_renew_option(IPV6_RTHDRDSTOPTS, &opt2->dst0opt, + (opt ? opt->dst0opt : NULL), + newopt, newtype, &p); + ipv6_renew_option(IPV6_RTHDR, + (struct ipv6_opt_hdr **)&opt2->srcrt, + (opt ? (struct ipv6_opt_hdr *)opt->srcrt : NULL), + newopt, newtype, &p); + ipv6_renew_option(IPV6_DSTOPTS, &opt2->dst1opt, + (opt ? opt->dst1opt : NULL), + newopt, newtype, &p); + + opt2->opt_nflen = (opt2->hopopt ? ipv6_optlen(opt2->hopopt) : 0) + + (opt2->dst0opt ? ipv6_optlen(opt2->dst0opt) : 0) + + (opt2->srcrt ? ipv6_optlen(opt2->srcrt) : 0); + opt2->opt_flen = (opt2->dst1opt ? ipv6_optlen(opt2->dst1opt) : 0); + + return opt2; +} +EXPORT_SYMBOL(ipv6_renew_options); + +struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, + struct ipv6_txoptions *opt) +{ + /* ignore the dest before srcrt unless srcrt is being included. + * --yoshfuji + */ + if (opt && opt->dst0opt && !opt->srcrt) { + if (opt_space != opt) { + memcpy(opt_space, opt, sizeof(*opt_space)); + opt = opt_space; + } + opt->opt_nflen -= ipv6_optlen(opt->dst0opt); + opt->dst0opt = NULL; + } + + return opt; +} +EXPORT_SYMBOL_GPL(ipv6_fixup_options); From patchwork Mon Apr 29 23:04:17 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092880 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="aUdiw84g"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxK1ZQ2z9s9T for ; Tue, 30 Apr 2019 09:04:53 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729697AbfD2XEw (ORCPT ); Mon, 29 Apr 2019 19:04:52 -0400 Received: from mail-io1-f68.google.com ([209.85.166.68]:41715 "EHLO mail-io1-f68.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729603AbfD2XEu (ORCPT ); Mon, 29 Apr 2019 19:04:50 -0400 Received: by mail-io1-f68.google.com with SMTP id r10so10520226ioc.8 for ; Mon, 29 Apr 2019 16:04:50 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=3ehiZ3qC4g9CrzHxpvZLq9Y09Ky2/tSRw0ikjA0r6WQ=; b=aUdiw84gOXkbAI6+HzqQthaDrbJxs6DPL+0v5+pYYR/SgaO1ZSZkauXnOIlwgoEnAp VYXWb2YWviUhbmTLQQXHSuCVL0qV/RuYH60+/Z2eO0LBG0TnVuoYDV5eoRKbLacd44Dl YX0nhsRoLjnhLuQDqnL0EflmGfcLHjXvRWi5gstEhlUKulW8OLBfCf/cNBYaBbY9JDZs rj5nVNfLbX+2PnsHuutRHPn/sawZ3t+49AzWdhhDWIhNjB2syZRTbTSB5mq+/Sp2AjsI cSRRKSpr6/IA9gzoFP7LRL0b1DEJENeetrqyakqMbmpNNGRhUpwENITegkTg/dRExwyH g5fg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=3ehiZ3qC4g9CrzHxpvZLq9Y09Ky2/tSRw0ikjA0r6WQ=; b=oVwUybnwHtuDbffW0udRnJf0CjSYcwPAge3g3/SCyinQOyaCoXFDMPPy9WJiBMh0oX cLL5bK7elJLj7H58Fp3KDDwcCU0A9WTdZaOjaXrbMc75IpV4B45befENZU3FV7akZIu/ o+lQ+fFJC+9glyANE8imwz9AXSuKmGYtmbNku3CVtVdli2KOd204BBAlxunC92ELhM+8 X+0m5e9954evqXGrf/zfP/5MnuPaojVqinfxj+UM2P6hpmG9NP18oKIhg8dqeBHJ1JZm aJN4MnqqGk/HFb5EthZVrC2hfoSBQ5Cq2VO3JejGgOP2hQWOHON1PNr4kJtlCaPq5HwW o0eA== X-Gm-Message-State: APjAAAVuaGKsLXKaUbyramN/RjQP0vdFPYAb174EAx7pOTWtPsYIWM1Q +Clono5d1a490P7KvJVLjkdveg== X-Google-Smtp-Source: APXvYqzgJVssrDVAQjVfY6ugONt2RWg+qYai+LznLM2o1fRe/l9EahDRRMr/TcB/hh17whqhaxRiuQ== X-Received: by 2002:a5d:84cc:: with SMTP id z12mr44588281ior.305.1556579089604; Mon, 29 Apr 2019 16:04:49 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.48 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:49 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 2/8] exthdrs: Registration of TLV handlers and parameters Date: Mon, 29 Apr 2019 16:04:17 -0700 Message-Id: <1556579063-1367-3-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Create a single TLV parameter table that holds meta information for IPv6 Hop-by-Hop and Destination TLVs. The data structure is composed of a 256 element array of u8's (one entry for each TLV type to allow O(1) lookup). Each entry provides an offset into an array of TLV proc data structures which follows the array of u8s. The TLV proc data structure contains parameters and handler functions for receiving and transmitting TLVs. The zeroth element in the TLV proc array provides default parameters for TLVs. A class attribute indicates the type of extension header in which the TLV may be used (e.g. Hop-by-Hop options, Destination options, or Destination options before the routing header). Functions are defined to manipulate entries in the TLV parameter table. * tlv_{set|unset}_proc set a TLV proc entry (ops and parameters) * tlv_{set|unset}_params set parameters only Receive TLV lookup and processing is modified to be a lookup in the TLV parameter table. An init table containing parameters for TLVs supported by the kernel is used to initialize the TLV table. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 60 +++++++++- include/uapi/linux/in6.h | 10 ++ net/ipv6/exthdrs.c | 52 +++++---- net/ipv6/exthdrs_common.c | 278 +++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/exthdrs_options.c | 62 ++++++---- 5 files changed, 415 insertions(+), 47 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index e36c2c1..bb667ed 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -386,13 +386,63 @@ struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, * and false, if it failed. * It MUST NOT touch skb->h. */ -struct tlvtype_proc { - int type; - bool (*func)(struct sk_buff *skb, int offset); +struct tlv_ops { + bool (*func)(unsigned int class, struct sk_buff *skb, int offset); }; -extern const struct tlvtype_proc tlvprocdestopt_lst[]; -extern const struct tlvtype_proc tlvprochopopt_lst[]; +struct tlv_params { + unsigned char rx_class : 3; +}; + +struct tlv_proc { + struct tlv_ops ops; + struct tlv_params params; +}; + +struct tlv_proc_init { + int type; + struct tlv_proc proc; +}; + +struct tlv_param_table_data { + unsigned char entries[256]; + unsigned char count; + struct rcu_head rcu; + struct tlv_proc procs[0]; +}; + +struct tlv_param_table { + struct tlv_param_table_data __rcu *data; +}; + +extern struct tlv_param_table ipv6_tlv_param_table; + +int tlv_set_proc(struct tlv_param_table *tlv_param_table, + unsigned char type, const struct tlv_proc *proc); +int tlv_unset_proc(struct tlv_param_table *tlv_param_table, unsigned char type); +int tlv_set_params(struct tlv_param_table *tlv_param_table, + unsigned char type, const struct tlv_params *params); +int tlv_unset_params(struct tlv_param_table *tlv_param_table, + unsigned char type); + +int exthdrs_init(struct tlv_param_table *tlv_param_table, + const struct tlv_proc_init *init_params, + int num_init_params); +void exthdrs_fini(struct tlv_param_table *tlv_param_table); + +int ipv6_exthdrs_options_init(void); +void ipv6_exthdrs_options_exit(void); + +/* tlv_get_proc assumes rcu_read_lock is held */ +static inline struct tlv_proc *tlv_get_proc( + struct tlv_param_table *tlv_param_table, + unsigned int type) +{ + struct tlv_param_table_data *tpt = + rcu_dereference(tlv_param_table->data); + + return &tpt->procs[tpt->entries[type]]; +} bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb, const struct inet6_skb_parm *opt); diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 9f2273a..8b9ac7f 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -297,4 +297,14 @@ struct in6_flowlabel_req { * ... * MRT6_MAX */ + +/* Flags for EH type that can use a TLV option */ +#define IPV6_TLV_CLASS_FLAG_HOPOPT BIT(0) +#define IPV6_TLV_CLASS_FLAG_RTRDSTOPT BIT(1) +#define IPV6_TLV_CLASS_FLAG_DSTOPT BIT(2) +#define IPV6_TLV_CLASS_MAX ((1 << 3) - 1) + +#define IPV6_TLV_CLASS_ANY_DSTOPT (IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \ + IPV6_TLV_CLASS_FLAG_DSTOPT) + #endif /* _UAPI_LINUX_IN6_H */ diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 6dbacf1..71a12a7 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -100,15 +100,14 @@ static bool ip6_tlvopt_unknown(struct sk_buff *skb, int optoff, /* Parse tlv encoded option header (hop-by-hop or destination) */ -static bool ip6_parse_tlv(const struct tlvtype_proc *procs, - struct sk_buff *skb, +static bool ip6_parse_tlv(unsigned int class, struct sk_buff *skb, int max_count) { int len = (skb_transport_header(skb)[1] + 1) << 3; const unsigned char *nh = skb_network_header(skb); int off = skb_network_header_len(skb); - const struct tlvtype_proc *curr; bool disallow_unknowns = false; + const struct tlv_proc *curr; int tlv_count = 0; int padlen = 0; @@ -117,12 +116,16 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, max_count = -max_count; } - if (skb_transport_offset(skb) + len > skb_headlen(skb)) - goto bad; + if (skb_transport_offset(skb) + len > skb_headlen(skb)) { + kfree_skb(skb); + return false; + } off += 2; len -= 2; + rcu_read_lock(); + while (len > 0) { int optlen = nh[off + 1] + 2; int i; @@ -162,19 +165,18 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, if (tlv_count > max_count) goto bad; - for (curr = procs; curr->type >= 0; curr++) { - if (curr->type == nh[off]) { - /* type specific length/alignment - checks will be performed in the - func(). */ - if (curr->func(skb, off) == false) - return false; - break; - } + curr = tlv_get_proc(&ipv6_tlv_param_table, nh[off]); + if ((curr->params.rx_class & class) && curr->ops.func) { + /* Handler will apply additional checks to + * the TLV + */ + if (!curr->ops.func(class, skb, off)) + goto bad_nofree; + } else if (!ip6_tlvopt_unknown(skb, off, + disallow_unknowns)) { + /* No appropriate handler, TLV is unknown */ + goto bad_nofree; } - if (curr->type < 0 && - !ip6_tlvopt_unknown(skb, off, disallow_unknowns)) - return false; padlen = 0; break; @@ -183,10 +185,14 @@ static bool ip6_parse_tlv(const struct tlvtype_proc *procs, len -= optlen; } - if (len == 0) + if (len == 0) { + rcu_read_unlock(); return true; + } bad: kfree_skb(skb); +bad_nofree: + rcu_read_unlock(); return false; } @@ -220,7 +226,7 @@ static int ipv6_destopt_rcv(struct sk_buff *skb) dstbuf = opt->dst1; #endif - if (ip6_parse_tlv(tlvprocdestopt_lst, skb, + if (ip6_parse_tlv(IPV6_TLV_CLASS_FLAG_DSTOPT, skb, init_net.ipv6.sysctl.max_dst_opts_cnt)) { skb->transport_header += extlen; opt = IP6CB(skb); @@ -602,8 +608,13 @@ int __init ipv6_exthdrs_init(void) if (ret) goto out_destopt; + ret = ipv6_exthdrs_options_init(); + if (ret) + goto out_nodata; out: return ret; +out_nodata: + inet6_del_protocol(&nodata_protocol, IPPROTO_NONE); out_destopt: inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); out_rthdr: @@ -613,6 +624,7 @@ int __init ipv6_exthdrs_init(void) void ipv6_exthdrs_exit(void) { + ipv6_exthdrs_options_exit(); inet6_del_protocol(&nodata_protocol, IPPROTO_NONE); inet6_del_protocol(&destopt_protocol, IPPROTO_DSTOPTS); inet6_del_protocol(&rthdr_protocol, IPPROTO_ROUTING); @@ -643,7 +655,7 @@ int ipv6_parse_hopopts(struct sk_buff *skb) goto fail_and_free; opt->flags |= IP6SKB_HOPBYHOP; - if (ip6_parse_tlv(tlvprochopopt_lst, skb, + if (ip6_parse_tlv(IPV6_TLV_CLASS_FLAG_HOPOPT, skb, init_net.ipv6.sysctl.max_hbh_opts_cnt)) { skb->transport_header += extlen; opt = IP6CB(skb); diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 179861c..0c0e32d 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -142,3 +142,281 @@ struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, return opt; } EXPORT_SYMBOL_GPL(ipv6_fixup_options); + +/* TLV parameter table functions and structures */ + +static void tlv_param_table_release(struct rcu_head *rcu) +{ + struct tlv_param_table_data *tpt = + container_of(rcu, struct tlv_param_table_data, rcu); + + kvfree(tpt); +} + +/* Default (unset) values for TLV parameters */ +static const struct tlv_proc tlv_default_proc = { +}; + +static size_t tlv_param_table_size(unsigned char count) +{ + return sizeof(struct tlv_param_table_data) + + (count * sizeof(struct tlv_proc)); +} + +static DEFINE_MUTEX(tlv_mutex); + +/* mutex held */ +static int __tlv_set_proc(struct tlv_param_table *tlv_param_table, + unsigned char type, const struct tlv_ops *ops, + const struct tlv_params *params) +{ + struct tlv_param_table_data *tpt, *old; + struct tlv_proc *tproc; + unsigned char count, pos; + + old = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + + if (old->entries[type]) { + /* Type is already set, modifying entry */ + pos = old->entries[type]; + count = old->count; + + /* If ops is not provided, take them from existing proc */ + if (!ops) + ops = &old->procs[pos].ops; + } else { + /* Type entry unset, need to create new entry */ + pos = old->count; + count = pos + 1; + } + + tpt = kvmalloc(tlv_param_table_size(count), GFP_KERNEL); + if (!tpt) + return -ENOMEM; + + memcpy(tpt, old, tlv_param_table_size(old->count)); + + tproc = &tpt->procs[pos]; + tproc->params = *params; + tproc->ops = ops ? *ops : tlv_default_proc.ops; + + tpt->entries[type] = pos; + tpt->count = count; + + rcu_assign_pointer(tlv_param_table->data, tpt); + + call_rcu(&old->rcu, tlv_param_table_release); + + return 0; +} + +/* mutex held */ +static int __tlv_unset_proc(struct tlv_param_table *tlv_param_table, + unsigned char type) +{ + struct tlv_param_table_data *tpt, *old; + unsigned char pos; + int i; + + old = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + + if (!old->entries[type]) { + /* Type entry already unset, nothing to do */ + return 0; + } + + tpt = kvmalloc(tlv_param_table_size(old->count - 1), GFP_KERNEL); + if (!tpt) + return -ENOMEM; + + pos = old->entries[type]; + + memcpy(tpt->procs, old->procs, pos * sizeof(struct tlv_proc)); + memcpy(&tpt->procs[pos], &old->procs[pos + 1], + (old->count - pos - 1) * sizeof(struct tlv_proc)); + + for (i = 0; i < 256; i++) { + if (old->entries[i] > pos) + tpt->entries[i] = old->entries[i] - 1; + else + tpt->entries[i] = old->entries[i]; + } + + /* Clear entry for type being unset (point to default params) */ + tpt->entries[type] = 0; + + tpt->count = old->count - 1; + + rcu_assign_pointer(tlv_param_table->data, tpt); + + call_rcu(&old->rcu, tlv_param_table_release); + + return 0; +} + +static void __tlv_destroy_param_table(struct tlv_param_table *tlv_param_table) +{ + struct tlv_param_table_data *tpt; + + mutex_lock(&tlv_mutex); + + tpt = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + if (tpt) { + rcu_assign_pointer(tlv_param_table->data, NULL); + call_rcu(&tpt->rcu, tlv_param_table_release); + } + + mutex_unlock(&tlv_mutex); +} + +int tlv_set_proc(struct tlv_param_table *tlv_param_table, unsigned char type, + const struct tlv_proc *proc) +{ + int ret; + + if (type < 2) + return -EINVAL; + + mutex_lock(&tlv_mutex); + ret = __tlv_set_proc(tlv_param_table, type, &proc->ops, + &proc->params); + mutex_unlock(&tlv_mutex); + + return ret; +} +EXPORT_SYMBOL(tlv_set_proc); + +int tlv_unset_proc(struct tlv_param_table *tlv_param_table, + unsigned char type) +{ + int ret; + + if (type < 2) + return -EINVAL; + + mutex_lock(&tlv_mutex); + ret = __tlv_unset_proc(tlv_param_table, type); + mutex_unlock(&tlv_mutex); + + return ret; +} +EXPORT_SYMBOL(tlv_unset_proc); + +int tlv_set_params(struct tlv_param_table *tlv_param_table, + unsigned char type, const struct tlv_params *params) +{ + int ret; + + if (type < 2) + return -EINVAL; + + mutex_lock(&tlv_mutex); + ret = __tlv_set_proc(tlv_param_table, type, NULL, params); + mutex_unlock(&tlv_mutex); + + return ret; +} +EXPORT_SYMBOL(tlv_set_params); + +/* tlv_internal_proc_type is used to check it the TLV proc was set + * internally. This is deduced by checking if any operations are + * defined. + */ +static bool tlv_internal_proc_type(struct tlv_proc *proc) +{ + return !!proc->ops.func; +} + +int tlv_unset_params(struct tlv_param_table *tlv_param_table, + unsigned char type) +{ + struct tlv_param_table_data *tpt; + struct tlv_proc *proc; + int entry, ret = 0; + + mutex_lock(&tlv_mutex); + + tpt = rcu_dereference_protected(tlv_param_table->data, + lockdep_is_held(&tlv_mutex)); + if (!tpt) + goto out; + + entry = tpt->entries[type]; + if (!entry) { + /* Entry is already unset */ + goto out; + } + + proc = &tpt->procs[entry]; + + if (tlv_internal_proc_type(proc)) { + /* TLV was set by internal source, so maintain + * the non-parameter fields (i.e. the operations). + */ + ret = __tlv_set_proc(tlv_param_table, type, &proc->ops, + &tlv_default_proc.params); + } else { + ret = __tlv_unset_proc(tlv_param_table, type); + } + +out: + mutex_unlock(&tlv_mutex); + + return ret; +} +EXPORT_SYMBOL(tlv_unset_params); + +int exthdrs_init(struct tlv_param_table *tlv_param_table, + const struct tlv_proc_init *tlv_init_params, + int num_init_params) +{ + struct tlv_param_table_data *tpt; + size_t tsize; + int i; + + tsize = tlv_param_table_size(num_init_params + 1); + + tpt = kvmalloc(tsize, GFP_KERNEL); + if (!tpt) + return -ENOMEM; + + memset(tpt, 0, tsize); + + /* Zeroth TLV proc entry is default */ + tpt->procs[0] = tlv_default_proc; + + for (i = 0; i < num_init_params; i++) { + const struct tlv_proc_init *tpi = &tlv_init_params[i]; + struct tlv_proc *tp = &tpt->procs[i + 1]; + + if (WARN_ON(tpi->type < 2)) { + /* Padding TLV initialized? */ + kvfree(tpt); + return -EINVAL; + } + if (WARN_ON(tpt->entries[tpi->type])) { + /* TLV type already set */ + kvfree(tpt); + return -EINVAL; + } + + *tp = tpi->proc; + tpt->entries[tpi->type] = i + 1; + } + + tpt->count = num_init_params + 1; + + RCU_INIT_POINTER(tlv_param_table->data, tpt); + + return 0; +} +EXPORT_SYMBOL(exthdrs_init); + +void exthdrs_fini(struct tlv_param_table *tlv_param_table) +{ + __tlv_destroy_param_table(tlv_param_table); +} +EXPORT_SYMBOL(exthdrs_fini); diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index 032e072..eb3ae2a 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -15,7 +15,7 @@ /* Destination options header */ #if IS_ENABLED(CONFIG_IPV6_MIP6) -static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) +static bool ipv6_dest_hao(unsigned int class, struct sk_buff *skb, int optoff) { struct ipv6_destopt_hao *hao; struct inet6_skb_parm *opt = IP6CB(skb); @@ -74,16 +74,6 @@ static bool ipv6_dest_hao(struct sk_buff *skb, int optoff) } #endif -const struct tlvtype_proc tlvprocdestopt_lst[] = { -#if IS_ENABLED(CONFIG_IPV6_MIP6) - { - .type = IPV6_TLV_HAO, - .func = ipv6_dest_hao, - }, -#endif - {-1, NULL} -}; - /* Hop-by-hop options */ /* Note: we cannot rely on skb_dst(skb) before we assign it in @@ -102,7 +92,7 @@ static inline struct net *ipv6_skb_net(struct sk_buff *skb) /* Router Alert as of RFC 2711 */ -static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) +static bool ipv6_hop_ra(unsigned int class, struct sk_buff *skb, int optoff) { const unsigned char *nh = skb_network_header(skb); @@ -120,7 +110,7 @@ static bool ipv6_hop_ra(struct sk_buff *skb, int optoff) /* Jumbo payload */ -static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) +static bool ipv6_hop_jumbo(unsigned int class, struct sk_buff *skb, int optoff) { const unsigned char *nh = skb_network_header(skb); struct inet6_dev *idev = __in6_dev_get_safely(skb->dev); @@ -164,7 +154,8 @@ static bool ipv6_hop_jumbo(struct sk_buff *skb, int optoff) /* CALIPSO RFC 5570 */ -static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) +static bool ipv6_hop_calipso(unsigned int class, struct sk_buff *skb, + int optoff) { const unsigned char *nh = skb_network_header(skb); @@ -184,18 +175,45 @@ static bool ipv6_hop_calipso(struct sk_buff *skb, int optoff) return false; } -const struct tlvtype_proc tlvprochopopt_lst[] = { +static const struct tlv_proc_init tlv_init_params[] __initconst = { +#if IS_ENABLED(CONFIG_IPV6_MIP6) { - .type = IPV6_TLV_ROUTERALERT, - .func = ipv6_hop_ra, + .type = IPV6_TLV_HAO, + + .proc.ops.func = ipv6_dest_hao, + .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_DSTOPT, }, +#endif { - .type = IPV6_TLV_JUMBO, - .func = ipv6_hop_jumbo, + .type = IPV6_TLV_ROUTERALERT, + + .proc.ops.func = ipv6_hop_ra, + .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT, }, { - .type = IPV6_TLV_CALIPSO, - .func = ipv6_hop_calipso, + .type = IPV6_TLV_JUMBO, + + .proc.ops.func = ipv6_hop_jumbo, + .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT, + }, + { + .type = IPV6_TLV_CALIPSO, + + .proc.ops.func = ipv6_hop_calipso, + .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT, }, - { -1, } }; + +struct tlv_param_table __rcu ipv6_tlv_param_table; +EXPORT_SYMBOL(ipv6_tlv_param_table); + +int __init ipv6_exthdrs_options_init(void) +{ + return exthdrs_init(&ipv6_tlv_param_table, tlv_init_params, + ARRAY_SIZE(tlv_init_params)); +} + +void ipv6_exthdrs_options_exit(void) +{ + exthdrs_fini(&ipv6_tlv_param_table); +} From patchwork Mon Apr 29 23:04:18 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092881 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="uaK1C2v3"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxL35RKz9sB8 for ; Tue, 30 Apr 2019 09:04:54 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729703AbfD2XEx (ORCPT ); Mon, 29 Apr 2019 19:04:53 -0400 Received: from mail-io1-f66.google.com ([209.85.166.66]:34892 "EHLO mail-io1-f66.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729684AbfD2XEw (ORCPT ); Mon, 29 Apr 2019 19:04:52 -0400 Received: by mail-io1-f66.google.com with SMTP id r18so10558139ioh.2 for ; Mon, 29 Apr 2019 16:04:51 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=iZhAzzypnr5pSmhBAHysbTdoqXf903caMqUB/gjm1Y8=; b=uaK1C2v3OZgRylgPHmBEacmIjZswebR/mVNya6jFFs1DfZv1mKa6eTmV5dX/RVRQUf FyHzAselME3TB0oGds+3rnfe+5CVyRl94RjotRea5tj7CyzuyCJitaHPzURSuDCHH+nb 990vLPAsY/YT3uP0dFaUMETByaDXqI1c/waPhV9V/8MklwzGRroI1f8YhWM6oD47R3ho PP5QDo1aW9ewNgzuL2sCAryAFtCNwWcuoorSbsMAggy6FmmDnF4LwotcxAboF8MJtqnE 7nyGOyhF65O5gnyiV/q3ZOVi6I24V1yLA//mjEDhYEJzCHKDhGbGs9BOzHg2y/+g36LV 36yw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=iZhAzzypnr5pSmhBAHysbTdoqXf903caMqUB/gjm1Y8=; b=VKlnmuBqH6TETkhZldaT0p6qkIIHX6ywI4Ob+yM6clFZy36qd2c1dFVqSh95uyi2yI e3Fy3oxacNpLGbW8SCe9P+wG/RiOO4K31txGR9wwD3dFBLuszM47pl/D6Gaa/zZVO5Gd mV8MZg4BDXpuBnZuARGWgGax0nCvUp2wA9+4WL5+/OjDq6N4Fs78HJBcCnrRHGgU0Wit 1jWZXIyzMbvyQ0AYHdw5ibiGBGZM83nE9lLnBFH8eHWI7VNrkHv09ZvsI4R5HUxtsmtz OL4HT+wjZCoiU4cmcK52+aPap0rvMo+t9ePjcZg3Z7FJGnr7sp8BunXlJdk/xp71pBKk XBDA== X-Gm-Message-State: APjAAAXvxuIzeLkTLwaIDCy5m/S/leM1v/frv9aklez/tHHlU1dX2sHP 7T03wKzLGxvEk9keXGpCbVnrdl2kCwQ= X-Google-Smtp-Source: APXvYqxUlXfvKYYLNxP+ipph/m2qeGksmd4msHRlOaJPacb61YqH5V51jfXq7RYnYGrhHLulDMFOVQ== X-Received: by 2002:a6b:c842:: with SMTP id y63mr211372iof.304.1556579091271; Mon, 29 Apr 2019 16:04:51 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.50 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:50 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 3/8] exthdrs: Add TX parameters Date: Mon, 29 Apr 2019 16:04:18 -0700 Message-Id: <1556579063-1367-4-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Define a number of transmit parameters for TLV Parameter table definitions. These will be used for validating TLVs that are set on a socket. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 26 ++++++++++++++++++++- include/uapi/linux/in6.h | 8 +++++++ net/ipv6/exthdrs.c | 2 +- net/ipv6/exthdrs_common.c | 17 ++++++++++++++ net/ipv6/exthdrs_options.c | 57 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 104 insertions(+), 6 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index bb667ed..4cebc48 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -390,8 +390,26 @@ struct tlv_ops { bool (*func)(unsigned int class, struct sk_buff *skb, int offset); }; +struct tlv_rx_params { + unsigned char class : 3; +}; + +struct tlv_tx_params { + unsigned char admin_perm : 2; + unsigned char user_perm : 2; + unsigned char class : 3; + unsigned char align_mult : 4; + unsigned char align_off : 4; + unsigned char data_len_mult : 4; + unsigned char data_len_off : 4; + unsigned char min_data_len; + unsigned char max_data_len; + unsigned char preferred_order; +}; + struct tlv_params { - unsigned char rx_class : 3; + struct tlv_rx_params r; + struct tlv_tx_params t; }; struct tlv_proc { @@ -417,6 +435,12 @@ struct tlv_param_table { extern struct tlv_param_table ipv6_tlv_param_table; +/* Preferred TLV ordering (placed by increasing order) */ +#define TLV_PREF_ORDER_HAO 10 +#define TLV_PREF_ORDER_ROUTERALERT 20 +#define TLV_PREF_ORDER_JUMBO 30 +#define TLV_PREF_ORDER_CALIPSO 40 + int tlv_set_proc(struct tlv_param_table *tlv_param_table, unsigned char type, const struct tlv_proc *proc); int tlv_unset_proc(struct tlv_param_table *tlv_param_table, unsigned char type); diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 8b9ac7f..6a99ee1 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -307,4 +307,12 @@ struct in6_flowlabel_req { #define IPV6_TLV_CLASS_ANY_DSTOPT (IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \ IPV6_TLV_CLASS_FLAG_DSTOPT) +/* TLV permissions values */ +enum { + IPV6_TLV_PERM_NONE, + IPV6_TLV_PERM_WITH_CHECK, + IPV6_TLV_PERM_NO_CHECK, + IPV6_TLV_PERM_MAX = IPV6_TLV_PERM_NO_CHECK +}; + #endif /* _UAPI_LINUX_IN6_H */ diff --git a/net/ipv6/exthdrs.c b/net/ipv6/exthdrs.c index 71a12a7..92a777f 100644 --- a/net/ipv6/exthdrs.c +++ b/net/ipv6/exthdrs.c @@ -166,7 +166,7 @@ static bool ip6_parse_tlv(unsigned int class, struct sk_buff *skb, goto bad; curr = tlv_get_proc(&ipv6_tlv_param_table, nh[off]); - if ((curr->params.rx_class & class) && curr->ops.func) { + if ((curr->params.r.class & class) && curr->ops.func) { /* Handler will apply additional checks to * the TLV */ diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 0c0e32d..cda4fb8 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -155,6 +155,14 @@ static void tlv_param_table_release(struct rcu_head *rcu) /* Default (unset) values for TLV parameters */ static const struct tlv_proc tlv_default_proc = { + .params.t = { + .preferred_order = 0, + .admin_perm = IPV6_TLV_PERM_NO_CHECK, + .user_perm = IPV6_TLV_PERM_NONE, + .align_mult = (4 - 1), /* Default alignment: 4n + 2 */ + .align_off = 2, + .max_data_len = 255, + }, }; static size_t tlv_param_table_size(unsigned char count) @@ -373,10 +381,13 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table, const struct tlv_proc_init *tlv_init_params, int num_init_params) { + unsigned long check_map[BITS_TO_LONGS(256)]; struct tlv_param_table_data *tpt; size_t tsize; int i; + memset(check_map, 0, sizeof(check_map)); + tsize = tlv_param_table_size(num_init_params + 1); tpt = kvmalloc(tsize, GFP_KERNEL); @@ -390,6 +401,7 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table, for (i = 0; i < num_init_params; i++) { const struct tlv_proc_init *tpi = &tlv_init_params[i]; + unsigned int order = tpi->proc.params.t.preferred_order; struct tlv_proc *tp = &tpt->procs[i + 1]; if (WARN_ON(tpi->type < 2)) { @@ -403,6 +415,11 @@ int exthdrs_init(struct tlv_param_table *tlv_param_table, return -EINVAL; } + if (order) { + WARN_ON(test_bit(order, check_map)); + set_bit(order, check_map); + } + *tp = tpi->proc; tpt->entries[tpi->type] = i + 1; } diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index eb3ae2a..7251229 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -181,26 +181,75 @@ static const struct tlv_proc_init tlv_init_params[] __initconst = { .type = IPV6_TLV_HAO, .proc.ops.func = ipv6_dest_hao, - .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_DSTOPT, + + .proc.params.r.class = IPV6_TLV_CLASS_FLAG_DSTOPT, + + .proc.params.t = { + .preferred_order = TLV_PREF_ORDER_HAO, + .admin_perm = IPV6_TLV_PERM_NO_CHECK, + .user_perm = IPV6_TLV_PERM_NONE, + .class = IPV6_TLV_CLASS_FLAG_DSTOPT, + .align_mult = (8 - 1), /* Align to 8n + 6 */ + .align_off = 6, + .min_data_len = 16, + .max_data_len = 16, + }, }, #endif { .type = IPV6_TLV_ROUTERALERT, .proc.ops.func = ipv6_hop_ra, - .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.t = { + .preferred_order = TLV_PREF_ORDER_ROUTERALERT, + .admin_perm = IPV6_TLV_PERM_NO_CHECK, + .user_perm = IPV6_TLV_PERM_NONE, + .class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .align_mult = (2 - 1), /* Align to 2n */ + .min_data_len = 2, + .max_data_len = 2, + }, + }, { .type = IPV6_TLV_JUMBO, .proc.ops.func = ipv6_hop_jumbo, - .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.t = { + .preferred_order = TLV_PREF_ORDER_JUMBO, + .admin_perm = IPV6_TLV_PERM_NO_CHECK, + .user_perm = IPV6_TLV_PERM_NONE, + .class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .align_mult = (4 - 1), /* Align to 4n + 2 */ + .align_off = 2, + .min_data_len = 4, + .max_data_len = 4, + }, }, { .type = IPV6_TLV_CALIPSO, .proc.ops.func = ipv6_hop_calipso, - .proc.params.rx_class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .proc.params.r.class = IPV6_TLV_CLASS_FLAG_HOPOPT, + + .proc.params.t = { + .preferred_order = TLV_PREF_ORDER_CALIPSO, + .admin_perm = IPV6_TLV_PERM_NO_CHECK, + .user_perm = IPV6_TLV_PERM_NONE, + .class = IPV6_TLV_CLASS_FLAG_HOPOPT, + .align_mult = (4 - 1), /* Align to 4n + 2 */ + .align_off = 2, + .min_data_len = 8, + .max_data_len = 252, + .data_len_mult = (4 - 1), + /* Length is multiple of 4 */ + }, }, }; From patchwork Mon Apr 29 23:04:19 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092882 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="x66hieOE"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxP67N9z9s9T for ; Tue, 30 Apr 2019 09:04:57 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729716AbfD2XE4 (ORCPT ); Mon, 29 Apr 2019 19:04:56 -0400 Received: from mail-it1-f195.google.com ([209.85.166.195]:35265 "EHLO mail-it1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729684AbfD2XEy (ORCPT ); Mon, 29 Apr 2019 19:04:54 -0400 Received: by mail-it1-f195.google.com with SMTP id l140so531113itb.0 for ; Mon, 29 Apr 2019 16:04:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=AxTcOyxCLDXLcQPHmYYw+ip5DSKArA/HlEpiDjTnmKQ=; b=x66hieOEr7tWlyX5rdx5RhYgNiHR4B7cwiSf9d51BFOOG9fLvUtBQYRnQYKaQteKOb kTBNzcJvmytW6exZS1JnzB4XZfv3QsD4+wN/srK4I+6xhmZY2C5khCqRhuao1LtkehPG aIj6h4wCfck+M6Iu/GDthJGxWDPlE/NdWocCvyQZgkKpdxRxMmYuN8c5NgfgoLTgVKhg 0cmmgbSr5cGsVFivpTmqFH70Pnhyew3OVX7ztP7jimmTfTPJJgc6xOHDwQmqet5Olclw GbFwcEqngKdVJlxO1spfvpaSUWFy+Fd6wX+UtRctOTjaC0rkeQheY/rVLRfaozpANMM/ xQBQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=AxTcOyxCLDXLcQPHmYYw+ip5DSKArA/HlEpiDjTnmKQ=; b=cOM3FQqI1rzzFL+eqwUzWySxbQh3tt5uRoEoleTeqOnBqYldNlfOkJRgVJQfDpRSzm Eyh+zsVF9ULxz+ZDy9PwRFn74YhdO1WHGjaaWmsj59b59LiZDe1hfN9kCLlkssmAbofj ICDLNdLeyP+Yl7hGkqaG3wV8Gm3opuNJtnA3jWfYRq4HQt7W1sdISH7gHuqBlNowuaLO ggqvD1gAMF7EZ026UxyGgeLWY9M+F1ceH1U4P53zP216LxVm35gGzM1bQuO0687WEge8 IJlXlourFHv6NF8KdILqQjDCNLTz8/+ue2CQilGYYmGInF7qMEP7Tjrl1CAwkbEitoUx gscg== X-Gm-Message-State: APjAAAWINf7CH9Xu7HvYMkrQOMOTNsnodDTFya38GxgdItREZ7+PZ2j8 xtZSZYjNhMFki44AsUgVuidhd3bCV3U= X-Google-Smtp-Source: APXvYqyOHtDt86znsgJ/t8DlYPzejW64fKeUpdomcj9qq6t84kTLgi7/fcLPZqTtMrcnrc/kLsXqUQ== X-Received: by 2002:a24:3201:: with SMTP id j1mr1393747ita.107.1556579092992; Mon, 29 Apr 2019 16:04:52 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.52 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:52 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 4/8] ip6tlvs: Add netlink interface Date: Mon, 29 Apr 2019 16:04:19 -0700 Message-Id: <1556579063-1367-5-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Add a netlink interface to manage the TX TLV parameters. Managed parameters include those for validating and sending TLVs being sent such as alignment, TLV ordering, length limits, etc. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 18 +++ include/uapi/linux/in6.h | 31 +++++ net/ipv6/exthdrs_common.c | 278 +++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/exthdrs_options.c | 81 ++++++++++++- 4 files changed, 406 insertions(+), 2 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 4cebc48..edf718f 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -449,6 +450,23 @@ int tlv_set_params(struct tlv_param_table *tlv_param_table, int tlv_unset_params(struct tlv_param_table *tlv_param_table, unsigned char type); +extern const struct nla_policy tlv_nl_policy[]; + +int tlv_nl_cmd_set(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info); +int tlv_nl_cmd_unset(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info); +int tlv_nl_cmd_get(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info); +int tlv_fill_info(struct tlv_param_table *tlv_param_table, + int tlv_type, struct sk_buff *msg, bool admin); +int tlv_nl_dump(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct netlink_callback *cb); + int exthdrs_init(struct tlv_param_table *tlv_param_table, const struct tlv_proc_init *init_params, int num_init_params); diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 6a99ee1..1c79361 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -306,6 +306,37 @@ struct in6_flowlabel_req { #define IPV6_TLV_CLASS_ANY_DSTOPT (IPV6_TLV_CLASS_FLAG_RTRDSTOPT | \ IPV6_TLV_CLASS_FLAG_DSTOPT) +/* NETLINK_GENERIC related info for IPv6 TLVs */ + +#define IPV6_TLV_GENL_NAME "ipv6-tlv" +#define IPV6_TLV_GENL_VERSION 0x1 + +enum { + IPV6_TLV_ATTR_UNSPEC, + IPV6_TLV_ATTR_TYPE, /* u8, > 1 */ + IPV6_TLV_ATTR_ORDER, /* u8 */ + IPV6_TLV_ATTR_ADMIN_PERM, /* u8, perm value */ + IPV6_TLV_ATTR_USER_PERM, /* u8, perm value */ + IPV6_TLV_ATTR_CLASS, /* u8, 3 bit flags */ + IPV6_TLV_ATTR_ALIGN_MULT, /* u8, 1 to 16 */ + IPV6_TLV_ATTR_ALIGN_OFF, /* u8, 0 to 15 */ + IPV6_TLV_ATTR_MIN_DATA_LEN, /* u8 (option data length) */ + IPV6_TLV_ATTR_MAX_DATA_LEN, /* u8 (option data length) */ + IPV6_TLV_ATTR_DATA_LEN_MULT, /* u8, 1 to 16 */ + IPV6_TLV_ATTR_DATA_LEN_OFF, /* u8, 0 to 15 */ + + __IPV6_TLV_ATTR_MAX, +}; + +#define IPV6_TLV_ATTR_MAX (__IPV6_TLV_ATTR_MAX - 1) + +enum { + IPV6_TLV_CMD_SET, + IPV6_TLV_CMD_UNSET, + IPV6_TLV_CMD_GET, + + __IPV6_TLV_CMD_MAX, +}; /* TLV permissions values */ enum { diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index cda4fb8..cfc9427 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -377,6 +377,284 @@ int tlv_unset_params(struct tlv_param_table *tlv_param_table, } EXPORT_SYMBOL(tlv_unset_params); +const struct nla_policy tlv_nl_policy[IPV6_TLV_ATTR_MAX + 1] = { + [IPV6_TLV_ATTR_TYPE] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_ORDER] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_ADMIN_PERM] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_USER_PERM] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_CLASS] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_ALIGN_MULT] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_ALIGN_OFF] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_MIN_DATA_LEN] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_MAX_DATA_LEN] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_DATA_LEN_OFF] = { .type = NLA_U8, }, + [IPV6_TLV_ATTR_DATA_LEN_MULT] = { .type = NLA_U8, }, +}; +EXPORT_SYMBOL(tlv_nl_policy); + +int tlv_nl_cmd_set(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info) +{ + struct tlv_params new_params; + struct tlv_tx_params *tptx; + struct tlv_proc *tproc; + int retv = -EINVAL, i; + u8 tlv_type, v; + + if (!info->attrs[IPV6_TLV_ATTR_TYPE]) + return -EINVAL; + + tlv_type = nla_get_u8(info->attrs[IPV6_TLV_ATTR_TYPE]); + if (tlv_type < 2) + return -EINVAL; + + rcu_read_lock(); + + /* Base new parameters on existing ones */ + tproc = tlv_get_proc(tlv_param_table, tlv_type); + new_params = tproc->params; + + if (info->attrs[IPV6_TLV_ATTR_ORDER]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ORDER]); + if (v) { + for (i = 2; i < 256; i++) { + tproc = tlv_get_proc(tlv_param_table, i); + tptx = &tproc->params.t; + + /* Preferred orders must be unique */ + if (tptx->preferred_order == v && + i != tlv_type) { + retv = -EALREADY; + goto out; + } + } + new_params.t.preferred_order = v; + } + } + + if (!new_params.t.preferred_order) { + unsigned long check_map[BITS_TO_LONGS(255)]; + int pos; + + /* Preferred order not specified, automatically set one. + * This is chosen to be the first value after the greatest + * order in use. + */ + memset(check_map, 0, sizeof(check_map)); + + for (i = 2; i < 256; i++) { + unsigned int order; + + tproc = tlv_get_proc(tlv_param_table, i); + tptx = &tproc->params.t; + order = tptx->preferred_order; + + if (!order) + continue; + + WARN_ON(test_bit(255 - order, check_map)); + set_bit(255 - order, check_map); + } + + pos = find_first_bit(check_map, 255); + if (pos) + new_params.t.preferred_order = 255 - (pos - 1); + else + new_params.t.preferred_order = 255 - + find_first_zero_bit(check_map, sizeof(check_map)); + } + + if (info->attrs[IPV6_TLV_ATTR_ADMIN_PERM]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ADMIN_PERM]); + if (v > IPV6_TLV_PERM_MAX) + goto out; + new_params.t.admin_perm = v; + } + + if (info->attrs[IPV6_TLV_ATTR_USER_PERM]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_USER_PERM]); + if (v > IPV6_TLV_PERM_MAX) + goto out; + new_params.t.user_perm = v; + } + + if (info->attrs[IPV6_TLV_ATTR_CLASS]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_CLASS]); + if (v > IPV6_TLV_CLASS_MAX) + goto out; + new_params.t.class = v; + } + + if (info->attrs[IPV6_TLV_ATTR_ALIGN_MULT]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ALIGN_MULT]); + if (v > 16 || v < 1) + goto out; + new_params.t.align_mult = v - 1; + } + + if (info->attrs[IPV6_TLV_ATTR_ALIGN_OFF]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_ALIGN_OFF]); + if (v > 15) + goto out; + new_params.t.align_off = v; + } + + if (info->attrs[IPV6_TLV_ATTR_MAX_DATA_LEN]) + new_params.t.max_data_len = + nla_get_u8(info->attrs[IPV6_TLV_ATTR_MAX_DATA_LEN]); + + if (info->attrs[IPV6_TLV_ATTR_MIN_DATA_LEN]) + new_params.t.min_data_len = + nla_get_u8(info->attrs[IPV6_TLV_ATTR_MIN_DATA_LEN]); + + if (info->attrs[IPV6_TLV_ATTR_DATA_LEN_MULT]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_DATA_LEN_MULT]); + if (v > 16 || v < 1) + goto out; + new_params.t.data_len_mult = v - 1; + } + + if (info->attrs[IPV6_TLV_ATTR_DATA_LEN_OFF]) { + v = nla_get_u8(info->attrs[IPV6_TLV_ATTR_DATA_LEN_OFF]); + if (v > 15) + goto out; + new_params.t.data_len_off = v; + } + + retv = tlv_set_params(tlv_param_table, tlv_type, &new_params); + +out: + rcu_read_unlock(); + return retv; +} +EXPORT_SYMBOL(tlv_nl_cmd_set); + +int tlv_nl_cmd_unset(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info) +{ + unsigned int tlv_type; + + if (!info->attrs[IPV6_TLV_ATTR_TYPE]) + return -EINVAL; + + tlv_type = nla_get_u8(info->attrs[IPV6_TLV_ATTR_TYPE]); + if (tlv_type < 2) + return -EINVAL; + + return tlv_unset_params(tlv_param_table, tlv_type); +} +EXPORT_SYMBOL(tlv_nl_cmd_unset); + +int tlv_fill_info(struct tlv_param_table *tlv_param_table, + int tlv_type, struct sk_buff *msg, bool admin) +{ + struct tlv_proc *tproc; + struct tlv_params *tp; + int ret = 0; + + rcu_read_lock(); + + tproc = tlv_get_proc(tlv_param_table, tlv_type); + tp = &tproc->params; + + if (nla_put_u8(msg, IPV6_TLV_ATTR_TYPE, tlv_type) || + nla_put_u8(msg, IPV6_TLV_ATTR_ORDER, tp->t.preferred_order) || + nla_put_u8(msg, IPV6_TLV_ATTR_USER_PERM, tp->t.user_perm) || + (admin && nla_put_u8(msg, IPV6_TLV_ATTR_ADMIN_PERM, + tp->t.admin_perm)) || + nla_put_u8(msg, IPV6_TLV_ATTR_CLASS, tp->t.class) || + nla_put_u8(msg, IPV6_TLV_ATTR_ALIGN_MULT, tp->t.align_mult + 1) || + nla_put_u8(msg, IPV6_TLV_ATTR_ALIGN_OFF, tp->t.align_off) || + nla_put_u8(msg, IPV6_TLV_ATTR_MIN_DATA_LEN, tp->t.min_data_len) || + nla_put_u8(msg, IPV6_TLV_ATTR_MAX_DATA_LEN, tp->t.max_data_len) || + nla_put_u8(msg, IPV6_TLV_ATTR_DATA_LEN_MULT, + tp->t.data_len_mult + 1) || + nla_put_u8(msg, IPV6_TLV_ATTR_DATA_LEN_OFF, tp->t.data_len_off)) + ret = -1; + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(tlv_fill_info); + +static int tlv_dump_info(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + int tlv_type, u32 portid, u32 seq, u32 flags, + struct sk_buff *skb, u8 cmd, bool admin) +{ + void *hdr; + + hdr = genlmsg_put(skb, portid, seq, tlv_nl_family, flags, cmd); + if (!hdr) + return -ENOMEM; + + if (tlv_fill_info(tlv_param_table, tlv_type, skb, admin) < 0) { + genlmsg_cancel(skb, hdr); + return -EMSGSIZE; + } + + genlmsg_end(skb, hdr); + + return 0; +} + +int tlv_nl_cmd_get(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct genl_info *info) +{ + struct sk_buff *msg; + int ret, tlv_type; + + if (!info->attrs[IPV6_TLV_ATTR_TYPE]) + return -EINVAL; + + tlv_type = nla_get_u8(info->attrs[IPV6_TLV_ATTR_TYPE]); + if (tlv_type < 2) + return -EINVAL; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + ret = tlv_dump_info(tlv_param_table, tlv_nl_family, + tlv_type, info->snd_portid, info->snd_seq, 0, msg, + info->genlhdr->cmd, + netlink_capable(skb, CAP_NET_ADMIN)); + if (ret < 0) { + nlmsg_free(msg); + return ret; + } + + return genlmsg_reply(msg, info); +} +EXPORT_SYMBOL(tlv_nl_cmd_get); + +int tlv_nl_dump(struct tlv_param_table *tlv_param_table, + struct genl_family *tlv_nl_family, + struct sk_buff *skb, struct netlink_callback *cb) +{ + int idx = 0, ret, i; + + for (i = 2; i < 256; i++) { + if (idx++ < cb->args[0]) + continue; + ret = tlv_dump_info(tlv_param_table, tlv_nl_family, i, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, NLM_F_MULTI, + skb, IPV6_TLV_CMD_GET, + netlink_capable(cb->skb, CAP_NET_ADMIN)); + if (ret) + break; + } + + cb->args[0] = idx; + return skb->len; +} +EXPORT_SYMBOL(tlv_nl_dump); + int exthdrs_init(struct tlv_param_table *tlv_param_table, const struct tlv_proc_init *tlv_init_params, int num_init_params) diff --git a/net/ipv6/exthdrs_options.c b/net/ipv6/exthdrs_options.c index 7251229..341f93d 100644 --- a/net/ipv6/exthdrs_options.c +++ b/net/ipv6/exthdrs_options.c @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #if IS_ENABLED(CONFIG_IPV6_MIP6) @@ -256,13 +257,89 @@ static const struct tlv_proc_init tlv_init_params[] __initconst = { struct tlv_param_table __rcu ipv6_tlv_param_table; EXPORT_SYMBOL(ipv6_tlv_param_table); +static int ipv6_tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info); +static int ipv6_tlv_nl_cmd_unset(struct sk_buff *skb, struct genl_info *info); +static int ipv6_tlv_nl_cmd_get(struct sk_buff *skb, struct genl_info *info); +static int ipv6_tlv_nl_dump(struct sk_buff *skb, struct netlink_callback *cb); + +static const struct genl_ops ipv6_tlv_nl_ops[] = { +{ + .cmd = IPV6_TLV_CMD_SET, + .doit = ipv6_tlv_nl_cmd_set, + .flags = GENL_ADMIN_PERM, +}, +{ + .cmd = IPV6_TLV_CMD_UNSET, + .doit = ipv6_tlv_nl_cmd_unset, + .flags = GENL_ADMIN_PERM, +}, +{ + .cmd = IPV6_TLV_CMD_GET, + .doit = ipv6_tlv_nl_cmd_get, + .dumpit = ipv6_tlv_nl_dump, +}, +}; + +struct genl_family ipv6_tlv_nl_family __ro_after_init = { + .hdrsize = 0, + .name = IPV6_TLV_GENL_NAME, + .version = IPV6_TLV_GENL_VERSION, + .maxattr = IPV6_TLV_ATTR_MAX, + .policy = tlv_nl_policy, + .netnsok = true, + .parallel_ops = true, + .ops = ipv6_tlv_nl_ops, + .n_ops = ARRAY_SIZE(ipv6_tlv_nl_ops), + .module = THIS_MODULE, +}; + +static int ipv6_tlv_nl_cmd_set(struct sk_buff *skb, struct genl_info *info) +{ + return tlv_nl_cmd_set(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, info); +} + +static int ipv6_tlv_nl_cmd_unset(struct sk_buff *skb, struct genl_info *info) +{ + return tlv_nl_cmd_unset(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, info); +} + +static int ipv6_tlv_nl_cmd_get(struct sk_buff *skb, struct genl_info *info) +{ + return tlv_nl_cmd_get(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, info); +} + +static int ipv6_tlv_nl_dump(struct sk_buff *skb, struct netlink_callback *cb) +{ + return tlv_nl_dump(&ipv6_tlv_param_table, &ipv6_tlv_nl_family, + skb, cb); +} + int __init ipv6_exthdrs_options_init(void) { - return exthdrs_init(&ipv6_tlv_param_table, tlv_init_params, - ARRAY_SIZE(tlv_init_params)); + int err; + + err = genl_register_family(&ipv6_tlv_nl_family); + if (err) + goto out; + + err = exthdrs_init(&ipv6_tlv_param_table, tlv_init_params, + ARRAY_SIZE(tlv_init_params)); + if (err) + goto out_unregister_genl; + + return 0; + +out_unregister_genl: + genl_unregister_family(&ipv6_tlv_nl_family); +out: + return err; } void ipv6_exthdrs_options_exit(void) { exthdrs_fini(&ipv6_tlv_param_table); + genl_unregister_family(&ipv6_tlv_nl_family); } From patchwork Mon Apr 29 23:04:20 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092884 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="UxId+iZ1"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxS1Xnxz9s9T for ; Tue, 30 Apr 2019 09:05:00 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729725AbfD2XE7 (ORCPT ); Mon, 29 Apr 2019 19:04:59 -0400 Received: from mail-it1-f195.google.com ([209.85.166.195]:53507 "EHLO mail-it1-f195.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729700AbfD2XE4 (ORCPT ); Mon, 29 Apr 2019 19:04:56 -0400 Received: by mail-it1-f195.google.com with SMTP id z4so1769348itc.3 for ; Mon, 29 Apr 2019 16:04:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=yUm51gWuKGVdPgdQ3k3DR/5JTAJ1UTdSoV14vxcGkkU=; b=UxId+iZ11ZyUnITmqJqmKv6Z9sP1OF2lDoHhtklfchZKN0smFY2sNToDZVsUtOAPy8 OaeFYMWs3TvPfm1myRrFfKVYhVMpAcpI3l5Oh8QV9XwduWVz6uFZ+eRdEvbEpQSWgbsh 6Apwj5BhPA/2A+884DtOUeXeod0QZ/L1BrI2QyZoZGmhDRcVzTLdyU2lZXvzzDtyS6jg TPq3zBM0oT8pYAlPXvm/5WuEaZhICEuskXA7VA4VVWuqFYfipJfqrB2XyI5fqV5YKmTP yGSlqolZTbolenRCd/NkmcuTdJR4smu5isrwRXJGAYeCde5TIR1hmIRWqnRY0WF9stDz M3Jg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=yUm51gWuKGVdPgdQ3k3DR/5JTAJ1UTdSoV14vxcGkkU=; b=drw21UCV6aAwf4wnyFHRKkSLxXVm84yeO7Fdt6Yi4h25KPrOEoUo6hmznb1HExm6Ch lq42kAk8VUGMgw2M83MtCmPk5fEUbMHMMiRVZnFTiqbKOwZG9MgjYj4i/UGP4ydlCywv eiDFLIR4bUG9MAph6Z4L6iBlIMwpPtFZSovgXTXwSNBJ1fGwSX+o146IysTGUTYqYq8B v4kTAsoLw0Oqi4Vhf9SYPFvb0Mp9QUTgLaLd56fTe6jfbIaVd4VKr932NE7FDkzZDk8y hRmzV3xu6LPFspQj7R5OMKauEIXkkNDcKYS1hE8vUhvAuBh4Rf6h4jEUOEUaV/VsgL68 +AyQ== X-Gm-Message-State: APjAAAX1gD4kpFGcEC5Al0DzD4R96xI5tEEUEjpyG7DuOb4o4wPjfoPw oMMrFa3FeFmcPrNXsUvR4cBAaA== X-Google-Smtp-Source: APXvYqz8yNi3VaqTBY8HaQD6a7Y3nYDwfyapGkldWVn5tmJeeXUN4EdLWFFduBdS6AdgDqSQYYQ1iQ== X-Received: by 2002:a24:3ec6:: with SMTP id s189mr1374273its.138.1556579094541; Mon, 29 Apr 2019 16:04:54 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.53 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:54 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 5/8] ip6tlvs: Validation of TX Destination and Hop-by-Hop options Date: Mon, 29 Apr 2019 16:04:20 -0700 Message-Id: <1556579063-1367-6-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Validate Destination and Hop-by-Hop options. This uses the information in the TLV parameters table to validate various aspects of both individual TLVs as well as a list of TLVs in an extension header. There are two levels of validation that can be performed: simple checks and deep checks. Simple checks validate only the most basic properties such as that the TLV list fits into the EH. Deep checks do a fine grained validation that includes perferred ordering, length limits, and length alignment. With proper permissions set in the TLV parameter table, this patch allows non-privileged users to send TLVs. Given that TLVs are open ended and potentially a source of DOS attack, deep checks are performed to limit the format that a non-privileged user can send. If deep checks are enabled, a canonical format for sending TLVs is enforced (in adherence with the robustness principle). A TLV must be well ordered with respect to the preferred order for the TLV. Each TLV must be aligned as described in the parameter table. Minimal padding (one padding TLV) is used to align TLVs. The length of the extension header as well as the count of non-padding TLVs is checked against max_*_opts_len and max_*_opts_cnt. For individual TLVs, length limits and length alignment is checked. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 23 +++ net/ipv6/datagram.c | 51 +++++-- net/ipv6/exthdrs_common.c | 376 ++++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/ipv6_sockglue.c | 39 ++--- 4 files changed, 450 insertions(+), 39 deletions(-) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index edf718f..8c19c6f 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -475,6 +475,20 @@ void exthdrs_fini(struct tlv_param_table *tlv_param_table); int ipv6_exthdrs_options_init(void); void ipv6_exthdrs_options_exit(void); +int ipv6_opt_validate_tlvs(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned int optname, bool admin, + unsigned int max_len, unsigned int max_cnt); +int ipv6_opt_validate_single_tlv(struct net *net, + struct tlv_param_table *tlv_param_table, + unsigned int optname, + unsigned char *tlv, size_t len, + bool deleting, bool admin); +int ipv6_opt_check_perm(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_txoptions *txopt, int optname, bool admin); + /* tlv_get_proc assumes rcu_read_lock is held */ static inline struct tlv_proc *tlv_get_proc( struct tlv_param_table *tlv_param_table, @@ -491,6 +505,15 @@ bool ipv6_opt_accepted(const struct sock *sk, const struct sk_buff *skb, struct ipv6_txoptions *ipv6_update_options(struct sock *sk, struct ipv6_txoptions *opt); +struct ipv6_txoptions *txoptions_from_opt(struct sock *sk, + struct tlv_param_table + *tlv_param_table, + struct ipv6_txoptions *opt, + int optname, char __user *optval, + unsigned int optlen, + unsigned int max_len, + unsigned int max_cnt); + static inline bool ipv6_accept_ra(struct inet6_dev *idev) { /* If forwarding is enabled, RA are not accepted unless the special diff --git a/net/ipv6/datagram.c b/net/ipv6/datagram.c index ee4a4e5..1e154ec 100644 --- a/net/ipv6/datagram.c +++ b/net/ipv6/datagram.c @@ -841,7 +841,10 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, break; case IPV6_2292HOPOPTS: - case IPV6_HOPOPTS: + case IPV6_HOPOPTS: { + int max_len = net->ipv6.sysctl.max_hbh_opts_len; + int max_cnt = net->ipv6.sysctl.max_hbh_opts_cnt; + if (opt->hopopt || cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) { err = -EINVAL; goto exit_f; @@ -853,15 +856,24 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, err = -EINVAL; goto exit_f; } - if (!ns_capable(net->user_ns, CAP_NET_RAW)) { - err = -EPERM; + + err = ipv6_opt_validate_tlvs(net, &ipv6_tlv_param_table, + hdr, IPV6_HOPOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW), + max_len, max_cnt); + if (err < 0) goto exit_f; - } + opt->opt_nflen += len; opt->hopopt = hdr; break; + } + + case IPV6_2292DSTOPTS: { + int max_len = net->ipv6.sysctl.max_dst_opts_len; + int max_cnt = net->ipv6.sysctl.max_dst_opts_cnt; - case IPV6_2292DSTOPTS: if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) { err = -EINVAL; goto exit_f; @@ -873,10 +885,14 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, err = -EINVAL; goto exit_f; } - if (!ns_capable(net->user_ns, CAP_NET_RAW)) { - err = -EPERM; + err = ipv6_opt_validate_tlvs(net, &ipv6_tlv_param_table, + hdr, IPV6_DSTOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW), + max_len, max_cnt); + if (err < 0) goto exit_f; - } + if (opt->dst1opt) { err = -EINVAL; goto exit_f; @@ -884,9 +900,13 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, opt->opt_flen += len; opt->dst1opt = hdr; break; + } case IPV6_DSTOPTS: - case IPV6_RTHDRDSTOPTS: + case IPV6_RTHDRDSTOPTS: { + int max_len = net->ipv6.sysctl.max_dst_opts_len; + int max_cnt = net->ipv6.sysctl.max_dst_opts_cnt; + if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_opt_hdr))) { err = -EINVAL; goto exit_f; @@ -898,10 +918,15 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, err = -EINVAL; goto exit_f; } - if (!ns_capable(net->user_ns, CAP_NET_RAW)) { - err = -EPERM; + + err = ipv6_opt_validate_tlvs(net, &ipv6_tlv_param_table, + hdr, IPV6_DSTOPTS, + ns_capable(net->user_ns, + CAP_NET_RAW), + max_len, max_cnt); + if (err < 0) goto exit_f; - } + if (cmsg->cmsg_type == IPV6_DSTOPTS) { opt->opt_flen += len; opt->dst1opt = hdr; @@ -910,7 +935,7 @@ int ip6_datagram_send_ctl(struct net *net, struct sock *sk, opt->dst0opt = hdr; } break; - + } case IPV6_2292RTHDR: case IPV6_RTHDR: if (cmsg->cmsg_len < CMSG_LEN(sizeof(struct ipv6_rt_hdr))) { diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index cfc9427..12925004 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -143,6 +143,312 @@ struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, } EXPORT_SYMBOL_GPL(ipv6_fixup_options); +/* TLV validation functions */ + +/* Validate a single non-padding TLV */ +static int __ipv6_opt_validate_single_tlv(struct net *net, unsigned char *tlv, + struct tlv_tx_params *tptx, + unsigned int class, bool *deep_check, + bool deleting, bool admin) +{ + if (tlv[0] < 2) /* Must be non-padding */ + return -EINVAL; + + /* Check permissions */ + switch (admin ? tptx->admin_perm : tptx->user_perm) { + case IPV6_TLV_PERM_NO_CHECK: + /* Allowed with no deep checks */ + *deep_check = false; + return 0; + case IPV6_TLV_PERM_WITH_CHECK: + /* Allowed with deep checks */ + *deep_check = true; + break; + default: + /* No permission */ + return -EPERM; + } + + /* Perform deep checks on the TLV */ + + /* Check class */ + if ((tptx->class & class) != class) + return -EINVAL; + + /* Don't bother checking lengths when deleting, the TLV is only + * needed here for lookup + */ + if (deleting) { + /* Don't bother with deep checks when deleting */ + *deep_check = false; + } else { + /* Check length */ + if (tlv[1] < tptx->min_data_len || tlv[1] > tptx->max_data_len) + return -EINVAL; + + /* Check length alignment */ + if ((tlv[1] % (tptx->data_len_mult + 1)) != tptx->data_len_off) + return -EINVAL; + } + + return 0; +} + +static unsigned int optname_to_tlv_class(int optname) +{ + switch (optname) { + case IPV6_HOPOPTS: + return IPV6_TLV_CLASS_FLAG_HOPOPT; + case IPV6_RTHDRDSTOPTS: + return IPV6_TLV_CLASS_FLAG_RTRDSTOPT; + case IPV6_DSTOPTS: + return IPV6_TLV_CLASS_FLAG_DSTOPT; + default: + return -1U; + } +} + +static int __ipv6_opt_validate_tlvs(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned int optname, bool deleting, + bool admin, unsigned int max_len, + unsigned int max_cnt) +{ + unsigned char *tlv = (unsigned char *)opt; + bool deep_check, did_deep_check = false; + unsigned int opt_len, tlv_len, offset; + unsigned int padding = 0, numpad = 0; + unsigned char prev_tlv_order = 0; + unsigned int class, cnt = 0; + struct tlv_tx_params *tptx; + int retc, ret = -EINVAL; + struct tlv_proc *tproc; + + opt_len = ipv6_optlen(opt); + offset = sizeof(*opt); + + class = optname_to_tlv_class(optname); + + rcu_read_lock(); + + while (offset < opt_len) { + switch (tlv[offset]) { + case IPV6_TLV_PAD1: + tlv_len = 1; + padding++; + numpad++; + break; + case IPV6_TLV_PADN: + if (offset + 1 >= opt_len) + goto out; + + tlv_len = tlv[offset + 1] + 2; + + if (offset + tlv_len > opt_len) + goto out; + + padding += tlv_len; + numpad++; + break; + default: + if (offset + 1 >= opt_len) + goto out; + + tlv_len = tlv[offset + 1] + 2; + + if (offset + tlv_len > opt_len) + goto out; + + tproc = tlv_get_proc(tlv_param_table, tlv[offset]); + tptx = &tproc->params.t; + + retc = __ipv6_opt_validate_single_tlv(net, &tlv[offset], + tptx, class, + &deep_check, + deleting, admin); + if (retc < 0) { + ret = retc; + goto out; + } + + if (deep_check) { + /* Check for too many options */ + if (++cnt > max_cnt) { + ret = -E2BIG; + goto out; + } + + /* Check order */ + if (tptx->preferred_order < prev_tlv_order) + goto out; + + /* Check alignment */ + if ((offset % (tptx->align_mult + 1)) != + tptx->align_off) + goto out; + + /* Check for right amount of padding */ + if (numpad > 1 || padding > tptx->align_mult) + goto out; + + prev_tlv_order = tptx->preferred_order; + + did_deep_check = true; + } + padding = 0; + numpad = 0; + } + offset += tlv_len; + } + + /* If we did at least one deep check apply length limit */ + if (did_deep_check && opt_len > max_len) { + ret = -EMSGSIZE; + goto out; + } + + /* All good */ + ret = 0; +out: + rcu_read_unlock(); + + return ret; +} + +/** + * ipv6_opt_validate_tlvs - Validate TLVs. + * @net: Current net + * @tlv_param_table: TLV parameter table + * @opt: The option header + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @admin: Set for privileged user + * @max_len: Maximum length for TLV + * @max_cnt: Maximum number of non-padding TLVs + * + * Description: + * Walks the TLVs in a list to verify that the TLV lengths and other + * parameters are in bounds for a Destination or Hop-by-Hop option. + * Return -EINVAL is there is a problem, zero otherwise. + */ +int ipv6_opt_validate_tlvs(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, unsigned int optname, + bool admin, unsigned int max_len, + unsigned int max_cnt) +{ + return __ipv6_opt_validate_tlvs(net, tlv_param_table, opt, optname, + false, admin, max_len, max_cnt); +} +EXPORT_SYMBOL(ipv6_opt_validate_tlvs); + +/** + * ipv6_opt_validate_single_tlv - Check that a single TLV is valid. + * @net: Current net + * @tlv_param_table: TLV parameter table + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @tlv: The TLV as array of bytes + * @len: Length of buffer holding TLV + * @deleting: TLV is being deleted + * @admin: Set for privileged user + * + * Description: + * Validates a single TLV. The TLV must be non-padding type. The length + * of the TLV (as determined by the second byte that gives length of the + * option data) must match @len. + */ +int ipv6_opt_validate_single_tlv(struct net *net, + struct tlv_param_table *tlv_param_table, + unsigned int optname, + unsigned char *tlv, size_t len, + bool deleting, bool admin) +{ + struct tlv_tx_params *tptx; + struct tlv_proc *tproc; + unsigned int class; + bool deep_check; + int ret = 0; + + class = optname_to_tlv_class(optname); + + if (tlv[0] < 2) + return -EINVAL; + + if (len < 2) + return -EINVAL; + + if (tlv[1] + 2 != len) + return -EINVAL; + + rcu_read_lock(); + + tproc = tlv_get_proc(tlv_param_table, tlv[0]); + tptx = &tproc->params.t; + + ret = __ipv6_opt_validate_single_tlv(net, tlv, tptx, class, + &deep_check, deleting, admin); + + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(ipv6_opt_validate_single_tlv); + +/** + * ipv6_opt_check_perm - Check that current capabilities allows modifying + * txopts. + * @net: Current net + * @tlv_param_table: TLV parameter table + * @txopts: TX options from the socket + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @admin: Set for privileged user + * + * Description: + * + * Checks whether the permissions of TLV that are set on a socket permit + * modificationr. + * + */ +int ipv6_opt_check_perm(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_txoptions *txopt, int optname, bool admin) +{ + struct ipv6_opt_hdr *opt; + int retv = -EPERM; + + if (!txopt) + return 0; + + switch (optname) { + case IPV6_HOPOPTS: + opt = txopt->hopopt; + break; + case IPV6_RTHDRDSTOPTS: + opt = txopt->dst0opt; + break; + case IPV6_DSTOPTS: + opt = txopt->dst1opt; + break; + default: + goto out; + } + + if (!opt) { + retv = 0; + goto out; + } + + /* Just call the validate function on the options as being + * deleted. + */ + retv = __ipv6_opt_validate_tlvs(net, tlv_param_table, opt, optname, + true, admin, -1U, -1U); + +out: + return retv; +} +EXPORT_SYMBOL(ipv6_opt_check_perm); + /* TLV parameter table functions and structures */ static void tlv_param_table_release(struct rcu_head *rcu) @@ -377,6 +683,76 @@ int tlv_unset_params(struct tlv_param_table *tlv_param_table, } EXPORT_SYMBOL(tlv_unset_params); +/* Utility function tp create TX options from a setsockopt that is setting + * options on a socket. + */ +struct ipv6_txoptions *txoptions_from_opt(struct sock *sk, + struct tlv_param_table + *tlv_param_table, + struct ipv6_txoptions *opt, + int optname, char __user *optval, + unsigned int optlen, + unsigned int max_len, + unsigned int max_cnt) +{ + struct ipv6_opt_hdr *new = NULL; + struct net *net = sock_net(sk); + int retv; + + /* remove any sticky options header with a zero option + * length, per RFC3542. + */ + if (optlen == 0) { + optval = NULL; + } else if (!optval) { + return ERR_PTR(-EINVAL); + } else if (optlen < sizeof(struct ipv6_opt_hdr) || + optlen & 0x7 || optlen > 8 * 255) { + return ERR_PTR(-EINVAL); + } else { + new = memdup_user(optval, optlen); + if (IS_ERR(new)) + return (struct ipv6_txoptions *)new; + if (unlikely(ipv6_optlen(new) > optlen)) { + kfree(new); + return ERR_PTR(-EINVAL); + } + } + + if (optname != IPV6_RTHDR) { + bool cap = ns_capable(net->user_ns, CAP_NET_RAW); + + /* First check if we have permission to delete + * the existing options on the socket. + */ + retv = ipv6_opt_check_perm(net, tlv_param_table, + opt, optname, cap); + if (retv < 0) { + kfree(new); + return ERR_PTR(retv); + } + + /* Check permissions and other validations on new + * TLVs + */ + if (new) { + retv = ipv6_opt_validate_tlvs(net, tlv_param_table, + new, optname, cap, + max_len, max_cnt); + if (retv < 0) { + kfree(new); + return ERR_PTR(retv); + } + } + } + + opt = ipv6_renew_options(sk, opt, optname, new); + kfree(new); + + return opt; +} +EXPORT_SYMBOL(txoptions_from_opt); + const struct nla_policy tlv_nl_policy[IPV6_TLV_ATTR_MAX + 1] = { [IPV6_TLV_ATTR_TYPE] = { .type = NLA_U8, }, [IPV6_TLV_ATTR_ORDER] = { .type = NLA_U8, }, diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index 40f21fe..5045818 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c @@ -398,39 +398,26 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, case IPV6_DSTOPTS: { struct ipv6_txoptions *opt; - struct ipv6_opt_hdr *new = NULL; + unsigned int max_len = -1U, max_cnt = -1U; - /* hop-by-hop / destination options are privileged option */ - retv = -EPERM; - if (optname != IPV6_RTHDR && !ns_capable(net->user_ns, CAP_NET_RAW)) + switch (optname) { + case IPV6_HOPOPTS: + max_len = net->ipv6.sysctl.max_hbh_opts_len; + max_cnt = net->ipv6.sysctl.max_hbh_opts_cnt; break; - - /* remove any sticky options header with a zero option - * length, per RFC3542. - */ - if (optlen == 0) - optval = NULL; - else if (!optval) - goto e_inval; - else if (optlen < sizeof(struct ipv6_opt_hdr) || - optlen & 0x7 || optlen > 8 * 255) - goto e_inval; - else { - new = memdup_user(optval, optlen); - if (IS_ERR(new)) { - retv = PTR_ERR(new); + case IPV6_RTHDRDSTOPTS: + case IPV6_DSTOPTS: + max_len = net->ipv6.sysctl.max_dst_opts_len; + max_cnt = net->ipv6.sysctl.max_dst_opts_cnt; break; - } - if (unlikely(ipv6_optlen(new) > optlen)) { - kfree(new); - goto e_inval; - } } opt = rcu_dereference_protected(np->opt, lockdep_sock_is_held(sk)); - opt = ipv6_renew_options(sk, opt, optname, new); - kfree(new); + opt = txoptions_from_opt(sk, &ipv6_tlv_param_table, opt, + optname, optval, optlen, max_len, + max_cnt); + if (IS_ERR(opt)) { retv = PTR_ERR(opt); break; From patchwork Mon Apr 29 23:04:21 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092883 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="YJ5KnG1g"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxQ75cWz9sB8 for ; Tue, 30 Apr 2019 09:04:58 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729720AbfD2XE6 (ORCPT ); Mon, 29 Apr 2019 19:04:58 -0400 Received: from mail-it1-f193.google.com ([209.85.166.193]:38612 "EHLO mail-it1-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729684AbfD2XE4 (ORCPT ); Mon, 29 Apr 2019 19:04:56 -0400 Received: by mail-it1-f193.google.com with SMTP id q19so1782325itk.3 for ; Mon, 29 Apr 2019 16:04:56 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=jwKsDaXGjYO7Q1qUenHH00w7DqPliGu7mDra++7KLns=; b=YJ5KnG1g68b961zyjHvdbGXH4DMOVWXTgOM3xUBxjSv+Hp5uEpnAKXsfAA1q55QoHN EIiBxKhJaReSMVsrBBpVLjtLmOhU8gmZyK8ucT69AWv94gfzLZN3D2Fe24fbXiKnoLaB GoNDeoJiUtoCThIa2ru/X61GiXKBpGBh1NYaRtlCOeibPrKwpd/I9+ytQIeCEOHkM8ef +Vq6V0rP/pt2jGLutjYF/DfjSu60995G2iaGisr0rPlakga4zS60iXq20li4RAGHlePZ HgFxLOQw4Fwq6ETkm0h+xJ1WeNyRtBBOtxNSAvwl2kxbc6/Ych+ixMbnfh1y8wqy7B+Q easA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=jwKsDaXGjYO7Q1qUenHH00w7DqPliGu7mDra++7KLns=; b=WOADJdjd6vVeTGmzMacnkGgGh+exyBgvKF4A/BUUu4rJcMETFSgDyu++6SnY2/qs7s /7hjkKQcK2pQtF3sFrfKlUDEycBFsHxmyXpk3ReZB6mszOMT6lSVcIFf2QP1b1MwN/nV cchTUYZerCSAmSGaNRBU6dOXh5npLweCqqg1fd4SVlLz5t+TD59ah4ljAEAzy4v9dldX 2qPTR2L/8kMCb3f/nCaJQTngHgbGG3KdQQkc2nlvftPBMbO7g3eItGYapHoQ5A9QXtxw +aCF/ujavkPncKIeTfmxXy1w34+mfNfQ2ZLYrow2GjfQvRBEEPqEDtd6y4dKdzeRnBjd ylWw== X-Gm-Message-State: APjAAAVbyGOAUpmKi1SYf8R8oh3WForTp7XK9zLz0QJUJWwHB7N0Yn9w 3z1A1HINHirNnW6ozJM03YA0gw== X-Google-Smtp-Source: APXvYqw93l6xhUc2Vaksul9ZKq9zzkjHW8UpCEze6irxyszwGaF3kvdjwu/HtPpdPxzd4QMBOsALow== X-Received: by 2002:a24:b349:: with SMTP id z9mr1419342iti.77.1556579096049; Mon, 29 Apr 2019 16:04:56 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.55 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:55 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 6/8] ipv6tlvs: opt_update function Date: Mon, 29 Apr 2019 16:04:21 -0700 Message-Id: <1556579063-1367-7-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Add a utility function to replace socket's options with a new set. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 2 ++ net/ipv6/ipv6_sockglue.c | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index 8c19c6f..a8c1e6c 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -379,6 +379,8 @@ struct ipv6_txoptions *ipv6_renew_options(struct sock *sk, struct ipv6_opt_hdr *newopt); struct ipv6_txoptions *ipv6_fixup_options(struct ipv6_txoptions *opt_space, struct ipv6_txoptions *opt); +int ipv6_opt_update(struct sock *sk, struct ipv6_txoptions *opt, + int which, struct ipv6_opt_hdr *new); /* * Parsing tlv encoded headers. diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index 5045818..b8ef0ea 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c @@ -118,6 +118,22 @@ struct ipv6_txoptions *ipv6_update_options(struct sock *sk, return opt; } +int ipv6_opt_update(struct sock *sk, struct ipv6_txoptions *opt, + int which, struct ipv6_opt_hdr *new) +{ + opt = ipv6_renew_options(sk, opt, which, new); + if (IS_ERR(opt)) + return PTR_ERR(opt); + + opt = ipv6_update_options(sk, opt); + if (opt) { + atomic_sub(opt->tot_len, &sk->sk_omem_alloc); + txopt_put(opt); + } + + return 0; +} + static bool setsockopt_needs_rtnl(int optname) { switch (optname) { From patchwork Mon Apr 29 23:04:22 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092886 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="EUaLA/oL"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxY0Nl9z9sB8 for ; Tue, 30 Apr 2019 09:05:05 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729734AbfD2XFD (ORCPT ); Mon, 29 Apr 2019 19:05:03 -0400 Received: from mail-it1-f193.google.com ([209.85.166.193]:53108 "EHLO mail-it1-f193.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729684AbfD2XE6 (ORCPT ); Mon, 29 Apr 2019 19:04:58 -0400 Received: by mail-it1-f193.google.com with SMTP id x132so1772015itf.2 for ; Mon, 29 Apr 2019 16:04:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=PtIyc21qvQR4VYJ1pXh389yIvJE9WGpco5jIFGYZ3ZI=; b=EUaLA/oLjQaMx5MOaNJsVofK07DiZNKlg/AmlL3ZjrehAeG9Do34uWb2FueZAklgE2 RCpu05TOCJRQ3CIumNK+Z7Zi0QmutrXd2IhDuIvdS059b5vMezqLyYTQgZ5R7WR12gEt 5SnLn12W3hU96K6QZIWFK9nspk5cSKB1e30yyJcEAFAQW1rhMplNGHDIl3QVbBGd9Dn+ UZ51LOfSUcj4mkSW0diNLO8FsNugWMfscZZF2DMys7MtY75hIh/2nJAzqi+RyHm2z+44 63UwxdlZZuV38VljyHqAy1dGNfJgE+eK3lYoNQwALpyywTraXHqE/sQdttIRWAmb/lv2 vsQA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=PtIyc21qvQR4VYJ1pXh389yIvJE9WGpco5jIFGYZ3ZI=; b=i1/owNjvD0yNIPn3cMHt98iNpXN3x21x3KOeVwtm3olZtbgLxwd0beEJLX9ojCtWWI 2c0mOC7jm7PvAFv+3WvW2LSSTwQLCOh+tX7kW3MM0Sd4Rulgm4q2L+QZ6rIX8OMeDe0J I3vvd8LK763Ijn6SUKNvabpintrN48AjWa4xxmRBaGdQyoLHA9TOAh8ZgnaNwLIi8yRM oXm9MFlAv7s/b8lbQmlGUpZlrWH0n8OxRuXOXuLJU41ETOLViXFmTq9MZm4WY82IHR9e gn+dR2eLHrxbrtgn+yBLLejK2HvJXkI87CzCZUEq08bimzffl7bTJEGhIH4dFC12FAJZ R9Fw== X-Gm-Message-State: APjAAAVAMjpE2fAGU7O4PF2w+L8X8e5X8ICGVtrcpcHLjmi2qtesKWjG XrEUf0xOf7ef1Mx0skp9GAF9fA== X-Google-Smtp-Source: APXvYqw20FLgSDE5/bOlYRLxgtZJi++4Y7TnrYVCQegVBHGan6Ibc+4+USwrPA3UCdX3rn4JTM5ggw== X-Received: by 2002:a05:660c:248:: with SMTP id t8mr1426009itk.162.1556579097642; Mon, 29 Apr 2019 16:04:57 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.56 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:57 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 7/8] ipv6tlvs: Infrastructure for manipulating individual TLVs Date: Mon, 29 Apr 2019 16:04:22 -0700 Message-Id: <1556579063-1367-8-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org Add utility functions in exthdrs_common.c to manipulate individual TVLs in Hop-by-Hop or Destination Options. This includes functions to find, insert, and delete in singleton TLV from on IPv6 options header. This code is based in part on the TLV option handling in calipso.c. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 13 ++ net/ipv6/exthdrs_common.c | 519 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 532 insertions(+) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index a8c1e6c..bf6e593f 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -491,6 +491,19 @@ int ipv6_opt_check_perm(struct net *net, struct tlv_param_table *tlv_param_table, struct ipv6_txoptions *txopt, int optname, bool admin); +int ipv6_opt_tlv_find(struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, unsigned char *targ_tlv, + unsigned int *start, unsigned int *end); +struct ipv6_opt_hdr *ipv6_opt_tlv_insert(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + int optname, unsigned char *tlv, + bool admin); +struct ipv6_opt_hdr *ipv6_opt_tlv_delete(struct net *net, + struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned char *tlv, bool admin); + /* tlv_get_proc assumes rcu_read_lock is held */ static inline struct tlv_proc *tlv_get_proc( struct tlv_param_table *tlv_param_table, diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 12925004..8acff49 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -449,6 +449,525 @@ int ipv6_opt_check_perm(struct net *net, } EXPORT_SYMBOL(ipv6_opt_check_perm); +/* Functions to manage individual TLVs */ + +/** + * __ipv6_opt_tlv_find - Finds a particular TLV in an IPv6 options header + * (destinaton or hop-by-hop options). If TLV is not present, then the + * preferred insertion point is determined. + * @opt: the options header (an EH header followed by data) + * @targ_tlv: Prototype of TLV to find + * @start: on return holds the offset of any leading padding if option + * is present, or offset at which option is inserted. + * @end: on return holds the offset of the first non-pad TLV after option + * if the option was found, else points to the first TLV after + * padding at intsertion point. + * + * Description: + * Finds the space occupied by particular option (including any leading and + * trailing padding), or the perferred position for insertion if the + * TLV is not present. + * + * If the option is found then @start and @end are set to the offsets within + * @opt of the start of padding before the first found option and the end of + * padding after the first found option. In this case the function returns + * the offset in @opt of the found option (a value >= 2 since the TLV + * must be after the option header). + * + * In the absence of the searched option, @start is set to offset in @opt at + * which the option may be inserted per the ordering and alignment rules + * in the TLV parameter table, and @end is set to the end + 1 of any + * padding at the @start offset. When the option is not found -ENOENT is + * returned. + * + * rcu_read_lock assumed held. + */ +static int __ipv6_opt_tlv_find(struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned char *targ_tlv, + unsigned int *start, unsigned int *end) +{ + unsigned int offset_s = 0, offset_e = 0, last_s = 0; + unsigned char *tlv = (unsigned char *)opt; + unsigned int pad_e = sizeof(*opt); + int ret_val = -ENOENT, tlv_len; + unsigned int opt_len, offset; + struct tlv_tx_params *tptx; + unsigned int targ_order; + bool found_cand = false; + struct tlv_proc *tproc; + + opt_len = ipv6_optlen(opt); + offset = sizeof(*opt); + + tproc = tlv_get_proc(tlv_param_table, targ_tlv[0]); + tptx = &tproc->params.t; + + targ_order = tptx->preferred_order; + + while (offset < opt_len) { + switch (tlv[offset]) { + case IPV6_TLV_PAD1: + if (offset_e) + offset_e = offset; + tlv_len = 1; + break; + case IPV6_TLV_PADN: + if (offset_e) + offset_e = offset; + tlv_len = tlv[offset + 1] + 2; + break; + default: + if (ret_val >= 0) + goto out; + + /* Not found yet */ + + if (tlv[offset] == targ_tlv[0]) { + /* Found it */ + + ret_val = offset; + offset_e = offset; + offset_s = last_s; + found_cand = true; + } else { + struct tlv_tx_params *tptx1; + + tproc = tlv_get_proc(tlv_param_table, + tlv[offset]); + tptx1 = &tproc->params.t; + + if (targ_order < tptx1->preferred_order && + !found_cand) { + /* Found candidate for insert location + */ + + pad_e = offset; + offset_s = last_s; + found_cand = true; + } + } + + last_s = offset; + tlv_len = tlv[offset + 1] + 2; + break; + } + + offset += tlv_len; + } + + if (!found_cand) { + /* Not found and insert point is after all options */ + offset_s = last_s; + pad_e = opt_len; + } + +out: + if (offset_s) + *start = offset_s + + (tlv[offset_s] ? tlv[offset_s + 1] + 2 : 1); + else + *start = sizeof(*opt); + + if (ret_val >= 0) + *end = offset_e + + (tlv[offset_e] ? tlv[offset_e + 1] + 2 : 1); + else + *end = pad_e; + + return ret_val; +} + +int ipv6_opt_tlv_find(struct tlv_param_table *tlv_param_table, + struct ipv6_opt_hdr *opt, unsigned char *targ_tlv, + unsigned int *start, unsigned int *end) +{ + int ret; + + rcu_read_lock(); + ret = __ipv6_opt_tlv_find(tlv_param_table, opt, targ_tlv, start, end); + rcu_read_unlock(); + + return ret; +} +EXPORT_SYMBOL(ipv6_opt_tlv_find); + +/** + * ipv6_opt_tlv_pad_write - Writes pad bytes in TLV format + * @buf: the buffer + * @offset: offset from start of buffer to write padding + * @count: number of pad bytes to write + * + * Description: + * Write @count bytes of TLV padding into @buffer starting at offset @offset. + * @count should be less than 8 - see RFC 4942. + * + */ +static int ipv6_opt_tlv_pad_write(unsigned char *buf, unsigned int offset, + unsigned int count) +{ + if (WARN_ON_ONCE(count >= 8)) + return -EINVAL; + + switch (count) { + case 0: + break; + case 1: + buf[offset] = IPV6_TLV_PAD1; + break; + default: + buf[offset] = IPV6_TLV_PADN; + buf[offset + 1] = count - 2; + if (count > 2) + memset(buf + offset + 2, 0, count - 2); + break; + } + return 0; +} + +static unsigned int compute_padding(unsigned int offset, unsigned int mult, + unsigned int moff) +{ + return (mult - ((offset - moff) % mult)) % mult; +} + +static int tlv_find_next(unsigned char *tlv, unsigned int offset, + unsigned int optlen) +{ + while (offset < optlen) { + switch (tlv[offset]) { + case IPV6_TLV_PAD1: + offset++; + break; + case IPV6_TLV_PADN: + offset += tlv[offset + 1] + 2; + break; + default: + return offset; + } + } + + return (optlen); +} + +/* __tlv_sum_alignment assumes ruc_read_lock is held */ +static size_t __tlv_sum_alignment(struct tlv_param_table *tlv_param_table, + unsigned char *tlv, unsigned int offset, + unsigned int optlen) +{ + int sum = 0; + + offset = tlv_find_next(tlv, offset, optlen); + + while (offset < optlen) { + struct tlv_tx_params *tptx; + struct tlv_proc *tproc; + + tproc = tlv_get_proc(tlv_param_table, tlv[offset]); + tptx = &tproc->params.t; + sum += tptx->align_mult; + offset += tlv[offset + 1] + 2; + offset = tlv_find_next(tlv, offset, optlen); + } + + return sum; +} + +/* __copy_and_align_tlvs assumes rcu_read_lock is held */ +static int __copy_and_align_tlvs(struct tlv_param_table *tlv_param_table, + unsigned int src_off, unsigned char *src, + unsigned int dst_off, unsigned char *dst, + unsigned int optlen) +{ + struct tlv_tx_params *tptx; + unsigned int padding, len; + struct tlv_proc *tproc; + + if (!src) + return dst_off; + + src_off = tlv_find_next(src, src_off, optlen); + + while (src_off < optlen) { + tproc = tlv_get_proc(tlv_param_table, src[src_off]); + tptx = &tproc->params.t; + + padding = compute_padding(dst_off, tptx->align_mult + 1, + tptx->align_off); + ipv6_opt_tlv_pad_write(dst, dst_off, padding); + dst_off += padding; + + len = src[src_off + 1] + 2; + memcpy(&dst[dst_off], &src[src_off], len); + + src_off += len; + dst_off += len; + src_off = tlv_find_next(src, src_off, optlen); + } + + return dst_off; +} + +static int count_tlvs(struct ipv6_opt_hdr *opt) +{ + unsigned char *tlv = (unsigned char *)opt; + unsigned int opt_len, tlv_len, offset, cnt = 0; + + opt_len = ipv6_optlen(opt); + offset = sizeof(*opt); + + while (offset < opt_len) { + switch (tlv[offset]) { + case IPV6_TLV_PAD1: + tlv_len = 1; + break; + case IPV6_TLV_PADN: + tlv_len = tlv[offset + 1] + 2; + break; + default: + cnt++; + tlv_len = tlv[offset + 1] + 2; + break; + } + offset += tlv_len; + } + + return cnt; +} + +#define IPV6_OPT_MAX_END_PAD 7 + +/** + * ipv6_opt_tlv_insert - Inserts a TLV into an IPv6 destination options + * or Hop-by-Hop options extension header. + * + * @net: Current net + * @opt: the original options extensions header + * @optname: IPV6_HOPOPTS, IPV6_RTHDRDSTOPTS, or IPV6_DSTOPTS + * @tlv: the new TLV being inserted + * @admin: Set for privileged user + * + * Description: + * Creates a new options header based on @opt with the specified option + * in @tlv option added to it. If @opt already contains the same type + * of TLV, then the TLV is overwritten, otherwise the new TLV is appended + * after any existing TLVs. If @opt is NULL then the new header + * will contain just the new option and any needed padding. + * + * Assumes option has been validated. + */ +struct ipv6_opt_hdr *ipv6_opt_tlv_insert(struct net *net, + struct tlv_param_table + *tlv_param_table, + struct ipv6_opt_hdr *opt, int optname, + unsigned char *tlv, bool admin) +{ + unsigned int start = 0, end = 0, buf_len, pad, optlen, max_align; + size_t tlv_len = tlv[1] + 2; + struct tlv_tx_params *tptx; + struct ipv6_opt_hdr *new; + struct tlv_proc *tproc; + int ret_val; + u8 perm; + + rcu_read_lock(); + + if (opt) { + optlen = ipv6_optlen(opt); + ret_val = __ipv6_opt_tlv_find(tlv_param_table, opt, + tlv, &start, &end); + if (ret_val < 0) { + if (ret_val != -ENOENT) { + rcu_read_unlock(); + return ERR_PTR(ret_val); + } + } else if (((unsigned char *)opt)[ret_val + 1] == tlv[1]) { + unsigned int roff = ret_val + tlv[1] + 2; + + if (!memcmp(&((unsigned char *)opt)[ret_val + 2], + &tlv[2], tlv[1])) { + /* New TLV is identical to old one, just + * return -EALREADY (not an error). + */ + + rcu_read_unlock(); + return ERR_PTR(-EALREADY); + } + + /* Replace existing TLV with one of the same length, + * we can fast path this. + */ + + rcu_read_unlock(); + + new = kmalloc(optlen, GFP_ATOMIC); + if (!new) + return ERR_PTR(-ENOMEM); + + memcpy((unsigned char *)new, + (unsigned char *)opt, ret_val); + memcpy((unsigned char *)new + ret_val, tlv, tlv[1] + 2); + memcpy((unsigned char *)new + roff, + (unsigned char *)opt + roff, optlen - roff); + + return new; + } + } else { + optlen = 0; + start = sizeof(*opt); + end = 0; + } + + tproc = tlv_get_proc(tlv_param_table, tlv[0]); + tptx = &tproc->params.t; + + /* Maximum buffer size we'll need including possible padding */ + max_align = __tlv_sum_alignment(tlv_param_table, (unsigned char *)opt, + end, optlen); + max_align += tptx->align_mult + IPV6_OPT_MAX_END_PAD; + + buf_len = optlen + start - end + tlv_len + max_align; + new = kmalloc(buf_len, GFP_ATOMIC); + if (!new) { + rcu_read_unlock(); + return ERR_PTR(-ENOMEM); + } + + buf_len = start; + + if (start > sizeof(*opt)) + memcpy(new, opt, start); + + pad = compute_padding(start, tptx->align_mult + 1, tptx->align_off); + ipv6_opt_tlv_pad_write((__u8 *)new, start, pad); + buf_len += pad; + + memcpy((__u8 *)new + buf_len, tlv, tlv_len); + buf_len += tlv_len; + + buf_len = __copy_and_align_tlvs(tlv_param_table, end, (__u8 *)opt, + buf_len, (__u8 *)new, optlen); + + perm = admin ? tptx->admin_perm : tptx->user_perm; + + rcu_read_unlock(); + + /* Trailer pad to 8 byte alignment */ + pad = (8 - (buf_len & 7)) & 7; + ipv6_opt_tlv_pad_write((__u8 *)new, buf_len, pad); + buf_len += pad; + + /* Set header */ + new->nexthdr = 0; + new->hdrlen = buf_len / 8 - 1; + + if (perm != IPV6_TLV_PERM_NO_CHECK) { + switch (optname) { + case IPV6_HOPOPTS: + if (buf_len > net->ipv6.sysctl.max_hbh_opts_len) + return ERR_PTR(-EMSGSIZE); + if (count_tlvs(new) > net->ipv6.sysctl.max_hbh_opts_cnt) + return ERR_PTR(-E2BIG); + break; + case IPV6_RTHDRDSTOPTS: + case IPV6_DSTOPTS: + if (buf_len > net->ipv6.sysctl.max_dst_opts_len) + return ERR_PTR(-EMSGSIZE); + if (count_tlvs(new) > net->ipv6.sysctl.max_dst_opts_cnt) + return ERR_PTR(-E2BIG); + break; + } + } + + return new; +} +EXPORT_SYMBOL(ipv6_opt_tlv_insert); + +/* rcu_read_lock assume held */ +struct ipv6_opt_hdr *__ipv6_opt_tlv_delete(struct tlv_param_table + *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned int start, + unsigned int end) +{ + unsigned int pad, optlen, buf_len; + struct ipv6_opt_hdr *new; + size_t max_align; + + optlen = ipv6_optlen(opt); + if (start == sizeof(*opt) && end == optlen) { + /* There's no other option in the header so return NULL */ + return NULL; + } + + max_align = __tlv_sum_alignment(tlv_param_table, + (unsigned char *)opt, end, optlen) + + IPV6_OPT_MAX_END_PAD; + + new = kmalloc(optlen - (end - start) + max_align, GFP_ATOMIC); + if (!new) + return ERR_PTR(-ENOMEM); /* DIFF */ + + memcpy(new, opt, start); + + buf_len = __copy_and_align_tlvs(tlv_param_table, end, (__u8 *)opt, + start, (__u8 *)new, optlen); + + /* Now set trailer padding, buf_len is at the end of the last TLV at + * this point + */ + pad = (8 - (buf_len & 7)) & 7; + ipv6_opt_tlv_pad_write((__u8 *)new, buf_len, pad); + buf_len += pad; + + /* Set new header length */ + new->hdrlen = buf_len / 8 - 1; + + return new; +} + +/** + * ipv6_opt_tlv_delete - Removes the specified option from the destination + * or Hop-by-Hop extension header. + * @net: Current net + * @opt: The original header + * @tlv: Prototype of TLV being removed + * @admin: Set for privileged user + * + * Description: + * Creates a new header based on @opt without the specified option in + * @tlv. A new options header is returned without the option. If @opt + * doesn't contain the specified option ERR_PTR(-ENOENT) is returned. + * If @opt contains no other non-padding options, NULL is returned. + * Otherwise, a new header is created and returned without the option + * (and removing as much padding as possible). + */ +struct ipv6_opt_hdr *ipv6_opt_tlv_delete(struct net *net, + struct tlv_param_table + *tlv_param_table, + struct ipv6_opt_hdr *opt, + unsigned char *tlv, bool admin) +{ + struct ipv6_opt_hdr *retopt; + unsigned int start, end; + int ret_val; + + rcu_read_lock(); + + ret_val = __ipv6_opt_tlv_find(tlv_param_table, opt, tlv, &start, &end); + if (ret_val < 0) { + rcu_read_unlock(); + return ERR_PTR(ret_val); + } + + retopt = __ipv6_opt_tlv_delete(tlv_param_table, opt, start, end); + + rcu_read_unlock(); + + return retopt; +} +EXPORT_SYMBOL(ipv6_opt_tlv_delete); + /* TLV parameter table functions and structures */ static void tlv_param_table_release(struct rcu_head *rcu) From patchwork Mon Apr 29 23:04:23 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Tom Herbert X-Patchwork-Id: 1092885 X-Patchwork-Delegate: davem@davemloft.net Return-Path: X-Original-To: patchwork-incoming-netdev@ozlabs.org Delivered-To: patchwork-incoming-netdev@ozlabs.org Authentication-Results: ozlabs.org; spf=none (mailfrom) smtp.mailfrom=vger.kernel.org (client-ip=209.132.180.67; helo=vger.kernel.org; envelope-from=netdev-owner@vger.kernel.org; receiver=) Authentication-Results: ozlabs.org; dmarc=none (p=none dis=none) header.from=herbertland.com Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=herbertland-com.20150623.gappssmtp.com header.i=@herbertland-com.20150623.gappssmtp.com header.b="gMTJ68sb"; dkim-atps=neutral Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by ozlabs.org (Postfix) with ESMTP id 44tKxX073Qz9s9T for ; Tue, 30 Apr 2019 09:05:04 +1000 (AEST) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1729730AbfD2XFC (ORCPT ); Mon, 29 Apr 2019 19:05:02 -0400 Received: from mail-it1-f196.google.com ([209.85.166.196]:50528 "EHLO mail-it1-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1729700AbfD2XFA (ORCPT ); Mon, 29 Apr 2019 19:05:00 -0400 Received: by mail-it1-f196.google.com with SMTP id q14so1799554itk.0 for ; Mon, 29 Apr 2019 16:04:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=herbertland-com.20150623.gappssmtp.com; s=20150623; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=YSIgnIty7ccXskGJtT8LdJlxc5XCeQS4PGqykBNvmSs=; b=gMTJ68sb9kbqJo1T25EvPsjjsMAkOOFtSXJm78uhdfDz2gRMnP1PdktaoR/GWJik9d eJB7qY8RjXTVh0KyGbPWjm3iMTYJFz133TSjs66A2095/Bf5gNR46XTn8hafq6UNp0Bn fzGBWJakM3WRWtbhTQMICLljk5urQCCwdEmvr6nE36EwTlBYWqT9dtIdNcivZPbW2zxV M3ceW64Ow1KwCqxdoaxNaqC1kEhotFKz7XSS3L0Rgwxy1Fza1E8XzPBGSeMMglrzJvPo 5nkp/5fHAcGVxTcI1cfnAtaxBjVPl2oaGz7P4OEerEwaiaJ+feYPHfQOzZ80IV3u8HJy PD2w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references; bh=YSIgnIty7ccXskGJtT8LdJlxc5XCeQS4PGqykBNvmSs=; b=VIOJotwm9r/05sdX3tZO9JOf1+qeHnyfb57TsW2tvpFb0kPgtXW2JgyHA7YLZB0I64 /TzElaVQHdVfc9RTjWoV01vjL/b2lc4m9CdSkHtBR5MN/dNo/bGKg7K9oHarSw30o23i cgXUTxqRRS10fDgPn1m4xjUbOWfAP5WKzTXBOmd0a8K7I2HRolnKwEQg9trvEQq9eRXa 4T058DXkzuvwF6Np5OcSmGe0Jz7iolKe3MWKjmqnjAyUGwoLA1nLycDq68rDdt0Zaif9 OkiLHkmot0fEOWs0l3X0T8st3b0rqPV+JXMJXc2AydbatnApkYfZpk0bmmD+3S+aZ+bm tKmw== X-Gm-Message-State: APjAAAXQe0dthbbOFi7WwYIjPdTvJJDFTzjlMuHrclGQhUz26a39BuQa 4CmPs3w2k0jIr24giMX/elegzapVB1M= X-Google-Smtp-Source: APXvYqzzpxae/yjGC5fuCB+M3CspFT/PrshEd/YPdvwTkNlxCJCJ1rkElsSSF8+oPPMKs/Cxe8N/tA== X-Received: by 2002:a24:ad5f:: with SMTP id a31mr1414530itj.55.1556579099190; Mon, 29 Apr 2019 16:04:59 -0700 (PDT) Received: from localhost.localdomain (107-0-94-194-ip-static.hfc.comcastbusiness.net. [107.0.94.194]) by smtp.gmail.com with ESMTPSA id y62sm340626itg.13.2019.04.29.16.04.58 (version=TLS1_2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Mon, 29 Apr 2019 16:04:58 -0700 (PDT) From: Tom Herbert X-Google-Original-From: Tom Herbert To: davem@davemloft.net, netdev@vger.kernel.org Cc: Tom Herbert Subject: [PATCH v8 net-next 8/8] ipv6tlvs: API for manipuateling TLVs on a connect socket Date: Mon, 29 Apr 2019 16:04:23 -0700 Message-Id: <1556579063-1367-9-git-send-email-tom@quantonium.net> X-Mailer: git-send-email 2.7.4 In-Reply-To: <1556579063-1367-1-git-send-email-tom@quantonium.net> References: <1556579063-1367-1-git-send-email-tom@quantonium.net> Sender: netdev-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: netdev@vger.kernel.org This patch provides an interface for adding a deleting individual Hop-by-Hop or Destination Options on a socket. The following IPv6 socket options are created: IPV6_HOPOPTS_TLV IPV6_RTHDRDSTOPTS_TLV IPV6_DSTOPTS_TLV IPV6_HOPOPTS_DEL_TLV IPV6_RTHDRDSTOPTS_DEL_TLV IPV6_DSTOPTS_DEL_TLV The function txoptions_from_tlv_opt does the heavy lifting to copy an options from userspace, validate the option, and call insert of delete TLV function. Signed-off-by: Tom Herbert --- include/net/ipv6.h | 7 +++++ include/uapi/linux/in6.h | 9 ++++++ net/ipv6/exthdrs_common.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++ net/ipv6/ipv6_sockglue.c | 27 ++++++++++++++++++ 4 files changed, 113 insertions(+) diff --git a/include/net/ipv6.h b/include/net/ipv6.h index bf6e593f..6aa4d8f 100644 --- a/include/net/ipv6.h +++ b/include/net/ipv6.h @@ -529,6 +529,13 @@ struct ipv6_txoptions *txoptions_from_opt(struct sock *sk, unsigned int max_len, unsigned int max_cnt); +struct ipv6_opt_hdr *txoptions_from_tlv_opt(struct sock *sk, + struct tlv_param_table + *tlv_param_table, + struct ipv6_txoptions *opt, + int optname, char __user *optval, + unsigned int optlen, int *which); + static inline bool ipv6_accept_ra(struct inet6_dev *idev) { /* If forwarding is enabled, RA are not accepted unless the special diff --git a/include/uapi/linux/in6.h b/include/uapi/linux/in6.h index 1c79361..018fc6f 100644 --- a/include/uapi/linux/in6.h +++ b/include/uapi/linux/in6.h @@ -289,6 +289,15 @@ struct in6_flowlabel_req { #define IPV6_RECVFRAGSIZE 77 #define IPV6_FREEBIND 78 +/* API to set single Destination or Hop-by-Hop options */ + +#define IPV6_HOPOPTS_TLV 79 +#define IPV6_RTHDRDSTOPTS_TLV 80 +#define IPV6_DSTOPTS_TLV 81 +#define IPV6_HOPOPTS_DEL_TLV 82 +#define IPV6_RTHDRDSTOPTS_DEL_TLV 83 +#define IPV6_DSTOPTS_DEL_TLV 84 + /* * Multicast Routing: * see include/uapi/linux/mroute6.h. diff --git a/net/ipv6/exthdrs_common.c b/net/ipv6/exthdrs_common.c index 8acff49..9e9ad85 100644 --- a/net/ipv6/exthdrs_common.c +++ b/net/ipv6/exthdrs_common.c @@ -1272,6 +1272,76 @@ struct ipv6_txoptions *txoptions_from_opt(struct sock *sk, } EXPORT_SYMBOL(txoptions_from_opt); +struct ipv6_opt_hdr *txoptions_from_tlv_opt(struct sock *sk, + struct tlv_param_table + *tlv_param_table, + struct ipv6_txoptions *opt, + int optname, char __user *optval, + unsigned int optlen, int *whichp) +{ + struct ipv6_opt_hdr *old = NULL, *new = NULL; + struct net *net = sock_net(sk); + bool deleting = false; + void *new_opt = NULL; + int which = -1, retv; + bool admin; + + new_opt = memdup_user(optval, optlen); + if (IS_ERR(new_opt)) + return new_opt; + + switch (optname) { + case IPV6_HOPOPTS_DEL_TLV: + deleting = true; + /* Fallthrough */ + case IPV6_HOPOPTS_TLV: + if (opt) + old = opt->hopopt; + which = IPV6_HOPOPTS; + break; + case IPV6_RTHDRDSTOPTS_DEL_TLV: + deleting = true; + /* Fallthrough */ + case IPV6_RTHDRDSTOPTS_TLV: + if (opt) + old = opt->dst0opt; + which = IPV6_RTHDRDSTOPTS; + break; + case IPV6_DSTOPTS_DEL_TLV: + deleting = true; + /* Fallthrough */ + case IPV6_DSTOPTS_TLV: + if (opt) + old = opt->dst1opt; + which = IPV6_DSTOPTS; + break; + } + + *whichp = which; + + admin = ns_capable(net->user_ns, CAP_NET_RAW); + + retv = ipv6_opt_validate_single_tlv(net, tlv_param_table, which, + new_opt, optlen, deleting, admin); + if (retv < 0) + return ERR_PTR(retv); + + if (deleting) { + if (!old) + return NULL; + new = ipv6_opt_tlv_delete(net, tlv_param_table, old, new_opt, + admin); + } else { + new = ipv6_opt_tlv_insert(net, tlv_param_table, old, which, + new_opt, admin); + } + + kfree(new_opt); + + return new; +} +EXPORT_SYMBOL(txoptions_from_tlv_opt); + const struct nla_policy tlv_nl_policy[IPV6_TLV_ATTR_MAX + 1] = { [IPV6_TLV_ATTR_TYPE] = { .type = NLA_U8, }, [IPV6_TLV_ATTR_ORDER] = { .type = NLA_U8, }, diff --git a/net/ipv6/ipv6_sockglue.c b/net/ipv6/ipv6_sockglue.c index b8ef0ea..3e4c0eb 100644 --- a/net/ipv6/ipv6_sockglue.c +++ b/net/ipv6/ipv6_sockglue.c @@ -476,6 +476,33 @@ static int do_ipv6_setsockopt(struct sock *sk, int level, int optname, break; } + case IPV6_HOPOPTS_TLV: + case IPV6_RTHDRDSTOPTS_TLV: + case IPV6_DSTOPTS_TLV: + case IPV6_HOPOPTS_DEL_TLV: + case IPV6_RTHDRDSTOPTS_DEL_TLV: + case IPV6_DSTOPTS_DEL_TLV: + { + struct ipv6_txoptions *opt; + struct ipv6_opt_hdr *new; + int which; + + opt = rcu_dereference_protected(np->opt, + lockdep_sock_is_held(sk)); + new = txoptions_from_tlv_opt(sk, &ipv6_tlv_param_table, opt, + optname, optval, optlen, &which); + if (IS_ERR(new)) { + retv = PTR_ERR(new); + break; + } + + retv = ipv6_opt_update(sk, opt, which, new); + + kfree(new); + + break; + } + case IPV6_PKTINFO: { struct in6_pktinfo pkt;