diff options
author | bms <bms@FreeBSD.org> | 2007-03-29 21:39:22 +0000 |
---|---|---|
committer | bms <bms@FreeBSD.org> | 2007-03-29 21:39:22 +0000 |
commit | 44f999134ffe53f466ccaa6b33fe11dc67c47746 (patch) | |
tree | 63a19ea2f8222ea1bd48a2dc80eb99332f4f56f6 /sys/netinet/in.c | |
parent | 16df05c48d392cb31d976cc1797106b97760116d (diff) | |
download | FreeBSD-src-44f999134ffe53f466ccaa6b33fe11dc67c47746.zip FreeBSD-src-44f999134ffe53f466ccaa6b33fe11dc67c47746.tar.gz |
Fix a bug in IPv4 address configuration exposed by refcounting.
* Join the IPv4 all-hosts multicast group 224.0.0.1 once only;
that is, when an IPv4 address is first configured on an interface.
* Do not join it for subsequent IPv4 addresses as this violates IGMP.
* Be sure to leave the group when all IPv4 addresses have been removed
from the interface.
* Add two DIAGNOSTIC printfs related to the issue.
Further care and attention is needed in this area; it is suggested that
netinet's attachment to the ifnet structure be compartmentalized and
non-implicit.
Bug found by: andre
MFC after: 1 month
Diffstat (limited to 'sys/netinet/in.c')
-rw-r--r-- | sys/netinet/in.c | 53 |
1 files changed, 40 insertions, 13 deletions
diff --git a/sys/netinet/in.c b/sys/netinet/in.c index d03a06f..04a9161 100644 --- a/sys/netinet/in.c +++ b/sys/netinet/in.c @@ -224,13 +224,17 @@ in_control(so, cmd, data, ifp, td) register struct ifreq *ifr = (struct ifreq *)data; register struct in_ifaddr *ia = 0, *iap; register struct ifaddr *ifa; + struct in_addr allhosts_addr; struct in_addr dst; struct in_ifaddr *oia; struct in_aliasreq *ifra = (struct in_aliasreq *)data; struct sockaddr_in oldaddr; int error, hostIsNew, iaIsNew, maskIsNew, s; + int iaIsFirst; + iaIsFirst = 0; iaIsNew = 0; + allhosts_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); switch (cmd) { case SIOCALIFADDR: @@ -281,6 +285,8 @@ in_control(so, cmd, data, ifp, td) break; } } + if (ia == NULL) + iaIsFirst = 1; } switch (cmd) { @@ -422,8 +428,11 @@ in_control(so, cmd, data, ifp, td) (struct sockaddr_in *) &ifr->ifr_addr, 1); if (error != 0 && iaIsNew) break; - if (error == 0) + if (error == 0) { + if (iaIsFirst && (ifp->if_flags & IFF_MULTICAST) != 0) + in_addmulti(&allhosts_addr, ifp); EVENTHANDLER_INVOKE(ifaddr_event, ifp); + } return (0); case SIOCSIFNETMASK: @@ -466,8 +475,11 @@ in_control(so, cmd, data, ifp, td) if ((ifp->if_flags & IFF_BROADCAST) && (ifra->ifra_broadaddr.sin_family == AF_INET)) ia->ia_broadaddr = ifra->ifra_broadaddr; - if (error == 0) + if (error == 0) { + if (iaIsFirst && (ifp->if_flags & IFF_MULTICAST) != 0) + in_addmulti(&allhosts_addr, ifp); EVENTHANDLER_INVOKE(ifaddr_event, ifp); + } return (error); case SIOCDIFADDR: @@ -502,8 +514,27 @@ in_control(so, cmd, data, ifp, td) s = splnet(); TAILQ_REMOVE(&ifp->if_addrhead, &ia->ia_ifa, ifa_link); TAILQ_REMOVE(&in_ifaddrhead, ia, ia_link); - if (ia->ia_addr.sin_family == AF_INET) + if (ia->ia_addr.sin_family == AF_INET) { LIST_REMOVE(ia, ia_hash); + /* + * If this is the last IPv4 address configured on this + * interface, leave the all-hosts group. + * XXX: This is quite ugly because of locking and structure. + */ + oia = NULL; + IFP_TO_IA(ifp, oia); + if (oia == NULL) { + struct in_multi *inm; + + IFF_LOCKGIANT(ifp); + IN_MULTI_LOCK(); + IN_LOOKUP_MULTI(allhosts_addr, ifp, inm); + if (inm != NULL) + in_delmulti_locked(inm); + IN_MULTI_UNLOCK(); + IFF_UNLOCKGIANT(ifp); + } + } IFAFREE(&ia->ia_ifa); splx(s); @@ -792,16 +823,6 @@ in_ifinit(ifp, ia, sin, scrub) if ((error = in_addprefix(ia, flags)) != 0) return (error); - /* - * If the interface supports multicast, join the "all hosts" - * multicast group on that interface. - */ - if (ifp->if_flags & IFF_MULTICAST) { - struct in_addr addr; - - addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP); - in_addmulti(&addr, ifp); - } return (error); } @@ -1113,6 +1134,9 @@ in_delmulti_locked(struct in_multi *inm) igmp_leavegroup(inm); ifma = inm->inm_ifma; +#ifdef DIAGNOSTIC + printf("%s: purging ifma %p\n", __func__, ifma); +#endif KASSERT(ifma->ifma_protospec == inm, ("%s: ifma_protospec != inm", __func__)); ifma->ifma_protospec = NULL; @@ -1134,6 +1158,9 @@ in_purgemaddrs(struct ifnet *ifp) struct in_multi *inm; struct in_multi *oinm; +#ifdef DIAGNOSTIC + printf("%s: purging ifp %p\n", __func__, ifp); +#endif IFF_LOCKGIANT(ifp); IN_MULTI_LOCK(); LIST_FOREACH_SAFE(inm, &in_multihead, inm_link, oinm) { |