summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Lamparter <equinox@opensourcerouting.org>2015-02-04 07:01:14 +0100
committerDavid Lamparter <equinox@opensourcerouting.org>2015-02-06 22:08:38 +0100
commit87fbe9ec6188f18c77171fa6ebad25dd2d3b8b1f (patch)
tree62dd60ba70aab5f31490e64c980371a4e1baaa48
parent7397217e9dbc7384951ea146c0f9ca5784f6561e (diff)
parent7d924b422ffdeb37027ac979c6a62d845499fab8 (diff)
downloadquagga-87fbe9ec6188f18c77171fa6ebad25dd2d3b8b1f.tar.bz2
quagga-87fbe9ec6188f18c77171fa6ebad25dd2d3b8b1f.tar.xz
pimd: merge pimd as of 2015-01-19
Welcome pimd to the Quagga daemon zoo! This is a merge of commit 77ae369 ("pimd: Log ifindex found for an interface when zebra lib reports a new connected address."), with the intermediate "reconnect" changes removed (c9adf00...d274381). d274381 is replaced with b162ab7, which includes some changes. In addition, 4 reconnect-related changes and 1 cosmetic one have been bumped out. The rebase command used to produce the branch that is merged here is: git rebase --onto b162ab7 c9adf00 77ae369 Note that 3 patches had their author rewritten from "Anonymous SR#108542 <>" (which is not a valid git author ID) to: "Savannah SR#108542 <nbahr@atcorp.com>" (which is the e-mail address listed in the associated Savannah ticket) Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
-rw-r--r--Makefile.am4
-rw-r--r--SERVICES1
-rwxr-xr-xconfigure.ac55
-rw-r--r--doc/Makefile.am8
-rw-r--r--doc/install.texi1
-rw-r--r--doc/pimd.8125
-rw-r--r--lib/command.c2
-rw-r--r--lib/command.h1
-rw-r--r--lib/log.c1
-rw-r--r--lib/log.h1
-rw-r--r--lib/memory.c13
-rw-r--r--lib/memtypes.c16
-rw-r--r--lib/route_types.txt2
-rw-r--r--lib/thread.h1
-rw-r--r--lib/vty.c2
-rw-r--r--lib/zclient.c7
-rw-r--r--lib/zclient.h1
-rw-r--r--pimd/.gitignore16
-rw-r--r--pimd/AUTHORS9
-rw-r--r--pimd/CAVEATS178
-rw-r--r--pimd/COMMANDS81
-rw-r--r--pimd/COPYING340
-rw-r--r--pimd/DEBUG86
-rw-r--r--pimd/LINUX_KERNEL_MROUTE_MFC26
-rw-r--r--pimd/Makefile.am76
-rw-r--r--pimd/README164
-rw-r--r--pimd/TODO426
-rw-r--r--pimd/TROUBLESHOOTING33
-rw-r--r--pimd/WHY_SSM32
-rwxr-xr-xpimd/git-clone-github.sh27
-rwxr-xr-xpimd/git-clone-savannah.sh27
-rw-r--r--pimd/pim_assert.c808
-rw-r--r--pimd/pim_assert.h75
-rw-r--r--pimd/pim_cmd.c4500
-rw-r--r--pimd/pim_cmd.h66
-rw-r--r--pimd/pim_hello.c529
-rw-r--r--pimd/pim_hello.h46
-rw-r--r--pimd/pim_iface.c1229
-rw-r--r--pimd/pim_iface.h161
-rw-r--r--pimd/pim_ifchannel.c893
-rw-r--r--pimd/pim_ifchannel.h145
-rw-r--r--pimd/pim_igmp.c1433
-rw-r--r--pimd/pim_igmp.h176
-rw-r--r--pimd/pim_igmp_join.c67
-rw-r--r--pimd/pim_igmp_join.h32
-rw-r--r--pimd/pim_igmpv3.c1729
-rw-r--r--pimd/pim_igmpv3.h100
-rw-r--r--pimd/pim_int.c44
-rw-r--r--pimd/pim_int.h31
-rw-r--r--pimd/pim_join.c445
-rw-r--r--pimd/pim_join.h43
-rw-r--r--pimd/pim_macro.c437
-rw-r--r--pimd/pim_macro.h44
-rw-r--r--pimd/pim_main.c294
-rw-r--r--pimd/pim_mroute.c451
-rw-r--r--pimd/pim_mroute.h173
-rw-r--r--pimd/pim_msg.c106
-rw-r--r--pimd/pim_msg.h52
-rw-r--r--pimd/pim_neighbor.c719
-rw-r--r--pimd/pim_neighbor.h74
-rw-r--r--pimd/pim_oil.c140
-rw-r--r--pimd/pim_oil.h53
-rw-r--r--pimd/pim_pim.c742
-rw-r--r--pimd/pim_pim.h71
-rw-r--r--pimd/pim_rand.c60
-rw-r--r--pimd/pim_rand.h30
-rw-r--r--pimd/pim_rpf.c260
-rw-r--r--pimd/pim_rpf.h36
-rw-r--r--pimd/pim_signals.c86
-rw-r--r--pimd/pim_signals.h28
-rw-r--r--pimd/pim_sock.c389
-rw-r--r--pimd/pim_sock.h57
-rw-r--r--pimd/pim_ssmpingd.c448
-rw-r--r--pimd/pim_ssmpingd.h45
-rw-r--r--pimd/pim_str.c46
-rw-r--r--pimd/pim_str.h32
-rw-r--r--pimd/pim_time.c165
-rw-r--r--pimd/pim_time.h40
-rw-r--r--pimd/pim_tlv.c721
-rw-r--r--pimd/pim_tlv.h133
-rw-r--r--pimd/pim_upstream.c683
-rw-r--r--pimd/pim_upstream.h122
-rw-r--r--pimd/pim_util.c122
-rw-r--r--pimd/pim_util.h37
-rw-r--r--pimd/pim_version.c25
-rw-r--r--pimd/pim_version.h30
-rw-r--r--pimd/pim_vty.c178
-rw-r--r--pimd/pim_vty.h32
-rw-r--r--pimd/pim_zebra.c1310
-rw-r--r--pimd/pim_zebra.h42
-rw-r--r--pimd/pim_zlookup.c457
-rw-r--r--pimd/pim_zlookup.h47
-rw-r--r--pimd/pimd.c141
-rw-r--r--pimd/pimd.conf.sample41
-rw-r--r--pimd/pimd.h157
-rwxr-xr-xpimd/quagga-bootstrap.sh23
-rwxr-xr-xpimd/quagga-build-no-vtysh.sh10
-rwxr-xr-xpimd/quagga-build.sh10
-rwxr-xr-xpimd/quagga-configure-no-vtysh.sh10
-rwxr-xr-xpimd/quagga-configure.sh10
-rwxr-xr-xpimd/quagga-git-add.sh12
-rwxr-xr-xpimd/quagga-memtypes.sh22
-rw-r--r--pimd/test_igmpv3_join.c149
-rw-r--r--ports/Makefile1
-rw-r--r--ports/pkg/DESCR1
-rw-r--r--redhat/quagga.spec.in3
-rw-r--r--vtysh/Makefile.am1
-rw-r--r--vtysh/vtysh.c1
-rw-r--r--vtysh/vtysh.h5
109 files changed, 23919 insertions, 9 deletions
diff --git a/Makefile.am b/Makefile.am
index b6082535..405142f8 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,12 +1,12 @@
## Process this file with automake to produce Makefile.in.
SUBDIRS = lib @ZEBRA@ @BGPD@ @RIPD@ @RIPNGD@ @OSPFD@ @OSPF6D@ @BABELD@ \
- @ISISD@ @WATCHQUAGGA@ @VTYSH@ @OSPFCLIENT@ @DOC@ m4 @pkgsrcdir@ \
+ @ISISD@ @PIMD@ @WATCHQUAGGA@ @VTYSH@ @OSPFCLIENT@ @DOC@ m4 @pkgsrcdir@ \
redhat @SOLARIS@ tests
DIST_SUBDIRS = lib zebra bgpd ripd ripngd ospfd ospf6d babeld \
isisd watchquagga vtysh ospfclient doc m4 pkgsrc redhat tests \
- solaris
+ solaris pimd
EXTRA_DIST = aclocal.m4 SERVICES TODO REPORTING-BUGS INSTALL.quagga.txt \
update-autotools \
diff --git a/SERVICES b/SERVICES
index 9c3546bc..c69d0c1a 100644
--- a/SERVICES
+++ b/SERVICES
@@ -17,3 +17,4 @@ bgpd 2605/tcp
ospf6d 2606/tcp
ospfapi 2607/tcp
isisd 2608/tcp
+pimd 2611/tcp
diff --git a/configure.ac b/configure.ac
index 9d188284..6632e543 100755
--- a/configure.ac
+++ b/configure.ac
@@ -220,6 +220,8 @@ AC_ARG_ENABLE(watchquagga,
[ --disable-watchquagga do not build watchquagga])
AC_ARG_ENABLE(isisd,
[ --enable-isisd build isisd])
+AC_ARG_ENABLE(pimd,
+[ --enable-pimd build pimd])
AC_ARG_ENABLE(solaris,
[ --enable-solaris build solaris])
AC_ARG_ENABLE(bgp-announce,
@@ -1362,6 +1364,13 @@ case "${enable_isisd}" in
esac
AM_CONDITIONAL(ISISD, test "x$ISISD" = "xisisd")
+case "${enable_pimd}" in
+ "yes") PIMD="pimd";;
+ "no" ) PIMD="";;
+ * ) ;;
+esac
+AM_CONDITIONAL(PIMD, test "x$PIMD" = "xpimd")
+
# XXX Perhaps auto-enable on Solaris, but that's messy for cross builds.
case "${enable_solaris}" in
"yes") SOLARIS="solaris";;
@@ -1385,6 +1394,7 @@ AC_SUBST(OSPF6D)
AC_SUBST(BABELD)
AC_SUBST(WATCHQUAGGA)
AC_SUBST(ISISD)
+AC_SUBST(PIMD)
AC_SUBST(SOLARIS)
AC_SUBST(VTYSH)
AC_SUBST(INCLUDES)
@@ -1467,7 +1477,8 @@ dnl sockaddr and netinet checks
dnl ---------------------------
AC_CHECK_TYPES([struct sockaddr, struct sockaddr_in,
struct sockaddr_in6, struct sockaddr_un, struct sockaddr_dl,
- socklen_t,
+ socklen_t, struct vifctl, struct mfcctl, struct sioc_sg_req,
+ vifi_t, struct sioc_vif_req, struct igmpmsg,
struct ifaliasreq, struct if6_aliasreq, struct in6_aliasreq,
struct nd_opt_adv_interval, struct rt_addrinfo,
struct nd_opt_homeagent_info, struct nd_opt_adv_interval],
@@ -1496,6 +1507,45 @@ AC_CHECK_TYPES([struct in_pktinfo],
AC_MSG_ERROR(['IRDP requires in_pktinfo at the moment!'])
fi], [QUAGGA_INCLUDES])
+dnl -----------------------
+dnl checking for IP_PKTINFO
+dnl -----------------------
+AC_MSG_CHECKING(for IP_PKTINFO)
+AC_TRY_COMPILE([#include <netdb.h>], [
+ int opt = IP_PKTINFO;
+], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_IP_PKTINFO, 1, [Have IP_PKTINFO])
+], [
+ AC_MSG_RESULT(no)
+])
+
+dnl ---------------------------
+dnl checking for IP_RECVDSTADDR
+dnl ---------------------------
+AC_MSG_CHECKING(for IP_RECVDSTADDR)
+AC_TRY_COMPILE([#include <netinet/in.h>], [
+ int opt = IP_RECVDSTADDR;
+], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_IP_RECVDSTADDR, 1, [Have IP_RECVDSTADDR])
+], [
+ AC_MSG_RESULT(no)
+])
+
+dnl ----------------------
+dnl checking for IP_RECVIF
+dnl ----------------------
+AC_MSG_CHECKING(for IP_RECVIF)
+AC_TRY_COMPILE([#include <netinet/in.h>], [
+ int opt = IP_RECVIF;
+], [
+ AC_MSG_RESULT(yes)
+ AC_DEFINE(HAVE_IP_RECVIF, 1, [Have IP_RECVIF])
+], [
+ AC_MSG_RESULT(no)
+])
+
dnl --------------------------------------
dnl checking for getrusage struct and call
dnl --------------------------------------
@@ -1685,6 +1735,7 @@ AC_DEFINE_UNQUOTED(PATH_OSPFD_PID, "$quagga_statedir/ospfd.pid",ospfd PID)
AC_DEFINE_UNQUOTED(PATH_OSPF6D_PID, "$quagga_statedir/ospf6d.pid",ospf6d PID)
AC_DEFINE_UNQUOTED(PATH_BABELD_PID, "$quagga_statedir/babeld.pid",babeld PID)
AC_DEFINE_UNQUOTED(PATH_ISISD_PID, "$quagga_statedir/isisd.pid",isisd PID)
+AC_DEFINE_UNQUOTED(PATH_PIMD_PID, "$quagga_statedir/pimd.pid",pimd PID)
AC_DEFINE_UNQUOTED(PATH_WATCHQUAGGA_PID, "$quagga_statedir/watchquagga.pid",watchquagga PID)
AC_DEFINE_UNQUOTED(ZEBRA_SERV_PATH, "$quagga_statedir/zserv.api",zebra api socket)
AC_DEFINE_UNQUOTED(ZEBRA_VTYSH_PATH, "$quagga_statedir/zebra.vty",zebra vty socket)
@@ -1695,6 +1746,7 @@ AC_DEFINE_UNQUOTED(OSPF_VTYSH_PATH, "$quagga_statedir/ospfd.vty",ospfd vty socke
AC_DEFINE_UNQUOTED(OSPF6_VTYSH_PATH, "$quagga_statedir/ospf6d.vty",ospf6d vty socket)
AC_DEFINE_UNQUOTED(BABEL_VTYSH_PATH, "$quagga_statedir/babeld.vty",babeld vty socket)
AC_DEFINE_UNQUOTED(ISIS_VTYSH_PATH, "$quagga_statedir/isisd.vty",isisd vty socket)
+AC_DEFINE_UNQUOTED(PIM_VTYSH_PATH, "$quagga_statedir/pimd.vty",pimd vty socket)
AC_DEFINE_UNQUOTED(DAEMON_VTY_DIR, "$quagga_statedir",daemon vty directory)
dnl -------------------------------
@@ -1720,6 +1772,7 @@ AC_CONFIG_FILES([Makefile lib/Makefile zebra/Makefile ripd/Makefile
ripngd/Makefile bgpd/Makefile ospfd/Makefile watchquagga/Makefile
ospf6d/Makefile isisd/Makefile babeld/Makefile vtysh/Makefile
doc/Makefile ospfclient/Makefile tests/Makefile m4/Makefile
+ pimd/Makefile
tests/bgpd.tests/Makefile
tests/libzebra.tests/Makefile
redhat/Makefile
diff --git a/doc/Makefile.am b/doc/Makefile.am
index f58657b9..bb7e87a1 100644
--- a/doc/Makefile.am
+++ b/doc/Makefile.am
@@ -61,7 +61,11 @@ quagga_TEXINFOS = appendix.texi babeld.texi basic.texi bgpd.texi filter.texi \
.dia.png:
$(DIATOPNG) "$@" $<
-man_MANS =
+man_MANS =
+
+if PIMD
+man_MANS += pimd.8
+endif
if BGPD
man_MANS += bgpd.8
@@ -105,7 +109,7 @@ endif
EXTRA_DIST = BGP-TypeCode draft-zebra-00.ms draft-zebra-00.txt \
bgpd.8 isisd.8 ospf6d.8 ospfclient.8 ospfd.8 ripd.8 \
- ripngd.8 vtysh.1 watchquagga.8 zebra.8 \
+ ripngd.8 pimd.8 vtysh.1 watchquagga.8 zebra.8 \
mpls/ChangeLog.opaque.txt mpls/cli_summary.txt \
mpls/opaque_lsa.txt mpls/ospfd.conf \
$(figures_sources) $(figures_png) $(figures_txt)
diff --git a/doc/install.texi b/doc/install.texi
index e958d845..7349e92c 100644
--- a/doc/install.texi
+++ b/doc/install.texi
@@ -273,6 +273,7 @@ bgpd 2605/tcp # BGPd vty
ospf6d 2606/tcp # OSPF6d vty
ospfapi 2607/tcp # ospfapi
isisd 2608/tcp # ISISd vty
+pimd 2611/tcp # PIMd vty
@end example
If you use a FreeBSD newer than 2.2.8, the above entries are already
diff --git a/doc/pimd.8 b/doc/pimd.8
new file mode 100644
index 00000000..0dd170a2
--- /dev/null
+++ b/doc/pimd.8
@@ -0,0 +1,125 @@
+.TH PIM 8 "10 December 2008" "Quagga PIM daemon" "Version 0.99.11"
+.SH NAME
+pimd \- a PIM routing for use with Quagga Routing Suite.
+.SH SYNOPSIS
+.B pimd
+[
+.B \-dhvZ
+] [
+.B \-f
+.I config-file
+] [
+.B \-i
+.I pid-file
+] [
+.B \-z
+.I path
+] [
+.B \-P
+.I port-number
+] [
+.B \-A
+.I vty-address
+] [
+.B \-u
+.I user
+] [
+.B \-g
+.I group
+]
+.SH DESCRIPTION
+.B pimd
+is a protocol-independent multicast component that works with the
+.B Quagga
+Routing Suite.
+.SH OPTIONS
+Options available for the
+.B pimd
+command:
+.TP
+\fB\-d\fR, \fB\-\-daemon\fR
+Runs in daemon mode, forking and exiting from tty.
+.TP
+\fB\-f\fR, \fB\-\-config-file \fR\fIconfig-file\fR
+Specifies the config file to use for startup. If not specified this
+option will likely default to \fB\fI/usr/local/etc/pimd.conf\fR.
+.TP
+\fB\-g\fR, \fB\-\-group \fR\fIgroup\fR
+Specify the group to run as. Default is \fIquagga\fR.
+.TP
+\fB\-h\fR, \fB\-\-help\fR
+A brief message.
+.TP
+\fB\-i\fR, \fB\-\-pid_file \fR\fIpid-file\fR
+When pimd starts its process identifier is written to
+\fB\fIpid-file\fR. The init system uses the recorded PID to stop or
+restart pimd. The likely default is \fB\fI/var/run/pimd.pid\fR.
+.TP
+\fB\-z\fR, \fB\-\-socket \fR\fIpath\fR
+Specify the socket path for contacting the zebra daemon.
+The likely default is \fB\fI/var/run/zserv.api\fR.
+.TP
+\fB\-P\fR, \fB\-\-vty_port \fR\fIport-number\fR
+Specify the port that the pimd VTY will listen on. This defaults to
+2611, as specified in \fB\fI/etc/services\fR.
+.TP
+\fB\-A\fR, \fB\-\-vty_addr \fR\fIvty-address\fR
+Specify the address that the pimd VTY will listen on. Default is all
+interfaces.
+.TP
+\fB\-u\fR, \fB\-\-user \fR\fIuser\fR
+Specify the user to run as. Default is \fIquagga\fR.
+.TP
+\fB\-v\fR, \fB\-\-version\fR
+Print the version and exit.
+.TP
+\fB\-Z\fR, \fB\-\-debug_zclient\fR
+Enable logging information for zclient debugging.
+.SH FILES
+.TP
+.BI /usr/local/sbin/pimd
+The default location of the
+.B pimd
+binary.
+.TP
+.BI /usr/local/etc/pimd.conf
+The default location of the
+.B pimd
+config file.
+.TP
+.BI /var/run/pimd.pid
+The default location of the
+.B pimd
+pid file.
+.TP
+.BI /var/run/zserv.api
+The default location of the
+.B zebra
+unix socket file.
+.TP
+.BI $(PWD)/pimd.log
+If the
+.B pimd
+process is config'd to output logs to a file, then you will find this
+file in the directory where you started \fBpimd\fR.
+.SH WARNING
+This man page is intended to be a quick reference for command line
+options.
+.SH DIAGNOSTICS
+The pimd process may log to standard output, to a VTY, to a log
+file, or through syslog to the system logs.
+.SH "SEE ALSO"
+.BR zebra (8),
+.BR vtysh (1)
+.SH BUGS
+\fBpimd\fR is in early development at the moment and is not ready for
+production use.
+
+.B pimd
+eats bugs for breakfast. If you have food for the maintainers try
+.BI https://github.com/udhos/qpimd
+.SH AUTHORS
+See
+.BI https://github.com/udhos/qpimd
+for an accurate list of authors.
+
diff --git a/lib/command.c b/lib/command.c
index d1af7fa2..8870a421 100644
--- a/lib/command.c
+++ b/lib/command.c
@@ -2872,6 +2872,7 @@ DEFUN (config_exit,
case KEYCHAIN_NODE:
case MASC_NODE:
case RMAP_NODE:
+ case PIM_NODE:
case VTY_NODE:
vty->node = CONFIG_NODE;
break;
@@ -2929,6 +2930,7 @@ DEFUN (config_end,
case KEYCHAIN_NODE:
case KEYCHAIN_KEY_NODE:
case MASC_NODE:
+ case PIM_NODE:
case VTY_NODE:
vty_config_unlock (vty);
vty->node = ENABLE_NODE;
diff --git a/lib/command.h b/lib/command.h
index 8dc50d0d..8eb0cbd9 100644
--- a/lib/command.h
+++ b/lib/command.h
@@ -88,6 +88,7 @@ enum node_type
OSPF_NODE, /* OSPF protocol mode */
OSPF6_NODE, /* OSPF protocol for IPv6 mode */
ISIS_NODE, /* ISIS protocol mode */
+ PIM_NODE, /* PIM protocol mode */
MASC_NODE, /* MASC for multicast. */
IRDP_NODE, /* ICMP Router Discovery Protocol mode. */
IP_NODE, /* Static ip route node. */
diff --git a/lib/log.c b/lib/log.c
index 04f8fab6..f02e4c73 100644
--- a/lib/log.c
+++ b/lib/log.c
@@ -51,6 +51,7 @@ const char *zlog_proto_names[] =
"BABEL",
"OSPF6",
"ISIS",
+ "PIM",
"MASC",
NULL,
};
diff --git a/lib/log.h b/lib/log.h
index f3b43ad1..77cd53bc 100644
--- a/lib/log.h
+++ b/lib/log.h
@@ -53,6 +53,7 @@ typedef enum
ZLOG_BABEL,
ZLOG_OSPF6,
ZLOG_ISIS,
+ ZLOG_PIM,
ZLOG_MASC
} zlog_proto_t;
diff --git a/lib/memory.c b/lib/memory.c
index 620bdee5..84daeeef 100644
--- a/lib/memory.c
+++ b/lib/memory.c
@@ -521,6 +521,17 @@ DEFUN (show_memory_isis,
return CMD_SUCCESS;
}
+DEFUN (show_memory_pim,
+ show_memory_pim_cmd,
+ "show memory pim",
+ SHOW_STR
+ "Memory statistics\n"
+ "PIM memory\n")
+{
+ show_memory_vty (vty, memory_list_pim);
+ return CMD_SUCCESS;
+}
+
void
memory_init (void)
{
@@ -545,6 +556,7 @@ memory_init (void)
install_element (VIEW_NODE, &show_memory_ospf_cmd);
install_element (VIEW_NODE, &show_memory_ospf6_cmd);
install_element (VIEW_NODE, &show_memory_isis_cmd);
+ install_element (VIEW_NODE, &show_memory_pim_cmd);
install_element (ENABLE_NODE, &show_memory_cmd);
install_element (ENABLE_NODE, &show_memory_all_cmd);
@@ -557,6 +569,7 @@ memory_init (void)
install_element (ENABLE_NODE, &show_memory_ospf_cmd);
install_element (ENABLE_NODE, &show_memory_ospf6_cmd);
install_element (ENABLE_NODE, &show_memory_isis_cmd);
+ install_element (ENABLE_NODE, &show_memory_pim_cmd);
}
/* Stats querying from users */
diff --git a/lib/memtypes.c b/lib/memtypes.c
index 47a34387..1a0c11fe 100644
--- a/lib/memtypes.c
+++ b/lib/memtypes.c
@@ -255,6 +255,21 @@ struct memory_list memory_list_isis[] =
{ -1, NULL },
};
+struct memory_list memory_list_pim[] =
+{
+ { MTYPE_PIM_CHANNEL_OIL, "PIM SSM (S,G) channel OIL" },
+ { MTYPE_PIM_INTERFACE, "PIM interface" },
+ { MTYPE_PIM_IGMP_JOIN, "PIM interface IGMP static join" },
+ { MTYPE_PIM_IGMP_SOCKET, "PIM interface IGMP socket" },
+ { MTYPE_PIM_IGMP_GROUP, "PIM interface IGMP group" },
+ { MTYPE_PIM_IGMP_GROUP_SOURCE, "PIM interface IGMP source" },
+ { MTYPE_PIM_NEIGHBOR, "PIM interface neighbor" },
+ { MTYPE_PIM_IFCHANNEL, "PIM interface (S,G) state" },
+ { MTYPE_PIM_UPSTREAM, "PIM upstream (S,G) state" },
+ { MTYPE_PIM_SSMPINGD, "PIM sspimgd socket" },
+ { -1, NULL },
+};
+
struct memory_list memory_list_vtysh[] =
{
{ MTYPE_VTYSH_CONFIG, "Vtysh configuration", },
@@ -271,5 +286,6 @@ struct mlist mlists[] __attribute__ ((unused)) = {
{ memory_list_ospf6, "OSPF6" },
{ memory_list_isis, "ISIS" },
{ memory_list_bgp, "BGP" },
+ { memory_list_pim, "PIM" },
{ NULL, NULL},
};
diff --git a/lib/route_types.txt b/lib/route_types.txt
index cebf01fc..1b856079 100644
--- a/lib/route_types.txt
+++ b/lib/route_types.txt
@@ -51,6 +51,7 @@ ZEBRA_ROUTE_OSPF, ospf, ospfd, 'O', 1, 0, "OSPF"
ZEBRA_ROUTE_OSPF6, ospf6, ospf6d, 'O', 0, 1, "OSPFv6"
ZEBRA_ROUTE_ISIS, isis, isisd, 'I', 1, 1, "IS-IS"
ZEBRA_ROUTE_BGP, bgp, bgpd, 'B', 1, 1, "BGP"
+ZEBRA_ROUTE_PIM, pim, pimd, 'P', 1, 0, "PIM"
# HSLS and OLSR both are AFI independent (so: 1, 1), however
# we want to disable for them for general Quagga distribution.
# This at least makes it trivial for users of these protocols
@@ -71,6 +72,7 @@ ZEBRA_ROUTE_OSPF, "Open Shortest Path First (OSPFv2)"
ZEBRA_ROUTE_OSPF6, "Open Shortest Path First (IPv6) (OSPFv3)"
ZEBRA_ROUTE_ISIS, "Intermediate System to Intermediate System (IS-IS)"
ZEBRA_ROUTE_BGP, "Border Gateway Protocol (BGP)"
+ZEBRA_ROUTE_PIM, "Protocol Independent Multicast (PIM)"
ZEBRA_ROUTE_HSLS, "Hazy-Sighted Link State Protocol (HSLS)"
ZEBRA_ROUTE_OLSR, "Optimised Link State Routing (OLSR)"
ZEBRA_ROUTE_BABEL, "Babel routing protocol (Babel)"
diff --git a/lib/thread.h b/lib/thread.h
index 4856dec7..43ffbf60 100644
--- a/lib/thread.h
+++ b/lib/thread.h
@@ -1,6 +1,5 @@
/* Thread management routine header.
* Copyright (C) 1998 Kunihiro Ishiguro
- * Portions Copyright (c) 2008 Everton da Silva Marques <everton.marques@gmail.com>
*
* This file is part of GNU Zebra.
*
diff --git a/lib/vty.c b/lib/vty.c
index 3f08b9e9..b8db7c89 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -713,6 +713,7 @@ vty_end_config (struct vty *vty)
case KEYCHAIN_NODE:
case KEYCHAIN_KEY_NODE:
case MASC_NODE:
+ case PIM_NODE:
case VTY_NODE:
vty_config_unlock (vty);
vty->node = ENABLE_NODE;
@@ -1117,6 +1118,7 @@ vty_stop_input (struct vty *vty)
case KEYCHAIN_NODE:
case KEYCHAIN_KEY_NODE:
case MASC_NODE:
+ case PIM_NODE:
case VTY_NODE:
vty_config_unlock (vty);
vty->node = ENABLE_NODE;
diff --git a/lib/zclient.c b/lib/zclient.c
index 3b5477e9..41ecbb61 100644
--- a/lib/zclient.c
+++ b/lib/zclient.c
@@ -229,7 +229,7 @@ zclient_socket_connect (struct zclient *zclient)
#ifdef HAVE_TCP_ZEBRA
zclient->sock = zclient_socket ();
#else
- zclient->sock = zclient_socket_un (zclient_serv_path ? zclient_serv_path : ZEBRA_SERV_PATH);
+ zclient->sock = zclient_socket_un (zclient_serv_path_get());
#endif
return zclient->sock;
}
@@ -1053,6 +1053,11 @@ zclient_event (enum event event, struct zclient *zclient)
}
}
+const char *const zclient_serv_path_get()
+{
+ return zclient_serv_path ? zclient_serv_path : ZEBRA_SERV_PATH;
+}
+
void
zclient_serv_path_set (char *path)
{
diff --git a/lib/zclient.h b/lib/zclient.h
index a660bbf1..d0c5450b 100644
--- a/lib/zclient.h
+++ b/lib/zclient.h
@@ -134,6 +134,7 @@ extern void zclient_free (struct zclient *);
extern int zclient_socket_connect (struct zclient *);
extern void zclient_serv_path_set (char *path);
+extern const char *const zclient_serv_path_get (void);
/* Send redistribute command to zebra daemon. Do not update zclient state. */
extern int zebra_redistribute_send (int command, struct zclient *, int type);
diff --git a/pimd/.gitignore b/pimd/.gitignore
new file mode 100644
index 00000000..51a2ac87
--- /dev/null
+++ b/pimd/.gitignore
@@ -0,0 +1,16 @@
+Makefile
+Makefile.in
+libpim.a
+pimd
+test_igmpv3_join
+tags
+TAGS
+.deps
+*.o
+*.lo
+*.la
+*.libs
+.arch-inventory
+.arch-ids
+*~
+*.loT
diff --git a/pimd/AUTHORS b/pimd/AUTHORS
new file mode 100644
index 00000000..f6135a41
--- /dev/null
+++ b/pimd/AUTHORS
@@ -0,0 +1,9 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+# Everton da Silva Marques <everton.marques@gmail.com>
+$ more ~/.gitconfig
+[user]
+ name = Everton Marques
+ email = everton.marques@gmail.com
+
+-x-
diff --git a/pimd/CAVEATS b/pimd/CAVEATS
new file mode 100644
index 00000000..9f07bda6
--- /dev/null
+++ b/pimd/CAVEATS
@@ -0,0 +1,178 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+C1 IGMPv3 backward compatibility with IGMPv1 and IGMPv2 is not
+ implemented. See RFC 3376, 7.3. Multicast Router Behavior. That's
+ because only Source-Specific Multicast is currently targeted.
+
+C2 IGMPv3 support for forwarding any-source groups is not
+ implemented. Traffic for groups in mode EXCLUDE {empty} won't be
+ forwarded. See RFC 3376, 6.3. Source-Specific Forwarding
+ Rules. That's because only Source-Specific Multicast is currently
+ targeted.
+
+C3 Load Splitting of IP Multicast Traffic over ECMP is not supported.
+ See also: RFC 2991
+ Multipath Issues in Unicast and Multicast Next-Hop Selection
+ http://www.rfc-editor.org/rfc/rfc2991.txt
+
+C4 IPSec AH authentication is not supported (RFC 4601:
+ 6.3. Authentication Using IPsec).
+
+C5 PIM support is limited to SSM mode as defined in section 4.8.2
+ (PIM-SSM-Only Routers) of RFC4601. That's because only
+ Source-Specific Multicast is currently targeted.
+
+C6 PIM implementation currently does not support IPv6. PIM-SSM
+ requires IGMPv3 for IPv4 and MLDv2 for IPv6. MLDv2 is currently
+ missing. See also CAVEAT C9.
+
+C7 FIXED (S,G) Assert state machine (RFC 4601, section 4.6.1) is not
+ implemented. See also TODO T6. See also CAVEAT C10.
+
+C8 It is not possible to disable join suppression in order to
+ explicitly track the join membership of individual downstream
+ routers.
+ - IGMPv3 Explicit Membership Tracking is not supported.
+ When explicit tracking is enabled on a router, the router can
+ individually track the Internet Group Management Protocol (IGMP)
+ membership state of all reporting hosts. This feature allows the
+ router to achieve minimal leave latencies when hosts leave a
+ multicast group or channel. Example:
+ conf t
+ interface eth0
+ ip igmp explicit-tracking
+
+C9 Only IPv4 Address Family (number=1) is supported in the PIM Address
+ Family field.
+ See also RFC 4601: 5.1. PIM Address Family
+ See also CAVEAT C6.
+ See also http://www.iana.org/assignments/address-family-numbers
+
+C10 FIXED Assert metric depends on metric_preference and
+ route_metric. Those parameters should be fetched from RIB
+ (zebra). See also pim_rpf.c, pim_rpf_update().
+
+C11 SSM Mapping is not supported
+
+ SSM Mapping Overview:
+
+ SSM mapping introduces a means for the last hop router to discover
+ sources sending to groups. When SSM mapping is configured, if a
+ router receives an IGMPv1 or IGMPv2 membership report for a
+ particular group G, the router translates this report into one or
+ more (S, G) channel memberships for the well-known sources
+ associated with this group.
+
+ When the router receives an IGMPv1 or IGMPv2 membership report for
+ a group G, the router uses SSM mapping to determine one or more
+ source IP addresses for the group G. SSM mapping then translates
+ the membership report as an IGMPv3 report INCLUDE (G, [S1, G],
+ [S2, G]...[Sn, G] and continues as if it had received an IGMPv3
+ report. The router then sends out PIM joins toward (S1, G) to (Sn,
+ G) and continues to be joined to these groups as long as it
+ continues to receive the IGMPv1 or IGMPv2 membership reports and
+ as long as the SSM mapping for the group remains the same. SSM
+ mapping, thus, enables you to leverage SSM for video delivery to
+ legacy STBs that do not support IGMPv3 or for applications that do
+ not take advantage of the IGMPv3 host stack.
+
+ SSM mapping enables the last hop router to determine the source
+ addresses either by a statically configured table on the router or
+ by consulting a DNS server. When the statically configured table
+ is changed, or when the DNS mapping changes, the router will leave
+ the current sources associated with the joined groups.
+
+C12 FIXED MRIB for incongruent unicast/multicast topologies is not
+ supported. RPF mechanism currently just looks up the information
+ in the unicast routing table.
+
+ See also:
+ RFC5110: 2.2.3. Issue: Overlapping Unicast/Multicast Topology
+
+ Sometimes, multicast RPF mechanisms first look up the multicast
+ routing table, or M-RIB ("topology database") with a longest
+ prefix match algorithm, and if they find any entry (including a
+ default route), that is used; if no match is found, the unicast
+ routing table is used instead.
+
+C13 Can't detect change of primary address before the actual change.
+ Possible approach is to craft old interface address into ip source
+ address by using raw ip socket.
+
+ See also:
+
+ RFC 4601: 4.3.1. Sending Hello Messages
+
+ Before an interface goes down or changes primary IP address, a
+ Hello message with a zero HoldTime should be sent immediately
+ (with the old IP address if the IP address changed).
+
+ See also pim_sock_delete().
+
+C14 FIXED Detection of interface primary address changes may fail when
+ there are multiple addresses.
+ See also TODO T32.
+
+C15 Changes in interface secondary address list are not immediately
+ detected.
+ See also detect_secondary_address_change
+ See also TODO T31.
+
+C16 AMT Draft (mboned-auto-multicast) is not supported.
+ AMT = Automatic IP Multicast Without Explicit Tunnels
+
+ See also:
+
+ Draft
+ http://tools.ietf.org/html/draft-ietf-mboned-auto-multicast
+ http://tools.ietf.org/html/draft-ietf-mboned-auto-multicast-09
+
+ AMT gateway implementation for Linux
+ http://cs.utdallas.edu/amt/
+
+ AMT for Streaming (IPTV) on Global IP Multicast by Greg Shepherd (Cisco)
+ http://nznog.miniconf.org/nznog-2008-sysadmin-miniconf-greg-shepherd-iptv.pdf
+
+C17 SNMP / RFC 5060 (PIM MIB) is not supported.
+
+C18 MFC never recovers from removal of static route to source
+
+ # route add -host 1.2.3.4 gw 192.168.56.10
+ Before removal:
+ quagga-pimd-router# sh ip mroute
+ Source Group Proto Input iVifI Output oVifI TTL Uptime
+ 1.2.3.4 232.1.2.3 I eth1 3 eth0 2 1 00:00:36
+
+ # route del -host 1.2.3.4 gw 192.168.56.10
+ After removal: sh ip mroute --> empty output
+
+ # route add -host 1.2.3.4 gw 192.168.56.10
+ After the route is restored: sh ip mroute --> never recovers (empty output)
+
+ At this point, "no ip pim ssm" on the upstream interface (eth0) crashes pimd:
+
+ 2014/02/14 16:30:14 PIM: ifmembership_set: (S,G)=(1.2.3.4,232.1.2.3) membership now is NOINFO on interface eth0
+ 2014/02/14 16:30:14 PIM: pim_ifchannel_update_assert_tracking_desired: AssertTrackingDesired(1.2.3.4,232.1.2.3,eth0) changed from 1 to 0
+ 2014/02/14 16:30:14 PIM: pim_zebra.c del_oif: nonexistent protocol mask 2 removed OIF eth0 (vif_index=2, min_ttl=0) from channel (S,G)=(1.2.3.4,232.1.2.3)
+ 2014/02/14 16:30:14 PIM: pim_ifchannel_update_could_assert: CouldAssert(1.2.3.4,232.1.2.3,eth0) changed from 1 to 0
+ 2014/02/14 16:30:14 PIM: pim_ifchannel_update_my_assert_metric: my_assert_metric(1.2.3.4,232.1.2.3,eth0) changed from 0,0,0,10.0.2.15 to 1,4294967295,4294967295,0.0.0.0
+ 2014/02/14 16:30:14 PIM: pim_zebra.c del_oif: nonexistent protocol mask 1 removed OIF eth0 (vif_index=2, min_ttl=0) from channel (S,G)=(1.2.3.4,232.1.2.3)
+ 2014/02/14 16:30:14 PIM: Assertion `!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)' failed in file pim_igmpv3.c, line 412, function igmp_source_delete
+
+C19 Provision to prevent group mode clash
+
+ Beware group mode clash. A host/application issuing IGMPv2
+ any-source joins for a group will disrupt SSM multicast for that
+ group.
+
+ For instance, support for source-specific static igmp WILL FAIL if
+ there is host/application issuing IGMPv2 any-source joins for the
+ same group.
+
+ The reason is the IGMPv2 any-source join forces qpimd to switch
+ the group mode to ASM (any-source multicast); however, qpimd is
+ unable to program ASM groups into the kernel; multicast won't
+ flow. There could be some provision to prevent such a behavior,
+ but currently there is none.
+
+-x-
diff --git a/pimd/COMMANDS b/pimd/COMMANDS
new file mode 100644
index 00000000..425ac822
--- /dev/null
+++ b/pimd/COMMANDS
@@ -0,0 +1,81 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+global configuration commands:
+ pimd:
+ ip multicast-routing Enable IP multicast forwarding
+ ip ssmpingd Enable ssmpingd operation
+
+ zebra:
+ ip mroute Configure static unicast route into MRIB for multicast RPF lookup
+
+interface configuration commands:
+ pimd:
+ ip igmp Enable IGMP operation
+ ip igmp join IGMP join multicast group
+ ip igmp query-interval <1-1800> IGMP host query interval
+ ip igmp query-max-response-time <1-25> IGMP max query response (seconds)
+ ip igmp query-max-response-time-dsec <10-250> IGMP max query response (deciseconds)
+ ip pim ssm Enable PIM SSM operation
+
+verification commands:
+ pimd:
+ show ip igmp interface IGMP interface information
+ show ip igmp join IGMP static join information
+ show ip igmp parameters IGMP parameters information
+ show ip igmp groups IGMP groups information
+ show ip igmp groups retransmissions IGMP group retransmission
+ show ip igmp sources IGMP sources information
+ show ip igmp sources retransmissions IGMP source retransmission
+ show ip pim address PIM interface address
+ show ip pim assert PIM interface assert
+ show ip pim assert-internal PIM interface internal assert state
+ show ip pim assert-metric PIM interface assert metric
+ show ip pim assert-winner-metric PIM interface assert winner metric
+ show ip pim designated-router PIM interface designated router
+ show ip pim hello PIM interface hello information
+ show ip pim interface PIM interface information
+ show ip pim lan-prune-delay PIM neighbors LAN prune delay parameters
+ show ip pim local-membership PIM interface local-membership
+ show ip pim jp-override-interval PIM interface J/P override interval
+ show ip pim join PIM interface join information
+ show ip pim neighbor PIM neighbor information
+ show ip pim rpf PIM cached source rpf information
+ show ip pim secondary PIM neighbor addresses
+ show ip pim upstream PIM upstream information
+ show ip pim upstream-join-desired PIM upstream join-desired
+ show ip pim upstream-rpf PIM upstream source rpf
+ show ip multicast Multicast global information
+ show ip mroute IP multicast routing table
+ show ip mroute count Route and packet count data
+ show ip rib IP unicast routing table
+ show ip ssmpingd ssmpingd operation
+
+ zebra:
+ show ip rpf Display RPF information for multicast source
+
+debug commands:
+ pimd:
+ clear ip interfaces Reset interfaces
+ clear ip igmp interfaces Reset IGMP interfaces
+ clear ip mroute Reset multicast routes
+ clear ip pim interfaces Reset PIM interfaces
+ clear ip pim oil Rescan PIM OIL (output interface list)
+ debug igmp IGMP protocol activity
+ debug mroute PIM interaction with kernel MFC cache
+ debug pim PIM protocol activity
+ debug pim zebra ZEBRA protocol activity
+ debug ssmpingd ssmpingd activity
+ show debugging State of each debugging option
+ test igmp receive report Test reception of IGMPv3 report
+ test pim receive assert Test reception of PIM assert
+ test pim receive dump Test reception of PIM packet dump
+ test pim receive hello Test reception of PIM hello
+ test pim receive join Test reception of PIM join
+ test pim receive prune Test reception of PIM prune
+ test pim receive upcall Test reception of kernel upcall
+
+statistics commands:
+ pimd:
+ show memory pim PIM memory statistics
+
+-x-
diff --git a/pimd/COPYING b/pimd/COPYING
new file mode 100644
index 00000000..3912109b
--- /dev/null
+++ b/pimd/COPYING
@@ -0,0 +1,340 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ 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; if not, write to the Free Software
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/pimd/DEBUG b/pimd/DEBUG
new file mode 100644
index 00000000..72fb8264
--- /dev/null
+++ b/pimd/DEBUG
@@ -0,0 +1,86 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+DEBUG HINTS
+
+ - Check the source is issuing multicast packets with TTL high enough
+ to reach the recipients.
+
+ - Check the multicast packets are not being dropped due to
+ fragmentation problems.
+
+ - Three easy options to test IGMPv3 joins from the receiver host:
+
+ 1) Configure pimd on the receiver host with "ip igmp join":
+
+ interface eth0
+ ip pim ssm
+ ip igmp join 239.1.1.1 1.1.1.1
+
+ 2) Use test_igmpv3_join command-line utility (provided with qpimd):
+
+ test_igmpv3_join eth0 239.1.1.1 1.1.1.1
+
+ 3) User the Stig Venaas' ssmping utility:
+
+ ssmping -I eth0 1.1.1.1
+
+ To see multicast responses with ssmping, you will need run on
+ the host 1.1.1.1 either:
+ a) Stig Venaas' ssmpingd command-line daemon
+ OR
+ b) qpimd built-in ssmpingd service:
+ conf t
+ ip ssmpingd 1.1.1.1
+
+ - Using nepim to generate multicast stream from 1.1.1.1 to 239.1.1.1:
+
+ Notices:
+
+ a) The host unicast address 1.1.1.1 must be reachable from the
+ receiver.
+
+ b) nepim tool requires the receiver must be started *before* the
+ sender.
+
+ First: Start a receiver for that stream by running:
+
+ nepim -q -6 -j 1.1.1.1+239.1.1.1@eth0
+ (Remember of enabling both "ip pim ssm" and "ip igmp" under eth0.)
+
+ Second: Start the sender at host 1.1.1.1.
+
+ The following command generates a 100-kbps multicast stream for
+ channel 1.1.1.1,239.1.1.1 with TTL 10 and 1000-byte payload per UDP
+ packet (to avoid fragmentation):
+
+ nepim -6 -M -b 1.1.1.1 -c 239.1.1.1 -T 10 -W 1000 -r 100k -a 1d
+
+
+
+SAMPLE DEBUG COMMANDS
+
+ conf t
+ int eth0
+ ip pim ssm
+
+ test pim receive hello eth0 192.168.0.2 600 10 111 1000 3000 0
+ test pim receive join eth0 600 192.168.0.1 192.168.0.2 239.1.1.1 1.1.1.1
+
+ show ip pim join
+
+
+INTEROPERABILITY WITH CISCO
+
+ ! Cisco IP Multicast command reference:
+ ! ftp://ftpeng.cisco.com/ipmulticast/Multicast-Commands
+ !
+ ip pim ssm default ! enable SSM mode for groups 232.0.0.0/8
+ ip multicast-routing
+ ip pim state-refresh disable
+ no ip pim dm-fallback
+ !
+ interface FastEthernet0
+ ip pim sparse-mode
+ ip igmp version 3
+
+-x-
diff --git a/pimd/LINUX_KERNEL_MROUTE_MFC b/pimd/LINUX_KERNEL_MROUTE_MFC
new file mode 100644
index 00000000..e87e567f
--- /dev/null
+++ b/pimd/LINUX_KERNEL_MROUTE_MFC
@@ -0,0 +1,26 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+#
+# The Linux Kernel MFC (Multicast Forwarding Cache)
+#
+
+# Check Linux kernel multicast interfaces:
+cat /proc/net/dev_mcast
+
+# Check that interface eth0 is forwarding multicast:
+cat /proc/sys/net/ipv4/conf/eth0/mc_forwarding
+
+# Check Linux kernel multicast VIFs:
+cat /proc/net/ip_mr_vif
+Interface BytesIn PktsIn BytesOut PktsOut Flags Local Remote
+
+# Check Linux kernel MFC:
+# Oifs format = vifi:TTL
+cat /proc/net/ip_mr_cache
+Group Origin Iif Pkts Bytes Wrong Oifs
+
+# iproute2 can display the MFC:
+ip mroute show
+(2.2.2.2, 239.2.2.2) Iif: eth1 Oifs: eth0
+
+# -- end-of-file --
diff --git a/pimd/Makefile.am b/pimd/Makefile.am
new file mode 100644
index 00000000..7173a231
--- /dev/null
+++ b/pimd/Makefile.am
@@ -0,0 +1,76 @@
+## Process this file with automake to produce Makefile.in.
+## $QuaggaId: $Format:%an, %ai, %h$ $
+
+# qpimd - pimd for quagga
+# Copyright (C) 2008 Everton da Silva Marques
+#
+# qpimd 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,
+# or (at your option) any later version.
+#
+# qpimd 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 qpimd; see the file COPYING. If not, write
+# to the Free Software Foundation, Inc., 59 Temple Place - Suite
+# 330, Boston, MA 02111-1307, USA.
+
+# PIM_DEBUG_BYDEFAULT: Automatically enables all pimd "debug ..." commands
+# PIM_ZCLIENT_DEBUG: Support for internal ZEBRA client debugging
+# PIM_CHECK_RECV_IFINDEX_SANITY: Compare socket ifindex with recv ifindex
+# PIM_REPORT_RECV_IFINDEX_MISMATCH: Report sock/recv ifindex mismatch
+# PIM_ENFORCE_LOOPFREE_MFC: Refuse adding looping MFC entries
+# PIM_UNEXPECTED_KERNEL_UPCALL: Report unexpected kernel upcall
+
+PIM_DEFS =
+#PIM_DEFS += -DPIM_DEBUG_BYDEFAULT
+PIM_DEFS += -DPIM_CHECK_RECV_IFINDEX_SANITY
+#PIM_DEFS += -DPIM_REPORT_RECV_IFINDEX_MISMATCH
+PIM_DEFS += -DPIM_ZCLIENT_DEBUG
+PIM_DEFS += -DPIM_ENFORCE_LOOPFREE_MFC
+#PIM_DEFS += -DPIM_UNEXPECTED_KERNEL_UPCALL
+
+INCLUDES = @INCLUDES@ -I.. -I$(top_srcdir) -I$(top_srcdir)/lib
+DEFS = @DEFS@ -DSYSCONFDIR=\"$(sysconfdir)/\" $(PIM_DEFS)
+INSTALL_SDATA=@INSTALL@ -m 600
+LIBS = @LIBS@
+
+AM_CFLAGS = $(PICFLAGS)
+AM_LDFLAGS = $(PILDFLAGS)
+
+noinst_LIBRARIES = libpim.a
+sbin_PROGRAMS = pimd
+bin_PROGRAMS = test_igmpv3_join
+
+libpim_a_SOURCES = \
+ 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_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 \
+ pim_msg.c pim_upstream.c pim_rpf.c pim_rand.c pim_macro.c \
+ pim_igmp_join.c pim_ssmpingd.c pim_int.c
+
+noinst_HEADERS = \
+ 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_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 \
+ pim_msg.h pim_upstream.h pim_rpf.h pim_rand.h pim_macro.h \
+ pim_igmp_join.h pim_ssmpingd.h pim_int.h
+
+pimd_SOURCES = \
+ pim_main.c $(libpim_a_SOURCES)
+
+test_igmpv3_join_SOURCES = \
+ test_igmpv3_join.c pim_igmp_join.c
+
+pimd_LDADD = ../lib/libzebra.la @LIBCAP@
+
+examplesdir = $(exampledir)
+dist_examples_DATA = pimd.conf.sample
diff --git a/pimd/README b/pimd/README
new file mode 100644
index 00000000..1e3f72c8
--- /dev/null
+++ b/pimd/README
@@ -0,0 +1,164 @@
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+#
+
+INTRODUCTION
+
+ qpimd aims to implement a PIM (Protocol Independent Multicast)
+ daemon for the Quagga Routing Suite.
+
+ Initially qpimd targets only PIM SSM (Source-Specific
+ Multicast) mode as defined in section 4.8.2 (PIM-SSM-Only
+ Routers) of RFC 4601.
+
+ In order to deliver end-to-end multicast routing control
+ plane, qpimd includes the router-side of IGMPv3 (RFC 3376).
+
+LICENSE
+
+ qpimd - pimd for quagga
+ Copyright (C) 2008 Everton da Silva Marques
+
+ qpimd 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,
+ or (at your option) any later version.
+
+ qpimd 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 qpimd; see the file COPYING. If not, write
+ to the Free Software Foundation, Inc., 59 Temple Place - Suite
+ 330, Boston, MA 02111-1307, USA.
+
+HOME SITE
+
+ qpimd lives at:
+
+ https://github.com/udhos/qpimd
+
+PLATFORMS
+
+ qpimd has been tested with Debian Lenny under Linux 2.6.
+
+REQUIREMENTS
+
+ qpimd requires Quagga (0.99.11 or higher from http://www.quagga.net)
+
+ The GNU Build System (Autotools) is required to build from
+ source code repository.
+
+ gawk is also needed to build with Autotools. Any other awk
+ usually won't work.
+
+BUILDING FROM QUAGGA GIT REPOSITORY
+
+ 1) Get the latest quagga source tree
+
+ # git clone git://code.quagga.net/quagga.git quagga
+
+ 2) Apply qpimd patch into quagga source tree
+
+ # patch -p1 -d quagga < pimd-0.153-quagga-git20090623.patch
+
+ 3) Compile and install quagga
+
+ # cd quagga
+ # ./bootstrap.sh
+ # ./configure --prefix=/usr/local/quagga --enable-pimd
+ # make
+ # make install
+
+BUILDING FROM QUAGGA TARBALL
+
+ 1) Get the latest quagga tarball
+
+ # wget http://www.quagga.net/download/quagga-0.99.13.tar.gz
+
+ 2) Unpack the quagga tarball
+
+ # tar xzf quagga-0.99.13.tar.gz
+
+ 3) Apply qpimd patch into quagga source tree
+
+ # patch -p1 -d quagga-0.99.13 < pimd-0.153-quagga-0.99.13.patch
+
+ 4) Compile and install quagga
+
+ # cd quagga-0.99.13
+ # ./configure --prefix=/usr/local/quagga --enable-pimd
+ # make
+ # make install
+
+USAGE
+
+ 1) Configure and start the zebra daemon
+
+ # cp /usr/local/quagga/etc/zebra.conf.sample /usr/local/quagga/etc/zebra.conf
+ # vi /usr/local/quagga/etc/zebra.conf
+ # /usr/local/quagga/sbin/zebra
+
+ 2) Configure and start the pimd daemon
+
+ # cp /usr/local/quagga/etc/pimd.conf.sample /usr/local/quagga/etc/pimd.conf
+ # vi /usr/local/quagga/etc/pimd.conf
+ # /usr/local/quagga/sbin/pimd
+
+ 3) Access pimd vty interface at port TCP 2611
+
+ # telnet localhost 2611
+
+CONFIGURATION COMMANDS
+
+ See available commands in the file pimd/COMMANDS.
+
+KNOWN CAVEATS
+
+ See list of known caveats in the file pimd/CAVEATS.
+
+SUPPORT
+
+ Please post comments, questions, patches, bug reports at the
+ support site:
+
+ https://github.com/udhos/qpimd
+
+RELATED WORK
+
+ igmprt: An IGMPv3-router implementation
+ - http://www.loria.fr/~lahmadi/igmpv3-router.html
+
+ USC pimd: PIMv2-SM daemon
+ - http://netweb.usc.edu/pim/pimd (URL broken in 2008-12-23)
+ - http://packages.debian.org/source/sid/pimd (from Debian)
+
+ troglobit pimd: This is the original USC pimd from
+ http://netweb.usc.edu/pim/. In January 16, 2010 it was revived
+ with the intention to collect patches floating around in
+ Debian, Gentoo, Lintrack and other distribution repositories
+ and to provide a central point of collaboration.
+ - http://github.com/troglobit/pimd
+
+ zpimd: zpimd is not dependent of zebra or any other routing daemon
+ - ftp://robur.slu.se/pub/Routing/Zebra
+ - http://sunsite2.icm.edu.pl/pub/unix/routing/zpimd
+
+ mrd6: an IPv6 Multicast Router for Linux systems
+ - http://fivebits.net/proj/mrd6/
+
+ MBGP: Implementation of RFC 2858 for Quagga
+ - git://git.coplanar.net/~balajig/quagga
+ - http://www.gossamer-threads.com/lists/quagga/dev/18000
+
+REFERENCES
+
+ IANA Protocol Independent Multicast (PIM) Parameters
+ http://www.iana.org/assignments/pim-parameters/pim-parameters.txt
+
+ Address Family Numbers
+ http://www.iana.org/assignments/address-family-numbers
+
+ -- END --
diff --git a/pimd/TODO b/pimd/TODO
new file mode 100644
index 00000000..2308573b
--- /dev/null
+++ b/pimd/TODO
@@ -0,0 +1,426 @@
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+T1 DONE Implement debug command
+ test pim receive join
+
+T2 DONE Implement debug command
+ test pim receive prune
+
+T3 DONE Per-interface Downstream (S,G) state machine
+ (RFC 4601 4.5.3. Receiving (S,G) Join/Prune Messages)
+
+T4 DONE Upstream (S,G) state machine
+ (RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages)
+
+T5 DONE Verify Data Packet Forwarding Rules
+ RFC 4601 4.2. Data Packet Forwarding Rules
+ RFC 4601 4.8.2. PIM-SSM-Only Routers
+
+ Additionally, the Packet forwarding rules of Section 4.2 can be
+ simplified in a PIM-SSM-only router:
+
+ iif is the incoming interface of the packet.
+ oiflist = NULL
+ if (iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined) {
+ oiflist = inherited_olist(S,G)
+ } else if (iif is in inherited_olist(S,G)) {
+ send Assert(S,G) on iif
+ }
+ oiflist = oiflist (-) iif
+ forward packet on all interfaces in oiflist
+
+ Macro:
+ inherited_olist(S,G) =
+ joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+
+T6 DONE Implement (S,G) Assert state machine (RFC 4601, section 4.6.1).
+ Changes in pim_ifchannel.ifassert_winner should trigger
+ pim_upstream_update_join_desired().
+ Depends on TODO T27.
+ Depends on TODO T33.
+ See also CAVEAT C7.
+ See also: RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages
+ Transitions from Joined State
+ RPF'(S,G) changes due to an Assert
+
+ http://www.hep.ucl.ac.uk/~ytl/multi-cast/pim-dm_01.html:
+
+ The PIM Assert mechanism is used to shutoff duplicate flows onto
+ the same multiaccess network. Routers detect this condiction when
+ they receive an (S,G) packet via a multi-access interface that is
+ in the (S,G) OIL. This causes the routers to send Assert
+ Messages.
+
+ Note that neighbors will not accept Join/Prune or Assert messages
+ from a router unless they have first heard a Hello message from that
+ router. 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.
+
+T7 DONE Implement hello option: LAN Prune Delay
+
+T8 DONE Implement J/P_Override_Interval(I)
+ Depends on TODO T7.
+ See pim_ifchannel.c, pim_ifchannel_prune(), jp_override_interval.
+
+T9 DONE Detect change in IGMPv3 RPF interface/next-hop for S and update.
+ channel_oil vif index accordingly ?
+ Beware accidentaly adding looped MFC entries (IIF=OIF).
+
+T10 DONE React to (S,G) join directed to another upstream address. See
+ also:
+
+ RFC 4601: 4.5.7. Sending (S,G) Join/Prune Messages
+
+ If a router wishes to propagate a Join(S,G) upstream, it must also
+ watch for messages on its upstream interface from other routers on
+ that subnet, and these may modify its behavior. If it sees a
+ Join(S,G) to the correct upstream neighbor, it should suppress its
+ own Join(S,G). If it sees a Prune(S,G), Prune(S,G,rpt), or
+ Prune(*,G) to the correct upstream neighbor towards S, it should
+ be prepared to override that prune by scheduling a Join(S,G) to be
+ sent almost immediately.
+
+T11 DONE Review protocol modifications for SSM
+ (RFC 4601 4.8.1. Protocol Modifications for SSM Destination
+ Addresses)
+
+T12 DONE Review updates of RPF entries.
+ FIXME pim_upstream.c send_join():
+ Currently only one upstream state is affected by detection of RPF change.
+ RPF change should affect all upstream states sharing the RPF cache.
+
+T13 DONE Check that RFC macros using S,G,RPF_interface(S) are actually
+ implemented with this strategy:
+ rpf_ifch=find_ifch(up->rpf->interface).
+ See pim_rpf.c pim_rpf_find_rpf_addr() for a correct example.
+
+ $ grep -i macro pimd/*.c
+ pimd/pim_iface.c: RFC 4601: 4.1.6. State Summarization Macros
+ pimd/pim_ifchannel.c: RFC 4601: 4.6.5. Assert State Macros
+ pimd/pim_ifchannel.c: RFC 4601: 4.1.6. State Summarization Macros
+ pimd/pim_ifchannel.c: RFC 4601: 4.1.6. State Summarization Macros
+ pimd/pim_ifchannel.c: RFC 4601: 4.6.5. Assert State Macros
+ pimd/pim_ifchannel.c: Macro:
+ pimd/pim_rpf.c: RFC 4601: 4.1.6. State Summarization Macros
+
+T14 DONE Send Assert(S,G) on iif as response to WRONGVIF kernel upcall.
+ See pim_mroute.c mroute_msg().
+
+T15 DONE Interface command to statically join (S,G).
+ interface eth0
+ ip igmp join-group 239.1.1.1 source 1.1.1.1
+
+T16 DONE RPF'(S,G) lookup is not working for S reachable with default route.
+ See "RPF'(S,G) not found" in pim_rpf_update() from pim_rpf.c.
+ Zebra daemon RIB is not reflecting changes in kernel routes
+ accurately?
+
+T17 DONE Prevent CLI from creating bogus interfaces.
+ Example:
+ conf t
+ interface xxx
+
+T18 Consider reliable pim solution (refresh reduction)
+ A Reliable Transport Mechanism for PIM
+ http://tools.ietf.org/wg/pim/draft-ietf-pim-port/
+ PORT=PIM-Over-Reliable-Transport
+
+T19 DONE Fix self as neighbor
+ See mailing list post:
+ http://lists.gnu.org/archive/html/qpimd-users/2009-04/msg00000.html
+
+T20 DONE Fix debug message: "pim_neighbor_update: internal error:
+ trying to replace same prefix list"
+ See mailing list post:
+ http://lists.gnu.org/archive/html/qpimd-users/2009-04/msg00000.html
+
+T21 DONE Clean-up PIM/IGMP interface mismatch debugging
+ See option PIM_CHECK_RECV_IFINDEX_SANITY in pimd/Makefile.am
+ See mailing list post:
+ http://lists.nongnu.org/archive/html/qpimd-users/2009-04/msg00003.html
+
+T22 DONE IGMP must be protected against adding looped MFC entries
+ created by both source and receiver attached to the same
+ interface.
+
+T23 DONE libzebra crash after zclient_lookup_nexthop.
+ See mailing list post:
+ http://lists.nongnu.org/archive/html/qpimd-users/2009-04/msg00008.html
+
+T24 DONE zserv may return recursive routes:
+ - nexthop type is set to ZEBRA_NEXTHOP_IPV4
+ - ifindex is not reported
+ - calls expecting ifindex (fib_lookup_if_vif_index) are disrupted
+ See also this mailing list post:
+ [PATCH 21/21] Link detect and recursive routes
+ http://www.gossamer-threads.com/lists/quagga/dev/17564
+
+T25 DONE Zclient nexthop lookup missing OSPF route to 1.1.1.1/32
+ See also:
+ pim_zlookup.c zclient_lookup_nexthop misses OSPF 1.1.1.1/32
+ zebra/zebra_vty.c show_ip_route_addr_cmd hits OSPF 1.1.1.1/32
+
+T26 DONE Zebra daemon is marking recursive static route as inactive.
+
+ FIXED: zebra daemon was incorrectly marking recursive routes
+ pointing to kernel routes as inactive:
+ zebra/zebra_rib.c nexthop_active_ipv4:
+ -- Original:
+ else if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_INTERNAL))
+ -- Fixed:
+ else if (CHECK_FLAG (rib->flags, ZEBRA_FLAG_INTERNAL) ||
+ match->type == ZEBRA_ROUTE_KERNEL)
+
+ Old problem description:
+
+ This prevents rib_match_ipv4 from returning its nexthop:
+ client: pim_zlookup.c zclient_read_nexthop
+ server: zebra/zserv.c zsend_ipv4_nexthop_lookup_v2 -> rib_match_ipv4
+
+ Kernel route is injected into zebra in zebra_rib.c rib_add_ipv4
+ Examples:
+ rt_netlink.c:726: rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, flags, &p, gate, src, index, table, metric, 0);
+ rt_netlink.c:864: rib_add_ipv4 (ZEBRA_ROUTE_KERNEL, 0, &p, gate, src, index, table, 0, 0);
+
+ This patch didn't fix the issue:
+ [PATCH 21/21] Link detect and recursive routes
+ http://www.gossamer-threads.com/lists/quagga/dev/17564
+
+ See the example below for the route 2.2.2.2.
+
+bash# route add -host 1.1.1.1 gw 127.0.0.1
+bash# route add -host 2.2.2.2 gw 1.1.1.1
+bash# netstat -nvr
+Kernel IP routing table
+Destination Gateway Genmask Flags MSS Window irtt Iface
+2.2.2.2 1.1.1.1 255.255.255.255 UGH 0 0 0 lo
+1.1.1.1 127.0.0.1 255.255.255.255 UGH 0 0 0 lo
+192.168.0.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
+0.0.0.0 192.168.0.2 0.0.0.0 UG 0 0 0 eth0
+bash#
+
+zebra# sh ip route
+Codes: K - kernel route, C - connected, S - static, R - RIP, O - OSPF,
+ I - ISIS, B - BGP, > - selected route, * - FIB route
+
+K>* 0.0.0.0/0 via 192.168.0.2, eth0
+K>* 1.1.1.1/32 via 127.0.0.1, lo
+K * 2.2.2.2/32 via 1.1.1.1, lo inactive
+C>* 127.0.0.0/8 is directly connected, lo
+C>* 192.168.0.0/24 is directly connected, eth0
+
+quagga-pimd-router# sh ip route 1.1.1.1
+Address NextHop Interface Metric Preference
+1.1.1.1 127.0.0.1 lo 0 0
+quagga-pimd-router#
+quagga-pimd-router# sh ip route 2.2.2.2
+Address NextHop Interface Metric Preference
+2.2.2.2 192.168.0.2 eth0 0 0
+quagga-pimd-router#
+
+T27 DONE Implement debug command
+ test pim receive assert
+ See also TODO T6: (S,G) Assert state machine.
+
+T28 DONE Bad IPv4 address family=02 in Join/Prune dump
+ Reported by Andrew Lunn <andrew.lunn@ascom.ch>
+
+ # 58-byte pim v2 Join/Prune dump
+ # ------------------------------
+ # IPv4 address family=02 is wrong, correct IPv4 address family is 01
+ # See http://www.iana.org/assignments/address-family-numbers
+ #
+ c8XX YY03 : ip src 200.xx.yy.3
+ e000 000d : ip dst 224.0.0.13
+ 9404 0000 : ip router alert option 148.4.0.0
+ 2300 ab13 : pimv2,type=3 res=00 checksum=ab13
+ 0200 : upstream family=02, encoding=00
+ c8XX YY08 : upstream 200.xx.yy.8
+ 0001 00d2 : res=00 groups=01 holdtime=00d2
+ 0200 0020 : group family=02, encoding=00, res=00, mask_len=20
+ ef01 0101 : group address 239.1.1.1
+ 0001 0000 : joined=0001 pruned=0000
+ 0200 0020 : source family=02, encoding=00, res=00, mask_len=20
+ 0101 0101 : source address 1.1.1.1
+
+T29 DONE Reset interface PIM-hello-sent counter when primary address changes
+ See pim_ifp->pim_ifstat_hello_sent
+
+ 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.
+
+T30 DONE Run interface DR election when primary address changes
+ Reported by Andrew Lunn <andrew.lunn@ascom.ch>
+ See pim_if_dr_election().
+
+T31 If an interface changes one of its secondary IP addresses, a Hello
+ message with an updated Address_List option and a non-zero
+ HoldTime should be sent immediately.
+ See also detect_secondary_address_change
+ See also CAVEAT C15.
+ See also RFC 4601: 4.3.1. Sending Hello Messages
+
+T32 FIXED Detection of interface primary address changes may fail when
+ there are multiple addresses.
+ See also CAVEAT C14.
+
+ pim_find_primary_addr() should return interface primary address
+ from connected list. Currently it returns the first address.
+
+ Zebra daemon "show int" is able to keep the primary address as
+ first address.
+
+T33 DONE Implement debug command: test pim receive upcall
+ See also TODO T6: (S,G) Assert state machine.
+
+T34 DONE assert_action_a1
+
+T35 DONE Review macros depending on interface I.
+
+ See also: grep ,I\) pimd/*.c
+
+ For the case (S,G,I) check if I is either
+ 1) interface attached to this per-interface S,G state (don't think so)
+ or
+ 2) an arbitrary interface (most probably)
+
+ For the arbitrary interface case (2), consider representing
+ interface ifp as its primary address (struct in_addr ifaddr). The
+ benefit is in_addr does not need to be dereferenced, so it does
+ not demand protection against crashes.
+
+T36 DONE React to zebra daemon link-detect up/down notification.
+ pim_ifp->primary_address is managed by detect_primary_address_change()
+ depending on to ifp->connected (managed by zebra_interface_address_read()).
+
+T37 DONE Review list of variables which may affect pim_upstream.c
+ pim_upstream_evaluate_join_desired().
+ Call pim_upstream_update_join_desired() accordingly.
+
+ See the order of invokation:
+ pim_if_dr_election(ifp);
+ pim_if_update_join_desired(pim_ifp); /* depends on DR */
+ pim_if_update_could_assert(ifp); /* depends on DR */
+ pim_if_update_my_assert_metric(ifp); /* depends on could_assert */
+
+ join_desired depends on:
+ pim_ifp->primary_address
+ pim_ifp->pim_dr_addr
+ ch->ifassert_winner_metric
+ ch->ifassert_winner
+ ch->local_ifmembership
+ ch->ifjoin_state
+ ch->upstream->rpf.source_nexthop.mrib_metric_preference
+ ch->upstream->rpf.source_nexthop.mrib_route_metric
+ ch->upstream->rpf.source_nexthop.interface
+
+T38 DONE Detect change in AssertTrackingDesired(S,G,I)
+
+ See the order of invokation:
+ dr_election: none
+ update_join_desired: depends on DR
+ update_tracking_desired: depends on DR, join_desired
+
+ AssertTrackingDesired(S,G,I) depends on:
+ pim_ifp->primary_address
+ pim_ifp->pim_dr_addr
+ ch->local_ifmembership
+ ch->ifassert_winner
+ ch->ifjoin_state
+ ch->upstream->rpf.source_nexthop.interface
+ PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(ch->upstream->flags)
+
+T39 DONE AssertTrackingDesired: flags is not matching evaluation
+
+ # show ip pim assert-internal
+ CA: CouldAssert
+ ECA: Evaluate CouldAssert
+ ATD: AssertTrackingDesired
+ eATD: Evaluate AssertTrackingDesired
+
+ Interface Address Source Group CA eCA ATD eATD
+ eth0 192.168.1.100 1.1.1.1 239.1.1.1 no no no yes
+ #
+
+T40 Lightweight MLDv2
+ http://tools.ietf.org/html/draft-ietf-mboned-lightweight-igmpv3-mldv2-05
+ http://www.ietf.org/internet-drafts/draft-ietf-mboned-lightweight-igmpv3-mldv2-05.txt
+ http://www.ietf.org/html.charters/mboned-charter.html
+
+T41 DONE ssmping support
+
+ See also:
+ http://www.venaas.no/multicast/ssmping/
+ draft-ietf-mboned-ssmping-07
+ http://tools.ietf.org/html/draft-ietf-mboned-ssmping-07
+
+ Example:
+
+ debug ssmpingd
+
+ conf t
+ ip ssmpingd 1.1.1.1
+
+ show ip ssmpingd
+
+T42 Static igmp join fails when loading config at boot time
+
+ ! Wrong behavior seen at boot time:
+ !
+ 2010/02/22 08:59:00 PIM: igmp_source_forward_start: ignoring request for
+ looped MFC entry (S,G)=(3.3.3.3,239.3.3.3): igmp_sock=12 oif=eth0 vif_index=2
+
+ ! Correct behavior seen later:
+ !
+ 2010/02/22 09:03:16 PIM: igmp_source_forward_start: ignoring request for
+ looped MFC entry (S,G)=(2.2.2.2,239.2.2.2): igmp_sock=17 oif=lo vif_index=1
+
+ ! To see the wrong message at boot:
+ !
+ debug igmp trace
+ !
+ interface lo
+ ip igmp
+ ip igmp join 239.2.2.2 2.2.2.2
+ ip igmp join 239.3.3.3 3.3.3.3
+ !
+
+ ! Interfaces indexes:
+ Interface Address ifi Vif PktsIn PktsOut BytesIn BytesOut
+ eth0 200.202.112.3 2 2 0 0 0 0
+ lo 127.0.0.1 1 1 0 0 0 0
+
+T43 PIM Neighbor Reduction
+ https://datatracker.ietf.org/doc/draft-wijnands-pim-neighbor-reduction/
+
+ "In a transit LAN (no directly connected source or receiver), many
+ of the PIM procedures don't apply. (...) This proposal describes
+ a procedure to reduce the amount of neighbors established over a
+ transit LAN."
+
+T44 Single Stream Multicast Fast Reroute (SMFR) Method
+ https://datatracker.ietf.org/doc/draft-liu-pim-single-stream-multicast-frr/
+
+ "This document proposes an IP multicast fast convergence method
+ based on differentiating primary and backup PIM join."
+
+T45 RFC5384 - The Join Attribute Format
+ "This document describes a modification of the Join message that
+ allows a node to associate attributes with a particular tree."
+
+T46 PIM Multi-Topology ID (MT-ID) Join-Attribute
+ http://tools.ietf.org/html/draft-cai-pim-mtid-00
+ Depends on T45.
+
+ "This draft introduces a new type of PIM Join Attribute used to
+ encode the identity of the topology PIM uses for RPF."
+
+-x-
diff --git a/pimd/TROUBLESHOOTING b/pimd/TROUBLESHOOTING
new file mode 100644
index 00000000..7d1f52de
--- /dev/null
+++ b/pimd/TROUBLESHOOTING
@@ -0,0 +1,33 @@
+TROUBLESHOOTING
+
+# Check kernel mcast cache
+# On Linux:
+ip mroute show
+
+! qpimd on last-hop router
+! . attached to mcast receiver
+! . runnning both PIM-SSM and IGMPv3
+!
+show ip mroute (kernel mcast programming is correct?)
+show ip pim upstream (we joined our upstream?)
+show ip pim neighbor (upstream is neighbor?)
+show ip pim interface (pim enabled on interfaces?)
+show ip multicast (multicast enabled at all?)
+show ip rib SRC (unicast route towards source?)
+
+show ip igmp sources (receiver joined on interface?)
+show ip igmp interface (igmp enabled on receiver interface?)
+
+! qpimd on intermmediate routers
+! . may be attached to mcast source
+! . runnning only PIM-SSM, not IGMPv3
+!
+show ip mroute (kernel mcast programming is correct?)
+show ip pim upstream (we joined our upstream?)
+show ip pim join (downstream joined us?)
+show ip pim neighbor (downstream is neighbor?)
+show ip pim interface (pim enabled on interfaces?)
+show ip multicast (multicast enabled at all?)
+show ip rib SRC (unicast route towards source?)
+
+--EOF--
diff --git a/pimd/WHY_SSM b/pimd/WHY_SSM
new file mode 100644
index 00000000..2e8c966f
--- /dev/null
+++ b/pimd/WHY_SSM
@@ -0,0 +1,32 @@
+WHY SSM
+
+Benefis of PIM SSM over PIM SM
+------------------------------
+
+- SSM consumes minimum link bandwidth
+- SSM simplifies multicast address management (specially important for
+ inter-domain multicast)
+ - SSM (S,G) channels easily provide unique per-application addressing
+ - SSM does not require MSDP between PIM domains
+- SSM does not suffer instabilities from traffic-driven SPT switchover
+- SSM is not suscetible to DoS attack from unwanted sources
+- SSM does not use RP. Some RP issues:
+ - RP is possible point of failure
+ - RP demands redundancy management
+ - RP may require PIM dense mode support for RP election
+ - RP is possible performance bottleneck
+ - RP may demand lots of extra management
+- SSM can be deployed in an existing PIM SM network (only the last hop
+ routers need to support IGMPv3)
+- SSM is easier to deploy and maintain
+
+PIM-SSM drawbacks
+-----------------
+
+- SSM requires IGMPv3 support on both receivers and last-hop routers
+- SSM may be memory intensive when managing (S,G) states for
+ many-to-many multicast distribution
+- SSM will keep (S,G) state as long as there are subscriptions from
+ receivers, even if the source is not actually sending traffic
+
+--EOF--
diff --git a/pimd/git-clone-github.sh b/pimd/git-clone-github.sh
new file mode 100755
index 00000000..ae2362a7
--- /dev/null
+++ b/pimd/git-clone-github.sh
@@ -0,0 +1,27 @@
+#! /bin/bash
+#
+# Github Developer Git Checkout
+#
+# Delete remote branch qpimd: git push origin :qpimd
+# (git push origin :refs/heads/branch_to_delete)
+# Delete remote tag v0.139: git push origin :v0.139
+# (git push origin :refs/tags/tag_to_delete)
+# Create remote-tracking branch: git checkout -b pim0.142 origin/pim0.142
+# Rename branch qpimd to pim: git branch -m qpimd pim
+# Commit changes: git commit -a
+# Send changes: git push --all
+#
+# Recipe to re-sync with Quagga repository:
+# git clone https://github.com/udhos/qpimd quagga
+# cd quagga
+# git checkout master
+# git pull http://git.sv.gnu.org/r/quagga.git master
+# git checkout -b pim origin/pim
+# git rebase master pim
+# # Test, then push back into Github repository:
+# git push origin :pim ;# delete remote branch pim
+# git push --all
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+git clone https://github.com/udhos/qpimd
diff --git a/pimd/git-clone-savannah.sh b/pimd/git-clone-savannah.sh
new file mode 100755
index 00000000..1aad51bb
--- /dev/null
+++ b/pimd/git-clone-savannah.sh
@@ -0,0 +1,27 @@
+#! /bin/bash
+#
+# Savannah Developer Git Checkout
+#
+# Delete remote branch qpimd: git push origin :qpimd
+# (git push origin :refs/heads/branch_to_delete)
+# Delete remote tag v0.139: git push origin :v0.139
+# (git push origin :refs/tags/tag_to_delete)
+# Create remote-tracking branch: git checkout -b pim0.142 origin/pim0.142
+# Rename branch qpimd to pim: git branch -m qpimd pim
+# Commit changes: git commit -a
+# Send changes: git push --all
+#
+# Recipe to re-sync with Quagga repository:
+# git clone ssh://evertonm@git.sv.gnu.org/srv/git/qpimd.git quagga
+# cd quagga
+# git checkout master
+# git pull git://code.quagga.net/quagga.git master
+# git checkout -b pim origin/pim
+# git rebase master pim
+# # Test, then push back into Savannah repository:
+# git push origin :pim ;# delete remote branch pim
+# git push --all
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+git clone ssh://evertonm@git.sv.gnu.org/srv/git/qpimd.git quagga
diff --git a/pimd/pim_assert.c b/pimd/pim_assert.c
new file mode 100644
index 00000000..ad21e08e
--- /dev/null
+++ b/pimd/pim_assert.c
@@ -0,0 +1,808 @@
+/*
+ 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 "prefix.h"
+
+#include "pimd.h"
+#include "pim_str.h"
+#include "pim_tlv.h"
+#include "pim_msg.h"
+#include "pim_pim.h"
+#include "pim_int.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_hello.h"
+#include "pim_macro.h"
+#include "pim_assert.h"
+#include "pim_ifchannel.h"
+
+static int assert_action_a3(struct pim_ifchannel *ch);
+static void assert_action_a2(struct pim_ifchannel *ch,
+ struct pim_assert_metric winner_metric);
+static void assert_action_a6(struct pim_ifchannel *ch,
+ struct pim_assert_metric winner_metric);
+
+void pim_ifassert_winner_set(struct pim_ifchannel *ch,
+ enum pim_ifassert_state new_state,
+ struct in_addr winner,
+ struct pim_assert_metric winner_metric)
+{
+ int winner_changed = (ch->ifassert_winner.s_addr != winner.s_addr);
+ int metric_changed = !pim_assert_metric_match(&ch->ifassert_winner_metric,
+ &winner_metric);
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ if (ch->ifassert_state != new_state) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: (S,G)=(%s,%s) assert state changed from %s to %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ pim_ifchannel_ifassert_name(ch->ifassert_state),
+ pim_ifchannel_ifassert_name(new_state),
+ ch->interface->name);
+ }
+
+ if (winner_changed) {
+ char src_str[100];
+ char grp_str[100];
+ char was_str[100];
+ char winner_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<was?>", ch->ifassert_winner, was_str, sizeof(was_str));
+ pim_inet4_dump("<winner?>", winner, winner_str, sizeof(winner_str));
+ zlog_debug("%s: (S,G)=(%s,%s) assert winner changed from %s to %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ was_str, winner_str, ch->interface->name);
+ }
+ } /* PIM_DEBUG_PIM_EVENTS */
+
+ ch->ifassert_state = new_state;
+ ch->ifassert_winner = winner;
+ ch->ifassert_winner_metric = winner_metric;
+ ch->ifassert_creation = pim_time_monotonic_sec();
+
+ if (winner_changed || metric_changed) {
+ pim_upstream_update_join_desired(ch->upstream);
+ pim_ifchannel_update_could_assert(ch);
+ pim_ifchannel_update_assert_tracking_desired(ch);
+ }
+}
+
+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, src_str, sizeof(src_str));
+ zlog_debug("%s: from %s on %s",
+ label, src_str, ifp->name);
+ }
+}
+
+static int preferred_assert(const struct pim_ifchannel *ch,
+ const struct pim_assert_metric *recv_metric)
+{
+ return pim_assert_metric_better(recv_metric,
+ &ch->ifassert_winner_metric);
+}
+
+static int acceptable_assert(const struct pim_assert_metric *my_metric,
+ const struct pim_assert_metric *recv_metric)
+{
+ return pim_assert_metric_better(recv_metric,
+ my_metric);
+}
+
+static int inferior_assert(const struct pim_assert_metric *my_metric,
+ const struct pim_assert_metric *recv_metric)
+{
+ return pim_assert_metric_better(my_metric,
+ recv_metric);
+}
+
+static int cancel_assert(const struct pim_assert_metric *recv_metric)
+{
+ return (recv_metric->metric_preference == PIM_ASSERT_METRIC_PREFERENCE_MAX)
+ &&
+ (recv_metric->route_metric == PIM_ASSERT_ROUTE_METRIC_MAX);
+}
+
+static void if_could_assert_do_a1(const char *caller,
+ struct pim_ifchannel *ch)
+{
+ if (PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) {
+ if (assert_action_a1(ch)) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: %s: (S,G)=(%s,%s) assert_action_a1 failure on interface %s",
+ __PRETTY_FUNCTION__, caller,
+ src_str, grp_str, ch->interface->name);
+ /* log warning only */
+ }
+ }
+}
+
+static int dispatch_assert(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ struct pim_assert_metric recv_metric)
+{
+ struct pim_ifchannel *ch;
+
+ ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+ if (!ch) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: (S,G)=(%s,%s) failure creating channel on interface %s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ifp->name);
+ return -1;
+ }
+
+ switch (ch->ifassert_state) {
+ case PIM_IFASSERT_NOINFO:
+ if (recv_metric.rpt_bit_flag) {
+ /* RPT bit set */
+ if_could_assert_do_a1(__PRETTY_FUNCTION__, ch);
+ }
+ else {
+ /* RPT bit clear */
+ if (inferior_assert(&ch->ifassert_my_metric, &recv_metric)) {
+ if_could_assert_do_a1(__PRETTY_FUNCTION__, ch);
+ }
+ else if (acceptable_assert(&ch->ifassert_my_metric, &recv_metric)) {
+ if (PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags)) {
+ assert_action_a6(ch, recv_metric);
+ }
+ }
+ }
+ break;
+ case PIM_IFASSERT_I_AM_WINNER:
+ if (preferred_assert(ch, &recv_metric)) {
+ assert_action_a2(ch, recv_metric);
+ }
+ else {
+ if (inferior_assert(&ch->ifassert_my_metric, &recv_metric)) {
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER); /* a3 requirement */
+ assert_action_a3(ch);
+ }
+ }
+ break;
+ case PIM_IFASSERT_I_AM_LOSER:
+ if (recv_metric.ip_address.s_addr == ch->ifassert_winner.s_addr) {
+ /* Assert from current winner */
+
+ if (cancel_assert(&recv_metric)) {
+ assert_action_a5(ch);
+ }
+ else {
+ if (inferior_assert(&ch->ifassert_my_metric, &recv_metric)) {
+ assert_action_a5(ch);
+ }
+ else if (acceptable_assert(&ch->ifassert_my_metric, &recv_metric)) {
+ if (!recv_metric.rpt_bit_flag) {
+ assert_action_a2(ch, recv_metric);
+ }
+ }
+ }
+ }
+ else if (preferred_assert(ch, &recv_metric)) {
+ assert_action_a2(ch, recv_metric);
+ }
+ break;
+ default:
+ {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: (S,G)=(%s,%s) invalid assert state %d on interface %s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ch->ifassert_state, ifp->name);
+ }
+ return -2;
+ }
+
+ return 0;
+}
+
+int pim_assert_recv(struct interface *ifp,
+ struct pim_neighbor *neigh,
+ struct in_addr src_addr,
+ uint8_t *buf, int buf_size)
+{
+ struct prefix msg_group_addr;
+ struct prefix msg_source_addr;
+ struct pim_assert_metric msg_metric;
+ int offset;
+ uint8_t *curr;
+ int curr_size;
+
+ on_trace(__PRETTY_FUNCTION__, ifp, src_addr);
+
+ curr = buf;
+ curr_size = buf_size;
+
+ /*
+ Parse assert group addr
+ */
+ offset = pim_parse_addr_group(ifp->name, src_addr,
+ &msg_group_addr,
+ curr, curr_size);
+ if (offset < 1) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: pim_parse_addr_group() failure: from %s on %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifp->name);
+ return -1;
+ }
+ curr += offset;
+ curr_size -= offset;
+
+ /*
+ Parse assert source addr
+ */
+ offset = pim_parse_addr_ucast(ifp->name, src_addr,
+ &msg_source_addr,
+ curr, curr_size);
+ if (offset < 1) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", 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 -2;
+ }
+ curr += offset;
+ curr_size -= offset;
+
+ if (curr_size != 8) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: preference/metric size is not 8: size=%d from %s on interface %s",
+ __PRETTY_FUNCTION__,
+ curr_size,
+ src_str, ifp->name);
+ return -3;
+ }
+
+ /*
+ Parse assert metric preference
+ */
+
+ msg_metric.metric_preference = pim_read_uint32_host(curr);
+
+ msg_metric.rpt_bit_flag = msg_metric.metric_preference & 0x80000000; /* save highest bit */
+ msg_metric.metric_preference &= ~0x80000000; /* clear highest bit */
+
+ curr += 4;
+
+ /*
+ Parse assert route metric
+ */
+
+ msg_metric.route_metric = pim_read_uint32_host(curr);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char neigh_str[100];
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<neigh?>", src_addr, neigh_str, sizeof(neigh_str));
+ pim_inet4_dump("<src?>", msg_source_addr.u.prefix4, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", msg_group_addr.u.prefix4, group_str, sizeof(group_str));
+ zlog_debug("%s: from %s on %s: (S,G)=(%s,%s) pref=%u metric=%u rpt_bit=%u",
+ __PRETTY_FUNCTION__, neigh_str, ifp->name,
+ source_str, group_str,
+ msg_metric.metric_preference,
+ msg_metric.route_metric,
+ PIM_FORCE_BOOLEAN(msg_metric.rpt_bit_flag));
+ }
+
+ msg_metric.ip_address = src_addr;
+
+ return dispatch_assert(ifp,
+ msg_source_addr.u.prefix4,
+ msg_group_addr.u.prefix4,
+ msg_metric);
+}
+
+/*
+ RFC 4601: 4.6.3. Assert Metrics
+
+ Assert metrics are defined as:
+
+ When comparing assert_metrics, the rpt_bit_flag, metric_preference,
+ and route_metric field are compared in order, where the first lower
+ value wins. If all fields are equal, the primary IP address of the
+ router that sourced the Assert message is used as a tie-breaker,
+ with the highest IP address winning.
+*/
+int pim_assert_metric_better(const struct pim_assert_metric *m1,
+ const struct pim_assert_metric *m2)
+{
+ if (m1->rpt_bit_flag < m2->rpt_bit_flag)
+ return 1;
+ if (m1->rpt_bit_flag > m2->rpt_bit_flag)
+ return 0;
+
+ if (m1->metric_preference < m2->metric_preference)
+ return 1;
+ if (m1->metric_preference > m2->metric_preference)
+ return 0;
+
+ if (m1->route_metric < m2->route_metric)
+ return 1;
+ if (m1->route_metric > m2->route_metric)
+ return 0;
+
+ return ntohl(m1->ip_address.s_addr) > ntohl(m2->ip_address.s_addr);
+}
+
+int pim_assert_metric_match(const struct pim_assert_metric *m1,
+ const struct pim_assert_metric *m2)
+{
+ if (m1->rpt_bit_flag != m2->rpt_bit_flag)
+ return 0;
+ if (m1->metric_preference != m2->metric_preference)
+ return 0;
+ if (m1->route_metric != m2->route_metric)
+ return 0;
+
+ return m1->ip_address.s_addr == m2->ip_address.s_addr;
+}
+
+int pim_assert_build_msg(uint8_t *pim_msg, int buf_size,
+ struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr,
+ uint32_t metric_preference,
+ uint32_t route_metric,
+ uint32_t rpt_bit_flag)
+{
+ uint8_t *buf_pastend = pim_msg + buf_size;
+ uint8_t *pim_msg_curr;
+ int pim_msg_size;
+ int remain;
+
+ pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN; /* skip room for pim header */
+
+ /* Encode group */
+ remain = buf_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("<grp?>", group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: failure encoding group address %s: space left=%d",
+ __PRETTY_FUNCTION__, group_str, remain);
+ return -1;
+ }
+
+ /* Encode source */
+ remain = buf_pastend - pim_msg_curr;
+ pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr,
+ remain,
+ source_addr);
+ if (!pim_msg_curr) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: failure encoding source address %s: space left=%d",
+ __PRETTY_FUNCTION__, source_str, remain);
+ return -2;
+ }
+
+ /* Metric preference */
+ pim_write_uint32(pim_msg_curr, rpt_bit_flag ?
+ metric_preference | 0x80000000 :
+ metric_preference);
+ pim_msg_curr += 4;
+
+ /* Route metric */
+ pim_write_uint32(pim_msg_curr, route_metric);
+ pim_msg_curr += 4;
+
+ /*
+ Add PIM header
+ */
+ pim_msg_size = pim_msg_curr - pim_msg;
+ pim_msg_build_header(pim_msg, pim_msg_size,
+ PIM_MSG_TYPE_ASSERT);
+
+ return pim_msg_size;
+}
+
+static int pim_assert_do(struct pim_ifchannel *ch,
+ struct pim_assert_metric metric)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ uint8_t pim_msg[1000];
+ int pim_msg_size;
+
+ ifp = ch->interface;
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ zlog_warn("%s: pim not enabled on interface: %s",
+ __PRETTY_FUNCTION__, ifp->name);
+ return -1;
+ }
+
+ pim_msg_size = pim_assert_build_msg(pim_msg, sizeof(pim_msg), ifp,
+ ch->group_addr, ch->source_addr,
+ metric.metric_preference,
+ metric.route_metric,
+ metric.rpt_bit_flag);
+ if (pim_msg_size < 1) {
+ zlog_warn("%s: failure building PIM assert message: msg_size=%d",
+ __PRETTY_FUNCTION__, pim_msg_size);
+ return -2;
+ }
+
+ /*
+ 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);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: to %s: (S,G)=(%s,%s) pref=%u metric=%u rpt_bit=%u",
+ __PRETTY_FUNCTION__,
+ ifp->name, source_str, group_str,
+ metric.metric_preference,
+ metric.route_metric,
+ PIM_FORCE_BOOLEAN(metric.rpt_bit_flag));
+ }
+
+ 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 -3;
+ }
+
+ return 0;
+}
+
+int pim_assert_send(struct pim_ifchannel *ch)
+{
+ return pim_assert_do(ch, ch->ifassert_my_metric);
+}
+
+/*
+ RFC 4601: 4.6.4. AssertCancel Messages
+
+ An AssertCancel(S,G) is an infinite metric assert with the RPT bit
+ set that names S as the source.
+ */
+static int pim_assert_cancel(struct pim_ifchannel *ch)
+{
+ struct pim_assert_metric metric;
+
+ metric.rpt_bit_flag = 0;
+ metric.metric_preference = PIM_ASSERT_METRIC_PREFERENCE_MAX;
+ metric.route_metric = PIM_ASSERT_ROUTE_METRIC_MAX;
+ metric.ip_address = ch->source_addr;
+
+ return pim_assert_do(ch, metric);
+}
+
+static int on_assert_timer(struct thread *t)
+{
+ struct pim_ifchannel *ch;
+ struct interface *ifp;
+
+ zassert(t);
+ ch = THREAD_ARG(t);
+ zassert(ch);
+
+ ifp = ch->interface;
+ zassert(ifp);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: (S,G)=(%s,%s) timer expired on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ }
+
+ ch->t_ifassert_timer = 0;
+
+ switch (ch->ifassert_state) {
+ case PIM_IFASSERT_I_AM_WINNER:
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER); /* a3 requirement */
+ assert_action_a3(ch);
+ break;
+ case PIM_IFASSERT_I_AM_LOSER:
+ assert_action_a5(ch);
+ break;
+ default:
+ {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: (S,G)=(%s,%s) invalid assert state %d on interface %s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ch->ifassert_state, ifp->name);
+ }
+ }
+
+ return 0;
+}
+
+static void assert_timer_off(struct pim_ifchannel *ch)
+{
+ struct interface *ifp;
+
+ zassert(ch);
+ ifp = ch->interface;
+ zassert(ifp);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ if (ch->t_ifassert_timer) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: (S,G)=(%s,%s) cancelling timer on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ }
+ }
+ THREAD_OFF(ch->t_ifassert_timer);
+ zassert(!ch->t_ifassert_timer);
+}
+
+static void pim_assert_timer_set(struct pim_ifchannel *ch,
+ int interval)
+{
+ struct interface *ifp;
+
+ zassert(ch);
+ ifp = ch->interface;
+ zassert(ifp);
+
+ assert_timer_off(ch);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: (S,G)=(%s,%s) starting %u sec timer on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, interval, ifp->name);
+ }
+
+ THREAD_TIMER_ON(master, ch->t_ifassert_timer,
+ on_assert_timer,
+ ch, interval);
+}
+
+static void pim_assert_timer_reset(struct pim_ifchannel *ch)
+{
+ pim_assert_timer_set(ch, PIM_ASSERT_TIME - PIM_ASSERT_OVERRIDE_INTERVAL);
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ (S,G) Assert State machine Actions
+
+ A1: Send Assert(S,G).
+ Set Assert Timer to (Assert_Time - Assert_Override_Interval).
+ Store self as AssertWinner(S,G,I).
+ Store spt_assert_metric(S,I) as AssertWinnerMetric(S,G,I).
+*/
+int assert_action_a1(struct pim_ifchannel *ch)
+{
+ struct interface *ifp = ch->interface;
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s) multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return -1; /* must return since pim_ifp is used below */
+ }
+
+ /* Switch to I_AM_WINNER before performing action_a3 below */
+ pim_ifassert_winner_set(ch, PIM_IFASSERT_I_AM_WINNER,
+ pim_ifp->primary_address,
+ pim_macro_spt_assert_metric(&ch->upstream->rpf,
+ pim_ifp->primary_address));
+
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER); /* a3 requirement */
+ if (assert_action_a3(ch)) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s) assert_action_a3 failure on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ /* warning only */
+ }
+
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER);
+
+ return 0;
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ (S,G) Assert State machine Actions
+
+ A2: Store new assert winner as AssertWinner(S,G,I) and assert
+ winner metric as AssertWinnerMetric(S,G,I).
+ Set Assert Timer to Assert_Time.
+*/
+static void assert_action_a2(struct pim_ifchannel *ch,
+ struct pim_assert_metric winner_metric)
+{
+ pim_ifassert_winner_set(ch, PIM_IFASSERT_I_AM_LOSER,
+ winner_metric.ip_address,
+ winner_metric);
+
+ pim_assert_timer_set(ch, PIM_ASSERT_TIME);
+
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER);
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ (S,G) Assert State machine Actions
+
+ A3: Send Assert(S,G).
+ Set Assert Timer to (Assert_Time - Assert_Override_Interval).
+*/
+static int assert_action_a3(struct pim_ifchannel *ch)
+{
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER);
+
+ pim_assert_timer_reset(ch);
+
+ if (pim_assert_send(ch)) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+
+ zlog_warn("%s: (S,G)=(%s,%s) failure sending assert on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name);
+ return -1;
+ }
+
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER);
+
+ return 0;
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ (S,G) Assert State machine Actions
+
+ A4: Send AssertCancel(S,G).
+ Delete assert info (AssertWinner(S,G,I) and
+ AssertWinnerMetric(S,G,I) will then return their default
+ values).
+*/
+void assert_action_a4(struct pim_ifchannel *ch)
+{
+ if (pim_assert_cancel(ch)) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: failure sending AssertCancel(%s,%s) on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name);
+ /* log warning only */
+ }
+
+ assert_action_a5(ch);
+
+ zassert(ch->ifassert_state == PIM_IFASSERT_NOINFO);
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ (S,G) Assert State machine Actions
+
+ A5: Delete assert info (AssertWinner(S,G,I) and
+ AssertWinnerMetric(S,G,I) will then return their default values).
+*/
+void assert_action_a5(struct pim_ifchannel *ch)
+{
+ reset_ifassert_state(ch);
+ zassert(ch->ifassert_state == PIM_IFASSERT_NOINFO);
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ (S,G) Assert State machine Actions
+
+ A6: Store new assert winner as AssertWinner(S,G,I) and assert
+ winner metric as AssertWinnerMetric(S,G,I).
+ Set Assert Timer to Assert_Time.
+ If (I is RPF_interface(S)) AND (UpstreamJPState(S,G) == true)
+ set SPTbit(S,G) to TRUE.
+*/
+static void assert_action_a6(struct pim_ifchannel *ch,
+ struct pim_assert_metric winner_metric)
+{
+ assert_action_a2(ch, winner_metric);
+
+ /*
+ If (I is RPF_interface(S)) AND (UpstreamJPState(S,G) == true) set
+ SPTbit(S,G) to TRUE.
+
+ Notice: For PIM SSM, SPTbit(S,G) is already always true.
+ */
+
+ zassert(ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER);
+}
+
diff --git a/pimd/pim_assert.h b/pimd/pim_assert.h
new file mode 100644
index 00000000..bd3fb3e2
--- /dev/null
+++ b/pimd/pim_assert.h
@@ -0,0 +1,75 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_ASSERT_H
+#define PIM_ASSERT_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_neighbor.h"
+#include "pim_ifchannel.h"
+
+/*
+ RFC 4601: 4.11. Timer Values
+
+ Note that for historical reasons, the Assert message lacks a
+ Holdtime field. Thus, changing the Assert Time from the default
+ value is not recommended.
+ */
+#define PIM_ASSERT_OVERRIDE_INTERVAL (3) /* seconds */
+#define PIM_ASSERT_TIME (180) /* seconds */
+
+#define PIM_ASSERT_METRIC_PREFERENCE_MAX (0xFFFFFFFF)
+#define PIM_ASSERT_ROUTE_METRIC_MAX (0xFFFFFFFF)
+
+void pim_ifassert_winner_set(struct pim_ifchannel *ch,
+ enum pim_ifassert_state new_state,
+ struct in_addr winner,
+ struct pim_assert_metric winner_metric);
+
+int pim_assert_recv(struct interface *ifp,
+ struct pim_neighbor *neigh,
+ struct in_addr src_addr,
+ uint8_t *buf, int buf_size);
+
+int pim_assert_metric_better(const struct pim_assert_metric *m1,
+ const struct pim_assert_metric *m2);
+int pim_assert_metric_match(const struct pim_assert_metric *m1,
+ const struct pim_assert_metric *m2);
+
+int pim_assert_build_msg(uint8_t *pim_msg, int buf_size,
+ struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr,
+ uint32_t metric_preference,
+ uint32_t route_metric,
+ uint32_t rpt_bit_flag);
+
+int pim_assert_send(struct pim_ifchannel *ch);
+
+int assert_action_a1(struct pim_ifchannel *ch);
+void assert_action_a4(struct pim_ifchannel *ch);
+void assert_action_a5(struct pim_ifchannel *ch);
+
+#endif /* PIM_ASSERT_H */
diff --git a/pimd/pim_cmd.c b/pimd/pim_cmd.c
new file mode 100644
index 00000000..6b2ac664
--- /dev/null
+++ b/pimd/pim_cmd.c
@@ -0,0 +1,4500 @@
+/*
+ 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 <sys/ioctl.h>
+
+#include <zebra.h>
+
+#include "command.h"
+#include "if.h"
+#include "prefix.h"
+#include "zclient.h"
+
+#include "pimd.h"
+#include "pim_cmd.h"
+#include "pim_iface.h"
+#include "pim_vty.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_igmp.h"
+#include "pim_igmpv3.h"
+#include "pim_sock.h"
+#include "pim_time.h"
+#include "pim_util.h"
+#include "pim_oil.h"
+#include "pim_neighbor.h"
+#include "pim_pim.h"
+#include "pim_ifchannel.h"
+#include "pim_hello.h"
+#include "pim_msg.h"
+#include "pim_upstream.h"
+#include "pim_rpf.h"
+#include "pim_macro.h"
+#include "pim_ssmpingd.h"
+#include "pim_zebra.h"
+
+static struct cmd_node pim_global_node = {
+ PIM_NODE,
+ "",
+ 1 /* vtysh ? yes */
+};
+
+static struct cmd_node interface_node = {
+ INTERFACE_NODE,
+ "%s(config-if)# ",
+ 1 /* vtysh ? yes */
+};
+
+static void pim_if_membership_clear(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ if (PIM_IF_TEST_PIM(pim_ifp->options) &&
+ PIM_IF_TEST_IGMP(pim_ifp->options)) {
+ return;
+ }
+
+ pim_ifchannel_membership_clear(ifp);
+}
+
+/*
+ When PIM is disabled on interface, IGMPv3 local membership
+ information is not injected into PIM interface state.
+
+ The function pim_if_membership_refresh() fetches all IGMPv3 local
+ membership information into PIM. It is intented to be called
+ whenever PIM is enabled on the interface in order to collect missed
+ local membership information.
+ */
+static void pim_if_membership_refresh(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ if (!PIM_IF_TEST_PIM(pim_ifp->options))
+ return;
+ if (!PIM_IF_TEST_IGMP(pim_ifp->options))
+ return;
+
+ /*
+ First clear off membership from all PIM (S,G) entries on the
+ interface
+ */
+
+ pim_ifchannel_membership_clear(ifp);
+
+ /*
+ Then restore PIM (S,G) membership from all IGMPv3 (S,G) entries on
+ the interface
+ */
+
+ /* scan igmp sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ struct listnode *grpnode;
+ struct igmp_group *grp;
+
+ /* scan igmp groups */
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+ struct listnode *srcnode;
+ struct igmp_source *src;
+
+ /* scan group sources */
+ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) {
+
+ if (IGMP_SOURCE_TEST_FORWARDING(src->source_flags)) {
+ pim_ifchannel_local_membership_add(ifp,
+ src->source_addr,
+ grp->group_addr);
+ }
+
+ } /* scan group sources */
+ } /* scan igmp groups */
+ } /* scan igmp sockets */
+
+ /*
+ Finally delete every PIM (S,G) entry lacking all state info
+ */
+
+ pim_ifchannel_delete_on_noinfo(ifp);
+
+}
+
+static void pim_show_assert(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty,
+ "Interface Address Source Group State Winner Uptime Timer%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ char ch_src_str[100];
+ char ch_grp_str[100];
+ char winner_str[100];
+ char uptime[10];
+ char timer[10];
+
+ pim_inet4_dump("<ch_src?>", ch->source_addr,
+ ch_src_str, sizeof(ch_src_str));
+ pim_inet4_dump("<ch_grp?>", ch->group_addr,
+ ch_grp_str, sizeof(ch_grp_str));
+ pim_inet4_dump("<assrt_win?>", ch->ifassert_winner,
+ winner_str, sizeof(winner_str));
+
+ pim_time_uptime(uptime, sizeof(uptime), now - ch->ifassert_creation);
+ pim_time_timer_to_mmss(timer, sizeof(timer),
+ ch->t_ifassert_timer);
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %-6s %-15s %-8s %-5s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ch_src_str,
+ ch_grp_str,
+ pim_ifchannel_ifassert_name(ch->ifassert_state),
+ winner_str,
+ uptime,
+ timer,
+ VTY_NEWLINE);
+ } /* scan interface channels */
+ } /* scan interfaces */
+}
+
+static void pim_show_assert_internal(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "CA: CouldAssert%s"
+ "ECA: Evaluate CouldAssert%s"
+ "ATD: AssertTrackingDesired%s"
+ "eATD: Evaluate AssertTrackingDesired%s%s",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty,
+ "Interface Address Source Group CA eCA ATD eATD%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ char ch_src_str[100];
+ char ch_grp_str[100];
+
+ pim_inet4_dump("<ch_src?>", ch->source_addr,
+ ch_src_str, sizeof(ch_src_str));
+ pim_inet4_dump("<ch_grp?>", ch->group_addr,
+ ch_grp_str, sizeof(ch_grp_str));
+ vty_out(vty, "%-9s %-15s %-15s %-15s %-3s %-3s %-3s %-4s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ch_src_str,
+ ch_grp_str,
+ PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags) ? "yes" : "no",
+ pim_macro_ch_could_assert_eval(ch) ? "yes" : "no",
+ PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags) ? "yes" : "no",
+ pim_macro_assert_tracking_desired_eval(ch) ? "yes" : "no",
+ VTY_NEWLINE);
+ } /* scan interface channels */
+ } /* scan interfaces */
+}
+
+static void pim_show_assert_metric(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "Interface Address Source Group RPT Pref Metric Address %s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ char ch_src_str[100];
+ char ch_grp_str[100];
+ char addr_str[100];
+ struct pim_assert_metric am;
+
+ am = pim_macro_spt_assert_metric(&ch->upstream->rpf, pim_ifp->primary_address);
+
+ pim_inet4_dump("<ch_src?>", ch->source_addr,
+ ch_src_str, sizeof(ch_src_str));
+ pim_inet4_dump("<ch_grp?>", ch->group_addr,
+ ch_grp_str, sizeof(ch_grp_str));
+ pim_inet4_dump("<addr?>", am.ip_address,
+ addr_str, sizeof(addr_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %-3s %4u %6u %-15s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ch_src_str,
+ ch_grp_str,
+ am.rpt_bit_flag ? "yes" : "no",
+ am.metric_preference,
+ am.route_metric,
+ addr_str,
+ VTY_NEWLINE);
+ } /* scan interface channels */
+ } /* scan interfaces */
+}
+
+static void pim_show_assert_winner_metric(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "Interface Address Source Group RPT Pref Metric Address %s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ char ch_src_str[100];
+ char ch_grp_str[100];
+ char addr_str[100];
+ struct pim_assert_metric *am;
+ char pref_str[5];
+ char metr_str[7];
+
+ am = &ch->ifassert_winner_metric;
+
+ pim_inet4_dump("<ch_src?>", ch->source_addr,
+ ch_src_str, sizeof(ch_src_str));
+ pim_inet4_dump("<ch_grp?>", ch->group_addr,
+ ch_grp_str, sizeof(ch_grp_str));
+ pim_inet4_dump("<addr?>", am->ip_address,
+ addr_str, sizeof(addr_str));
+
+ if (am->metric_preference == PIM_ASSERT_METRIC_PREFERENCE_MAX)
+ snprintf(pref_str, sizeof(pref_str), "INFI");
+ else
+ snprintf(pref_str, sizeof(pref_str), "%4u", am->metric_preference);
+
+ if (am->route_metric == PIM_ASSERT_ROUTE_METRIC_MAX)
+ snprintf(metr_str, sizeof(metr_str), "INFI");
+ else
+ snprintf(metr_str, sizeof(metr_str), "%6u", am->route_metric);
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %-3s %-4s %-6s %-15s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ch_src_str,
+ ch_grp_str,
+ am->rpt_bit_flag ? "yes" : "no",
+ pref_str,
+ metr_str,
+ addr_str,
+ VTY_NEWLINE);
+ } /* scan interface channels */
+ } /* scan interfaces */
+}
+
+static void pim_show_membership(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "Interface Address Source Group Membership%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ char ch_src_str[100];
+ char ch_grp_str[100];
+
+ pim_inet4_dump("<ch_src?>", ch->source_addr,
+ ch_src_str, sizeof(ch_src_str));
+ pim_inet4_dump("<ch_grp?>", ch->group_addr,
+ ch_grp_str, sizeof(ch_grp_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %-10s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ch_src_str,
+ ch_grp_str,
+ ch->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO ?
+ "NOINFO" : "INCLUDE",
+ VTY_NEWLINE);
+ } /* scan interface channels */
+ } /* scan interfaces */
+
+}
+
+static void igmp_show_interfaces(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty,
+ "Interface Address ifIndex Socket Uptime Multi Broad MLoop AllMu Prmsc Del%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char uptime[10];
+ int mloop;
+
+ pim_time_uptime(uptime, sizeof(uptime), now - igmp->sock_creation);
+
+ mloop = pim_socket_mcastloop_get(igmp->fd);
+
+ vty_out(vty, "%-9s %-15s %7d %6d %8s %5s %5s %5s %5s %5s %3s%s",
+ ifp->name,
+ inet_ntoa(igmp->ifaddr),
+ ifp->ifindex,
+ igmp->fd,
+ uptime,
+ if_is_multicast(ifp) ? "yes" : "no",
+ if_is_broadcast(ifp) ? "yes" : "no",
+ (mloop < 0) ? "?" : (mloop ? "yes" : "no"),
+ (ifp->flags & IFF_ALLMULTI) ? "yes" : "no",
+ (ifp->flags & IFF_PROMISC) ? "yes" : "no",
+ PIM_IF_IS_DELETED(ifp) ? "yes" : "no",
+ VTY_NEWLINE);
+ }
+ }
+}
+
+static void igmp_show_interface_join(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty,
+ "Interface Address Source Group Socket Uptime %s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct listnode *join_node;
+ struct igmp_join *ij;
+ struct in_addr pri_addr;
+ char pri_addr_str[100];
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (!pim_ifp->igmp_join_list)
+ continue;
+
+ pri_addr = pim_find_primary_addr(ifp);
+ pim_inet4_dump("<pri?>", pri_addr, pri_addr_str, sizeof(pri_addr_str));
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_join_list, join_node, ij)) {
+ char group_str[100];
+ char source_str[100];
+ char uptime[10];
+
+ pim_time_uptime(uptime, sizeof(uptime), now - ij->sock_creation);
+ pim_inet4_dump("<grp?>", ij->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", ij->source_addr, source_str, sizeof(source_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %6d %8s%s",
+ ifp->name,
+ pri_addr_str,
+ source_str,
+ group_str,
+ ij->sock_fd,
+ uptime,
+ VTY_NEWLINE);
+ } /* for (pim_ifp->igmp_join_list) */
+
+ } /* for (iflist) */
+
+}
+
+static void show_interface_address(struct vty *vty)
+{
+ struct listnode *ifpnode;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "Interface Primary Secondary %s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifpnode, ifp)) {
+ struct listnode *ifcnode;
+ struct connected *ifc;
+ struct in_addr pri_addr;
+ char pri_addr_str[100];
+
+ pri_addr = pim_find_primary_addr(ifp);
+
+ pim_inet4_dump("<pri?>", pri_addr, pri_addr_str, sizeof(pri_addr_str));
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, ifcnode, ifc)) {
+ char sec_addr_str[100];
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ if (p->u.prefix4.s_addr == pri_addr.s_addr) {
+ sec_addr_str[0] = '\0';
+ }
+ else {
+ pim_inet4_dump("<sec?>", p->u.prefix4, sec_addr_str, sizeof(sec_addr_str));
+ }
+
+ vty_out(vty, "%-9s %-15s %-15s%s",
+ ifp->name,
+ pri_addr_str,
+ sec_addr_str,
+ VTY_NEWLINE);
+ }
+ }
+}
+
+static void pim_show_dr(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty,
+ "NonPri: Number of neighbors missing DR Priority hello option%s%s",
+ VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty, "Interface Address DR Uptime Elections Changes NonPri%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ char dr_str[100];
+ char dr_uptime[10];
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ pim_time_uptime_begin(dr_uptime, sizeof(dr_uptime),
+ now, pim_ifp->pim_dr_election_last);
+
+ pim_inet4_dump("<dr?>", pim_ifp->pim_dr_addr,
+ dr_str, sizeof(dr_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %8s %9d %7d %6d%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ dr_str,
+ dr_uptime,
+ pim_ifp->pim_dr_election_count,
+ pim_ifp->pim_dr_election_changes,
+ pim_ifp->pim_dr_num_nondrpri_neighbors,
+ VTY_NEWLINE);
+ }
+}
+
+static void pim_show_hello(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty, "Interface Address Period Timer StatStart Recv Rfail Send Sfail%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ char hello_period[10];
+ char hello_timer[10];
+ char stat_uptime[10];
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ pim_time_timer_to_mmss(hello_timer, sizeof(hello_timer), pim_ifp->t_pim_hello_timer);
+ pim_time_mmss(hello_period, sizeof(hello_period), pim_ifp->pim_hello_period);
+ pim_time_uptime(stat_uptime, sizeof(stat_uptime), now - pim_ifp->pim_ifstat_start);
+
+ vty_out(vty, "%-9s %-15s %6s %5s %9s %4u %5u %4u %5u%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ hello_period,
+ hello_timer,
+ stat_uptime,
+ pim_ifp->pim_ifstat_hello_recv,
+ pim_ifp->pim_ifstat_hello_recvfail,
+ pim_ifp->pim_ifstat_hello_sent,
+ pim_ifp->pim_ifstat_hello_sendfail,
+ VTY_NEWLINE);
+ }
+}
+
+static void pim_show_interfaces(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty, "Interface Address ifIndex Socket Uptime Multi Broad MLoop AllMu Prmsc Del%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ char uptime[10];
+ int mloop;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ pim_time_uptime(uptime, sizeof(uptime), now - pim_ifp->pim_sock_creation);
+
+ mloop = pim_socket_mcastloop_get(pim_ifp->pim_sock_fd);
+
+ vty_out(vty, "%-9s %-15s %7d %6d %8s %5s %5s %5s %5s %5s %3s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ifp->ifindex,
+ pim_ifp->pim_sock_fd,
+ uptime,
+ if_is_multicast(ifp) ? "yes" : "no",
+ if_is_broadcast(ifp) ? "yes" : "no",
+ (mloop < 0) ? "?" : (mloop ? "yes" : "no"),
+ (ifp->flags & IFF_ALLMULTI) ? "yes" : "no",
+ (ifp->flags & IFF_PROMISC) ? "yes" : "no",
+ PIM_IF_IS_DELETED(ifp) ? "yes" : "no",
+ VTY_NEWLINE);
+ }
+}
+
+static void pim_show_join(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty,
+ "Interface Address Source Group State Uptime Expire Prune%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ char ch_src_str[100];
+ char ch_grp_str[100];
+ char uptime[10];
+ char expire[10];
+ char prune[10];
+
+ pim_inet4_dump("<ch_src?>", ch->source_addr,
+ ch_src_str, sizeof(ch_src_str));
+ pim_inet4_dump("<ch_grp?>", ch->group_addr,
+ ch_grp_str, sizeof(ch_grp_str));
+
+ pim_time_uptime_begin(uptime, sizeof(uptime), now, ch->ifjoin_creation);
+ pim_time_timer_to_mmss(expire, sizeof(expire),
+ ch->t_ifjoin_expiry_timer);
+ pim_time_timer_to_mmss(prune, sizeof(prune),
+ ch->t_ifjoin_prune_pending_timer);
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %-6s %8s %-6s %5s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ch_src_str,
+ ch_grp_str,
+ pim_ifchannel_ifjoin_name(ch->ifjoin_state),
+ uptime,
+ expire,
+ prune,
+ VTY_NEWLINE);
+ } /* scan interface channels */
+ } /* scan interfaces */
+
+}
+
+static void pim_show_neighbors(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty,
+ "Recv flags: H=holdtime L=lan_prune_delay P=dr_priority G=generation_id A=address_list%s"
+ " T=can_disable_join_suppression%s%s",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty, "Interface Address Neighbor Uptime Timer Holdt DrPri GenId Recv %s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *neighnode;
+ struct pim_neighbor *neigh;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+ char uptime[10];
+ char holdtime[10];
+ char expire[10];
+ char neigh_src_str[100];
+ char recv[7];
+
+ pim_inet4_dump("<src?>", neigh->source_addr,
+ neigh_src_str, sizeof(neigh_src_str));
+ pim_time_uptime(uptime, sizeof(uptime), now - neigh->creation);
+ pim_time_mmss(holdtime, sizeof(holdtime), neigh->holdtime);
+ pim_time_timer_to_mmss(expire, sizeof(expire), neigh->t_expire_timer);
+
+ recv[0] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_HOLDTIME) ? 'H' : ' ';
+ recv[1] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY) ? 'L' : ' ';
+ recv[2] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY) ? 'P' : ' ';
+ recv[3] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_GENERATION_ID) ? 'G' : ' ';
+ recv[4] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_ADDRESS_LIST) ? 'A' : ' ';
+ recv[5] = PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION) ? 'T' : ' ';
+ recv[6] = '\0';
+
+ vty_out(vty, "%-9s %-15s %-15s %8s %5s %5s %5u %08x %6s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ neigh_src_str,
+ uptime,
+ expire,
+ holdtime,
+ neigh->dr_priority,
+ neigh->generation_id,
+ recv,
+ VTY_NEWLINE);
+ }
+
+
+ }
+}
+
+static void pim_show_lan_prune_delay(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "PrDly=propagation_delay (msec) OvInt=override_interval (msec)%s"
+ "HiDly=highest_propagation_delay (msec) HiInt=highest_override_interval (msec)%s"
+ "NoDly=number_of_non_lan_delay_neighbors%s"
+ "T=t_bit LPD=lan_prune_delay_hello_option%s%s",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty, "Interface Address PrDly OvInt NoDly HiDly HiInt T | Neighbor LPD PrDly OvInt T%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *neighnode;
+ struct pim_neighbor *neigh;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+ char neigh_src_str[100];
+
+ pim_inet4_dump("<src?>", neigh->source_addr,
+ neigh_src_str, sizeof(neigh_src_str));
+
+ vty_out(vty, "%-9s %-15s %5u %5u %5u %5u %5u %1u | %-15s %-3s %5u %5u %1u%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ pim_ifp->pim_propagation_delay_msec,
+ pim_ifp->pim_override_interval_msec,
+ pim_ifp->pim_number_of_nonlandelay_neighbors,
+ pim_ifp->pim_neighbors_highest_propagation_delay_msec,
+ pim_ifp->pim_neighbors_highest_override_interval_msec,
+ PIM_FORCE_BOOLEAN(PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options)),
+ neigh_src_str,
+ PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY) ? "yes" : "no",
+ neigh->propagation_delay_msec,
+ neigh->override_interval_msec,
+ PIM_FORCE_BOOLEAN(PIM_OPTION_IS_SET(neigh->hello_options,
+ PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION)),
+ VTY_NEWLINE);
+ }
+
+ }
+}
+
+static void pim_show_jp_override_interval(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "EffPDelay=effective_propagation_delay (msec)%s"
+ "EffOvrInt=override_interval (msec)%s"
+ "JPOvrInt=jp_override_interval (msec)%s%s",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty, "Interface Address LAN_Delay EffPDelay EffOvrInt JPOvrInt%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ vty_out(vty, "%-9s %-15s %-9s %9u %9u %8u%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ pim_if_lan_delay_enabled(ifp) ? "enabled" : "disabled",
+ pim_if_effective_propagation_delay_msec(ifp),
+ pim_if_effective_override_interval_msec(ifp),
+ pim_if_jp_override_interval_msec(ifp),
+ VTY_NEWLINE);
+ }
+}
+
+static void pim_show_neighbors_secondary(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+
+ vty_out(vty, "Interface Address Neighbor Secondary %s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct listnode *neighnode;
+ struct pim_neighbor *neigh;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->pim_sock_fd < 0)
+ continue;
+
+ ifaddr = pim_ifp->primary_address;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+ char neigh_src_str[100];
+ struct listnode *prefix_node;
+ struct prefix *p;
+
+ if (!neigh->prefix_list)
+ continue;
+
+ pim_inet4_dump("<src?>", neigh->source_addr,
+ neigh_src_str, sizeof(neigh_src_str));
+
+ for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, prefix_node, p)) {
+ char neigh_sec_str[100];
+
+ if (p->family != AF_INET)
+ continue;
+
+ pim_inet4_dump("<src?>", p->u.prefix4,
+ neigh_sec_str, sizeof(neigh_sec_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ neigh_src_str,
+ neigh_sec_str,
+ VTY_NEWLINE);
+ }
+ }
+ }
+}
+
+static void pim_show_upstream(struct vty *vty)
+{
+ struct listnode *upnode;
+ struct pim_upstream *up;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty, "Source Group State Uptime JoinTimer RefCnt%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, upnode, up)) {
+ char src_str[100];
+ char grp_str[100];
+ char uptime[10];
+ char join_timer[10];
+
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_time_uptime(uptime, sizeof(uptime), now - up->state_transition);
+ pim_time_timer_to_hhmmss(join_timer, sizeof(join_timer), up->t_join_timer);
+
+ vty_out(vty, "%-15s %-15s %-5s %-8s %-9s %6d%s",
+ src_str,
+ grp_str,
+ up->join_state == PIM_UPSTREAM_JOINED ? "Jnd" : "NtJnd",
+ uptime,
+ join_timer,
+ up->ref_count,
+ VTY_NEWLINE);
+ }
+}
+
+static void pim_show_join_desired(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct listnode *chnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+ char src_str[100];
+ char grp_str[100];
+
+ vty_out(vty,
+ "Interface Source Group LostAssert Joins PimInclude JoinDesired EvalJD%s",
+ VTY_NEWLINE);
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, chnode, ch)) {
+ struct pim_upstream *up = ch->upstream;
+
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %-10s %-5s %-10s %-11s %-6s%s",
+ ifp->name,
+ src_str,
+ grp_str,
+ pim_macro_ch_lost_assert(ch) ? "yes" : "no",
+ pim_macro_chisin_joins(ch) ? "yes" : "no",
+ pim_macro_chisin_pim_include(ch) ? "yes" : "no",
+ PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags) ? "yes" : "no",
+ pim_upstream_evaluate_join_desired(up) ? "yes" : "no",
+ VTY_NEWLINE);
+ }
+ }
+}
+
+static void pim_show_upstream_rpf(struct vty *vty)
+{
+ struct listnode *upnode;
+ struct pim_upstream *up;
+
+ vty_out(vty,
+ "Source Group RpfIface RibNextHop RpfAddress %s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, upnode, up)) {
+ char src_str[100];
+ char grp_str[100];
+ char rpf_nexthop_str[100];
+ char rpf_addr_str[100];
+ struct pim_rpf *rpf;
+ const char *rpf_ifname;
+
+ rpf = &up->rpf;
+
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<nexthop?>", rpf->source_nexthop.mrib_nexthop_addr, rpf_nexthop_str, sizeof(rpf_nexthop_str));
+ pim_inet4_dump("<rpf?>", rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+
+ rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "<ifname?>";
+
+ vty_out(vty, "%-15s %-15s %-8s %-15s %-15s%s",
+ src_str,
+ grp_str,
+ rpf_ifname,
+ rpf_nexthop_str,
+ rpf_addr_str,
+ VTY_NEWLINE);
+ }
+}
+
+static void show_rpf_refresh_stats(struct vty *vty, time_t now)
+{
+ char refresh_uptime[10];
+
+ pim_time_uptime_begin(refresh_uptime, sizeof(refresh_uptime), now, qpim_rpf_cache_refresh_last);
+
+ vty_out(vty,
+ "RPF Cache Refresh Delay: %ld msecs%s"
+ "RPF Cache Refresh Timer: %ld msecs%s"
+ "RPF Cache Refresh Requests: %lld%s"
+ "RPF Cache Refresh Events: %lld%s"
+ "RPF Cache Refresh Last: %s%s",
+ qpim_rpf_cache_refresh_delay_msec, VTY_NEWLINE,
+ pim_time_timer_remain_msec(qpim_rpf_cache_refresher), VTY_NEWLINE,
+ (long long)qpim_rpf_cache_refresh_requests, VTY_NEWLINE,
+ (long long)qpim_rpf_cache_refresh_events, VTY_NEWLINE,
+ refresh_uptime, VTY_NEWLINE);
+}
+
+static void show_scan_oil_stats(struct vty *vty, time_t now)
+{
+ char uptime_scan_oil[10];
+ char uptime_mroute_add[10];
+ char uptime_mroute_del[10];
+
+ pim_time_uptime_begin(uptime_scan_oil, sizeof(uptime_scan_oil), now, qpim_scan_oil_last);
+ pim_time_uptime_begin(uptime_mroute_add, sizeof(uptime_mroute_add), now, qpim_mroute_add_last);
+ pim_time_uptime_begin(uptime_mroute_del, sizeof(uptime_mroute_del), now, qpim_mroute_del_last);
+
+ vty_out(vty,
+ "Scan OIL - Last: %s Events: %lld%s"
+ "MFC Add - Last: %s Events: %lld%s"
+ "MFC Del - Last: %s Events: %lld%s",
+ uptime_scan_oil, (long long) qpim_scan_oil_events, VTY_NEWLINE,
+ uptime_mroute_add, (long long) qpim_mroute_add_events, VTY_NEWLINE,
+ uptime_mroute_del, (long long) qpim_mroute_del_events, VTY_NEWLINE);
+}
+
+static void pim_show_rpf(struct vty *vty)
+{
+ struct listnode *up_node;
+ struct pim_upstream *up;
+ time_t now = pim_time_monotonic_sec();
+
+ show_rpf_refresh_stats(vty, now);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ vty_out(vty,
+ "Source Group RpfIface RpfAddress RibNextHop Metric Pref%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) {
+ char src_str[100];
+ char grp_str[100];
+ char rpf_addr_str[100];
+ char rib_nexthop_str[100];
+ const char *rpf_ifname;
+ struct pim_rpf *rpf = &up->rpf;
+
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<rpf?>", rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+ pim_inet4_dump("<nexthop?>", rpf->source_nexthop.mrib_nexthop_addr, rib_nexthop_str, sizeof(rib_nexthop_str));
+
+ rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "<ifname?>";
+
+ vty_out(vty, "%-15s %-15s %-8s %-15s %-15s %6d %4d%s",
+ src_str,
+ grp_str,
+ rpf_ifname,
+ rpf_addr_str,
+ rib_nexthop_str,
+ rpf->source_nexthop.mrib_route_metric,
+ rpf->source_nexthop.mrib_metric_preference,
+ VTY_NEWLINE);
+ }
+}
+
+static void igmp_show_querier(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+
+ vty_out(vty, "Interface Address Querier StartCount Query-Timer Other-Timer%s", VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ if (!pim_ifp)
+ continue;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char query_hhmmss[10];
+ char other_hhmmss[10];
+
+ pim_time_timer_to_hhmmss(query_hhmmss, sizeof(query_hhmmss), igmp->t_igmp_query_timer);
+ pim_time_timer_to_hhmmss(other_hhmmss, sizeof(other_hhmmss), igmp->t_other_querier_timer);
+
+ vty_out(vty, "%-9s %-15s %-7s %10d %11s %11s%s",
+ ifp->name,
+ inet_ntoa(igmp->ifaddr),
+ igmp->t_igmp_query_timer ? "THIS" : "OTHER",
+ igmp->startup_query_count,
+ query_hhmmss,
+ other_hhmmss,
+ VTY_NEWLINE);
+ }
+ }
+}
+
+static void igmp_show_groups(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty, "Interface Address Group Mode Timer Srcs V Uptime %s", VTY_NEWLINE);
+
+ /* scan interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ if (!pim_ifp)
+ continue;
+
+ /* scan igmp sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char ifaddr_str[100];
+ struct listnode *grpnode;
+ struct igmp_group *grp;
+
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+ /* scan igmp groups */
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+ char group_str[100];
+ char hhmmss[10];
+ char uptime[10];
+
+ pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+ pim_time_timer_to_hhmmss(hhmmss, sizeof(hhmmss), grp->t_group_timer);
+ pim_time_uptime(uptime, sizeof(uptime), now - grp->group_creation);
+
+ vty_out(vty, "%-9s %-15s %-15s %4s %8s %4d %d %8s%s",
+ ifp->name,
+ ifaddr_str,
+ group_str,
+ grp->group_filtermode_isexcl ? "EXCL" : "INCL",
+ hhmmss,
+ grp->group_source_list ? listcount(grp->group_source_list) : 0,
+ igmp_group_compat_mode(igmp, grp),
+ uptime,
+ VTY_NEWLINE);
+
+ } /* scan igmp groups */
+ } /* scan igmp sockets */
+ } /* scan interfaces */
+}
+
+static void igmp_show_group_retransmission(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty, "Interface Address Group RetTimer Counter RetSrcs%s", VTY_NEWLINE);
+
+ /* scan interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ if (!pim_ifp)
+ continue;
+
+ /* scan igmp sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char ifaddr_str[100];
+ struct listnode *grpnode;
+ struct igmp_group *grp;
+
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+ /* scan igmp groups */
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+ char group_str[100];
+ char grp_retr_mmss[10];
+ struct listnode *src_node;
+ struct igmp_source *src;
+ int grp_retr_sources = 0;
+
+ pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+ pim_time_timer_to_mmss(grp_retr_mmss, sizeof(grp_retr_mmss), grp->t_group_query_retransmit_timer);
+
+
+ /* count group sources with retransmission state */
+ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, src_node, src)) {
+ if (src->source_query_retransmit_count > 0) {
+ ++grp_retr_sources;
+ }
+ }
+
+ vty_out(vty, "%-9s %-15s %-15s %-8s %7d %7d%s",
+ ifp->name,
+ ifaddr_str,
+ group_str,
+ grp_retr_mmss,
+ grp->group_specific_query_retransmit_count,
+ grp_retr_sources,
+ VTY_NEWLINE);
+
+ } /* scan igmp groups */
+ } /* scan igmp sockets */
+ } /* scan interfaces */
+}
+
+static void igmp_show_parameters(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty,
+ "QRV: Robustness Variable SQI: Startup Query Interval%s"
+ "QQI: Query Interval OQPI: Other Querier Present Interval%s"
+ "QRI: Query Response Interval LMQT: Last Member Query Time%s"
+ "GMI: Group Membership Interval OHPI: Older Host Present Interval%s%s",
+ VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty,
+ "Interface Address QRV QQI QRI GMI SQI OQPI LMQT OHPI %s",
+ VTY_NEWLINE);
+
+ /* scan interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ if (!pim_ifp)
+ continue;
+
+ /* scan igmp sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char ifaddr_str[100];
+ long gmi_dsec; /* Group Membership Interval */
+ long oqpi_dsec; /* Other Querier Present Interval */
+ int sqi;
+ long lmqt_dsec;
+ long ohpi_dsec;
+ long qri_dsec;
+
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+ gmi_dsec = PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ pim_ifp->igmp_query_max_response_time_dsec) / 100;
+
+ sqi = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
+
+ oqpi_dsec = PIM_IGMP_OQPI_MSEC(igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ pim_ifp->igmp_query_max_response_time_dsec) / 100;
+
+ lmqt_dsec = PIM_IGMP_LMQT_MSEC(pim_ifp->igmp_query_max_response_time_dsec,
+ igmp->querier_robustness_variable) / 100;
+
+ ohpi_dsec = PIM_IGMP_OHPI_DSEC(igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ pim_ifp->igmp_query_max_response_time_dsec);
+
+ qri_dsec = pim_ifp->igmp_query_max_response_time_dsec;
+
+ vty_out(vty,
+ "%-9s %-15s %3d %3d %3ld.%ld %3ld.%ld %3d %3ld.%ld %3ld.%ld %3ld.%ld%s",
+ ifp->name,
+ ifaddr_str,
+ igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ qri_dsec / 10, qri_dsec % 10,
+ gmi_dsec / 10, gmi_dsec % 10,
+ sqi,
+ oqpi_dsec / 10, oqpi_dsec % 10,
+ lmqt_dsec / 10, lmqt_dsec % 10,
+ ohpi_dsec / 10, ohpi_dsec % 10,
+ VTY_NEWLINE);
+
+ } /* scan igmp sockets */
+ } /* scan interfaces */
+}
+
+static void igmp_show_sources(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+ time_t now;
+
+ now = pim_time_monotonic_sec();
+
+ vty_out(vty, "Interface Address Group Source Timer Fwd Uptime %s", VTY_NEWLINE);
+
+ /* scan interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ if (!pim_ifp)
+ continue;
+
+ /* scan igmp sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char ifaddr_str[100];
+ struct listnode *grpnode;
+ struct igmp_group *grp;
+
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+ /* scan igmp groups */
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+ char group_str[100];
+ struct listnode *srcnode;
+ struct igmp_source *src;
+
+ pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+
+ /* scan group sources */
+ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) {
+ char source_str[100];
+ char mmss[10];
+ char uptime[10];
+
+ pim_inet4_dump("<source?>", src->source_addr, source_str, sizeof(source_str));
+
+ pim_time_timer_to_mmss(mmss, sizeof(mmss), src->t_source_timer);
+
+ pim_time_uptime(uptime, sizeof(uptime), now - src->source_creation);
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %5s %3s %8s%s",
+ ifp->name,
+ ifaddr_str,
+ group_str,
+ source_str,
+ mmss,
+ IGMP_SOURCE_TEST_FORWARDING(src->source_flags) ? "Y" : "N",
+ uptime,
+ VTY_NEWLINE);
+
+ } /* scan group sources */
+ } /* scan igmp groups */
+ } /* scan igmp sockets */
+ } /* scan interfaces */
+}
+
+static void igmp_show_source_retransmission(struct vty *vty)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ vty_out(vty, "Interface Address Group Source Counter%s", VTY_NEWLINE);
+
+ /* scan interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp = ifp->info;
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ if (!pim_ifp)
+ continue;
+
+ /* scan igmp sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ char ifaddr_str[100];
+ struct listnode *grpnode;
+ struct igmp_group *grp;
+
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+
+ /* scan igmp groups */
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grpnode, grp)) {
+ char group_str[100];
+ struct listnode *srcnode;
+ struct igmp_source *src;
+
+ pim_inet4_dump("<group?>", grp->group_addr, group_str, sizeof(group_str));
+
+ /* scan group sources */
+ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, srcnode, src)) {
+ char source_str[100];
+
+ pim_inet4_dump("<source?>", src->source_addr, source_str, sizeof(source_str));
+
+ vty_out(vty, "%-9s %-15s %-15s %-15s %7d%s",
+ ifp->name,
+ ifaddr_str,
+ group_str,
+ source_str,
+ src->source_query_retransmit_count,
+ VTY_NEWLINE);
+
+ } /* scan group sources */
+ } /* scan igmp groups */
+ } /* scan igmp sockets */
+ } /* scan interfaces */
+}
+
+static void clear_igmp_interfaces()
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct interface *ifp;
+
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_if_addr_del_all_igmp(ifp);
+ }
+
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_if_addr_add_all(ifp);
+ }
+}
+
+static void clear_pim_interfaces()
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct interface *ifp;
+
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ if (ifp->info) {
+ pim_neighbor_delete_all(ifp, "interface cleared");
+ }
+ }
+}
+
+static void clear_interfaces()
+{
+ clear_igmp_interfaces();
+ clear_pim_interfaces();
+}
+
+DEFUN (pim_interface,
+ pim_interface_cmd,
+ "interface IFNAME",
+ "Select an interface to configure\n"
+ "Interface's name\n")
+{
+ struct interface *ifp;
+ const char *ifname = argv[0];
+ size_t sl;
+
+ sl = strlen(ifname);
+ if (sl > INTERFACE_NAMSIZ) {
+ vty_out(vty, "%% Interface name %s is invalid: length exceeds "
+ "%d characters%s",
+ ifname, INTERFACE_NAMSIZ, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ ifp = if_lookup_by_name_len(ifname, sl);
+ if (!ifp) {
+ vty_out(vty, "%% Interface %s does not exist%s", ifname, VTY_NEWLINE);
+
+ /* Returning here would prevent pimd from booting when there are
+ interface commands in pimd.conf, since all interfaces are
+ unknown at pimd boot time (the zebra daemon has not been
+ contacted for interface discovery). */
+
+ ifp = if_get_by_name_len(ifname, sl);
+ if (!ifp) {
+ vty_out(vty, "%% Could not create interface %s%s", ifname, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+
+ vty->index = ifp;
+ vty->node = INTERFACE_NODE;
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_interfaces,
+ clear_ip_interfaces_cmd,
+ "clear ip interfaces",
+ CLEAR_STR
+ IP_STR
+ "Reset interfaces\n")
+{
+ clear_interfaces();
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_igmp_interfaces,
+ clear_ip_igmp_interfaces_cmd,
+ "clear ip igmp interfaces",
+ CLEAR_STR
+ IP_STR
+ CLEAR_IP_IGMP_STR
+ "Reset IGMP interfaces\n")
+{
+ clear_igmp_interfaces();
+
+ return CMD_SUCCESS;
+}
+
+static void mroute_add_all()
+{
+ struct listnode *node;
+ struct channel_oil *c_oil;
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+ if (pim_mroute_add(&c_oil->oil)) {
+ /* just log warning */
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ zlog_warn("%s %s: (S,G)=(%s,%s) failure writing MFC",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str);
+ }
+ }
+}
+
+static void mroute_del_all()
+{
+ struct listnode *node;
+ struct channel_oil *c_oil;
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+ if (pim_mroute_del(&c_oil->oil)) {
+ /* just log warning */
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ zlog_warn("%s %s: (S,G)=(%s,%s) failure clearing MFC",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str);
+ }
+ }
+}
+
+DEFUN (clear_ip_mroute,
+ clear_ip_mroute_cmd,
+ "clear ip mroute",
+ CLEAR_STR
+ IP_STR
+ "Reset multicast routes\n")
+{
+ mroute_del_all();
+ mroute_add_all();
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_pim_interfaces,
+ clear_ip_pim_interfaces_cmd,
+ "clear ip pim interfaces",
+ CLEAR_STR
+ IP_STR
+ CLEAR_IP_PIM_STR
+ "Reset PIM interfaces\n")
+{
+ clear_pim_interfaces();
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (clear_ip_pim_oil,
+ clear_ip_pim_oil_cmd,
+ "clear ip pim oil",
+ CLEAR_STR
+ IP_STR
+ CLEAR_IP_PIM_STR
+ "Rescan PIM OIL (output interface list)\n")
+{
+ pim_scan_oil();
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_interface,
+ show_ip_igmp_interface_cmd,
+ "show ip igmp interface",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ "IGMP interface information\n")
+{
+ igmp_show_interfaces(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_join,
+ show_ip_igmp_join_cmd,
+ "show ip igmp join",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ "IGMP static join information\n")
+{
+ igmp_show_interface_join(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_groups,
+ show_ip_igmp_groups_cmd,
+ "show ip igmp groups",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ IGMP_GROUP_STR)
+{
+ igmp_show_groups(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_groups_retransmissions,
+ show_ip_igmp_groups_retransmissions_cmd,
+ "show ip igmp groups retransmissions",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ IGMP_GROUP_STR
+ "IGMP group retransmissions\n")
+{
+ igmp_show_group_retransmission(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_parameters,
+ show_ip_igmp_parameters_cmd,
+ "show ip igmp parameters",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ "IGMP parameters information\n")
+{
+ igmp_show_parameters(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_sources,
+ show_ip_igmp_sources_cmd,
+ "show ip igmp sources",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ IGMP_SOURCE_STR)
+{
+ igmp_show_sources(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_sources_retransmissions,
+ show_ip_igmp_sources_retransmissions_cmd,
+ "show ip igmp sources retransmissions",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ IGMP_SOURCE_STR
+ "IGMP source retransmissions\n")
+{
+ igmp_show_source_retransmission(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_igmp_querier,
+ show_ip_igmp_querier_cmd,
+ "show ip igmp querier",
+ SHOW_STR
+ IP_STR
+ IGMP_STR
+ "IGMP querier information\n")
+{
+ igmp_show_querier(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_address,
+ show_ip_pim_address_cmd,
+ "show ip pim address",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface address\n")
+{
+ show_interface_address(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert,
+ show_ip_pim_assert_cmd,
+ "show ip pim assert",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface assert\n")
+{
+ pim_show_assert(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert_internal,
+ show_ip_pim_assert_internal_cmd,
+ "show ip pim assert-internal",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface internal assert state\n")
+{
+ pim_show_assert_internal(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert_metric,
+ show_ip_pim_assert_metric_cmd,
+ "show ip pim assert-metric",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface assert metric\n")
+{
+ pim_show_assert_metric(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_assert_winner_metric,
+ show_ip_pim_assert_winner_metric_cmd,
+ "show ip pim assert-winner-metric",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface assert winner metric\n")
+{
+ pim_show_assert_winner_metric(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_dr,
+ show_ip_pim_dr_cmd,
+ "show ip pim designated-router",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface designated router\n")
+{
+ pim_show_dr(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_hello,
+ show_ip_pim_hello_cmd,
+ "show ip pim hello",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface hello information\n")
+{
+ pim_show_hello(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_interface,
+ show_ip_pim_interface_cmd,
+ "show ip pim interface",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface information\n")
+{
+ pim_show_interfaces(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_join,
+ show_ip_pim_join_cmd,
+ "show ip pim join",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface join information\n")
+{
+ pim_show_join(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_lan_prune_delay,
+ show_ip_pim_lan_prune_delay_cmd,
+ "show ip pim lan-prune-delay",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM neighbors LAN prune delay parameters\n")
+{
+ pim_show_lan_prune_delay(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_local_membership,
+ show_ip_pim_local_membership_cmd,
+ "show ip pim local-membership",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface local-membership\n")
+{
+ pim_show_membership(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_jp_override_interval,
+ show_ip_pim_jp_override_interval_cmd,
+ "show ip pim jp-override-interval",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM interface J/P override interval\n")
+{
+ pim_show_jp_override_interval(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_neighbor,
+ show_ip_pim_neighbor_cmd,
+ "show ip pim neighbor",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM neighbor information\n")
+{
+ pim_show_neighbors(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_secondary,
+ show_ip_pim_secondary_cmd,
+ "show ip pim secondary",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM neighbor addresses\n")
+{
+ pim_show_neighbors_secondary(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_upstream,
+ show_ip_pim_upstream_cmd,
+ "show ip pim upstream",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM upstream information\n")
+{
+ pim_show_upstream(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_upstream_join_desired,
+ show_ip_pim_upstream_join_desired_cmd,
+ "show ip pim upstream-join-desired",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM upstream join-desired\n")
+{
+ pim_show_join_desired(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_upstream_rpf,
+ show_ip_pim_upstream_rpf_cmd,
+ "show ip pim upstream-rpf",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM upstream source rpf\n")
+{
+ pim_show_upstream_rpf(vty);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_pim_rpf,
+ show_ip_pim_rpf_cmd,
+ "show ip pim rpf",
+ SHOW_STR
+ IP_STR
+ PIM_STR
+ "PIM cached source rpf information\n")
+{
+ pim_show_rpf(vty);
+
+ return CMD_SUCCESS;
+}
+
+static void show_multicast_interfaces(struct vty *vty)
+{
+ struct listnode *node;
+ struct interface *ifp;
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ vty_out(vty, "Interface Address ifi Vif PktsIn PktsOut BytesIn BytesOut%s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+ struct sioc_vif_req vreq;
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ continue;
+
+ memset(&vreq, 0, sizeof(vreq));
+ vreq.vifi = pim_ifp->mroute_vif_index;
+
+ if (ioctl(qpim_mroute_socket_fd, SIOCGETVIFCNT, &vreq)) {
+ int e = errno;
+ vty_out(vty,
+ "ioctl(SIOCGETVIFCNT=%d) failure for interface %s vif_index=%d: errno=%d: %s%s",
+ SIOCGETVIFCNT,
+ ifp->name,
+ pim_ifp->mroute_vif_index,
+ e,
+ safe_strerror(e),
+ VTY_NEWLINE);
+ continue;
+ }
+
+ ifaddr = pim_ifp->primary_address;
+
+ vty_out(vty, "%-9s %-15s %3d %3d %7lu %7lu %10lu %10lu%s",
+ ifp->name,
+ inet_ntoa(ifaddr),
+ ifp->ifindex,
+ pim_ifp->mroute_vif_index,
+ vreq.icount,
+ vreq.ocount,
+ vreq.ibytes,
+ vreq.obytes,
+ VTY_NEWLINE);
+ }
+}
+
+DEFUN (show_ip_multicast,
+ show_ip_multicast_cmd,
+ "show ip multicast",
+ SHOW_STR
+ IP_STR
+ "Multicast global information\n")
+{
+ time_t now = pim_time_monotonic_sec();
+
+ if (PIM_MROUTE_IS_ENABLED) {
+ char uptime[10];
+
+ vty_out(vty, "Mroute socket descriptor: %d%s",
+ qpim_mroute_socket_fd,
+ VTY_NEWLINE);
+
+ pim_time_uptime(uptime, sizeof(uptime), now - qpim_mroute_socket_creation);
+ vty_out(vty, "Mroute socket uptime: %s%s",
+ uptime,
+ VTY_NEWLINE);
+ }
+ else {
+ vty_out(vty, "Multicast disabled%s",
+ VTY_NEWLINE);
+ }
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "Zclient update socket: ");
+ if (qpim_zclient_update) {
+ vty_out(vty, "%d failures=%d%s", qpim_zclient_update->sock,
+ qpim_zclient_update->fail, VTY_NEWLINE);
+ }
+ else {
+ vty_out(vty, "<null zclient>%s", VTY_NEWLINE);
+ }
+ vty_out(vty, "Zclient lookup socket: ");
+ if (qpim_zclient_lookup) {
+ vty_out(vty, "%d failures=%d%s", qpim_zclient_lookup->sock,
+ qpim_zclient_lookup->fail, VTY_NEWLINE);
+ }
+ else {
+ vty_out(vty, "<null zclient>%s", VTY_NEWLINE);
+ }
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "Current highest VifIndex: %d%s",
+ qpim_mroute_oif_highest_vif_index,
+ VTY_NEWLINE);
+ vty_out(vty, "Maximum highest VifIndex: %d%s",
+ MAXVIFS - 1,
+ VTY_NEWLINE);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+ vty_out(vty, "Upstream Join Timer: %d secs%s",
+ qpim_t_periodic,
+ VTY_NEWLINE);
+ vty_out(vty, "Join/Prune Holdtime: %d secs%s",
+ PIM_JP_HOLDTIME,
+ VTY_NEWLINE);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ show_rpf_refresh_stats(vty, now);
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ show_scan_oil_stats(vty, now);
+
+ show_multicast_interfaces(vty);
+
+ return CMD_SUCCESS;
+}
+
+static void show_mroute(struct vty *vty)
+{
+ struct listnode *node;
+ struct channel_oil *c_oil;
+ time_t now;
+
+ vty_out(vty, "Proto: I=IGMP P=PIM%s%s", VTY_NEWLINE, VTY_NEWLINE);
+
+ vty_out(vty, "Source Group Proto Input iVifI Output oVifI TTL Uptime %s",
+ VTY_NEWLINE);
+
+ now = pim_time_monotonic_sec();
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+ char group_str[100];
+ char source_str[100];
+ int oif_vif_index;
+
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+
+ for (oif_vif_index = 0; oif_vif_index < MAXVIFS; ++oif_vif_index) {
+ struct interface *ifp_in;
+ struct interface *ifp_out;
+ char oif_uptime[10];
+ int ttl;
+ char proto[5];
+
+ ttl = c_oil->oil.mfcc_ttls[oif_vif_index];
+ if (ttl < 1)
+ continue;
+
+ ifp_in = pim_if_find_by_vif_index(c_oil->oil.mfcc_parent);
+ ifp_out = pim_if_find_by_vif_index(oif_vif_index);
+
+ pim_time_uptime(oif_uptime, sizeof(oif_uptime), now - c_oil->oif_creation[oif_vif_index]);
+
+ proto[0] = '\0';
+ if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_PIM) {
+ strcat(proto, "P");
+ }
+ if (c_oil->oif_flags[oif_vif_index] & PIM_OIF_FLAG_PROTO_IGMP) {
+ strcat(proto, "I");
+ }
+
+ vty_out(vty, "%-15s %-15s %-5s %-5s %5d %-6s %5d %3d %8s %s",
+ source_str,
+ group_str,
+ proto,
+ ifp_in ? ifp_in->name : "<iif?>",
+ c_oil->oil.mfcc_parent,
+ ifp_out ? ifp_out->name : "<oif?>",
+ oif_vif_index,
+ ttl,
+ oif_uptime,
+ VTY_NEWLINE);
+ }
+ }
+}
+
+DEFUN (show_ip_mroute,
+ show_ip_mroute_cmd,
+ "show ip mroute",
+ SHOW_STR
+ IP_STR
+ MROUTE_STR)
+{
+ show_mroute(vty);
+ return CMD_SUCCESS;
+}
+
+static void show_mroute_count(struct vty *vty)
+{
+ struct listnode *node;
+ struct channel_oil *c_oil;
+
+ vty_out(vty, "%s", VTY_NEWLINE);
+
+ vty_out(vty, "Source Group Packets Bytes WrongIf %s",
+ VTY_NEWLINE);
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+ char group_str[100];
+ char source_str[100];
+ struct sioc_sg_req sgreq;
+
+ memset(&sgreq, 0, sizeof(sgreq));
+ sgreq.src = c_oil->oil.mfcc_origin;
+ sgreq.grp = c_oil->oil.mfcc_mcastgrp;
+
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+
+ if (ioctl(qpim_mroute_socket_fd, SIOCGETSGCNT, &sgreq)) {
+ int e = errno;
+ vty_out(vty,
+ "ioctl(SIOCGETSGCNT=%d) failure for (S,G)=(%s,%s): errno=%d: %s%s",
+ SIOCGETSGCNT,
+ source_str,
+ group_str,
+ e,
+ safe_strerror(e),
+ VTY_NEWLINE);
+ continue;
+ }
+
+ vty_out(vty, "%-15s %-15s %7ld %10ld %7ld %s",
+ source_str,
+ group_str,
+ sgreq.pktcnt,
+ sgreq.bytecnt,
+ sgreq.wrong_if,
+ VTY_NEWLINE);
+
+ }
+}
+
+DEFUN (show_ip_mroute_count,
+ show_ip_mroute_count_cmd,
+ "show ip mroute count",
+ SHOW_STR
+ IP_STR
+ MROUTE_STR
+ "Route and packet count data\n")
+{
+ show_mroute_count(vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN (show_ip_rib,
+ show_ip_rib_cmd,
+ "show ip rib A.B.C.D",
+ SHOW_STR
+ IP_STR
+ RIB_STR
+ "Unicast address\n")
+{
+ struct in_addr addr;
+ const char *addr_str;
+ struct pim_nexthop nexthop;
+ char nexthop_addr_str[100];
+ int result;
+
+ addr_str = argv[0];
+ result = inet_pton(AF_INET, addr_str, &addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad unicast address %s: errno=%d: %s%s",
+ addr_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (pim_nexthop_lookup(&nexthop, addr)) {
+ vty_out(vty, "Failure querying RIB nexthop for unicast address %s%s",
+ addr_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty, "Address NextHop Interface Metric Preference%s",
+ VTY_NEWLINE);
+
+ pim_inet4_dump("<nexthop?>", nexthop.mrib_nexthop_addr,
+ nexthop_addr_str, sizeof(nexthop_addr_str));
+
+ vty_out(vty, "%-15s %-15s %-9s %6d %10d%s",
+ addr_str,
+ nexthop_addr_str,
+ nexthop.interface ? nexthop.interface->name : "<ifname?>",
+ nexthop.mrib_route_metric,
+ nexthop.mrib_metric_preference,
+ VTY_NEWLINE);
+
+ return CMD_SUCCESS;
+}
+
+static void show_ssmpingd(struct vty *vty)
+{
+ struct listnode *node;
+ struct ssmpingd_sock *ss;
+ time_t now;
+
+ vty_out(vty, "Source Socket Address Port Uptime Requests%s",
+ VTY_NEWLINE);
+
+ if (!qpim_ssmpingd_list)
+ return;
+
+ now = pim_time_monotonic_sec();
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_ssmpingd_list, node, ss)) {
+ char source_str[100];
+ char ss_uptime[10];
+ struct sockaddr_in bind_addr;
+ socklen_t len = sizeof(bind_addr);
+ char bind_addr_str[100];
+
+ pim_inet4_dump("<src?>", ss->source_addr, source_str, sizeof(source_str));
+
+ if (pim_socket_getsockname(ss->sock_fd, (struct sockaddr *) &bind_addr, &len)) {
+ vty_out(vty, "%% Failure reading socket name for ssmpingd source %s on fd=%d%s",
+ source_str, ss->sock_fd, VTY_NEWLINE);
+ }
+
+ pim_inet4_dump("<addr?>", bind_addr.sin_addr, bind_addr_str, sizeof(bind_addr_str));
+ pim_time_uptime(ss_uptime, sizeof(ss_uptime), now - ss->creation);
+
+ vty_out(vty, "%-15s %6d %-15s %5d %8s %8lld%s",
+ source_str,
+ ss->sock_fd,
+ bind_addr_str,
+ ntohs(bind_addr.sin_port),
+ ss_uptime,
+ (long long)ss->requests,
+ VTY_NEWLINE);
+ }
+}
+
+DEFUN (show_ip_ssmpingd,
+ show_ip_ssmpingd_cmd,
+ "show ip ssmpingd",
+ SHOW_STR
+ IP_STR
+ SHOW_SSMPINGD_STR)
+{
+ show_ssmpingd(vty);
+ return CMD_SUCCESS;
+}
+
+DEFUN (ip_multicast_routing,
+ ip_multicast_routing_cmd,
+ PIM_CMD_IP_MULTICAST_ROUTING,
+ IP_STR
+ "Enable IP multicast forwarding\n")
+{
+ pim_mroute_socket_enable();
+ pim_if_add_vif_all();
+ mroute_add_all();
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_ip_multicast_routing,
+ no_ip_multicast_routing_cmd,
+ PIM_CMD_NO " " PIM_CMD_IP_MULTICAST_ROUTING,
+ NO_STR
+ IP_STR
+ "Global IP configuration subcommands\n"
+ "Enable IP multicast forwarding\n")
+{
+ mroute_del_all();
+ pim_if_del_vif_all();
+ pim_mroute_socket_disable();
+ return CMD_SUCCESS;
+}
+
+DEFUN (ip_ssmpingd,
+ ip_ssmpingd_cmd,
+ "ip ssmpingd [A.B.C.D]",
+ IP_STR
+ CONF_SSMPINGD_STR
+ "Source address\n")
+{
+ int result;
+ struct in_addr source_addr;
+ const char *source_str = (argc > 0) ? argv[0] : "0.0.0.0";
+
+ result = inet_pton(AF_INET, source_str, &source_addr);
+ if (result <= 0) {
+ vty_out(vty, "%% Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ result = pim_ssmpingd_start(source_addr);
+ if (result) {
+ vty_out(vty, "%% Failure starting ssmpingd for source %s: %d%s",
+ source_str, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_ip_ssmpingd,
+ no_ip_ssmpingd_cmd,
+ "no ip ssmpingd [A.B.C.D]",
+ NO_STR
+ IP_STR
+ CONF_SSMPINGD_STR
+ "Source address\n")
+{
+ int result;
+ struct in_addr source_addr;
+ const char *source_str = (argc > 0) ? argv[0] : "0.0.0.0";
+
+ result = inet_pton(AF_INET, source_str, &source_addr);
+ if (result <= 0) {
+ vty_out(vty, "%% Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ result = pim_ssmpingd_stop(source_addr);
+ if (result) {
+ vty_out(vty, "%% Failure stopping ssmpingd for source %s: %d%s",
+ source_str, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_ip_igmp,
+ interface_ip_igmp_cmd,
+ "ip igmp",
+ IP_STR
+ IFACE_IGMP_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp) {
+ pim_ifp = pim_if_new(ifp, 1 /* igmp=true */, 0 /* pim=false */);
+ if (!pim_ifp) {
+ vty_out(vty, "Could not enable IGMP on interface %s%s",
+ ifp->name, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+ else {
+ PIM_IF_DO_IGMP(pim_ifp->options);
+ }
+
+ pim_if_addr_add_all(ifp);
+ pim_if_membership_refresh(ifp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp,
+ interface_no_ip_igmp_cmd,
+ "no ip igmp",
+ NO_STR
+ IP_STR
+ IFACE_IGMP_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return CMD_SUCCESS;
+
+ PIM_IF_DONT_IGMP(pim_ifp->options);
+
+ pim_if_membership_clear(ifp);
+
+ pim_if_addr_del_all_igmp(ifp);
+
+ if (!PIM_IF_TEST_PIM(pim_ifp->options)) {
+ pim_if_delete(ifp);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_ip_igmp_join,
+ interface_ip_igmp_join_cmd,
+ "ip igmp join A.B.C.D A.B.C.D",
+ IP_STR
+ IFACE_IGMP_STR
+ "IGMP join multicast group\n"
+ "Multicast group address\n"
+ "Source address\n")
+{
+ struct interface *ifp;
+ const char *group_str;
+ const char *source_str;
+ struct in_addr group_addr;
+ struct in_addr source_addr;
+ int result;
+
+ ifp = vty->index;
+
+ /* Group address */
+ group_str = argv[0];
+ result = inet_pton(AF_INET, group_str, &group_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+ group_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Source address */
+ source_str = argv[1];
+ result = inet_pton(AF_INET, source_str, &source_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ result = pim_if_igmp_join_add(ifp, group_addr, source_addr);
+ if (result) {
+ vty_out(vty, "%% Failure joining IGMP group %s source %s on interface %s: %d%s",
+ group_str, source_str, ifp->name, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_join,
+ interface_no_ip_igmp_join_cmd,
+ "no ip igmp join A.B.C.D A.B.C.D",
+ NO_STR
+ IP_STR
+ IFACE_IGMP_STR
+ "IGMP join multicast group\n"
+ "Multicast group address\n"
+ "Source address\n")
+{
+ struct interface *ifp;
+ const char *group_str;
+ const char *source_str;
+ struct in_addr group_addr;
+ struct in_addr source_addr;
+ int result;
+
+ ifp = vty->index;
+
+ /* Group address */
+ group_str = argv[0];
+ result = inet_pton(AF_INET, group_str, &group_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+ group_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Source address */
+ source_str = argv[1];
+ result = inet_pton(AF_INET, source_str, &source_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ result = pim_if_igmp_join_del(ifp, group_addr, source_addr);
+ if (result) {
+ vty_out(vty, "%% Failure leaving IGMP group %s source %s on interface %s: %d%s",
+ group_str, source_str, ifp->name, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+/*
+ CLI reconfiguration affects the interface level (struct pim_interface).
+ This function propagates the reconfiguration to every active socket
+ for that interface.
+ */
+static void igmp_sock_query_interval_reconfig(struct igmp_sock *igmp)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+
+ zassert(igmp);
+
+ /* other querier present? */
+
+ if (igmp->t_other_querier_timer)
+ return;
+
+ /* this is the querier */
+
+ zassert(igmp->interface);
+ zassert(igmp->interface->info);
+
+ ifp = igmp->interface;
+ pim_ifp = ifp->info;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char ifaddr_str[100];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+ zlog_debug("%s: Querier %s on %s reconfig query_interval=%d",
+ __PRETTY_FUNCTION__,
+ ifaddr_str,
+ ifp->name,
+ pim_ifp->igmp_default_query_interval);
+ }
+
+ /*
+ igmp_startup_mode_on() will reset QQI:
+
+ igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
+ */
+ igmp_startup_mode_on(igmp);
+}
+
+static void igmp_sock_query_reschedule(struct igmp_sock *igmp)
+{
+ if (igmp->t_igmp_query_timer) {
+ /* other querier present */
+ zassert(igmp->t_igmp_query_timer);
+ zassert(!igmp->t_other_querier_timer);
+
+ pim_igmp_general_query_off(igmp);
+ pim_igmp_general_query_on(igmp);
+
+ zassert(igmp->t_igmp_query_timer);
+ zassert(!igmp->t_other_querier_timer);
+ }
+ else {
+ /* this is the querier */
+
+ zassert(!igmp->t_igmp_query_timer);
+ zassert(igmp->t_other_querier_timer);
+
+ pim_igmp_other_querier_timer_off(igmp);
+ pim_igmp_other_querier_timer_on(igmp);
+
+ zassert(!igmp->t_igmp_query_timer);
+ zassert(igmp->t_other_querier_timer);
+ }
+}
+
+static void change_query_interval(struct pim_interface *pim_ifp,
+ int query_interval)
+{
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ pim_ifp->igmp_default_query_interval = query_interval;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ igmp_sock_query_interval_reconfig(igmp);
+ igmp_sock_query_reschedule(igmp);
+ }
+}
+
+static void change_query_max_response_time(struct pim_interface *pim_ifp,
+ int query_max_response_time_dsec)
+{
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ pim_ifp->igmp_query_max_response_time_dsec = query_max_response_time_dsec;
+
+ /*
+ Below we modify socket/group/source timers in order to quickly
+ reflect the change. Otherwise, those timers would eventually catch
+ up.
+ */
+
+ /* scan all sockets */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_socket_list, sock_node, igmp)) {
+ struct listnode *grp_node;
+ struct igmp_group *grp;
+
+ /* reschedule socket general query */
+ igmp_sock_query_reschedule(igmp);
+
+ /* scan socket groups */
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, grp_node, grp)) {
+ struct listnode *src_node;
+ struct igmp_source *src;
+
+ /* reset group timers for groups in EXCLUDE mode */
+ if (grp->group_filtermode_isexcl) {
+ igmp_group_reset_gmi(grp);
+ }
+
+ /* scan group sources */
+ for (ALL_LIST_ELEMENTS_RO(grp->group_source_list, src_node, src)) {
+
+ /* reset source timers for sources with running timers */
+ if (src->t_source_timer) {
+ igmp_source_reset_gmi(igmp, grp, src);
+ }
+ }
+ }
+ }
+}
+
+#define IGMP_QUERY_INTERVAL_MIN (1)
+#define IGMP_QUERY_INTERVAL_MAX (1800)
+
+DEFUN (interface_ip_igmp_query_interval,
+ interface_ip_igmp_query_interval_cmd,
+ PIM_CMD_IP_IGMP_QUERY_INTERVAL " <1-1800>",
+ IP_STR
+ IFACE_IGMP_STR
+ IFACE_IGMP_QUERY_INTERVAL_STR
+ "Query interval in seconds\n")
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int query_interval;
+ int query_interval_dsec;
+
+ ifp = vty->index;
+ 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;
+ }
+
+ query_interval = atoi(argv[0]);
+ query_interval_dsec = 10 * query_interval;
+
+ /*
+ It seems we don't need to check bounds since command.c does it
+ already, but we verify them anyway for extra safety.
+ */
+ if (query_interval < IGMP_QUERY_INTERVAL_MIN) {
+ vty_out(vty, "General query interval %d lower than minimum %d%s",
+ query_interval,
+ IGMP_QUERY_INTERVAL_MIN,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (query_interval > IGMP_QUERY_INTERVAL_MAX) {
+ vty_out(vty, "General query interval %d higher than maximum %d%s",
+ query_interval,
+ IGMP_QUERY_INTERVAL_MAX,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) {
+ vty_out(vty,
+ "Can't set general query interval %d dsec <= query max response time %d dsec.%s",
+ query_interval_dsec, pim_ifp->igmp_query_max_response_time_dsec,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ change_query_interval(pim_ifp, query_interval);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_query_interval,
+ interface_no_ip_igmp_query_interval_cmd,
+ PIM_CMD_NO " " PIM_CMD_IP_IGMP_QUERY_INTERVAL,
+ NO_STR
+ IP_STR
+ IFACE_IGMP_STR
+ IFACE_IGMP_QUERY_INTERVAL_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int default_query_interval_dsec;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ return CMD_SUCCESS;
+
+ default_query_interval_dsec = IGMP_GENERAL_QUERY_INTERVAL * 10;
+
+ if (default_query_interval_dsec <= pim_ifp->igmp_query_max_response_time_dsec) {
+ vty_out(vty,
+ "Can't set default general query interval %d dsec <= query max response time %d dsec.%s",
+ default_query_interval_dsec, pim_ifp->igmp_query_max_response_time_dsec,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ change_query_interval(pim_ifp, IGMP_GENERAL_QUERY_INTERVAL);
+
+ return CMD_SUCCESS;
+}
+
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MIN (1)
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MAX (25)
+
+DEFUN (interface_ip_igmp_query_max_response_time,
+ interface_ip_igmp_query_max_response_time_cmd,
+ PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME " <1-25>",
+ IP_STR
+ IFACE_IGMP_STR
+ IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR
+ "Query response value in seconds\n")
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int query_max_response_time;
+
+ ifp = vty->index;
+ 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;
+ }
+
+ query_max_response_time = atoi(argv[0]);
+
+ /*
+ It seems we don't need to check bounds since command.c does it
+ already, but we verify them anyway for extra safety.
+ */
+ if (query_max_response_time < IGMP_QUERY_MAX_RESPONSE_TIME_MIN) {
+ vty_out(vty, "Query max response time %d sec lower than minimum %d sec%s",
+ query_max_response_time,
+ IGMP_QUERY_MAX_RESPONSE_TIME_MIN,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (query_max_response_time > IGMP_QUERY_MAX_RESPONSE_TIME_MAX) {
+ vty_out(vty, "Query max response time %d sec higher than maximum %d sec%s",
+ query_max_response_time,
+ IGMP_QUERY_MAX_RESPONSE_TIME_MAX,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (query_max_response_time >= pim_ifp->igmp_default_query_interval) {
+ vty_out(vty,
+ "Can't set query max response time %d sec >= general query interval %d sec%s",
+ query_max_response_time, pim_ifp->igmp_default_query_interval,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ change_query_max_response_time(pim_ifp, 10 * query_max_response_time);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_query_max_response_time,
+ interface_no_ip_igmp_query_max_response_time_cmd,
+ PIM_CMD_NO " " PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME,
+ NO_STR
+ IP_STR
+ IFACE_IGMP_STR
+ IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int default_query_interval_dsec;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ return CMD_SUCCESS;
+
+ default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval;
+
+ if (IGMP_QUERY_MAX_RESPONSE_TIME_DSEC >= default_query_interval_dsec) {
+ vty_out(vty,
+ "Can't set default query max response time %d dsec >= general query interval %d dsec.%s",
+ IGMP_QUERY_MAX_RESPONSE_TIME_DSEC, default_query_interval_dsec,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ change_query_max_response_time(pim_ifp, IGMP_QUERY_MAX_RESPONSE_TIME_DSEC);
+
+ return CMD_SUCCESS;
+}
+
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC (10)
+#define IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC (250)
+
+DEFUN (interface_ip_igmp_query_max_response_time_dsec,
+ interface_ip_igmp_query_max_response_time_dsec_cmd,
+ PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC " <10-250>",
+ IP_STR
+ IFACE_IGMP_STR
+ IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR
+ "Query response value in deciseconds\n")
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int query_max_response_time_dsec;
+ int default_query_interval_dsec;
+
+ ifp = vty->index;
+ 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;
+ }
+
+ query_max_response_time_dsec = atoi(argv[0]);
+
+ /*
+ It seems we don't need to check bounds since command.c does it
+ already, but we verify them anyway for extra safety.
+ */
+ if (query_max_response_time_dsec < IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC) {
+ vty_out(vty, "Query max response time %d dsec lower than minimum %d dsec%s",
+ query_max_response_time_dsec,
+ IGMP_QUERY_MAX_RESPONSE_TIME_MIN_DSEC,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ if (query_max_response_time_dsec > IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC) {
+ vty_out(vty, "Query max response time %d dsec higher than maximum %d dsec%s",
+ query_max_response_time_dsec,
+ IGMP_QUERY_MAX_RESPONSE_TIME_MAX_DSEC,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval;
+
+ if (query_max_response_time_dsec >= default_query_interval_dsec) {
+ vty_out(vty,
+ "Can't set query max response time %d dsec >= general query interval %d dsec%s",
+ query_max_response_time_dsec, default_query_interval_dsec,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ change_query_max_response_time(pim_ifp, query_max_response_time_dsec);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_igmp_query_max_response_time_dsec,
+ interface_no_ip_igmp_query_max_response_time_dsec_cmd,
+ PIM_CMD_NO " " PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC,
+ NO_STR
+ IP_STR
+ IFACE_IGMP_STR
+ IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int default_query_interval_dsec;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp)
+ return CMD_SUCCESS;
+
+ default_query_interval_dsec = 10 * pim_ifp->igmp_default_query_interval;
+
+ if (IGMP_QUERY_MAX_RESPONSE_TIME_DSEC >= default_query_interval_dsec) {
+ vty_out(vty,
+ "Can't set default query max response time %d dsec >= general query interval %d dsec.%s",
+ IGMP_QUERY_MAX_RESPONSE_TIME_DSEC, default_query_interval_dsec,
+ VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ change_query_max_response_time(pim_ifp, IGMP_QUERY_MAX_RESPONSE_TIME_DSEC);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_ip_pim_ssm,
+ interface_ip_pim_ssm_cmd,
+ "ip pim ssm",
+ IP_STR
+ PIM_STR
+ IFACE_PIM_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp) {
+ pim_ifp = pim_if_new(ifp, 0 /* igmp=false */, 1 /* pim=true */);
+ if (!pim_ifp) {
+ vty_out(vty, "Could not enable PIM on interface%s", VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+ else {
+ PIM_IF_DO_PIM(pim_ifp->options);
+ }
+
+ pim_if_addr_add_all(ifp);
+ pim_if_membership_refresh(ifp);
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (interface_no_ip_pim_ssm,
+ interface_no_ip_pim_ssm_cmd,
+ "no ip pim ssm",
+ NO_STR
+ IP_STR
+ PIM_STR
+ IFACE_PIM_STR)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+
+ ifp = vty->index;
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return CMD_SUCCESS;
+
+ PIM_IF_DONT_PIM(pim_ifp->options);
+
+ pim_if_membership_clear(ifp);
+
+ /*
+ pim_if_addr_del_all() removes all sockets from
+ pim_ifp->igmp_socket_list.
+ */
+ pim_if_addr_del_all(ifp);
+
+ /*
+ pim_sock_delete() removes all neighbors from
+ pim_ifp->pim_neighbor_list.
+ */
+ pim_sock_delete(ifp, "pim unconfigured on interface");
+
+ if (!PIM_IF_TEST_IGMP(pim_ifp->options)) {
+ pim_if_delete(ifp);
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_igmp,
+ debug_igmp_cmd,
+ "debug igmp",
+ DEBUG_STR
+ DEBUG_IGMP_STR)
+{
+ PIM_DO_DEBUG_IGMP_EVENTS;
+ PIM_DO_DEBUG_IGMP_PACKETS;
+ PIM_DO_DEBUG_IGMP_TRACE;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp,
+ no_debug_igmp_cmd,
+ "no debug igmp",
+ NO_STR
+ DEBUG_STR
+ DEBUG_IGMP_STR)
+{
+ PIM_DONT_DEBUG_IGMP_EVENTS;
+ PIM_DONT_DEBUG_IGMP_PACKETS;
+ PIM_DONT_DEBUG_IGMP_TRACE;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp,
+ undebug_igmp_cmd,
+ "undebug igmp",
+ UNDEBUG_STR
+ DEBUG_IGMP_STR)
+
+DEFUN (debug_igmp_events,
+ debug_igmp_events_cmd,
+ "debug igmp events",
+ DEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_EVENTS_STR)
+{
+ PIM_DO_DEBUG_IGMP_EVENTS;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp_events,
+ no_debug_igmp_events_cmd,
+ "no debug igmp events",
+ NO_STR
+ DEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_EVENTS_STR)
+{
+ PIM_DONT_DEBUG_IGMP_EVENTS;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp_events,
+ undebug_igmp_events_cmd,
+ "undebug igmp events",
+ UNDEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_EVENTS_STR)
+
+DEFUN (debug_igmp_packets,
+ debug_igmp_packets_cmd,
+ "debug igmp packets",
+ DEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_PACKETS_STR)
+{
+ PIM_DO_DEBUG_IGMP_PACKETS;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp_packets,
+ no_debug_igmp_packets_cmd,
+ "no debug igmp packets",
+ NO_STR
+ DEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_PACKETS_STR)
+{
+ PIM_DONT_DEBUG_IGMP_PACKETS;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp_packets,
+ undebug_igmp_packets_cmd,
+ "undebug igmp packets",
+ UNDEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_PACKETS_STR)
+
+DEFUN (debug_igmp_trace,
+ debug_igmp_trace_cmd,
+ "debug igmp trace",
+ DEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_TRACE_STR)
+{
+ PIM_DO_DEBUG_IGMP_TRACE;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_igmp_trace,
+ no_debug_igmp_trace_cmd,
+ "no debug igmp trace",
+ NO_STR
+ DEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_TRACE_STR)
+{
+ PIM_DONT_DEBUG_IGMP_TRACE;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_igmp_trace,
+ undebug_igmp_trace_cmd,
+ "undebug igmp trace",
+ UNDEBUG_STR
+ DEBUG_IGMP_STR
+ DEBUG_IGMP_TRACE_STR)
+
+DEFUN (debug_mroute,
+ debug_mroute_cmd,
+ "debug mroute",
+ DEBUG_STR
+ DEBUG_MROUTE_STR)
+{
+ PIM_DO_DEBUG_MROUTE;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_mroute,
+ no_debug_mroute_cmd,
+ "no debug mroute",
+ NO_STR
+ DEBUG_STR
+ DEBUG_MROUTE_STR)
+{
+ PIM_DONT_DEBUG_MROUTE;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_mroute,
+ undebug_mroute_cmd,
+ "undebug mroute",
+ UNDEBUG_STR
+ DEBUG_MROUTE_STR)
+
+DEFUN (debug_pim,
+ debug_pim_cmd,
+ "debug pim",
+ DEBUG_STR
+ DEBUG_PIM_STR)
+{
+ PIM_DO_DEBUG_PIM_EVENTS;
+ PIM_DO_DEBUG_PIM_PACKETS;
+ PIM_DO_DEBUG_PIM_TRACE;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim,
+ no_debug_pim_cmd,
+ "no debug pim",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR)
+{
+ PIM_DONT_DEBUG_PIM_EVENTS;
+ PIM_DONT_DEBUG_PIM_PACKETS;
+ PIM_DONT_DEBUG_PIM_TRACE;
+
+ PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND;
+ PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV;
+
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim,
+ undebug_pim_cmd,
+ "undebug pim",
+ UNDEBUG_STR
+ DEBUG_PIM_STR)
+
+DEFUN (debug_pim_events,
+ debug_pim_events_cmd,
+ "debug pim events",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_EVENTS_STR)
+{
+ PIM_DO_DEBUG_PIM_EVENTS;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_events,
+ no_debug_pim_events_cmd,
+ "no debug pim events",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_EVENTS_STR)
+{
+ PIM_DONT_DEBUG_PIM_EVENTS;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_events,
+ undebug_pim_events_cmd,
+ "undebug pim events",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_EVENTS_STR)
+
+DEFUN (debug_pim_packets,
+ debug_pim_packets_cmd,
+ "debug pim packets",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETS_STR)
+{
+ PIM_DO_DEBUG_PIM_PACKETS;
+ vty_out (vty, "PIM Packet debugging is on %s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN (debug_pim_packets_filter,
+ debug_pim_packets_filter_cmd,
+ "debug pim packets (hello|joins)",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETS_STR
+ DEBUG_PIM_HELLO_PACKETS_STR
+ DEBUG_PIM_J_P_PACKETS_STR)
+{
+ if (strncmp(argv[0],"h",1) == 0)
+ {
+ PIM_DO_DEBUG_PIM_HELLO;
+ vty_out (vty, "PIM Hello debugging is on %s", VTY_NEWLINE);
+ }
+ else if (strncmp(argv[0],"j",1) == 0)
+ {
+ PIM_DO_DEBUG_PIM_J_P;
+ vty_out (vty, "PIM Join/Prune debugging is on %s", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_packets,
+ no_debug_pim_packets_cmd,
+ "no debug pim packets",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETS_STR
+ DEBUG_PIM_HELLO_PACKETS_STR
+ DEBUG_PIM_J_P_PACKETS_STR)
+{
+ PIM_DONT_DEBUG_PIM_PACKETS;
+ vty_out (vty, "PIM Packet debugging is off %s", VTY_NEWLINE);
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_packets_filter,
+ no_debug_pim_packets_filter_cmd,
+ "no debug pim packets (hello|joins)",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETS_STR
+ DEBUG_PIM_HELLO_PACKETS_STR
+ DEBUG_PIM_J_P_PACKETS_STR)
+{
+ if (strncmp(argv[0],"h",1) == 0)
+ {
+ PIM_DONT_DEBUG_PIM_HELLO;
+ vty_out (vty, "PIM Hello debugging is off %s", VTY_NEWLINE);
+ }
+ else if (strncmp(argv[0],"j",1) == 0)
+ {
+ PIM_DONT_DEBUG_PIM_J_P;
+ vty_out (vty, "PIM Join/Prune debugging is off %s", VTY_NEWLINE);
+ }
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_packets,
+ undebug_pim_packets_cmd,
+ "undebug pim packets",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETS_STR)
+
+DEFUN (debug_pim_packetdump_send,
+ debug_pim_packetdump_send_cmd,
+ "debug pim packet-dump send",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETDUMP_STR
+ DEBUG_PIM_PACKETDUMP_SEND_STR)
+{
+ PIM_DO_DEBUG_PIM_PACKETDUMP_SEND;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_packetdump_send,
+ no_debug_pim_packetdump_send_cmd,
+ "no debug pim packet-dump send",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETDUMP_STR
+ DEBUG_PIM_PACKETDUMP_SEND_STR)
+{
+ PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_packetdump_send,
+ undebug_pim_packetdump_send_cmd,
+ "undebug pim packet-dump send",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETDUMP_STR
+ DEBUG_PIM_PACKETDUMP_SEND_STR)
+
+DEFUN (debug_pim_packetdump_recv,
+ debug_pim_packetdump_recv_cmd,
+ "debug pim packet-dump receive",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETDUMP_STR
+ DEBUG_PIM_PACKETDUMP_RECV_STR)
+{
+ PIM_DO_DEBUG_PIM_PACKETDUMP_RECV;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_packetdump_recv,
+ no_debug_pim_packetdump_recv_cmd,
+ "no debug pim packet-dump receive",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETDUMP_STR
+ DEBUG_PIM_PACKETDUMP_RECV_STR)
+{
+ PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_packetdump_recv,
+ undebug_pim_packetdump_recv_cmd,
+ "undebug pim packet-dump receive",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_PACKETDUMP_STR
+ DEBUG_PIM_PACKETDUMP_RECV_STR)
+
+DEFUN (debug_pim_trace,
+ debug_pim_trace_cmd,
+ "debug pim trace",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_TRACE_STR)
+{
+ PIM_DO_DEBUG_PIM_TRACE;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_trace,
+ no_debug_pim_trace_cmd,
+ "no debug pim trace",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_TRACE_STR)
+{
+ PIM_DONT_DEBUG_PIM_TRACE;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_trace,
+ undebug_pim_trace_cmd,
+ "undebug pim trace",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_TRACE_STR)
+
+DEFUN (debug_ssmpingd,
+ debug_ssmpingd_cmd,
+ "debug ssmpingd",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_SSMPINGD_STR)
+{
+ PIM_DO_DEBUG_SSMPINGD;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_ssmpingd,
+ no_debug_ssmpingd_cmd,
+ "no debug ssmpingd",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_SSMPINGD_STR)
+{
+ PIM_DONT_DEBUG_SSMPINGD;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_ssmpingd,
+ undebug_ssmpingd_cmd,
+ "undebug ssmpingd",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_SSMPINGD_STR)
+
+DEFUN (debug_pim_zebra,
+ debug_pim_zebra_cmd,
+ "debug pim zebra",
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_ZEBRA_STR)
+{
+ PIM_DO_DEBUG_ZEBRA;
+ return CMD_SUCCESS;
+}
+
+DEFUN (no_debug_pim_zebra,
+ no_debug_pim_zebra_cmd,
+ "no debug pim zebra",
+ NO_STR
+ DEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_ZEBRA_STR)
+{
+ PIM_DONT_DEBUG_ZEBRA;
+ return CMD_SUCCESS;
+}
+
+ALIAS (no_debug_pim_zebra,
+ undebug_pim_zebra_cmd,
+ "undebug pim zebra",
+ UNDEBUG_STR
+ DEBUG_PIM_STR
+ DEBUG_PIM_ZEBRA_STR)
+
+DEFUN (show_debugging,
+ show_debugging_cmd,
+ "show debugging",
+ SHOW_STR
+ "State of each debugging option\n")
+{
+ pim_debug_config_write(vty);
+ return CMD_SUCCESS;
+}
+
+static struct igmp_sock *find_igmp_sock_by_fd(int fd)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ struct pim_interface *pim_ifp;
+ struct igmp_sock *igmp;
+
+ if (!ifp->info)
+ continue;
+
+ pim_ifp = ifp->info;
+
+ /* lookup igmp socket under current interface */
+ igmp = igmp_sock_lookup_by_fd(pim_ifp->igmp_socket_list, fd);
+ if (igmp)
+ return igmp;
+ }
+
+ return 0;
+}
+
+DEFUN (test_igmp_receive_report,
+ test_igmp_receive_report_cmd,
+ "test igmp receive report <0-65535> A.B.C.D <1-6> .LINE",
+ "Test\n"
+ "Test IGMP protocol\n"
+ "Test IGMP message\n"
+ "Test IGMP report\n"
+ "Socket\n"
+ "IGMP group address\n"
+ "Record type\n"
+ "Sources\n")
+{
+ char buf[1000];
+ char *igmp_msg;
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ int ip_msg_len;
+ int igmp_msg_len;
+ const char *socket;
+ int socket_fd;
+ const char *grp_str;
+ struct in_addr grp_addr;
+ const char *record_type_str;
+ int record_type;
+ const char *src_str;
+ int result;
+ struct igmp_sock *igmp;
+ char *group_record;
+ int num_sources;
+ struct in_addr *sources;
+ struct in_addr *src_addr;
+ int argi;
+
+ socket = argv[0];
+ socket_fd = atoi(socket);
+ igmp = find_igmp_sock_by_fd(socket_fd);
+ if (!igmp) {
+ vty_out(vty, "Could not find IGMP socket %s: fd=%d%s",
+ socket, socket_fd, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ grp_str = argv[1];
+ result = inet_pton(AF_INET, grp_str, &grp_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+ grp_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ record_type_str = argv[2];
+ record_type = atoi(record_type_str);
+
+ /*
+ Tweak IP header
+ */
+ ip_hdr = (struct ip *) buf;
+ ip_hdr->ip_p = PIM_IP_PROTO_IGMP;
+ ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+ ip_hdr->ip_hl = ip_hlen >> 2; /* ip header length in 4-byte words */
+ ip_hdr->ip_src = igmp->ifaddr;
+ ip_hdr->ip_dst = igmp->ifaddr;
+
+ /*
+ Build IGMP v3 report message
+ */
+ 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_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));
+
+ /* Scan LINE sources */
+ sources = (struct in_addr *) (group_record + IGMP_V3_GROUP_RECORD_SOURCE_OFFSET);
+ src_addr = sources;
+ for (argi = 3; argi < argc; ++argi,++src_addr) {
+ src_str = argv[argi];
+ result = inet_pton(AF_INET, src_str, src_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+ src_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ }
+ num_sources = src_addr - sources;
+
+ *(uint16_t *)(group_record + IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET) = htons(num_sources);
+
+ 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);
+
+ /* "receive" message */
+
+ ip_msg_len = ip_hlen + igmp_msg_len;
+ result = pim_igmp_packet(igmp, buf, ip_msg_len);
+ if (result) {
+ vty_out(vty, "pim_igmp_packet(len=%d) returned: %d%s",
+ ip_msg_len, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int hexval(uint8_t ch)
+{
+ return isdigit(ch) ? (ch - '0') : (10 + tolower(ch) - 'a');
+}
+
+DEFUN (test_pim_receive_dump,
+ test_pim_receive_dump_cmd,
+ "test pim receive dump INTERFACE A.B.C.D .LINE",
+ "Test\n"
+ "Test PIM protocol\n"
+ "Test PIM message reception\n"
+ "Test PIM packet dump reception from neighbor\n"
+ "Interface\n"
+ "Neighbor address\n"
+ "Packet dump\n")
+{
+ uint8_t buf[1000];
+ uint8_t *pim_msg;
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ int ip_msg_len;
+ int pim_msg_size;
+ const char *neigh_str;
+ struct in_addr neigh_addr;
+ const char *ifname;
+ struct interface *ifp;
+ int argi;
+ int result;
+
+ /* Find interface */
+ ifname = argv[0];
+ ifp = if_lookup_by_name(ifname);
+ if (!ifp) {
+ vty_out(vty, "No such interface name %s%s",
+ ifname, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Neighbor address */
+ neigh_str = argv[1];
+ result = inet_pton(AF_INET, neigh_str, &neigh_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad neighbor address %s: errno=%d: %s%s",
+ neigh_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /*
+ Tweak IP header
+ */
+ ip_hdr = (struct ip *) buf;
+ ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+ ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+ ip_hdr->ip_hl = ip_hlen >> 2; /* ip header length in 4-byte words */
+ ip_hdr->ip_src = neigh_addr;
+ ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+ /*
+ Build PIM hello message
+ */
+ pim_msg = buf + ip_hlen;
+ pim_msg_size = 0;
+
+ /* Scan LINE dump into buffer */
+ for (argi = 2; argi < argc; ++argi) {
+ const char *str = argv[argi];
+ int str_len = strlen(str);
+ int str_last = str_len - 1;
+ int i;
+
+ if (str_len % 2) {
+ vty_out(vty, "%% Uneven hex array arg %d=%s%s",
+ argi, str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ for (i = 0; i < str_last; i += 2) {
+ uint8_t octet;
+ int left;
+ uint8_t h1 = str[i];
+ uint8_t h2 = str[i + 1];
+
+ if (!isxdigit(h1) || !isxdigit(h2)) {
+ vty_out(vty, "%% Non-hex octet %c%c at hex array arg %d=%s%s",
+ h1, h2, argi, str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ octet = (hexval(h1) << 4) + hexval(h2);
+
+ left = sizeof(buf) - ip_hlen - pim_msg_size;
+ if (left < 1) {
+ vty_out(vty, "%% Overflow buf_size=%zu buf_left=%d at hex array arg %d=%s octet %02x%s",
+ sizeof(buf), left, argi, str, octet, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pim_msg[pim_msg_size++] = octet;
+ }
+ }
+
+ ip_msg_len = ip_hlen + pim_msg_size;
+
+ vty_out(vty, "Receiving: buf_size=%zu ip_msg_size=%d pim_msg_size=%d%s",
+ sizeof(buf), ip_msg_len, pim_msg_size, VTY_NEWLINE);
+
+ /* "receive" message */
+
+ result = pim_pim_packet(ifp, buf, ip_msg_len);
+ if (result) {
+ vty_out(vty, "%% pim_pim_packet(len=%d) returned failure: %d%s",
+ ip_msg_len, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (test_pim_receive_hello,
+ test_pim_receive_hello_cmd,
+ "test pim receive hello INTERFACE A.B.C.D <0-65535> <0-65535> <0-65535> <0-32767> <0-65535> <0-1>[LINE]",
+ "Test\n"
+ "Test PIM protocol\n"
+ "Test PIM message reception\n"
+ "Test PIM hello reception from neighbor\n"
+ "Interface\n"
+ "Neighbor address\n"
+ "Neighbor holdtime\n"
+ "Neighbor DR priority\n"
+ "Neighbor generation ID\n"
+ "Neighbor propagation delay (msec)\n"
+ "Neighbor override interval (msec)\n"
+ "Neighbor LAN prune delay T-bit\n"
+ "Neighbor secondary addresses\n")
+{
+ uint8_t buf[1000];
+ uint8_t *pim_msg;
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ int ip_msg_len;
+ int pim_tlv_size;
+ int pim_msg_size;
+ const char *neigh_str;
+ struct in_addr neigh_addr;
+ const char *ifname;
+ struct interface *ifp;
+ uint16_t neigh_holdtime;
+ uint16_t neigh_propagation_delay;
+ uint16_t neigh_override_interval;
+ int neigh_can_disable_join_suppression;
+ uint32_t neigh_dr_priority;
+ uint32_t neigh_generation_id;
+ int argi;
+ int result;
+
+ /* Find interface */
+ ifname = argv[0];
+ ifp = if_lookup_by_name(ifname);
+ if (!ifp) {
+ vty_out(vty, "No such interface name %s%s",
+ ifname, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Neighbor address */
+ neigh_str = argv[1];
+ result = inet_pton(AF_INET, neigh_str, &neigh_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad neighbor address %s: errno=%d: %s%s",
+ neigh_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ neigh_holdtime = atoi(argv[2]);
+ neigh_dr_priority = atoi(argv[3]);
+ neigh_generation_id = atoi(argv[4]);
+ neigh_propagation_delay = atoi(argv[5]);
+ neigh_override_interval = atoi(argv[6]);
+ neigh_can_disable_join_suppression = atoi(argv[7]);
+
+ /*
+ Tweak IP header
+ */
+ ip_hdr = (struct ip *) buf;
+ ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+ ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+ ip_hdr->ip_hl = ip_hlen >> 2; /* ip header length in 4-byte words */
+ ip_hdr->ip_src = neigh_addr;
+ ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+ /*
+ Build PIM hello message
+ */
+ pim_msg = buf + ip_hlen;
+
+ /* Scan LINE addresses */
+ for (argi = 8; argi < argc; ++argi) {
+ const char *sec_str = argv[argi];
+ struct in_addr sec_addr;
+ result = inet_pton(AF_INET, sec_str, &sec_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad neighbor secondary address %s: errno=%d: %s%s",
+ sec_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ vty_out(vty,
+ "FIXME WRITEME consider neighbor secondary address %s%s",
+ sec_str, VTY_NEWLINE);
+ }
+
+ pim_tlv_size = pim_hello_build_tlv(ifp->name,
+ pim_msg + PIM_PIM_MIN_LEN,
+ sizeof(buf) - ip_hlen - PIM_PIM_MIN_LEN,
+ neigh_holdtime,
+ neigh_dr_priority,
+ neigh_generation_id,
+ neigh_propagation_delay,
+ neigh_override_interval,
+ neigh_can_disable_join_suppression,
+ 0 /* FIXME secondary address list */);
+ if (pim_tlv_size < 0) {
+ vty_out(vty, "pim_hello_build_tlv() returned failure: %d%s",
+ pim_tlv_size, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ pim_msg_size = pim_tlv_size + PIM_PIM_MIN_LEN;
+
+ pim_msg_build_header(pim_msg, pim_msg_size,
+ PIM_MSG_TYPE_HELLO);
+
+ /* "receive" message */
+
+ ip_msg_len = ip_hlen + pim_msg_size;
+ result = pim_pim_packet(ifp, buf, ip_msg_len);
+ if (result) {
+ vty_out(vty, "pim_pim_packet(len=%d) returned failure: %d%s",
+ ip_msg_len, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (test_pim_receive_assert,
+ test_pim_receive_assert_cmd,
+ "test pim receive assert INTERFACE A.B.C.D A.B.C.D A.B.C.D <0-65535> <0-65535> <0-1>",
+ "Test\n"
+ "Test PIM protocol\n"
+ "Test PIM message reception\n"
+ "Test reception of PIM assert\n"
+ "Interface\n"
+ "Neighbor address\n"
+ "Assert multicast group address\n"
+ "Assert unicast source address\n"
+ "Assert metric preference\n"
+ "Assert route metric\n"
+ "Assert RPT bit flag\n")
+{
+ uint8_t buf[1000];
+ uint8_t *buf_pastend = buf + sizeof(buf);
+ uint8_t *pim_msg;
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ int ip_msg_len;
+ int pim_msg_size;
+ const char *neigh_str;
+ struct in_addr neigh_addr;
+ const char *group_str;
+ struct in_addr group_addr;
+ const char *source_str;
+ struct in_addr source_addr;
+ const char *ifname;
+ struct interface *ifp;
+ uint32_t assert_metric_preference;
+ uint32_t assert_route_metric;
+ uint32_t assert_rpt_bit_flag;
+ int remain;
+ int result;
+
+ /* Find interface */
+ ifname = argv[0];
+ ifp = if_lookup_by_name(ifname);
+ if (!ifp) {
+ vty_out(vty, "No such interface name %s%s",
+ ifname, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Neighbor address */
+ neigh_str = argv[1];
+ result = inet_pton(AF_INET, neigh_str, &neigh_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad neighbor address %s: errno=%d: %s%s",
+ neigh_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Group address */
+ group_str = argv[2];
+ result = inet_pton(AF_INET, group_str, &group_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+ group_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Source address */
+ source_str = argv[3];
+ result = inet_pton(AF_INET, source_str, &source_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ assert_metric_preference = atoi(argv[4]);
+ assert_route_metric = atoi(argv[5]);
+ assert_rpt_bit_flag = atoi(argv[6]);
+
+ remain = buf_pastend - buf;
+ if (remain < (int) sizeof(struct ip)) {
+ vty_out(vty, "No room for ip header: buf_size=%d < ip_header_size=%zu%s",
+ remain, sizeof(struct ip), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /*
+ Tweak IP header
+ */
+ ip_hdr = (struct ip *) buf;
+ ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+ ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+ ip_hdr->ip_hl = ip_hlen >> 2; /* ip header length in 4-byte words */
+ ip_hdr->ip_src = neigh_addr;
+ ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+ /*
+ Build PIM assert message
+ */
+ pim_msg = buf + ip_hlen; /* skip ip header */
+
+ pim_msg_size = pim_assert_build_msg(pim_msg, buf_pastend - pim_msg, ifp,
+ group_addr, source_addr,
+ assert_metric_preference,
+ assert_route_metric,
+ assert_rpt_bit_flag);
+ if (pim_msg_size < 0) {
+ vty_out(vty, "Failure building PIM assert message: size=%d%s",
+ pim_msg_size, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* "receive" message */
+
+ ip_msg_len = ip_hlen + pim_msg_size;
+ result = pim_pim_packet(ifp, buf, ip_msg_len);
+ if (result) {
+ vty_out(vty, "pim_pim_packet(len=%d) returned failure: %d%s",
+ ip_msg_len, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+static int recv_joinprune(struct vty *vty,
+ const char *argv[],
+ int src_is_join)
+{
+ uint8_t buf[1000];
+ const uint8_t *buf_pastend = buf + sizeof(buf);
+ uint8_t *pim_msg;
+ uint8_t *pim_msg_curr;
+ int pim_msg_size;
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ int ip_msg_len;
+ uint16_t neigh_holdtime;
+ const char *neigh_dst_str;
+ struct in_addr neigh_dst_addr;
+ const char *neigh_src_str;
+ struct in_addr neigh_src_addr;
+ const char *group_str;
+ struct in_addr group_addr;
+ const char *source_str;
+ struct in_addr source_addr;
+ const char *ifname;
+ struct interface *ifp;
+ int result;
+ int remain;
+ uint16_t num_joined;
+ uint16_t num_pruned;
+
+ /* Find interface */
+ ifname = argv[0];
+ ifp = if_lookup_by_name(ifname);
+ if (!ifp) {
+ vty_out(vty, "No such interface name %s%s",
+ ifname, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ neigh_holdtime = atoi(argv[1]);
+
+ /* Neighbor destination address */
+ neigh_dst_str = argv[2];
+ result = inet_pton(AF_INET, neigh_dst_str, &neigh_dst_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad neighbor destination address %s: errno=%d: %s%s",
+ neigh_dst_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Neighbor source address */
+ neigh_src_str = argv[3];
+ result = inet_pton(AF_INET, neigh_src_str, &neigh_src_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad neighbor source address %s: errno=%d: %s%s",
+ neigh_src_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Multicast group address */
+ group_str = argv[4];
+ result = inet_pton(AF_INET, group_str, &group_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+ group_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Multicast source address */
+ source_str = argv[5];
+ result = inet_pton(AF_INET, source_str, &source_addr);
+ if (result <= 0) {
+ vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /*
+ Tweak IP header
+ */
+ ip_hdr = (struct ip *) buf;
+ ip_hdr->ip_p = PIM_IP_PROTO_PIM;
+ ip_hlen = PIM_IP_HEADER_MIN_LEN; /* ip header length in bytes */
+ ip_hdr->ip_hl = ip_hlen >> 2; /* ip header length in 4-byte words */
+ ip_hdr->ip_src = neigh_src_addr;
+ ip_hdr->ip_dst = qpim_all_pim_routers_addr;
+
+ /*
+ Build PIM message
+ */
+ pim_msg = buf + ip_hlen;
+
+ /* skip room for pim header */
+ pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN;
+
+ remain = buf_pastend - pim_msg_curr;
+ pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr,
+ remain,
+ neigh_dst_addr);
+ if (!pim_msg_curr) {
+ vty_out(vty, "Failure encoding destination address %s: space left=%d%s",
+ neigh_dst_str, remain, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ remain = buf_pastend - pim_msg_curr;
+ if (remain < 4) {
+ vty_out(vty, "Group will not fit: space left=%d%s",
+ remain, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ *pim_msg_curr = 0; /* reserved */
+ ++pim_msg_curr;
+ *pim_msg_curr = 1; /* number of groups */
+ ++pim_msg_curr;
+ *((uint16_t *) pim_msg_curr) = htons(neigh_holdtime);
+ ++pim_msg_curr;
+ ++pim_msg_curr;
+
+ remain = buf_pastend - pim_msg_curr;
+ pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr,
+ remain,
+ group_addr);
+ if (!pim_msg_curr) {
+ vty_out(vty, "Failure encoding group address %s: space left=%d%s",
+ group_str, remain, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ remain = buf_pastend - pim_msg_curr;
+ if (remain < 4) {
+ vty_out(vty, "Sources will not fit: space left=%d%s",
+ remain, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ if (src_is_join) {
+ num_joined = 1;
+ num_pruned = 0;
+ }
+ else {
+ num_joined = 0;
+ num_pruned = 1;
+ }
+
+ /* number of joined sources */
+ *((uint16_t *) pim_msg_curr) = htons(num_joined);
+ ++pim_msg_curr;
+ ++pim_msg_curr;
+
+ /* number of pruned sources */
+ *((uint16_t *) pim_msg_curr) = htons(num_pruned);
+ ++pim_msg_curr;
+ ++pim_msg_curr;
+
+ remain = buf_pastend - pim_msg_curr;
+ pim_msg_curr = pim_msg_addr_encode_ipv4_source(pim_msg_curr,
+ remain,
+ source_addr);
+ if (!pim_msg_curr) {
+ vty_out(vty, "Failure encoding source address %s: space left=%d%s",
+ source_str, remain, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* 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);
+
+ /*
+ "Receive" message
+ */
+
+ ip_msg_len = ip_hlen + pim_msg_size;
+ result = pim_pim_packet(ifp, buf, ip_msg_len);
+ if (result) {
+ vty_out(vty, "pim_pim_packet(len=%d) returned failure: %d%s",
+ ip_msg_len, result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+DEFUN (test_pim_receive_join,
+ test_pim_receive_join_cmd,
+ "test pim receive join INTERFACE <0-65535> A.B.C.D A.B.C.D A.B.C.D A.B.C.D",
+ "Test\n"
+ "Test PIM protocol\n"
+ "Test PIM message reception\n"
+ "Test PIM join reception from neighbor\n"
+ "Interface\n"
+ "Neighbor holdtime\n"
+ "Upstream neighbor unicast destination address\n"
+ "Downstream neighbor unicast source address\n"
+ "Multicast group address\n"
+ "Unicast source address\n")
+{
+ return recv_joinprune(vty, argv, 1 /* src_is_join=true */);
+}
+
+DEFUN (test_pim_receive_prune,
+ test_pim_receive_prune_cmd,
+ "test pim receive prune INTERFACE <0-65535> A.B.C.D A.B.C.D A.B.C.D A.B.C.D",
+ "Test\n"
+ "Test PIM protocol\n"
+ "Test PIM message reception\n"
+ "Test PIM prune reception from neighbor\n"
+ "Interface\n"
+ "Neighbor holdtime\n"
+ "Upstream neighbor unicast destination address\n"
+ "Downstream neighbor unicast source address\n"
+ "Multicast group address\n"
+ "Unicast source address\n")
+{
+ return recv_joinprune(vty, argv, 0 /* src_is_join=false */);
+}
+
+DEFUN (test_pim_receive_upcall,
+ test_pim_receive_upcall_cmd,
+ "test pim receive upcall (nocache|wrongvif|wholepkt) <0-65535> A.B.C.D A.B.C.D",
+ "Test\n"
+ "Test PIM protocol\n"
+ "Test PIM message reception\n"
+ "Test reception of kernel upcall\n"
+ "NOCACHE kernel upcall\n"
+ "WRONGVIF kernel upcall\n"
+ "WHOLEPKT kernel upcall\n"
+ "Input interface vif index\n"
+ "Multicast group address\n"
+ "Multicast source address\n")
+{
+ struct igmpmsg msg;
+ const char *upcall_type;
+ const char *group_str;
+ const char *source_str;
+ int result;
+
+ upcall_type = argv[0];
+
+ if (upcall_type[0] == 'n')
+ msg.im_msgtype = IGMPMSG_NOCACHE;
+ else if (upcall_type[1] == 'r')
+ msg.im_msgtype = IGMPMSG_WRONGVIF;
+ else if (upcall_type[1] == 'h')
+ msg.im_msgtype = IGMPMSG_WHOLEPKT;
+ else {
+ vty_out(vty, "Unknown kernel upcall type: %s%s",
+ upcall_type, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ msg.im_vif = atoi(argv[1]);
+
+ /* Group address */
+ group_str = argv[2];
+ result = inet_pton(AF_INET, group_str, &msg.im_dst);
+ if (result <= 0) {
+ vty_out(vty, "Bad group address %s: errno=%d: %s%s",
+ group_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ /* Source address */
+ source_str = argv[3];
+ result = inet_pton(AF_INET, source_str, &msg.im_src);
+ if (result <= 0) {
+ vty_out(vty, "Bad source address %s: errno=%d: %s%s",
+ source_str, errno, safe_strerror(errno), VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ msg.im_mbz = 0; /* Must be zero */
+
+ result = pim_mroute_msg(-1, (char *) &msg, sizeof(msg));
+ if (result) {
+ vty_out(vty, "pim_mroute_msg(len=%zu) returned failure: %d%s",
+ sizeof(msg), result, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+
+ return CMD_SUCCESS;
+}
+
+void pim_cmd_init()
+{
+ install_node (&pim_global_node, pim_global_config_write); /* PIM_NODE */
+ install_node (&interface_node, pim_interface_config_write); /* INTERFACE_NODE */
+
+ install_element (CONFIG_NODE, &ip_multicast_routing_cmd);
+ install_element (CONFIG_NODE, &no_ip_multicast_routing_cmd);
+ install_element (CONFIG_NODE, &ip_ssmpingd_cmd);
+ install_element (CONFIG_NODE, &no_ip_ssmpingd_cmd);
+#if 0
+ install_element (CONFIG_NODE, &interface_cmd); /* from if.h */
+#else
+ install_element (CONFIG_NODE, &pim_interface_cmd);
+#endif
+ install_element (CONFIG_NODE, &no_interface_cmd); /* from if.h */
+
+ install_default (INTERFACE_NODE);
+ install_element (INTERFACE_NODE, &interface_ip_igmp_cmd);
+ 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_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);
+ install_element (INTERFACE_NODE, &interface_no_ip_igmp_query_max_response_time_cmd);
+ install_element (INTERFACE_NODE, &interface_ip_igmp_query_max_response_time_dsec_cmd);
+ install_element (INTERFACE_NODE, &interface_no_ip_igmp_query_max_response_time_dsec_cmd);
+ install_element (INTERFACE_NODE, &interface_ip_pim_ssm_cmd);
+ install_element (INTERFACE_NODE, &interface_no_ip_pim_ssm_cmd);
+
+ install_element (VIEW_NODE, &show_ip_igmp_interface_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_join_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_parameters_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_groups_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_groups_retransmissions_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_sources_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_sources_retransmissions_cmd);
+ install_element (VIEW_NODE, &show_ip_igmp_querier_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_assert_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_assert_internal_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_assert_metric_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_assert_winner_metric_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_dr_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_hello_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_interface_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_join_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_jp_override_interval_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_lan_prune_delay_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_local_membership_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_neighbor_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_rpf_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_secondary_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_upstream_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_upstream_join_desired_cmd);
+ install_element (VIEW_NODE, &show_ip_pim_upstream_rpf_cmd);
+ install_element (VIEW_NODE, &show_ip_multicast_cmd);
+ install_element (VIEW_NODE, &show_ip_mroute_cmd);
+ install_element (VIEW_NODE, &show_ip_mroute_count_cmd);
+ install_element (VIEW_NODE, &show_ip_rib_cmd);
+ install_element (VIEW_NODE, &show_ip_ssmpingd_cmd);
+ install_element (VIEW_NODE, &show_debugging_cmd);
+
+ install_element (ENABLE_NODE, &clear_ip_interfaces_cmd);
+ install_element (ENABLE_NODE, &clear_ip_igmp_interfaces_cmd);
+ install_element (ENABLE_NODE, &clear_ip_mroute_cmd);
+ install_element (ENABLE_NODE, &clear_ip_pim_interfaces_cmd);
+ install_element (ENABLE_NODE, &clear_ip_pim_oil_cmd);
+
+ install_element (ENABLE_NODE, &show_ip_igmp_interface_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_join_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_parameters_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_groups_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_groups_retransmissions_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_sources_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_sources_retransmissions_cmd);
+ install_element (ENABLE_NODE, &show_ip_igmp_querier_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_address_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_assert_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_assert_internal_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_assert_metric_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_assert_winner_metric_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_dr_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_hello_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_interface_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_join_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_jp_override_interval_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_lan_prune_delay_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_local_membership_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_neighbor_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_rpf_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_secondary_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_upstream_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_upstream_join_desired_cmd);
+ install_element (ENABLE_NODE, &show_ip_pim_upstream_rpf_cmd);
+ install_element (ENABLE_NODE, &show_ip_multicast_cmd);
+ install_element (ENABLE_NODE, &show_ip_mroute_cmd);
+ install_element (ENABLE_NODE, &show_ip_mroute_count_cmd);
+ install_element (ENABLE_NODE, &show_ip_rib_cmd);
+ install_element (ENABLE_NODE, &show_ip_ssmpingd_cmd);
+ install_element (ENABLE_NODE, &show_debugging_cmd);
+
+ install_element (ENABLE_NODE, &test_igmp_receive_report_cmd);
+ install_element (ENABLE_NODE, &test_pim_receive_assert_cmd);
+ install_element (ENABLE_NODE, &test_pim_receive_dump_cmd);
+ install_element (ENABLE_NODE, &test_pim_receive_hello_cmd);
+ install_element (ENABLE_NODE, &test_pim_receive_join_cmd);
+ install_element (ENABLE_NODE, &test_pim_receive_prune_cmd);
+ install_element (ENABLE_NODE, &test_pim_receive_upcall_cmd);
+
+ install_element (ENABLE_NODE, &debug_igmp_cmd);
+ install_element (ENABLE_NODE, &no_debug_igmp_cmd);
+ install_element (ENABLE_NODE, &undebug_igmp_cmd);
+ install_element (ENABLE_NODE, &debug_igmp_events_cmd);
+ install_element (ENABLE_NODE, &no_debug_igmp_events_cmd);
+ install_element (ENABLE_NODE, &undebug_igmp_events_cmd);
+ install_element (ENABLE_NODE, &debug_igmp_packets_cmd);
+ install_element (ENABLE_NODE, &no_debug_igmp_packets_cmd);
+ install_element (ENABLE_NODE, &undebug_igmp_packets_cmd);
+ install_element (ENABLE_NODE, &debug_igmp_trace_cmd);
+ install_element (ENABLE_NODE, &no_debug_igmp_trace_cmd);
+ install_element (ENABLE_NODE, &undebug_igmp_trace_cmd);
+ install_element (ENABLE_NODE, &debug_mroute_cmd);
+ install_element (ENABLE_NODE, &no_debug_mroute_cmd);
+ install_element (ENABLE_NODE, &debug_pim_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_cmd);
+ install_element (ENABLE_NODE, &debug_pim_events_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_events_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_events_cmd);
+ install_element (ENABLE_NODE, &debug_pim_packets_cmd);
+ install_element (ENABLE_NODE, &debug_pim_packets_filter_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_packets_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_packets_filter_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_packets_cmd);
+ install_element (ENABLE_NODE, &debug_pim_packetdump_send_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_packetdump_send_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_packetdump_send_cmd);
+ install_element (ENABLE_NODE, &debug_pim_packetdump_recv_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_packetdump_recv_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_packetdump_recv_cmd);
+ install_element (ENABLE_NODE, &debug_pim_trace_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_trace_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_trace_cmd);
+ install_element (ENABLE_NODE, &debug_ssmpingd_cmd);
+ install_element (ENABLE_NODE, &no_debug_ssmpingd_cmd);
+ install_element (ENABLE_NODE, &undebug_ssmpingd_cmd);
+ install_element (ENABLE_NODE, &debug_pim_zebra_cmd);
+ install_element (ENABLE_NODE, &no_debug_pim_zebra_cmd);
+ install_element (ENABLE_NODE, &undebug_pim_zebra_cmd);
+
+ install_element (CONFIG_NODE, &debug_igmp_cmd);
+ install_element (CONFIG_NODE, &no_debug_igmp_cmd);
+ install_element (CONFIG_NODE, &undebug_igmp_cmd);
+ install_element (CONFIG_NODE, &debug_igmp_events_cmd);
+ install_element (CONFIG_NODE, &no_debug_igmp_events_cmd);
+ install_element (CONFIG_NODE, &undebug_igmp_events_cmd);
+ install_element (CONFIG_NODE, &debug_igmp_packets_cmd);
+ install_element (CONFIG_NODE, &no_debug_igmp_packets_cmd);
+ install_element (CONFIG_NODE, &undebug_igmp_packets_cmd);
+ install_element (CONFIG_NODE, &debug_igmp_trace_cmd);
+ install_element (CONFIG_NODE, &no_debug_igmp_trace_cmd);
+ install_element (CONFIG_NODE, &undebug_igmp_trace_cmd);
+ install_element (CONFIG_NODE, &debug_mroute_cmd);
+ install_element (CONFIG_NODE, &no_debug_mroute_cmd);
+ install_element (CONFIG_NODE, &debug_pim_cmd);
+ install_element (CONFIG_NODE, &no_debug_pim_cmd);
+ install_element (CONFIG_NODE, &undebug_pim_cmd);
+ install_element (CONFIG_NODE, &debug_pim_events_cmd);
+ install_element (CONFIG_NODE, &no_debug_pim_events_cmd);
+ install_element (CONFIG_NODE, &undebug_pim_events_cmd);
+ install_element (CONFIG_NODE, &debug_pim_packets_cmd);
+ install_element (CONFIG_NODE, &debug_pim_packets_filter_cmd);
+ install_element (CONFIG_NODE, &no_debug_pim_packets_cmd);
+ install_element (CONFIG_NODE, &no_debug_pim_packets_filter_cmd);
+ install_element (CONFIG_NODE, &undebug_pim_packets_cmd);
+ install_element (CONFIG_NODE, &debug_pim_trace_cmd);
+ install_element (CONFIG_NODE, &no_debug_pim_trace_cmd);
+ install_element (CONFIG_NODE, &undebug_pim_trace_cmd);
+ install_element (CONFIG_NODE, &debug_ssmpingd_cmd);
+ install_element (CONFIG_NODE, &no_debug_ssmpingd_cmd);
+ install_element (CONFIG_NODE, &undebug_ssmpingd_cmd);
+ install_element (CONFIG_NODE, &debug_pim_zebra_cmd);
+ install_element (CONFIG_NODE, &no_debug_pim_zebra_cmd);
+ install_element (CONFIG_NODE, &undebug_pim_zebra_cmd);
+}
diff --git a/pimd/pim_cmd.h b/pimd/pim_cmd.h
new file mode 100644
index 00000000..c5037400
--- /dev/null
+++ b/pimd/pim_cmd.h
@@ -0,0 +1,66 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_CMD_H
+#define PIM_CMD_H
+
+#define PIM_STR "PIM information\n"
+#define IGMP_STR "IGMP information\n"
+#define IGMP_GROUP_STR "IGMP groups information\n"
+#define IGMP_SOURCE_STR "IGMP sources information\n"
+#define CONF_SSMPINGD_STR "Enable ssmpingd operation\n"
+#define SHOW_SSMPINGD_STR "ssmpingd operation\n"
+#define IFACE_PIM_STR "Enable PIM SSM operation\n"
+#define IFACE_IGMP_STR "Enable IGMP operation\n"
+#define IFACE_IGMP_QUERY_INTERVAL_STR "IGMP host query interval\n"
+#define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_STR "IGMP max query response value (seconds)\n"
+#define IFACE_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC_STR "IGMP max query response value (deciseconds)\n"
+#define DEBUG_IGMP_STR "IGMP protocol activity\n"
+#define DEBUG_IGMP_EVENTS_STR "IGMP protocol events\n"
+#define DEBUG_IGMP_PACKETS_STR "IGMP protocol packets\n"
+#define DEBUG_IGMP_TRACE_STR "IGMP internal daemon activity\n"
+#define DEBUG_MROUTE_STR "PIM interaction with kernel MFC cache\n"
+#define DEBUG_PIM_STR "PIM protocol activity\n"
+#define DEBUG_PIM_EVENTS_STR "PIM protocol events\n"
+#define DEBUG_PIM_PACKETS_STR "PIM protocol packets\n"
+#define DEBUG_PIM_HELLO_PACKETS_STR "PIM Hello protocol packets\n"
+#define DEBUG_PIM_J_P_PACKETS_STR "PIM Join/Prune protocol packets\n"
+#define DEBUG_PIM_PACKETDUMP_STR "PIM packet dump\n"
+#define DEBUG_PIM_PACKETDUMP_SEND_STR "Dump sent packets\n"
+#define DEBUG_PIM_PACKETDUMP_RECV_STR "Dump received packets\n"
+#define DEBUG_PIM_TRACE_STR "PIM internal daemon activity\n"
+#define DEBUG_PIM_ZEBRA_STR "ZEBRA protocol activity\n"
+#define DEBUG_SSMPINGD_STR "ssmpingd activity\n"
+#define CLEAR_IP_IGMP_STR "IGMP clear commands\n"
+#define CLEAR_IP_PIM_STR "PIM clear commands\n"
+#define MROUTE_STR "IP multicast routing table\n"
+#define RIB_STR "IP unicast routing table\n"
+
+#define PIM_CMD_NO "no"
+#define PIM_CMD_IP_MULTICAST_ROUTING "ip multicast-routing"
+#define PIM_CMD_IP_IGMP_QUERY_INTERVAL "ip igmp query-interval"
+#define PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME "ip igmp query-max-response-time"
+#define PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC "ip igmp query-max-response-time-dsec"
+
+void pim_cmd_init(void);
+
+#endif /* PIM_CMD_H */
diff --git a/pimd/pim_hello.c b/pimd/pim_hello.c
new file mode 100644
index 00000000..12857831
--- /dev/null
+++ b/pimd/pim_hello.c
@@ -0,0 +1,529 @@
+/*
+ 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_pim.h"
+#include "pim_str.h"
+#include "pim_tlv.h"
+#include "pim_util.h"
+#include "pim_hello.h"
+#include "pim_iface.h"
+#include "pim_neighbor.h"
+#include "pim_upstream.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, src_str, sizeof(src_str));
+ zlog_debug("%s: from %s on %s",
+ label, src_str, ifp->name);
+ }
+}
+
+static void tlv_trace_bool(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int isset, int value)
+{
+ if (isset) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello option from %s on interface %s: %s=%d",
+ label,
+ src_str, ifname,
+ tlv_name, value);
+ }
+}
+
+static void tlv_trace_uint16(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int isset, uint16_t value)
+{
+ if (isset) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello option from %s on interface %s: %s=%u",
+ label,
+ src_str, ifname,
+ tlv_name, value);
+ }
+}
+
+static void tlv_trace_uint32(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int isset, uint32_t value)
+{
+ if (isset) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello option from %s on interface %s: %s=%u",
+ label,
+ src_str, ifname,
+ tlv_name, value);
+ }
+}
+
+static void tlv_trace_uint32_hex(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int isset, uint32_t value)
+{
+ if (isset) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello option from %s on interface %s: %s=%08x",
+ label,
+ src_str, ifname,
+ tlv_name, value);
+ }
+}
+
+#if 0
+static void tlv_trace(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int isset)
+{
+ if (isset) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello option from %s on interface %s: %s",
+ label,
+ src_str, ifname,
+ tlv_name);
+ }
+}
+#endif
+
+static void tlv_trace_list(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int isset, struct list *addr_list)
+{
+ if (isset) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello option from %s on interface %s: %s size=%d list=%p",
+ label,
+ src_str, ifname,
+ tlv_name,
+ addr_list ? ((int) listcount(addr_list)) : -1,
+ (void *) addr_list);
+ }
+}
+
+#define FREE_ADDR_LIST \
+ if (hello_option_addr_list) { \
+ list_delete(hello_option_addr_list); \
+ }
+
+#define FREE_ADDR_LIST_THEN_RETURN(code) \
+{ \
+ FREE_ADDR_LIST \
+ return (code); \
+}
+
+int pim_hello_recv(struct interface *ifp,
+ struct in_addr src_addr,
+ uint8_t *tlv_buf, int tlv_buf_size)
+{
+ struct pim_interface *pim_ifp;
+ struct pim_neighbor *neigh;
+ uint8_t *tlv_curr;
+ uint8_t *tlv_pastend;
+ pim_hello_options hello_options = 0; /* bit array recording options found */
+ uint16_t hello_option_holdtime = 0;
+ uint16_t hello_option_propagation_delay = 0;
+ uint16_t hello_option_override_interval = 0;
+ uint32_t hello_option_dr_priority = 0;
+ uint32_t hello_option_generation_id = 0;
+ struct list *hello_option_addr_list = 0;
+
+ on_trace(__PRETTY_FUNCTION__, ifp, src_addr);
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ ++pim_ifp->pim_ifstat_hello_recv;
+
+ /*
+ Parse PIM hello TLVs
+ */
+ zassert(tlv_buf_size >= 0);
+ tlv_curr = tlv_buf;
+ tlv_pastend = tlv_buf + tlv_buf_size;
+
+ while (tlv_curr < tlv_pastend) {
+ uint16_t option_type;
+ uint16_t option_len;
+ int remain = tlv_pastend - tlv_curr;
+
+ if (remain < PIM_TLV_MIN_SIZE) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: short PIM hello TLV size=%d < min=%d from %s on interface %s",
+ __PRETTY_FUNCTION__,
+ remain, PIM_TLV_MIN_SIZE,
+ src_str, ifp->name);
+ FREE_ADDR_LIST_THEN_RETURN(-1);
+ }
+
+ option_type = PIM_TLV_GET_TYPE(tlv_curr);
+ tlv_curr += PIM_TLV_TYPE_SIZE;
+ option_len = PIM_TLV_GET_LENGTH(tlv_curr);
+ tlv_curr += PIM_TLV_LENGTH_SIZE;
+
+ if ((tlv_curr + option_len) > tlv_pastend) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: long PIM hello TLV type=%d length=%d > left=%td from %s on interface %s",
+ __PRETTY_FUNCTION__,
+ option_type, option_len, tlv_pastend - tlv_curr,
+ src_str, ifp->name);
+ FREE_ADDR_LIST_THEN_RETURN(-2);
+ }
+
+ if (PIM_DEBUG_PIM_TRACE || PIM_DEBUG_PIM_HELLO) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: parse left_size=%d: PIM hello TLV type=%d length=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ remain,
+ option_type, option_len,
+ src_str, ifp->name);
+ }
+
+ switch (option_type) {
+ case PIM_MSG_OPTION_TYPE_HOLDTIME:
+ if (pim_tlv_parse_holdtime(ifp->name, src_addr,
+ &hello_options,
+ &hello_option_holdtime,
+ option_len,
+ tlv_curr)) {
+ FREE_ADDR_LIST_THEN_RETURN(-3);
+ }
+ break;
+ case PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY:
+ if (pim_tlv_parse_lan_prune_delay(ifp->name, src_addr,
+ &hello_options,
+ &hello_option_propagation_delay,
+ &hello_option_override_interval,
+ option_len,
+ tlv_curr)) {
+ FREE_ADDR_LIST_THEN_RETURN(-4);
+ }
+ break;
+ case PIM_MSG_OPTION_TYPE_DR_PRIORITY:
+ if (pim_tlv_parse_dr_priority(ifp->name, src_addr,
+ &hello_options,
+ &hello_option_dr_priority,
+ option_len,
+ tlv_curr)) {
+ FREE_ADDR_LIST_THEN_RETURN(-5);
+ }
+ break;
+ case PIM_MSG_OPTION_TYPE_GENERATION_ID:
+ if (pim_tlv_parse_generation_id(ifp->name, src_addr,
+ &hello_options,
+ &hello_option_generation_id,
+ option_len,
+ tlv_curr)) {
+ FREE_ADDR_LIST_THEN_RETURN(-6);
+ }
+ break;
+ case PIM_MSG_OPTION_TYPE_ADDRESS_LIST:
+ if (pim_tlv_parse_addr_list(ifp->name, src_addr,
+ &hello_options,
+ &hello_option_addr_list,
+ option_len,
+ tlv_curr)) {
+ return -7;
+ }
+ break;
+ case PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH:
+ if (PIM_DEBUG_PIM_TRACE || PIM_DEBUG_PIM_HELLO) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: ignoring PIM hello dense-mode state refresh TLV option type=%d length=%d from %s on interface %s",
+ __PRETTY_FUNCTION__,
+ option_type, option_len,
+ src_str, ifp->name);
+ }
+ break;
+ default:
+ {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: ignoring unknown PIM hello TLV type=%d length=%d from %s on interface %s",
+ __PRETTY_FUNCTION__,
+ option_type, option_len,
+ src_str, ifp->name);
+ }
+ }
+
+ tlv_curr += option_len;
+ }
+
+ /*
+ Check received PIM hello options
+ */
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ tlv_trace_uint16(__PRETTY_FUNCTION__, "holdtime",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME),
+ hello_option_holdtime);
+ tlv_trace_uint16(__PRETTY_FUNCTION__, "propagation_delay",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY),
+ hello_option_propagation_delay);
+ tlv_trace_uint16(__PRETTY_FUNCTION__, "override_interval",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY),
+ hello_option_override_interval);
+ tlv_trace_bool(__PRETTY_FUNCTION__, "can_disable_join_suppression",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY),
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION));
+ tlv_trace_uint32(__PRETTY_FUNCTION__, "dr_priority",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_DR_PRIORITY),
+ hello_option_dr_priority);
+ tlv_trace_uint32_hex(__PRETTY_FUNCTION__, "generation_id",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID),
+ hello_option_generation_id);
+ tlv_trace_list(__PRETTY_FUNCTION__, "address_list",
+ ifp->name, src_addr,
+ PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_ADDRESS_LIST),
+ hello_option_addr_list);
+ }
+
+ if (!PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: PIM hello missing holdtime from %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifp->name);
+ }
+
+ /*
+ New neighbor?
+ */
+
+ neigh = pim_neighbor_find(ifp, src_addr);
+ if (!neigh) {
+ /* Add as new neighbor */
+
+ neigh = pim_neighbor_add(ifp, src_addr,
+ hello_options,
+ hello_option_holdtime,
+ hello_option_propagation_delay,
+ hello_option_override_interval,
+ hello_option_dr_priority,
+ hello_option_generation_id,
+ hello_option_addr_list);
+ if (!neigh) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: failure creating PIM neighbor %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifp->name);
+ FREE_ADDR_LIST_THEN_RETURN(-8);
+ }
+
+ /* actual addr list has been saved under neighbor */
+ return 0;
+ }
+
+ /*
+ Received generation ID ?
+ */
+
+ if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_GENERATION_ID)) {
+ /* GenID mismatch ? */
+ if (!PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_GENERATION_ID) ||
+ (hello_option_generation_id != neigh->generation_id)) {
+
+ /* GenID changed */
+
+ pim_upstream_rpf_genid_changed(neigh->source_addr);
+
+ /* GenID mismatch, then replace neighbor */
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: GenId mismatch new=%08x old=%08x: replacing neighbor %s on %s",
+ __PRETTY_FUNCTION__,
+ hello_option_generation_id,
+ neigh->generation_id,
+ src_str, ifp->name);
+ }
+
+ pim_upstream_rpf_genid_changed(neigh->source_addr);
+
+ pim_neighbor_delete(ifp, neigh, "GenID mismatch");
+ neigh = pim_neighbor_add(ifp, src_addr,
+ hello_options,
+ hello_option_holdtime,
+ hello_option_propagation_delay,
+ hello_option_override_interval,
+ hello_option_dr_priority,
+ hello_option_generation_id,
+ hello_option_addr_list);
+ if (!neigh) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: failure re-creating PIM neighbor %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifp->name);
+ FREE_ADDR_LIST_THEN_RETURN(-9);
+ }
+ /* actual addr list is saved under neighbor */
+ return 0;
+
+ } /* GenId mismatch: replace neighbor */
+
+ } /* GenId received */
+
+ /*
+ Update existing neighbor
+ */
+
+ pim_neighbor_update(neigh,
+ hello_options,
+ hello_option_holdtime,
+ hello_option_dr_priority,
+ hello_option_addr_list);
+ /* actual addr list is saved under neighbor */
+ return 0;
+}
+
+int pim_hello_build_tlv(const char *ifname,
+ uint8_t *tlv_buf, int tlv_buf_size,
+ uint16_t holdtime,
+ uint32_t dr_priority,
+ uint32_t generation_id,
+ uint16_t propagation_delay,
+ uint16_t override_interval,
+ int can_disable_join_suppression,
+ struct list *ifconnected)
+{
+ uint8_t *curr = tlv_buf;
+ uint8_t *pastend = tlv_buf + tlv_buf_size;
+ uint8_t *tmp;
+
+ /*
+ * Append options
+ */
+
+ /* Holdtime */
+ curr = pim_tlv_append_uint16(curr,
+ pastend,
+ PIM_MSG_OPTION_TYPE_HOLDTIME,
+ holdtime);
+ if (!curr) {
+ zlog_warn("%s: could not set PIM hello Holdtime option for interface %s",
+ __PRETTY_FUNCTION__, ifname);
+ return -1;
+ }
+
+ /* LAN Prune Delay */
+ tmp = pim_tlv_append_2uint16(curr,
+ pastend,
+ PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY,
+ propagation_delay,
+ override_interval);
+ if (!tmp) {
+ zlog_warn("%s: could not set PIM LAN Prune Delay option for interface %s",
+ __PRETTY_FUNCTION__, ifname);
+ return -1;
+ }
+ if (can_disable_join_suppression) {
+ *((uint8_t*)(curr) + 4) |= 0x80; /* enable T bit */
+ }
+ curr = tmp;
+
+ /* DR Priority */
+ curr = pim_tlv_append_uint32(curr,
+ pastend,
+ PIM_MSG_OPTION_TYPE_DR_PRIORITY,
+ dr_priority);
+ if (!curr) {
+ zlog_warn("%s: could not set PIM hello DR Priority option for interface %s",
+ __PRETTY_FUNCTION__, ifname);
+ return -2;
+ }
+
+ /* Generation ID */
+ curr = pim_tlv_append_uint32(curr,
+ pastend,
+ PIM_MSG_OPTION_TYPE_GENERATION_ID,
+ generation_id);
+ if (!curr) {
+ zlog_warn("%s: could not set PIM hello Generation ID option for interface %s",
+ __PRETTY_FUNCTION__, ifname);
+ return -3;
+ }
+
+ /* Secondary Address List */
+ if (ifconnected) {
+ curr = pim_tlv_append_addrlist_ucast(curr,
+ pastend,
+ ifconnected);
+ if (!curr) {
+ zlog_warn("%s: could not set PIM hello Secondary Address List option for interface %s",
+ __PRETTY_FUNCTION__, ifname);
+ return -4;
+ }
+ }
+
+ return curr - tlv_buf;
+}
+
+/*
+ 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.
+*/
+void pim_hello_require(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+
+ zassert(pim_ifp);
+
+ if (pim_ifp->pim_ifstat_hello_sent)
+ return;
+
+ pim_hello_restart_now(ifp); /* Send hello and restart timer */
+}
diff --git a/pimd/pim_hello.h b/pimd/pim_hello.h
new file mode 100644
index 00000000..b5e272d5
--- /dev/null
+++ b/pimd/pim_hello.h
@@ -0,0 +1,46 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_HELLO_H
+#define PIM_HELLO_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+int pim_hello_recv(struct interface *ifp,
+ struct in_addr src_addr,
+ uint8_t *tlv_buf, int tlv_buf_size);
+
+int pim_hello_build_tlv(const char *ifname,
+ uint8_t *tlv_buf, int tlv_buf_size,
+ uint16_t holdtime,
+ uint32_t dr_priority,
+ uint32_t generation_id,
+ uint16_t propagation_delay,
+ uint16_t override_interval,
+ int can_disable_join_suppression,
+ struct list *ifconnected);
+
+void pim_hello_require(struct interface *ifp);
+
+#endif /* PIM_HELLO_H */
diff --git a/pimd/pim_iface.c b/pimd/pim_iface.c
new file mode 100644
index 00000000..ecf9ef6b
--- /dev/null
+++ b/pimd/pim_iface.c
@@ -0,0 +1,1229 @@
+/*
+ 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 "if.h"
+#include "log.h"
+#include "vty.h"
+#include "memory.h"
+#include "prefix.h"
+
+#include "pimd.h"
+#include "pim_iface.h"
+#include "pim_igmp.h"
+#include "pim_mroute.h"
+#include "pim_oil.h"
+#include "pim_str.h"
+#include "pim_pim.h"
+#include "pim_neighbor.h"
+#include "pim_ifchannel.h"
+#include "pim_rand.h"
+#include "pim_sock.h"
+#include "pim_time.h"
+#include "pim_ssmpingd.h"
+
+static void pim_if_igmp_join_del_all(struct interface *ifp);
+
+void pim_if_init()
+{
+ if_init();
+}
+
+static void *if_list_clean(struct pim_interface *pim_ifp)
+{
+ if (pim_ifp->igmp_join_list) {
+ list_delete(pim_ifp->igmp_join_list);
+ }
+
+ if (pim_ifp->igmp_socket_list) {
+ list_delete(pim_ifp->igmp_socket_list);
+ }
+
+ if (pim_ifp->pim_neighbor_list) {
+ list_delete(pim_ifp->pim_neighbor_list);
+ }
+
+ if (pim_ifp->pim_ifchannel_list) {
+ list_delete(pim_ifp->pim_ifchannel_list);
+ }
+
+ XFREE(MTYPE_PIM_INTERFACE, pim_ifp);
+
+ return 0;
+}
+
+struct pim_interface *pim_if_new(struct interface *ifp, int igmp, int pim)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ zassert(!ifp->info);
+
+ pim_ifp = XMALLOC(MTYPE_PIM_INTERFACE, sizeof(*pim_ifp));
+ if (!pim_ifp) {
+ zlog_err("PIM XMALLOC(%zu) failure", sizeof(*pim_ifp));
+ return 0;
+ }
+
+ pim_ifp->options = 0;
+ pim_ifp->mroute_vif_index = -1;
+
+ 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;
+ pim_ifp->igmp_specific_query_max_response_time_dsec = IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC;
+
+ /*
+ RFC 3376: 8.3. Query Response Interval
+ The number of seconds represented by the [Query Response Interval]
+ must be less than the [Query Interval].
+ */
+ zassert(pim_ifp->igmp_query_max_response_time_dsec < pim_ifp->igmp_default_query_interval);
+
+ if (pim)
+ PIM_IF_DO_PIM(pim_ifp->options);
+ if (igmp)
+ PIM_IF_DO_IGMP(pim_ifp->options);
+
+#if 0
+ /* FIXME: Should join? */
+ PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(pim_ifp->options);
+#endif
+
+ pim_ifp->igmp_join_list = 0;
+ pim_ifp->igmp_socket_list = 0;
+ pim_ifp->pim_neighbor_list = 0;
+ pim_ifp->pim_ifchannel_list = 0;
+
+ /* list of struct igmp_sock */
+ pim_ifp->igmp_socket_list = list_new();
+ if (!pim_ifp->igmp_socket_list) {
+ zlog_err("%s %s: failure: igmp_socket_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return if_list_clean(pim_ifp);
+ }
+ pim_ifp->igmp_socket_list->del = (void (*)(void *)) igmp_sock_free;
+
+ /* list of struct pim_neighbor */
+ pim_ifp->pim_neighbor_list = list_new();
+ if (!pim_ifp->pim_neighbor_list) {
+ zlog_err("%s %s: failure: pim_neighbor_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return if_list_clean(pim_ifp);
+ }
+ pim_ifp->pim_neighbor_list->del = (void (*)(void *)) pim_neighbor_free;
+
+ /* list of struct pim_ifchannel */
+ pim_ifp->pim_ifchannel_list = list_new();
+ if (!pim_ifp->pim_ifchannel_list) {
+ zlog_err("%s %s: failure: pim_ifchannel_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return if_list_clean(pim_ifp);
+ }
+ pim_ifp->pim_ifchannel_list->del = (void (*)(void *)) pim_ifchannel_free;
+
+ ifp->info = pim_ifp;
+
+ pim_sock_reset(ifp);
+
+ zassert(PIM_IF_TEST_PIM(pim_ifp->options) || PIM_IF_TEST_IGMP(pim_ifp->options));
+
+ if (PIM_MROUTE_IS_ENABLED) {
+ pim_if_add_vif(ifp);
+ }
+
+ return pim_ifp;
+}
+
+void pim_if_delete(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ if (pim_ifp->igmp_join_list) {
+ pim_if_igmp_join_del_all(ifp);
+ }
+ zassert(!pim_ifp->igmp_join_list);
+
+ zassert(pim_ifp->igmp_socket_list);
+ zassert(!listcount(pim_ifp->igmp_socket_list));
+
+ zassert(pim_ifp->pim_neighbor_list);
+ zassert(!listcount(pim_ifp->pim_neighbor_list));
+
+ zassert(pim_ifp->pim_ifchannel_list);
+ zassert(!listcount(pim_ifp->pim_ifchannel_list));
+
+ if (PIM_MROUTE_IS_ENABLED) {
+ pim_if_del_vif(ifp);
+ }
+
+ list_delete(pim_ifp->igmp_socket_list);
+ list_delete(pim_ifp->pim_neighbor_list);
+ list_delete(pim_ifp->pim_ifchannel_list);
+
+ XFREE(MTYPE_PIM_INTERFACE, pim_ifp);
+
+ ifp->info = 0;
+}
+
+void pim_if_update_could_assert(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct listnode *next_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+ pim_ifchannel_update_could_assert(ch);
+ }
+}
+
+static void pim_if_update_my_assert_metric(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct listnode *next_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+ pim_ifchannel_update_my_assert_metric(ch);
+ }
+}
+
+static void pim_addr_change(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ pim_if_dr_election(ifp); /* router's own DR Priority (addr) changes -- Done TODO T30 */
+ pim_if_update_join_desired(pim_ifp); /* depends on DR */
+ pim_if_update_could_assert(ifp); /* depends on DR */
+ pim_if_update_my_assert_metric(ifp); /* depends on could_assert */
+ pim_if_update_assert_tracking_desired(ifp); /* depends on DR, join_desired */
+
+ /*
+ RFC 4601: 4.3.1. Sending Hello Messages
+
+ 1) Before an interface goes down or changes primary IP address, a
+ Hello message with a zero HoldTime should be sent immediately
+ (with the old IP address if the IP address changed).
+ -- FIXME See CAVEAT C13
+
+ 2) After an interface has changed its IP address, it MUST send a
+ Hello message with its new IP address.
+ -- DONE below
+
+ 3) If an interface changes one of its secondary IP addresses, a
+ Hello message with an updated Address_List option and a non-zero
+ HoldTime should be sent immediately.
+ -- FIXME See TODO T31
+ */
+ pim_ifp->pim_ifstat_hello_sent = 0; /* reset hello counter */
+ if (pim_ifp->pim_sock_fd < 0)
+ return;
+ pim_hello_restart_now(ifp); /* send hello and restart timer */
+}
+
+static void on_primary_address_change(struct interface *ifp,
+ const char *caller,
+ struct in_addr old_addr,
+ struct in_addr new_addr)
+{
+ struct pim_interface *pim_ifp;
+
+ {
+ char old_str[100];
+ char new_str[100];
+ pim_inet4_dump("<old?>", old_addr, old_str, sizeof(old_str));
+ pim_inet4_dump("<new?>", new_addr, new_str, sizeof(new_str));
+ zlog_info("%s: %s: primary address changed from %s to %s on interface %s",
+ __PRETTY_FUNCTION__, caller,
+ old_str, new_str, ifp->name);
+ }
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ return;
+ }
+
+ if (!PIM_IF_TEST_PIM(pim_ifp->options)) {
+ return;
+ }
+
+ pim_addr_change(ifp);
+}
+
+static int detect_primary_address_change(struct interface *ifp,
+ int force_prim_as_any,
+ const char *caller)
+{
+ struct pim_interface *pim_ifp;
+ struct in_addr new_prim_addr;
+ int changed;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return 0;
+
+ if (force_prim_as_any)
+ new_prim_addr = qpim_inaddr_any;
+ else
+ new_prim_addr = pim_find_primary_addr(ifp);
+
+ changed = new_prim_addr.s_addr != pim_ifp->primary_address.s_addr;
+
+ if (PIM_DEBUG_ZEBRA) {
+ char new_prim_str[100];
+ char old_prim_str[100];
+ pim_inet4_dump("<new?>", new_prim_addr, new_prim_str, sizeof(new_prim_str));
+ pim_inet4_dump("<old?>", pim_ifp->primary_address, old_prim_str, sizeof(old_prim_str));
+ zlog_debug("%s: old=%s new=%s on interface %s: %s",
+ __PRETTY_FUNCTION__,
+ old_prim_str, new_prim_str, ifp->name,
+ changed ? "changed" : "unchanged");
+ }
+
+ if (changed) {
+ struct in_addr old_addr = pim_ifp->primary_address;
+ pim_ifp->primary_address = new_prim_addr;
+
+ on_primary_address_change(ifp, caller, old_addr, new_prim_addr);
+ }
+
+ return changed;
+}
+
+static void detect_secondary_address_change(struct interface *ifp,
+ const char *caller)
+{
+ struct pim_interface *pim_ifp;
+ int changed;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return;
+
+ changed = 1; /* true */
+ zlog_debug("FIXME T31 C15 %s: on interface %s: acting on any addr change",
+ __PRETTY_FUNCTION__, ifp->name);
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s: on interface %s: %s",
+ __PRETTY_FUNCTION__,
+ ifp->name, changed ? "changed" : "unchanged");
+ }
+
+ if (!changed) {
+ return;
+ }
+
+ if (!PIM_IF_TEST_PIM(pim_ifp->options)) {
+ return;
+ }
+
+ pim_addr_change(ifp);
+}
+
+static void detect_address_change(struct interface *ifp,
+ int force_prim_as_any,
+ const char *caller)
+{
+ int prim_changed;
+
+ prim_changed = detect_primary_address_change(ifp, force_prim_as_any, caller);
+ if (prim_changed) {
+ /* no need to detect secondary change because
+ the reaction would be the same */
+ return;
+ }
+
+ detect_secondary_address_change(ifp, caller);
+}
+
+void pim_if_addr_add(struct connected *ifc)
+{
+ struct pim_interface *pim_ifp;
+ struct interface *ifp;
+ struct in_addr ifaddr;
+
+ zassert(ifc);
+
+ ifp = ifc->ifp;
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return;
+
+ if (!if_is_operative(ifp))
+ return;
+
+ /* if (PIM_DEBUG_ZEBRA) */ {
+ char buf[BUFSIZ];
+ prefix2str(ifc->address, buf, BUFSIZ);
+ zlog_debug("%s: %s ifindex=%d connected IP address %s %s",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex, buf,
+ CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ?
+ "secondary" : "primary");
+ }
+
+ ifaddr = ifc->address->u.prefix4;
+
+ detect_address_change(ifp, 0, __PRETTY_FUNCTION__);
+
+ if (PIM_IF_TEST_IGMP(pim_ifp->options)) {
+ struct igmp_sock *igmp;
+
+ /* lookup IGMP socket */
+ igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list,
+ ifaddr);
+ if (!igmp) {
+ /* if addr new, add IGMP socket */
+ pim_igmp_sock_add(pim_ifp->igmp_socket_list, ifaddr, ifp);
+ }
+ } /* igmp */
+
+ if (PIM_IF_TEST_PIM(pim_ifp->options)) {
+
+ /* Interface has a valid primary address ? */
+ if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) {
+
+ /* Interface has a valid socket ? */
+ if (pim_ifp->pim_sock_fd < 0) {
+ if (pim_sock_add(ifp)) {
+ zlog_warn("Failure creating PIM socket for interface %s",
+ ifp->name);
+ }
+ }
+
+ }
+ } /* pim */
+
+ if (PIM_MROUTE_IS_ENABLED) {
+ /*
+ PIM or IGMP is enabled on interface, and there is at least one
+ address assigned, then try to create a vif_index.
+ */
+ if (pim_ifp->mroute_vif_index < 0) {
+ pim_if_add_vif(ifp);
+ }
+ }
+}
+
+static void pim_if_addr_del_igmp(struct connected *ifc)
+{
+ struct pim_interface *pim_ifp = ifc->ifp->info;
+ struct igmp_sock *igmp;
+ struct in_addr ifaddr;
+
+ if (ifc->address->family != AF_INET) {
+ /* non-IPv4 address */
+ return;
+ }
+
+ if (!pim_ifp) {
+ /* IGMP not enabled on interface */
+ return;
+ }
+
+ ifaddr = ifc->address->u.prefix4;
+
+ /* lookup IGMP socket */
+ igmp = pim_igmp_sock_lookup_ifaddr(pim_ifp->igmp_socket_list,
+ ifaddr);
+ if (igmp) {
+ /* if addr found, del IGMP socket */
+ igmp_sock_delete(igmp);
+ }
+}
+
+static void pim_if_addr_del_pim(struct connected *ifc)
+{
+ struct pim_interface *pim_ifp = ifc->ifp->info;
+
+ if (ifc->address->family != AF_INET) {
+ /* non-IPv4 address */
+ return;
+ }
+
+ if (!pim_ifp) {
+ /* PIM not enabled on interface */
+ return;
+ }
+
+ if (PIM_INADDR_ISNOT_ANY(pim_ifp->primary_address)) {
+ /* Interface keeps a valid primary address */
+ return;
+ }
+
+ if (pim_ifp->pim_sock_fd < 0) {
+ /* Interface does not hold a valid socket any longer */
+ return;
+ }
+
+ /*
+ pim_sock_delete() closes the socket, stops read and timer threads,
+ and kills all neighbors.
+ */
+ pim_sock_delete(ifc->ifp, "last address has been removed from interface");
+}
+
+void pim_if_addr_del(struct connected *ifc, int force_prim_as_any)
+{
+ struct interface *ifp;
+
+ zassert(ifc);
+ ifp = ifc->ifp;
+ zassert(ifp);
+
+ /* if (PIM_DEBUG_ZEBRA) */ {
+ char buf[BUFSIZ];
+ prefix2str(ifc->address, buf, BUFSIZ);
+ zlog_debug("%s: %s ifindex=%d disconnected IP address %s %s",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex, buf,
+ CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ?
+ "secondary" : "primary");
+ }
+
+ detect_address_change(ifp, force_prim_as_any, __PRETTY_FUNCTION__);
+
+ pim_if_addr_del_igmp(ifc);
+ pim_if_addr_del_pim(ifc);
+}
+
+void pim_if_addr_add_all(struct interface *ifp)
+{
+ struct connected *ifc;
+ struct listnode *node;
+ struct listnode *nextnode;
+
+ /* PIM/IGMP enabled ? */
+ if (!ifp->info)
+ return;
+
+ for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ pim_if_addr_add(ifc);
+ }
+}
+
+void pim_if_addr_del_all(struct interface *ifp)
+{
+ struct connected *ifc;
+ struct listnode *node;
+ struct listnode *nextnode;
+
+ /* PIM/IGMP enabled ? */
+ if (!ifp->info)
+ return;
+
+ for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ pim_if_addr_del(ifc, 1 /* force_prim_as_any=true */);
+ }
+}
+
+void pim_if_addr_del_all_igmp(struct interface *ifp)
+{
+ struct connected *ifc;
+ struct listnode *node;
+ struct listnode *nextnode;
+
+ /* PIM/IGMP enabled ? */
+ if (!ifp->info)
+ return;
+
+ for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ pim_if_addr_del_igmp(ifc);
+ }
+}
+
+void pim_if_addr_del_all_pim(struct interface *ifp)
+{
+ struct connected *ifc;
+ struct listnode *node;
+ struct listnode *nextnode;
+
+ /* PIM/IGMP enabled ? */
+ if (!ifp->info)
+ return;
+
+ for (ALL_LIST_ELEMENTS(ifp->connected, node, nextnode, ifc)) {
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ pim_if_addr_del_pim(ifc);
+ }
+}
+
+static struct in_addr find_first_nonsec_addr(struct interface *ifp)
+{
+ struct connected *ifc;
+ struct listnode *node;
+ struct in_addr addr;
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) {
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ if (PIM_INADDR_IS_ANY(p->u.prefix4)) {
+ zlog_warn("%s: null IPv4 address connected to interface %s",
+ __PRETTY_FUNCTION__, ifp->name);
+ continue;
+ }
+
+ if (CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY))
+ continue;
+
+ return p->u.prefix4;
+ }
+
+ addr.s_addr = PIM_NET_INADDR_ANY;
+
+ return addr;
+}
+
+struct in_addr pim_find_primary_addr(struct interface *ifp)
+{
+ return find_first_nonsec_addr(ifp);
+}
+
+/*
+ pim_if_add_vif() uses ifindex as vif_index
+
+ see also pim_if_find_vifindex_by_ifindex()
+ */
+int pim_if_add_vif(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp = ifp->info;
+ struct in_addr ifaddr;
+
+ zassert(pim_ifp);
+
+ if (pim_ifp->mroute_vif_index > 0) {
+ zlog_warn("%s: vif_index=%d > 0 on interface %s ifindex=%d",
+ __PRETTY_FUNCTION__,
+ pim_ifp->mroute_vif_index, ifp->name, ifp->ifindex);
+ return -1;
+ }
+
+ if (ifp->ifindex < 1) {
+ zlog_warn("%s: ifindex=%d < 1 on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->ifindex, ifp->name);
+ return -2;
+ }
+
+ if (ifp->ifindex >= MAXVIFS) {
+ zlog_warn("%s: ifindex=%d >= MAXVIFS=%d on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->ifindex, MAXVIFS, ifp->name);
+ return -3;
+ }
+
+ ifaddr = pim_ifp->primary_address;
+ if (PIM_INADDR_IS_ANY(ifaddr)) {
+ zlog_warn("%s: could not get address for interface %s ifindex=%d",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex);
+ return -4;
+ }
+
+ if (pim_mroute_add_vif(ifp->ifindex, ifaddr)) {
+ /* pim_mroute_add_vif reported error */
+ return -5;
+ }
+
+ pim_ifp->mroute_vif_index = ifp->ifindex;
+
+ /*
+ Update highest vif_index
+ */
+ if (pim_ifp->mroute_vif_index > qpim_mroute_oif_highest_vif_index) {
+ qpim_mroute_oif_highest_vif_index = pim_ifp->mroute_vif_index;
+ }
+
+ return 0;
+}
+
+static int iflist_find_highest_vif_index()
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int highest_vif_index = -1;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ if (pim_ifp->mroute_vif_index > highest_vif_index) {
+ highest_vif_index = pim_ifp->mroute_vif_index;
+ }
+ }
+
+ return highest_vif_index;
+}
+
+int pim_if_del_vif(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp = ifp->info;
+ int old_vif_index;
+
+ if (pim_ifp->mroute_vif_index < 1) {
+ zlog_warn("%s: vif_index=%d < 1 on interface %s ifindex=%d",
+ __PRETTY_FUNCTION__,
+ pim_ifp->mroute_vif_index, ifp->name, ifp->ifindex);
+ return -1;
+ }
+
+ if (pim_mroute_del_vif(pim_ifp->mroute_vif_index)) {
+ /* pim_mroute_del_vif reported error */
+ return -2;
+ }
+
+ /*
+ Update highest vif_index
+ */
+
+ /* save old vif_index in order to compare with highest below */
+ old_vif_index = pim_ifp->mroute_vif_index;
+
+ pim_ifp->mroute_vif_index = -1;
+
+ if (old_vif_index == qpim_mroute_oif_highest_vif_index) {
+ qpim_mroute_oif_highest_vif_index = iflist_find_highest_vif_index();
+ }
+
+ return 0;
+}
+
+void pim_if_add_vif_all()
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct interface *ifp;
+
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ if (!ifp->info)
+ continue;
+
+ pim_if_add_vif(ifp);
+ }
+}
+
+void pim_if_del_vif_all()
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct interface *ifp;
+
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ if (!ifp->info)
+ continue;
+
+ pim_if_del_vif(ifp);
+ }
+}
+
+struct interface *pim_if_find_by_vif_index(int vif_index)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ if (ifp->info) {
+ struct pim_interface *pim_ifp;
+ pim_ifp = ifp->info;
+ if (vif_index == pim_ifp->mroute_vif_index)
+ return ifp;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ pim_if_add_vif() uses ifindex as vif_index
+ */
+int pim_if_find_vifindex_by_ifindex(int ifindex)
+{
+ return ifindex;
+}
+
+int pim_if_lan_delay_enabled(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+ zassert(pim_ifp->pim_number_of_nonlandelay_neighbors >= 0);
+
+ return pim_ifp->pim_number_of_nonlandelay_neighbors == 0;
+}
+
+uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp)
+{
+ if (pim_if_lan_delay_enabled(ifp)) {
+ struct pim_interface *pim_ifp;
+ pim_ifp = ifp->info;
+ return pim_ifp->pim_neighbors_highest_propagation_delay_msec;
+ }
+ else {
+ return PIM_DEFAULT_PROPAGATION_DELAY_MSEC;
+ }
+}
+
+uint16_t pim_if_effective_override_interval_msec(struct interface *ifp)
+{
+ if (pim_if_lan_delay_enabled(ifp)) {
+ struct pim_interface *pim_ifp;
+ pim_ifp = ifp->info;
+ return pim_ifp->pim_neighbors_highest_override_interval_msec;
+ }
+ else {
+ return PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC;
+ }
+}
+
+int pim_if_t_override_msec(struct interface *ifp)
+{
+ int effective_override_interval_msec;
+ int t_override_msec;
+
+ effective_override_interval_msec =
+ pim_if_effective_override_interval_msec(ifp);
+
+ t_override_msec = pim_rand_next(0, effective_override_interval_msec);
+
+ return t_override_msec;
+}
+
+uint16_t pim_if_jp_override_interval_msec(struct interface *ifp)
+{
+ return pim_if_effective_propagation_delay_msec(ifp) +
+ pim_if_effective_override_interval_msec(ifp);
+}
+
+/*
+ RFC 4601: 4.1.6. State Summarization Macros
+
+ The function NBR( I, A ) uses information gathered through PIM Hello
+ messages to map the IP address A of a directly connected PIM
+ neighbor router on interface I to the primary IP address of the same
+ router (Section 4.3.4). The primary IP address of a neighbor is the
+ address that it uses as the source of its PIM Hello messages.
+*/
+struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp,
+ struct in_addr addr)
+{
+ struct listnode *neighnode;
+ struct pim_neighbor *neigh;
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ zlog_warn("%s: multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->name);
+ return 0;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neighnode, neigh)) {
+
+ /* primary address ? */
+ if (neigh->source_addr.s_addr == addr.s_addr)
+ return neigh;
+
+ /* secondary address ? */
+ if (pim_neighbor_find_secondary(neigh, addr))
+ return neigh;
+ }
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s: neighbor not found for address %s on interface %s",
+ __PRETTY_FUNCTION__,
+ addr_str, ifp->name);
+ }
+
+ return 0;
+}
+
+long pim_if_t_suppressed_msec(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ long t_suppressed_msec;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ /* join suppression disabled ? */
+ if (PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options))
+ return 0;
+
+ /* t_suppressed = t_periodic * rand(1.1, 1.4) */
+
+ t_suppressed_msec = qpim_t_periodic * pim_rand_next(1100, 1400);
+
+ return t_suppressed_msec;
+}
+
+static void igmp_join_free(struct igmp_join *ij)
+{
+ XFREE(MTYPE_PIM_IGMP_JOIN, ij);
+}
+
+static struct igmp_join *igmp_join_find(struct list *join_list,
+ struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ struct listnode *node;
+ struct igmp_join *ij;
+
+ zassert(join_list);
+
+ for (ALL_LIST_ELEMENTS_RO(join_list, node, ij)) {
+ if ((group_addr.s_addr == ij->group_addr.s_addr) &&
+ (source_addr.s_addr == ij->source_addr.s_addr))
+ return ij;
+ }
+
+ return 0;
+}
+
+static int igmp_join_sock(const char *ifname,
+ int ifindex,
+ struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ int join_fd;
+
+ join_fd = pim_socket_raw(IPPROTO_IGMP);
+ if (join_fd < 0) {
+ return -1;
+ }
+
+ if (pim_socket_join_source(join_fd, ifindex, group_addr, source_addr, ifname)) {
+ close(join_fd);
+ return -2;
+ }
+
+ return join_fd;
+}
+
+static struct igmp_join *igmp_join_new(struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct igmp_join *ij;
+ int join_fd;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ join_fd = igmp_join_sock(ifp->name, ifp->ifindex, group_addr, source_addr);
+ if (join_fd < 0) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: igmp_join_sock() failure for IGMP group %s source %s on interface %s",
+ __PRETTY_FUNCTION__,
+ group_str, source_str, ifp->name);
+ return 0;
+ }
+
+ ij = XMALLOC(MTYPE_PIM_IGMP_JOIN, sizeof(*ij));
+ if (!ij) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_err("%s: XMALLOC(%zu) failure for IGMP group %s source %s on interface %s",
+ __PRETTY_FUNCTION__,
+ sizeof(*ij), group_str, source_str, ifp->name);
+ close(join_fd);
+ return 0;
+ }
+
+ ij->sock_fd = join_fd;
+ ij->group_addr = group_addr;
+ ij->source_addr = source_addr;
+ ij->sock_creation = pim_time_monotonic_sec();
+
+ listnode_add(pim_ifp->igmp_join_list, ij);
+
+ return ij;
+}
+
+int pim_if_igmp_join_add(struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct igmp_join *ij;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ zlog_warn("%s: multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->name);
+ return -1;
+ }
+
+ if (!pim_ifp->igmp_join_list) {
+ pim_ifp->igmp_join_list = list_new();
+ if (!pim_ifp->igmp_join_list) {
+ zlog_err("%s %s: failure: igmp_join_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return -2;
+ }
+ pim_ifp->igmp_join_list->del = (void (*)(void *)) igmp_join_free;
+ }
+
+ ij = igmp_join_find(pim_ifp->igmp_join_list, group_addr, source_addr);
+ if (ij) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: can't re-join existing IGMP group %s source %s on interface %s",
+ __PRETTY_FUNCTION__,
+ group_str, source_str, ifp->name);
+ return -3;
+ }
+
+ ij = igmp_join_new(ifp, group_addr, source_addr);
+ if (!ij) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: igmp_join_new() failure for IGMP group %s source %s on interface %s",
+ __PRETTY_FUNCTION__,
+ group_str, source_str, ifp->name);
+ return -4;
+ }
+
+ {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_debug("%s: issued static igmp join for channel (S,G)=(%s,%s) on interface %s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ifp->name);
+ }
+
+ return 0;
+}
+
+
+
+int pim_if_igmp_join_del(struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct igmp_join *ij;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ zlog_warn("%s: multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->name);
+ return -1;
+ }
+
+ if (!pim_ifp->igmp_join_list) {
+ zlog_warn("%s: no IGMP join on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->name);
+ return -2;
+ }
+
+ ij = igmp_join_find(pim_ifp->igmp_join_list, group_addr, source_addr);
+ if (!ij) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: could not find IGMP group %s source %s on interface %s",
+ __PRETTY_FUNCTION__,
+ group_str, source_str, ifp->name);
+ return -3;
+ }
+
+ if (close(ij->sock_fd)) {
+ int e = errno;
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: failure closing sock_fd=%d for IGMP group %s source %s on interface %s: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ ij->sock_fd, group_str, source_str, ifp->name, e, safe_strerror(e));
+ /* warning only */
+ }
+ listnode_delete(pim_ifp->igmp_join_list, ij);
+ igmp_join_free(ij);
+ if (listcount(pim_ifp->igmp_join_list) < 1) {
+ list_delete(pim_ifp->igmp_join_list);
+ pim_ifp->igmp_join_list = 0;
+ }
+
+ return 0;
+}
+
+static void pim_if_igmp_join_del_all(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct listnode *nextnode;
+ struct igmp_join *ij;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ zlog_warn("%s: multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->name);
+ return;
+ }
+
+ if (!pim_ifp->igmp_join_list)
+ return;
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->igmp_join_list, node, nextnode, ij))
+ pim_if_igmp_join_del(ifp, ij->group_addr, ij->source_addr);
+}
+
+/*
+ RFC 4601
+
+ Transitions from "I am Assert Loser" State
+
+ Current Winner's GenID Changes or NLT Expires
+
+ The Neighbor Liveness Timer associated with the current winner
+ expires or we receive a Hello message from the current winner
+ reporting a different GenID from the one it previously reported.
+ This indicates that the current winner's interface or router has
+ gone down (and may have come back up), and so we must assume it no
+ longer knows it was the winner.
+ */
+void pim_if_assert_on_neighbor_down(struct interface *ifp,
+ struct in_addr neigh_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct listnode *next_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+ /* Is (S,G,I) assert loser ? */
+ if (ch->ifassert_state != PIM_IFASSERT_I_AM_LOSER)
+ continue;
+ /* Dead neighbor was winner ? */
+ if (ch->ifassert_winner.s_addr != neigh_addr.s_addr)
+ continue;
+
+ assert_action_a5(ch);
+ }
+}
+
+void pim_if_update_join_desired(struct pim_interface *pim_ifp)
+{
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ /* clear off flag from interface's upstreams */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(ch->upstream->flags);
+ }
+
+ /* scan per-interface (S,G,I) state on this I interface */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ struct pim_upstream *up = ch->upstream;
+
+ if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(up->flags))
+ continue;
+
+ /* update join_desired for the global (S,G) state */
+ pim_upstream_update_join_desired(up);
+ PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(up->flags);
+ }
+}
+
+void pim_if_update_assert_tracking_desired(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct listnode *next_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return;
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+ pim_ifchannel_update_assert_tracking_desired(ch);
+ }
+}
diff --git a/pimd/pim_iface.h b/pimd/pim_iface.h
new file mode 100644
index 00000000..4b06b9ff
--- /dev/null
+++ b/pimd/pim_iface.h
@@ -0,0 +1,161 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_IFACE_H
+#define PIM_IFACE_H
+
+#include <zebra.h>
+
+#include "if.h"
+#include "vty.h"
+
+#include "pim_igmp.h"
+#include "pim_upstream.h"
+
+#define PIM_IF_MASK_PIM (1 << 0)
+#define PIM_IF_MASK_IGMP (1 << 1)
+#define PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS (1 << 2)
+#define PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION (1 << 3)
+
+#define PIM_IF_IS_DELETED(ifp) ((ifp)->ifindex == IFINDEX_INTERNAL)
+
+#define PIM_IF_TEST_PIM(options) (PIM_IF_MASK_PIM & (options))
+#define PIM_IF_TEST_IGMP(options) (PIM_IF_MASK_IGMP & (options))
+#define PIM_IF_TEST_IGMP_LISTEN_ALLROUTERS(options) (PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS & (options))
+#define PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) (PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION & (options))
+
+#define PIM_IF_DO_PIM(options) ((options) |= PIM_IF_MASK_PIM)
+#define PIM_IF_DO_IGMP(options) ((options) |= PIM_IF_MASK_IGMP)
+#define PIM_IF_DO_IGMP_LISTEN_ALLROUTERS(options) ((options) |= PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS)
+#define PIM_IF_DO_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) ((options) |= PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION)
+
+#define PIM_IF_DONT_PIM(options) ((options) &= ~PIM_IF_MASK_PIM)
+#define PIM_IF_DONT_IGMP(options) ((options) &= ~PIM_IF_MASK_IGMP)
+#define PIM_IF_DONT_IGMP_LISTEN_ALLROUTERS(options) ((options) &= ~PIM_IF_MASK_IGMP_LISTEN_ALLROUTERS)
+#define PIM_IF_DONT_PIM_CAN_DISABLE_JOIN_SUPRESSION(options) ((options) &= ~PIM_IF_MASK_PIM_CAN_DISABLE_JOIN_SUPRESSION)
+
+struct pim_interface {
+ uint32_t options; /* bit vector */
+ int mroute_vif_index;
+ struct in_addr primary_address; /* remember addr to detect change */
+
+ 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 */
+ int igmp_specific_query_max_response_time_dsec; /* IGMPv3 Max Response Time in dsecs for specific queries */
+ struct list *igmp_socket_list; /* list of struct igmp_sock */
+ struct list *igmp_join_list; /* list of struct igmp_join */
+
+ int pim_sock_fd; /* PIM socket file descriptor */
+ struct thread *t_pim_sock_read; /* thread for reading PIM socket */
+ int64_t pim_sock_creation; /* timestamp of PIM socket creation */
+
+ struct thread *t_pim_hello_timer;
+ int pim_hello_period;
+ int pim_default_holdtime;
+ int pim_triggered_hello_delay;
+ uint32_t pim_generation_id;
+ uint16_t pim_propagation_delay_msec; /* config */
+ uint16_t pim_override_interval_msec; /* config */
+ struct list *pim_neighbor_list; /* list of struct pim_neighbor */
+ struct list *pim_ifchannel_list; /* list of struct pim_ifchannel */
+
+ /* neighbors without lan_delay */
+ int pim_number_of_nonlandelay_neighbors;
+ uint16_t pim_neighbors_highest_propagation_delay_msec;
+ uint16_t pim_neighbors_highest_override_interval_msec;
+
+ /* DR Election */
+ int64_t pim_dr_election_last; /* timestamp */
+ int pim_dr_election_count;
+ int pim_dr_election_changes;
+ struct in_addr pim_dr_addr;
+ uint32_t pim_dr_priority; /* config */
+ int pim_dr_num_nondrpri_neighbors; /* neighbors without dr_pri */
+
+ int64_t pim_ifstat_start; /* start timestamp for stats */
+ uint32_t pim_ifstat_hello_sent;
+ uint32_t pim_ifstat_hello_sendfail;
+ uint32_t pim_ifstat_hello_recv;
+ uint32_t pim_ifstat_hello_recvfail;
+};
+
+/*
+ if default_holdtime is set (>= 0), use it;
+ otherwise default_holdtime is 3.5 * hello_period
+ */
+#define PIM_IF_DEFAULT_HOLDTIME(pim_ifp) \
+ (((pim_ifp)->pim_default_holdtime < 0) ? \
+ ((pim_ifp)->pim_hello_period * 7 / 2) : \
+ ((pim_ifp)->pim_default_holdtime))
+
+void pim_if_init(void);
+
+struct pim_interface *pim_if_new(struct interface *ifp, int igmp, int pim);
+void pim_if_delete(struct interface *ifp);
+void pim_if_addr_add(struct connected *ifc);
+void pim_if_addr_del(struct connected *ifc, int force_prim_as_any);
+void pim_if_addr_add_all(struct interface *ifp);
+void pim_if_addr_del_all(struct interface *ifp);
+void pim_if_addr_del_all_igmp(struct interface *ifp);
+void pim_if_addr_del_all_pim(struct interface *ifp);
+
+int pim_if_add_vif(struct interface *ifp);
+int pim_if_del_vif(struct interface *ifp);
+void pim_if_add_vif_all(void);
+void pim_if_del_vif_all(void);
+
+struct interface *pim_if_find_by_vif_index(int vif_index);
+int pim_if_find_vifindex_by_ifindex(int ifindex);
+
+int pim_if_lan_delay_enabled(struct interface *ifp);
+uint16_t pim_if_effective_propagation_delay_msec(struct interface *ifp);
+uint16_t pim_if_effective_override_interval_msec(struct interface *ifp);
+uint16_t pim_if_jp_override_interval_msec(struct interface *ifp);
+struct pim_neighbor *pim_if_find_neighbor(struct interface *ifp,
+ struct in_addr addr);
+
+long pim_if_t_suppressed_msec(struct interface *ifp);
+int pim_if_t_override_msec(struct interface *ifp);
+
+struct in_addr pim_find_primary_addr(struct interface *ifp);
+
+int pim_if_igmp_join_add(struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr);
+int pim_if_igmp_join_del(struct interface *ifp,
+ struct in_addr group_addr,
+ struct in_addr source_addr);
+
+void pim_if_update_could_assert(struct interface *ifp);
+
+void pim_if_assert_on_neighbor_down(struct interface *ifp,
+ struct in_addr neigh_addr);
+
+void pim_if_rpf_interface_changed(struct interface *old_rpf_ifp,
+ struct pim_upstream *up);
+
+void pim_if_update_join_desired(struct pim_interface *pim_ifp);
+
+void pim_if_update_assert_tracking_desired(struct interface *ifp);
+
+#endif /* PIM_IFACE_H */
diff --git a/pimd/pim_ifchannel.c b/pimd/pim_ifchannel.c
new file mode 100644
index 00000000..e253a0ea
--- /dev/null
+++ b/pimd/pim_ifchannel.c
@@ -0,0 +1,893 @@
+/*
+ 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 "linklist.h"
+#include "thread.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_ifchannel.h"
+#include "pim_zebra.h"
+#include "pim_time.h"
+#include "pim_msg.h"
+#include "pim_pim.h"
+#include "pim_join.h"
+#include "pim_rpf.h"
+#include "pim_macro.h"
+
+void pim_ifchannel_free(struct pim_ifchannel *ch)
+{
+ zassert(!ch->t_ifjoin_expiry_timer);
+ zassert(!ch->t_ifjoin_prune_pending_timer);
+ zassert(!ch->t_ifassert_timer);
+
+ XFREE(MTYPE_PIM_IFCHANNEL, ch);
+}
+
+void pim_ifchannel_delete(struct pim_ifchannel *ch)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ch->interface->info;
+ zassert(pim_ifp);
+
+ if (ch->ifjoin_state != PIM_IFJOIN_NOINFO) {
+ pim_upstream_update_join_desired(ch->upstream);
+ }
+
+ pim_upstream_del(ch->upstream);
+
+ THREAD_OFF(ch->t_ifjoin_expiry_timer);
+ THREAD_OFF(ch->t_ifjoin_prune_pending_timer);
+ THREAD_OFF(ch->t_ifassert_timer);
+
+ /*
+ notice that listnode_delete() can't be moved
+ into pim_ifchannel_free() because the later is
+ called by list_delete_all_node()
+ */
+ listnode_delete(pim_ifp->pim_ifchannel_list, ch);
+
+ pim_ifchannel_free(ch);
+}
+
+#define IFCHANNEL_NOINFO(ch) \
+ ( \
+ ((ch)->local_ifmembership == PIM_IFMEMBERSHIP_NOINFO) \
+ && \
+ ((ch)->ifjoin_state == PIM_IFJOIN_NOINFO) \
+ && \
+ ((ch)->ifassert_state == PIM_IFASSERT_NOINFO) \
+ )
+
+static void delete_on_noinfo(struct pim_ifchannel *ch)
+{
+ if (IFCHANNEL_NOINFO(ch)) {
+
+ /* In NOINFO state, timers should have been cleared */
+ zassert(!ch->t_ifjoin_expiry_timer);
+ zassert(!ch->t_ifjoin_prune_pending_timer);
+ zassert(!ch->t_ifassert_timer);
+
+ pim_ifchannel_delete(ch);
+ }
+}
+
+void pim_ifchannel_ifjoin_switch(const char *caller,
+ struct pim_ifchannel *ch,
+ enum pim_ifjoin_state new_state)
+{
+ enum pim_ifjoin_state old_state = ch->ifjoin_state;
+
+ if (old_state == new_state) {
+ zlog_debug("%s calledby %s: non-transition on state %d (%s)",
+ __PRETTY_FUNCTION__, caller, new_state,
+ pim_ifchannel_ifjoin_name(new_state));
+ return;
+ }
+
+ zassert(old_state != new_state);
+
+ ch->ifjoin_state = new_state;
+
+ /* Transition to/from NOINFO ? */
+ if (
+ (old_state == PIM_IFJOIN_NOINFO)
+ ||
+ (new_state == PIM_IFJOIN_NOINFO)
+ ) {
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("PIM_IFCHANNEL_%s: (S,G)=(%s,%s) on interface %s",
+ ((new_state == PIM_IFJOIN_NOINFO) ? "DOWN" : "UP"),
+ src_str, grp_str, ch->interface->name);
+ }
+
+ /*
+ Record uptime of state transition to/from NOINFO
+ */
+ ch->ifjoin_creation = pim_time_monotonic_sec();
+
+ pim_upstream_update_join_desired(ch->upstream);
+ pim_ifchannel_update_could_assert(ch);
+ pim_ifchannel_update_assert_tracking_desired(ch);
+ }
+}
+
+const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state)
+{
+ switch (ifjoin_state) {
+ case PIM_IFJOIN_NOINFO: return "NOINFO";
+ case PIM_IFJOIN_JOIN: return "JOIN";
+ case PIM_IFJOIN_PRUNE_PENDING: return "PRUNEP";
+ }
+
+ return "ifjoin_bad_state";
+}
+
+const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state)
+{
+ switch (ifassert_state) {
+ case PIM_IFASSERT_NOINFO: return "NOINFO";
+ case PIM_IFASSERT_I_AM_WINNER: return "WINNER";
+ case PIM_IFASSERT_I_AM_LOSER: return "LOSER";
+ }
+
+ return "ifassert_bad_state";
+}
+
+/*
+ RFC 4601: 4.6.5. Assert State Macros
+
+ AssertWinner(S,G,I) defaults to NULL and AssertWinnerMetric(S,G,I)
+ defaults to Infinity when in the NoInfo state.
+*/
+void reset_ifassert_state(struct pim_ifchannel *ch)
+{
+ THREAD_OFF(ch->t_ifassert_timer);
+
+ pim_ifassert_winner_set(ch,
+ PIM_IFASSERT_NOINFO,
+ qpim_inaddr_any,
+ qpim_infinite_assert_metric);
+}
+
+static struct pim_ifchannel *pim_ifchannel_new(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_ifchannel *ch;
+ struct pim_interface *pim_ifp;
+ struct pim_upstream *up;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ up = pim_upstream_add(source_addr, group_addr);
+ if (!up) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ zlog_err("%s: could not attach upstream (S,G)=(%s,%s) on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return 0;
+ }
+
+ ch = XMALLOC(MTYPE_PIM_IFCHANNEL, sizeof(*ch));
+ if (!ch) {
+ zlog_err("%s: PIM XMALLOC(%zu) failure",
+ __PRETTY_FUNCTION__, sizeof(*ch));
+ return 0;
+ }
+
+ ch->flags = 0;
+ ch->upstream = up;
+ ch->interface = ifp;
+ ch->source_addr = source_addr;
+ ch->group_addr = group_addr;
+ ch->local_ifmembership = PIM_IFMEMBERSHIP_NOINFO;
+
+ ch->ifjoin_state = PIM_IFJOIN_NOINFO;
+ ch->t_ifjoin_expiry_timer = 0;
+ ch->t_ifjoin_prune_pending_timer = 0;
+ ch->ifjoin_creation = 0;
+
+ /* Assert state */
+ ch->t_ifassert_timer = 0;
+ reset_ifassert_state(ch);
+ if (pim_macro_ch_could_assert_eval(ch))
+ PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags);
+ else
+ PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags);
+
+ if (pim_macro_assert_tracking_desired_eval(ch))
+ PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags);
+ else
+ PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags);
+
+ ch->ifassert_my_metric = pim_macro_ch_my_assert_metric_eval(ch);
+
+ /* Attach to list */
+ listnode_add(pim_ifp->pim_ifchannel_list, ch);
+
+ zassert(IFCHANNEL_NOINFO(ch));
+
+ return ch;
+}
+
+struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+
+ if (!pim_ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ ifp->name);
+ return 0;
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ if (
+ (source_addr.s_addr == ch->source_addr.s_addr) &&
+ (group_addr.s_addr == ch->group_addr.s_addr)
+ ) {
+ return ch;
+ }
+ }
+
+ return 0;
+}
+
+static void ifmembership_set(struct pim_ifchannel *ch,
+ enum pim_ifmembership membership)
+{
+ if (ch->local_ifmembership == membership)
+ return;
+
+ /* if (PIM_DEBUG_PIM_EVENTS) */ {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: (S,G)=(%s,%s) membership now is %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ membership == PIM_IFMEMBERSHIP_INCLUDE ? "INCLUDE" : "NOINFO",
+ ch->interface->name);
+ }
+
+ ch->local_ifmembership = membership;
+
+ pim_upstream_update_join_desired(ch->upstream);
+ pim_ifchannel_update_could_assert(ch);
+ pim_ifchannel_update_assert_tracking_desired(ch);
+}
+
+
+void pim_ifchannel_membership_clear(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *ch_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_ifchannel_list, ch_node, ch)) {
+ ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO);
+ }
+}
+
+void pim_ifchannel_delete_on_noinfo(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct listnode *next_node;
+ struct pim_ifchannel *ch;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, node, next_node, ch)) {
+ delete_on_noinfo(ch);
+ }
+}
+
+struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_ifchannel *ch;
+ char src_str[100];
+ char grp_str[100];
+
+ ch = pim_ifchannel_find(ifp, source_addr, group_addr);
+ if (ch)
+ return ch;
+
+ ch = pim_ifchannel_new(ifp, source_addr, group_addr);
+ if (ch)
+ return ch;
+
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: pim_ifchannel_new() failure for (S,G)=(%s,%s) on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+
+ return 0;
+}
+
+static void ifjoin_to_noinfo(struct pim_ifchannel *ch)
+{
+ pim_forward_stop(ch);
+ pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_NOINFO);
+ delete_on_noinfo(ch);
+}
+
+static int on_ifjoin_expiry_timer(struct thread *t)
+{
+ struct pim_ifchannel *ch;
+
+ zassert(t);
+ ch = THREAD_ARG(t);
+ zassert(ch);
+
+ ch->t_ifjoin_expiry_timer = 0;
+
+ zassert(ch->ifjoin_state == PIM_IFJOIN_JOIN);
+
+ ifjoin_to_noinfo(ch);
+ /* ch may have been deleted */
+
+ return 0;
+}
+
+static void prune_echo(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct in_addr neigh_dst_addr;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ neigh_dst_addr = pim_ifp->primary_address;
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char source_str[100];
+ char group_str[100];
+ char neigh_dst_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<neigh?>", neigh_dst_addr, neigh_dst_str, sizeof(neigh_dst_str));
+ zlog_debug("%s: sending PruneEcho(S,G)=(%s,%s) to upstream=%s on interface %s",
+ __PRETTY_FUNCTION__, source_str, group_str, neigh_dst_str, ifp->name);
+ }
+
+ pim_joinprune_send(ifp, neigh_dst_addr, source_addr, group_addr,
+ 0 /* boolean: send_join=false (prune) */);
+}
+
+static int on_ifjoin_prune_pending_timer(struct thread *t)
+{
+ struct pim_ifchannel *ch;
+ int send_prune_echo; /* boolean */
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct in_addr ch_source;
+ struct in_addr ch_group;
+
+ zassert(t);
+ ch = THREAD_ARG(t);
+ zassert(ch);
+
+ ch->t_ifjoin_prune_pending_timer = 0;
+
+ zassert(ch->ifjoin_state == PIM_IFJOIN_PRUNE_PENDING);
+
+ /* Send PruneEcho(S,G) ? */
+ ifp = ch->interface;
+ pim_ifp = ifp->info;
+ send_prune_echo = (listcount(pim_ifp->pim_neighbor_list) > 1);
+
+ /* Save (S,G) */
+ ch_source = ch->source_addr;
+ ch_group = ch->group_addr;
+
+ ifjoin_to_noinfo(ch);
+ /* from here ch may have been deleted */
+
+ if (send_prune_echo)
+ prune_echo(ifp, ch_source, ch_group);
+
+ return 0;
+}
+
+static void check_recv_upstream(int is_join,
+ struct interface *recv_ifp,
+ struct in_addr upstream,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ uint8_t source_flags,
+ int holdtime)
+{
+ struct pim_upstream *up;
+
+ /* Upstream (S,G) in Joined state ? */
+ up = pim_upstream_find(source_addr, group_addr);
+ if (!up)
+ return;
+ if (up->join_state != PIM_UPSTREAM_JOINED)
+ return;
+
+ /* Upstream (S,G) in Joined state */
+
+ if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
+ /* RPF'(S,G) not found */
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s %s: RPF'(%s,%s) not found",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str);
+ return;
+ }
+
+ /* upstream directed to RPF'(S,G) ? */
+ if (upstream.s_addr != up->rpf.rpf_addr.s_addr) {
+ char src_str[100];
+ char grp_str[100];
+ char up_str[100];
+ char rpf_str[100];
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<up?>", upstream, up_str, sizeof(up_str));
+ pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
+ zlog_warn("%s %s: (S,G)=(%s,%s) upstream=%s not directed to RPF'(S,G)=%s on interface %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ up_str, rpf_str, recv_ifp->name);
+ return;
+ }
+ /* upstream directed to RPF'(S,G) */
+
+ if (is_join) {
+ /* Join(S,G) to RPF'(S,G) */
+ pim_upstream_join_suppress(up, up->rpf.rpf_addr, holdtime);
+ return;
+ }
+
+ /* Prune to RPF'(S,G) */
+
+ if (source_flags & PIM_RPT_BIT_MASK) {
+ if (source_flags & PIM_WILDCARD_BIT_MASK) {
+ /* Prune(*,G) to RPF'(S,G) */
+ pim_upstream_join_timer_decrease_to_t_override("Prune(*,G)",
+ up, up->rpf.rpf_addr);
+ return;
+ }
+
+ /* Prune(S,G,rpt) to RPF'(S,G) */
+ pim_upstream_join_timer_decrease_to_t_override("Prune(S,G,rpt)",
+ up, up->rpf.rpf_addr);
+ return;
+ }
+
+ /* Prune(S,G) to RPF'(S,G) */
+ pim_upstream_join_timer_decrease_to_t_override("Prune(S,G)", up,
+ up->rpf.rpf_addr);
+}
+
+static int nonlocal_upstream(int is_join,
+ struct interface *recv_ifp,
+ struct in_addr upstream,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ uint8_t source_flags,
+ uint16_t holdtime)
+{
+ struct pim_interface *recv_pim_ifp;
+ int is_local; /* boolean */
+
+ recv_pim_ifp = recv_ifp->info;
+ zassert(recv_pim_ifp);
+
+ is_local = (upstream.s_addr == recv_pim_ifp->primary_address.s_addr);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char up_str[100];
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<upstream?>", upstream, up_str, sizeof(up_str));
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: recv %s (S,G)=(%s,%s) to %s upstream=%s on %s",
+ __PRETTY_FUNCTION__,
+ is_join ? "join" : "prune",
+ src_str, grp_str,
+ is_local ? "local" : "non-local",
+ up_str, recv_ifp->name);
+ }
+
+ if (is_local)
+ return 0;
+
+ /*
+ Since recv upstream addr was not directed to our primary
+ address, check if we should react to it in any way.
+ */
+ check_recv_upstream(is_join, recv_ifp, upstream, source_addr, group_addr,
+ source_flags, holdtime);
+
+ return 1; /* non-local */
+}
+
+void pim_ifchannel_join_add(struct interface *ifp,
+ struct in_addr neigh_addr,
+ struct in_addr upstream,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ uint8_t source_flags,
+ uint16_t holdtime)
+{
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ if (nonlocal_upstream(1 /* join */, ifp, upstream,
+ source_addr, group_addr, source_flags, holdtime)) {
+ return;
+ }
+
+ ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+ if (!ch)
+ return;
+
+ /*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ Transitions from "I am Assert Loser" State
+
+ Receive Join(S,G) on Interface I
+
+ We receive a Join(S,G) that has the Upstream Neighbor Address
+ field set to my primary IP address on interface I. The action is
+ to transition to NoInfo state, delete this (S,G) assert state
+ (Actions A5 below), and allow the normal PIM Join/Prune mechanisms
+ to operate.
+
+ Notice: The nonlocal_upstream() test above ensures the upstream
+ address of the join message is our primary address.
+ */
+ if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
+ char src_str[100];
+ char grp_str[100];
+ char neigh_str[100];
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<neigh?>", neigh_addr, neigh_str, sizeof(neigh_str));
+ zlog_warn("%s: Assert Loser recv Join(%s,%s) from %s on %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, neigh_str, ifp->name);
+
+ assert_action_a5(ch);
+ }
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ switch (ch->ifjoin_state) {
+ case PIM_IFJOIN_NOINFO:
+ pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN);
+ if (pim_macro_chisin_oiflist(ch)) {
+ pim_forward_start(ch);
+ }
+ break;
+ case PIM_IFJOIN_JOIN:
+ zassert(!ch->t_ifjoin_prune_pending_timer);
+
+ /*
+ In the JOIN state ch->t_ifjoin_expiry_timer may be NULL due to a
+ previously received join message with holdtime=0xFFFF.
+ */
+ if (ch->t_ifjoin_expiry_timer) {
+ unsigned long remain =
+ thread_timer_remain_second(ch->t_ifjoin_expiry_timer);
+ if (remain > holdtime) {
+ /*
+ RFC 4601: 4.5.3. Receiving (S,G) Join/Prune Messages
+
+ Transitions from Join State
+
+ The (S,G) downstream state machine on interface I remains in
+ Join state, and the Expiry Timer (ET) is restarted, set to
+ maximum of its current value and the HoldTime from the
+ triggering Join/Prune message.
+
+ Conclusion: Do not change the ET if the current value is
+ higher than the received join holdtime.
+ */
+ return;
+ }
+ }
+ THREAD_OFF(ch->t_ifjoin_expiry_timer);
+ break;
+ case PIM_IFJOIN_PRUNE_PENDING:
+ zassert(!ch->t_ifjoin_expiry_timer);
+ zassert(ch->t_ifjoin_prune_pending_timer);
+ THREAD_OFF(ch->t_ifjoin_prune_pending_timer);
+ pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_JOIN);
+ break;
+ }
+
+ zassert(!IFCHANNEL_NOINFO(ch));
+
+ if (holdtime != 0xFFFF) {
+ THREAD_TIMER_ON(master, ch->t_ifjoin_expiry_timer,
+ on_ifjoin_expiry_timer,
+ ch, holdtime);
+ }
+}
+
+void pim_ifchannel_prune(struct interface *ifp,
+ struct in_addr upstream,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ uint8_t source_flags,
+ uint16_t holdtime)
+{
+ struct pim_ifchannel *ch;
+ int jp_override_interval_msec;
+
+ if (nonlocal_upstream(0 /* prune */, ifp, upstream,
+ source_addr, group_addr, source_flags, holdtime)) {
+ return;
+ }
+
+ ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+ if (!ch)
+ return;
+
+ switch (ch->ifjoin_state) {
+ case PIM_IFJOIN_NOINFO:
+ case PIM_IFJOIN_PRUNE_PENDING:
+ /* nothing to do */
+ break;
+ case PIM_IFJOIN_JOIN:
+ {
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ifp->info;
+
+ zassert(ch->t_ifjoin_expiry_timer);
+ zassert(!ch->t_ifjoin_prune_pending_timer);
+
+ THREAD_OFF(ch->t_ifjoin_expiry_timer);
+
+ pim_ifchannel_ifjoin_switch(__PRETTY_FUNCTION__, ch, PIM_IFJOIN_PRUNE_PENDING);
+
+ if (listcount(pim_ifp->pim_neighbor_list) > 1) {
+ jp_override_interval_msec = pim_if_jp_override_interval_msec(ifp);
+ }
+ else {
+ jp_override_interval_msec = 0; /* schedule to expire immediately */
+ /* If we called ifjoin_prune() directly instead, care should
+ be taken not to use "ch" afterwards since it would be
+ deleted. */
+ }
+
+ THREAD_TIMER_MSEC_ON(master, ch->t_ifjoin_prune_pending_timer,
+ on_ifjoin_prune_pending_timer,
+ ch, jp_override_interval_msec);
+
+ zassert(!ch->t_ifjoin_expiry_timer);
+ zassert(ch->t_ifjoin_prune_pending_timer);
+ }
+ break;
+ }
+
+}
+
+void pim_ifchannel_local_membership_add(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_ifchannel *ch;
+ struct pim_interface *pim_ifp;
+
+ /* PIM enabled on interface? */
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return;
+ if (!PIM_IF_TEST_PIM(pim_ifp->options))
+ return;
+
+ ch = pim_ifchannel_add(ifp, source_addr, group_addr);
+ if (!ch) {
+ return;
+ }
+
+ ifmembership_set(ch, PIM_IFMEMBERSHIP_INCLUDE);
+
+ zassert(!IFCHANNEL_NOINFO(ch));
+}
+
+void pim_ifchannel_local_membership_del(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_ifchannel *ch;
+ struct pim_interface *pim_ifp;
+
+ /* PIM enabled on interface? */
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ return;
+ if (!PIM_IF_TEST_PIM(pim_ifp->options))
+ return;
+
+ ch = pim_ifchannel_find(ifp, source_addr, group_addr);
+ if (!ch)
+ return;
+
+ ifmembership_set(ch, PIM_IFMEMBERSHIP_NOINFO);
+
+ delete_on_noinfo(ch);
+}
+
+void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch)
+{
+ int old_couldassert = PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags));
+ int new_couldassert = PIM_FORCE_BOOLEAN(pim_macro_ch_could_assert_eval(ch));
+
+ if (new_couldassert == old_couldassert)
+ return;
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: CouldAssert(%s,%s,%s) changed from %d to %d",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name,
+ old_couldassert, new_couldassert);
+ }
+
+ if (new_couldassert) {
+ /* CouldAssert(S,G,I) switched from FALSE to TRUE */
+ PIM_IF_FLAG_SET_COULD_ASSERT(ch->flags);
+ }
+ else {
+ /* CouldAssert(S,G,I) switched from TRUE to FALSE */
+ PIM_IF_FLAG_UNSET_COULD_ASSERT(ch->flags);
+
+ if (ch->ifassert_state == PIM_IFASSERT_I_AM_WINNER) {
+ assert_action_a4(ch);
+ }
+ }
+
+ pim_ifchannel_update_my_assert_metric(ch);
+}
+
+/*
+ my_assert_metric may be affected by:
+
+ CouldAssert(S,G)
+ pim_ifp->primary_address
+ rpf->source_nexthop.mrib_metric_preference;
+ rpf->source_nexthop.mrib_route_metric;
+ */
+void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch)
+{
+ struct pim_assert_metric my_metric_new = pim_macro_ch_my_assert_metric_eval(ch);
+
+ if (pim_assert_metric_match(&my_metric_new, &ch->ifassert_my_metric))
+ return;
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ char old_addr_str[100];
+ char new_addr_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<old_addr?>", ch->ifassert_my_metric.ip_address, old_addr_str, sizeof(old_addr_str));
+ pim_inet4_dump("<new_addr?>", my_metric_new.ip_address, new_addr_str, sizeof(new_addr_str));
+ zlog_debug("%s: my_assert_metric(%s,%s,%s) changed from %u,%u,%u,%s to %u,%u,%u,%s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name,
+ ch->ifassert_my_metric.rpt_bit_flag,
+ ch->ifassert_my_metric.metric_preference,
+ ch->ifassert_my_metric.route_metric,
+ old_addr_str,
+ my_metric_new.rpt_bit_flag,
+ my_metric_new.metric_preference,
+ my_metric_new.route_metric,
+ new_addr_str);
+ }
+
+ ch->ifassert_my_metric = my_metric_new;
+
+ if (pim_assert_metric_better(&ch->ifassert_my_metric,
+ &ch->ifassert_winner_metric)) {
+ assert_action_a5(ch);
+ }
+}
+
+void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch)
+{
+ int old_atd = PIM_FORCE_BOOLEAN(PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(ch->flags));
+ int new_atd = PIM_FORCE_BOOLEAN(pim_macro_assert_tracking_desired_eval(ch));
+
+ if (new_atd == old_atd)
+ return;
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: AssertTrackingDesired(%s,%s,%s) changed from %d to %d",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name,
+ old_atd, new_atd);
+ }
+
+ if (new_atd) {
+ /* AssertTrackingDesired(S,G,I) switched from FALSE to TRUE */
+ PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(ch->flags);
+ }
+ else {
+ /* AssertTrackingDesired(S,G,I) switched from TRUE to FALSE */
+ PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(ch->flags);
+
+ if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
+ assert_action_a5(ch);
+ }
+ }
+}
diff --git a/pimd/pim_ifchannel.h b/pimd/pim_ifchannel.h
new file mode 100644
index 00000000..e6f1c294
--- /dev/null
+++ b/pimd/pim_ifchannel.h
@@ -0,0 +1,145 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_IFCHANNEL_H
+#define PIM_IFCHANNEL_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_upstream.h"
+
+enum pim_ifmembership {
+ PIM_IFMEMBERSHIP_NOINFO,
+ PIM_IFMEMBERSHIP_INCLUDE
+};
+
+enum pim_ifjoin_state {
+ PIM_IFJOIN_NOINFO,
+ PIM_IFJOIN_JOIN,
+ PIM_IFJOIN_PRUNE_PENDING
+};
+
+enum pim_ifassert_state {
+ PIM_IFASSERT_NOINFO,
+ PIM_IFASSERT_I_AM_WINNER,
+ PIM_IFASSERT_I_AM_LOSER
+};
+
+struct pim_assert_metric {
+ uint32_t rpt_bit_flag;
+ uint32_t metric_preference;
+ uint32_t route_metric;
+ struct in_addr ip_address; /* neighbor router that sourced the Assert message */
+};
+
+/*
+ Flag to detect change in CouldAssert(S,G,I)
+*/
+#define PIM_IF_FLAG_MASK_COULD_ASSERT (1 << 0)
+#define PIM_IF_FLAG_TEST_COULD_ASSERT(flags) ((flags) & PIM_IF_FLAG_MASK_COULD_ASSERT)
+#define PIM_IF_FLAG_SET_COULD_ASSERT(flags) ((flags) |= PIM_IF_FLAG_MASK_COULD_ASSERT)
+#define PIM_IF_FLAG_UNSET_COULD_ASSERT(flags) ((flags) &= ~PIM_IF_FLAG_MASK_COULD_ASSERT)
+/*
+ Flag to detect change in AssertTrackingDesired(S,G,I)
+*/
+#define PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED (1 << 1)
+#define PIM_IF_FLAG_TEST_ASSERT_TRACKING_DESIRED(flags) ((flags) & PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED)
+#define PIM_IF_FLAG_SET_ASSERT_TRACKING_DESIRED(flags) ((flags) |= PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED)
+#define PIM_IF_FLAG_UNSET_ASSERT_TRACKING_DESIRED(flags) ((flags) &= ~PIM_IF_FLAG_MASK_ASSERT_TRACKING_DESIRED)
+
+/*
+ Per-interface (S,G) state
+*/
+struct pim_ifchannel {
+ struct in_addr source_addr; /* (S,G) source key */
+ struct in_addr group_addr; /* (S,G) group key */
+ struct interface *interface; /* backpointer to interface */
+ uint32_t flags;
+
+ /* IGMPv3 determined interface has local members for (S,G) ? */
+ enum pim_ifmembership local_ifmembership;
+
+ /* Per-interface (S,G) Join/Prune State (Section 4.1.4 of RFC4601) */
+ enum pim_ifjoin_state ifjoin_state;
+ struct thread *t_ifjoin_expiry_timer;
+ struct thread *t_ifjoin_prune_pending_timer;
+ int64_t ifjoin_creation; /* Record uptime of ifjoin state */
+
+ /* Per-interface (S,G) Assert State (Section 4.6.1 of RFC4601) */
+ enum pim_ifassert_state ifassert_state;
+ struct thread *t_ifassert_timer;
+ struct in_addr ifassert_winner;
+ struct pim_assert_metric ifassert_winner_metric;
+ int64_t ifassert_creation; /* Record uptime of ifassert state */
+ struct pim_assert_metric ifassert_my_metric;
+
+ /* Upstream (S,G) state */
+ struct pim_upstream *upstream;
+};
+
+void pim_ifchannel_free(struct pim_ifchannel *ch);
+void pim_ifchannel_delete(struct pim_ifchannel *ch);
+void pim_ifchannel_membership_clear(struct interface *ifp);
+void pim_ifchannel_delete_on_noinfo(struct interface *ifp);
+struct pim_ifchannel *pim_ifchannel_find(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr);
+struct pim_ifchannel *pim_ifchannel_add(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr);
+void pim_ifchannel_join_add(struct interface *ifp,
+ struct in_addr neigh_addr,
+ struct in_addr upstream,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ uint8_t source_flags,
+ uint16_t holdtime);
+void pim_ifchannel_prune(struct interface *ifp,
+ struct in_addr upstream,
+ struct in_addr source_addr,
+ struct in_addr group_addr,
+ uint8_t source_flags,
+ uint16_t holdtime);
+void pim_ifchannel_local_membership_add(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr);
+void pim_ifchannel_local_membership_del(struct interface *ifp,
+ struct in_addr source_addr,
+ struct in_addr group_addr);
+
+void pim_ifchannel_ifjoin_switch(const char *caller,
+ struct pim_ifchannel *ch,
+ enum pim_ifjoin_state new_state);
+const char *pim_ifchannel_ifjoin_name(enum pim_ifjoin_state ifjoin_state);
+const char *pim_ifchannel_ifassert_name(enum pim_ifassert_state ifassert_state);
+
+int pim_ifchannel_isin_oiflist(struct pim_ifchannel *ch);
+
+void reset_ifassert_state(struct pim_ifchannel *ch);
+
+void pim_ifchannel_update_could_assert(struct pim_ifchannel *ch);
+void pim_ifchannel_update_my_assert_metric(struct pim_ifchannel *ch);
+void pim_ifchannel_update_assert_tracking_desired(struct pim_ifchannel *ch);
+
+#endif /* PIM_IFCHANNEL_H */
diff --git a/pimd/pim_igmp.c b/pimd/pim_igmp.c
new file mode 100644
index 00000000..4fd3edcb
--- /dev/null
+++ b/pimd/pim_igmp.c
@@ -0,0 +1,1433 @@
+/*
+ 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 "memory.h"
+
+#include "pimd.h"
+#include "pim_igmp.h"
+#include "pim_igmpv3.h"
+#include "pim_iface.h"
+#include "pim_sock.h"
+#include "pim_mroute.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#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);
+
+static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
+ struct in_addr group_addr);
+
+static int igmp_sock_open(struct in_addr ifaddr, int ifindex, uint32_t pim_options)
+{
+ int fd;
+ int join = 0;
+ struct in_addr group;
+
+ fd = pim_socket_mcast(IPPROTO_IGMP, ifaddr, 1 /* loop=true */);
+ 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;
+ }
+ 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));
+ }
+ }
+
+ /*
+ IGMP routers periodically send IGMP general queries to AllSystems=224.0.0.1
+ IGMP routers must receive general queries for querier election.
+ */
+ if (inet_aton(PIM_ALL_SYSTEMS, &group)) {
+ if (!pim_socket_join(fd, group, ifaddr, ifindex))
+ ++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_SYSTEMS, errno, safe_strerror(errno));
+ }
+
+ if (inet_aton(PIM_ALL_IGMP_ROUTERS, &group)) {
+ if (!pim_socket_join(fd, group, ifaddr, ifindex)) {
+ ++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_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));
+ close(fd);
+ fd = -1;
+ }
+
+ return fd;
+}
+
+#undef IGMP_SOCK_DUMP
+
+#ifdef IGMP_SOCK_DUMP
+static void igmp_sock_dump(array_t *igmp_sock_array)
+{
+ int size = array_size(igmp_sock_array);
+ for (int i = 0; i < size; ++i) {
+
+ struct igmp_sock *igmp = array_get(igmp_sock_array, i);
+
+ zlog_debug("%s %s: [%d/%d] igmp_addr=%s fd=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ i, size,
+ inet_ntoa(igmp->ifaddr),
+ igmp->fd);
+ }
+}
+#endif
+
+struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
+ struct in_addr ifaddr)
+{
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+#ifdef IGMP_SOCK_DUMP
+ igmp_sock_dump(igmp_sock_list);
+#endif
+
+ for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
+ if (ifaddr.s_addr == igmp->ifaddr.s_addr)
+ return igmp;
+
+ return 0;
+}
+
+struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list,
+ int fd)
+{
+ struct listnode *sock_node;
+ struct igmp_sock *igmp;
+
+ for (ALL_LIST_ELEMENTS_RO(igmp_sock_list, sock_node, igmp))
+ if (fd == igmp->fd)
+ return igmp;
+
+ return 0;
+}
+
+static int pim_igmp_other_querier_expire(struct thread *t)
+{
+ struct igmp_sock *igmp;
+
+ zassert(t);
+ igmp = THREAD_ARG(t);
+ zassert(igmp);
+
+ zassert(igmp->t_other_querier_timer);
+ zassert(!igmp->t_igmp_query_timer);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char ifaddr_str[100];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+ zlog_debug("%s: Querier %s resuming",
+ __PRETTY_FUNCTION__,
+ ifaddr_str);
+ }
+
+ igmp->t_other_querier_timer = 0;
+
+ /*
+ We are the current querier, then
+ re-start sending general queries.
+ */
+ pim_igmp_general_query_on(igmp);
+
+ return 0;
+}
+
+void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp)
+{
+ long other_querier_present_interval_msec;
+ struct pim_interface *pim_ifp;
+
+ zassert(igmp);
+ zassert(igmp->interface);
+ zassert(igmp->interface->info);
+
+ pim_ifp = igmp->interface->info;
+
+ if (igmp->t_other_querier_timer) {
+ /*
+ There is other querier present already,
+ then reset the other-querier-present timer.
+ */
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char ifaddr_str[100];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+ zlog_debug("Querier %s resetting TIMER event for Other-Querier-Present",
+ ifaddr_str);
+ }
+
+ THREAD_OFF(igmp->t_other_querier_timer);
+ zassert(!igmp->t_other_querier_timer);
+ }
+ else {
+ /*
+ We are the current querier, then stop sending general queries:
+ igmp->t_igmp_query_timer = 0;
+ */
+ pim_igmp_general_query_off(igmp);
+ }
+
+ /*
+ Since this socket is starting the other-querier-present timer,
+ there should not be periodic query timer for this socket.
+ */
+ zassert(!igmp->t_igmp_query_timer);
+
+ /*
+ RFC 3376: 8.5. Other Querier Present Interval
+
+ The Other Querier Present Interval is the length of time that must
+ pass before a multicast router decides that there is no longer
+ another multicast router which should be the querier. This value
+ MUST be ((the Robustness Variable) times (the Query Interval)) plus
+ (one half of one Query Response Interval).
+
+ other_querier_present_interval_msec = \
+ igmp->querier_robustness_variable * \
+ 1000 * igmp->querier_query_interval + \
+ 100 * (pim_ifp->query_max_response_time_dsec >> 1);
+ */
+ other_querier_present_interval_msec =
+ PIM_IGMP_OQPI_MSEC(igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ pim_ifp->igmp_query_max_response_time_dsec);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char ifaddr_str[100];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+ zlog_debug("Querier %s scheduling %ld.%03ld sec TIMER event for Other-Querier-Present",
+ ifaddr_str,
+ other_querier_present_interval_msec / 1000,
+ other_querier_present_interval_msec % 1000);
+ }
+
+ THREAD_TIMER_MSEC_ON(master, igmp->t_other_querier_timer,
+ pim_igmp_other_querier_expire,
+ igmp, other_querier_present_interval_msec);
+}
+
+void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp)
+{
+ zassert(igmp);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ if (igmp->t_other_querier_timer) {
+ char ifaddr_str[100];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+ zlog_debug("IGMP querier %s fd=%d cancelling other-querier-present TIMER event on %s",
+ ifaddr_str, igmp->fd, igmp->interface->name);
+ }
+ }
+ THREAD_OFF(igmp->t_other_querier_timer);
+ 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)
+{
+ 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);
+
+ /* 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 query v%d from %s on %s: checksum mismatch: received=%x computed=%x",
+ query_version, from_str, ifp->name, recv_checksum, checksum);
+ return -1;
+ }
+
+ 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);
+ }
+
+ /*
+ RFC 3376: 6.6.2. Querier Election
+
+ When a router receives a query with a lower IP address, it sets
+ the Other-Querier-Present timer to Other Querier Present Interval
+ and ceases to send queries on the network if it was the previously
+ 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));
+ zlog_debug("%s: local address %s (%u) lost querier election to %s (%u)",
+ ifp->name,
+ ifaddr_str, ntohl(igmp->ifaddr.s_addr),
+ from_str, ntohl(from.s_addr));
+ }
+
+ pim_igmp_other_querier_timer_on(igmp);
+ }
+
+ 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;
+
+ 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;
+ }
+
+ 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;
+
+ 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) */
+
+ 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);
+
+ } /* for (group records) */
+
+ return 0;
+}
+
+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);
+ }
+}
+
+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, ifp->name);
+ 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)
+{
+ 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 v1 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, ifp->name);
+ if (!group) {
+ return -1;
+ }
+
+ group->last_igmp_v1_report_dsec = pim_time_monotonic_dsec();
+
+ return 0;
+}
+
+int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len)
+{
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ char *igmp_msg;
+ int igmp_msg_len;
+ int msg_type;
+ char from_str[100];
+ char to_str[100];
+
+ if (len < sizeof(*ip_hdr)) {
+ zlog_warn("IGMP packet size=%zu shorter than minimum=%zu",
+ len, sizeof(*ip_hdr));
+ return -1;
+ }
+
+ ip_hdr = (struct ip *) buf;
+
+ pim_inet4_dump("<src?>", ip_hdr->ip_src, from_str , sizeof(from_str));
+ pim_inet4_dump("<dst?>", ip_hdr->ip_dst, to_str , sizeof(to_str));
+
+ ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
+
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ zlog_debug("Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d",
+ from_str, to_str, igmp->interface->name, len, ip_hlen, ip_hdr->ip_p);
+ }
+
+ if (ip_hdr->ip_p != PIM_IP_PROTO_IGMP) {
+ zlog_warn("IP packet protocol=%d is not IGMP=%d",
+ ip_hdr->ip_p, PIM_IP_PROTO_IGMP);
+ return -1;
+ }
+
+ if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
+ zlog_warn("IP packet header size=%zu shorter than minimum=%d",
+ ip_hlen, PIM_IP_HEADER_MIN_LEN);
+ return -1;
+ }
+ if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
+ zlog_warn("IP packet header size=%zu greater than maximum=%d",
+ ip_hlen, PIM_IP_HEADER_MAX_LEN);
+ return -1;
+ }
+
+ igmp_msg = buf + ip_hlen;
+ msg_type = *igmp_msg;
+ igmp_msg_len = len - ip_hlen;
+
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ zlog_debug("Recv IGMP packet from %s to %s on %s: ttl=%d msg_type=%d msg_size=%d",
+ from_str, to_str, igmp->interface->name, ip_hdr->ip_ttl, msg_type,
+ igmp_msg_len);
+ }
+
+ if (igmp_msg_len < PIM_IGMP_MIN_LEN) {
+ zlog_warn("IGMP message size=%d shorter than minimum=%d",
+ igmp_msg_len, PIM_IGMP_MIN_LEN);
+ return -1;
+ }
+
+ switch (msg_type) {
+ case PIM_IGMP_MEMBERSHIP_QUERY:
+ {
+ int max_resp_code = igmp_msg[1];
+ int query_version;
+
+ /*
+ RFC 3376: 7.1. Query Version Distinctions
+ IGMPv1 Query: length = 8 octets AND Max Resp Code field is zero
+ IGMPv2 Query: length = 8 octets AND Max Resp Code field is non-zero
+ IGMPv3 Query: length >= 12 octets
+ */
+
+ if (igmp_msg_len == 8) {
+ query_version = max_resp_code ? 2 : 1;
+ }
+ else if (igmp_msg_len >= 12) {
+ query_version = 3;
+ }
+ else {
+ zlog_warn("Unknown IGMP query version");
+ return -1;
+ }
+
+ return recv_igmp_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);
+
+ case PIM_IGMP_V2_MEMBERSHIP_REPORT:
+ return igmp_v2_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);
+
+ case PIM_IGMP_V2_LEAVE_GROUP:
+ return igmp_v2_leave(igmp, ip_hdr->ip_src, from_str,
+ igmp_msg, igmp_msg_len);
+ }
+
+ zlog_warn("Ignoring unsupported IGMP message type: %d", msg_type);
+
+ return -1;
+}
+
+static int pim_igmp_general_query(struct thread *t);
+
+void pim_igmp_general_query_on(struct igmp_sock *igmp)
+{
+ struct pim_interface *pim_ifp;
+ int startup_mode;
+ int query_interval;
+
+ zassert(igmp);
+ zassert(igmp->interface);
+
+ /*
+ Since this socket is starting as querier,
+ there should not exist a timer for other-querier-present.
+ */
+ zassert(!igmp->t_other_querier_timer);
+ pim_ifp = igmp->interface->info;
+ zassert(pim_ifp);
+
+ /*
+ RFC 3376: 8.6. Startup Query Interval
+
+ The Startup Query Interval is the interval between General Queries
+ sent by a Querier on startup. Default: 1/4 the Query Interval.
+ */
+ startup_mode = igmp->startup_query_count > 0;
+ if (startup_mode) {
+ --igmp->startup_query_count;
+
+ /* query_interval = pim_ifp->igmp_default_query_interval >> 2; */
+ query_interval = PIM_IGMP_SQI(pim_ifp->igmp_default_query_interval);
+ }
+ else {
+ query_interval = igmp->querier_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 scheduling %d-second (%s) TIMER event for IGMP query on fd=%d",
+ ifaddr_str,
+ query_interval,
+ startup_mode ? "startup" : "non-startup",
+ igmp->fd);
+ }
+ igmp->t_igmp_query_timer = 0;
+ zassert(!igmp->t_igmp_query_timer);
+ THREAD_TIMER_ON(master, igmp->t_igmp_query_timer,
+ pim_igmp_general_query,
+ igmp, query_interval);
+}
+
+void pim_igmp_general_query_off(struct igmp_sock *igmp)
+{
+ zassert(igmp);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ if (igmp->t_igmp_query_timer) {
+ char ifaddr_str[100];
+ pim_inet4_dump("<ifaddr?>", igmp->ifaddr, ifaddr_str, sizeof(ifaddr_str));
+ zlog_debug("IGMP querier %s fd=%d cancelling query TIMER event on %s",
+ ifaddr_str, igmp->fd, igmp->interface->name);
+ }
+ }
+ THREAD_OFF(igmp->t_igmp_query_timer);
+ zassert(!igmp->t_igmp_query_timer);
+}
+
+/* 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;
+
+ zassert(t);
+
+ igmp = THREAD_ARG(t);
+
+ zassert(igmp);
+ zassert(igmp->interface);
+ zassert(igmp->interface->info);
+
+ pim_ifp = igmp->interface->info;
+
+ /*
+ RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+ In IGMPv3, General Queries are sent with an IP destination address
+ of 224.0.0.1, the all-systems multicast address. Group-Specific
+ and Group-and-Source-Specific Queries are sent with an IP
+ destination address equal to the multicast address of interest.
+ */
+
+ dst_addr.s_addr = htonl(INADDR_ALLHOSTS_GROUP);
+ group_addr.s_addr = PIM_NET_INADDR_ANY;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char querier_str[100];
+ char dst_str[100];
+ pim_inet4_dump("<querier?>", igmp->ifaddr, querier_str,
+ sizeof(querier_str));
+ pim_inet4_dump("<dst?>", dst_addr, dst_str, sizeof(dst_str));
+ zlog_debug("Querier %s issuing IGMP general query to %s on %s",
+ 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);
+
+ 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) {
+ zlog_debug("Scheduling READ event on IGMP socket fd=%d",
+ igmp->fd);
+ }
+ igmp->t_igmp_read = 0;
+ 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;
+ int 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 != (int) 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 == (int) 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);
+ pim_igmp_general_query_off(igmp);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ if (igmp->t_igmp_read) {
+ zlog_debug("Cancelling READ event on IGMP socket %s fd=%d on interface %s",
+ inet_ntoa(igmp->ifaddr), igmp->fd,
+ igmp->interface->name);
+ }
+ }
+ THREAD_OFF(igmp->t_igmp_read);
+ zassert(!igmp->t_igmp_read);
+
+ if (close(igmp->fd)) {
+ zlog_err("Failure closing IGMP socket %s fd=%d on interface %s: errno=%d: %s",
+ inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name,
+ errno, safe_strerror(errno));
+ }
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ zlog_debug("Deleted IGMP socket %s fd=%d on interface %s",
+ inet_ntoa(igmp->ifaddr), igmp->fd, igmp->interface->name);
+ }
+}
+
+void igmp_startup_mode_on(struct igmp_sock *igmp)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = igmp->interface->info;
+
+ /*
+ RFC 3376: 8.7. Startup Query Count
+
+ The Startup Query Count is the number of Queries sent out on
+ startup, separated by the Startup Query Interval. Default: the
+ Robustness Variable.
+ */
+ igmp->startup_query_count = igmp->querier_robustness_variable;
+
+ /*
+ Since we're (re)starting, reset QQI to default Query Interval
+ */
+ igmp->querier_query_interval = pim_ifp->igmp_default_query_interval;
+}
+
+static void igmp_group_free(struct igmp_group *group)
+{
+ zassert(!group->t_group_query_retransmit_timer);
+ zassert(!group->t_group_timer);
+ zassert(group->group_source_list);
+ zassert(!listcount(group->group_source_list));
+
+ list_free(group->group_source_list);
+
+ XFREE(MTYPE_PIM_IGMP_GROUP, group);
+}
+
+static void igmp_group_delete(struct igmp_group *group)
+{
+ struct listnode *src_node;
+ struct listnode *src_nextnode;
+ struct igmp_source *src;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("Deleting IGMP group %s from socket %d interface %s",
+ group_str,
+ group->group_igmp_sock->fd,
+ group->group_igmp_sock->interface->name);
+ }
+
+ for (ALL_LIST_ELEMENTS(group->group_source_list, src_node, src_nextnode, src)) {
+ igmp_source_delete(src);
+ }
+
+ if (group->t_group_query_retransmit_timer) {
+ THREAD_OFF(group->t_group_query_retransmit_timer);
+ zassert(!group->t_group_query_retransmit_timer);
+ }
+
+ group_timer_off(group);
+ listnode_delete(group->group_igmp_sock->igmp_group_list, group);
+ igmp_group_free(group);
+}
+
+void igmp_group_delete_empty_include(struct igmp_group *group)
+{
+ zassert(!group->group_filtermode_isexcl);
+ zassert(!listcount(group->group_source_list));
+
+ igmp_group_delete(group);
+}
+
+void igmp_sock_free(struct igmp_sock *igmp)
+{
+ zassert(!igmp->t_igmp_read);
+ zassert(!igmp->t_igmp_query_timer);
+ zassert(!igmp->t_other_querier_timer);
+ zassert(igmp->igmp_group_list);
+ zassert(!listcount(igmp->igmp_group_list));
+
+ list_free(igmp->igmp_group_list);
+
+ XFREE(MTYPE_PIM_IGMP_SOCKET, igmp);
+}
+
+void igmp_sock_delete(struct igmp_sock *igmp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *grp_node;
+ struct listnode *grp_nextnode;
+ struct igmp_group *grp;
+
+ for (ALL_LIST_ELEMENTS(igmp->igmp_group_list, grp_node, grp_nextnode, grp)) {
+ igmp_group_delete(grp);
+ }
+
+ sock_close(igmp);
+
+ pim_ifp = igmp->interface->info;
+
+ listnode_delete(pim_ifp->igmp_socket_list, igmp);
+
+ igmp_sock_free(igmp);
+}
+
+static struct igmp_sock *igmp_sock_new(int fd,
+ struct in_addr ifaddr,
+ struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct igmp_sock *igmp;
+
+ pim_ifp = ifp->info;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ zlog_debug("Creating IGMP socket fd=%d for address %s on interface %s",
+ fd, inet_ntoa(ifaddr), ifp->name);
+ }
+
+ igmp = XMALLOC(MTYPE_PIM_IGMP_SOCKET, sizeof(*igmp));
+ if (!igmp) {
+ zlog_warn("%s %s: XMALLOC() failure",
+ __FILE__, __PRETTY_FUNCTION__);
+ return 0;
+ }
+
+ igmp->igmp_group_list = list_new();
+ if (!igmp->igmp_group_list) {
+ zlog_err("%s %s: failure: igmp_group_list = list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return 0;
+ }
+ igmp->igmp_group_list->del = (void (*)(void *)) igmp_group_free;
+
+ igmp->fd = fd;
+ igmp->interface = ifp;
+ igmp->ifaddr = ifaddr;
+ igmp->t_igmp_read = 0;
+ igmp->t_igmp_query_timer = 0;
+ igmp->t_other_querier_timer = 0; /* no other querier present */
+ igmp->querier_robustness_variable = pim_ifp->igmp_default_robustness_variable;
+ igmp->sock_creation = pim_time_monotonic_sec();
+
+ /*
+ igmp_startup_mode_on() will reset QQI:
+
+ 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;
+}
+
+struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
+ struct in_addr ifaddr,
+ struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct igmp_sock *igmp;
+ int fd;
+
+ pim_ifp = ifp->info;
+
+ fd = igmp_sock_open(ifaddr, ifp->ifindex, pim_ifp->options);
+ if (fd < 0) {
+ zlog_warn("Could not open IGMP socket for %s on %s",
+ inet_ntoa(ifaddr), ifp->name);
+ return 0;
+ }
+
+ igmp = igmp_sock_new(fd, ifaddr, ifp);
+ if (!igmp) {
+ zlog_err("%s %s: igmp_sock_new() failure",
+ __FILE__, __PRETTY_FUNCTION__);
+ close(fd);
+ return 0;
+ }
+
+ listnode_add(igmp_sock_list, igmp);
+
+#ifdef IGMP_SOCK_DUMP
+ igmp_sock_dump(igmp_sock_array);
+#endif
+
+ return igmp;
+}
+
+/*
+ RFC 3376: 6.5. Switching Router Filter-Modes
+
+ When a router's filter-mode for a group is EXCLUDE and the group
+ timer expires, the router filter-mode for the group transitions to
+ INCLUDE.
+
+ A router uses source records with running source timers as its state
+ for the switch to a filter-mode of INCLUDE. If there are any source
+ records with source timers greater than zero (i.e., requested to be
+ forwarded), a router switches to filter-mode of INCLUDE using those
+ source records. Source records whose timers are zero (from the
+ previous EXCLUDE mode) are deleted.
+ */
+static int igmp_group_timer(struct thread *t)
+{
+ struct igmp_group *group;
+
+ zassert(t);
+ group = THREAD_ARG(t);
+ zassert(group);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: Timer for group %s on interface %s",
+ __PRETTY_FUNCTION__,
+ group_str, group->group_igmp_sock->interface->name);
+ }
+
+ zassert(group->group_filtermode_isexcl);
+
+ group->t_group_timer = 0;
+ group->group_filtermode_isexcl = 0;
+
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ igmp_anysource_forward_stop(group);
+
+ igmp_source_delete_expired(group->group_source_list);
+
+ zassert(!group->t_group_timer);
+ zassert(!group->group_filtermode_isexcl);
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ If there are no more source records for the group, delete group
+ record.
+ */
+ if (listcount(group->group_source_list) < 1) {
+ igmp_group_delete_empty_include(group);
+ }
+
+ return 0;
+}
+
+static void group_timer_off(struct igmp_group *group)
+{
+ if (!group->t_group_timer)
+ return;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("Cancelling TIMER event for group %s on %s",
+ group_str, group->group_igmp_sock->interface->name);
+ }
+
+ THREAD_OFF(group->t_group_timer);
+ zassert(!group->t_group_timer);
+}
+
+void igmp_group_timer_on(struct igmp_group *group,
+ long interval_msec, const char *ifname)
+{
+ group_timer_off(group);
+
+ if (PIM_DEBUG_IGMP_EVENTS) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s on %s",
+ interval_msec / 1000,
+ interval_msec % 1000,
+ group_str, ifname);
+ }
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and
+ it represents the time for the *filter-mode* of the group to
+ expire and switch to INCLUDE mode.
+ */
+ zassert(group->group_filtermode_isexcl);
+
+ THREAD_TIMER_MSEC_ON(master, group->t_group_timer,
+ igmp_group_timer,
+ group, interval_msec);
+}
+
+static struct igmp_group *find_group_by_addr(struct igmp_sock *igmp,
+ struct in_addr group_addr)
+{
+ struct igmp_group *group;
+ struct listnode *node;
+
+ for (ALL_LIST_ELEMENTS_RO(igmp->igmp_group_list, node, group))
+ if (group_addr.s_addr == group->group_addr.s_addr)
+ return group;
+
+ return 0;
+}
+
+struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
+ struct in_addr group_addr,
+ const char *ifname)
+{
+ struct igmp_group *group;
+
+ group = find_group_by_addr(igmp, group_addr);
+ if (group) {
+ return group;
+ }
+
+ /*
+ Non-existant group is created as INCLUDE {empty}:
+
+ RFC 3376 - 5.1. Action on Change of Interface State
+
+ If no interface state existed for that multicast address before
+ the change (i.e., the change consisted of creating a new
+ per-interface record), or if no state exists after the change
+ (i.e., the change consisted of deleting a per-interface record),
+ then the "non-existent" state is considered to have a filter mode
+ of INCLUDE and an empty source list.
+ */
+
+ group = XMALLOC(MTYPE_PIM_IGMP_GROUP, sizeof(*group));
+ if (!group) {
+ zlog_warn("%s %s: XMALLOC() failure",
+ __FILE__, __PRETTY_FUNCTION__);
+ return 0; /* error, not found, could not create */
+ }
+
+ group->group_source_list = list_new();
+ if (!group->group_source_list) {
+ zlog_warn("%s %s: list_new() failure",
+ __FILE__, __PRETTY_FUNCTION__);
+ XFREE(MTYPE_PIM_IGMP_GROUP, group); /* discard group */
+ return 0; /* error, not found, could not initialize */
+ }
+ group->group_source_list->del = (void (*)(void *)) igmp_source_free;
+
+ group->t_group_timer = 0;
+ group->t_group_query_retransmit_timer = 0;
+ group->group_specific_query_retransmit_count = 0;
+ group->group_addr = group_addr;
+ group->group_igmp_sock = igmp;
+ group->last_igmp_v1_report_dsec = -1;
+ group->last_igmp_v2_report_dsec = -1;
+ group->group_creation = pim_time_monotonic_sec();
+
+ /* initialize new group as INCLUDE {empty} */
+ group->group_filtermode_isexcl = 0; /* 0=INCLUDE, 1=EXCLUDE */
+
+ listnode_add(igmp->igmp_group_list, group);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("Creating new IGMP group %s on socket %d interface %s",
+ group_str, group->group_igmp_sock->fd, ifname);
+ }
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and
+ it represents the time for the *filter-mode* of the group to
+ expire and switch to INCLUDE mode.
+ */
+ zassert(!group->group_filtermode_isexcl); /* INCLUDE mode */
+ zassert(!group->t_group_timer); /* group timer == 0 */
+
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ igmp_anysource_forward_stop(group);
+
+ return group;
+}
diff --git a/pimd/pim_igmp.h b/pimd/pim_igmp.h
new file mode 100644
index 00000000..d45f223b
--- /dev/null
+++ b/pimd/pim_igmp.h
@@ -0,0 +1,176 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_IGMP_H
+#define PIM_IGMP_H
+
+#include <netinet/in.h>
+
+#include <zebra.h>
+#include "vty.h"
+#include "linklist.h"
+
+/*
+ The following sizes are likely to support
+ any message sent within local MTU.
+*/
+#define PIM_IGMP_BUFSIZE_READ (20000)
+#define PIM_IGMP_BUFSIZE_WRITE (20000)
+
+#define PIM_IGMP_MEMBERSHIP_QUERY (0x11)
+#define PIM_IGMP_V1_MEMBERSHIP_REPORT (0x12)
+#define PIM_IGMP_V2_MEMBERSHIP_REPORT (0x16)
+#define PIM_IGMP_V2_LEAVE_GROUP (0x17)
+#define PIM_IGMP_V3_MEMBERSHIP_REPORT (0x22)
+
+#define IGMP_V3_REPORT_HEADER_SIZE (8)
+#define IGMP_V3_GROUP_RECORD_MIN_SIZE (8)
+#define IGMP_V3_MSG_MIN_SIZE (IGMP_V3_REPORT_HEADER_SIZE + \
+ IGMP_V3_GROUP_RECORD_MIN_SIZE)
+#define IGMP_V12_MSG_SIZE (8)
+
+#define IGMP_V3_GROUP_RECORD_TYPE_OFFSET (0)
+#define IGMP_V3_GROUP_RECORD_AUXDATALEN_OFFSET (1)
+#define IGMP_V3_GROUP_RECORD_NUMSOURCES_OFFSET (2)
+#define IGMP_V3_GROUP_RECORD_GROUP_OFFSET (4)
+#define IGMP_V3_GROUP_RECORD_SOURCE_OFFSET (8)
+
+/* RFC 3376: 8.1. Robustness Variable - Default: 2 */
+#define IGMP_DEFAULT_ROBUSTNESS_VARIABLE (2)
+
+/* RFC 3376: 8.2. Query Interval - Default: 125 seconds */
+#define IGMP_GENERAL_QUERY_INTERVAL (125)
+
+/* RFC 3376: 8.3. Query Response Interval - Default: 100 deciseconds */
+#define IGMP_QUERY_MAX_RESPONSE_TIME_DSEC (100)
+
+/* RFC 3376: 8.8. Last Member Query Interval - Default: 10 deciseconds */
+#define IGMP_SPECIFIC_QUERY_MAX_RESPONSE_TIME_DSEC (10)
+
+struct igmp_join {
+ struct in_addr group_addr;
+ struct in_addr source_addr;
+ int sock_fd;
+ time_t sock_creation;
+};
+
+struct igmp_sock {
+ int fd;
+ struct interface *interface;
+ struct in_addr ifaddr;
+ time_t sock_creation;
+
+ struct thread *t_igmp_read; /* read: IGMP sockets */
+ struct thread *t_igmp_query_timer; /* timer: issue IGMP general queries */
+ struct thread *t_other_querier_timer; /* timer: other querier present */
+
+ int querier_query_interval; /* QQI */
+ int querier_robustness_variable; /* QRV */
+ int startup_query_count;
+
+ struct list *igmp_group_list; /* list of struct igmp_group */
+};
+
+struct igmp_sock *pim_igmp_sock_lookup_ifaddr(struct list *igmp_sock_list,
+ struct in_addr ifaddr);
+struct igmp_sock *igmp_sock_lookup_by_fd(struct list *igmp_sock_list,
+ int fd);
+struct igmp_sock *pim_igmp_sock_add(struct list *igmp_sock_list,
+ struct in_addr ifaddr,
+ struct interface *ifp);
+void igmp_sock_delete(struct igmp_sock *igmp);
+void igmp_sock_free(struct igmp_sock *igmp);
+
+int pim_igmp_packet(struct igmp_sock *igmp, char *buf, size_t len);
+
+void pim_igmp_general_query_on(struct igmp_sock *igmp);
+void pim_igmp_general_query_off(struct igmp_sock *igmp);
+void pim_igmp_other_querier_timer_on(struct igmp_sock *igmp);
+void pim_igmp_other_querier_timer_off(struct igmp_sock *igmp);
+
+#define IGMP_SOURCE_MASK_FORWARDING (1 << 0)
+#define IGMP_SOURCE_MASK_DELETE (1 << 1)
+#define IGMP_SOURCE_MASK_SEND (1 << 2)
+#define IGMP_SOURCE_TEST_FORWARDING(flags) ((flags) & IGMP_SOURCE_MASK_FORWARDING)
+#define IGMP_SOURCE_TEST_DELETE(flags) ((flags) & IGMP_SOURCE_MASK_DELETE)
+#define IGMP_SOURCE_TEST_SEND(flags) ((flags) & IGMP_SOURCE_MASK_SEND)
+#define IGMP_SOURCE_DO_FORWARDING(flags) ((flags) |= IGMP_SOURCE_MASK_FORWARDING)
+#define IGMP_SOURCE_DO_DELETE(flags) ((flags) |= IGMP_SOURCE_MASK_DELETE)
+#define IGMP_SOURCE_DO_SEND(flags) ((flags) |= IGMP_SOURCE_MASK_SEND)
+#define IGMP_SOURCE_DONT_FORWARDING(flags) ((flags) &= ~IGMP_SOURCE_MASK_FORWARDING)
+#define IGMP_SOURCE_DONT_DELETE(flags) ((flags) &= ~IGMP_SOURCE_MASK_DELETE)
+#define IGMP_SOURCE_DONT_SEND(flags) ((flags) &= ~IGMP_SOURCE_MASK_SEND)
+
+struct igmp_source {
+ struct in_addr source_addr;
+ struct thread *t_source_timer;
+ struct igmp_group *source_group; /* back pointer */
+ time_t source_creation;
+ uint32_t source_flags;
+ struct channel_oil *source_channel_oil;
+
+ /*
+ RFC 3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+ */
+ int source_query_retransmit_count;
+};
+
+struct igmp_group {
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and it
+ represents the time for the *filter-mode* of the group to expire and
+ switch to INCLUDE mode.
+ */
+ struct thread *t_group_timer;
+
+ /* Shared between group-specific and
+ group-and-source-specific retransmissions */
+ struct thread *t_group_query_retransmit_timer;
+
+ /* Counter exclusive for group-specific retransmissions
+ (not used by group-and-source-specific retransmissions,
+ since sources have their counters) */
+ int group_specific_query_retransmit_count;
+
+ struct in_addr group_addr;
+ int group_filtermode_isexcl; /* 0=INCLUDE, 1=EXCLUDE */
+ struct list *group_source_list; /* list of struct igmp_source */
+ time_t group_creation;
+ struct igmp_sock *group_igmp_sock; /* back pointer */
+ int64_t last_igmp_v1_report_dsec;
+ int64_t last_igmp_v2_report_dsec;
+};
+
+struct igmp_group *igmp_add_group_by_addr(struct igmp_sock *igmp,
+ struct in_addr group_addr,
+ const char *ifname);
+
+void igmp_group_delete_empty_include(struct igmp_group *group);
+
+void igmp_startup_mode_on(struct igmp_sock *igmp);
+
+void igmp_group_timer_on(struct igmp_group *group,
+ long interval_msec, const char *ifname);
+
+#endif /* PIM_IGMP_H */
diff --git a/pimd/pim_igmp_join.c b/pimd/pim_igmp_join.c
new file mode 100644
index 00000000..693a42b7
--- /dev/null
+++ b/pimd/pim_igmp_join.c
@@ -0,0 +1,67 @@
+/*
+ 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 <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+
+#include "pim_igmp_join.h"
+
+#ifndef SOL_IP
+#define SOL_IP IPPROTO_IP
+#endif
+
+#ifndef MCAST_JOIN_SOURCE_GROUP
+#define MCAST_JOIN_SOURCE_GROUP 46
+struct group_source_req
+{
+ uint32_t gsr_interface;
+ struct sockaddr_storage gsr_group;
+ struct sockaddr_storage gsr_source;
+};
+#endif
+
+int pim_igmp_join_source(int fd, int ifindex,
+ struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ struct group_source_req req;
+ struct sockaddr_in *group_sa = (struct sockaddr_in *) &req.gsr_group;
+ struct sockaddr_in *source_sa = (struct sockaddr_in *) &req.gsr_source;
+
+ memset(group_sa, 0, sizeof(*group_sa));
+ group_sa->sin_family = AF_INET;
+ group_sa->sin_addr = group_addr;
+ group_sa->sin_port = htons(0);
+
+ memset(source_sa, 0, sizeof(*source_sa));
+ source_sa->sin_family = AF_INET;
+ source_sa->sin_addr = source_addr;
+ source_sa->sin_port = htons(0);
+
+ req.gsr_interface = ifindex;
+
+ return setsockopt(fd, SOL_IP, MCAST_JOIN_SOURCE_GROUP,
+ &req, sizeof(req));
+
+ return 0;
+}
diff --git a/pimd/pim_igmp_join.h b/pimd/pim_igmp_join.h
new file mode 100644
index 00000000..1127af12
--- /dev/null
+++ b/pimd/pim_igmp_join.h
@@ -0,0 +1,32 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_IGMP_JOIN_H
+#define PIM_IGMP_JOIN_H
+
+#include <netinet/in.h>
+
+int pim_igmp_join_source(int fd, int ifindex,
+ struct in_addr group_addr,
+ struct in_addr source_addr);
+
+#endif /* PIM_IGMP_JOIN_H */
diff --git a/pimd/pim_igmpv3.c b/pimd/pim_igmpv3.c
new file mode 100644
index 00000000..3baddbfa
--- /dev/null
+++ b/pimd/pim_igmpv3.c
@@ -0,0 +1,1729 @@
+/*
+ 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 "memory.h"
+
+#include "pimd.h"
+#include "pim_iface.h"
+#include "pim_igmp.h"
+#include "pim_igmpv3.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_time.h"
+#include "pim_zebra.h"
+#include "pim_oil.h"
+
+static void group_retransmit_timer_on(struct igmp_group *group);
+static long igmp_group_timer_remain_msec(struct igmp_group *group);
+static long igmp_source_timer_remain_msec(struct igmp_source *source);
+static void group_query_send(struct igmp_group *group);
+static void source_query_send_by_flag(struct igmp_group *group,
+ int num_sources_tosend);
+
+static void on_trace(const char *label,
+ struct interface *ifp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char from_str[100];
+ char group_str[100];
+
+ pim_inet4_dump("<from?>", from, from_str, sizeof(from_str));
+ pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+
+ zlog_debug("%s: from %s on %s: group=%s sources=%d",
+ label, from_str, ifp->name, group_str, num_sources);
+ }
+}
+
+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;
+ struct pim_interface *pim_ifp;
+ struct igmp_sock *igmp;
+ struct interface *ifp;
+
+ igmp = group->group_igmp_sock;
+ ifp = igmp->interface;
+ pim_ifp = ifp->info;
+
+ /*
+ RFC 3376: 8.4. Group Membership Interval
+
+ The Group Membership Interval is the amount of time that must pass
+ before a multicast router decides there are no more members of a
+ group or a particular source on a network.
+
+ This value MUST be ((the Robustness Variable) times (the Query
+ Interval)) plus (one Query Response Interval).
+
+ group_membership_interval_msec = querier_robustness_variable *
+ (1000 * querier_query_interval) +
+ 100 * query_response_interval_dsec;
+ */
+ group_membership_interval_msec =
+ PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ pim_ifp->igmp_query_max_response_time_dsec);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("Resetting group %s timer to GMI=%ld.%03ld sec on %s",
+ group_str,
+ group_membership_interval_msec / 1000,
+ group_membership_interval_msec % 1000,
+ ifp->name);
+ }
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and
+ it represents the time for the *filter-mode* of the group to
+ expire and switch to INCLUDE mode.
+ */
+ zassert(group->group_filtermode_isexcl);
+
+ igmp_group_timer_on(group, group_membership_interval_msec, ifp->name);
+}
+
+static int igmp_source_timer(struct thread *t)
+{
+ struct igmp_source *source;
+ struct igmp_group *group;
+
+ zassert(t);
+ source = THREAD_ARG(t);
+ zassert(source);
+
+ group = source->source_group;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_debug("%s: Source timer expired for group %s source %s on %s",
+ __PRETTY_FUNCTION__,
+ group_str, source_str,
+ group->group_igmp_sock->interface->name);
+ }
+
+ zassert(source->t_source_timer);
+ source->t_source_timer = 0;
+
+ /*
+ RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules
+
+ Group
+ Filter-Mode Source Timer Value Action
+ ----------- ------------------ ------
+ INCLUDE TIMER == 0 Suggest to stop forwarding
+ traffic from source and
+ remove source record. If
+ there are no more source
+ records for the group, delete
+ group record.
+
+ EXCLUDE TIMER == 0 Suggest to not forward
+ traffic from source
+ (DO NOT remove record)
+
+ Source timer switched from (T > 0) to (T == 0): disable forwarding.
+ */
+
+ zassert(!source->t_source_timer);
+
+ if (group->group_filtermode_isexcl) {
+ /* EXCLUDE mode */
+
+ igmp_source_forward_stop(source);
+ }
+ else {
+ /* INCLUDE mode */
+
+ /* igmp_source_delete() will stop forwarding source */
+ igmp_source_delete(source);
+
+ /*
+ If there are no more source records for the group, delete group
+ record.
+ */
+ if (!listcount(group->group_source_list)) {
+ igmp_group_delete_empty_include(group);
+ }
+ }
+
+ return 0;
+}
+
+static void source_timer_off(struct igmp_group *group,
+ struct igmp_source *source)
+{
+ if (!source->t_source_timer)
+ return;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_debug("Cancelling TIMER event for group %s source %s on %s",
+ group_str, source_str,
+ group->group_igmp_sock->interface->name);
+ }
+
+ THREAD_OFF(source->t_source_timer);
+ zassert(!source->t_source_timer);
+}
+
+static void igmp_source_timer_on(struct igmp_group *group,
+ struct igmp_source *source,
+ long interval_msec)
+{
+ source_timer_off(group, source);
+
+ if (PIM_DEBUG_IGMP_EVENTS) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_debug("Scheduling %ld.%03ld sec TIMER event for group %s source %s on %s",
+ interval_msec / 1000,
+ interval_msec % 1000,
+ group_str, source_str,
+ group->group_igmp_sock->interface->name);
+ }
+
+ THREAD_TIMER_MSEC_ON(master, source->t_source_timer,
+ igmp_source_timer,
+ source, interval_msec);
+ zassert(source->t_source_timer);
+
+ /*
+ RFC 3376: 6.3. IGMPv3 Source-Specific Forwarding Rules
+
+ Source timer switched from (T == 0) to (T > 0): enable forwarding.
+ */
+ igmp_source_forward_start(source);
+}
+
+void igmp_source_reset_gmi(struct igmp_sock *igmp,
+ struct igmp_group *group,
+ struct igmp_source *source)
+{
+ long group_membership_interval_msec;
+ struct pim_interface *pim_ifp;
+ struct interface *ifp;
+
+ ifp = igmp->interface;
+ pim_ifp = ifp->info;
+
+ group_membership_interval_msec =
+ PIM_IGMP_GMI_MSEC(igmp->querier_robustness_variable,
+ igmp->querier_query_interval,
+ pim_ifp->igmp_query_max_response_time_dsec);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ char source_str[100];
+
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+
+ zlog_debug("Resetting source %s timer to GMI=%ld.%03ld sec for group %s on %s",
+ source_str,
+ group_membership_interval_msec / 1000,
+ group_membership_interval_msec % 1000,
+ group_str,
+ ifp->name);
+ }
+
+ igmp_source_timer_on(group, source,
+ group_membership_interval_msec);
+}
+
+static void source_mark_delete_flag(struct list *source_list)
+{
+ struct listnode *src_node;
+ struct igmp_source *src;
+
+ for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+ IGMP_SOURCE_DO_DELETE(src->source_flags);
+ }
+}
+
+static void source_mark_send_flag(struct list *source_list)
+{
+ struct listnode *src_node;
+ struct igmp_source *src;
+
+ for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+ IGMP_SOURCE_DO_SEND(src->source_flags);
+ }
+}
+
+static int source_mark_send_flag_by_timer(struct list *source_list)
+{
+ struct listnode *src_node;
+ struct igmp_source *src;
+ int num_marked_sources = 0;
+
+ for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+ /* Is source timer running? */
+ if (src->t_source_timer) {
+ IGMP_SOURCE_DO_SEND(src->source_flags);
+ ++num_marked_sources;
+ }
+ else {
+ IGMP_SOURCE_DONT_SEND(src->source_flags);
+ }
+ }
+
+ return num_marked_sources;
+}
+
+static void source_clear_send_flag(struct list *source_list)
+{
+ struct listnode *src_node;
+ struct igmp_source *src;
+
+ for (ALL_LIST_ELEMENTS_RO(source_list, src_node, src)) {
+ IGMP_SOURCE_DONT_SEND(src->source_flags);
+ }
+}
+
+/*
+ Any source (*,G) is forwarded only if mode is EXCLUDE {empty}
+*/
+static void group_exclude_fwd_anysrc_ifempty(struct igmp_group *group)
+{
+ zassert(group->group_filtermode_isexcl);
+
+ if (listcount(group->group_source_list) < 1) {
+ igmp_anysource_forward_start(group);
+ }
+}
+
+void igmp_source_free(struct igmp_source *source)
+{
+ /* make sure there is no source timer running */
+ zassert(!source->t_source_timer);
+
+ XFREE(MTYPE_PIM_IGMP_GROUP_SOURCE, source);
+}
+
+static void source_channel_oil_detach(struct igmp_source *source)
+{
+ if (source->source_channel_oil) {
+ pim_channel_oil_del(source->source_channel_oil);
+ source->source_channel_oil = 0;
+ }
+}
+
+/*
+ igmp_source_delete: stop fowarding, and delete the source
+ igmp_source_forward_stop: stop fowarding, but keep the source
+*/
+void igmp_source_delete(struct igmp_source *source)
+{
+ struct igmp_group *group;
+
+ group = source->source_group;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_debug("Deleting IGMP source %s for group %s from socket %d interface %s",
+ source_str, group_str,
+ group->group_igmp_sock->fd,
+ group->group_igmp_sock->interface->name);
+ }
+
+ source_timer_off(group, source);
+ igmp_source_forward_stop(source);
+
+ /* sanity check that forwarding has been disabled */
+ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: forwarding=ON(!) IGMP source %s for group %s from socket %d interface %s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str,
+ group->group_igmp_sock->fd,
+ group->group_igmp_sock->interface->name);
+ /* warning only */
+ }
+
+ source_channel_oil_detach(source);
+
+ /*
+ notice that listnode_delete() can't be moved
+ into igmp_source_free() because the later is
+ called by list_delete_all_node()
+ */
+ listnode_delete(group->group_source_list, source);
+
+ igmp_source_free(source);
+
+ if (group->group_filtermode_isexcl) {
+ group_exclude_fwd_anysrc_ifempty(group);
+ }
+}
+
+static void source_delete_by_flag(struct list *source_list)
+{
+ struct listnode *src_node;
+ struct listnode *src_nextnode;
+ struct igmp_source *src;
+
+ for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src))
+ if (IGMP_SOURCE_TEST_DELETE(src->source_flags))
+ igmp_source_delete(src);
+}
+
+void igmp_source_delete_expired(struct list *source_list)
+{
+ struct listnode *src_node;
+ struct listnode *src_nextnode;
+ struct igmp_source *src;
+
+ for (ALL_LIST_ELEMENTS(source_list, src_node, src_nextnode, src))
+ if (!src->t_source_timer)
+ igmp_source_delete(src);
+}
+
+struct igmp_source *igmp_find_source_by_addr(struct igmp_group *group,
+ struct in_addr src_addr)
+{
+ struct listnode *src_node;
+ struct igmp_source *src;
+
+ for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src))
+ if (src_addr.s_addr == src->source_addr.s_addr)
+ return src;
+
+ return 0;
+}
+
+static struct igmp_source *source_new(struct igmp_group *group,
+ struct in_addr src_addr,
+ const char *ifname)
+{
+ struct igmp_source *src;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", src_addr, source_str, sizeof(source_str));
+ zlog_debug("Creating new IGMP source %s for group %s on socket %d interface %s",
+ source_str, group_str,
+ group->group_igmp_sock->fd,
+ ifname);
+ }
+
+ src = XMALLOC(MTYPE_PIM_IGMP_GROUP_SOURCE, sizeof(*src));
+ if (!src) {
+ zlog_warn("%s %s: XMALLOC() failure",
+ __FILE__, __PRETTY_FUNCTION__);
+ return 0; /* error, not found, could not create */
+ }
+
+ src->t_source_timer = 0;
+ src->source_group = group; /* back pointer */
+ src->source_addr = src_addr;
+ src->source_creation = pim_time_monotonic_sec();
+ src->source_flags = 0;
+ src->source_query_retransmit_count = 0;
+ src->source_channel_oil = 0;
+
+ listnode_add(group->group_source_list, src);
+
+ zassert(!src->t_source_timer); /* source timer == 0 */
+
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ igmp_anysource_forward_stop(group);
+
+ return src;
+}
+
+static struct igmp_source *add_source_by_addr(struct igmp_sock *igmp,
+ struct igmp_group *group,
+ struct in_addr src_addr,
+ const char *ifname)
+{
+ struct igmp_source *src;
+
+ src = igmp_find_source_by_addr(group, src_addr);
+ if (src) {
+ return src;
+ }
+
+ src = source_new(group, src_addr, ifname);
+ if (!src) {
+ return 0;
+ }
+
+ return src;
+}
+
+static void allow(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ struct interface *ifp = igmp->interface;
+ struct igmp_group *group;
+ int i;
+
+ /* non-existant group is created as INCLUDE {empty} */
+ group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+ if (!group) {
+ return;
+ }
+
+ /* scan received sources */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ source = add_source_by_addr(igmp, group, *src_addr, ifp->name);
+ if (!source) {
+ continue;
+ }
+
+ /*
+ RFC 3376: 6.4.1. Reception of Current-State Records
+
+ When receiving IS_IN reports for groups in EXCLUDE mode is
+ sources should be moved from set with (timers = 0) to set with
+ (timers > 0).
+
+ igmp_source_reset_gmi() below, resetting the source timers to
+ GMI, accomplishes this.
+ */
+ igmp_source_reset_gmi(igmp, group, source);
+
+ } /* scan received sources */
+}
+
+void igmpv3_report_isin(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ on_trace(__PRETTY_FUNCTION__,
+ igmp->interface, from, group_addr, num_sources, sources);
+
+ allow(igmp, from, group_addr, num_sources, sources);
+}
+
+static void isex_excl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ int i;
+
+ /* EXCLUDE mode */
+ zassert(group->group_filtermode_isexcl);
+
+ /* E.1: set deletion flag for known sources (X,Y) */
+ source_mark_delete_flag(group->group_source_list);
+
+ /* scan received sources (A) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* E.2: lookup reported source from (A) in (X,Y) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ /* E.3: if found, clear deletion flag: (X*A) or (Y*A) */
+ IGMP_SOURCE_DONT_DELETE(source->source_flags);
+ }
+ else {
+ /* E.4: if not found, create source with timer=GMI: (A-X-Y) */
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+ zassert(!source->t_source_timer); /* timer == 0 */
+ igmp_source_reset_gmi(group->group_igmp_sock, group, source);
+ zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+ }
+
+ } /* scan received sources */
+
+ /* E.5: delete all sources marked with deletion flag: (X-A) and (Y-A) */
+ source_delete_by_flag(group->group_source_list);
+}
+
+static void isex_incl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ int i;
+
+ /* INCLUDE mode */
+ zassert(!group->group_filtermode_isexcl);
+
+ /* I.1: set deletion flag for known sources (A) */
+ source_mark_delete_flag(group->group_source_list);
+
+ /* scan received sources (B) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* I.2: lookup reported source (B) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ /* I.3: if found, clear deletion flag (A*B) */
+ IGMP_SOURCE_DONT_DELETE(source->source_flags);
+ }
+ else {
+ /* I.4: if not found, create source with timer=0 (B-A) */
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+ zassert(!source->t_source_timer); /* (B-A) timer=0 */
+ }
+
+ } /* scan received sources */
+
+ /* I.5: delete all sources marked with deletion flag (A-B) */
+ source_delete_by_flag(group->group_source_list);
+
+ group->group_filtermode_isexcl = 1; /* boolean=true */
+
+ zassert(group->group_filtermode_isexcl);
+
+ group_exclude_fwd_anysrc_ifempty(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)
+{
+ struct interface *ifp = igmp->interface;
+ struct igmp_group *group;
+
+ on_trace(__PRETTY_FUNCTION__,
+ ifp, from, group_addr, num_sources, sources);
+
+ /* non-existant group is created as INCLUDE {empty} */
+ group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+ if (!group) {
+ return;
+ }
+
+ if (group->group_filtermode_isexcl) {
+ /* EXCLUDE mode */
+ isex_excl(group, num_sources, sources);
+ }
+ else {
+ /* INCLUDE mode */
+ isex_incl(group, num_sources, sources);
+ zassert(group->group_filtermode_isexcl);
+ }
+
+ zassert(group->group_filtermode_isexcl);
+
+ igmp_group_reset_gmi(group);
+}
+
+static void toin_incl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ struct igmp_sock *igmp = group->group_igmp_sock;
+ int num_sources_tosend = listcount(group->group_source_list);
+ int i;
+
+ /* Set SEND flag for all known sources (A) */
+ source_mark_send_flag(group->group_source_list);
+
+ /* Scan received sources (B) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* Lookup reported source (B) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ /* If found, clear SEND flag (A*B) */
+ IGMP_SOURCE_DONT_SEND(source->source_flags);
+ --num_sources_tosend;
+ }
+ else {
+ /* If not found, create new source */
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+ }
+
+ /* (B)=GMI */
+ igmp_source_reset_gmi(igmp, group, source);
+ }
+
+ /* Send sources marked with SEND flag: Q(G,A-B) */
+ if (num_sources_tosend > 0) {
+ source_query_send_by_flag(group, num_sources_tosend);
+ }
+}
+
+static void toin_excl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ struct igmp_sock *igmp = group->group_igmp_sock;
+ int num_sources_tosend;
+ int i;
+
+ /* Set SEND flag for X (sources with timer > 0) */
+ num_sources_tosend = source_mark_send_flag_by_timer(group->group_source_list);
+
+ /* Scan received sources (A) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* Lookup reported source (A) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ if (source->t_source_timer) {
+ /* If found and timer running, clear SEND flag (X*A) */
+ IGMP_SOURCE_DONT_SEND(source->source_flags);
+ --num_sources_tosend;
+ }
+ }
+ else {
+ /* If not found, create new source */
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+ }
+
+ /* (A)=GMI */
+ igmp_source_reset_gmi(igmp, group, source);
+ }
+
+ /* Send sources marked with SEND flag: Q(G,X-A) */
+ if (num_sources_tosend > 0) {
+ source_query_send_by_flag(group, num_sources_tosend);
+ }
+
+ /* Send Q(G) */
+ group_query_send(group);
+}
+
+void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ struct interface *ifp = igmp->interface;
+ struct igmp_group *group;
+
+ on_trace(__PRETTY_FUNCTION__,
+ ifp, from, group_addr, num_sources, sources);
+
+ /* non-existant group is created as INCLUDE {empty} */
+ group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+ if (!group) {
+ return;
+ }
+
+ if (group->group_filtermode_isexcl) {
+ /* EXCLUDE mode */
+ toin_excl(group, num_sources, sources);
+ }
+ else {
+ /* INCLUDE mode */
+ toin_incl(group, num_sources, sources);
+ }
+}
+
+static void toex_incl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ int num_sources_tosend = 0;
+ int i;
+
+ zassert(!group->group_filtermode_isexcl);
+
+ /* Set DELETE flag for all known sources (A) */
+ source_mark_delete_flag(group->group_source_list);
+
+ /* Clear off SEND flag from all known sources (A) */
+ source_clear_send_flag(group->group_source_list);
+
+ /* Scan received sources (B) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* Lookup reported source (B) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ /* If found, clear deletion flag: (A*B) */
+ IGMP_SOURCE_DONT_DELETE(source->source_flags);
+ /* and set SEND flag (A*B) */
+ IGMP_SOURCE_DO_SEND(source->source_flags);
+ ++num_sources_tosend;
+ }
+ else {
+ /* If source not found, create source with timer=0: (B-A)=0 */
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+ zassert(!source->t_source_timer); /* (B-A) timer=0 */
+ }
+
+ } /* Scan received sources (B) */
+
+ group->group_filtermode_isexcl = 1; /* boolean=true */
+
+ /* Delete all sources marked with DELETE flag (A-B) */
+ source_delete_by_flag(group->group_source_list);
+
+ /* Send sources marked with SEND flag: Q(G,A*B) */
+ if (num_sources_tosend > 0) {
+ source_query_send_by_flag(group, num_sources_tosend);
+ }
+
+ zassert(group->group_filtermode_isexcl);
+
+ group_exclude_fwd_anysrc_ifempty(group);
+}
+
+static void toex_excl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ int num_sources_tosend = 0;
+ int i;
+
+ /* set DELETE flag for all known sources (X,Y) */
+ source_mark_delete_flag(group->group_source_list);
+
+ /* clear off SEND flag from all known sources (X,Y) */
+ source_clear_send_flag(group->group_source_list);
+
+ /* scan received sources (A) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* lookup reported source (A) in known sources (X,Y) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ /* if found, clear off DELETE flag from reported source (A) */
+ IGMP_SOURCE_DONT_DELETE(source->source_flags);
+ }
+ else {
+ /* if not found, create source with Group Timer: (A-X-Y)=Group Timer */
+ long group_timer_msec;
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+
+ zassert(!source->t_source_timer); /* timer == 0 */
+ group_timer_msec = igmp_group_timer_remain_msec(group);
+ igmp_source_timer_on(group, source, group_timer_msec);
+ zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+
+ /* make sure source is created with DELETE flag unset */
+ zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags));
+ }
+
+ /* make sure reported source has DELETE flag unset */
+ zassert(!IGMP_SOURCE_TEST_DELETE(source->source_flags));
+
+ if (source->t_source_timer) {
+ /* if source timer>0 mark SEND flag: Q(G,A-Y) */
+ IGMP_SOURCE_DO_SEND(source->source_flags);
+ ++num_sources_tosend;
+ }
+
+ } /* scan received sources (A) */
+
+ /*
+ delete all sources marked with DELETE flag:
+ Delete (X-A)
+ Delete (Y-A)
+ */
+ source_delete_by_flag(group->group_source_list);
+
+ /* send sources marked with SEND flag: Q(G,A-Y) */
+ if (num_sources_tosend > 0) {
+ source_query_send_by_flag(group, num_sources_tosend);
+ }
+}
+
+void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ struct interface *ifp = igmp->interface;
+ struct igmp_group *group;
+
+ on_trace(__PRETTY_FUNCTION__,
+ ifp, from, group_addr, num_sources, sources);
+
+ /* non-existant group is created as INCLUDE {empty} */
+ group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+ if (!group) {
+ return;
+ }
+
+ if (group->group_filtermode_isexcl) {
+ /* EXCLUDE mode */
+ toex_excl(group, num_sources, sources);
+ }
+ else {
+ /* INCLUDE mode */
+ toex_incl(group, num_sources, sources);
+ zassert(group->group_filtermode_isexcl);
+ }
+ zassert(group->group_filtermode_isexcl);
+
+ /* Group Timer=GMI */
+ igmp_group_reset_gmi(group);
+}
+
+void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ on_trace(__PRETTY_FUNCTION__,
+ igmp->interface, from, group_addr, num_sources, sources);
+
+ allow(igmp, from, group_addr, num_sources, sources);
+}
+
+/*
+ RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+
+ When transmitting a group specific query, if the group timer is
+ larger than LMQT, the "Suppress Router-Side Processing" bit is set
+ in the query message.
+*/
+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;
+
+ igmp = group->group_igmp_sock;
+ pim_ifp = igmp->interface->info;
+
+ lmqc = igmp->querier_robustness_variable;
+ lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec;
+ lmqt_msec = lmqc * lmqi_msec;
+
+ /*
+ RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+
+ When transmitting a group specific query, if the group timer is
+ larger than LMQT, the "Suppress Router-Side Processing" bit is set
+ in the query message.
+ */
+ s_flag = igmp_group_timer_remain_msec(group) > lmqt_msec;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("retransmit_group_specific_query: group %s on %s: s_flag=%d count=%d",
+ group_str, igmp->interface->name, s_flag,
+ group->group_specific_query_retransmit_count);
+ }
+
+ /*
+ RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+ Group-Specific and Group-and-Source-Specific Queries are sent with
+ an IP destination address equal to the multicast address of
+ 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);
+}
+
+/*
+ RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+
+ When building a group and source specific query for a group G, two
+ separate query messages are sent for the group. The first one has
+ the "Suppress Router-Side Processing" bit set and contains all the
+ sources with retransmission state and timers greater than LMQT. The
+ second has the "Suppress Router-Side Processing" bit clear and
+ contains all the sources with retransmission state and timers lower
+ or equal to LMQT. If either of the two calculated messages does not
+ contain any sources, then its transmission is suppressed.
+ */
+static int group_retransmit_sources(struct igmp_group *group,
+ int send_with_sflag_set)
+{
+ 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 */
+ char query_buf1[PIM_IGMP_BUFSIZE_WRITE]; /* 1 = with s_flag set */
+ char query_buf2[PIM_IGMP_BUFSIZE_WRITE]; /* 2 = with s_flag clear */
+ int query_buf1_max_sources;
+ int query_buf2_max_sources;
+ struct in_addr *source_addr1;
+ struct in_addr *source_addr2;
+ int num_sources_tosend1;
+ int num_sources_tosend2;
+ struct listnode *src_node;
+ struct igmp_source *src;
+ int num_retransmit_sources_left = 0;
+
+ query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2;
+ query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2;
+
+ source_addr1 = (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET);
+ source_addr2 = (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET);
+
+ igmp = group->group_igmp_sock;
+ pim_ifp = igmp->interface->info;
+
+ lmqc = igmp->querier_robustness_variable;
+ lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec;
+ lmqt_msec = lmqc * lmqi_msec;
+
+ /* Scan all group sources */
+ for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) {
+
+ /* Source has retransmission state? */
+ if (src->source_query_retransmit_count < 1)
+ continue;
+
+ if (--src->source_query_retransmit_count > 0) {
+ ++num_retransmit_sources_left;
+ }
+
+ /* Copy source address into appropriate query buffer */
+ if (igmp_source_timer_remain_msec(src) > lmqt_msec) {
+ *source_addr1 = src->source_addr;
+ ++source_addr1;
+ }
+ else {
+ *source_addr2 = src->source_addr;
+ ++source_addr2;
+ }
+
+ }
+
+ num_sources_tosend1 = source_addr1 - (struct in_addr *)(query_buf1 + IGMP_V3_SOURCES_OFFSET);
+ num_sources_tosend2 = source_addr2 - (struct in_addr *)(query_buf2 + IGMP_V3_SOURCES_OFFSET);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("retransmit_grp&src_specific_query: group %s on %s: srcs_with_sflag=%d srcs_wo_sflag=%d will_send_sflag=%d retransmit_src_left=%d",
+ group_str, igmp->interface->name,
+ num_sources_tosend1,
+ num_sources_tosend2,
+ send_with_sflag_set,
+ num_retransmit_sources_left);
+ }
+
+ if (num_sources_tosend1 > 0) {
+ /*
+ Send group-and-source-specific query with s_flag set and all
+ sources with timers greater than LMQT.
+ */
+
+ if (send_with_sflag_set) {
+
+ query_buf1_max_sources = (sizeof(query_buf1) - IGMP_V3_SOURCES_OFFSET) >> 2;
+ if (num_sources_tosend1 > query_buf1_max_sources) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: group %s on %s: s_flag=1 unable to fit %d sources into buf_size=%zu (max_sources=%d)",
+ __PRETTY_FUNCTION__, group_str, igmp->interface->name,
+ num_sources_tosend1, sizeof(query_buf1), query_buf1_max_sources);
+ }
+ else {
+ /*
+ RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+ Group-Specific and Group-and-Source-Specific Queries are sent with
+ an IP destination address equal to the multicast address of
+ 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);
+
+ }
+
+ } /* send_with_sflag_set */
+
+ }
+
+ if (num_sources_tosend2 > 0) {
+ /*
+ Send group-and-source-specific query with s_flag clear and all
+ sources with timers lower or equal to LMQT.
+ */
+
+ query_buf2_max_sources = (sizeof(query_buf2) - IGMP_V3_SOURCES_OFFSET) >> 2;
+ if (num_sources_tosend2 > query_buf2_max_sources) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: group %s on %s: s_flag=0 unable to fit %d sources into buf_size=%zu (max_sources=%d)",
+ __PRETTY_FUNCTION__, group_str, igmp->interface->name,
+ num_sources_tosend2, sizeof(query_buf2), query_buf2_max_sources);
+ }
+ else {
+ /*
+ RFC3376: 4.1.12. IP Destination Addresses for Queries
+
+ Group-Specific and Group-and-Source-Specific Queries are sent with
+ an IP destination address equal to the multicast address of
+ 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);
+
+ }
+ }
+
+ return num_retransmit_sources_left;
+}
+
+static int igmp_group_retransmit(struct thread *t)
+{
+ struct igmp_group *group;
+ int num_retransmit_sources_left;
+ int send_with_sflag_set; /* boolean */
+
+ zassert(t);
+ group = THREAD_ARG(t);
+ zassert(group);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("group_retransmit_timer: group %s on %s",
+ group_str, group->group_igmp_sock->interface->name);
+ }
+
+ /* Retransmit group-specific queries? (RFC3376: 6.6.3.1) */
+ if (group->group_specific_query_retransmit_count > 0) {
+
+ /* Retransmit group-specific queries (RFC3376: 6.6.3.1) */
+ group_retransmit_group(group);
+ --group->group_specific_query_retransmit_count;
+
+ /*
+ RFC3376: 6.6.3.2
+ If a group specific query is scheduled to be transmitted at the
+ same time as a group and source specific query for the same group,
+ then transmission of the group and source specific message with the
+ "Suppress Router-Side Processing" bit set may be suppressed.
+ */
+ send_with_sflag_set = 0; /* boolean=false */
+ }
+ else {
+ send_with_sflag_set = 1; /* boolean=true */
+ }
+
+ /* Retransmit group-and-source-specific queries (RFC3376: 6.6.3.2) */
+ num_retransmit_sources_left = group_retransmit_sources(group,
+ send_with_sflag_set);
+
+ group->t_group_query_retransmit_timer = 0;
+
+ /*
+ Keep group retransmit timer running if there is any retransmit
+ counter pending
+ */
+ if ((num_retransmit_sources_left > 0) ||
+ (group->group_specific_query_retransmit_count > 0)) {
+ group_retransmit_timer_on(group);
+ }
+
+ return 0;
+}
+
+/*
+ group_retransmit_timer_on:
+ if group retransmit timer isn't running, starts it;
+ otherwise, do nothing
+*/
+static void group_retransmit_timer_on(struct igmp_group *group)
+{
+ struct igmp_sock *igmp;
+ struct pim_interface *pim_ifp;
+ long lmqi_msec; /* Last Member Query Interval */
+
+ /* if group retransmit timer is running, do nothing */
+ if (group->t_group_query_retransmit_timer) {
+ return;
+ }
+
+ igmp = group->group_igmp_sock;
+ pim_ifp = igmp->interface->info;
+
+ lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("Scheduling %ld.%03ld sec retransmit timer for group %s on %s",
+ lmqi_msec / 1000,
+ lmqi_msec % 1000,
+ group_str,
+ igmp->interface->name);
+ }
+
+ THREAD_TIMER_MSEC_ON(master, group->t_group_query_retransmit_timer,
+ igmp_group_retransmit,
+ group, lmqi_msec);
+}
+
+static long igmp_group_timer_remain_msec(struct igmp_group *group)
+{
+ return pim_time_timer_remain_msec(group->t_group_timer);
+}
+
+static long igmp_source_timer_remain_msec(struct igmp_source *source)
+{
+ return pim_time_timer_remain_msec(source->t_source_timer);
+}
+
+/*
+ RFC3376: 6.6.3.1. Building and Sending Group Specific Queries
+*/
+static void group_query_send(struct igmp_group *group)
+{
+ long lmqc; /* Last Member Query Count */
+
+ lmqc = group->group_igmp_sock->querier_robustness_variable;
+
+ /* lower group timer to lmqt */
+ igmp_group_timer_lower_to_lmqt(group);
+
+ /* reset retransmission counter */
+ group->group_specific_query_retransmit_count = lmqc;
+
+ /* immediately send group specific query (decrease retransmit counter by 1)*/
+ group_retransmit_group(group);
+
+ /* make sure group retransmit timer is running */
+ group_retransmit_timer_on(group);
+}
+
+/*
+ RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+*/
+static void source_query_send_by_flag(struct igmp_group *group,
+ int num_sources_tosend)
+{
+ struct igmp_sock *igmp;
+ struct pim_interface *pim_ifp;
+ struct listnode *src_node;
+ struct igmp_source *src;
+ long lmqc; /* Last Member Query Count */
+ long lmqi_msec; /* Last Member Query Interval */
+ long lmqt_msec; /* Last Member Query Time */
+
+ zassert(num_sources_tosend > 0);
+
+ igmp = group->group_igmp_sock;
+ pim_ifp = igmp->interface->info;
+
+ lmqc = igmp->querier_robustness_variable;
+ lmqi_msec = 100 * pim_ifp->igmp_specific_query_max_response_time_dsec;
+ lmqt_msec = lmqc * lmqi_msec;
+
+ /*
+ RFC3376: 6.6.3.2. Building and Sending Group and Source Specific Queries
+
+ (...) for each of the sources in X of group G, with source timer larger
+ than LMQT:
+ o Set number of retransmissions for each source to [Last Member
+ Query Count].
+ o Lower source timer to LMQT.
+ */
+ for (ALL_LIST_ELEMENTS_RO(group->group_source_list, src_node, src)) {
+ if (IGMP_SOURCE_TEST_SEND(src->source_flags)) {
+ /* source "src" in X of group G */
+ if (igmp_source_timer_remain_msec(src) > lmqt_msec) {
+ src->source_query_retransmit_count = lmqc;
+ igmp_source_timer_lower_to_lmqt(src);
+ }
+ }
+ }
+
+ /* send group-and-source specific queries */
+ group_retransmit_sources(group, 1 /* send_with_sflag_set=true */);
+
+ /* make sure group retransmit timer is running */
+ group_retransmit_timer_on(group);
+}
+
+static void block_excl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ int num_sources_tosend = 0;
+ int i;
+
+ /* 1. clear off SEND flag from all known sources (X,Y) */
+ source_clear_send_flag(group->group_source_list);
+
+ /* 2. scan received sources (A) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* lookup reported source (A) in known sources (X,Y) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (!source) {
+ /* 3: if not found, create source with Group Timer: (A-X-Y)=Group Timer */
+ long group_timer_msec;
+ source = source_new(group, *src_addr,
+ group->group_igmp_sock->interface->name);
+ if (!source) {
+ /* ugh, internal malloc failure, skip source */
+ continue;
+ }
+
+ zassert(!source->t_source_timer); /* timer == 0 */
+ group_timer_msec = igmp_group_timer_remain_msec(group);
+ igmp_source_timer_on(group, source, group_timer_msec);
+ zassert(source->t_source_timer); /* (A-X-Y) timer > 0 */
+ }
+
+ if (source->t_source_timer) {
+ /* 4. if source timer>0 mark SEND flag: Q(G,A-Y) */
+ IGMP_SOURCE_DO_SEND(source->source_flags);
+ ++num_sources_tosend;
+ }
+ }
+
+ /* 5. send sources marked with SEND flag: Q(G,A-Y) */
+ if (num_sources_tosend > 0) {
+ source_query_send_by_flag(group, num_sources_tosend);
+ }
+}
+
+static void block_incl(struct igmp_group *group,
+ int num_sources, struct in_addr *sources)
+{
+ int num_sources_tosend = 0;
+ int i;
+
+ /* 1. clear off SEND flag from all known sources (B) */
+ source_clear_send_flag(group->group_source_list);
+
+ /* 2. scan received sources (A) */
+ for (i = 0; i < num_sources; ++i) {
+ struct igmp_source *source;
+ struct in_addr *src_addr;
+
+ src_addr = sources + i;
+
+ /* lookup reported source (A) in known sources (B) */
+ source = igmp_find_source_by_addr(group, *src_addr);
+ if (source) {
+ /* 3. if found (A*B), mark SEND flag: Q(G,A*B) */
+ IGMP_SOURCE_DO_SEND(source->source_flags);
+ ++num_sources_tosend;
+ }
+ }
+
+ /* 4. send sources marked with SEND flag: Q(G,A*B) */
+ if (num_sources_tosend > 0) {
+ source_query_send_by_flag(group, num_sources_tosend);
+ }
+}
+
+void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources)
+{
+ struct interface *ifp = igmp->interface;
+ struct igmp_group *group;
+
+ on_trace(__PRETTY_FUNCTION__,
+ ifp, from, group_addr, num_sources, sources);
+
+ /* non-existant group is created as INCLUDE {empty} */
+ group = igmp_add_group_by_addr(igmp, group_addr, ifp->name);
+ if (!group) {
+ return;
+ }
+
+ if (group->group_filtermode_isexcl) {
+ /* EXCLUDE mode */
+ block_excl(group, num_sources, sources);
+ }
+ else {
+ /* INCLUDE mode */
+ block_incl(group, num_sources, sources);
+ }
+}
+
+void igmp_group_timer_lower_to_lmqt(struct igmp_group *group)
+{
+ struct igmp_sock *igmp;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ char *ifname;
+ int lmqi_dsec; /* Last Member Query Interval */
+ int lmqc; /* Last Member Query Count */
+ int lmqt_msec; /* Last Member Query Time */
+
+ /*
+ RFC 3376: 6.2.2. Definition of Group Timers
+
+ The group timer is only used when a group is in EXCLUDE mode and
+ it represents the time for the *filter-mode* of the group to
+ expire and switch to INCLUDE mode.
+ */
+ if (!group->group_filtermode_isexcl) {
+ return;
+ }
+
+ igmp = group->group_igmp_sock;
+ ifp = igmp->interface;
+ pim_ifp = ifp->info;
+ ifname = ifp->name;
+
+ lmqi_dsec = pim_ifp->igmp_specific_query_max_response_time_dsec;
+ lmqc = igmp->querier_robustness_variable;
+ lmqt_msec = PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: group %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec",
+ __PRETTY_FUNCTION__,
+ group_str, ifname,
+ lmqc, lmqi_dsec, lmqt_msec);
+ }
+
+ zassert(group->group_filtermode_isexcl);
+
+ igmp_group_timer_on(group, lmqt_msec, ifname);
+}
+
+void igmp_source_timer_lower_to_lmqt(struct igmp_source *source)
+{
+ struct igmp_group *group;
+ struct igmp_sock *igmp;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ char *ifname;
+ int lmqi_dsec; /* Last Member Query Interval */
+ int lmqc; /* Last Member Query Count */
+ int lmqt_msec; /* Last Member Query Time */
+
+ group = source->source_group;
+ igmp = group->group_igmp_sock;
+ ifp = igmp->interface;
+ pim_ifp = ifp->info;
+ ifname = ifp->name;
+
+ lmqi_dsec = pim_ifp->igmp_specific_query_max_response_time_dsec;
+ lmqc = igmp->querier_robustness_variable;
+ lmqt_msec = PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc); /* lmqt_msec = (100 * lmqi_dsec) * lmqc */
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_debug("%s: group %s source %s on %s: LMQC=%d LMQI=%d dsec LMQT=%d msec",
+ __PRETTY_FUNCTION__,
+ group_str, source_str, ifname,
+ lmqc, lmqi_dsec, lmqt_msec);
+ }
+
+ 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)
+{
+ ssize_t msg_size;
+ uint8_t max_resp_code;
+ uint8_t qqic;
+ ssize_t sent;
+ struct sockaddr_in to;
+ socklen_t tolen;
+ uint16_t checksum;
+
+ zassert(num_sources >= 0);
+
+ msg_size = IGMP_V3_SOURCES_OFFSET + (num_sources << 2);
+ if (msg_size > query_buf_size) {
+ zlog_err("%s %s: unable to send: msg_size=%zd larger than query_buf_size=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ msg_size, query_buf_size);
+ return;
+ }
+
+ s_flag = PIM_FORCE_BOOLEAN(s_flag);
+ zassert((s_flag == 0) || (s_flag == 1));
+
+ max_resp_code = igmp_msg_encode16to8(query_max_response_time_dsec);
+ qqic = igmp_msg_encode16to8(querier_query_interval);
+
+ /*
+ RFC 3376: 4.1.6. QRV (Querier's Robustness Variable)
+
+ If non-zero, the QRV field contains the [Robustness Variable]
+ value used by the querier, i.e., the sender of the Query. If the
+ querier's [Robustness Variable] exceeds 7, the maximum value of
+ the QRV field, the QRV is set to zero.
+ */
+ if (querier_robustness_variable > 7) {
+ querier_robustness_variable = 0;
+ }
+
+ 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 */
+ memcpy(query_buf+4, &group_addr, sizeof(struct in_addr));
+
+ query_buf[8] = (s_flag << 3) | querier_robustness_variable;
+ query_buf[9] = qqic;
+ *(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;
+
+ 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);
+ }
+
+#if 0
+ memset(&to, 0, sizeof(to));
+#endif
+ to.sin_family = AF_INET;
+ to.sin_addr = dst_addr;
+#if 0
+ to.sin_port = htons(0);
+#endif
+ tolen = sizeof(to);
+
+ sent = sendto(fd, query_buf, msg_size, MSG_DONTWAIT, &to, tolen);
+ if (sent != (ssize_t) msg_size) {
+ int e = errno;
+ 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("%s: sendto() failure to %s on %s: group=%s msg_size=%zd: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ dst_str, ifname, group_str, msg_size,
+ e, safe_strerror(e));
+ }
+ 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);
+ }
+ return;
+ }
+
+ /*
+ s_flag sanity test: s_flag must be set for general queries
+
+ 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 (!s_flag) {
+ /* general query? */
+ if (PIM_INADDR_IS_ANY(group_addr)) {
+ 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_warn("%s: to %s on %s: group=%s sources=%d: s_flag is clear for general query!",
+ __PRETTY_FUNCTION__,
+ dst_str, ifname, group_str, num_sources);
+ }
+ }
+
+}
diff --git a/pimd/pim_igmpv3.h b/pimd/pim_igmpv3.h
new file mode 100644
index 00000000..bb7e9267
--- /dev/null
+++ b/pimd/pim_igmpv3.h
@@ -0,0 +1,100 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_IGMPV3_H
+#define PIM_IGMPV3_H
+
+#include <zebra.h>
+#include "if.h"
+
+#define IGMP_V3_CHECKSUM_OFFSET (2)
+#define IGMP_V3_REPORT_NUMGROUPS_OFFSET (6)
+#define IGMP_V3_REPORT_GROUPPRECORD_OFFSET (8)
+#define IGMP_V3_NUMSOURCES_OFFSET (10)
+#define IGMP_V3_SOURCES_OFFSET (12)
+
+/* GMI: Group Membership Interval */
+#define PIM_IGMP_GMI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * (qri_dsec))
+
+/* OQPI: Other Querier Present Interval */
+#define PIM_IGMP_OQPI_MSEC(qrv,qqi,qri_dsec) ((qrv) * (1000 * (qqi)) + 100 * ((qri_dsec) >> 1))
+
+/* SQI: Startup Query Interval */
+#define PIM_IGMP_SQI(qi) (((qi) < 4) ? 1 : ((qi) >> 2))
+
+/* LMQT: Last Member Query Time */
+#define PIM_IGMP_LMQT_MSEC(lmqi_dsec, lmqc) ((lmqc) * (100 * (lmqi_dsec)))
+
+/* OHPI: Older Host Present Interval */
+#define PIM_IGMP_OHPI_DSEC(qrv,qqi,qri_dsec) ((qrv) * (10 * (qqi)) + (qri_dsec))
+
+void igmp_group_reset_gmi(struct igmp_group *group);
+void igmp_source_reset_gmi(struct igmp_sock *igmp,
+ struct igmp_group *group,
+ struct igmp_source *source);
+
+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);
+void igmpv3_report_toin(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources);
+void igmpv3_report_toex(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources);
+void igmpv3_report_allow(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources);
+void igmpv3_report_block(struct igmp_sock *igmp, struct in_addr from,
+ struct in_addr group_addr,
+ int num_sources, struct in_addr *sources);
+
+void igmp_group_timer_lower_to_lmqt(struct igmp_group *group);
+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);
+
+#endif /* PIM_IGMPV3_H */
diff --git a/pimd/pim_int.c b/pimd/pim_int.c
new file mode 100644
index 00000000..2ff1a116
--- /dev/null
+++ b/pimd/pim_int.c
@@ -0,0 +1,44 @@
+/*
+ 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 <string.h>
+#include <netinet/in.h>
+
+#include "pim_int.h"
+
+uint32_t pim_read_uint32_host(const uint8_t *buf)
+{
+ uint32_t val;
+ memcpy(&val, buf, sizeof(val));
+ /* val is in netorder */
+ val = ntohl(val);
+ /* val is in hostorder */
+ return val;
+}
+
+void pim_write_uint32(uint8_t *buf, uint32_t val_host)
+{
+ /* val_host is in host order */
+ val_host = htonl(val_host);
+ /* val_host is in netorder */
+ memcpy(buf, &val_host, sizeof(val_host));
+}
diff --git a/pimd/pim_int.h b/pimd/pim_int.h
new file mode 100644
index 00000000..d64b1032
--- /dev/null
+++ b/pimd/pim_int.h
@@ -0,0 +1,31 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_INT_H
+#define PIM_INT_H
+
+#include <stdint.h>
+
+uint32_t pim_read_uint32_host(const uint8_t *buf);
+void pim_write_uint32(uint8_t *buf, uint32_t val_host);
+
+#endif /* PIM_INT_H */
diff --git a/pimd/pim_join.c b/pimd/pim_join.c
new file mode 100644
index 00000000..9d8e0012
--- /dev/null
+++ b/pimd/pim_join.c
@@ -0,0 +1,445 @@
+/*
+ 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 "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, 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?>", upstream, up_str, sizeof(up_str));
+ pim_inet4_dump("<src?>", source, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<neigh?>", 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?>", upstream, up_str, sizeof(up_str));
+ pim_inet4_dump("<src?>", source, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", group, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<neigh?>", 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?>", 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?>", 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?>", 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?>", src_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<addr?>", 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?>", 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?>", src_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<addr?>", msg_upstream_addr.u.prefix4,
+ upstream_str, sizeof(upstream_str));
+ pim_inet4_dump("<grp?>", 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("<src?>", source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<dst?>", 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("<src?>", source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<dst?>", 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("<dst?>", 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("<grp?>", 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("<src?>", 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;
+}
diff --git a/pimd/pim_join.h b/pimd/pim_join.h
new file mode 100644
index 00000000..37ec0f45
--- /dev/null
+++ b/pimd/pim_join.h
@@ -0,0 +1,43 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_JOIN_H
+#define PIM_JOIN_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_neighbor.h"
+
+int pim_joinprune_recv(struct interface *ifp,
+ struct pim_neighbor *neigh,
+ struct in_addr src_addr,
+ uint8_t *tlv_buf, int tlv_buf_size);
+
+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);
+
+#endif /* PIM_JOIN_H */
diff --git a/pimd/pim_macro.c b/pimd/pim_macro.c
new file mode 100644
index 00000000..3f565325
--- /dev/null
+++ b/pimd/pim_macro.c
@@ -0,0 +1,437 @@
+/*
+ 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 "pim_macro.h"
+#include "pimd.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_ifchannel.h"
+
+#define PIM_IFP_I_am_DR(pim_ifp) ((pim_ifp)->pim_dr_addr.s_addr == (pim_ifp)->primary_address.s_addr)
+
+/*
+ DownstreamJPState(S,G,I) is the per-interface state machine for
+ receiving (S,G) Join/Prune messages.
+
+ DownstreamJPState(S,G,I) is either Join or Prune-Pending ?
+*/
+static int downstream_jpstate_isjoined(const struct pim_ifchannel *ch)
+{
+ return ch->ifjoin_state != PIM_IFJOIN_NOINFO;
+}
+
+/*
+ The clause "local_receiver_include(S,G,I)" is true if the IGMP/MLD
+ module or other local membership mechanism has determined that local
+ members on interface I desire to receive traffic sent specifically
+ by S to G.
+*/
+static int local_receiver_include(const struct pim_ifchannel *ch)
+{
+ /* local_receiver_include(S,G,I) ? */
+ return ch->local_ifmembership == PIM_IFMEMBERSHIP_INCLUDE;
+}
+
+/*
+ RFC 4601: 4.1.6. State Summarization Macros
+
+ The set "joins(S,G)" is the set of all interfaces on which the
+ router has received (S,G) Joins:
+
+ joins(S,G) =
+ { all interfaces I such that
+ DownstreamJPState(S,G,I) is either Join or Prune-Pending }
+
+ DownstreamJPState(S,G,I) is either Join or Prune-Pending ?
+*/
+int pim_macro_chisin_joins(const struct pim_ifchannel *ch)
+{
+ return downstream_jpstate_isjoined(ch);
+}
+
+/*
+ RFC 4601: 4.6.5. Assert State Macros
+
+ The set "lost_assert(S,G)" is the set of all interfaces on which the
+ router has received (S,G) joins but has lost an (S,G) assert.
+
+ lost_assert(S,G) =
+ { all interfaces I such that
+ lost_assert(S,G,I) == TRUE }
+
+ bool lost_assert(S,G,I) {
+ if ( RPF_interface(S) == I ) {
+ return FALSE
+ } else {
+ return ( AssertWinner(S,G,I) != NULL AND
+ AssertWinner(S,G,I) != me AND
+ (AssertWinnerMetric(S,G,I) is better
+ than spt_assert_metric(S,I) )
+ }
+ }
+
+ AssertWinner(S,G,I) is the IP source address of the Assert(S,G)
+ packet that won an Assert.
+*/
+int pim_macro_ch_lost_assert(const struct pim_ifchannel *ch)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_assert_metric spt_assert_metric;
+
+ ifp = ch->interface;
+ if (!ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): null interface",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str);
+ return 0; /* false */
+ }
+
+ /* RPF_interface(S) == I ? */
+ if (ch->upstream->rpf.source_nexthop.interface == ifp)
+ return 0; /* false */
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ifp->name);
+ return 0; /* false */
+ }
+
+ if (PIM_INADDR_IS_ANY(ch->ifassert_winner))
+ return 0; /* false */
+
+ /* AssertWinner(S,G,I) == me ? */
+ if (ch->ifassert_winner.s_addr == pim_ifp->primary_address.s_addr)
+ return 0; /* false */
+
+ spt_assert_metric = pim_macro_spt_assert_metric(&ch->upstream->rpf,
+ pim_ifp->primary_address);
+
+ return pim_assert_metric_better(&ch->ifassert_winner_metric,
+ &spt_assert_metric);
+}
+
+/*
+ RFC 4601: 4.1.6. State Summarization Macros
+
+ pim_include(S,G) =
+ { all interfaces I such that:
+ ( (I_am_DR( I ) AND lost_assert(S,G,I) == FALSE )
+ OR AssertWinner(S,G,I) == me )
+ AND local_receiver_include(S,G,I) }
+
+ AssertWinner(S,G,I) is the IP source address of the Assert(S,G)
+ packet that won an Assert.
+*/
+int pim_macro_chisin_pim_include(const struct pim_ifchannel *ch)
+{
+ struct pim_interface *pim_ifp = ch->interface->info;
+
+ if (!pim_ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name);
+ return 0; /* false */
+ }
+
+ /* local_receiver_include(S,G,I) ? */
+ if (!local_receiver_include(ch))
+ return 0; /* false */
+
+ /* OR AssertWinner(S,G,I) == me ? */
+ if (ch->ifassert_winner.s_addr == pim_ifp->primary_address.s_addr)
+ return 1; /* true */
+
+ return (
+ /* I_am_DR( I ) ? */
+ PIM_IFP_I_am_DR(pim_ifp)
+ &&
+ /* lost_assert(S,G,I) == FALSE ? */
+ (!pim_macro_ch_lost_assert(ch))
+ );
+}
+
+int pim_macro_chisin_joins_or_include(const struct pim_ifchannel *ch)
+{
+ if (pim_macro_chisin_joins(ch))
+ return 1; /* true */
+
+ return pim_macro_chisin_pim_include(ch);
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ CouldAssert(S,G,I) =
+ SPTbit(S,G)==TRUE
+ AND (RPF_interface(S) != I)
+ AND (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) )
+ (+) ( pim_include(*,G) (-) pim_exclude(S,G) )
+ (-) lost_assert(*,G)
+ (+) joins(S,G) (+) pim_include(S,G) ) )
+
+ CouldAssert(S,G,I) is true for downstream interfaces that would be in
+ the inherited_olist(S,G) if (S,G) assert information was not taken
+ into account.
+
+ CouldAssert(S,G,I) may be affected by changes in the following:
+
+ pim_ifp->primary_address
+ pim_ifp->pim_dr_addr
+ ch->ifassert_winner_metric
+ ch->ifassert_winner
+ ch->local_ifmembership
+ ch->ifjoin_state
+ ch->upstream->rpf.source_nexthop.mrib_metric_preference
+ ch->upstream->rpf.source_nexthop.mrib_route_metric
+ ch->upstream->rpf.source_nexthop.interface
+*/
+int pim_macro_ch_could_assert_eval(const struct pim_ifchannel *ch)
+{
+ struct interface *ifp;
+
+ /* SPTbit(S,G) is always true for PIM-SSM-Only Routers */
+
+ ifp = ch->interface;
+ if (!ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): null interface",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str);
+ return 0; /* false */
+ }
+
+ /* RPF_interface(S) != I ? */
+ if (ch->upstream->rpf.source_nexthop.interface == ifp)
+ return 0; /* false */
+
+ /* I in joins(S,G) (+) pim_include(S,G) ? */
+ return pim_macro_chisin_joins_or_include(ch);
+}
+
+/*
+ RFC 4601: 4.6.3. Assert Metrics
+
+ spt_assert_metric(S,I) gives the assert metric we use if we're
+ sending an assert based on active (S,G) forwarding state:
+
+ assert_metric
+ spt_assert_metric(S,I) {
+ return {0,MRIB.pref(S),MRIB.metric(S),my_ip_address(I)}
+ }
+*/
+struct pim_assert_metric pim_macro_spt_assert_metric(const struct pim_rpf *rpf,
+ struct in_addr ifaddr)
+{
+ struct pim_assert_metric metric;
+
+ metric.rpt_bit_flag = 0;
+ metric.metric_preference = rpf->source_nexthop.mrib_metric_preference;
+ metric.route_metric = rpf->source_nexthop.mrib_route_metric;
+ metric.ip_address = ifaddr;
+
+ return metric;
+}
+
+/*
+ RFC 4601: 4.6.3. Assert Metrics
+
+ An assert metric for (S,G) to include in (or compare against) an
+ Assert message sent on interface I should be computed using the
+ following pseudocode:
+
+ assert_metric my_assert_metric(S,G,I) {
+ if( CouldAssert(S,G,I) == TRUE ) {
+ return spt_assert_metric(S,I)
+ } else if( CouldAssert(*,G,I) == TRUE ) {
+ return rpt_assert_metric(G,I)
+ } else {
+ return infinite_assert_metric()
+ }
+ }
+*/
+struct pim_assert_metric pim_macro_ch_my_assert_metric_eval(const struct pim_ifchannel *ch)
+{
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ch->interface->info;
+
+ if (pim_ifp) {
+ if (PIM_IF_FLAG_TEST_COULD_ASSERT(ch->flags)) {
+ return pim_macro_spt_assert_metric(&ch->upstream->rpf, pim_ifp->primary_address);
+ }
+ }
+
+ return qpim_infinite_assert_metric;
+}
+
+/*
+ RFC 4601 4.2. Data Packet Forwarding Rules
+ RFC 4601 4.8.2. PIM-SSM-Only Routers
+
+ Macro:
+ inherited_olist(S,G) =
+ joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+*/
+static int pim_macro_chisin_inherited_olist(const struct pim_ifchannel *ch)
+{
+ if (pim_macro_ch_lost_assert(ch))
+ return 0; /* false */
+
+ return pim_macro_chisin_joins_or_include(ch);
+}
+
+/*
+ RFC 4601 4.2. Data Packet Forwarding Rules
+ RFC 4601 4.8.2. PIM-SSM-Only Routers
+
+ Additionally, the Packet forwarding rules of Section 4.2 can be
+ simplified in a PIM-SSM-only router:
+
+ iif is the incoming interface of the packet.
+ oiflist = NULL
+ if (iif == RPF_interface(S) AND UpstreamJPState(S,G) == Joined) {
+ oiflist = inherited_olist(S,G)
+ } else if (iif is in inherited_olist(S,G)) {
+ send Assert(S,G) on iif
+ }
+ oiflist = oiflist (-) iif
+ forward packet on all interfaces in oiflist
+
+ Macro:
+ inherited_olist(S,G) =
+ joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+
+ Note:
+ - The following test is performed as response to WRONGVIF kernel
+ upcall:
+ if (iif is in inherited_olist(S,G)) {
+ send Assert(S,G) on iif
+ }
+ See pim_mroute.c mroute_msg().
+*/
+int pim_macro_chisin_oiflist(const struct pim_ifchannel *ch)
+{
+ if (ch->upstream->join_state != PIM_UPSTREAM_JOINED) {
+ /* oiflist is NULL */
+ return 0; /* false */
+ }
+
+ /* oiflist = oiflist (-) iif */
+ if (ch->interface == ch->upstream->rpf.source_nexthop.interface)
+ return 0; /* false */
+
+ return pim_macro_chisin_inherited_olist(ch);
+}
+
+/*
+ RFC 4601: 4.6.1. (S,G) Assert Message State Machine
+
+ AssertTrackingDesired(S,G,I) =
+ (I in ( ( joins(*,*,RP(G)) (+) joins(*,G) (-) prunes(S,G,rpt) )
+ (+) ( pim_include(*,G) (-) pim_exclude(S,G) )
+ (-) lost_assert(*,G)
+ (+) joins(S,G) ) )
+ OR (local_receiver_include(S,G,I) == TRUE
+ AND (I_am_DR(I) OR (AssertWinner(S,G,I) == me)))
+ OR ((RPF_interface(S) == I) AND (JoinDesired(S,G) == TRUE))
+ OR ((RPF_interface(RP(G)) == I) AND (JoinDesired(*,G) == TRUE)
+ AND (SPTbit(S,G) == FALSE))
+
+ AssertTrackingDesired(S,G,I) is true on any interface in which an
+ (S,G) assert might affect our behavior.
+*/
+int pim_macro_assert_tracking_desired_eval(const struct pim_ifchannel *ch)
+{
+ struct pim_interface *pim_ifp;
+ struct interface *ifp;
+
+ ifp = ch->interface;
+ if (!ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): null interface",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str);
+ return 0; /* false */
+ }
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", ch->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", ch->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: (S,G)=(%s,%s): multicast not enabled on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, ch->interface->name);
+ return 0; /* false */
+ }
+
+ /* I in joins(S,G) ? */
+ if (pim_macro_chisin_joins(ch))
+ return 1; /* true */
+
+ /* local_receiver_include(S,G,I) ? */
+ if (local_receiver_include(ch)) {
+ /* I_am_DR(I) ? */
+ if (PIM_IFP_I_am_DR(pim_ifp))
+ return 1; /* true */
+
+ /* AssertWinner(S,G,I) == me ? */
+ if (ch->ifassert_winner.s_addr == pim_ifp->primary_address.s_addr)
+ return 1; /* true */
+ }
+
+ /* RPF_interface(S) == I ? */
+ if (ch->upstream->rpf.source_nexthop.interface == ifp) {
+ /* JoinDesired(S,G) ? */
+ if (PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(ch->upstream->flags))
+ return 1; /* true */
+ }
+
+ return 0; /* false */
+}
+
diff --git a/pimd/pim_macro.h b/pimd/pim_macro.h
new file mode 100644
index 00000000..472fa9b4
--- /dev/null
+++ b/pimd/pim_macro.h
@@ -0,0 +1,44 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_MACRO_H
+#define PIM_MACRO_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_upstream.h"
+#include "pim_ifchannel.h"
+
+int pim_macro_ch_lost_assert(const struct pim_ifchannel *ch);
+int pim_macro_chisin_joins(const struct pim_ifchannel *ch);
+int pim_macro_chisin_pim_include(const struct pim_ifchannel *ch);
+int pim_macro_chisin_joins_or_include(const struct pim_ifchannel *ch);
+int pim_macro_ch_could_assert_eval(const struct pim_ifchannel *ch);
+struct pim_assert_metric pim_macro_spt_assert_metric(const struct pim_rpf *rpf,
+ struct in_addr ifaddr);
+struct pim_assert_metric pim_macro_ch_my_assert_metric_eval(const struct pim_ifchannel *ch);
+int pim_macro_chisin_oiflist(const struct pim_ifchannel *ch);
+int pim_macro_assert_tracking_desired_eval(const struct pim_ifchannel *ch);
+
+#endif /* PIM_MACRO_H */
diff --git a/pimd/pim_main.c b/pimd/pim_main.c
new file mode 100644
index 00000000..b57f8811
--- /dev/null
+++ b/pimd/pim_main.c
@@ -0,0 +1,294 @@
+/*
+ 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 "version.h"
+#include <getopt.h>
+#include "command.h"
+#include "thread.h"
+#include <signal.h>
+
+#include "memory.h"
+#include "filter.h"
+#include "vty.h"
+#include "sigevent.h"
+#include "version.h"
+
+#include "pimd.h"
+#include "pim_version.h"
+#include "pim_signals.h"
+#include "pim_zebra.h"
+
+#ifdef PIM_ZCLIENT_DEBUG
+extern int zclient_debug;
+#endif
+
+extern struct host host;
+
+char config_default[] = SYSCONFDIR PIMD_DEFAULT_CONFIG;
+
+struct option longopts[] = {
+ { "daemon", no_argument, NULL, 'd'},
+ { "config_file", required_argument, NULL, 'f'},
+ { "pid_file", required_argument, NULL, 'i'},
+ { "vty_addr", required_argument, NULL, 'A'},
+ { "vty_port", required_argument, NULL, 'P'},
+ { "version", no_argument, NULL, 'v'},
+ { "debug_zclient", no_argument, NULL, 'Z'},
+ { "help", no_argument, NULL, 'h'},
+ { 0 }
+};
+
+/* pimd privileges */
+zebra_capabilities_t _caps_p [] =
+{
+ ZCAP_NET_ADMIN,
+ ZCAP_SYS_ADMIN,
+ ZCAP_NET_RAW,
+};
+
+/* pimd privileges to run with */
+struct zebra_privs_t pimd_privs =
+{
+#if defined(QUAGGA_USER) && defined(QUAGGA_GROUP)
+ .user = QUAGGA_USER,
+ .group = QUAGGA_GROUP,
+#endif
+#ifdef VTY_GROUP
+ .vty_group = VTY_GROUP,
+#endif
+ .caps_p = _caps_p,
+ .cap_num_p = sizeof(_caps_p)/sizeof(_caps_p[0]),
+ .cap_num_i = 0
+};
+
+char* progname;
+const char *pid_file = PATH_PIMD_PID;
+
+static void usage(int status)
+{
+ if (status != 0)
+ fprintf (stderr, "Try `%s --help' for more information.\n", progname);
+ else {
+ printf ("Usage : %s [OPTION...]\n\
+Daemon which manages PIM.\n\n\
+-d, --daemon Run in daemon mode\n\
+-f, --config_file Set configuration file name\n\
+-i, --pid_file Set process identifier file name\n\
+-z, --socket Set path of zebra socket\n\
+-A, --vty_addr Set vty's bind address\n\
+-P, --vty_port Set vty's port number\n\
+-v, --version Print program version\n\
+"
+
+#ifdef PIM_ZCLIENT_DEBUG
+"\
+-Z, --debug_zclient Enable zclient debugging\n\
+"
+#endif
+
+"\
+-h, --help Display this help and exit\n\
+\n\
+Report bugs to %s\n", progname, PIMD_BUG_ADDRESS);
+ }
+
+ exit (status);
+}
+
+
+int main(int argc, char** argv, char** envp) {
+ char *p;
+ char *vty_addr = NULL;
+ int vty_port = -1;
+ int daemon_mode = 0;
+ char *config_file = NULL;
+ char *zebra_sock_path = NULL;
+ struct thread thread;
+
+ umask(0027);
+
+ progname = ((p = strrchr(argv[0], '/')) ? ++p : argv[0]);
+
+ zlog_default = openzlog(progname, ZLOG_PIM,
+ LOG_CONS|LOG_NDELAY|LOG_PID, LOG_DAEMON);
+
+ /* this while just reads the options */
+ while (1) {
+ int opt;
+
+ opt = getopt_long (argc, argv, "df:i:z:A:P:vZh", longopts, 0);
+
+ if (opt == EOF)
+ break;
+
+ switch (opt) {
+ case 0:
+ break;
+ case 'd':
+ daemon_mode = 1;
+ break;
+ case 'f':
+ config_file = optarg;
+ break;
+ case 'i':
+ pid_file = optarg;
+ break;
+ case 'z':
+ zebra_sock_path = optarg;
+ break;
+ case 'A':
+ vty_addr = optarg;
+ break;
+ case 'P':
+ vty_port = atoi (optarg);
+ break;
+ case 'v':
+ printf(PIMD_PROGNAME " version %s\n", PIMD_VERSION);
+ print_version(QUAGGA_PROGNAME);
+ exit (0);
+ break;
+#ifdef PIM_ZCLIENT_DEBUG
+ case 'Z':
+ zclient_debug = 1;
+ break;
+#endif
+ case 'h':
+ usage (0);
+ break;
+ default:
+ usage (1);
+ break;
+ }
+ }
+
+ master = thread_master_create();
+
+ /*
+ * Temporarily send zlog to stdout
+ */
+ zlog_default->maxlvl[ZLOG_DEST_STDOUT] = zlog_default->default_lvl;
+ zlog_notice("Boot logging temporarily directed to stdout - begin");
+
+ zlog_notice("Quagga %s " PIMD_PROGNAME " %s starting",
+ QUAGGA_VERSION, PIMD_VERSION);
+
+ /*
+ * Initializations
+ */
+ zprivs_init (&pimd_privs);
+ pim_signals_init();
+ cmd_init(1);
+ vty_init(master);
+ memory_init();
+ access_list_init();
+ pim_init();
+
+ /*
+ * reset zlog default, then will obey configuration file
+ */
+ zlog_notice("Boot logging temporarily directed to stdout - end");
+#if 0
+ /* this would disable logging to stdout, but config has not been
+ loaded yet to reconfig the logging output */
+ zlog_default->maxlvl[ZLOG_DEST_STDOUT] = ZLOG_DISABLED;
+#endif
+
+ /*
+ Initialize zclient "update" and "lookup" sockets
+ */
+ pim_zebra_init(zebra_sock_path);
+
+ zlog_notice("Loading configuration - begin");
+
+ /* Get configuration file. */
+ vty_read_config(config_file, config_default);
+
+ /*
+ Starting from here zlog_* functions will log according configuration
+ */
+
+ zlog_notice("Loading configuration - end");
+
+ /* Change to the daemon program. */
+ if (daemon_mode) {
+ if (daemon(0, 0)) {
+ zlog_warn("failed to daemonize");
+ }
+ }
+
+ /* Process ID file creation. */
+ pid_output(pid_file);
+
+ /* Create pimd VTY socket */
+ if (vty_port < 0)
+ vty_port = PIMD_VTY_PORT;
+ vty_serv_sock(vty_addr, vty_port, PIM_VTYSH_PATH);
+
+ zlog_notice("Quagga %s " PIMD_PROGNAME " %s starting, VTY interface at port TCP %d",
+ QUAGGA_VERSION, PIMD_VERSION, vty_port);
+
+#ifdef PIM_DEBUG_BYDEFAULT
+ zlog_notice("PIM_DEBUG_BYDEFAULT: Enabling all debug commands");
+ PIM_DO_DEBUG_PIM_EVENTS;
+ PIM_DO_DEBUG_PIM_PACKETS;
+ PIM_DO_DEBUG_PIM_TRACE;
+ PIM_DO_DEBUG_IGMP_EVENTS;
+ PIM_DO_DEBUG_IGMP_PACKETS;
+ PIM_DO_DEBUG_IGMP_TRACE;
+ PIM_DO_DEBUG_ZEBRA;
+#endif
+
+#ifdef PIM_ZCLIENT_DEBUG
+ zlog_notice("PIM_ZCLIENT_DEBUG: zclient debugging is supported, mode is %s (see option -Z)",
+ zclient_debug ? "ON" : "OFF");
+#endif
+
+#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
+ zlog_notice("PIM_CHECK_RECV_IFINDEX_SANITY: will match sock/recv ifindex");
+#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
+ zlog_notice("PIM_REPORT_RECV_IFINDEX_MISMATCH: will report sock/recv ifindex mismatch");
+#endif
+#endif
+
+#ifdef PIM_UNEXPECTED_KERNEL_UPCALL
+ zlog_notice("PIM_UNEXPECTED_KERNEL_UPCALL: report unexpected kernel upcall");
+#endif
+
+#ifdef HAVE_CLOCK_MONOTONIC
+ zlog_notice("HAVE_CLOCK_MONOTONIC");
+#else
+ zlog_notice("!HAVE_CLOCK_MONOTONIC");
+#endif
+
+ while (thread_fetch(master, &thread))
+ thread_call(&thread);
+
+ zlog_err("%s %s: thread_fetch() returned NULL, exiting",
+ __FILE__, __PRETTY_FUNCTION__);
+
+ /* never reached */
+ return 0;
+}
diff --git a/pimd/pim_mroute.c b/pimd/pim_mroute.c
new file mode 100644
index 00000000..fa460e28
--- /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;
+}
diff --git a/pimd/pim_mroute.h b/pimd/pim_mroute.h
new file mode 100644
index 00000000..350b1e37
--- /dev/null
+++ b/pimd/pim_mroute.h
@@ -0,0 +1,173 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_MROUTE_H
+#define PIM_MROUTE_H
+
+/*
+ For msghdr.msg_control in Solaris 10
+*/
+#ifndef _XPG4_2
+#define _XPG4_2
+#endif
+#ifndef __EXTENSIONS__
+#define __EXTENSIONS__
+#endif
+
+#include <netinet/in.h>
+#ifdef HAVE_NETINET_IP_MROUTE_H
+#include <netinet/ip_mroute.h>
+#endif
+
+#define PIM_MROUTE_MIN_TTL (1)
+
+/*
+ Below: from <linux/mroute.h>
+*/
+
+#ifndef MAXVIFS
+#define MAXVIFS (32)
+#endif
+
+#ifndef SIOCGETVIFCNT
+#define SIOCGETVIFCNT SIOCPROTOPRIVATE /* IP protocol privates */
+#define SIOCGETSGCNT (SIOCPROTOPRIVATE+1)
+#define SIOCGETRPF (SIOCPROTOPRIVATE+2)
+#endif
+
+#ifndef MRT_INIT
+#define MRT_BASE 200
+#define MRT_INIT (MRT_BASE) /* Activate the kernel mroute code */
+#define MRT_DONE (MRT_BASE+1) /* Shutdown the kernel mroute */
+#define MRT_ADD_VIF (MRT_BASE+2) /* Add a virtual interface */
+#define MRT_DEL_VIF (MRT_BASE+3) /* Delete a virtual interface */
+#define MRT_ADD_MFC (MRT_BASE+4) /* Add a multicast forwarding entry */
+#define MRT_DEL_MFC (MRT_BASE+5) /* Delete a multicast forwarding entry */
+#define MRT_VERSION (MRT_BASE+6) /* Get the kernel multicast version */
+#define MRT_ASSERT (MRT_BASE+7) /* Activate PIM assert mode */
+#define MRT_PIM (MRT_BASE+8) /* enable PIM code */
+#endif
+
+#ifndef HAVE_VIFI_T
+typedef unsigned short vifi_t;
+#endif
+
+#ifndef HAVE_STRUCT_VIFCTL
+struct vifctl {
+ vifi_t vifc_vifi; /* Index of VIF */
+ unsigned char vifc_flags; /* VIFF_ flags */
+ unsigned char vifc_threshold; /* ttl limit */
+ unsigned int vifc_rate_limit; /* Rate limiter values (NI) */
+ struct in_addr vifc_lcl_addr; /* Our address */
+ struct in_addr vifc_rmt_addr; /* IPIP tunnel addr */
+};
+#endif
+
+#ifndef HAVE_STRUCT_MFCCTL
+struct mfcctl {
+ struct in_addr mfcc_origin; /* Origin of mcast */
+ struct in_addr mfcc_mcastgrp; /* Group in question */
+ vifi_t mfcc_parent; /* Where it arrived */
+ unsigned char mfcc_ttls[MAXVIFS]; /* Where it is going */
+ unsigned int mfcc_pkt_cnt; /* pkt count for src-grp */
+ unsigned int mfcc_byte_cnt;
+ unsigned int mfcc_wrong_if;
+ int mfcc_expire;
+};
+#endif
+
+/*
+ * Group count retrieval for mrouted
+ */
+/*
+ struct sioc_sg_req sgreq;
+ memset(&sgreq, 0, sizeof(sgreq));
+ memcpy(&sgreq.src, &source_addr, sizeof(sgreq.src));
+ memcpy(&sgreq.grp, &group_addr, sizeof(sgreq.grp));
+ ioctl(mrouter_s4, SIOCGETSGCNT, &sgreq);
+ */
+#ifndef HAVE_STRUCT_SIOC_SG_REQ
+struct sioc_sg_req {
+ struct in_addr src;
+ struct in_addr grp;
+ unsigned long pktcnt;
+ unsigned long bytecnt;
+ unsigned long wrong_if;
+};
+#endif
+
+/*
+ * To get vif packet counts
+ */
+/*
+ struct sioc_vif_req vreq;
+ memset(&vreq, 0, sizeof(vreq));
+ vreq.vifi = vif_index;
+ ioctl(mrouter_s4, SIOCGETVIFCNT, &vreq);
+ */
+#ifndef HAVE_STRUCT_SIOC_VIF_REQ
+struct sioc_vif_req {
+ vifi_t vifi; /* Which iface */
+ unsigned long icount; /* In packets */
+ unsigned long ocount; /* Out packets */
+ unsigned long ibytes; /* In bytes */
+ unsigned long obytes; /* Out bytes */
+};
+#endif
+
+/*
+ * Pseudo messages used by mrouted
+ */
+#ifndef IGMPMSG_NOCACHE
+#define IGMPMSG_NOCACHE 1 /* Kern cache fill request to mrouted */
+#define IGMPMSG_WRONGVIF 2 /* For PIM assert processing (unused) */
+#define IGMPMSG_WHOLEPKT 3 /* For PIM Register processing */
+#endif
+
+#ifndef HAVE_STRUCT_IGMPMSG
+struct igmpmsg
+{
+ uint32_t unused1,unused2;
+ unsigned char im_msgtype; /* What is this */
+ unsigned char im_mbz; /* Must be zero */
+ unsigned char im_vif; /* Interface (this ought to be a vifi_t!) */
+ unsigned char unused3;
+ struct in_addr im_src,im_dst;
+};
+#endif
+
+/*
+ Above: from <linux/mroute.h>
+*/
+
+int pim_mroute_socket_enable(void);
+int pim_mroute_socket_disable(void);
+
+int pim_mroute_add_vif(int vif_index, struct in_addr ifaddr);
+int pim_mroute_del_vif(int vif_index);
+
+int pim_mroute_add(struct mfcctl *mc);
+int pim_mroute_del(struct mfcctl *mc);
+
+int pim_mroute_msg(int fd, const char *buf, int buf_size);
+
+#endif /* PIM_MROUTE_H */
diff --git a/pimd/pim_msg.c b/pimd/pim_msg.c
new file mode 100644
index 00000000..8ead7ce6
--- /dev/null
+++ b/pimd/pim_msg.c
@@ -0,0 +1,106 @@
+/*
+ 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 "pimd.h"
+#include "pim_pim.h"
+#include "pim_msg.h"
+#include "pim_util.h"
+
+void pim_msg_build_header(uint8_t *pim_msg, int pim_msg_size,
+ uint8_t pim_msg_type)
+{
+ uint16_t checksum;
+
+ zassert(pim_msg_size >= PIM_PIM_MIN_LEN);
+
+ /*
+ * Write header
+ */
+
+ *(uint8_t *) PIM_MSG_HDR_OFFSET_VERSION(pim_msg) = (PIM_PROTO_VERSION << 4) | pim_msg_type;
+ *(uint8_t *) PIM_MSG_HDR_OFFSET_RESERVED(pim_msg) = 0;
+
+ /*
+ * Compute checksum
+ */
+
+ *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) = 0;
+ checksum = in_cksum(pim_msg, pim_msg_size);
+ *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) = checksum;
+}
+
+uint8_t *pim_msg_addr_encode_ipv4_ucast(uint8_t *buf,
+ int buf_size,
+ struct in_addr addr)
+{
+ const int ENCODED_IPV4_UCAST_SIZE = 6;
+
+ if (buf_size < ENCODED_IPV4_UCAST_SIZE) {
+ return 0;
+ }
+
+ buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */
+ buf[1] = '\0'; /* native encoding */
+ memcpy(buf+2, &addr, sizeof(struct in_addr));
+
+ return buf + ENCODED_IPV4_UCAST_SIZE;
+}
+
+uint8_t *pim_msg_addr_encode_ipv4_group(uint8_t *buf,
+ int buf_size,
+ struct in_addr addr)
+{
+ const int ENCODED_IPV4_GROUP_SIZE = 8;
+
+ if (buf_size < ENCODED_IPV4_GROUP_SIZE) {
+ return 0;
+ }
+
+ buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */
+ buf[1] = '\0'; /* native encoding */
+ buf[2] = '\0'; /* reserved */
+ buf[3] = 32; /* mask len */
+ memcpy(buf+4, &addr, sizeof(struct in_addr));
+
+ return buf + ENCODED_IPV4_GROUP_SIZE;
+}
+
+uint8_t *pim_msg_addr_encode_ipv4_source(uint8_t *buf,
+ int buf_size,
+ struct in_addr addr)
+{
+ const int ENCODED_IPV4_SOURCE_SIZE = 8;
+
+ if (buf_size < ENCODED_IPV4_SOURCE_SIZE) {
+ return 0;
+ }
+
+ buf[0] = PIM_MSG_ADDRESS_FAMILY_IPV4; /* addr family */
+ buf[1] = '\0'; /* native encoding */
+ buf[2] = 4; /* reserved = 0 | S bit = 1 | W bit = 0 | R bit = 0 */
+ buf[3] = 32; /* mask len */
+ memcpy(buf+4, &addr, sizeof(struct in_addr));
+
+ return buf + ENCODED_IPV4_SOURCE_SIZE;
+}
diff --git a/pimd/pim_msg.h b/pimd/pim_msg.h
new file mode 100644
index 00000000..a884fc84
--- /dev/null
+++ b/pimd/pim_msg.h
@@ -0,0 +1,52 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_MSG_H
+#define PIM_MSG_H
+
+#include <netinet/in.h>
+
+/*
+ Number Description
+ ---------- ------------------
+ 0 Reserved
+ 1 IP (IP version 4)
+ 2 IP6 (IP version 6)
+
+ From:
+ http://www.iana.org/assignments/address-family-numbers
+*/
+#define PIM_MSG_ADDRESS_FAMILY_IPV4 (1)
+
+void pim_msg_build_header(uint8_t *pim_msg, int pim_msg_size,
+ uint8_t pim_msg_type);
+uint8_t *pim_msg_addr_encode_ipv4_ucast(uint8_t *buf,
+ int buf_size,
+ struct in_addr addr);
+uint8_t *pim_msg_addr_encode_ipv4_group(uint8_t *buf,
+ int buf_size,
+ struct in_addr addr);
+uint8_t *pim_msg_addr_encode_ipv4_source(uint8_t *buf,
+ int buf_size,
+ struct in_addr addr);
+
+#endif /* PIM_MSG_H */
diff --git a/pimd/pim_neighbor.c b/pimd/pim_neighbor.c
new file mode 100644
index 00000000..9404cec1
--- /dev/null
+++ b/pimd/pim_neighbor.c
@@ -0,0 +1,719 @@
+/*
+ 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 "prefix.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_neighbor.h"
+#include "pim_time.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_pim.h"
+#include "pim_upstream.h"
+#include "pim_ifchannel.h"
+
+static void dr_election_by_addr(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct pim_neighbor *neigh;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ pim_ifp->pim_dr_addr = pim_ifp->primary_address;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("%s: on interface %s",
+ __PRETTY_FUNCTION__,
+ ifp->name);
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
+ if (ntohl(neigh->source_addr.s_addr) > ntohl(pim_ifp->pim_dr_addr.s_addr)) {
+ pim_ifp->pim_dr_addr = neigh->source_addr;
+ }
+ }
+}
+
+static void dr_election_by_pri(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct pim_neighbor *neigh;
+ uint32_t dr_pri;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ pim_ifp->pim_dr_addr = pim_ifp->primary_address;
+ dr_pri = pim_ifp->pim_dr_priority;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("%s: dr pri %u on interface %s",
+ __PRETTY_FUNCTION__,
+ dr_pri, ifp->name);
+ }
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_info("%s: neigh pri %u addr %x if dr addr %x",
+ __PRETTY_FUNCTION__,
+ neigh->dr_priority,
+ ntohl(neigh->source_addr.s_addr),
+ ntohl(pim_ifp->pim_dr_addr.s_addr));
+ }
+ if (
+ (neigh->dr_priority > dr_pri) ||
+ (
+ (neigh->dr_priority == dr_pri) &&
+ (ntohl(neigh->source_addr.s_addr) > ntohl(pim_ifp->pim_dr_addr.s_addr))
+ )
+ ) {
+ pim_ifp->pim_dr_addr = neigh->source_addr;
+ dr_pri = neigh->dr_priority;
+ }
+ }
+}
+
+/*
+ RFC 4601: 4.3.2. DR Election
+
+ A router's idea of the current DR on an interface can change when a
+ PIM Hello message is received, when a neighbor times out, or when a
+ router's own DR Priority changes.
+ */
+void pim_if_dr_election(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp = ifp->info;
+ struct in_addr old_dr_addr;
+
+ ++pim_ifp->pim_dr_election_count;
+
+ old_dr_addr = pim_ifp->pim_dr_addr;
+
+ if (pim_ifp->pim_dr_num_nondrpri_neighbors) {
+ dr_election_by_addr(ifp);
+ }
+ else {
+ dr_election_by_pri(ifp);
+ }
+
+ /* DR changed ? */
+ if (old_dr_addr.s_addr != pim_ifp->pim_dr_addr.s_addr) {
+
+ /* if (PIM_DEBUG_PIM_EVENTS) */ {
+ char dr_old_str[100];
+ char dr_new_str[100];
+ pim_inet4_dump("<old_dr?>", old_dr_addr, dr_old_str, sizeof(dr_old_str));
+ pim_inet4_dump("<new_dr?>", pim_ifp->pim_dr_addr, dr_new_str, sizeof(dr_new_str));
+ zlog_debug("%s: DR was %s now is %s on interface %s",
+ __PRETTY_FUNCTION__,
+ dr_old_str, dr_new_str, ifp->name);
+ }
+
+ pim_ifp->pim_dr_election_last = pim_time_monotonic_sec(); /* timestamp */
+ ++pim_ifp->pim_dr_election_changes;
+ pim_if_update_join_desired(pim_ifp);
+ pim_if_update_could_assert(ifp);
+ pim_if_update_assert_tracking_desired(ifp);
+ }
+}
+
+static void update_dr_priority(struct pim_neighbor *neigh,
+ pim_hello_options hello_options,
+ uint32_t dr_priority)
+{
+ pim_hello_options will_set_pri; /* boolean */
+ pim_hello_options bit_flip; /* boolean */
+ pim_hello_options pri_change; /* boolean */
+
+ will_set_pri = PIM_OPTION_IS_SET(hello_options,
+ PIM_OPTION_MASK_DR_PRIORITY);
+
+ bit_flip =
+ (
+ will_set_pri !=
+ PIM_OPTION_IS_SET(neigh->hello_options, PIM_OPTION_MASK_DR_PRIORITY)
+ );
+
+ if (bit_flip) {
+ struct pim_interface *pim_ifp = neigh->interface->info;
+
+ /* update num. of neighbors without dr_pri */
+
+ if (will_set_pri) {
+ --pim_ifp->pim_dr_num_nondrpri_neighbors;
+ }
+ else {
+ ++pim_ifp->pim_dr_num_nondrpri_neighbors;
+ }
+ }
+
+ pri_change =
+ (
+ bit_flip
+ ||
+ (neigh->dr_priority != dr_priority)
+ );
+
+ if (will_set_pri) {
+ neigh->dr_priority = dr_priority;
+ }
+ else {
+ neigh->dr_priority = 0; /* cosmetic unset */
+ }
+
+ if (pri_change) {
+ /*
+ RFC 4601: 4.3.2. DR Election
+
+ A router's idea of the current DR on an interface can change when a
+ PIM Hello message is received, when a neighbor times out, or when a
+ router's own DR Priority changes.
+ */
+ pim_if_dr_election(neigh->interface); // router's own DR Priority changes
+ }
+}
+
+static int on_neighbor_timer(struct thread *t)
+{
+ struct pim_neighbor *neigh;
+ struct interface *ifp;
+ char msg[100];
+
+ zassert(t);
+ neigh = THREAD_ARG(t);
+ zassert(neigh);
+
+ ifp = neigh->interface;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+ zlog_debug("Expired %d sec holdtime for neighbor %s on interface %s",
+ neigh->holdtime, src_str, ifp->name);
+ }
+
+ neigh->t_expire_timer = 0;
+
+ snprintf(msg, sizeof(msg), "%d-sec holdtime expired", neigh->holdtime);
+ pim_neighbor_delete(ifp, neigh, msg);
+
+ /*
+ RFC 4601: 4.3.2. DR Election
+
+ A router's idea of the current DR on an interface can change when a
+ PIM Hello message is received, when a neighbor times out, or when a
+ router's own DR Priority changes.
+ */
+ pim_if_dr_election(ifp); // neighbor times out
+
+ return 0;
+}
+
+static void neighbor_timer_off(struct pim_neighbor *neigh)
+{
+ if (PIM_DEBUG_PIM_TRACE) {
+ if (neigh->t_expire_timer) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: cancelling timer for neighbor %s on %s",
+ __PRETTY_FUNCTION__,
+ src_str, neigh->interface->name);
+ }
+ }
+ THREAD_OFF(neigh->t_expire_timer);
+ zassert(!neigh->t_expire_timer);
+}
+
+void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime)
+{
+ neigh->holdtime = holdtime;
+
+ neighbor_timer_off(neigh);
+
+ /*
+ 0xFFFF is request for no holdtime
+ */
+ if (neigh->holdtime == 0xFFFF) {
+ return;
+ }
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: starting %u sec timer for neighbor %s on %s",
+ __PRETTY_FUNCTION__,
+ neigh->holdtime, src_str, neigh->interface->name);
+ }
+
+ THREAD_TIMER_ON(master, neigh->t_expire_timer,
+ on_neighbor_timer,
+ neigh, neigh->holdtime);
+}
+
+static struct pim_neighbor *pim_neighbor_new(struct interface *ifp,
+ struct in_addr source_addr,
+ pim_hello_options hello_options,
+ uint16_t holdtime,
+ uint16_t propagation_delay,
+ uint16_t override_interval,
+ uint32_t dr_priority,
+ uint32_t generation_id,
+ struct list *addr_list)
+{
+ struct pim_interface *pim_ifp;
+ struct pim_neighbor *neigh;
+ char src_str[100];
+
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ neigh = XMALLOC(MTYPE_PIM_NEIGHBOR, sizeof(*neigh));
+ if (!neigh) {
+ zlog_err("%s: PIM XMALLOC(%zu) failure",
+ __PRETTY_FUNCTION__, sizeof(*neigh));
+ return 0;
+ }
+
+ neigh->creation = pim_time_monotonic_sec();
+ neigh->source_addr = source_addr;
+ neigh->hello_options = hello_options;
+ neigh->propagation_delay_msec = propagation_delay;
+ neigh->override_interval_msec = override_interval;
+ neigh->dr_priority = dr_priority;
+ neigh->generation_id = generation_id;
+ neigh->prefix_list = addr_list;
+ neigh->t_expire_timer = 0;
+ neigh->interface = ifp;
+
+ pim_neighbor_timer_reset(neigh, holdtime);
+
+ pim_inet4_dump("<src?>", source_addr, src_str, sizeof(src_str));
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ zlog_debug("%s: creating PIM neighbor %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifp->name);
+ }
+
+ zlog_info("PIM NEIGHBOR UP: neighbor %s on interface %s",
+ src_str, ifp->name);
+
+ if (neigh->propagation_delay_msec > pim_ifp->pim_neighbors_highest_propagation_delay_msec) {
+ pim_ifp->pim_neighbors_highest_propagation_delay_msec = neigh->propagation_delay_msec;
+ }
+ if (neigh->override_interval_msec > pim_ifp->pim_neighbors_highest_override_interval_msec) {
+ pim_ifp->pim_neighbors_highest_override_interval_msec = neigh->override_interval_msec;
+ }
+
+ if (!PIM_OPTION_IS_SET(neigh->hello_options,
+ PIM_OPTION_MASK_LAN_PRUNE_DELAY)) {
+ /* update num. of neighbors without hello option lan_delay */
+ ++pim_ifp->pim_number_of_nonlandelay_neighbors;
+ }
+
+ if (!PIM_OPTION_IS_SET(neigh->hello_options,
+ PIM_OPTION_MASK_DR_PRIORITY)) {
+ /* update num. of neighbors without hello option dr_pri */
+ ++pim_ifp->pim_dr_num_nondrpri_neighbors;
+ }
+
+ /*
+ RFC 4601: 4.3.2. DR Election
+
+ A router's idea of the current DR on an interface can change when a
+ PIM Hello message is received, when a neighbor times out, or when a
+ router's own DR Priority changes.
+ */
+ pim_if_dr_election(neigh->interface); // new neighbor -- should not trigger dr election...
+
+ /*
+ RFC 4601: 4.3.1. Sending Hello Messages
+
+ To allow new or rebooting routers to learn of PIM neighbors quickly,
+ when a Hello message is received from a new neighbor, or a Hello
+ message with a new GenID is received from an existing neighbor, a
+ new Hello message should be sent on this interface after a
+ randomized delay between 0 and Triggered_Hello_Delay.
+ */
+ pim_hello_restart_triggered(neigh->interface);
+
+ return neigh;
+}
+
+static void delete_prefix_list(struct pim_neighbor *neigh)
+{
+ if (neigh->prefix_list) {
+
+#ifdef DUMP_PREFIX_LIST
+ struct listnode *p_node;
+ struct prefix *p;
+ char addr_str[10];
+ int list_size = neigh->prefix_list ? (int) listcount(neigh->prefix_list) : -1;
+ int i = 0;
+ for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, p_node, p)) {
+ pim_inet4_dump("<addr?>", p->u.prefix4, addr_str, sizeof(addr_str));
+ zlog_debug("%s: DUMP_PREFIX_LIST neigh=%x prefix_list=%x prefix=%x addr=%s [%d/%d]",
+ __PRETTY_FUNCTION__,
+ (unsigned) neigh, (unsigned) neigh->prefix_list, (unsigned) p,
+ addr_str, i, list_size);
+ ++i;
+ }
+#endif
+
+ list_delete(neigh->prefix_list);
+ neigh->prefix_list = 0;
+ }
+}
+
+void pim_neighbor_free(struct pim_neighbor *neigh)
+{
+ zassert(!neigh->t_expire_timer);
+
+ delete_prefix_list(neigh);
+
+ XFREE(MTYPE_PIM_NEIGHBOR, neigh);
+}
+
+struct pim_neighbor *pim_neighbor_find(struct interface *ifp,
+ struct in_addr source_addr)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *node;
+ struct pim_neighbor *neigh;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, node, neigh)) {
+ if (source_addr.s_addr == neigh->source_addr.s_addr) {
+ return neigh;
+ }
+ }
+
+ return 0;
+}
+
+struct pim_neighbor *pim_neighbor_add(struct interface *ifp,
+ struct in_addr source_addr,
+ pim_hello_options hello_options,
+ uint16_t holdtime,
+ uint16_t propagation_delay,
+ uint16_t override_interval,
+ uint32_t dr_priority,
+ uint32_t generation_id,
+ struct list *addr_list)
+{
+ struct pim_interface *pim_ifp;
+ struct pim_neighbor *neigh;
+
+ neigh = pim_neighbor_new(ifp, source_addr,
+ hello_options,
+ holdtime,
+ propagation_delay,
+ override_interval,
+ dr_priority,
+ generation_id,
+ addr_list);
+ if (!neigh) {
+ return 0;
+ }
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ listnode_add(pim_ifp->pim_neighbor_list, neigh);
+
+ return neigh;
+}
+
+static uint16_t
+find_neighbors_next_highest_propagation_delay_msec(struct interface *ifp,
+ struct pim_neighbor *highest_neigh)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *neigh_node;
+ struct pim_neighbor *neigh;
+ uint16_t next_highest_delay_msec;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ next_highest_delay_msec = pim_ifp->pim_propagation_delay_msec;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) {
+ if (neigh == highest_neigh)
+ continue;
+ if (neigh->propagation_delay_msec > next_highest_delay_msec)
+ next_highest_delay_msec = neigh->propagation_delay_msec;
+ }
+
+ return next_highest_delay_msec;
+}
+
+static uint16_t
+find_neighbors_next_highest_override_interval_msec(struct interface *ifp,
+ struct pim_neighbor *highest_neigh)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *neigh_node;
+ struct pim_neighbor *neigh;
+ uint16_t next_highest_interval_msec;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ next_highest_interval_msec = pim_ifp->pim_override_interval_msec;
+
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node, neigh)) {
+ if (neigh == highest_neigh)
+ continue;
+ if (neigh->override_interval_msec > next_highest_interval_msec)
+ next_highest_interval_msec = neigh->override_interval_msec;
+ }
+
+ return next_highest_interval_msec;
+}
+
+void pim_neighbor_delete(struct interface *ifp,
+ struct pim_neighbor *neigh,
+ const char *delete_message)
+{
+ struct pim_interface *pim_ifp;
+ char src_str[100];
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ pim_inet4_dump("<src?>", neigh->source_addr, src_str, sizeof(src_str));
+ zlog_info("PIM NEIGHBOR DOWN: neighbor %s on interface %s: %s",
+ src_str, ifp->name, delete_message);
+
+ neighbor_timer_off(neigh);
+
+ pim_if_assert_on_neighbor_down(ifp, neigh->source_addr);
+
+ if (!PIM_OPTION_IS_SET(neigh->hello_options,
+ PIM_OPTION_MASK_LAN_PRUNE_DELAY)) {
+ /* update num. of neighbors without hello option lan_delay */
+
+ --pim_ifp->pim_number_of_nonlandelay_neighbors;
+ }
+
+ if (!PIM_OPTION_IS_SET(neigh->hello_options,
+ PIM_OPTION_MASK_DR_PRIORITY)) {
+ /* update num. of neighbors without dr_pri */
+
+ --pim_ifp->pim_dr_num_nondrpri_neighbors;
+ }
+
+ zassert(neigh->propagation_delay_msec <= pim_ifp->pim_neighbors_highest_propagation_delay_msec);
+ zassert(neigh->override_interval_msec <= pim_ifp->pim_neighbors_highest_override_interval_msec);
+
+ if (pim_if_lan_delay_enabled(ifp)) {
+
+ /* will delete a neighbor with highest propagation delay? */
+ if (neigh->propagation_delay_msec == pim_ifp->pim_neighbors_highest_propagation_delay_msec) {
+ /* then find the next highest propagation delay */
+ pim_ifp->pim_neighbors_highest_propagation_delay_msec =
+ find_neighbors_next_highest_propagation_delay_msec(ifp, neigh);
+ }
+
+ /* will delete a neighbor with highest override interval? */
+ if (neigh->override_interval_msec == pim_ifp->pim_neighbors_highest_override_interval_msec) {
+ /* then find the next highest propagation delay */
+ pim_ifp->pim_neighbors_highest_override_interval_msec =
+ find_neighbors_next_highest_override_interval_msec(ifp, neigh);
+ }
+ }
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("%s: deleting PIM neighbor %s on interface %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifp->name);
+ }
+
+ listnode_delete(pim_ifp->pim_neighbor_list, neigh);
+
+ pim_neighbor_free(neigh);
+}
+
+void pim_neighbor_delete_all(struct interface *ifp,
+ const char *delete_message)
+{
+ struct pim_interface *pim_ifp;
+ struct listnode *neigh_node;
+ struct listnode *neigh_nextnode;
+ struct pim_neighbor *neigh;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_neighbor_list, neigh_node,
+ neigh_nextnode, neigh)) {
+ pim_neighbor_delete(ifp, neigh, delete_message);
+ }
+}
+
+struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh,
+ struct in_addr addr)
+{
+ struct listnode *node;
+ struct prefix *p;
+
+ if (!neigh->prefix_list)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS_RO(neigh->prefix_list, node, p)) {
+ if (p->family == AF_INET) {
+ if (addr.s_addr == p->u.prefix4.s_addr) {
+ return p;
+ }
+ }
+ }
+
+ return 0;
+}
+
+/*
+ RFC 4601: 4.3.4. Maintaining Secondary Address Lists
+
+ All the advertised secondary addresses in received Hello messages
+ must be checked against those previously advertised by all other
+ PIM neighbors on that interface. If there is a conflict and the
+ same secondary address was previously advertised by another
+ neighbor, then only the most recently received mapping MUST be
+ maintained, and an error message SHOULD be logged to the
+ administrator in a rate-limited manner.
+*/
+static void delete_from_neigh_addr(struct interface *ifp,
+ struct list *addr_list,
+ struct in_addr neigh_addr)
+{
+ struct listnode *addr_node;
+ struct prefix *addr;
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ zassert(addr_list);
+
+ /*
+ Scan secondary address list
+ */
+ for (ALL_LIST_ELEMENTS_RO(addr_list, addr_node,
+ addr)) {
+ struct listnode *neigh_node;
+ struct pim_neighbor *neigh;
+
+ if (addr->family != AF_INET)
+ continue;
+
+ /*
+ Scan neighbors
+ */
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, neigh_node,
+ neigh)) {
+ {
+ struct prefix *p = pim_neighbor_find_secondary(neigh, addr->u.prefix4);
+ if (p) {
+ char addr_str[100];
+ char this_neigh_str[100];
+ char other_neigh_str[100];
+
+ pim_inet4_dump("<addr?>", addr->u.prefix4, addr_str, sizeof(addr_str));
+ pim_inet4_dump("<neigh1?>", neigh_addr, this_neigh_str, sizeof(this_neigh_str));
+ pim_inet4_dump("<neigh2?>", neigh->source_addr, other_neigh_str, sizeof(other_neigh_str));
+
+ zlog_info("secondary addr %s recvd from neigh %s deleted from neigh %s on %s",
+ addr_str, this_neigh_str, other_neigh_str, ifp->name);
+
+ listnode_delete(neigh->prefix_list, p);
+ prefix_free(p);
+ }
+ }
+
+ } /* scan neighbors */
+
+ } /* scan addr list */
+
+}
+
+void pim_neighbor_update(struct pim_neighbor *neigh,
+ pim_hello_options hello_options,
+ uint16_t holdtime,
+ uint32_t dr_priority,
+ struct list *addr_list)
+{
+ struct pim_interface *pim_ifp = neigh->interface->info;
+
+ /* Received holdtime ? */
+ if (PIM_OPTION_IS_SET(hello_options, PIM_OPTION_MASK_HOLDTIME)) {
+ pim_neighbor_timer_reset(neigh, holdtime);
+ }
+ else {
+ pim_neighbor_timer_reset(neigh, PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
+ }
+
+#ifdef DUMP_PREFIX_LIST
+ zlog_debug("%s: DUMP_PREFIX_LIST old_prefix_list=%x old_size=%d new_prefix_list=%x new_size=%d",
+ __PRETTY_FUNCTION__,
+ (unsigned) neigh->prefix_list,
+ neigh->prefix_list ? (int) listcount(neigh->prefix_list) : -1,
+ (unsigned) addr_list,
+ addr_list ? (int) listcount(addr_list) : -1);
+#endif
+
+ if (neigh->prefix_list == addr_list) {
+ if (addr_list) {
+ zlog_err("%s: internal error: trying to replace same prefix list=%p",
+ __PRETTY_FUNCTION__, (void *) addr_list);
+ }
+ }
+ else {
+ /* Delete existing secondary address list */
+ delete_prefix_list(neigh);
+ }
+
+ if (addr_list) {
+ delete_from_neigh_addr(neigh->interface, addr_list, neigh->source_addr);
+ }
+
+ /* Replace secondary address list */
+ neigh->prefix_list = addr_list;
+
+ update_dr_priority(neigh,
+ hello_options,
+ dr_priority);
+ /*
+ Copy flags
+ */
+ neigh->hello_options = hello_options;
+}
diff --git a/pimd/pim_neighbor.h b/pimd/pim_neighbor.h
new file mode 100644
index 00000000..8f19c750
--- /dev/null
+++ b/pimd/pim_neighbor.h
@@ -0,0 +1,74 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_NEIGHBOR_H
+#define PIM_NEIGHBOR_H
+
+#include <zebra.h>
+
+#include "if.h"
+#include "linklist.h"
+
+#include "pim_tlv.h"
+
+struct pim_neighbor {
+ int64_t creation; /* timestamp of creation */
+ struct in_addr source_addr;
+ pim_hello_options hello_options;
+ uint16_t holdtime;
+ uint16_t propagation_delay_msec;
+ uint16_t override_interval_msec;
+ uint32_t dr_priority;
+ uint32_t generation_id;
+ struct list *prefix_list; /* list of struct prefix */
+ struct thread *t_expire_timer;
+ struct interface *interface;
+};
+
+void pim_neighbor_timer_reset(struct pim_neighbor *neigh, uint16_t holdtime);
+void pim_neighbor_free(struct pim_neighbor *neigh);
+struct pim_neighbor *pim_neighbor_find(struct interface *ifp,
+ struct in_addr source_addr);
+struct pim_neighbor *pim_neighbor_add(struct interface *ifp,
+ struct in_addr source_addr,
+ pim_hello_options hello_options,
+ uint16_t holdtime,
+ uint16_t propagation_delay,
+ uint16_t override_interval,
+ uint32_t dr_priority,
+ uint32_t generation_id,
+ struct list *addr_list);
+void pim_neighbor_delete(struct interface *ifp,
+ struct pim_neighbor *neigh,
+ const char *delete_message);
+void pim_neighbor_delete_all(struct interface *ifp,
+ const char *delete_message);
+void pim_neighbor_update(struct pim_neighbor *neigh,
+ pim_hello_options hello_options,
+ uint16_t holdtime,
+ uint32_t dr_priority,
+ struct list *addr_list);
+struct prefix *pim_neighbor_find_secondary(struct pim_neighbor *neigh,
+ struct in_addr addr);
+void pim_if_dr_election(struct interface *ifp);
+
+#endif /* PIM_NEIGHBOR_H */
diff --git a/pimd/pim_oil.c b/pimd/pim_oil.c
new file mode 100644
index 00000000..1aaece3f
--- /dev/null
+++ b/pimd/pim_oil.c
@@ -0,0 +1,140 @@
+/*
+ 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 "memory.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_oil.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+
+void pim_channel_oil_free(struct channel_oil *c_oil)
+{
+ XFREE(MTYPE_PIM_CHANNEL_OIL, c_oil);
+}
+
+static void pim_channel_oil_delete(struct channel_oil *c_oil)
+{
+ /*
+ notice that listnode_delete() can't be moved
+ into pim_channel_oil_free() because the later is
+ called by list_delete_all_node()
+ */
+ listnode_delete(qpim_channel_oil_list, c_oil);
+
+ pim_channel_oil_free(c_oil);
+}
+
+static struct channel_oil *channel_oil_new(struct in_addr group_addr,
+ struct in_addr source_addr,
+ int input_vif_index)
+{
+ struct channel_oil *c_oil;
+ struct interface *ifp_in;
+
+ ifp_in = pim_if_find_by_vif_index(input_vif_index);
+ if (!ifp_in) {
+ /* warning only */
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: (S,G)=(%s,%s) could not find input interface for input_vif_index=%d",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, input_vif_index);
+ }
+
+ c_oil = XCALLOC(MTYPE_PIM_CHANNEL_OIL, sizeof(*c_oil));
+ if (!c_oil) {
+ zlog_err("PIM XCALLOC(%zu) failure", sizeof(*c_oil));
+ return 0;
+ }
+
+ c_oil->oil.mfcc_mcastgrp = group_addr;
+ c_oil->oil.mfcc_origin = source_addr;
+ c_oil->oil.mfcc_parent = input_vif_index;
+ c_oil->oil_ref_count = 1;
+
+ zassert(c_oil->oil_size == 0);
+
+ return c_oil;
+}
+
+static struct channel_oil *pim_add_channel_oil(struct in_addr group_addr,
+ struct in_addr source_addr,
+ int input_vif_index)
+{
+ struct channel_oil *c_oil;
+
+ c_oil = channel_oil_new(group_addr, source_addr, input_vif_index);
+ if (!c_oil) {
+ zlog_warn("PIM XCALLOC(%zu) failure", sizeof(*c_oil));
+ return 0;
+ }
+
+ listnode_add(qpim_channel_oil_list, c_oil);
+
+ return c_oil;
+}
+
+static struct channel_oil *pim_find_channel_oil(struct in_addr group_addr,
+ struct in_addr source_addr)
+{
+ struct listnode *node;
+ struct channel_oil *c_oil;
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_channel_oil_list, node, c_oil)) {
+ if ((group_addr.s_addr == c_oil->oil.mfcc_mcastgrp.s_addr) &&
+ (source_addr.s_addr == c_oil->oil.mfcc_origin.s_addr))
+ return c_oil;
+ }
+
+ return 0;
+}
+
+struct channel_oil *pim_channel_oil_add(struct in_addr group_addr,
+ struct in_addr source_addr,
+ int input_vif_index)
+{
+ struct channel_oil *c_oil;
+
+ c_oil = pim_find_channel_oil(group_addr, source_addr);
+ if (c_oil) {
+ ++c_oil->oil_ref_count;
+ return c_oil;
+ }
+
+ return pim_add_channel_oil(group_addr, source_addr, input_vif_index);
+}
+
+void pim_channel_oil_del(struct channel_oil *c_oil)
+{
+ --c_oil->oil_ref_count;
+
+ if (c_oil->oil_ref_count < 1) {
+ pim_channel_oil_delete(c_oil);
+ }
+}
diff --git a/pimd/pim_oil.h b/pimd/pim_oil.h
new file mode 100644
index 00000000..1753545a
--- /dev/null
+++ b/pimd/pim_oil.h
@@ -0,0 +1,53 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_OIL_H
+#define PIM_OIL_H
+
+#include "pim_mroute.h"
+
+#define PIM_OIF_FLAG_PROTO_IGMP (1 << 0) /* bitmask 1 */
+#define PIM_OIF_FLAG_PROTO_PIM (1 << 1) /* bitmask 2 */
+#define PIM_OIF_FLAG_PROTO_ANY (3) /* bitmask (1 | 2) */
+
+/*
+ qpim_channel_oil_list holds a list of struct channel_oil.
+
+ Each channel_oil.oil is used to control an (S,G) entry in the Kernel
+ Multicast Forwarding Cache.
+*/
+
+struct channel_oil {
+ struct mfcctl oil;
+ int oil_size;
+ int oil_ref_count;
+ time_t oif_creation[MAXVIFS];
+ uint32_t oif_flags[MAXVIFS];
+};
+
+void pim_channel_oil_free(struct channel_oil *c_oil);
+struct channel_oil *pim_channel_oil_add(struct in_addr group_addr,
+ struct in_addr source_addr,
+ int input_vif_index);
+void pim_channel_oil_del(struct channel_oil *c_oil);
+
+#endif /* PIM_OIL_H */
diff --git a/pimd/pim_pim.c b/pimd/pim_pim.c
new file mode 100644
index 00000000..f6f4c953
--- /dev/null
+++ b/pimd/pim_pim.c
@@ -0,0 +1,742 @@
+/*
+ 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 "thread.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_sock.h"
+#include "pim_str.h"
+#include "pim_util.h"
+#include "pim_tlv.h"
+#include "pim_neighbor.h"
+#include "pim_hello.h"
+#include "pim_join.h"
+#include "pim_assert.h"
+#include "pim_msg.h"
+#include "pim_rand.h"
+
+static int on_pim_hello_send(struct thread *t);
+static int pim_hello_send(struct interface *ifp,
+ uint16_t holdtime);
+
+static void sock_close(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp = ifp->info;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ if (pim_ifp->t_pim_sock_read) {
+ zlog_debug("Cancelling READ event for PIM socket fd=%d on interface %s",
+ pim_ifp->pim_sock_fd,
+ ifp->name);
+ }
+ }
+ THREAD_OFF(pim_ifp->t_pim_sock_read);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ if (pim_ifp->t_pim_hello_timer) {
+ zlog_debug("Cancelling PIM hello timer for interface %s",
+ ifp->name);
+ }
+ }
+ THREAD_OFF(pim_ifp->t_pim_hello_timer);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("Deleting PIM socket fd=%d on interface %s",
+ pim_ifp->pim_sock_fd, ifp->name);
+ }
+
+ if (close(pim_ifp->pim_sock_fd)) {
+ zlog_warn("Failure closing PIM socket fd=%d on interface %s: errno=%d: %s",
+ pim_ifp->pim_sock_fd, ifp->name,
+ errno, safe_strerror(errno));
+ }
+
+ pim_ifp->pim_sock_fd = -1;
+ pim_ifp->pim_sock_creation = 0;
+
+ zassert(pim_ifp->pim_sock_fd < 0);
+ zassert(!pim_ifp->t_pim_sock_read);
+ zassert(!pim_ifp->t_pim_hello_timer);
+ zassert(!pim_ifp->pim_sock_creation);
+}
+
+void pim_sock_delete(struct interface *ifp, const char *delete_message)
+{
+ zlog_info("PIM INTERFACE DOWN: on interface %s: %s",
+ ifp->name, delete_message);
+
+ if (!ifp->info) {
+ zlog_err("%s: %s: but PIM not enabled on interface %s (!)",
+ __PRETTY_FUNCTION__, delete_message, ifp->name);
+ return;
+ }
+
+ /*
+ RFC 4601: 4.3.1. Sending Hello Messages
+
+ Before an interface goes down or changes primary IP address, a Hello
+ message with a zero HoldTime should be sent immediately (with the
+ old IP address if the IP address changed).
+ */
+ pim_hello_send(ifp, 0 /* zero-sec holdtime */);
+
+ pim_neighbor_delete_all(ifp, delete_message);
+
+ sock_close(ifp);
+}
+
+int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len)
+{
+ struct ip *ip_hdr;
+ size_t ip_hlen; /* ip header length in bytes */
+ char src_str[100];
+ char dst_str[100];
+ uint8_t *pim_msg;
+ int pim_msg_len;
+ uint8_t pim_version;
+ uint8_t pim_type;
+ uint16_t pim_checksum; /* received checksum */
+ uint16_t checksum; /* computed checksum */
+ struct pim_neighbor *neigh;
+
+ if (!ifp->info) {
+ zlog_warn("%s: PIM not enabled on interface %s",
+ __PRETTY_FUNCTION__, ifp->name);
+ return -1;
+ }
+
+ if (len < sizeof(*ip_hdr)) {
+ zlog_warn("PIM packet size=%zu shorter than minimum=%zu",
+ len, sizeof(*ip_hdr));
+ return -1;
+ }
+
+ ip_hdr = (struct ip *) buf;
+
+ pim_inet4_dump("<src?>", ip_hdr->ip_src, src_str, sizeof(src_str));
+ pim_inet4_dump("<dst?>", ip_hdr->ip_dst, dst_str, sizeof(dst_str));
+
+ ip_hlen = ip_hdr->ip_hl << 2; /* ip_hl gives length in 4-byte words */
+
+ if (PIM_DEBUG_PIM_PACKETS) {
+ zlog_debug("Recv IP packet from %s to %s on %s: size=%zu ip_header_size=%zu ip_proto=%d",
+ src_str, dst_str, ifp->name, len, ip_hlen, ip_hdr->ip_p);
+ }
+
+ if (ip_hdr->ip_p != PIM_IP_PROTO_PIM) {
+ zlog_warn("IP packet protocol=%d is not PIM=%d",
+ ip_hdr->ip_p, PIM_IP_PROTO_PIM);
+ return -1;
+ }
+
+ if (ip_hlen < PIM_IP_HEADER_MIN_LEN) {
+ zlog_warn("IP packet header size=%zu shorter than minimum=%d",
+ ip_hlen, PIM_IP_HEADER_MIN_LEN);
+ return -1;
+ }
+ if (ip_hlen > PIM_IP_HEADER_MAX_LEN) {
+ zlog_warn("IP packet header size=%zu greater than maximum=%d",
+ ip_hlen, PIM_IP_HEADER_MAX_LEN);
+ return -1;
+ }
+
+ pim_msg = buf + ip_hlen;
+ pim_msg_len = len - ip_hlen;
+
+ if (PIM_DEBUG_PIM_PACKETDUMP_RECV) {
+ pim_pkt_dump(__PRETTY_FUNCTION__, pim_msg, pim_msg_len);
+ }
+
+ if (pim_msg_len < PIM_PIM_MIN_LEN) {
+ zlog_warn("PIM message size=%d shorter than minimum=%d",
+ pim_msg_len, PIM_PIM_MIN_LEN);
+ return -1;
+ }
+
+ pim_version = PIM_MSG_HDR_GET_VERSION(pim_msg);
+ pim_type = PIM_MSG_HDR_GET_TYPE(pim_msg);
+
+ if (pim_version != PIM_PROTO_VERSION) {
+ zlog_warn("Ignoring PIM pkt from %s with unsupported version: %d",
+ ifp->name, pim_version);
+ return -1;
+ }
+
+ /* save received checksum */
+ pim_checksum = PIM_MSG_HDR_GET_CHECKSUM(pim_msg);
+
+ /* for computing checksum */
+ *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) = 0;
+
+ checksum = in_cksum(pim_msg, pim_msg_len);
+ if (checksum != pim_checksum) {
+ zlog_warn("Ignoring PIM pkt from %s with invalid checksum: received=%x calculated=%x",
+ ifp->name, pim_checksum, checksum);
+ return -1;
+ }
+
+ if (PIM_DEBUG_PIM_PACKETS) {
+ zlog_debug("Recv PIM packet from %s to %s on %s: ttl=%d pim_version=%d pim_type=%d pim_msg_size=%d checksum=%x",
+ src_str, dst_str, ifp->name, ip_hdr->ip_ttl,
+ pim_version, pim_type, pim_msg_len, checksum);
+ }
+
+ if (pim_type == PIM_MSG_TYPE_HELLO) {
+ int result = pim_hello_recv(ifp,
+ ip_hdr->ip_src,
+ pim_msg + PIM_MSG_HEADER_LEN,
+ pim_msg_len - PIM_MSG_HEADER_LEN);
+ if (!result) {
+ pim_if_dr_election(ifp); /* PIM Hello message is received */
+ }
+ return result;
+ }
+
+ neigh = pim_neighbor_find(ifp, ip_hdr->ip_src);
+ if (!neigh) {
+ zlog_warn("%s %s: non-hello PIM message type=%d from non-neighbor %s on %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ pim_type, src_str, ifp->name);
+ return -1;
+ }
+
+ switch (pim_type) {
+ case PIM_MSG_TYPE_JOIN_PRUNE:
+ return pim_joinprune_recv(ifp, neigh,
+ ip_hdr->ip_src,
+ pim_msg + PIM_MSG_HEADER_LEN,
+ pim_msg_len - PIM_MSG_HEADER_LEN);
+ case PIM_MSG_TYPE_ASSERT:
+ return pim_assert_recv(ifp, neigh,
+ ip_hdr->ip_src,
+ pim_msg + PIM_MSG_HEADER_LEN,
+ pim_msg_len - PIM_MSG_HEADER_LEN);
+ default:
+ zlog_warn("%s %s: unsupported PIM message type=%d from %s on %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ pim_type, src_str, ifp->name);
+ }
+
+ return -1;
+}
+
+static void pim_sock_read_on(struct interface *ifp);
+
+static int pim_sock_read(struct thread *t)
+{
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ int fd;
+ struct sockaddr_in from;
+ struct sockaddr_in to;
+ socklen_t fromlen = sizeof(from);
+ socklen_t tolen = sizeof(to);
+ uint8_t buf[PIM_PIM_BUFSIZE_READ];
+ int len;
+ int ifindex = -1;
+ int result = -1; /* defaults to bad */
+
+ zassert(t);
+
+ ifp = THREAD_ARG(t);
+ zassert(ifp);
+
+ fd = THREAD_FD(t);
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ zassert(fd == pim_ifp->pim_sock_fd);
+
+ len = pim_socket_recvfromto(fd, buf, sizeof(buf),
+ &from, &fromlen,
+ &to, &tolen,
+ &ifindex);
+ if (len < 0) {
+ zlog_warn("Failure receiving IP PIM packet on fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ goto done;
+ }
+
+ if (PIM_DEBUG_PIM_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 PIM pkt size=%d from %s to %s on fd=%d on ifindex=%d (sock_ifindex=%d)",
+ len, from_str, to_str, fd, ifindex, ifp->ifindex);
+ }
+
+ if (PIM_DEBUG_PIM_PACKETDUMP_RECV) {
+ pim_pkt_dump(__PRETTY_FUNCTION__, buf, len);
+ }
+
+#ifdef PIM_CHECK_RECV_IFINDEX_SANITY
+ /* ifindex sanity check */
+ if (ifindex != (int) ifp->ifindex) {
+ char from_str[100];
+ char to_str[100];
+ struct interface *recv_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?>");
+
+ recv_ifp = if_lookup_by_index(ifindex);
+ if (recv_ifp) {
+ zassert(ifindex == (int) recv_ifp->ifindex);
+ }
+
+#ifdef PIM_REPORT_RECV_IFINDEX_MISMATCH
+ zlog_warn("Interface mismatch: recv PIM pkt from %s to %s on fd=%d: recv_ifindex=%d (%s) sock_ifindex=%d (%s)",
+ from_str, to_str, fd,
+ ifindex, recv_ifp ? recv_ifp->name : "<if-notfound>",
+ ifp->ifindex, ifp->name);
+#endif
+ goto done;
+ }
+#endif
+
+ int fail = pim_pim_packet(ifp, buf, len);
+ if (fail) {
+ zlog_warn("%s: pim_pim_packet() return=%d",
+ __PRETTY_FUNCTION__, fail);
+ goto done;
+ }
+
+ result = 0; /* good */
+
+ done:
+ pim_sock_read_on(ifp);
+
+ if (result) {
+ ++pim_ifp->pim_ifstat_hello_recvfail;
+ }
+
+ return result;
+}
+
+static void pim_sock_read_on(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ zassert(ifp->info);
+
+ pim_ifp = ifp->info;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("Scheduling READ event on PIM socket fd=%d",
+ pim_ifp->pim_sock_fd);
+ }
+ pim_ifp->t_pim_sock_read = 0;
+ zassert(!pim_ifp->t_pim_sock_read);
+ THREAD_READ_ON(master, pim_ifp->t_pim_sock_read, pim_sock_read, ifp,
+ pim_ifp->pim_sock_fd);
+}
+
+static int pim_sock_open(struct in_addr ifaddr, int ifindex)
+{
+ int fd;
+
+ fd = pim_socket_mcast(IPPROTO_PIM, ifaddr, 0 /* loop=false */);
+ if (fd < 0)
+ return -1;
+
+ if (pim_socket_join(fd, qpim_all_pim_routers_addr, ifaddr, ifindex)) {
+ return -2;
+ }
+
+ return fd;
+}
+
+void pim_ifstat_reset(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp) {
+ return;
+ }
+
+ pim_ifp->pim_ifstat_start = pim_time_monotonic_sec();
+ pim_ifp->pim_ifstat_hello_sent = 0;
+ pim_ifp->pim_ifstat_hello_sendfail = 0;
+ pim_ifp->pim_ifstat_hello_recv = 0;
+ pim_ifp->pim_ifstat_hello_recvfail = 0;
+}
+
+void pim_sock_reset(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ zassert(ifp->info);
+
+ pim_ifp = ifp->info;
+
+ pim_ifp->primary_address = pim_find_primary_addr(ifp);
+
+ pim_ifp->pim_sock_fd = -1;
+ pim_ifp->pim_sock_creation = 0;
+ pim_ifp->t_pim_sock_read = 0;
+
+ pim_ifp->t_pim_hello_timer = 0;
+ pim_ifp->pim_hello_period = PIM_DEFAULT_HELLO_PERIOD;
+ pim_ifp->pim_default_holdtime = -1; /* unset: means 3.5 * pim_hello_period */
+ pim_ifp->pim_triggered_hello_delay = PIM_DEFAULT_TRIGGERED_HELLO_DELAY;
+ pim_ifp->pim_dr_priority = PIM_DEFAULT_DR_PRIORITY;
+ pim_ifp->pim_propagation_delay_msec = PIM_DEFAULT_PROPAGATION_DELAY_MSEC;
+ pim_ifp->pim_override_interval_msec = PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC;
+ if (PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION) {
+ PIM_IF_DO_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options);
+ }
+ else {
+ PIM_IF_DONT_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options);
+ }
+
+ /* neighbors without lan_delay */
+ pim_ifp->pim_number_of_nonlandelay_neighbors = 0;
+ pim_ifp->pim_neighbors_highest_propagation_delay_msec = 0;
+ pim_ifp->pim_neighbors_highest_override_interval_msec = 0;
+
+ /* DR Election */
+ pim_ifp->pim_dr_election_last = 0; /* timestamp */
+ pim_ifp->pim_dr_election_count = 0;
+ pim_ifp->pim_dr_election_changes = 0;
+ pim_ifp->pim_dr_num_nondrpri_neighbors = 0; /* neighbors without dr_pri */
+ pim_ifp->pim_dr_addr = pim_ifp->primary_address;
+
+ pim_ifstat_reset(ifp);
+}
+
+int pim_msg_send(int fd,
+ struct in_addr dst,
+ uint8_t *pim_msg,
+ int pim_msg_size,
+ const char *ifname)
+{
+ ssize_t sent;
+ struct sockaddr_in to;
+ socklen_t tolen;
+
+ if (PIM_DEBUG_PIM_PACKETS) {
+ char dst_str[100];
+ pim_inet4_dump("<dst?>", dst, dst_str, sizeof(dst_str));
+ zlog_debug("%s: to %s on %s: msg_size=%d checksum=%x",
+ __PRETTY_FUNCTION__,
+ dst_str, ifname, pim_msg_size,
+ *(uint16_t *) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg));
+ }
+
+#if 0
+ memset(&to, 0, sizeof(to));
+#endif
+ to.sin_family = AF_INET;
+ to.sin_addr = dst;
+#if 0
+ to.sin_port = htons(0);
+#endif
+ tolen = sizeof(to);
+
+ if (PIM_DEBUG_PIM_PACKETDUMP_SEND) {
+ pim_pkt_dump(__PRETTY_FUNCTION__, pim_msg, pim_msg_size);
+ }
+
+ sent = sendto(fd, pim_msg, pim_msg_size, MSG_DONTWAIT, &to, tolen);
+ if (sent != (ssize_t) pim_msg_size) {
+ int e = errno;
+ char dst_str[100];
+ pim_inet4_dump("<dst?>", dst, dst_str, sizeof(dst_str));
+ if (sent < 0) {
+ zlog_warn("%s: sendto() failure to %s on %s: fd=%d msg_size=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ dst_str, ifname, fd, pim_msg_size,
+ e, safe_strerror(e));
+ }
+ else {
+ zlog_warn("%s: sendto() partial to %s on %s: fd=%d msg_size=%d: sent=%zd",
+ __PRETTY_FUNCTION__,
+ dst_str, ifname, fd,
+ pim_msg_size, sent);
+ }
+ return -1;
+ }
+
+ return 0;
+}
+
+static int hello_send(struct interface *ifp,
+ uint16_t holdtime)
+{
+ uint8_t pim_msg[PIM_PIM_BUFSIZE_WRITE];
+ struct pim_interface *pim_ifp;
+ int pim_tlv_size;
+ int pim_msg_size;
+
+ pim_ifp = ifp->info;
+
+ if (PIM_DEBUG_PIM_PACKETS || PIM_DEBUG_PIM_HELLO) {
+ char dst_str[100];
+ pim_inet4_dump("<dst?>", qpim_all_pim_routers_addr, dst_str, sizeof(dst_str));
+ zlog_debug("%s: to %s on %s: holdt=%u prop_d=%u overr_i=%u dis_join_supp=%d dr_prio=%u gen_id=%08x addrs=%d",
+ __PRETTY_FUNCTION__,
+ dst_str, ifp->name,
+ holdtime,
+ pim_ifp->pim_propagation_delay_msec, pim_ifp->pim_override_interval_msec,
+ PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options),
+ pim_ifp->pim_dr_priority, pim_ifp->pim_generation_id,
+ listcount(ifp->connected));
+ }
+
+ pim_tlv_size = pim_hello_build_tlv(ifp->name,
+ pim_msg + PIM_PIM_MIN_LEN,
+ sizeof(pim_msg) - PIM_PIM_MIN_LEN,
+ holdtime,
+ pim_ifp->pim_dr_priority,
+ pim_ifp->pim_generation_id,
+ pim_ifp->pim_propagation_delay_msec,
+ pim_ifp->pim_override_interval_msec,
+ PIM_IF_TEST_PIM_CAN_DISABLE_JOIN_SUPRESSION(pim_ifp->options),
+ ifp->connected);
+ if (pim_tlv_size < 0) {
+ return -1;
+ }
+
+ pim_msg_size = pim_tlv_size + PIM_PIM_MIN_LEN;
+
+ zassert(pim_msg_size >= PIM_PIM_MIN_LEN);
+ zassert(pim_msg_size <= PIM_PIM_BUFSIZE_WRITE);
+
+ pim_msg_build_header(pim_msg, pim_msg_size,
+ PIM_MSG_TYPE_HELLO);
+
+ 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 -2;
+ }
+
+ return 0;
+}
+
+static int pim_hello_send(struct interface *ifp,
+ uint16_t holdtime)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ if (hello_send(ifp, holdtime)) {
+ ++pim_ifp->pim_ifstat_hello_sendfail;
+
+ zlog_warn("Could not send PIM hello on interface %s",
+ ifp->name);
+ return -1;
+ }
+
+ ++pim_ifp->pim_ifstat_hello_sent;
+
+ return 0;
+}
+
+static void hello_resched(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("Rescheduling %d sec hello on interface %s",
+ pim_ifp->pim_hello_period, ifp->name);
+ }
+ THREAD_OFF(pim_ifp->t_pim_hello_timer);
+ THREAD_TIMER_ON(master, pim_ifp->t_pim_hello_timer,
+ on_pim_hello_send,
+ ifp, pim_ifp->pim_hello_period);
+}
+
+/*
+ Periodic hello timer
+ */
+static int on_pim_hello_send(struct thread *t)
+{
+ struct pim_interface *pim_ifp;
+ struct interface *ifp;
+
+ zassert(t);
+ ifp = THREAD_ARG(t);
+ zassert(ifp);
+
+ pim_ifp = ifp->info;
+
+ /*
+ * Schedule next hello
+ */
+ pim_ifp->t_pim_hello_timer = 0;
+ hello_resched(ifp);
+
+ /*
+ * Send hello
+ */
+ return pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
+}
+
+/*
+ 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.
+ */
+void pim_hello_restart_now(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ /*
+ * Reset next hello timer
+ */
+ hello_resched(ifp);
+
+ /*
+ * Immediately send hello
+ */
+ pim_hello_send(ifp, PIM_IF_DEFAULT_HOLDTIME(pim_ifp));
+}
+
+/*
+ RFC 4601: 4.3.1. Sending Hello Messages
+
+ To allow new or rebooting routers to learn of PIM neighbors quickly,
+ when a Hello message is received from a new neighbor, or a Hello
+ message with a new GenID is received from an existing neighbor, a
+ new Hello message should be sent on this interface after a
+ randomized delay between 0 and Triggered_Hello_Delay.
+ */
+void pim_hello_restart_triggered(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ int triggered_hello_delay_msec;
+ int random_msec;
+
+ zassert(ifp);
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ triggered_hello_delay_msec = 1000 * pim_ifp->pim_triggered_hello_delay;
+
+ if (pim_ifp->t_pim_hello_timer) {
+ long remain_msec = pim_time_timer_remain_msec(pim_ifp->t_pim_hello_timer);
+ if (remain_msec <= triggered_hello_delay_msec) {
+ /* Rescheduling hello would increase the delay, then it's faster
+ to just wait for the scheduled periodic hello. */
+ return;
+ }
+
+ THREAD_OFF(pim_ifp->t_pim_hello_timer);
+ pim_ifp->t_pim_hello_timer = 0;
+ }
+ zassert(!pim_ifp->t_pim_hello_timer);
+
+ random_msec = pim_rand_next(0, triggered_hello_delay_msec);
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ zlog_debug("Scheduling %d msec triggered hello on interface %s",
+ random_msec, ifp->name);
+ }
+
+ THREAD_TIMER_MSEC_ON(master, pim_ifp->t_pim_hello_timer,
+ on_pim_hello_send,
+ ifp, random_msec);
+}
+
+int pim_sock_add(struct interface *ifp)
+{
+ struct pim_interface *pim_ifp;
+ struct in_addr ifaddr;
+
+ pim_ifp = ifp->info;
+ zassert(pim_ifp);
+
+ if (pim_ifp->pim_sock_fd >= 0) {
+ zlog_warn("Can't recreate existing PIM socket fd=%d for interface %s",
+ pim_ifp->pim_sock_fd, ifp->name);
+ return -1;
+ }
+
+ ifaddr = pim_ifp->primary_address;
+
+ pim_ifp->pim_sock_fd = pim_sock_open(ifaddr, ifp->ifindex);
+ if (pim_ifp->pim_sock_fd < 0) {
+ zlog_warn("Could not open PIM socket on interface %s",
+ ifp->name);
+ return -2;
+ }
+
+ pim_ifp->t_pim_sock_read = 0;
+ pim_ifp->pim_sock_creation = pim_time_monotonic_sec();
+
+ pim_ifp->pim_generation_id = pim_rand() & (int64_t) 0xFFFFFFFF;
+
+ zlog_info("PIM INTERFACE UP: on interface %s ifindex=%d",
+ ifp->name, ifp->ifindex);
+
+ /*
+ * Start receiving PIM messages
+ */
+ pim_sock_read_on(ifp);
+
+ /*
+ * Start sending PIM hello's
+ */
+ pim_hello_restart_triggered(ifp);
+
+ return 0;
+}
diff --git a/pimd/pim_pim.h b/pimd/pim_pim.h
new file mode 100644
index 00000000..6be1a3e6
--- /dev/null
+++ b/pimd/pim_pim.h
@@ -0,0 +1,71 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_PIM_H
+#define PIM_PIM_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#define PIM_PIM_BUFSIZE_READ (20000)
+#define PIM_PIM_BUFSIZE_WRITE (20000)
+
+#define PIM_NEXTHOP_IFINDEX_TAB_SIZE (20)
+
+#define PIM_DEFAULT_HELLO_PERIOD (30) /* seconds, RFC 4601: 4.11 */
+#define PIM_DEFAULT_TRIGGERED_HELLO_DELAY (5) /* seconds, RFC 4601: 4.11 */
+#define PIM_DEFAULT_DR_PRIORITY (1) /* RFC 4601: 4.3.1 */
+#define PIM_DEFAULT_PROPAGATION_DELAY_MSEC (500) /* RFC 4601: 4.11. Timer Values */
+#define PIM_DEFAULT_OVERRIDE_INTERVAL_MSEC (2500) /* RFC 4601: 4.11. Timer Values */
+#define PIM_DEFAULT_CAN_DISABLE_JOIN_SUPPRESSION (0) /* boolean */
+#define PIM_DEFAULT_T_PERIODIC (60) /* RFC 4601: 4.11. Timer Values */
+
+#define PIM_MSG_TYPE_HELLO (0)
+#define PIM_MSG_TYPE_JOIN_PRUNE (3)
+#define PIM_MSG_TYPE_ASSERT (5)
+
+#define PIM_MSG_HDR_OFFSET_VERSION(pim_msg) (pim_msg)
+#define PIM_MSG_HDR_OFFSET_TYPE(pim_msg) (pim_msg)
+#define PIM_MSG_HDR_OFFSET_RESERVED(pim_msg) (((char *)(pim_msg)) + 1)
+#define PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg) (((char *)(pim_msg)) + 2)
+
+#define PIM_MSG_HDR_GET_VERSION(pim_msg) ((*(uint8_t*) PIM_MSG_HDR_OFFSET_VERSION(pim_msg)) >> 4)
+#define PIM_MSG_HDR_GET_TYPE(pim_msg) ((*(uint8_t*) PIM_MSG_HDR_OFFSET_TYPE(pim_msg)) & 0xF)
+#define PIM_MSG_HDR_GET_CHECKSUM(pim_msg) (*(uint16_t*) PIM_MSG_HDR_OFFSET_CHECKSUM(pim_msg))
+
+void pim_ifstat_reset(struct interface *ifp);
+void pim_sock_reset(struct interface *ifp);
+int pim_sock_add(struct interface *ifp);
+void pim_sock_delete(struct interface *ifp, const char *delete_message);
+void pim_hello_restart_now(struct interface *ifp);
+void pim_hello_restart_triggered(struct interface *ifp);
+
+int pim_pim_packet(struct interface *ifp, uint8_t *buf, size_t len);
+
+int pim_msg_send(int fd,
+ struct in_addr dst,
+ uint8_t *pim_msg,
+ int pim_msg_size,
+ const char *ifname);
+
+#endif /* PIM_PIM_H */
diff --git a/pimd/pim_rand.c b/pimd/pim_rand.c
new file mode 100644
index 00000000..df2a1111
--- /dev/null
+++ b/pimd/pim_rand.c
@@ -0,0 +1,60 @@
+/*
+ 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 "pim_rand.h"
+#include "pim_time.h"
+
+/* Quick and dirty random number generator from NUMERICAL RECIPES IN C:
+ THE ART OF SCIENTIFIC COMPUTING (ISBN 0-521-43108-5). */
+/* BEWARE: '_qseed_' is assigned! */
+#define QRANDOM(_qseed_) ((_qseed_) = (((_qseed_) * 1664525L) + 1013904223L))
+
+static long qpim_rand_seed;
+
+void pim_rand_init()
+{
+ qpim_rand_seed = pim_time_monotonic_sec() ^ getpid();
+}
+
+long pim_rand()
+{
+ return QRANDOM(qpim_rand_seed);
+}
+
+int pim_rand_next(int min, int max)
+{
+ long rand;
+
+ assert(min <= max);
+
+ /* FIXME better random generator ? */
+
+ rand = QRANDOM(qpim_rand_seed);
+ if (rand < 0)
+ rand = -rand;
+ rand = rand % (1 + max - min) + min;
+
+ assert(rand >= min);
+ assert(rand <= max);
+
+ return rand;
+}
diff --git a/pimd/pim_rand.h b/pimd/pim_rand.h
new file mode 100644
index 00000000..a1df5054
--- /dev/null
+++ b/pimd/pim_rand.h
@@ -0,0 +1,30 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_RAND_H
+#define PIM_RAND_H
+
+void pim_rand_init(void);
+long pim_rand(void);
+int pim_rand_next(int min, int max);
+
+#endif /* PIM_RAND_H */
diff --git a/pimd/pim_rpf.c b/pimd/pim_rpf.c
new file mode 100644
index 00000000..dedc60a5
--- /dev/null
+++ b/pimd/pim_rpf.c
@@ -0,0 +1,260 @@
+/*
+ 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 "prefix.h"
+#include "memory.h"
+
+#include "pimd.h"
+#include "pim_rpf.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_iface.h"
+#include "pim_zlookup.h"
+#include "pim_ifchannel.h"
+
+static struct in_addr pim_rpf_find_rpf_addr(struct pim_upstream *up);
+
+int pim_nexthop_lookup(struct pim_nexthop *nexthop,
+ struct in_addr addr)
+{
+ struct pim_zlookup_nexthop nexthop_tab[PIM_NEXTHOP_IFINDEX_TAB_SIZE];
+ int num_ifindex;
+ struct interface *ifp;
+ int first_ifindex;
+
+ num_ifindex = zclient_lookup_nexthop(qpim_zclient_lookup, nexthop_tab,
+ PIM_NEXTHOP_IFINDEX_TAB_SIZE,
+ addr, PIM_NEXTHOP_LOOKUP_MAX);
+ if (num_ifindex < 1) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: could not find nexthop ifindex for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ addr_str);
+ return -1;
+ }
+
+ first_ifindex = nexthop_tab[0].ifindex;
+
+ if (num_ifindex > 1) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s %s: FIXME ignoring multiple nexthop ifindex'es num_ifindex=%d for address %s (using only ifindex=%d)",
+ __FILE__, __PRETTY_FUNCTION__,
+ num_ifindex, addr_str, first_ifindex);
+ /* debug warning only, do not return */
+ }
+
+ ifp = if_lookup_by_index(first_ifindex);
+ if (!ifp) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: could not find interface for ifindex %d (address %s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ first_ifindex, addr_str);
+ return -2;
+ }
+
+ if (!ifp->info) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s: multicast not enabled on input interface %s (ifindex=%d, RPF for source %s)",
+ __PRETTY_FUNCTION__,
+ ifp->name, first_ifindex, addr_str);
+ /* debug warning only, do not return */
+ }
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char nexthop_str[100];
+ char addr_str[100];
+ pim_inet4_dump("<nexthop?>", nexthop_tab[0].nexthop_addr, nexthop_str, sizeof(nexthop_str));
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s %s: found nexthop %s for address %s: interface %s ifindex=%d metric=%d pref=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ nexthop_str, addr_str,
+ ifp->name, first_ifindex,
+ nexthop_tab[0].route_metric,
+ nexthop_tab[0].protocol_distance);
+ }
+
+ /* update nextop data */
+ nexthop->interface = ifp;
+ nexthop->mrib_nexthop_addr = nexthop_tab[0].nexthop_addr;
+ nexthop->mrib_metric_preference = nexthop_tab[0].protocol_distance;
+ nexthop->mrib_route_metric = nexthop_tab[0].route_metric;
+
+ return 0;
+}
+
+static int nexthop_mismatch(const struct pim_nexthop *nh1,
+ const struct pim_nexthop *nh2)
+{
+ return (nh1->interface != nh2->interface)
+ ||
+ (nh1->mrib_nexthop_addr.s_addr != nh2->mrib_nexthop_addr.s_addr)
+ ||
+ (nh1->mrib_metric_preference != nh2->mrib_metric_preference)
+ ||
+ (nh1->mrib_route_metric != nh2->mrib_route_metric);
+}
+
+enum pim_rpf_result pim_rpf_update(struct pim_upstream *up,
+ struct in_addr *old_rpf_addr)
+{
+ struct in_addr save_rpf_addr;
+ struct pim_nexthop save_nexthop;
+ struct pim_rpf *rpf = &up->rpf;
+
+ save_nexthop = rpf->source_nexthop; /* detect change in pim_nexthop */
+ save_rpf_addr = rpf->rpf_addr; /* detect change in RPF'(S,G) */
+
+ if (pim_nexthop_lookup(&rpf->source_nexthop,
+ up->source_addr)) {
+ return PIM_RPF_FAILURE;
+ }
+
+ rpf->rpf_addr = pim_rpf_find_rpf_addr(up);
+ if (PIM_INADDR_IS_ANY(rpf->rpf_addr)) {
+ /* RPF'(S,G) not found */
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s %s: RPF'(%s,%s) not found: won't send join upstream",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str);
+ /* warning only */
+ }
+
+ /* detect change in pim_nexthop */
+ if (nexthop_mismatch(&rpf->source_nexthop, &save_nexthop)) {
+
+ /* if (PIM_DEBUG_PIM_EVENTS) */ {
+ char src_str[100];
+ char grp_str[100];
+ char nhaddr_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<addr?>", rpf->source_nexthop.mrib_nexthop_addr, nhaddr_str, sizeof(nhaddr_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s) source nexthop now is: interface=%s address=%s pref=%d metric=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "<ifname?>",
+ nhaddr_str,
+ rpf->source_nexthop.mrib_metric_preference,
+ rpf->source_nexthop.mrib_route_metric);
+ /* warning only */
+ }
+
+ pim_upstream_update_join_desired(up);
+ pim_upstream_update_could_assert(up);
+ pim_upstream_update_my_assert_metric(up);
+ }
+
+ /* detect change in RPF_interface(S) */
+ if (save_nexthop.interface != rpf->source_nexthop.interface) {
+
+ /* if (PIM_DEBUG_PIM_EVENTS) */ {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s) RPF_interface(S) changed from %s to %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ save_nexthop.interface ? save_nexthop.interface->name : "<oldif?>",
+ rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : "<newif?>");
+ /* warning only */
+ }
+
+ pim_upstream_rpf_interface_changed(up, save_nexthop.interface);
+ }
+
+ /* detect change in RPF'(S,G) */
+ if (save_rpf_addr.s_addr != rpf->rpf_addr.s_addr) {
+
+ /* return old rpf to caller ? */
+ if (old_rpf_addr)
+ *old_rpf_addr = save_rpf_addr;
+
+ return PIM_RPF_CHANGED;
+ }
+
+ return PIM_RPF_OK;
+}
+
+/*
+ RFC 4601: 4.1.6. State Summarization Macros
+
+ neighbor RPF'(S,G) {
+ if ( I_Am_Assert_Loser(S, G, RPF_interface(S) )) {
+ return AssertWinner(S, G, RPF_interface(S) )
+ } else {
+ return NBR( RPF_interface(S), MRIB.next_hop( S ) )
+ }
+ }
+
+ RPF'(*,G) and RPF'(S,G) indicate the neighbor from which data
+ packets should be coming and to which joins should be sent on the RP
+ tree and SPT, respectively.
+*/
+static struct in_addr pim_rpf_find_rpf_addr(struct pim_upstream *up)
+{
+ struct pim_ifchannel *rpf_ch;
+ struct pim_neighbor *neigh;
+ struct in_addr rpf_addr;
+
+ if (!up->rpf.source_nexthop.interface) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_warn("%s: missing RPF interface for upstream (S,G)=(%s,%s)",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str);
+
+ rpf_addr.s_addr = PIM_NET_INADDR_ANY;
+ return rpf_addr;
+ }
+
+ rpf_ch = pim_ifchannel_find(up->rpf.source_nexthop.interface,
+ up->source_addr, up->group_addr);
+ if (rpf_ch) {
+ if (rpf_ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
+ return rpf_ch->ifassert_winner;
+ }
+ }
+
+ /* return NBR( RPF_interface(S), MRIB.next_hop( S ) ) */
+
+ neigh = pim_if_find_neighbor(up->rpf.source_nexthop.interface,
+ up->rpf.source_nexthop.mrib_nexthop_addr);
+ if (neigh)
+ rpf_addr = neigh->source_addr;
+ else
+ rpf_addr.s_addr = PIM_NET_INADDR_ANY;
+
+ return rpf_addr;
+}
diff --git a/pimd/pim_rpf.h b/pimd/pim_rpf.h
new file mode 100644
index 00000000..078e89f3
--- /dev/null
+++ b/pimd/pim_rpf.h
@@ -0,0 +1,36 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_RPF_H
+#define PIM_RPF_H
+
+#include <zebra.h>
+
+#include "pim_upstream.h"
+#include "pim_neighbor.h"
+
+int pim_nexthop_lookup(struct pim_nexthop *nexthop,
+ struct in_addr addr);
+enum pim_rpf_result pim_rpf_update(struct pim_upstream *up,
+ struct in_addr *old_rpf_addr);
+
+#endif /* PIM_RPF_H */
diff --git a/pimd/pim_signals.c b/pimd/pim_signals.c
new file mode 100644
index 00000000..d1350b08
--- /dev/null
+++ b/pimd/pim_signals.c
@@ -0,0 +1,86 @@
+/*
+ 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 <signal.h>
+
+#include <zebra.h>
+#include "sigevent.h"
+#include "memory.h"
+#include "log.h"
+
+#include "pim_signals.h"
+#include "pimd.h"
+
+/*
+ * Signal handlers
+ */
+
+static void pim_sighup()
+{
+ zlog_debug ("SIGHUP received, ignoring");
+}
+
+static void pim_sigint()
+{
+ zlog_notice("Terminating on signal SIGINT");
+ pim_terminate();
+ exit(1);
+}
+
+static void pim_sigterm()
+{
+ zlog_notice("Terminating on signal SIGTERM");
+ pim_terminate();
+ exit(1);
+}
+
+static void pim_sigusr1()
+{
+ zlog_debug ("SIGUSR1 received");
+ zlog_rotate (NULL);
+}
+
+static struct quagga_signal_t pimd_signals[] =
+{
+ {
+ .signal = SIGHUP,
+ .handler = &pim_sighup,
+ },
+ {
+ .signal = SIGUSR1,
+ .handler = &pim_sigusr1,
+ },
+ {
+ .signal = SIGINT,
+ .handler = &pim_sigint,
+ },
+ {
+ .signal = SIGTERM,
+ .handler = &pim_sigterm,
+ },
+};
+
+void pim_signals_init()
+{
+ signal_init(master, array_size(pimd_signals), pimd_signals);
+}
+
diff --git a/pimd/pim_signals.h b/pimd/pim_signals.h
new file mode 100644
index 00000000..62523c03
--- /dev/null
+++ b/pimd/pim_signals.h
@@ -0,0 +1,28 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_SIGNALS_H
+#define PIM_SIGNALS_H
+
+void pim_signals_init(void);
+
+#endif /* PIM_SIGNALS_H */
diff --git a/pimd/pim_sock.c b/pimd/pim_sock.c
new file mode 100644
index 00000000..4816c567
--- /dev/null
+++ b/pimd/pim_sock.c
@@ -0,0 +1,389 @@
+/*
+ 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 "pim_mroute.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/igmp.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <netdb.h>
+#include <errno.h>
+
+#include <zebra.h>
+#include "log.h"
+#include "privs.h"
+
+#include "pimd.h"
+#include "pim_sock.h"
+#include "pim_str.h"
+#include "pim_igmp_join.h"
+
+/* GLOBAL VARS */
+extern struct zebra_privs_t pimd_privs;
+
+int pim_socket_raw(int protocol)
+{
+ int fd;
+
+ if ( pimd_privs.change (ZPRIVS_RAISE) )
+ zlog_err ("pim_sockek_raw: could not raise privs, %s",
+ safe_strerror (errno) );
+
+ fd = socket(AF_INET, SOCK_RAW, protocol);
+
+ if ( pimd_privs.change (ZPRIVS_LOWER) )
+ zlog_err ("pim_socket_raw: could not lower privs, %s",
+ safe_strerror (errno) );
+
+ if (fd < 0) {
+ zlog_warn("Could not create raw socket: errno=%d: %s",
+ errno, safe_strerror(errno));
+ return PIM_SOCK_ERR_SOCKET;
+ }
+
+ return fd;
+}
+
+int pim_socket_mcast(int protocol, struct in_addr ifaddr, int loop)
+{
+ int fd;
+
+ fd = pim_socket_raw(protocol);
+ if (fd < 0) {
+ zlog_warn("Could not create multicast socket: errno=%d: %s",
+ errno, safe_strerror(errno));
+ return PIM_SOCK_ERR_SOCKET;
+ }
+
+ /* Needed to obtain destination address from recvmsg() */
+ {
+#if defined(HAVE_IP_PKTINFO)
+ /* Linux IP_PKTINFO */
+ int opt = 1;
+ if (setsockopt(fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt))) {
+ zlog_warn("Could not set IP_PKTINFO on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ }
+#elif defined(HAVE_IP_RECVDSTADDR)
+ /* BSD IP_RECVDSTADDR */
+ int opt = 1;
+ if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt))) {
+ zlog_warn("Could not set IP_RECVDSTADDR on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ }
+#else
+ zlog_err("%s %s: Missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()",
+ __FILE__, __PRETTY_FUNCTION__);
+ close(fd);
+ return PIM_SOCK_ERR_DSTADDR;
+#endif
+ }
+
+
+ /* Set router alert (RFC 2113) for all IGMP messages (RFC 3376 4. Message Formats)*/
+ if (protocol == IPPROTO_IGMP) {
+ char ra[4];
+ ra[0] = 148;
+ ra[1] = 4;
+ ra[2] = 0;
+ ra[3] = 0;
+ if (setsockopt(fd, IPPROTO_IP, IP_OPTIONS, ra, 4)) {
+ zlog_warn("Could not set Router Alert Option on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_RA;
+ }
+ }
+
+ {
+ int reuse = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ (void *) &reuse, sizeof(reuse))) {
+ zlog_warn("Could not set Reuse Address Option on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_REUSE;
+ }
+ }
+
+ {
+ const int MTTL = 1;
+ int ttl = MTTL;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+ (void *) &ttl, sizeof(ttl))) {
+ zlog_warn("Could not set multicast TTL=%d on socket fd=%d: errno=%d: %s",
+ MTTL, fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_TTL;
+ }
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+ (void *) &loop, sizeof(loop))) {
+ zlog_warn("Could not %s Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+ loop ? "enable" : "disable",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_LOOP;
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
+ (void *) &ifaddr, sizeof(ifaddr))) {
+ zlog_warn("Could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_IFACE;
+ }
+
+ {
+ long flags;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ zlog_warn("Could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_NONBLOCK_GETFL;
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
+ zlog_warn("Could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_NONBLOCK_SETFL;
+ }
+ }
+
+ return fd;
+}
+
+int pim_socket_join(int fd, struct in_addr group,
+ struct in_addr ifaddr, int ifindex)
+{
+ int ret;
+
+#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+ struct ip_mreqn opt;
+#else
+ struct ip_mreq opt;
+#endif
+
+ opt.imr_multiaddr = group;
+
+#ifdef HAVE_STRUCT_IP_MREQN_IMR_IFINDEX
+ opt.imr_address = ifaddr;
+ opt.imr_ifindex = ifindex;
+#else
+ opt.imr_interface = ifaddr;
+#endif
+
+ ret = setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &opt, sizeof(opt));
+ if (ret) {
+ char group_str[100];
+ char ifaddr_str[100];
+ if (!inet_ntop(AF_INET, &group, group_str , sizeof(group_str)))
+ sprintf(group_str, "<group?>");
+ if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str , sizeof(ifaddr_str)))
+ sprintf(ifaddr_str, "<ifaddr?>");
+
+ zlog_err("Failure socket joining fd=%d group %s on interface address %s: errno=%d: %s",
+ fd, group_str, ifaddr_str, errno, safe_strerror(errno));
+ return ret;
+ }
+
+ if (PIM_DEBUG_TRACE) {
+ char group_str[100];
+ char ifaddr_str[100];
+ if (!inet_ntop(AF_INET, &group, group_str , sizeof(group_str)))
+ sprintf(group_str, "<group?>");
+ if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str , sizeof(ifaddr_str)))
+ sprintf(ifaddr_str, "<ifaddr?>");
+
+ zlog_debug("Socket fd=%d joined group %s on interface address %s",
+ fd, group_str, ifaddr_str);
+ }
+
+ return ret;
+}
+
+int pim_socket_join_source(int fd, int ifindex,
+ struct in_addr group_addr,
+ struct in_addr source_addr,
+ const char *ifname)
+{
+ if (pim_igmp_join_source(fd, ifindex, group_addr, source_addr)) {
+ int e = errno;
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: setsockopt(fd=%d) failure for IGMP group %s source %s ifindex %d on interface %s: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ fd, group_str, source_str, ifindex, ifname,
+ e, safe_strerror(e));
+ return -1;
+ }
+
+ return 0;
+}
+
+int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len,
+ struct sockaddr_in *from, socklen_t *fromlen,
+ struct sockaddr_in *to, socklen_t *tolen,
+ int *ifindex)
+{
+ struct msghdr msgh;
+ struct cmsghdr *cmsg;
+ struct iovec iov;
+ char cbuf[1000];
+ int err;
+
+ /*
+ * IP_PKTINFO / IP_RECVDSTADDR don't yield sin_port.
+ * Use getsockname() to get sin_port.
+ */
+ if (to) {
+ struct sockaddr_in si;
+ socklen_t si_len = sizeof(si);
+
+ ((struct sockaddr_in *) to)->sin_family = AF_INET;
+
+ if (pim_socket_getsockname(fd, (struct sockaddr *) &si, &si_len)) {
+ ((struct sockaddr_in *) to)->sin_port = ntohs(0);
+ ((struct sockaddr_in *) to)->sin_addr.s_addr = ntohl(0);
+ }
+ else {
+ ((struct sockaddr_in *) to)->sin_port = si.sin_port;
+ ((struct sockaddr_in *) to)->sin_addr = si.sin_addr;
+ }
+
+ if (tolen)
+ *tolen = sizeof(si);
+ }
+
+ memset(&msgh, 0, sizeof(struct msghdr));
+ iov.iov_base = buf;
+ iov.iov_len = len;
+ msgh.msg_control = cbuf;
+ msgh.msg_controllen = sizeof(cbuf);
+ msgh.msg_name = from;
+ msgh.msg_namelen = fromlen ? *fromlen : 0;
+ msgh.msg_iov = &iov;
+ msgh.msg_iovlen = 1;
+ msgh.msg_flags = 0;
+
+ err = recvmsg(fd, &msgh, 0);
+ if (err < 0)
+ return err;
+
+ if (fromlen)
+ *fromlen = msgh.msg_namelen;
+
+ for (cmsg = CMSG_FIRSTHDR(&msgh);
+ cmsg != NULL;
+ cmsg = CMSG_NXTHDR(&msgh,cmsg)) {
+
+#ifdef HAVE_IP_PKTINFO
+ if ((cmsg->cmsg_level == SOL_IP) && (cmsg->cmsg_type == IP_PKTINFO)) {
+ struct in_pktinfo *i = (struct in_pktinfo *) CMSG_DATA(cmsg);
+ if (to)
+ ((struct sockaddr_in *) to)->sin_addr = i->ipi_addr;
+ if (tolen)
+ *tolen = sizeof(struct sockaddr_in);
+ if (ifindex)
+ *ifindex = i->ipi_ifindex;
+
+ if (to && PIM_DEBUG_PACKETS) {
+ char to_str[100];
+ pim_inet4_dump("<to?>", to->sin_addr, to_str, sizeof(to_str));
+ zlog_debug("%s: HAVE_IP_PKTINFO to=%s,%d",
+ __PRETTY_FUNCTION__,
+ to_str, ntohs(to->sin_port));
+ }
+
+ break;
+ }
+#endif
+
+#ifdef HAVE_IP_RECVDSTADDR
+ if ((cmsg->cmsg_level == IPPROTO_IP) && (cmsg->cmsg_type == IP_RECVDSTADDR)) {
+ struct in_addr *i = (struct in_addr *) CMSG_DATA(cmsg);
+ if (to)
+ ((struct sockaddr_in *) to)->sin_addr = *i;
+ if (tolen)
+ *tolen = sizeof(struct sockaddr_in);
+
+ if (to && PIM_DEBUG_PACKETS) {
+ char to_str[100];
+ pim_inet4_dump("<to?>", to->sin_addr, to_str, sizeof(to_str));
+ zlog_debug("%s: HAVE_IP_RECVDSTADDR to=%s,%d",
+ __PRETTY_FUNCTION__,
+ to_str, ntohs(to->sin_port));
+ }
+
+ break;
+ }
+#endif
+
+#if defined(HAVE_IP_RECVIF) && defined(CMSG_IFINDEX)
+ if (cmsg->cmsg_type == IP_RECVIF)
+ if (ifindex)
+ *ifindex = CMSG_IFINDEX(cmsg);
+#endif
+
+ } /* for (cmsg) */
+
+ return err; /* len */
+}
+
+int pim_socket_mcastloop_get(int fd)
+{
+ int loop;
+ socklen_t loop_len = sizeof(loop);
+
+ if (getsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+ &loop, &loop_len)) {
+ int e = errno;
+ zlog_warn("Could not get Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ errno = e;
+ return PIM_SOCK_ERR_LOOP;
+ }
+
+ return loop;
+}
+
+int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen)
+{
+ if (getsockname(fd, name, namelen)) {
+ int e = errno;
+ zlog_warn("Could not get Socket Name for socket fd=%d: errno=%d: %s",
+ fd, errno, safe_strerror(errno));
+ errno = e;
+ return PIM_SOCK_ERR_NAME;
+ }
+
+ return PIM_SOCK_ERR_NONE;
+}
diff --git a/pimd/pim_sock.h b/pimd/pim_sock.h
new file mode 100644
index 00000000..cfe39ad1
--- /dev/null
+++ b/pimd/pim_sock.h
@@ -0,0 +1,57 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_SOCK_H
+#define PIM_SOCK_H
+
+#include <netinet/in.h>
+
+#define PIM_SOCK_ERR_NONE (0) /* No error */
+#define PIM_SOCK_ERR_SOCKET (-1) /* socket() */
+#define PIM_SOCK_ERR_RA (-2) /* Router Alert option */
+#define PIM_SOCK_ERR_REUSE (-3) /* Reuse option */
+#define PIM_SOCK_ERR_TTL (-4) /* TTL option */
+#define PIM_SOCK_ERR_LOOP (-5) /* Loopback option */
+#define PIM_SOCK_ERR_IFACE (-6) /* Outgoing interface option */
+#define PIM_SOCK_ERR_DSTADDR (-7) /* Outgoing interface option */
+#define PIM_SOCK_ERR_NONBLOCK_GETFL (-8) /* Get O_NONBLOCK */
+#define PIM_SOCK_ERR_NONBLOCK_SETFL (-9) /* Set O_NONBLOCK */
+#define PIM_SOCK_ERR_NAME (-10) /* Socket name (getsockname) */
+
+int pim_socket_raw(int protocol);
+int pim_socket_mcast(int protocol, struct in_addr ifaddr, int loop);
+int pim_socket_join(int fd, struct in_addr group,
+ struct in_addr ifaddr, int ifindex);
+int pim_socket_join_source(int fd, int ifindex,
+ struct in_addr group_addr,
+ struct in_addr source_addr,
+ const char *ifname);
+int pim_socket_recvfromto(int fd, uint8_t *buf, size_t len,
+ struct sockaddr_in *from, socklen_t *fromlen,
+ struct sockaddr_in *to, socklen_t *tolen,
+ int *ifindex);
+
+int pim_socket_mcastloop_get(int fd);
+
+int pim_socket_getsockname(int fd, struct sockaddr *name, socklen_t *namelen);
+
+#endif /* PIM_SOCK_H */
diff --git a/pimd/pim_ssmpingd.c b/pimd/pim_ssmpingd.c
new file mode 100644
index 00000000..fd1eac06
--- /dev/null
+++ b/pimd/pim_ssmpingd.c
@@ -0,0 +1,448 @@
+/*
+ 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 "if.h"
+#include "log.h"
+#include "memory.h"
+
+#include "pim_ssmpingd.h"
+#include "pim_time.h"
+#include "pim_sock.h"
+#include "pim_str.h"
+#include "pimd.h"
+
+static const char * const PIM_SSMPINGD_REPLY_GROUP = "232.43.211.234";
+
+enum {
+ PIM_SSMPINGD_REQUEST = 'Q',
+ PIM_SSMPINGD_REPLY = 'A'
+};
+
+static void ssmpingd_read_on(struct ssmpingd_sock *ss);
+
+void pim_ssmpingd_init()
+{
+ int result;
+
+ zassert(!qpim_ssmpingd_list);
+
+ result = inet_pton(AF_INET, PIM_SSMPINGD_REPLY_GROUP, &qpim_ssmpingd_group_addr);
+
+ zassert(result > 0);
+}
+
+void pim_ssmpingd_destroy()
+{
+ if (qpim_ssmpingd_list) {
+ list_free(qpim_ssmpingd_list);
+ qpim_ssmpingd_list = 0;
+ }
+}
+
+static struct ssmpingd_sock *ssmpingd_find(struct in_addr source_addr)
+{
+ struct listnode *node;
+ struct ssmpingd_sock *ss;
+
+ if (!qpim_ssmpingd_list)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_ssmpingd_list, node, ss))
+ if (source_addr.s_addr == ss->source_addr.s_addr)
+ return ss;
+
+ return 0;
+}
+
+static void ssmpingd_free(struct ssmpingd_sock *ss)
+{
+ XFREE(MTYPE_PIM_SSMPINGD, ss);
+}
+
+static int ssmpingd_socket(struct in_addr addr, int port, int mttl)
+{
+ struct sockaddr_in sockaddr;
+ int fd;
+
+ fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ zlog_err("%s: could not create socket: errno=%d: %s",
+ __PRETTY_FUNCTION__, errno, safe_strerror(errno));
+ return -1;
+ }
+
+ sockaddr.sin_family = AF_INET;
+ sockaddr.sin_addr = addr;
+ sockaddr.sin_port = htons(port);
+
+ if (bind(fd, &sockaddr, sizeof(sockaddr))) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s: bind(fd=%d,addr=%s,port=%d,len=%zu) failure: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ fd, addr_str, port, sizeof(sockaddr),
+ errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ /* Needed to obtain destination address from recvmsg() */
+ {
+#if defined(HAVE_IP_PKTINFO)
+ /* Linux IP_PKTINFO */
+ int opt = 1;
+ if (setsockopt(fd, SOL_IP, IP_PKTINFO, &opt, sizeof(opt))) {
+ zlog_warn("%s: could not set IP_PKTINFO on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ }
+#elif defined(HAVE_IP_RECVDSTADDR)
+ /* BSD IP_RECVDSTADDR */
+ int opt = 1;
+ if (setsockopt(fd, IPPROTO_IP, IP_RECVDSTADDR, &opt, sizeof(opt))) {
+ zlog_warn("%s: could not set IP_RECVDSTADDR on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ }
+#else
+ zlog_err("%s %s: missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()",
+ __FILE__, __PRETTY_FUNCTION__);
+ close(fd);
+ return -1;
+#endif
+ }
+
+ {
+ int reuse = 1;
+ if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
+ (void *) &reuse, sizeof(reuse))) {
+ zlog_warn("%s: could not set Reuse Address Option on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_TTL,
+ (void *) &mttl, sizeof(mttl))) {
+ zlog_warn("%s: could not set multicast TTL=%d on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, mttl, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ {
+ int loop = 0;
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_LOOP,
+ (void *) &loop, sizeof(loop))) {
+ zlog_warn("%s: could not %s Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ loop ? "enable" : "disable",
+ fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_LOOP;
+ }
+ }
+
+ if (setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF,
+ (void *) &addr, sizeof(addr))) {
+ zlog_warn("%s: could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ {
+ long flags;
+
+ flags = fcntl(fd, F_GETFL, 0);
+ if (flags < 0) {
+ zlog_warn("%s: could not get fcntl(F_GETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ if (fcntl(fd, F_SETFL, flags | O_NONBLOCK)) {
+ zlog_warn("%s: could not set fcntl(F_SETFL,O_NONBLOCK) on socket fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+ }
+
+ return fd;
+}
+
+static void ssmpingd_delete(struct ssmpingd_sock *ss)
+{
+ zassert(ss);
+ zassert(qpim_ssmpingd_list);
+
+ THREAD_OFF(ss->t_sock_read);
+
+ if (close(ss->sock_fd)) {
+ int e = errno;
+ char source_str[100];
+ pim_inet4_dump("<src?>", ss->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: failure closing ssmpingd sock_fd=%d for source %s: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ ss->sock_fd, source_str, e, safe_strerror(e));
+ /* warning only */
+ }
+
+ listnode_delete(qpim_ssmpingd_list, ss);
+ ssmpingd_free(ss);
+}
+
+static void ssmpingd_sendto(struct ssmpingd_sock *ss,
+ const uint8_t *buf,
+ int len,
+ struct sockaddr_in to)
+{
+ socklen_t tolen = sizeof(to);
+ int sent;
+
+ sent = sendto(ss->sock_fd, buf, len, MSG_DONTWAIT, &to, tolen);
+ if (sent != len) {
+ int e = errno;
+ char to_str[100];
+ pim_inet4_dump("<to?>", to.sin_addr, to_str, sizeof(to_str));
+ if (sent < 0) {
+ zlog_warn("%s: sendto() failure to %s,%d: fd=%d len=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ to_str, ntohs(to.sin_port), ss->sock_fd, len,
+ e, safe_strerror(e));
+ }
+ else {
+ zlog_warn("%s: sendto() partial to %s,%d: fd=%d len=%d: sent=%d",
+ __PRETTY_FUNCTION__,
+ to_str, ntohs(to.sin_port), ss->sock_fd,
+ len, sent);
+ }
+ }
+}
+
+static int ssmpingd_read_msg(struct ssmpingd_sock *ss)
+{
+ struct interface *ifp;
+ struct sockaddr_in from;
+ struct sockaddr_in to;
+ socklen_t fromlen = sizeof(from);
+ socklen_t tolen = sizeof(to);
+ int ifindex = -1;
+ uint8_t buf[1000];
+ int len;
+
+ ++ss->requests;
+
+ len = pim_socket_recvfromto(ss->sock_fd, buf, sizeof(buf),
+ &from, &fromlen,
+ &to, &tolen,
+ &ifindex);
+ if (len < 0) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", ss->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: failure receiving ssmping for source %s on fd=%d: errno=%d: %s",
+ __PRETTY_FUNCTION__, source_str, ss->sock_fd, errno, safe_strerror(errno));
+ return -1;
+ }
+
+ ifp = if_lookup_by_index(ifindex);
+
+ if (buf[0] != PIM_SSMPINGD_REQUEST) {
+ char source_str[100];
+ char from_str[100];
+ char to_str[100];
+ pim_inet4_dump("<src?>", ss->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<from?>", from.sin_addr, from_str, sizeof(from_str));
+ pim_inet4_dump("<to?>", to.sin_addr, to_str, sizeof(to_str));
+ zlog_warn("%s: bad ssmping type=%d from %s,%d to %s,%d on interface %s ifindex=%d fd=%d src=%s",
+ __PRETTY_FUNCTION__,
+ buf[0],
+ from_str, ntohs(from.sin_port),
+ to_str, ntohs(to.sin_port),
+ ifp ? ifp->name : "<iface?>",
+ ifindex, ss->sock_fd,
+ source_str);
+ return 0;
+ }
+
+ if (PIM_DEBUG_SSMPINGD) {
+ char source_str[100];
+ char from_str[100];
+ char to_str[100];
+ pim_inet4_dump("<src?>", ss->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<from?>", from.sin_addr, from_str, sizeof(from_str));
+ pim_inet4_dump("<to?>", to.sin_addr, to_str, sizeof(to_str));
+ zlog_debug("%s: recv ssmping from %s,%d to %s,%d on interface %s ifindex=%d fd=%d src=%s",
+ __PRETTY_FUNCTION__,
+ from_str, ntohs(from.sin_port),
+ to_str, ntohs(to.sin_port),
+ ifp ? ifp->name : "<iface?>",
+ ifindex, ss->sock_fd,
+ source_str);
+ }
+
+ buf[0] = PIM_SSMPINGD_REPLY;
+
+ /* unicast reply */
+ ssmpingd_sendto(ss, buf, len, from);
+
+ /* multicast reply */
+ from.sin_addr = qpim_ssmpingd_group_addr;
+ ssmpingd_sendto(ss, buf, len, from);
+
+ return 0;
+}
+
+static int ssmpingd_sock_read(struct thread *t)
+{
+ struct ssmpingd_sock *ss;
+ int sock_fd;
+ int result;
+
+ zassert(t);
+
+ ss = THREAD_ARG(t);
+ zassert(ss);
+
+ sock_fd = THREAD_FD(t);
+ zassert(sock_fd == ss->sock_fd);
+
+ result = ssmpingd_read_msg(ss);
+
+ /* Keep reading */
+ ss->t_sock_read = 0;
+ ssmpingd_read_on(ss);
+
+ return result;
+}
+
+static void ssmpingd_read_on(struct ssmpingd_sock *ss)
+{
+ zassert(!ss->t_sock_read);
+ THREAD_READ_ON(master, ss->t_sock_read,
+ ssmpingd_sock_read, ss, ss->sock_fd);
+}
+
+static struct ssmpingd_sock *ssmpingd_new(struct in_addr source_addr)
+{
+ struct ssmpingd_sock *ss;
+ int sock_fd;
+
+ if (!qpim_ssmpingd_list) {
+ qpim_ssmpingd_list = list_new();
+ if (!qpim_ssmpingd_list) {
+ zlog_err("%s %s: failure: qpim_ssmpingd_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return 0;
+ }
+ qpim_ssmpingd_list->del = (void (*)(void *)) ssmpingd_free;
+ }
+
+ sock_fd = ssmpingd_socket(source_addr, /* port: */ 4321, /* mTTL: */ 64);
+ if (sock_fd < 0) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: ssmpingd_socket() failure for source %s",
+ __PRETTY_FUNCTION__, source_str);
+ return 0;
+ }
+
+ ss = XMALLOC(MTYPE_PIM_SSMPINGD, sizeof(*ss));
+ if (!ss) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_err("%s: XMALLOC(%zu) failure for ssmpingd source %s",
+ __PRETTY_FUNCTION__,
+ sizeof(*ss), source_str);
+ close(sock_fd);
+ return 0;
+ }
+
+ ss->sock_fd = sock_fd;
+ ss->t_sock_read = 0;
+ ss->source_addr = source_addr;
+ ss->creation = pim_time_monotonic_sec();
+ ss->requests = 0;
+
+ listnode_add(qpim_ssmpingd_list, ss);
+
+ ssmpingd_read_on(ss);
+
+ return ss;
+}
+
+int pim_ssmpingd_start(struct in_addr source_addr)
+{
+ struct ssmpingd_sock *ss;
+
+ ss = ssmpingd_find(source_addr);
+ if (ss) {
+ /* silently ignore request to recreate entry */
+ return 0;
+ }
+
+ {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_info("%s: starting ssmpingd for source %s",
+ __PRETTY_FUNCTION__, source_str);
+ }
+
+ ss = ssmpingd_new(source_addr);
+ if (!ss) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: ssmpingd_new() failure for source %s",
+ __PRETTY_FUNCTION__, source_str);
+ return -1;
+ }
+
+ return 0;
+}
+
+int pim_ssmpingd_stop(struct in_addr source_addr)
+{
+ struct ssmpingd_sock *ss;
+
+ ss = ssmpingd_find(source_addr);
+ if (!ss) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s: could not find ssmpingd for source %s",
+ __PRETTY_FUNCTION__, source_str);
+ return -1;
+ }
+
+ {
+ char source_str[100];
+ pim_inet4_dump("<src?>", source_addr, source_str, sizeof(source_str));
+ zlog_info("%s: stopping ssmpingd for source %s",
+ __PRETTY_FUNCTION__, source_str);
+ }
+
+ ssmpingd_delete(ss);
+
+ return 0;
+}
diff --git a/pimd/pim_ssmpingd.h b/pimd/pim_ssmpingd.h
new file mode 100644
index 00000000..4bef20b2
--- /dev/null
+++ b/pimd/pim_ssmpingd.h
@@ -0,0 +1,45 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_SSMPINGD_H
+#define PIM_SSMPINGD_H
+
+#include <zebra.h>
+
+#include "if.h"
+
+#include "pim_iface.h"
+
+struct ssmpingd_sock {
+ int sock_fd; /* socket */
+ struct thread *t_sock_read; /* thread for reading socket */
+ struct in_addr source_addr; /* source address */
+ int64_t creation; /* timestamp of socket creation */
+ int64_t requests; /* counter */
+};
+
+void pim_ssmpingd_init(void);
+void pim_ssmpingd_destroy(void);
+int pim_ssmpingd_start(struct in_addr source_addr);
+int pim_ssmpingd_stop(struct in_addr source_addr);
+
+#endif /* PIM_SSMPINGD_H */
diff --git a/pimd/pim_str.c b/pimd/pim_str.c
new file mode 100644
index 00000000..af5a184d
--- /dev/null
+++ b/pimd/pim_str.c
@@ -0,0 +1,46 @@
+/*
+ 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 <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+#include <zebra.h>
+
+#include "log.h"
+
+#include "pim_str.h"
+
+void pim_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size)
+{
+ int save_errno = errno;
+
+ if (!inet_ntop(AF_INET, &addr, buf, buf_size)) {
+ int e = errno;
+ zlog_warn("pim_inet4_dump: inet_ntop(AF_INET,buf_size=%d): errno=%d: %s",
+ buf_size, e, safe_strerror(e));
+ if (onfail)
+ snprintf(buf, buf_size, "%s", onfail);
+ }
+
+ errno = save_errno;
+}
diff --git a/pimd/pim_str.h b/pimd/pim_str.h
new file mode 100644
index 00000000..925f17f7
--- /dev/null
+++ b/pimd/pim_str.h
@@ -0,0 +1,32 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_STR_H
+#define PIM_STR_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+
+void pim_inet4_dump(const char *onfail, struct in_addr addr, char *buf, int buf_size);
+
+#endif
diff --git a/pimd/pim_time.c b/pimd/pim_time.c
new file mode 100644
index 00000000..097b470b
--- /dev/null
+++ b/pimd/pim_time.c
@@ -0,0 +1,165 @@
+/*
+ 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 <string.h>
+#include <sys/time.h>
+#include <time.h>
+
+#include <zebra.h>
+#include "log.h"
+#include "thread.h"
+
+#include "pim_time.h"
+
+static int gettime_monotonic(struct timeval *tv)
+{
+ int result;
+
+ result = gettimeofday(tv, 0);
+ if (result) {
+ zlog_err("%s: gettimeofday() failure: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ errno, safe_strerror(errno));
+ }
+
+ return result;
+}
+
+/*
+ pim_time_monotonic_sec():
+ number of seconds since some unspecified starting point
+*/
+int64_t pim_time_monotonic_sec()
+{
+ struct timeval now_tv;
+
+ if (gettime_monotonic(&now_tv)) {
+ zlog_err("%s: gettime_monotonic() failure: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ errno, safe_strerror(errno));
+ return -1;
+ }
+
+ return now_tv.tv_sec;
+}
+
+/*
+ pim_time_monotonic_dsec():
+ number of deciseconds since some unspecified starting point
+*/
+int64_t pim_time_monotonic_dsec()
+{
+ struct timeval now_tv;
+ int64_t now_dsec;
+
+ if (gettime_monotonic(&now_tv)) {
+ zlog_err("%s: gettime_monotonic() failure: errno=%d: %s",
+ __PRETTY_FUNCTION__,
+ errno, safe_strerror(errno));
+ return -1;
+ }
+
+ now_dsec = ((int64_t) now_tv.tv_sec) * 10 + ((int64_t) now_tv.tv_usec) / 100000;
+
+ return now_dsec;
+}
+
+int pim_time_mmss(char *buf, int buf_size, long sec)
+{
+ long mm;
+ int wr;
+
+ zassert(buf_size >= 5);
+
+ mm = sec / 60;
+ sec %= 60;
+
+ wr = snprintf(buf, buf_size, "%02ld:%02ld", mm, sec);
+
+ return wr != 8;
+}
+
+static int pim_time_hhmmss(char *buf, int buf_size, long sec)
+{
+ long hh;
+ long mm;
+ int wr;
+
+ zassert(buf_size >= 8);
+
+ hh = sec / 3600;
+ sec %= 3600;
+ mm = sec / 60;
+ sec %= 60;
+
+ wr = snprintf(buf, buf_size, "%02ld:%02ld:%02ld", hh, mm, sec);
+
+ return wr != 8;
+}
+
+void pim_time_timer_to_mmss(char *buf, int buf_size, struct thread *t_timer)
+{
+ if (t_timer) {
+ pim_time_mmss(buf, buf_size,
+ thread_timer_remain_second(t_timer));
+ }
+ else {
+ snprintf(buf, buf_size, "--:--");
+ }
+}
+
+void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct thread *t_timer)
+{
+ if (t_timer) {
+ pim_time_hhmmss(buf, buf_size,
+ thread_timer_remain_second(t_timer));
+ }
+ else {
+ snprintf(buf, buf_size, "--:--:--");
+ }
+}
+
+void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec)
+{
+ zassert(buf_size >= 8);
+
+ pim_time_hhmmss(buf, buf_size, uptime_sec);
+}
+
+void pim_time_uptime_begin(char *buf, int buf_size, int64_t now, int64_t begin)
+{
+ if (begin > 0)
+ pim_time_uptime(buf, buf_size, now - begin);
+ else
+ snprintf(buf, buf_size, "--:--:--");
+}
+
+long pim_time_timer_remain_msec(struct thread *t_timer)
+{
+ /* FIXME: Actually fetch msec resolution from thread */
+
+ /* no timer thread running means timer has expired: return 0 */
+
+ return t_timer ?
+ 1000 * thread_timer_remain_second(t_timer) :
+ 0;
+}
diff --git a/pimd/pim_time.h b/pimd/pim_time.h
new file mode 100644
index 00000000..2984d9a8
--- /dev/null
+++ b/pimd/pim_time.h
@@ -0,0 +1,40 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_TIME_H
+#define PIM_TIME_H
+
+#include <stdint.h>
+
+#include <zebra.h>
+#include "thread.h"
+
+int64_t pim_time_monotonic_sec(void);
+int64_t pim_time_monotonic_dsec(void);
+int pim_time_mmss(char *buf, int buf_size, long sec);
+void pim_time_timer_to_mmss(char *buf, int buf_size, struct thread *t);
+void pim_time_timer_to_hhmmss(char *buf, int buf_size, struct thread *t);
+void pim_time_uptime(char *buf, int buf_size, int64_t uptime_sec);
+void pim_time_uptime_begin(char *buf, int buf_size, int64_t now, int64_t begin);
+long pim_time_timer_remain_msec(struct thread *t_timer);
+
+#endif /* PIM_TIME_H */
diff --git a/pimd/pim_tlv.c b/pimd/pim_tlv.c
new file mode 100644
index 00000000..95ee5ab0
--- /dev/null
+++ b/pimd/pim_tlv.c
@@ -0,0 +1,721 @@
+/*
+ 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 "prefix.h"
+
+#include "pimd.h"
+#include "pim_int.h"
+#include "pim_tlv.h"
+#include "pim_str.h"
+#include "pim_msg.h"
+
+uint8_t *pim_tlv_append_uint16(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ uint16_t option_type,
+ uint16_t option_value)
+{
+ uint16_t option_len = 2;
+
+ if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) {
+ zlog_warn("%s: buffer overflow: left=%zd needed=%d",
+ __PRETTY_FUNCTION__,
+ buf_pastend - buf, PIM_TLV_OPTION_SIZE(option_len));
+ return 0;
+ }
+
+ *(uint16_t *) buf = htons(option_type);
+ buf += 2;
+ *(uint16_t *) buf = htons(option_len);
+ buf += 2;
+ *(uint16_t *) buf = htons(option_value);
+ buf += option_len;
+
+ return buf;
+}
+
+uint8_t *pim_tlv_append_2uint16(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ uint16_t option_type,
+ uint16_t option_value1,
+ uint16_t option_value2)
+{
+ uint16_t option_len = 4;
+
+ if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) {
+ zlog_warn("%s: buffer overflow: left=%zd needed=%d",
+ __PRETTY_FUNCTION__,
+ buf_pastend - buf, PIM_TLV_OPTION_SIZE(option_len));
+ return 0;
+ }
+
+ *(uint16_t *) buf = htons(option_type);
+ buf += 2;
+ *(uint16_t *) buf = htons(option_len);
+ buf += 2;
+ *(uint16_t *) buf = htons(option_value1);
+ buf += 2;
+ *(uint16_t *) buf = htons(option_value2);
+ buf += 2;
+
+ return buf;
+}
+
+uint8_t *pim_tlv_append_uint32(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ uint16_t option_type,
+ uint32_t option_value)
+{
+ uint16_t option_len = 4;
+
+ if ((buf + PIM_TLV_OPTION_SIZE(option_len)) > buf_pastend) {
+ zlog_warn("%s: buffer overflow: left=%zd needed=%d",
+ __PRETTY_FUNCTION__,
+ buf_pastend - buf, PIM_TLV_OPTION_SIZE(option_len));
+ return 0;
+ }
+
+ *(uint16_t *) buf = htons(option_type);
+ buf += 2;
+ *(uint16_t *) buf = htons(option_len);
+ buf += 2;
+ pim_write_uint32(buf, option_value);
+ buf += option_len;
+
+ return buf;
+}
+
+#define ucast_ipv4_encoding_len (2 + sizeof(struct in_addr))
+
+uint8_t *pim_tlv_append_addrlist_ucast(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ struct list *ifconnected)
+{
+ struct listnode *node;
+ uint16_t option_len = 0;
+
+ uint8_t *curr;
+
+ node = listhead(ifconnected);
+
+ /* Empty address list ? */
+ if (!node) {
+ return buf;
+ }
+
+ /* Skip first address (primary) */
+ node = listnextnode(node);
+
+ /* Scan secondary address list */
+ curr = buf + 4; /* skip T and L */
+ for (; node; node = listnextnode(node)) {
+ struct connected *ifc = listgetdata(node);
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ if ((curr + ucast_ipv4_encoding_len) > buf_pastend) {
+ zlog_warn("%s: buffer overflow: left=%zd needed=%zu",
+ __PRETTY_FUNCTION__,
+ buf_pastend - curr, ucast_ipv4_encoding_len);
+ return 0;
+ }
+
+ /* Write encoded unicast IPv4 address */
+ *(uint8_t *) curr = PIM_MSG_ADDRESS_FAMILY_IPV4; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+ ++curr;
+ *(uint8_t *) curr = 0; /* ucast IPv4 native encoding type (RFC 4601: 4.9.1) */
+ ++curr;
+ memcpy(curr, &p->u.prefix4, sizeof(struct in_addr));
+ curr += sizeof(struct in_addr);
+
+ option_len += ucast_ipv4_encoding_len;
+ }
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_warn("%s: number of encoded secondary unicast IPv4 addresses: %zu",
+ __PRETTY_FUNCTION__,
+ option_len / ucast_ipv4_encoding_len);
+ }
+
+ if (option_len < 1) {
+ /* Empty secondary unicast IPv4 address list */
+ return buf;
+ }
+
+ /*
+ * Write T and L
+ */
+ *(uint16_t *) buf = htons(PIM_MSG_OPTION_TYPE_ADDRESS_LIST);
+ *(uint16_t *) (buf + 2) = htons(option_len);
+
+ return curr;
+}
+
+static int check_tlv_length(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ int correct_len, int option_len)
+{
+ if (option_len != correct_len) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: PIM hello %s TLV with incorrect value size=%d correct=%d from %s on interface %s",
+ label, tlv_name,
+ option_len, correct_len,
+ src_str, ifname);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void check_tlv_redefinition_uint16(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ pim_hello_options options,
+ pim_hello_options opt_mask,
+ uint16_t new, uint16_t old)
+{
+ if (PIM_OPTION_IS_SET(options, opt_mask)) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: PIM hello TLV redefined %s=%u old=%u from %s on interface %s",
+ label, tlv_name,
+ new, old,
+ src_str, ifname);
+ }
+}
+
+static void check_tlv_redefinition_uint32(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ pim_hello_options options,
+ pim_hello_options opt_mask,
+ uint32_t new, uint32_t old)
+{
+ if (PIM_OPTION_IS_SET(options, opt_mask)) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: PIM hello TLV redefined %s=%u old=%u from %s on interface %s",
+ label, tlv_name,
+ new, old,
+ src_str, ifname);
+ }
+}
+
+static void check_tlv_redefinition_uint32_hex(const char *label, const char *tlv_name,
+ const char *ifname, struct in_addr src_addr,
+ pim_hello_options options,
+ pim_hello_options opt_mask,
+ uint32_t new, uint32_t old)
+{
+ if (PIM_OPTION_IS_SET(options, opt_mask)) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: PIM hello TLV redefined %s=%08x old=%08x from %s on interface %s",
+ label, tlv_name,
+ new, old,
+ src_str, ifname);
+ }
+}
+
+int pim_tlv_parse_holdtime(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint16_t *hello_option_holdtime,
+ uint16_t option_len,
+ const uint8_t *tlv_curr)
+{
+ const char *label = "holdtime";
+
+ if (check_tlv_length(__PRETTY_FUNCTION__, label,
+ ifname, src_addr,
+ sizeof(uint16_t), option_len)) {
+ return -1;
+ }
+
+ check_tlv_redefinition_uint16(__PRETTY_FUNCTION__, label,
+ ifname, src_addr,
+ *hello_options, PIM_OPTION_MASK_HOLDTIME,
+ PIM_TLV_GET_HOLDTIME(tlv_curr),
+ *hello_option_holdtime);
+
+ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_HOLDTIME);
+
+ *hello_option_holdtime = PIM_TLV_GET_HOLDTIME(tlv_curr);
+
+ return 0;
+}
+
+int pim_tlv_parse_lan_prune_delay(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint16_t *hello_option_propagation_delay,
+ uint16_t *hello_option_override_interval,
+ uint16_t option_len,
+ const uint8_t *tlv_curr)
+{
+ if (check_tlv_length(__PRETTY_FUNCTION__, "lan_prune_delay",
+ ifname, src_addr,
+ sizeof(uint32_t), option_len)) {
+ return -1;
+ }
+
+ check_tlv_redefinition_uint16(__PRETTY_FUNCTION__, "propagation_delay",
+ ifname, src_addr,
+ *hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY,
+ PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr),
+ *hello_option_propagation_delay);
+
+ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_LAN_PRUNE_DELAY);
+
+ *hello_option_propagation_delay = PIM_TLV_GET_PROPAGATION_DELAY(tlv_curr);
+ if (PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(tlv_curr)) {
+ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION);
+ }
+ else {
+ PIM_OPTION_UNSET(*hello_options, PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION);
+ }
+ ++tlv_curr;
+ ++tlv_curr;
+ *hello_option_override_interval = PIM_TLV_GET_OVERRIDE_INTERVAL(tlv_curr);
+
+ return 0;
+}
+
+int pim_tlv_parse_dr_priority(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint32_t *hello_option_dr_priority,
+ uint16_t option_len,
+ const uint8_t *tlv_curr)
+{
+ const char *label = "dr_priority";
+
+ if (check_tlv_length(__PRETTY_FUNCTION__, label,
+ ifname, src_addr,
+ sizeof(uint32_t), option_len)) {
+ return -1;
+ }
+
+ check_tlv_redefinition_uint32(__PRETTY_FUNCTION__, label,
+ ifname, src_addr,
+ *hello_options, PIM_OPTION_MASK_DR_PRIORITY,
+ PIM_TLV_GET_DR_PRIORITY(tlv_curr),
+ *hello_option_dr_priority);
+
+ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_DR_PRIORITY);
+
+ *hello_option_dr_priority = PIM_TLV_GET_DR_PRIORITY(tlv_curr);
+
+ return 0;
+}
+
+int pim_tlv_parse_generation_id(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint32_t *hello_option_generation_id,
+ uint16_t option_len,
+ const uint8_t *tlv_curr)
+{
+ const char *label = "generation_id";
+
+ if (check_tlv_length(__PRETTY_FUNCTION__, label,
+ ifname, src_addr,
+ sizeof(uint32_t), option_len)) {
+ return -1;
+ }
+
+ check_tlv_redefinition_uint32_hex(__PRETTY_FUNCTION__, label,
+ ifname, src_addr,
+ *hello_options, PIM_OPTION_MASK_GENERATION_ID,
+ PIM_TLV_GET_GENERATION_ID(tlv_curr),
+ *hello_option_generation_id);
+
+ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_GENERATION_ID);
+
+ *hello_option_generation_id = PIM_TLV_GET_GENERATION_ID(tlv_curr);
+
+ return 0;
+}
+
+int pim_parse_addr_ucast(const char *ifname, struct in_addr src_addr,
+ struct prefix *p,
+ const uint8_t *buf,
+ int buf_size)
+{
+ const int ucast_encoding_min_len = 3; /* 1 family + 1 type + 1 addr */
+ const uint8_t *addr;
+ const uint8_t *pastend;
+ int family;
+ int type;
+
+ if (buf_size < ucast_encoding_min_len) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unicast address encoding overflow: left=%d needed=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ buf_size, ucast_encoding_min_len,
+ src_str, ifname);
+ return -1;
+ }
+
+ addr = buf;
+ pastend = buf + buf_size;
+
+ family = *addr++;
+ type = *addr++;
+
+ switch (family) {
+ case PIM_MSG_ADDRESS_FAMILY_IPV4:
+ if (type) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unknown unicast address encoding type=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ type, src_str, ifname);
+ return -2;
+ }
+
+ if ((addr + sizeof(struct in_addr)) > pastend) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: IPv4 unicast address overflow: left=%zd needed=%zu from %s on %s",
+ __PRETTY_FUNCTION__,
+ pastend - addr, sizeof(struct in_addr),
+ src_str, ifname);
+ return -3;
+ }
+
+ p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+ memcpy(&p->u.prefix4, addr, sizeof(struct in_addr));
+
+ addr += sizeof(struct in_addr);
+
+ break;
+ default:
+ {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unknown unicast address encoding family=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ family, src_str, ifname);
+ return -4;
+ }
+ }
+
+ return addr - buf;
+}
+
+int pim_parse_addr_group(const char *ifname, struct in_addr src_addr,
+ struct prefix *p,
+ const uint8_t *buf,
+ int buf_size)
+{
+ const int grp_encoding_min_len = 4; /* 1 family + 1 type + 1 reserved + 1 addr */
+ const uint8_t *addr;
+ const uint8_t *pastend;
+ int family;
+ int type;
+ int mask_len;
+
+ if (buf_size < grp_encoding_min_len) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: group address encoding overflow: left=%d needed=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ buf_size, grp_encoding_min_len,
+ src_str, ifname);
+ return -1;
+ }
+
+ addr = buf;
+ pastend = buf + buf_size;
+
+ family = *addr++;
+ type = *addr++;
+ //++addr;
+ ++addr; /* skip b_reserved_z fields */
+ mask_len = *addr++;
+
+ switch (family) {
+ case PIM_MSG_ADDRESS_FAMILY_IPV4:
+ if (type) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unknown group address encoding type=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ type, src_str, ifname);
+ return -2;
+ }
+
+ if ((addr + sizeof(struct in_addr)) > pastend) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: IPv4 group address overflow: left=%zd needed=%zu from %s on %s",
+ __PRETTY_FUNCTION__,
+ pastend - addr, sizeof(struct in_addr),
+ src_str, ifname);
+ return -3;
+ }
+
+ p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+ memcpy(&p->u.prefix4, addr, sizeof(struct in_addr));
+ p->prefixlen = mask_len;
+
+ addr += sizeof(struct in_addr);
+
+ break;
+ default:
+ {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unknown group address encoding family=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ family, src_str, ifname);
+ return -4;
+ }
+ }
+
+ return addr - buf;
+}
+
+int pim_parse_addr_source(const char *ifname,
+ struct in_addr src_addr,
+ struct prefix *p,
+ uint8_t *flags,
+ const uint8_t *buf,
+ int buf_size)
+{
+ const int src_encoding_min_len = 4; /* 1 family + 1 type + 1 reserved + 1 addr */
+ const uint8_t *addr;
+ const uint8_t *pastend;
+ int family;
+ int type;
+ int mask_len;
+
+ if (buf_size < src_encoding_min_len) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: source address encoding overflow: left=%d needed=%d from %s on %s",
+ __PRETTY_FUNCTION__,
+ buf_size, src_encoding_min_len,
+ src_str, ifname);
+ return -1;
+ }
+
+ addr = buf;
+ pastend = buf + buf_size;
+
+ family = *addr++;
+ type = *addr++;
+ *flags = *addr++;
+ mask_len = *addr++;
+
+ switch (family) {
+ case PIM_MSG_ADDRESS_FAMILY_IPV4:
+ if (type) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unknown source address encoding type=%d from %s on %s: %02x%02x%02x%02x%02x%02x%02x%02x",
+ __PRETTY_FUNCTION__,
+ type, src_str, ifname,
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
+ return -2;
+ }
+
+ if ((addr + sizeof(struct in_addr)) > pastend) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: IPv4 source address overflow: left=%zd needed=%zu from %s on %s",
+ __PRETTY_FUNCTION__,
+ pastend - addr, sizeof(struct in_addr),
+ src_str, ifname);
+ return -3;
+ }
+
+ p->family = AF_INET; /* notice: AF_INET != PIM_MSG_ADDRESS_FAMILY_IPV4 */
+ memcpy(&p->u.prefix4, addr, sizeof(struct in_addr));
+ p->prefixlen = mask_len;
+
+ /*
+ RFC 4601: 4.9.1 Encoded Source and Group Address Formats
+
+ Encoded-Source Address
+
+ The mask length MUST be equal to the mask length in bits for
+ the given Address Family and Encoding Type (32 for IPv4 native
+ and 128 for IPv6 native). A router SHOULD ignore any messages
+ received with any other mask length.
+ */
+ if (p->prefixlen != 32) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", p->u.prefix4, src_str, sizeof(src_str));
+ zlog_warn("%s: IPv4 bad source address mask: %s/%d",
+ __PRETTY_FUNCTION__, src_str, p->prefixlen);
+ return -4;
+ }
+
+ addr += sizeof(struct in_addr);
+
+ break;
+ default:
+ {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: unknown source address encoding family=%d from %s on %s: %02x%02x%02x%02x%02x%02x%02x%02x",
+ __PRETTY_FUNCTION__,
+ family, src_str, ifname,
+ buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7]);
+ return -5;
+ }
+ }
+
+ return addr - buf;
+}
+
+#define FREE_ADDR_LIST(hello_option_addr_list) \
+{ \
+ if (hello_option_addr_list) { \
+ list_delete(hello_option_addr_list); \
+ hello_option_addr_list = 0; \
+ } \
+}
+
+int pim_tlv_parse_addr_list(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ struct list **hello_option_addr_list,
+ uint16_t option_len,
+ const uint8_t *tlv_curr)
+{
+ const uint8_t *addr;
+ const uint8_t *pastend;
+
+ zassert(hello_option_addr_list);
+
+ /*
+ Scan addr list
+ */
+ addr = tlv_curr;
+ pastend = tlv_curr + option_len;
+ while (addr < pastend) {
+ struct prefix tmp;
+ int addr_offset;
+
+ /*
+ Parse ucast addr
+ */
+ addr_offset = pim_parse_addr_ucast(ifname, src_addr, &tmp,
+ addr, pastend - addr);
+ if (addr_offset < 1) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifname);
+ FREE_ADDR_LIST(*hello_option_addr_list);
+ return -1;
+ }
+ addr += addr_offset;
+
+ /*
+ Debug
+ */
+ if (PIM_DEBUG_PIM_TRACE) {
+ switch (tmp.family) {
+ case AF_INET:
+ {
+ char addr_str[100];
+ char src_str[100];
+ pim_inet4_dump("<addr?>", tmp.u.prefix4, addr_str, sizeof(addr_str));
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello TLV option: list_old_size=%d IPv4 address %s from %s on %s",
+ __PRETTY_FUNCTION__,
+ *hello_option_addr_list ?
+ ((int) listcount(*hello_option_addr_list)) : -1,
+ addr_str, src_str, ifname);
+ }
+ break;
+ default:
+ {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_debug("%s: PIM hello TLV option: list_old_size=%d UNKNOWN address family from %s on %s",
+ __PRETTY_FUNCTION__,
+ *hello_option_addr_list ?
+ ((int) listcount(*hello_option_addr_list)) : -1,
+ src_str, ifname);
+ }
+ }
+ }
+
+ /*
+ Exclude neighbor's primary address if incorrectly included in
+ the secondary address list
+ */
+ if (tmp.family == AF_INET) {
+ if (tmp.u.prefix4.s_addr == src_addr.s_addr) {
+ char src_str[100];
+ pim_inet4_dump("<src?>", src_addr, src_str, sizeof(src_str));
+ zlog_warn("%s: ignoring primary address in secondary list from %s on %s",
+ __PRETTY_FUNCTION__,
+ src_str, ifname);
+ continue;
+ }
+ }
+
+ /*
+ Allocate list if needed
+ */
+ if (!*hello_option_addr_list) {
+ *hello_option_addr_list = list_new();
+ if (!*hello_option_addr_list) {
+ zlog_err("%s %s: failure: hello_option_addr_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return -2;
+ }
+ (*hello_option_addr_list)->del = (void (*)(void *)) prefix_free;
+ }
+
+ /*
+ Attach addr to list
+ */
+ {
+ struct prefix *p;
+ p = prefix_new();
+ if (!p) {
+ zlog_err("%s %s: failure: prefix_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ FREE_ADDR_LIST(*hello_option_addr_list);
+ return -3;
+ }
+ p->family = tmp.family;
+ p->u.prefix4 = tmp.u.prefix4;
+ listnode_add(*hello_option_addr_list, p);
+ }
+
+ } /* while (addr < pastend) */
+
+ /*
+ Mark hello option
+ */
+ PIM_OPTION_SET(*hello_options, PIM_OPTION_MASK_ADDRESS_LIST);
+
+ return 0;
+}
diff --git a/pimd/pim_tlv.h b/pimd/pim_tlv.h
new file mode 100644
index 00000000..b802cf89
--- /dev/null
+++ b/pimd/pim_tlv.h
@@ -0,0 +1,133 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_TLV_H
+#define PIM_TLV_H
+
+#include <zebra.h>
+
+#include "config.h"
+#include "if.h"
+#include "linklist.h"
+
+#ifdef HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif /* HAVE_INTTYPES_H */
+
+#define PIM_MSG_OPTION_TYPE_HOLDTIME (1)
+#define PIM_MSG_OPTION_TYPE_LAN_PRUNE_DELAY (2)
+#define PIM_MSG_OPTION_TYPE_DR_PRIORITY (19)
+#define PIM_MSG_OPTION_TYPE_GENERATION_ID (20)
+#define PIM_MSG_OPTION_TYPE_DM_STATE_REFRESH (21)
+#define PIM_MSG_OPTION_TYPE_ADDRESS_LIST (24)
+
+typedef uint32_t pim_hello_options;
+#define PIM_OPTION_MASK_HOLDTIME (1 << 0) /* recv holdtime */
+#define PIM_OPTION_MASK_LAN_PRUNE_DELAY (1 << 1) /* recv lan_prune_delay */
+#define PIM_OPTION_MASK_DR_PRIORITY (1 << 2) /* recv dr_priority */
+#define PIM_OPTION_MASK_GENERATION_ID (1 << 3) /* recv generation_id */
+#define PIM_OPTION_MASK_ADDRESS_LIST (1 << 4) /* recv secondary address list */
+#define PIM_OPTION_MASK_CAN_DISABLE_JOIN_SUPPRESSION (1 << 5) /* T bit value (valid if recv lan_prune_delay) */
+
+#define PIM_RPT_BIT_MASK (1 << 0)
+#define PIM_WILDCARD_BIT_MASK (1 << 1)
+
+#define PIM_OPTION_SET(options, option_mask) ((options) |= (option_mask))
+#define PIM_OPTION_UNSET(options, option_mask) ((options) &= ~(option_mask))
+#define PIM_OPTION_IS_SET(options, option_mask) ((options) & (option_mask))
+
+#define PIM_TLV_GET_UINT16(buf) ntohs(*(const uint16_t *)(buf))
+#define PIM_TLV_GET_UINT32(buf) ntohl(*(const uint32_t *)(buf))
+#define PIM_TLV_GET_TYPE(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_LENGTH(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_HOLDTIME(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_PROPAGATION_DELAY(buf) (PIM_TLV_GET_UINT16(buf) & 0x7FFF)
+#define PIM_TLV_GET_OVERRIDE_INTERVAL(buf) PIM_TLV_GET_UINT16(buf)
+#define PIM_TLV_GET_CAN_DISABLE_JOIN_SUPPRESSION(buf) ((*(const uint8_t *)(buf)) & 0x80)
+#define PIM_TLV_GET_DR_PRIORITY(buf) PIM_TLV_GET_UINT32(buf)
+#define PIM_TLV_GET_GENERATION_ID(buf) PIM_TLV_GET_UINT32(buf)
+
+#define PIM_TLV_TYPE_SIZE (2)
+#define PIM_TLV_LENGTH_SIZE (2)
+#define PIM_TLV_MIN_SIZE (PIM_TLV_TYPE_SIZE + PIM_TLV_LENGTH_SIZE)
+#define PIM_TLV_OPTION_SIZE(option_len) (PIM_TLV_MIN_SIZE + (option_len))
+
+uint8_t *pim_tlv_append_uint16(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ uint16_t option_type,
+ uint16_t option_value);
+uint8_t *pim_tlv_append_2uint16(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ uint16_t option_type,
+ uint16_t option_value1,
+ uint16_t option_value2);
+uint8_t *pim_tlv_append_uint32(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ uint16_t option_type,
+ uint32_t option_value);
+uint8_t *pim_tlv_append_addrlist_ucast(uint8_t *buf,
+ const uint8_t *buf_pastend,
+ struct list *ifconnected);
+
+int pim_tlv_parse_holdtime(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint16_t *hello_option_holdtime,
+ uint16_t option_len,
+ const uint8_t *tlv_curr);
+int pim_tlv_parse_lan_prune_delay(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint16_t *hello_option_propagation_delay,
+ uint16_t *hello_option_override_interval,
+ uint16_t option_len,
+ const uint8_t *tlv_curr);
+int pim_tlv_parse_dr_priority(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint32_t *hello_option_dr_priority,
+ uint16_t option_len,
+ const uint8_t *tlv_curr);
+int pim_tlv_parse_generation_id(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ uint32_t *hello_option_generation_id,
+ uint16_t option_len,
+ const uint8_t *tlv_curr);
+int pim_tlv_parse_addr_list(const char *ifname, struct in_addr src_addr,
+ pim_hello_options *hello_options,
+ struct list **hello_option_addr_list,
+ uint16_t option_len,
+ const uint8_t *tlv_curr);
+
+int pim_parse_addr_ucast(const char *ifname, struct in_addr src_addr,
+ struct prefix *p,
+ const uint8_t *buf,
+ int buf_size);
+int pim_parse_addr_group(const char *ifname, struct in_addr src_addr,
+ struct prefix *p,
+ const uint8_t *buf,
+ int buf_size);
+int pim_parse_addr_source(const char *ifname,
+ struct in_addr src_addr,
+ struct prefix *p,
+ uint8_t *flags,
+ const uint8_t *buf,
+ int buf_size);
+
+#endif /* PIM_TLV_H */
diff --git a/pimd/pim_upstream.c b/pimd/pim_upstream.c
new file mode 100644
index 00000000..d02f9154
--- /dev/null
+++ b/pimd/pim_upstream.c
@@ -0,0 +1,683 @@
+/*
+ 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 "zebra/rib.h"
+
+#include "log.h"
+#include "zclient.h"
+#include "memory.h"
+#include "thread.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_time.h"
+#include "pim_iface.h"
+#include "pim_join.h"
+#include "pim_zlookup.h"
+#include "pim_upstream.h"
+#include "pim_ifchannel.h"
+#include "pim_neighbor.h"
+#include "pim_rpf.h"
+#include "pim_zebra.h"
+#include "pim_oil.h"
+#include "pim_macro.h"
+
+static void join_timer_start(struct pim_upstream *up);
+static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up);
+
+void pim_upstream_free(struct pim_upstream *up)
+{
+ XFREE(MTYPE_PIM_UPSTREAM, up);
+}
+
+static void upstream_channel_oil_detach(struct pim_upstream *up)
+{
+ if (up->channel_oil) {
+ pim_channel_oil_del(up->channel_oil);
+ up->channel_oil = 0;
+ }
+}
+
+void pim_upstream_delete(struct pim_upstream *up)
+{
+ THREAD_OFF(up->t_join_timer);
+
+ upstream_channel_oil_detach(up);
+
+ /*
+ notice that listnode_delete() can't be moved
+ into pim_upstream_free() because the later is
+ called by list_delete_all_node()
+ */
+ listnode_delete(qpim_upstream_list, up);
+
+ pim_upstream_free(up);
+}
+
+static void send_join(struct pim_upstream *up)
+{
+ zassert(up->join_state == PIM_UPSTREAM_JOINED);
+
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ if (PIM_INADDR_IS_ANY(up->rpf.rpf_addr)) {
+ char src_str[100];
+ char grp_str[100];
+ char rpf_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_str, sizeof(rpf_str));
+ zlog_warn("%s: can't send join upstream: RPF'(%s,%s)=%s",
+ __PRETTY_FUNCTION__,
+ src_str, grp_str, rpf_str);
+ /* warning only */
+ }
+ }
+
+ /* send Join(S,G) to the current upstream neighbor */
+ pim_joinprune_send(up->rpf.source_nexthop.interface,
+ up->rpf.rpf_addr,
+ up->source_addr,
+ up->group_addr,
+ 1 /* join */);
+}
+
+static int on_join_timer(struct thread *t)
+{
+ struct pim_upstream *up;
+
+ zassert(t);
+ up = THREAD_ARG(t);
+ zassert(up);
+
+ send_join(up);
+
+ up->t_join_timer = 0;
+ join_timer_start(up);
+
+ return 0;
+}
+
+static void join_timer_start(struct pim_upstream *up)
+{
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: starting %d sec timer for upstream (S,G)=(%s,%s)",
+ __PRETTY_FUNCTION__,
+ qpim_t_periodic,
+ src_str, grp_str);
+ }
+
+ zassert(!up->t_join_timer);
+
+ THREAD_TIMER_ON(master, up->t_join_timer,
+ on_join_timer,
+ up, qpim_t_periodic);
+}
+
+void pim_upstream_join_timer_restart(struct pim_upstream *up)
+{
+ THREAD_OFF(up->t_join_timer);
+ join_timer_start(up);
+}
+
+static void pim_upstream_join_timer_restart_msec(struct pim_upstream *up,
+ int interval_msec)
+{
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: restarting %d msec timer for upstream (S,G)=(%s,%s)",
+ __PRETTY_FUNCTION__,
+ interval_msec,
+ src_str, grp_str);
+ }
+
+ THREAD_OFF(up->t_join_timer);
+ THREAD_TIMER_MSEC_ON(master, up->t_join_timer,
+ on_join_timer,
+ up, interval_msec);
+}
+
+void pim_upstream_join_suppress(struct pim_upstream *up,
+ struct in_addr rpf_addr,
+ int holdtime)
+{
+ long t_joinsuppress_msec;
+ long join_timer_remain_msec;
+
+ t_joinsuppress_msec = MIN(pim_if_t_suppressed_msec(up->rpf.source_nexthop.interface),
+ 1000 * holdtime);
+
+ join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ char grp_str[100];
+ char rpf_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
+ zlog_debug("%s %s: detected Join(%s,%s) to RPF'(S,G)=%s: join_timer=%ld msec t_joinsuppress=%ld msec",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str,
+ rpf_str,
+ join_timer_remain_msec, t_joinsuppress_msec);
+ }
+
+ if (join_timer_remain_msec < t_joinsuppress_msec) {
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s %s: suppressing Join(S,G)=(%s,%s) for %ld msec",
+ __FILE__, __PRETTY_FUNCTION__,
+ src_str, grp_str, t_joinsuppress_msec);
+ }
+
+ pim_upstream_join_timer_restart_msec(up, t_joinsuppress_msec);
+ }
+}
+
+void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label,
+ struct pim_upstream *up,
+ struct in_addr rpf_addr)
+{
+ long join_timer_remain_msec;
+ int t_override_msec;
+
+ join_timer_remain_msec = pim_time_timer_remain_msec(up->t_join_timer);
+ t_override_msec = pim_if_t_override_msec(up->rpf.source_nexthop.interface);
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ char grp_str[100];
+ char rpf_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<rpf?>", rpf_addr, rpf_str, sizeof(rpf_str));
+ zlog_debug("%s: to RPF'(%s,%s)=%s: join_timer=%ld msec t_override=%d msec",
+ debug_label,
+ src_str, grp_str, rpf_str,
+ join_timer_remain_msec, t_override_msec);
+ }
+
+ if (join_timer_remain_msec > t_override_msec) {
+ if (PIM_DEBUG_PIM_TRACE) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: decreasing (S,G)=(%s,%s) join timer to t_override=%d msec",
+ debug_label,
+ src_str, grp_str,
+ t_override_msec);
+ }
+
+ pim_upstream_join_timer_restart_msec(up, t_override_msec);
+ }
+}
+
+static void forward_on(struct pim_upstream *up)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+ if (ch->upstream != up)
+ continue;
+
+ if (pim_macro_chisin_oiflist(ch))
+ pim_forward_start(ch);
+
+ } /* scan iface channel list */
+ } /* scan iflist */
+}
+
+static void forward_off(struct pim_upstream *up)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+ if (ch->upstream != up)
+ continue;
+
+ pim_forward_stop(ch);
+
+ } /* scan iface channel list */
+ } /* scan iflist */
+}
+
+static void pim_upstream_switch(struct pim_upstream *up,
+ enum pim_upstream_state new_state)
+{
+ enum pim_upstream_state old_state = up->join_state;
+
+ zassert(old_state != new_state);
+
+ up->join_state = new_state;
+ up->state_transition = pim_time_monotonic_sec();
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ char src_str[100];
+ char grp_str[100];
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ zlog_debug("%s: PIM_UPSTREAM_%s: (S,G)=(%s,%s)",
+ __PRETTY_FUNCTION__,
+ ((new_state == PIM_UPSTREAM_JOINED) ? "JOINED" : "NOTJOINED"),
+ src_str, grp_str);
+ }
+
+ pim_upstream_update_assert_tracking_desired(up);
+
+ if (new_state == PIM_UPSTREAM_JOINED) {
+ forward_on(up);
+ send_join(up);
+ join_timer_start(up);
+ }
+ else {
+ forward_off(up);
+ pim_joinprune_send(up->rpf.source_nexthop.interface,
+ up->rpf.rpf_addr,
+ up->source_addr,
+ up->group_addr,
+ 0 /* prune */);
+ zassert(up->t_join_timer);
+ THREAD_OFF(up->t_join_timer);
+ }
+
+}
+
+static struct pim_upstream *pim_upstream_new(struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_upstream *up;
+
+ up = XMALLOC(MTYPE_PIM_UPSTREAM, sizeof(*up));
+ if (!up) {
+ zlog_err("%s: PIM XMALLOC(%zu) failure",
+ __PRETTY_FUNCTION__, sizeof(*up));
+ return 0;
+ }
+
+ up->source_addr = source_addr;
+ up->group_addr = group_addr;
+ up->flags = 0;
+ up->ref_count = 1;
+ up->t_join_timer = 0;
+ up->join_state = 0;
+ up->state_transition = pim_time_monotonic_sec();
+ up->channel_oil = 0;
+
+ up->rpf.source_nexthop.interface = 0;
+ up->rpf.source_nexthop.mrib_nexthop_addr.s_addr = PIM_NET_INADDR_ANY;
+ up->rpf.source_nexthop.mrib_metric_preference = qpim_infinite_assert_metric.metric_preference;
+ up->rpf.source_nexthop.mrib_route_metric = qpim_infinite_assert_metric.route_metric;
+ up->rpf.rpf_addr.s_addr = PIM_NET_INADDR_ANY;
+
+ pim_rpf_update(up, 0);
+
+ listnode_add(qpim_upstream_list, up);
+
+ return up;
+}
+
+struct pim_upstream *pim_upstream_find(struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct listnode *up_node;
+ struct pim_upstream *up;
+
+ for (ALL_LIST_ELEMENTS_RO(qpim_upstream_list, up_node, up)) {
+ if (
+ (source_addr.s_addr == up->source_addr.s_addr) &&
+ (group_addr.s_addr == up->group_addr.s_addr)
+ ) {
+ return up;
+ }
+ }
+
+ return 0;
+}
+
+struct pim_upstream *pim_upstream_add(struct in_addr source_addr,
+ struct in_addr group_addr)
+{
+ struct pim_upstream *up;
+
+ up = pim_upstream_find(source_addr, group_addr);
+ if (up) {
+ ++up->ref_count;
+ }
+ else {
+ up = pim_upstream_new(source_addr, group_addr);
+ }
+
+ return up;
+}
+
+void pim_upstream_del(struct pim_upstream *up)
+{
+ --up->ref_count;
+
+ if (up->ref_count < 1) {
+ pim_upstream_delete(up);
+ }
+}
+
+/*
+ Evaluate JoinDesired(S,G):
+
+ JoinDesired(S,G) is true if there is a downstream (S,G) interface I
+ in the set:
+
+ inherited_olist(S,G) =
+ joins(S,G) (+) pim_include(S,G) (-) lost_assert(S,G)
+
+ JoinDesired(S,G) may be affected by changes in the following:
+
+ pim_ifp->primary_address
+ pim_ifp->pim_dr_addr
+ ch->ifassert_winner_metric
+ ch->ifassert_winner
+ ch->local_ifmembership
+ ch->ifjoin_state
+ ch->upstream->rpf.source_nexthop.mrib_metric_preference
+ ch->upstream->rpf.source_nexthop.mrib_route_metric
+ ch->upstream->rpf.source_nexthop.interface
+
+ See also pim_upstream_update_join_desired() below.
+ */
+int pim_upstream_evaluate_join_desired(struct pim_upstream *up)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+ if (ch->upstream != up)
+ continue;
+
+ if (pim_macro_ch_lost_assert(ch))
+ continue; /* keep searching */
+
+ if (pim_macro_chisin_joins_or_include(ch))
+ return 1; /* true */
+ } /* scan iface channel list */
+ } /* scan iflist */
+
+ return 0; /* false */
+}
+
+/*
+ See also pim_upstream_evaluate_join_desired() above.
+*/
+void pim_upstream_update_join_desired(struct pim_upstream *up)
+{
+ int was_join_desired; /* boolean */
+ int is_join_desired; /* boolean */
+
+ was_join_desired = PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(up->flags);
+
+ is_join_desired = pim_upstream_evaluate_join_desired(up);
+ if (is_join_desired)
+ PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(up->flags);
+ else
+ PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(up->flags);
+
+ /* switched from false to true */
+ if (is_join_desired && !was_join_desired) {
+ zassert(up->join_state == PIM_UPSTREAM_NOTJOINED);
+ pim_upstream_switch(up, PIM_UPSTREAM_JOINED);
+ return;
+ }
+
+ /* switched from true to false */
+ if (!is_join_desired && was_join_desired) {
+ zassert(up->join_state == PIM_UPSTREAM_JOINED);
+ pim_upstream_switch(up, PIM_UPSTREAM_NOTJOINED);
+ return;
+ }
+}
+
+/*
+ RFC 4601 4.5.7. Sending (S,G) Join/Prune Messages
+ Transitions from Joined State
+ RPF'(S,G) GenID changes
+
+ The upstream (S,G) state machine remains in Joined state. If the
+ Join Timer is set to expire in more than t_override seconds, reset
+ it so that it expires after t_override seconds.
+*/
+void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr)
+{
+ struct listnode *up_node;
+ struct listnode *up_nextnode;
+ struct pim_upstream *up;
+
+ /*
+ Scan all (S,G) upstreams searching for RPF'(S,G)=neigh_addr
+ */
+ for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) {
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char neigh_str[100];
+ char src_str[100];
+ char grp_str[100];
+ char rpf_addr_str[100];
+ pim_inet4_dump("<neigh?>", neigh_addr, neigh_str, sizeof(neigh_str));
+ pim_inet4_dump("<src?>", up->source_addr, src_str, sizeof(src_str));
+ pim_inet4_dump("<grp?>", up->group_addr, grp_str, sizeof(grp_str));
+ pim_inet4_dump("<rpf?>", up->rpf.rpf_addr, rpf_addr_str, sizeof(rpf_addr_str));
+ zlog_debug("%s: matching neigh=%s against upstream (S,G)=(%s,%s) joined=%d rpf_addr=%s",
+ __PRETTY_FUNCTION__,
+ neigh_str, src_str, grp_str,
+ up->join_state == PIM_UPSTREAM_JOINED,
+ rpf_addr_str);
+ }
+
+ /* consider only (S,G) upstream in Joined state */
+ if (up->join_state != PIM_UPSTREAM_JOINED)
+ continue;
+
+ /* match RPF'(S,G)=neigh_addr */
+ if (up->rpf.rpf_addr.s_addr != neigh_addr.s_addr)
+ continue;
+
+ pim_upstream_join_timer_decrease_to_t_override("RPF'(S,G) GenID change",
+ up, neigh_addr);
+ }
+}
+
+
+void pim_upstream_rpf_interface_changed(struct pim_upstream *up,
+ struct interface *old_rpf_ifp)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct interface *ifp;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct pim_ifchannel *ch;
+ struct pim_interface *pim_ifp;
+
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* search all ifchannels */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+ if (ch->upstream != up)
+ continue;
+
+ if (ch->ifassert_state == PIM_IFASSERT_I_AM_LOSER) {
+ if (
+ /* RPF_interface(S) was NOT I */
+ (old_rpf_ifp == ch->interface)
+ &&
+ /* RPF_interface(S) stopped being I */
+ (ch->upstream->rpf.source_nexthop.interface != ch->interface)
+ ) {
+ assert_action_a5(ch);
+ }
+ } /* PIM_IFASSERT_I_AM_LOSER */
+
+ pim_ifchannel_update_assert_tracking_desired(ch);
+ }
+ }
+}
+
+void pim_upstream_update_could_assert(struct pim_upstream *up)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+ if (ch->upstream != up)
+ continue;
+
+ pim_ifchannel_update_could_assert(ch);
+
+ } /* scan iface channel list */
+ } /* scan iflist */
+}
+
+void pim_upstream_update_my_assert_metric(struct pim_upstream *up)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+ if (ch->upstream != up)
+ continue;
+
+ pim_ifchannel_update_my_assert_metric(ch);
+
+ } /* scan iface channel list */
+ } /* scan iflist */
+}
+
+static void pim_upstream_update_assert_tracking_desired(struct pim_upstream *up)
+{
+ struct listnode *ifnode;
+ struct listnode *ifnextnode;
+ struct listnode *chnode;
+ struct listnode *chnextnode;
+ struct interface *ifp;
+ struct pim_interface *pim_ifp;
+ struct pim_ifchannel *ch;
+
+ /* scan all interfaces */
+ for (ALL_LIST_ELEMENTS(iflist, ifnode, ifnextnode, ifp)) {
+ pim_ifp = ifp->info;
+ if (!pim_ifp)
+ continue;
+
+ /* scan per-interface (S,G) state */
+ for (ALL_LIST_ELEMENTS(pim_ifp->pim_ifchannel_list, chnode, chnextnode, ch)) {
+
+ if (ch->upstream != up)
+ continue;
+
+ pim_ifchannel_update_assert_tracking_desired(ch);
+
+ } /* scan iface channel list */
+ } /* scan iflist */
+}
diff --git a/pimd/pim_upstream.h b/pimd/pim_upstream.h
new file mode 100644
index 00000000..5b5182dd
--- /dev/null
+++ b/pimd/pim_upstream.h
@@ -0,0 +1,122 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_UPSTREAM_H
+#define PIM_UPSTREAM_H
+
+#include <zebra.h>
+
+#define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED (1 << 0)
+#define PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED (2 << 0)
+
+#define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)
+#define PIM_UPSTREAM_FLAG_TEST_DR_JOIN_DESIRED_UPDATED(flags) ((flags) & PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)
+
+#define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)
+#define PIM_UPSTREAM_FLAG_SET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) |= PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)
+
+#define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED)
+#define PIM_UPSTREAM_FLAG_UNSET_DR_JOIN_DESIRED_UPDATED(flags) ((flags) &= ~PIM_UPSTREAM_FLAG_MASK_DR_JOIN_DESIRED_UPDATED)
+
+/*
+ RFC 4601:
+
+ Metric Preference
+ Preference value assigned to the unicast routing protocol that
+ provided the route to the multicast source or Rendezvous-Point.
+
+ Metric
+ The unicast routing table metric associated with the route used to
+ reach the multicast source or Rendezvous-Point. The metric is in
+ units applicable to the unicast routing protocol used.
+*/
+struct pim_nexthop {
+ struct interface *interface; /* RPF_interface(S) */
+ struct in_addr mrib_nexthop_addr; /* MRIB.next_hop(S) */
+ uint32_t mrib_metric_preference; /* MRIB.pref(S) */
+ uint32_t mrib_route_metric; /* MRIB.metric(S) */
+};
+
+struct pim_rpf {
+ struct pim_nexthop source_nexthop;
+ struct in_addr rpf_addr; /* RPF'(S,G) */
+};
+
+enum pim_rpf_result {
+ PIM_RPF_OK = 0,
+ PIM_RPF_CHANGED,
+ PIM_RPF_FAILURE
+};
+
+enum pim_upstream_state {
+ PIM_UPSTREAM_NOTJOINED,
+ PIM_UPSTREAM_JOINED
+};
+
+/*
+ Upstream (S,G) channel in Joined state
+
+ (S,G) in the "Not Joined" state is not represented
+
+ See RFC 4601: 4.5.7. Sending (S,G) Join/Prune Message
+*/
+struct pim_upstream {
+ struct in_addr source_addr; /* (S,G) source key */
+ struct in_addr group_addr; /* (S,G) group key */
+ uint32_t flags;
+ struct channel_oil *channel_oil;
+
+ enum pim_upstream_state join_state;
+ int ref_count;
+
+ struct pim_rpf rpf;
+
+ struct thread *t_join_timer;
+ int64_t state_transition; /* Record current state uptime */
+};
+
+void pim_upstream_free(struct pim_upstream *up);
+void pim_upstream_delete(struct pim_upstream *up);
+struct pim_upstream *pim_upstream_find(struct in_addr source_addr,
+ struct in_addr group_addr);
+struct pim_upstream *pim_upstream_add(struct in_addr source_addr,
+ struct in_addr group_addr);
+void pim_upstream_del(struct pim_upstream *up);
+
+int pim_upstream_evaluate_join_desired(struct pim_upstream *up);
+void pim_upstream_update_join_desired(struct pim_upstream *up);
+
+void pim_upstream_join_suppress(struct pim_upstream *up,
+ struct in_addr rpf_addr,
+ int holdtime);
+void pim_upstream_join_timer_decrease_to_t_override(const char *debug_label,
+ struct pim_upstream *up,
+ struct in_addr rpf_addr);
+void pim_upstream_join_timer_restart(struct pim_upstream *up);
+void pim_upstream_rpf_genid_changed(struct in_addr neigh_addr);
+void pim_upstream_rpf_interface_changed(struct pim_upstream *up,
+ struct interface *old_rpf_ifp);
+
+void pim_upstream_update_could_assert(struct pim_upstream *up);
+void pim_upstream_update_my_assert_metric(struct pim_upstream *up);
+
+#endif /* PIM_UPSTREAM_H */
diff --git a/pimd/pim_util.c b/pimd/pim_util.c
new file mode 100644
index 00000000..fdfed2bf
--- /dev/null
+++ b/pimd/pim_util.c
@@ -0,0 +1,122 @@
+/*
+ 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 "pim_util.h"
+
+/*
+ RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
+
+ If QQIC < 128, QQI = QQIC
+ If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3)
+
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+
+ |1| exp | mant |
+ +-+-+-+-+-+-+-+-+
+
+ Since exp=0..7 then (exp+3)=3..10, then QQI has
+ one of the following bit patterns:
+
+ exp=0: QQI = 0000.0000.1MMM.M000
+ exp=1: QQI = 0000.0001.MMMM.0000
+ ...
+ exp=6: QQI = 001M.MMM0.0000.0000
+ exp=7: QQI = 01MM.MM00.0000.0000
+ --------- ---------
+ 0x4 0x0 0x0 0x0
+*/
+uint8_t igmp_msg_encode16to8(uint16_t value)
+{
+ uint8_t code;
+
+ if (value < 128) {
+ code = value;
+ }
+ else {
+ uint16_t mask = 0x4000;
+ uint8_t exp;
+ uint16_t mant;
+ for (exp = 7; exp > 0; --exp) {
+ if (mask & value)
+ break;
+ mask >>= 1;
+ }
+ mant = 0x000F & (value >> (exp + 3));
+ code = ((uint8_t) 1 << 7) | ((uint8_t) exp << 4) | (uint8_t) mant;
+ }
+
+ return code;
+}
+
+/*
+ RFC 3376: 4.1.7. QQIC (Querier's Query Interval Code)
+
+ If QQIC < 128, QQI = QQIC
+ If QQIC >= 128, QQI = (mant | 0x10) << (exp + 3)
+
+ 0 1 2 3 4 5 6 7
+ +-+-+-+-+-+-+-+-+
+ |1| exp | mant |
+ +-+-+-+-+-+-+-+-+
+*/
+uint16_t igmp_msg_decode8to16(uint8_t code)
+{
+ uint16_t value;
+
+ if (code < 128) {
+ value = code;
+ }
+ else {
+ uint16_t mant = (code & 0x0F);
+ uint8_t exp = (code & 0x70) >> 4;
+ value = (mant | 0x10) << (exp + 3);
+ }
+
+ return value;
+}
+
+void pim_pkt_dump(const char *label, const uint8_t *buf, int size)
+{
+ char dump_buf[1000];
+ int i = 0;
+ int j = 0;
+
+ for (; i < size; ++i, j += 2) {
+ int left = sizeof(dump_buf) - j;
+ if (left < 4) {
+ if (left > 1) {
+ strcat(dump_buf + j, "!"); /* mark as truncated */
+ }
+ break;
+ }
+ snprintf(dump_buf + j, left, "%02x", buf[i]);
+ }
+
+ zlog_debug("%s: pkt dump size=%d: %s",
+ label,
+ size,
+ dump_buf);
+}
diff --git a/pimd/pim_util.h b/pimd/pim_util.h
new file mode 100644
index 00000000..a8613e2b
--- /dev/null
+++ b/pimd/pim_util.h
@@ -0,0 +1,37 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_UTIL_H
+#define PIM_UTIL_H
+
+#include <stdint.h>
+
+#include <zebra.h>
+
+#include "checksum.h"
+
+uint8_t igmp_msg_encode16to8(uint16_t value);
+uint16_t igmp_msg_decode8to16(uint8_t code);
+
+void pim_pkt_dump(const char *label, const uint8_t *buf, int size);
+
+#endif /* PIM_UTIL_H */
diff --git a/pimd/pim_version.c b/pimd/pim_version.c
new file mode 100644
index 00000000..fe7e5634
--- /dev/null
+++ b/pimd/pim_version.c
@@ -0,0 +1,25 @@
+/*
+ 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 "pim_version.h"
+
+const char * const PIMD_VERSION = PIMD_VERSION_STR;
diff --git a/pimd/pim_version.h b/pimd/pim_version.h
new file mode 100644
index 00000000..ef9f370c
--- /dev/null
+++ b/pimd/pim_version.h
@@ -0,0 +1,30 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_VERSION_H
+#define PIM_VERSION_H
+
+#define PIMD_VERSION_STR "0.166"
+
+const char * const PIMD_VERSION;
+
+#endif /* PIM_VERSION_H */
diff --git a/pimd/pim_vty.c b/pimd/pim_vty.c
new file mode 100644
index 00000000..bcace95c
--- /dev/null
+++ b/pimd/pim_vty.c
@@ -0,0 +1,178 @@
+/*
+ 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 "if.h"
+#include "linklist.h"
+
+#include "pimd.h"
+#include "pim_vty.h"
+#include "pim_iface.h"
+#include "pim_cmd.h"
+#include "pim_str.h"
+#include "pim_ssmpingd.h"
+
+int pim_debug_config_write(struct vty *vty)
+{
+ int writes = 0;
+
+ if (PIM_DEBUG_IGMP_EVENTS) {
+ vty_out(vty, "debug igmp events%s", VTY_NEWLINE);
+ ++writes;
+ }
+ if (PIM_DEBUG_IGMP_PACKETS) {
+ vty_out(vty, "debug igmp packets%s", VTY_NEWLINE);
+ ++writes;
+ }
+ if (PIM_DEBUG_IGMP_TRACE) {
+ vty_out(vty, "debug igmp trace%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ if (PIM_DEBUG_MROUTE) {
+ vty_out(vty, "debug mroute%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ if (PIM_DEBUG_PIM_EVENTS) {
+ vty_out(vty, "debug pim events%s", VTY_NEWLINE);
+ ++writes;
+ }
+ if (PIM_DEBUG_PIM_PACKETS) {
+ vty_out(vty, "debug pim packets%s", VTY_NEWLINE);
+ ++writes;
+ }
+ if (PIM_DEBUG_PIM_PACKETDUMP_SEND) {
+ vty_out(vty, "debug pim packet-dump send%s", VTY_NEWLINE);
+ ++writes;
+ }
+ if (PIM_DEBUG_PIM_PACKETDUMP_RECV) {
+ vty_out(vty, "debug pim packet-dump receive%s", VTY_NEWLINE);
+ ++writes;
+ }
+ if (PIM_DEBUG_PIM_TRACE) {
+ vty_out(vty, "debug pim trace%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ if (PIM_DEBUG_ZEBRA) {
+ vty_out(vty, "debug pim zebra%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ if (PIM_DEBUG_SSMPINGD) {
+ vty_out(vty, "debug ssmpingd%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ return writes;
+}
+
+int pim_global_config_write(struct vty *vty)
+{
+ int writes = 0;
+
+ if (PIM_MROUTE_IS_ENABLED) {
+ vty_out(vty, "%s%s", PIM_CMD_IP_MULTICAST_ROUTING, VTY_NEWLINE);
+ ++writes;
+ }
+
+ if (qpim_ssmpingd_list) {
+ struct listnode *node;
+ struct ssmpingd_sock *ss;
+ vty_out(vty, "!%s", VTY_NEWLINE);
+ ++writes;
+ for (ALL_LIST_ELEMENTS_RO(qpim_ssmpingd_list, node, ss)) {
+ char source_str[100];
+ pim_inet4_dump("<src?>", ss->source_addr, source_str, sizeof(source_str));
+ vty_out(vty, "ip ssmpingd %s%s", source_str, VTY_NEWLINE);
+ ++writes;
+ }
+ }
+
+ return writes;
+}
+
+int pim_interface_config_write(struct vty *vty)
+{
+ int writes = 0;
+ struct listnode *node;
+ struct interface *ifp;
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, node, ifp)) {
+
+ /* IF name */
+ vty_out(vty, "interface %s%s", ifp->name, VTY_NEWLINE);
+ ++writes;
+
+ if (ifp->info) {
+ struct pim_interface *pim_ifp = ifp->info;
+
+ /* IF ip pim ssm */
+ if (PIM_IF_TEST_PIM(pim_ifp->options)) {
+ vty_out(vty, " ip pim ssm%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ /* IF ip igmp */
+ if (PIM_IF_TEST_IGMP(pim_ifp->options)) {
+ vty_out(vty, " ip igmp%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ /* IF ip igmp query-interval */
+ vty_out(vty, " %s %d%s",
+ PIM_CMD_IP_IGMP_QUERY_INTERVAL,
+ pim_ifp->igmp_default_query_interval,
+ VTY_NEWLINE);
+ ++writes;
+
+ /* IF ip igmp query-max-response-time */
+ vty_out(vty, " %s %d%s",
+ PIM_CMD_IP_IGMP_QUERY_MAX_RESPONSE_TIME_DSEC,
+ pim_ifp->igmp_query_max_response_time_dsec,
+ VTY_NEWLINE);
+ ++writes;
+
+ /* IF ip igmp join */
+ if (pim_ifp->igmp_join_list) {
+ struct listnode *node;
+ struct igmp_join *ij;
+ for (ALL_LIST_ELEMENTS_RO(pim_ifp->igmp_join_list, node, ij)) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<grp?>", ij->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<src?>", ij->source_addr, source_str, sizeof(source_str));
+ vty_out(vty, " ip igmp join %s %s%s",
+ group_str, source_str,
+ VTY_NEWLINE);
+ ++writes;
+ }
+ }
+ }
+ vty_out(vty, "!%s", VTY_NEWLINE);
+ ++writes;
+ }
+
+ return writes;
+}
diff --git a/pimd/pim_vty.h b/pimd/pim_vty.h
new file mode 100644
index 00000000..904ee553
--- /dev/null
+++ b/pimd/pim_vty.h
@@ -0,0 +1,32 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_VTY_H
+#define PIM_VTY_H
+
+#include "vty.h"
+
+int pim_debug_config_write(struct vty *vty);
+int pim_global_config_write(struct vty *vty);
+int pim_interface_config_write(struct vty *vty);
+
+#endif /* PIM_VTY_H */
diff --git a/pimd/pim_zebra.c b/pimd/pim_zebra.c
new file mode 100644
index 00000000..6f241d59
--- /dev/null
+++ b/pimd/pim_zebra.c
@@ -0,0 +1,1310 @@
+/*
+ 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 "zebra/rib.h"
+
+#include "if.h"
+#include "log.h"
+#include "prefix.h"
+#include "zclient.h"
+#include "stream.h"
+#include "network.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_zebra.h"
+#include "pim_iface.h"
+#include "pim_str.h"
+#include "pim_oil.h"
+#include "pim_rpf.h"
+#include "pim_time.h"
+#include "pim_join.h"
+#include "pim_zlookup.h"
+#include "pim_ifchannel.h"
+
+#undef PIM_DEBUG_IFADDR_DUMP
+#define PIM_DEBUG_IFADDR_DUMP
+
+static int fib_lookup_if_vif_index(struct in_addr addr);
+static int del_oif(struct channel_oil *channel_oil,
+ struct interface *oif,
+ uint32_t proto_mask);
+
+static void zclient_broken(struct zclient *zclient)
+{
+ struct listnode *ifnode;
+ struct interface *ifp;
+
+ zlog_warn("%s %s: broken zclient connection",
+ __FILE__, __PRETTY_FUNCTION__);
+
+ for (ALL_LIST_ELEMENTS_RO(iflist, ifnode, ifp)) {
+ pim_if_addr_del_all(ifp);
+ }
+
+ /* upon return, zclient will discard connected addresses */
+}
+
+/* Router-id update message from zebra. */
+static int pim_router_id_update_zebra(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct prefix router_id;
+
+ zebra_router_id_update_read(zclient->ibuf, &router_id);
+
+ return 0;
+}
+
+static int pim_zebra_if_add(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct interface *ifp;
+
+ /*
+ zebra api adds/dels interfaces using the same call
+ interface_add_read below, see comments in lib/zclient.c
+ */
+ ifp = zebra_interface_add_read(zclient->ibuf);
+ if (!ifp)
+ return 0;
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+ ifp->mtu, if_is_operative(ifp));
+ }
+
+ if (if_is_operative(ifp))
+ pim_if_addr_add_all(ifp);
+
+ return 0;
+}
+
+static int pim_zebra_if_del(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct interface *ifp;
+
+ /*
+ zebra api adds/dels interfaces using the same call
+ interface_add_read below, see comments in lib/zclient.c
+
+ comments in lib/zclient.c seem to indicate that calling
+ zebra_interface_add_read is the correct call, but that
+ results in an attemted out of bounds read which causes
+ pimd to assert. Other clients use zebra_interface_state_read
+ and it appears to work just fine.
+ */
+ ifp = zebra_interface_state_read(zclient->ibuf);
+ if (!ifp)
+ return 0;
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+ ifp->mtu, if_is_operative(ifp));
+ }
+
+ if (!if_is_operative(ifp))
+ pim_if_addr_del_all(ifp);
+
+ return 0;
+}
+
+static int pim_zebra_if_state_up(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct interface *ifp;
+
+ /*
+ zebra api notifies interface up/down events by using the same call
+ zebra_interface_state_read below, see comments in lib/zclient.c
+ */
+ ifp = zebra_interface_state_read(zclient->ibuf);
+ if (!ifp)
+ return 0;
+
+ zlog_info("INTERFACE UP: %s ifindex=%d", ifp->name, ifp->ifindex);
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+ ifp->mtu, if_is_operative(ifp));
+ }
+
+ if (if_is_operative(ifp)) {
+ /*
+ pim_if_addr_add_all() suffices for bringing up both IGMP and PIM
+ */
+ pim_if_addr_add_all(ifp);
+ }
+
+ return 0;
+}
+
+static int pim_zebra_if_state_down(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct interface *ifp;
+
+ /*
+ zebra api notifies interface up/down events by using the same call
+ zebra_interface_state_read below, see comments in lib/zclient.c
+ */
+ ifp = zebra_interface_state_read(zclient->ibuf);
+ if (!ifp)
+ return 0;
+
+ zlog_info("INTERFACE DOWN: %s ifindex=%d", ifp->name, ifp->ifindex);
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s: %s index %d flags %ld metric %d mtu %d operative %d",
+ __PRETTY_FUNCTION__,
+ ifp->name, ifp->ifindex, (long)ifp->flags, ifp->metric,
+ ifp->mtu, if_is_operative(ifp));
+ }
+
+ if (!if_is_operative(ifp)) {
+ /*
+ pim_if_addr_del_all() suffices for shutting down IGMP,
+ but not for shutting down PIM
+ */
+ pim_if_addr_del_all(ifp);
+
+ /*
+ pim_sock_delete() closes the socket, stops read and timer threads,
+ and kills all neighbors.
+ */
+ if (ifp->info) {
+ pim_sock_delete(ifp, "link down");
+ }
+ }
+
+ return 0;
+}
+
+#ifdef PIM_DEBUG_IFADDR_DUMP
+static void dump_if_address(struct interface *ifp)
+{
+ struct connected *ifc;
+ struct listnode *node;
+
+ zlog_debug("%s %s: interface %s addresses:",
+ __FILE__, __PRETTY_FUNCTION__,
+ ifp->name);
+
+ for (ALL_LIST_ELEMENTS_RO(ifp->connected, node, ifc)) {
+ struct prefix *p = ifc->address;
+
+ if (p->family != AF_INET)
+ continue;
+
+ zlog_debug("%s %s: interface %s address %s %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ ifp->name,
+ inet_ntoa(p->u.prefix4),
+ CHECK_FLAG(ifc->flags, ZEBRA_IFA_SECONDARY) ?
+ "secondary" : "primary");
+ }
+}
+#endif
+
+static int pim_zebra_if_address_add(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct connected *c;
+ struct prefix *p;
+
+ zassert(command == ZEBRA_INTERFACE_ADDRESS_ADD);
+
+ /*
+ zebra api notifies address adds/dels events by using the same call
+ interface_add_read below, see comments in lib/zclient.c
+
+ zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_ADD, ...)
+ will add address to interface list by calling
+ connected_add_by_prefix()
+ */
+ c = zebra_interface_address_read(command, zclient->ibuf);
+ if (!c)
+ return 0;
+
+ p = c->address;
+ if (p->family != AF_INET)
+ return 0;
+
+ if (PIM_DEBUG_ZEBRA) {
+ char buf[BUFSIZ];
+ prefix2str(p, buf, BUFSIZ);
+ zlog_debug("%s: %s connected IP address %s flags %u %s",
+ __PRETTY_FUNCTION__,
+ c->ifp->name, buf, c->flags,
+ CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary");
+
+#ifdef PIM_DEBUG_IFADDR_DUMP
+ dump_if_address(c->ifp);
+#endif
+ }
+
+ if (!CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY)) {
+ /* trying to add primary address */
+
+ struct in_addr primary_addr = pim_find_primary_addr(c->ifp);
+ if (primary_addr.s_addr != p->u.prefix4.s_addr) {
+ /* but we had a primary address already */
+
+ char buf[BUFSIZ];
+ char old[100];
+
+ prefix2str(p, buf, BUFSIZ);
+ pim_inet4_dump("<old?>", primary_addr, old, sizeof(old));
+
+ zlog_warn("%s: %s primary addr old=%s: forcing secondary flag on new=%s",
+ __PRETTY_FUNCTION__,
+ c->ifp->name, old, buf);
+ SET_FLAG(c->flags, ZEBRA_IFA_SECONDARY);
+ }
+ }
+
+ pim_if_addr_add(c);
+
+ return 0;
+}
+
+static int pim_zebra_if_address_del(int command, struct zclient *client,
+ zebra_size_t length)
+{
+ struct connected *c;
+ struct prefix *p;
+
+ zassert(command == ZEBRA_INTERFACE_ADDRESS_DELETE);
+
+ /*
+ zebra api notifies address adds/dels events by using the same call
+ interface_add_read below, see comments in lib/zclient.c
+
+ zebra_interface_address_read(ZEBRA_INTERFACE_ADDRESS_DELETE, ...)
+ will remove address from interface list by calling
+ connected_delete_by_prefix()
+ */
+ c = zebra_interface_address_read(command, client->ibuf);
+ if (!c)
+ return 0;
+
+ p = c->address;
+ if (p->family != AF_INET)
+ return 0;
+
+ if (PIM_DEBUG_ZEBRA) {
+ char buf[BUFSIZ];
+ prefix2str(p, buf, BUFSIZ);
+ zlog_debug("%s: %s disconnected IP address %s flags %u %s",
+ __PRETTY_FUNCTION__,
+ c->ifp->name, buf, c->flags,
+ CHECK_FLAG(c->flags, ZEBRA_IFA_SECONDARY) ? "secondary" : "primary");
+
+#ifdef PIM_DEBUG_IFADDR_DUMP
+ dump_if_address(c->ifp);
+#endif
+ }
+
+ pim_if_addr_del(c, 0);
+
+ return 0;
+}
+
+static void scan_upstream_rpf_cache()
+{
+ struct listnode *up_node;
+ struct listnode *up_nextnode;
+ struct pim_upstream *up;
+
+ for (ALL_LIST_ELEMENTS(qpim_upstream_list, up_node, up_nextnode, up)) {
+ struct in_addr old_rpf_addr;
+ enum pim_rpf_result rpf_result;
+
+ rpf_result = pim_rpf_update(up, &old_rpf_addr);
+ if (rpf_result == PIM_RPF_FAILURE)
+ continue;
+
+ if (rpf_result == PIM_RPF_CHANGED) {
+
+ if (up->join_state == PIM_UPSTREAM_JOINED) {
+
+ /*
+ RFC 4601: 4.5.7. Sending (S,G) Join/Prune Messages
+
+ Transitions from Joined State
+
+ RPF'(S,G) changes not due to an Assert
+
+ The upstream (S,G) state machine remains in Joined
+ state. Send Join(S,G) to the new upstream neighbor, which is
+ the new value of RPF'(S,G). Send Prune(S,G) to the old
+ upstream neighbor, which is the old value of RPF'(S,G). Set
+ the Join Timer (JT) to expire after t_periodic seconds.
+ */
+
+
+ /* send Prune(S,G) to the old upstream neighbor */
+ pim_joinprune_send(up->rpf.source_nexthop.interface,
+ old_rpf_addr,
+ up->source_addr,
+ up->group_addr,
+ 0 /* prune */);
+
+ /* send Join(S,G) to the current upstream neighbor */
+ pim_joinprune_send(up->rpf.source_nexthop.interface,
+ up->rpf.rpf_addr,
+ up->source_addr,
+ up->group_addr,
+ 1 /* join */);
+
+ pim_upstream_join_timer_restart(up);
+ } /* up->join_state == PIM_UPSTREAM_JOINED */
+
+ /* FIXME can join_desired actually be changed by pim_rpf_update()
+ returning PIM_RPF_CHANGED ? */
+ pim_upstream_update_join_desired(up);
+
+ } /* PIM_RPF_CHANGED */
+
+ } /* for (qpim_upstream_list) */
+
+}
+
+void pim_scan_oil()
+{
+ struct listnode *node;
+ struct listnode *nextnode;
+ struct channel_oil *c_oil;
+
+ qpim_scan_oil_last = pim_time_monotonic_sec();
+ ++qpim_scan_oil_events;
+
+ for (ALL_LIST_ELEMENTS(qpim_channel_oil_list, node, nextnode, c_oil)) {
+ int old_vif_index;
+ int input_iface_vif_index = fib_lookup_if_vif_index(c_oil->oil.mfcc_origin);
+ if (input_iface_vif_index < 1) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ zlog_warn("%s %s: could not find input interface for (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str);
+ continue;
+ }
+
+ if (input_iface_vif_index == c_oil->oil.mfcc_parent) {
+ /* RPF unchanged */
+ continue;
+ }
+
+ if (PIM_DEBUG_ZEBRA) {
+ struct interface *old_iif = pim_if_find_by_vif_index(c_oil->oil.mfcc_parent);
+ struct interface *new_iif = pim_if_find_by_vif_index(input_iface_vif_index);
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s) input interface changed from %s vif_index=%d to %s vif_index=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ old_iif ? old_iif->name : "<old_iif?>", c_oil->oil.mfcc_parent,
+ new_iif ? new_iif->name : "<new_iif?>", input_iface_vif_index);
+ }
+
+ /* new iif loops to existing oif ? */
+ if (c_oil->oil.mfcc_ttls[input_iface_vif_index]) {
+ struct interface *new_iif = pim_if_find_by_vif_index(input_iface_vif_index);
+
+ if (PIM_DEBUG_ZEBRA) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s) new iif loops to existing oif: %s vif_index=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ new_iif ? new_iif->name : "<new_iif?>", input_iface_vif_index);
+ }
+
+ del_oif(c_oil, new_iif, PIM_OIF_FLAG_PROTO_ANY);
+ }
+
+ /* update iif vif_index */
+ old_vif_index = c_oil->oil.mfcc_parent;
+ c_oil->oil.mfcc_parent = input_iface_vif_index;
+
+ /* update kernel multicast forwarding cache (MFC) */
+ if (pim_mroute_add(&c_oil->oil)) {
+ /* just log warning */
+ struct interface *old_iif = pim_if_find_by_vif_index(old_vif_index);
+ struct interface *new_iif = pim_if_find_by_vif_index(input_iface_vif_index);
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", c_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ zlog_warn("%s %s: (S,G)=(%s,%s) failure updating input interface from %s vif_index=%d to %s vif_index=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ old_iif ? old_iif->name : "<old_iif?>", c_oil->oil.mfcc_parent,
+ new_iif ? new_iif->name : "<new_iif?>", input_iface_vif_index);
+ continue;
+ }
+
+ } /* for (qpim_channel_oil_list) */
+}
+
+static int on_rpf_cache_refresh(struct thread *t)
+{
+ zassert(t);
+ zassert(qpim_rpf_cache_refresher);
+
+ qpim_rpf_cache_refresher = 0;
+
+ /* update PIM protocol state */
+ scan_upstream_rpf_cache();
+
+ /* update kernel multicast forwarding cache (MFC) */
+ pim_scan_oil();
+
+ qpim_rpf_cache_refresh_last = pim_time_monotonic_sec();
+ ++qpim_rpf_cache_refresh_events;
+
+ return 0;
+}
+
+static void sched_rpf_cache_refresh()
+{
+ ++qpim_rpf_cache_refresh_requests;
+
+ if (qpim_rpf_cache_refresher) {
+ /* Refresh timer is already running */
+ return;
+ }
+
+ /* Start refresh timer */
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s: triggering %ld msec timer",
+ __PRETTY_FUNCTION__,
+ qpim_rpf_cache_refresh_delay_msec);
+ }
+
+ THREAD_TIMER_MSEC_ON(master, qpim_rpf_cache_refresher,
+ on_rpf_cache_refresh,
+ 0, qpim_rpf_cache_refresh_delay_msec);
+}
+
+static int redist_read_ipv4_route(int command, struct zclient *zclient,
+ zebra_size_t length)
+{
+ struct stream *s;
+ struct zapi_ipv4 api;
+ unsigned long ifindex;
+ struct in_addr nexthop;
+ struct prefix_ipv4 p;
+ int min_len = 4;
+
+ if (length < min_len) {
+ zlog_warn("%s %s: short buffer: length=%d min=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ length, min_len);
+ return -1;
+ }
+
+ s = zclient->ibuf;
+ ifindex = 0;
+ nexthop.s_addr = 0;
+
+ /* Type, flags, message. */
+ api.type = stream_getc(s);
+ api.flags = stream_getc(s);
+ api.message = stream_getc(s);
+
+ /* IPv4 prefix length. */
+ memset(&p, 0, sizeof(struct prefix_ipv4));
+ p.family = AF_INET;
+ p.prefixlen = stream_getc(s);
+
+ min_len +=
+ PSIZE(p.prefixlen) +
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP) ? 5 : 0 +
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX) ? 5 : 0 +
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? 1 : 0 +
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? 4 : 0;
+
+ if (PIM_DEBUG_ZEBRA) {
+ zlog_debug("%s %s: length=%d min_len=%d flags=%s%s%s%s",
+ __FILE__, __PRETTY_FUNCTION__,
+ length, min_len,
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP) ? "nh" : "",
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX) ? " ifi" : "",
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? " dist" : "",
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? " metr" : "");
+ }
+
+ if (length < min_len) {
+ zlog_warn("%s %s: short buffer: length=%d min_len=%d flags=%s%s%s%s",
+ __FILE__, __PRETTY_FUNCTION__,
+ length, min_len,
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP) ? "nh" : "",
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX) ? " ifi" : "",
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? " dist" : "",
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? " metr" : "");
+ return -1;
+ }
+
+ /* IPv4 prefix. */
+ stream_get(&p.prefix, s, PSIZE(p.prefixlen));
+
+ /* Nexthop, ifindex, distance, metric. */
+ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP)) {
+ api.nexthop_num = stream_getc(s);
+ nexthop.s_addr = stream_get_ipv4(s);
+ }
+ if (CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX)) {
+ api.ifindex_num = stream_getc(s);
+ ifindex = stream_getl(s);
+ }
+
+ api.distance = CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ?
+ stream_getc(s) :
+ 0;
+
+ api.metric = CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ?
+ stream_getl(s) :
+ 0;
+
+ switch (command) {
+ case ZEBRA_IPV4_ROUTE_ADD:
+ if (PIM_DEBUG_ZEBRA) {
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_debug("%s: add %s %s/%d "
+ "nexthop %s ifindex %ld metric%s %u distance%s %u",
+ __PRETTY_FUNCTION__,
+ zebra_route_string(api.type),
+ inet_ntop(AF_INET, &p.prefix, buf[0], sizeof(buf[0])),
+ p.prefixlen,
+ inet_ntop(AF_INET, &nexthop, buf[1], sizeof(buf[1])),
+ ifindex,
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? "-recv" : "-miss",
+ api.metric,
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? "-recv" : "-miss",
+ api.distance);
+ }
+ break;
+ case ZEBRA_IPV4_ROUTE_DELETE:
+ if (PIM_DEBUG_ZEBRA) {
+ char buf[2][INET_ADDRSTRLEN];
+ zlog_debug("%s: delete %s %s/%d "
+ "nexthop %s ifindex %ld metric%s %u distance%s %u",
+ __PRETTY_FUNCTION__,
+ zebra_route_string(api.type),
+ inet_ntop(AF_INET, &p.prefix, buf[0], sizeof(buf[0])),
+ p.prefixlen,
+ inet_ntop(AF_INET, &nexthop, buf[1], sizeof(buf[1])),
+ ifindex,
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC) ? "-recv" : "-miss",
+ api.metric,
+ CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE) ? "-recv" : "-miss",
+ api.distance);
+ }
+ break;
+ default:
+ zlog_warn("%s: unknown command=%d", __PRETTY_FUNCTION__, command);
+ return -1;
+ }
+
+ sched_rpf_cache_refresh();
+
+ return 0;
+}
+
+void pim_zebra_init(char *zebra_sock_path)
+{
+ int i;
+
+ if (zebra_sock_path)
+ zclient_serv_path_set(zebra_sock_path);
+
+#ifdef HAVE_TCP_ZEBRA
+ zlog_notice("zclient update contacting ZEBRA daemon at socket TCP %s,%d", "127.0.0.1", ZEBRA_PORT);
+#else
+ zlog_notice("zclient update contacting ZEBRA daemon at socket UNIX %s", zclient_serv_path_get());
+#endif
+
+ /* Socket for receiving updates from Zebra daemon */
+ qpim_zclient_update = zclient_new();
+
+ qpim_zclient_update->router_id_update = pim_router_id_update_zebra;
+ qpim_zclient_update->interface_add = pim_zebra_if_add;
+ qpim_zclient_update->interface_delete = pim_zebra_if_del;
+ qpim_zclient_update->interface_up = pim_zebra_if_state_up;
+ qpim_zclient_update->interface_down = pim_zebra_if_state_down;
+ qpim_zclient_update->interface_address_add = pim_zebra_if_address_add;
+ qpim_zclient_update->interface_address_delete = pim_zebra_if_address_del;
+ qpim_zclient_update->ipv4_route_add = redist_read_ipv4_route;
+ qpim_zclient_update->ipv4_route_delete = redist_read_ipv4_route;
+
+ zclient_init(qpim_zclient_update, ZEBRA_ROUTE_PIM);
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_info("zclient_init cleared redistribution request");
+ }
+
+ zassert(qpim_zclient_update->redist_default == ZEBRA_ROUTE_PIM);
+
+ /* Request all redistribution */
+ for (i = 0; i < ZEBRA_ROUTE_MAX; i++) {
+ if (i == qpim_zclient_update->redist_default)
+ continue;
+ qpim_zclient_update->redist[i] = 1;
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_debug("%s: requesting redistribution for %s (%i)",
+ __PRETTY_FUNCTION__, zebra_route_string(i), i);
+ }
+ }
+
+ /* Request default information */
+ qpim_zclient_update->default_information = 1;
+ if (PIM_DEBUG_PIM_TRACE) {
+ zlog_info("%s: requesting default information redistribution",
+ __PRETTY_FUNCTION__);
+
+ zlog_notice("%s: zclient update socket initialized",
+ __PRETTY_FUNCTION__);
+ }
+
+ zassert(!qpim_zclient_lookup);
+ qpim_zclient_lookup = zclient_lookup_new();
+ zassert(qpim_zclient_lookup);
+}
+
+void igmp_anysource_forward_start(struct igmp_group *group)
+{
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ zassert(group->group_filtermode_isexcl);
+ zassert(listcount(group->group_source_list) < 1);
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ zlog_debug("%s %s: UNIMPLEMENTED",
+ __FILE__, __PRETTY_FUNCTION__);
+ }
+}
+
+void igmp_anysource_forward_stop(struct igmp_group *group)
+{
+ /* Any source (*,G) is forwarded only if mode is EXCLUDE {empty} */
+ zassert((!group->group_filtermode_isexcl) || (listcount(group->group_source_list) > 0));
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ zlog_debug("%s %s: UNIMPLEMENTED",
+ __FILE__, __PRETTY_FUNCTION__);
+ }
+}
+
+static int fib_lookup_if_vif_index(struct in_addr addr)
+{
+ struct pim_zlookup_nexthop nexthop_tab[PIM_NEXTHOP_IFINDEX_TAB_SIZE];
+ int num_ifindex;
+ int vif_index;
+ int first_ifindex;
+
+ num_ifindex = zclient_lookup_nexthop(qpim_zclient_lookup, nexthop_tab,
+ PIM_NEXTHOP_IFINDEX_TAB_SIZE, addr,
+ PIM_NEXTHOP_LOOKUP_MAX);
+ if (num_ifindex < 1) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: could not find nexthop ifindex for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ addr_str);
+ return -1;
+ }
+
+ first_ifindex = nexthop_tab[0].ifindex;
+
+ if (num_ifindex > 1) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s %s: FIXME ignoring multiple nexthop ifindex'es num_ifindex=%d for address %s (using only ifindex=%d)",
+ __FILE__, __PRETTY_FUNCTION__,
+ num_ifindex, addr_str, first_ifindex);
+ /* debug warning only, do not return */
+ }
+
+ if (PIM_DEBUG_ZEBRA) {
+ char addr_str[100];
+ pim_inet4_dump("<ifaddr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s %s: found nexthop ifindex=%d (interface %s) for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ first_ifindex, ifindex2ifname(first_ifindex), addr_str);
+ }
+
+ vif_index = pim_if_find_vifindex_by_ifindex(first_ifindex);
+
+ if (vif_index < 1) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: low vif_index=%d < 1 nexthop for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ vif_index, addr_str);
+ return -2;
+ }
+
+ zassert(qpim_mroute_oif_highest_vif_index < MAXVIFS);
+
+ if (vif_index > qpim_mroute_oif_highest_vif_index) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: high vif_index=%d > highest_vif_index=%d nexthop for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ vif_index, qpim_mroute_oif_highest_vif_index, addr_str);
+
+ zlog_warn("%s %s: pim disabled on interface %s vif_index=%d ?",
+ __FILE__, __PRETTY_FUNCTION__,
+ ifindex2ifname(vif_index),
+ vif_index);
+
+ return -3;
+ }
+
+ return vif_index;
+}
+
+static int add_oif(struct channel_oil *channel_oil,
+ struct interface *oif,
+ uint32_t proto_mask)
+{
+ struct pim_interface *pim_ifp;
+ int old_ttl;
+
+ zassert(channel_oil);
+
+ pim_ifp = oif->info;
+
+ if (PIM_DEBUG_MROUTE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s): proto_mask=%u OIF=%s vif_index=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index);
+ }
+
+ if (pim_ifp->mroute_vif_index < 1) {
+ zlog_warn("%s %s: interface %s vif_index=%d < 1",
+ __FILE__, __PRETTY_FUNCTION__,
+ oif->name, pim_ifp->mroute_vif_index);
+ return -1;
+ }
+
+#ifdef PIM_ENFORCE_LOOPFREE_MFC
+ /*
+ Prevent creating MFC entry with OIF=IIF.
+
+ This is a protection against implementation mistakes.
+
+ PIM protocol implicitely ensures loopfree multicast topology.
+
+ IGMP must be protected against adding looped MFC entries created
+ by both source and receiver attached to the same interface. See
+ TODO T22.
+ */
+ if (pim_ifp->mroute_vif_index == channel_oil->oil.mfcc_parent) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: refusing protocol mask %u request for IIF=OIF=%s (vif_index=%d) for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index,
+ source_str, group_str);
+ return -2;
+ }
+#endif
+
+ zassert(qpim_mroute_oif_highest_vif_index < MAXVIFS);
+ zassert(pim_ifp->mroute_vif_index <= qpim_mroute_oif_highest_vif_index);
+
+ /* Prevent single protocol from subscribing same interface to
+ channel (S,G) multiple times */
+ if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: existing protocol mask %u requested OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index,
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+ source_str, group_str);
+ return -3;
+ }
+
+ /* Allow other protocol to request subscription of same interface to
+ channel (S,G) multiple times, by silently ignoring further
+ requests */
+ if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & PIM_OIF_FLAG_PROTO_ANY) {
+
+ /* Check the OIF really exists before returning, and only log
+ warning otherwise */
+ if (channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] < 1) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: new protocol mask %u requested nonexistent OIF %s (vif_index=%d, min_ttl=%d) for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index,
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+ source_str, group_str);
+ }
+
+ return 0;
+ }
+
+ old_ttl = channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index];
+
+ if (old_ttl > 0) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: interface %s (vif_index=%d) is existing output for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ oif->name, pim_ifp->mroute_vif_index,
+ source_str, group_str);
+ return -4;
+ }
+
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = PIM_MROUTE_MIN_TTL;
+
+ if (pim_mroute_add(&channel_oil->oil)) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: could not add output interface %s (vif_index=%d) for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ oif->name, pim_ifp->mroute_vif_index,
+ source_str, group_str);
+
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = old_ttl;
+ return -5;
+ }
+
+ channel_oil->oif_creation[pim_ifp->mroute_vif_index] = pim_time_monotonic_sec();
+ ++channel_oil->oil_size;
+ channel_oil->oif_flags[pim_ifp->mroute_vif_index] |= proto_mask;
+
+ if (PIM_DEBUG_MROUTE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s): proto_mask=%u OIF=%s vif_index=%d: DONE",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index);
+ }
+
+ return 0;
+}
+
+static int del_oif(struct channel_oil *channel_oil,
+ struct interface *oif,
+ uint32_t proto_mask)
+{
+ struct pim_interface *pim_ifp;
+ int old_ttl;
+
+ zassert(channel_oil);
+
+ pim_ifp = oif->info;
+
+ zassert(pim_ifp->mroute_vif_index >= 1);
+ zassert(qpim_mroute_oif_highest_vif_index < MAXVIFS);
+ zassert(pim_ifp->mroute_vif_index <= qpim_mroute_oif_highest_vif_index);
+
+ if (PIM_DEBUG_MROUTE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s): proto_mask=%u OIF=%s vif_index=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index);
+ }
+
+ /* Prevent single protocol from unsubscribing same interface from
+ channel (S,G) multiple times */
+ if (!(channel_oil->oif_flags[pim_ifp->mroute_vif_index] & proto_mask)) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: nonexistent protocol mask %u removed OIF %s (vif_index=%d, min_ttl=%d) from channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index,
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+ source_str, group_str);
+ return -2;
+ }
+
+ /* Mark that protocol is no longer interested in this OIF */
+ channel_oil->oif_flags[pim_ifp->mroute_vif_index] &= ~proto_mask;
+
+ /* Allow multiple protocols to unsubscribe same interface from
+ channel (S,G) multiple times, by silently ignoring requests while
+ there is at least one protocol interested in the channel */
+ if (channel_oil->oif_flags[pim_ifp->mroute_vif_index] & PIM_OIF_FLAG_PROTO_ANY) {
+
+ /* Check the OIF keeps existing before returning, and only log
+ warning otherwise */
+ if (channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] < 1) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: protocol mask %u removing nonexistent OIF %s (vif_index=%d, min_ttl=%d) from channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index,
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index],
+ source_str, group_str);
+ }
+
+ return 0;
+ }
+
+ old_ttl = channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index];
+
+ if (old_ttl < 1) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: interface %s (vif_index=%d) is not output for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ oif->name, pim_ifp->mroute_vif_index,
+ source_str, group_str);
+ return -3;
+ }
+
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = 0;
+
+ if (pim_mroute_add(&channel_oil->oil)) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: could not remove output interface %s (vif_index=%d) from channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ oif->name, pim_ifp->mroute_vif_index,
+ source_str, group_str);
+
+ channel_oil->oil.mfcc_ttls[pim_ifp->mroute_vif_index] = old_ttl;
+ return -4;
+ }
+
+ --channel_oil->oil_size;
+
+ if (channel_oil->oil_size < 1) {
+ if (pim_mroute_del(&channel_oil->oil)) {
+ /* just log a warning in case of failure */
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_warn("%s %s: failure removing OIL for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str);
+ }
+ }
+
+ if (PIM_DEBUG_MROUTE) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", channel_oil->oil.mfcc_origin, source_str, sizeof(source_str));
+ zlog_debug("%s %s: (S,G)=(%s,%s): proto_mask=%u OIF=%s vif_index=%d: DONE",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str,
+ proto_mask, oif->name, pim_ifp->mroute_vif_index);
+ }
+
+ return 0;
+}
+
+void igmp_source_forward_start(struct igmp_source *source)
+{
+ struct igmp_group *group;
+ int result;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", source->source_group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: (S,G)=(%s,%s) igmp_sock=%d oif=%s fwd=%d",
+ __PRETTY_FUNCTION__,
+ source_str, group_str,
+ source->source_group->group_igmp_sock->fd,
+ source->source_group->group_igmp_sock->interface->name,
+ IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+ }
+
+ /* Prevent IGMP interface from installing multicast route multiple
+ times */
+ if (IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+ return;
+ }
+
+ group = source->source_group;
+
+ if (!source->source_channel_oil) {
+ struct pim_interface *pim_oif;
+ int input_iface_vif_index = fib_lookup_if_vif_index(source->source_addr);
+ if (input_iface_vif_index < 1) {
+ char source_str[100];
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s %s: could not find input interface for source %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str);
+ return;
+ }
+
+ /*
+ Protect IGMP against adding looped MFC entries created by both
+ source and receiver attached to the same interface. See TODO
+ T22.
+ */
+ pim_oif = source->source_group->group_igmp_sock->interface->info;
+ if (!pim_oif) {
+ zlog_warn("%s: multicast not enabled on oif=%s ?",
+ __PRETTY_FUNCTION__,
+ source->source_group->group_igmp_sock->interface->name);
+ return;
+ }
+ if (pim_oif->mroute_vif_index < 1) {
+ zlog_warn("%s %s: oif=%s vif_index=%d < 1",
+ __FILE__, __PRETTY_FUNCTION__,
+ source->source_group->group_igmp_sock->interface->name,
+ pim_oif->mroute_vif_index);
+ return;
+ }
+ if (input_iface_vif_index == pim_oif->mroute_vif_index) {
+ /* ignore request for looped MFC entry */
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", source->source_group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: ignoring request for looped MFC entry (S,G)=(%s,%s): igmp_sock=%d oif=%s vif_index=%d",
+ __PRETTY_FUNCTION__,
+ source_str, group_str,
+ source->source_group->group_igmp_sock->fd,
+ source->source_group->group_igmp_sock->interface->name,
+ input_iface_vif_index);
+ }
+ return;
+ }
+
+ source->source_channel_oil = pim_channel_oil_add(group->group_addr,
+ source->source_addr,
+ input_iface_vif_index);
+ if (!source->source_channel_oil) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", group->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s %s: could not create OIL for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str);
+ return;
+ }
+ }
+
+ result = add_oif(source->source_channel_oil,
+ group->group_igmp_sock->interface,
+ PIM_OIF_FLAG_PROTO_IGMP);
+ if (result) {
+ zlog_warn("%s: add_oif() failed with return=%d",
+ __func__, result);
+ return;
+ }
+
+ /*
+ Feed IGMPv3-gathered local membership information into PIM
+ per-interface (S,G) state.
+ */
+ pim_ifchannel_local_membership_add(group->group_igmp_sock->interface,
+ source->source_addr, group->group_addr);
+
+ IGMP_SOURCE_DO_FORWARDING(source->source_flags);
+}
+
+/*
+ igmp_source_forward_stop: stop fowarding, but keep the source
+ igmp_source_delete: stop fowarding, and delete the source
+ */
+void igmp_source_forward_stop(struct igmp_source *source)
+{
+ struct igmp_group *group;
+ int result;
+
+ if (PIM_DEBUG_IGMP_TRACE) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", source->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", source->source_group->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: (S,G)=(%s,%s) igmp_sock=%d oif=%s fwd=%d",
+ __PRETTY_FUNCTION__,
+ source_str, group_str,
+ source->source_group->group_igmp_sock->fd,
+ source->source_group->group_igmp_sock->interface->name,
+ IGMP_SOURCE_TEST_FORWARDING(source->source_flags));
+ }
+
+ /* Prevent IGMP interface from removing multicast route multiple
+ times */
+ if (!IGMP_SOURCE_TEST_FORWARDING(source->source_flags)) {
+ return;
+ }
+
+ group = source->source_group;
+
+ /*
+ It appears that in certain circumstances that
+ igmp_source_forward_stop is called when IGMP forwarding
+ was not enabled in oif_flags for this outgoing interface.
+ Possibly because of multiple calls. When that happens, we
+ enter the below if statement and this function returns early
+ which in turn triggers the calling function to assert.
+ Making the call to del_oif and ignoring the return code
+ fixes the issue without ill effect, similar to
+ pim_forward_stop below.
+ */
+ result = del_oif(source->source_channel_oil,
+ group->group_igmp_sock->interface,
+ PIM_OIF_FLAG_PROTO_IGMP);
+ if (result) {
+ zlog_warn("%s: del_oif() failed with return=%d",
+ __func__, result);
+ return;
+ }
+
+ /*
+ Feed IGMPv3-gathered local membership information into PIM
+ per-interface (S,G) state.
+ */
+ pim_ifchannel_local_membership_del(group->group_igmp_sock->interface,
+ source->source_addr, group->group_addr);
+
+ IGMP_SOURCE_DONT_FORWARDING(source->source_flags);
+}
+
+void pim_forward_start(struct pim_ifchannel *ch)
+{
+ struct pim_upstream *up = ch->upstream;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", ch->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", ch->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: (S,G)=(%s,%s) oif=%s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ch->interface->name);
+ }
+
+ if (!up->channel_oil) {
+ int input_iface_vif_index = fib_lookup_if_vif_index(up->source_addr);
+ if (input_iface_vif_index < 1) {
+ char source_str[100];
+ pim_inet4_dump("<source?>", up->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s %s: could not find input interface for source %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str);
+ return;
+ }
+
+ up->channel_oil = pim_channel_oil_add(up->group_addr, up->source_addr,
+ input_iface_vif_index);
+ if (!up->channel_oil) {
+ char group_str[100];
+ char source_str[100];
+ pim_inet4_dump("<group?>", up->group_addr, group_str, sizeof(group_str));
+ pim_inet4_dump("<source?>", up->source_addr, source_str, sizeof(source_str));
+ zlog_warn("%s %s: could not create OIL for channel (S,G)=(%s,%s)",
+ __FILE__, __PRETTY_FUNCTION__,
+ source_str, group_str);
+ return;
+ }
+ }
+
+ add_oif(up->channel_oil,
+ ch->interface,
+ PIM_OIF_FLAG_PROTO_PIM);
+}
+
+void pim_forward_stop(struct pim_ifchannel *ch)
+{
+ struct pim_upstream *up = ch->upstream;
+
+ if (PIM_DEBUG_PIM_TRACE) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", ch->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", ch->group_addr, group_str, sizeof(group_str));
+ zlog_debug("%s: (S,G)=(%s,%s) oif=%s",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ch->interface->name);
+ }
+
+ if (!up->channel_oil) {
+ char source_str[100];
+ char group_str[100];
+ pim_inet4_dump("<source?>", ch->source_addr, source_str, sizeof(source_str));
+ pim_inet4_dump("<group?>", ch->group_addr, group_str, sizeof(group_str));
+ zlog_warn("%s: (S,G)=(%s,%s) oif=%s missing channel OIL",
+ __PRETTY_FUNCTION__,
+ source_str, group_str, ch->interface->name);
+
+ return;
+ }
+
+ del_oif(up->channel_oil,
+ ch->interface,
+ PIM_OIF_FLAG_PROTO_PIM);
+}
diff --git a/pimd/pim_zebra.h b/pimd/pim_zebra.h
new file mode 100644
index 00000000..d624c866
--- /dev/null
+++ b/pimd/pim_zebra.h
@@ -0,0 +1,42 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_ZEBRA_H
+#define PIM_ZEBRA_H
+
+#include "pim_igmp.h"
+#include "pim_ifchannel.h"
+
+void pim_zebra_init(char *zebra_sock_path);
+
+void pim_scan_oil(void);
+
+void igmp_anysource_forward_start(struct igmp_group *group);
+void igmp_anysource_forward_stop(struct igmp_group *group);
+
+void igmp_source_forward_start(struct igmp_source *source);
+void igmp_source_forward_stop(struct igmp_source *source);
+
+void pim_forward_start(struct pim_ifchannel *ch);
+void pim_forward_stop(struct pim_ifchannel *ch);
+
+#endif /* PIM_ZEBRA_H */
diff --git a/pimd/pim_zlookup.c b/pimd/pim_zlookup.c
new file mode 100644
index 00000000..2e71dc4e
--- /dev/null
+++ b/pimd/pim_zlookup.c
@@ -0,0 +1,457 @@
+/*
+ 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 "zebra/rib.h"
+
+#include "log.h"
+#include "prefix.h"
+#include "zclient.h"
+#include "stream.h"
+#include "network.h"
+#include "thread.h"
+
+#include "pimd.h"
+#include "pim_pim.h"
+#include "pim_str.h"
+#include "pim_zlookup.h"
+
+extern int zclient_debug;
+
+static void zclient_lookup_sched(struct zclient *zlookup, int delay);
+
+/* Connect to zebra for nexthop lookup. */
+static int zclient_lookup_connect(struct thread *t)
+{
+ struct zclient *zlookup;
+
+ zlookup = THREAD_ARG(t);
+ zlookup->t_connect = NULL;
+
+ if (zlookup->sock >= 0) {
+ return 0;
+ }
+
+ if (zclient_socket_connect(zlookup) < 0) {
+ ++zlookup->fail;
+ zlog_warn("%s: failure connecting zclient socket: failures=%d",
+ __PRETTY_FUNCTION__, zlookup->fail);
+ }
+ else {
+ zlookup->fail = 0; /* reset counter on connection */
+ }
+
+ zassert(!zlookup->t_connect);
+ if (zlookup->sock < 0) {
+ /* Since last connect failed, retry within 10 secs */
+ zclient_lookup_sched(zlookup, 10);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Schedule connection with delay. */
+static void zclient_lookup_sched(struct zclient *zlookup, int delay)
+{
+ zassert(!zlookup->t_connect);
+
+ THREAD_TIMER_ON(master, zlookup->t_connect,
+ zclient_lookup_connect,
+ zlookup, delay);
+
+ zlog_notice("%s: zclient lookup connection scheduled for %d seconds",
+ __PRETTY_FUNCTION__, delay);
+}
+
+/* Schedule connection for now. */
+static void zclient_lookup_sched_now(struct zclient *zlookup)
+{
+ zassert(!zlookup->t_connect);
+
+ zlookup->t_connect = thread_add_event(master, zclient_lookup_connect,
+ zlookup, 0);
+
+ zlog_notice("%s: zclient lookup immediate connection scheduled",
+ __PRETTY_FUNCTION__);
+}
+
+/* Schedule reconnection, if needed. */
+static void zclient_lookup_reconnect(struct zclient *zlookup)
+{
+ if (zlookup->t_connect) {
+ return;
+ }
+
+ zclient_lookup_sched_now(zlookup);
+}
+
+static void zclient_lookup_failed(struct zclient *zlookup)
+{
+ if (zlookup->sock >= 0) {
+ if (close(zlookup->sock)) {
+ zlog_warn("%s: closing fd=%d: errno=%d %s", __func__, zlookup->sock,
+ errno, safe_strerror(errno));
+ }
+ zlookup->sock = -1;
+ }
+
+ zclient_lookup_reconnect(zlookup);
+}
+
+struct zclient *zclient_lookup_new()
+{
+ struct zclient *zlookup;
+
+ zlookup = zclient_new();
+ if (!zlookup) {
+ zlog_err("%s: zclient_new() failure",
+ __PRETTY_FUNCTION__);
+ return 0;
+ }
+
+ zlookup->sock = -1;
+ zlookup->ibuf = stream_new(ZEBRA_MAX_PACKET_SIZ);
+ zlookup->obuf = stream_new(ZEBRA_MAX_PACKET_SIZ);
+ zlookup->t_connect = 0;
+
+ zclient_lookup_sched_now(zlookup);
+
+ zlog_notice("%s: zclient lookup socket initialized",
+ __PRETTY_FUNCTION__);
+
+ return zlookup;
+}
+
+static int zclient_read_nexthop(struct zclient *zlookup,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size,
+ struct in_addr addr)
+{
+ int num_ifindex = 0;
+ struct stream *s;
+ const uint16_t MIN_LEN = 14; /* getc=1 getc=1 getw=2 getipv4=4 getc=1 getl=4 getc=1 */
+ uint16_t length, len;
+ u_char marker;
+ u_char version;
+ uint16_t command;
+ int nbytes;
+ struct in_addr raddr;
+ uint8_t distance;
+ uint32_t metric;
+ int nexthop_num;
+ int i;
+
+ if (PIM_DEBUG_ZEBRA) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s: addr=%s",
+ __PRETTY_FUNCTION__,
+ addr_str);
+ }
+
+ s = zlookup->ibuf;
+ stream_reset(s);
+
+ nbytes = stream_read(s, zlookup->sock, 2);
+ if (nbytes < 2) {
+ zlog_err("%s %s: failure reading zclient lookup socket: nbytes=%d",
+ __FILE__, __PRETTY_FUNCTION__, nbytes);
+ zclient_lookup_failed(zlookup);
+ return -1;
+ }
+ length = stream_getw(s);
+
+ len = length - 2;
+
+ if (len < MIN_LEN) {
+ zlog_err("%s %s: failure reading zclient lookup socket: len=%d < MIN_LEN=%d",
+ __FILE__, __PRETTY_FUNCTION__, len, MIN_LEN);
+ zclient_lookup_failed(zlookup);
+ return -2;
+ }
+
+ nbytes = stream_read(s, zlookup->sock, len);
+ if (nbytes < (length - 2)) {
+ zlog_err("%s %s: failure reading zclient lookup socket: nbytes=%d < len=%d",
+ __FILE__, __PRETTY_FUNCTION__, nbytes, len);
+ zclient_lookup_failed(zlookup);
+ return -3;
+ }
+ marker = stream_getc(s);
+ version = stream_getc(s);
+
+ if (version != ZSERV_VERSION || marker != ZEBRA_HEADER_MARKER) {
+ zlog_err("%s: socket %d version mismatch, marker %d, version %d",
+ __func__, zlookup->sock, marker, version);
+ return -4;
+ }
+
+ command = stream_getw(s);
+ if (command != ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB) {
+ zlog_err("%s: socket %d command mismatch: %d",
+ __func__, zlookup->sock, command);
+ return -5;
+ }
+
+ raddr.s_addr = stream_get_ipv4(s);
+
+ if (raddr.s_addr != addr.s_addr) {
+ char addr_str[100];
+ char raddr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ pim_inet4_dump("<raddr?>", raddr, raddr_str, sizeof(raddr_str));
+ zlog_warn("%s: address mismatch: addr=%s raddr=%s",
+ __PRETTY_FUNCTION__,
+ addr_str, raddr_str);
+ /* warning only */
+ }
+
+ distance = stream_getc(s);
+ metric = stream_getl(s);
+ nexthop_num = stream_getc(s);
+
+ if (nexthop_num < 1) {
+ zlog_err("%s: socket %d bad nexthop_num=%d",
+ __func__, zlookup->sock, nexthop_num);
+ return -6;
+ }
+
+ len -= MIN_LEN;
+
+ for (i = 0; i < nexthop_num; ++i) {
+ enum nexthop_types_t nexthop_type;
+
+ if (len < 1) {
+ zlog_err("%s: socket %d empty input expecting nexthop_type: len=%d",
+ __func__, zlookup->sock, len);
+ return -7;
+ }
+
+ nexthop_type = stream_getc(s);
+ --len;
+
+ switch (nexthop_type) {
+ case ZEBRA_NEXTHOP_IFINDEX:
+ case ZEBRA_NEXTHOP_IFNAME:
+ case ZEBRA_NEXTHOP_IPV4_IFINDEX:
+ if (num_ifindex >= tab_size) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: found too many nexthop ifindexes (%d > %d) for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ (num_ifindex + 1), tab_size, addr_str);
+ return num_ifindex;
+ }
+ if (nexthop_type == ZEBRA_NEXTHOP_IPV4_IFINDEX) {
+ if (len < 4) {
+ zlog_err("%s: socket %d short input expecting nexthop IPv4-addr: len=%d",
+ __func__, zlookup->sock, len);
+ return -8;
+ }
+ nexthop_tab[num_ifindex].nexthop_addr.s_addr = stream_get_ipv4(s);
+ len -= 4;
+ }
+ else {
+ nexthop_tab[num_ifindex].nexthop_addr.s_addr = PIM_NET_INADDR_ANY;
+ }
+ nexthop_tab[num_ifindex].ifindex = stream_getl(s);
+ nexthop_tab[num_ifindex].protocol_distance = distance;
+ nexthop_tab[num_ifindex].route_metric = metric;
+ ++num_ifindex;
+ break;
+ case ZEBRA_NEXTHOP_IPV4:
+ if (num_ifindex >= tab_size) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: found too many nexthop ifindexes (%d > %d) for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ (num_ifindex + 1), tab_size, addr_str);
+ return num_ifindex;
+ }
+ nexthop_tab[num_ifindex].nexthop_addr.s_addr = stream_get_ipv4(s);
+ len -= 4;
+ nexthop_tab[num_ifindex].ifindex = 0;
+ nexthop_tab[num_ifindex].protocol_distance = distance;
+ nexthop_tab[num_ifindex].route_metric = metric;
+ {
+ char addr_str[100];
+ char nexthop_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ pim_inet4_dump("<nexthop?>", nexthop_tab[num_ifindex].nexthop_addr, nexthop_str, sizeof(nexthop_str));
+ zlog_warn("%s %s: zebra returned recursive nexthop %s for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ nexthop_str, addr_str);
+ }
+ ++num_ifindex;
+ break;
+ default:
+ /* do nothing */
+ {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: found non-ifindex nexthop type=%d for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ nexthop_type, addr_str);
+ }
+ break;
+ }
+ }
+
+ return num_ifindex;
+}
+
+static int zclient_lookup_nexthop_once(struct zclient *zlookup,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size,
+ struct in_addr addr)
+{
+ struct stream *s;
+ int ret;
+
+ if (PIM_DEBUG_ZEBRA) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_debug("%s: addr=%s",
+ __PRETTY_FUNCTION__,
+ addr_str);
+ }
+
+ /* Check socket. */
+ if (zlookup->sock < 0) {
+ zlog_err("%s %s: zclient lookup socket is not connected",
+ __FILE__, __PRETTY_FUNCTION__);
+ zclient_lookup_failed(zlookup);
+ return -1;
+ }
+
+ s = zlookup->obuf;
+ stream_reset(s);
+ zclient_create_header(s, ZEBRA_IPV4_NEXTHOP_LOOKUP_MRIB);
+ stream_put_in_addr(s, &addr);
+ stream_putw_at(s, 0, stream_get_endp(s));
+
+ ret = writen(zlookup->sock, s->data, stream_get_endp(s));
+ if (ret < 0) {
+ zlog_err("%s %s: writen() failure writing to zclient lookup socket",
+ __FILE__, __PRETTY_FUNCTION__);
+ zclient_lookup_failed(zlookup);
+ return -2;
+ }
+ if (ret == 0) {
+ zlog_err("%s %s: connection closed on zclient lookup socket",
+ __FILE__, __PRETTY_FUNCTION__);
+ zclient_lookup_failed(zlookup);
+ return -3;
+ }
+
+ return zclient_read_nexthop(zlookup, nexthop_tab,
+ tab_size, addr);
+}
+
+int zclient_lookup_nexthop(struct zclient *zlookup,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size,
+ struct in_addr addr,
+ int max_lookup)
+{
+ int lookup;
+ uint32_t route_metric = 0xFFFFFFFF;
+ uint8_t protocol_distance = 0xFF;
+
+ for (lookup = 0; lookup < max_lookup; ++lookup) {
+ int num_ifindex;
+ int first_ifindex;
+ struct in_addr nexthop_addr;
+
+ num_ifindex = zclient_lookup_nexthop_once(qpim_zclient_lookup, nexthop_tab,
+ PIM_NEXTHOP_IFINDEX_TAB_SIZE, addr);
+ if (num_ifindex < 1) {
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: lookup=%d/%d: could not find nexthop ifindex for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ lookup, max_lookup, addr_str);
+ return -1;
+ }
+
+ if (lookup < 1) {
+ /* this is the non-recursive lookup - save original metric/distance */
+ route_metric = nexthop_tab[0].route_metric;
+ protocol_distance = nexthop_tab[0].protocol_distance;
+ }
+
+ /*
+ FIXME: Non-recursive nexthop ensured only for first ifindex.
+ However, recursive route lookup should really be fixed in zebra daemon.
+ See also TODO T24.
+ */
+ first_ifindex = nexthop_tab[0].ifindex;
+ nexthop_addr = nexthop_tab[0].nexthop_addr;
+ if (first_ifindex > 0) {
+ /* found: first ifindex is non-recursive nexthop */
+
+ if (lookup > 0) {
+ /* Report non-recursive success after first lookup */
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_info("%s %s: lookup=%d/%d: found non-recursive ifindex=%d for address %s dist=%d met=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ lookup, max_lookup, first_ifindex, addr_str,
+ nexthop_tab[0].protocol_distance,
+ nexthop_tab[0].route_metric);
+
+ /* use last address as nexthop address */
+ nexthop_tab[0].nexthop_addr = addr;
+
+ /* report original route metric/distance */
+ nexthop_tab[0].route_metric = route_metric;
+ nexthop_tab[0].protocol_distance = protocol_distance;
+ }
+
+ return num_ifindex;
+ }
+
+ {
+ char addr_str[100];
+ char nexthop_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ pim_inet4_dump("<nexthop?>", nexthop_addr, nexthop_str, sizeof(nexthop_str));
+ zlog_warn("%s %s: lookup=%d/%d: zebra returned recursive nexthop %s for address %s dist=%d met=%d",
+ __FILE__, __PRETTY_FUNCTION__,
+ lookup, max_lookup, nexthop_str, addr_str,
+ nexthop_tab[0].protocol_distance,
+ nexthop_tab[0].route_metric);
+ }
+
+ addr = nexthop_addr; /* use nexthop addr for recursive lookup */
+
+ } /* for (max_lookup) */
+
+ char addr_str[100];
+ pim_inet4_dump("<addr?>", addr, addr_str, sizeof(addr_str));
+ zlog_warn("%s %s: lookup=%d/%d: failure searching recursive nexthop ifindex for address %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ lookup, max_lookup, addr_str);
+
+ return -2;
+}
diff --git a/pimd/pim_zlookup.h b/pimd/pim_zlookup.h
new file mode 100644
index 00000000..1f184942
--- /dev/null
+++ b/pimd/pim_zlookup.h
@@ -0,0 +1,47 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIM_ZLOOKUP_H
+#define PIM_ZLOOKUP_H
+
+#include <zebra.h>
+
+#include "zclient.h"
+
+#define PIM_NEXTHOP_LOOKUP_MAX (3) /* max. recursive route lookup */
+
+struct pim_zlookup_nexthop {
+ struct in_addr nexthop_addr;
+ int ifindex;
+ uint32_t route_metric;
+ uint8_t protocol_distance;
+};
+
+struct zclient *zclient_lookup_new(void);
+
+int zclient_lookup_nexthop(struct zclient *zlookup,
+ struct pim_zlookup_nexthop nexthop_tab[],
+ const int tab_size,
+ struct in_addr addr,
+ int max_lookup);
+
+#endif /* PIM_ZLOOKUP_H */
diff --git a/pimd/pimd.c b/pimd/pimd.c
new file mode 100644
index 00000000..855defcc
--- /dev/null
+++ b/pimd/pimd.c
@@ -0,0 +1,141 @@
+/*
+ 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 "memory.h"
+
+#include "pimd.h"
+#include "pim_cmd.h"
+#include "pim_iface.h"
+#include "pim_zebra.h"
+#include "pim_str.h"
+#include "pim_oil.h"
+#include "pim_pim.h"
+#include "pim_upstream.h"
+#include "pim_rand.h"
+#include "pim_rpf.h"
+#include "pim_ssmpingd.h"
+
+const char *const PIM_ALL_SYSTEMS = MCAST_ALL_SYSTEMS;
+const char *const PIM_ALL_ROUTERS = MCAST_ALL_ROUTERS;
+const char *const PIM_ALL_PIM_ROUTERS = MCAST_ALL_PIM_ROUTERS;
+const char *const PIM_ALL_IGMP_ROUTERS = MCAST_ALL_IGMP_ROUTERS;
+
+struct thread_master *master = 0;
+uint32_t qpim_debugs = 0;
+int qpim_mroute_socket_fd = -1;
+int64_t qpim_mroute_socket_creation = 0; /* timestamp of creation */
+struct thread *qpim_mroute_socket_reader = 0;
+int qpim_mroute_oif_highest_vif_index = -1;
+struct list *qpim_channel_oil_list = 0;
+int qpim_t_periodic = PIM_DEFAULT_T_PERIODIC; /* Period between Join/Prune Messages */
+struct list *qpim_upstream_list = 0;
+struct zclient *qpim_zclient_update = 0;
+struct zclient *qpim_zclient_lookup = 0;
+struct pim_assert_metric qpim_infinite_assert_metric;
+long qpim_rpf_cache_refresh_delay_msec = 10000;
+struct thread *qpim_rpf_cache_refresher = 0;
+int64_t qpim_rpf_cache_refresh_requests = 0;
+int64_t qpim_rpf_cache_refresh_events = 0;
+int64_t qpim_rpf_cache_refresh_last = 0;
+struct in_addr qpim_inaddr_any;
+struct list *qpim_ssmpingd_list = 0;
+struct in_addr qpim_ssmpingd_group_addr;
+int64_t qpim_scan_oil_events = 0;
+int64_t qpim_scan_oil_last = 0;
+int64_t qpim_mroute_add_events = 0;
+int64_t qpim_mroute_add_last = 0;
+int64_t qpim_mroute_del_events = 0;
+int64_t qpim_mroute_del_last = 0;
+
+static void pim_free()
+{
+ pim_ssmpingd_destroy();
+
+ if (qpim_channel_oil_list)
+ list_free(qpim_channel_oil_list);
+
+ if (qpim_upstream_list)
+ list_free(qpim_upstream_list);
+}
+
+void pim_init()
+{
+ pim_rand_init();
+
+ if (!inet_aton(PIM_ALL_PIM_ROUTERS, &qpim_all_pim_routers_addr)) {
+ zlog_err("%s %s: could not solve %s to group address: errno=%d: %s",
+ __FILE__, __PRETTY_FUNCTION__,
+ PIM_ALL_PIM_ROUTERS, errno, safe_strerror(errno));
+ zassert(0);
+ return;
+ }
+
+ qpim_channel_oil_list = list_new();
+ if (!qpim_channel_oil_list) {
+ zlog_err("%s %s: failure: channel_oil_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ return;
+ }
+ qpim_channel_oil_list->del = (void (*)(void *)) pim_channel_oil_free;
+
+ qpim_upstream_list = list_new();
+ if (!qpim_upstream_list) {
+ zlog_err("%s %s: failure: upstream_list=list_new()",
+ __FILE__, __PRETTY_FUNCTION__);
+ pim_free();
+ return;
+ }
+ qpim_upstream_list->del = (void (*)(void *)) pim_upstream_free;
+
+ qpim_mroute_socket_fd = -1; /* mark mroute as disabled */
+ qpim_mroute_oif_highest_vif_index = -1;
+
+ zassert(!qpim_debugs);
+ zassert(!PIM_MROUTE_IS_ENABLED);
+
+ qpim_inaddr_any.s_addr = PIM_NET_INADDR_ANY;
+
+ /*
+ RFC 4601: 4.6.3. Assert Metrics
+
+ assert_metric
+ infinite_assert_metric() {
+ return {1,infinity,infinity,0}
+ }
+ */
+ qpim_infinite_assert_metric.rpt_bit_flag = 1;
+ qpim_infinite_assert_metric.metric_preference = PIM_ASSERT_METRIC_PREFERENCE_MAX;
+ qpim_infinite_assert_metric.route_metric = PIM_ASSERT_ROUTE_METRIC_MAX;
+ qpim_infinite_assert_metric.ip_address = qpim_inaddr_any;
+
+ pim_if_init();
+ pim_cmd_init();
+ pim_ssmpingd_init();
+}
+
+void pim_terminate()
+{
+ pim_free();
+}
diff --git a/pimd/pimd.conf.sample b/pimd/pimd.conf.sample
new file mode 100644
index 00000000..67530856
--- /dev/null
+++ b/pimd/pimd.conf.sample
@@ -0,0 +1,41 @@
+!
+! pimd sample configuration file
+! $QuaggaId: $Format:%an, %ai, %h$ $
+!
+hostname quagga-pimd-router
+password zebra
+!enable password zebra
+!
+!log file pimd.log
+log stdout
+!
+line vty
+ exec-timeout 60
+!
+!debug igmp
+!debug pim
+!debug pim zebra
+!
+ip multicast-routing
+!
+! ! You may want to enable ssmpingd for troubleshooting
+! ! See http://www.venaas.no/multicast/ssmping/
+! !
+! ip ssmpingd 1.1.1.1
+! ip ssmpingd 2.2.2.2
+!
+! ! HINTS:
+! ! - Enable "ip pim ssm" on the interface directly attached to the
+! ! multicast source host (if this is the first-hop router)
+! ! - Enable "ip pim ssm" on pim-routers-facing interfaces
+! ! - Enable "ip igmp" on IGMPv3-hosts-facing interfaces
+! ! - In order to inject IGMPv3 local membership information in the
+! ! PIM protocol state, enable both "ip pim ssm" and "ip igmp" on
+! ! the same interface; otherwise PIM won't advertise
+! ! IGMPv3-learned membership to other PIM routers
+!
+interface eth0
+ ip pim ssm
+ ip igmp
+
+! -x-
diff --git a/pimd/pimd.h b/pimd/pimd.h
new file mode 100644
index 00000000..22a29220
--- /dev/null
+++ b/pimd/pimd.h
@@ -0,0 +1,157 @@
+/*
+ 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$ $
+*/
+
+#ifndef PIMD_H
+#define PIMD_H
+
+#include <stdint.h>
+
+#include "pim_mroute.h"
+#include "pim_assert.h"
+
+#define PIMD_PROGNAME "pimd"
+#define PIMD_DEFAULT_CONFIG "pimd.conf"
+#define PIMD_VTY_PORT 2611
+#define PIMD_BUG_ADDRESS "https://github.com/udhos/qpimd"
+
+#define PIM_IP_HEADER_MIN_LEN (20)
+#define PIM_IP_HEADER_MAX_LEN (60)
+#define PIM_IP_PROTO_IGMP (2)
+#define PIM_IP_PROTO_PIM (103)
+#define PIM_IGMP_MIN_LEN (8)
+#define PIM_MSG_HEADER_LEN (4)
+#define PIM_PIM_MIN_LEN PIM_MSG_HEADER_LEN
+#define PIM_PROTO_VERSION (2)
+
+#define MCAST_ALL_SYSTEMS "224.0.0.1"
+#define MCAST_ALL_ROUTERS "224.0.0.2"
+#define MCAST_ALL_PIM_ROUTERS "224.0.0.13"
+#define MCAST_ALL_IGMP_ROUTERS "224.0.0.22"
+
+#define PIM_FORCE_BOOLEAN(expr) ((expr) != 0)
+
+#define PIM_NET_INADDR_ANY (htonl(INADDR_ANY))
+#define PIM_INADDR_IS_ANY(addr) ((addr).s_addr == PIM_NET_INADDR_ANY) /* struct in_addr addr */
+#define PIM_INADDR_ISNOT_ANY(addr) ((addr).s_addr != PIM_NET_INADDR_ANY) /* struct in_addr addr */
+
+#define PIM_MASK_PIM_EVENTS (1 << 0)
+#define PIM_MASK_PIM_PACKETS (1 << 1)
+#define PIM_MASK_PIM_PACKETDUMP_SEND (1 << 2)
+#define PIM_MASK_PIM_PACKETDUMP_RECV (1 << 3)
+#define PIM_MASK_PIM_TRACE (1 << 4)
+#define PIM_MASK_IGMP_EVENTS (1 << 5)
+#define PIM_MASK_IGMP_PACKETS (1 << 6)
+#define PIM_MASK_IGMP_TRACE (1 << 7)
+#define PIM_MASK_ZEBRA (1 << 8)
+#define PIM_MASK_SSMPINGD (1 << 9)
+#define PIM_MASK_MROUTE (1 << 10)
+#define PIM_MASK_PIM_HELLO (1 << 11)
+#define PIM_MASK_PIM_J_P (1 << 12)
+
+const char *const PIM_ALL_SYSTEMS;
+const char *const PIM_ALL_ROUTERS;
+const char *const PIM_ALL_PIM_ROUTERS;
+const char *const PIM_ALL_IGMP_ROUTERS;
+
+struct thread_master *master;
+uint32_t qpim_debugs;
+int qpim_mroute_socket_fd;
+int64_t qpim_mroute_socket_creation; /* timestamp of creation */
+struct thread *qpim_mroute_socket_reader;
+int qpim_mroute_oif_highest_vif_index;
+struct list *qpim_channel_oil_list; /* list of struct channel_oil */
+struct in_addr qpim_all_pim_routers_addr;
+int qpim_t_periodic; /* Period between Join/Prune Messages */
+struct list *qpim_upstream_list; /* list of struct pim_upstream */
+struct zclient *qpim_zclient_update;
+struct zclient *qpim_zclient_lookup;
+struct pim_assert_metric qpim_infinite_assert_metric;
+long qpim_rpf_cache_refresh_delay_msec;
+struct thread *qpim_rpf_cache_refresher;
+int64_t qpim_rpf_cache_refresh_requests;
+int64_t qpim_rpf_cache_refresh_events;
+int64_t qpim_rpf_cache_refresh_last;
+struct in_addr qpim_inaddr_any;
+struct list *qpim_ssmpingd_list; /* list of struct ssmpingd_sock */
+struct in_addr qpim_ssmpingd_group_addr;
+int64_t qpim_scan_oil_events;
+int64_t qpim_scan_oil_last;
+int64_t qpim_mroute_add_events;
+int64_t qpim_mroute_add_last;
+int64_t qpim_mroute_del_events;
+int64_t qpim_mroute_del_last;
+
+#define PIM_JP_HOLDTIME (qpim_t_periodic * 7 / 2)
+
+#define PIM_MROUTE_IS_ENABLED (qpim_mroute_socket_fd >= 0)
+#define PIM_MROUTE_IS_DISABLED (qpim_mroute_socket_fd < 0)
+
+#define PIM_DEBUG_PIM_EVENTS (qpim_debugs & PIM_MASK_PIM_EVENTS)
+#define PIM_DEBUG_PIM_PACKETS (qpim_debugs & PIM_MASK_PIM_PACKETS)
+#define PIM_DEBUG_PIM_PACKETDUMP_SEND (qpim_debugs & PIM_MASK_PIM_PACKETDUMP_SEND)
+#define PIM_DEBUG_PIM_PACKETDUMP_RECV (qpim_debugs & PIM_MASK_PIM_PACKETDUMP_RECV)
+#define PIM_DEBUG_PIM_TRACE (qpim_debugs & PIM_MASK_PIM_TRACE)
+#define PIM_DEBUG_IGMP_EVENTS (qpim_debugs & PIM_MASK_IGMP_EVENTS)
+#define PIM_DEBUG_IGMP_PACKETS (qpim_debugs & PIM_MASK_IGMP_PACKETS)
+#define PIM_DEBUG_IGMP_TRACE (qpim_debugs & PIM_MASK_IGMP_TRACE)
+#define PIM_DEBUG_ZEBRA (qpim_debugs & PIM_MASK_ZEBRA)
+#define PIM_DEBUG_SSMPINGD (qpim_debugs & PIM_MASK_SSMPINGD)
+#define PIM_DEBUG_MROUTE (qpim_debugs & PIM_MASK_MROUTE)
+#define PIM_DEBUG_PIM_HELLO (qpim_debugs & PIM_MASK_PIM_HELLO)
+#define PIM_DEBUG_PIM_J_P (qpim_debugs & PIM_MASK_PIM_J_P)
+
+#define PIM_DEBUG_EVENTS (qpim_debugs & (PIM_MASK_PIM_EVENTS | PIM_MASK_IGMP_EVENTS))
+#define PIM_DEBUG_PACKETS (qpim_debugs & (PIM_MASK_PIM_PACKETS | PIM_MASK_IGMP_PACKETS))
+#define PIM_DEBUG_TRACE (qpim_debugs & (PIM_MASK_PIM_TRACE | PIM_MASK_IGMP_TRACE))
+
+#define PIM_DO_DEBUG_PIM_EVENTS (qpim_debugs |= PIM_MASK_PIM_EVENTS)
+#define PIM_DO_DEBUG_PIM_PACKETS (qpim_debugs |= PIM_MASK_PIM_PACKETS)
+#define PIM_DO_DEBUG_PIM_PACKETDUMP_SEND (qpim_debugs |= PIM_MASK_PIM_PACKETDUMP_SEND)
+#define PIM_DO_DEBUG_PIM_PACKETDUMP_RECV (qpim_debugs |= PIM_MASK_PIM_PACKETDUMP_RECV)
+#define PIM_DO_DEBUG_PIM_TRACE (qpim_debugs |= PIM_MASK_PIM_TRACE)
+#define PIM_DO_DEBUG_IGMP_EVENTS (qpim_debugs |= PIM_MASK_IGMP_EVENTS)
+#define PIM_DO_DEBUG_IGMP_PACKETS (qpim_debugs |= PIM_MASK_IGMP_PACKETS)
+#define PIM_DO_DEBUG_IGMP_TRACE (qpim_debugs |= PIM_MASK_IGMP_TRACE)
+#define PIM_DO_DEBUG_ZEBRA (qpim_debugs |= PIM_MASK_ZEBRA)
+#define PIM_DO_DEBUG_SSMPINGD (qpim_debugs |= PIM_MASK_SSMPINGD)
+#define PIM_DO_DEBUG_MROUTE (qpim_debugs |= PIM_MASK_MROUTE)
+#define PIM_DO_DEBUG_PIM_HELLO (qpim_debugs |= PIM_MASK_PIM_HELLO)
+#define PIM_DO_DEBUG_PIM_J_P (qpim_debugs |= PIM_MASK_PIM_J_P)
+
+#define PIM_DONT_DEBUG_PIM_EVENTS (qpim_debugs &= ~PIM_MASK_PIM_EVENTS)
+#define PIM_DONT_DEBUG_PIM_PACKETS (qpim_debugs &= ~PIM_MASK_PIM_PACKETS)
+#define PIM_DONT_DEBUG_PIM_PACKETDUMP_SEND (qpim_debugs &= ~PIM_MASK_PIM_PACKETDUMP_SEND)
+#define PIM_DONT_DEBUG_PIM_PACKETDUMP_RECV (qpim_debugs &= ~PIM_MASK_PIM_PACKETDUMP_RECV)
+#define PIM_DONT_DEBUG_PIM_TRACE (qpim_debugs &= ~PIM_MASK_PIM_TRACE)
+#define PIM_DONT_DEBUG_IGMP_EVENTS (qpim_debugs &= ~PIM_MASK_IGMP_EVENTS)
+#define PIM_DONT_DEBUG_IGMP_PACKETS (qpim_debugs &= ~PIM_MASK_IGMP_PACKETS)
+#define PIM_DONT_DEBUG_IGMP_TRACE (qpim_debugs &= ~PIM_MASK_IGMP_TRACE)
+#define PIM_DONT_DEBUG_ZEBRA (qpim_debugs &= ~PIM_MASK_ZEBRA)
+#define PIM_DONT_DEBUG_SSMPINGD (qpim_debugs &= ~PIM_MASK_SSMPINGD)
+#define PIM_DONT_DEBUG_MROUTE (qpim_debugs &= ~PIM_MASK_MROUTE)
+#define PIM_DONT_DEBUG_PIM_HELLO (qpim_debugs &= ~PIM_MASK_PIM_HELLO)
+#define PIM_DONT_DEBUG_PIM_J_P (qpim_debugs &= ~PIM_MASK_PIM_J_P)
+
+void pim_init(void);
+void pim_terminate(void);
+
+#endif /* PIMD_H */
diff --git a/pimd/quagga-bootstrap.sh b/pimd/quagga-bootstrap.sh
new file mode 100755
index 00000000..4ec443d9
--- /dev/null
+++ b/pimd/quagga-bootstrap.sh
@@ -0,0 +1,23 @@
+#! /bin/bash
+#
+# Bootstrap Quagga autotools for pimd.
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-bootstrap.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+me=`basename $0`
+msg () {
+ echo >&2 $me: $*
+}
+
+if [ -f ./bootstrap.sh ]; then
+ msg found ./bootstrap.sh from quagga
+ ./bootstrap.sh
+else
+ msg missing ./bootstrap.sh from quagga
+ #autoreconf -i --force
+ #bootstrap from tarball prefers autoreconf -i
+ autoreconf -i
+fi
diff --git a/pimd/quagga-build-no-vtysh.sh b/pimd/quagga-build-no-vtysh.sh
new file mode 100755
index 00000000..7136f670
--- /dev/null
+++ b/pimd/quagga-build-no-vtysh.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+#
+# Build minimum Quagga needed for pimd.
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-build-no-vtysh.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+./pimd/quagga-memtypes.sh && ./pimd/quagga-bootstrap.sh && ./pimd/quagga-configure-no-vtysh.sh && make
diff --git a/pimd/quagga-build.sh b/pimd/quagga-build.sh
new file mode 100755
index 00000000..2ad476d0
--- /dev/null
+++ b/pimd/quagga-build.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+#
+# Build minimum Quagga needed for pimd.
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-build.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+./pimd/quagga-memtypes.sh && ./pimd/quagga-bootstrap.sh && ./pimd/quagga-configure.sh && make
diff --git a/pimd/quagga-configure-no-vtysh.sh b/pimd/quagga-configure-no-vtysh.sh
new file mode 100755
index 00000000..b3052dce
--- /dev/null
+++ b/pimd/quagga-configure-no-vtysh.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+#
+# Configure for minimum Quagga build needed for pimd.
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-configure-no-vtysh.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+./configure --disable-babeld --disable-bgpd --disable-ripd --disable-ripngd --disable-ospfd --disable-ospf6d --disable-watchquagga --disable-bgp-announce --disable-ospfapi --disable-ospfclient --disable-rtadv --disable-irdp --enable-pimd --enable-tcp-zebra --enable-ipv6
diff --git a/pimd/quagga-configure.sh b/pimd/quagga-configure.sh
new file mode 100755
index 00000000..72329eb9
--- /dev/null
+++ b/pimd/quagga-configure.sh
@@ -0,0 +1,10 @@
+#! /bin/bash
+#
+# Configure for minimum Quagga build needed for pimd.
+#
+# Run from quagga's top dir as:
+# . pimd/quagga-configure.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+tail -1 ./pimd/quagga-configure-no-vtysh.sh --enable-vtysh
diff --git a/pimd/quagga-git-add.sh b/pimd/quagga-git-add.sh
new file mode 100755
index 00000000..3824e984
--- /dev/null
+++ b/pimd/quagga-git-add.sh
@@ -0,0 +1,12 @@
+#! /bin/bash
+#
+# Add to git new files created by qpimd patch
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-git-add.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+chmod a+rx pimd/*.sh
+git add doc/pimd.8
+git add pimd
diff --git a/pimd/quagga-memtypes.sh b/pimd/quagga-memtypes.sh
new file mode 100755
index 00000000..e86f414b
--- /dev/null
+++ b/pimd/quagga-memtypes.sh
@@ -0,0 +1,22 @@
+#! /bin/bash
+#
+# Check lib/memtypes.h from Quagga
+#
+# Run from quagga's top dir as:
+# ./pimd/quagga-memtypes.sh
+#
+# $QuaggaId: $Format:%an, %ai, %h$ $
+
+me=`basename $0`
+msg () {
+ echo >&2 $me: $*
+}
+
+memtypes_h=lib/memtypes.h
+if [ -e $memtypes_h ]; then
+ memtypes_h_size=`ls -s $memtypes_h | cut -d' ' -f1`
+ if [ "$memtypes_h_size" -lt 1 ]; then
+ msg WARNING: removing empty file: $memtypes_h -- awk failed?
+ rm $memtypes_h
+ fi
+fi
diff --git a/pimd/test_igmpv3_join.c b/pimd/test_igmpv3_join.c
new file mode 100644
index 00000000..fe64fbc0
--- /dev/null
+++ b/pimd/test_igmpv3_join.c
@@ -0,0 +1,149 @@
+/*
+ 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 <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+
+#include "pim_igmp_join.h"
+
+const char *prog_name = 0;
+
+static int iface_solve_index(const char *ifname)
+{
+ struct if_nameindex *ini;
+ int ifindex = -1;
+ int i;
+
+ if (!ifname)
+ return -1;
+
+ ini = if_nameindex();
+ if (!ini) {
+ int err = errno;
+ fprintf(stderr,
+ "%s: interface=%s: failure solving index: errno=%d: %s\n",
+ prog_name, ifname, err, strerror(err));
+ errno = err;
+ return -1;
+ }
+
+ for (i = 0; ini[i].if_index; ++i) {
+#if 0
+ fprintf(stderr,
+ "%s: interface=%s matching against local ifname=%s ifindex=%d\n",
+ prog_name, ifname, ini[i].if_name, ini[i].if_index);
+#endif
+ if (!strcmp(ini[i].if_name, ifname)) {
+ ifindex = ini[i].if_index;
+ break;
+ }
+ }
+
+ if_freenameindex(ini);
+
+ return ifindex;
+}
+
+int main(int argc, const char *argv[])
+{
+ struct in_addr group_addr;
+ struct in_addr source_addr;
+ const char *ifname;
+ const char *group;
+ const char *source;
+ int ifindex;
+ int result;
+ int fd;
+
+ prog_name = argv[0];
+
+ fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ fprintf(stderr,
+ "%s: could not create socket: socket(): errno=%d: %s\n",
+ prog_name, errno, strerror(errno));
+ exit(1);
+ }
+
+ if (argc != 4) {
+ fprintf(stderr,
+ "usage: %s interface group source\n"
+ "example: %s eth0 232.1.1.1 1.1.1.1\n",
+ prog_name, prog_name);
+ exit(1);
+ }
+
+ ifname = argv[1];
+ group = argv[2];
+ source = argv[3];
+
+ ifindex = iface_solve_index(ifname);
+ if (ifindex < 0) {
+ fprintf(stderr, "%s: could not find interface: %s\n",
+ prog_name, ifname);
+ exit(1);
+ }
+
+ result = inet_pton(AF_INET, group, &group_addr);
+ if (result <= 0) {
+ fprintf(stderr, "%s: bad group address: %s\n",
+ prog_name, group);
+ exit(1);
+ }
+
+ result = inet_pton(AF_INET, source, &source_addr);
+ if (result <= 0) {
+ fprintf(stderr, "%s: bad source address: %s\n",
+ prog_name, source);
+ exit(1);
+ }
+
+ result = pim_igmp_join_source(fd, ifindex, group_addr, source_addr);
+ if (result) {
+ fprintf(stderr,
+ "%s: setsockopt(fd=%d) failure for IGMP group %s source %s ifindex %d on interface %s: errno=%d: %s\n",
+ prog_name, fd, group, source, ifindex, ifname,
+ errno, strerror(errno));
+ exit(1);
+ }
+
+ printf("%s: joined channel (S,G)=(%s,%s) on interface %s\n",
+ prog_name, source, group, ifname);
+
+ printf("%s: waiting...\n", prog_name);
+
+ getchar();
+
+ close(fd);
+
+ printf("%s: left channel (S,G)=(%s,%s) on interface %s\n",
+ prog_name, source, group, ifname);
+
+ exit(0);
+}
diff --git a/ports/Makefile b/ports/Makefile
index d085d06e..86f77bd0 100644
--- a/ports/Makefile
+++ b/ports/Makefile
@@ -54,5 +54,6 @@ post-install:
@echo " ripngd 2603/tcp # RIPngd vty";
@echo " ospfd 2604/tcp # OSPFd vty";
@echo " bgpd 2605/tcp # BGPd vty";
+ @echo " pimd 2611/tcp # PIMd vty";
.include <bsd.port.mk>
diff --git a/ports/pkg/DESCR b/ports/pkg/DESCR
index 0c8d01b7..aeb1950e 100644
--- a/ports/pkg/DESCR
+++ b/ports/pkg/DESCR
@@ -47,6 +47,7 @@ ripd 2602/tcp # RIPd vty
ripngd 2603/tcp # RIPngd vty
ospfd 2604/tcp # OSPFd vty
bgpd 2605/tcp # BGPd vty
+pimd 2611/tcp # PIMd vty
I recommend you to add upper list to /etc/services.
diff --git a/redhat/quagga.spec.in b/redhat/quagga.spec.in
index 0ce25ca3..75835bbe 100644
--- a/redhat/quagga.spec.in
+++ b/redhat/quagga.spec.in
@@ -303,6 +303,9 @@ zebra_spec_add_service ospfapi 2607/tcp "OSPF-API"
%if %{with_isisd}
zebra_spec_add_service isisd 2608/tcp "ISISd vty"
%endif
+%if %{with_pimd}
+zebra_spec_add_service pimd 2611/tcp "PIMd vty"
+%endif
for daemon in %daemon_list ; do
/sbin/chkconfig --add ${daemon}
diff --git a/vtysh/Makefile.am b/vtysh/Makefile.am
index 5c325ec2..f9dea2de 100644
--- a/vtysh/Makefile.am
+++ b/vtysh/Makefile.am
@@ -25,6 +25,7 @@ vtysh_cmd_FILES = $(top_srcdir)/bgpd/*.c $(top_srcdir)/isisd/*.c \
$(top_srcdir)/ospfd/*.c $(top_srcdir)/ospf6d/*.c \
$(top_srcdir)/ripd/*.c $(top_srcdir)/ripngd/*.c \
$(top_srcdir)/babeld/*.c \
+ $(top_srcdir)/pimd/pim_cmd.c \
$(top_srcdir)/lib/keychain.c $(top_srcdir)/lib/routemap.c \
$(top_srcdir)/lib/filter.c $(top_srcdir)/lib/plist.c \
$(top_srcdir)/lib/distribute.c $(top_srcdir)/lib/if_rmap.c \
diff --git a/vtysh/vtysh.c b/vtysh/vtysh.c
index 89b9b257..984f8d39 100644
--- a/vtysh/vtysh.c
+++ b/vtysh/vtysh.c
@@ -59,6 +59,7 @@ struct vtysh_client
{ .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH},
{ .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH},
{ .fd = -1, .name = "babeld", .flag = VTYSH_BABELD, .path = BABEL_VTYSH_PATH},
+ { .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH},
};
diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h
index 3cc7bafe..1681a71a 100644
--- a/vtysh/vtysh.h
+++ b/vtysh/vtysh.h
@@ -30,9 +30,10 @@
#define VTYSH_BGPD 0x20
#define VTYSH_ISISD 0x40
#define VTYSH_BABELD 0x80
-#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD
+#define VTYSH_PIMD 0x100
+#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_BABELD
-#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD
+#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_BABELD|VTYSH_PIMD
/* vtysh local configuration file. */
#define VTYSH_DEFAULT_CONFIG "vtysh.conf"