summaryrefslogtreecommitdiffstats
path: root/pimd/pim_ssmpingd.c
diff options
context:
space:
mode:
Diffstat (limited to 'pimd/pim_ssmpingd.c')
-rw-r--r--pimd/pim_ssmpingd.c395
1 files changed, 395 insertions, 0 deletions
diff --git a/pimd/pim_ssmpingd.c b/pimd/pim_ssmpingd.c
new file mode 100644
index 0000000..c8d4037
--- /dev/null
+++ b/pimd/pim_ssmpingd.c
@@ -0,0 +1,395 @@
+/*
+ * 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
+ */
+
+#include <zebra.h>
+
+#include "if.h"
+#include "log.h"
+#include "memory.h"
+#include "sockopt.h"
+#include "vrf.h"
+#include "lib_errors.h"
+
+#include "pimd.h"
+#include "pim_instance.h"
+#include "pim_ssmpingd.h"
+#include "pim_time.h"
+#include "pim_sock.h"
+#include "network.h"
+
+#if PIM_IPV == 4
+static const char *const PIM_SSMPINGD_REPLY_GROUP = "232.43.211.234";
+#else
+static const char *const PIM_SSMPINGD_REPLY_GROUP = "ff3e::4321:1234";
+#endif
+
+enum { PIM_SSMPINGD_REQUEST = 'Q', PIM_SSMPINGD_REPLY = 'A' };
+
+static void ssmpingd_read_on(struct ssmpingd_sock *ss);
+
+void pim_ssmpingd_init(struct pim_instance *pim)
+{
+ int result;
+
+ assert(!pim->ssmpingd_list);
+
+ result = inet_pton(PIM_AF, PIM_SSMPINGD_REPLY_GROUP,
+ &pim->ssmpingd_group_addr);
+
+ assert(result > 0);
+}
+
+void pim_ssmpingd_destroy(struct pim_instance *pim)
+{
+ if (pim->ssmpingd_list)
+ list_delete(&pim->ssmpingd_list);
+}
+
+static struct ssmpingd_sock *ssmpingd_find(struct pim_instance *pim,
+ pim_addr source_addr)
+{
+ struct listnode *node;
+ struct ssmpingd_sock *ss;
+
+ if (!pim->ssmpingd_list)
+ return 0;
+
+ for (ALL_LIST_ELEMENTS_RO(pim->ssmpingd_list, node, ss))
+ if (!pim_addr_cmp(source_addr, ss->source_addr))
+ return ss;
+
+ return 0;
+}
+
+static void ssmpingd_free(struct ssmpingd_sock *ss)
+{
+ XFREE(MTYPE_PIM_SSMPINGD, ss);
+}
+
+#if PIM_IPV == 4
+static inline int ssmpingd_setsockopt(int fd, pim_addr addr, int mttl)
+{
+ /* Needed to obtain destination address from recvmsg() */
+#if defined(HAVE_IP_PKTINFO)
+ /* Linux and Solaris IP_PKTINFO */
+ int opt = 1;
+ if (setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &opt, sizeof(opt))) {
+ zlog_warn(
+ "%s: could not set IP_PKTINFO on socket fd=%d: errno=%d: %s",
+ __func__, 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",
+ __func__, fd, errno, safe_strerror(errno));
+ }
+#else
+ flog_err(
+ EC_LIB_DEVELOPMENT,
+ "%s %s: missing IP_PKTINFO and IP_RECVDSTADDR: unable to get dst addr from recvmsg()",
+ __FILE__, __func__);
+ close(fd);
+ return -1;
+#endif
+
+ if (setsockopt_ipv4_multicast_loop(fd, 0)) {
+ zlog_warn(
+ "%s: could not disable Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+ __func__, 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",
+ __func__, 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",
+ __func__, mttl, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ return 0;
+}
+#else
+static inline int ssmpingd_setsockopt(int fd, pim_addr addr, int mttl)
+{
+ setsockopt_ipv6_pktinfo(fd, 1);
+ setsockopt_ipv6_multicast_hops(fd, mttl);
+
+ if (setsockopt_ipv6_multicast_loop(fd, 0)) {
+ zlog_warn(
+ "%s: could not disable Multicast Loopback Option on socket fd=%d: errno=%d: %s",
+ __func__, fd, errno, safe_strerror(errno));
+ close(fd);
+ return PIM_SOCK_ERR_LOOP;
+ }
+
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (void *)&addr,
+ sizeof(addr))) {
+ zlog_warn(
+ "%s: could not set Outgoing Interface Option on socket fd=%d: errno=%d: %s",
+ __func__, fd, errno, safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+ return 0;
+}
+#endif
+
+
+static int ssmpingd_socket(pim_addr addr, int port, int mttl)
+{
+ struct sockaddr_storage sockaddr;
+ int fd;
+ int ret;
+ socklen_t len = sizeof(sockaddr);
+
+ fd = socket(PIM_AF, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ flog_err_sys(EC_LIB_SOCKET,
+ "%s: could not create socket: errno=%d: %s",
+ __func__, errno, safe_strerror(errno));
+ return -1;
+ }
+
+ pim_socket_getsockname(fd, (struct sockaddr *)&sockaddr, &len);
+
+ if (bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr))) {
+ zlog_warn(
+ "%s: bind(fd=%d,addr=%pSUp,port=%d,len=%zu) failure: errno=%d: %s",
+ __func__, fd, &sockaddr, port, sizeof(sockaddr), errno,
+ safe_strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ set_nonblocking(fd);
+ sockopt_reuseaddr(fd);
+
+ ret = ssmpingd_setsockopt(fd, addr, mttl);
+ if (ret) {
+ zlog_warn("ssmpingd_setsockopt failed");
+ close(fd);
+ return -1;
+ }
+
+ return fd;
+}
+
+static void ssmpingd_delete(struct ssmpingd_sock *ss)
+{
+ assert(ss);
+
+ THREAD_OFF(ss->t_sock_read);
+
+ if (close(ss->sock_fd)) {
+ zlog_warn(
+ "%s: failure closing ssmpingd sock_fd=%d for source %pPA: errno=%d: %s",
+ __func__, ss->sock_fd, &ss->source_addr, errno,
+ safe_strerror(errno));
+ /* warning only */
+ }
+
+ listnode_delete(ss->pim->ssmpingd_list, ss);
+ ssmpingd_free(ss);
+}
+
+static void ssmpingd_sendto(struct ssmpingd_sock *ss, const uint8_t *buf,
+ int len, struct sockaddr_storage to)
+{
+ socklen_t tolen = sizeof(to);
+ int sent;
+
+ sent = sendto(ss->sock_fd, buf, len, MSG_DONTWAIT,
+ (struct sockaddr *)&to, tolen);
+ if (sent != len) {
+ if (sent < 0) {
+ zlog_warn(
+ "%s: sendto() failure to %pSUp,fd=%d len=%d: errno=%d: %s",
+ __func__, &to, ss->sock_fd, len, errno,
+ safe_strerror(errno));
+ } else {
+ zlog_warn(
+ "%s: sendto() partial to %pSUp, fd=%d len=%d: sent=%d",
+ __func__, &to, ss->sock_fd, len, sent);
+ }
+ }
+}
+
+static int ssmpingd_read_msg(struct ssmpingd_sock *ss)
+{
+ struct interface *ifp;
+ struct sockaddr_storage from;
+ struct sockaddr_storage to;
+ socklen_t fromlen = sizeof(from);
+ socklen_t tolen = sizeof(to);
+ ifindex_t 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) {
+ zlog_warn(
+ "%s: failure receiving ssmping for source %pPA on fd=%d: errno=%d: %s",
+ __func__, &ss->source_addr, ss->sock_fd, errno,
+ safe_strerror(errno));
+ return -1;
+ }
+
+ ifp = if_lookup_by_index(ifindex, ss->pim->vrf->vrf_id);
+
+ if (buf[0] != PIM_SSMPINGD_REQUEST) {
+ zlog_warn(
+ "%s: bad ssmping type=%d from %pSUp to %pSUp on interface %s ifindex=%d fd=%d src=%pPA",
+ __func__, buf[0], &from, &to,
+ ifp ? ifp->name : "<iface?>", ifindex, ss->sock_fd,
+ &ss->source_addr);
+ return 0;
+ }
+
+ if (PIM_DEBUG_SSMPINGD) {
+ zlog_debug(
+ "%s: recv ssmping from %pSUp, to %pSUp, on interface %s ifindex=%d fd=%d src=%pPA",
+ __func__, &from, &to, ifp ? ifp->name : "<iface?>",
+ ifindex, ss->sock_fd, &ss->source_addr);
+ }
+
+ buf[0] = PIM_SSMPINGD_REPLY;
+
+ /* unicast reply */
+ ssmpingd_sendto(ss, buf, len, from);
+
+ /* multicast reply */
+ memcpy(&from, &ss->pim->ssmpingd_group_addr, sizeof(pim_addr));
+ ssmpingd_sendto(ss, buf, len, from);
+
+ return 0;
+}
+
+static void ssmpingd_sock_read(struct thread *t)
+{
+ struct ssmpingd_sock *ss;
+
+ ss = THREAD_ARG(t);
+
+ ssmpingd_read_msg(ss);
+
+ /* Keep reading */
+ ssmpingd_read_on(ss);
+}
+
+static void ssmpingd_read_on(struct ssmpingd_sock *ss)
+{
+ thread_add_read(router->master, ssmpingd_sock_read, ss, ss->sock_fd,
+ &ss->t_sock_read);
+}
+
+static struct ssmpingd_sock *ssmpingd_new(struct pim_instance *pim,
+ pim_addr source_addr)
+{
+ struct ssmpingd_sock *ss;
+ int sock_fd;
+
+ if (!pim->ssmpingd_list) {
+ pim->ssmpingd_list = list_new();
+ pim->ssmpingd_list->del = (void (*)(void *))ssmpingd_free;
+ }
+
+ sock_fd =
+ ssmpingd_socket(source_addr, /* port: */ 4321, /* mTTL: */ 64);
+ if (sock_fd < 0) {
+ zlog_warn("%s: ssmpingd_socket() failure for source %pPA",
+ __func__, &source_addr);
+ return 0;
+ }
+
+ ss = XCALLOC(MTYPE_PIM_SSMPINGD, sizeof(*ss));
+
+ ss->pim = pim;
+ ss->sock_fd = sock_fd;
+ ss->t_sock_read = NULL;
+ ss->source_addr = source_addr;
+ ss->creation = pim_time_monotonic_sec();
+ ss->requests = 0;
+
+ listnode_add(pim->ssmpingd_list, ss);
+
+ ssmpingd_read_on(ss);
+
+ return ss;
+}
+
+int pim_ssmpingd_start(struct pim_instance *pim, pim_addr source_addr)
+{
+ struct ssmpingd_sock *ss;
+
+ ss = ssmpingd_find(pim, source_addr);
+ if (ss) {
+ /* silently ignore request to recreate entry */
+ return 0;
+ }
+
+ zlog_info("%s: starting ssmpingd for source %pPAs", __func__,
+ &source_addr);
+
+ ss = ssmpingd_new(pim, source_addr);
+ if (!ss) {
+ zlog_warn("%s: ssmpingd_new() failure for source %pPAs",
+ __func__, &source_addr);
+ return -1;
+ }
+
+ return 0;
+}
+
+int pim_ssmpingd_stop(struct pim_instance *pim, pim_addr source_addr)
+{
+ struct ssmpingd_sock *ss;
+
+ ss = ssmpingd_find(pim, source_addr);
+ if (!ss) {
+ zlog_warn("%s: could not find ssmpingd for source %pPAs",
+ __func__, &source_addr);
+ return -1;
+ }
+
+ zlog_info("%s: stopping ssmpingd for source %pPAs", __func__,
+ &source_addr);
+
+ ssmpingd_delete(ss);
+
+ return 0;
+}