summaryrefslogtreecommitdiffstats
path: root/pimd/pim_mroute.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_mroute.c')
-rw-r--r--pimd/pim_mroute.c451
1 files changed, 451 insertions, 0 deletions
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
new file mode 100644
index 000000000..fa460e28f
--- /dev/null
+++ b/pimd/pim_mroute.c
@@ -0,0 +1,451 @@
+/*
+ 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 <zebra.h>
+#include "log.h"
+#include "privs.h"
+
+#include "pimd.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_macro.h"
+
+/* GLOBAL VARS */
+extern struct zebra_privs_t pimd_privs;
+
+static void mroute_read_on(void);
+
+static int pim_mroute_set(int fd, int enable)
+{
+ int err;
+ int opt = enable ? MRT_INIT : MRT_DONE;
+ socklen_t opt_len = sizeof(opt);
+
+ err = setsockopt(fd, IPPROTO_IP, opt, &opt, opt_len);
+ if (err) {
+ int e = errno;
+ zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,%s=%d): errno=%d: %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ fd, enable ? "MRT_INIT" : "MRT_DONE", opt, e, safe_strerror(e));
+ errno = e;
+ return -1;
+ }
+
+#if 0
+ zlog_info("%s %s: setsockopt(fd=%d,IPPROTO_IP,MRT_INIT,opt=%d): ok",
+ __FILE__, __PRETTY_FUNCTION__,
+ fd, opt);
+#endif
+
+ return 0;
+}
+
+int pim_mroute_msg(int fd, const char *buf, int buf_size)
+{
+ struct interface *ifp;
+ const struct ip *ip_hdr;
+ const struct igmpmsg *msg;
+ const char *upcall;
+ char src_str[100];
+ char grp_str[100];
+
+ ip_hdr = (const struct ip *) buf;
+
+ /* kernel upcall must have protocol=0 */
+ if (ip_hdr->ip_p) {
+ /* this is not a kernel upcall */
+#ifdef PIM_UNEXPECTED_KERNEL_UPCALL
+ zlog_warn("%s: not a kernel upcall proto=%d msg_size=%d",
+ __PRETTY_FUNCTION__, ip_hdr->ip_p, buf_size);
+#endif
+ return 0;
+ }
+
+ msg = (const struct igmpmsg *) buf;
+
+ switch (msg->im_msgtype) {
+ case IGMPMSG_NOCACHE: upcall = "NOCACHE"; break;
+ case IGMPMSG_WRONGVIF: upcall = "WRONGVIF"; break;
+ case IGMPMSG_WHOLEPKT: upcall = "WHOLEPKT"; break;
+ default: upcall = "<unknown_upcall?>";
+ }
+ ifp = pim_if_find_by_vif_index(msg->im_vif);
+ pim_inet4_dump("<src?>", msg->im_src, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", msg->im_dst, grp_str, sizeof(grp_str));
+
+ if (msg->im_msgtype == IGMPMSG_WRONGVIF) {
+ struct pim_ifchannel *ch;
+ struct pim_interface *pim_ifp;
+
+ /*
+ Send Assert(S,G) on iif as response to WRONGVIF kernel upcall.
+
+ RFC 4601 4.8.2. PIM-SSM-Only Routers
+
+ iif is the incoming interface of the packet.
+ if (iif is in inherited_olist(S,G)) {
+ send Assert(S,G) on iif
+ }
+ */
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("%s: WRONGVIF from fd=%d for (S,G)=(%s,%s) on %s vifi=%d",
+ __PRETTY_FUNCTION__,
+ fd,
+ src_str,
+ grp_str,
+ ifp ? ifp->name : "<ifname?>",
+ msg->im_vif);
+ }
+
+ if (!ifp) {
+ zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) could not find input interface for input_vif_index=%d",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, msg->im_vif);
+ return -1;
+ }
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return -2;
+ }
+
+ ch = pim_ifchannel_find(ifp, msg->im_src, msg->im_dst);
+ if (!ch) {
+ zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) could not find channel on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return -3;
+ }
+
+ /*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ Transitions from NoInfo State
+
+ An (S,G) data packet arrives on interface I, AND
+ CouldAssert(S,G,I)==TRUE An (S,G) data packet arrived on an
+ downstream interface that is in our (S,G) outgoing interface
+ list. We optimistically assume that we will be the assert
+ winner for this (S,G), and so we transition to the "I am Assert
+ Winner" state and perform Actions A1 (below), which will
+ initiate the assert negotiation for (S,G).
+ */
+
+ if (ch->ifassert_state != PIM_IFASSERT_NOINFO) {
+ zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) channel is not on Assert NoInfo state for interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return -4;
+ }
+
+ if (!PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) {
+ zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) interface %s is not downstream for channel",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return -5;
+ }
+
+ if (assert_action_a1(ch)) {
+ zlog_warn("%s: WRONGVIF (S,G)=(%s,%s) assert_action_a1 failure on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return -6;
+ }
+
+ return 0;
+ } /* IGMPMSG_WRONGVIF */
+
+ zlog_warn("%s: kernel upcall %s type=%d ip_p=%d from fd=%d for (S,G)=(%s,%s) on %s vifi=%d",
+ __PRETTY_FUNCTION__,
+ upcall,
+ msg->im_msgtype,
+ ip_hdr->ip_p,
+ fd,
+ src_str,
+ grp_str,
+ ifp ? ifp->name : "<ifname?>",
+ msg->im_vif);
+
+ return 0;
+}
+
+static int mroute_read_msg(int fd)
+{
+ const int msg_min_size = MAX(sizeof(struct ip), sizeof(struct igmpmsg));
+ char buf[1000];
+ int rd;
+
+ if (((int) sizeof(buf)) < msg_min_size) {
+ zlog_err("%s: fd=%d: buf size=%zu lower than msg_min=%d",
+ __PRETTY_FUNCTION__, fd, sizeof(buf), msg_min_size);
+ return -1;
+ }
+
+ rd = read(fd, buf, sizeof(buf));
+ if (rd < 0) {
+ zlog_warn("%s: failure reading fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ return -2;
+ }
+
+ if (rd < msg_min_size) {
+ zlog_warn("%s: short message reading fd=%d: read=%d msg_min=%d",
+ __PRETTY_FUNCTION__, fd, rd, msg_min_size);
+ return -3;
+ }
+
+ return pim_mroute_msg(fd, buf, rd);
+}
+
+static int mroute_read(struct thread *t)
+{
+ int fd;
+ int result;
+
+ zassert(t);
+ zassert(!THREAD_ARG(t));
+
+ fd = THREAD_FD(t);
+ zassert(fd == qpim_mroute_socket_fd);
+
+ result = mroute_read_msg(fd);
+
+ /* Keep reading */
+ qpim_mroute_socket_reader = 0;
+ mroute_read_on();
+
+ return result;
+}
+
+static void mroute_read_on()
+{
+ zassert(!qpim_mroute_socket_reader);
+ zassert(PIM_MROUTE_IS_ENABLED);
+
+ THREAD_READ_ON(master, qpim_mroute_socket_reader,
+ mroute_read, 0, qpim_mroute_socket_fd);
+}
+
+static void mroute_read_off()
+{
+ THREAD_OFF(qpim_mroute_socket_reader);
+}
+
+int pim_mroute_socket_enable()
+{
+ int fd;
+
+ if (PIM_MROUTE_IS_ENABLED)
+ return -1;
+
+ if ( pimd_privs.change (ZPRIVS_RAISE) )
+ zlog_err ("pim_mroute_socket_enable: could not raise privs, %s",
+ safe_strerror (errno) );
+
+ fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP);
+
+ if ( pimd_privs.change (ZPRIVS_LOWER) )
+ zlog_err ("pim_mroute_socket_enable: could not lower privs, %s",
+ safe_strerror (errno) );
+
+ if (fd < 0) {
+ zlog_warn("Could not create mroute socket: errno=%d: %s",
+ errno, safe_strerror(errno));
+ return -2;
+ }
+
+ if (pim_mroute_set(fd, 1)) {
+ zlog_warn("Could not enable mroute on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return -3;
+ }
+
+ qpim_mroute_socket_fd = fd;
+ qpim_mroute_socket_creation = pim_time_monotonic_sec();
+ mroute_read_on();
+
+ zassert(PIM_MROUTE_IS_ENABLED);
+
+ return 0;
+}
+
+int pim_mroute_socket_disable()
+{
+ if (PIM_MROUTE_IS_DISABLED)
+ return -1;
+
+ if (pim_mroute_set(qpim_mroute_socket_fd, 0)) {
+ zlog_warn("Could not disable mroute on socket fd=%d: errno=%d: %s",
+ qpim_mroute_socket_fd, errno, safe_strerror(errno));
+ return -2;
+ }
+
+ if (close(qpim_mroute_socket_fd)) {
+ zlog_warn("Failure closing mroute socket: fd=%d errno=%d: %s",
+ qpim_mroute_socket_fd, errno, safe_strerror(errno));
+ return -3;
+ }
+
+ mroute_read_off();
+ qpim_mroute_socket_fd = -1;
+
+ zassert(PIM_MROUTE_IS_DISABLED);
+
+ return 0;
+}
+
+/*
+ For each network interface (e.g., physical or a virtual tunnel) that
+ would be used for multicast forwarding, a corresponding multicast
+ interface must be added to the kernel.
+ */
+int pim_mroute_add_vif(int vif_index, struct in_addr ifaddr)
+{
+ struct vifctl vc;
+ int err;
+
+ if (PIM_MROUTE_IS_DISABLED) {
+ zlog_warn("%s: global multicast is disabled",
+ __PRETTY_FUNCTION__);
+ return -1;
+ }
+
+ memset(&vc, 0, sizeof(vc));
+ vc.vifc_vifi = vif_index;
+ vc.vifc_flags = 0;
+ vc.vifc_threshold = PIM_MROUTE_MIN_TTL;
+ vc.vifc_rate_limit = 0;
+ memcpy(&vc.vifc_lcl_addr, &ifaddr, sizeof(vc.vifc_lcl_addr));
+
+#ifdef PIM_DVMRP_TUNNEL
+ if (vc.vifc_flags & VIFF_TUNNEL) {
+ memcpy(&vc.vifc_rmt_addr, &vif_remote_addr, sizeof(vc.vifc_rmt_addr));
+ }
+#endif
+
+ err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_ADD_VIF, (void*) &vc, sizeof(vc));
+ if (err) {
+ char ifaddr_str[100];
+ int e = errno;
+
+ pim_inet4_dump("<ifaddr?>", ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+ zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_ADD_VIF,vif_index=%d,ifaddr=%s): errno=%d: %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ qpim_mroute_socket_fd, vif_index, ifaddr_str,
+ e, safe_strerror(e));
+ errno = e;
+ return -2;
+ }
+
+ return 0;
+}
+
+int pim_mroute_del_vif(int vif_index)
+{
+ struct vifctl vc;
+ int err;
+
+ if (PIM_MROUTE_IS_DISABLED) {
+ zlog_warn("%s: global multicast is disabled",
+ __PRETTY_FUNCTION__);
+ return -1;
+ }
+
+ memset(&vc, 0, sizeof(vc));
+ vc.vifc_vifi = vif_index;
+
+ err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_DEL_VIF, (void*) &vc, sizeof(vc));
+ if (err) {
+ int e = errno;
+ zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_DEL_VIF,vif_index=%d): errno=%d: %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ qpim_mroute_socket_fd, vif_index,
+ e, safe_strerror(e));
+ errno = e;
+ return -2;
+ }
+
+ return 0;
+}
+
+int pim_mroute_add(struct mfcctl *mc)
+{
+ int err;
+
+ qpim_mroute_add_last = pim_time_monotonic_sec();
+ ++qpim_mroute_add_events;
+
+ if (PIM_MROUTE_IS_DISABLED) {
+ zlog_warn("%s: global multicast is disabled",
+ __PRETTY_FUNCTION__);
+ return -1;
+ }
+
+ err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_ADD_MFC,
+ mc, sizeof(*mc));
+ if (err) {
+ int e = errno;
+ zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_ADD_MFC): errno=%d: %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ qpim_mroute_socket_fd,
+ e, safe_strerror(e));
+ errno = e;
+ return -2;
+ }
+
+ return 0;
+}
+
+int pim_mroute_del(struct mfcctl *mc)
+{
+ int err;
+
+ qpim_mroute_del_last = pim_time_monotonic_sec();
+ ++qpim_mroute_del_events;
+
+ if (PIM_MROUTE_IS_DISABLED) {
+ zlog_warn("%s: global multicast is disabled",
+ __PRETTY_FUNCTION__);
+ return -1;
+ }
+
+ err = setsockopt(qpim_mroute_socket_fd, IPPROTO_IP, MRT_DEL_MFC, mc, sizeof(*mc));
+ if (err) {
+ int e = errno;
+ zlog_warn("%s %s: failure: setsockopt(fd=%d,IPPROTO_IP,MRT_DEL_MFC): errno=%d: %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ qpim_mroute_socket_fd,
+ e, safe_strerror(e));
+ errno = e;
+ return -2;
+ }
+
+ return 0;
+}