// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *
 * Copyright (C) 2000  Robert Olsson.
 * Swedish University of Agricultural Sciences
 */

/*
 * 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 <netinet/ip_icmp.h>

#include "checksum.h"
#include "command.h"
#include "connected.h"
#include "if.h"
#include "ioctl.h"
#include "log.h"
#include "log.h"
#include "memory.h"
#include "prefix.h"
#include "sockopt.h"
#include "sockunion.h"
#include "sockunion.h"
#include "stream.h"
#include "frrevent.h"
#include "vty.h"
#include "zclient.h"
#include "lib_errors.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"


/* GLOBAL VARS */

int irdp_sock = -1;

extern struct event *t_irdp_raw;

static void parse_irdp_packet(char *p, int len, struct interface *ifp)
{
	struct ip *ip = (struct ip *)p;
	struct icmphdr *icmp;
	struct in_addr src;
	int ip_hlen, iplen, datalen;
	struct zebra_if *zi;
	struct irdp_interface *irdp;
	uint16_t saved_chksum;
	char buf[PREFIX_STRLEN];

	zi = ifp->info;
	if (!zi)
		return;

	irdp = zi->irdp;
	if (!irdp)
		return;

	ip_hlen = ip->ip_hl << 2;

	sockopt_iphdrincl_swab_systoh(ip);

	iplen = ip->ip_len;
	datalen = len - ip_hlen;
	src = ip->ip_src;

	if (len != iplen) {
		flog_err(EC_ZEBRA_IRDP_LEN_MISMATCH,
			 "IRDP: RX length doesn't match IP length");
		return;
	}

	if (iplen < ICMP_MINLEN) {
		flog_err(EC_ZEBRA_IRDP_LEN_MISMATCH,
			 "IRDP: RX ICMP packet too short from %pI4",
			 &src);
		return;
	}

	/* XXX: RAW doesn't receive link-layer, surely? ??? */
	/* Check so we don't checksum packets longer than oure RX_BUF - (ethlen
	 +
	 len of IP-header) 14+20 */
	if (iplen > IRDP_RX_BUF - 34) {
		flog_err(EC_ZEBRA_IRDP_LEN_MISMATCH,
			 "IRDP: RX ICMP packet too long from %pI4",
			 &src);
		return;
	}

	icmp = (struct icmphdr *)(p + ip_hlen);

	saved_chksum = icmp->checksum;
	icmp->checksum = 0;
	/* check icmp checksum */
	if (in_cksum(icmp, datalen) != saved_chksum) {
		flog_warn(
			EC_ZEBRA_IRDP_BAD_CHECKSUM,
			"IRDP: RX ICMP packet from %pI4 Bad checksum, silently ignored",
			&src);
		return;
	}

	/* Handle just only IRDP */
	if (!(icmp->type == ICMP_ROUTERADVERT
	      || icmp->type == ICMP_ROUTERSOLICIT))
		return;

	if (icmp->code != 0) {
		flog_warn(
			EC_ZEBRA_IRDP_BAD_TYPE_CODE,
			"IRDP: RX packet type %d from %pI4 Bad ICMP type code, silently ignored",
			icmp->type, &src);
		return;
	}

	if (!((ntohl(ip->ip_dst.s_addr) == INADDR_BROADCAST)
	      && (irdp->flags & IF_BROADCAST))
	    || (ntohl(ip->ip_dst.s_addr) == INADDR_ALLRTRS_GROUP
		&& !(irdp->flags & IF_BROADCAST))) {
		flog_warn(
			EC_ZEBRA_IRDP_BAD_RX_FLAGS,
			"IRDP: RX illegal from %pI4 to %s while %s operates in %s; Please correct settings",
			&src,
			ntohl(ip->ip_dst.s_addr) == INADDR_ALLRTRS_GROUP
				? "multicast"
				: inet_ntop(AF_INET, &ip->ip_dst,
					    buf, sizeof(buf)),
			ifp->name,
			irdp->flags & IF_BROADCAST ? "broadcast" : "multicast");
		return;
	}

	switch (icmp->type) {
	case ICMP_ROUTERADVERT:
		break;

	case ICMP_ROUTERSOLICIT:

		if (irdp->flags & IF_DEBUG_MESSAGES)
			zlog_debug("IRDP: RX Solicit on %s from %pI4",
				   ifp->name, &src);

		process_solicit(ifp);
		break;

	default:
		flog_warn(
			EC_ZEBRA_IRDP_BAD_TYPE_CODE,
			"IRDP: RX packet type %d from %pI4 Bad ICMP type code, silently ignored",
			icmp->type, &src);
	}
}

static int irdp_recvmsg(int sock, uint8_t *buf, int size, int *ifindex)
{
	struct msghdr msg;
	struct iovec iov;
	char adata[CMSG_SPACE(SOPT_SIZE_CMSG_PKTINFO_IPV4())];
	int ret;

	memset(&msg, 0, sizeof(msg));
	msg.msg_name = (void *)0;
	msg.msg_namelen = 0;
	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 = size;

	ret = recvmsg(sock, &msg, 0);
	if (ret < 0) {
		flog_warn(EC_LIB_SOCKET, "IRDP: recvmsg: read error %s",
			  safe_strerror(errno));
		return ret;
	}

	if (msg.msg_flags & MSG_TRUNC) {
		flog_warn(EC_LIB_SOCKET, "IRDP: recvmsg: truncated message");
		return ret;
	}
	if (msg.msg_flags & MSG_CTRUNC) {
		flog_warn(EC_LIB_SOCKET,
			  "IRDP: recvmsg: truncated control message");
		return ret;
	}

	*ifindex = getsockopt_ifindex(AF_INET, &msg);

	return ret;
}

void irdp_read_raw(struct event *r)
{
	struct interface *ifp;
	struct zebra_if *zi;
	struct irdp_interface *irdp;
	char buf[IRDP_RX_BUF];
	int ret, ifindex = 0;

	int irdp_sock = EVENT_FD(r);
	event_add_read(zrouter.master, irdp_read_raw, NULL, irdp_sock,
		       &t_irdp_raw);

	ret = irdp_recvmsg(irdp_sock, (uint8_t *)buf, IRDP_RX_BUF, &ifindex);

	if (ret < 0)
		flog_warn(EC_LIB_SOCKET, "IRDP: RX Error length = %d", ret);

	ifp = if_lookup_by_index(ifindex, VRF_DEFAULT);
	if (!ifp)
		return;

	zi = ifp->info;
	if (!zi)
		return;

	irdp = zi->irdp;
	if (!irdp)
		return;

	if (!(irdp->flags & IF_ACTIVE)) {

		if (irdp->flags & IF_DEBUG_MISC)
			zlog_debug("IRDP: RX ICMP for disabled interface %s",
				   ifp->name);
		return;
	}

	if (irdp->flags & IF_DEBUG_PACKET) {
		int i;
		zlog_debug("IRDP: RX (idx %d) ", ifindex);
		for (i = 0; i < ret; i++)
			zlog_debug("IRDP: RX %x ", buf[i] & 0xFF);
	}

	parse_irdp_packet(buf, ret, ifp);
}

void send_packet(struct interface *ifp, struct stream *s, uint32_t dst,
		 struct prefix *p, uint32_t ttl)
{
	static struct sockaddr_in sockdst = {AF_INET};
	struct ip *ip;
	struct icmphdr *icmp;
	struct msghdr *msg;
	struct cmsghdr *cmsg;
	struct iovec iovector;
	char msgbuf[256];
	char buf[256];
	struct in_pktinfo *pktinfo;
	unsigned long src;
	uint8_t on;

	if (!(ifp->flags & IFF_UP))
		return;

	if (p)
		src = ntohl(p->u.prefix4.s_addr);
	else
		src = 0; /* Is filled in */

	ip = (struct ip *)buf;
	ip->ip_hl = sizeof(struct ip) >> 2;
	ip->ip_v = IPVERSION;
	ip->ip_tos = 0xC0;
	ip->ip_off = 0L;
	ip->ip_p = 1; /* IP_ICMP */
	ip->ip_ttl = ttl;
	ip->ip_src.s_addr = src;
	ip->ip_dst.s_addr = dst;
	icmp = (struct icmphdr *)(buf + sizeof(struct ip));

	/* Merge IP header with icmp packet */
	assert(stream_get_endp(s) < (sizeof(buf) - sizeof(struct ip)));
	stream_get(icmp, s, stream_get_endp(s));

	/* icmp->checksum is already calculated */
	ip->ip_len = sizeof(struct ip) + stream_get_endp(s);

	on = 1;
	if (setsockopt(irdp_sock, IPPROTO_IP, IP_HDRINCL, (char *)&on,
		       sizeof(on))
	    < 0)
		flog_err(EC_LIB_SOCKET,
			 "IRDP: Cannot set IP_HDRINCLU %s(%d) on %s",
			 safe_strerror(errno), errno, ifp->name);


	if (dst == INADDR_BROADCAST) {
		uint32_t bon = 1;

		if (setsockopt(irdp_sock, SOL_SOCKET, SO_BROADCAST, &bon,
			       sizeof(bon))
		    < 0)
			flog_err(EC_LIB_SOCKET,
				 "IRDP: Cannot set SO_BROADCAST %s(%d) on %s",
				 safe_strerror(errno), errno, ifp->name);
	}

	if (dst != INADDR_BROADCAST)
		setsockopt_ipv4_multicast_loop(irdp_sock, 0);

	memset(&sockdst, 0, sizeof(sockdst));
	sockdst.sin_family = AF_INET;
	sockdst.sin_addr.s_addr = dst;

	cmsg = (struct cmsghdr *)(msgbuf + sizeof(struct msghdr));
	cmsg->cmsg_len = sizeof(struct cmsghdr) + sizeof(struct in_pktinfo);
	cmsg->cmsg_level = SOL_IP;
	cmsg->cmsg_type = IP_PKTINFO;
	pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg);
	pktinfo->ipi_ifindex = ifp->ifindex;
	pktinfo->ipi_spec_dst.s_addr = src;
	pktinfo->ipi_addr.s_addr = src;

	iovector.iov_base = (void *)buf;
	iovector.iov_len = ip->ip_len;
	msg = (struct msghdr *)msgbuf;
	msg->msg_name = &sockdst;
	msg->msg_namelen = sizeof(sockdst);
	msg->msg_iov = &iovector;
	msg->msg_iovlen = 1;
	msg->msg_control = cmsg;
	msg->msg_controllen = cmsg->cmsg_len;

	sockopt_iphdrincl_swab_htosys(ip);

	if (sendmsg(irdp_sock, msg, 0) < 0)
		flog_err(EC_LIB_SOCKET,
			 "IRDP: sendmsg send failure %s(%d) on %s",
			 safe_strerror(errno), errno, ifp->name);
}
