// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Interface looking up by ioctl ().
 * Copyright (C) 1997, 98 Kunihiro Ishiguro
 */

#include <zebra.h>
#include <sys/ioctl.h>

#ifdef OPEN_BSD

#include "if.h"
#include "sockunion.h"
#include "prefix.h"
#include "ioctl.h"
#include "connected.h"
#include "memory.h"
#include "log.h"
#include "vrf.h"
#include "vty.h"
#include "lib_errors.h"

#include "zebra/interface.h"
#include "zebra/rib.h"
#include "zebra/rt.h"
#include "zebra/zebra_errors.h"

#include <ifaddrs.h>

/* Interface looking up using infamous SIOCGIFCONF. */
static int interface_list_ioctl(void)
{
	int ret;
	int sock;
#define IFNUM_BASE 32
	int ifnum;
	struct ifreq *ifreq;
	struct ifconf ifconf;
	struct interface *ifp;
	int n;
	int lastlen;

	/* Normally SIOCGIFCONF works with AF_INET socket. */
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock < 0) {
		flog_err_sys(EC_LIB_SOCKET,
			     "Can't make AF_INET socket stream: %s",
			     safe_strerror(errno));
		return -1;
	}

/* Set initial ifreq count.  This will be double when SIOCGIFCONF
   fail.  Solaris has SIOCGIFNUM. */
#ifdef SIOCGIFNUM
	ret = ioctl(sock, SIOCGIFNUM, &ifnum);
	if (ret < 0)
		ifnum = IFNUM_BASE;
	else
		ifnum++;
#else
	ifnum = IFNUM_BASE;
#endif /* SIOCGIFNUM */

	ifconf.ifc_buf = NULL;

	lastlen = 0;
	/* Loop until SIOCGIFCONF success. */
	for (;;) {
		ifconf.ifc_len = sizeof(struct ifreq) * ifnum;
		ifconf.ifc_buf =
			XREALLOC(MTYPE_TMP, ifconf.ifc_buf, ifconf.ifc_len);

		ret = ioctl(sock, SIOCGIFCONF, &ifconf);

		if (ret < 0) {
			flog_err_sys(EC_LIB_SYSTEM_CALL, "SIOCGIFCONF: %s",
				     safe_strerror(errno));
			goto end;
		}
		/* Repeatedly get info til buffer fails to grow. */
		if (ifconf.ifc_len > lastlen) {
			lastlen = ifconf.ifc_len;
			ifnum += 10;
			continue;
		}
		/* Success. */
		break;
	}

	/* Allocate interface. */
	ifreq = ifconf.ifc_req;

#ifdef OPEN_BSD
	for (n = 0; n < ifconf.ifc_len;) {
		unsigned int size;

		ifreq = (struct ifreq *)((caddr_t)ifconf.ifc_req + n);
		ifp = if_get_by_name(ifreq->ifr_name, VRF_DEFAULT,
				     VRF_DEFAULT_NAME);
		if_add_update(ifp);
		size = ifreq->ifr_addr.sa_len;
		if (size < sizeof(ifreq->ifr_addr))
			size = sizeof(ifreq->ifr_addr);
		size += sizeof(ifreq->ifr_name);
		n += size;
	}
#else
	for (n = 0; n < ifconf.ifc_len; n += sizeof(struct ifreq)) {
		ifp = if_get_by_name(ifreq->ifr_name, VRF_DEFAULT,
				     VRF_DEFAULT_NAME);
		if_add_update(ifp);
		ifreq++;
	}
#endif /* OPEN_BSD */

end:
	close(sock);
	XFREE(MTYPE_TMP, ifconf.ifc_buf);

	return ret;
}

/* Get interface's index by ioctl. */
static int if_get_index(struct interface *ifp)
{
	if_set_index(ifp, if_nametoindex(ifp->name));
	return ifp->ifindex;
}

#ifdef SIOCGIFHWADDR
static int if_get_hwaddr(struct interface *ifp)
{
	int ret;
	struct ifreq ifreq;
	int i;

	strlcpy(ifreq.ifr_name, ifp->name, sizeof(ifreq.ifr_name));
	ifreq.ifr_addr.sa_family = AF_INET;

	/* Fetch Hardware address if available. */
	ret = vrf_if_ioctl(SIOCGIFHWADDR, (caddr_t)&ifreq, ifp->vrf->vrf_id);
	if (ret < 0)
		ifp->hw_addr_len = 0;
	else {
		memcpy(ifp->hw_addr, ifreq.ifr_hwaddr.sa_data, 6);

		for (i = 0; i < 6; i++)
			if (ifp->hw_addr[i] != 0)
				break;

		if (i == 6)
			ifp->hw_addr_len = 0;
		else
			ifp->hw_addr_len = 6;
	}
	return 0;
}
#endif /* SIOCGIFHWADDR */

static int if_getaddrs(void)
{
	int ret;
	struct ifaddrs *ifap;
	struct ifaddrs *ifapfree;
	struct interface *ifp;
	int prefixlen;

	ret = getifaddrs(&ifap);
	if (ret != 0) {
		flog_err_sys(EC_LIB_SYSTEM_CALL, "getifaddrs(): %s",
			     safe_strerror(errno));
		return -1;
	}

	for (ifapfree = ifap; ifap; ifap = ifap->ifa_next) {
		if (ifap->ifa_addr == NULL) {
			flog_err(
				EC_LIB_INTERFACE,
				"%s: nonsensical ifaddr with NULL ifa_addr, ifname %s",
				__func__,
				(ifap->ifa_name ? ifap->ifa_name : "(null)"));
			continue;
		}

		ifp = if_lookup_by_name(ifap->ifa_name, VRF_DEFAULT);
		if (ifp == NULL) {
			flog_err(EC_LIB_INTERFACE,
				 "%s: Can't lookup interface %s", __func__,
				 ifap->ifa_name);
			continue;
		}

		if (ifap->ifa_addr->sa_family == AF_INET) {
			struct sockaddr_in *addr;
			struct sockaddr_in *mask;
			struct sockaddr_in *dest;
			struct in_addr *dest_pnt;
			int flags = 0;

			addr = (struct sockaddr_in *)ifap->ifa_addr;
			mask = (struct sockaddr_in *)ifap->ifa_netmask;
			prefixlen = ip_masklen(mask->sin_addr);

			dest_pnt = NULL;

			if (if_is_pointopoint(ifp) && ifap->ifa_dstaddr
			    && !IPV4_ADDR_SAME(&addr->sin_addr,
					       &((struct sockaddr_in *)
							 ifap->ifa_dstaddr)
							->sin_addr)) {
				dest = (struct sockaddr_in *)ifap->ifa_dstaddr;
				dest_pnt = &dest->sin_addr;
				flags = ZEBRA_IFA_PEER;
			} else if (ifap->ifa_broadaddr
				   && !IPV4_ADDR_SAME(
					      &addr->sin_addr,
					      &((struct sockaddr_in *)
							ifap->ifa_broadaddr)
						       ->sin_addr)) {
				dest = (struct sockaddr_in *)
					       ifap->ifa_broadaddr;
				dest_pnt = &dest->sin_addr;
			}

			connected_add_ipv4(ifp, flags, &addr->sin_addr,
					   prefixlen, dest_pnt, NULL,
					   METRIC_MAX);
		}
		if (ifap->ifa_addr->sa_family == AF_INET6) {
			struct sockaddr_in6 *addr;
			struct sockaddr_in6 *mask;
			int flags = 0;

			addr = (struct sockaddr_in6 *)ifap->ifa_addr;
			mask = (struct sockaddr_in6 *)ifap->ifa_netmask;
			prefixlen = ip6_masklen(mask->sin6_addr);

#if defined(KAME)
			if (IN6_IS_ADDR_LINKLOCAL(&addr->sin6_addr)) {
				addr->sin6_scope_id =
					ntohs(*(uint16_t *)&addr->sin6_addr
						       .s6_addr[2]);
				addr->sin6_addr.s6_addr[2] =
					addr->sin6_addr.s6_addr[3] = 0;
			}
#endif

			connected_add_ipv6(ifp, flags, &addr->sin6_addr, NULL,
					   prefixlen, NULL, METRIC_MAX);
		}
	}

	freeifaddrs(ifapfree);

	return 0;
}

/* Fetch interface information via ioctl(). */
static void interface_info_ioctl()
{
	struct vrf *vrf = vrf_lookup_by_id(VRF_DEFAULT);
	struct interface *ifp;

	FOR_ALL_INTERFACES (vrf, ifp) {
		if_get_index(ifp);
#ifdef SIOCGIFHWADDR
		if_get_hwaddr(ifp);
#endif /* SIOCGIFHWADDR */
		if_get_flags(ifp);
		if_get_mtu(ifp);
		if_get_metric(ifp);
	}
}

/* Lookup all interface information. */
void interface_list(struct zebra_ns *zns)
{

	zlog_info("%s: NS %u", __func__, zns->ns_id);

/* Linux can do both proc & ioctl, ioctl is the only way to get
   interface aliases in 2.2 series kernels. */
#ifdef HAVE_PROC_NET_DEV
	interface_list_proc();
#endif /* HAVE_PROC_NET_DEV */
	interface_list_ioctl();

	/* After listing is done, get index, address, flags and other
	   interface's information. */
	interface_info_ioctl();

	if_getaddrs();

#if defined(HAVE_PROC_NET_IF_INET6)
	/* Linux provides interface's IPv6 address via
	   /proc/net/if_inet6. */
	ifaddr_proc_ipv6();
#endif /* HAVE_PROC_NET_IF_INET6 */
}

#endif /* OPEN_BSD */
