sctp: Add ip option support
authorRichard Haines <richard_c_haines@btinternet.com>
Sat, 24 Feb 2018 16:18:51 +0000 (16:18 +0000)
committerPaul Moore <paul@paul-moore.com>
Mon, 26 Feb 2018 22:43:54 +0000 (17:43 -0500)
Add ip option support to allow LSM security modules to utilise CIPSO/IPv4
and CALIPSO/IPv6 services.

Signed-off-by: Richard Haines <richard_c_haines@btinternet.com>
Acked-by: Neil Horman <nhorman@tuxdriver.com>
Acked-by: Marcelo Ricardo Leitner <marcelo.leitner@gmail.com>
Signed-off-by: Paul Moore <paul@paul-moore.com>
include/net/sctp/sctp.h
include/net/sctp/structs.h
net/sctp/chunk.c
net/sctp/ipv6.c
net/sctp/output.c
net/sctp/protocol.c
net/sctp/socket.c

index f7ae6b0a21d0663bf61e1370202604e0e66896a3..25c5c876881896c003305b38a04f912230f05b20 100644 (file)
@@ -441,9 +441,11 @@ static inline int sctp_list_single_entry(struct list_head *head)
 static inline int sctp_frag_point(const struct sctp_association *asoc, int pmtu)
 {
        struct sctp_sock *sp = sctp_sk(asoc->base.sk);
+       struct sctp_af *af = sp->pf->af;
        int frag = pmtu;
 
-       frag -= sp->pf->af->net_header_len;
+       frag -= af->ip_options_len(asoc->base.sk);
+       frag -= af->net_header_len;
        frag -= sizeof(struct sctphdr) + sctp_datachk_len(&asoc->stream);
 
        if (asoc->user_frag)
index 03e92dda1813bc262d58c5a0866399d45676b2c9..ead5fcedc283c5eaccbe0365f2000e98a6c5afa6 100644 (file)
@@ -491,6 +491,7 @@ struct sctp_af {
        void            (*ecn_capable)(struct sock *sk);
        __u16           net_header_len;
        int             sockaddr_len;
+       int             (*ip_options_len)(struct sock *sk);
        sa_family_t     sa_family;
        struct list_head list;
 };
@@ -515,6 +516,7 @@ struct sctp_pf {
        int (*addr_to_user)(struct sctp_sock *sk, union sctp_addr *addr);
        void (*to_sk_saddr)(union sctp_addr *, struct sock *sk);
        void (*to_sk_daddr)(union sctp_addr *, struct sock *sk);
+       void (*copy_ip_options)(struct sock *sk, struct sock *newsk);
        struct sctp_af *af;
 };
 
index 991a530c6b31667ec15b95a08035a5e8f71476f4..d726d213de9abe8f9ba2999c42643f52e9a96b22 100644 (file)
@@ -171,6 +171,8 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
        struct list_head *pos, *temp;
        struct sctp_chunk *chunk;
        struct sctp_datamsg *msg;
+       struct sctp_sock *sp;
+       struct sctp_af *af;
        int err;
 
        msg = sctp_datamsg_new(GFP_KERNEL);
@@ -189,9 +191,11 @@ struct sctp_datamsg *sctp_datamsg_from_user(struct sctp_association *asoc,
        /* This is the biggest possible DATA chunk that can fit into
         * the packet
         */
-       max_data = asoc->pathmtu -
-                  sctp_sk(asoc->base.sk)->pf->af->net_header_len -
-                  sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream);
+       sp = sctp_sk(asoc->base.sk);
+       af = sp->pf->af;
+       max_data = asoc->pathmtu - af->net_header_len -
+                  sizeof(struct sctphdr) - sctp_datachk_len(&asoc->stream) -
+                  af->ip_options_len(asoc->base.sk);
        max_data = SCTP_TRUNC4(max_data);
 
        /* If the the peer requested that we authenticate DATA chunks
index e35d4f73d2dffffea6d966714ce7f011b3fdeae5..30a05a80262e650ad4b1d237fe3d157a89f52b2f 100644 (file)
@@ -427,6 +427,41 @@ static void sctp_v6_copy_addrlist(struct list_head *addrlist,
        rcu_read_unlock();
 }
 
+/* Copy over any ip options */
+static void sctp_v6_copy_ip_options(struct sock *sk, struct sock *newsk)
+{
+       struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
+       struct ipv6_txoptions *opt;
+
+       newnp = inet6_sk(newsk);
+
+       rcu_read_lock();
+       opt = rcu_dereference(np->opt);
+       if (opt) {
+               opt = ipv6_dup_options(newsk, opt);
+               if (!opt)
+                       pr_err("%s: Failed to copy ip options\n", __func__);
+       }
+       RCU_INIT_POINTER(newnp->opt, opt);
+       rcu_read_unlock();
+}
+
+/* Account for the IP options */
+static int sctp_v6_ip_options_len(struct sock *sk)
+{
+       struct ipv6_pinfo *np = inet6_sk(sk);
+       struct ipv6_txoptions *opt;
+       int len = 0;
+
+       rcu_read_lock();
+       opt = rcu_dereference(np->opt);
+       if (opt)
+               len = opt->opt_flen + opt->opt_nflen;
+
+       rcu_read_unlock();
+       return len;
+}
+
 /* Initialize a sockaddr_storage from in incoming skb. */
 static void sctp_v6_from_skb(union sctp_addr *addr, struct sk_buff *skb,
                             int is_saddr)
@@ -666,7 +701,6 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
        struct sock *newsk;
        struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
        struct sctp6_sock *newsctp6sk;
-       struct ipv6_txoptions *opt;
 
        newsk = sk_alloc(sock_net(sk), PF_INET6, GFP_KERNEL, sk->sk_prot, kern);
        if (!newsk)
@@ -689,12 +723,7 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
        newnp->ipv6_ac_list = NULL;
        newnp->ipv6_fl_list = NULL;
 
-       rcu_read_lock();
-       opt = rcu_dereference(np->opt);
-       if (opt)
-               opt = ipv6_dup_options(newsk, opt);
-       RCU_INIT_POINTER(newnp->opt, opt);
-       rcu_read_unlock();
+       sctp_v6_copy_ip_options(sk, newsk);
 
        /* Initialize sk's sport, dport, rcv_saddr and daddr for getsockname()
         * and getpeername().
@@ -1041,6 +1070,7 @@ static struct sctp_af sctp_af_inet6 = {
        .ecn_capable       = sctp_v6_ecn_capable,
        .net_header_len    = sizeof(struct ipv6hdr),
        .sockaddr_len      = sizeof(struct sockaddr_in6),
+       .ip_options_len    = sctp_v6_ip_options_len,
 #ifdef CONFIG_COMPAT
        .compat_setsockopt = compat_ipv6_setsockopt,
        .compat_getsockopt = compat_ipv6_getsockopt,
@@ -1059,6 +1089,7 @@ static struct sctp_pf sctp_pf_inet6 = {
        .addr_to_user  = sctp_v6_addr_to_user,
        .to_sk_saddr   = sctp_v6_to_sk_saddr,
        .to_sk_daddr   = sctp_v6_to_sk_daddr,
+       .copy_ip_options = sctp_v6_copy_ip_options,
        .af            = &sctp_af_inet6,
 };
 
index 01a26ee051e3878ced4253429b5017708d0c138f..a58d13c2d4439982d20db28045c2b43a9f308006 100644 (file)
@@ -69,7 +69,11 @@ static enum sctp_xmit sctp_packet_will_fit(struct sctp_packet *packet,
 
 static void sctp_packet_reset(struct sctp_packet *packet)
 {
+       /* sctp_packet_transmit() relies on this to reset size to the
+        * current overhead after sending packets.
+        */
        packet->size = packet->overhead;
+
        packet->has_cookie_echo = 0;
        packet->has_sack = 0;
        packet->has_data = 0;
@@ -87,6 +91,7 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
        struct sctp_transport *tp = packet->transport;
        struct sctp_association *asoc = tp->asoc;
        struct sock *sk;
+       size_t overhead = sizeof(struct ipv6hdr) + sizeof(struct sctphdr);
 
        pr_debug("%s: packet:%p vtag:0x%x\n", __func__, packet, vtag);
        packet->vtag = vtag;
@@ -95,10 +100,22 @@ void sctp_packet_config(struct sctp_packet *packet, __u32 vtag,
        if (!sctp_packet_empty(packet))
                return;
 
-       /* set packet max_size with pathmtu */
+       /* set packet max_size with pathmtu, then calculate overhead */
        packet->max_size = tp->pathmtu;
-       if (!asoc)
+       if (asoc) {
+               struct sctp_sock *sp = sctp_sk(asoc->base.sk);
+               struct sctp_af *af = sp->pf->af;
+
+               overhead = af->net_header_len +
+                          af->ip_options_len(asoc->base.sk);
+               overhead += sizeof(struct sctphdr);
+               packet->overhead = overhead;
+               packet->size = overhead;
+       } else {
+               packet->overhead = overhead;
+               packet->size = overhead;
                return;
+       }
 
        /* update dst or transport pathmtu if in need */
        sk = asoc->base.sk;
@@ -140,23 +157,14 @@ void sctp_packet_init(struct sctp_packet *packet,
                      struct sctp_transport *transport,
                      __u16 sport, __u16 dport)
 {
-       struct sctp_association *asoc = transport->asoc;
-       size_t overhead;
-
        pr_debug("%s: packet:%p transport:%p\n", __func__, packet, transport);
 
        packet->transport = transport;
        packet->source_port = sport;
        packet->destination_port = dport;
        INIT_LIST_HEAD(&packet->chunk_list);
-       if (asoc) {
-               struct sctp_sock *sp = sctp_sk(asoc->base.sk);
-               overhead = sp->pf->af->net_header_len;
-       } else {
-               overhead = sizeof(struct ipv6hdr);
-       }
-       overhead += sizeof(struct sctphdr);
-       packet->overhead = overhead;
+       /* The overhead will be calculated by sctp_packet_config() */
+       packet->overhead = 0;
        sctp_packet_reset(packet);
        packet->vtag = 0;
 }
index 91813e686c6737aaf20ca1ea2f3978a833c5eff6..02f23ad7160c07057d62729c2dfe96bd7088dcd8 100644 (file)
@@ -237,6 +237,45 @@ int sctp_copy_local_addr_list(struct net *net, struct sctp_bind_addr *bp,
        return error;
 }
 
+/* Copy over any ip options */
+static void sctp_v4_copy_ip_options(struct sock *sk, struct sock *newsk)
+{
+       struct inet_sock *newinet, *inet = inet_sk(sk);
+       struct ip_options_rcu *inet_opt, *newopt = NULL;
+
+       newinet = inet_sk(newsk);
+
+       rcu_read_lock();
+       inet_opt = rcu_dereference(inet->inet_opt);
+       if (inet_opt) {
+               newopt = sock_kmalloc(newsk, sizeof(*inet_opt) +
+                                     inet_opt->opt.optlen, GFP_ATOMIC);
+               if (newopt)
+                       memcpy(newopt, inet_opt, sizeof(*inet_opt) +
+                              inet_opt->opt.optlen);
+               else
+                       pr_err("%s: Failed to copy ip options\n", __func__);
+       }
+       RCU_INIT_POINTER(newinet->inet_opt, newopt);
+       rcu_read_unlock();
+}
+
+/* Account for the IP options */
+static int sctp_v4_ip_options_len(struct sock *sk)
+{
+       struct inet_sock *inet = inet_sk(sk);
+       struct ip_options_rcu *inet_opt;
+       int len = 0;
+
+       rcu_read_lock();
+       inet_opt = rcu_dereference(inet->inet_opt);
+       if (inet_opt)
+               len = inet_opt->opt.optlen;
+
+       rcu_read_unlock();
+       return len;
+}
+
 /* Initialize a sctp_addr from in incoming skb.  */
 static void sctp_v4_from_skb(union sctp_addr *addr, struct sk_buff *skb,
                             int is_saddr)
@@ -588,6 +627,8 @@ static struct sock *sctp_v4_create_accept_sk(struct sock *sk,
        sctp_copy_sock(newsk, sk, asoc);
        sock_reset_flag(newsk, SOCK_ZAPPED);
 
+       sctp_v4_copy_ip_options(sk, newsk);
+
        newinet = inet_sk(newsk);
 
        newinet->inet_daddr = asoc->peer.primary_addr.v4.sin_addr.s_addr;
@@ -1006,6 +1047,7 @@ static struct sctp_pf sctp_pf_inet = {
        .addr_to_user  = sctp_v4_addr_to_user,
        .to_sk_saddr   = sctp_v4_to_sk_saddr,
        .to_sk_daddr   = sctp_v4_to_sk_daddr,
+       .copy_ip_options = sctp_v4_copy_ip_options,
        .af            = &sctp_af_inet
 };
 
@@ -1090,6 +1132,7 @@ static struct sctp_af sctp_af_inet = {
        .ecn_capable       = sctp_v4_ecn_capable,
        .net_header_len    = sizeof(struct iphdr),
        .sockaddr_len      = sizeof(struct sockaddr_in),
+       .ip_options_len    = sctp_v4_ip_options_len,
 #ifdef CONFIG_COMPAT
        .compat_setsockopt = compat_ip_setsockopt,
        .compat_getsockopt = compat_ip_getsockopt,
index bf271f8c2dc9b28c992f9be3bc9b6a7bfa5fdb82..eb55c63d1990f9245c82d223d2730a672f21f581 100644 (file)
@@ -3138,6 +3138,7 @@ static int sctp_setsockopt_mappedv4(struct sock *sk, char __user *optval, unsign
 static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned int optlen)
 {
        struct sctp_sock *sp = sctp_sk(sk);
+       struct sctp_af *af = sp->pf->af;
        struct sctp_assoc_value params;
        struct sctp_association *asoc;
        int val;
@@ -3162,7 +3163,8 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
        if (val) {
                int min_len, max_len;
 
-               min_len = SCTP_DEFAULT_MINSEGMENT - sp->pf->af->net_header_len;
+               min_len = SCTP_DEFAULT_MINSEGMENT - af->net_header_len;
+               min_len -= af->ip_options_len(sk);
                min_len -= sizeof(struct sctphdr) +
                           sizeof(struct sctp_data_chunk);
 
@@ -3175,7 +3177,8 @@ static int sctp_setsockopt_maxseg(struct sock *sk, char __user *optval, unsigned
        asoc = sctp_id2assoc(sk, params.assoc_id);
        if (asoc) {
                if (val == 0) {
-                       val = asoc->pathmtu - sp->pf->af->net_header_len;
+                       val = asoc->pathmtu - af->net_header_len;
+                       val -= af->ip_options_len(sk);
                        val -= sizeof(struct sctphdr) +
                               sctp_datachk_len(&asoc->stream);
                }
@@ -5087,9 +5090,11 @@ int sctp_do_peeloff(struct sock *sk, sctp_assoc_t id, struct socket **sockp)
        sctp_copy_sock(sock->sk, sk, asoc);
 
        /* Make peeled-off sockets more like 1-1 accepted sockets.
-        * Set the daddr and initialize id to something more random
+        * Set the daddr and initialize id to something more random and also
+        * copy over any ip options.
         */
        sp->pf->to_sk_daddr(&asoc->peer.primary_addr, sk);
+       sp->pf->copy_ip_options(sk, sock->sk);
 
        /* Populate the fields of the newsk from the oldsk and migrate the
         * asoc to the newsk.