/* PIM for Quagga Copyright (C) 2008 Everton da Silva Marques 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 $QuaggaId: $Format:%an, %ai, %h$ $ */ #include #include "log.h" #include "prefix.h" #include "pimd.h" #include "pim_str.h" #include "pim_tlv.h" #include "pim_msg.h" #include "pim_pim.h" #include "pim_join.h" #include "pim_iface.h" #include "pim_hello.h" #include "pim_ifchannel.h" static void on_trace(const char *label, struct interface *ifp, struct in_addr src) { if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; pim_inet4_dump("", src, src_str, sizeof(src_str)); zlog_debug("%s: from %s on %s", label, src_str, ifp->name); } } static void recv_join(struct interface *ifp, struct pim_neighbor *neigh, uint16_t holdtime, struct in_addr upstream, struct in_addr group, struct in_addr source, uint8_t source_flags) { if (PIM_DEBUG_PIM_TRACE) { char up_str[100]; char src_str[100]; char grp_str[100]; char neigh_str[100]; pim_inet4_dump("", upstream, up_str, sizeof(up_str)); pim_inet4_dump("", source, src_str, sizeof(src_str)); pim_inet4_dump("", group, grp_str, sizeof(grp_str)); pim_inet4_dump("", neigh->source_addr, neigh_str, sizeof(neigh_str)); zlog_warn("%s: join (S,G)=(%s,%s) rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s", __PRETTY_FUNCTION__, src_str, grp_str, source_flags & PIM_RPT_BIT_MASK, source_flags & PIM_WILDCARD_BIT_MASK, up_str, holdtime, neigh_str, ifp->name); } /* Restart join expiry timer */ pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, source, group, source_flags, holdtime); } static void recv_prune(struct interface *ifp, struct pim_neighbor *neigh, uint16_t holdtime, struct in_addr upstream, struct in_addr group, struct in_addr source, uint8_t source_flags) { if (PIM_DEBUG_PIM_TRACE) { char up_str[100]; char src_str[100]; char grp_str[100]; char neigh_str[100]; pim_inet4_dump("", upstream, up_str, sizeof(up_str)); pim_inet4_dump("", source, src_str, sizeof(src_str)); pim_inet4_dump("", group, grp_str, sizeof(grp_str)); pim_inet4_dump("", neigh->source_addr, neigh_str, sizeof(neigh_str)); zlog_warn("%s: prune (S,G)=(%s,%s) rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s", __PRETTY_FUNCTION__, src_str, grp_str, source_flags & PIM_RPT_BIT_MASK, source_flags & PIM_WILDCARD_BIT_MASK, up_str, holdtime, neigh_str, ifp->name); } pim_ifchannel_prune(ifp, upstream, source, group, source_flags, holdtime); } int pim_joinprune_recv(struct interface *ifp, struct pim_neighbor *neigh, struct in_addr src_addr, uint8_t *tlv_buf, int tlv_buf_size) { struct prefix msg_upstream_addr; uint8_t msg_num_groups; uint16_t msg_holdtime; int addr_offset; uint8_t *buf; uint8_t *pastend; int remain; int group; on_trace(__PRETTY_FUNCTION__, ifp, src_addr); buf = tlv_buf; pastend = tlv_buf + tlv_buf_size; /* Parse ucast addr */ addr_offset = pim_parse_addr_ucast(ifp->name, src_addr, &msg_upstream_addr, buf, pastend - buf); #if 0 zlog_warn("%s: pim_parse_addr_ucast addr_offset=%d", __PRETTY_FUNCTION__, addr_offset); #endif if (addr_offset < 1) { char src_str[100]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s", __PRETTY_FUNCTION__, src_str, ifp->name); return -1; } buf += addr_offset; /* Check upstream address family */ if (msg_upstream_addr.family != AF_INET) { if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: ignoring join/prune directed to unexpected addr family=%d from %s on %s", __PRETTY_FUNCTION__, msg_upstream_addr.family, src_str, ifp->name); } return -2; } remain = pastend - buf; if (remain < 4) { char src_str[100]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: short join/prune message buffer for group list: size=%d minimum=%d from %s on %s", __PRETTY_FUNCTION__, remain, 4, src_str, ifp->name); return -4; } ++buf; /* skip reserved byte */ msg_num_groups = *(const uint8_t *) buf; ++buf; msg_holdtime = ntohs(*(const uint16_t *) buf); ++buf; ++buf; if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; char upstream_str[100]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); pim_inet4_dump("", msg_upstream_addr.u.prefix4, upstream_str, sizeof(upstream_str)); zlog_warn("%s: join/prune upstream=%s groups=%d holdtime=%d from %s on %s", __PRETTY_FUNCTION__, upstream_str, msg_num_groups, msg_holdtime, src_str, ifp->name); } /* Scan groups */ for (group = 0; group < msg_num_groups; ++group) { struct prefix msg_group_addr; struct prefix msg_source_addr; uint8_t msg_source_flags; uint16_t msg_num_joined_sources; uint16_t msg_num_pruned_sources; int source; addr_offset = pim_parse_addr_group(ifp->name, src_addr, &msg_group_addr, buf, pastend - buf); #if 0 zlog_warn("%s: pim_parse_addr_group addr_offset=%d", __PRETTY_FUNCTION__, addr_offset); #endif if (addr_offset < 1) { return -5; } buf += addr_offset; remain = pastend - buf; if (remain < 4) { char src_str[100]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); zlog_warn("%s: short join/prune buffer for source list: size=%d minimum=%d from %s on %s", __PRETTY_FUNCTION__, remain, 4, src_str, ifp->name); return -6; } msg_num_joined_sources = ntohs(*(const uint16_t *) buf); buf += 2; msg_num_pruned_sources = ntohs(*(const uint16_t *) buf); buf += 2; if (PIM_DEBUG_PIM_TRACE) { char src_str[100]; char upstream_str[100]; char group_str[100]; pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); pim_inet4_dump("", msg_upstream_addr.u.prefix4, upstream_str, sizeof(upstream_str)); pim_inet4_dump("", msg_group_addr.u.prefix4, group_str, sizeof(group_str)); zlog_warn("%s: join/prune upstream=%s group=%s/%d join_src=%d prune_src=%d from %s on %s", __PRETTY_FUNCTION__, upstream_str, group_str, msg_group_addr.prefixlen, msg_num_joined_sources, msg_num_pruned_sources, src_str, ifp->name); } /* Scan joined sources */ for (source = 0; source < msg_num_joined_sources; ++source) { addr_offset = pim_parse_addr_source(ifp->name, src_addr, &msg_source_addr, &msg_source_flags, buf, pastend - buf); #if 0 zlog_warn("%s: pim_parse_addr_source addr_offset=%d", __PRETTY_FUNCTION__, addr_offset); #endif if (addr_offset < 1) { return -7; } buf += addr_offset; recv_join(ifp, neigh, msg_holdtime, msg_upstream_addr.u.prefix4, msg_group_addr.u.prefix4, msg_source_addr.u.prefix4, msg_source_flags); } /* Scan pruned sources */ for (source = 0; source < msg_num_pruned_sources; ++source) { addr_offset = pim_parse_addr_source(ifp->name, src_addr, &msg_source_addr, &msg_source_flags, buf, pastend - buf); if (addr_offset < 1) { return -8; } buf += addr_offset; recv_prune(ifp, neigh, msg_holdtime, msg_upstream_addr.u.prefix4, msg_group_addr.u.prefix4, msg_source_addr.u.prefix4, msg_source_flags); } } /* scan groups */ return 0; } int pim_joinprune_send(struct interface *ifp, struct in_addr upstream_addr, struct in_addr source_addr, struct in_addr group_addr, int send_join) { struct pim_interface *pim_ifp; uint8_t pim_msg[1000]; const uint8_t *pastend = pim_msg + sizeof(pim_msg); uint8_t *pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN; /* room for pim header */ int pim_msg_size; int remain; zassert(ifp); pim_ifp = ifp->info; if (!pim_ifp) { zlog_warn("%s: multicast not enabled on interface %s", __PRETTY_FUNCTION__, ifp->name); return -1; } if (PIM_DEBUG_PIM_TRACE) { char source_str[100]; char group_str[100]; char dst_str[100]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", upstream_addr, dst_str, sizeof(dst_str)); zlog_debug("%s: sending %s(S,G)=(%s,%s) to upstream=%s on interface %s", __PRETTY_FUNCTION__, send_join ? "Join" : "Prune", source_str, group_str, dst_str, ifp->name); } if (PIM_INADDR_IS_ANY(upstream_addr)) { if (PIM_DEBUG_PIM_TRACE) { char source_str[100]; char group_str[100]; char dst_str[100]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); pim_inet4_dump("", upstream_addr, dst_str, sizeof(dst_str)); zlog_debug("%s: %s(S,G)=(%s,%s): upstream=%s is myself on interface %s", __PRETTY_FUNCTION__, send_join ? "Join" : "Prune", source_str, group_str, dst_str, ifp->name); } return 0; } /* RFC 4601: 4.3.1. Sending Hello Messages Thus, if a router needs to send a Join/Prune or Assert message on an interface on which it has not yet sent a Hello message with the currently configured IP address, then it MUST immediately send the relevant Hello message without waiting for the Hello Timer to expire, followed by the Join/Prune or Assert message. */ pim_hello_require(ifp); /* Build PIM message */ remain = pastend - pim_msg_curr; pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr, remain, upstream_addr); if (!pim_msg_curr) { char dst_str[100]; pim_inet4_dump("", upstream_addr, dst_str, sizeof(dst_str)); zlog_warn("%s: failure encoding destination address %s: space left=%d", __PRETTY_FUNCTION__, dst_str, remain); return -3; } remain = pastend - pim_msg_curr; if (remain < 4) { zlog_warn("%s: group will not fit: space left=%d", __PRETTY_FUNCTION__, remain); return -4; } *pim_msg_curr = 0; /* reserved */ ++pim_msg_curr; *pim_msg_curr = 1; /* number of groups */ ++pim_msg_curr; *((uint16_t *) pim_msg_curr) = htons(PIM_JP_HOLDTIME); ++pim_msg_curr; ++pim_msg_curr; remain = pastend - pim_msg_curr; pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr, remain, group_addr); if (!pim_msg_curr) { char group_str[100]; pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); zlog_warn("%s: failure encoding group address %s: space left=%d", __PRETTY_FUNCTION__, group_str, remain); return -5; } remain = pastend - pim_msg_curr; if (remain < 4) { zlog_warn("%s: sources will not fit: space left=%d", __PRETTY_FUNCTION__, remain); return -6; } /* number of joined sources */ *((uint16_t *) pim_msg_curr) = htons(send_join ? 1 : 0); ++pim_msg_curr; ++pim_msg_curr; /* number of pruned sources */ *((uint16_t *) pim_msg_curr) = htons(send_join ? 0 : 1); ++pim_msg_curr; ++pim_msg_curr; remain = pastend - pim_msg_curr; pim_msg_curr = pim_msg_addr_encode_ipv4_source(pim_msg_curr, remain, source_addr); if (!pim_msg_curr) { char source_str[100]; pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); zlog_warn("%s: failure encoding source address %s: space left=%d", __PRETTY_FUNCTION__, source_str, remain); return -7; } /* Add PIM header */ pim_msg_size = pim_msg_curr - pim_msg; pim_msg_build_header(pim_msg, pim_msg_size, PIM_MSG_TYPE_JOIN_PRUNE); if (pim_msg_send(pim_ifp->pim_sock_fd, qpim_all_pim_routers_addr, pim_msg, pim_msg_size, ifp->name)) { zlog_warn("%s: could not send PIM message on interface %s", __PRETTY_FUNCTION__, ifp->name); return -8; } return 0; }