0
0
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:
Ondrej Zajicek (work) 2018-03-12 00:15:13 +01:00
commit 5c5a1b862c
15 changed files with 1017 additions and 7 deletions

View File

@ -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'`

View File

@ -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)

View File

@ -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)
{

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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 */
/*

View File

@ -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));

View File

@ -2,6 +2,7 @@ H Protocols
C babel
C bfd
C bgp
C igmp
C ospf
C pipe
C radv

2
proto/igmp/Doc Normal file
View File

@ -0,0 +1,2 @@
S igmp.c
S packets.c

6
proto/igmp/Makefile Normal file
View 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
View 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
View 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
View 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
View 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;
}