Initial Release
[project/omcproxy.git] / src / igmp.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 <sys/socket.h>
22 #include <sys/ioctl.h>
23 #include <netinet/ip.h>
24 #include <netinet/in.h>
25 #include <net/if.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "querier.h"
30
31 // Test if multicast-group is valid and relevant
32 static inline bool igmp_is_valid_group(in_addr_t group)
33 {
34 return IN_MULTICAST(be32_to_cpu(group));
35 }
36
37 // Handle an IGMP-record from an IGMP-packet (called by igmp_receive)
38 static ssize_t igmp_handle_record(struct groups *groups, const uint8_t *data, size_t len)
39 {
40 struct igmpv3_grec *r = (struct igmpv3_grec*)data;
41
42 if (len < sizeof(*r))
43 return -1;
44
45 size_t nsrc = ntohs(r->grec_nsrcs);
46 size_t read = sizeof(*r) + nsrc * sizeof(struct in_addr) + r->grec_auxwords * 4;
47
48 if (len < read)
49 return -1;
50
51 if (r->grec_type >= UPDATE_IS_INCLUDE && r->grec_type <= UPDATE_BLOCK &&
52 igmp_is_valid_group(r->grec_mca)) {
53 struct in6_addr addr, sources[nsrc];
54 querier_map(&addr, r->grec_mca);
55
56 for (size_t i = 0; i < nsrc; ++i)
57 querier_map(&sources[i], r->grec_src[i]);
58
59 groups_update_state(groups, &addr, sources, nsrc, r->grec_type);
60 }
61
62 return read;
63 }
64
65 // Receive and parse an IGMP-packet (called by uloop as callback)
66 void igmp_handle(struct mrib_querier *mrib, const struct igmphdr *igmp, size_t len,
67 const struct sockaddr_in *from)
68 {
69 struct querier_iface *q = container_of(mrib, struct querier_iface, mrib);
70 omgp_time_t now = omgp_time();
71 char addrbuf[INET_ADDRSTRLEN];
72 struct in6_addr group;
73
74 querier_map(&group, igmp->group);
75 inet_ntop(AF_INET, &from->sin_addr, addrbuf, sizeof(addrbuf));
76
77 if (igmp->type == IGMP_HOST_MEMBERSHIP_QUERY) {
78 struct igmpv3_query *query = (struct igmpv3_query*)igmp;
79
80 if (len != sizeof(*igmp) && ((size_t)len < sizeof(*query) ||
81 (size_t)len < sizeof(*query) + ntohs(query->nsrcs) * sizeof(struct in_addr)))
82 return;
83
84 if (query->group && !igmp_is_valid_group(query->group))
85 return;
86
87 // Setup query target address
88 struct in_addr addr;
89 if (mrib_igmp_source(mrib, &addr))
90 return;
91
92 bool suppress = false;
93 size_t nsrc = 0;
94 int robustness = 2;
95 omgp_time_t mrd = 10000;
96 omgp_time_t query_interval = 125000;
97
98 if (igmp->code)
99 mrd = 100 * ((len == sizeof(*igmp)) ? igmp->code : querier_qqi(igmp->code));
100
101 if ((size_t)len > sizeof(*igmp)) {
102 if (query->qrv)
103 robustness = query->qrv;
104
105 if (query->qqic)
106 query_interval = querier_qqi(query->qqic) * 1000;
107
108 suppress = query->suppress;
109 nsrc = ntohs(query->nsrcs);
110 }
111
112 if (!suppress && query->group) {
113 struct in6_addr sources[nsrc];
114 for (size_t i = 0; i < nsrc; ++i)
115 querier_map(&sources[i], query->srcs[i]);
116
117 groups_update_timers(&q->groups, &group, sources, nsrc);
118 }
119
120 int election = memcmp(&from->sin_addr, &addr, sizeof(from->sin_addr));
121 L_INFO("%s: detected other querier %s with priority %d on %d",
122 __FUNCTION__, addrbuf, election, q->ifindex);
123
124 // TODO: we ignore IGMPv1/v2 queriers for now, since a lot of them are dumb switches
125
126 if (election < 0 && !query->group && len > sizeof(*igmp)) {
127 groups_update_config(&q->groups, false, mrd, query_interval, robustness);
128
129 q->igmp_other_querier = true;
130 q->igmp_next_query = now + (q->groups.cfg_v4.query_response_interval / 2) +
131 (q->groups.cfg_v4.robustness * q->groups.cfg_v4.query_interval);
132 }
133 } else if (igmp->type == IGMPV3_HOST_MEMBERSHIP_REPORT) {
134 struct igmpv3_report *report = (struct igmpv3_report*)igmp;
135
136 if ((size_t)len <= sizeof(*report))
137 return;
138
139 uint8_t *ibuf = (uint8_t*)igmp;
140 size_t count = ntohs(report->ngrec);
141 size_t offset = sizeof(*report);
142
143 while (count > 0 && offset < len) {
144 ssize_t read = igmp_handle_record(&q->groups, &ibuf[offset], len - offset);
145 if (read < 0)
146 break;
147
148 offset += read;
149 --count;
150 }
151 } else if (igmp->type == IGMPV2_HOST_MEMBERSHIP_REPORT ||
152 igmp->type == IGMP_HOST_LEAVE_MESSAGE ||
153 igmp->type == IGMP_HOST_MEMBERSHIP_REPORT) {
154
155 if (len != sizeof(*igmp) || !igmp_is_valid_group(igmp->group))
156 return;
157
158 groups_update_state(&q->groups, &group, NULL, 0,
159 (igmp->type == IGMPV2_HOST_MEMBERSHIP_REPORT) ? UPDATE_REPORT :
160 (igmp->type == IGMP_HOST_MEMBERSHIP_REPORT) ? UPDATE_REPORT_V1 : UPDATE_DONE);
161 }
162
163 uloop_timeout_set(&q->timeout, 0);
164 }
165
166 // Send generic / group-specific / group-and-source specific IGMP-query
167 int igmp_send_query(struct querier_iface *q,
168 const struct in6_addr *group,
169 const struct list_head *sources,
170 bool suppress)
171 {
172 uint8_t qqic = querier_qqic(((group) ? q->groups.cfg_v4.last_listener_query_interval :
173 q->groups.cfg_v4.query_response_interval) / 100);
174 struct {
175 struct igmpv3_query q;
176 struct in_addr srcs[QUERIER_MAX_SOURCE];
177 } query = {.q = {
178 .type = IGMP_HOST_MEMBERSHIP_QUERY,
179 .code = qqic,
180 .qrv = q->groups.cfg_v4.robustness,
181 .suppress = suppress,
182 .qqic = querier_qqic(q->groups.cfg_v4.query_interval / 1000),
183 }};
184
185 struct group_source *s;
186 size_t cnt = 0;
187 if (sources) {
188 list_for_each_entry(s, sources, head) {
189 if (cnt >= QUERIER_MAX_SOURCE) {
190 L_WARN("%s: maximum source count (%d) exceeded",
191 __FUNCTION__, QUERIER_MAX_SOURCE);
192 break;
193 }
194
195 query.q.srcs[cnt] = querier_unmap(&s->addr);
196 }
197 }
198 query.q.nsrcs = htons(cnt);
199
200 struct sockaddr_in dest = { .sin_family = AF_INET, .sin_addr = {htonl(0xe0000001U)}};
201 if (group) {
202 query.q.group = querier_unmap(group);
203 dest.sin_addr.s_addr = query.q.group;
204 }
205
206 return mrib_send_igmp(&q->mrib, &query.q,
207 sizeof(query.q) + cnt * sizeof(query.srcs[0]), &dest);
208 }
209