mirror of
https://gitlab.nic.cz/labs/bird.git
synced 2025-01-09 18:41:55 +00:00
Added the IGMP protocol
This commit is contained in:
parent
76dd4730ea
commit
b5b6ab653d
12
bird.conf
12
bird.conf
@ -42,3 +42,15 @@ protocol static {
|
|||||||
|
|
||||||
protocol rip {
|
protocol rip {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protocol igmp {
|
||||||
|
# interface "*" {
|
||||||
|
# robustness 2;
|
||||||
|
# query interval 125 s;
|
||||||
|
# query response interval 10 s;
|
||||||
|
# startup query interval 31 s;
|
||||||
|
# startup query count 2;
|
||||||
|
# last member query interval 1 s;
|
||||||
|
# last member query count 2;
|
||||||
|
# };
|
||||||
|
}
|
||||||
|
@ -167,8 +167,8 @@ fi
|
|||||||
|
|
||||||
AC_SUBST(iproutedir)
|
AC_SUBST(iproutedir)
|
||||||
|
|
||||||
# all_protocols="$proto_bfd babel bgp ospf pipe radv rip static"
|
# all_protocols="$proto_bfd babel bgp igmp ospf pipe radv rip static"
|
||||||
all_protocols="$proto_bfd ospf pipe radv rip static"
|
all_protocols="$proto_bfd igmp ospf pipe radv rip static"
|
||||||
|
|
||||||
all_protocols=`echo $all_protocols | sed 's/ /,/g'`
|
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_ALL_ROUTERS ipa_build4(224, 0, 0, 5)
|
||||||
#define IP4_OSPF_DES_ROUTERS ipa_build4(224, 0, 0, 6)
|
#define IP4_OSPF_DES_ROUTERS ipa_build4(224, 0, 0, 6)
|
||||||
#define IP4_RIP_ROUTERS ipa_build4(224, 0, 0, 9)
|
#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_NODES ipa_build6(0xFF020000, 0, 0, 1)
|
||||||
#define IP6_ALL_ROUTERS ipa_build6(0xFF020000, 0, 0, 2)
|
#define IP6_ALL_ROUTERS ipa_build6(0xFF020000, 0, 0, 2)
|
||||||
|
@ -1262,6 +1262,9 @@ protos_build(void)
|
|||||||
#ifdef CONFIG_BABEL
|
#ifdef CONFIG_BABEL
|
||||||
proto_build(&proto_babel);
|
proto_build(&proto_babel);
|
||||||
#endif
|
#endif
|
||||||
|
#ifdef CONFIG_IGMP
|
||||||
|
proto_build(&proto_igmp);
|
||||||
|
#endif
|
||||||
|
|
||||||
proto_pool = rp_new(&root_pool, "Protocols");
|
proto_pool = rp_new(&root_pool, "Protocols");
|
||||||
proto_shutdown_timer = tm_new(proto_pool);
|
proto_shutdown_timer = tm_new(proto_pool);
|
||||||
|
@ -81,7 +81,8 @@ void protos_dump_all(void);
|
|||||||
|
|
||||||
extern struct protocol
|
extern struct protocol
|
||||||
proto_device, proto_radv, proto_rip, proto_static,
|
proto_device, proto_radv, proto_rip, proto_static,
|
||||||
proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel;
|
proto_ospf, proto_pipe, proto_bgp, proto_bfd,
|
||||||
|
proto_igmp;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Routing Protocol Instance
|
* Routing Protocol Instance
|
||||||
|
@ -399,6 +399,7 @@ typedef struct rta {
|
|||||||
#define RTS_BGP 11 /* BGP route */
|
#define RTS_BGP 11 /* BGP route */
|
||||||
#define RTS_PIPE 12 /* Inter-table wormhole */
|
#define RTS_PIPE 12 /* Inter-table wormhole */
|
||||||
#define RTS_BABEL 13 /* Babel route */
|
#define RTS_BABEL 13 /* Babel route */
|
||||||
|
#define RTS_IGMP 14 /* IGMP multicast request */
|
||||||
|
|
||||||
#define RTC_UNICAST 0
|
#define RTC_UNICAST 0
|
||||||
#define RTC_BROADCAST 1
|
#define RTC_BROADCAST 1
|
||||||
|
4
proto/igmp/Makefile
Normal file
4
proto/igmp/Makefile
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
src := igmp.c packets.c
|
||||||
|
obj := $(src-o-files)
|
||||||
|
$(all-daemon)
|
||||||
|
$(cf-local)
|
77
proto/igmp/config.Y
Normal file
77
proto/igmp/config.Y
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
* BIRD -- IGMP protocol
|
||||||
|
*
|
||||||
|
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||||
|
*
|
||||||
|
* 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_config_finish(this_proto); })
|
||||||
|
|
||||||
|
igmp_proto_start: proto_start IGMP {
|
||||||
|
this_proto = proto_config_new(&proto_igmp, $1);
|
||||||
|
igmp_config_init(IGMP_CFG);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
|
||||||
|
igmp_proto:
|
||||||
|
igmp_proto_start proto_name '{'
|
||||||
|
| igmp_proto igmp_proto_item ';'
|
||||||
|
;
|
||||||
|
|
||||||
|
igmp_proto_item:
|
||||||
|
proto_item
|
||||||
|
| proto_channel
|
||||||
|
| INTERFACE igmp_iface
|
||||||
|
;
|
||||||
|
|
||||||
|
igmp_iface_start:
|
||||||
|
{
|
||||||
|
this_ipatt = cfg_allocz(sizeof(struct igmp_iface_config));
|
||||||
|
add_tail(&IGMP_CFG->patt_list, NODE this_ipatt);
|
||||||
|
igmp_iface_config_init(IGMP_IFACE);
|
||||||
|
};
|
||||||
|
|
||||||
|
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_iface_config_finish(IGMP_IFACE); }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CF_CODE
|
||||||
|
|
||||||
|
CF_END
|
490
proto/igmp/igmp.c
Normal file
490
proto/igmp/igmp.c
Normal file
@ -0,0 +1,490 @@
|
|||||||
|
/*
|
||||||
|
* BIRD --IGMP protocol
|
||||||
|
*
|
||||||
|
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||||
|
*
|
||||||
|
* 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's mutlicast request table. It needs to hold state for
|
||||||
|
* every group with local listeners.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "igmp.h"
|
||||||
|
#include "lib/ip.h"
|
||||||
|
#include "conf/conf.h"
|
||||||
|
|
||||||
|
#define HASH_GRP_KEY(n) n->ga
|
||||||
|
#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)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* A change occured, update the request tables.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
igmp_notify_routing(struct igmp_grp *grp, int join)
|
||||||
|
{
|
||||||
|
net_addr_mreq4 addr = NET_ADDR_MREQ4(grp->ga, grp->ifa->iface->index);
|
||||||
|
struct igmp_proto *p = grp->ifa->proto;
|
||||||
|
|
||||||
|
TRACE(D_EVENTS, "iface %s %s group %I", grp->ifa->iface->name, join ? "joined" : "left", ipa_from_ip4(grp->ga));
|
||||||
|
|
||||||
|
net *n = net_get(p->mreq_channel->table, (net_addr *) &addr);
|
||||||
|
if (join)
|
||||||
|
{
|
||||||
|
rta a0 = {
|
||||||
|
.src = p->p.main_source,
|
||||||
|
.source = RTS_IGMP,
|
||||||
|
.dest = RTD_MREQUEST,
|
||||||
|
.iface = grp->ifa->iface,
|
||||||
|
};
|
||||||
|
rta *a = rta_lookup(&a0);
|
||||||
|
rte *e = rte_get_temp(a);
|
||||||
|
|
||||||
|
e->net = n;
|
||||||
|
rte_update2(p->mreq_channel, (net_addr *) &addr, e, p->p.main_source);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rte_update2(p->mreq_channel, (net_addr *) &addr, NULL, p->p.main_source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_gen_query_hook(struct timer *tm)
|
||||||
|
{
|
||||||
|
struct igmp_iface *ifa = tm->data;
|
||||||
|
|
||||||
|
if (ifa->startup_query_cnt > 0)
|
||||||
|
ifa->startup_query_cnt--;
|
||||||
|
|
||||||
|
igmp_tx_query(ifa, IP4_NONE);
|
||||||
|
|
||||||
|
tm_start(ifa->gen_query, ifa->startup_query_cnt
|
||||||
|
? ifa->cf->startup_query_int TO_S
|
||||||
|
: ifa->cf->query_int TO_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_other_present_expire(struct timer *tm)
|
||||||
|
{
|
||||||
|
struct igmp_iface *ifa = tm->data;
|
||||||
|
|
||||||
|
ifa->query_state = IGMP_QS_QUERIER;
|
||||||
|
tm_start(ifa->gen_query, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
Group state management
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_grp_free(struct igmp_grp *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_grp_v1_timer_expire(struct timer *tm)
|
||||||
|
{
|
||||||
|
struct igmp_grp *grp = tm->data;
|
||||||
|
|
||||||
|
if (grp->join_state == IGMP_JS_V1MEMB)
|
||||||
|
grp->join_state = IGMP_JS_MEMB;
|
||||||
|
else
|
||||||
|
bug("V1 timer expired without v1 hosts");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_grp_join_timer_expire(struct timer *tm)
|
||||||
|
{
|
||||||
|
struct igmp_grp *grp = tm->data;
|
||||||
|
|
||||||
|
switch (grp->join_state) {
|
||||||
|
case IGMP_JS_NOMEMB:
|
||||||
|
bug("IGMP GRP without members expired.");
|
||||||
|
return;
|
||||||
|
case IGMP_JS_V1MEMB:
|
||||||
|
case IGMP_JS_MEMB:
|
||||||
|
case IGMP_JS_CHECK:
|
||||||
|
grp->join_state = IGMP_JS_NOMEMB;
|
||||||
|
igmp_notify_routing(grp, 0);
|
||||||
|
igmp_grp_free(grp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_grp_retransmit_expire(struct timer *tm)
|
||||||
|
{
|
||||||
|
struct igmp_grp *grp = tm->data;
|
||||||
|
|
||||||
|
if (grp->join_state != IGMP_JS_CHECK)
|
||||||
|
return;
|
||||||
|
|
||||||
|
igmp_tx_query(grp->ifa, grp->ga);
|
||||||
|
tm_start(grp->rxmt_timer, grp->ifa->cf->last_member_query_int TO_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct igmp_grp *
|
||||||
|
igmp_grp_new(struct igmp_iface *ifa, ip4_addr *ga)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = ifa->proto;
|
||||||
|
struct igmp_grp *grp = mb_allocz(p->p.pool, sizeof(struct igmp_grp));
|
||||||
|
grp->ga = *ga;
|
||||||
|
grp->ifa = ifa;
|
||||||
|
HASH_INSERT(ifa->groups, HASH_GRP, grp);
|
||||||
|
|
||||||
|
grp->join_timer = tm_new_set(ifa->proto->p.pool, igmp_grp_join_timer_expire, grp, 0, 0);
|
||||||
|
grp->rxmt_timer = tm_new_set(ifa->proto->p.pool, igmp_grp_retransmit_expire, grp, 0, 0);
|
||||||
|
grp->v1_host_timer = tm_new_set(ifa->proto->p.pool, igmp_grp_v1_timer_expire, grp, 0, 0);
|
||||||
|
|
||||||
|
return grp;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct igmp_grp *
|
||||||
|
igmp_grp_find(struct igmp_iface *ifa, ip4_addr *ga)
|
||||||
|
{
|
||||||
|
return HASH_FIND(ifa->groups, HASH_GRP, *ga);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
Iface management
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
igmp_iface_is_up(struct igmp_iface *ifa)
|
||||||
|
{
|
||||||
|
return !!ifa->sk;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct igmp_iface *
|
||||||
|
igmp_iface_new(struct igmp_proto *p, struct iface *iface, struct igmp_iface_config *ic)
|
||||||
|
{
|
||||||
|
struct igmp_iface *ifa = mb_allocz(p->p.pool, sizeof(struct igmp_iface));
|
||||||
|
add_tail(&p->iface_list, NODE ifa);
|
||||||
|
ifa->iface = iface;
|
||||||
|
ifa->cf = ic;
|
||||||
|
ifa->proto = p;
|
||||||
|
ifa->gen_id = random_u32();
|
||||||
|
ifa->startup_query_cnt = ifa->cf->startup_query_cnt;
|
||||||
|
|
||||||
|
ifa->gen_query = tm_new_set(p->p.pool, igmp_gen_query_hook, ifa, 0, 0);
|
||||||
|
ifa->other_present = tm_new_set(p->p.pool, igmp_other_present_expire, ifa, 0, 0);
|
||||||
|
|
||||||
|
HASH_INIT(ifa->groups, p->p.pool, 8);
|
||||||
|
|
||||||
|
if (igmp_sk_open(ifa))
|
||||||
|
log(L_ERR "Failed opening socket for IGMP");
|
||||||
|
|
||||||
|
ifa->query_state = IGMP_QS_QUERIER;
|
||||||
|
tm_start(ifa->gen_query, 0);
|
||||||
|
|
||||||
|
return ifa;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
igmp_iface_down(struct igmp_iface *ifa)
|
||||||
|
{
|
||||||
|
if (!igmp_iface_is_up(ifa))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
rfree(ifa->sk);
|
||||||
|
ifa->sk = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
igmp_iface_free(struct igmp_iface* ifa)
|
||||||
|
{
|
||||||
|
rem_node(NODE ifa);
|
||||||
|
|
||||||
|
HASH_WALK_DELSAFE(ifa->groups, next, grp)
|
||||||
|
{
|
||||||
|
igmp_notify_routing(grp, 0);
|
||||||
|
igmp_grp_free(grp);
|
||||||
|
}
|
||||||
|
HASH_WALK_END;
|
||||||
|
|
||||||
|
rfree(ifa->gen_query);
|
||||||
|
rfree(ifa->other_present);
|
||||||
|
mb_free(ifa);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char *join_states[] = {
|
||||||
|
[IGMP_JS_NOMEMB] = "no members",
|
||||||
|
[IGMP_JS_MEMB] = "members",
|
||||||
|
[IGMP_JS_V1MEMB] = "v1 members",
|
||||||
|
[IGMP_JS_CHECK] = "about to expire",
|
||||||
|
};
|
||||||
|
|
||||||
|
static char *query_states[] = {
|
||||||
|
[IGMP_QS_INIT] = "initializing",
|
||||||
|
[IGMP_QS_QUERIER] = "querier",
|
||||||
|
[IGMP_QS_NONQUERIER] = "other querier present",
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_iface_dump(struct igmp_iface *ifa)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = ifa->proto;
|
||||||
|
|
||||||
|
debug("\tInterface %s is %s, %s\n", ifa->iface->name, igmp_iface_is_up(ifa) ? "up" : "down", query_states[ifa->query_state]);
|
||||||
|
|
||||||
|
HASH_WALK(ifa->groups, next, grp)
|
||||||
|
TRACE(D_EVENTS, "\t\tGroup %I4: %s\n", grp->ga, join_states[grp->join_state]);
|
||||||
|
HASH_WALK_END;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct igmp_iface *
|
||||||
|
igmp_iface_find(struct igmp_proto *p, struct iface * ifa)
|
||||||
|
{
|
||||||
|
struct igmp_iface * pif;
|
||||||
|
WALK_LIST(pif, p->iface_list)
|
||||||
|
if (pif->iface == ifa)
|
||||||
|
return pif;
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
igmp_iface_config_init(struct igmp_iface_config * ifc)
|
||||||
|
{
|
||||||
|
init_list(&ifc->i.ipn_list);
|
||||||
|
|
||||||
|
ifc->robustness = 2;
|
||||||
|
ifc->query_int = 125 S;
|
||||||
|
ifc->query_response_int = 10 S;
|
||||||
|
ifc->last_member_query_int = 1 S;
|
||||||
|
ifc->last_member_query_cnt = -1U;
|
||||||
|
|
||||||
|
ifc->startup_query_cnt = -1U;
|
||||||
|
ifc->startup_query_int = -1U;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
igmp_iface_config_finish(struct igmp_iface_config * ifc)
|
||||||
|
{
|
||||||
|
/* Explicit constraints - probably bail out? */
|
||||||
|
if (ifc->robustness == 0)
|
||||||
|
cf_error("IGMP: Robustness must be at least 1.");
|
||||||
|
|
||||||
|
if (ifc->query_response_int > ifc->query_int)
|
||||||
|
cf_error("IGMP: The query response interval must not be greater than the query interval.");
|
||||||
|
|
||||||
|
/* Dependent default values */
|
||||||
|
if (ifc->startup_query_int == -1U)
|
||||||
|
ifc->startup_query_int = ifc->query_int / 4;
|
||||||
|
|
||||||
|
if (ifc->startup_query_cnt == -1U)
|
||||||
|
ifc->startup_query_cnt = ifc->robustness;
|
||||||
|
|
||||||
|
if (ifc->last_member_query_cnt == -1U)
|
||||||
|
ifc->last_member_query_cnt = ifc->robustness;
|
||||||
|
|
||||||
|
ifc->group_memb_int = ifc->robustness * ifc->query_int + ifc->query_response_int;
|
||||||
|
ifc->other_querier_int = ifc->robustness * ifc->query_int + ifc->query_response_int / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
Protocol logic
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
int
|
||||||
|
igmp_query_received(struct igmp_iface *ifa, ip4_addr from)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (ifa->query_state != IGMP_QS_QUERIER)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
/* Find first IPv4 address of the interface */
|
||||||
|
/* XXX: cache and update in if_notify? */
|
||||||
|
struct ifa *my_addr = ifa_find_match(ifa->iface, NB_IP4);
|
||||||
|
|
||||||
|
/* Another router with lower IP shall be the Querier */
|
||||||
|
if (ip4_compare(ipa_to_ip4(my_addr->ip), from) > 0)
|
||||||
|
{
|
||||||
|
ifa->query_state = IGMP_QS_NONQUERIER;
|
||||||
|
tm_start(ifa->other_present, ifa->cf->other_querier_int TO_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
igmp_membership_report(struct igmp_grp *grp, u8 igmp_version, u8 resp_time)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = grp->ifa->proto;
|
||||||
|
uint last_state = grp->join_state;
|
||||||
|
TRACE(D_PACKETS, "Membership report received for group %I4 on iface %s", grp->ga, grp->ifa->iface->name);
|
||||||
|
|
||||||
|
if (grp->ifa->query_state == IGMP_QS_QUERIER && igmp_version == 1)
|
||||||
|
{
|
||||||
|
grp->join_state = IGMP_JS_V1MEMB;
|
||||||
|
tm_start(grp->v1_host_timer, grp->ifa->cf->group_memb_int TO_S);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grp->join_state != IGMP_JS_V1MEMB)
|
||||||
|
grp->join_state = IGMP_JS_MEMB;
|
||||||
|
|
||||||
|
tm_stop(grp->rxmt_timer);
|
||||||
|
tm_start(grp->join_timer, grp->ifa->cf->group_memb_int TO_S);
|
||||||
|
|
||||||
|
if (last_state == IGMP_JS_NOMEMB)
|
||||||
|
igmp_notify_routing(grp, 1);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
igmp_leave(struct igmp_grp *grp, u8 resp_time)
|
||||||
|
{
|
||||||
|
if (!grp)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct igmp_proto *p = grp->ifa->proto;
|
||||||
|
|
||||||
|
TRACE(D_PACKETS, "Leave received for group %I4 on iface %s", grp->ga, grp->ifa->iface->name);
|
||||||
|
grp->join_state = IGMP_JS_CHECK;
|
||||||
|
tm_start(grp->rxmt_timer, 0);
|
||||||
|
tm_start(grp->join_timer, (grp->ifa->cf->last_member_query_int TO_S) * grp->ifa->cf->last_member_query_cnt);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
Others
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
void
|
||||||
|
igmp_config_init(struct igmp_config *cf)
|
||||||
|
{
|
||||||
|
init_list(&cf->patt_list);
|
||||||
|
igmp_iface_config_init(&cf->default_iface_cf);
|
||||||
|
igmp_iface_config_finish(&cf->default_iface_cf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
igmp_config_finish(struct proto_config *c)
|
||||||
|
{
|
||||||
|
if (NULL == proto_cf_find_channel(c, NET_MREQ4))
|
||||||
|
channel_config_new(NULL, NET_MREQ4, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_if_notify(struct proto *P, uint flags, struct iface *iface)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = (struct igmp_proto *) P;
|
||||||
|
struct igmp_config *c = (struct igmp_config *) P->cf;
|
||||||
|
|
||||||
|
if (iface->flags & IF_IGNORE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (flags & IF_CHANGE_UP)
|
||||||
|
{
|
||||||
|
struct igmp_iface_config *ic;
|
||||||
|
ic = (struct igmp_iface_config *) iface_patt_find(&c->patt_list, iface, iface->addr);
|
||||||
|
if (!ic)
|
||||||
|
ic = &c->default_iface_cf;
|
||||||
|
igmp_iface_new(p, iface, ic);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (flags & IF_CHANGE_DOWN)
|
||||||
|
{
|
||||||
|
struct igmp_iface * ifa = igmp_iface_find(p, iface);
|
||||||
|
igmp_iface_down(ifa);
|
||||||
|
igmp_iface_free(ifa);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
igmp_start(struct proto *P)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = (struct igmp_proto *) P;
|
||||||
|
init_list(&p->iface_list);
|
||||||
|
return PS_UP;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
igmp_shutdown(struct proto *P)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = (struct igmp_proto *) P;
|
||||||
|
struct igmp_iface *ifa;
|
||||||
|
WALK_LIST_FIRST(ifa, p->iface_list)
|
||||||
|
{
|
||||||
|
igmp_iface_down(ifa);
|
||||||
|
igmp_iface_free(ifa);
|
||||||
|
}
|
||||||
|
|
||||||
|
return PS_DOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
igmp_dump(struct proto *P)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = (struct igmp_proto *) P;
|
||||||
|
struct igmp_iface *ifa;
|
||||||
|
WALK_LIST(ifa, p->iface_list)
|
||||||
|
igmp_iface_dump(ifa);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We do not want to receive any route updates.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
igmp_reject(struct proto *p, rte **e, ea_list **attrs, struct linpool *pool)
|
||||||
|
{ return -1; }
|
||||||
|
|
||||||
|
static struct proto *
|
||||||
|
igmp_init(struct proto_config *C)
|
||||||
|
{
|
||||||
|
struct proto *P = proto_new(C);
|
||||||
|
struct igmp_proto *p = (struct igmp_proto *) P;
|
||||||
|
|
||||||
|
p->mreq_channel = proto_add_channel(P, proto_cf_find_channel(C, NET_MREQ4));
|
||||||
|
|
||||||
|
p->cf = (struct igmp_config *) C;
|
||||||
|
P->if_notify = igmp_if_notify;
|
||||||
|
P->import_control = igmp_reject;
|
||||||
|
|
||||||
|
return P;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct protocol proto_igmp = {
|
||||||
|
.name = "IGMP",
|
||||||
|
.template = "igmp%d",
|
||||||
|
.preference = DEF_PREF_STATIC,
|
||||||
|
.proto_size = sizeof(struct igmp_proto),
|
||||||
|
.config_size = sizeof(struct igmp_config),
|
||||||
|
.channel_mask = NB_MREQ4,
|
||||||
|
.init = igmp_init,
|
||||||
|
.dump = igmp_dump,
|
||||||
|
.start = igmp_start,
|
||||||
|
.shutdown = igmp_shutdown,
|
||||||
|
};
|
||||||
|
|
113
proto/igmp/igmp.h
Normal file
113
proto/igmp/igmp.h
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
/*
|
||||||
|
* BIRD --IGMP protocol
|
||||||
|
*
|
||||||
|
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||||
|
*
|
||||||
|
* 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/hash.h"
|
||||||
|
#include "lib/socket.h"
|
||||||
|
#include "filter/filter.h"
|
||||||
|
|
||||||
|
#include <linux/mroute.h>
|
||||||
|
|
||||||
|
|
||||||
|
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_memb_int, other_querier_int;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct igmp_config
|
||||||
|
{
|
||||||
|
struct proto_config c;
|
||||||
|
list patt_list; /* list of ifaces (struct igmp_iface_config) */
|
||||||
|
|
||||||
|
struct igmp_iface_config default_iface_cf;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct igmp_grp
|
||||||
|
{
|
||||||
|
struct igmp_grp *next; /* member of igmp_iface->groups */
|
||||||
|
struct igmp_iface *ifa;
|
||||||
|
|
||||||
|
ip4_addr ga;
|
||||||
|
uint join_state;
|
||||||
|
timer *join_timer, *v1_host_timer, *rxmt_timer;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IGMP_JS_NOMEMB 0
|
||||||
|
#define IGMP_JS_MEMB 1
|
||||||
|
#define IGMP_JS_V1MEMB 2
|
||||||
|
#define IGMP_JS_CHECK 3
|
||||||
|
|
||||||
|
struct igmp_iface
|
||||||
|
{
|
||||||
|
node n; /* member of igmp_proto->iface_list */
|
||||||
|
struct igmp_proto *proto;
|
||||||
|
struct iface *iface;
|
||||||
|
struct igmp_iface_config *cf;
|
||||||
|
|
||||||
|
sock *sk; /* The one receiving packets */
|
||||||
|
uint query_state; /* initial / querier / non-querier */
|
||||||
|
vifi_t vifi; /* VIF containing just this device */
|
||||||
|
|
||||||
|
HASH(struct igmp_grp) groups;
|
||||||
|
|
||||||
|
u32 gen_id;
|
||||||
|
uint startup_query_cnt; /* Remaining startup queries to send */
|
||||||
|
timer *gen_query, *other_present;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IGMP_QS_INIT 0
|
||||||
|
#define IGMP_QS_QUERIER 1
|
||||||
|
#define IGMP_QS_NONQUERIER 2
|
||||||
|
|
||||||
|
struct igmp_proto
|
||||||
|
{
|
||||||
|
struct proto p;
|
||||||
|
struct igmp_config *cf;
|
||||||
|
|
||||||
|
struct channel *mreq_channel; /* Channel to multicast requests table */
|
||||||
|
|
||||||
|
list iface_list; /* list of managed ifaces (struct igmp_iface) */
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IGMP_PROTO 2
|
||||||
|
|
||||||
|
/* igmp.c */
|
||||||
|
int igmp_query_received(struct igmp_iface *ifa, ip4_addr from);
|
||||||
|
int igmp_membership_report(struct igmp_grp *grp, u8 igmp_version, u8 resp_time);
|
||||||
|
int igmp_leave(struct igmp_grp *grp, u8 resp_time);
|
||||||
|
|
||||||
|
struct igmp_grp *igmp_grp_new(struct igmp_iface *ifa, ip4_addr *ga);
|
||||||
|
struct igmp_grp *igmp_grp_find(struct igmp_iface *ifa, ip4_addr *ga);
|
||||||
|
|
||||||
|
void igmp_config_init(struct igmp_config *cf);
|
||||||
|
void igmp_config_finish(struct proto_config *cf);
|
||||||
|
void igmp_iface_config_init(struct igmp_iface_config * ifc);
|
||||||
|
void igmp_iface_config_finish(struct igmp_iface_config * ifc);
|
||||||
|
|
||||||
|
/* packets.c */
|
||||||
|
int igmp_sk_open(struct igmp_iface * ifa);
|
||||||
|
int igmp_tx_query(struct igmp_iface *ifa, ip4_addr addr);
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
152
proto/igmp/packets.c
Normal file
152
proto/igmp/packets.c
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/*
|
||||||
|
* BIRD --IGMP protocol
|
||||||
|
*
|
||||||
|
* (c) 2016 Ondrej Hlavaty <aearsis@eideo.cz>
|
||||||
|
*
|
||||||
|
* Can be freely distributed and used under the terms of the GNU GPL.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "igmp.h"
|
||||||
|
#include "lib/checksum.h"
|
||||||
|
|
||||||
|
struct igmp_pkt {
|
||||||
|
u8 type;
|
||||||
|
u8 resp_time;
|
||||||
|
u16 checksum;
|
||||||
|
u32 addr;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define IGMP_TP_MS_QUERY 0x11
|
||||||
|
#define IGMP_TP_V1_MS_REPORT 0x12
|
||||||
|
#define IGMP_TP_V2_MS_REPORT 0x16
|
||||||
|
#define IGMP_TP_LEAVE 0x17
|
||||||
|
|
||||||
|
#define DROP(args...) do { TRACE(D_PACKETS, "Dropping packet: " args); goto drop; } while(0)
|
||||||
|
int
|
||||||
|
igmp_accept(struct igmp_iface *ifa, ip4_addr from, struct igmp_pkt *pkt)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = ifa->proto;
|
||||||
|
|
||||||
|
if (pkt->type == IGMP_TP_MS_QUERY)
|
||||||
|
return igmp_query_received(ifa, from);
|
||||||
|
|
||||||
|
ip4_addr addr = get_ip4(&pkt->addr);
|
||||||
|
struct igmp_grp *grp = igmp_grp_find(ifa, &addr);
|
||||||
|
|
||||||
|
if (pkt->type == IGMP_TP_LEAVE)
|
||||||
|
return igmp_leave(grp, pkt->resp_time);
|
||||||
|
|
||||||
|
if (!grp)
|
||||||
|
grp = igmp_grp_new(ifa, &addr);
|
||||||
|
|
||||||
|
switch (pkt->type) {
|
||||||
|
case IGMP_TP_V1_MS_REPORT:
|
||||||
|
igmp_membership_report(grp, 1, 10);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IGMP_TP_V2_MS_REPORT:
|
||||||
|
igmp_membership_report(grp, 2, pkt->resp_time);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
DROP("Unknown type");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
drop:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
igmp_rx_hook(sock *sk, int len)
|
||||||
|
{
|
||||||
|
struct igmp_iface *ifa = sk->data;
|
||||||
|
struct igmp_proto *p = ifa->proto;
|
||||||
|
|
||||||
|
struct igmp_pkt *pkt = (struct igmp_pkt *) sk_rx_buffer(sk, &len);
|
||||||
|
|
||||||
|
if (len < sizeof(struct igmp_pkt))
|
||||||
|
DROP("Shorter than 8 bytes");
|
||||||
|
|
||||||
|
/* Longer packets are in IGMPv3 */
|
||||||
|
if (len != 8)
|
||||||
|
DROP("Expected pkt length 8, not %i (probably newer IGMP)", len);
|
||||||
|
|
||||||
|
if (!ipsum_verify(pkt, len, NULL))
|
||||||
|
DROP("Invalid checksum");
|
||||||
|
|
||||||
|
return igmp_accept(sk->data, ipa_to_ip4(sk->faddr), pkt);
|
||||||
|
|
||||||
|
drop:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
igmp_err_hook(sock *sk, int err)
|
||||||
|
{
|
||||||
|
struct igmp_iface *ifa = sk->data;
|
||||||
|
struct igmp_proto *p = ifa->proto;
|
||||||
|
|
||||||
|
TRACE(D_EVENTS, "IGMP err %m", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
igmp_tx_query(struct igmp_iface *ifa, ip4_addr addr)
|
||||||
|
{
|
||||||
|
struct igmp_proto *p = ifa->proto;
|
||||||
|
struct igmp_pkt *pkt = (struct igmp_pkt *) ifa->sk->tbuf;
|
||||||
|
|
||||||
|
pkt->type = IGMP_TP_MS_QUERY;
|
||||||
|
pkt->resp_time = (ifa->cf->query_response_int TO_MS) / 100;
|
||||||
|
put_ip4(&pkt->addr, addr);
|
||||||
|
|
||||||
|
pkt->checksum = 0;
|
||||||
|
pkt->checksum = ipsum_calculate(pkt, sizeof(struct igmp_pkt), NULL);
|
||||||
|
|
||||||
|
ifa->sk->daddr = ip4_zero(addr) ? IP4_ALL_NODES : ipa_from_ip4(addr);
|
||||||
|
|
||||||
|
if (ip4_zero(addr))
|
||||||
|
TRACE(D_PACKETS, "Sending general query on iface %s", ifa->iface->name);
|
||||||
|
else
|
||||||
|
TRACE(D_PACKETS, "Sending query to grp %I4 on iface %s", addr, ifa->iface->name);
|
||||||
|
|
||||||
|
sk_send(ifa->sk, 8);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
igmp_sk_open(struct igmp_iface *ifa)
|
||||||
|
{
|
||||||
|
sock *sk = sk_new(ifa->proto->p.pool);
|
||||||
|
sk->type = SK_IGMP;
|
||||||
|
sk->saddr = ifa->iface->addr->ip;
|
||||||
|
sk->iface = ifa->iface;
|
||||||
|
|
||||||
|
sk->data = ifa;
|
||||||
|
sk->ttl = 1;
|
||||||
|
sk->tos = IP_PREC_INTERNET_CONTROL;
|
||||||
|
sk->rx_hook = igmp_rx_hook;
|
||||||
|
sk->err_hook = igmp_err_hook;
|
||||||
|
|
||||||
|
sk->tbsize = ifa->iface->mtu;
|
||||||
|
|
||||||
|
if (sk_open(sk) < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if (sk_setup_multicast(sk) < 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 0;
|
||||||
|
|
||||||
|
err:
|
||||||
|
log(L_ERR "%s: Socket error: %s%#m", ifa->proto->p.name, sk->err);
|
||||||
|
rfree(sk);
|
||||||
|
return -1;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user