arm: configs: linux: enable wifi load driver when boot kernel
[firefly-linux-kernel-4.4.55.git] / net / ipv4 / inet_diag.c
index ab9f8a66615d0872b586a0c2745196c58f6026be..fcb83b2a61f00681e1d918c0ec862c051d537e71 100644 (file)
@@ -44,6 +44,8 @@ struct inet_diag_entry {
        u16 dport;
        u16 family;
        u16 userlocks;
+       u32 ifindex;
+       u32 mark;
 };
 
 static DEFINE_MUTEX(inet_diag_table_mutex);
@@ -96,6 +98,7 @@ static size_t inet_sk_attr_size(void)
                + nla_total_size(1) /* INET_DIAG_SHUTDOWN */
                + nla_total_size(1) /* INET_DIAG_TOS */
                + nla_total_size(1) /* INET_DIAG_TCLASS */
+               + nla_total_size(4) /* INET_DIAG_MARK */
                + nla_total_size(sizeof(struct inet_diag_meminfo))
                + nla_total_size(sizeof(struct inet_diag_msg))
                + nla_total_size(SK_MEMINFO_VARS * sizeof(u32))
@@ -108,7 +111,8 @@ int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
                      struct sk_buff *skb, const struct inet_diag_req_v2 *req,
                      struct user_namespace *user_ns,
                      u32 portid, u32 seq, u16 nlmsg_flags,
-                     const struct nlmsghdr *unlh)
+                     const struct nlmsghdr *unlh,
+                     bool net_admin)
 {
        const struct inet_sock *inet = inet_sk(sk);
        const struct tcp_congestion_ops *ca_ops;
@@ -158,6 +162,9 @@ int inet_sk_diag_fill(struct sock *sk, struct inet_connection_sock *icsk,
        }
 #endif
 
+       if (net_admin && nla_put_u32(skb, INET_DIAG_MARK, sk->sk_mark))
+               goto errout;
+
        r->idiag_uid = from_kuid_munged(user_ns, sock_i_uid(sk));
        r->idiag_inode = sock_i_ino(sk);
 
@@ -256,10 +263,11 @@ static int inet_csk_diag_fill(struct sock *sk,
                              const struct inet_diag_req_v2 *req,
                              struct user_namespace *user_ns,
                              u32 portid, u32 seq, u16 nlmsg_flags,
-                             const struct nlmsghdr *unlh)
+                             const struct nlmsghdr *unlh,
+                             bool net_admin)
 {
-       return inet_sk_diag_fill(sk, inet_csk(sk), skb, req,
-                                user_ns, portid, seq, nlmsg_flags, unlh);
+       return inet_sk_diag_fill(sk, inet_csk(sk), skb, req, user_ns,
+                                portid, seq, nlmsg_flags, unlh, net_admin);
 }
 
 static int inet_twsk_diag_fill(struct sock *sk,
@@ -301,8 +309,9 @@ static int inet_twsk_diag_fill(struct sock *sk,
 
 static int inet_req_diag_fill(struct sock *sk, struct sk_buff *skb,
                              u32 portid, u32 seq, u16 nlmsg_flags,
-                             const struct nlmsghdr *unlh)
+                             const struct nlmsghdr *unlh, bool net_admin)
 {
+       struct request_sock *reqsk = inet_reqsk(sk);
        struct inet_diag_msg *r;
        struct nlmsghdr *nlh;
        long tmo;
@@ -316,7 +325,7 @@ static int inet_req_diag_fill(struct sock *sk, struct sk_buff *skb,
        inet_diag_msg_common_fill(r, sk);
        r->idiag_state = TCP_SYN_RECV;
        r->idiag_timer = 1;
-       r->idiag_retrans = inet_reqsk(sk)->num_retrans;
+       r->idiag_retrans = reqsk->num_retrans;
 
        BUILD_BUG_ON(offsetof(struct inet_request_sock, ir_cookie) !=
                     offsetof(struct sock, sk_cookie));
@@ -328,6 +337,10 @@ static int inet_req_diag_fill(struct sock *sk, struct sk_buff *skb,
        r->idiag_uid    = 0;
        r->idiag_inode  = 0;
 
+       if (net_admin && nla_put_u32(skb, INET_DIAG_MARK,
+                                    inet_rsk(reqsk)->ir_mark))
+               return -EMSGSIZE;
+
        nlmsg_end(skb, nlh);
        return 0;
 }
@@ -336,7 +349,7 @@ static int sk_diag_fill(struct sock *sk, struct sk_buff *skb,
                        const struct inet_diag_req_v2 *r,
                        struct user_namespace *user_ns,
                        u32 portid, u32 seq, u16 nlmsg_flags,
-                       const struct nlmsghdr *unlh)
+                       const struct nlmsghdr *unlh, bool net_admin)
 {
        if (sk->sk_state == TCP_TIME_WAIT)
                return inet_twsk_diag_fill(sk, skb, portid, seq,
@@ -344,46 +357,66 @@ static int sk_diag_fill(struct sock *sk, struct sk_buff *skb,
 
        if (sk->sk_state == TCP_NEW_SYN_RECV)
                return inet_req_diag_fill(sk, skb, portid, seq,
-                                         nlmsg_flags, unlh);
+                                         nlmsg_flags, unlh, net_admin);
 
        return inet_csk_diag_fill(sk, skb, r, user_ns, portid, seq,
-                                 nlmsg_flags, unlh);
+                                 nlmsg_flags, unlh, net_admin);
 }
 
-int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
-                           struct sk_buff *in_skb,
-                           const struct nlmsghdr *nlh,
-                           const struct inet_diag_req_v2 *req)
+struct sock *inet_diag_find_one_icsk(struct net *net,
+                                    struct inet_hashinfo *hashinfo,
+                                    const struct inet_diag_req_v2 *req)
 {
-       struct net *net = sock_net(in_skb->sk);
-       struct sk_buff *rep;
        struct sock *sk;
-       int err;
 
-       err = -EINVAL;
        if (req->sdiag_family == AF_INET)
                sk = inet_lookup(net, hashinfo, req->id.idiag_dst[0],
                                 req->id.idiag_dport, req->id.idiag_src[0],
                                 req->id.idiag_sport, req->id.idiag_if);
 #if IS_ENABLED(CONFIG_IPV6)
-       else if (req->sdiag_family == AF_INET6)
-               sk = inet6_lookup(net, hashinfo,
-                                 (struct in6_addr *)req->id.idiag_dst,
-                                 req->id.idiag_dport,
-                                 (struct in6_addr *)req->id.idiag_src,
-                                 req->id.idiag_sport,
-                                 req->id.idiag_if);
+       else if (req->sdiag_family == AF_INET6) {
+               if (ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_dst) &&
+                   ipv6_addr_v4mapped((struct in6_addr *)req->id.idiag_src))
+                       sk = inet_lookup(net, hashinfo, req->id.idiag_dst[3],
+                                        req->id.idiag_dport, req->id.idiag_src[3],
+                                        req->id.idiag_sport, req->id.idiag_if);
+               else
+                       sk = inet6_lookup(net, hashinfo,
+                                         (struct in6_addr *)req->id.idiag_dst,
+                                         req->id.idiag_dport,
+                                         (struct in6_addr *)req->id.idiag_src,
+                                         req->id.idiag_sport,
+                                         req->id.idiag_if);
+       }
 #endif
        else
-               goto out_nosk;
+               return ERR_PTR(-EINVAL);
 
-       err = -ENOENT;
        if (!sk)
-               goto out_nosk;
+               return ERR_PTR(-ENOENT);
 
-       err = sock_diag_check_cookie(sk, req->id.idiag_cookie);
-       if (err)
-               goto out;
+       if (sock_diag_check_cookie(sk, req->id.idiag_cookie)) {
+               sock_gen_put(sk);
+               return ERR_PTR(-ENOENT);
+       }
+
+       return sk;
+}
+EXPORT_SYMBOL_GPL(inet_diag_find_one_icsk);
+
+int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
+                           struct sk_buff *in_skb,
+                           const struct nlmsghdr *nlh,
+                           const struct inet_diag_req_v2 *req)
+{
+       struct net *net = sock_net(in_skb->sk);
+       struct sk_buff *rep;
+       struct sock *sk;
+       int err;
+
+       sk = inet_diag_find_one_icsk(net, hashinfo, req);
+       if (IS_ERR(sk))
+               return PTR_ERR(sk);
 
        rep = nlmsg_new(inet_sk_attr_size(), GFP_KERNEL);
        if (!rep) {
@@ -394,7 +427,8 @@ int inet_diag_dump_one_icsk(struct inet_hashinfo *hashinfo,
        err = sk_diag_fill(sk, rep, req,
                           sk_user_ns(NETLINK_CB(in_skb).sk),
                           NETLINK_CB(in_skb).portid,
-                          nlh->nlmsg_seq, 0, nlh);
+                          nlh->nlmsg_seq, 0, nlh,
+                          netlink_net_capable(in_skb, CAP_NET_ADMIN));
        if (err < 0) {
                WARN_ON(err == -EMSGSIZE);
                nlmsg_free(rep);
@@ -409,12 +443,11 @@ out:
        if (sk)
                sock_gen_put(sk);
 
-out_nosk:
        return err;
 }
 EXPORT_SYMBOL_GPL(inet_diag_dump_one_icsk);
 
-static int inet_diag_get_exact(struct sk_buff *in_skb,
+static int inet_diag_cmd_exact(int cmd, struct sk_buff *in_skb,
                               const struct nlmsghdr *nlh,
                               const struct inet_diag_req_v2 *req)
 {
@@ -424,8 +457,12 @@ static int inet_diag_get_exact(struct sk_buff *in_skb,
        handler = inet_diag_lock_handler(req->sdiag_protocol);
        if (IS_ERR(handler))
                err = PTR_ERR(handler);
-       else
+       else if (cmd == SOCK_DIAG_BY_FAMILY)
                err = handler->dump_one(in_skb, nlh, req);
+       else if (cmd == SOCK_DESTROY_BACKPORT && handler->destroy)
+               err = handler->destroy(in_skb, req);
+       else
+               err = -EOPNOTSUPP;
        inet_diag_unlock_handler(handler);
 
        return err;
@@ -529,6 +566,22 @@ static int inet_diag_bc_run(const struct nlattr *_bc,
                        yes = 0;
                        break;
                }
+               case INET_DIAG_BC_DEV_COND: {
+                       u32 ifindex;
+
+                       ifindex = *((const u32 *)(op + 1));
+                       if (ifindex != entry->ifindex)
+                               yes = 0;
+                       break;
+               }
+               case INET_DIAG_BC_MARK_COND: {
+                       struct inet_diag_markcond *cond;
+
+                       cond = (struct inet_diag_markcond *)(op + 1);
+                       if ((entry->mark & cond->mask) != cond->mark)
+                               yes = 0;
+                       break;
+               }
                }
 
                if (yes) {
@@ -571,7 +624,14 @@ int inet_diag_bc_sk(const struct nlattr *bc, struct sock *sk)
        entry_fill_addrs(&entry, sk);
        entry.sport = inet->inet_num;
        entry.dport = ntohs(inet->inet_dport);
+       entry.ifindex = sk->sk_bound_dev_if;
        entry.userlocks = sk_fullsock(sk) ? sk->sk_userlocks : 0;
+       if (sk_fullsock(sk))
+               entry.mark = sk->sk_mark;
+       else if (sk->sk_state == TCP_NEW_SYN_RECV)
+               entry.mark = inet_rsk(inet_reqsk(sk))->ir_mark;
+       else
+               entry.mark = 0;
 
        return inet_diag_bc_run(bc, &entry);
 }
@@ -594,6 +654,17 @@ static int valid_cc(const void *bc, int len, int cc)
        return 0;
 }
 
+/* data is u32 ifindex */
+static bool valid_devcond(const struct inet_diag_bc_op *op, int len,
+                         int *min_len)
+{
+       /* Check ifindex space. */
+       *min_len += sizeof(u32);
+       if (len < *min_len)
+               return false;
+
+       return true;
+}
 /* Validate an inet_diag_hostcond. */
 static bool valid_hostcond(const struct inet_diag_bc_op *op, int len,
                           int *min_len)
@@ -643,10 +714,25 @@ static bool valid_port_comparison(const struct inet_diag_bc_op *op,
        return true;
 }
 
-static int inet_diag_bc_audit(const void *bytecode, int bytecode_len)
+static bool valid_markcond(const struct inet_diag_bc_op *op, int len,
+                          int *min_len)
+{
+       *min_len += sizeof(struct inet_diag_markcond);
+       return len >= *min_len;
+}
+
+static int inet_diag_bc_audit(const struct nlattr *attr,
+                             const struct sk_buff *skb)
 {
-       const void *bc = bytecode;
-       int  len = bytecode_len;
+       bool net_admin = netlink_net_capable(skb, CAP_NET_ADMIN);
+       const void *bytecode, *bc;
+       int bytecode_len, len;
+
+       if (!attr || nla_len(attr) < sizeof(struct inet_diag_bc_op))
+               return -EINVAL;
+
+       bytecode = bc = nla_data(attr);
+       len = bytecode_len = nla_len(attr);
 
        while (len > 0) {
                int min_len = sizeof(struct inet_diag_bc_op);
@@ -658,6 +744,10 @@ static int inet_diag_bc_audit(const void *bytecode, int bytecode_len)
                        if (!valid_hostcond(bc, len, &min_len))
                                return -EINVAL;
                        break;
+               case INET_DIAG_BC_DEV_COND:
+                       if (!valid_devcond(bc, len, &min_len))
+                               return -EINVAL;
+                       break;
                case INET_DIAG_BC_S_GE:
                case INET_DIAG_BC_S_LE:
                case INET_DIAG_BC_D_GE:
@@ -665,6 +755,12 @@ static int inet_diag_bc_audit(const void *bytecode, int bytecode_len)
                        if (!valid_port_comparison(bc, len, &min_len))
                                return -EINVAL;
                        break;
+               case INET_DIAG_BC_MARK_COND:
+                       if (!net_admin)
+                               return -EPERM;
+                       if (!valid_markcond(bc, len, &min_len))
+                               return -EINVAL;
+                       break;
                case INET_DIAG_BC_AUTO:
                case INET_DIAG_BC_JMP:
                case INET_DIAG_BC_NOP:
@@ -693,7 +789,8 @@ static int inet_csk_diag_dump(struct sock *sk,
                              struct sk_buff *skb,
                              struct netlink_callback *cb,
                              const struct inet_diag_req_v2 *r,
-                             const struct nlattr *bc)
+                             const struct nlattr *bc,
+                             bool net_admin)
 {
        if (!inet_diag_bc_sk(bc, sk))
                return 0;
@@ -701,7 +798,8 @@ static int inet_csk_diag_dump(struct sock *sk,
        return inet_csk_diag_fill(sk, skb, r,
                                  sk_user_ns(NETLINK_CB(cb->skb).sk),
                                  NETLINK_CB(cb->skb).portid,
-                                 cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh);
+                                 cb->nlh->nlmsg_seq, NLM_F_MULTI, cb->nlh,
+                                 net_admin);
 }
 
 static void twsk_build_assert(void)
@@ -737,6 +835,7 @@ void inet_diag_dump_icsk(struct inet_hashinfo *hashinfo, struct sk_buff *skb,
        struct net *net = sock_net(skb->sk);
        int i, num, s_i, s_num;
        u32 idiag_states = r->idiag_states;
+       bool net_admin = netlink_net_capable(cb->skb, CAP_NET_ADMIN);
 
        if (idiag_states & TCPF_SYN_RECV)
                idiag_states |= TCPF_NEW_SYN_RECV;
@@ -778,7 +877,8 @@ void inet_diag_dump_icsk(struct inet_hashinfo *hashinfo, struct sk_buff *skb,
                                    cb->args[3] > 0)
                                        goto next_listen;
 
-                               if (inet_csk_diag_dump(sk, skb, cb, r, bc) < 0) {
+                               if (inet_csk_diag_dump(sk, skb, cb, r,
+                                                      bc, net_admin) < 0) {
                                        spin_unlock_bh(&ilb->lock);
                                        goto done;
                                }
@@ -846,7 +946,7 @@ skip_listen_ht:
                                           sk_user_ns(NETLINK_CB(cb->skb).sk),
                                           NETLINK_CB(cb->skb).portid,
                                           cb->nlh->nlmsg_seq, NLM_F_MULTI,
-                                          cb->nlh);
+                                          cb->nlh, net_admin);
                        if (res < 0) {
                                spin_unlock_bh(lock);
                                goto done;
@@ -938,7 +1038,7 @@ static int inet_diag_get_exact_compat(struct sk_buff *in_skb,
        req.idiag_states = rc->idiag_states;
        req.id = rc->id;
 
-       return inet_diag_get_exact(in_skb, nlh, &req);
+       return inet_diag_cmd_exact(SOCK_DIAG_BY_FAMILY, in_skb, nlh, &req);
 }
 
 static int inet_diag_rcv_msg_compat(struct sk_buff *skb, struct nlmsghdr *nlh)
@@ -953,13 +1053,13 @@ static int inet_diag_rcv_msg_compat(struct sk_buff *skb, struct nlmsghdr *nlh)
        if (nlh->nlmsg_flags & NLM_F_DUMP) {
                if (nlmsg_attrlen(nlh, hdrlen)) {
                        struct nlattr *attr;
+                       int err;
 
                        attr = nlmsg_find_attr(nlh, hdrlen,
                                               INET_DIAG_REQ_BYTECODE);
-                       if (!attr ||
-                           nla_len(attr) < sizeof(struct inet_diag_bc_op) ||
-                           inet_diag_bc_audit(nla_data(attr), nla_len(attr)))
-                               return -EINVAL;
+                       err = inet_diag_bc_audit(attr, skb);
+                       if (err)
+                               return err;
                }
                {
                        struct netlink_dump_control c = {
@@ -972,7 +1072,7 @@ static int inet_diag_rcv_msg_compat(struct sk_buff *skb, struct nlmsghdr *nlh)
        return inet_diag_get_exact_compat(skb, nlh);
 }
 
-static int inet_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h)
+static int inet_diag_handler_cmd(struct sk_buff *skb, struct nlmsghdr *h)
 {
        int hdrlen = sizeof(struct inet_diag_req_v2);
        struct net *net = sock_net(skb->sk);
@@ -980,16 +1080,17 @@ static int inet_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h)
        if (nlmsg_len(h) < hdrlen)
                return -EINVAL;
 
-       if (h->nlmsg_flags & NLM_F_DUMP) {
+       if (h->nlmsg_type == SOCK_DIAG_BY_FAMILY &&
+           h->nlmsg_flags & NLM_F_DUMP) {
                if (nlmsg_attrlen(h, hdrlen)) {
                        struct nlattr *attr;
+                       int err;
 
                        attr = nlmsg_find_attr(h, hdrlen,
                                               INET_DIAG_REQ_BYTECODE);
-                       if (!attr ||
-                           nla_len(attr) < sizeof(struct inet_diag_bc_op) ||
-                           inet_diag_bc_audit(nla_data(attr), nla_len(attr)))
-                               return -EINVAL;
+                       err = inet_diag_bc_audit(attr, skb);
+                       if (err)
+                               return err;
                }
                {
                        struct netlink_dump_control c = {
@@ -999,7 +1100,7 @@ static int inet_diag_handler_dump(struct sk_buff *skb, struct nlmsghdr *h)
                }
        }
 
-       return inet_diag_get_exact(skb, h, nlmsg_data(h));
+       return inet_diag_cmd_exact(h->nlmsg_type, skb, h, nlmsg_data(h));
 }
 
 static
@@ -1050,14 +1151,16 @@ int inet_diag_handler_get_info(struct sk_buff *skb, struct sock *sk)
 
 static const struct sock_diag_handler inet_diag_handler = {
        .family = AF_INET,
-       .dump = inet_diag_handler_dump,
+       .dump = inet_diag_handler_cmd,
        .get_info = inet_diag_handler_get_info,
+       .destroy = inet_diag_handler_cmd,
 };
 
 static const struct sock_diag_handler inet6_diag_handler = {
        .family = AF_INET6,
-       .dump = inet_diag_handler_dump,
+       .dump = inet_diag_handler_cmd,
        .get_info = inet_diag_handler_get_info,
+       .destroy = inet_diag_handler_cmd,
 };
 
 int inet_diag_register(const struct inet_diag_handler *h)