// SPDX-License-Identifier: GPL-2.0-or-later
/* RIPngd Zebra
 * Copyright (C) 2002 6WIND <vincent.jardin@6wind.com>
 */

/* This file is required in order to support properly the RIPng nexthop
 * feature.
 */

#include <zebra.h>

/* For struct udphdr. */
#include <netinet/udp.h>

#include "linklist.h"
#include "stream.h"
#include "log.h"
#include "memory.h"
#include "vty.h"
#include "if.h"
#include "prefix.h"

#include "ripngd/ripngd.h"
#include "ripngd/ripng_debug.h"
#include "ripngd/ripng_nexthop.h"

DEFINE_MTYPE_STATIC(RIPNGD, RIPNG_RTE_DATA, "RIPng rte data");

#define DEBUG 1

struct ripng_rte_data {
	struct prefix_ipv6 *p;
	struct ripng_info *rinfo;
	struct ripng_aggregate *aggregate;
};

void _ripng_rte_del(struct ripng_rte_data *A);
int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B);

#define METRIC_OUT(a)                                                          \
	((a)->rinfo ? (a)->rinfo->metric_out : (a)->aggregate->metric_out)
#define NEXTHOP_OUT_PTR(a)                                                     \
	((a)->rinfo ? &((a)->rinfo->nexthop_out)                               \
		    : &((a)->aggregate->nexthop_out))
#define TAG_OUT(a) ((a)->rinfo ? (a)->rinfo->tag_out : (a)->aggregate->tag_out)

struct list *ripng_rte_new(void)
{
	struct list *rte;

	rte = list_new();
	rte->cmp = (int (*)(void *, void *))_ripng_rte_cmp;
	rte->del = (void (*)(void *))_ripng_rte_del;

	return rte;
}

void ripng_rte_free(struct list *ripng_rte_list)
{
	list_delete(&ripng_rte_list);
}

/* Delete RTE */
void _ripng_rte_del(struct ripng_rte_data *A)
{
	XFREE(MTYPE_RIPNG_RTE_DATA, A);
}

/* Compare RTE:
 *  return +  if A > B
 *         0  if A = B
 *         -  if A < B
 */
int _ripng_rte_cmp(struct ripng_rte_data *A, struct ripng_rte_data *B)
{
	return addr6_cmp(NEXTHOP_OUT_PTR(A), NEXTHOP_OUT_PTR(B));
}

/* Add routing table entry */
void ripng_rte_add(struct list *ripng_rte_list, struct prefix_ipv6 *p,
		   struct ripng_info *rinfo, struct ripng_aggregate *aggregate)
{

	struct ripng_rte_data *data;

	/* At least one should not be null */
	assert(!rinfo || !aggregate);

	data = XMALLOC(MTYPE_RIPNG_RTE_DATA, sizeof(*data));
	data->p = p;
	data->rinfo = rinfo;
	data->aggregate = aggregate;

	listnode_add_sort(ripng_rte_list, data);
}

/* Send the RTE with the nexthop support
 */
void ripng_rte_send(struct list *ripng_rte_list, struct interface *ifp,
		    struct sockaddr_in6 *to)
{
	struct ripng_interface *ri = ifp->info;
	struct ripng *ripng = ri->ripng;
	struct ripng_rte_data *data;
	struct listnode *node, *nnode;

	struct in6_addr last_nexthop;
	struct in6_addr myself_nexthop;

	struct stream *s;
	int num;
	int mtu;
	int rtemax;
	int ret;

	/* Most of the time, there is no nexthop */
	memset(&last_nexthop, 0, sizeof(last_nexthop));

	/* Use myself_nexthop if the nexthop is not a link-local address,
	 * because
	 * we remain a right path without beeing the optimal one.
	 */
	memset(&myself_nexthop, 0, sizeof(myself_nexthop));

	/* Output stream get from ripng structre.  XXX this should be
	   interface structure. */
	s = ripng->obuf;

	/* Reset stream and RTE counter. */
	stream_reset(s);
	num = 0;

	mtu = ifp->mtu6;
	if (mtu < 0)
		mtu = IFMINMTU;

	rtemax = (MIN(mtu, RIPNG_MAX_PACKET_SIZE) - IPV6_HDRLEN
		  - sizeof(struct udphdr) - sizeof(struct ripng_packet)
		  + sizeof(struct rte))
		 / sizeof(struct rte);

	for (ALL_LIST_ELEMENTS(ripng_rte_list, node, nnode, data)) {
		/* (2.1) Next hop support */
		if (!IPV6_ADDR_SAME(&last_nexthop, NEXTHOP_OUT_PTR(data))) {

			/* A nexthop entry should be at least followed by 1 RTE
			 */
			if (num == (rtemax - 1)) {
				ret = ripng_send_packet((caddr_t)STREAM_DATA(s),
							stream_get_endp(s), to,
							ifp);

				if (ret >= 0 && IS_RIPNG_DEBUG_SEND)
					ripng_packet_dump(
						(struct ripng_packet *)
							STREAM_DATA(s),
						stream_get_endp(s), "SEND");
				num = 0;
				stream_reset(s);
			}

			/* Add the nexthop (2.1) */

			/* If the received next hop address is not a link-local
			 * address,
			 * it should be treated as 0:0:0:0:0:0:0:0.
			 */
			if (!IN6_IS_ADDR_LINKLOCAL(NEXTHOP_OUT_PTR(data)))
				last_nexthop = myself_nexthop;
			else
				last_nexthop = *NEXTHOP_OUT_PTR(data);

			num = ripng_write_rte(num, s, NULL, &last_nexthop, 0,
					      RIPNG_METRIC_NEXTHOP);
		} else {
			/* Rewrite the nexthop for each new packet */
			if ((num == 0)
			    && !IPV6_ADDR_SAME(&last_nexthop, &myself_nexthop))
				num = ripng_write_rte(num, s, NULL,
						      &last_nexthop, 0,
						      RIPNG_METRIC_NEXTHOP);
		}
		num = ripng_write_rte(num, s, data->p, NULL, TAG_OUT(data),
				      METRIC_OUT(data));

		if (num == rtemax) {
			ret = ripng_send_packet((caddr_t)STREAM_DATA(s),
						stream_get_endp(s), to, ifp);

			if (ret >= 0 && IS_RIPNG_DEBUG_SEND)
				ripng_packet_dump(
					(struct ripng_packet *)STREAM_DATA(s),
					stream_get_endp(s), "SEND");
			num = 0;
			stream_reset(s);
		}
	}

	/* If unwritten RTE exist, flush it. */
	if (num != 0) {
		ret = ripng_send_packet((caddr_t)STREAM_DATA(s),
					stream_get_endp(s), to, ifp);

		if (ret >= 0 && IS_RIPNG_DEBUG_SEND)
			ripng_packet_dump((struct ripng_packet *)STREAM_DATA(s),
					  stream_get_endp(s), "SEND");
		stream_reset(s);
	}
}
