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.
21 #include <sys/socket.h>
22 #include <sys/ioctl.h>
23 #include <netinet/ip.h>
24 #include <netinet/in.h>
31 // Test if multicast-group is valid and relevant
32 static inline bool igmp_is_valid_group(in_addr_t group
)
34 return IN_MULTICAST(be32_to_cpu(group
));
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
)
40 struct igmpv3_grec
*r
= (struct igmpv3_grec
*)data
;
45 size_t nsrc
= ntohs(r
->grec_nsrcs
);
46 size_t read
= sizeof(*r
) + nsrc
* sizeof(struct in_addr
) + r
->grec_auxwords
* 4;
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
);
56 for (size_t i
= 0; i
< nsrc
; ++i
)
57 querier_map(&sources
[i
], r
->grec_src
[i
]);
59 groups_update_state(groups
, &addr
, sources
, nsrc
, r
->grec_type
);
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
)
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
;
74 querier_map(&group
, igmp
->group
);
75 inet_ntop(AF_INET
, &from
->sin_addr
, addrbuf
, sizeof(addrbuf
));
77 if (igmp
->type
== IGMP_HOST_MEMBERSHIP_QUERY
) {
78 struct igmpv3_query
*query
= (struct igmpv3_query
*)igmp
;
80 if (len
!= sizeof(*igmp
) && ((size_t)len
< sizeof(*query
) ||
81 (size_t)len
< sizeof(*query
) + ntohs(query
->nsrcs
) * sizeof(struct in_addr
)))
84 if (query
->group
&& !igmp_is_valid_group(query
->group
))
87 // Setup query target address
89 if (mrib_igmp_source(mrib
, &addr
))
92 bool suppress
= false;
95 omgp_time_t mrd
= 10000;
96 omgp_time_t query_interval
= 125000;
99 mrd
= 100 * ((len
== sizeof(*igmp
)) ? igmp
->code
: querier_qqi(igmp
->code
));
101 if ((size_t)len
> sizeof(*igmp
)) {
103 robustness
= query
->qrv
;
106 query_interval
= querier_qqi(query
->qqic
) * 1000;
108 suppress
= query
->suppress
;
109 nsrc
= ntohs(query
->nsrcs
);
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
]);
117 groups_update_timers(&q
->groups
, &group
, sources
, nsrc
);
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
);
124 // TODO: we ignore IGMPv1/v2 queriers for now, since a lot of them are dumb switches
126 if (election
< 0 && !query
->group
&& len
> sizeof(*igmp
)) {
127 groups_update_config(&q
->groups
, false, mrd
, query_interval
, robustness
);
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
);
133 } else if (igmp
->type
== IGMPV3_HOST_MEMBERSHIP_REPORT
) {
134 struct igmpv3_report
*report
= (struct igmpv3_report
*)igmp
;
136 if ((size_t)len
<= sizeof(*report
))
139 uint8_t *ibuf
= (uint8_t*)igmp
;
140 size_t count
= ntohs(report
->ngrec
);
141 size_t offset
= sizeof(*report
);
143 while (count
> 0 && offset
< len
) {
144 ssize_t read
= igmp_handle_record(&q
->groups
, &ibuf
[offset
], len
- offset
);
151 } else if (igmp
->type
== IGMPV2_HOST_MEMBERSHIP_REPORT
||
152 igmp
->type
== IGMP_HOST_LEAVE_MESSAGE
||
153 igmp
->type
== IGMP_HOST_MEMBERSHIP_REPORT
) {
155 if (len
!= sizeof(*igmp
) || !igmp_is_valid_group(igmp
->group
))
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
);
163 uloop_timeout_set(&q
->timeout
, 0);
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
,
172 uint8_t qqic
= querier_qqic(((group
) ? q
->groups
.cfg_v4
.last_listener_query_interval
:
173 q
->groups
.cfg_v4
.query_response_interval
) / 100);
175 struct igmpv3_query q
;
176 struct in_addr srcs
[QUERIER_MAX_SOURCE
];
178 .type
= IGMP_HOST_MEMBERSHIP_QUERY
,
180 .qrv
= q
->groups
.cfg_v4
.robustness
,
181 .suppress
= suppress
,
182 .qqic
= querier_qqic(q
->groups
.cfg_v4
.query_interval
/ 1000),
185 struct group_source
*s
;
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
);
195 query
.q
.srcs
[cnt
] = querier_unmap(&s
->addr
);
198 query
.q
.nsrcs
= htons(cnt
);
200 struct sockaddr_in dest
= { .sin_family
= AF_INET
, .sin_addr
= {htonl(0xe0000001U
)}};
202 query
.q
.group
= querier_unmap(group
);
203 dest
.sin_addr
.s_addr
= query
.q
.group
;
206 return mrib_send_igmp(&q
->mrib
, &query
.q
,
207 sizeof(query
.q
) + cnt
* sizeof(query
.srcs
[0]), &dest
);