| /* |
| 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 "pimd.h" |
| #include "pim_mroute.h" |
| #include "pim_str.h" |
| #include "pim_time.h" |
| #include "pim_iface.h" |
| #include "pim_macro.h" |
| |
| 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, 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=%d 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, 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; |
| |
| fd = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); |
| if (fd < 0) { |
| zlog_warn("Could not create mroute socket: errno=%d: %s", |
| errno, 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, 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, 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, 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, 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, strerror(e)); |
| errno = e; |
| return -2; |
| } |
| |
| return 0; |
| } |
| |
| int pim_mroute_add(struct mfcctl *mc) |
| { |
| int err; |
| |
| 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, strerror(e)); |
| errno = e; |
| return -2; |
| } |
| |
| return 0; |
| } |
| |
| int pim_mroute_del(struct mfcctl *mc) |
| { |
| int err; |
| |
| 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, strerror(e)); |
| errno = e; |
| return -2; |
| } |
| |
| return 0; |
| } |