Skip to content

Commit 81dacb1

Browse files
committed
iface: RFC3376 §8.6/§8.7 startup query burst
When becoming IGMP querier (on interface startup or after the prior querier times out), immediately send the first General Query, then schedule (Robustness Variable − 1) additional queries at the Startup Query Interval (Query Interval / 4, default ≈ 31 s). This lets hosts learn about the querier quickly at startup rather than waiting up to one full Query Interval for the second query. The startup timer is cancelled when the interface leaves service or when a better querier is discovered; the query callback self-cancels if the querier flag is no longer set when it fires. Fixes #5 Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
1 parent 494f829 commit 81dacb1

4 files changed

Lines changed: 77 additions & 10 deletions

File tree

src/iface.c

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ static void stop_iface (struct ifi *ifi);
2525
static void send_query (struct ifi *v, uint32_t dst, int code, uint32_t group);
2626
static void query_groups (int timeout, void *arg);
2727

28+
static void startup_query_cb (int timeout, void *arg);
29+
static void startup_query_timer(struct ifi *ifi);
30+
2831
static void router_timeout_cb (int timeout, void *arg);
2932

3033
static void delete_group_cb (int timeout, void *arg);
@@ -87,6 +90,7 @@ void iface_zero(struct ifi *ifi)
8790
ifi->ifi_querier = NULL;
8891
ifi->ifi_query_interval = igmp_query_interval;
8992
ifi->ifi_timerid = 0;
93+
ifi->ifi_startup_timerid = 0;
9094
ifi->ifi_igmpv1_warn = 0;
9195
}
9296

@@ -173,6 +177,7 @@ void iface_check_election(struct ifi *ifi)
173177
dbg("Assuming %squerier duties on interface %s",
174178
is_igmp_proxy(ifi) ? "proxy " : "", ifi->ifi_name);
175179
send_query(ifi, allhosts_group, igmp_response_interval, 0);
180+
startup_query_timer(ifi); /* RFC3376 §8.6, §8.7 startup query burst */
176181
}
177182

178183
/*
@@ -361,8 +366,12 @@ static void stop_iface(struct ifi *ifi)
361366
struct listaddr *a, *tmp;
362367

363368
/*
364-
* Stop query timer
369+
* Stop query timers (periodic and startup burst)
365370
*/
371+
if (ifi->ifi_startup_timerid > 0) {
372+
pev_timer_del(ifi->ifi_startup_timerid);
373+
ifi->ifi_startup_timerid = 0;
374+
}
366375
if (ifi->ifi_timerid > 0)
367376
pev_timer_del(ifi->ifi_timerid);
368377
ifi->ifi_timerid = 0;
@@ -899,6 +908,61 @@ void accept_membership_report(int ifindex, int vid, uint32_t src, uint32_t dst,
899908
}
900909
}
901910

911+
/*
912+
* RFC3376 §8.6/§8.7: Send remaining startup queries at Startup Query Interval.
913+
* Called once per fire; re-arms itself until the count reaches zero.
914+
*/
915+
static void startup_query_cb(int timeout, void *arg)
916+
{
917+
cbk_t *cbk = (cbk_t *)arg;
918+
struct ifi *ifi;
919+
920+
ifi = config_find_iface(cbk->ifindex, cbk->vid);
921+
if (!ifi || !(ifi->ifi_flags & IFIF_IGMP_QUERIER))
922+
goto end;
923+
924+
send_query(ifi, allhosts_group, igmp_response_interval, 0);
925+
if (--cbk->num > 0) {
926+
pev_timer_set(ifi->ifi_startup_timerid, cbk->delay * 1000000);
927+
return;
928+
}
929+
930+
end:
931+
if (ifi)
932+
ifi->ifi_startup_timerid = pev_timer_del(ifi->ifi_startup_timerid);
933+
}
934+
935+
/*
936+
* Schedule the startup query burst: igmp_robustness - 1 additional queries
937+
* (the first was already sent) at Startup Query Interval (Query Interval / 4).
938+
*/
939+
static void startup_query_timer(struct ifi *ifi)
940+
{
941+
uint32_t qi = ifi->ifi_query_interval ? ifi->ifi_query_interval : igmp_query_interval;
942+
uint32_t sqi = qi / 4 > 0 ? qi / 4 : 1;
943+
cbk_t *cbk;
944+
945+
if (igmp_robustness <= 1)
946+
return;
947+
948+
cbk = calloc(1, sizeof(cbk_t));
949+
if (!cbk) {
950+
err("failed allocating startup query timer");
951+
exit(EX_OSERR);
952+
}
953+
954+
cbk->ifindex = ifi->ifi_index;
955+
cbk->vid = ifi->ifi_vlan;
956+
cbk->num = igmp_robustness - 1;
957+
cbk->delay = sqi;
958+
959+
if (ifi->ifi_startup_timerid > 0)
960+
pev_timer_del(ifi->ifi_startup_timerid);
961+
962+
ifi->ifi_startup_timerid = pev_timer_add(sqi * 1000000, 0, startup_query_cb, cbk);
963+
pev_timer_set_cb_del(ifi->ifi_startup_timerid, free);
964+
}
965+
902966
/*
903967
* When an active querier times out we assume the role here.
904968
*/
@@ -913,6 +977,7 @@ static void router_timeout_cb(int timeout, void *arg)
913977

914978
ifi->ifi_flags |= IFIF_IGMP_QUERIER;
915979
send_query(ifi, allhosts_group, igmp_response_interval, 0);
980+
startup_query_timer(ifi); /* RFC3376 §8.6, §8.7 startup query burst */
916981
}
917982

918983
/*

src/iface.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@ struct ifi {
1818
int ifi_vlan; /* Raw VLAN ID for send and accept */
1919
char ifi_name[IFNAMSIZ]; /* interface name */
2020
int ifi_index; /* Primarily for Linux systems */
21-
uint32_t ifi_inaddr; /* Current address of this interface */
21+
uint32_t ifi_inaddr; /* Current address of this interface */
2222
uint32_t ifi_query_interval; /* IGMP query interval */
2323
struct listaddr *ifi_querier; /* IGMP querier (one or none) */
2424
int ifi_timerid; /* IGMP query timer */
25+
int ifi_startup_timerid;/* startup query burst timer */
2526
int ifi_igmpv1_warn; /* To rate-limit IGMPv1 warnings */
2627
uint8_t ifi_hwaddr[6]; /* MAC address of this interface */
2728
};

test/sleepy.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# 2nd interface has a later arrival of an IP address, we expect two Q,
1212
# but might get three due to global query timer currently.
1313
#
14-
# 3rd interfae is down at start, comes up with one Q for last interval
14+
# 3rd interface is down at start, comes up with at least one Q for last interval
1515
#
1616

1717
# shellcheck source=/dev/null
@@ -87,8 +87,8 @@ cat "/tmp/$NM/result"
8787

8888
echo " => $lines1 IGMP Query on eth0, expected 3"
8989
echo " => $lines2 IGMP Query on eth1, expected 2"
90-
echo " => $lines3 IGMP Query on eth2, expected 1"
90+
echo " => $lines3 IGMP Query on eth2, expected >=1"
9191
# shellcheck disable=SC2086 disable=SC2166
92-
[ $lines1 -ge 3 -a $lines2 -ge 2 -a $lines3 -eq 1 ] || FAIL
92+
[ $lines1 -ge 3 -a $lines2 -ge 2 -a $lines3 -ge 1 ] || FAIL
9393

9494
OK

test/two.sh

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#!/bin/sh
2-
# Verifies proper parsing of .conf file, and operation wrt two queries
3-
# in the given time frame. TODO: check delta between queries.
2+
# Verifies proper parsing of .conf file, and operation wrt queries in the
3+
# given time frame: initial query + startup burst (RFC3376 §8.6/§8.7) + one
4+
# periodic. TODO: check delta between queries.
45

56
# shellcheck source=/dev/null
67
. "$(dirname "$0")/lib.sh"
@@ -50,9 +51,9 @@ lines1=$(tshark -n -r "/tmp/$NM/eth0.pcap" 2>/dev/null | grep "IGMPv3 50 Members
5051
lines2=$(tshark -n -r "/tmp/$NM/eth1.pcap" 2>/dev/null | grep "IGMPv3 50 Membership Query" | tee -a "/tmp/$NM/result" | wc -l)
5152
cat "/tmp/$NM/result"
5253

53-
echo " => $lines1 IGMP Query on eth0, expected 2"
54-
echo " => $lines2 IGMP Query on eth1, expected 2"
54+
echo " => $lines1 IGMP Query on eth0, expected 3"
55+
echo " => $lines2 IGMP Query on eth1, expected 3"
5556
# shellcheck disable=SC2086 disable=SC2166
56-
[ $lines1 -eq 2 -a $lines2 -eq 2 ] || FAIL
57+
[ $lines1 -ge 3 -a $lines2 -ge 3 ] || FAIL
5758

5859
OK

0 commit comments

Comments
 (0)