From 12e41d03bd49a60da9238694b5d8bd0bde3eff97 Mon Sep 17 00:00:00 2001 From: David Lamparter Date: Wed, 4 Feb 2015 07:01:14 +0100 Subject: [PATCH] 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 " (which is the e-mail address listed in the associated Savannah ticket) Signed-off-by: David Lamparter --- Makefile.am | 4 +- SERVICES | 1 + configure.ac | 55 +- doc/Makefile.am | 6 +- doc/install.texi | 1 + doc/pimd.8 | 125 + lib/command.c | 2 + lib/command.h | 1 + lib/log.c | 1 + lib/log.h | 1 + lib/memory.c | 11 + lib/memtypes.c | 16 + lib/route_types.txt | 2 + lib/thread.h | 1 - lib/vty.c | 2 + lib/zclient.c | 7 +- lib/zclient.h | 1 + pimd/.gitignore | 16 + pimd/AUTHORS | 9 + pimd/CAVEATS | 178 ++ pimd/COMMANDS | 81 + pimd/COPYING | 340 +++ pimd/DEBUG | 86 + pimd/LINUX_KERNEL_MROUTE_MFC | 26 + pimd/Makefile.am | 76 + pimd/README | 164 ++ pimd/TODO | 426 +++ pimd/TROUBLESHOOTING | 33 + pimd/WHY_SSM | 32 + pimd/git-clone-github.sh | 27 + pimd/git-clone-savannah.sh | 27 + pimd/pim_assert.c | 808 ++++++ pimd/pim_assert.h | 75 + pimd/pim_cmd.c | 4500 +++++++++++++++++++++++++++++ pimd/pim_cmd.h | 66 + pimd/pim_hello.c | 529 ++++ pimd/pim_hello.h | 46 + pimd/pim_iface.c | 1229 ++++++++ pimd/pim_iface.h | 161 ++ pimd/pim_ifchannel.c | 893 ++++++ pimd/pim_ifchannel.h | 145 + pimd/pim_igmp.c | 1433 +++++++++ pimd/pim_igmp.h | 176 ++ pimd/pim_igmp_join.c | 67 + pimd/pim_igmp_join.h | 32 + pimd/pim_igmpv3.c | 1729 +++++++++++ pimd/pim_igmpv3.h | 100 + pimd/pim_int.c | 44 + pimd/pim_int.h | 31 + pimd/pim_join.c | 445 +++ pimd/pim_join.h | 43 + pimd/pim_macro.c | 437 +++ pimd/pim_macro.h | 44 + pimd/pim_main.c | 294 ++ pimd/pim_mroute.c | 451 +++ pimd/pim_mroute.h | 173 ++ pimd/pim_msg.c | 106 + pimd/pim_msg.h | 52 + pimd/pim_neighbor.c | 719 +++++ pimd/pim_neighbor.h | 74 + pimd/pim_oil.c | 140 + pimd/pim_oil.h | 53 + pimd/pim_pim.c | 742 +++++ pimd/pim_pim.h | 71 + pimd/pim_rand.c | 60 + pimd/pim_rand.h | 30 + pimd/pim_rpf.c | 260 ++ pimd/pim_rpf.h | 36 + pimd/pim_signals.c | 86 + pimd/pim_signals.h | 28 + pimd/pim_sock.c | 389 +++ pimd/pim_sock.h | 57 + pimd/pim_ssmpingd.c | 448 +++ pimd/pim_ssmpingd.h | 45 + pimd/pim_str.c | 46 + pimd/pim_str.h | 32 + pimd/pim_time.c | 165 ++ pimd/pim_time.h | 40 + pimd/pim_tlv.c | 721 +++++ pimd/pim_tlv.h | 133 + pimd/pim_upstream.c | 683 +++++ pimd/pim_upstream.h | 122 + pimd/pim_util.c | 122 + pimd/pim_util.h | 37 + pimd/pim_version.c | 25 + pimd/pim_version.h | 30 + pimd/pim_vty.c | 178 ++ pimd/pim_vty.h | 32 + pimd/pim_zebra.c | 1312 +++++++++ pimd/pim_zebra.h | 42 + pimd/pim_zlookup.c | 457 +++ pimd/pim_zlookup.h | 47 + pimd/pimd.c | 141 + pimd/pimd.conf.sample | 41 + pimd/pimd.h | 157 + pimd/quagga-bootstrap.sh | 23 + pimd/quagga-build-no-vtysh.sh | 10 + pimd/quagga-build.sh | 10 + pimd/quagga-configure-no-vtysh.sh | 10 + pimd/quagga-configure.sh | 10 + pimd/quagga-git-add.sh | 12 + pimd/quagga-memtypes.sh | 22 + pimd/test_igmpv3_join.c | 149 + ports/Makefile | 1 + ports/pkg/DESCR | 1 + redhat/quagga.spec.in | 3 + vtysh/Makefile.am | 1 + vtysh/vtysh.c | 1 + vtysh/vtysh.h | 8 +- 109 files changed, 23920 insertions(+), 9 deletions(-) create mode 100644 doc/pimd.8 create mode 100644 pimd/.gitignore create mode 100644 pimd/AUTHORS create mode 100644 pimd/CAVEATS create mode 100644 pimd/COMMANDS create mode 100644 pimd/COPYING create mode 100644 pimd/DEBUG create mode 100644 pimd/LINUX_KERNEL_MROUTE_MFC create mode 100644 pimd/Makefile.am create mode 100644 pimd/README create mode 100644 pimd/TODO create mode 100644 pimd/TROUBLESHOOTING create mode 100644 pimd/WHY_SSM create mode 100755 pimd/git-clone-github.sh create mode 100755 pimd/git-clone-savannah.sh create mode 100644 pimd/pim_assert.c create mode 100644 pimd/pim_assert.h create mode 100644 pimd/pim_cmd.c create mode 100644 pimd/pim_cmd.h create mode 100644 pimd/pim_hello.c create mode 100644 pimd/pim_hello.h create mode 100644 pimd/pim_iface.c create mode 100644 pimd/pim_iface.h create mode 100644 pimd/pim_ifchannel.c create mode 100644 pimd/pim_ifchannel.h create mode 100644 pimd/pim_igmp.c create mode 100644 pimd/pim_igmp.h create mode 100644 pimd/pim_igmp_join.c create mode 100644 pimd/pim_igmp_join.h create mode 100644 pimd/pim_igmpv3.c create mode 100644 pimd/pim_igmpv3.h create mode 100644 pimd/pim_int.c create mode 100644 pimd/pim_int.h create mode 100644 pimd/pim_join.c create mode 100644 pimd/pim_join.h create mode 100644 pimd/pim_macro.c create mode 100644 pimd/pim_macro.h create mode 100644 pimd/pim_main.c create mode 100644 pimd/pim_mroute.c create mode 100644 pimd/pim_mroute.h create mode 100644 pimd/pim_msg.c create mode 100644 pimd/pim_msg.h create mode 100644 pimd/pim_neighbor.c create mode 100644 pimd/pim_neighbor.h create mode 100644 pimd/pim_oil.c create mode 100644 pimd/pim_oil.h create mode 100644 pimd/pim_pim.c create mode 100644 pimd/pim_pim.h create mode 100644 pimd/pim_rand.c create mode 100644 pimd/pim_rand.h create mode 100644 pimd/pim_rpf.c create mode 100644 pimd/pim_rpf.h create mode 100644 pimd/pim_signals.c create mode 100644 pimd/pim_signals.h create mode 100644 pimd/pim_sock.c create mode 100644 pimd/pim_sock.h create mode 100644 pimd/pim_ssmpingd.c create mode 100644 pimd/pim_ssmpingd.h create mode 100644 pimd/pim_str.c create mode 100644 pimd/pim_str.h create mode 100644 pimd/pim_time.c create mode 100644 pimd/pim_time.h create mode 100644 pimd/pim_tlv.c create mode 100644 pimd/pim_tlv.h create mode 100644 pimd/pim_upstream.c create mode 100644 pimd/pim_upstream.h create mode 100644 pimd/pim_util.c create mode 100644 pimd/pim_util.h create mode 100644 pimd/pim_version.c create mode 100644 pimd/pim_version.h create mode 100644 pimd/pim_vty.c create mode 100644 pimd/pim_vty.h create mode 100644 pimd/pim_zebra.c create mode 100644 pimd/pim_zebra.h create mode 100644 pimd/pim_zlookup.c create mode 100644 pimd/pim_zlookup.h create mode 100644 pimd/pimd.c create mode 100644 pimd/pimd.conf.sample create mode 100644 pimd/pimd.h create mode 100755 pimd/quagga-bootstrap.sh create mode 100755 pimd/quagga-build-no-vtysh.sh create mode 100755 pimd/quagga-build.sh create mode 100755 pimd/quagga-configure-no-vtysh.sh create mode 100755 pimd/quagga-configure.sh create mode 100755 pimd/quagga-git-add.sh create mode 100755 pimd/quagga-memtypes.sh create mode 100644 pimd/test_igmpv3_join.c diff --git a/Makefile.am b/Makefile.am index 7e01a22d1..1b97d58c4 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@ \ - @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 \ 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 9c3546bc8..c69d0c1a7 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 2ad3cc2e3..a3a58df86 100755 --- a/configure.ac +++ b/configure.ac @@ -236,6 +236,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, @@ -1382,6 +1384,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";; @@ -1404,6 +1413,7 @@ AC_SUBST(OSPFD) AC_SUBST(OSPF6D) AC_SUBST(WATCHQUAGGA) AC_SUBST(ISISD) +AC_SUBST(PIMD) AC_SUBST(SOLARIS) AC_SUBST(VTYSH) AC_SUBST(INCLUDES) @@ -1486,7 +1496,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], @@ -1515,6 +1526,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 ], [ + 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 ], [ + 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 ], [ + 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 -------------------------------------- @@ -1687,6 +1737,7 @@ AC_DEFINE_UNQUOTED(PATH_BGPD_PID, "$quagga_statedir/bgpd.pid",bgpd PID) 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_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) @@ -1696,6 +1747,7 @@ AC_DEFINE_UNQUOTED(BGP_VTYSH_PATH, "$quagga_statedir/bgpd.vty",bgpd vty socket) AC_DEFINE_UNQUOTED(OSPF_VTYSH_PATH, "$quagga_statedir/ospfd.vty",ospfd vty socket) AC_DEFINE_UNQUOTED(OSPF6_VTYSH_PATH, "$quagga_statedir/ospf6d.vty",ospf6d 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 ------------------------------- @@ -1721,6 +1773,7 @@ AC_CONFIG_FILES([Makefile lib/Makefile zebra/Makefile ripd/Makefile ripngd/Makefile bgpd/Makefile ospfd/Makefile watchquagga/Makefile ospf6d/Makefile isisd/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 96aee59d5..5ba103f2f 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -63,6 +63,10 @@ quagga_TEXINFOS = appendix.texi basic.texi bgpd.texi filter.texi \ man_MANS = quagga.1 +if PIMD +man_MANS += pimd.8 +endif + if BGPD man_MANS += bgpd.8 endif @@ -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 e958d845f..7349e92cb 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 000000000..0dd170a2c --- /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 4e8e24f49..a71cb5ddd 100644 --- a/lib/command.c +++ b/lib/command.c @@ -2943,6 +2943,7 @@ DEFUN (config_exit, case KEYCHAIN_NODE: case MASC_NODE: case RMAP_NODE: + case PIM_NODE: case VTY_NODE: vty->node = CONFIG_NODE; break; @@ -3000,6 +3001,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 20bdb38a9..7051e1ca5 100644 --- a/lib/command.h +++ b/lib/command.h @@ -89,6 +89,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 a8577bf9a..5d673e216 100644 --- a/lib/log.c +++ b/lib/log.c @@ -50,6 +50,7 @@ const char *zlog_proto_names[] = "RIPNG", "OSPF6", "ISIS", + "PIM", "MASC", NULL, }; diff --git a/lib/log.h b/lib/log.h index e09d747ee..f22be2318 100644 --- a/lib/log.h +++ b/lib/log.h @@ -52,6 +52,7 @@ typedef enum ZLOG_RIPNG, ZLOG_OSPF6, ZLOG_ISIS, + ZLOG_PIM, ZLOG_MASC } zlog_proto_t; diff --git a/lib/memory.c b/lib/memory.c index 172ddfc49..93ff702ca 100644 --- a/lib/memory.c +++ b/lib/memory.c @@ -416,6 +416,17 @@ DEFUN (show_memory, } +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) { diff --git a/lib/memtypes.c b/lib/memtypes.c index d77ba0e97..65f32a849 100644 --- a/lib/memtypes.c +++ b/lib/memtypes.c @@ -271,6 +271,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", }, @@ -287,5 +302,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 605d4b134..8fc3092ca 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_TABLE, "Non-main Kernel Routing Table" diff --git a/lib/thread.h b/lib/thread.h index 4ca1e3ce0..853ef3fb7 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 * * This file is part of GNU Zebra. * diff --git a/lib/vty.c b/lib/vty.c index 105d55927..146a20fb0 100644 --- a/lib/vty.c +++ b/lib/vty.c @@ -714,6 +714,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 d86b576c1..0cb3c63db 100644 --- a/lib/zclient.c +++ b/lib/zclient.c @@ -260,7 +260,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; } @@ -1582,6 +1582,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 ad82a21f3..39bc5d674 100644 --- a/lib/zclient.h +++ b/lib/zclient.h @@ -167,6 +167,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); extern int redist_check_instance (struct redist_proto *, u_short); extern void redist_add_instance (struct redist_proto *, u_short); diff --git a/pimd/.gitignore b/pimd/.gitignore new file mode 100644 index 000000000..51a2ac87e --- /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 000000000..f6135a410 --- /dev/null +++ b/pimd/AUTHORS @@ -0,0 +1,9 @@ +# $QuaggaId: $Format:%an, %ai, %h$ $ + +# Everton da Silva Marques +$ 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 000000000..9f07bda66 --- /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 000000000..425ac8229 --- /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 000000000..3912109b5 --- /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. + + + Copyright (C) + + 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. + + , 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 000000000..72fb8264b --- /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 000000000..e87e567f9 --- /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 000000000..7173a231a --- /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 000000000..1e3f72c83 --- /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 000000000..2308573b8 --- /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 + + # 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 + 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 000000000..7d1f52de8 --- /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 000000000..2e8c966f1 --- /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 000000000..ae2362a73 --- /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 000000000..1aad51bb2 --- /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 000000000..ad21e08ef --- /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 + +#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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", ch->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", ch->ifassert_winner, was_str, sizeof(was_str)); + pim_inet4_dump("", 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_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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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_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_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_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("", src_addr, neigh_str, sizeof(neigh_str)); + pim_inet4_dump("", msg_source_addr.u.prefix4, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", 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("", 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("", ch->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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 000000000..bd3fb3e25 --- /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 + +#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 000000000..6b2ac6646 --- /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 + +#include + +#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->source_addr, + ch_src_str, sizeof(ch_src_str)); + pim_inet4_dump("", ch->group_addr, + ch_grp_str, sizeof(ch_grp_str)); + pim_inet4_dump("", 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->source_addr, + ch_src_str, sizeof(ch_src_str)); + pim_inet4_dump("", 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->source_addr, + ch_src_str, sizeof(ch_src_str)); + pim_inet4_dump("", ch->group_addr, + ch_grp_str, sizeof(ch_grp_str)); + pim_inet4_dump("", 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->source_addr, + ch_src_str, sizeof(ch_src_str)); + pim_inet4_dump("", ch->group_addr, + ch_grp_str, sizeof(ch_grp_str)); + pim_inet4_dump("", 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->source_addr, + ch_src_str, sizeof(ch_src_str)); + pim_inet4_dump("", 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_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("", ij->group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_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("", 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("", 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->source_addr, + ch_src_str, sizeof(ch_src_str)); + pim_inet4_dump("", 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("", 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("", 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("", 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("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", rpf->source_nexthop.mrib_nexthop_addr, rpf_nexthop_str, sizeof(rpf_nexthop_str)); + pim_inet4_dump("", rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str)); + + rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""; + + 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", rpf->rpf_addr, rpf_addr_str, sizeof(rpf_addr_str)); + pim_inet4_dump("", rpf->source_nexthop.mrib_nexthop_addr, rib_nexthop_str, sizeof(rib_nexthop_str)); + + rpf_ifname = rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""; + + 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", 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("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); + pim_inet4_dump("", 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, "%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, "%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("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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 : "", + c_oil->oil.mfcc_parent, + ifp_out ? ifp_out->name : "", + 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("", c_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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.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 : "", + 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("", 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("", 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("", 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 000000000..c50374009 --- /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 000000000..128578318 --- /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 + +#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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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 000000000..b5e272d5d --- /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 + +#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 000000000..ecf9ef6bc --- /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 + +#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_addr, old_str, sizeof(old_str)); + pim_inet4_dump("", 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_prim_addr, new_prim_str, sizeof(new_prim_str)); + pim_inet4_dump("", 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_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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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 000000000..4b06b9ff2 --- /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 + +#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 000000000..e253a0ea9 --- /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 + +#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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", upstream, up_str, sizeof(up_str)); + pim_inet4_dump("", 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, up_str, sizeof(up_str)); + pim_inet4_dump("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", ch->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", ch->ifassert_my_metric.ip_address, old_addr_str, sizeof(old_addr_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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 000000000..e6f1c2947 --- /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 + +#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 000000000..4fd3edcb8 --- /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 + +#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("", 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("", 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("", 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("", 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_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("", 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("", 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_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, ""); + + 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_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("", ip_hdr->ip_src, from_str , sizeof(from_str)); + pim_inet4_dump("", 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("", 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("", 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("", igmp->ifaddr, querier_str, + sizeof(querier_str)); + pim_inet4_dump("", 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, ""); + if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str))) + sprintf(to_str, ""); + + 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, ""); + if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str))) + sprintf(to_str, ""); + + 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 : "", + 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_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_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_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_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_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 000000000..d45f223ba --- /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 + +#include +#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 000000000..693a42b75 --- /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 +#include +#include + +#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 000000000..1127af120 --- /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 + +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 000000000..3baddbfae --- /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 +#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_str, sizeof(from_str)); + pim_inet4_dump("", 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_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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_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_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_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_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_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_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_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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", 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_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", 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_addr, dst_str, sizeof(dst_str)); + pim_inet4_dump("", 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 000000000..bb7e92672 --- /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 +#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 000000000..2ff1a116e --- /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 +#include + +#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 000000000..d64b10327 --- /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 + +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 000000000..9d8e0012e --- /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 + +#include "log.h" +#include "prefix.h" + +#include "pimd.h" +#include "pim_str.h" +#include "pim_tlv.h" +#include "pim_msg.h" +#include "pim_pim.h" +#include "pim_join.h" +#include "pim_iface.h" +#include "pim_hello.h" +#include "pim_ifchannel.h" + +static void on_trace(const char *label, + struct interface *ifp, struct in_addr src) +{ + if (PIM_DEBUG_PIM_TRACE) { + char src_str[100]; + pim_inet4_dump("", src, src_str, sizeof(src_str)); + zlog_debug("%s: from %s on %s", + label, src_str, ifp->name); + } +} + +static void recv_join(struct interface *ifp, + struct pim_neighbor *neigh, + uint16_t holdtime, + struct in_addr upstream, + struct in_addr group, + struct in_addr source, + uint8_t source_flags) +{ + if (PIM_DEBUG_PIM_TRACE) { + char up_str[100]; + char src_str[100]; + char grp_str[100]; + char neigh_str[100]; + pim_inet4_dump("", upstream, up_str, sizeof(up_str)); + pim_inet4_dump("", source, src_str, sizeof(src_str)); + pim_inet4_dump("", group, grp_str, sizeof(grp_str)); + pim_inet4_dump("", neigh->source_addr, neigh_str, sizeof(neigh_str)); + zlog_warn("%s: join (S,G)=(%s,%s) rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s", + __PRETTY_FUNCTION__, + src_str, grp_str, + source_flags & PIM_RPT_BIT_MASK, + source_flags & PIM_WILDCARD_BIT_MASK, + up_str, holdtime, neigh_str, ifp->name); + } + + /* Restart join expiry timer */ + pim_ifchannel_join_add(ifp, neigh->source_addr, upstream, + source, group, source_flags, holdtime); +} + +static void recv_prune(struct interface *ifp, + struct pim_neighbor *neigh, + uint16_t holdtime, + struct in_addr upstream, + struct in_addr group, + struct in_addr source, + uint8_t source_flags) +{ + if (PIM_DEBUG_PIM_TRACE) { + char up_str[100]; + char src_str[100]; + char grp_str[100]; + char neigh_str[100]; + pim_inet4_dump("", upstream, up_str, sizeof(up_str)); + pim_inet4_dump("", source, src_str, sizeof(src_str)); + pim_inet4_dump("", group, grp_str, sizeof(grp_str)); + pim_inet4_dump("", neigh->source_addr, neigh_str, sizeof(neigh_str)); + zlog_warn("%s: prune (S,G)=(%s,%s) rpt=%d wc=%d upstream=%s holdtime=%d from %s on %s", + __PRETTY_FUNCTION__, + src_str, grp_str, + source_flags & PIM_RPT_BIT_MASK, + source_flags & PIM_WILDCARD_BIT_MASK, + up_str, holdtime, neigh_str, ifp->name); + } + + pim_ifchannel_prune(ifp, upstream, source, group, source_flags, holdtime); +} + +int pim_joinprune_recv(struct interface *ifp, + struct pim_neighbor *neigh, + struct in_addr src_addr, + uint8_t *tlv_buf, int tlv_buf_size) +{ + struct prefix msg_upstream_addr; + uint8_t msg_num_groups; + uint16_t msg_holdtime; + int addr_offset; + uint8_t *buf; + uint8_t *pastend; + int remain; + int group; + + on_trace(__PRETTY_FUNCTION__, ifp, src_addr); + + buf = tlv_buf; + pastend = tlv_buf + tlv_buf_size; + + /* + Parse ucast addr + */ + addr_offset = pim_parse_addr_ucast(ifp->name, src_addr, + &msg_upstream_addr, + buf, pastend - buf); +#if 0 + zlog_warn("%s: pim_parse_addr_ucast addr_offset=%d", + __PRETTY_FUNCTION__, + addr_offset); +#endif + if (addr_offset < 1) { + char src_str[100]; + pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); + zlog_warn("%s: pim_parse_addr_ucast() failure: from %s on %s", + __PRETTY_FUNCTION__, + src_str, ifp->name); + return -1; + } + buf += addr_offset; + + /* + Check upstream address family + */ + if (msg_upstream_addr.family != AF_INET) { + if (PIM_DEBUG_PIM_TRACE) { + char src_str[100]; + pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); + zlog_warn("%s: ignoring join/prune directed to unexpected addr family=%d from %s on %s", + __PRETTY_FUNCTION__, + msg_upstream_addr.family, src_str, ifp->name); + } + return -2; + } + + remain = pastend - buf; + if (remain < 4) { + char src_str[100]; + pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); + zlog_warn("%s: short join/prune message buffer for group list: size=%d minimum=%d from %s on %s", + __PRETTY_FUNCTION__, + remain, 4, src_str, ifp->name); + return -4; + } + + ++buf; /* skip reserved byte */ + msg_num_groups = *(const uint8_t *) buf; + ++buf; + msg_holdtime = ntohs(*(const uint16_t *) buf); + ++buf; + ++buf; + + if (PIM_DEBUG_PIM_TRACE) { + char src_str[100]; + char upstream_str[100]; + pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", msg_upstream_addr.u.prefix4, + upstream_str, sizeof(upstream_str)); + zlog_warn("%s: join/prune upstream=%s groups=%d holdtime=%d from %s on %s", + __PRETTY_FUNCTION__, + upstream_str, msg_num_groups, msg_holdtime, + src_str, ifp->name); + } + + /* Scan groups */ + for (group = 0; group < msg_num_groups; ++group) { + struct prefix msg_group_addr; + struct prefix msg_source_addr; + uint8_t msg_source_flags; + uint16_t msg_num_joined_sources; + uint16_t msg_num_pruned_sources; + int source; + + addr_offset = pim_parse_addr_group(ifp->name, src_addr, + &msg_group_addr, + buf, pastend - buf); +#if 0 + zlog_warn("%s: pim_parse_addr_group addr_offset=%d", + __PRETTY_FUNCTION__, + addr_offset); +#endif + if (addr_offset < 1) { + return -5; + } + buf += addr_offset; + + remain = pastend - buf; + if (remain < 4) { + char src_str[100]; + pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); + zlog_warn("%s: short join/prune buffer for source list: size=%d minimum=%d from %s on %s", + __PRETTY_FUNCTION__, + remain, 4, src_str, ifp->name); + return -6; + } + + msg_num_joined_sources = ntohs(*(const uint16_t *) buf); + buf += 2; + msg_num_pruned_sources = ntohs(*(const uint16_t *) buf); + buf += 2; + + if (PIM_DEBUG_PIM_TRACE) { + char src_str[100]; + char upstream_str[100]; + char group_str[100]; + pim_inet4_dump("", src_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", msg_upstream_addr.u.prefix4, + upstream_str, sizeof(upstream_str)); + pim_inet4_dump("", msg_group_addr.u.prefix4, + group_str, sizeof(group_str)); + zlog_warn("%s: join/prune upstream=%s group=%s/%d join_src=%d prune_src=%d from %s on %s", + __PRETTY_FUNCTION__, + upstream_str, group_str, msg_group_addr.prefixlen, + msg_num_joined_sources, msg_num_pruned_sources, + src_str, ifp->name); + } + + /* Scan joined sources */ + for (source = 0; source < msg_num_joined_sources; ++source) { + addr_offset = pim_parse_addr_source(ifp->name, src_addr, + &msg_source_addr, + &msg_source_flags, + buf, pastend - buf); +#if 0 + zlog_warn("%s: pim_parse_addr_source addr_offset=%d", + __PRETTY_FUNCTION__, + addr_offset); +#endif + if (addr_offset < 1) { + return -7; + } + + buf += addr_offset; + + recv_join(ifp, neigh, msg_holdtime, + msg_upstream_addr.u.prefix4, + msg_group_addr.u.prefix4, + msg_source_addr.u.prefix4, + msg_source_flags); + } + + /* Scan pruned sources */ + for (source = 0; source < msg_num_pruned_sources; ++source) { + addr_offset = pim_parse_addr_source(ifp->name, src_addr, + &msg_source_addr, + &msg_source_flags, + buf, pastend - buf); + if (addr_offset < 1) { + return -8; + } + + buf += addr_offset; + + recv_prune(ifp, neigh, msg_holdtime, + msg_upstream_addr.u.prefix4, + msg_group_addr.u.prefix4, + msg_source_addr.u.prefix4, + msg_source_flags); + } + + } /* scan groups */ + + return 0; +} + +int pim_joinprune_send(struct interface *ifp, + struct in_addr upstream_addr, + struct in_addr source_addr, + struct in_addr group_addr, + int send_join) +{ + struct pim_interface *pim_ifp; + uint8_t pim_msg[1000]; + const uint8_t *pastend = pim_msg + sizeof(pim_msg); + uint8_t *pim_msg_curr = pim_msg + PIM_MSG_HEADER_LEN; /* room for pim header */ + int pim_msg_size; + int remain; + + zassert(ifp); + + pim_ifp = ifp->info; + + if (!pim_ifp) { + zlog_warn("%s: multicast not enabled on interface %s", + __PRETTY_FUNCTION__, + ifp->name); + return -1; + } + + if (PIM_DEBUG_PIM_TRACE) { + char source_str[100]; + char group_str[100]; + char dst_str[100]; + pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", upstream_addr, dst_str, sizeof(dst_str)); + zlog_debug("%s: sending %s(S,G)=(%s,%s) to upstream=%s on interface %s", + __PRETTY_FUNCTION__, + send_join ? "Join" : "Prune", + source_str, group_str, dst_str, ifp->name); + } + + if (PIM_INADDR_IS_ANY(upstream_addr)) { + if (PIM_DEBUG_PIM_TRACE) { + char source_str[100]; + char group_str[100]; + char dst_str[100]; + pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", upstream_addr, dst_str, sizeof(dst_str)); + zlog_debug("%s: %s(S,G)=(%s,%s): upstream=%s is myself on interface %s", + __PRETTY_FUNCTION__, + send_join ? "Join" : "Prune", + source_str, group_str, dst_str, ifp->name); + } + return 0; + } + + /* + RFC 4601: 4.3.1. Sending Hello Messages + + Thus, if a router needs to send a Join/Prune or Assert message on + an interface on which it has not yet sent a Hello message with the + currently configured IP address, then it MUST immediately send the + relevant Hello message without waiting for the Hello Timer to + expire, followed by the Join/Prune or Assert message. + */ + pim_hello_require(ifp); + + /* + Build PIM message + */ + + remain = pastend - pim_msg_curr; + pim_msg_curr = pim_msg_addr_encode_ipv4_ucast(pim_msg_curr, + remain, + upstream_addr); + if (!pim_msg_curr) { + char dst_str[100]; + pim_inet4_dump("", upstream_addr, dst_str, sizeof(dst_str)); + zlog_warn("%s: failure encoding destination address %s: space left=%d", + __PRETTY_FUNCTION__, dst_str, remain); + return -3; + } + + remain = pastend - pim_msg_curr; + if (remain < 4) { + zlog_warn("%s: group will not fit: space left=%d", + __PRETTY_FUNCTION__, remain); + return -4; + } + + *pim_msg_curr = 0; /* reserved */ + ++pim_msg_curr; + *pim_msg_curr = 1; /* number of groups */ + ++pim_msg_curr; + *((uint16_t *) pim_msg_curr) = htons(PIM_JP_HOLDTIME); + ++pim_msg_curr; + ++pim_msg_curr; + + remain = pastend - pim_msg_curr; + pim_msg_curr = pim_msg_addr_encode_ipv4_group(pim_msg_curr, + remain, + group_addr); + if (!pim_msg_curr) { + char group_str[100]; + pim_inet4_dump("", group_addr, group_str, sizeof(group_str)); + zlog_warn("%s: failure encoding group address %s: space left=%d", + __PRETTY_FUNCTION__, group_str, remain); + return -5; + } + + remain = pastend - pim_msg_curr; + if (remain < 4) { + zlog_warn("%s: sources will not fit: space left=%d", + __PRETTY_FUNCTION__, remain); + return -6; + } + + /* number of joined sources */ + *((uint16_t *) pim_msg_curr) = htons(send_join ? 1 : 0); + ++pim_msg_curr; + ++pim_msg_curr; + + /* number of pruned sources */ + *((uint16_t *) pim_msg_curr) = htons(send_join ? 0 : 1); + ++pim_msg_curr; + ++pim_msg_curr; + + remain = pastend - pim_msg_curr; + pim_msg_curr = pim_msg_addr_encode_ipv4_source(pim_msg_curr, + remain, + source_addr); + if (!pim_msg_curr) { + char source_str[100]; + pim_inet4_dump("", source_addr, source_str, sizeof(source_str)); + zlog_warn("%s: failure encoding source address %s: space left=%d", + __PRETTY_FUNCTION__, source_str, remain); + return -7; + } + + /* Add PIM header */ + + pim_msg_size = pim_msg_curr - pim_msg; + + pim_msg_build_header(pim_msg, pim_msg_size, + PIM_MSG_TYPE_JOIN_PRUNE); + + if (pim_msg_send(pim_ifp->pim_sock_fd, + qpim_all_pim_routers_addr, + pim_msg, + pim_msg_size, + ifp->name)) { + zlog_warn("%s: could not send PIM message on interface %s", + __PRETTY_FUNCTION__, ifp->name); + return -8; + } + + return 0; +} diff --git a/pimd/pim_join.h b/pimd/pim_join.h new file mode 100644 index 000000000..37ec0f452 --- /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 + +#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 000000000..3f5653252 --- /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 + +#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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", ch->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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 000000000..472fa9b4e --- /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 + +#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 000000000..b57f88113 --- /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 + +#include "log.h" +#include "privs.h" +#include "version.h" +#include +#include "command.h" +#include "thread.h" +#include + +#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 000000000..fa460e28f --- /dev/null +++ b/pimd/pim_mroute.c @@ -0,0 +1,451 @@ +/* + PIM for Quagga + Copyright (C) 2008 Everton da Silva Marques + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + MA 02110-1301 USA + + $QuaggaId: $Format:%an, %ai, %h$ $ +*/ + +#include +#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 = ""; + } + ifp = pim_if_find_by_vif_index(msg->im_vif); + pim_inet4_dump("", msg->im_src, src_str, sizeof(src_str)); + pim_inet4_dump("", 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 : "", + 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 : "", + 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_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 000000000..350b1e373 --- /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 +#ifdef HAVE_NETINET_IP_MROUTE_H +#include +#endif + +#define PIM_MROUTE_MIN_TTL (1) + +/* + Below: from +*/ + +#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 +*/ + +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 000000000..8ead7ce64 --- /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 + +#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 000000000..a884fc84d --- /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 + +/* + 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 000000000..9404cec13 --- /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 + +#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_addr, dr_old_str, sizeof(dr_old_str)); + pim_inet4_dump("", 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("", 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("", 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("", 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("", 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("", 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("", 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->u.prefix4, addr_str, sizeof(addr_str)); + pim_inet4_dump("", neigh_addr, this_neigh_str, sizeof(this_neigh_str)); + pim_inet4_dump("", 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 000000000..8f19c7505 --- /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 + +#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 000000000..1aaece3f4 --- /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 + +#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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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 000000000..1753545ab --- /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 000000000..f6f4c9535 --- /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 + +#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("", ip_hdr->ip_src, src_str, sizeof(src_str)); + pim_inet4_dump("", 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, ""); + if (!inet_ntop(AF_INET, &to.sin_addr, to_str, sizeof(to_str))) + sprintf(to_str, ""); + + 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, ""); + if (!inet_ntop(AF_INET, &to.sin_addr, to_str , sizeof(to_str))) + sprintf(to_str, ""); + + 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 : "", + 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_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_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("", 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 000000000..6be1a3e68 --- /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 + +#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 000000000..df2a1111e --- /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 000000000..a1df5054e --- /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 000000000..dedc60a55 --- /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 + +#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_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_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_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_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_tab[0].nexthop_addr, nexthop_str, sizeof(nexthop_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", 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 : "", + 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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 : "", + rpf->source_nexthop.interface ? rpf->source_nexthop.interface->name : ""); + /* 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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 000000000..078e89f3e --- /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 + +#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 000000000..d1350b08b --- /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 + +#include +#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 000000000..62523c038 --- /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 000000000..4816c567e --- /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 +#include +#include +#include +#include +#include +#include +#include + +#include +#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, ""); + if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str , sizeof(ifaddr_str))) + sprintf(ifaddr_str, ""); + + 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, ""); + if (!inet_ntop(AF_INET, &ifaddr, ifaddr_str , sizeof(ifaddr_str))) + sprintf(ifaddr_str, ""); + + 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("", group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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->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->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 000000000..cfe39ad1e --- /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 + +#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 000000000..fd1eac06e --- /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 + +#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_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("", 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.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("", 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("", ss->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", from.sin_addr, from_str, sizeof(from_str)); + pim_inet4_dump("", 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 : "", + 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("", ss->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", from.sin_addr, from_str, sizeof(from_str)); + pim_inet4_dump("", 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 : "", + 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("", 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("", 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("", 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("", 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("", 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("", 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 000000000..4bef20b20 --- /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 + +#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 000000000..af5a184d2 --- /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 +#include +#include + +#include + +#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 000000000..925f17f73 --- /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 +#include +#include + +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 000000000..097b470ba --- /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 +#include +#include + +#include +#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 000000000..2984d9a8d --- /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 + +#include +#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 000000000..95ee5ab0e --- /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 + +#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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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("", 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_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_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("", tmp.u.prefix4, addr_str, sizeof(addr_str)); + pim_inet4_dump("", 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_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_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 000000000..b802cf897 --- /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 + +#include "config.h" +#include "if.h" +#include "linklist.h" + +#ifdef HAVE_INTTYPES_H +#include +#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 000000000..d02f91548 --- /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 + +#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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", 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_addr, neigh_str, sizeof(neigh_str)); + pim_inet4_dump("", up->source_addr, src_str, sizeof(src_str)); + pim_inet4_dump("", up->group_addr, grp_str, sizeof(grp_str)); + pim_inet4_dump("", 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 000000000..5b5182dd4 --- /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 + +#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 000000000..fdfed2bf2 --- /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 + +#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 000000000..a8613e2b9 --- /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 + +#include + +#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 000000000..fe7e56343 --- /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 000000000..ef9f370c7 --- /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 000000000..bcace95c9 --- /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 + +#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("", 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("", ij->group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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 000000000..904ee5530 --- /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 000000000..fc131969b --- /dev/null +++ b/pimd/pim_zebra.c @@ -0,0 +1,1312 @@ +/* + PIM for Quagga + Copyright (C) 2008 Everton da Silva Marques + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; see the file COPYING; if not, write to the + Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, + MA 02110-1301 USA + + $QuaggaId: $Format:%an, %ai, %h$ $ +*/ + +#include + +#include "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("", 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("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); + pim_inet4_dump("", 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 : "", c_oil->oil.mfcc_parent, + new_iif ? new_iif->name : "", 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("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); + pim_inet4_dump("", 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 : "", 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("", c_oil->oil.mfcc_origin, source_str, sizeof(source_str)); + pim_inet4_dump("", 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 : "", c_oil->oil.mfcc_parent, + new_iif ? new_iif->name : "", 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 */ + zclient_redistribute_default (ZEBRA_REDISTRIBUTE_DEFAULT_ADD, + qpim_zclient_update, VRF_DEFAULT); + + 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_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_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("", 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_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_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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", channel_oil->oil.mfcc_mcastgrp, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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_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_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", ch->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", 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("", up->group_addr, group_str, sizeof(group_str)); + pim_inet4_dump("", 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("", ch->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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("", ch->source_addr, source_str, sizeof(source_str)); + pim_inet4_dump("", 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 000000000..d624c8666 --- /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 000000000..2e71dc4ef --- /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 +#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_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_str, sizeof(addr_str)); + pim_inet4_dump("", 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_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_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_str, sizeof(addr_str)); + pim_inet4_dump("", 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_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_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_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_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_str, sizeof(addr_str)); + pim_inet4_dump("", 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_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 000000000..1f184942c --- /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 + +#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 000000000..855defcc7 --- /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 + +#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 000000000..67530856b --- /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 000000000..22a29220d --- /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 + +#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 000000000..4ec443d93 --- /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 000000000..7136f6705 --- /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 000000000..2ad476d00 --- /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 000000000..b3052dceb --- /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 000000000..72329eb90 --- /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 000000000..3824e984e --- /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 000000000..e86f414b8 --- /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 000000000..fe64fbc00 --- /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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 d085d06ec..86f77bd00 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 diff --git a/ports/pkg/DESCR b/ports/pkg/DESCR index 0c8d01b76..aeb1950e2 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 a9b031216..43feea411 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 77cf56380..935d2c2bd 100644 --- a/vtysh/Makefile.am +++ b/vtysh/Makefile.am @@ -24,6 +24,7 @@ EXTRA_DIST = extract.pl 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)/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 9c628a0ee..f5190fb0b 100644 --- a/vtysh/vtysh.c +++ b/vtysh/vtysh.c @@ -66,6 +66,7 @@ struct vtysh_client vtysh_client[] = { .fd = -1, .name = "ospf6d", .flag = VTYSH_OSPF6D, .path = OSPF6_VTYSH_PATH, .next = NULL}, { .fd = -1, .name = "bgpd", .flag = VTYSH_BGPD, .path = BGP_VTYSH_PATH, .next = NULL}, { .fd = -1, .name = "isisd", .flag = VTYSH_ISISD, .path = ISIS_VTYSH_PATH, .next = NULL}, + { .fd = -1, .name = "pimd", .flag = VTYSH_PIMD, .path = PIM_VTYSH_PATH, .next = NULL}, }; /* diff --git a/vtysh/vtysh.h b/vtysh/vtysh.h index da6dd2a06..9bc9f6d1b 100644 --- a/vtysh/vtysh.h +++ b/vtysh/vtysh.h @@ -29,9 +29,11 @@ #define VTYSH_OSPF6D 0x10 #define VTYSH_BGPD 0x20 #define VTYSH_ISISD 0x40 -#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD -#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD -#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD +#define VTYSH_PIMD 0x100 + +#define VTYSH_ALL VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_ISISD|VTYSH_PIMD +#define VTYSH_RMAP VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_BGPD|VTYSH_PIMD +#define VTYSH_INTERFACE VTYSH_ZEBRA|VTYSH_RIPD|VTYSH_RIPNGD|VTYSH_OSPFD|VTYSH_OSPF6D|VTYSH_ISISD|VTYSH_PIMD #define VTYSH_VRF VTYSH_ZEBRA /* vtysh local configuration file. */ -- 2.39.5