rxrpc: Provide kernel interface to set max lifespan on a call
[sfrench/cifs-2.6.git] / net / xfrm / xfrm_output.c
index 9333153bafda9052c6c68f257ae0d2685d3d5441..a55510f9ff35a711af26e416c6168370fac04ac6 100644 (file)
 #include <linux/slab.h>
 #include <linux/spinlock.h>
 #include <net/dst.h>
+#include <net/inet_ecn.h>
 #include <net/xfrm.h>
 
+#include "xfrm_inout.h"
+
 static int xfrm_output2(struct net *net, struct sock *sk, struct sk_buff *skb);
+static int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb);
 
 static int xfrm_skb_check_space(struct sk_buff *skb)
 {
@@ -50,6 +54,360 @@ static struct dst_entry *skb_dst_pop(struct sk_buff *skb)
        return child;
 }
 
+/* Add encapsulation header.
+ *
+ * The IP header will be moved forward to make space for the encapsulation
+ * header.
+ */
+static int xfrm4_transport_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+       struct iphdr *iph = ip_hdr(skb);
+       int ihl = iph->ihl * 4;
+
+       skb_set_inner_transport_header(skb, skb_transport_offset(skb));
+
+       skb_set_network_header(skb, -x->props.header_len);
+       skb->mac_header = skb->network_header +
+                         offsetof(struct iphdr, protocol);
+       skb->transport_header = skb->network_header + ihl;
+       __skb_pull(skb, ihl);
+       memmove(skb_network_header(skb), iph, ihl);
+       return 0;
+}
+
+/* Add encapsulation header.
+ *
+ * The IP header and mutable extension headers will be moved forward to make
+ * space for the encapsulation header.
+ */
+static int xfrm6_transport_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       struct ipv6hdr *iph;
+       u8 *prevhdr;
+       int hdr_len;
+
+       iph = ipv6_hdr(skb);
+       skb_set_inner_transport_header(skb, skb_transport_offset(skb));
+
+       hdr_len = x->type->hdr_offset(x, skb, &prevhdr);
+       if (hdr_len < 0)
+               return hdr_len;
+       skb_set_mac_header(skb,
+                          (prevhdr - x->props.header_len) - skb->data);
+       skb_set_network_header(skb, -x->props.header_len);
+       skb->transport_header = skb->network_header + hdr_len;
+       __skb_pull(skb, hdr_len);
+       memmove(ipv6_hdr(skb), iph, hdr_len);
+       return 0;
+#else
+       WARN_ON_ONCE(1);
+       return -EAFNOSUPPORT;
+#endif
+}
+
+/* Add route optimization header space.
+ *
+ * The IP header and mutable extension headers will be moved forward to make
+ * space for the route optimization header.
+ */
+static int xfrm6_ro_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       struct ipv6hdr *iph;
+       u8 *prevhdr;
+       int hdr_len;
+
+       iph = ipv6_hdr(skb);
+
+       hdr_len = x->type->hdr_offset(x, skb, &prevhdr);
+       if (hdr_len < 0)
+               return hdr_len;
+       skb_set_mac_header(skb,
+                          (prevhdr - x->props.header_len) - skb->data);
+       skb_set_network_header(skb, -x->props.header_len);
+       skb->transport_header = skb->network_header + hdr_len;
+       __skb_pull(skb, hdr_len);
+       memmove(ipv6_hdr(skb), iph, hdr_len);
+
+       x->lastused = ktime_get_real_seconds();
+
+       return 0;
+#else
+       WARN_ON_ONCE(1);
+       return -EAFNOSUPPORT;
+#endif
+}
+
+/* Add encapsulation header.
+ *
+ * The top IP header will be constructed per draft-nikander-esp-beet-mode-06.txt.
+ */
+static int xfrm4_beet_encap_add(struct xfrm_state *x, struct sk_buff *skb)
+{
+       struct ip_beet_phdr *ph;
+       struct iphdr *top_iph;
+       int hdrlen, optlen;
+
+       hdrlen = 0;
+       optlen = XFRM_MODE_SKB_CB(skb)->optlen;
+       if (unlikely(optlen))
+               hdrlen += IPV4_BEET_PHMAXLEN - (optlen & 4);
+
+       skb_set_network_header(skb, -x->props.header_len - hdrlen +
+                              (XFRM_MODE_SKB_CB(skb)->ihl - sizeof(*top_iph)));
+       if (x->sel.family != AF_INET6)
+               skb->network_header += IPV4_BEET_PHMAXLEN;
+       skb->mac_header = skb->network_header +
+                         offsetof(struct iphdr, protocol);
+       skb->transport_header = skb->network_header + sizeof(*top_iph);
+
+       xfrm4_beet_make_header(skb);
+
+       ph = __skb_pull(skb, XFRM_MODE_SKB_CB(skb)->ihl - hdrlen);
+
+       top_iph = ip_hdr(skb);
+
+       if (unlikely(optlen)) {
+               if (WARN_ON(optlen < 0))
+                       return -EINVAL;
+
+               ph->padlen = 4 - (optlen & 4);
+               ph->hdrlen = optlen / 8;
+               ph->nexthdr = top_iph->protocol;
+               if (ph->padlen)
+                       memset(ph + 1, IPOPT_NOP, ph->padlen);
+
+               top_iph->protocol = IPPROTO_BEETPH;
+               top_iph->ihl = sizeof(struct iphdr) / 4;
+       }
+
+       top_iph->saddr = x->props.saddr.a4;
+       top_iph->daddr = x->id.daddr.a4;
+
+       return 0;
+}
+
+/* Add encapsulation header.
+ *
+ * The top IP header will be constructed per RFC 2401.
+ */
+static int xfrm4_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
+{
+       struct dst_entry *dst = skb_dst(skb);
+       struct iphdr *top_iph;
+       int flags;
+
+       skb_set_inner_network_header(skb, skb_network_offset(skb));
+       skb_set_inner_transport_header(skb, skb_transport_offset(skb));
+
+       skb_set_network_header(skb, -x->props.header_len);
+       skb->mac_header = skb->network_header +
+                         offsetof(struct iphdr, protocol);
+       skb->transport_header = skb->network_header + sizeof(*top_iph);
+       top_iph = ip_hdr(skb);
+
+       top_iph->ihl = 5;
+       top_iph->version = 4;
+
+       top_iph->protocol = xfrm_af2proto(skb_dst(skb)->ops->family);
+
+       /* DS disclosing depends on XFRM_SA_XFLAG_DONT_ENCAP_DSCP */
+       if (x->props.extra_flags & XFRM_SA_XFLAG_DONT_ENCAP_DSCP)
+               top_iph->tos = 0;
+       else
+               top_iph->tos = XFRM_MODE_SKB_CB(skb)->tos;
+       top_iph->tos = INET_ECN_encapsulate(top_iph->tos,
+                                           XFRM_MODE_SKB_CB(skb)->tos);
+
+       flags = x->props.flags;
+       if (flags & XFRM_STATE_NOECN)
+               IP_ECN_clear(top_iph);
+
+       top_iph->frag_off = (flags & XFRM_STATE_NOPMTUDISC) ?
+               0 : (XFRM_MODE_SKB_CB(skb)->frag_off & htons(IP_DF));
+
+       top_iph->ttl = ip4_dst_hoplimit(xfrm_dst_child(dst));
+
+       top_iph->saddr = x->props.saddr.a4;
+       top_iph->daddr = x->id.daddr.a4;
+       ip_select_ident(dev_net(dst->dev), skb, NULL);
+
+       return 0;
+}
+
+#if IS_ENABLED(CONFIG_IPV6)
+static int xfrm6_tunnel_encap_add(struct xfrm_state *x, struct sk_buff *skb)
+{
+       struct dst_entry *dst = skb_dst(skb);
+       struct ipv6hdr *top_iph;
+       int dsfield;
+
+       skb_set_inner_network_header(skb, skb_network_offset(skb));
+       skb_set_inner_transport_header(skb, skb_transport_offset(skb));
+
+       skb_set_network_header(skb, -x->props.header_len);
+       skb->mac_header = skb->network_header +
+                         offsetof(struct ipv6hdr, nexthdr);
+       skb->transport_header = skb->network_header + sizeof(*top_iph);
+       top_iph = ipv6_hdr(skb);
+
+       top_iph->version = 6;
+
+       memcpy(top_iph->flow_lbl, XFRM_MODE_SKB_CB(skb)->flow_lbl,
+              sizeof(top_iph->flow_lbl));
+       top_iph->nexthdr = xfrm_af2proto(skb_dst(skb)->ops->family);
+
+       if (x->props.extra_flags & XFRM_SA_XFLAG_DONT_ENCAP_DSCP)
+               dsfield = 0;
+       else
+               dsfield = XFRM_MODE_SKB_CB(skb)->tos;
+       dsfield = INET_ECN_encapsulate(dsfield, XFRM_MODE_SKB_CB(skb)->tos);
+       if (x->props.flags & XFRM_STATE_NOECN)
+               dsfield &= ~INET_ECN_MASK;
+       ipv6_change_dsfield(top_iph, 0, dsfield);
+       top_iph->hop_limit = ip6_dst_hoplimit(xfrm_dst_child(dst));
+       top_iph->saddr = *(struct in6_addr *)&x->props.saddr;
+       top_iph->daddr = *(struct in6_addr *)&x->id.daddr;
+       return 0;
+}
+
+static int xfrm6_beet_encap_add(struct xfrm_state *x, struct sk_buff *skb)
+{
+       struct ipv6hdr *top_iph;
+       struct ip_beet_phdr *ph;
+       int optlen, hdr_len;
+
+       hdr_len = 0;
+       optlen = XFRM_MODE_SKB_CB(skb)->optlen;
+       if (unlikely(optlen))
+               hdr_len += IPV4_BEET_PHMAXLEN - (optlen & 4);
+
+       skb_set_network_header(skb, -x->props.header_len - hdr_len);
+       if (x->sel.family != AF_INET6)
+               skb->network_header += IPV4_BEET_PHMAXLEN;
+       skb->mac_header = skb->network_header +
+                         offsetof(struct ipv6hdr, nexthdr);
+       skb->transport_header = skb->network_header + sizeof(*top_iph);
+       ph = __skb_pull(skb, XFRM_MODE_SKB_CB(skb)->ihl - hdr_len);
+
+       xfrm6_beet_make_header(skb);
+
+       top_iph = ipv6_hdr(skb);
+       if (unlikely(optlen)) {
+               if (WARN_ON(optlen < 0))
+                       return -EINVAL;
+
+               ph->padlen = 4 - (optlen & 4);
+               ph->hdrlen = optlen / 8;
+               ph->nexthdr = top_iph->nexthdr;
+               if (ph->padlen)
+                       memset(ph + 1, IPOPT_NOP, ph->padlen);
+
+               top_iph->nexthdr = IPPROTO_BEETPH;
+       }
+
+       top_iph->saddr = *(struct in6_addr *)&x->props.saddr;
+       top_iph->daddr = *(struct in6_addr *)&x->id.daddr;
+       return 0;
+}
+#endif
+
+/* Add encapsulation header.
+ *
+ * On exit, the transport header will be set to the start of the
+ * encapsulation header to be filled in by x->type->output and the mac
+ * header will be set to the nextheader (protocol for IPv4) field of the
+ * extension header directly preceding the encapsulation header, or in
+ * its absence, that of the top IP header.
+ * The value of the network header will always point to the top IP header
+ * while skb->data will point to the payload.
+ */
+static int xfrm4_prepare_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+       int err;
+
+       err = xfrm_inner_extract_output(x, skb);
+       if (err)
+               return err;
+
+       IPCB(skb)->flags |= IPSKB_XFRM_TUNNEL_SIZE;
+       skb->protocol = htons(ETH_P_IP);
+
+       switch (x->outer_mode.encap) {
+       case XFRM_MODE_BEET:
+               return xfrm4_beet_encap_add(x, skb);
+       case XFRM_MODE_TUNNEL:
+               return xfrm4_tunnel_encap_add(x, skb);
+       }
+
+       WARN_ON_ONCE(1);
+       return -EOPNOTSUPP;
+}
+
+static int xfrm6_prepare_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+#if IS_ENABLED(CONFIG_IPV6)
+       int err;
+
+       err = xfrm_inner_extract_output(x, skb);
+       if (err)
+               return err;
+
+       skb->ignore_df = 1;
+       skb->protocol = htons(ETH_P_IPV6);
+
+       switch (x->outer_mode.encap) {
+       case XFRM_MODE_BEET:
+               return xfrm6_beet_encap_add(x, skb);
+       case XFRM_MODE_TUNNEL:
+               return xfrm6_tunnel_encap_add(x, skb);
+       default:
+               WARN_ON_ONCE(1);
+               return -EOPNOTSUPP;
+       }
+#endif
+       WARN_ON_ONCE(1);
+       return -EAFNOSUPPORT;
+}
+
+static int xfrm_outer_mode_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+       switch (x->outer_mode.encap) {
+       case XFRM_MODE_BEET:
+       case XFRM_MODE_TUNNEL:
+               if (x->outer_mode.family == AF_INET)
+                       return xfrm4_prepare_output(x, skb);
+               if (x->outer_mode.family == AF_INET6)
+                       return xfrm6_prepare_output(x, skb);
+               break;
+       case XFRM_MODE_TRANSPORT:
+               if (x->outer_mode.family == AF_INET)
+                       return xfrm4_transport_output(x, skb);
+               if (x->outer_mode.family == AF_INET6)
+                       return xfrm6_transport_output(x, skb);
+               break;
+       case XFRM_MODE_ROUTEOPTIMIZATION:
+               if (x->outer_mode.family == AF_INET6)
+                       return xfrm6_ro_output(x, skb);
+               WARN_ON_ONCE(1);
+               break;
+       default:
+               WARN_ON_ONCE(1);
+               break;
+       }
+
+       return -EOPNOTSUPP;
+}
+
+#if IS_ENABLED(CONFIG_NET_PKTGEN)
+int pktgen_xfrm_outer_mode_output(struct xfrm_state *x, struct sk_buff *skb)
+{
+       return xfrm_outer_mode_output(x, skb);
+}
+EXPORT_SYMBOL_GPL(pktgen_xfrm_outer_mode_output);
+#endif
+
 static int xfrm_output_one(struct sk_buff *skb, int err)
 {
        struct dst_entry *dst = skb_dst(skb);
@@ -68,7 +426,7 @@ static int xfrm_output_one(struct sk_buff *skb, int err)
 
                skb->mark = xfrm_smark_get(skb->mark, x);
 
-               err = x->outer_mode->output(x, skb);
+               err = xfrm_outer_mode_output(x, skb);
                if (err) {
                        XFRM_INC_STATS(net, LINUX_MIB_XFRMOUTSTATEMODEERROR);
                        goto error_nolock;
@@ -131,7 +489,7 @@ resume:
                }
                skb_dst_set(skb, dst);
                x = dst->xfrm;
-       } while (x && !(x->outer_mode->flags & XFRM_MODE_FLAG_TUNNEL));
+       } while (x && !(x->outer_mode.flags & XFRM_MODE_FLAG_TUNNEL));
 
        return 0;
 
@@ -258,20 +616,29 @@ out:
 }
 EXPORT_SYMBOL_GPL(xfrm_output);
 
-int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb)
+static int xfrm_inner_extract_output(struct xfrm_state *x, struct sk_buff *skb)
 {
-       struct xfrm_mode *inner_mode;
+       const struct xfrm_state_afinfo *afinfo;
+       const struct xfrm_mode *inner_mode;
+       int err = -EAFNOSUPPORT;
+
        if (x->sel.family == AF_UNSPEC)
                inner_mode = xfrm_ip2inner_mode(x,
                                xfrm_af2proto(skb_dst(skb)->ops->family));
        else
-               inner_mode = x->inner_mode;
+               inner_mode = &x->inner_mode;
 
        if (inner_mode == NULL)
                return -EAFNOSUPPORT;
-       return inner_mode->afinfo->extract_output(x, skb);
+
+       rcu_read_lock();
+       afinfo = xfrm_state_afinfo_get_rcu(inner_mode->family);
+       if (likely(afinfo))
+               err = afinfo->extract_output(x, skb);
+       rcu_read_unlock();
+
+       return err;
 }
-EXPORT_SYMBOL_GPL(xfrm_inner_extract_output);
 
 void xfrm_local_error(struct sk_buff *skb, int mtu)
 {