/* $OpenBSD: group.c,v 1.1 2006/06/01 14:12:20 norby Exp $ */ /* * Copyright (c) 2006 Esben Norby <norby@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/time.h> #include <sys/socket.h> #include <stdlib.h> #include <string.h> #include <event.h> #include "igmp.h" #include "dvmrpd.h" #include "dvmrp.h" #include "dvmrpe.h" #include "log.h" #include "control.h" void dead_timer(int, short, void *); int start_dead_timer(struct group *); int start_dead_timer_all(struct group *); int stop_dead_timer(struct group *); void v1_host_timer(int, short, void *); int start_v1_host_timer(struct group *); int stop_v1_host_timer(struct group *); void retrans_timer(int, short, void *); int start_retrans_timer(struct group *); int stop_retrans_timer(struct group *); extern struct dvmrpd_conf *deconf; #define MAX_ACTIONS 4 struct { int state; enum group_event event; enum group_action action[MAX_ACTIONS]; int new_state; } grp_fsm[] = { /* current state event that happened action(s) to take resulting state */ /* querier fsm */ {GRP_STA_NO_MEMB_PRSNT, GRP_EVT_V2_REPORT_RCVD, {GRP_ACT_ADD_ROUTE, GRP_ACT_START_TMR, GRP_ACT_END}, GRP_STA_MEMB_PRSNT}, {GRP_STA_NO_MEMB_PRSNT, GRP_EVT_V1_REPORT_RCVD, {GRP_ACT_ADD_ROUTE, GRP_ACT_START_TMR, GRP_ACT_START_V1_HOST_TMR, GRP_ACT_END}, GRP_STA_MEMB_PRSNT}, {GRP_STA_MEMB_PRSNT, GRP_EVT_TMR_EXPIRED, {GRP_ACT_DEL_ROUTE, GRP_ACT_END}, GRP_STA_NO_MEMB_PRSNT}, {GRP_STA_MEMB_PRSNT, GRP_EVT_V2_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_END}, 0}, {GRP_STA_MEMB_PRSNT, GRP_EVT_V1_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_START_V1_HOST_TMR, GRP_ACT_END}, GRP_STA_V1_MEMB_PRSNT}, {GRP_STA_MEMB_PRSNT, GRP_EVT_LEAVE_RCVD, {GRP_ACT_START_TMR_ALL, GRP_ACT_START_RETRANS_TMR, GRP_ACT_SEND_GRP_QUERY, GRP_ACT_END}, GRP_STA_CHECK_MEMB}, {GRP_STA_CHECK_MEMB, GRP_EVT_V2_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_END}, GRP_STA_MEMB_PRSNT}, {GRP_STA_CHECK_MEMB, GRP_EVT_TMR_EXPIRED, {GRP_ACT_DEL_ROUTE, GRP_ACT_CLR_RETRANS_TMR, GRP_ACT_END}, GRP_STA_NO_MEMB_PRSNT}, {GRP_STA_CHECK_MEMB, GRP_EVT_RETRANS_TMR_EXP,{GRP_ACT_SEND_GRP_QUERY, GRP_ACT_START_RETRANS_TMR, GRP_ACT_END}, 0}, {GRP_STA_CHECK_MEMB, GRP_EVT_V1_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_START_V1_HOST_TMR, GRP_ACT_END}, GRP_STA_V1_MEMB_PRSNT}, {GRP_STA_V1_MEMB_PRSNT, GRP_EVT_V1_HOST_TMR_EXP,{GRP_ACT_NOTHING, GRP_ACT_END}, GRP_STA_MEMB_PRSNT}, {GRP_STA_V1_MEMB_PRSNT, GRP_EVT_V1_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_START_V1_HOST_TMR, GRP_ACT_END}, 0}, {GRP_STA_V1_MEMB_PRSNT, GRP_EVT_V2_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_END}, 0}, {GRP_STA_V1_MEMB_PRSNT, GRP_EVT_TMR_EXPIRED, {GRP_ACT_DEL_ROUTE, GRP_ACT_END}, GRP_STA_NO_MEMB_PRSNT}, /* non querier fsm */ {GRP_STA_NO_MEMB_PRSNT, GRP_EVT_REPORT_RCVD, {GRP_ACT_ADD_ROUTE, GRP_ACT_START_TMR, GRP_ACT_END}, GRP_STA_MEMB_PRSNT}, {GRP_STA_MEMB_PRSNT, GRP_EVT_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_END}, 0}, {GRP_STA_MEMB_PRSNT, GRP_EVT_QUERY_RCVD, {GRP_ACT_START_TMR_ALL, GRP_ACT_END}, GRP_STA_CHECK_MEMB}, {GRP_STA_CHECK_MEMB, GRP_EVT_REPORT_RCVD, {GRP_ACT_START_TMR, GRP_ACT_END}, GRP_STA_MEMB_PRSNT}, {-1, GRP_EVT_NOTHING, {GRP_ACT_NOTHING, GRP_ACT_END}, 0}, }; const char * const group_action_names[] = { "END MARKER", "START TIMER", "START ALL TIMER", "START RETRANSMISSION TIMER", "START V1 HOST TIMER", "SEND GROUP QUERY", "ADD ROUTE", "DEL ROUTE", "CLEAR RETRANSMISSION TIMER", "NOTHING" }; static const char * const group_event_names[] = { "V2 REPORT RCVD", "V1 REPORT RCVD", "LEAVE RCVD", "TIMER EXPIRED", "RETRANS TIMER EXPIRED", "V1 HOST TIMER EXPIRED", "REPORT RCVD", "QUERY RCVD", "NOTHING" }; int group_fsm(struct group *group, enum group_event event) { struct mfc mfc; int old_state; int new_state = 0; int i, j, ret = 0; old_state = group->state; for (i = 0; grp_fsm[i].state != -1; i++) if ((grp_fsm[i].state & old_state) && (grp_fsm[i].event == event)) { new_state = grp_fsm[i].new_state; break; } if (grp_fsm[i].state == -1) { /* XXX event outside of the defined fsm, ignore it. */ log_debug("group_fsm: group %s, event '%s' not expected in " "state '%s'", inet_ntoa(group->addr), group_event_name(event), group_state_name(old_state)); return (0); } for (j = 0; grp_fsm[i].action[j] != GRP_ACT_END; j++) { switch (grp_fsm[i].action[j]) { case GRP_ACT_START_TMR: ret = start_dead_timer(group); break; case GRP_ACT_START_TMR_ALL: ret = start_dead_timer_all(group); break; case GRP_ACT_START_RETRANS_TMR: ret = start_retrans_timer(group); break; case GRP_ACT_START_V1_HOST_TMR: ret = start_v1_host_timer(group); break; case GRP_ACT_SEND_GRP_QUERY: ret = send_igmp_query(group->iface, group); break; case GRP_ACT_ADD_ROUTE: mfc.origin.s_addr = 0; mfc.group = group->addr; mfc.ifindex = group->iface->ifindex; /* XXX fixme */ dvmrpe_imsg_compose_rde(IMSG_MFC_ADD, 0, 0, &mfc, sizeof(mfc)); break; case GRP_ACT_DEL_ROUTE: mfc.origin.s_addr = 0; mfc.group = group->addr; mfc.ifindex = group->iface->ifindex; /* XXX fixme */ dvmrpe_imsg_compose_rde(IMSG_MFC_DEL, 0, 0, &mfc, sizeof(mfc)); break; case GRP_ACT_CLR_RETRANS_TMR: ret = stop_retrans_timer(group); break; case GRP_ACT_NOTHING: case GRP_ACT_END: /* do nothing */ break; } if (ret) { log_debug("group_fsm: error changing state for " "group %s, event '%s', state '%s'", inet_ntoa(group->addr), group_event_name(event), group_state_name(old_state)); return (-1); } } if (new_state != 0) group->state = new_state; for (j = 0; grp_fsm[i].action[j] != GRP_ACT_END; j++) log_debug("group_fsm: event '%s' resulted in action '%s' and " "changing state for group %s from '%s' to '%s'", group_event_name(event), group_action_name(grp_fsm[i].action[j]), inet_ntoa(group->addr), group_state_name(old_state), group_state_name(group->state)); return (ret); } /* timers */ void dead_timer(int fd, short event, void *arg) { struct group *group = arg; log_debug("dead_timer: %s", inet_ntoa(group->addr)); group_fsm(group, GRP_EVT_TMR_EXPIRED); } int start_dead_timer(struct group *group) { struct timeval tv; log_debug("start_dead_timer: %s", inet_ntoa(group->addr)); /* Group Membership Interval */ timerclear(&tv); tv.tv_sec = group->iface->robustness * group->iface->query_interval + (group->iface->query_resp_interval / 2); return (evtimer_add(&group->dead_timer, &tv)); } int start_dead_timer_all(struct group *group) { struct timeval tv; log_debug("start_dead_timer_all: %s", inet_ntoa(group->addr)); timerclear(&tv); if (group->iface->state == IF_STA_QUERIER) { /* querier */ tv.tv_sec = group->iface->last_member_query_interval * group->iface->last_member_query_cnt; } else { /* non querier */ /* XXX max response time received in packet */ tv.tv_sec = group->iface->recv_query_resp_interval * group->iface->last_member_query_cnt; } return (evtimer_add(&group->dead_timer, &tv)); } int stop_dead_timer(struct group *group) { log_debug("stop_dead_timer: %s", inet_ntoa(group->addr)); return (evtimer_del(&group->dead_timer)); } void v1_host_timer(int fd, short event, void *arg) { struct group *group = arg; log_debug("v1_host_timer: %s", inet_ntoa(group->addr)); group_fsm(group, GRP_EVT_V1_HOST_TMR_EXP); } int start_v1_host_timer(struct group *group) { struct timeval tv; log_debug("start_v1_host_timer: %s", inet_ntoa(group->addr)); /* Group Membership Interval */ timerclear(&tv); tv.tv_sec = group->iface->robustness * group->iface->query_interval + (group->iface->query_resp_interval / 2); return (evtimer_add(&group->v1_host_timer, &tv)); } int stop_v1_host_timer(struct group *group) { log_debug("stop_v1_host_timer: %s", inet_ntoa(group->addr)); return (evtimer_del(&group->v1_host_timer)); } void retrans_timer(int fd, short event, void *arg) { struct group *group = arg; struct timeval tv; log_debug("retrans_timer: %s", inet_ntoa(group->addr)); send_igmp_query(group->iface, group); /* reschedule retrans_timer */ if (group->state == GRP_STA_CHECK_MEMB) { timerclear(&tv); tv.tv_sec = group->iface->last_member_query_interval; evtimer_add(&group->retrans_timer, &tv); } } int start_retrans_timer(struct group *group) { struct timeval tv; log_debug("start_retrans_timer: %s", inet_ntoa(group->addr)); timerclear(&tv); tv.tv_sec = group->iface->last_member_query_interval; return (evtimer_add(&group->retrans_timer, &tv)); } int stop_retrans_timer(struct group *group) { log_debug("stop_retrans_timer: %s", inet_ntoa(group->addr)); return (evtimer_del(&group->retrans_timer)); } /* group list */ struct group * group_list_add(struct iface *iface, u_int32_t group) { struct group *ge; struct timeval now; /* validate group id */ if (!IN_MULTICAST(htonl(group))) fatalx("group_list_add: invalid group"); if ((ge = group_list_find(iface, group)) != NULL) { return (ge); } if ((ge = calloc(1, sizeof(*ge))) == NULL) fatal("group_list_add"); ge->addr.s_addr = group; ge->state = GRP_STA_NO_MEMB_PRSNT; evtimer_set(&ge->dead_timer, dead_timer, ge); evtimer_set(&ge->v1_host_timer, v1_host_timer, ge); evtimer_set(&ge->retrans_timer, retrans_timer, ge); gettimeofday(&now, NULL); ge->uptime = now.tv_sec; TAILQ_INSERT_TAIL(&iface->group_list, ge, entry); iface->group_cnt++; ge->iface = iface; log_debug("group_list_add: interface %s, group %s", iface->name, inet_ntoa(ge->addr)); return (ge); } void group_list_remove(struct iface *iface, struct group *group) { log_debug("group_list_remove: interface %s, group %s", iface->name, inet_ntoa(group->addr)); /* stop timers */ stop_dead_timer(group); start_v1_host_timer(group); stop_retrans_timer(group); TAILQ_REMOVE(&iface->group_list, group, entry); free(group); iface->group_cnt--; } struct group * group_list_find(struct iface *iface, u_int32_t group) { struct group *ge = NULL; /* validate group id */ if (!IN_MULTICAST(htonl(group))) fatalx("group_list_find: invalid group"); TAILQ_FOREACH(ge, &iface->group_list, entry) { if (ge->addr.s_addr == group) return (ge); } return (ge); } void group_list_clr(struct iface *iface) { struct group *ge; while ((ge = TAILQ_FIRST(&iface->group_list)) != NULL) { TAILQ_REMOVE(&iface->group_list, ge, entry); free(ge); } iface->group_cnt = 0; } int group_list_empty(struct iface *iface) { return (TAILQ_EMPTY(&iface->group_list)); } void group_list_dump(struct iface *iface, struct ctl_conn *c) { struct group *ge; struct ctl_group *gctl; TAILQ_FOREACH(ge, &iface->group_list, entry) { gctl = group_to_ctl(ge); imsg_compose(&c->ibuf, IMSG_CTL_SHOW_IGMP, 0, 0, gctl, sizeof(struct ctl_group)); } } /* names */ const char * group_event_name(int event) { return (group_event_names[event]); } const char * group_action_name(int action) { return (group_action_names[action]); } struct ctl_group * group_to_ctl(struct group *group) { static struct ctl_group gctl; struct timeval tv, now, res; memcpy(&gctl.addr, &group->addr, sizeof(gctl.addr)); gctl.state = group->state; gettimeofday(&now, NULL); if (evtimer_pending(&group->dead_timer, &tv)) { timersub(&tv, &now, &res); gctl.dead_timer = res.tv_sec; } else gctl.dead_timer = 0; if (group->state != GRP_STA_NO_MEMB_PRSNT) { gctl.uptime = now.tv_sec - group->uptime; } else gctl.uptime = 0; return (&gctl); }