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 <t...@quantonium.net>
---
 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;
-- 
2.7.4

Reply via email to