Initial Release
[project/omcproxy.git] / src / client.c
1 /*
2 * Author: Steven Barth <steven at midlink.org>
3 *
4 * Copyright 2015 Deutsche Telekom AG
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 */
19
20 #include <errno.h>
21 #include <string.h>
22 #include <unistd.h>
23 #include <alloca.h>
24
25 #include <arpa/inet.h>
26 #include <netinet/in.h>
27 #include <linux/mroute6.h>
28 #include <libubox/list.h>
29 #include <libubox/avl.h>
30
31 #include "client.h"
32
33
34 // Add / update / remove a client entry for a multicast group
35 int client_set(struct client *client, const struct in6_addr *group,
36 bool include, const struct in6_addr sources[], size_t cnt)
37 {
38 int family = (IN6_IS_ADDR_V4MAPPED(group)) ? AF_INET : AF_INET6;
39 int sol = (family == AF_INET) ? SOL_IP : SOL_IPV6;
40 char addrbuf[INET6_ADDRSTRLEN];
41 size_t len = sizeof(struct group_filter) + cnt * sizeof(struct sockaddr_storage);
42 struct {
43 struct group_filter f;
44 struct sockaddr_storage s[];
45 } *filter = alloca(len);
46 struct sockaddr_in *in_addr = (struct sockaddr_in*)&filter->f.gf_group;
47 struct sockaddr_in6 *in6_addr = (struct sockaddr_in6*)&filter->f.gf_group;
48
49 inet_ntop(AF_INET6, group, addrbuf, sizeof(addrbuf));
50 L_DEBUG("%s: %s on %d => %s (+%d sources)", __FUNCTION__, addrbuf,
51 client->ifindex, (include) ? "include" : "exclude", (int)cnt);
52
53 // Construct MSFILTER for outgoing IGMP / MLD
54 memset(filter, 0, len);
55 filter->f.gf_interface = client->ifindex;
56 filter->f.gf_fmode = include ? MCAST_INCLUDE : MCAST_EXCLUDE;
57 filter->f.gf_group.ss_family = family;
58 filter->f.gf_numsrc = cnt;
59
60 if (family == AF_INET)
61 client_unmap(&in_addr->sin_addr, group);
62 else
63 in6_addr->sin6_addr = *group;
64
65 for (size_t i = 0; i < cnt; ++i) {
66 filter->f.gf_slist[i].ss_family = family;
67
68 in_addr = (struct sockaddr_in*)&filter->f.gf_slist[i];
69 in6_addr = (struct sockaddr_in6*)&filter->f.gf_slist[i];
70
71 if (family == AF_INET)
72 client_unmap(&in_addr->sin_addr, &sources[i]);
73 else
74 in6_addr->sin6_addr = sources[i];
75 }
76
77 int fd = (family == AF_INET) ? client->igmp_fd : client->mld_fd;
78 setsockopt(fd, sol, MCAST_LEAVE_GROUP, filter, sizeof(struct group_req));
79 if (!include || cnt > 0) {
80 if (setsockopt(fd, sol, MCAST_JOIN_GROUP, filter, sizeof(struct group_req))
81 && family == AF_INET && errno == ENOBUFS) {
82 L_WARN("proxy: kernel denied joining multicast group. check igmp_max_memberships?");
83 return -errno;
84 }
85
86 if (setsockopt(fd, sol, MCAST_MSFILTER, filter, len))
87 return -errno;
88 }
89 return 0;
90 }
91
92 // Initialize client-instance
93 int client_init(struct client *client, int ifindex)
94 {
95 client->igmp_fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
96 if (client->igmp_fd < 0)
97 return -errno;
98
99 client->mld_fd = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, 0);
100 if (client->mld_fd < 0)
101 return -errno;
102
103 client->ifindex = ifindex;
104 return 0;
105 }
106
107 // Cleanup client-instance
108 void client_deinit(struct client *client)
109 {
110 if (client->ifindex) {
111 close(client->igmp_fd);
112 close(client->mld_fd);
113 client->igmp_fd = -1;
114 client->mld_fd = -1;
115 client->ifindex = 0;
116 }
117 }