1 /* Multicast group management (join/leave) API
2  *
3  * Copyright (C) 2001-2005  Carsten Schill <carsten@cschill.de>
4  * Copyright (C) 2006-2009  Julien BLACHE <jb@jblache.org>
5  * Copyright (C) 2009       Todd Hayton <todd.hayton@gmail.com>
6  * Copyright (C) 2009-2011  Micha Lenk <micha@debian.org>
7  * Copyright (C) 2011-2013  Joachim Nilsson <troglobit@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
22  */
23 
24 #include "mclab.h"
25 
26 static int mcgroup4_socket = -1;
27 
28 #ifdef __linux__
29 /* Extremely simple "drop everything" filter for Linux so we do not get
30  * a copy each packet of every routed group we join. */
31 static struct sock_filter filter[] = {
32 	{ 0x6, 0, 0, 0x00000000 },
33 };
34 
35 static struct sock_fprog fprog = {
36 	sizeof(filter) / sizeof(filter[0]),
37 	filter
38 };
39 #endif
40 
41 static struct iface *find_valid_iface(const char *ifname, int cmd)
42 {
43 	const char *command = cmd == 'j' ? "Join" : "Leave";
44 	struct iface *iface = iface_find_by_name(ifname);
45 
46 	if (!iface) {
47 		smclog(LOG_WARNING, "%s multicast group, unknown interface %s", command, ifname);
48 		return NULL;
49 	}
50 
51 	return iface;
52 }
53 
54 static void mcgroup4_init(void)
55 {
56 	if (mcgroup4_socket < 0) {
57 #ifdef SOCK_CLOEXEC
58 		mcgroup4_socket = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
59 #else
60 		mcgroup4_socket = socket(AF_INET, SOCK_DGRAM, 0);
61 #endif
62 		if (mcgroup4_socket < 0) {
63 			smclog(LOG_ERR, "Failed creating socket for joining IPv4 multicast groups: %m");
64 			exit(255);
65 		}
66 #ifndef SOCK_CLOEXEC
67 		if (fcntl(mcgroup4_socket, F_SETFD, FD_CLOEXEC) < 0) {
68 			smclog(LOG_WARNING, "Failed creating socket for joining IPv4 multicast groups: %m");
69 			close(mcgroup4_socket);
70 			mcgroup4_socket = -1;
71 			return;
72 		}
73 #endif
74 
75 #ifdef __linux__
76 		if (setsockopt(mcgroup4_socket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
77 			smclog(LOG_DEBUG, "Failed setting IPv4 socket filter, continuing anyway");
78 #endif
79 	}
80 }
81 
82 static int mcgroup_join_leave_ipv4(int sd, int cmd, const char *ifname, struct in_addr group)
83 {
84 	int joinleave = cmd == 'j' ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP;
85 	struct ip_mreq mreq;
86 	struct iface *iface = find_valid_iface(ifname, cmd);
87 
88 	if (!iface)
89 		return 1;
90 
91 	mreq.imr_multiaddr.s_addr = group.s_addr;
92 	mreq.imr_interface.s_addr = iface->inaddr.s_addr;
93 	if (setsockopt(sd, IPPROTO_IP, joinleave, (void *)&mreq, sizeof(mreq))) {
94 		if (EADDRINUSE != errno)
95 			smclog(LOG_WARNING, "%s MEMBERSHIP failed: %m", cmd == 'j' ? "ADD" : "DROP");
96 		return 1;
97 	}
98 
99 	return 0;
100 }
101 
102 /*
103  * Joins the MC group with the address 'group' on the interface 'ifname'.
104  * The join is bound to the UDP socket 'sd', so if this socket is
105  * closed the membership is dropped.
106  *
107  * returns: - 0 if the function succeeds
108  *          - 1 if parameters are wrong or the join fails
109  */
110 int mcgroup4_join(const char *ifname, struct in_addr group)
111 {
112 	mcgroup4_init();
113 
114 	return mcgroup_join_leave_ipv4(mcgroup4_socket, 'j', ifname, group);
115 }
116 
117 /*
118  * Leaves the MC group with the address 'group' on the interface 'ifname'.
119  *
120  * returns: - 0 if the function succeeds
121  *          - 1 if parameters are wrong or the join fails
122  */
123 int mcgroup4_leave(const char *ifname, struct in_addr group)
124 {
125 	mcgroup4_init();
126 
127 	return mcgroup_join_leave_ipv4(mcgroup4_socket, 'l', ifname, group);
128 }
129 
130 /*
131  * Close IPv4 multicast socket to kernel to leave any joined groups
132  */
133 void mcgroup4_disable(void)
134 {
135 	if (mcgroup4_socket != -1) {
136 		close(mcgroup4_socket);
137 		mcgroup4_socket = -1;
138 	}
139 }
140 
141 #ifdef HAVE_IPV6_MULTICAST_HOST
142 static int mcgroup6_socket = -1;
143 
144 static void mcgroup6_init(void)
145 {
146 	if (mcgroup6_socket < 0) {
147 #ifdef SOCK_CLOEXEC
148 		mcgroup6_socket = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
149 #else
150 		mcgroup6_socket = socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP);
151 #endif
152 		if (mcgroup6_socket < 0) {
153 			smclog(LOG_WARNING, "Failed creating socket for joining IPv6 multicast groups: %m");
154 			return;
155 		}
156 #ifndef SOCK_CLOEXEC
157 		if (fcntl(mcgroup6_socket, F_SETFD, FD_CLOEXEC) < 0) {
158 			smclog(LOG_WARNING, "Failed creating socket for joining IPv6 multicast groups: %m");
159 			close(mcgroup6_socket);
160 			mcgroup6_socket = -1;
161 			return;
162 		}
163 #endif
164 
165 #ifdef __linux__
166 		if (setsockopt(mcgroup6_socket, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)) < 0)
167 			smclog(LOG_DEBUG, "Failed setting IPv6 socket filter, continuing anyway");
168 #endif
169 	}
170 }
171 
172 static int mcgroup_join_leave_ipv6(int sd, int cmd, const char *ifname, struct in6_addr group)
173 {
174 	int joinleave = cmd == 'j' ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP;
175 	struct ipv6_mreq mreq;
176 	struct iface *iface = find_valid_iface(ifname, cmd);
177 
178 	if (!iface)
179 		return 1;
180 
181 	mreq.ipv6mr_multiaddr = group;
182 	mreq.ipv6mr_interface = iface->ifindex;
183 	if (setsockopt(sd, IPPROTO_IPV6, joinleave, (void *)&mreq, sizeof(mreq))) {
184 		if (EADDRINUSE != errno)
185 			smclog(LOG_WARNING, "%s MEMBERSHIP failed: %m", cmd == 'j' ? "ADD" : "DROP");
186 		return 1;
187 	}
188 
189 	return 0;
190 }
191 
192 /*
193  * Joins the MC group with the address 'group' on the interface 'ifname'.
194  * The join is bound to the UDP socket 'sd', so if this socket is
195  * closed the membership is dropped.
196  *
197  * returns: - 0 if the function succeeds
198  *          - 1 if parameters are wrong or the join fails
199  */
200 int mcgroup6_join(const char *ifname, struct in6_addr group)
201 {
202 	mcgroup6_init();
203 
204 	return mcgroup_join_leave_ipv6(mcgroup6_socket, 'j', ifname, group);
205 }
206 
207 /*
208  * Leaves the MC group with the address 'group' on the interface 'ifname'.
209  *
210  * returns: - 0 if the function succeeds
211  *          - 1 if parameters are wrong or the join fails
212  */
213 int mcgroup6_leave(const char *ifname, struct in6_addr group)
214 {
215 	mcgroup6_init();
216 
217 	return mcgroup_join_leave_ipv6(mcgroup6_socket, 'l', ifname, group);
218 }
219 #endif /* HAVE_IPV6_MULTICAST_HOST */
220 
221 /*
222  * Close IPv6 multicast socket to kernel to leave any joined groups
223  */
224 void mcgroup6_disable(void)
225 {
226 #ifdef HAVE_IPV6_MULTICAST_HOST
227 	if (mcgroup6_socket != -1) {
228 		close(mcgroup6_socket);
229 		mcgroup6_socket = -1;
230 	}
231 #endif /* HAVE_IPV6_MULTICAST_HOST */
232 }
233 
234 /**
235  * Local Variables:
236  *  version-control: t
237  *  indent-tabs-mode: t
238  *  c-file-style: "linux"
239  * End:
240  */
241