Merge branch 'for-3.8/core' of git://git.kernel.dk/linux-block
[cascardo/linux.git] / net / ipv4 / ipmr.c
index 3eab2b2..a9454cb 100644 (file)
@@ -65,6 +65,7 @@
 #include <net/checksum.h>
 #include <net/netlink.h>
 #include <net/fib_rules.h>
+#include <linux/netconf.h>
 
 #if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)
 #define CONFIG_IP_PIMSM        1
@@ -83,8 +84,8 @@ struct mr_table {
        struct vif_device       vif_table[MAXVIFS];
        int                     maxvif;
        atomic_t                cache_resolve_queue_len;
-       int                     mroute_do_assert;
-       int                     mroute_do_pim;
+       bool                    mroute_do_assert;
+       bool                    mroute_do_pim;
 #if defined(CONFIG_IP_PIMSM_V1) || defined(CONFIG_IP_PIMSM_V2)
        int                     mroute_reg_vif_num;
 #endif
@@ -133,6 +134,8 @@ static int ipmr_cache_report(struct mr_table *mrt,
                             struct sk_buff *pkt, vifi_t vifi, int assert);
 static int __ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,
                              struct mfc_cache *c, struct rtmsg *rtm);
+static void mroute_netlink_event(struct mr_table *mrt, struct mfc_cache *mfc,
+                                int cmd);
 static void mroute_clean_tables(struct mr_table *mrt);
 static void ipmr_expire_process(unsigned long arg);
 
@@ -582,6 +585,9 @@ static int vif_delete(struct mr_table *mrt, int vifi, int notify,
        in_dev = __in_dev_get_rtnl(dev);
        if (in_dev) {
                IPV4_DEVCONF(in_dev->cnf, MC_FORWARDING)--;
+               inet_netconf_notify_devconf(dev_net(dev),
+                                           NETCONFA_MC_FORWARDING,
+                                           dev->ifindex, &in_dev->cnf);
                ip_rt_multicast_event(in_dev);
        }
 
@@ -665,6 +671,7 @@ static void ipmr_expire_process(unsigned long arg)
                }
 
                list_del(&c->list);
+               mroute_netlink_event(mrt, c, RTM_DELROUTE);
                ipmr_destroy_unres(mrt, c);
        }
 
@@ -772,6 +779,8 @@ static int vif_add(struct net *net, struct mr_table *mrt,
                return -EADDRNOTAVAIL;
        }
        IPV4_DEVCONF(in_dev->cnf, MC_FORWARDING)++;
+       inet_netconf_notify_devconf(net, NETCONFA_MC_FORWARDING, dev->ifindex,
+                                   &in_dev->cnf);
        ip_rt_multicast_event(in_dev);
 
        /* Fill in the VIF structures */
@@ -1020,6 +1029,7 @@ ipmr_cache_unresolved(struct mr_table *mrt, vifi_t vifi, struct sk_buff *skb)
 
                atomic_inc(&mrt->cache_resolve_queue_len);
                list_add(&c->list, &mrt->mfc_unres_queue);
+               mroute_netlink_event(mrt, c, RTM_NEWROUTE);
 
                if (atomic_read(&mrt->cache_resolve_queue_len) == 1)
                        mod_timer(&mrt->ipmr_expire_timer, c->mfc_un.unres.expires);
@@ -1054,7 +1064,7 @@ static int ipmr_mfc_delete(struct mr_table *mrt, struct mfcctl *mfc)
                if (c->mfc_origin == mfc->mfcc_origin.s_addr &&
                    c->mfc_mcastgrp == mfc->mfcc_mcastgrp.s_addr) {
                        list_del_rcu(&c->list);
-
+                       mroute_netlink_event(mrt, c, RTM_DELROUTE);
                        ipmr_cache_free(c);
                        return 0;
                }
@@ -1089,6 +1099,7 @@ static int ipmr_mfc_add(struct net *net, struct mr_table *mrt,
                if (!mrtsock)
                        c->mfc_flags |= MFC_STATIC;
                write_unlock_bh(&mrt_lock);
+               mroute_netlink_event(mrt, c, RTM_NEWROUTE);
                return 0;
        }
 
@@ -1131,6 +1142,7 @@ static int ipmr_mfc_add(struct net *net, struct mr_table *mrt,
                ipmr_cache_resolve(net, mrt, uc, c);
                ipmr_cache_free(uc);
        }
+       mroute_netlink_event(mrt, c, RTM_NEWROUTE);
        return 0;
 }
 
@@ -1159,6 +1171,7 @@ static void mroute_clean_tables(struct mr_table *mrt)
                        if (c->mfc_flags & MFC_STATIC)
                                continue;
                        list_del_rcu(&c->list);
+                       mroute_netlink_event(mrt, c, RTM_DELROUTE);
                        ipmr_cache_free(c);
                }
        }
@@ -1167,6 +1180,7 @@ static void mroute_clean_tables(struct mr_table *mrt)
                spin_lock_bh(&mfc_unres_lock);
                list_for_each_entry_safe(c, next, &mrt->mfc_unres_queue, list) {
                        list_del(&c->list);
+                       mroute_netlink_event(mrt, c, RTM_DELROUTE);
                        ipmr_destroy_unres(mrt, c);
                }
                spin_unlock_bh(&mfc_unres_lock);
@@ -1185,6 +1199,9 @@ static void mrtsock_destruct(struct sock *sk)
        ipmr_for_each_table(mrt, net) {
                if (sk == rtnl_dereference(mrt->mroute_sk)) {
                        IPV4_DEVCONF_ALL(net, MC_FORWARDING)--;
+                       inet_netconf_notify_devconf(net, NETCONFA_MC_FORWARDING,
+                                                   NETCONFA_IFINDEX_ALL,
+                                                   net->ipv4.devconf_all);
                        RCU_INIT_POINTER(mrt->mroute_sk, NULL);
                        mroute_clean_tables(mrt);
                }
@@ -1207,23 +1224,24 @@ int ip_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, unsi
        struct net *net = sock_net(sk);
        struct mr_table *mrt;
 
+       if (sk->sk_type != SOCK_RAW ||
+           inet_sk(sk)->inet_num != IPPROTO_IGMP)
+               return -EOPNOTSUPP;
+
        mrt = ipmr_get_table(net, raw_sk(sk)->ipmr_table ? : RT_TABLE_DEFAULT);
        if (mrt == NULL)
                return -ENOENT;
 
        if (optname != MRT_INIT) {
                if (sk != rcu_access_pointer(mrt->mroute_sk) &&
-                   !capable(CAP_NET_ADMIN))
+                   !ns_capable(net->user_ns, CAP_NET_ADMIN))
                        return -EACCES;
        }
 
        switch (optname) {
        case MRT_INIT:
-               if (sk->sk_type != SOCK_RAW ||
-                   inet_sk(sk)->inet_num != IPPROTO_IGMP)
-                       return -EOPNOTSUPP;
                if (optlen != sizeof(int))
-                       return -ENOPROTOOPT;
+                       return -EINVAL;
 
                rtnl_lock();
                if (rtnl_dereference(mrt->mroute_sk)) {
@@ -1235,6 +1253,9 @@ int ip_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, unsi
                if (ret == 0) {
                        rcu_assign_pointer(mrt->mroute_sk, sk);
                        IPV4_DEVCONF_ALL(net, MC_FORWARDING)++;
+                       inet_netconf_notify_devconf(net, NETCONFA_MC_FORWARDING,
+                                                   NETCONFA_IFINDEX_ALL,
+                                                   net->ipv4.devconf_all);
                }
                rtnl_unlock();
                return ret;
@@ -1284,9 +1305,11 @@ int ip_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, unsi
        case MRT_ASSERT:
        {
                int v;
+               if (optlen != sizeof(v))
+                       return -EINVAL;
                if (get_user(v, (int __user *)optval))
                        return -EFAULT;
-               mrt->mroute_do_assert = (v) ? 1 : 0;
+               mrt->mroute_do_assert = v;
                return 0;
        }
 #ifdef CONFIG_IP_PIMSM
@@ -1294,9 +1317,11 @@ int ip_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, unsi
        {
                int v;
 
+               if (optlen != sizeof(v))
+                       return -EINVAL;
                if (get_user(v, (int __user *)optval))
                        return -EFAULT;
-               v = (v) ? 1 : 0;
+               v = !!v;
 
                rtnl_lock();
                ret = 0;
@@ -1329,7 +1354,8 @@ int ip_mroute_setsockopt(struct sock *sk, int optname, char __user *optval, unsi
                } else {
                        if (!ipmr_new_table(net, v))
                                ret = -ENOMEM;
-                       raw_sk(sk)->ipmr_table = v;
+                       else
+                               raw_sk(sk)->ipmr_table = v;
                }
                rtnl_unlock();
                return ret;
@@ -1355,6 +1381,10 @@ int ip_mroute_getsockopt(struct sock *sk, int optname, char __user *optval, int
        struct net *net = sock_net(sk);
        struct mr_table *mrt;
 
+       if (sk->sk_type != SOCK_RAW ||
+           inet_sk(sk)->inet_num != IPPROTO_IGMP)
+               return -EOPNOTSUPP;
+
        mrt = ipmr_get_table(net, raw_sk(sk)->ipmr_table ? : RT_TABLE_DEFAULT);
        if (mrt == NULL)
                return -ENOENT;
@@ -2024,6 +2054,7 @@ static int __ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,
        int ct;
        struct rtnexthop *nhp;
        struct nlattr *mp_attr;
+       struct rta_mfc_stats mfcs;
 
        /* If cache is unresolved, don't try to parse IIF and OIF */
        if (c->mfc_parent >= MAXVIFS)
@@ -2052,6 +2083,12 @@ static int __ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,
 
        nla_nest_end(skb, mp_attr);
 
+       mfcs.mfcs_packets = c->mfc_un.res.pkt;
+       mfcs.mfcs_bytes = c->mfc_un.res.bytes;
+       mfcs.mfcs_wrong_if = c->mfc_un.res.wrong_if;
+       if (nla_put(skb, RTA_MFC_STATS, sizeof(mfcs), &mfcs) < 0)
+               return -EMSGSIZE;
+
        rtm->rtm_type = RTN_MULTICAST;
        return 1;
 }
@@ -2121,12 +2158,13 @@ int ipmr_get_route(struct net *net, struct sk_buff *skb,
 }
 
 static int ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,
-                           u32 portid, u32 seq, struct mfc_cache *c)
+                           u32 portid, u32 seq, struct mfc_cache *c, int cmd)
 {
        struct nlmsghdr *nlh;
        struct rtmsg *rtm;
+       int err;
 
-       nlh = nlmsg_put(skb, portid, seq, RTM_NEWROUTE, sizeof(*rtm), NLM_F_MULTI);
+       nlh = nlmsg_put(skb, portid, seq, cmd, sizeof(*rtm), NLM_F_MULTI);
        if (nlh == NULL)
                return -EMSGSIZE;
 
@@ -2140,13 +2178,18 @@ static int ipmr_fill_mroute(struct mr_table *mrt, struct sk_buff *skb,
                goto nla_put_failure;
        rtm->rtm_type     = RTN_MULTICAST;
        rtm->rtm_scope    = RT_SCOPE_UNIVERSE;
-       rtm->rtm_protocol = RTPROT_UNSPEC;
+       if (c->mfc_flags & MFC_STATIC)
+               rtm->rtm_protocol = RTPROT_STATIC;
+       else
+               rtm->rtm_protocol = RTPROT_MROUTED;
        rtm->rtm_flags    = 0;
 
        if (nla_put_be32(skb, RTA_SRC, c->mfc_origin) ||
            nla_put_be32(skb, RTA_DST, c->mfc_mcastgrp))
                goto nla_put_failure;
-       if (__ipmr_fill_mroute(mrt, skb, c, rtm) < 0)
+       err = __ipmr_fill_mroute(mrt, skb, c, rtm);
+       /* do not break the dump if cache is unresolved */
+       if (err < 0 && err != -ENOENT)
                goto nla_put_failure;
 
        return nlmsg_end(skb, nlh);
@@ -2156,6 +2199,52 @@ nla_put_failure:
        return -EMSGSIZE;
 }
 
+static size_t mroute_msgsize(bool unresolved, int maxvif)
+{
+       size_t len =
+               NLMSG_ALIGN(sizeof(struct rtmsg))
+               + nla_total_size(4)     /* RTA_TABLE */
+               + nla_total_size(4)     /* RTA_SRC */
+               + nla_total_size(4)     /* RTA_DST */
+               ;
+
+       if (!unresolved)
+               len = len
+                     + nla_total_size(4)       /* RTA_IIF */
+                     + nla_total_size(0)       /* RTA_MULTIPATH */
+                     + maxvif * NLA_ALIGN(sizeof(struct rtnexthop))
+                                               /* RTA_MFC_STATS */
+                     + nla_total_size(sizeof(struct rta_mfc_stats))
+               ;
+
+       return len;
+}
+
+static void mroute_netlink_event(struct mr_table *mrt, struct mfc_cache *mfc,
+                                int cmd)
+{
+       struct net *net = read_pnet(&mrt->net);
+       struct sk_buff *skb;
+       int err = -ENOBUFS;
+
+       skb = nlmsg_new(mroute_msgsize(mfc->mfc_parent >= MAXVIFS, mrt->maxvif),
+                       GFP_ATOMIC);
+       if (skb == NULL)
+               goto errout;
+
+       err = ipmr_fill_mroute(mrt, skb, 0, 0, mfc, cmd);
+       if (err < 0)
+               goto errout;
+
+       rtnl_notify(skb, net, 0, RTNLGRP_IPV4_MROUTE, NULL, GFP_ATOMIC);
+       return;
+
+errout:
+       kfree_skb(skb);
+       if (err < 0)
+               rtnl_set_sk_err(net, RTNLGRP_IPV4_MROUTE, err);
+}
+
 static int ipmr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
 {
        struct net *net = sock_net(skb->sk);
@@ -2182,13 +2271,29 @@ static int ipmr_rtm_dumproute(struct sk_buff *skb, struct netlink_callback *cb)
                                if (ipmr_fill_mroute(mrt, skb,
                                                     NETLINK_CB(cb->skb).portid,
                                                     cb->nlh->nlmsg_seq,
-                                                    mfc) < 0)
+                                                    mfc, RTM_NEWROUTE) < 0)
                                        goto done;
 next_entry:
                                e++;
                        }
                        e = s_e = 0;
                }
+               spin_lock_bh(&mfc_unres_lock);
+               list_for_each_entry(mfc, &mrt->mfc_unres_queue, list) {
+                       if (e < s_e)
+                               goto next_entry2;
+                       if (ipmr_fill_mroute(mrt, skb,
+                                            NETLINK_CB(cb->skb).portid,
+                                            cb->nlh->nlmsg_seq,
+                                            mfc, RTM_NEWROUTE) < 0) {
+                               spin_unlock_bh(&mfc_unres_lock);
+                               goto done;
+                       }
+next_entry2:
+                       e++;
+               }
+               spin_unlock_bh(&mfc_unres_lock);
+               e = s_e = 0;
                s_h = 0;
 next_table:
                t++;