Coverage Report

Created: 2025-10-08 06:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/frr/pimd/pim_igmpv2.c
Line
Count
Source
1
// SPDX-License-Identifier: GPL-2.0-or-later
2
/*
3
 * PIM for Quagga
4
 * Copyright (C) 2016 Cumulus Networks, Inc.
5
 * Daniel Walton
6
 */
7
8
#include "zebra.h"
9
10
#include "pimd.h"
11
#include "pim_instance.h"
12
#include "pim_igmp.h"
13
#include "pim_igmpv2.h"
14
#include "pim_igmpv3.h"
15
#include "pim_ssm.h"
16
#include "pim_str.h"
17
#include "pim_time.h"
18
#include "pim_util.h"
19
20
21
static void on_trace(const char *label, struct interface *ifp,
22
         struct in_addr from)
23
0
{
24
0
  if (PIM_DEBUG_GM_TRACE) {
25
0
    char from_str[INET_ADDRSTRLEN];
26
0
    pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
27
0
    zlog_debug("%s: from %s on %s", label, from_str, ifp->name);
28
0
  }
29
0
}
30
31
void igmp_v2_send_query(struct gm_group *group, int fd, const char *ifname,
32
      char *query_buf, struct in_addr dst_addr,
33
      struct in_addr group_addr,
34
      int query_max_response_time_dsec)
35
0
{
36
0
  ssize_t msg_size = 8;
37
0
  uint8_t max_resp_code;
38
0
  ssize_t sent;
39
0
  struct sockaddr_in to;
40
0
  socklen_t tolen;
41
0
  uint16_t checksum;
42
43
  /* max_resp_code must be non-zero else this will look like an IGMP v1
44
   * query */
45
  /* RFC 2236: 2.2. , v2's is equal to it */
46
0
  max_resp_code = query_max_response_time_dsec;
47
0
  assert(max_resp_code > 0);
48
49
0
  query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY;
50
0
  query_buf[1] = max_resp_code;
51
0
  *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) =
52
0
    0; /* for computing checksum */
53
0
  memcpy(query_buf + 4, &group_addr, sizeof(struct in_addr));
54
55
0
  checksum = in_cksum(query_buf, msg_size);
56
0
  *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum;
57
58
0
  if (PIM_DEBUG_GM_PACKETS) {
59
0
    char dst_str[INET_ADDRSTRLEN];
60
0
    char group_str[INET_ADDRSTRLEN];
61
0
    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
62
0
    pim_inet4_dump("<group?>", group_addr, group_str,
63
0
             sizeof(group_str));
64
0
    zlog_debug("Send IGMPv2 QUERY to %s on %s for group %s",
65
0
         dst_str, ifname, group_str);
66
0
  }
67
68
0
  memset(&to, 0, sizeof(to));
69
0
  to.sin_family = AF_INET;
70
0
  to.sin_addr = dst_addr;
71
0
  tolen = sizeof(to);
72
73
0
  sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT,
74
0
          (struct sockaddr *)&to, tolen);
75
0
  if (sent != (ssize_t)msg_size) {
76
0
    char dst_str[INET_ADDRSTRLEN];
77
0
    char group_str[INET_ADDRSTRLEN];
78
0
    pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
79
0
    pim_inet4_dump("<group?>", group_addr, group_str,
80
0
             sizeof(group_str));
81
0
    if (sent < 0) {
82
0
      zlog_warn(
83
0
        "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s",
84
0
        dst_str, ifname, group_str, msg_size, errno,
85
0
        safe_strerror(errno));
86
0
    } else {
87
0
      zlog_warn(
88
0
        "Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: sent=%zd",
89
0
        dst_str, ifname, group_str, msg_size, sent);
90
0
    }
91
0
    return;
92
0
  }
93
0
}
94
95
int igmp_v2_recv_report(struct gm_sock *igmp, struct in_addr from,
96
      const char *from_str, char *igmp_msg, int igmp_msg_len)
97
0
{
98
0
  struct interface *ifp = igmp->interface;
99
0
  struct in_addr group_addr;
100
0
  struct pim_interface *pim_ifp;
101
0
  char group_str[INET_ADDRSTRLEN];
102
103
0
  on_trace(__func__, igmp->interface, from);
104
105
0
  pim_ifp = ifp->info;
106
107
0
  if (igmp->mtrace_only)
108
0
    return 0;
109
110
0
  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
111
0
    if (PIM_DEBUG_GM_PACKETS)
112
0
      zlog_debug(
113
0
        "Recv IGMPv2 REPORT from %s on %s: size=%d other than correct=%d",
114
0
        from_str, ifp->name, igmp_msg_len,
115
0
        IGMP_V12_MSG_SIZE);
116
0
  }
117
118
0
  if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
119
0
    zlog_warn(
120
0
      "Recv IGMPv2 REPORT from %s on %s: size=%d with invalid checksum",
121
0
      from_str, ifp->name, igmp_msg_len);
122
0
    return -1;
123
0
  }
124
125
  /* Collecting IGMP Rx stats */
126
0
  igmp->igmp_stats.report_v2++;
127
128
0
  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
129
130
0
  if (PIM_DEBUG_GM_PACKETS) {
131
0
    pim_inet4_dump("<dst?>", group_addr, group_str,
132
0
             sizeof(group_str));
133
0
    zlog_debug("Recv IGMPv2 REPORT from %s on %s for %s", from_str,
134
0
         ifp->name, group_str);
135
0
  }
136
137
  /*
138
   * RFC 4604
139
   * section 2.2.1
140
   * EXCLUDE mode does not apply to SSM addresses, and an SSM-aware router
141
   * will ignore MODE_IS_EXCLUDE and CHANGE_TO_EXCLUDE_MODE requests in
142
   * the SSM range.
143
   */
144
0
  if (pim_is_grp_ssm(pim_ifp->pim, group_addr)) {
145
0
    if (PIM_DEBUG_GM_PACKETS) {
146
0
      zlog_debug(
147
0
        "Ignoring IGMPv2 group record %pI4 from %s on %s exclude mode in SSM range",
148
0
        &group_addr.s_addr, from_str, ifp->name);
149
0
    }
150
0
    return -1;
151
0
  }
152
153
154
  /*
155
   * RFC 3376
156
   * 7.3.2. In the Presence of Older Version Group Members
157
   *
158
   * When Group Compatibility Mode is IGMPv2, a router internally
159
   * translates the following IGMPv2 messages for that group to their
160
   * IGMPv3 equivalents:
161
   *
162
   * IGMPv2 Message                IGMPv3 Equivalent
163
   * --------------                -----------------
164
   * Report                        IS_EX( {} )
165
   * Leave                         TO_IN( {} )
166
   */
167
0
  igmpv3_report_isex(igmp, from, group_addr, 0, NULL, 1);
168
169
0
  return 0;
170
0
}
171
172
int igmp_v2_recv_leave(struct gm_sock *igmp, struct ip *ip_hdr,
173
           const char *from_str, char *igmp_msg, int igmp_msg_len)
174
0
{
175
0
  struct interface *ifp = igmp->interface;
176
0
  struct in_addr group_addr;
177
0
  char group_str[INET_ADDRSTRLEN];
178
0
  struct in_addr from = ip_hdr->ip_src;
179
180
0
  on_trace(__func__, igmp->interface, from);
181
182
0
  if (igmp->mtrace_only)
183
0
    return 0;
184
185
0
  if (igmp_msg_len != IGMP_V12_MSG_SIZE) {
186
0
    if (PIM_DEBUG_GM_PACKETS)
187
0
      zlog_debug(
188
0
        "Recv IGMPv2 LEAVE from %s on %s: size=%d other than correct=%d",
189
0
        from_str, ifp->name, igmp_msg_len,
190
0
        IGMP_V12_MSG_SIZE);
191
0
  }
192
193
0
  if (igmp_validate_checksum(igmp_msg, igmp_msg_len) == -1) {
194
0
    zlog_warn(
195
0
      "Recv IGMPv2 LEAVE from %s on %s with invalid checksum",
196
0
      from_str, ifp->name);
197
0
    return -1;
198
0
  }
199
200
201
0
  memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr));
202
203
0
  if (PIM_DEBUG_GM_PACKETS) {
204
0
    pim_inet4_dump("<dst?>", group_addr, group_str,
205
0
             sizeof(group_str));
206
0
    zlog_debug("Recv IGMPv2 LEAVE from %s on %s for %s", from_str,
207
0
         ifp->name, group_str);
208
0
  }
209
  /*
210
   * As per RFC 2236, section 9:
211
   Message Type                  Destination Group
212
   ------------                  -----------------
213
   General Query                 ALL-SYSTEMS (224.0.0.1)
214
   Group-Specific Query          The group being queried
215
   Membership Report             The group being reported
216
   Leave Message                 ALL-ROUTERS (224.0.0.2)
217
218
   Note: in older (i.e., non-standard and now obsolete) versions of
219
   IGMPv2, hosts send Leave Messages to the group being left.  A
220
   router SHOULD accept Leave Messages addressed to the group being
221
   left in the interests of backwards compatibility with such hosts.
222
   In all cases, however, hosts MUST send to the ALL-ROUTERS address
223
   to be compliant with this specification.
224
  */
225
0
  if ((ntohl(ip_hdr->ip_dst.s_addr) != INADDR_ALLRTRS_GROUP)
226
0
      && (ip_hdr->ip_dst.s_addr != group_addr.s_addr)) {
227
0
    if (PIM_DEBUG_GM_EVENTS)
228
0
      zlog_debug(
229
0
        "IGMPv2 Leave message is ignored since received on address other than ALL-ROUTERS or Group-address");
230
0
    return -1;
231
0
  }
232
233
  /* Collecting IGMP Rx stats */
234
0
  igmp->igmp_stats.leave_v2++;
235
236
  /*
237
   * RFC 3376
238
   * 7.3.2. In the Presence of Older Version Group Members
239
   *
240
   * When Group Compatibility Mode is IGMPv2, a router internally
241
   * translates the following IGMPv2 messages for that group to their
242
   * IGMPv3 equivalents:
243
   *
244
   * IGMPv2 Message                IGMPv3 Equivalent
245
   * --------------                -----------------
246
   * Report                        IS_EX( {} )
247
   * Leave                         TO_IN( {} )
248
   */
249
0
  igmpv3_report_toin(igmp, from, group_addr, 0, NULL);
250
251
0
  return 0;
252
0
}