Commit message: zebra: stack overrun in IPv6 RA receive code (CVE-2016-1245)

The IPv6 RA code also receives ICMPv6 RS and RA messages.
Unfortunately, by bad coding practice, the buffer size specified on
receiving such messages mixed up 2 constants that in fact have
different values.

The code itself has:
 #define RTADV_MSG_SIZE 4096
While BUFSIZ is system-dependent, in my case (x86_64 glibc):
 /usr/include/_G_config.h:#define _G_BUFSIZ 8192
 /usr/include/libio.h:#define _IO_BUFSIZ _G_BUFSIZ
 /usr/include/stdio.h:# define BUFSIZ _IO_BUFSIZ

FreeBSD, OpenBSD, NetBSD and Illumos are not affected, since all of them
have BUFSIZ == 1024.

As the latter is passed to the kernel on recvmsg(), it's possible to
overwrite 4kB of stack -- with ICMPv6 packets that can be globally sent
to any of the system's addresses (using fragmentation to get to 8k).

(The socket has filters installed limiting this to RS and RA packets,
but does not have a filter for source address or TTL.)

Issue discovered by trying to test other stuff, which randomly caused
the stack to be smaller than 8kB in that code location, which then
causes the kernel to report EFAULT (Bad address).

Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
Reviewed-by: Donald Sharp <sharpd@cumulusnetworks.com>
CVE: CVE-2016-1245
CWE: CWE-119
Vulnerability description: It was discovered that the zebra daemon in Quagga before 1.0.20161017 suffered from a stack-based buffer overflow when processing IPv6 Neighbor Discovery messages. The root cause was relying on BUFSIZ to be compatible with a message size; however, BUFSIZ is system-dependent.

Project: quagga
File: rtadv.c

if_join_all_router (int sock, struct interface *ifp)
{
  int ret;

  struct ipv6_mreq mreq;

  memset (&mreq, 0, sizeof (struct ipv6_mreq));
  inet_pton (AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr);
  mreq.ipv6mr_interface = ifp->ifindex;

  ret = setsockopt (sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, 
		    (char *) &mreq, sizeof mreq);
  if (ret < 0)
    zlog_warn ("can't setsockopt IPV6_JOIN_GROUP: %s", safe_strerror (errno));

  zlog_info ("rtadv: %s join to all-routers multicast group", ifp->name);

  return 0;
}

Project: quagga
File: rtadv.c

if_leave_all_router (int sock, struct interface *ifp)
{
  int ret;

  struct ipv6_mreq mreq;

  memset (&mreq, 0, sizeof (struct ipv6_mreq));
  inet_pton (AF_INET6, ALLROUTER, &mreq.ipv6mr_multiaddr);
  mreq.ipv6mr_interface = ifp->ifindex;

  ret = setsockopt (sock, IPPROTO_IPV6, IPV6_LEAVE_GROUP, 
		    (char *) &mreq, sizeof mreq);
  if (ret < 0)
    zlog_warn ("can't setsockopt IPV6_LEAVE_GROUP: %s", safe_strerror (errno));

  zlog_info ("rtadv: %s leave from all-routers multicast group", ifp->name);

  return 0;
}

Project: quagga
File: rtadv.c

rtadv_cmd_init (void)
{
  install_element (INTERFACE_NODE, &ipv6_nd_suppress_ra_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_suppress_ra_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_ra_interval_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_ra_interval_msec_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_ra_interval_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_ra_interval_val_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_ra_interval_msec_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_ra_lifetime_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_ra_lifetime_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_ra_lifetime_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_reachable_time_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_reachable_time_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_reachable_time_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_managed_config_flag_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_managed_config_flag_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_other_config_flag_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_other_config_flag_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_homeagent_config_flag_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_homeagent_config_flag_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_homeagent_preference_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_homeagent_preference_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_homeagent_preference_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_homeagent_lifetime_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_homeagent_lifetime_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_homeagent_lifetime_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_adv_interval_config_option_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_adv_interval_config_option_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_rev_rtaddr_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_nortaddr_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_rev_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_noauto_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_offlink_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_rtaddr_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_noval_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_noval_rev_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_noval_noauto_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_noval_offlink_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_noval_rtaddr_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_prefix_prefix_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_prefix_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_router_preference_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_router_preference_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_router_preference_val_cmd);
  install_element (INTERFACE_NODE, &ipv6_nd_mtu_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_mtu_cmd);
  install_element (INTERFACE_NODE, &no_ipv6_nd_mtu_val_cmd);
}

Project: quagga
File: rtadv.c

rtadv_cmd_init (void)
{
  /* Empty.*/;
}

Project: quagga
File: rtadv.c

rtadv_config_write (struct vty *vty, struct interface *ifp)
{
  struct zebra_if *zif;
  struct listnode *node;
  struct rtadv_prefix *rprefix;
  char buf[PREFIX_STRLEN];
  int interval;

  zif = ifp->info;

  if (! if_is_loopback (ifp))
    {
      if (zif->rtadv.AdvSendAdvertisements)
	vty_out (vty, " no ipv6 nd suppress-ra%s", VTY_NEWLINE);
    }

  
  interval = zif->rtadv.MaxRtrAdvInterval;
  if (interval % 1000)
    vty_out (vty, " ipv6 nd ra-interval msec %d%s", interval,
	     VTY_NEWLINE);
  else
    if (interval != RTADV_MAX_RTR_ADV_INTERVAL)
      vty_out (vty, " ipv6 nd ra-interval %d%s", interval / 1000,
	     VTY_NEWLINE);

  if (zif->rtadv.AdvIntervalOption)
    vty_out (vty, " ipv6 nd adv-interval-option%s", VTY_NEWLINE);

  if (zif->rtadv.AdvDefaultLifetime != -1)
    vty_out (vty, " ipv6 nd ra-lifetime %d%s", zif->rtadv.AdvDefaultLifetime,
	     VTY_NEWLINE);

  if (zif->rtadv.HomeAgentPreference)
    vty_out (vty, " ipv6 nd home-agent-preference %u%s",
	     zif->rtadv.HomeAgentPreference, VTY_NEWLINE);

  if (zif->rtadv.HomeAgentLifetime != -1)
    vty_out (vty, " ipv6 nd home-agent-lifetime %u%s",
	     zif->rtadv.HomeAgentLifetime, VTY_NEWLINE);

  if (zif->rtadv.AdvHomeAgentFlag)
    vty_out (vty, " ipv6 nd home-agent-config-flag%s", VTY_NEWLINE);

  if (zif->rtadv.AdvReachableTime)
    vty_out (vty, " ipv6 nd reachable-time %d%s", zif->rtadv.AdvReachableTime,
	     VTY_NEWLINE);

  if (zif->rtadv.AdvManagedFlag)
    vty_out (vty, " ipv6 nd managed-config-flag%s", VTY_NEWLINE);

  if (zif->rtadv.AdvOtherConfigFlag)
    vty_out (vty, " ipv6 nd other-config-flag%s", VTY_NEWLINE);

  if (zif->rtadv.DefaultPreference != RTADV_PREF_MEDIUM)
    vty_out (vty, " ipv6 nd router-preference %s%s",
	     rtadv_pref_strs[zif->rtadv.DefaultPreference],
	     VTY_NEWLINE);

  if (zif->rtadv.AdvLinkMTU)
    vty_out (vty, " ipv6 nd mtu %d%s", zif->rtadv.AdvLinkMTU, VTY_NEWLINE);

  for (ALL_LIST_ELEMENTS_RO (zif->rtadv.AdvPrefixList, node, rprefix))
    {
      vty_out (vty, " ipv6 nd prefix %s",
               prefix2str (&rprefix->prefix, buf, sizeof(buf)));
      if ((rprefix->AdvValidLifetime != RTADV_VALID_LIFETIME) || 
	  (rprefix->AdvPreferredLifetime != RTADV_PREFERRED_LIFETIME))
	{
	  if (rprefix->AdvValidLifetime == UINT32_MAX)
	    vty_out (vty, " infinite");
	  else
	    vty_out (vty, " %u", rprefix->AdvValidLifetime);
	  if (rprefix->AdvPreferredLifetime == UINT32_MAX)
	    vty_out (vty, " infinite");
	  else
	    vty_out (vty, " %u", rprefix->AdvPreferredLifetime);
	}
      if (!rprefix->AdvOnLinkFlag)
	vty_out (vty, " off-link");
      if (!rprefix->AdvAutonomousFlag)
	vty_out (vty, " no-autoconfig");
      if (rprefix->AdvRouterAddressFlag)
	vty_out (vty, " router-address");
      vty_out (vty, "%s", VTY_NEWLINE);
    }
}

Project: quagga
File: rtadv.c

rtadv_event (struct zebra_vrf *zvrf, enum rtadv_event event, int val)
{
  struct rtadv *rtadv = &zvrf->rtadv;

  switch (event)
    {
    case RTADV_START:
      if (! rtadv->ra_read)
	rtadv->ra_read = thread_add_read (zebrad.master, rtadv_read, zvrf, val);
      if (! rtadv->ra_timer)
	rtadv->ra_timer = thread_add_event (zebrad.master, rtadv_timer,
	                                    zvrf, 0);
      break;
    case RTADV_STOP:
      if (rtadv->ra_timer)
	{
	  thread_cancel (rtadv->ra_timer);
	  rtadv->ra_timer = NULL;
	}
      if (rtadv->ra_read)
	{
	  thread_cancel (rtadv->ra_read);
	  rtadv->ra_read = NULL;
	}
      break;
    case RTADV_TIMER:
      if (! rtadv->ra_timer)
	rtadv->ra_timer = thread_add_timer (zebrad.master, rtadv_timer, zvrf,
	                                    val);
      break;
    case RTADV_TIMER_MSEC:
      if (! rtadv->ra_timer)
	rtadv->ra_timer = thread_add_timer_msec (zebrad.master, rtadv_timer, 
					    zvrf, val);
      break;
    case RTADV_READ:
      if (! rtadv->ra_read)
	rtadv->ra_read = thread_add_read (zebrad.master, rtadv_read, zvrf, val);
      break;
    default:
      break;
    }
  return;
}

Project: quagga
File: rtadv.c

rtadv_init (struct zebra_vrf *zvrf)
{
  zvrf->rtadv.sock = rtadv_make_socket (zvrf->vrf_id);
}

Project: quagga
File: rtadv.c

rtadv_init (struct zebra_vrf *zvrf)
{
  /* Empty.*/;
}

Project: quagga
File: rtadv.c

rtadv_make_socket (vrf_id_t vrf_id)
{
  int sock;
  int ret;
  struct icmp6_filter filter;

  if ( zserv_privs.change (ZPRIVS_RAISE) )
       zlog_err ("rtadv_make_socket: could not raise privs, %s",
                  safe_strerror (errno) );
                  
  sock = vrf_socket (AF_INET6, SOCK_RAW, IPPROTO_ICMPV6, vrf_id);

  if ( zserv_privs.change (ZPRIVS_LOWER) )
       zlog_err ("rtadv_make_socket: could not lower privs, %s",
       			 safe_strerror (errno) );

  /* When we can't make ICMPV6 socket simply back.  Router
     advertisement feature will not be supported. */
  if (sock < 0)
    {
      close (sock);
      return -1;
    }

  ret = setsockopt_ipv6_pktinfo (sock, 1);
  if (ret < 0)
    {
      close (sock);
      return ret;
    }
  ret = setsockopt_ipv6_multicast_loop (sock, 0);
  if (ret < 0)
    {
      close (sock);
      return ret;
    }
  ret = setsockopt_ipv6_unicast_hops (sock, 255);
  if (ret < 0)
    {
      close (sock);
      return ret;
    }
  ret = setsockopt_ipv6_multicast_hops (sock, 255);
  if (ret < 0)
    {
      close (sock);
      return ret;
    }
  ret = setsockopt_ipv6_hoplimit (sock, 1);
  if (ret < 0)
    {
      close (sock);
      return ret;
    }

  ICMP6_FILTER_SETBLOCKALL(&filter);
  ICMP6_FILTER_SETPASS (ND_ROUTER_SOLICIT, &filter);
  ICMP6_FILTER_SETPASS (ND_ROUTER_ADVERT, &filter);

  ret = setsockopt (sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter,
		    sizeof (struct icmp6_filter));
  if (ret < 0)
    {
      zlog_info ("ICMP6_FILTER set fail: %s", safe_strerror (errno));
      return ret;
    }

  return sock;
}

Project: quagga
File: rtadv.c

rtadv_prefix_free (struct rtadv_prefix *rtadv_prefix)
{
  XFREE (MTYPE_RTADV_PREFIX, rtadv_prefix);
}

Project: quagga
File: rtadv.c

rtadv_prefix_get (struct list *rplist, struct prefix_ipv6 *p)
{
  struct rtadv_prefix *rprefix;
  
  rprefix = rtadv_prefix_lookup (rplist, p);
  if (rprefix)
    return rprefix;

  rprefix = rtadv_prefix_new ();
  memcpy (&rprefix->prefix, p, sizeof (struct prefix_ipv6));
  listnode_add (rplist, rprefix);

  return rprefix;
}

Project: quagga
File: rtadv.c

rtadv_prefix_lookup (struct list *rplist, struct prefix_ipv6 *p)
{
  struct listnode *node;
  struct rtadv_prefix *rprefix;

  for (ALL_LIST_ELEMENTS_RO (rplist, node, rprefix))
    if (prefix_same ((struct prefix *) &rprefix->prefix, (struct prefix *) p))
      return rprefix;
  return NULL;
}

Project: quagga
File: rtadv.c

rtadv_prefix_new (void)
{
  return XCALLOC (MTYPE_RTADV_PREFIX, sizeof (struct rtadv_prefix));
}

Project: quagga
File: rtadv.c

rtadv_prefix_reset (struct zebra_if *zif, struct rtadv_prefix *rp)
{
  struct rtadv_prefix *rprefix;
  
  rprefix = rtadv_prefix_lookup (zif->rtadv.AdvPrefixList, &rp->prefix);
  if (rprefix != NULL)
    {
      listnode_delete (zif->rtadv.AdvPrefixList, (void *) rprefix);
      rtadv_prefix_free (rprefix);
      return 1;
    }
  else
    return 0;
}

Project: quagga
File: rtadv.c

rtadv_prefix_set (struct zebra_if *zif, struct rtadv_prefix *rp)
{
  struct rtadv_prefix *rprefix;
  
  rprefix = rtadv_prefix_get (zif->rtadv.AdvPrefixList, &rp->prefix);

  /* Set parameters. */
  rprefix->AdvValidLifetime = rp->AdvValidLifetime;
  rprefix->AdvPreferredLifetime = rp->AdvPreferredLifetime;
  rprefix->AdvOnLinkFlag = rp->AdvOnLinkFlag;
  rprefix->AdvAutonomousFlag = rp->AdvAutonomousFlag;
  rprefix->AdvRouterAddressFlag = rp->AdvRouterAddressFlag;
}

Project: quagga
File: rtadv.c

rtadv_process_advert (void)
{
  zlog_info ("Router advertisement received");
}

Project: quagga
File: rtadv.c

rtadv_process_packet (u_char *buf, unsigned int len, ifindex_t ifindex,
    int hoplimit, vrf_id_t vrf_id)
{
  struct icmp6_hdr *icmph;
  struct interface *ifp;
  struct zebra_if *zif;

  /* Interface search. */
  ifp = if_lookup_by_index_vrf (ifindex, vrf_id);
  if (ifp == NULL)
    {
      zlog_warn ("Unknown interface index: %d, vrf %u", ifindex, vrf_id);
      return;
    }

  if (if_is_loopback (ifp))
    return;

  /* Check interface configuration. */
  zif = ifp->info;
  if (! zif->rtadv.AdvSendAdvertisements)
    return;

  /* ICMP message length check. */
  if (len < sizeof (struct icmp6_hdr))
    {
      zlog_warn ("Invalid ICMPV6 packet length: %d", len);
      return;
    }

  icmph = (struct icmp6_hdr *) buf;

  /* ICMP message type check. */
  if (icmph->icmp6_type != ND_ROUTER_SOLICIT &&
      icmph->icmp6_type != ND_ROUTER_ADVERT)
    {
      zlog_warn ("Unwanted ICMPV6 message type: %d", icmph->icmp6_type);
      return;
    }

  /* Hoplimit check. */
  if (hoplimit >= 0 && hoplimit != 255)
    {
      zlog_warn ("Invalid hoplimit %d for router advertisement ICMP packet",
		 hoplimit);
      return;
    }

  /* Check ICMP message type. */
  if (icmph->icmp6_type == ND_ROUTER_SOLICIT)
    rtadv_process_solicit (ifp);
  else if (icmph->icmp6_type == ND_ROUTER_ADVERT)
    rtadv_process_advert ();

  return;
}

Project: quagga
File: rtadv.c

rtadv_process_solicit (struct interface *ifp)
{
  struct zebra_vrf *zvrf = vrf_info_lookup (ifp->vrf_id);

  zlog_info ("Router solicitation received on %s vrf %u", ifp->name, zvrf->vrf_id);

  rtadv_send_packet (zvrf->rtadv.sock, ifp);
}

Project: quagga
File: rtadv.c

rtadv_recv_packet (int sock, u_char *buf, int buflen,
		   struct sockaddr_in6 *from, ifindex_t *ifindex,
		   int *hoplimit)
{
  int ret;
  struct msghdr msg;
  struct iovec iov;
  struct cmsghdr  *cmsgptr;
  struct in6_addr dst;

  char adata[1024];

  /* Fill in message and iovec. */
  msg.msg_name = (void *) from;
  msg.msg_namelen = sizeof (struct sockaddr_in6);
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = (void *) adata;
  msg.msg_controllen = sizeof adata;
  iov.iov_base = buf;
  iov.iov_len = buflen;

  /* If recvmsg fail return minus value. */
  ret = recvmsg (sock, &msg, 0);
  if (ret < 0)
    return ret;

  for (cmsgptr = ZCMSG_FIRSTHDR(&msg); cmsgptr != NULL;
       cmsgptr = CMSG_NXTHDR(&msg, cmsgptr)) 
    {
      /* I want interface index which this packet comes from. */
      if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
	  cmsgptr->cmsg_type == IPV6_PKTINFO) 
	{
	  struct in6_pktinfo *ptr;
	  
	  ptr = (struct in6_pktinfo *) CMSG_DATA (cmsgptr);
	  *ifindex = ptr->ipi6_ifindex;
	  memcpy(&dst, &ptr->ipi6_addr, sizeof(ptr->ipi6_addr));
        }

      /* Incoming packet's hop limit. */
      if (cmsgptr->cmsg_level == IPPROTO_IPV6 &&
	  cmsgptr->cmsg_type == IPV6_HOPLIMIT)
	{
	  int *hoptr = (int *) CMSG_DATA (cmsgptr);
	  *hoplimit = *hoptr;
	}
    }
  return ret;
}

Project: quagga
File: rtadv.c

rtadv_send_packet (int sock, struct interface *ifp)
{
  struct msghdr msg;
  struct iovec iov;
  struct cmsghdr  *cmsgptr;
  struct in6_pktinfo *pkt;
  struct sockaddr_in6 addr;
#ifdef HAVE_STRUCT_SOCKADDR_DL
  struct sockaddr_dl *sdl;
#endif /* HAVE_STRUCT_SOCKADDR_DL */
  static void *adata = NULL;
  unsigned char buf[RTADV_MSG_SIZE];
  struct nd_router_advert *rtadv;
  int ret;
  int len = 0;
  struct zebra_if *zif;
  struct rtadv_prefix *rprefix;
  u_char all_nodes_addr[] = {0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
  struct listnode *node;
  u_int16_t pkt_RouterLifetime;

  /*
   * Allocate control message bufffer.  This is dynamic because
   * CMSG_SPACE is not guaranteed not to call a function.  Note that
   * the size will be different on different architectures due to
   * differing alignment rules.
   */
  if (adata == NULL)
    {
      /* XXX Free on shutdown. */
      adata = malloc(CMSG_SPACE(sizeof(struct in6_pktinfo)));
	   
      if (adata == NULL)
	zlog_err("rtadv_send_packet: can't malloc control data\n");
    }

  /* Logging of packet. */
  if (IS_ZEBRA_DEBUG_PACKET)
    zlog_debug ("Router advertisement send to %s", ifp->name);

  /* Fill in sockaddr_in6. */
  memset (&addr, 0, sizeof (struct sockaddr_in6));
  addr.sin6_family = AF_INET6;
#ifdef SIN6_LEN
  addr.sin6_len = sizeof (struct sockaddr_in6);
#endif /* SIN6_LEN */
  addr.sin6_port = htons (IPPROTO_ICMPV6);
  IPV6_ADDR_COPY (&addr.sin6_addr, all_nodes_addr);

  /* Fetch interface information. */
  zif = ifp->info;

  /* Make router advertisement message. */
  rtadv = (struct nd_router_advert *) buf;

  rtadv->nd_ra_type = ND_ROUTER_ADVERT;
  rtadv->nd_ra_code = 0;
  rtadv->nd_ra_cksum = 0;

  rtadv->nd_ra_curhoplimit = 64;

  /* RFC4191: Default Router Preference is 0 if Router Lifetime is 0. */
  rtadv->nd_ra_flags_reserved =
    zif->rtadv.AdvDefaultLifetime == 0 ? 0 : zif->rtadv.DefaultPreference;
  rtadv->nd_ra_flags_reserved <<= 3;

  if (zif->rtadv.AdvManagedFlag)
    rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED;
  if (zif->rtadv.AdvOtherConfigFlag)
    rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER;
  if (zif->rtadv.AdvHomeAgentFlag)
    rtadv->nd_ra_flags_reserved |= ND_RA_FLAG_HOME_AGENT;
  /* Note that according to Neighbor Discovery (RFC 4861 [18]),
   * AdvDefaultLifetime is by default based on the value of
   * MaxRtrAdvInterval.  AdvDefaultLifetime is used in the Router Lifetime
   * field of Router Advertisements.  Given that this field is expressed
   * in seconds, a small MaxRtrAdvInterval value can result in a zero
   * value for this field.  To prevent this, routers SHOULD keep
   * AdvDefaultLifetime in at least one second, even if the use of
   * MaxRtrAdvInterval would result in a smaller value. -- RFC6275, 7.5 */
  pkt_RouterLifetime = zif->rtadv.AdvDefaultLifetime != -1 ?
    zif->rtadv.AdvDefaultLifetime :
    MAX (1, 0.003 * zif->rtadv.MaxRtrAdvInterval);
  rtadv->nd_ra_router_lifetime = htons (pkt_RouterLifetime);
  rtadv->nd_ra_reachable = htonl (zif->rtadv.AdvReachableTime);
  rtadv->nd_ra_retransmit = htonl (0);

  len = sizeof (struct nd_router_advert);

  /* If both the Home Agent Preference and Home Agent Lifetime are set to
   * their default values specified above, this option SHOULD NOT be
   * included in the Router Advertisement messages sent by this home
   * agent. -- RFC6275, 7.4 */
  if
  (
    zif->rtadv.AdvHomeAgentFlag &&
    (zif->rtadv.HomeAgentPreference || zif->rtadv.HomeAgentLifetime != -1)
  )
    {
      struct nd_opt_homeagent_info *ndopt_hai = 
	(struct nd_opt_homeagent_info *)(buf + len);
      ndopt_hai->nd_opt_hai_type = ND_OPT_HA_INFORMATION;
      ndopt_hai->nd_opt_hai_len = 1;
      ndopt_hai->nd_opt_hai_reserved = 0;
      ndopt_hai->nd_opt_hai_preference = htons(zif->rtadv.HomeAgentPreference);
      /* 16-bit unsigned integer.  The lifetime associated with the home
       * agent in units of seconds.  The default value is the same as the
       * Router Lifetime, as specified in the main body of the Router
       * Advertisement.  The maximum value corresponds to 18.2 hours.  A
       * value of 0 MUST NOT be used. -- RFC6275, 7.5 */
      ndopt_hai->nd_opt_hai_lifetime = htons
      (
        zif->rtadv.HomeAgentLifetime != -1 ?
        zif->rtadv.HomeAgentLifetime :
        MAX (1, pkt_RouterLifetime) /* 0 is OK for RL, but not for HAL*/
      );
      len += sizeof(struct nd_opt_homeagent_info);
    }

  if (zif->rtadv.AdvIntervalOption)
    {
      struct nd_opt_adv_interval *ndopt_adv = 
	(struct nd_opt_adv_interval *)(buf + len);
      ndopt_adv->nd_opt_ai_type = ND_OPT_ADV_INTERVAL;
      ndopt_adv->nd_opt_ai_len = 1;
      ndopt_adv->nd_opt_ai_reserved = 0;
      ndopt_adv->nd_opt_ai_interval = htonl(zif->rtadv.MaxRtrAdvInterval);
      len += sizeof(struct nd_opt_adv_interval);
    }

  /* Fill in prefix. */
  for (ALL_LIST_ELEMENTS_RO (zif->rtadv.AdvPrefixList, node, rprefix))
    {
      struct nd_opt_prefix_info *pinfo;

      pinfo = (struct nd_opt_prefix_info *) (buf + len);

      pinfo->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION;
      pinfo->nd_opt_pi_len = 4;
      pinfo->nd_opt_pi_prefix_len = rprefix->prefix.prefixlen;

      pinfo->nd_opt_pi_flags_reserved = 0;
      if (rprefix->AdvOnLinkFlag)
	pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK;
      if (rprefix->AdvAutonomousFlag)
	pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO;
      if (rprefix->AdvRouterAddressFlag)
	pinfo->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_RADDR;

      pinfo->nd_opt_pi_valid_time = htonl (rprefix->AdvValidLifetime);
      pinfo->nd_opt_pi_preferred_time = htonl (rprefix->AdvPreferredLifetime);
      pinfo->nd_opt_pi_reserved2 = 0;

      IPV6_ADDR_COPY (&pinfo->nd_opt_pi_prefix, &rprefix->prefix.prefix);

#ifdef DEBUG
      {
	u_char buf[INET6_ADDRSTRLEN];

	zlog_debug ("DEBUG %s", inet_ntop (AF_INET6, &pinfo->nd_opt_pi_prefix, 
	           buf, INET6_ADDRSTRLEN));

      }
#endif /* DEBUG */

      len += sizeof (struct nd_opt_prefix_info);
    }

  /* Hardware address. */
  if (ifp->hw_addr_len != 0)
    {
      buf[len++] = ND_OPT_SOURCE_LINKADDR;

      /* Option length should be rounded up to next octet if
         the link address does not end on an octet boundary. */
      buf[len++] = (ifp->hw_addr_len + 9) >> 3;

      memcpy (buf + len, ifp->hw_addr, ifp->hw_addr_len);
      len += ifp->hw_addr_len;

      /* Pad option to end on an octet boundary. */
      memset (buf + len, 0, -(ifp->hw_addr_len + 2) & 0x7);
      len += -(ifp->hw_addr_len + 2) & 0x7;
    }

  /* MTU */
  if (zif->rtadv.AdvLinkMTU)
    {
      struct nd_opt_mtu * opt = (struct nd_opt_mtu *) (buf + len);
      opt->nd_opt_mtu_type = ND_OPT_MTU;
      opt->nd_opt_mtu_len = 1;
      opt->nd_opt_mtu_reserved = 0;
      opt->nd_opt_mtu_mtu = htonl (zif->rtadv.AdvLinkMTU);
      len += sizeof (struct nd_opt_mtu);
    }

  msg.msg_name = (void *) &addr;
  msg.msg_namelen = sizeof (struct sockaddr_in6);
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = (void *) adata;
  msg.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo));
  msg.msg_flags = 0;
  iov.iov_base = buf;
  iov.iov_len = len;

  cmsgptr = ZCMSG_FIRSTHDR(&msg);
  cmsgptr->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo));
  cmsgptr->cmsg_level = IPPROTO_IPV6;
  cmsgptr->cmsg_type = IPV6_PKTINFO;

  pkt = (struct in6_pktinfo *) CMSG_DATA (cmsgptr);
  memset (&pkt->ipi6_addr, 0, sizeof (struct in6_addr));
  pkt->ipi6_ifindex = ifp->ifindex;

  ret = sendmsg (sock, &msg, 0);
  if (ret < 0)
    {
      zlog_err ("rtadv_send_packet: sendmsg %d (%s)\n",
		errno, safe_strerror(errno));
    }
}

Project: quagga
File: rtadv.c

rtadv_terminate (struct zebra_vrf *zvrf)
{
  rtadv_event (zvrf, RTADV_STOP, 0);

  if (zvrf->rtadv.sock >= 0)
    {
      close (zvrf->rtadv.sock);
      zvrf->rtadv.sock = -1;
    }

  zvrf->rtadv.adv_if_count = 0;
  zvrf->rtadv.adv_msec_if_count = 0;
}

Project: quagga
File: rtadv.c

rtadv_terminate (struct zebra_vrf *zvrf)
{
  /* Empty.*/;
}

Project: quagga
File: rtadv.c

rtadv_timer (struct thread *thread)
{
  struct zebra_vrf *zvrf = THREAD_ARG (thread);
  struct listnode *node, *nnode;
  struct interface *ifp;
  struct zebra_if *zif;
  int period;

  zvrf->rtadv.ra_timer = NULL;
  if (zvrf->rtadv.adv_msec_if_count == 0)
    {
      period = 1000; /* 1 s */
      rtadv_event (zvrf, RTADV_TIMER, 1 /* 1 s */);
    } 
  else
    {
      period = 10; /* 10 ms */
      rtadv_event (zvrf, RTADV_TIMER_MSEC, 10 /* 10 ms */);
    }

  for (ALL_LIST_ELEMENTS (vrf_iflist (zvrf->vrf_id), node, nnode, ifp))
    {
      if (if_is_loopback (ifp) || ! if_is_operative (ifp))
	continue;

      zif = ifp->info;

      if (zif->rtadv.AdvSendAdvertisements)
	{ 
	  zif->rtadv.AdvIntervalTimer -= period;
	  if (zif->rtadv.AdvIntervalTimer <= 0)
	    {
	      /* FIXME: using MaxRtrAdvInterval each time isn't what section
	         6.2.4 of RFC4861 tells to do. */
	      zif->rtadv.AdvIntervalTimer = zif->rtadv.MaxRtrAdvInterval;
	      rtadv_send_packet (zvrf->rtadv.sock, ifp);
	    }
	}
    }
  return 0;
}