};
 
 static const struct nla_policy compat_policy[XFRMA_MAX+1] = {
+       [XFRMA_UNSPEC]          = { .strict_start_type = XFRMA_SA_DIR },
        [XFRMA_SA]              = { .len = XMSGSIZE(compat_xfrm_usersa_info)},
        [XFRMA_POLICY]          = { .len = XMSGSIZE(compat_xfrm_userpolicy_info)},
        [XFRMA_LASTUSED]        = { .type = NLA_U64},
        [XFRMA_SET_MARK_MASK]   = { .type = NLA_U32 },
        [XFRMA_IF_ID]           = { .type = NLA_U32 },
        [XFRMA_MTIMER_THRESH]   = { .type = NLA_U32 },
+       [XFRMA_SA_DIR]          = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
 };
 
 static struct nlmsghdr *xfrm_nlmsg_put_compat(struct sk_buff *skb,
        case XFRMA_SET_MARK_MASK:
        case XFRMA_IF_ID:
        case XFRMA_MTIMER_THRESH:
+       case XFRMA_SA_DIR:
                return xfrm_nla_cpy(dst, src, nla_len(src));
        default:
-               BUILD_BUG_ON(XFRMA_MAX != XFRMA_MTIMER_THRESH);
+               BUILD_BUG_ON(XFRMA_MAX != XFRMA_SA_DIR);
                pr_warn_once("unsupported nla_type %d\n", src->nla_type);
                return -EOPNOTSUPP;
        }
        int err;
 
        if (type > XFRMA_MAX) {
-               BUILD_BUG_ON(XFRMA_MAX != XFRMA_MTIMER_THRESH);
+               BUILD_BUG_ON(XFRMA_MAX != XFRMA_SA_DIR);
                NL_SET_ERR_MSG(extack, "Bad attribute");
                return -EOPNOTSUPP;
        }
 
 }
 
 static inline int verify_replay(struct xfrm_usersa_info *p,
-                               struct nlattr **attrs,
+                               struct nlattr **attrs, u8 sa_dir,
                                struct netlink_ext_ack *extack)
 {
        struct nlattr *rt = attrs[XFRMA_REPLAY_ESN_VAL];
                return -EINVAL;
        }
 
+       if (sa_dir == XFRM_SA_DIR_OUT)  {
+               if (rs->replay_window) {
+                       NL_SET_ERR_MSG(extack, "Replay window should be 0 for output SA");
+                       return -EINVAL;
+               }
+               if (rs->seq || rs->seq_hi) {
+                       NL_SET_ERR_MSG(extack,
+                                      "Replay seq and seq_hi should be 0 for output SA");
+                       return -EINVAL;
+               }
+               if (rs->bmp_len) {
+                       NL_SET_ERR_MSG(extack, "Replay bmp_len should 0 for output SA");
+                       return -EINVAL;
+               }
+       }
+
+       if (sa_dir == XFRM_SA_DIR_IN)  {
+               if (rs->oseq || rs->oseq_hi) {
+                       NL_SET_ERR_MSG(extack,
+                                      "Replay oseq and oseq_hi should be 0 for input SA");
+                       return -EINVAL;
+               }
+       }
+
        return 0;
 }
 
                             struct netlink_ext_ack *extack)
 {
        int err;
+       u8 sa_dir = attrs[XFRMA_SA_DIR] ? nla_get_u8(attrs[XFRMA_SA_DIR]) : 0;
 
        err = -EINVAL;
        switch (p->family) {
                goto out;
        if ((err = verify_sec_ctx_len(attrs, extack)))
                goto out;
-       if ((err = verify_replay(p, attrs, extack)))
+       if ((err = verify_replay(p, attrs, sa_dir, extack)))
                goto out;
 
        err = -EINVAL;
                        err = -EINVAL;
                        goto out;
                }
+
+               if (sa_dir == XFRM_SA_DIR_OUT) {
+                       NL_SET_ERR_MSG(extack,
+                                      "MTIMER_THRESH attribute should not be set on output SA");
+                       err = -EINVAL;
+                       goto out;
+               }
+       }
+
+       if (sa_dir == XFRM_SA_DIR_OUT) {
+               if (p->flags & XFRM_STATE_DECAP_DSCP) {
+                       NL_SET_ERR_MSG(extack, "Flag DECAP_DSCP should not be set for output SA");
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (p->flags & XFRM_STATE_ICMP) {
+                       NL_SET_ERR_MSG(extack, "Flag ICMP should not be set for output SA");
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (p->flags & XFRM_STATE_WILDRECV) {
+                       NL_SET_ERR_MSG(extack, "Flag WILDRECV should not be set for output SA");
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (p->replay_window) {
+                       NL_SET_ERR_MSG(extack, "Replay window should be 0 for output SA");
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (attrs[XFRMA_REPLAY_VAL]) {
+                       struct xfrm_replay_state *replay;
+
+                       replay = nla_data(attrs[XFRMA_REPLAY_VAL]);
+
+                       if (replay->seq || replay->bitmap) {
+                               NL_SET_ERR_MSG(extack,
+                                              "Replay seq and bitmap should be 0 for output SA");
+                               err = -EINVAL;
+                               goto out;
+                       }
+               }
+       }
+
+       if (sa_dir == XFRM_SA_DIR_IN) {
+               if (p->flags & XFRM_STATE_NOPMTUDISC) {
+                       NL_SET_ERR_MSG(extack, "Flag NOPMTUDISC should not be set for input SA");
+                       err = -EINVAL;
+                       goto out;
+               }
+
+               if (attrs[XFRMA_SA_EXTRA_FLAGS]) {
+                       u32 xflags = nla_get_u32(attrs[XFRMA_SA_EXTRA_FLAGS]);
+
+                       if (xflags & XFRM_SA_XFLAG_DONT_ENCAP_DSCP) {
+                               NL_SET_ERR_MSG(extack, "Flag DONT_ENCAP_DSCP should not be set for input SA");
+                               err = -EINVAL;
+                               goto out;
+                       }
+
+                       if (xflags & XFRM_SA_XFLAG_OSEQ_MAY_WRAP) {
+                               NL_SET_ERR_MSG(extack, "Flag OSEQ_MAY_WRAP should not be set for input SA");
+                               err = -EINVAL;
+                               goto out;
+                       }
+
+               }
        }
 
 out:
        if (attrs[XFRMA_IF_ID])
                x->if_id = nla_get_u32(attrs[XFRMA_IF_ID]);
 
+       if (attrs[XFRMA_SA_DIR])
+               x->dir = nla_get_u8(attrs[XFRMA_SA_DIR]);
+
        err = __xfrm_init_state(x, false, attrs[XFRMA_OFFLOAD_DEV], extack);
        if (err)
                goto error;
                if (ret)
                        goto out;
        }
-       if (x->mapping_maxage)
+       if (x->mapping_maxage) {
                ret = nla_put_u32(skb, XFRMA_MTIMER_THRESH, x->mapping_maxage);
+               if (ret)
+                       goto out;
+       }
+       if (x->dir)
+               ret = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
 out:
        return ret;
 }
        if (err)
                goto out;
 
+       if (attrs[XFRMA_SA_DIR])
+               x->dir = nla_get_u8(attrs[XFRMA_SA_DIR]);
+
        resp_skb = xfrm_state_netlink(skb, x, nlh->nlmsg_seq);
        if (IS_ERR(resp_skb)) {
                err = PTR_ERR(resp_skb);
               + nla_total_size_64bit(sizeof(struct xfrm_lifetime_cur))
               + nla_total_size(sizeof(struct xfrm_mark))
               + nla_total_size(4) /* XFRM_AE_RTHR */
-              + nla_total_size(4); /* XFRM_AE_ETHR */
+              + nla_total_size(4) /* XFRM_AE_ETHR */
+              + nla_total_size(sizeof(x->dir)); /* XFRMA_SA_DIR */
 }
 
 static int build_aevent(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
        if (err)
                goto out_cancel;
 
+       if (x->dir) {
+               err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
+               if (err)
+                       goto out_cancel;
+       }
+
        nlmsg_end(skb, nlh);
        return 0;
 
 #undef XMSGSIZE
 
 const struct nla_policy xfrma_policy[XFRMA_MAX+1] = {
+       [XFRMA_UNSPEC]          = { .strict_start_type = XFRMA_SA_DIR },
        [XFRMA_SA]              = { .len = sizeof(struct xfrm_usersa_info)},
        [XFRMA_POLICY]          = { .len = sizeof(struct xfrm_userpolicy_info)},
        [XFRMA_LASTUSED]        = { .type = NLA_U64},
        [XFRMA_SET_MARK_MASK]   = { .type = NLA_U32 },
        [XFRMA_IF_ID]           = { .type = NLA_U32 },
        [XFRMA_MTIMER_THRESH]   = { .type = NLA_U32 },
+       [XFRMA_SA_DIR]          = NLA_POLICY_RANGE(NLA_U8, XFRM_SA_DIR_IN, XFRM_SA_DIR_OUT),
 };
 EXPORT_SYMBOL_GPL(xfrma_policy);
 
 
 static inline unsigned int xfrm_expire_msgsize(void)
 {
-       return NLMSG_ALIGN(sizeof(struct xfrm_user_expire))
-              + nla_total_size(sizeof(struct xfrm_mark));
+       return NLMSG_ALIGN(sizeof(struct xfrm_user_expire)) +
+              nla_total_size(sizeof(struct xfrm_mark)) +
+              nla_total_size(sizeof_field(struct xfrm_state, dir));
 }
 
 static int build_expire(struct sk_buff *skb, struct xfrm_state *x, const struct km_event *c)
        if (err)
                return err;
 
+       if (x->dir) {
+               err = nla_put_u8(skb, XFRMA_SA_DIR, x->dir);
+               if (err)
+                       return err;
+       }
+
        nlmsg_end(skb, nlh);
        return 0;
 }
        if (x->mapping_maxage)
                l += nla_total_size(sizeof(x->mapping_maxage));
 
+       if (x->dir)
+               l += nla_total_size(sizeof(x->dir));
+
        return l;
 }