diff options
author | Daniel Walton <dwalton@cumulusnetworks.com> | 2016-10-20 15:34:29 +0200 |
---|---|---|
committer | Donald Sharp <sharpd@cumulusnetworks.com> | 2016-12-22 02:26:11 +0100 |
commit | b05b72e80b840fd8628b890cb6fa5c8ca7d73dd3 (patch) | |
tree | a8d5f0314de5725102affaf9b0968a030548ea5c /pimd | |
parent | pimd: Allow debugs entered in conf t mode to persist (diff) | |
download | frr-b05b72e80b840fd8628b890cb6fa5c8ca7d73dd3.tar.xz frr-b05b72e80b840fd8628b890cb6fa5c8ca7d73dd3.zip |
pimd: add support for IGMPv2
Signed-off-by: Daniel Walton <dwalton@cumulusnetworks.com>
Ticket: CM-7962
Diffstat (limited to 'pimd')
-rw-r--r-- | pimd/Makefile.am | 4 | ||||
-rw-r--r-- | pimd/pim_cmd.c | 76 | ||||
-rw-r--r-- | pimd/pim_iface.c | 1 | ||||
-rw-r--r-- | pimd/pim_iface.h | 1 | ||||
-rw-r--r-- | pimd/pim_igmp.c | 592 | ||||
-rw-r--r-- | pimd/pim_igmp.h | 20 | ||||
-rw-r--r-- | pimd/pim_igmpv2.c | 190 | ||||
-rw-r--r-- | pimd/pim_igmpv2.h | 41 | ||||
-rw-r--r-- | pimd/pim_igmpv3.c | 465 | ||||
-rw-r--r-- | pimd/pim_igmpv3.h | 44 | ||||
-rw-r--r-- | pimd/pim_mroute.c | 114 | ||||
-rw-r--r-- | pimd/pim_vty.c | 9 |
12 files changed, 888 insertions, 669 deletions
diff --git a/pimd/Makefile.am b/pimd/Makefile.am index 07679f279..bb3d1844c 100644 --- a/pimd/Makefile.am +++ b/pimd/Makefile.am @@ -48,7 +48,7 @@ noinst_PROGRAMS = test_igmpv3_join libpim_a_SOURCES = \ pim_memory.c \ pimd.c pim_version.c pim_cmd.c pim_signals.c pim_iface.c \ - pim_vty.c pim_igmp.c pim_sock.c pim_zebra.c \ + pim_vty.c pim_igmp.c pim_sock.c pim_zebra.c pim_igmpv2.c \ pim_igmpv3.c pim_str.c pim_mroute.c pim_util.c pim_time.c \ pim_oil.c pim_zlookup.c pim_pim.c pim_tlv.c pim_neighbor.c \ pim_hello.c pim_ifchannel.c pim_join.c pim_assert.c \ @@ -60,7 +60,7 @@ libpim_a_SOURCES = \ noinst_HEADERS = \ pim_memory.h \ pimd.h pim_version.h pim_cmd.h pim_signals.h pim_iface.h \ - pim_vty.h pim_igmp.h pim_sock.h pim_zebra.h \ + pim_vty.h pim_igmp.h pim_sock.h pim_zebra.h pim_igmpv2.h \ pim_igmpv3.h pim_str.h pim_mroute.h pim_util.h pim_time.h \ pim_oil.h pim_zlookup.h pim_pim.h pim_tlv.h pim_neighbor.h \ pim_hello.h pim_ifchannel.h pim_join.h pim_assert.h \ diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c index e9b3a4e13..c6390271f 100644 --- a/pimd/pim_cmd.c +++ b/pimd/pim_cmd.c @@ -510,7 +510,7 @@ static void igmp_show_interfaces(struct vty *vty, u_char uj) json = json_object_new_object(); else vty_out(vty, - "Interface State Address Querier Query Timer Uptime%s", + "Interface State Address V Querier Query Timer Uptime%s", VTY_NEWLINE); for (ALL_LIST_ELEMENTS_RO (vrf_iflist (VRF_DEFAULT), node, ifp)) { @@ -534,6 +534,7 @@ static void igmp_show_interfaces(struct vty *vty, u_char uj) json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); json_object_string_add(json_row, "upTime", uptime); + json_object_int_add(json_row, "version", pim_ifp->igmp_version); if (igmp->t_igmp_query_timer) { json_object_boolean_true_add(json_row, "querier"); @@ -543,10 +544,11 @@ static void igmp_show_interfaces(struct vty *vty, u_char uj) json_object_object_add(json, ifp->name, json_row); } else { - vty_out(vty, "%-9s %5s %15s %7s %11s %8s%s", + vty_out(vty, "%-9s %5s %15s %d %7s %11s %8s%s", ifp->name, if_is_up(ifp) ? "up" : "down", inet_ntoa(igmp->ifaddr), + pim_ifp->igmp_version, igmp->t_igmp_query_timer ? "local" : "other", query_hhmmss, uptime, @@ -627,12 +629,12 @@ static void igmp_show_interfaces_single(struct vty *vty, const char *ifname, u_c if (uj) { json_row = json_object_new_object(); json_object_pim_ifp_add(json_row, ifp); - json_object_string_add(json_row, "upTime", uptime); json_object_string_add(json_row, "querier", igmp->t_igmp_query_timer ? "local" : "other"); json_object_int_add(json_row, "queryStartCount", igmp->startup_query_count); json_object_string_add(json_row, "queryQueryTimer", query_hhmmss); json_object_string_add(json_row, "queryOtherTimer", other_hhmmss); + json_object_int_add(json_row, "version", pim_ifp->igmp_version); json_object_int_add(json_row, "timerGroupMembershipIntervalMsec", gmi_msec); json_object_int_add(json_row, "timerLastMemberQueryMsec", lmqt_msec); json_object_int_add(json_row, "timerOlderHostPresentIntervalMsec", ohpi_msec); @@ -649,6 +651,7 @@ static void igmp_show_interfaces_single(struct vty *vty, const char *ifname, u_c vty_out(vty, "State : %s%s", if_is_up(ifp) ? "up" : "down", VTY_NEWLINE); vty_out(vty, "Address : %s%s", inet_ntoa(pim_ifp->primary_address), VTY_NEWLINE); vty_out(vty, "Uptime : %s%s", uptime, VTY_NEWLINE); + vty_out(vty, "Version : %d%s", pim_ifp->igmp_version, VTY_NEWLINE); vty_out(vty, "%s", VTY_NEWLINE); vty_out(vty, "%s", VTY_NEWLINE); @@ -1974,10 +1977,13 @@ static void igmp_show_groups(struct vty *vty, u_char uj) json_row = json_object_new_object(); json_object_string_add(json_row, "source", ifaddr_str); json_object_string_add(json_row, "group", group_str); - json_object_string_add(json_row, "mode", grp->group_filtermode_isexcl ? "EXCLUDE" : "INCLUDE"); + + if (grp->igmp_version == 3) + json_object_string_add(json_row, "mode", grp->group_filtermode_isexcl ? "EXCLUDE" : "INCLUDE"); + json_object_string_add(json_row, "timer", hhmmss); json_object_int_add(json_row, "sourcesCount", grp->group_source_list ? listcount(grp->group_source_list) : 0); - json_object_int_add(json_row, "version", igmp_group_compat_mode(igmp, grp)); + json_object_int_add(json_row, "version", grp->igmp_version); json_object_string_add(json_row, "uptime", uptime); json_object_object_add(json_iface, group_str, json_row); @@ -1986,10 +1992,10 @@ static void igmp_show_groups(struct vty *vty, u_char uj) ifp->name, ifaddr_str, group_str, - grp->group_filtermode_isexcl ? "EXCL" : "INCL", + grp->igmp_version == 3 ? (grp->group_filtermode_isexcl ? "EXCL" : "INCL") : "----", hhmmss, grp->group_source_list ? listcount(grp->group_source_list) : 0, - igmp_group_compat_mode(igmp, grp), + grp->igmp_version, uptime, VTY_NEWLINE); } @@ -3914,6 +3920,56 @@ DEFUN (interface_no_ip_igmp_query_interval, return CMD_SUCCESS; } +DEFUN (interface_ip_igmp_version, + interface_ip_igmp_version_cmd, + "ip igmp version <2-3>", + IP_STR + IFACE_IGMP_STR + "IGMP version\n" + "IGMP version number\n") +{ + VTY_DECLVAR_CONTEXT(interface,ifp); + struct pim_interface *pim_ifp; + int igmp_version; + + pim_ifp = ifp->info; + + if (!pim_ifp) { + vty_out(vty, + "IGMP not enabled on interface %s. Please enable IGMP first.%s", + ifp->name, + VTY_NEWLINE); + return CMD_WARNING; + } + + igmp_version = atoi(argv[3]->arg); + pim_ifp->igmp_version = igmp_version; + + return CMD_SUCCESS; +} + +DEFUN (interface_no_ip_igmp_version, + interface_no_ip_igmp_version_cmd, + "no ip igmp version <2-3>", + NO_STR + IP_STR + IFACE_IGMP_STR + "IGMP version\n" + "IGMP version number\n") +{ + VTY_DECLVAR_CONTEXT(interface, ifp); + struct pim_interface *pim_ifp; + + pim_ifp = ifp->info; + + if (!pim_ifp) + return CMD_SUCCESS; + + pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; + + return CMD_SUCCESS; +} + #define IGMP_QUERY_MAX_RESPONSE_TIME_MIN (1) #define IGMP_QUERY_MAX_RESPONSE_TIME_MAX (25) @@ -5073,7 +5129,7 @@ DEFUN (test_igmp_receive_report, igmp_msg = buf + ip_hlen; group_record = igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET; *igmp_msg = PIM_IGMP_V3_MEMBERSHIP_REPORT; /* type */ - *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0; /* for computing checksum */ + *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET) = 0; /* for computing checksum */ *(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET) = htons(1); /* one group record */ *(uint8_t *) (group_record + IGMP_V3_GROUP_RECORD_TYPE_OFFSET) = record_type; memcpy(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, &grp_addr, sizeof(struct in_addr)); @@ -5097,7 +5153,7 @@ DEFUN (test_igmp_receive_report, igmp_msg_len = IGMP_V3_MSG_MIN_SIZE + (num_sources << 4); /* v3 report for one single group record */ /* compute checksum */ - *(uint16_t *)(igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = in_cksum(igmp_msg, igmp_msg_len); + *(uint16_t *)(igmp_msg + IGMP_CHECKSUM_OFFSET) = in_cksum(igmp_msg, igmp_msg_len); /* "receive" message */ @@ -5731,6 +5787,8 @@ void pim_cmd_init() install_element (INTERFACE_NODE, &interface_no_ip_igmp_cmd); install_element (INTERFACE_NODE, &interface_ip_igmp_join_cmd); install_element (INTERFACE_NODE, &interface_no_ip_igmp_join_cmd); + install_element (INTERFACE_NODE, &interface_ip_igmp_version_cmd); + install_element (INTERFACE_NODE, &interface_no_ip_igmp_version_cmd); install_element (INTERFACE_NODE, &interface_ip_igmp_query_interval_cmd); install_element (INTERFACE_NODE, &interface_no_ip_igmp_query_interval_cmd); install_element (INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_cmd); diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c index b6012d1ed..ceab93edf 100644 --- a/pimd/pim_iface.c +++ b/pimd/pim_iface.c @@ -106,6 +106,7 @@ struct pim_interface *pim_if_new(struct interface *ifp, int igmp, int pim) pim_ifp->options = 0; pim_ifp->mroute_vif_index = -1; + pim_ifp->igmp_version = IGMP_DEFAULT_VERSION; pim_ifp->igmp_default_robustness_variable = IGMP_DEFAULT_ROBUSTNESS_VARIABLE; pim_ifp->igmp_default_query_interval = IGMP_GENERAL_QUERY_INTERVAL; pim_ifp->igmp_query_max_response_time_dsec = IGMP_QUERY_MAX_RESPONSE_TIME_DSEC; diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h index 0be4918d7..87530c602 100644 --- a/pimd/pim_iface.h +++ b/pimd/pim_iface.h @@ -65,6 +65,7 @@ struct pim_interface { ifindex_t mroute_vif_index; struct in_addr primary_address; /* remember addr to detect change */ + int igmp_version; /* IGMP version */ int igmp_default_robustness_variable; /* IGMPv3 QRV */ int igmp_default_query_interval; /* IGMPv3 secs between general queries */ int igmp_query_max_response_time_dsec; /* IGMPv3 Max Response Time in dsecs for general queries */ diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c index e14ff2922..56f2457f9 100644 --- a/pimd/pim_igmp.c +++ b/pimd/pim_igmp.c @@ -27,6 +27,7 @@ #include "pimd.h" #include "pim_igmp.h" +#include "pim_igmpv2.h" #include "pim_igmpv3.h" #include "pim_iface.h" #include "pim_sock.h" @@ -36,34 +37,31 @@ #include "pim_time.h" #include "pim_zebra.h" -#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE (1) -#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE (2) -#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3) -#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4) -#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES (5) -#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES (6) - static void group_timer_off(struct igmp_group *group); +/* This socket is used for TXing IGMP packets only, IGMP RX happens + * in pim_mroute_msg() + */ static int igmp_sock_open(struct in_addr ifaddr, ifindex_t ifindex, uint32_t pim_options) { int fd; int join = 0; struct in_addr group; - fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifindex, 1 /* loop=true */); + fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, ifindex, 0 /* loop=false */); + if (fd < 0) return -1; if (PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(pim_options)) { if (inet_aton(PIM_ALL_ROUTERS, &group)) { if (!pim_socket_join(fd, group, ifaddr, ifindex)) - ++join; + ++join; } else { zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s", - __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), - PIM_ALL_ROUTERS, errno, safe_strerror(errno)); + __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), + PIM_ALL_ROUTERS, errno, safe_strerror(errno)); } } @@ -77,8 +75,8 @@ static int igmp_sock_open(struct in_addr ifaddr, ifindex_t ifindex, uint32_t pim } else { zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s", - __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), - PIM_ALL_SYSTEMS, errno, safe_strerror(errno)); + __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), + PIM_ALL_SYSTEMS, errno, safe_strerror(errno)); } if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) { @@ -88,13 +86,13 @@ static int igmp_sock_open(struct in_addr ifaddr, ifindex_t ifindex, uint32_t pim } else { zlog_warn("%s %s: IGMP socket fd=%d interface %s: could not solve %s to group address: errno=%d: %s", - __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), - PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno)); + __FILE__, __PRETTY_FUNCTION__, fd, inet_ntoa(ifaddr), + PIM_ALL_IGMP_ROUTERS, errno, safe_strerror(errno)); } if (!join) { zlog_err("IGMP socket fd=%d could not join any group on interface address %s", - fd, inet_ntoa(ifaddr)); + fd, inet_ntoa(ifaddr)); close(fd); fd = -1; } @@ -271,31 +269,27 @@ void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp) zassert(!igmp->t_other_querier_timer); } -static int recv_igmp_query(struct igmp_sock *igmp, int query_version, - int max_resp_code, - struct in_addr from, const char *from_str, - char *igmp_msg, int igmp_msg_len) +static int +igmp_recv_query(struct igmp_sock *igmp, int query_version, + int max_resp_code, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) { struct interface *ifp; struct pim_interface *pim_ifp; - uint8_t resv_s_qrv = 0; - uint8_t s_flag = 0; - uint8_t qrv = 0; struct in_addr group_addr; uint16_t recv_checksum; uint16_t checksum; - int i; - //group_addr = *(struct in_addr *)(igmp_msg + 4); memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); ifp = igmp->interface; pim_ifp = ifp->info; - recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET); + recv_checksum = *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET); /* for computing checksum */ - *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0; + *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET) = 0; checksum = in_cksum(igmp_msg, igmp_msg_len); if (checksum != recv_checksum) { @@ -304,12 +298,33 @@ static int recv_igmp_query(struct igmp_sock *igmp, int query_version, return -1; } + /* RFC 3376 defines some guidelines on operating in backwards compatibility + * with older versions of IGMP but there are some gaps in the logic: + * + * - once we drop from say version 3 to version 2 we will never go back to + * version 3 even if the node that TXed an IGMP v2 query upgrades to v3 + * + * - The node with the lowest IP is the querier so we will only know to drop + * from v3 to v2 if the node that is the querier is also the one that is + * running igmp v2. If a non-querier only supports igmp v2 we will have + * no way of knowing. + * + * For now we will simplify things and inform the user that they need to + * configure all PIM routers to use the same version of IGMP. + */ + if (query_version != pim_ifp->igmp_version) { + zlog_warn("Recv IGMP query v%d from %s on %s but we are using v%d, please " + "configure all PIM routers on this subnet to use the same " + "IGMP version", + query_version, from_str, ifp->name, pim_ifp->igmp_version); + return 0; + } + if (PIM_DEBUG_IGMP_PACKETS) { char group_str[100]; pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); - zlog_debug("Recv IGMP query v%d from %s on %s: size=%d checksum=%x group=%s", - query_version, from_str, ifp->name, - igmp_msg_len, checksum, group_str); + zlog_debug("Recv IGMP query v%d from %s on %s for group %s", + query_version, from_str, ifp->name, group_str); } /* @@ -321,7 +336,7 @@ static int recv_igmp_query(struct igmp_sock *igmp, int query_version, elected querier. */ if (ntohl(from.s_addr) < ntohl(igmp->ifaddr.s_addr)) { - + if (PIM_DEBUG_IGMP_TRACE) { char ifaddr_str[100]; pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); @@ -334,272 +349,11 @@ static int recv_igmp_query(struct igmp_sock *igmp, int query_version, pim_igmp_other_querier_timer_on(igmp); } + /* IGMP version 3 is the only one where we process the RXed query */ if (query_version == 3) { - /* - RFC 3376: 4.1.6. QRV (Querier's Robustness Variable) - - Routers adopt the QRV value from the most recently received Query - as their own [Robustness Variable] value, unless that most - recently received QRV was zero, in which case the receivers use - the default [Robustness Variable] value specified in section 8.1 - or a statically configured value. - */ - resv_s_qrv = igmp_msg[8]; - qrv = 7 & resv_s_qrv; - igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable; - } - - /* - RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) - - Multicast routers that are not the current querier adopt the QQI - value from the most recently received Query as their own [Query - Interval] value, unless that most recently received QQI was zero, - in which case the receiving routers use the default. - */ - if (igmp->t_other_querier_timer && query_version == 3) { - /* other querier present */ - uint8_t qqic; - uint16_t qqi; - qqic = igmp_msg[9]; - qqi = igmp_msg_decode8to16(qqic); - igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval; - - if (PIM_DEBUG_IGMP_TRACE) { - char ifaddr_str[100]; - pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); - zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)", - ifaddr_str, - qqi ? "recv-non-default" : "default", - igmp->querier_query_interval, - qqic, - from_str); - } - } - - /* - RFC 3376: 6.6.1. Timer Updates - - When a router sends or receives a query with a clear Suppress - Router-Side Processing flag, it must update its timers to reflect - the correct timeout values for the group or sources being queried. - - General queries don't trigger timer update. - */ - if (query_version == 3) { - s_flag = (1 << 3) & resv_s_qrv; - } - else { - /* Neither V1 nor V2 have this field. Pimd should really go into - * a compatibility mode here and run as V2 (or V1) but it doesn't - * so for now, lets just set the flag to suppress these timer updates. - */ - s_flag = 1; - } - - if (!s_flag) { - /* s_flag is clear */ - - if (PIM_INADDR_IS_ANY(group_addr)) { - /* this is a general query */ - - /* log that general query should have the s_flag set */ - zlog_warn("General IGMP query v%d from %s on %s: Suppress Router-Side Processing flag is clear", - query_version, from_str, ifp->name); - } - else { - struct igmp_group *group; - - /* this is a non-general query: perform timer updates */ - - group = find_group_by_addr(igmp, group_addr); - if (group) { - int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET)); - - /* - RFC 3376: 6.6.1. Timer Updates - Query Q(G,A): Source Timer for sources in A are lowered to LMQT - Query Q(G): Group Timer is lowered to LMQT - */ - if (recv_num_sources < 1) { - /* Query Q(G): Group Timer is lowered to LMQT */ - - igmp_group_timer_lower_to_lmqt(group); - } - else { - /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */ - - /* Scan sources in query and lower their timers to LMQT */ - struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET); - for (i = 0; i < recv_num_sources; ++i) { - //struct in_addr src_addr = sources[i]; - //struct igmp_source *src = igmp_find_source_by_addr(group, src_addr); - struct in_addr src_addr; - struct igmp_source *src; - memcpy(&src_addr, sources + i, sizeof(struct in_addr)); - src = igmp_find_source_by_addr(group, src_addr); - if (src) { - igmp_source_timer_lower_to_lmqt(src); - } - } - } - - } - else { - char group_str[100]; - pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); - zlog_warn("IGMP query v%d from %s on %s: could not find group %s for timer update", - query_version, from_str, ifp->name, group_str); - } - } - } /* s_flag is clear: timer updates */ - - return 0; -} - -static int igmp_v3_report(struct igmp_sock *igmp, - struct in_addr from, const char *from_str, - char *igmp_msg, int igmp_msg_len) -{ - uint16_t recv_checksum; - uint16_t checksum; - int num_groups; - uint8_t *group_record; - uint8_t *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len; - struct interface *ifp = igmp->interface; - int i; - int local_ncb = 0; - - if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) { - zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d", - from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE); - return -1; - } - - recv_checksum = *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET); - - /* for computing checksum */ - *(uint16_t *) (igmp_msg + IGMP_V3_CHECKSUM_OFFSET) = 0; - - checksum = in_cksum(igmp_msg, igmp_msg_len); - if (checksum != recv_checksum) { - zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x", - from_str, ifp->name, recv_checksum, checksum); - return -1; + igmp_v3_recv_query(igmp, from_str, igmp_msg); } - num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET)); - if (num_groups < 1) { - zlog_warn("Recv IGMP report v3 from %s on %s: missing group records", - from_str, ifp->name); - return -1; - } - - if (PIM_DEBUG_IGMP_PACKETS) { - zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d", - from_str, ifp->name, igmp_msg_len, checksum, num_groups); - } - - group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET; - - /* Scan groups */ - for (i = 0; i < num_groups; ++i) { - struct in_addr rec_group; - uint8_t *sources; - uint8_t *src; - int rec_type; - int rec_auxdatalen; - int rec_num_sources; - int j; - struct prefix lncb; - struct prefix g; - - if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) { - zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end", - from_str, ifp->name); - return -1; - } - - rec_type = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET]; - rec_auxdatalen = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET]; - rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET)); - - //rec_group = *(struct in_addr *)(group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET); - memcpy(&rec_group, group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, sizeof(struct in_addr)); - - if (PIM_DEBUG_IGMP_PACKETS) { - zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s", - from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group)); - } - - /* Scan sources */ - - sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET; - - for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) { - - if ((src + 4) > report_pastend) { - zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end", - from_str, ifp->name); - return -1; - } - - if (PIM_DEBUG_IGMP_PACKETS) { - char src_str[200]; - - if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str))) - sprintf(src_str, "<source?>"); - - zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s", - from_str, ifp->name, i, inet_ntoa(rec_group), src_str); - } - } /* for (sources) */ - - - lncb.family = AF_INET; - lncb.u.prefix4.s_addr = 0x000000E0; - lncb.prefixlen = 24; - - g.family = AF_INET; - g.u.prefix4 = rec_group; - g.prefixlen = 32; - /* - * If we receive a igmp report with the group in 224.0.0.0/24 - * then we should ignore it - */ - if (prefix_match(&lncb, &g)) - local_ncb = 1; - - if (!local_ncb) - switch (rec_type) { - case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE: - igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); - break; - case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE: - igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); - break; - case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE: - igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); - break; - case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE: - igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); - break; - case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES: - igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); - break; - case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES: - igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); - break; - default: - zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d", - from_str, ifp->name, rec_type); - } - - group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2); - local_ncb = 0; - - } /* for (group records) */ - return 0; } @@ -614,66 +368,10 @@ static void on_trace(const char *label, } } -static int igmp_v2_report(struct igmp_sock *igmp, - struct in_addr from, const char *from_str, - char *igmp_msg, int igmp_msg_len) -{ - struct interface *ifp = igmp->interface; - struct igmp_group *group; - struct in_addr group_addr; - - on_trace(__PRETTY_FUNCTION__, igmp->interface, from); - - if (igmp_msg_len != IGMP_V12_MSG_SIZE) { - zlog_warn("Recv IGMP report v2 from %s on %s: size=%d other than correct=%d", - from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); - return -1; - } - - if (PIM_DEBUG_IGMP_TRACE) { - zlog_warn("%s %s: FIXME WRITEME", - __FILE__, __PRETTY_FUNCTION__); - } - - //group_addr = *(struct in_addr *)(igmp_msg + 4); - memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); - - /* non-existant group is created as INCLUDE {empty} */ - group = igmp_add_group_by_addr(igmp, group_addr); - if (!group) { - return -1; - } - - group->last_igmp_v2_report_dsec = pim_time_monotonic_dsec(); - - return 0; -} - -static int igmp_v2_leave(struct igmp_sock *igmp, - struct in_addr from, const char *from_str, - char *igmp_msg, int igmp_msg_len) -{ - struct interface *ifp = igmp->interface; - - on_trace(__PRETTY_FUNCTION__, igmp->interface, from); - - if (igmp_msg_len != IGMP_V12_MSG_SIZE) { - zlog_warn("Recv IGMP leave v2 from %s on %s: size=%d other than correct=%d", - from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); - return -1; - } - - if (PIM_DEBUG_IGMP_TRACE) { - zlog_warn("%s %s: FIXME WRITEME", - __FILE__, __PRETTY_FUNCTION__); - } - - return 0; -} - -static int igmp_v1_report(struct igmp_sock *igmp, - struct in_addr from, const char *from_str, - char *igmp_msg, int igmp_msg_len) +static int +igmp_v1_recv_report (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) { struct interface *ifp = igmp->interface; struct igmp_group *group; @@ -692,7 +390,6 @@ static int igmp_v1_report(struct igmp_sock *igmp, __FILE__, __PRETTY_FUNCTION__); } - //group_addr = *(struct in_addr *)(igmp_msg + 4); memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); /* non-existant group is created as INCLUDE {empty} */ @@ -791,26 +488,26 @@ int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len) return -1; } - return recv_igmp_query(igmp, query_version, max_resp_code, + return igmp_recv_query(igmp, query_version, max_resp_code, ip_hdr->ip_src, from_str, igmp_msg, igmp_msg_len); } case PIM_IGMP_V3_MEMBERSHIP_REPORT: - return igmp_v3_report(igmp, ip_hdr->ip_src, from_str, - igmp_msg, igmp_msg_len); + return igmp_v3_recv_report(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); case PIM_IGMP_V2_MEMBERSHIP_REPORT: - return igmp_v2_report(igmp, ip_hdr->ip_src, from_str, - igmp_msg, igmp_msg_len); + return igmp_v2_recv_report(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); case PIM_IGMP_V1_MEMBERSHIP_REPORT: - return igmp_v1_report(igmp, ip_hdr->ip_src, from_str, - igmp_msg, igmp_msg_len); + return igmp_v1_recv_report(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); case PIM_IGMP_V2_LEAVE_GROUP: - return igmp_v2_leave(igmp, ip_hdr->ip_src, from_str, - igmp_msg, igmp_msg_len); + return igmp_v2_recv_leave(igmp, ip_hdr->ip_src, from_str, + igmp_msg, igmp_msg_len); } zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type); @@ -895,11 +592,11 @@ void pim_igmp_general_query_off(struct igmp_sock *igmp) /* Issue IGMP general query */ static int pim_igmp_general_query(struct thread *t) { - char query_buf[PIM_IGMP_BUFSIZE_WRITE]; struct igmp_sock *igmp; struct in_addr dst_addr; struct in_addr group_addr; struct pim_interface *pim_ifp; + int query_buf_size; zassert(t); @@ -911,6 +608,14 @@ static int pim_igmp_general_query(struct thread *t) pim_ifp = igmp->interface->info; + if (pim_ifp->igmp_version == 3) { + query_buf_size = PIM_IGMP_BUFSIZE_WRITE; + } else { + query_buf_size = IGMP_V12_MSG_SIZE; + } + + char query_buf[query_buf_size]; + /* RFC3376: 4.1.12. IP Destination Addresses for Queries @@ -933,124 +638,25 @@ static int pim_igmp_general_query(struct thread *t) querier_str, dst_str, igmp->interface->name); } - pim_igmp_send_membership_query(0 /* igmp_group */, - igmp->fd, - igmp->interface->name, - query_buf, - sizeof(query_buf), - 0 /* num_sources */, - dst_addr, - group_addr, - pim_ifp->igmp_query_max_response_time_dsec, - 1 /* s_flag: always set for general queries */, - igmp->querier_robustness_variable, - igmp->querier_query_interval); + igmp_send_query (pim_ifp->igmp_version, + 0 /* igmp_group */, + igmp->fd, + igmp->interface->name, + query_buf, + sizeof(query_buf), + 0 /* num_sources */, + dst_addr, + group_addr, + pim_ifp->igmp_query_max_response_time_dsec, + 1 /* s_flag: always set for general queries */, + igmp->querier_robustness_variable, + igmp->querier_query_interval); pim_igmp_general_query_on(igmp); return 0; } -static int pim_igmp_read(struct thread *t); - -static void igmp_read_on(struct igmp_sock *igmp) -{ - zassert(igmp); - - if (PIM_DEBUG_IGMP_TRACE_DETAIL) { - zlog_debug("Scheduling READ event on IGMP socket fd=%d", - igmp->fd); - } - igmp->t_igmp_read = NULL; - zassert(!igmp->t_igmp_read); - THREAD_READ_ON(master, igmp->t_igmp_read, pim_igmp_read, igmp, igmp->fd); -} - -static int pim_igmp_read(struct thread *t) -{ - struct igmp_sock *igmp; - int fd; - struct sockaddr_in from; - struct sockaddr_in to; - socklen_t fromlen = sizeof(from); - socklen_t tolen = sizeof(to); - uint8_t buf[PIM_IGMP_BUFSIZE_READ]; - int len; - ifindex_t ifindex = -1; - int result = -1; /* defaults to bad */ - - zassert(t); - - igmp = THREAD_ARG(t); - - zassert(igmp); - - fd = THREAD_FD(t); - - zassert(fd == igmp->fd); - - len = pim_socket_recvfromto(fd, buf, sizeof(buf), - &from, &fromlen, - &to, &tolen, - &ifindex); - if (len < 0) { - zlog_warn("Failure receiving IP IGMP packet on fd=%d: errno=%d: %s", - fd, errno, safe_strerror(errno)); - goto done; - } - - if (PIM_DEBUG_IGMP_PACKETS) { - char from_str[100]; - char to_str[100]; - - if (!inet_ntop(AF_INET, &from.sin_addr, from_str, sizeof(from_str))) - sprintf(from_str, "<from?>"); - if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str))) - sprintf(to_str, "<to?>"); - - zlog_debug("Recv IP IGMP pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)", - len, from_str, to_str, fd, ifindex, igmp->interface->ifindex); - } - -#ifdef PIM_CHECK_RECV_IFINDEX_SANITY - /* ifindex sanity check */ - if (ifindex != igmp->interface->ifindex) { - char from_str[100]; - char to_str[100]; - struct interface *ifp; - - if (!inet_ntop(AF_INET, &from.sin_addr, from_str , sizeof(from_str))) - sprintf(from_str, "<from?>"); - if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str))) - sprintf(to_str, "<to?>"); - - ifp = if_lookup_by_index(ifindex); - if (ifp) { - zassert(ifindex == ifp->ifindex); - } - -#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH - zlog_warn("Interface mismatch: recv IGMP pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)", - from_str, to_str, fd, - ifindex, ifp ? ifp->name : "<if-notfound>", - igmp->interface->ifindex, igmp->interface->name); -#endif - goto done; - } -#endif - - if (pim_igmp_packet(igmp, (char *)buf, len)) { - goto done; - } - - result = 0; /* good */ - - done: - igmp_read_on(igmp); - - return result; -} - static void sock_close(struct igmp_sock *igmp) { pim_igmp_other_querier_timer_off(igmp); @@ -1219,8 +825,6 @@ static struct igmp_sock *igmp_sock_new(int fd, igmp->querier_query_interval = pim_ifp->igmp_default_query_interval; */ igmp_startup_mode_on(igmp); - - igmp_read_on(igmp); pim_igmp_general_query_on(igmp); return igmp; @@ -1421,6 +1025,7 @@ struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp, group->last_igmp_v1_report_dsec = -1; group->last_igmp_v2_report_dsec = -1; group->group_creation = pim_time_monotonic_sec(); + group->igmp_version = IGMP_DEFAULT_VERSION; /* initialize new group as INCLUDE {empty} */ group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */ @@ -1449,3 +1054,32 @@ struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp, return group; } + +void +igmp_send_query (int igmp_version, + struct igmp_group *group, + int fd, + const char *ifname, + char *query_buf, + int query_buf_size, + int num_sources, + struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec, + uint8_t s_flag, + uint8_t querier_robustness_variable, + uint16_t querier_query_interval) +{ + if (igmp_version == 3) { + igmp_v3_send_query (group, fd, ifname, query_buf, + query_buf_size, num_sources, + dst_addr, group_addr, + query_max_response_time_dsec, s_flag, + querier_robustness_variable, + querier_query_interval); + } else if (igmp_version == 2) { + igmp_v2_send_query (group, fd, ifname, query_buf, + dst_addr, group_addr, + query_max_response_time_dsec); + } +} diff --git a/pimd/pim_igmp.h b/pimd/pim_igmp.h index 2067bb30a..fa2c255dd 100644 --- a/pimd/pim_igmp.h +++ b/pimd/pim_igmp.h @@ -52,6 +52,7 @@ #define IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET (2) #define IGMP_V3_GROUP_RECORD_GROUP_OFFSET (4) #define IGMP_V3_GROUP_RECORD_SOURCE_OFFSET (8) +#define IGMP_CHECKSUM_OFFSET (2) /* RFC 3376: 8.1. Robustness Variable - Default: 2 */ #define IGMP_DEFAULT_ROBUSTNESS_VARIABLE (2) @@ -65,6 +66,8 @@ /* RFC 3376: 8.8. Last Member Query Interval - Default: 10 deciseconds */ #define IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC (10) +#define IGMP_DEFAULT_VERSION (3) + struct igmp_join { struct in_addr group_addr; struct in_addr source_addr; @@ -152,6 +155,9 @@ struct igmp_group { since sources have their counters) */ int group_specific_query_retransmit_count; + /* compatibility mode - igmp v1, v2 or v3 */ + int igmp_version; + struct in_addr group_addr; int group_filtermode_isexcl; /* 0=INCLUDE, 1=EXCLUDE */ struct list *group_source_list; /* list of struct igmp_source */ @@ -176,4 +182,18 @@ void igmp_group_timer_on(struct igmp_group *group, struct igmp_source * source_new (struct igmp_group *group, struct in_addr src_addr); + +void igmp_send_query(int igmp_version, + struct igmp_group *group, + int fd, + const char *ifname, + char *query_buf, + int query_buf_size, + int num_sources, + struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec, + uint8_t s_flag, + uint8_t querier_robustness_variable, + uint16_t querier_query_interval); #endif /* PIM_IGMP_H */ diff --git a/pimd/pim_igmpv2.c b/pimd/pim_igmpv2.c new file mode 100644 index 000000000..cc78f18d7 --- /dev/null +++ b/pimd/pim_igmpv2.c @@ -0,0 +1,190 @@ +/* + * PIM for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + * Daniel Walton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "pimd.h" +#include "pim_igmp.h" +#include "pim_igmpv2.h" +#include "pim_igmpv3.h" +#include "pim_str.h" +#include "pim_time.h" +#include "pim_util.h" + + +static void +on_trace (const char *label, + struct interface *ifp, struct in_addr from) +{ + if (PIM_DEBUG_IGMP_TRACE) { + char from_str[100]; + pim_inet4_dump("<from?>", from, from_str, sizeof(from_str)); + zlog_debug("%s: from %s on %s", + label, from_str, ifp->name); + } +} + +void +igmp_v2_send_query (struct igmp_group *group, + int fd, + const char *ifname, + char *query_buf, + struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec) +{ + ssize_t msg_size = 8; + uint8_t max_resp_code; + ssize_t sent; + struct sockaddr_in to; + socklen_t tolen; + uint16_t checksum; + + /* max_resp_code must be non-zero else this will look like an IGMP v1 query */ + max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec); + zassert(max_resp_code > 0); + + query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY; + query_buf[1] = max_resp_code; + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = 0; /* for computing checksum */ + memcpy(query_buf+4, &group_addr, sizeof(struct in_addr)); + + checksum = in_cksum(query_buf, msg_size); + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum; + + if (PIM_DEBUG_IGMP_PACKETS) { + char dst_str[100]; + char group_str[100]; + pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); + zlog_debug("Send IGMPv2 QUERY to %s on %s for group %s", + dst_str, ifname, group_str); + } + + memset(&to, 0, sizeof(to)); + to.sin_family = AF_INET; + to.sin_addr = dst_addr; + tolen = sizeof(to); + + sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, + (struct sockaddr *)&to, tolen); + if (sent != (ssize_t) msg_size) { + char dst_str[100]; + char group_str[100]; + pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); + if (sent < 0) { + zlog_warn("Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s", + dst_str, ifname, group_str, msg_size, errno, safe_strerror(errno)); + } + else { + zlog_warn("Send IGMPv2 QUERY failed due to %s on %s: group=%s msg_size=%zd: sent=%zd", + dst_str, ifname, group_str, msg_size, sent); + } + return; + } +} + +int +igmp_v2_recv_report (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) +{ + struct interface *ifp = igmp->interface; + struct in_addr group_addr; + char group_str[100]; + + on_trace(__PRETTY_FUNCTION__, igmp->interface, from); + + if (igmp_msg_len != IGMP_V12_MSG_SIZE) { + zlog_warn("Recv IGMPv2 REPORT from %s on %s: size=%d other than correct=%d", + from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); + return -1; + } + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + if (PIM_DEBUG_IGMP_PACKETS) { + pim_inet4_dump("<dst?>", group_addr, group_str, sizeof(group_str)); + zlog_debug("Recv IGMPv2 REPORT from %s on %s for %s", + from_str, ifp->name, group_str); + } + + /* + * RFC 3376 + * 7.3.2. In the Presence of Older Version Group Members + * + * When Group Compatibility Mode is IGMPv2, a router internally + * translates the following IGMPv2 messages for that group to their + * IGMPv3 equivalents: + * + * IGMPv2 Message IGMPv3 Equivalent + * -------------- ----------------- + * Report IS_EX( {} ) + * Leave TO_IN( {} ) + */ + igmpv3_report_isex (igmp, from, group_addr, 0, NULL, 1); + + return 0; +} + +int +igmp_v2_recv_leave (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) +{ + struct interface *ifp = igmp->interface; + struct in_addr group_addr; + char group_str[100]; + + on_trace(__PRETTY_FUNCTION__, igmp->interface, from); + + if (igmp_msg_len != IGMP_V12_MSG_SIZE) { + zlog_warn("Recv IGMPv2 LEAVE from %s on %s: size=%d other than correct=%d", + from_str, ifp->name, igmp_msg_len, IGMP_V12_MSG_SIZE); + return -1; + } + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + if (PIM_DEBUG_IGMP_PACKETS) { + pim_inet4_dump("<dst?>", group_addr, group_str, sizeof(group_str)); + zlog_debug("Recv IGMPv2 LEAVE from %s on %s for %s", + from_str, ifp->name, group_str); + } + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + + /* + * RFC 3376 + * 7.3.2. In the Presence of Older Version Group Members + * + * When Group Compatibility Mode is IGMPv2, a router internally + * translates the following IGMPv2 messages for that group to their + * IGMPv3 equivalents: + * + * IGMPv2 Message IGMPv3 Equivalent + * -------------- ----------------- + * Report IS_EX( {} ) + * Leave TO_IN( {} ) + */ + igmpv3_report_toin (igmp, from, group_addr, 0, NULL); + + return 0; +} diff --git a/pimd/pim_igmpv2.h b/pimd/pim_igmpv2.h new file mode 100644 index 000000000..10a247772 --- /dev/null +++ b/pimd/pim_igmpv2.h @@ -0,0 +1,41 @@ +/* + * PIM for Quagga + * Copyright (C) 2016 Cumulus Networks, Inc. + * Daniel Walton + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#ifndef PIM_IGMPV2_H +#define PIM_IGMPV2_H + +void igmp_v2_send_query (struct igmp_group *group, + int fd, + const char *ifname, + char *query_buf, + struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec); + +int igmp_v2_recv_report (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len); + +int igmp_v2_recv_leave (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len); + +#endif /* PIM_IGMPV2_H */ diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c index ba75fc221..21a5176a9 100644 --- a/pimd/pim_igmpv3.c +++ b/pimd/pim_igmpv3.c @@ -58,50 +58,6 @@ static void on_trace(const char *label, } } -int igmp_group_compat_mode(const struct igmp_sock *igmp, - const struct igmp_group *group) -{ - struct pim_interface *pim_ifp; - int64_t now_dsec; - long older_host_present_interval_dsec; - - zassert(igmp); - zassert(igmp->interface); - zassert(igmp->interface->info); - - pim_ifp = igmp->interface->info; - - /* - RFC 3376: 8.13. Older Host Present Interval - - This value MUST be ((the Robustness Variable) times (the Query - Interval)) plus (one Query Response Interval). - - older_host_present_interval_dsec = \ - igmp->querier_robustness_variable * \ - 10 * igmp->querier_query_interval + \ - pim_ifp->query_max_response_time_dsec; - */ - older_host_present_interval_dsec = - PIM_IGMP_OHPI_DSEC(igmp->querier_robustness_variable, - igmp->querier_query_interval, - pim_ifp->igmp_query_max_response_time_dsec); - - now_dsec = pim_time_monotonic_dsec(); - if (now_dsec < 1) { - /* broken timer logged by pim_time_monotonic_dsec() */ - return 3; - } - - if ((now_dsec - group->last_igmp_v1_report_dsec) < older_host_present_interval_dsec) - return 1; /* IGMPv1 */ - - if ((now_dsec - group->last_igmp_v2_report_dsec) < older_host_present_interval_dsec) - return 2; /* IGMPv2 */ - - return 3; /* IGMPv3 */ -} - void igmp_group_reset_gmi(struct igmp_group *group) { long group_membership_interval_msec; @@ -705,7 +661,8 @@ static void isex_incl(struct igmp_group *group, void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, - int num_sources, struct in_addr *sources) + int num_sources, struct in_addr *sources, + int from_igmp_v2_report) { struct interface *ifp = igmp->interface; struct igmp_group *group; @@ -719,6 +676,10 @@ void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from, return; } + /* So we can display how we learned the group in our show command output */ + if (from_igmp_v2_report) + group->igmp_version = 2; + if (group->group_filtermode_isexcl) { /* EXCLUDE mode */ isex_excl(group, num_sources, sources); @@ -1050,17 +1011,25 @@ void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from, */ static void group_retransmit_group(struct igmp_group *group) { - char query_buf[PIM_IGMP_BUFSIZE_WRITE]; struct igmp_sock *igmp; struct pim_interface *pim_ifp; long lmqc; /* Last Member Query Count */ long lmqi_msec; /* Last Member Query Interval */ long lmqt_msec; /* Last Member Query Time */ int s_flag; + int query_buf_size; igmp = group->group_igmp_sock; pim_ifp = igmp->interface->info; + if (pim_ifp->igmp_version == 3) { + query_buf_size = PIM_IGMP_BUFSIZE_WRITE; + } else { + query_buf_size = IGMP_V12_MSG_SIZE; + } + + char query_buf[query_buf_size]; + lmqc = igmp->querier_robustness_variable; lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec; lmqt_msec = lmqc * lmqi_msec; @@ -1090,18 +1059,19 @@ static void group_retransmit_group(struct igmp_group *group) interest. */ - pim_igmp_send_membership_query(group, - igmp->fd, - igmp->interface->name, - query_buf, - sizeof(query_buf), - 0 /* num_sources_tosend */, - group->group_addr /* dst_addr */, - group->group_addr /* group_addr */, - pim_ifp->igmp_specific_query_max_response_time_dsec, - s_flag, - igmp->querier_robustness_variable, - igmp->querier_query_interval); + igmp_send_query(pim_ifp->igmp_version, + group, + igmp->fd, + igmp->interface->name, + query_buf, + sizeof(query_buf), + 0 /* num_sources_tosend */, + group->group_addr /* dst_addr */, + group->group_addr /* group_addr */, + pim_ifp->igmp_specific_query_max_response_time_dsec, + s_flag, + igmp->querier_robustness_variable, + igmp->querier_query_interval); } /* @@ -1208,19 +1178,19 @@ static int group_retransmit_sources(struct igmp_group *group, interest. */ - pim_igmp_send_membership_query(group, - igmp->fd, - igmp->interface->name, - query_buf1, - sizeof(query_buf1), - num_sources_tosend1, - group->group_addr, - group->group_addr, - pim_ifp->igmp_specific_query_max_response_time_dsec, - 1 /* s_flag */, - igmp->querier_robustness_variable, - igmp->querier_query_interval); - + igmp_send_query(pim_ifp->igmp_version, + group, + igmp->fd, + igmp->interface->name, + query_buf1, + sizeof(query_buf1), + num_sources_tosend1, + group->group_addr, + group->group_addr, + pim_ifp->igmp_specific_query_max_response_time_dsec, + 1 /* s_flag */, + igmp->querier_robustness_variable, + igmp->querier_query_interval); } } /* send_with_sflag_set */ @@ -1250,19 +1220,19 @@ static int group_retransmit_sources(struct igmp_group *group, interest. */ - pim_igmp_send_membership_query(group, - igmp->fd, - igmp->interface->name, - query_buf2, - sizeof(query_buf2), - num_sources_tosend2, - group->group_addr, - group->group_addr, - pim_ifp->igmp_specific_query_max_response_time_dsec, - 0 /* s_flag */, - igmp->querier_robustness_variable, - igmp->querier_query_interval); - + igmp_send_query(pim_ifp->igmp_version, + group, + igmp->fd, + igmp->interface->name, + query_buf2, + sizeof(query_buf2), + num_sources_tosend2, + group->group_addr, + group->group_addr, + pim_ifp->igmp_specific_query_max_response_time_dsec, + 0 /* s_flag */, + igmp->querier_robustness_variable, + igmp->querier_query_interval); } } @@ -1623,32 +1593,19 @@ void igmp_source_timer_lower_to_lmqt(struct igmp_source *source) igmp_source_timer_on(group, source, lmqt_msec); } -/* - Copy sources to message: - - struct in_addr *sources = (struct in_addr *)(query_buf + IGMP_V3_SOURCES_OFFSET); - if (num_sources > 0) { - struct listnode *node; - struct igmp_source *src; - int i = 0; - - for (ALL_LIST_ELEMENTS_RO(source_list, node, src)) { - sources[i++] = src->source_addr; - } - } -*/ -void pim_igmp_send_membership_query(struct igmp_group *group, - int fd, - const char *ifname, - char *query_buf, - int query_buf_size, - int num_sources, - struct in_addr dst_addr, - struct in_addr group_addr, - int query_max_response_time_dsec, - uint8_t s_flag, - uint8_t querier_robustness_variable, - uint16_t querier_query_interval) +void +igmp_v3_send_query (struct igmp_group *group, + int fd, + const char *ifname, + char *query_buf, + int query_buf_size, + int num_sources, + struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec, + uint8_t s_flag, + uint8_t querier_robustness_variable, + uint16_t querier_query_interval) { ssize_t msg_size; uint8_t max_resp_code; @@ -1688,7 +1645,7 @@ void pim_igmp_send_membership_query(struct igmp_group *group, query_buf[0] = PIM_IGMP_MEMBERSHIP_QUERY; query_buf[1] = max_resp_code; - *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET) = 0; /* for computing checksum */ + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = 0; /* for computing checksum */ memcpy(query_buf+4, &group_addr, sizeof(struct in_addr)); query_buf[8] = (s_flag << 3) | querier_robustness_variable; @@ -1696,18 +1653,17 @@ void pim_igmp_send_membership_query(struct igmp_group *group, *(uint16_t *)(query_buf + IGMP_V3_NUMSOURCES_OFFSET) = htons(num_sources); checksum = in_cksum(query_buf, msg_size); - *(uint16_t *)(query_buf + IGMP_V3_CHECKSUM_OFFSET) = checksum; + *(uint16_t *)(query_buf + IGMP_CHECKSUM_OFFSET) = checksum; if (PIM_DEBUG_IGMP_PACKETS) { char dst_str[100]; char group_str[100]; pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); - zlog_debug("%s: to %s on %s: group=%s sources=%d msg_size=%zd s_flag=%x QRV=%u QQI=%u QQIC=%02x checksum=%x", - __PRETTY_FUNCTION__, - dst_str, ifname, group_str, num_sources, - msg_size, s_flag, querier_robustness_variable, - querier_query_interval, qqic, checksum); + zlog_debug("Send IGMPv3 query to %s on %s for group %s, sources=%d msg_size=%zd s_flag=%x QRV=%u QQI=%u QQIC=%02x", + dst_str, ifname, group_str, + num_sources, msg_size, s_flag, querier_robustness_variable, + querier_query_interval, qqic); } memset(&to, 0, sizeof(to)); @@ -1723,16 +1679,12 @@ void pim_igmp_send_membership_query(struct igmp_group *group, pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str)); pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); if (sent < 0) { - zlog_warn("%s: sendto() failure to %s on %s: group=%s msg_size=%zd: errno=%d: %s", - __PRETTY_FUNCTION__, - dst_str, ifname, group_str, msg_size, - errno, safe_strerror(errno)); + zlog_warn("Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: errno=%d: %s", + dst_str, ifname, group_str, msg_size, errno, safe_strerror(errno)); } else { - zlog_warn("%s: sendto() partial to %s on %s: group=%s msg_size=%zd: sent=%zd", - __PRETTY_FUNCTION__, - dst_str, ifname, group_str, - msg_size, sent); + zlog_warn("Send IGMPv3 query failed due to %s on %s: group=%s msg_size=%zd: sent=%zd", + dst_str, ifname, group_str, msg_size, sent); } return; } @@ -1760,5 +1712,268 @@ void pim_igmp_send_membership_query(struct igmp_group *group, dst_str, ifname, group_str, num_sources); } } +} + +void +igmp_v3_recv_query (struct igmp_sock *igmp, const char *from_str, char *igmp_msg) +{ + struct interface *ifp; + struct pim_interface *pim_ifp; + struct in_addr group_addr; + uint8_t resv_s_qrv = 0; + uint8_t s_flag = 0; + uint8_t qrv = 0; + int i; + + memcpy(&group_addr, igmp_msg + 4, sizeof(struct in_addr)); + ifp = igmp->interface; + pim_ifp = ifp->info; + + /* + * RFC 3376: 4.1.6. QRV (Querier's Robustness Variable) + * + * Routers adopt the QRV value from the most recently received Query + * as their own [Robustness Variable] value, unless that most + * recently received QRV was zero, in which case the receivers use + * the default [Robustness Variable] value specified in section 8.1 + * or a statically configured value. + */ + resv_s_qrv = igmp_msg[8]; + qrv = 7 & resv_s_qrv; + igmp->querier_robustness_variable = qrv ? qrv : pim_ifp->igmp_default_robustness_variable; + + /* + * RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code) + * + * Multicast routers that are not the current querier adopt the QQI + * value from the most recently received Query as their own [Query + * Interval] value, unless that most recently received QQI was zero, + * in which case the receiving routers use the default. + */ + if (igmp->t_other_querier_timer) { + /* other querier present */ + uint8_t qqic; + uint16_t qqi; + qqic = igmp_msg[9]; + qqi = igmp_msg_decode8to16(qqic); + igmp->querier_query_interval = qqi ? qqi : pim_ifp->igmp_default_query_interval; + + if (PIM_DEBUG_IGMP_TRACE) { + char ifaddr_str[100]; + pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str)); + zlog_debug("Querier %s new query interval is %s QQI=%u sec (recv QQIC=%02x from %s)", + ifaddr_str, + qqi ? "recv-non-default" : "default", + igmp->querier_query_interval, + qqic, + from_str); + } + } + + /* + * RFC 3376: 6.6.1. Timer Updates + * + * When a router sends or receives a query with a clear Suppress + * Router-Side Processing flag, it must update its timers to reflect + * the correct timeout values for the group or sources being queried. + * + * General queries don't trigger timer update. + */ + s_flag = (1 << 3) & resv_s_qrv; + + if (!s_flag) { + /* s_flag is clear */ + + if (PIM_INADDR_IS_ANY(group_addr)) { + /* this is a general query */ + /* log that general query should have the s_flag set */ + zlog_warn("General IGMP query v3 from %s on %s: Suppress Router-Side Processing flag is clear", + from_str, ifp->name); + } else { + struct igmp_group *group; + + /* this is a non-general query: perform timer updates */ + + group = find_group_by_addr(igmp, group_addr); + if (group) { + int recv_num_sources = ntohs(*(uint16_t *)(igmp_msg + IGMP_V3_NUMSOURCES_OFFSET)); + + /* + * RFC 3376: 6.6.1. Timer Updates + * Query Q(G,A): Source Timer for sources in A are lowered to LMQT + * Query Q(G): Group Timer is lowered to LMQT + */ + if (recv_num_sources < 1) { + /* Query Q(G): Group Timer is lowered to LMQT */ + + igmp_group_timer_lower_to_lmqt(group); + } else { + /* Query Q(G,A): Source Timer for sources in A are lowered to LMQT */ + + /* Scan sources in query and lower their timers to LMQT */ + struct in_addr *sources = (struct in_addr *)(igmp_msg + IGMP_V3_SOURCES_OFFSET); + for (i = 0; i < recv_num_sources; ++i) { + struct in_addr src_addr; + struct igmp_source *src; + memcpy(&src_addr, sources + i, sizeof(struct in_addr)); + src = igmp_find_source_by_addr(group, src_addr); + if (src) { + igmp_source_timer_lower_to_lmqt(src); + } + } + } + } else { + char group_str[100]; + pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str)); + zlog_warn("IGMP query v3 from %s on %s: could not find group %s for timer update", + from_str, ifp->name, group_str); + } + } + } /* s_flag is clear: timer updates */ +} + +int +igmp_v3_recv_report (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len) +{ + uint16_t recv_checksum; + uint16_t checksum; + int num_groups; + uint8_t *group_record; + uint8_t *report_pastend = (uint8_t *) igmp_msg + igmp_msg_len; + struct interface *ifp = igmp->interface; + int i; + int local_ncb = 0; + + if (igmp_msg_len < IGMP_V3_MSG_MIN_SIZE) { + zlog_warn("Recv IGMP report v3 from %s on %s: size=%d shorter than minimum=%d", + from_str, ifp->name, igmp_msg_len, IGMP_V3_MSG_MIN_SIZE); + return -1; + } + + recv_checksum = *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET); + + /* for computing checksum */ + *(uint16_t *) (igmp_msg + IGMP_CHECKSUM_OFFSET) = 0; + + checksum = in_cksum(igmp_msg, igmp_msg_len); + if (checksum != recv_checksum) { + zlog_warn("Recv IGMP report v3 from %s on %s: checksum mismatch: received=%x computed=%x", + from_str, ifp->name, recv_checksum, checksum); + return -1; + } + + num_groups = ntohs(*(uint16_t *) (igmp_msg + IGMP_V3_REPORT_NUMGROUPS_OFFSET)); + if (num_groups < 1) { + zlog_warn("Recv IGMP report v3 from %s on %s: missing group records", + from_str, ifp->name); + return -1; + } + if (PIM_DEBUG_IGMP_PACKETS) { + zlog_debug("Recv IGMP report v3 from %s on %s: size=%d checksum=%x groups=%d", + from_str, ifp->name, igmp_msg_len, checksum, num_groups); + } + + group_record = (uint8_t *) igmp_msg + IGMP_V3_REPORT_GROUPPRECORD_OFFSET; + + /* Scan groups */ + for (i = 0; i < num_groups; ++i) { + struct in_addr rec_group; + uint8_t *sources; + uint8_t *src; + int rec_type; + int rec_auxdatalen; + int rec_num_sources; + int j; + struct prefix lncb; + struct prefix g; + + if ((group_record + IGMP_V3_GROUP_RECORD_MIN_SIZE) > report_pastend) { + zlog_warn("Recv IGMP report v3 from %s on %s: group record beyond report end", + from_str, ifp->name); + return -1; + } + + rec_type = group_record[IGMP_V3_GROUP_RECORD_TYPE_OFFSET]; + rec_auxdatalen = group_record[IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET]; + rec_num_sources = ntohs(* (uint16_t *) (group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET)); + + memcpy(&rec_group, group_record + IGMP_V3_GROUP_RECORD_GROUP_OFFSET, sizeof(struct in_addr)); + + if (PIM_DEBUG_IGMP_PACKETS) { + zlog_debug("Recv IGMP report v3 from %s on %s: record=%d type=%d auxdatalen=%d sources=%d group=%s", + from_str, ifp->name, i, rec_type, rec_auxdatalen, rec_num_sources, inet_ntoa(rec_group)); + } + + /* Scan sources */ + + sources = group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET; + + for (j = 0, src = sources; j < rec_num_sources; ++j, src += 4) { + + if ((src + 4) > report_pastend) { + zlog_warn("Recv IGMP report v3 from %s on %s: group source beyond report end", + from_str, ifp->name); + return -1; + } + + if (PIM_DEBUG_IGMP_PACKETS) { + char src_str[200]; + + if (!inet_ntop(AF_INET, src, src_str , sizeof(src_str))) + sprintf(src_str, "<source?>"); + + zlog_debug("Recv IGMP report v3 from %s on %s: record=%d group=%s source=%s", + from_str, ifp->name, i, inet_ntoa(rec_group), src_str); + } + } /* for (sources) */ + + + lncb.family = AF_INET; + lncb.u.prefix4.s_addr = 0x000000E0; + lncb.prefixlen = 24; + + g.family = AF_INET; + g.u.prefix4 = rec_group; + g.prefixlen = 32; + /* + * If we receive a igmp report with the group in 224.0.0.0/24 + * then we should ignore it + */ + if (prefix_match(&lncb, &g)) + local_ncb = 1; + + if (!local_ncb) + switch (rec_type) { + case IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE: + igmpv3_report_isin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); + break; + case IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE: + igmpv3_report_isex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources, 0); + break; + case IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE: + igmpv3_report_toin(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); + break; + case IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE: + igmpv3_report_toex(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); + break; + case IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES: + igmpv3_report_allow(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); + break; + case IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES: + igmpv3_report_block(igmp, from, rec_group, rec_num_sources, (struct in_addr *) sources); + break; + default: + zlog_warn("Recv IGMP report v3 from %s on %s: unknown record type: type=%d", + from_str, ifp->name, rec_type); + } + + group_record += 8 + (rec_num_sources << 2) + (rec_auxdatalen << 2); + local_ncb = 0; + + } /* for (group records) */ + + return 0; } diff --git a/pimd/pim_igmpv3.h b/pimd/pim_igmpv3.h index 37baca5c8..33a918bad 100644 --- a/pimd/pim_igmpv3.h +++ b/pimd/pim_igmpv3.h @@ -31,6 +31,13 @@ #define IGMP_V3_NUMSOURCES_OFFSET (10) #define IGMP_V3_SOURCES_OFFSET (12) +#define IGMP_GRP_REC_TYPE_MODE_IS_INCLUDE (1) +#define IGMP_GRP_REC_TYPE_MODE_IS_EXCLUDE (2) +#define IGMP_GRP_REC_TYPE_CHANGE_TO_INCLUDE_MODE (3) +#define IGMP_GRP_REC_TYPE_CHANGE_TO_EXCLUDE_MODE (4) +#define IGMP_GRP_REC_TYPE_ALLOW_NEW_SOURCES (5) +#define IGMP_GRP_REC_TYPE_BLOCK_OLD_SOURCES (6) + /* GMI: Group Membership Interval */ #define PIM_IGMP_GMI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * (qri_dsec)) @@ -55,15 +62,13 @@ void igmp_source_free(struct igmp_source *source); void igmp_source_delete(struct igmp_source *source); void igmp_source_delete_expired(struct list *source_list); -int igmp_group_compat_mode(const struct igmp_sock *igmp, - const struct igmp_group *group); - void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); void igmpv3_report_isex(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, - int num_sources, struct in_addr *sources); + int num_sources, struct in_addr *sources, + int from_igmp_v2_report); void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from, struct in_addr group_addr, int num_sources, struct in_addr *sources); @@ -83,17 +88,24 @@ void igmp_source_timer_lower_to_lmqt(struct igmp_source *source); struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group, struct in_addr src_addr); -void pim_igmp_send_membership_query(struct igmp_group *group, - int fd, - const char *ifname, - char *query_buf, - int query_buf_size, - int num_sources, - struct in_addr dst_addr, - struct in_addr group_addr, - int query_max_response_time_dsec, - uint8_t s_flag, - uint8_t querier_robustness_variable, - uint16_t querier_query_interval); +void igmp_v3_send_query (struct igmp_group *group, + int fd, + const char *ifname, + char *query_buf, + int query_buf_size, + int num_sources, + struct in_addr dst_addr, + struct in_addr group_addr, + int query_max_response_time_dsec, + uint8_t s_flag, + uint8_t querier_robustness_variable, + uint16_t querier_query_interval); + +void igmp_v3_recv_query (struct igmp_sock *igmp, const char *from_str, + char *igmp_msg); + +int igmp_v3_recv_report (struct igmp_sock *igmp, + struct in_addr from, const char *from_str, + char *igmp_msg, int igmp_msg_len); #endif /* PIM_IGMPV3_H */ diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c index b9c97557c..341d73336 100644 --- a/pimd/pim_mroute.c +++ b/pimd/pim_mroute.c @@ -399,59 +399,97 @@ pim_mroute_msg_wrvifwhole (int fd, struct interface *ifp, const char *buf) int pim_mroute_msg(int fd, const char *buf, int buf_size) { struct interface *ifp; + struct pim_interface *pim_ifp; const struct ip *ip_hdr; const struct igmpmsg *msg; + char ip_src_str[100] = ""; + char ip_dst_str[100] = ""; char src_str[100] = "<src?>"; char grp_str[100] = "<grp?>"; + struct in_addr ifaddr; + struct igmp_sock *igmp; ip_hdr = (const struct ip *) buf; - /* kernel upcall must have protocol=0 */ - if (ip_hdr->ip_p) { - /* this is not a kernel upcall */ + if (ip_hdr->ip_p == IPPROTO_IGMP) { + + /* We have the IP packet but we do not know which interface this packet was + * received on. Find the interface that is on the same subnet as the source + * of the IP packet. + */ + ifp = if_lookup_address_vrf((void *) &ip_hdr->ip_src, AF_INET, VRF_DEFAULT); + + if (!ifp) { + if (PIM_DEBUG_MROUTE_DETAIL) { + pim_inet4_dump("<src?>", ip_hdr->ip_src, ip_src_str, sizeof(ip_src_str)); + pim_inet4_dump("<dst?>", ip_hdr->ip_dst, ip_dst_str, sizeof(ip_dst_str)); + + zlog_warn("%s: igmp kernel upcall could not find interface for %s -> %s", + __PRETTY_FUNCTION__, + ip_src_str, + ip_dst_str); + } + return 0; + } + + pim_ifp = ifp->info; + ifaddr = pim_find_primary_addr(ifp); + igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list, ifaddr); + + if (PIM_DEBUG_MROUTE_DETAIL) { + pim_inet4_dump("<src?>", ip_hdr->ip_src, ip_src_str, sizeof(ip_src_str)); + pim_inet4_dump("<dst?>", ip_hdr->ip_dst, ip_dst_str, sizeof(ip_dst_str)); + + zlog_warn("%s: igmp kernel upcall on %s for %s -> %s", + __PRETTY_FUNCTION__, ifp->name, ip_src_str, ip_dst_str); + } + + pim_igmp_packet(igmp, (char *)buf, buf_size); + + } else if (ip_hdr->ip_p) { if (PIM_DEBUG_MROUTE_DETAIL) { pim_inet4_dump("<src?>", ip_hdr->ip_src, src_str, sizeof(src_str)); pim_inet4_dump("<grp?>", ip_hdr->ip_dst, grp_str, sizeof(grp_str)); - zlog_debug("%s: not a kernel upcall proto=%d src: %s dst: %s msg_size=%d", - __PRETTY_FUNCTION__, ip_hdr->ip_p, src_str, grp_str, buf_size); + zlog_debug("%s: no kernel upcall proto=%d src: %s dst: %s msg_size=%d", + __PRETTY_FUNCTION__, ip_hdr->ip_p, src_str, grp_str, buf_size); } - return 0; - } - msg = (const struct igmpmsg *) buf; + } else { + msg = (const struct igmpmsg *) buf; - ifp = pim_if_find_by_vif_index(msg->im_vif); + ifp = pim_if_find_by_vif_index(msg->im_vif); - if (PIM_DEBUG_MROUTE) { - pim_inet4_dump("<src?>", msg->im_src, src_str, sizeof(src_str)); - pim_inet4_dump("<grp?>", msg->im_dst, grp_str, sizeof(grp_str)); - zlog_warn("%s: kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d size=%d", - __PRETTY_FUNCTION__, - igmpmsgtype2str[msg->im_msgtype], - msg->im_msgtype, - ip_hdr->ip_p, - fd, - src_str, - grp_str, - ifp->name, - msg->im_vif, buf_size); - } + if (PIM_DEBUG_MROUTE) { + pim_inet4_dump("<src?>", msg->im_src, src_str, sizeof(src_str)); + pim_inet4_dump("<grp?>", msg->im_dst, grp_str, sizeof(grp_str)); + zlog_warn("%s: pim kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d size=%d", + __PRETTY_FUNCTION__, + igmpmsgtype2str[msg->im_msgtype], + msg->im_msgtype, + ip_hdr->ip_p, + fd, + src_str, + grp_str, + ifp->name, + msg->im_vif, buf_size); + } - switch (msg->im_msgtype) { - case IGMPMSG_WRONGVIF: - return pim_mroute_msg_wrongvif(fd, ifp, msg); - break; - case IGMPMSG_NOCACHE: - return pim_mroute_msg_nocache(fd, ifp, msg); - break; - case IGMPMSG_WHOLEPKT: - return pim_mroute_msg_wholepkt(fd, ifp, (const char *)msg); - break; - case IGMPMSG_WRVIFWHOLE: - return pim_mroute_msg_wrvifwhole (fd, ifp, (const char *)msg); - break; - default: - break; + switch (msg->im_msgtype) { + case IGMPMSG_WRONGVIF: + return pim_mroute_msg_wrongvif(fd, ifp, msg); + break; + case IGMPMSG_NOCACHE: + return pim_mroute_msg_nocache(fd, ifp, msg); + break; + case IGMPMSG_WHOLEPKT: + return pim_mroute_msg_wholepkt(fd, ifp, (const char *)msg); + break; + case IGMPMSG_WRVIFWHOLE: + return pim_mroute_msg_wrvifwhole (fd, ifp, (const char *)msg); + break; + default: + break; + } } return 0; diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c index 3eb04d347..644fb8e69 100644 --- a/pimd/pim_vty.c +++ b/pimd/pim_vty.c @@ -215,6 +215,15 @@ int pim_interface_config_write(struct vty *vty) ++writes; } + /* ip igmp version */ + if (pim_ifp->igmp_version != IGMP_DEFAULT_VERSION) + { + vty_out(vty, " ip igmp version %d%s", + pim_ifp->igmp_version, + VTY_NEWLINE); + ++writes; + } + /* IF ip igmp query-interval */ if (pim_ifp->igmp_default_query_interval != IGMP_GENERAL_QUERY_INTERVAL) { |