summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--UPDATING19
-rw-r--r--lib/libc/net/Makefile.inc5
-rw-r--r--lib/libc/net/Symbol.map4
-rw-r--r--share/man/man4/ip.416
-rw-r--r--sys/conf/files1
-rw-r--r--sys/netinet/igmp.h39
-rw-r--r--sys/netinet/igmp_var.h56
-rw-r--r--sys/netinet/in.c165
-rw-r--r--sys/netinet/in.h124
-rw-r--r--sys/netinet/in_mcast.c1786
-rw-r--r--sys/netinet/in_pcb.c3
-rw-r--r--sys/netinet/in_var.h48
-rw-r--r--sys/netinet/ip_carp.c3
-rw-r--r--sys/netinet/ip_output.c500
-rw-r--r--sys/netinet/ip_var.h37
-rw-r--r--sys/netinet/sctp_pcb.c2
-rw-r--r--sys/netinet/udp_usrreq.c121
-rw-r--r--sys/netinet/udp_var.h1
-rw-r--r--sys/netinet6/in6.h20
-rw-r--r--sys/netinet6/in6_ifattach.c32
-rw-r--r--sys/netinet6/in6_pcb.c3
-rw-r--r--sys/sys/param.h2
-rw-r--r--sys/sys/socket.h3
-rw-r--r--tools/regression/netinet/ipsockopt/ipsockopt.c4
-rw-r--r--usr.bin/netstat/inet.c5
-rw-r--r--usr.sbin/mtest/mtest.c38
26 files changed, 2311 insertions, 726 deletions
diff --git a/UPDATING b/UPDATING
index d509867..a6cfbe2 100644
--- a/UPDATING
+++ b/UPDATING
@@ -21,6 +21,25 @@ NOTE TO PEOPLE WHO THINK THAT FreeBSD 7.x IS SLOW:
developers choose to disable these features on build machines
to maximize performance.
+20070612:
+ The IPv4 multicast socket code has been considerably modified, and
+ moved to the file sys/netinet/in_mcast.c. Initial support for the
+ RFC 3678 Source-Specific Multicast Socket API has been added to
+ the IPv4 network stack.
+
+ Strict multicast and broadcast reception is now the default for
+ UDP/IPv4 sockets; the net.inet.udp.strict_mcast_mship sysctl variable
+ has now been removed.
+
+ The RFC 1724 hack for interface selection has been removed; the use
+ of the Linux-derived ip_mreqn structure with IP_MULTICAST_IF has
+ been added to replace it. Consumers such as routed will soon be
+ updated to reflect this.
+
+ These changes affect users who are running routed(8) or rdisc(8)
+ from the FreeBSD base system on point-to-point or unnumbered
+ interfaces.
+
20070610:
The net80211 layer has changed significantly and all wireless
drivers that depend on it need to be recompiled. Further these
diff --git a/lib/libc/net/Makefile.inc b/lib/libc/net/Makefile.inc
index 3592f50..23c1c9d 100644
--- a/lib/libc/net/Makefile.inc
+++ b/lib/libc/net/Makefile.inc
@@ -14,7 +14,7 @@ SRCS+= base64.c ether_addr.c eui64.c \
ip6opt.c linkaddr.c map_v4v6.c name6.c ntoh.c \
nsdispatch.c nslexer.c nsparser.c nss_compat.c \
rcmd.c rcmdsh.c recv.c rthdr.c sctp_sys_calls.c send.c \
- sockatmark.c vars.c
+ sockatmark.c sourcefilter.c vars.c
.if ${MK_NS_CACHING} != "no"
SRCS+= nscache.c nscachedcli.c
@@ -52,6 +52,7 @@ MAN+= byteorder.3 ethers.3 eui64.3 \
inet6_opt_init.3 inet6_option_space.3 inet6_rth_space.3 \
inet6_rthdr_space.3 linkaddr.3 \
nsdispatch.3 rcmd.3 rcmdsh.3 resolver.3 sockatmark.3 \
+ setsourcefilter.3 \
sctp_bindx.3 sctp_connectx.3 sctp_freepaddrs.3 \
sctp_getaddrlen.3 sctp_getassocid.3 sctp_getpaddrs.3 \
sctp_opt_info.3 sctp_recvmsg.3 sctp_send.3 sctp_sendmsg.3 \
@@ -121,6 +122,8 @@ MLINKS+=resolver.3 dn_comp.3 resolver.3 dn_expand.3 resolver.3 res_init.3 \
resolver.3 res_search.3 resolver.3 res_send.3 resolver.3 dn_skipname.3 \
resolver.3 ns_get16.3 resolver.3 ns_get32.3 \
resolver.3 ns_put16.3 resolver.3 ns_put32.3
+MLINKS+=sourcefilter.3 setipv4sourcefilter.3 getipv4sourcefilter.3 \
+ sourcefilter.3 setsourcefilter.3 getsourcefilter.3
.if ${MK_HESIOD} != "no"
SRCS+= hesiod.c
diff --git a/lib/libc/net/Symbol.map b/lib/libc/net/Symbol.map
index 7fb0f3b..de44cc5 100644
--- a/lib/libc/net/Symbol.map
+++ b/lib/libc/net/Symbol.map
@@ -137,6 +137,10 @@ FBSD_1.0 {
sctp_send;
sctp_sendx;
sctp_recvmsg;
+ setipv4sourcefilter;
+ getipv4sourcefilter;
+ getsourcefilter;
+ setsourcefilter;
};
FBSDprivate_1.0 {
diff --git a/share/man/man4/ip.4 b/share/man/man4/ip.4
index e531f74..833c463 100644
--- a/share/man/man4/ip.4
+++ b/share/man/man4/ip.4
@@ -32,7 +32,7 @@
.\" @(#)ip.4 8.2 (Berkeley) 11/30/93
.\" $FreeBSD$
.\"
-.Dd March 18, 2007
+.Dd April 9, 2007
.Dt IP 4
.Os
.Sh NAME
@@ -420,6 +420,16 @@ where "addr" is the local
address of the desired interface or
.Dv INADDR_ANY
to specify the default interface.
+.Pp
+To specify an interface by index, an instance of
+.Vt ip_mreqn
+should be passed instead.
+The
+.Vt imr_ifindex
+member should be set to the index of the desired interface,
+or 0 to specify the default interface.
+The kernel differentiates between these two structures by their size.
+.\"
An interface's local IP address and multicast capability can
be obtained via the
.Dv SIOCGIFCONF
@@ -672,3 +682,7 @@ The
.Nm
protocol appeared in
.Bx 4.2 .
+The
+.Vt ip_mreqn
+structure appeared in
+.Tn Linux 2.4 .
diff --git a/sys/conf/files b/sys/conf/files
index c08878a..08309bc 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1801,6 +1801,7 @@ netinet/ip_carp.c optional carp
netinet/in_gif.c optional gif inet
netinet/ip_gre.c optional gre inet
netinet/ip_id.c optional inet
+netinet/in_mcast.c optional inet
netinet/in_pcb.c optional inet
netinet/in_proto.c optional inet \
compile-with "${NORMAL_C} -I$S/contrib/pf"
diff --git a/sys/netinet/igmp.h b/sys/netinet/igmp.h
index 05a70fc..4c2f9e0 100644
--- a/sys/netinet/igmp.h
+++ b/sys/netinet/igmp.h
@@ -55,7 +55,42 @@ struct igmp {
struct in_addr igmp_group; /* group address being reported */
}; /* (zero for queries) */
-#define IGMP_MINLEN 8
+struct igmpv3 {
+ u_char igmp_type; /* version & type of IGMP message */
+ u_char igmp_code; /* subtype for routing msgs */
+ u_short igmp_cksum; /* IP-style checksum */
+ struct in_addr igmp_group; /* group address being reported */
+ /* (zero for queries) */
+ u_char igmp_misc; /* reserved/suppress/robustness */
+ u_char igmp_qqi; /* querier's query interval */
+ u_short igmp_numsrc; /* number of sources */
+ /*struct in_addr igmp_sources[1];*/ /* source addresses */
+};
+
+struct igmp_grouprec {
+ u_char ig_type; /* record type */
+ u_char ig_datalen; /* length of auxiliary data */
+ u_short ig_numsrc; /* number of sources */
+ struct in_addr ig_group; /* group address being reported */
+ /*struct in_addr ig_sources[1];*/ /* source addresses */
+};
+
+struct igmp_report {
+ u_char ir_type; /* record type */
+ u_char ir_rsv1; /* reserved */
+ u_short ir_cksum; /* checksum */
+ u_short ir_rsv2; /* reserved */
+ u_short ir_numgrps; /* number of group records */
+ struct igmp_grouprec ir_groups[1]; /* group records */
+};
+
+#define IGMP_MINLEN 8
+#define IGMP_HDRLEN 8
+#define IGMP_GRPREC_HDRLEN 8
+#define IGMP_PREPEND 0
+
+#define IGMP_QRV(pigmp) ((pigmp)->igmp_misc & (0x07)) /* XXX */
+#define IGMP_MAXSOURCES(len) (((len) - 12) >> 2) /* XXX */
/*
* Message types, including version number.
@@ -71,6 +106,8 @@ struct igmp {
#define IGMP_MTRACE_RESP 0x1e /* traceroute resp.(to sender)*/
#define IGMP_MTRACE 0x1f /* mcast traceroute messages */
+#define IGMP_V3_MEMBERSHIP_REPORT 0x22 /* Ver. 3 membership report */
+
#define IGMP_MAX_HOST_REPORT_DELAY 10 /* max delay for response to */
/* query (in seconds) according */
/* to RFC1112 */
diff --git a/sys/netinet/igmp_var.h b/sys/netinet/igmp_var.h
index aa083a8..11c3769 100644
--- a/sys/netinet/igmp_var.h
+++ b/sys/netinet/igmp_var.h
@@ -56,6 +56,7 @@ struct igmpstat {
u_int igps_rcv_badreports; /* received invalid reports */
u_int igps_rcv_ourreports; /* received reports for our groups */
u_int igps_snd_reports; /* sent membership reports */
+ u_int igps_rcv_toolong; /* received with too many bytes */
};
#ifdef _KERNEL
@@ -68,12 +69,20 @@ struct igmpstat {
#define IGMP_IREPORTEDLAST 1
/*
+ * State masks for IGMPv3
+ */
+#define IGMP_V3_NONEXISTENT 0x01
+#define IGMP_V3_OTHERMEMBER 0x02
+#define IGMP_V3_IREPORTEDLAST 0x04
+
+/*
* We must remember what version the subnet's querier is.
* We conveniently use the IGMP message type for the proper
* membership report to keep this state.
*/
#define IGMP_V1_ROUTER IGMP_V1_MEMBERSHIP_REPORT
#define IGMP_V2_ROUTER IGMP_V2_MEMBERSHIP_REPORT
+#define IGMP_V3_ROUTER IGMP_V3_MEMBERSHIP_REPORT
/*
* Revert to new router if we haven't heard from an old router in
@@ -81,6 +90,51 @@ struct igmpstat {
*/
#define IGMP_AGE_THRESHOLD 540
+/*
+ * IGMPv3 protocol defaults
+ */
+#define IGMP_INIT_ROBVAR 2 /* Robustness */
+#define IGMP_MAX_ROBVAR 7
+#define IGMP_INIT_QRYINT 125 /* Querier's Query interval */
+#define IGMP_MAX_QRYINT 255
+#define IGMP_INIT_QRYRSP 10 /* Query Response interval */
+#define IGMP_DEF_QRYMRT 10
+#define IGMP_UNSOL_INT 1 /* Unsolicited Report interval */
+
+/*
+ * IGMPv3 report types
+ */
+#define IGMP_REPORT_MODE_IN 1 /* mode-is-include */
+#define IGMP_REPORT_MODE_EX 2 /* mode-is-exclude */
+#define IGMP_REPORT_TO_IN 3 /* change-to-include */
+#define IGMP_REPORT_TO_EX 4 /* change-to-exclude */
+#define IGMP_REPORT_ALLOW_NEW 5 /* allow-new-sources */
+#define IGMP_REPORT_BLOCK_OLD 6 /* block-old-sources */
+
+/*
+ * Report types
+ */
+#define IGMP_MASK_CUR_STATE 0x01 /* Report current-state */
+#define IGMP_MASK_ALLOW_NEW 0x02 /* Report source as allow-new */
+#define IGMP_MASK_BLOCK_OLD 0x04 /* Report source as block-old */
+#define IGMP_MASK_TO_IN 0x08 /* Report source as to_in */
+#define IGMP_MASK_TO_EX 0x10 /* Report source as to_ex */
+#define IGMP_MASK_STATE_T1 0x20 /* State at T1 */
+#define IGMP_MASK_STATE_T2 0x40 /* State at T2 */
+#define IGMP_MASK_IF_STATE 0x80 /* Report current-state per interface */
+
+#define IGMP_MASK_STATE_TX (IGMP_MASK_STATE_T1 | IGMP_MASK_STATE_T2)
+#define IGMP_MASK_PENDING (IGMP_MASK_CUR_STATE | \
+ IGMP_MASK_ALLOW_NEW | \
+ IGMP_MASK_BLOCK_OLD)
+
+/*
+ * List identifiers
+ */
+#define IGMP_EXCLUDE_LIST 1 /* exclude list used to tag report */
+#define IGMP_INCLUDE_LIST 2 /* include list used to tag report */
+#define IGMP_RECORDED_LIST 3 /* recorded list used to tag report */
+
void igmp_init(void);
void igmp_input(struct mbuf *, int);
void igmp_joingroup(struct in_multi *);
@@ -100,6 +154,6 @@ SYSCTL_DECL(_net_inet_igmp);
#define IGMPCTL_NAMES { \
{ 0, 0 }, \
- { "stats", CTLTYPE_STRUCT }, \
+ { "stats", CTLTYPE_STRUCT } \
}
#endif
diff --git a/sys/netinet/in.c b/sys/netinet/in.c
index dd20e00..d0c36fa 100644
--- a/sys/netinet/in.c
+++ b/sys/netinet/in.c
@@ -49,10 +49,7 @@
#include <netinet/in.h>
#include <netinet/in_var.h>
#include <netinet/in_pcb.h>
-
-#include <netinet/igmp_var.h>
-
-static MALLOC_DEFINE(M_IPMADDR, "in_multi", "internet multicast address");
+#include <netinet/ip_var.h>
static int in_mask2len(struct in_addr *);
static void in_len2mask(struct in_addr *, int);
@@ -74,17 +71,6 @@ SYSCTL_INT(_net_inet_ip, OID_AUTO, same_prefix_carp_only, CTLFLAG_RW,
&sameprefixcarponly, 0,
"Refuse to create same prefixes on different interfaces");
-/*
- * The IPv4 multicast list (in_multihead and associated structures) are
- * protected by the global in_multi_mtx. See in_var.h for more details. For
- * now, in_multi_mtx is marked as recursible due to IGMP's calling back into
- * ip_output() to send IGMP packets while holding the lock; this probably is
- * not quite desirable.
- */
-struct in_multihead in_multihead; /* XXX BSS initialization */
-struct mtx in_multi_mtx;
-MTX_SYSINIT(in_multi_mtx, &in_multi_mtx, "in_multi_mtx", MTX_DEF | MTX_RECURSE);
-
extern struct inpcbinfo ripcbinfo;
extern struct inpcbinfo udbinfo;
@@ -977,155 +963,6 @@ in_broadcast(struct in_addr in, struct ifnet *ifp)
}
/*
- * Add an address to the list of IP multicast addresses for a given interface.
- */
-struct in_multi *
-in_addmulti(struct in_addr *ap, struct ifnet *ifp)
-{
- struct in_multi *inm;
-
- inm = NULL;
-
- IFF_LOCKGIANT(ifp);
- IN_MULTI_LOCK();
-
- IN_LOOKUP_MULTI(*ap, ifp, inm);
- if (inm != NULL) {
- /*
- * If we already joined this group, just bump the
- * refcount and return it.
- */
- KASSERT(inm->inm_refcount >= 1,
- ("%s: bad refcount %d", __func__, inm->inm_refcount));
- ++inm->inm_refcount;
- } else do {
- struct sockaddr_in sin;
- struct ifmultiaddr *ifma;
- struct in_multi *ninm;
- int error;
-
- bzero(&sin, sizeof sin);
- sin.sin_family = AF_INET;
- sin.sin_len = sizeof(struct sockaddr_in);
- sin.sin_addr = *ap;
-
- /*
- * Check if a link-layer group is already associated
- * with this network-layer group on the given ifnet.
- * If so, bump the refcount on the existing network-layer
- * group association and return it.
- */
- error = if_addmulti(ifp, (struct sockaddr *)&sin, &ifma);
- if (error)
- break;
- if (ifma->ifma_protospec != NULL) {
- inm = (struct in_multi *)ifma->ifma_protospec;
-#ifdef INVARIANTS
- if (inm->inm_ifma != ifma || inm->inm_ifp != ifp ||
- inm->inm_addr.s_addr != ap->s_addr)
- panic("%s: ifma is inconsistent", __func__);
-#endif
- ++inm->inm_refcount;
- break;
- }
-
- /*
- * A new membership is needed; construct it and
- * perform the IGMP join.
- */
- ninm = malloc(sizeof(*ninm), M_IPMADDR, M_NOWAIT | M_ZERO);
- if (ninm == NULL) {
- if_delmulti_ifma(ifma);
- break;
- }
- ninm->inm_addr = *ap;
- ninm->inm_ifp = ifp;
- ninm->inm_ifma = ifma;
- ninm->inm_refcount = 1;
- ifma->ifma_protospec = ninm;
- LIST_INSERT_HEAD(&in_multihead, ninm, inm_link);
-
- igmp_joingroup(ninm);
-
- inm = ninm;
- } while (0);
-
- IN_MULTI_UNLOCK();
- IFF_UNLOCKGIANT(ifp);
-
- return (inm);
-}
-
-/*
- * Delete a multicast address record.
- * It is OK to call this routine if the underlying ifnet went away.
- *
- * XXX: To deal with the ifp going away, we cheat; the link-layer code in net
- * will set ifma_ifp to NULL when the associated ifnet instance is detached
- * from the system.
- * The only reason we need to violate layers and check ifma_ifp here at all
- * is because certain hardware drivers still require Giant to be held,
- * and it must always be taken before other locks.
- */
-void
-in_delmulti(struct in_multi *inm)
-{
- struct ifnet *ifp;
-
- KASSERT(inm->inm_ifma != NULL, ("%s: no ifma", __func__));
- ifp = inm->inm_ifma->ifma_ifp;
-
- if (ifp != NULL) {
- /*
- * Sanity check that netinet's notion of ifp is the
- * same as net's.
- */
- KASSERT(inm->inm_ifp == ifp, ("%s: bad ifp", __func__));
- IFF_LOCKGIANT(ifp);
- }
-
- IN_MULTI_LOCK();
- in_delmulti_locked(inm);
- IN_MULTI_UNLOCK();
-
- if (ifp != NULL)
- IFF_UNLOCKGIANT(ifp);
-}
-
-/*
- * Delete a multicast address record, with locks held.
- *
- * It is OK to call this routine if the ifp went away.
- * Assumes that caller holds the IN_MULTI lock, and that
- * Giant was taken before other locks if required by the hardware.
- */
-void
-in_delmulti_locked(struct in_multi *inm)
-{
- struct ifmultiaddr *ifma;
-
- IN_MULTI_LOCK_ASSERT();
- KASSERT(inm->inm_refcount >= 1, ("%s: freeing freed inm", __func__));
-
- if (--inm->inm_refcount == 0) {
- 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;
-
- LIST_REMOVE(inm, inm_link);
- free(inm, M_IPMADDR);
-
- if_delmulti_ifma(ifma);
- }
-}
-
-/*
* Delete all IPv4 multicast address records, and associated link-layer
* multicast address records, associated with ifp.
*/
diff --git a/sys/netinet/in.h b/sys/netinet/in.h
index 89181e0..1fca43d 100644
--- a/sys/netinet/in.h
+++ b/sys/netinet/in.h
@@ -84,6 +84,33 @@ struct in_addr {
#define _STRUCT_IN_ADDR_DECLARED
#endif
+#ifndef _SOCKLEN_T_DECLARED
+typedef __socklen_t socklen_t;
+#define _SOCKLEN_T_DECLARED
+#endif
+
+/* Avoid collision with original definition in sys/socket.h. */
+#ifndef _STRUCT_SOCKADDR_STORAGE_DECLARED
+/*
+ * RFC 2553: protocol-independent placeholder for socket addresses
+ */
+#define _SS_MAXSIZE 128U
+#define _SS_ALIGNSIZE (sizeof(__int64_t))
+#define _SS_PAD1SIZE (_SS_ALIGNSIZE - sizeof(unsigned char) - \
+ sizeof(sa_family_t))
+#define _SS_PAD2SIZE (_SS_MAXSIZE - sizeof(unsigned char) - \
+ sizeof(sa_family_t) - _SS_PAD1SIZE - _SS_ALIGNSIZE)
+
+struct sockaddr_storage {
+ unsigned char ss_len; /* address length */
+ sa_family_t ss_family; /* address family */
+ char __ss_pad1[_SS_PAD1SIZE];
+ __int64_t __ss_align; /* force desired struct alignment */
+ char __ss_pad2[_SS_PAD2SIZE];
+};
+#define _STRUCT_SOCKADDR_STORAGE_DECLARED
+#endif
+
/* Socket address, internet style. */
struct sockaddr_in {
uint8_t sin_len;
@@ -390,7 +417,8 @@ __END_DECLS
#define IP_RECVDSTADDR 7 /* bool; receive IP dst addr w/dgram */
#define IP_SENDSRCADDR IP_RECVDSTADDR /* cmsg_type to set src addr */
#define IP_RETOPTS 8 /* ip_opts; set/get IP options */
-#define IP_MULTICAST_IF 9 /* u_char; set/get IP multicast i/f */
+#define IP_MULTICAST_IF 9 /* struct in_addr *or* struct ip_mreqn;
+ * set/get IP multicast i/f */
#define IP_MULTICAST_TTL 10 /* u_char; set/get IP multicast ttl */
#define IP_MULTICAST_LOOP 11 /* u_char; set/get IP multicast loopback */
#define IP_ADD_MEMBERSHIP 12 /* ip_mreq; add an IP group membership */
@@ -435,6 +463,23 @@ __END_DECLS
#define IP_MINTTL 66 /* minimum TTL for packet or drop */
#define IP_DONTFRAG 67 /* don't fragment packet */
+/* IPv4 Source Filter Multicast API [RFC3678] */
+#define IP_ADD_SOURCE_MEMBERSHIP 70 /* join a source-specific group */
+#define IP_DROP_SOURCE_MEMBERSHIP 71 /* drop a single source */
+#define IP_BLOCK_SOURCE 72 /* block a source */
+#define IP_UNBLOCK_SOURCE 73 /* unblock a source */
+
+/* The following option is private; do not use it from user applications. */
+#define IP_MSFILTER 74 /* set/get filter list */
+
+/* Protocol Independent Multicast API [RFC3678] */
+#define MCAST_JOIN_GROUP 80 /* join an any-source group */
+#define MCAST_LEAVE_GROUP 81 /* leave all sources for group */
+#define MCAST_JOIN_SOURCE_GROUP 82 /* join a source-specific group */
+#define MCAST_LEAVE_SOURCE_GROUP 83 /* leave a single source */
+#define MCAST_BLOCK_SOURCE 84 /* block a source */
+#define MCAST_UNBLOCK_SOURCE 85 /* unblock a source */
+
/*
* Defaults and limits for options
*/
@@ -448,6 +493,7 @@ __END_DECLS
*/
#define IP_MIN_MEMBERSHIPS 31
#define IP_MAX_MEMBERSHIPS 4095
+#define IP_MAX_SOURCE_FILTER 1024 /* # of filters per socket, per group */
/*
* Argument structure for IP_ADD_MEMBERSHIP and IP_DROP_MEMBERSHIP.
@@ -458,6 +504,82 @@ struct ip_mreq {
};
/*
+ * Modified argument structure for IP_MULTICAST_IF, obtained from Linux.
+ * This is used to specify an interface index for multicast sends, as
+ * the IPv4 legacy APIs do not support this (unless IP_SENDIF is available).
+ */
+struct ip_mreqn {
+ struct in_addr imr_multiaddr; /* IP multicast address of group */
+ struct in_addr imr_address; /* local IP address of interface */
+ int imr_ifindex; /* Interface index; cast to uint32_t */
+};
+
+/*
+ * Argument structure for IPv4 Multicast Source Filter APIs. [RFC3678]
+ */
+struct ip_mreq_source {
+ struct in_addr imr_multiaddr; /* IP multicast address of group */
+ struct in_addr imr_sourceaddr; /* IP address of source */
+ struct in_addr imr_interface; /* local IP address of interface */
+};
+
+/*
+ * Argument structures for Protocol-Independent Multicast Source
+ * Filter APIs. [RFC3678]
+ */
+struct group_req {
+ uint32_t gr_interface; /* interface index */
+ struct sockaddr_storage gr_group; /* group address */
+};
+
+struct group_source_req {
+ uint32_t gsr_interface; /* interface index */
+ struct sockaddr_storage gsr_group; /* group address */
+ struct sockaddr_storage gsr_source; /* source address */
+};
+
+#ifndef __MSFILTERREQ_DEFINED
+#define __MSFILTERREQ_DEFINED
+/*
+ * The following structure is private; do not use it from user applications.
+ * It is used to communicate IP_MSFILTER/IPV6_MSFILTER information between
+ * the RFC 3678 libc functions and the kernel.
+ */
+struct __msfilterreq {
+ uint32_t msfr_ifindex; /* interface index */
+ uint32_t msfr_fmode; /* filter mode for group */
+ uint32_t msfr_nsrcs; /* # of sources in msfr_srcs */
+ struct sockaddr_storage msfr_group; /* group address */
+ struct sockaddr_storage *msfr_srcs; /* pointer to the first member
+ * of a contiguous array of
+ * sources to filter in full.
+ */
+};
+#endif
+
+struct sockaddr;
+
+/*
+ * Advanced (Full-state) APIs [RFC3678]
+ * The RFC specifies uint_t for the 6th argument to [sg]etsourcefilter().
+ * We use uint32_t here to be consistent.
+ */
+int setipv4sourcefilter(int, struct in_addr, struct in_addr, uint32_t,
+ uint32_t, struct in_addr *);
+int getipv4sourcefilter(int, struct in_addr, struct in_addr, uint32_t *,
+ uint32_t *, struct in_addr *);
+int setsourcefilter(int, uint32_t, struct sockaddr *, socklen_t,
+ uint32_t, uint32_t, struct sockaddr_storage *);
+int getsourcefilter(int, uint32_t, struct sockaddr *, socklen_t,
+ uint32_t *, uint32_t *, struct sockaddr_storage *);
+
+/*
+ * Filter modes; also used to represent per-socket filter mode internally.
+ */
+#define MCAST_INCLUDE 1 /* fmode: include these source(s) */
+#define MCAST_EXCLUDE 2 /* fmode: exclude these source(s) */
+
+/*
* Argument for IP_PORTRANGE:
* - which range to search when port is unspecified at bind() or connect()
*/
diff --git a/sys/netinet/in_mcast.c b/sys/netinet/in_mcast.c
new file mode 100644
index 0000000..0f0dc2d
--- /dev/null
+++ b/sys/netinet/in_mcast.c
@@ -0,0 +1,1786 @@
+/*-
+ * Copyright (c) 2007 Bruce M. Simpson.
+ * Copyright (c) 2005 Robert N. M. Watson.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * IPv4 multicast socket, group, and socket option processing module.
+ * Until further notice, this file requires INET to compile.
+ * TODO: Make this infrastructure independent of address family.
+ * TODO: Teach netinet6 to use this code.
+ * TODO: Hook up SSM logic to IGMPv3/MLDv2.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/sysctl.h>
+
+#include <net/if.h>
+#include <net/if_dl.h>
+#include <net/route.h>
+
+#include <netinet/in.h>
+#include <netinet/in_systm.h>
+#include <netinet/in_pcb.h>
+#include <netinet/in_var.h>
+#include <netinet/ip_var.h>
+#include <netinet/igmp_var.h>
+
+#ifndef __SOCKUNION_DECLARED
+union sockunion {
+ struct sockaddr_storage ss;
+ struct sockaddr sa;
+ struct sockaddr_dl sdl;
+ struct sockaddr_in sin;
+#ifdef INET6
+ struct sockaddr_in6 sin6;
+#endif
+};
+typedef union sockunion sockunion_t;
+#define __SOCKUNION_DECLARED
+#endif /* __SOCKUNION_DECLARED */
+
+static MALLOC_DEFINE(M_IPMADDR, "in_multi", "IPv4 multicast group");
+static MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "IPv4 multicast options");
+static MALLOC_DEFINE(M_IPMSOURCE, "in_msource", "IPv4 multicast source filter");
+
+/*
+ * The IPv4 multicast list (in_multihead and associated structures) are
+ * protected by the global in_multi_mtx. See in_var.h for more details. For
+ * now, in_multi_mtx is marked as recursible due to IGMP's calling back into
+ * ip_output() to send IGMP packets while holding the lock; this probably is
+ * not quite desirable.
+ */
+struct in_multihead in_multihead; /* XXX BSS initialization */
+struct mtx in_multi_mtx;
+MTX_SYSINIT(in_multi_mtx, &in_multi_mtx, "in_multi_mtx", MTX_DEF | MTX_RECURSE);
+
+/*
+ * Functions with non-static linkage defined in this file should be
+ * declared in in_var.h:
+ * imo_match_group()
+ * imo_match_source()
+ * in_addmulti()
+ * in_delmulti()
+ * in_delmulti_locked()
+ * and ip_var.h:
+ * inp_freemoptions()
+ * inp_getmoptions()
+ * inp_setmoptions()
+ */
+static int imo_grow(struct ip_moptions *);
+static int imo_join_source(struct ip_moptions *, size_t, sockunion_t *);
+static int imo_leave_source(struct ip_moptions *, size_t, sockunion_t *);
+static int inp_change_source_filter(struct inpcb *, struct sockopt *);
+static struct ip_moptions *
+ inp_findmoptions(struct inpcb *);
+static int inp_get_source_filters(struct inpcb *, struct sockopt *);
+static int inp_join_group(struct inpcb *, struct sockopt *);
+static int inp_leave_group(struct inpcb *, struct sockopt *);
+static int inp_set_multicast_if(struct inpcb *, struct sockopt *);
+static int inp_set_source_filters(struct inpcb *, struct sockopt *);
+
+/*
+ * Resize the ip_moptions vector to the next power-of-two minus 1.
+ * May be called with locks held; do not sleep.
+ */
+static int
+imo_grow(struct ip_moptions *imo)
+{
+ struct in_multi **nmships;
+ struct in_multi **omships;
+ struct in_mfilter *nmfilters;
+ struct in_mfilter *omfilters;
+ size_t idx;
+ size_t newmax;
+ size_t oldmax;
+
+ nmships = NULL;
+ nmfilters = NULL;
+ omships = imo->imo_membership;
+ omfilters = imo->imo_mfilters;
+ oldmax = imo->imo_max_memberships;
+ newmax = ((oldmax + 1) * 2) - 1;
+
+ if (newmax <= IP_MAX_MEMBERSHIPS) {
+ nmships = (struct in_multi **)realloc(omships,
+ sizeof(struct in_multi *) * newmax, M_IPMOPTS, M_NOWAIT);
+ nmfilters = (struct in_mfilter *)realloc(omfilters,
+ sizeof(struct in_mfilter) * newmax, M_IPMSOURCE, M_NOWAIT);
+ if (nmships != NULL && nmfilters != NULL) {
+ /* Initialize newly allocated source filter heads. */
+ for (idx = oldmax; idx < newmax; idx++) {
+ nmfilters[idx].imf_fmode = MCAST_EXCLUDE;
+ nmfilters[idx].imf_nsources = 0;
+ TAILQ_INIT(&nmfilters[idx].imf_sources);
+ }
+ imo->imo_max_memberships = newmax;
+ imo->imo_membership = nmships;
+ imo->imo_mfilters = nmfilters;
+ }
+ }
+
+ if (nmships == NULL || nmfilters == NULL) {
+ if (nmships != NULL)
+ free(nmships, M_IPMOPTS);
+ if (nmfilters != NULL)
+ free(nmfilters, M_IPMSOURCE);
+ return (ETOOMANYREFS);
+ }
+
+ return (0);
+}
+
+/*
+ * Add a source to a multicast filter list.
+ * Assumes the associated inpcb is locked.
+ */
+static int
+imo_join_source(struct ip_moptions *imo, size_t gidx, sockunion_t *src)
+{
+ struct in_msource *ims, *nims;
+ struct in_mfilter *imf;
+
+ KASSERT(src->ss.ss_family == AF_INET, ("%s: !AF_INET", __func__));
+ KASSERT(imo->imo_mfilters != NULL,
+ ("%s: imo_mfilters vector not allocated", __func__));
+
+ imf = &imo->imo_mfilters[gidx];
+ if (imf->imf_nsources == IP_MAX_SOURCE_FILTER)
+ return (ENOBUFS);
+
+ ims = imo_match_source(imo, gidx, &src->sa);
+ if (ims != NULL)
+ return (EADDRNOTAVAIL);
+
+ /* Do not sleep with inp lock held. */
+ MALLOC(nims, struct in_msource *, sizeof(struct in_msource),
+ M_IPMSOURCE, M_NOWAIT | M_ZERO);
+ if (nims == NULL)
+ return (ENOBUFS);
+
+ nims->ims_addr = src->ss;
+ TAILQ_INSERT_TAIL(&imf->imf_sources, nims, ims_next);
+ imf->imf_nsources++;
+
+ return (0);
+}
+
+static int
+imo_leave_source(struct ip_moptions *imo, size_t gidx, sockunion_t *src)
+{
+ struct in_msource *ims;
+ struct in_mfilter *imf;
+
+ KASSERT(src->ss.ss_family == AF_INET, ("%s: !AF_INET", __func__));
+ KASSERT(imo->imo_mfilters != NULL,
+ ("%s: imo_mfilters vector not allocated", __func__));
+
+ imf = &imo->imo_mfilters[gidx];
+ if (imf->imf_nsources == IP_MAX_SOURCE_FILTER)
+ return (ENOBUFS);
+
+ ims = imo_match_source(imo, gidx, &src->sa);
+ if (ims == NULL)
+ return (EADDRNOTAVAIL);
+
+ TAILQ_REMOVE(&imf->imf_sources, ims, ims_next);
+ FREE(ims, M_IPMSOURCE);
+ imf->imf_nsources--;
+
+ return (0);
+}
+
+/*
+ * Find an IPv4 multicast group entry for this ip_moptions instance
+ * which matches the specified group, and optionally an interface.
+ * Return its index into the array, or -1 if not found.
+ */
+size_t
+imo_match_group(struct ip_moptions *imo, struct ifnet *ifp,
+ struct sockaddr *group)
+{
+ sockunion_t *gsa;
+ struct in_multi **pinm;
+ int idx;
+ int nmships;
+
+ gsa = (sockunion_t *)group;
+
+ /* The imo_membership array may be lazy allocated. */
+ if (imo->imo_membership == NULL || imo->imo_num_memberships == 0)
+ return (-1);
+
+ nmships = imo->imo_num_memberships;
+ pinm = &imo->imo_membership[0];
+ for (idx = 0; idx < nmships; idx++, pinm++) {
+ if (*pinm == NULL)
+ continue;
+#if 0
+ printf("%s: trying ifp = %p, inaddr = %s ", __func__,
+ ifp, inet_ntoa(gsa->sin.sin_addr));
+ printf("against %p, %s\n",
+ (*pinm)->inm_ifp, inet_ntoa((*pinm)->inm_addr));
+#endif
+ if ((ifp == NULL || ((*pinm)->inm_ifp == ifp)) &&
+ (*pinm)->inm_addr.s_addr == gsa->sin.sin_addr.s_addr) {
+ break;
+ }
+ }
+ if (idx >= nmships)
+ idx = -1;
+
+ return (idx);
+}
+
+/*
+ * Find a multicast source entry for this imo which matches
+ * the given group index for this socket, and source address.
+ */
+struct in_msource *
+imo_match_source(struct ip_moptions *imo, size_t gidx, struct sockaddr *src)
+{
+ struct in_mfilter *imf;
+ struct in_msource *ims, *pims;
+
+ KASSERT(src->sa_family == AF_INET, ("%s: !AF_INET", __func__));
+ KASSERT(gidx != -1 && gidx < imo->imo_num_memberships,
+ ("%s: invalid index %d\n", __func__, (int)gidx));
+
+ /* The imo_mfilters array may be lazy allocated. */
+ if (imo->imo_mfilters == NULL)
+ return (NULL);
+
+ pims = NULL;
+ imf = &imo->imo_mfilters[gidx];
+ TAILQ_FOREACH(ims, &imf->imf_sources, ims_next) {
+ /*
+ * Perform bitwise comparison of two IPv4 addresses.
+ * TODO: Do the same for IPv6.
+ * Do not use sa_equal() for this as it is not aware of
+ * deeper structure in sockaddr_in or sockaddr_in6.
+ */
+ if (((struct sockaddr_in *)&ims->ims_addr)->sin_addr.s_addr ==
+ ((struct sockaddr_in *)src)->sin_addr.s_addr) {
+ pims = ims;
+ break;
+ }
+ }
+
+ return (pims);
+}
+
+/*
+ * Join an IPv4 multicast group.
+ */
+struct in_multi *
+in_addmulti(struct in_addr *ap, struct ifnet *ifp)
+{
+ struct in_multi *inm;
+
+ inm = NULL;
+
+ IFF_LOCKGIANT(ifp);
+ IN_MULTI_LOCK();
+
+ IN_LOOKUP_MULTI(*ap, ifp, inm);
+ if (inm != NULL) {
+ /*
+ * If we already joined this group, just bump the
+ * refcount and return it.
+ */
+ KASSERT(inm->inm_refcount >= 1,
+ ("%s: bad refcount %d", __func__, inm->inm_refcount));
+ ++inm->inm_refcount;
+ } else do {
+ sockunion_t gsa;
+ struct ifmultiaddr *ifma;
+ struct in_multi *ninm;
+ int error;
+
+ memset(&gsa, 0, sizeof(gsa));
+ gsa.sin.sin_family = AF_INET;
+ gsa.sin.sin_len = sizeof(struct sockaddr_in);
+ gsa.sin.sin_addr = *ap;
+
+ /*
+ * Check if a link-layer group is already associated
+ * with this network-layer group on the given ifnet.
+ * If so, bump the refcount on the existing network-layer
+ * group association and return it.
+ */
+ error = if_addmulti(ifp, &gsa.sa, &ifma);
+ if (error)
+ break;
+ if (ifma->ifma_protospec != NULL) {
+ inm = (struct in_multi *)ifma->ifma_protospec;
+#ifdef INVARIANTS
+ if (inm->inm_ifma != ifma || inm->inm_ifp != ifp ||
+ inm->inm_addr.s_addr != ap->s_addr)
+ panic("%s: ifma is inconsistent", __func__);
+#endif
+ ++inm->inm_refcount;
+ break;
+ }
+
+ /*
+ * A new membership is needed; construct it and
+ * perform the IGMP join.
+ */
+ ninm = malloc(sizeof(*ninm), M_IPMADDR, M_NOWAIT | M_ZERO);
+ if (ninm == NULL) {
+ if_delmulti_ifma(ifma);
+ break;
+ }
+ ninm->inm_addr = *ap;
+ ninm->inm_ifp = ifp;
+ ninm->inm_ifma = ifma;
+ ninm->inm_refcount = 1;
+ ifma->ifma_protospec = ninm;
+ LIST_INSERT_HEAD(&in_multihead, ninm, inm_link);
+
+ igmp_joingroup(ninm);
+
+ inm = ninm;
+ } while (0);
+
+ IN_MULTI_UNLOCK();
+ IFF_UNLOCKGIANT(ifp);
+
+ return (inm);
+}
+
+/*
+ * Leave an IPv4 multicast group.
+ * It is OK to call this routine if the underlying ifnet went away.
+ *
+ * XXX: To deal with the ifp going away, we cheat; the link-layer code in net
+ * will set ifma_ifp to NULL when the associated ifnet instance is detached
+ * from the system.
+ *
+ * The only reason we need to violate layers and check ifma_ifp here at all
+ * is because certain hardware drivers still require Giant to be held,
+ * and it must always be taken before other locks.
+ */
+void
+in_delmulti(struct in_multi *inm)
+{
+ struct ifnet *ifp;
+
+ KASSERT(inm != NULL, ("%s: inm is NULL", __func__));
+ KASSERT(inm->inm_ifma != NULL, ("%s: no ifma", __func__));
+ ifp = inm->inm_ifma->ifma_ifp;
+
+ if (ifp != NULL) {
+ /*
+ * Sanity check that netinet's notion of ifp is the
+ * same as net's.
+ */
+ KASSERT(inm->inm_ifp == ifp, ("%s: bad ifp", __func__));
+ IFF_LOCKGIANT(ifp);
+ }
+
+ IN_MULTI_LOCK();
+ in_delmulti_locked(inm);
+ IN_MULTI_UNLOCK();
+
+ if (ifp != NULL)
+ IFF_UNLOCKGIANT(ifp);
+}
+
+/*
+ * Delete a multicast address record, with locks held.
+ *
+ * It is OK to call this routine if the ifp went away.
+ * Assumes that caller holds the IN_MULTI lock, and that
+ * Giant was taken before other locks if required by the hardware.
+ */
+void
+in_delmulti_locked(struct in_multi *inm)
+{
+ struct ifmultiaddr *ifma;
+
+ IN_MULTI_LOCK_ASSERT();
+ KASSERT(inm->inm_refcount >= 1, ("%s: freeing freed inm", __func__));
+
+ if (--inm->inm_refcount == 0) {
+ igmp_leavegroup(inm);
+
+ ifma = inm->inm_ifma;
+#ifdef DIAGNOSTIC
+ if (bootverbose)
+ printf("%s: purging ifma %p\n", __func__, ifma);
+#endif
+ KASSERT(ifma->ifma_protospec == inm,
+ ("%s: ifma_protospec != inm", __func__));
+ ifma->ifma_protospec = NULL;
+
+ LIST_REMOVE(inm, inm_link);
+ free(inm, M_IPMADDR);
+
+ if_delmulti_ifma(ifma);
+ }
+}
+
+/*
+ * Block or unblock an ASM/SSM multicast source on an inpcb.
+ */
+static int
+inp_change_source_filter(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct group_source_req gsr;
+ sockunion_t *gsa, *ssa;
+ struct ifnet *ifp;
+ struct in_mfilter *imf;
+ struct ip_moptions *imo;
+ struct in_msource *ims;
+ size_t idx;
+ int error;
+ int block;
+
+ ifp = NULL;
+ error = 0;
+ block = 0;
+
+ memset(&gsr, 0, sizeof(struct group_source_req));
+ gsa = (sockunion_t *)&gsr.gsr_group;
+ ssa = (sockunion_t *)&gsr.gsr_source;
+
+ switch (sopt->sopt_name) {
+ case IP_BLOCK_SOURCE:
+ case IP_UNBLOCK_SOURCE: {
+ struct ip_mreq_source mreqs;
+
+ error = sooptcopyin(sopt, &mreqs,
+ sizeof(struct ip_mreq_source),
+ sizeof(struct ip_mreq_source));
+ if (error)
+ return (error);
+
+ gsa->sin.sin_family = AF_INET;
+ gsa->sin.sin_len = sizeof(struct sockaddr_in);
+ gsa->sin.sin_addr = mreqs.imr_multiaddr;
+
+ ssa->sin.sin_family = AF_INET;
+ ssa->sin.sin_len = sizeof(struct sockaddr_in);
+ ssa->sin.sin_addr = mreqs.imr_sourceaddr;
+
+ if (mreqs.imr_interface.s_addr != INADDR_ANY)
+ INADDR_TO_IFP(mreqs.imr_interface, ifp);
+
+ if (sopt->sopt_name == IP_BLOCK_SOURCE)
+ block = 1;
+
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: imr_interface = %s, ifp = %p\n",
+ __func__, inet_ntoa(mreqs.imr_interface), ifp);
+ }
+#endif
+ break;
+ }
+
+ case MCAST_BLOCK_SOURCE:
+ case MCAST_UNBLOCK_SOURCE:
+ error = sooptcopyin(sopt, &gsr,
+ sizeof(struct group_source_req),
+ sizeof(struct group_source_req));
+ if (error)
+ return (error);
+
+ if (gsa->sin.sin_family != AF_INET ||
+ gsa->sin.sin_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+
+ if (ssa->sin.sin_family != AF_INET ||
+ ssa->sin.sin_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+
+ if (gsr.gsr_interface == 0 || if_index < gsr.gsr_interface)
+ return (EADDRNOTAVAIL);
+
+ ifp = ifnet_byindex(gsr.gsr_interface);
+
+ if (sopt->sopt_name == MCAST_BLOCK_SOURCE)
+ block = 1;
+ break;
+
+ default:
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: unknown sopt_name %d\n", __func__,
+ sopt->sopt_name);
+ }
+#endif
+ return (EOPNOTSUPP);
+ break;
+ }
+
+ /* XXX INET6 */
+ if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
+ return (EINVAL);
+
+ /*
+ * Check if we are actually a member of this group.
+ */
+ imo = inp_findmoptions(inp);
+ idx = imo_match_group(imo, ifp, &gsa->sa);
+ if (idx == -1 || imo->imo_mfilters == NULL) {
+ error = EADDRNOTAVAIL;
+ goto out_locked;
+ }
+
+ KASSERT(imo->imo_mfilters != NULL,
+ ("%s: imo_mfilters not allocated", __func__));
+ imf = &imo->imo_mfilters[idx];
+
+ /*
+ * SSM multicast truth table for block/unblock operations.
+ *
+ * Operation Filter Mode Entry exists? Action
+ *
+ * block exclude no add source to filter
+ * unblock include no add source to filter
+ * block include no EINVAL
+ * unblock exclude no EINVAL
+ * block exclude yes EADDRNOTAVAIL
+ * unblock include yes EADDRNOTAVAIL
+ * block include yes remove source from filter
+ * unblock exclude yes remove source from filter
+ *
+ * FreeBSD does not explicitly distinguish between ASM and SSM
+ * mode sockets; all sockets are assumed to have a filter list.
+ */
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: imf_fmode is %s\n", __func__,
+ imf->imf_fmode == MCAST_INCLUDE ? "include" : "exclude");
+ }
+#endif
+ ims = imo_match_source(imo, idx, &ssa->sa);
+ if (ims == NULL) {
+ if ((block == 1 && imf->imf_fmode == MCAST_EXCLUDE) ||
+ (block == 0 && imf->imf_fmode == MCAST_INCLUDE)) {
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: adding %s to filter list\n",
+ __func__, inet_ntoa(ssa->sin.sin_addr));
+ }
+#endif
+ error = imo_join_source(imo, idx, ssa);
+ }
+ if ((block == 1 && imf->imf_fmode == MCAST_INCLUDE) ||
+ (block == 0 && imf->imf_fmode == MCAST_EXCLUDE)) {
+ /*
+ * If the socket is in inclusive mode:
+ * the source is already blocked as it has no entry.
+ * If the socket is in exclusive mode:
+ * the source is already unblocked as it has no entry.
+ */
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: ims %p; %s already [un]blocked\n",
+ __func__, ims,
+ inet_ntoa(ssa->sin.sin_addr));
+ }
+#endif
+ error = EINVAL;
+ }
+ } else {
+ if ((block == 1 && imf->imf_fmode == MCAST_EXCLUDE) ||
+ (block == 0 && imf->imf_fmode == MCAST_INCLUDE)) {
+ /*
+ * If the socket is in exclusive mode:
+ * the source is already blocked as it has an entry.
+ * If the socket is in inclusive mode:
+ * the source is already unblocked as it has an entry.
+ */
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: ims %p; %s already [un]blocked\n",
+ __func__, ims,
+ inet_ntoa(ssa->sin.sin_addr));
+ }
+#endif
+ error = EADDRNOTAVAIL;
+ }
+ if ((block == 1 && imf->imf_fmode == MCAST_INCLUDE) ||
+ (block == 0 && imf->imf_fmode == MCAST_EXCLUDE)) {
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: removing %s from filter list\n",
+ __func__, inet_ntoa(ssa->sin.sin_addr));
+ }
+#endif
+ error = imo_leave_source(imo, idx, ssa);
+ }
+ }
+
+out_locked:
+ INP_UNLOCK(inp);
+ return (error);
+}
+
+/*
+ * Given an inpcb, return its multicast options structure pointer. Accepts
+ * an unlocked inpcb pointer, but will return it locked. May sleep.
+ */
+static struct ip_moptions *
+inp_findmoptions(struct inpcb *inp)
+{
+ struct ip_moptions *imo;
+ struct in_multi **immp;
+ struct in_mfilter *imfp;
+ size_t idx;
+
+ INP_LOCK(inp);
+ if (inp->inp_moptions != NULL)
+ return (inp->inp_moptions);
+
+ 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 | M_ZERO);
+ imfp = (struct in_mfilter *)malloc(
+ sizeof(struct in_mfilter) * IP_MIN_MEMBERSHIPS,
+ M_IPMSOURCE, M_WAITOK);
+
+ imo->imo_multicast_ifp = NULL;
+ imo->imo_multicast_addr.s_addr = INADDR_ANY;
+ imo->imo_multicast_vif = -1;
+ 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;
+
+ /* Initialize per-group source filters. */
+ for (idx = 0; idx < IP_MIN_MEMBERSHIPS; idx++) {
+ imfp[idx].imf_fmode = MCAST_EXCLUDE;
+ imfp[idx].imf_nsources = 0;
+ TAILQ_INIT(&imfp[idx].imf_sources);
+ }
+ imo->imo_mfilters = imfp;
+
+ INP_LOCK(inp);
+ if (inp->inp_moptions != NULL) {
+ free(imfp, M_IPMSOURCE);
+ free(immp, M_IPMOPTS);
+ free(imo, M_IPMOPTS);
+ return (inp->inp_moptions);
+ }
+ inp->inp_moptions = imo;
+ return (imo);
+}
+
+/*
+ * Discard the IP multicast options (and source filters).
+ */
+void
+inp_freemoptions(struct ip_moptions *imo)
+{
+ struct in_mfilter *imf;
+ struct in_msource *ims, *tims;
+ size_t idx, nmships;
+
+ KASSERT(imo != NULL, ("%s: ip_moptions is NULL", __func__));
+
+ nmships = imo->imo_num_memberships;
+ for (idx = 0; idx < nmships; ++idx) {
+ in_delmulti(imo->imo_membership[idx]);
+
+ if (imo->imo_mfilters != NULL) {
+ imf = &imo->imo_mfilters[idx];
+ TAILQ_FOREACH_SAFE(ims, &imf->imf_sources,
+ ims_next, tims) {
+ TAILQ_REMOVE(&imf->imf_sources, ims, ims_next);
+ FREE(ims, M_IPMSOURCE);
+ imf->imf_nsources--;
+ }
+ KASSERT(imf->imf_nsources == 0,
+ ("%s: did not free all imf_nsources", __func__));
+ }
+ }
+
+ if (imo->imo_mfilters != NULL)
+ free(imo->imo_mfilters, M_IPMSOURCE);
+ free(imo->imo_membership, M_IPMOPTS);
+ free(imo, M_IPMOPTS);
+}
+
+/*
+ * Atomically get source filters on a socket for an IPv4 multicast group.
+ * Called with INP lock held; returns with lock released.
+ */
+static int
+inp_get_source_filters(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct __msfilterreq msfr;
+ sockunion_t *gsa;
+ struct ifnet *ifp;
+ struct ip_moptions *imo;
+ struct in_mfilter *imf;
+ struct in_msource *ims;
+ struct sockaddr_storage *ptss;
+ struct sockaddr_storage *tss;
+ int error;
+ size_t idx;
+
+ INP_LOCK_ASSERT(inp);
+
+ imo = inp->inp_moptions;
+ KASSERT(imo != NULL, ("%s: null ip_moptions", __func__));
+
+ INP_UNLOCK(inp);
+
+ error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
+ sizeof(struct __msfilterreq));
+ if (error)
+ return (error);
+
+ if (msfr.msfr_ifindex == 0 || if_index < msfr.msfr_ifindex)
+ return (EINVAL);
+
+ ifp = ifnet_byindex(msfr.msfr_ifindex);
+ if (ifp == NULL)
+ return (EINVAL);
+
+ INP_LOCK(inp);
+
+ /*
+ * Lookup group on the socket.
+ */
+ gsa = (sockunion_t *)&msfr.msfr_group;
+ idx = imo_match_group(imo, ifp, &gsa->sa);
+ if (idx == -1 || imo->imo_mfilters == NULL) {
+ INP_UNLOCK(inp);
+ return (EADDRNOTAVAIL);
+ }
+
+ imf = &imo->imo_mfilters[idx];
+ msfr.msfr_fmode = imf->imf_fmode;
+ msfr.msfr_nsrcs = imf->imf_nsources;
+
+ /*
+ * If the user specified a buffer, copy out the source filter
+ * entries to userland gracefully.
+ * msfr.msfr_nsrcs is always set to the total number of filter
+ * entries which the kernel currently has for this group.
+ */
+ tss = NULL;
+ if (msfr.msfr_srcs != NULL && msfr.msfr_nsrcs > 0) {
+ /*
+ * Make a copy of the source vector so that we do not
+ * thrash the inpcb lock whilst copying it out.
+ * We only copy out the number of entries which userland
+ * has asked for, but we always tell userland how big the
+ * buffer really needs to be.
+ */
+ MALLOC(tss, struct sockaddr_storage *,
+ sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs,
+ M_TEMP, M_NOWAIT);
+ if (tss == NULL) {
+ error = ENOBUFS;
+ } else {
+ ptss = tss;
+ TAILQ_FOREACH(ims, &imf->imf_sources, ims_next) {
+ memcpy(ptss++, &ims->ims_addr,
+ sizeof(struct sockaddr_storage));
+ }
+ }
+ }
+
+ INP_UNLOCK(inp);
+
+ if (tss != NULL) {
+ error = copyout(tss, msfr.msfr_srcs,
+ sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs);
+ FREE(tss, M_TEMP);
+ }
+
+ if (error)
+ return (error);
+
+ error = sooptcopyout(sopt, &msfr, sizeof(struct __msfilterreq));
+
+ return (error);
+}
+
+/*
+ * Return the IP multicast options in response to user getsockopt().
+ */
+int
+inp_getmoptions(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct ip_mreqn mreqn;
+ struct ip_moptions *imo;
+ struct ifnet *ifp;
+ struct in_ifaddr *ia;
+ int error, optval;
+ u_char coptval;
+
+ INP_LOCK(inp);
+ imo = inp->inp_moptions;
+
+ error = 0;
+ switch (sopt->sopt_name) {
+ case IP_MULTICAST_VIF:
+ if (imo != NULL)
+ optval = imo->imo_multicast_vif;
+ else
+ optval = -1;
+ INP_UNLOCK(inp);
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+ break;
+
+ case IP_MULTICAST_IF:
+ memset(&mreqn, 0, sizeof(struct ip_mreqn));
+ if (imo != NULL) {
+ ifp = imo->imo_multicast_ifp;
+ if (imo->imo_multicast_addr.s_addr != INADDR_ANY) {
+ mreqn.imr_address = imo->imo_multicast_addr;
+ } else if (ifp != NULL) {
+ mreqn.imr_ifindex = ifp->if_index;
+ IFP_TO_IA(ifp, ia);
+ if (ia != NULL) {
+ mreqn.imr_address =
+ IA_SIN(ia)->sin_addr;
+ }
+ }
+ }
+ INP_UNLOCK(inp);
+ if (sopt->sopt_valsize == sizeof(struct ip_mreqn)) {
+ error = sooptcopyout(sopt, &mreqn,
+ sizeof(struct ip_mreqn));
+ } else {
+ error = sooptcopyout(sopt, &mreqn.imr_address,
+ sizeof(struct in_addr));
+ }
+ break;
+
+ case IP_MULTICAST_TTL:
+ if (imo == 0)
+ optval = coptval = IP_DEFAULT_MULTICAST_TTL;
+ else
+ optval = coptval = imo->imo_multicast_ttl;
+ INP_UNLOCK(inp);
+ if (sopt->sopt_valsize == sizeof(u_char))
+ error = sooptcopyout(sopt, &coptval, sizeof(u_char));
+ else
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+ break;
+
+ case IP_MULTICAST_LOOP:
+ if (imo == 0)
+ optval = coptval = IP_DEFAULT_MULTICAST_LOOP;
+ else
+ optval = coptval = imo->imo_multicast_loop;
+ INP_UNLOCK(inp);
+ if (sopt->sopt_valsize == sizeof(u_char))
+ error = sooptcopyout(sopt, &coptval, sizeof(u_char));
+ else
+ error = sooptcopyout(sopt, &optval, sizeof(int));
+ break;
+
+ case IP_MSFILTER:
+ if (imo == NULL) {
+ error = EADDRNOTAVAIL;
+ INP_UNLOCK(inp);
+ } else {
+ error = inp_get_source_filters(inp, sopt);
+ }
+ break;
+
+ default:
+ INP_UNLOCK(inp);
+ error = ENOPROTOOPT;
+ break;
+ }
+
+ INP_UNLOCK_ASSERT(inp);
+
+ return (error);
+}
+
+/*
+ * Join an IPv4 multicast group, possibly with a source.
+ */
+static int
+inp_join_group(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct group_source_req gsr;
+ sockunion_t *gsa, *ssa;
+ struct ifnet *ifp;
+ struct in_mfilter *imf;
+ struct ip_moptions *imo;
+ struct in_multi *inm;
+ size_t idx;
+ int error;
+
+ ifp = NULL;
+ error = 0;
+
+ memset(&gsr, 0, sizeof(struct group_source_req));
+ gsa = (sockunion_t *)&gsr.gsr_group;
+ gsa->ss.ss_family = AF_UNSPEC;
+ ssa = (sockunion_t *)&gsr.gsr_source;
+ ssa->ss.ss_family = AF_UNSPEC;
+
+ switch (sopt->sopt_name) {
+ case IP_ADD_MEMBERSHIP:
+ case IP_ADD_SOURCE_MEMBERSHIP: {
+ struct ip_mreq_source mreqs;
+
+ if (sopt->sopt_name == IP_ADD_MEMBERSHIP) {
+ error = sooptcopyin(sopt, &mreqs,
+ sizeof(struct ip_mreq),
+ sizeof(struct ip_mreq));
+ /*
+ * Do argument switcharoo from ip_mreq into
+ * ip_mreq_source to avoid using two instances.
+ */
+ mreqs.imr_interface = mreqs.imr_sourceaddr;
+ mreqs.imr_sourceaddr.s_addr = INADDR_ANY;
+ } else if (sopt->sopt_name == IP_ADD_SOURCE_MEMBERSHIP) {
+ error = sooptcopyin(sopt, &mreqs,
+ sizeof(struct ip_mreq_source),
+ sizeof(struct ip_mreq_source));
+ }
+ if (error)
+ return (error);
+
+ gsa->sin.sin_family = AF_INET;
+ gsa->sin.sin_len = sizeof(struct sockaddr_in);
+ gsa->sin.sin_addr = mreqs.imr_multiaddr;
+
+ if (sopt->sopt_name == IP_ADD_SOURCE_MEMBERSHIP) {
+ ssa->sin.sin_family = AF_INET;
+ ssa->sin.sin_len = sizeof(struct sockaddr_in);
+ ssa->sin.sin_addr = mreqs.imr_sourceaddr;
+ }
+
+ /*
+ * Obtain ifp. If no interface address was provided,
+ * use the interface of the route to the given multicast
+ * address (usually this is the default route).
+ */
+ if (mreqs.imr_interface.s_addr != INADDR_ANY) {
+ INADDR_TO_IFP(mreqs.imr_interface, ifp);
+ } else {
+ struct route ro;
+
+ ro.ro_rt = NULL;
+ *(struct sockaddr_in *)&ro.ro_dst = gsa->sin;
+ rtalloc_ign(&ro, RTF_CLONING);
+ if (ro.ro_rt == NULL) {
+#ifdef DIAGNOSTIC
+ printf("%s: no route to %s\n", __func__,
+ inet_ntoa(gsa->sin.sin_addr));
+#endif
+ return (EADDRNOTAVAIL);
+ }
+ ifp = ro.ro_rt->rt_ifp;
+ KASSERT(ifp != NULL, ("%s: null ifp", __func__));
+ RTFREE(ro.ro_rt);
+ }
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: imr_interface = %s, ifp = %p\n",
+ __func__, inet_ntoa(mreqs.imr_interface), ifp);
+ }
+#endif
+ break;
+ }
+
+ case MCAST_JOIN_GROUP:
+ case MCAST_JOIN_SOURCE_GROUP:
+ if (sopt->sopt_name == MCAST_JOIN_GROUP) {
+ error = sooptcopyin(sopt, &gsr,
+ sizeof(struct group_req),
+ sizeof(struct group_req));
+ } else if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
+ error = sooptcopyin(sopt, &gsr,
+ sizeof(struct group_source_req),
+ sizeof(struct group_source_req));
+ }
+ if (error)
+ return (error);
+
+ if (gsa->sin.sin_family != AF_INET ||
+ gsa->sin.sin_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+
+ /*
+ * Overwrite the port field if present, as the sockaddr
+ * being copied in may be matched with a binary comparison.
+ * XXX INET6
+ */
+ gsa->sin.sin_port = 0;
+ if (sopt->sopt_name == MCAST_JOIN_SOURCE_GROUP) {
+ if (ssa->sin.sin_family != AF_INET ||
+ ssa->sin.sin_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+ ssa->sin.sin_port = 0;
+ }
+
+ /*
+ * Obtain the ifp.
+ */
+ if (gsr.gsr_interface == 0 || if_index < gsr.gsr_interface)
+ return (EADDRNOTAVAIL);
+ ifp = ifnet_byindex(gsr.gsr_interface);
+
+ break;
+
+ default:
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: unknown sopt_name %d\n", __func__,
+ sopt->sopt_name);
+ }
+#endif
+ return (EOPNOTSUPP);
+ break;
+ }
+
+ if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
+ return (EINVAL);
+
+ if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0)
+ return (EADDRNOTAVAIL);
+
+ /*
+ * Check if we already hold membership of this group for this inpcb.
+ * If so, we do not need to perform the initial join.
+ */
+ imo = inp_findmoptions(inp);
+ idx = imo_match_group(imo, ifp, &gsa->sa);
+ if (idx != -1) {
+ if (ssa->ss.ss_family != AF_UNSPEC) {
+ /*
+ * Attempting to join an ASM group (when already
+ * an ASM or SSM member) is an error.
+ */
+ error = EADDRNOTAVAIL;
+ } else {
+ imf = &imo->imo_mfilters[idx];
+ if (imf->imf_nsources == 0) {
+ /*
+ * Attempting to join an SSM group (when
+ * already an ASM member) is an error.
+ */
+ error = EINVAL;
+ } else {
+ /*
+ * Attempting to join an SSM group (when
+ * already an SSM member) means "add this
+ * source to the inclusive filter list".
+ */
+ error = imo_join_source(imo, idx, ssa);
+ }
+ }
+ goto out_locked;
+ }
+
+ /*
+ * Call imo_grow() to reallocate the membership and source filter
+ * vectors if they are full. If the size would exceed the hard limit,
+ * then we know we've really run out of entries. We keep the INP
+ * lock held to avoid introducing a race condition.
+ */
+ if (imo->imo_num_memberships == imo->imo_max_memberships) {
+ error = imo_grow(imo);
+ if (error)
+ goto out_locked;
+ }
+
+ /*
+ * So far, so good: perform the layer 3 join, layer 2 join,
+ * and make an IGMP announcement if needed.
+ */
+ inm = in_addmulti(&gsa->sin.sin_addr, ifp);
+ if (inm == NULL) {
+ error = ENOBUFS;
+ goto out_locked;
+ }
+ idx = imo->imo_num_memberships;
+ imo->imo_membership[idx] = inm;
+ imo->imo_num_memberships++;
+
+ KASSERT(imo->imo_mfilters != NULL,
+ ("%s: imf_mfilters vector was not allocated", __func__));
+ imf = &imo->imo_mfilters[idx];
+ KASSERT(TAILQ_EMPTY(&imf->imf_sources),
+ ("%s: imf_sources not empty", __func__));
+
+ /*
+ * If this is a new SSM group join (i.e. a source was specified
+ * with this group), add this source to the filter list.
+ */
+ if (ssa->ss.ss_family != AF_UNSPEC) {
+ /*
+ * An initial SSM join implies that this socket's membership
+ * of the multicast group is now in inclusive mode.
+ */
+ imf->imf_fmode = MCAST_INCLUDE;
+
+ error = imo_join_source(imo, idx, ssa);
+ if (error) {
+ /*
+ * Drop inp lock before calling in_delmulti(),
+ * to prevent a lock order reversal.
+ */
+ --imo->imo_num_memberships;
+ INP_UNLOCK(inp);
+ in_delmulti(inm);
+ return (error);
+ }
+ }
+
+out_locked:
+ INP_UNLOCK(inp);
+ return (error);
+}
+
+/*
+ * Leave an IPv4 multicast group on an inpcb, possibly with a source.
+ */
+static int
+inp_leave_group(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct group_source_req gsr;
+ struct ip_mreq_source mreqs;
+ sockunion_t *gsa, *ssa;
+ struct ifnet *ifp;
+ struct in_mfilter *imf;
+ struct ip_moptions *imo;
+ struct in_msource *ims, *tims;
+ struct in_multi *inm;
+ size_t idx;
+ int error;
+
+ ifp = NULL;
+ error = 0;
+
+ memset(&gsr, 0, sizeof(struct group_source_req));
+ gsa = (sockunion_t *)&gsr.gsr_group;
+ gsa->ss.ss_family = AF_UNSPEC;
+ ssa = (sockunion_t *)&gsr.gsr_source;
+ ssa->ss.ss_family = AF_UNSPEC;
+
+ switch (sopt->sopt_name) {
+ case IP_DROP_MEMBERSHIP:
+ case IP_DROP_SOURCE_MEMBERSHIP:
+ if (sopt->sopt_name == IP_DROP_MEMBERSHIP) {
+ error = sooptcopyin(sopt, &mreqs,
+ sizeof(struct ip_mreq),
+ sizeof(struct ip_mreq));
+ /*
+ * Swap interface and sourceaddr arguments,
+ * as ip_mreq and ip_mreq_source are laid
+ * out differently.
+ */
+ mreqs.imr_interface = mreqs.imr_sourceaddr;
+ mreqs.imr_sourceaddr.s_addr = INADDR_ANY;
+ } else if (sopt->sopt_name == IP_DROP_SOURCE_MEMBERSHIP) {
+ error = sooptcopyin(sopt, &mreqs,
+ sizeof(struct ip_mreq_source),
+ sizeof(struct ip_mreq_source));
+ }
+ if (error)
+ return (error);
+
+ gsa->sin.sin_family = AF_INET;
+ gsa->sin.sin_len = sizeof(struct sockaddr_in);
+ gsa->sin.sin_addr = mreqs.imr_multiaddr;
+
+ if (sopt->sopt_name == IP_DROP_SOURCE_MEMBERSHIP) {
+ ssa->sin.sin_family = AF_INET;
+ ssa->sin.sin_len = sizeof(struct sockaddr_in);
+ ssa->sin.sin_addr = mreqs.imr_sourceaddr;
+ }
+
+ if (gsa->sin.sin_addr.s_addr != INADDR_ANY)
+ INADDR_TO_IFP(mreqs.imr_interface, ifp);
+
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: imr_interface = %s, ifp = %p\n",
+ __func__, inet_ntoa(mreqs.imr_interface), ifp);
+ }
+#endif
+ break;
+
+ case MCAST_LEAVE_GROUP:
+ case MCAST_LEAVE_SOURCE_GROUP:
+ if (sopt->sopt_name == MCAST_LEAVE_GROUP) {
+ error = sooptcopyin(sopt, &gsr,
+ sizeof(struct group_req),
+ sizeof(struct group_req));
+ } else if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
+ error = sooptcopyin(sopt, &gsr,
+ sizeof(struct group_source_req),
+ sizeof(struct group_source_req));
+ }
+ if (error)
+ return (error);
+
+ if (gsa->sin.sin_family != AF_INET ||
+ gsa->sin.sin_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+
+ if (sopt->sopt_name == MCAST_LEAVE_SOURCE_GROUP) {
+ if (ssa->sin.sin_family != AF_INET ||
+ ssa->sin.sin_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+ }
+
+ if (gsr.gsr_interface == 0 || if_index < gsr.gsr_interface)
+ return (EADDRNOTAVAIL);
+
+ ifp = ifnet_byindex(gsr.gsr_interface);
+ break;
+
+ default:
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: unknown sopt_name %d\n", __func__,
+ sopt->sopt_name);
+ }
+#endif
+ return (EOPNOTSUPP);
+ break;
+ }
+
+ if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
+ return (EINVAL);
+
+ /*
+ * Find the membership in the membership array.
+ */
+ imo = inp_findmoptions(inp);
+ idx = imo_match_group(imo, ifp, &gsa->sa);
+ if (idx == -1) {
+ error = EADDRNOTAVAIL;
+ goto out_locked;
+ }
+ imf = &imo->imo_mfilters[idx];
+
+ /*
+ * If we were instructed only to leave a given source, do so.
+ */
+ if (ssa->ss.ss_family != AF_UNSPEC) {
+ if (imf->imf_nsources == 0 ||
+ imf->imf_fmode == MCAST_EXCLUDE) {
+ /*
+ * Attempting to SSM leave an ASM group
+ * is an error; should use *_BLOCK_SOURCE instead.
+ * Attempting to SSM leave a source in a group when
+ * the socket is in 'exclude mode' is also an error.
+ */
+ error = EINVAL;
+ } else {
+ error = imo_leave_source(imo, idx, ssa);
+ }
+ /*
+ * If an error occurred, or this source is not the last
+ * source in the group, do not leave the whole group.
+ */
+ if (error || imf->imf_nsources > 0)
+ goto out_locked;
+ }
+
+ /*
+ * Give up the multicast address record to which the membership points.
+ */
+ inm = imo->imo_membership[idx];
+ in_delmulti(inm);
+
+ /*
+ * Free any source filters for this group if they exist.
+ * Revert inpcb to the default MCAST_EXCLUDE state.
+ */
+ if (imo->imo_mfilters != NULL) {
+ TAILQ_FOREACH_SAFE(ims, &imf->imf_sources, ims_next, tims) {
+ TAILQ_REMOVE(&imf->imf_sources, ims, ims_next);
+ FREE(ims, M_IPMSOURCE);
+ imf->imf_nsources--;
+ }
+ KASSERT(imf->imf_nsources == 0,
+ ("%s: imf_nsources not 0", __func__));
+ KASSERT(TAILQ_EMPTY(&imf->imf_sources),
+ ("%s: imf_sources not empty", __func__));
+ imf->imf_fmode = MCAST_EXCLUDE;
+ }
+
+ /*
+ * Remove the gap in the membership array.
+ */
+ for (++idx; idx < imo->imo_num_memberships; ++idx)
+ imo->imo_membership[idx-1] = imo->imo_membership[idx];
+ imo->imo_num_memberships--;
+
+out_locked:
+ INP_UNLOCK(inp);
+ return (error);
+}
+
+/*
+ * Select the interface for transmitting IPv4 multicast datagrams.
+ *
+ * Either an instance of struct in_addr or an instance of struct ip_mreqn
+ * may be passed to this socket option. An address of INADDR_ANY or an
+ * interface index of 0 is used to remove a previous selection.
+ * When no interface is selected, one is chosen for every send.
+ */
+static int
+inp_set_multicast_if(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct in_addr addr;
+ struct ip_mreqn mreqn;
+ struct ifnet *ifp;
+ struct ip_moptions *imo;
+ int error;
+
+ if (sopt->sopt_valsize == sizeof(struct ip_mreqn)) {
+ /*
+ * An interface index was specified using the
+ * Linux-derived ip_mreqn structure.
+ */
+ error = sooptcopyin(sopt, &mreqn, sizeof(struct ip_mreqn),
+ sizeof(struct ip_mreqn));
+ if (error)
+ return (error);
+
+ if (mreqn.imr_ifindex < 0 || if_index < mreqn.imr_ifindex)
+ return (EINVAL);
+
+ if (mreqn.imr_ifindex == 0) {
+ ifp = NULL;
+ } else {
+ ifp = ifnet_byindex(mreqn.imr_ifindex);
+ if (ifp == NULL)
+ return (EADDRNOTAVAIL);
+ }
+ } else {
+ /*
+ * An interface was specified by IPv4 address.
+ * This is the traditional BSD usage.
+ */
+ error = sooptcopyin(sopt, &addr, sizeof(struct in_addr),
+ sizeof(struct in_addr));
+ if (error)
+ return (error);
+ if (addr.s_addr == INADDR_ANY) {
+ ifp = NULL;
+ } else {
+ INADDR_TO_IFP(addr, ifp);
+ if (ifp == NULL)
+ return (EADDRNOTAVAIL);
+ }
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: ifp = %p, addr = %s\n",
+ __func__, ifp, inet_ntoa(addr)); /* XXX INET6 */
+ }
+#endif
+ }
+
+ /* Reject interfaces which do not support multicast. */
+ if (ifp != NULL && (ifp->if_flags & IFF_MULTICAST) == 0)
+ return (EOPNOTSUPP);
+
+ imo = inp_findmoptions(inp);
+ imo->imo_multicast_ifp = ifp;
+ imo->imo_multicast_addr.s_addr = INADDR_ANY;
+ INP_UNLOCK(inp);
+
+ return (0);
+}
+
+/*
+ * Atomically set source filters on a socket for an IPv4 multicast group.
+ */
+static int
+inp_set_source_filters(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct __msfilterreq msfr;
+ sockunion_t *gsa;
+ struct ifnet *ifp;
+ struct in_mfilter *imf;
+ struct ip_moptions *imo;
+ struct in_msource *ims, *tims;
+ size_t idx;
+ int error;
+
+ error = sooptcopyin(sopt, &msfr, sizeof(struct __msfilterreq),
+ sizeof(struct __msfilterreq));
+ if (error)
+ return (error);
+
+ if (msfr.msfr_nsrcs > IP_MAX_SOURCE_FILTER ||
+ (msfr.msfr_fmode != MCAST_EXCLUDE &&
+ msfr.msfr_fmode != MCAST_INCLUDE))
+ return (EINVAL);
+
+ if (msfr.msfr_group.ss_family != AF_INET ||
+ msfr.msfr_group.ss_len != sizeof(struct sockaddr_in))
+ return (EINVAL);
+
+ gsa = (sockunion_t *)&msfr.msfr_group;
+ if (!IN_MULTICAST(ntohl(gsa->sin.sin_addr.s_addr)))
+ return (EINVAL);
+
+ gsa->sin.sin_port = 0; /* ignore port */
+
+ if (msfr.msfr_ifindex == 0 || if_index < msfr.msfr_ifindex)
+ return (EADDRNOTAVAIL);
+
+ ifp = ifnet_byindex(msfr.msfr_ifindex);
+ if (ifp == NULL)
+ return (EADDRNOTAVAIL);
+
+ /*
+ * Take the INP lock.
+ * Check if this socket is a member of this group.
+ */
+ imo = inp_findmoptions(inp);
+ idx = imo_match_group(imo, ifp, &gsa->sa);
+ if (idx == -1 || imo->imo_mfilters == NULL) {
+ error = EADDRNOTAVAIL;
+ goto out_locked;
+ }
+ imf = &imo->imo_mfilters[idx];
+
+#ifdef DIAGNOSTIC
+ if (bootverbose)
+ printf("%s: clearing source list\n", __func__);
+#endif
+
+ /*
+ * Remove any existing source filters.
+ */
+ TAILQ_FOREACH_SAFE(ims, &imf->imf_sources, ims_next, tims) {
+ TAILQ_REMOVE(&imf->imf_sources, ims, ims_next);
+ FREE(ims, M_IPMSOURCE);
+ imf->imf_nsources--;
+ }
+ KASSERT(imf->imf_nsources == 0,
+ ("%s: source list not cleared", __func__));
+
+ /*
+ * Apply any new source filters, if present.
+ */
+ if (msfr.msfr_nsrcs > 0) {
+ struct in_msource **pnims;
+ struct in_msource *nims;
+ struct sockaddr_storage *kss;
+ struct sockaddr_storage *pkss;
+ sockunion_t *psu;
+ int i, j;
+
+ /*
+ * Drop the inp lock so we may sleep if we need to
+ * in order to satisfy a malloc request.
+ * We will re-take it before changing socket state.
+ */
+ INP_UNLOCK(inp);
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: loading %lu source list entries\n",
+ __func__, (unsigned long)msfr.msfr_nsrcs);
+ }
+#endif
+ /*
+ * Make a copy of the user-space source vector so
+ * that we may copy them with a single copyin. This
+ * allows us to deal with page faults up-front.
+ */
+ MALLOC(kss, struct sockaddr_storage *,
+ sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs,
+ M_TEMP, M_WAITOK);
+ error = copyin(msfr.msfr_srcs, kss,
+ sizeof(struct sockaddr_storage) * msfr.msfr_nsrcs);
+ if (error) {
+ FREE(kss, M_TEMP);
+ return (error);
+ }
+
+ /*
+ * Perform argument checking on every sockaddr_storage
+ * structure in the vector provided to us. Overwrite
+ * fields which should not apply to source entries.
+ * TODO: Check for duplicate sources on this pass.
+ */
+ psu = (sockunion_t *)kss;
+ for (i = 0; i < msfr.msfr_nsrcs; i++, psu++) {
+ switch (psu->ss.ss_family) {
+ case AF_INET:
+ if (psu->sin.sin_len !=
+ sizeof(struct sockaddr_in)) {
+ error = EINVAL;
+ } else {
+ psu->sin.sin_port = 0;
+ }
+ break;
+#ifdef notyet
+ case AF_INET6;
+ if (psu->sin6.sin6_len !=
+ sizeof(struct sockaddr_in6)) {
+ error = EINVAL;
+ } else {
+ psu->sin6.sin6_port = 0;
+ psu->sin6.sin6_flowinfo = 0;
+ }
+ break;
+#endif
+ default:
+ error = EAFNOSUPPORT;
+ break;
+ }
+ if (error)
+ break;
+ }
+ if (error) {
+ FREE(kss, M_TEMP);
+ return (error);
+ }
+
+ /*
+ * Allocate a block to track all the in_msource
+ * entries we are about to allocate, in case we
+ * abruptly need to free them.
+ */
+ MALLOC(pnims, struct in_msource **,
+ sizeof(struct in_msource *) * msfr.msfr_nsrcs,
+ M_TEMP, M_WAITOK | M_ZERO);
+
+ /*
+ * Allocate up to nsrcs individual chunks.
+ * If we encounter an error, backtrack out of
+ * all allocations cleanly; updates must be atomic.
+ */
+ pkss = kss;
+ nims = NULL;
+ for (i = 0; i < msfr.msfr_nsrcs; i++, pkss++) {
+ MALLOC(nims, struct in_msource *,
+ sizeof(struct in_msource) * msfr.msfr_nsrcs,
+ M_IPMSOURCE, M_WAITOK | M_ZERO);
+ pnims[i] = nims;
+ }
+ if (i < msfr.msfr_nsrcs) {
+ for (j = 0; j < i; j++) {
+ if (pnims[j] != NULL)
+ FREE(pnims[j], M_IPMSOURCE);
+ }
+ FREE(pnims, M_TEMP);
+ FREE(kss, M_TEMP);
+ return (ENOBUFS);
+ }
+
+ INP_UNLOCK_ASSERT(inp);
+
+ /*
+ * Finally, apply the filters to the socket.
+ * Re-take the inp lock; we are changing socket state.
+ */
+ pkss = kss;
+ INP_LOCK(inp);
+ for (i = 0; i < msfr.msfr_nsrcs; i++, pkss++) {
+ memcpy(&(pnims[i]->ims_addr), pkss,
+ sizeof(struct sockaddr_storage));
+ TAILQ_INSERT_TAIL(&imf->imf_sources, pnims[i],
+ ims_next);
+ imf->imf_nsources++;
+ }
+ FREE(pnims, M_TEMP);
+ FREE(kss, M_TEMP);
+ }
+
+ /*
+ * Update the filter mode on the socket before releasing the inpcb.
+ */
+ INP_LOCK_ASSERT(inp);
+ imf->imf_fmode = msfr.msfr_fmode;
+
+out_locked:
+ INP_UNLOCK(inp);
+ return (error);
+}
+
+/*
+ * Set the IP multicast options in response to user setsockopt().
+ *
+ * Many of the socket options handled in this function duplicate the
+ * functionality of socket options in the regular unicast API. However,
+ * it is not possible to merge the duplicate code, because the idempotence
+ * of the IPv4 multicast part of the BSD Sockets API must be preserved;
+ * the effects of these options must be treated as separate and distinct.
+ */
+int
+inp_setmoptions(struct inpcb *inp, struct sockopt *sopt)
+{
+ struct ip_moptions *imo;
+ int error;
+
+ error = 0;
+
+ switch (sopt->sopt_name) {
+ case IP_MULTICAST_VIF: {
+ int vifi;
+ /*
+ * Select a multicast VIF for transmission.
+ * Only useful if multicast forwarding is active.
+ */
+ if (legal_vif_num == NULL) {
+ error = EOPNOTSUPP;
+ break;
+ }
+ error = sooptcopyin(sopt, &vifi, sizeof(int), sizeof(int));
+ if (error)
+ break;
+ if (!legal_vif_num(vifi) && (vifi != -1)) {
+ error = EINVAL;
+ break;
+ }
+ imo = inp_findmoptions(inp);
+ imo->imo_multicast_vif = vifi;
+ INP_UNLOCK(inp);
+ break;
+ }
+
+ case IP_MULTICAST_IF:
+ error = inp_set_multicast_if(inp, sopt);
+ break;
+
+ case IP_MULTICAST_TTL: {
+ u_char ttl;
+
+ /*
+ * Set the IP time-to-live for outgoing multicast packets.
+ * The original multicast API required a char argument,
+ * which is inconsistent with the rest of the socket API.
+ * We allow either a char or an int.
+ */
+ if (sopt->sopt_valsize == sizeof(u_char)) {
+ error = sooptcopyin(sopt, &ttl, sizeof(u_char),
+ sizeof(u_char));
+ if (error)
+ break;
+ } else {
+ u_int ittl;
+
+ error = sooptcopyin(sopt, &ittl, sizeof(u_int),
+ sizeof(u_int));
+ if (error)
+ break;
+ if (ittl > 255) {
+ error = EINVAL;
+ break;
+ }
+ ttl = (u_char)ittl;
+ }
+ imo = inp_findmoptions(inp);
+ imo->imo_multicast_ttl = ttl;
+ INP_UNLOCK(inp);
+ break;
+ }
+
+ case IP_MULTICAST_LOOP: {
+ u_char loop;
+
+ /*
+ * Set the loopback flag for outgoing multicast packets.
+ * Must be zero or one. The original multicast API required a
+ * char argument, which is inconsistent with the rest
+ * of the socket API. We allow either a char or an int.
+ */
+ if (sopt->sopt_valsize == sizeof(u_char)) {
+ error = sooptcopyin(sopt, &loop, sizeof(u_char),
+ sizeof(u_char));
+ if (error)
+ break;
+ } else {
+ u_int iloop;
+
+ error = sooptcopyin(sopt, &iloop, sizeof(u_int),
+ sizeof(u_int));
+ if (error)
+ break;
+ loop = (u_char)iloop;
+ }
+ imo = inp_findmoptions(inp);
+ imo->imo_multicast_loop = !!loop;
+ INP_UNLOCK(inp);
+ break;
+ }
+
+ case IP_ADD_MEMBERSHIP:
+ case IP_ADD_SOURCE_MEMBERSHIP:
+ case MCAST_JOIN_GROUP:
+ case MCAST_JOIN_SOURCE_GROUP:
+ error = inp_join_group(inp, sopt);
+ break;
+
+ case IP_DROP_MEMBERSHIP:
+ case IP_DROP_SOURCE_MEMBERSHIP:
+ case MCAST_LEAVE_GROUP:
+ case MCAST_LEAVE_SOURCE_GROUP:
+ error = inp_leave_group(inp, sopt);
+ break;
+
+ case IP_BLOCK_SOURCE:
+ case IP_UNBLOCK_SOURCE:
+ case MCAST_BLOCK_SOURCE:
+ case MCAST_UNBLOCK_SOURCE:
+ error = inp_change_source_filter(inp, sopt);
+ break;
+
+ case IP_MSFILTER:
+ error = inp_set_source_filters(inp, sopt);
+ break;
+
+ default:
+ error = EOPNOTSUPP;
+ break;
+ }
+
+ INP_UNLOCK_ASSERT(inp);
+
+ return (error);
+}
diff --git a/sys/netinet/in_pcb.c b/sys/netinet/in_pcb.c
index 61f2894..e91ac55 100644
--- a/sys/netinet/in_pcb.c
+++ b/sys/netinet/in_pcb.c
@@ -735,7 +735,8 @@ in_pcbfree(struct inpcb *inp)
in_pcbremlists(inp);
if (inp->inp_options)
(void)m_free(inp->inp_options);
- ip_freemoptions(inp->inp_moptions);
+ if (inp->inp_moptions != NULL)
+ inp_freemoptions(inp->inp_moptions);
inp->inp_vflag = 0;
#ifdef MAC
diff --git a/sys/netinet/in_var.h b/sys/netinet/in_var.h
index 7605199..47a160a 100644
--- a/sys/netinet/in_var.h
+++ b/sys/netinet/in_var.h
@@ -147,6 +147,12 @@ struct router_info {
int rti_type; /* type of router which is querier on this interface */
int rti_time; /* # of slow timeouts since last old query */
SLIST_ENTRY(router_info) rti_list;
+#ifdef notyet
+ int rti_timev1; /* IGMPv1 querier present */
+ int rti_timev2; /* IGMPv2 querier present */
+ int rti_timer; /* report to general query */
+ int rti_qrv; /* querier robustness */
+#endif
};
/*
@@ -166,7 +172,43 @@ struct in_multi {
u_int inm_state; /* state of the membership */
struct router_info *inm_rti; /* router info*/
u_int inm_refcount; /* reference count */
+#ifdef notyet /* IGMPv3 source-specific multicast fields */
+ TAILQ_HEAD(, in_msfentry) inm_msf; /* all active source filters */
+ TAILQ_HEAD(, in_msfentry) inm_msf_record; /* recorded sources */
+ TAILQ_HEAD(, in_msfentry) inm_msf_exclude; /* exclude sources */
+ TAILQ_HEAD(, in_msfentry) inm_msf_include; /* include sources */
+ /* XXX: should this lot go to the router_info structure? */
+ /* XXX: can/should these be callouts? */
+ /* IGMP protocol timers */
+ int32_t inm_ti_curstate; /* current state timer */
+ int32_t inm_ti_statechg; /* state change timer */
+ /* IGMP report timers */
+ uint16_t inm_rpt_statechg; /* state change report timer */
+ uint16_t inm_rpt_toxx; /* fmode change report timer */
+ /* IGMP protocol state */
+ uint16_t inm_fmode; /* filter mode */
+ uint32_t inm_recsrc_count; /* # of recorded sources */
+ uint16_t inm_exclude_sock_count; /* # of exclude-mode sockets */
+ uint16_t inm_gass_count; /* # of g-a-s queries */
+#endif
+};
+
+#ifdef notyet
+/*
+ * Internet multicast source filter list. This list is used to store
+ * IP multicast source addresses for each membership on an interface.
+ * TODO: Allocate these structures using UMA.
+ * TODO: Find an easier way of linking the struct into two lists at once.
+ */
+struct in_msfentry {
+ TAILQ_ENTRY(in_msfentry) isf_link; /* next filter in all-list */
+ TAILQ_ENTRY(in_msfentry) isf_next; /* next filter in queue */
+ struct in_addr isf_addr; /* the address of this source */
+ uint16_t isf_refcount; /* reference count */
+ uint16_t isf_reporttag; /* what to report to the IGMP router */
+ uint16_t isf_rexmit; /* retransmission state/count */
};
+#endif
#ifdef _KERNEL
@@ -246,6 +288,12 @@ do { \
} while(0)
struct route;
+struct ip_moptions;
+
+size_t imo_match_group(struct ip_moptions *, struct ifnet *,
+ struct sockaddr *);
+struct in_msource *imo_match_source(struct ip_moptions *, size_t,
+ struct sockaddr *);
struct in_multi *in_addmulti(struct in_addr *, struct ifnet *);
void in_delmulti(struct in_multi *);
void in_delmulti_locked(struct in_multi *);
diff --git a/sys/netinet/ip_carp.c b/sys/netinet/ip_carp.c
index 32b8680..4cabe30 100644
--- a/sys/netinet/ip_carp.c
+++ b/sys/netinet/ip_carp.c
@@ -380,6 +380,7 @@ carp_clone_create(struct if_clone *ifc, int unit, caddr_t params)
sc->sc_imo.imo_membership = (struct in_multi **)malloc(
(sizeof(struct in_multi *) * IP_MIN_MEMBERSHIPS), M_CARP,
M_WAITOK);
+ sc->sc_imo.imo_mfilters = NULL;
sc->sc_imo.imo_max_memberships = IP_MIN_MEMBERSHIPS;
sc->sc_imo.imo_multicast_vif = -1;
@@ -1397,6 +1398,8 @@ carp_multicast_cleanup(struct carp_softc *sc)
imo->imo_membership[n] = NULL;
}
}
+ KASSERT(imo->imo_mfilters == NULL,
+ ("%s: imo_mfilters != NULL", __func__));
imo->imo_num_memberships = 0;
imo->imo_multicast_ifp = NULL;
}
diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c
index 4c4a51f..2b800dc 100644
--- a/sys/netinet/ip_output.c
+++ b/sys/netinet/ip_output.c
@@ -73,8 +73,6 @@
#include <security/mac/mac_framework.h>
-static MALLOC_DEFINE(M_IPMOPTS, "ip_moptions", "internet multicast options");
-
#define print_ip(x, a, y) printf("%s %d.%d.%d.%d%s",\
x, (ntohl(a.s_addr)>>24)&0xFF,\
(ntohl(a.s_addr)>>16)&0xFF,\
@@ -89,11 +87,8 @@ SYSCTL_INT(_net_inet_ip, OID_AUTO, mbuf_frag_size, CTLFLAG_RW,
&mbuf_frag_size, 0, "Fragment outgoing mbufs to this size");
#endif
-static struct ifnet *ip_multicast_if(struct in_addr *, int *);
static void ip_mloopback
(struct ifnet *, struct mbuf *, struct sockaddr_in *, int);
-static int ip_getmoptions(struct inpcb *, struct sockopt *);
-static int ip_setmoptions(struct inpcb *, struct sockopt *);
extern struct protosw inetsw[];
@@ -930,13 +925,28 @@ ip_ctloutput(struct socket *so, struct sockopt *sopt)
break;
#undef OPTSET
+ /*
+ * Multicast socket options are processed by the in_mcast
+ * module.
+ */
case IP_MULTICAST_IF:
case IP_MULTICAST_VIF:
case IP_MULTICAST_TTL:
case IP_MULTICAST_LOOP:
case IP_ADD_MEMBERSHIP:
case IP_DROP_MEMBERSHIP:
- error = ip_setmoptions(inp, sopt);
+ case IP_ADD_SOURCE_MEMBERSHIP:
+ case IP_DROP_SOURCE_MEMBERSHIP:
+ case IP_BLOCK_SOURCE:
+ case IP_UNBLOCK_SOURCE:
+ case IP_MSFILTER:
+ case MCAST_JOIN_GROUP:
+ case MCAST_LEAVE_GROUP:
+ case MCAST_JOIN_SOURCE_GROUP:
+ case MCAST_LEAVE_SOURCE_GROUP:
+ case MCAST_BLOCK_SOURCE:
+ case MCAST_UNBLOCK_SOURCE:
+ error = inp_setmoptions(inp, sopt);
break;
case IP_PORTRANGE:
@@ -1095,11 +1105,16 @@ ip_ctloutput(struct socket *so, struct sockopt *sopt)
error = sooptcopyout(sopt, &optval, sizeof optval);
break;
+ /*
+ * Multicast socket options are processed by the in_mcast
+ * module.
+ */
case IP_MULTICAST_IF:
case IP_MULTICAST_VIF:
case IP_MULTICAST_TTL:
case IP_MULTICAST_LOOP:
- error = ip_getmoptions(inp, sopt);
+ case IP_MSFILTER:
+ error = inp_getmoptions(inp, sopt);
break;
#if defined(IPSEC) || defined(FAST_IPSEC)
@@ -1132,477 +1147,6 @@ ip_ctloutput(struct socket *so, struct sockopt *sopt)
}
/*
- * XXX
- * The whole multicast option thing needs to be re-thought.
- * Several of these options are equally applicable to non-multicast
- * transmission, and one (IP_MULTICAST_TTL) totally duplicates a
- * standard option (IP_TTL).
- */
-
-/*
- * following RFC1724 section 3.3, 0.0.0.0/8 is interpreted as interface index.
- */
-static struct ifnet *
-ip_multicast_if(struct in_addr *a, int *ifindexp)
-{
- int ifindex;
- struct ifnet *ifp;
-
- if (ifindexp)
- *ifindexp = 0;
- if (ntohl(a->s_addr) >> 24 == 0) {
- ifindex = ntohl(a->s_addr) & 0xffffff;
- if (ifindex < 0 || if_index < ifindex)
- return NULL;
- ifp = ifnet_byindex(ifindex);
- if (ifindexp)
- *ifindexp = ifindex;
- } else {
- INADDR_TO_IFP(*a, ifp);
- }
- return ifp;
-}
-
-/*
- * Given an inpcb, return its multicast options structure pointer. Accepts
- * an unlocked inpcb pointer, but will return it locked. May sleep.
- */
-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)
- return (inp->inp_moptions);
-
- 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;
- imo->imo_multicast_vif = -1;
- 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);
- }
- inp->inp_moptions = imo;
- return (imo);
-}
-
-/*
- * Set the IP multicast options in response to user setsockopt().
- */
-static int
-ip_setmoptions(struct inpcb *inp, struct sockopt *sopt)
-{
- int error = 0;
- int i;
- struct in_addr addr;
- struct ip_mreq mreq;
- struct ifnet *ifp;
- struct ip_moptions *imo;
- struct route ro;
- struct sockaddr_in *dst;
- int ifindex;
- int s;
-
- switch (sopt->sopt_name) {
- /* store an index number for the vif you wanna use in the send */
- case IP_MULTICAST_VIF:
- if (legal_vif_num == 0) {
- error = EOPNOTSUPP;
- break;
- }
- error = sooptcopyin(sopt, &i, sizeof i, sizeof i);
- if (error)
- break;
- if (!legal_vif_num(i) && (i != -1)) {
- error = EINVAL;
- break;
- }
- imo = ip_findmoptions(inp);
- imo->imo_multicast_vif = i;
- INP_UNLOCK(inp);
- break;
-
- case IP_MULTICAST_IF:
- /*
- * Select the interface for outgoing multicast packets.
- */
- error = sooptcopyin(sopt, &addr, sizeof addr, sizeof addr);
- if (error)
- break;
- /*
- * INADDR_ANY is used to remove a previous selection.
- * When no interface is selected, a default one is
- * chosen every time a multicast packet is sent.
- */
- imo = ip_findmoptions(inp);
- if (addr.s_addr == INADDR_ANY) {
- imo->imo_multicast_ifp = NULL;
- INP_UNLOCK(inp);
- break;
- }
- /*
- * The selected interface is identified by its local
- * IP address. Find the interface and confirm that
- * it supports multicasting.
- */
- s = splimp();
- ifp = ip_multicast_if(&addr, &ifindex);
- if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
- INP_UNLOCK(inp);
- splx(s);
- error = EADDRNOTAVAIL;
- break;
- }
- imo->imo_multicast_ifp = ifp;
- if (ifindex)
- imo->imo_multicast_addr = addr;
- else
- imo->imo_multicast_addr.s_addr = INADDR_ANY;
- INP_UNLOCK(inp);
- splx(s);
- break;
-
- case IP_MULTICAST_TTL:
- /*
- * Set the IP time-to-live for outgoing multicast packets.
- * The original multicast API required a char argument,
- * which is inconsistent with the rest of the socket API.
- * We allow either a char or an int.
- */
- if (sopt->sopt_valsize == 1) {
- u_char ttl;
- error = sooptcopyin(sopt, &ttl, 1, 1);
- if (error)
- break;
- imo = ip_findmoptions(inp);
- imo->imo_multicast_ttl = ttl;
- INP_UNLOCK(inp);
- } else {
- u_int ttl;
- error = sooptcopyin(sopt, &ttl, sizeof ttl,
- sizeof ttl);
- if (error)
- break;
- if (ttl > 255)
- error = EINVAL;
- else {
- imo = ip_findmoptions(inp);
- imo->imo_multicast_ttl = ttl;
- INP_UNLOCK(inp);
- }
- }
- break;
-
- case IP_MULTICAST_LOOP:
- /*
- * Set the loopback flag for outgoing multicast packets.
- * Must be zero or one. The original multicast API required a
- * char argument, which is inconsistent with the rest
- * of the socket API. We allow either a char or an int.
- */
- if (sopt->sopt_valsize == 1) {
- u_char loop;
- error = sooptcopyin(sopt, &loop, 1, 1);
- if (error)
- break;
- imo = ip_findmoptions(inp);
- imo->imo_multicast_loop = !!loop;
- INP_UNLOCK(inp);
- } else {
- u_int loop;
- error = sooptcopyin(sopt, &loop, sizeof loop,
- sizeof loop);
- if (error)
- break;
- imo = ip_findmoptions(inp);
- imo->imo_multicast_loop = !!loop;
- INP_UNLOCK(inp);
- }
- break;
-
- case IP_ADD_MEMBERSHIP:
- /*
- * Add a multicast group membership.
- * Group must be a valid IP multicast address.
- */
- error = sooptcopyin(sopt, &mreq, sizeof mreq, sizeof mreq);
- if (error)
- break;
-
- if (!IN_MULTICAST(ntohl(mreq.imr_multiaddr.s_addr))) {
- error = EINVAL;
- break;
- }
- s = splimp();
- /*
- * If no interface address was provided, use the interface of
- * the route to the given multicast address.
- */
- if (mreq.imr_interface.s_addr == INADDR_ANY) {
- bzero((caddr_t)&ro, sizeof(ro));
- dst = (struct sockaddr_in *)&ro.ro_dst;
- dst->sin_len = sizeof(*dst);
- dst->sin_family = AF_INET;
- dst->sin_addr = mreq.imr_multiaddr;
- rtalloc_ign(&ro, RTF_CLONING);
- if (ro.ro_rt == NULL) {
- error = EADDRNOTAVAIL;
- splx(s);
- break;
- }
- ifp = ro.ro_rt->rt_ifp;
- RTFREE(ro.ro_rt);
- }
- else {
- ifp = ip_multicast_if(&mreq.imr_interface, NULL);
- }
-
- /*
- * See if we found an interface, and confirm that it
- * supports multicast.
- */
- if (ifp == NULL || (ifp->if_flags & IFF_MULTICAST) == 0) {
- error = EADDRNOTAVAIL;
- splx(s);
- break;
- }
- /*
- * See if the membership already exists or if all the
- * membership slots are full.
- */
- imo = ip_findmoptions(inp);
- for (i = 0; i < imo->imo_num_memberships; ++i) {
- if (imo->imo_membership[i]->inm_ifp == ifp &&
- imo->imo_membership[i]->inm_addr.s_addr
- == mreq.imr_multiaddr.s_addr)
- break;
- }
- if (i < imo->imo_num_memberships) {
- INP_UNLOCK(inp);
- error = EADDRINUSE;
- splx(s);
- break;
- }
- 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
- * address list for the given interface.
- */
- if ((imo->imo_membership[i] =
- in_addmulti(&mreq.imr_multiaddr, ifp)) == NULL) {
- INP_UNLOCK(inp);
- error = ENOBUFS;
- splx(s);
- break;
- }
- ++imo->imo_num_memberships;
- INP_UNLOCK(inp);
- splx(s);
- break;
-
- case IP_DROP_MEMBERSHIP:
- /*
- * Drop a multicast group membership.
- * Group must be a valid IP multicast address.
- */
- error = sooptcopyin(sopt, &mreq, sizeof mreq, sizeof mreq);
- if (error)
- break;
-
- if (!IN_MULTICAST(ntohl(mreq.imr_multiaddr.s_addr))) {
- error = EINVAL;
- break;
- }
-
- s = splimp();
- /*
- * If an interface address was specified, get a pointer
- * to its ifnet structure.
- */
- if (mreq.imr_interface.s_addr == INADDR_ANY)
- ifp = NULL;
- else {
- ifp = ip_multicast_if(&mreq.imr_interface, NULL);
- if (ifp == NULL) {
- error = EADDRNOTAVAIL;
- splx(s);
- break;
- }
- }
- /*
- * Find the membership in the membership array.
- */
- imo = ip_findmoptions(inp);
- for (i = 0; i < imo->imo_num_memberships; ++i) {
- if ((ifp == NULL ||
- imo->imo_membership[i]->inm_ifp == ifp) &&
- imo->imo_membership[i]->inm_addr.s_addr ==
- mreq.imr_multiaddr.s_addr)
- break;
- }
- if (i == imo->imo_num_memberships) {
- INP_UNLOCK(inp);
- error = EADDRNOTAVAIL;
- splx(s);
- break;
- }
- /*
- * Give up the multicast address record to which the
- * membership points.
- */
- in_delmulti(imo->imo_membership[i]);
- /*
- * Remove the gap in the membership array.
- */
- for (++i; i < imo->imo_num_memberships; ++i)
- imo->imo_membership[i-1] = imo->imo_membership[i];
- --imo->imo_num_memberships;
- INP_UNLOCK(inp);
- splx(s);
- break;
-
- default:
- error = EOPNOTSUPP;
- break;
- }
-
- return (error);
-}
-
-/*
- * Return the IP multicast options in response to user getsockopt().
- */
-static int
-ip_getmoptions(struct inpcb *inp, struct sockopt *sopt)
-{
- struct ip_moptions *imo;
- struct in_addr addr;
- struct in_ifaddr *ia;
- int error, optval;
- u_char coptval;
-
- INP_LOCK(inp);
- imo = inp->inp_moptions;
-
- error = 0;
- switch (sopt->sopt_name) {
- case IP_MULTICAST_VIF:
- if (imo != NULL)
- optval = imo->imo_multicast_vif;
- else
- optval = -1;
- INP_UNLOCK(inp);
- error = sooptcopyout(sopt, &optval, sizeof optval);
- break;
-
- case IP_MULTICAST_IF:
- if (imo == NULL || imo->imo_multicast_ifp == NULL)
- addr.s_addr = INADDR_ANY;
- else if (imo->imo_multicast_addr.s_addr) {
- /* return the value user has set */
- addr = imo->imo_multicast_addr;
- } else {
- IFP_TO_IA(imo->imo_multicast_ifp, ia);
- addr.s_addr = (ia == NULL) ? INADDR_ANY
- : IA_SIN(ia)->sin_addr.s_addr;
- }
- INP_UNLOCK(inp);
- error = sooptcopyout(sopt, &addr, sizeof addr);
- break;
-
- case IP_MULTICAST_TTL:
- if (imo == 0)
- optval = coptval = IP_DEFAULT_MULTICAST_TTL;
- else
- optval = coptval = imo->imo_multicast_ttl;
- INP_UNLOCK(inp);
- if (sopt->sopt_valsize == 1)
- error = sooptcopyout(sopt, &coptval, 1);
- else
- error = sooptcopyout(sopt, &optval, sizeof optval);
- break;
-
- case IP_MULTICAST_LOOP:
- if (imo == 0)
- optval = coptval = IP_DEFAULT_MULTICAST_LOOP;
- else
- optval = coptval = imo->imo_multicast_loop;
- INP_UNLOCK(inp);
- if (sopt->sopt_valsize == 1)
- error = sooptcopyout(sopt, &coptval, 1);
- else
- error = sooptcopyout(sopt, &optval, sizeof optval);
- break;
-
- default:
- INP_UNLOCK(inp);
- error = ENOPROTOOPT;
- break;
- }
- INP_UNLOCK_ASSERT(inp);
-
- return (error);
-}
-
-/*
- * Discard the IP multicast options.
- */
-void
-ip_freemoptions(struct ip_moptions *imo)
-{
- register int i;
-
- 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);
- }
-}
-
-/*
* Routine called from ip_output() to loop back a copy of an IP multicast
* packet to the input queue of a specified interface. Note that this
* calls the output routine of the loopback "driver", but with an interface
diff --git a/sys/netinet/ip_var.h b/sys/netinet/ip_var.h
index 1ad36bb..eef4e1f 100644
--- a/sys/netinet/ip_var.h
+++ b/sys/netinet/ip_var.h
@@ -79,8 +79,28 @@ struct ipoption {
};
/*
+ * Multicast source list entry.
+ */
+struct in_msource {
+ TAILQ_ENTRY(in_msource) ims_next; /* next source */
+ struct sockaddr_storage ims_addr; /* address of this source */
+};
+
+/*
+ * Multicast filter descriptor; there is one instance per group membership
+ * on a socket, allocated as an expandable vector hung off ip_moptions.
+ * struct in_multi contains separate IPv4-stack-wide state for IGMPv3.
+ */
+struct in_mfilter {
+ uint16_t imf_fmode; /* filter mode for this socket/group */
+ uint16_t imf_nsources; /* # of sources for this socket/group */
+ TAILQ_HEAD(, in_msource) imf_sources; /* source list */
+};
+
+/*
* Structure attached to inpcb.ip_moptions and
* passed to ip_output when IP multicast options are in use.
+ * This structure is lazy-allocated.
*/
struct ip_moptions {
struct ifnet *imo_multicast_ifp; /* ifp for outgoing multicasts */
@@ -91,6 +111,7 @@ struct ip_moptions {
u_short imo_num_memberships; /* no. memberships this socket */
u_short imo_max_memberships; /* max memberships this socket */
struct in_multi **imo_membership; /* group memberships */
+ struct in_mfilter *imo_mfilters; /* source filters */
};
struct ipstat {
@@ -127,12 +148,11 @@ struct ipstat {
#ifdef _KERNEL
-/*
- * Flags passed to ip_output as last parameter.
- */
-#define IP_FORWARDING 0x01 /* most of ip header exists */
-#define IP_RAWOUTPUT 0x02 /* raw ip header exists */
-#define IP_SENDONES 0x04 /* send all-ones broadcast */
+/* flags passed to ip_output as last parameter */
+#define IP_FORWARDING 0x1 /* most of ip header exists */
+#define IP_RAWOUTPUT 0x2 /* raw ip header exists */
+#define IP_SENDONES 0x4 /* send all-ones broadcast */
+#define IP_SENDTOIF 0x8 /* send on specific ifnet */
#define IP_ROUTETOIF SO_DONTROUTE /* 0x10 bypass routing tables */
#define IP_ALLOWBROADCAST SO_BROADCAST /* 0x20 can send broadcast packets */
@@ -167,12 +187,15 @@ extern u_long (*ip_mcast_src)(int);
extern int rsvp_on;
extern struct pr_usrreqs rip_usrreqs;
+void inp_freemoptions(struct ip_moptions *);
+int inp_getmoptions(struct inpcb *, struct sockopt *);
+int inp_setmoptions(struct inpcb *, struct sockopt *);
+
int ip_ctloutput(struct socket *, struct sockopt *sopt);
void ip_drain(void);
void ip_fini(void *xtp);
int ip_fragment(struct ip *ip, struct mbuf **m_frag, int mtu,
u_long if_hwassist_flags, int sw_csum);
-void ip_freemoptions(struct ip_moptions *);
void ip_forward(struct mbuf *m, int srcrt);
void ip_init(void);
extern int
diff --git a/sys/netinet/sctp_pcb.c b/sys/netinet/sctp_pcb.c
index 5423208..1db8d33 100644
--- a/sys/netinet/sctp_pcb.c
+++ b/sys/netinet/sctp_pcb.c
@@ -2791,7 +2791,7 @@ sctp_inpcb_free(struct sctp_inpcb *inp, int immediate, int from)
ip_pcb->inp_options = 0;
}
if (ip_pcb->inp_moptions) {
- ip_freemoptions(ip_pcb->inp_moptions);
+ inp_freemoptions(ip_pcb->inp_moptions);
ip_pcb->inp_moptions = 0;
}
#ifdef INET6
diff --git a/sys/netinet/udp_usrreq.c b/sys/netinet/udp_usrreq.c
index f6031d6..1679699 100644
--- a/sys/netinet/udp_usrreq.c
+++ b/sys/netinet/udp_usrreq.c
@@ -113,10 +113,6 @@ static int blackhole = 0;
SYSCTL_INT(_net_inet_udp, OID_AUTO, blackhole, CTLFLAG_RW, &blackhole, 0,
"Do not send port unreachables for refused connects");
-static int strict_mcast_mship = 0;
-SYSCTL_INT(_net_inet_udp, OID_AUTO, strict_mcast_mship, CTLFLAG_RW,
- &strict_mcast_mship, 0, "Only send multicast to member sockets");
-
struct inpcbhead udb; /* from udp_var.h */
struct inpcbinfo udbinfo;
@@ -176,6 +172,7 @@ udp_input(struct mbuf *m, int off)
int iphlen = off;
struct ip *ip;
struct udphdr *uh;
+ struct ifnet *ifp;
struct inpcb *inp;
int len;
struct ip save_ip;
@@ -184,6 +181,7 @@ udp_input(struct mbuf *m, int off)
struct m_tag *fwd_tag;
#endif
+ ifp = m->m_pkthdr.rcvif;
udpstat.udps_ipackets++;
/*
@@ -301,25 +299,10 @@ udp_input(struct mbuf *m, int off)
INP_INFO_RLOCK(&udbinfo);
if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) ||
- in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif)) {
+ in_broadcast(ip->ip_dst, ifp)) {
struct inpcb *last;
+ struct ip_moptions *imo;
- /*
- * Deliver a multicast or broadcast datagram to *all* sockets
- * for which the local and remote addresses and ports match
- * those of the incoming datagram. This allows more than one
- * process to receive multi/broadcasts on the same port.
- * (This really ought to be done for unicast datagrams as
- * well, but that would cause problems with existing
- * applications that open both address-specific sockets and a
- * wildcard socket listening to the same port -- they would
- * end up receiving duplicates of every unicast datagram.
- * Those applications open the multiple sockets to overcome
- * an inadequacy of the UDP socket interface, but for
- * backwards compatibility we avoid the problem here rather
- * than fixing the interface. Maybe 4.5BSD will remedy
- * this?)
- */
last = NULL;
LIST_FOREACH(inp, &udb, inp_list) {
if (inp->inp_lport != uh->uh_dport)
@@ -328,45 +311,83 @@ udp_input(struct mbuf *m, int off)
if ((inp->inp_vflag & INP_IPV4) == 0)
continue;
#endif
- if (inp->inp_laddr.s_addr != INADDR_ANY) {
- if (inp->inp_laddr.s_addr != ip->ip_dst.s_addr)
+ if (inp->inp_laddr.s_addr != INADDR_ANY &&
+ inp->inp_laddr.s_addr != ip->ip_dst.s_addr)
continue;
- }
- if (inp->inp_faddr.s_addr != INADDR_ANY) {
- if (inp->inp_faddr.s_addr !=
- ip->ip_src.s_addr ||
- inp->inp_fport != uh->uh_sport)
+ if (inp->inp_faddr.s_addr != INADDR_ANY &&
+ inp->inp_faddr.s_addr != ip->ip_src.s_addr)
continue;
- }
-
/*
- * Check multicast packets to make sure they are only
- * sent to sockets with multicast memberships for the
- * packet's destination address and arrival interface
+ * XXX: Do not check source port of incoming datagram
+ * unless inp_connect() has been called to bind the
+ * fport part of the 4-tuple; the source could be
+ * trying to talk to us with an ephemeral port.
*/
-#define MSHIP(_inp, n) ((_inp)->inp_moptions->imo_membership[(n)])
-#define NMSHIPS(_inp) ((_inp)->inp_moptions->imo_num_memberships)
+ if (inp->inp_fport != 0 &&
+ inp->inp_fport != uh->uh_sport)
+ continue;
+
INP_LOCK(inp);
- if (strict_mcast_mship && inp->inp_moptions != NULL) {
- int mship, foundmship = 0;
-
- for (mship = 0; mship < NMSHIPS(inp);
- mship++) {
- if (MSHIP(inp, mship)->inm_addr.s_addr
- == ip->ip_dst.s_addr &&
- MSHIP(inp, mship)->inm_ifp
- == m->m_pkthdr.rcvif) {
- foundmship = 1;
- break;
+
+ /*
+ * Handle socket delivery policy for any-source
+ * and source-specific multicast. [RFC3678]
+ */
+ imo = inp->inp_moptions;
+ if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr)) &&
+ imo != NULL) {
+ struct sockaddr_in sin;
+ struct in_msource *ims;
+ int blocked, mode;
+ size_t idx;
+
+ bzero(&sin, sizeof(struct sockaddr_in));
+ sin.sin_len = sizeof(struct sockaddr_in);
+ sin.sin_family = AF_INET;
+ sin.sin_addr = ip->ip_dst;
+
+ blocked = 0;
+ idx = imo_match_group(imo, ifp,
+ (struct sockaddr *)&sin);
+ if (idx == -1) {
+ /*
+ * No group membership for this socket.
+ * Do not bump udps_noportbcast, as
+ * this will happen further down.
+ */
+ blocked++;
+ } else {
+ /*
+ * Check for a multicast source filter
+ * entry on this socket for this group.
+ * MCAST_EXCLUDE is the default
+ * behaviour. It means default accept;
+ * entries, if present, denote sources
+ * to be excluded from delivery.
+ */
+ ims = imo_match_source(imo, idx,
+ (struct sockaddr *)&udp_in);
+ mode = imo->imo_mfilters[idx].imf_fmode;
+ if ((ims != NULL &&
+ mode == MCAST_EXCLUDE) ||
+ (ims == NULL &&
+ mode == MCAST_INCLUDE)) {
+#ifdef DIAGNOSTIC
+ if (bootverbose) {
+ printf("%s: blocked by"
+ " source filter\n",
+ __func__);
+ }
+#endif
+ udpstat.udps_filtermcast++;
+ blocked++;
}
}
- if (foundmship == 0) {
+ if (blocked != 0) {
INP_UNLOCK(inp);
continue;
}
}
-#undef NMSHIPS
-#undef MSHIP
if (last != NULL) {
struct mbuf *n;
@@ -410,7 +431,7 @@ udp_input(struct mbuf *m, int off)
* Locate pcb for datagram.
*/
inp = in_pcblookup_hash(&udbinfo, ip->ip_src, uh->uh_sport,
- ip->ip_dst, uh->uh_dport, 1, m->m_pkthdr.rcvif);
+ ip->ip_dst, uh->uh_dport, 1, ifp);
if (inp == NULL) {
if (udp_log_in_vain) {
char buf[4*sizeof "123"];
diff --git a/sys/netinet/udp_var.h b/sys/netinet/udp_var.h
index 015cc4e..fc4a21c 100644
--- a/sys/netinet/udp_var.h
+++ b/sys/netinet/udp_var.h
@@ -68,6 +68,7 @@ struct udpstat {
u_long udps_fastout; /* output packets on fast path */
/* of no socket on port, arrived as multicast */
u_long udps_noportmcast;
+ u_long udps_filtermcast; /* blocked by multicast filter */
};
/*
diff --git a/sys/netinet6/in6.h b/sys/netinet6/in6.h
index 4df2b87..380b8c3 100644
--- a/sys/netinet6/in6.h
+++ b/sys/netinet6/in6.h
@@ -467,6 +467,14 @@ struct route_in6 {
* the source address.
*/
+/*
+ * The following option is private; do not use it from user applications.
+ * It is deliberately defined to the same value as IP_MSFILTER.
+ */
+#define IPV6_MSFILTER 74 /* struct __msfilterreq;
+ * set/get multicast source filter list.
+ */
+
/* to define items, should talk with KAME guys first, for *BSD compatibility */
#define IPV6_RTHDR_LOOSE 0 /* this hop need not be a neighbor. XXX old spec */
@@ -487,6 +495,18 @@ struct ipv6_mreq {
unsigned int ipv6mr_interface;
};
+#ifdef notyet
+/*
+ * Argument structure for IPV6_ADD_SOURCE_MEMBERSHIP,
+ * IPV6_DROP_SOURCE_MEMBERSHIP, IPV6_BLOCK_SOURCE, and IPV6_UNBLOCK_SOURCE.
+ */
+struct ipv6_mreq_source {
+ struct in6_addr ipv6mr_multiaddr;
+ struct in6_addr ipv6mr_sourceaddr;
+ uint32_t ipv6mr_interface;
+};
+#endif
+
/*
* IPV6_PKTINFO: Packet information(RFC2292 sec 5)
*/
diff --git a/sys/netinet6/in6_ifattach.c b/sys/netinet6/in6_ifattach.c
index b78b5c9..e00e7fa 100644
--- a/sys/netinet6/in6_ifattach.c
+++ b/sys/netinet6/in6_ifattach.c
@@ -76,6 +76,7 @@ static int generate_tmp_ifid __P((u_int8_t *, const u_int8_t *, u_int8_t *));
static int get_ifid __P((struct ifnet *, struct ifnet *, struct in6_addr *));
static int in6_ifattach_linklocal __P((struct ifnet *, struct ifnet *));
static int in6_ifattach_loopback __P((struct ifnet *));
+static void in6_purgemaddrs __P((struct ifnet *));
#define EUI64_GBIT 0x01
#define EUI64_UBIT 0x02
@@ -798,18 +799,10 @@ in6_ifdetach(ifp)
IFAFREE(&oia->ia_ifa);
}
- /* leave from all multicast groups joined */
-
in6_pcbpurgeif0(&udbinfo, ifp);
in6_pcbpurgeif0(&ripcbinfo, ifp);
-
- for (in6m = LIST_FIRST(&in6_multihead); in6m; in6m = in6m_next) {
- in6m_next = LIST_NEXT(in6m, in6m_entry);
- if (in6m->in6m_ifp != ifp)
- continue;
- in6_delmulti(in6m);
- in6m = NULL;
- }
+ /* leave from all multicast groups joined */
+ in6_purgemaddrs(ifp);
/*
* remove neighbor management table. we call it twice just to make
@@ -898,3 +891,22 @@ in6_tmpaddrtimer(ignored_arg)
splx(s);
}
+
+static void
+in6_purgemaddrs(ifp)
+ struct ifnet *ifp;
+{
+ struct in6_multi *in6m;
+ struct in6_multi *oin6m;
+
+#ifdef DIAGNOSTIC
+ printf("%s: purging ifp %p\n", __func__, ifp);
+#endif
+
+ IFF_LOCKGIANT(ifp);
+ LIST_FOREACH_SAFE(in6m, &in6_multihead, in6m_entry, oin6m) {
+ if (in6m->in6m_ifp == ifp)
+ in6_delmulti(in6m);
+ }
+ IFF_UNLOCKGIANT(ifp);
+}
diff --git a/sys/netinet6/in6_pcb.c b/sys/netinet6/in6_pcb.c
index 5ea647e..863e53f 100644
--- a/sys/netinet6/in6_pcb.c
+++ b/sys/netinet6/in6_pcb.c
@@ -456,7 +456,8 @@ in6_pcbfree(struct inpcb *inp)
/* Check and free IPv4 related resources in case of mapped addr */
if (inp->inp_options)
(void)m_free(inp->inp_options);
- ip_freemoptions(inp->inp_moptions);
+ if (inp->inp_moptions != NULL)
+ inp_freemoptions(inp->inp_moptions);
inp->inp_vflag = 0;
INP_UNLOCK(inp);
uma_zfree(ipi->ipi_zone, inp);
diff --git a/sys/sys/param.h b/sys/sys/param.h
index 4d01ed4..c770573 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -57,7 +57,7 @@
* is created, otherwise 1.
*/
#undef __FreeBSD_version
-#define __FreeBSD_version 700047 /* Master, propagated to newvers */
+#define __FreeBSD_version 700048 /* Master, propagated to newvers */
#ifndef LOCORE
#include <sys/types.h>
diff --git a/sys/sys/socket.h b/sys/sys/socket.h
index ac1b1ed..79d180b 100644
--- a/sys/sys/socket.h
+++ b/sys/sys/socket.h
@@ -234,6 +234,7 @@ struct sockproto {
};
#endif
+#ifndef _STRUCT_SOCKADDR_STORAGE_DECLARED
/*
* RFC 2553: protocol-independent placeholder for socket addresses
*/
@@ -251,6 +252,8 @@ struct sockaddr_storage {
__int64_t __ss_align; /* force desired struct alignment */
char __ss_pad2[_SS_PAD2SIZE];
};
+#define _STRUCT_SOCKADDR_STORAGE_DECLARED
+#endif
#if __BSD_VISIBLE
/*
diff --git a/tools/regression/netinet/ipsockopt/ipsockopt.c b/tools/regression/netinet/ipsockopt/ipsockopt.c
index ffcb48e..d03ddf6 100644
--- a/tools/regression/netinet/ipsockopt/ipsockopt.c
+++ b/tools/regression/netinet/ipsockopt/ipsockopt.c
@@ -679,7 +679,7 @@ test_ip_multicast_membership(int sock, const char *socktypename)
* this usually maps to the interface to which the default
* route is pointing.
*/
- for (i = 0; i < nmcastgroups; i++) {
+ for (i = 1; i < nmcastgroups+1; i++) {
mreq.imr_multiaddr.s_addr = htonl((basegroup + i));
mreq.imr_interface.s_addr = INADDR_ANY;
inet_ntop(AF_INET, &mreq.imr_multiaddr, addrbuf, sizeof(addrbuf));
@@ -692,7 +692,7 @@ test_ip_multicast_membership(int sock, const char *socktypename)
sock, socktypename, addrbuf, "INADDR_ANY");
}
}
- for (i = 0; i < nmcastgroups; i++) {
+ for (i = 1; i < nmcastgroups+1; i++) {
mreq.imr_multiaddr.s_addr = htonl((basegroup + i));
mreq.imr_interface.s_addr = INADDR_ANY;
inet_ntop(AF_INET, &mreq.imr_multiaddr, addrbuf, sizeof(addrbuf));
diff --git a/usr.bin/netstat/inet.c b/usr.bin/netstat/inet.c
index 2d96cb6..1be7b87 100644
--- a/usr.bin/netstat/inet.c
+++ b/usr.bin/netstat/inet.c
@@ -513,7 +513,7 @@ udp_stats(u_long off __unused, const char *name, int af1 __unused)
p1a(udps_nosum, "\t%lu with no checksum\n");
p1a(udps_noport, "\t%lu dropped due to no socket\n");
p(udps_noportbcast,
- "\t%lu broadcast/multicast datagram%s dropped due to no socket\n");
+ "\t%lu broadcast/multicast datagram%s undelivered\n");
p1a(udps_fullsock, "\t%lu dropped due to full socket buffers\n");
p1a(udpps_pcbhashmiss, "\t%lu not for hashed pcb\n");
delivered = udpstat.udps_ipackets -
@@ -526,6 +526,9 @@ udp_stats(u_long off __unused, const char *name, int af1 __unused)
if (delivered || sflag <= 1)
printf("\t%lu delivered\n", delivered);
p(udps_opackets, "\t%lu datagram%s output\n");
+ /* the next statistic is cumulative in udps_noportbcast */
+ p(udps_filtermcast,
+ "\t%lu time%s multicast source filter matched\n");
#undef p
#undef p1a
}
diff --git a/usr.sbin/mtest/mtest.c b/usr.sbin/mtest/mtest.c
index 747a1f3..28c0fc6 100644
--- a/usr.sbin/mtest/mtest.c
+++ b/usr.sbin/mtest/mtest.c
@@ -55,6 +55,15 @@ __FBSDID("$FreeBSD$");
#include <err.h>
#include <unistd.h>
+/* The following two socket options are private to the kernel and libc. */
+
+#ifndef IP_SETMSFILTER
+#define IP_SETMSFILTER 74 /* atomically set filter list */
+#endif
+#ifndef IP_GETMSFILTER
+#define IP_GETMSFILTER 75 /* get filter list */
+#endif
+
static void process_file(char *, int);
static void process_cmd(char*, int, FILE *fp);
static void usage(void);
@@ -135,14 +144,14 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
{
char str1[STR_SIZE];
char str2[STR_SIZE];
-#ifdef WITH_IGMPV3
char str3[STR_SIZE];
+#ifdef WITH_IGMPV3
char filtbuf[IP_MSFILTER_SIZE(MAX_ADDRS)];
#endif
struct ifreq ifr;
struct ip_mreq imr;
-#ifdef WITH_IGMPV3
struct ip_mreq_source imrs;
+#ifdef WITH_IGMPV3
struct ip_msfilter *imsfp;
#endif
char *line;
@@ -256,6 +265,7 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
*/
case 'i':
case 'e':
+ /* XXX: SIOCSIPMSFILTER will be made an internal API. */
if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
printf("-1\n");
break;
@@ -284,10 +294,13 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
else
printf("ok\n");
break;
+#endif /* WITH_IGMPV3 */
/*
* Allow or block traffic from a source, using the
* delta based api.
+ * XXX: Currently we allow this to be used with the ASM-only
+ * implementation of RFC3678 in FreeBSD 7.
*/
case 't':
case 'b':
@@ -302,6 +315,8 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
break;
}
+#ifdef WITH_IGMPV3
+ /* XXX: SIOCSIPMSFILTER will be made an internal API. */
/* First determine out current filter mode. */
imsfp = (struct ip_msfilter *)filtbuf;
imsfp->imsf_multiaddr.s_addr = imrs.imr_multiaddr.s_addr;
@@ -325,13 +340,22 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
opt = (*cmd == 't') ? IP_ADD_SOURCE_MEMBERSHIP :
IP_DROP_SOURCE_MEMBERSHIP;
}
+#else /* !WITH_IGMPV3 */
+ /*
+ * Don't look before we leap; we may only block or unblock
+ * sources on a socket in exclude mode.
+ */
+ opt = (*cmd == 't') ? IP_UNBLOCK_SOURCE : IP_BLOCK_SOURCE;
+#endif /* WITH_IGMPV3 */
if (setsockopt(s, IPPROTO_IP, opt, &imrs, sizeof(imrs)) == -1)
warn("ioctl IP_ADD_SOURCE_MEMBERSHIP/IP_DROP_SOURCE_MEMBERSHIP/IP_UNBLOCK_SOURCE/IP_BLOCK_SOURCE");
else
printf("ok\n");
break;
+#ifdef WITH_IGMPV3
case 'g':
+ /* XXX: SIOCSIPMSFILTER will be made an internal API. */
if ((sscanf(line, "%s %s %d", str1, str2, &n)) != 3) {
printf("-1\n");
break;
@@ -360,11 +384,11 @@ process_cmd(char *cmd, int s, FILE *fp __unused)
printf("%s\n", inet_ntoa(imsfp->imsf_slist[i]));
}
break;
-#else /* !WITH_IGMPV3 */
+#endif /* !WITH_IGMPV3 */
+
+#ifndef WITH_IGMPV3
case 'i':
case 'e':
- case 't':
- case 'b':
case 'g':
printf("warning: IGMPv3 is not supported by this version "
"of FreeBSD; command ignored.\n");
@@ -389,11 +413,15 @@ usage(void)
printf("d ifname e.e.e.e.e.e - delete ether multicast address\n");
printf("m ifname 1/0 - set/clear ether allmulti flag\n");
printf("p ifname 1/0 - set/clear ether promisc flag\n");
+#ifdef WITH_IGMPv3
printf("i g.g.g.g i.i.i.i n - set n include mode src filter\n");
printf("e g.g.g.g i.i.i.i n - set n exclude mode src filter\n");
+#endif
printf("t g.g.g.g i.i.i.i s.s.s.s - allow traffic from src\n");
printf("b g.g.g.g i.i.i.i s.s.s.s - block traffic from src\n");
+#ifdef WITH_IGMPV3
printf("g g.g.g.g i.i.i.i n - get and show n src filters\n");
+#endif
printf("f filename - read command(s) from file\n");
printf("s seconds - sleep for some time\n");
printf("q - quit\n");
OpenPOWER on IntegriCloud