cregit-Linux how code gets into the kernel

Release 4.8 net/ipv6/mcast.c

Directory: net/ipv6
/*
 *      Multicast support for IPv6
 *      Linux INET6 implementation
 *
 *      Authors:
 *      Pedro Roque             <roque@di.fc.ul.pt>
 *
 *      Based on linux/ipv4/igmp.c and linux/ipv4/ip_sockglue.c
 *
 *      This program is free software; you can redistribute it and/or
 *      modify it under the terms of the GNU General Public License
 *      as published by the Free Software Foundation; either version
 *      2 of the License, or (at your option) any later version.
 */

/* Changes:
 *
 *      yoshfuji        : fix format of router-alert option
 *      YOSHIFUJI Hideaki @USAGI:
 *              Fixed source address for MLD message based on
 *              <draft-ietf-magma-mld-source-05.txt>.
 *      YOSHIFUJI Hideaki @USAGI:
 *              - Ignore Queries for invalid addresses.
 *              - MLD for link-local addresses.
 *      David L Stevens <dlstevens@us.ibm.com>:
 *              - MLDv2 support
 */

#include <linux/module.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/socket.h>
#include <linux/sockios.h>
#include <linux/jiffies.h>
#include <linux/times.h>
#include <linux/net.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <linux/netdevice.h>
#include <linux/if_arp.h>
#include <linux/route.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/pkt_sched.h>
#include <net/mld.h>

#include <linux/netfilter.h>
#include <linux/netfilter_ipv6.h>

#include <net/net_namespace.h>
#include <net/sock.h>
#include <net/snmp.h>

#include <net/ipv6.h>
#include <net/protocol.h>
#include <net/if_inet6.h>
#include <net/ndisc.h>
#include <net/addrconf.h>
#include <net/ip6_route.h>
#include <net/inet_common.h>

#include <net/ip6_checksum.h>

/* Ensure that we have struct in6_addr aligned on 32bit word. */

static void *__mld2_query_bugs[] __attribute__((__unused__)) = {
	BUILD_BUG_ON_NULL(offsetof(struct mld2_query, mld2q_srcs) % 4),
	BUILD_BUG_ON_NULL(offsetof(struct mld2_report, mld2r_grec) % 4),
	BUILD_BUG_ON_NULL(offsetof(struct mld2_grec, grec_mca) % 4)
};


static struct in6_addr mld2_all_mcr = MLD2_ALL_MCR_INIT;

static void igmp6_join_group(struct ifmcaddr6 *ma);
static void igmp6_leave_group(struct ifmcaddr6 *ma);
static void igmp6_timer_handler(unsigned long data);

static void mld_gq_timer_expire(unsigned long data);
static void mld_ifc_timer_expire(unsigned long data);
static void mld_ifc_event(struct inet6_dev *idev);
static void mld_add_delrec(struct inet6_dev *idev, struct ifmcaddr6 *pmc);
static void mld_del_delrec(struct inet6_dev *idev, const struct in6_addr *addr);
static void mld_clear_delrec(struct inet6_dev *idev);
static bool mld_in_v1_mode(const struct inet6_dev *idev);
static int sf_setstate(struct ifmcaddr6 *pmc);
static void sf_markstate(struct ifmcaddr6 *pmc);
static void ip6_mc_clear_src(struct ifmcaddr6 *pmc);
static int ip6_mc_del_src(struct inet6_dev *idev, const struct in6_addr *pmca,
			  int sfmode, int sfcount, const struct in6_addr *psfsrc,
			  int delta);
static int ip6_mc_add_src(struct inet6_dev *idev, const struct in6_addr *pmca,
			  int sfmode, int sfcount, const struct in6_addr *psfsrc,
			  int delta);
static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
			    struct inet6_dev *idev);


#define MLD_QRV_DEFAULT		2
/* RFC3810, 9.2. Query Interval */

#define MLD_QI_DEFAULT		(125 * HZ)
/* RFC3810, 9.3. Query Response Interval */

#define MLD_QRI_DEFAULT		(10 * HZ)

/* RFC3810, 8.1 Query Version Distinctions */

#define MLD_V1_QUERY_LEN	24

#define MLD_V2_QUERY_LEN_MIN	28


#define IPV6_MLD_MAX_MSF	64


int sysctl_mld_max_msf __read_mostly = IPV6_MLD_MAX_MSF;

int sysctl_mld_qrv __read_mostly = MLD_QRV_DEFAULT;

/*
 *      socket join on multicast group
 */


#define for_each_pmc_rcu(np, pmc)				\
	for (pmc = rcu_dereference(np->ipv6_mc_list);           \
             pmc != NULL;                                       \
             pmc = rcu_dereference(pmc->next))


static int unsolicited_report_interval(struct inet6_dev *idev) { int iv; if (mld_in_v1_mode(idev)) iv = idev->cnf.mldv1_unsolicited_report_interval; else iv = idev->cnf.mldv2_unsolicited_report_interval; return iv > 0 ? iv : 1; }

Contributors

PersonTokensPropCommitsCommitProp
hannes frederic sowahannes frederic sowa4697.87%150.00%
daniel borkmanndaniel borkmann12.13%150.00%
Total47100.00%2100.00%


int ipv6_sock_mc_join(struct sock *sk, int ifindex, const struct in6_addr *addr) { struct net_device *dev = NULL; struct ipv6_mc_socklist *mc_lst; struct ipv6_pinfo *np = inet6_sk(sk); struct net *net = sock_net(sk); int err; ASSERT_RTNL(); if (!ipv6_addr_is_multicast(addr)) return -EINVAL; rcu_read_lock(); for_each_pmc_rcu(np, mc_lst) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && ipv6_addr_equal(&mc_lst->addr, addr)) { rcu_read_unlock(); return -EADDRINUSE; } } rcu_read_unlock(); mc_lst = sock_kmalloc(sk, sizeof(struct ipv6_mc_socklist), GFP_KERNEL); if (!mc_lst) return -ENOMEM; mc_lst->next = NULL; mc_lst->addr = *addr; if (ifindex == 0) { struct rt6_info *rt; rt = rt6_lookup(net, addr, NULL, 0, 0); if (rt) { dev = rt->dst.dev; ip6_rt_put(rt); } } else dev = __dev_get_by_index(net, ifindex); if (!dev) { sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); return -ENODEV; } mc_lst->ifindex = dev->ifindex; mc_lst->sfmode = MCAST_EXCLUDE; rwlock_init(&mc_lst->sflock); mc_lst->sflist = NULL; /* * now add/increase the group membership on the device */ err = ipv6_dev_mc_inc(dev, addr); if (err) { sock_kfree_s(sk, mc_lst, sizeof(*mc_lst)); return err; } mc_lst->next = np->ipv6_mc_list; rcu_assign_pointer(np->ipv6_mc_list, mc_lst); return 0; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git16050.96%416.67%
david l stevensdavid l stevens9329.62%312.50%
eric dumazeteric dumazet154.78%14.17%
daniel lezcanodaniel lezcano103.18%28.33%
hideaki yoshifujihideaki yoshifuji92.87%416.67%
linus torvaldslinus torvalds82.55%14.17%
madhu challamadhu challa72.23%14.17%
david s. millerdavid s. miller30.96%14.17%
ian morrisian morris20.64%14.17%
americo wangamerico wang20.64%28.33%
alexey dobriyanalexey dobriyan20.64%14.17%
al viroal viro10.32%14.17%
eric w. biedermaneric w. biederman10.32%14.17%
marcelo ricardo leitnermarcelo ricardo leitner10.32%14.17%
Total314100.00%24100.00%

EXPORT_SYMBOL(ipv6_sock_mc_join); /* * socket leave on multicast group */
int ipv6_sock_mc_drop(struct sock *sk, int ifindex, const struct in6_addr *addr) { struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_mc_socklist *mc_lst; struct ipv6_mc_socklist __rcu **lnk; struct net *net = sock_net(sk); ASSERT_RTNL(); if (!ipv6_addr_is_multicast(addr)) return -EINVAL; for (lnk = &np->ipv6_mc_list; (mc_lst = rtnl_dereference(*lnk)) != NULL; lnk = &mc_lst->next) { if ((ifindex == 0 || mc_lst->ifindex == ifindex) && ipv6_addr_equal(&mc_lst->addr, addr)) { struct net_device *dev; *lnk = mc_lst->next; dev = __dev_get_by_index(net, mc_lst->ifindex); if (dev) { struct inet6_dev *idev = __in6_dev_get(dev); (void) ip6_mc_leave_src(sk, mc_lst, idev); if (idev) __ipv6_dev_mc_dec(idev, &mc_lst->addr); } else (void) ip6_mc_leave_src(sk, mc_lst, NULL); atomic_sub(sizeof(*mc_lst), &sk->sk_omem_alloc); kfree_rcu(mc_lst, rcu); return 0; } } return -EADDRNOTAVAIL; }

Contributors

PersonTokensPropCommitsCommitProp
pre-gitpre-git11550.44%625.00%
david l stevensdavid l stevens4017.54%28.33%
eric dumazeteric dumazet187.89%28.33%
li weili wei125.26%14.17%
daniel lezcanodaniel lezcano125.26%14.17%
madhu challamadhu challa83.51%14.17%
alexey kuznetsovalexey kuznetsov83.51%14.17%
hideaki yoshifujihideaki yoshifuji62.63%416.67%
david s. millerdavid s. miller31.32%14.17%
lai jiangshanlai jiangshan20.88%14.17%
americo wangamerico wang20.88%28.33%
eric w. biedermaneric w. biederman10.44%14.17%
marcelo ricardo leitnermarcelo ricardo leitner10.44%14.17%
Total228100.00%24100.00%

EXPORT_SYMBOL(ipv6_sock_mc_drop); /* called with rcu_read_lock() */
static struct inet6_dev *ip6_mc_find_dev_rcu(struct net *net, const struct in6_addr *group, int ifindex) { struct net_device *dev = NULL; struct inet6_dev *idev = NULL; if (ifindex == 0) { struct rt6_info *rt = rt6_lookup(net, group, NULL, 0, 0); if (rt) { dev = rt->dst.dev; ip6_rt_put(rt); } } else dev = dev_get_by_index_rcu(net, ifindex); if (!dev) return NULL; idev = __in6_dev_get(dev); if (!idev) return NULL; read_lock_bh(&idev->lock); if (idev->dead) { read_unlock_bh(&idev->lock); return NULL; } return idev; }

Contributors

PersonTokensPropCommitsCommitProp
david l stevensdavid l stevens6746.53%16.67%
pre-gitpre-git4329.86%533.33%
eric dumazeteric dumazet1711.81%213.33%
daniel lezcanodaniel lezcano85.56%213.33%
david s. millerdavid s. miller32.08%16.67%
al viroal viro32.08%16.67%
adrian bunkadrian bunk10.69%16.67%
americo wangamerico wang10.69%16.67%
eric w. biedermaneric w. biederman10.69%16.67%
Total144100.00%15100.00%


void ipv6_sock_mc_close(struct sock *sk) { struct ipv6_pinfo *np = inet6_sk(sk); struct ipv6_mc_socklist *mc_lst; struct net *net = sock_net(sk); if (!rcu_access_pointer(np->ipv6_mc_list)) return; rtnl_lock(); while ((mc_lst = rtnl_dereference(np->ipv6_mc_list)) != NULL) { struct net_device *dev; np->ipv6_mc_list = mc_lst->next; dev = __dev_get_by_index(net, mc_lst->ifindex); if (dev) { struct inet6_dev *idev = __in6_dev_get(dev); (void) ip6_mc_leave_src(sk, mc_lst, idev); if (idev) __ipv6_dev_mc_dec(idev, &mc_lst->addr); } else (void) ip6_mc_leave_src(sk, mc_lst, NULL); atomic_sub(sizeof(*mc_lst), &sk->sk_omem_alloc); kfree_rcu(mc_lst, rcu); } rtnl_unlock(); }

Contributors

PersonTokensPropCommitsCommitProp
david l stevensdavid l stevens7946.75%212.50%
pre-gitpre-git3118.34%212.50%
eric dumazeteric dumazet2514.79%318.75%
david s. millerdavid s. miller116.51%16.25%
daniel lezcanodaniel lezcano84.73%16.25%
sabrina dubrocasabrina dubroca63.55%16.25%
hideaki yoshifujihideaki yoshifuji42.37%212.50%
americo wangamerico wang21.18%212.50%
lai jiangshanlai jiangshan21.18%16.25%
eric w. biedermaneric w. biederman10.59%16.25%
Total169100.00%16100.00%


int ip6_mc_source(int add, int omode, struct sock *sk, struct group_source_req *pgsr) { struct in6_addr *source, *group; struct ipv6_mc_socklist *pmc; struct inet6_dev *idev; struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *psl; struct net *net = sock_net(sk); int i, j, rv; int leavegroup = 0; int pmclocked = 0; int err; source = &((struct sockaddr_in6 *)&pgsr->gsr_source)->sin6_addr; group = &((struct sockaddr_in6 *)&pgsr->gsr_group)->sin6_addr; if (!ipv6_addr_is_multicast(group)) return -EINVAL; rcu_read_lock(); idev = ip6_mc_find_dev_rcu(net, group, pgsr->gsr_interface); if (!idev) { rcu_read_unlock(); return -ENODEV; } err = -EADDRNOTAVAIL; for_each_pmc_rcu(inet6, pmc) { if (pgsr->gsr_interface && pmc->ifindex != pgsr->gsr_interface) continue; if (ipv6_addr_equal(&pmc->addr, group)) break; } if (!pmc) { /* must have a prior join */ err = -EINVAL; goto done; } /* if a source filter was set, must be the same mode as before */ if (pmc->sflist) { if (pmc->sfmode != omode) { err = -EINVAL; goto done; } } else if (pmc->sfmode != omode) { /* allow mode switches for empty-set filters */ ip6_mc_add_src(idev, group, omode, 0, NULL, 0); ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0); pmc->sfmode = omode; } write_lock(&pmc->sflock); pmclocked = 1; psl = pmc->sflist; if (!add) { if (!psl) goto done; /* err = -EADDRNOTAVAIL */ rv = !0; for (i = 0; i < psl->sl_count; i++) { rv = !ipv6_addr_equal(&psl->sl_addr[i], source); if (rv == 0) break; } if (rv) /* source not found */ goto done; /* err = -EADDRNOTAVAIL */ /* special case - (INCLUDE, empty) == LEAVE_GROUP */ if (psl->sl_count == 1 && omode == MCAST_INCLUDE) { leavegroup = 1; goto done; } /* update the interface filter */ ip6_mc_del_src(idev, group, omode, 1, source, 1); for (j = i+1; j < psl->sl_count; j++) psl->sl_addr[j-1] = psl->sl_addr[j]; psl->sl_count--; err = 0; goto done; } /* else, add a new source to the filter */ if (psl && psl->sl_count >= sysctl_mld_max_msf) { err = -ENOBUFS; goto done; } if (!psl || psl->sl_count == psl->sl_max) { struct ip6_sf_socklist *newpsl; int count = IP6_SFBLOCK; if (psl) count += psl->sl_max; newpsl = sock_kmalloc(sk, IP6_SFLSIZE(count), GFP_ATOMIC); if (!newpsl) { err = -ENOBUFS; goto done; } newpsl->sl_max = count; newpsl->sl_count = count - IP6_SFBLOCK; if (psl) { for (i = 0; i < psl->sl_count; i++) newpsl->sl_addr[i] = psl->sl_addr[i]; sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max)); } pmc->sflist = psl = newpsl; } rv = 1; /* > 0 for insert logic below if sl_count is 0 */ for (i = 0; i < psl->sl_count; i++) { rv = !ipv6_addr_equal(&psl->sl_addr[i], source); if (rv == 0) /* There is an error in the address. */ goto done; } for (j = psl->sl_count-1; j >= i; j--) psl->sl_addr[j+1] = psl->sl_addr[j]; psl->sl_addr[i] = *source; psl->sl_count++; err = 0; /* update the interface list */ ip6_mc_add_src(idev, group, omode, 1, source, 1); done: if (pmclocked) write_unlock(&pmc->sflock); read_unlock_bh(&idev->lock); rcu_read_unlock(); if (leavegroup) err = ipv6_sock_mc_drop(sk, pgsr->gsr_interface, group); return err; }

Contributors

PersonTokensPropCommitsCommitProp
david l stevensdavid l stevens58174.30%726.92%
pre-gitpre-git14718.80%726.92%
eric dumazeteric dumazet172.17%27.69%
chris wrightchris wright101.28%13.85%
hideaki yoshifujihideaki yoshifuji91.15%415.38%
daniel lezcanodaniel lezcano91.15%13.85%
marcelo ricardo leitnermarcelo ricardo leitner30.38%13.85%
jean sacrenjean sacren20.26%13.85%
andrew mortonandrew morton20.26%13.85%
al viroal viro20.26%13.85%
Total782100.00%26100.00%


int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) { const struct in6_addr *group; struct ipv6_mc_socklist *pmc; struct inet6_dev *idev; struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *newpsl, *psl; struct net *net = sock_net(sk); int leavegroup = 0; int i, err; group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr; if (!ipv6_addr_is_multicast(group)) return -EINVAL; if (gsf->gf_fmode != MCAST_INCLUDE && gsf->gf_fmode != MCAST_EXCLUDE) return -EINVAL; rcu_read_lock(); idev = ip6_mc_find_dev_rcu(net, group, gsf->gf_interface); if (!idev) { rcu_read_unlock(); return -ENODEV; } err = 0; if (gsf->gf_fmode == MCAST_INCLUDE && gsf->gf_numsrc == 0) { leavegroup = 1; goto done; } for_each_pmc_rcu(inet6, pmc) { if (pmc->ifindex != gsf->gf_interface) continue; if (ipv6_addr_equal(&pmc->addr, group)) break; } if (!pmc) { /* must have a prior join */ err = -EINVAL; goto done; } if (gsf->gf_numsrc) { newpsl = sock_kmalloc(sk, IP6_SFLSIZE(gsf->gf_numsrc), GFP_ATOMIC); if (!newpsl) { err = -ENOBUFS; goto done; } newpsl->sl_max = newpsl->sl_count = gsf->gf_numsrc; for (i = 0; i < newpsl->sl_count; ++i) { struct sockaddr_in6 *psin6; psin6 = (struct sockaddr_in6 *)&gsf->gf_slist[i]; newpsl->sl_addr[i] = psin6->sin6_addr; } err = ip6_mc_add_src(idev, group, gsf->gf_fmode, newpsl->sl_count, newpsl->sl_addr, 0); if (err) { sock_kfree_s(sk, newpsl, IP6_SFLSIZE(newpsl->sl_max)); goto done; } } else { newpsl = NULL; (void) ip6_mc_add_src(idev, group, gsf->gf_fmode, 0, NULL, 0); } write_lock(&pmc->sflock); psl = pmc->sflist; if (psl) { (void) ip6_mc_del_src(idev, group, pmc->sfmode, psl->sl_count, psl->sl_addr, 0); sock_kfree_s(sk, psl, IP6_SFLSIZE(psl->sl_max)); } else (void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0); pmc->sflist = newpsl; pmc->sfmode = gsf->gf_fmode; write_unlock(&pmc->sflock); err = 0; done: read_unlock_bh(&idev->lock); rcu_read_unlock(); if (leavegroup) err = ipv6_sock_mc_drop(sk, gsf->gf_interface, group); return err; }

Contributors

PersonTokensPropCommitsCommitProp
david l stevensdavid l stevens39374.01%421.05%
pre-gitpre-git7614.31%526.32%
yan zhengyan zheng224.14%15.26%
eric dumazeteric dumazet203.77%315.79%
daniel lezcanodaniel lezcano91.69%15.26%
hideaki yoshifujihideaki yoshifuji50.94%315.79%
david s. millerdavid s. miller40.75%15.26%
al viroal viro20.38%15.26%
Total531100.00%19100.00%


int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf, struct group_filter __user *optval, int __user *optlen) { int err, i, count, copycount; const struct in6_addr *group; struct ipv6_mc_socklist *pmc; struct inet6_dev *idev; struct ipv6_pinfo *inet6 = inet6_sk(sk); struct ip6_sf_socklist *psl; struct net *net = sock_net(sk); group = &((struct sockaddr_in6 *)&gsf->gf_group)->sin6_addr; if (!ipv6_addr_is_multicast(group)) return -EINVAL; rcu_read_lock(); idev = ip6_mc_find_dev_rcu(net, group, gsf->gf_interface); if (!idev) { rcu_read_unlock(); return -ENODEV; } err = -EADDRNOTAVAIL; /* changes to the ipv6_mc_list require the socket lock and * rtnl lock. We have the socket lock and rcu read lock, * so reading the list is safe. */ for_each_pmc_rcu(inet6, pmc) { if (pmc->ifindex != gsf->gf_interface) continue; if (ipv6_addr_equal(group, &pmc->addr)) break; } if (!pmc) /* must have a prior join */ goto done; gsf->gf_fmode = pmc->sfmode; psl = pmc->sflist; count = psl ? psl->sl_count : 0; read_unlock_bh(&idev->lock); rcu_read_unlock(); copycount = count < gsf->gf_numsrc ? count : gsf->gf_numsrc; gsf->gf_numsrc = count; if (put_user(GROUP_FILTER_SIZE(copycount), optlen) || copy_to_user(optval, gsf, GROUP_FILTER_SIZE(0))) { return -EFAULT; } /* changes to psl require the socket lock, and a write lock * on pmc->sflock. We have the socket lock so reading here is safe. */ for (i = 0; i < copycount; i++) { struct sockaddr_in6 *psin6; struct sockaddr_storage ss; psin6 = (struct sockaddr_in6 *)&ss; memset(&ss, 0, sizeof(ss)); psin6->sin6_family = AF_INET6; psin6->sin6_addr = psl->sl_addr[i]; if (copy_to_user(&optval->gf_slist[i], &ss, sizeof(ss))) return -EFAULT; } return 0; done: read_unlock_bh(&idev->lock); rcu_read_unlock(); return err; }

Contributors

PersonTokensPropCommitsCommitProp
david l stevensdavid l stevens25968.88%16.25%
pre-gitpre-git7921.01%637.50%
eric dumazeteric dumazet205.32%318.75%
daniel lezcanodaniel lezcano92.39%16.25%
hideaki yoshifujihideaki yoshifuji51.33%318.75%
al viroal viro20.53%16.25%
americo wangamerico wang20.53%16.25%
Total376100.00%16100.00%


bool inet6_mc_check(struct sock *sk, const struct in6_addr *mc_addr, const struct in6_addr *src_addr) { struct