mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2025-01-03 07:31:54 +00:00
Merge commit 'aearsis/pim^' into pim
This commit is contained in:
commit
5c5a1b862c
@ -261,7 +261,7 @@ if test "$enable_mpls_kernel" != no ; then
|
||||
fi
|
||||
fi
|
||||
|
||||
all_protocols="$proto_bfd babel bgp ospf pipe radv rip $proto_rpki static"
|
||||
all_protocols="$proto_bfd babel bgp igmp ospf pipe radv rip $proto_rpki static"
|
||||
|
||||
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
|
||||
|
||||
|
1
lib/ip.h
1
lib/ip.h
@ -20,6 +20,7 @@
|
||||
#define IP4_OSPF_ALL_ROUTERS ipa_build4(224, 0, 0, 5)
|
||||
#define IP4_OSPF_DES_ROUTERS ipa_build4(224, 0, 0, 6)
|
||||
#define IP4_RIP_ROUTERS ipa_build4(224, 0, 0, 9)
|
||||
#define IP4_IGMP_ROUTERS ipa_build4(224, 0, 0, 22)
|
||||
|
||||
#define IP6_ALL_NODES ipa_build6(0xFF020000, 0, 0, 1)
|
||||
#define IP6_ALL_ROUTERS ipa_build6(0xFF020000, 0, 0, 2)
|
||||
|
@ -172,6 +172,14 @@ tm_start(timer *t, btime after)
|
||||
tm_set(t, current_time() + MAX(after, 0));
|
||||
}
|
||||
|
||||
void
|
||||
tm_shift(timer *t, btime delta)
|
||||
{
|
||||
btime now = current_time();
|
||||
if (t->expires && (t->expires > now))
|
||||
tm_set(t, MAX(now, t->expires + delta));
|
||||
}
|
||||
|
||||
void
|
||||
tm_stop(timer *t)
|
||||
{
|
||||
|
@ -53,6 +53,7 @@ extern btime boot_time;
|
||||
timer *tm_new(pool *p);
|
||||
void tm_set(timer *t, btime when);
|
||||
void tm_start(timer *t, btime after);
|
||||
void tm_shift(timer *t, btime delta);
|
||||
void tm_stop(timer *t);
|
||||
|
||||
static inline int
|
||||
|
@ -1310,6 +1310,9 @@ protos_build(void)
|
||||
#ifdef CONFIG_RPKI
|
||||
proto_build(&proto_rpki);
|
||||
#endif
|
||||
#ifdef CONFIG_IGMP
|
||||
proto_build(&proto_igmp);
|
||||
#endif
|
||||
|
||||
proto_pool = rp_new(&root_pool, "Protocols");
|
||||
proto_shutdown_timer = tm_new(proto_pool);
|
||||
|
@ -79,8 +79,8 @@ void protos_dump_all(void);
|
||||
*/
|
||||
|
||||
extern struct protocol
|
||||
proto_device, proto_radv, proto_rip, proto_static,
|
||||
proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel, proto_rpki;
|
||||
proto_device, proto_radv, proto_rip, proto_static, proto_ospf, proto_pipe,
|
||||
proto_bgp, proto_bfd, proto_babel, proto_rpki, proto_igmp;
|
||||
|
||||
/*
|
||||
* Routing Protocol Instance
|
||||
|
@ -422,7 +422,7 @@ typedef struct rta {
|
||||
#define RTS_PIPE 12 /* Inter-table wormhole */
|
||||
#define RTS_BABEL 13 /* Babel route */
|
||||
#define RTS_RPKI 14 /* Route Origin Authorization */
|
||||
|
||||
#define RTS_IGMP 15 /* IGMP multicast request */
|
||||
|
||||
#define RTC_UNICAST 0
|
||||
#define RTC_BROADCAST 1
|
||||
@ -688,6 +688,7 @@ extern struct protocol *attr_class_to_protocol[EAP_MAX];
|
||||
#define DEF_PREF_RIP 120 /* RIP */
|
||||
#define DEF_PREF_BGP 100 /* BGP */
|
||||
#define DEF_PREF_RPKI 100 /* RPKI */
|
||||
#define DEF_PREF_IGMP 100 /* IGMP */
|
||||
#define DEF_PREF_INHERITED 10 /* Routes inherited from other routing daemons */
|
||||
|
||||
/*
|
||||
|
@ -1204,8 +1204,8 @@ rta_dump(rta *a)
|
||||
{
|
||||
static char *rts[] = { "RTS_DUMMY", "RTS_STATIC", "RTS_INHERIT", "RTS_DEVICE",
|
||||
"RTS_STAT_DEV", "RTS_REDIR", "RTS_RIP",
|
||||
"RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1",
|
||||
"RTS_OSPF_EXT2", "RTS_BGP", "RTS_PIPE", "RTS_BABEL" };
|
||||
"RTS_OSPF", "RTS_OSPF_IA", "RTS_OSPF_EXT1", "RTS_OSPF_EXT2",
|
||||
"RTS_BGP", "RTS_PIPE", "RTS_BABEL", "RTS_IGMP" };
|
||||
static char *rtd[] = { "", " DEV", " HOLE", " UNREACH", " PROHIBIT" };
|
||||
|
||||
debug("p=%s uc=%d %s %s%s h=%04x",
|
||||
@ -1257,7 +1257,7 @@ void
|
||||
rta_show(struct cli *c, rta *a, ea_list *eal)
|
||||
{
|
||||
static char *src_names[] = { "dummy", "static", "inherit", "device", "static-device", "redirect",
|
||||
"RIP", "OSPF", "OSPF-IA", "OSPF-E1", "OSPF-E2", "BGP", "pipe" };
|
||||
"RIP", "OSPF", "OSPF-IA", "OSPF-E1", "OSPF-E2", "BGP", "pipe", "Babel", "RPKI", "IGMP" };
|
||||
int i;
|
||||
|
||||
cli_printf(c, -1008, "\tType: %s %s", src_names[a->source], ip_scope_text(a->scope));
|
||||
|
2
proto/igmp/Doc
Normal file
2
proto/igmp/Doc
Normal file
@ -0,0 +1,2 @@
|
||||
S igmp.c
|
||||
S packets.c
|
6
proto/igmp/Makefile
Normal file
6
proto/igmp/Makefile
Normal file
@ -0,0 +1,6 @@
|
||||
src := igmp.c packets.c
|
||||
obj := $(src-o-files)
|
||||
$(all-daemon)
|
||||
$(cf-local)
|
||||
|
||||
tests_objs := $(tests_objs) $(src-o-files)
|
86
proto/igmp/config.Y
Normal file
86
proto/igmp/config.Y
Normal file
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* BIRD -- Internet Group Management Protocol (IGMP)
|
||||
*
|
||||
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||
* (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
|
||||
* (c) 2018 CZ.NIC z.s.p.o.
|
||||
*
|
||||
* Can be freely distributed and used under the terms of the GNU GPL.
|
||||
*/
|
||||
|
||||
CF_HDR
|
||||
|
||||
#include "proto/igmp/igmp.h"
|
||||
|
||||
CF_DEFINES
|
||||
|
||||
#define IGMP_CFG ((struct igmp_config *) this_proto)
|
||||
#define IGMP_IFACE ((struct igmp_iface_config *) this_ipatt)
|
||||
|
||||
CF_DECLS
|
||||
|
||||
CF_KEYWORDS(IGMP, ROBUSTNESS, STARTUP, QUERY, COUNT, INTERVAL, LAST, MEMBER, RESPONSE)
|
||||
|
||||
CF_GRAMMAR
|
||||
|
||||
CF_ADDTO(proto, igmp_proto)
|
||||
|
||||
igmp_proto_start: proto_start IGMP
|
||||
{
|
||||
this_proto = proto_config_new(&proto_igmp, $1);
|
||||
init_list(&IGMP_CFG->iface_list);
|
||||
};
|
||||
|
||||
igmp_proto_item:
|
||||
proto_item
|
||||
| proto_channel
|
||||
| INTERFACE igmp_iface
|
||||
;
|
||||
|
||||
igmp_proto_opts:
|
||||
/* empty */
|
||||
| igmp_proto_opts igmp_proto_item ';'
|
||||
;
|
||||
|
||||
igmp_proto:
|
||||
igmp_proto_start proto_name '{' igmp_proto_opts '}';
|
||||
|
||||
|
||||
igmp_iface_start:
|
||||
{
|
||||
this_ipatt = cfg_allocz(sizeof(struct igmp_iface_config));
|
||||
add_tail(&IGMP_CFG->iface_list, NODE this_ipatt);
|
||||
init_list(&IGMP_IFACE->i.ipn_list);
|
||||
|
||||
IGMP_IFACE->robustness = IGMP_DEFAULT_ROBUSTNESS;
|
||||
IGMP_IFACE->query_int = IGMP_DEFAULT_QUERY_INT;
|
||||
IGMP_IFACE->query_response_int = IGMP_DEFAULT_RESPONSE_INT;
|
||||
IGMP_IFACE->last_member_query_int = IGMP_DEFAULT_LAST_MEMBER_INT;
|
||||
};
|
||||
|
||||
igmp_iface_item:
|
||||
ROBUSTNESS expr { IGMP_IFACE->robustness = $2; }
|
||||
| QUERY INTERVAL expr_us { IGMP_IFACE->query_int = $3; }
|
||||
| STARTUP QUERY COUNT expr { IGMP_IFACE->startup_query_cnt = $4; }
|
||||
| STARTUP QUERY INTERVAL expr_us { IGMP_IFACE->startup_query_int = $4; }
|
||||
| QUERY RESPONSE INTERVAL expr_us { IGMP_IFACE->query_response_int = $4; }
|
||||
| LAST MEMBER QUERY COUNT expr { IGMP_IFACE->last_member_query_cnt = $5; }
|
||||
| LAST MEMBER QUERY INTERVAL expr_us { IGMP_IFACE->last_member_query_int = $5; }
|
||||
;
|
||||
|
||||
igmp_iface_opts:
|
||||
/* empty */
|
||||
| igmp_iface_opts igmp_iface_item ';'
|
||||
;
|
||||
|
||||
igmp_iface_opt_list:
|
||||
/* empty */
|
||||
| '{' igmp_iface_opts '}'
|
||||
;
|
||||
|
||||
igmp_iface:
|
||||
igmp_iface_start iface_patt_list_nopx igmp_iface_opt_list { igmp_finish_iface_config(IGMP_IFACE); }
|
||||
|
||||
CF_CODE
|
||||
|
||||
CF_END
|
624
proto/igmp/igmp.c
Normal file
624
proto/igmp/igmp.c
Normal file
@ -0,0 +1,624 @@
|
||||
/*
|
||||
* BIRD -- Internet Group Management Protocol (IGMP)
|
||||
*
|
||||
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||
* (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
|
||||
* (c) 2018 CZ.NIC z.s.p.o.
|
||||
*
|
||||
* Can be freely distributed and used under the terms of the GNU GPL.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DOC: Internet Group Management Protocol, Version 2
|
||||
*
|
||||
* The Internet Group Management Protocol (IGMP) is used by IP hosts to report
|
||||
* their multicast group memberships to any immediately-neighboring multicast
|
||||
* routers. This memo describes only the use of IGMP between hosts and routers
|
||||
* to determine group membership. Routers that are members of multicast groups
|
||||
* are expected to behave as hosts as well as routers, and may even respond to
|
||||
* their own queries. IGMP may also be used between routers, but such use is not
|
||||
* specified here.
|
||||
*
|
||||
* Its implementation is split into three files, |packets.c| handling low-level
|
||||
* packet formats and |igmp.c| implementing BIRD interface and protocol logic.
|
||||
*
|
||||
* IGMP communicates with hosts, and publishes requests for groups and
|
||||
* interfaces to the BIRD mutlicast request table. It needs to hold state for
|
||||
* every group with local listeners.
|
||||
*/
|
||||
|
||||
#include "igmp.h"
|
||||
|
||||
|
||||
#define HASH_GRP_KEY(n) n->address
|
||||
#define HASH_GRP_NEXT(n) n->next
|
||||
#define HASH_GRP_EQ(a,b) ip4_equal(a,b)
|
||||
#define HASH_GRP_FN(k) ip4_hash(k)
|
||||
|
||||
static const char *igmp_join_state_names[] = {
|
||||
[IGMP_JS_NO_MEMBERS] = "NoMembers",
|
||||
[IGMP_JS_MEMBERS] = "Members",
|
||||
[IGMP_JS_V1_MEMBERS] = "MembersV1",
|
||||
[IGMP_JS_CHECKING] = "Checking",
|
||||
};
|
||||
|
||||
static const char *igmp_query_state_names[] = {
|
||||
[IGMP_QS_INIT] = "Init",
|
||||
[IGMP_QS_QUERIER] = "Querier",
|
||||
[IGMP_QS_NONQUERIER] = "Listener",
|
||||
};
|
||||
|
||||
static struct igmp_group *igmp_find_group(struct igmp_iface *ifa, ip4_addr addr);
|
||||
static struct igmp_group *igmp_get_group(struct igmp_iface *ifa, ip4_addr addr);
|
||||
static void igmp_remove_group(struct igmp_group *grp);
|
||||
static void igmp_announce_group(struct igmp_group *grp, int up);
|
||||
static void igmp_group_set_state(struct igmp_group *grp, uint state);
|
||||
static void igmp_iface_set_state(struct igmp_iface *ifa, uint state);
|
||||
|
||||
|
||||
/*
|
||||
* IGMP protocol logic
|
||||
*/
|
||||
|
||||
static void
|
||||
igmp_query_timeout(struct timer *tm)
|
||||
{
|
||||
struct igmp_iface *ifa = tm->data;
|
||||
|
||||
ASSERT(ifa->query_state == IGMP_QS_QUERIER);
|
||||
|
||||
igmp_send_query(ifa, IP4_NONE, ifa->cf->query_response_int);
|
||||
|
||||
if (ifa->startup_query_cnt > 0)
|
||||
ifa->startup_query_cnt--;
|
||||
|
||||
tm_start(ifa->query_timer, ifa->startup_query_cnt
|
||||
? ifa->cf->startup_query_int
|
||||
: ifa->cf->query_int);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_other_present_timeout(struct timer *tm)
|
||||
{
|
||||
struct igmp_iface *ifa = tm->data;
|
||||
|
||||
ASSERT(ifa->query_state == IGMP_QS_NONQUERIER);
|
||||
|
||||
igmp_iface_set_state(ifa, IGMP_QS_QUERIER);
|
||||
tm_start(ifa->query_timer, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_join_timeout(struct timer *tm)
|
||||
{
|
||||
struct igmp_group *grp = tm->data;
|
||||
|
||||
ASSERT(grp->join_state != IGMP_JS_NO_MEMBERS);
|
||||
|
||||
igmp_group_set_state(grp, IGMP_JS_NO_MEMBERS);
|
||||
igmp_announce_group(grp, 0);
|
||||
igmp_remove_group(grp);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_v1_host_timeout(struct timer *tm)
|
||||
{
|
||||
struct igmp_group *grp = tm->data;
|
||||
|
||||
ASSERT(grp->join_state == IGMP_JS_V1_MEMBERS);
|
||||
|
||||
igmp_group_set_state(grp, IGMP_JS_MEMBERS);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_rxmt_timeout(struct timer *tm)
|
||||
{
|
||||
struct igmp_group *grp = tm->data;
|
||||
struct igmp_iface *ifa = grp->ifa;
|
||||
|
||||
ASSERT(grp->join_state == IGMP_JS_CHECKING);
|
||||
|
||||
igmp_send_query(ifa, grp->address, ifa->cf->last_member_query_int);
|
||||
tm_start(grp->rxmt_timer, ifa->cf->last_member_query_int);
|
||||
}
|
||||
|
||||
void
|
||||
igmp_handle_query(struct igmp_iface *ifa, ip4_addr addr, ip4_addr from, btime resp_time)
|
||||
{
|
||||
/* Another router with lower IP shall be the Querier */
|
||||
if ((ifa->query_state == IGMP_QS_QUERIER) &&
|
||||
(ip4_compare(from, ipa_to_ip4(ifa->sk->saddr)) < 0))
|
||||
{
|
||||
igmp_iface_set_state(ifa, IGMP_QS_NONQUERIER);
|
||||
ifa->startup_query_cnt = 0;
|
||||
tm_stop(ifa->query_timer);
|
||||
tm_start(ifa->other_present, ifa->cf->other_querier_int);
|
||||
}
|
||||
|
||||
if ((ifa->query_state == IGMP_QS_NONQUERIER) && ip4_nonzero(addr))
|
||||
{
|
||||
struct igmp_group *grp = igmp_find_group(ifa, addr);
|
||||
|
||||
if (grp && (grp->join_state == IGMP_JS_MEMBERS))
|
||||
{
|
||||
igmp_group_set_state(grp, IGMP_JS_CHECKING);
|
||||
tm_start(grp->join_timer, resp_time * ifa->cf->last_member_query_cnt);
|
||||
tm_stop(grp->rxmt_timer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
igmp_handle_report(struct igmp_iface *ifa, ip4_addr addr, int version)
|
||||
{
|
||||
struct igmp_group *grp = igmp_get_group(ifa, addr);
|
||||
uint last_state = grp->join_state;
|
||||
|
||||
if (version == 1)
|
||||
{
|
||||
igmp_group_set_state(grp, IGMP_JS_V1_MEMBERS);
|
||||
tm_start(grp->v1_host_timer, ifa->cf->group_member_int + 100 MS);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (last_state != IGMP_JS_V1_MEMBERS)
|
||||
igmp_group_set_state(grp, IGMP_JS_MEMBERS);
|
||||
}
|
||||
|
||||
tm_start(grp->join_timer, ifa->cf->group_member_int);
|
||||
tm_stop(grp->rxmt_timer);
|
||||
|
||||
if (last_state == IGMP_JS_NO_MEMBERS)
|
||||
igmp_announce_group(grp, 1);
|
||||
}
|
||||
|
||||
void
|
||||
igmp_handle_leave(struct igmp_iface *ifa, ip4_addr addr)
|
||||
{
|
||||
if (ifa->query_state == IGMP_QS_QUERIER)
|
||||
{
|
||||
struct igmp_group *grp = igmp_find_group(ifa, addr);
|
||||
|
||||
if (grp && (grp->join_state == IGMP_JS_MEMBERS))
|
||||
{
|
||||
igmp_group_set_state(grp, IGMP_JS_CHECKING);
|
||||
tm_start(grp->join_timer, ifa->cf->last_member_query_int * ifa->cf->last_member_query_cnt);
|
||||
tm_start(grp->rxmt_timer, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IGMP groups
|
||||
*/
|
||||
|
||||
static struct igmp_group *
|
||||
igmp_find_group(struct igmp_iface *ifa, ip4_addr addr)
|
||||
{
|
||||
return HASH_FIND(ifa->groups, HASH_GRP, addr);
|
||||
}
|
||||
|
||||
static struct igmp_group *
|
||||
igmp_get_group(struct igmp_iface *ifa, ip4_addr addr)
|
||||
{
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
struct igmp_group *grp = igmp_find_group(ifa, addr);
|
||||
|
||||
if (grp)
|
||||
return grp;
|
||||
|
||||
grp = mb_allocz(p->p.pool, sizeof(struct igmp_group));
|
||||
grp->address = addr;
|
||||
grp->ifa = ifa;
|
||||
HASH_INSERT(ifa->groups, HASH_GRP, grp);
|
||||
|
||||
grp->join_timer = tm_new_init(p->p.pool, igmp_join_timeout, grp, 0, 0);
|
||||
grp->rxmt_timer = tm_new_init(p->p.pool, igmp_rxmt_timeout, grp, 0, 0);
|
||||
grp->v1_host_timer = tm_new_init(p->p.pool, igmp_v1_host_timeout, grp, 0, 0);
|
||||
|
||||
return grp;
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_remove_group(struct igmp_group *grp)
|
||||
{
|
||||
rfree(grp->join_timer);
|
||||
rfree(grp->v1_host_timer);
|
||||
rfree(grp->rxmt_timer);
|
||||
|
||||
HASH_REMOVE(grp->ifa->groups, HASH_GRP, grp);
|
||||
mb_free(grp);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_announce_group(struct igmp_group *grp, int up)
|
||||
{
|
||||
struct igmp_proto *p = grp->ifa->proto;
|
||||
net_addr_mreq4 addr = NET_ADDR_MREQ4(grp->address, grp->ifa->iface->index);
|
||||
|
||||
if (up)
|
||||
{
|
||||
rta a0 = {
|
||||
.src = p->p.main_source,
|
||||
.source = RTS_IGMP,
|
||||
.scope = SCOPE_UNIVERSE,
|
||||
.dest = RTD_NONE,
|
||||
.nh.iface = grp->ifa->iface,
|
||||
};
|
||||
rta *a = rta_lookup(&a0);
|
||||
rte *e = rte_get_temp(a);
|
||||
|
||||
e->pflags = 0;
|
||||
rte_update(&p->p, (net_addr *) &addr, e);
|
||||
}
|
||||
else
|
||||
{
|
||||
rte_update(&p->p, (net_addr *) &addr, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_group_set_state(struct igmp_group *grp, uint state)
|
||||
{
|
||||
struct igmp_proto *p = grp->ifa->proto;
|
||||
uint last_state = grp->join_state;
|
||||
|
||||
if (state == last_state)
|
||||
return;
|
||||
|
||||
TRACE(D_EVENTS, "Group %I4 on %s changed state from %s to %s",
|
||||
grp->address, grp->ifa->iface->name,
|
||||
igmp_join_state_names[last_state], igmp_join_state_names[state]);
|
||||
|
||||
grp->join_state = state;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IGMP interfaces
|
||||
*/
|
||||
|
||||
static struct igmp_iface *
|
||||
igmp_find_iface(struct igmp_proto *p, struct iface *what)
|
||||
{
|
||||
struct igmp_iface *ifa;
|
||||
|
||||
WALK_LIST(ifa, p->iface_list)
|
||||
if (ifa->iface == what)
|
||||
return ifa;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
igmp_iface_locked(struct object_lock *lock)
|
||||
{
|
||||
struct igmp_iface *ifa = lock->data;
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
|
||||
if (!igmp_open_socket(ifa))
|
||||
{
|
||||
log(L_ERR "%s: Cannot open socket for %s", p->p.name, ifa->iface->name);
|
||||
return;
|
||||
}
|
||||
|
||||
igmp_iface_set_state(ifa, IGMP_QS_QUERIER);
|
||||
ifa->startup_query_cnt = ifa->cf->startup_query_cnt;
|
||||
tm_start(ifa->query_timer, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_add_iface(struct igmp_proto *p, struct iface *iface, struct igmp_iface_config *ic)
|
||||
{
|
||||
struct igmp_iface *ifa;
|
||||
|
||||
TRACE(D_EVENTS, "Adding interface %s", iface->name);
|
||||
|
||||
ifa = mb_allocz(p->p.pool, sizeof(struct igmp_iface));
|
||||
add_tail(&p->iface_list, NODE ifa);
|
||||
ifa->proto = p;
|
||||
ifa->iface = iface;
|
||||
ifa->cf = ic;
|
||||
|
||||
ifa->query_timer = tm_new_init(p->p.pool, igmp_query_timeout, ifa, 0, 0);
|
||||
ifa->other_present = tm_new_init(p->p.pool, igmp_other_present_timeout, ifa, 0, 0);
|
||||
|
||||
HASH_INIT(ifa->groups, p->p.pool, 8);
|
||||
|
||||
ifa->mif = mif_get(p->mif_group, iface);
|
||||
if (!ifa->mif)
|
||||
{
|
||||
log(L_ERR "%s: Cannot enable multicast on %s, too many MIFs", p->p.name, ifa->iface->name);
|
||||
return;
|
||||
}
|
||||
|
||||
struct object_lock *lock = olock_new(p->p.pool);
|
||||
lock->type = OBJLOCK_IP;
|
||||
lock->port = IGMP_PROTO;
|
||||
lock->iface = iface;
|
||||
lock->data = ifa;
|
||||
lock->hook = igmp_iface_locked;
|
||||
ifa->lock = lock;
|
||||
|
||||
olock_acquire(lock);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_remove_iface(struct igmp_proto *p, struct igmp_iface *ifa)
|
||||
{
|
||||
TRACE(D_EVENTS, "Removing interface %s", ifa->iface->name);
|
||||
|
||||
HASH_WALK_DELSAFE(ifa->groups, next, grp)
|
||||
{
|
||||
igmp_announce_group(grp, 0);
|
||||
igmp_remove_group(grp);
|
||||
}
|
||||
HASH_WALK_END;
|
||||
|
||||
rem_node(NODE ifa);
|
||||
|
||||
/* This is not a resource */
|
||||
if (ifa->mif)
|
||||
mif_free(p->mif_group, ifa->mif);
|
||||
|
||||
rfree(ifa->sk);
|
||||
rfree(ifa->lock);
|
||||
rfree(ifa->query_timer);
|
||||
rfree(ifa->other_present);
|
||||
|
||||
mb_free(ifa);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_iface_set_state(struct igmp_iface *ifa, uint state)
|
||||
{
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
uint last_state = ifa->query_state;
|
||||
|
||||
if (state == last_state)
|
||||
return;
|
||||
|
||||
TRACE(D_EVENTS, "Interface %s changed state from %s to %s", ifa->iface->name,
|
||||
igmp_query_state_names[last_state], igmp_query_state_names[state]);
|
||||
|
||||
ifa->query_state = state;
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_iface_dump(struct igmp_iface *ifa)
|
||||
{
|
||||
debug("\tInterface %s: %s\n", ifa->iface->name, igmp_query_state_names[ifa->query_state]);
|
||||
|
||||
HASH_WALK(ifa->groups, next, grp)
|
||||
debug("\t\tGroup %I4: %s\n", grp->address, igmp_join_state_names[grp->join_state]);
|
||||
HASH_WALK_END;
|
||||
}
|
||||
|
||||
void
|
||||
igmp_finish_iface_config(struct igmp_iface_config *cf)
|
||||
{
|
||||
if (cf->query_response_int >= cf->query_int)
|
||||
cf_error("Query response interval must be less than query interval");
|
||||
|
||||
/* Dependent default values */
|
||||
if (!cf->startup_query_int)
|
||||
cf->startup_query_int = cf->query_int / 4;
|
||||
|
||||
if (!cf->startup_query_cnt)
|
||||
cf->startup_query_cnt = cf->robustness;
|
||||
|
||||
if (!cf->last_member_query_cnt)
|
||||
cf->last_member_query_cnt = cf->robustness;
|
||||
|
||||
cf->group_member_int = cf->robustness * cf->query_int + cf->query_response_int;
|
||||
cf->other_querier_int = cf->robustness * cf->query_int + cf->query_response_int / 2;
|
||||
}
|
||||
|
||||
static int
|
||||
igmp_reconfigure_iface(struct igmp_proto *p, struct igmp_iface *ifa, struct igmp_iface_config *new)
|
||||
{
|
||||
struct igmp_iface_config *old = ifa->cf;
|
||||
|
||||
TRACE(D_EVENTS, "Reconfiguring interface %s", ifa->iface->name);
|
||||
|
||||
ifa->cf = new;
|
||||
|
||||
/* We reconfigure just the query timer, as it is periodic */
|
||||
if (ifa->query_state == IGMP_QS_QUERIER)
|
||||
{
|
||||
if (ifa->startup_query_cnt)
|
||||
{
|
||||
if (new->startup_query_cnt != old->startup_query_cnt)
|
||||
{
|
||||
int delta = (int) new->startup_query_cnt - (int) old->startup_query_cnt;
|
||||
ifa->startup_query_cnt = MAX(1, ifa->startup_query_cnt + delta);
|
||||
}
|
||||
|
||||
if (new->startup_query_int != old->startup_query_int)
|
||||
tm_shift(ifa->query_timer, new->startup_query_int - old->startup_query_int);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (new->query_int != old->query_int)
|
||||
tm_shift(ifa->query_timer, new->query_int - old->query_int);
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_reconfigure_ifaces(struct igmp_proto *p, struct igmp_config *cf)
|
||||
{
|
||||
struct iface *iface;
|
||||
|
||||
WALK_LIST(iface, iface_list)
|
||||
{
|
||||
if (!(iface->flags & IF_UP))
|
||||
continue;
|
||||
|
||||
/* Ignore non-multicast ifaces */
|
||||
if (!(iface->flags & IF_MULTICAST))
|
||||
continue;
|
||||
|
||||
/* Ignore ifaces without IPv4 address */
|
||||
if (!iface->addr4)
|
||||
continue;
|
||||
|
||||
struct igmp_iface *ifa = igmp_find_iface(p, iface);
|
||||
struct igmp_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, NULL);
|
||||
|
||||
if (ifa && ic)
|
||||
{
|
||||
if (igmp_reconfigure_iface(p, ifa, ic))
|
||||
continue;
|
||||
|
||||
/* Hard restart */
|
||||
log(L_INFO "%s: Restarting interface %s", p->p.name, ifa->iface->name);
|
||||
igmp_remove_iface(p, ifa);
|
||||
igmp_add_iface(p, iface, ic);
|
||||
}
|
||||
|
||||
if (ifa && !ic)
|
||||
igmp_remove_iface(p, ifa);
|
||||
|
||||
if (!ifa && ic)
|
||||
igmp_add_iface(p, iface, ic);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* IGMP protocol glue
|
||||
*/
|
||||
|
||||
void
|
||||
igmp_postconfig(struct proto_config *CF)
|
||||
{
|
||||
// struct igmp_config *cf = (void *) CF;
|
||||
|
||||
/* Define default channel */
|
||||
if (EMPTY_LIST(CF->channels))
|
||||
channel_config_new(NULL, net_label[NET_MREQ4], NET_MREQ4, CF);
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_if_notify(struct proto *P, uint flags, struct iface *iface)
|
||||
{
|
||||
struct igmp_proto *p = (void *) P;
|
||||
struct igmp_config *cf = (void *) P->cf;
|
||||
|
||||
if (iface->flags & IF_IGNORE)
|
||||
return;
|
||||
|
||||
if (flags & IF_CHANGE_UP)
|
||||
{
|
||||
struct igmp_iface_config *ic = (void *) iface_patt_find(&cf->iface_list, iface, NULL);
|
||||
|
||||
/* Ignore non-multicast ifaces */
|
||||
if (!(iface->flags & IF_MULTICAST))
|
||||
return;
|
||||
|
||||
/* Ignore ifaces without IPv4 address */
|
||||
if (!iface->addr4)
|
||||
return;
|
||||
|
||||
if (ic)
|
||||
igmp_add_iface(p, iface, ic);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
struct igmp_iface *ifa = igmp_find_iface(p, iface);
|
||||
|
||||
if (!ifa)
|
||||
return;
|
||||
|
||||
if (flags & IF_CHANGE_DOWN)
|
||||
{
|
||||
igmp_remove_iface(p, ifa);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static struct proto *
|
||||
igmp_init(struct proto_config *CF)
|
||||
{
|
||||
struct igmp_proto *p = proto_new(CF);
|
||||
|
||||
p->p.main_channel = proto_add_channel(&p->p, proto_cf_main_channel(CF));
|
||||
|
||||
p->p.if_notify = igmp_if_notify;
|
||||
|
||||
p->mif_group = global_mif_group;
|
||||
|
||||
return &p->p;
|
||||
}
|
||||
|
||||
static int
|
||||
igmp_start(struct proto *P)
|
||||
{
|
||||
struct igmp_proto *p = (void *) P;
|
||||
|
||||
init_list(&p->iface_list);
|
||||
p->log_pkt_tbf = (struct tbf){ .rate = 1, .burst = 5 };
|
||||
|
||||
return PS_UP;
|
||||
}
|
||||
|
||||
static int
|
||||
igmp_shutdown(struct proto *P)
|
||||
{
|
||||
struct igmp_proto *p = (void *) P;
|
||||
struct igmp_iface *ifa;
|
||||
|
||||
WALK_LIST_FIRST(ifa, p->iface_list)
|
||||
igmp_remove_iface(p, ifa);
|
||||
|
||||
return PS_DOWN;
|
||||
}
|
||||
|
||||
static int
|
||||
igmp_reconfigure(struct proto *P, struct proto_config *CF)
|
||||
{
|
||||
struct igmp_proto *p = (void *) P;
|
||||
struct igmp_config *new = (void *) CF;
|
||||
|
||||
if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
|
||||
return 0;
|
||||
|
||||
TRACE(D_EVENTS, "Reconfiguring");
|
||||
|
||||
p->p.cf = CF;
|
||||
igmp_reconfigure_ifaces(p, new);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
igmp_dump(struct proto *P)
|
||||
{
|
||||
struct igmp_proto *p = (void *) P;
|
||||
struct igmp_iface *ifa;
|
||||
|
||||
WALK_LIST(ifa, p->iface_list)
|
||||
igmp_iface_dump(ifa);
|
||||
}
|
||||
|
||||
|
||||
struct protocol proto_igmp = {
|
||||
.name = "IGMP",
|
||||
.template = "igmp%d",
|
||||
.preference = DEF_PREF_IGMP,
|
||||
.channel_mask = NB_MREQ4,
|
||||
.proto_size = sizeof(struct igmp_proto),
|
||||
.config_size = sizeof(struct igmp_config),
|
||||
.postconfig = igmp_postconfig,
|
||||
.init = igmp_init,
|
||||
.dump = igmp_dump,
|
||||
.start = igmp_start,
|
||||
.shutdown = igmp_shutdown,
|
||||
.reconfigure = igmp_reconfigure,
|
||||
};
|
110
proto/igmp/igmp.h
Normal file
110
proto/igmp/igmp.h
Normal file
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* BIRD -- IGMP protocol
|
||||
*
|
||||
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||
* (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
|
||||
* (c) 2018 CZ.NIC z.s.p.o.
|
||||
*
|
||||
* Can be freely distributed and used under the terms of the GNU GPL.
|
||||
*/
|
||||
|
||||
#ifndef _BIRD_IGMP_H_
|
||||
#define _BIRD_IGMP_H_
|
||||
|
||||
#include "nest/bird.h"
|
||||
#include "nest/iface.h"
|
||||
#include "nest/locks.h"
|
||||
#include "nest/protocol.h"
|
||||
#include "nest/route.h"
|
||||
#include "conf/conf.h"
|
||||
#include "lib/lists.h"
|
||||
#include "lib/hash.h"
|
||||
#include "lib/socket.h"
|
||||
#include "lib/timer.h"
|
||||
|
||||
|
||||
#define IGMP_PROTO 2
|
||||
|
||||
#define IGMP_DEFAULT_ROBUSTNESS 2
|
||||
#define IGMP_DEFAULT_QUERY_INT (125 S_)
|
||||
#define IGMP_DEFAULT_RESPONSE_INT (10 S_)
|
||||
#define IGMP_DEFAULT_LAST_MEMBER_INT (1 S_)
|
||||
|
||||
|
||||
struct igmp_config
|
||||
{
|
||||
struct proto_config c;
|
||||
list iface_list; /* List of ifaces (struct igmp_iface_config) */
|
||||
};
|
||||
|
||||
struct igmp_iface_config
|
||||
{
|
||||
struct iface_patt i;
|
||||
|
||||
/* These are configurable */
|
||||
uint robustness, startup_query_cnt, last_member_query_cnt;
|
||||
btime query_int, query_response_int, startup_query_int, last_member_query_int;
|
||||
|
||||
/* These are not */
|
||||
btime group_member_int, other_querier_int;
|
||||
};
|
||||
|
||||
struct igmp_proto
|
||||
{
|
||||
struct proto p;
|
||||
list iface_list; /* List of interfaces (struct igmp_iface) */
|
||||
struct mif_group *mif_group; /* Associated MIF group for multicast routes */
|
||||
|
||||
struct tbf log_pkt_tbf; /* TBF for packet messages */
|
||||
};
|
||||
|
||||
struct igmp_iface
|
||||
{
|
||||
node n; /* Member of igmp_proto->iface_list */
|
||||
struct igmp_proto *proto;
|
||||
struct iface *iface; /* Underyling core interface */
|
||||
struct mif *mif; /* Associated multicast iface */
|
||||
struct igmp_iface_config *cf; /* Related config, must be updated in reconfigure */
|
||||
struct object_lock *lock; /* Interface lock */
|
||||
sock *sk; /* IGMP socket */
|
||||
|
||||
HASH(struct igmp_group) groups;
|
||||
|
||||
uint query_state; /* initial / querier / non-querier */
|
||||
int startup_query_cnt; /* Remaining startup queries to send */
|
||||
timer *query_timer, *other_present;
|
||||
};
|
||||
|
||||
struct igmp_group
|
||||
{
|
||||
struct igmp_group *next; /* Member of igmp_iface->groups */
|
||||
struct igmp_iface *ifa;
|
||||
ip4_addr address;
|
||||
|
||||
uint join_state;
|
||||
timer *join_timer, *v1_host_timer, *rxmt_timer;
|
||||
};
|
||||
|
||||
|
||||
#define IGMP_JS_NO_MEMBERS 0
|
||||
#define IGMP_JS_MEMBERS 1
|
||||
#define IGMP_JS_V1_MEMBERS 2
|
||||
#define IGMP_JS_CHECKING 3
|
||||
|
||||
#define IGMP_QS_INIT 0
|
||||
#define IGMP_QS_QUERIER 1
|
||||
#define IGMP_QS_NONQUERIER 2
|
||||
|
||||
|
||||
/* igmp.c */
|
||||
void igmp_handle_query(struct igmp_iface *ifa, ip4_addr addr, ip4_addr from, btime resp_time);
|
||||
void igmp_handle_report(struct igmp_iface *ifa, ip4_addr addr, int version);
|
||||
void igmp_handle_leave(struct igmp_iface *ifa, ip4_addr addr);
|
||||
void igmp_finish_iface_config(struct igmp_iface_config *cf);
|
||||
|
||||
/* packets.c */
|
||||
int igmp_open_socket(struct igmp_iface *ifa);
|
||||
void igmp_send_query(struct igmp_iface *ifa, ip4_addr addr, btime resp_time);
|
||||
|
||||
|
||||
#endif
|
167
proto/igmp/packets.c
Normal file
167
proto/igmp/packets.c
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
* BIRD -- Internet Group Management Protocol (IGMP)
|
||||
*
|
||||
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||
* (c) 2018 Ondrej Zajicek <santiago@crfreenet.org>
|
||||
* (c) 2018 CZ.NIC z.s.p.o.
|
||||
*
|
||||
* Can be freely distributed and used under the terms of the GNU GPL.
|
||||
*/
|
||||
|
||||
#include "igmp.h"
|
||||
#include "lib/checksum.h"
|
||||
|
||||
#include <linux/mroute.h>
|
||||
|
||||
|
||||
struct igmp_packet
|
||||
{
|
||||
u8 type;
|
||||
u8 resp_time;
|
||||
u16 checksum;
|
||||
u32 addr;
|
||||
} PACKED;
|
||||
|
||||
#define IGMP_MSG_QUERY 0x11
|
||||
#define IGMP_MSG_V1_REPORT 0x12
|
||||
#define IGMP_MSG_V2_REPORT 0x16
|
||||
#define IGMP_MSG_LEAVE 0x17
|
||||
|
||||
|
||||
#define DROP(DSC,VAL) do { err_dsc = DSC; err_val = VAL; goto drop; } while(0)
|
||||
|
||||
#define LOG_PKT(msg, args...) \
|
||||
log_rl(&p->log_pkt_tbf, L_REMOTE "%s: " msg, p->p.name, args)
|
||||
|
||||
int
|
||||
igmp_rx_hook(sock *sk, uint len)
|
||||
{
|
||||
struct igmp_iface *ifa = sk->data;
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
const char *err_dsc = NULL;
|
||||
uint err_val = 0;
|
||||
|
||||
struct igmp_packet *pkt = (void *) sk_rx_buffer(sk, &len);
|
||||
|
||||
if (pkt == NULL)
|
||||
DROP("bad IP header", len);
|
||||
|
||||
if (len < sizeof(struct igmp_packet))
|
||||
DROP("too short", len);
|
||||
|
||||
if (!ipsum_verify(pkt, len, NULL))
|
||||
DROP("invalid checksum", pkt->checksum);
|
||||
|
||||
ip4_addr from = ipa_to_ip4(sk->faddr);
|
||||
ip4_addr addr = get_ip4(&pkt->addr);
|
||||
|
||||
switch (pkt->type)
|
||||
{
|
||||
case IGMP_MSG_QUERY:
|
||||
TRACE(D_PACKETS, "Query received from %I4 on %s", from, ifa->iface->name);
|
||||
/* FIXME: Warning if resp_time == 0 */
|
||||
igmp_handle_query(ifa, addr, from, pkt->resp_time * (btime) 100000);
|
||||
break;
|
||||
|
||||
case IGMP_MSG_V1_REPORT:
|
||||
TRACE(D_PACKETS, "Report (v1) received from %I4 on %s for %I4", from, ifa->iface->name, addr);
|
||||
igmp_handle_report(ifa, addr, 1);
|
||||
break;
|
||||
|
||||
case IGMP_MSG_V2_REPORT:
|
||||
TRACE(D_PACKETS, "Report (v2) received from %I4 on %s for %I4", from, ifa->iface->name, addr);
|
||||
igmp_handle_report(ifa, addr, 2);
|
||||
break;
|
||||
|
||||
case IGMP_MSG_LEAVE:
|
||||
TRACE(D_PACKETS, "Leave received from %I4 on %s for %I4", from, ifa->iface->name, addr);
|
||||
igmp_handle_leave(ifa, addr);
|
||||
break;
|
||||
|
||||
default:
|
||||
TRACE(D_PACKETS, "Unknown IGMP packet (0x%x) from %I4 on %s", pkt->type, from, ifa->iface->name);
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
|
||||
drop:
|
||||
LOG_PKT("Bad packet from %I on %s - %s (%u)",
|
||||
sk->faddr, sk->iface->name, err_dsc, err_val);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
igmp_err_hook(sock *sk, int err)
|
||||
{
|
||||
struct igmp_iface *ifa = sk->data;
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
|
||||
log(L_ERR "%s: Socket error on %s: %M", p->p.name, ifa->iface->name, err);
|
||||
}
|
||||
|
||||
void
|
||||
igmp_send_query(struct igmp_iface *ifa, ip4_addr addr, btime resp_time)
|
||||
{
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
struct igmp_packet *pkt = (void *) ifa->sk->tbuf;
|
||||
|
||||
pkt->type = IGMP_MSG_QUERY;
|
||||
pkt->resp_time = resp_time / 100000;
|
||||
put_ip4(&pkt->addr, addr);
|
||||
|
||||
pkt->checksum = 0;
|
||||
pkt->checksum = ipsum_calculate(pkt, sizeof(struct igmp_packet), NULL);
|
||||
|
||||
ifa->sk->daddr = ip4_zero(addr) ? IP4_ALL_NODES : ipa_from_ip4(addr);
|
||||
|
||||
if (ip4_zero(addr))
|
||||
TRACE(D_PACKETS, "Sending query on %s", ifa->iface->name);
|
||||
else
|
||||
TRACE(D_PACKETS, "Sending query on %s for %I4", ifa->iface->name, addr);
|
||||
|
||||
sk_send(ifa->sk, sizeof(struct igmp_packet));
|
||||
}
|
||||
|
||||
int
|
||||
igmp_open_socket(struct igmp_iface *ifa)
|
||||
{
|
||||
struct igmp_proto *p = ifa->proto;
|
||||
|
||||
sock *sk = sk_new(p->p.pool);
|
||||
sk->type = SK_IP;
|
||||
sk->dport = IGMP_PROTO;
|
||||
sk->saddr = ifa->iface->addr4->ip;
|
||||
sk->iface = ifa->iface;
|
||||
|
||||
sk->rx_hook = igmp_rx_hook;
|
||||
sk->err_hook = igmp_err_hook;
|
||||
sk->data = ifa;
|
||||
|
||||
sk->tbsize = ifa->iface->mtu;
|
||||
sk->tos = IP_PREC_INTERNET_CONTROL;
|
||||
sk->ttl = 1;
|
||||
|
||||
if (sk_open(sk) < 0)
|
||||
goto err;
|
||||
|
||||
if (sk_setup_multicast(sk) < 0)
|
||||
goto err;
|
||||
|
||||
if (sk_setup_igmp(sk, p->mif_group, ifa->mif) < 0)
|
||||
goto err;
|
||||
|
||||
if (sk_join_group(sk, IP4_IGMP_ROUTERS) < 0)
|
||||
goto err;
|
||||
|
||||
if (sk_join_group(sk, IP4_ALL_ROUTERS) < 0)
|
||||
goto err;
|
||||
|
||||
ifa->sk = sk;
|
||||
return 1;
|
||||
|
||||
err:
|
||||
log(L_ERR "%s: Socket error: %s%#m", ifa->proto->p.name, sk->err);
|
||||
rfree(sk);
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user