2 * Author: Steven Barth <steven at midlink.org>
4 * Copyright 2015 Deutsche Telekom AG
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
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 #include <arpa/inet.h>
23 #include <sys/ioctl.h>
24 #include <sys/socket.h>
25 #include <sys/timerfd.h>
27 #include <netinet/in.h>
28 #include <netinet/ip6.h>
29 #include <netinet/icmp6.h>
32 #include <linux/mroute6.h>
42 struct in6_addr addrs
[0];
45 // Test whether group address is valid and interesting
46 static inline bool mld_is_valid_group(const struct in6_addr
*addr
)
48 return IN6_IS_ADDR_MULTICAST(addr
);
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
)
59 struct in6_addr sources
[];
60 } *r
= (struct mld_record
*)data
;
65 size_t nsrc
= ntohs(r
->nsrc
);
66 size_t read
= sizeof(*r
) + nsrc
* sizeof(struct in6_addr
) + r
->aux
;
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
);
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
)
80 char addrbuf
[INET_ADDRSTRLEN
];
81 omgp_time_t now
= omgp_time();
82 inet_ntop(AF_INET6
, &hdr
->mld_addr
, addrbuf
, sizeof(addrbuf
));
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
;
88 if (len
!= 24 && ((size_t)len
< sizeof(*query
) ||
89 (size_t)len
< sizeof(*query
) + ntohs(query
->nsrc
) * sizeof(struct in6_addr
)))
92 if (!IN6_IS_ADDR_UNSPECIFIED(&query
->mld
.mld_addr
) &&
93 !mld_is_valid_group(&query
->mld
.mld_addr
))
96 // Detect local source address
98 if (mrib_mld_source(mrib
, &addr
))
101 bool suppress
= false;
104 omgp_time_t mrd
= 10000;
105 omgp_time_t query_interval
= 125000;
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]);
112 if (query
->s_qrv
& 0x7)
113 robustness
= query
->s_qrv
& 0x7;
116 query_interval
= querier_qqi(query
->qqic
) * 1000;
119 if (!suppress
&& !IN6_IS_ADDR_UNSPECIFIED(&query
->mld
.mld_addr
))
120 groups_update_timers(&q
->groups
, &query
->mld
.mld_addr
, query
->addrs
, nsrc
);
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
);
126 // TODO: we ignore MLDv1 queriers for now, since a lot of them are dumb switches
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
);
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
);
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
))
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
);
144 while (count
> 0 && offset
< (ssize_t
)len
) {
145 ssize_t read
= mld_handle_record(&q
->groups
, &buf
[offset
], len
- offset
);
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
))
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
);
160 uloop_timeout_set(&q
->timeout
, 0);
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
)
168 uint16_t mrc
= querier_mrc((group
) ? q
->groups
.cfg_v6
.last_listener_query_interval
:
169 q
->groups
.cfg_v6
.query_response_interval
);
172 struct in6_addr addrs
[QUERIER_MAX_SOURCE
];
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),
179 struct group_source
*s
;
182 list_for_each_entry(s
, sources
, head
) {
183 if (cnt
>= QUERIER_MAX_SOURCE
)
184 break; // TODO: log source overflow
186 query
.addrs
[cnt
++] = s
->addr
;
189 query
.q
.nsrc
= htons(cnt
);
191 struct sockaddr_in6 dest
= {AF_INET6
, 0, 0, IPV6_ALL_NODES_INIT
, q
->ifindex
};
194 query
.q
.mld
.mld_addr
= dest
.sin6_addr
= *group
;
196 return mrib_send_mld(&q
->mrib
, &query
.q
.mld
,
197 sizeof(query
.q
) + cnt
* sizeof(query
.addrs
[0]), &dest
);