Initial Release
[project/omcproxy.git] / src / mld.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 <stdlib.h>
22 #include <arpa/inet.h>
23 #include <sys/ioctl.h>
24 #include <sys/socket.h>
25 #include <sys/timerfd.h>
26
27 #include <netinet/in.h>
28 #include <netinet/ip6.h>
29 #include <netinet/icmp6.h>
30 #include <unistd.h>
31 #include <ifaddrs.h>
32 #include <linux/mroute6.h>
33
34 #include "mrib.h"
35 #include "querier.h"
36
37 struct mld_query {
38 struct mld_hdr mld;
39 uint8_t s_qrv;
40 uint8_t qqic;
41 uint16_t nsrc;
42 struct in6_addr addrs[0];
43 };
44
45 // Test whether group address is valid and interesting
46 static inline bool mld_is_valid_group(const struct in6_addr *addr)
47 {
48 return IN6_IS_ADDR_MULTICAST(addr);
49 }
50
51 // Handle Multicast Address Record from MLD-Packets (called by mld_receive)
52 static ssize_t mld_handle_record(struct groups *groups, const uint8_t *data, size_t len)
53 {
54 struct mld_record {
55 uint8_t type;
56 uint8_t aux;
57 uint16_t nsrc;
58 struct in6_addr addr;
59 struct in6_addr sources[];
60 } *r = (struct mld_record*)data;
61
62 if (len < sizeof(*r))
63 return -1;
64
65 size_t nsrc = ntohs(r->nsrc);
66 size_t read = sizeof(*r) + nsrc * sizeof(struct in6_addr) + r->aux;
67 if (len < read)
68 return -1;
69
70 if (r->type >= UPDATE_IS_INCLUDE && r->type <= UPDATE_BLOCK && mld_is_valid_group(&r->addr))
71 groups_update_state(groups, &r->addr, r->sources, nsrc, r->type);
72
73 return read;
74 }
75
76 // Receive an MLD-Packet from a node (called by uloop as callback)
77 void mld_handle(struct mrib_querier *mrib, const struct mld_hdr *hdr, size_t len,
78 const struct sockaddr_in6 *from)
79 {
80 char addrbuf[INET_ADDRSTRLEN];
81 omgp_time_t now = omgp_time();
82 inet_ntop(AF_INET6, &hdr->mld_addr, addrbuf, sizeof(addrbuf));
83
84 struct querier_iface *q = container_of(mrib, struct querier_iface, mrib);
85 if (hdr->mld_icmp6_hdr.icmp6_type == ICMPV6_MGM_QUERY) {
86 struct mld_query *query = (struct mld_query*)hdr;
87
88 if (len != 24 && ((size_t)len < sizeof(*query) ||
89 (size_t)len < sizeof(*query) + ntohs(query->nsrc) * sizeof(struct in6_addr)))
90 return;
91
92 if (!IN6_IS_ADDR_UNSPECIFIED(&query->mld.mld_addr) &&
93 !mld_is_valid_group(&query->mld.mld_addr))
94 return;
95
96 // Detect local source address
97 struct in6_addr addr;
98 if (mrib_mld_source(mrib, &addr))
99 return;
100
101 bool suppress = false;
102 size_t nsrc = 0;
103 int robustness = 2;
104 omgp_time_t mrd = 10000;
105 omgp_time_t query_interval = 125000;
106
107 if (query->mld.mld_icmp6_hdr.icmp6_dataun.icmp6_un_data16[0])
108 mrd = (len == 24) ? ntohs(query->mld.mld_icmp6_hdr.icmp6_dataun.icmp6_un_data16[0]) :
109 querier_mrd(query->mld.mld_icmp6_hdr.icmp6_dataun.icmp6_un_data16[0]);
110
111 if (len > 24) {
112 if (query->s_qrv & 0x7)
113 robustness = query->s_qrv & 0x7;
114
115 if (query->qqic)
116 query_interval = querier_qqi(query->qqic) * 1000;
117 }
118
119 if (!suppress && !IN6_IS_ADDR_UNSPECIFIED(&query->mld.mld_addr))
120 groups_update_timers(&q->groups, &query->mld.mld_addr, query->addrs, nsrc);
121
122 int election = memcmp(&from->sin6_addr, &addr, sizeof(from->sin6_addr));
123 L_INFO("%s: detected other querier %s with priority %d on %d",
124 __FUNCTION__, addrbuf, election, q->ifindex);
125
126 // TODO: we ignore MLDv1 queriers for now, since a lot of them are dumb switches
127
128 if (election < 0 && IN6_IS_ADDR_UNSPECIFIED(&query->mld.mld_addr) && len > 24) {
129 groups_update_config(&q->groups, true, mrd, query_interval, robustness);
130
131 q->mld_other_querier = true;
132 q->mld_next_query = now + (q->groups.cfg_v6.query_response_interval / 2) +
133 (q->groups.cfg_v6.robustness * q->groups.cfg_v6.query_interval);
134 }
135 } else if (hdr->mld_icmp6_hdr.icmp6_type == ICMPV6_MLD2_REPORT) {
136 struct icmp6_hdr *mld_report = (struct icmp6_hdr *)hdr;
137 if ((size_t)len <= sizeof(*mld_report))
138 return;
139
140 uint8_t *buf = (uint8_t*)hdr;
141 size_t count = ntohs(mld_report->icmp6_dataun.icmp6_un_data16[1]);
142 ssize_t offset = sizeof(*mld_report);
143
144 while (count > 0 && offset < (ssize_t)len) {
145 ssize_t read = mld_handle_record(&q->groups, &buf[offset], len - offset);
146 if (read < 0)
147 break;
148
149 offset += read;
150 --count;
151 }
152 } else if (hdr->mld_icmp6_hdr.icmp6_type == MLD_LISTENER_REPORT ||
153 hdr->mld_icmp6_hdr.icmp6_type == MLD_LISTENER_REDUCTION) {
154 if (len != 24 || !mld_is_valid_group(&hdr->mld_addr))
155 return;
156
157 groups_update_state(&q->groups, &hdr->mld_addr, NULL, 0,
158 (hdr->mld_icmp6_hdr.icmp6_type == MLD_LISTENER_REPORT) ? UPDATE_REPORT : UPDATE_DONE);
159 }
160 uloop_timeout_set(&q->timeout, 0);
161 }
162
163
164 // Send generic / group-specific / group-and-source-specific queries
165 ssize_t mld_send_query(struct querier_iface *q, const struct in6_addr *group,
166 const struct list_head *sources, bool suppress)
167 {
168 uint16_t mrc = querier_mrc((group) ? q->groups.cfg_v6.last_listener_query_interval :
169 q->groups.cfg_v6.query_response_interval);
170 struct {
171 struct mld_query q;
172 struct in6_addr addrs[QUERIER_MAX_SOURCE];
173 } query = {.q = {
174 .mld = {.mld_icmp6_hdr = {MLD_LISTENER_QUERY, 0, 0, {.icmp6_un_data16 = {mrc, 0}}}},
175 .s_qrv = (q->groups.cfg_v6.robustness & 0x7) | (suppress ? QUERIER_SUPPRESS : 0),
176 .qqic = querier_qqic(q->groups.cfg_v6.query_interval / 1000),
177 }};
178
179 struct group_source *s;
180 size_t cnt = 0;
181 if (sources) {
182 list_for_each_entry(s, sources, head) {
183 if (cnt >= QUERIER_MAX_SOURCE)
184 break; // TODO: log source overflow
185
186 query.addrs[cnt++] = s->addr;
187 }
188 }
189 query.q.nsrc = htons(cnt);
190
191 struct sockaddr_in6 dest = {AF_INET6, 0, 0, IPV6_ALL_NODES_INIT, q->ifindex};
192
193 if (group)
194 query.q.mld.mld_addr = dest.sin6_addr = *group;
195
196 return mrib_send_mld(&q->mrib, &query.q.mld,
197 sizeof(query.q) + cnt * sizeof(query.addrs[0]), &dest);
198 }