text: /*
   *
   * Copyright (C) 2000  Robert Olsson.
   * Swedish University of Agricultural Sciences
   *
   * This file is part of GNU Zebra.
   *
   * GNU Zebra 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.
   *
   * GNU Zebra 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
   */
  
  /*
   * This work includes work with the following copywrite:
   *
   * Copyright (C) 1997, 2000 Kunihiro Ishiguro
   *
   */
  
  /*
   * Thanks to Jens Laas at Swedish University of Agricultural Sciences
   * for reviewing and tests.
   */
  
  
  #include <zebra.h>
  
  #include "if.h"
  #include "vty.h"
  #include "sockunion.h"
  #include "sockopt.h"
  #include "prefix.h"
  #include "command.h"
  #include "memory.h"
  #include "stream.h"
  #include "ioctl.h"
  #include "connected.h"
  #include "log.h"
  #include "zclient.h"
  #include "thread.h"
  #include "privs.h"
  #include "libfrr.h"
  #include "lib_errors.h"
  #include "lib/version.h"
  #include "zebra/interface.h"
  #include "zebra/rtadv.h"
  #include "zebra/rib.h"
  #include "zebra/zebra_router.h"
  #include "zebra/redistribute.h"
  #include "zebra/irdp.h"
  #include "zebra/zebra_errors.h"
  #include <netinet/ip_icmp.h>
  
  #include "checksum.h"
  #include "if.h"
  #include "sockunion.h"
  #include "log.h"
  #include "network.h"
  
  /* GLOBAL VARS */
  
  extern struct zebra_privs_t zserv_privs;
  
  struct thread *t_irdp_raw;
  
  /* Timer interval of irdp. */
  int irdp_timer_interval = IRDP_DEFAULT_INTERVAL;
  
  int irdp_sock_init(void)
  {
  	int ret, i;
  	int save_errno;
  	int sock;
  
  	frr_with_privs(&zserv_privs) {
  
  		sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
  		save_errno = errno;
  
  	}
  
  	if (sock < 0) {
  		flog_err_sys(EC_LIB_SOCKET, "IRDP: can't create irdp socket %s",
  			     safe_strerror(save_errno));
  		return sock;
  	};
  
  	i = 1;
  	ret = setsockopt(sock, IPPROTO_IP, IP_TTL, (void *)&i, sizeof(i));
  	if (ret < 0) {
  		flog_err_sys(EC_LIB_SOCKET, "IRDP: can't do irdp sockopt %s",
  			     safe_strerror(errno));
  		close(sock);
  		return ret;
  	};
  
  	ret = setsockopt_ifindex(AF_INET, sock, 1);
  	if (ret < 0) {
  		flog_err_sys(EC_LIB_SOCKET, "IRDP: can't do irdp sockopt %s",
  			     safe_strerror(errno));
  		close(sock);
  		return ret;
  	};
  
  	t_irdp_raw = NULL;
  	thread_add_read(zrouter.master, irdp_read_raw, NULL, sock, &t_irdp_raw);
  
  	return sock;
  }
  
  
  static int get_pref(struct irdp_interface *irdp, struct prefix *p)
  {
  	struct listnode *node;
  	struct Adv *adv;
  
  	/* Use default preference or use the override pref */
  
  	if (irdp->AdvPrefList == NULL)
  		return irdp->Preference;
  
  	for (ALL_LIST_ELEMENTS_RO(irdp->AdvPrefList, node, adv))
  		if (p->u.prefix4.s_addr == adv->ip.s_addr)
  			return adv->pref;
  
  	return irdp->Preference;
  }
  
  /* Make ICMP Router Advertisement Message. */
  static int make_advertisement_packet(struct interface *ifp, struct prefix *p,
  				     struct stream *s)
  {
  	struct zebra_if *zi = ifp->info;
  	struct irdp_interface *irdp = zi->irdp;
  	int size;
  	int pref;
  	uint16_t checksum;
  
  	pref = get_pref(irdp, p);
  
  	stream_putc(s, ICMP_ROUTERADVERT); /* Type. */
  	stream_putc(s, 0);		   /* Code. */
  	stream_putw(s, 0);		   /* Checksum. */
  	stream_putc(s, 1);		   /* Num address. */
  	stream_putc(s, 2);		   /* Address Entry Size. */
  
  	if (irdp->flags & IF_SHUTDOWN)
  		stream_putw(s, 0);
  	else
  		stream_putw(s, irdp->Lifetime);
  
  	stream_putl(s, htonl(p->u.prefix4.s_addr)); /* Router address. */
  	stream_putl(s, pref);
  
  	/* in_cksum return network byte order value */
  	size = 16;
  	checksum = in_cksum(s->data, size);
  	stream_putw_at(s, 2, htons(checksum));
  
  	return size;
  }
  
  static void irdp_send(struct interface *ifp, struct prefix *p, struct stream *s)
  {
  	struct zebra_if *zi = ifp->info;
  	struct irdp_interface *irdp = zi->irdp;
  	uint32_t dst;
  	uint32_t ttl = 1;
  
  	if (!irdp)
  		return;
  	if (!(ifp->flags & IFF_UP))
  		return;
  
  	if (irdp->flags & IF_BROADCAST)
  		dst = INADDR_BROADCAST;
  	else
  		dst = htonl(INADDR_ALLHOSTS_GROUP);
  
  	if (irdp->flags & IF_DEBUG_MESSAGES)
  		zlog_debug(
  			"IRDP: TX Advert on %s %pFX Holdtime=%d Preference=%d",
  			ifp->name, p,
  			irdp->flags & IF_SHUTDOWN ? 0 : irdp->Lifetime,
  			get_pref(irdp, p));
  
  	send_packet(ifp, s, dst, p, ttl);
  }
  
  static void irdp_advertisement(struct interface *ifp, struct prefix *p)
  {
  	struct stream *s;
  	s = stream_new(128);
  	make_advertisement_packet(ifp, p, s);
  	irdp_send(ifp, p, s);
  	stream_free(s);
  }
  
  int irdp_send_thread(struct thread *t_advert)
  {
  	uint32_t timer, tmp;
  	struct interface *ifp = THREAD_ARG(t_advert);
  	struct zebra_if *zi = ifp->info;
  	struct irdp_interface *irdp = zi->irdp;
  	struct prefix *p;
  	struct listnode *node, *nnode;
  	struct connected *ifc;
  
  	if (!irdp)
  		return 0;
  
  	irdp->flags &= ~IF_SOLICIT;
  
  	if (ifp->connected)
  		for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, ifc)) {
  			p = ifc->address;
  
  			if (p->family != AF_INET)
  				continue;
  
  			irdp_advertisement(ifp, p);
  			irdp->irdp_sent++;
  		}
  
  	tmp = irdp->MaxAdvertInterval - irdp->MinAdvertInterval;
  	timer = frr_weak_random() % (tmp + 1);
  	timer = irdp->MinAdvertInterval + timer;
  
  	if (irdp->irdp_sent < MAX_INITIAL_ADVERTISEMENTS
  	    && timer > MAX_INITIAL_ADVERT_INTERVAL)
  		timer = MAX_INITIAL_ADVERT_INTERVAL;
  
  	if (irdp->flags & IF_DEBUG_MISC)
  		zlog_debug("IRDP: New timer for %s set to %u", ifp->name,
  			   timer);
  
  	irdp->t_advertise = NULL;
  	thread_add_timer(zrouter.master, irdp_send_thread, ifp, timer,
  			 &irdp->t_advertise);
  	return 0;
  }
  
  void irdp_advert_off(struct interface *ifp)
  {
  	struct zebra_if *zi = ifp->info;
  	struct irdp_interface *irdp = zi->irdp;
  	struct listnode *node, *nnode;
  	int i;
  	struct connected *ifc;
  	struct prefix *p;
  
  	if (!irdp)
  		return;
  
  	thread_cancel(&irdp->t_advertise);
  
  	if (ifp->connected)
  		for (ALL_LIST_ELEMENTS(ifp->connected, node, nnode, ifc)) {
  			p = ifc->address;
  
  			/* Output some packets with Lifetime 0
  			   we should add a wait...
  			*/
  
  			for (i = 0; i < IRDP_LAST_ADVERT_MESSAGES; i++) {
  				irdp->irdp_sent++;
  				irdp_advertisement(ifp, p);
  			}
  		}
  }
  
  
  void process_solicit(struct interface *ifp)
  {
  	struct zebra_if *zi = ifp->info;
  	struct irdp_interface *irdp = zi->irdp;
  	uint32_t timer;
  
  	if (!irdp)
  		return;
  
  	/* When SOLICIT is active we reject further incoming solicits
  	   this keeps down the answering rate so we don't have think
  	   about DoS attacks here. */
  
  	if (irdp->flags & IF_SOLICIT)
  		return;
  
  	irdp->flags |= IF_SOLICIT;
  	thread_cancel(&irdp->t_advertise);
  
  	timer = (frr_weak_random() % MAX_RESPONSE_DELAY) + 1;
  
  	irdp->t_advertise = NULL;
  	thread_add_timer(zrouter.master, irdp_send_thread, ifp, timer,
  			 &irdp->t_advertise);
  }
  
  static int irdp_finish(void)
  {
  	struct vrf *vrf;
  	struct interface *ifp;
  	struct zebra_if *zi;
  	struct irdp_interface *irdp;
  
  	zlog_info("IRDP: Received shutdown notification.");
  
  	RB_FOREACH (vrf, vrf_id_head, &vrfs_by_id)
  		FOR_ALL_INTERFACES (vrf, ifp) {
  			zi = ifp->info;
  
  			if (!zi)
  				continue;
  			irdp = zi->irdp;
  			if (!irdp)
  				continue;
  
  			if (irdp->flags & IF_ACTIVE) {
  				irdp->flags |= IF_SHUTDOWN;
  				irdp_advert_off(ifp);
  			}
  		}
  	return 0;
  }
  
  static int irdp_init(struct thread_master *master)
  {
  	irdp_if_init();
  
  	hook_register(frr_early_fini, irdp_finish);
  	return 0;
  }
  
  static int irdp_module_init(void)
  {
  	hook_register(frr_late_init, irdp_init);
  	return 0;
  }
  
  FRR_MODULE_SETUP(.name = "zebra_irdp", .version = FRR_VERSION,
  		 .description = "zebra IRDP module", .init = irdp_module_init,
  );
  