diff options
author | bms <bms@FreeBSD.org> | 2006-05-14 14:22:49 +0000 |
---|---|---|
committer | bms <bms@FreeBSD.org> | 2006-05-14 14:22:49 +0000 |
commit | 18895e4f429bdde8c9a49ec2740de9f7ee1eb851 (patch) | |
tree | 0b86a0cd16f824f56236d76a1229680116c518af | |
parent | 28c1069b5c9c208fec373fcfcd5aa45351b7bc2a (diff) | |
download | FreeBSD-src-18895e4f429bdde8c9a49ec2740de9f7ee1eb851.zip FreeBSD-src-18895e4f429bdde8c9a49ec2740de9f7ee1eb851.tar.gz |
Fix a long-standing limitation in IPv4 multicast group membership.
By making the imo_membership array a dynamically allocated vector,
this minimizes disruption to existing IPv4 multicast code. This
change breaks the ABI for the kernel module ip_mroute.ko, and may
cause a small amount of churn for folks working on the IGMPv3 merge.
Previously, sockets were subject to a compile-time limitation on
the number of IPv4 group memberships, which was hard-coded to 20.
The imo_membership relationship, however, is 1:1 with regards to
a tuple of multicast group address and interface address. Users who
ran routing protocols such as OSPF ran into this limitation on machines
with a large system interface tree.
-rw-r--r-- | sys/netinet/in.h | 9 | ||||
-rw-r--r-- | sys/netinet/ip_output.c | 30 | ||||
-rw-r--r-- | sys/netinet/ip_var.h | 5 |
3 files changed, 40 insertions, 4 deletions
diff --git a/sys/netinet/in.h b/sys/netinet/in.h index 1476bf4..570ed33 100644 --- a/sys/netinet/in.h +++ b/sys/netinet/in.h @@ -424,7 +424,14 @@ __END_DECLS */ #define IP_DEFAULT_MULTICAST_TTL 1 /* normally limit m'casts to 1 hop */ #define IP_DEFAULT_MULTICAST_LOOP 1 /* normally hear sends if a member */ -#define IP_MAX_MEMBERSHIPS 20 /* per socket */ + +/* + * The imo_membership vector for each socket is now dynamically allocated at + * run-time, bounded by USHRT_MAX, and is reallocated when needed, sized + * according to a power-of-two increment. + */ +#define IP_MIN_MEMBERSHIPS 31 +#define IP_MAX_MEMBERSHIPS 4095 /* * Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP. diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c index 915512e..9cb9871 100644 --- a/sys/netinet/ip_output.c +++ b/sys/netinet/ip_output.c @@ -1138,6 +1138,7 @@ static struct ip_moptions * ip_findmoptions(struct inpcb *inp) { struct ip_moptions *imo; + struct in_multi **immp; INP_LOCK(inp); if (inp->inp_moptions != NULL) @@ -1146,6 +1147,8 @@ ip_findmoptions(struct inpcb *inp) INP_UNLOCK(inp); imo = (struct ip_moptions*)malloc(sizeof(*imo), M_IPMOPTS, M_WAITOK); + immp = (struct in_multi **)malloc((sizeof(*immp) * IP_MIN_MEMBERSHIPS), + M_IPMOPTS, M_WAITOK); imo->imo_multicast_ifp = NULL; imo->imo_multicast_addr.s_addr = INADDR_ANY; @@ -1153,9 +1156,12 @@ ip_findmoptions(struct inpcb *inp) imo->imo_multicast_ttl = IP_DEFAULT_MULTICAST_TTL; imo->imo_multicast_loop = IP_DEFAULT_MULTICAST_LOOP; imo->imo_num_memberships = 0; + imo->imo_max_memberships = IP_MIN_MEMBERSHIPS; + imo->imo_membership = immp; INP_LOCK(inp); if (inp->inp_moptions != NULL) { + free(immp, M_IPMOPTS); free(imo, M_IPMOPTS); return (inp->inp_moptions); } @@ -1360,11 +1366,32 @@ ip_setmoptions(struct inpcb *inp, struct sockopt *sopt) splx(s); break; } - if (i == IP_MAX_MEMBERSHIPS) { + if (imo->imo_num_memberships == imo->imo_max_memberships) { + struct in_multi **nmships, **omships; + size_t newmax; + /* + * Resize the vector to next power-of-two minus 1. If the + * size would exceed the maximum then we know we've really + * run out of entries. Otherwise, we realloc() the vector + * with the INP lock held to avoid introducing a race. + */ + nmships = NULL; + omships = imo->imo_membership; + newmax = ((imo->imo_max_memberships + 1) * 2) - 1; + if (newmax <= IP_MAX_MEMBERSHIPS) { + nmships = (struct in_multi **)realloc(omships, +sizeof(*nmships) * newmax, M_IPMOPTS, M_NOWAIT); + if (nmships != NULL) { + imo->imo_membership = nmships; + imo->imo_max_memberships = newmax; + } + } + if (nmships == NULL) { INP_UNLOCK(inp); error = ETOOMANYREFS; splx(s); break; + } } /* * Everything looks good; add a new record to the multicast @@ -1538,6 +1565,7 @@ ip_freemoptions(imo) if (imo != NULL) { for (i = 0; i < imo->imo_num_memberships; ++i) in_delmulti(imo->imo_membership[i]); + free(imo->imo_membership, M_IPMOPTS); free(imo, M_IPMOPTS); } } diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h index 6d0a122..a051825 100644 --- a/sys/netinet/ip_var.h +++ b/sys/netinet/ip_var.h @@ -85,11 +85,12 @@ struct ipoption { struct ip_moptions { struct ifnet *imo_multicast_ifp; /* ifp for outgoing multicasts */ struct in_addr imo_multicast_addr; /* ifindex/addr on MULTICAST_IF */ + u_long imo_multicast_vif; /* vif num outgoing multicasts */ u_char imo_multicast_ttl; /* TTL for outgoing multicasts */ u_char imo_multicast_loop; /* 1 => hear sends if a member */ u_short imo_num_memberships; /* no. memberships this socket */ - struct in_multi *imo_membership[IP_MAX_MEMBERSHIPS]; - u_long imo_multicast_vif; /* vif num outgoing multicasts */ + u_short imo_max_memberships; /* max memberships this socket */ + struct in_multi **imo_membership; /* group memberships */ }; struct ipstat { |