blob: fa460e28f336398df58d2bf041f0786790090154 [file] [log] [blame]
/*
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;
}