diff options
author | David L Stevens <dlstevens@us.ibm.com> | 2006-01-18 17:20:56 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2006-01-18 17:20:56 -0500 |
commit | ad12583f46bcb6ce93ccd99fa063c0d701146b2e (patch) | |
tree | 58d68cf1a60cd3cf2b8ee0e9fbdcb38454e2082e | |
parent | 7ac5459ec0f074022818af35c589b9e2b406d7c3 (diff) |
[IPV4]: Fix multiple bugs in IGMPv3
1) fix "mld_marksources()" to
a) send nothing when all queried sources are excluded
b) send full exclude report when source queried sources are
not excluded
c) don't schedule a timer when there's nothing to report
2) fix "add_grec()" to send empty-source records when it should
The original check doesn't account for a non-empty source
list with all sources inactive; the new code keeps that
short-circuit case, and also generates the group header
with an empty list if needed.
3) fix mca_crcount decrement to be after add_grec(), which needs
its original value
4) add/remove delete records and prevent current advertisements
when an exclude-mode filter moves from "active" to "inactive"
or vice versa based on new filter additions.
Items 1-3 are just IPv4 versions of the IPv6 bugs found
by Yan Zheng and fixed earlier. Item #4 is a related bug that
affects exclude-mode change records only (but not queries) and
also occurs in IPv6 (IPv6 version coming soon).
Signed-off-by: David L Stevens <dlstevens@us.ibm.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | net/ipv4/igmp.c | 152 |
1 files changed, 122 insertions, 30 deletions
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index 192092b89e53..d8ce7133cd8f 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c | |||
@@ -233,7 +233,18 @@ static int is_in(struct ip_mc_list *pmc, struct ip_sf_list *psf, int type, | |||
233 | case IGMPV3_MODE_IS_EXCLUDE: | 233 | case IGMPV3_MODE_IS_EXCLUDE: |
234 | if (gdeleted || sdeleted) | 234 | if (gdeleted || sdeleted) |
235 | return 0; | 235 | return 0; |
236 | return !(pmc->gsquery && !psf->sf_gsresp); | 236 | if (!(pmc->gsquery && !psf->sf_gsresp)) { |
237 | if (pmc->sfmode == MCAST_INCLUDE) | ||
238 | return 1; | ||
239 | /* don't include if this source is excluded | ||
240 | * in all filters | ||
241 | */ | ||
242 | if (psf->sf_count[MCAST_INCLUDE]) | ||
243 | return type == IGMPV3_MODE_IS_INCLUDE; | ||
244 | return pmc->sfcount[MCAST_EXCLUDE] == | ||
245 | psf->sf_count[MCAST_EXCLUDE]; | ||
246 | } | ||
247 | return 0; | ||
237 | case IGMPV3_CHANGE_TO_INCLUDE: | 248 | case IGMPV3_CHANGE_TO_INCLUDE: |
238 | if (gdeleted || sdeleted) | 249 | if (gdeleted || sdeleted) |
239 | return 0; | 250 | return 0; |
@@ -385,7 +396,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, | |||
385 | struct igmpv3_report *pih; | 396 | struct igmpv3_report *pih; |
386 | struct igmpv3_grec *pgr = NULL; | 397 | struct igmpv3_grec *pgr = NULL; |
387 | struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list; | 398 | struct ip_sf_list *psf, *psf_next, *psf_prev, **psf_list; |
388 | int scount, first, isquery, truncate; | 399 | int scount, stotal, first, isquery, truncate; |
389 | 400 | ||
390 | if (pmc->multiaddr == IGMP_ALL_HOSTS) | 401 | if (pmc->multiaddr == IGMP_ALL_HOSTS) |
391 | return skb; | 402 | return skb; |
@@ -395,25 +406,13 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, | |||
395 | truncate = type == IGMPV3_MODE_IS_EXCLUDE || | 406 | truncate = type == IGMPV3_MODE_IS_EXCLUDE || |
396 | type == IGMPV3_CHANGE_TO_EXCLUDE; | 407 | type == IGMPV3_CHANGE_TO_EXCLUDE; |
397 | 408 | ||
409 | stotal = scount = 0; | ||
410 | |||
398 | psf_list = sdeleted ? &pmc->tomb : &pmc->sources; | 411 | psf_list = sdeleted ? &pmc->tomb : &pmc->sources; |
399 | 412 | ||
400 | if (!*psf_list) { | 413 | if (!*psf_list) |
401 | if (type == IGMPV3_ALLOW_NEW_SOURCES || | 414 | goto empty_source; |
402 | type == IGMPV3_BLOCK_OLD_SOURCES) | 415 | |
403 | return skb; | ||
404 | if (pmc->crcount || isquery) { | ||
405 | /* make sure we have room for group header and at | ||
406 | * least one source. | ||
407 | */ | ||
408 | if (skb && AVAILABLE(skb) < sizeof(struct igmpv3_grec)+ | ||
409 | sizeof(__u32)) { | ||
410 | igmpv3_sendpack(skb); | ||
411 | skb = NULL; /* add_grhead will get a new one */ | ||
412 | } | ||
413 | skb = add_grhead(skb, pmc, type, &pgr); | ||
414 | } | ||
415 | return skb; | ||
416 | } | ||
417 | pih = skb ? (struct igmpv3_report *)skb->h.igmph : NULL; | 416 | pih = skb ? (struct igmpv3_report *)skb->h.igmph : NULL; |
418 | 417 | ||
419 | /* EX and TO_EX get a fresh packet, if needed */ | 418 | /* EX and TO_EX get a fresh packet, if needed */ |
@@ -426,7 +425,6 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, | |||
426 | } | 425 | } |
427 | } | 426 | } |
428 | first = 1; | 427 | first = 1; |
429 | scount = 0; | ||
430 | psf_prev = NULL; | 428 | psf_prev = NULL; |
431 | for (psf=*psf_list; psf; psf=psf_next) { | 429 | for (psf=*psf_list; psf; psf=psf_next) { |
432 | u32 *psrc; | 430 | u32 *psrc; |
@@ -460,7 +458,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, | |||
460 | } | 458 | } |
461 | psrc = (u32 *)skb_put(skb, sizeof(u32)); | 459 | psrc = (u32 *)skb_put(skb, sizeof(u32)); |
462 | *psrc = psf->sf_inaddr; | 460 | *psrc = psf->sf_inaddr; |
463 | scount++; | 461 | scount++; stotal++; |
464 | if ((type == IGMPV3_ALLOW_NEW_SOURCES || | 462 | if ((type == IGMPV3_ALLOW_NEW_SOURCES || |
465 | type == IGMPV3_BLOCK_OLD_SOURCES) && psf->sf_crcount) { | 463 | type == IGMPV3_BLOCK_OLD_SOURCES) && psf->sf_crcount) { |
466 | psf->sf_crcount--; | 464 | psf->sf_crcount--; |
@@ -475,6 +473,21 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ip_mc_list *pmc, | |||
475 | } | 473 | } |
476 | psf_prev = psf; | 474 | psf_prev = psf; |
477 | } | 475 | } |
476 | |||
477 | empty_source: | ||
478 | if (!stotal) { | ||
479 | if (type == IGMPV3_ALLOW_NEW_SOURCES || | ||
480 | type == IGMPV3_BLOCK_OLD_SOURCES) | ||
481 | return skb; | ||
482 | if (pmc->crcount || isquery) { | ||
483 | /* make sure we have room for group header */ | ||
484 | if (skb && AVAILABLE(skb)<sizeof(struct igmpv3_grec)) { | ||
485 | igmpv3_sendpack(skb); | ||
486 | skb = NULL; /* add_grhead will get a new one */ | ||
487 | } | ||
488 | skb = add_grhead(skb, pmc, type, &pgr); | ||
489 | } | ||
490 | } | ||
478 | if (pgr) | 491 | if (pgr) |
479 | pgr->grec_nsrcs = htons(scount); | 492 | pgr->grec_nsrcs = htons(scount); |
480 | 493 | ||
@@ -557,11 +570,11 @@ static void igmpv3_send_cr(struct in_device *in_dev) | |||
557 | skb = add_grec(skb, pmc, dtype, 1, 1); | 570 | skb = add_grec(skb, pmc, dtype, 1, 1); |
558 | } | 571 | } |
559 | if (pmc->crcount) { | 572 | if (pmc->crcount) { |
560 | pmc->crcount--; | ||
561 | if (pmc->sfmode == MCAST_EXCLUDE) { | 573 | if (pmc->sfmode == MCAST_EXCLUDE) { |
562 | type = IGMPV3_CHANGE_TO_INCLUDE; | 574 | type = IGMPV3_CHANGE_TO_INCLUDE; |
563 | skb = add_grec(skb, pmc, type, 1, 0); | 575 | skb = add_grec(skb, pmc, type, 1, 0); |
564 | } | 576 | } |
577 | pmc->crcount--; | ||
565 | if (pmc->crcount == 0) { | 578 | if (pmc->crcount == 0) { |
566 | igmpv3_clear_zeros(&pmc->tomb); | 579 | igmpv3_clear_zeros(&pmc->tomb); |
567 | igmpv3_clear_zeros(&pmc->sources); | 580 | igmpv3_clear_zeros(&pmc->sources); |
@@ -594,12 +607,12 @@ static void igmpv3_send_cr(struct in_device *in_dev) | |||
594 | 607 | ||
595 | /* filter mode changes */ | 608 | /* filter mode changes */ |
596 | if (pmc->crcount) { | 609 | if (pmc->crcount) { |
597 | pmc->crcount--; | ||
598 | if (pmc->sfmode == MCAST_EXCLUDE) | 610 | if (pmc->sfmode == MCAST_EXCLUDE) |
599 | type = IGMPV3_CHANGE_TO_EXCLUDE; | 611 | type = IGMPV3_CHANGE_TO_EXCLUDE; |
600 | else | 612 | else |
601 | type = IGMPV3_CHANGE_TO_INCLUDE; | 613 | type = IGMPV3_CHANGE_TO_INCLUDE; |
602 | skb = add_grec(skb, pmc, type, 0, 0); | 614 | skb = add_grec(skb, pmc, type, 0, 0); |
615 | pmc->crcount--; | ||
603 | } | 616 | } |
604 | spin_unlock_bh(&pmc->lock); | 617 | spin_unlock_bh(&pmc->lock); |
605 | } | 618 | } |
@@ -735,11 +748,43 @@ static void igmp_timer_expire(unsigned long data) | |||
735 | ip_ma_put(im); | 748 | ip_ma_put(im); |
736 | } | 749 | } |
737 | 750 | ||
738 | static void igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __u32 *srcs) | 751 | /* mark EXCLUDE-mode sources */ |
752 | static int igmp_xmarksources(struct ip_mc_list *pmc, int nsrcs, __u32 *srcs) | ||
753 | { | ||
754 | struct ip_sf_list *psf; | ||
755 | int i, scount; | ||
756 | |||
757 | scount = 0; | ||
758 | for (psf=pmc->sources; psf; psf=psf->sf_next) { | ||
759 | if (scount == nsrcs) | ||
760 | break; | ||
761 | for (i=0; i<nsrcs; i++) { | ||
762 | /* skip inactive filters */ | ||
763 | if (pmc->sfcount[MCAST_INCLUDE] || | ||
764 | pmc->sfcount[MCAST_EXCLUDE] != | ||
765 | psf->sf_count[MCAST_EXCLUDE]) | ||
766 | continue; | ||
767 | if (srcs[i] == psf->sf_inaddr) { | ||
768 | scount++; | ||
769 | break; | ||
770 | } | ||
771 | } | ||
772 | } | ||
773 | pmc->gsquery = 0; | ||
774 | if (scount == nsrcs) /* all sources excluded */ | ||
775 | return 0; | ||
776 | return 1; | ||
777 | } | ||
778 | |||
779 | static int igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __u32 *srcs) | ||
739 | { | 780 | { |
740 | struct ip_sf_list *psf; | 781 | struct ip_sf_list *psf; |
741 | int i, scount; | 782 | int i, scount; |
742 | 783 | ||
784 | if (pmc->sfmode == MCAST_EXCLUDE) | ||
785 | return igmp_xmarksources(pmc, nsrcs, srcs); | ||
786 | |||
787 | /* mark INCLUDE-mode sources */ | ||
743 | scount = 0; | 788 | scount = 0; |
744 | for (psf=pmc->sources; psf; psf=psf->sf_next) { | 789 | for (psf=pmc->sources; psf; psf=psf->sf_next) { |
745 | if (scount == nsrcs) | 790 | if (scount == nsrcs) |
@@ -751,6 +796,12 @@ static void igmp_marksources(struct ip_mc_list *pmc, int nsrcs, __u32 *srcs) | |||
751 | break; | 796 | break; |
752 | } | 797 | } |
753 | } | 798 | } |
799 | if (!scount) { | ||
800 | pmc->gsquery = 0; | ||
801 | return 0; | ||
802 | } | ||
803 | pmc->gsquery = 1; | ||
804 | return 1; | ||
754 | } | 805 | } |
755 | 806 | ||
756 | static void igmp_heard_report(struct in_device *in_dev, u32 group) | 807 | static void igmp_heard_report(struct in_device *in_dev, u32 group) |
@@ -845,6 +896,8 @@ static void igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb, | |||
845 | */ | 896 | */ |
846 | read_lock(&in_dev->mc_list_lock); | 897 | read_lock(&in_dev->mc_list_lock); |
847 | for (im=in_dev->mc_list; im!=NULL; im=im->next) { | 898 | for (im=in_dev->mc_list; im!=NULL; im=im->next) { |
899 | int changed; | ||
900 | |||
848 | if (group && group != im->multiaddr) | 901 | if (group && group != im->multiaddr) |
849 | continue; | 902 | continue; |
850 | if (im->multiaddr == IGMP_ALL_HOSTS) | 903 | if (im->multiaddr == IGMP_ALL_HOSTS) |
@@ -854,10 +907,11 @@ static void igmp_heard_query(struct in_device *in_dev, struct sk_buff *skb, | |||
854 | im->gsquery = im->gsquery && mark; | 907 | im->gsquery = im->gsquery && mark; |
855 | else | 908 | else |
856 | im->gsquery = mark; | 909 | im->gsquery = mark; |
857 | if (im->gsquery) | 910 | changed = !im->gsquery || |
858 | igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs); | 911 | igmp_marksources(im, ntohs(ih3->nsrcs), ih3->srcs); |
859 | spin_unlock_bh(&im->lock); | 912 | spin_unlock_bh(&im->lock); |
860 | igmp_mod_timer(im, max_delay); | 913 | if (changed) |
914 | igmp_mod_timer(im, max_delay); | ||
861 | } | 915 | } |
862 | read_unlock(&in_dev->mc_list_lock); | 916 | read_unlock(&in_dev->mc_list_lock); |
863 | } | 917 | } |
@@ -1510,7 +1564,7 @@ static void sf_markstate(struct ip_mc_list *pmc) | |||
1510 | 1564 | ||
1511 | static int sf_setstate(struct ip_mc_list *pmc) | 1565 | static int sf_setstate(struct ip_mc_list *pmc) |
1512 | { | 1566 | { |
1513 | struct ip_sf_list *psf; | 1567 | struct ip_sf_list *psf, *dpsf; |
1514 | int mca_xcount = pmc->sfcount[MCAST_EXCLUDE]; | 1568 | int mca_xcount = pmc->sfcount[MCAST_EXCLUDE]; |
1515 | int qrv = pmc->interface->mr_qrv; | 1569 | int qrv = pmc->interface->mr_qrv; |
1516 | int new_in, rv; | 1570 | int new_in, rv; |
@@ -1522,8 +1576,46 @@ static int sf_setstate(struct ip_mc_list *pmc) | |||
1522 | !psf->sf_count[MCAST_INCLUDE]; | 1576 | !psf->sf_count[MCAST_INCLUDE]; |
1523 | } else | 1577 | } else |
1524 | new_in = psf->sf_count[MCAST_INCLUDE] != 0; | 1578 | new_in = psf->sf_count[MCAST_INCLUDE] != 0; |
1525 | if (new_in != psf->sf_oldin) { | 1579 | if (new_in) { |
1526 | psf->sf_crcount = qrv; | 1580 | if (!psf->sf_oldin) { |
1581 | struct ip_sf_list *prev = 0; | ||
1582 | |||
1583 | for (dpsf=pmc->tomb; dpsf; dpsf=dpsf->sf_next) { | ||
1584 | if (dpsf->sf_inaddr == psf->sf_inaddr) | ||
1585 | break; | ||
1586 | prev = dpsf; | ||
1587 | } | ||
1588 | if (dpsf) { | ||
1589 | if (prev) | ||
1590 | prev->sf_next = dpsf->sf_next; | ||
1591 | else | ||
1592 | pmc->tomb = dpsf->sf_next; | ||
1593 | kfree(dpsf); | ||
1594 | } | ||
1595 | psf->sf_crcount = qrv; | ||
1596 | rv++; | ||
1597 | } | ||
1598 | } else if (psf->sf_oldin) { | ||
1599 | |||
1600 | psf->sf_crcount = 0; | ||
1601 | /* | ||
1602 | * add or update "delete" records if an active filter | ||
1603 | * is now inactive | ||
1604 | */ | ||
1605 | for (dpsf=pmc->tomb; dpsf; dpsf=dpsf->sf_next) | ||
1606 | if (dpsf->sf_inaddr == psf->sf_inaddr) | ||
1607 | break; | ||
1608 | if (!dpsf) { | ||
1609 | dpsf = (struct ip_sf_list *) | ||
1610 | kmalloc(sizeof(*dpsf), GFP_ATOMIC); | ||
1611 | if (!dpsf) | ||
1612 | continue; | ||
1613 | *dpsf = *psf; | ||
1614 | /* pmc->lock held by callers */ | ||
1615 | dpsf->sf_next = pmc->tomb; | ||
1616 | pmc->tomb = dpsf; | ||
1617 | } | ||
1618 | dpsf->sf_crcount = qrv; | ||
1527 | rv++; | 1619 | rv++; |
1528 | } | 1620 | } |
1529 | } | 1621 | } |