/* $Id: pxremap.c $ */
/** @file
 * NAT Network - Loopback remapping.
 */

/*
 * Copyright (C) 2013-2023 Oracle and/or its affiliates.
 *
 * This file is part of VirtualBox base platform packages, as
 * available from https://www.virtualbox.org.
 *
 * This program 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, in version 3 of the
 * License.
 *
 * This program 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; if not, see <https://www.gnu.org/licenses>.
 *
 * SPDX-License-Identifier: GPL-3.0-only
 */

/*
 * This file contains functions pertinent to magic address remapping.
 *
 * We want to expose host's loopback interfaces to the guest by
 * mapping them to the addresses from the same prefix/subnet, so if,
 * for example proxy interface is 10.0.2.1, we redirect traffic to
 * 10.0.2.2 to host's 127.0.0.1 loopback.  If need be, we may extend
 * this to provide additional mappings, e.g. 127.0.1.1 loopback
 * address is used on Ubuntu 12.10+ for NetworkManager's dnsmasq.
 *
 * Ditto for IPv6, except that IPv6 only has one loopback address.
 */
#define LOG_GROUP LOG_GROUP_NAT_SERVICE

#include "winutils.h"
#include "pxremap.h"
#include "proxy.h"

#include "lwip/netif.h"
#include "netif/etharp.h"       /* proxy arp hook */

#include "lwip/ip4.h"           /* IPv4 divert hook */
#include "lwip/ip6.h"           /* IPv6 divert hook */

#include <string.h>


/**
 * Check if "dst" is an IPv4 address that proxy remaps to host's
 * loopback.
 */
static int
proxy_ip4_is_mapped_loopback(struct netif *netif, const ip_addr_t *dst, ip_addr_t *lo)
{
    u32_t off;
    const struct ip4_lomap *lomap;
    size_t i;

    LWIP_ASSERT1(dst != NULL);

    if (g_proxy_options->lomap_desc == NULL) {
        return 0;
    }

    if (!ip_addr_netcmp(dst, &netif->ip_addr, &netif->netmask)) {
        return 0;
    }

    /* XXX: TODO: check netif is a proxying netif! */

    off = ntohl(ip4_addr_get_u32(dst) & ~ip4_addr_get_u32(&netif->netmask));
    lomap = g_proxy_options->lomap_desc->lomap;
    for (i = 0; i < g_proxy_options->lomap_desc->num_lomap; ++i) {
        if (off == lomap[i].off) {
            if (lo != NULL) {
                ip_addr_copy(*lo, lomap[i].loaddr);
            }
            return 1;
        }
    }
    return 0;
}


#if ARP_PROXY
/**
 * Hook function for etharp_arp_input() - returns true to cause proxy
 * ARP reply to be generated for "dst".
 */
int
pxremap_proxy_arp(struct netif *netif, ip_addr_t *dst)
{
    return proxy_ip4_is_mapped_loopback(netif, dst, NULL);
}
#endif /* ARP_PROXY */


/**
 * Hook function for ip_forward() - returns true to divert packets to
 * "dst" to proxy (instead of forwarding them via "netif" or dropping).
 */
int
pxremap_ip4_divert(struct netif *netif, ip_addr_t *dst)
{
    return proxy_ip4_is_mapped_loopback(netif, dst, NULL);
}


/**
 * Mapping from local network to loopback for outbound connections.
 *
 * Copy "src" to "dst" with ip_addr_set(dst, src), but if "src" is a
 * local network address that maps host's loopback address, copy
 * loopback address to "dst".
 */
int
pxremap_outbound_ip4(ip_addr_t *dst, ip_addr_t *src)
{
    struct netif *netif;

    LWIP_ASSERT1(dst != NULL);
    LWIP_ASSERT1(src != NULL);

    for (netif = netif_list; netif != NULL; netif = netif->next) {
        if (netif_is_up(netif) /* && this is a proxy netif */) {
            if (proxy_ip4_is_mapped_loopback(netif, src, dst)) {
                return PXREMAP_MAPPED;
            }
        }
    }

    /* not remapped, just copy src */
    ip_addr_set(dst, src);
    return PXREMAP_ASIS;
}


/**
 * Mapping from loopback to local network for inbound (port-forwarded)
 * connections.
 *
 * Copy "src" to "dst" with ip_addr_set(dst, src), but if "src" is a
 * host's loopback address, copy local network address that maps it to
 * "dst".
 */
int
pxremap_inbound_ip4(ip_addr_t *dst, ip_addr_t *src)
{
    struct netif *netif;
    const struct ip4_lomap *lomap;
    unsigned int i;

    if (ip4_addr1(src) != IP_LOOPBACKNET) {
        ip_addr_set(dst, src);
        return PXREMAP_ASIS;
    }

    if (g_proxy_options->lomap_desc == NULL) {
        return PXREMAP_FAILED;
    }

#if 0 /* ?TODO: with multiple interfaces we need to consider fwspec::dst */
    netif = ip_route(target);
    if (netif == NULL) {
        return PXREMAP_FAILED;
    }
#else
    netif = netif_list;
    LWIP_ASSERT1(netif != NULL);
    LWIP_ASSERT1(netif->next == NULL);
#endif

    lomap = g_proxy_options->lomap_desc->lomap;
    for (i = 0; i < g_proxy_options->lomap_desc->num_lomap; ++i) {
        if (ip_addr_cmp(src, &lomap[i].loaddr)) {
            ip_addr_t net;

            ip_addr_get_network(&net, &netif->ip_addr, &netif->netmask);
            ip4_addr_set_u32(dst,
                             htonl(ntohl(ip4_addr_get_u32(&net))
                                   + lomap[i].off));
            return PXREMAP_MAPPED;
        }
    }

    return PXREMAP_FAILED;
}


static int
proxy_ip6_is_mapped_loopback(struct netif *netif, ip6_addr_t *dst)
{
    int i;

    /* XXX: TODO: check netif is a proxying netif! */

    LWIP_ASSERT1(dst != NULL);

    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
        if (ip6_addr_ispreferred(netif_ip6_addr_state(netif, i))
            && ip6_addr_isuniquelocal(netif_ip6_addr(netif, i)))
        {
            ip6_addr_t *ifaddr = netif_ip6_addr(netif, i);
            if (memcmp(dst, ifaddr, sizeof(ip6_addr_t) - 1) == 0
                && ((IP6_ADDR_BLOCK8(dst) & 0xff)
                    == (IP6_ADDR_BLOCK8(ifaddr) & 0xff) + 1))
            {
                return 1;
            }
        }
    }

    return 0;
}


/**
 * Hook function for nd6_input() - returns true to cause proxy NA
 * reply to be generated for "dst".
 */
int
pxremap_proxy_na(struct netif *netif, ip6_addr_t *dst)
{
    return proxy_ip6_is_mapped_loopback(netif, dst);
}


/**
 * Hook function for ip6_forward() - returns true to divert packets to
 * "dst" to proxy (instead of forwarding them via "netif" or dropping).
 */
int
pxremap_ip6_divert(struct netif *netif, ip6_addr_t *dst)
{
    return proxy_ip6_is_mapped_loopback(netif, dst);
}


/**
 * Mapping from local network to loopback for outbound connections.
 *
 * Copy "src" to "dst" with ip6_addr_set(dst, src), but if "src" is a
 * local network address that maps host's loopback address, copy IPv6
 * loopback address to "dst".
 */
int
pxremap_outbound_ip6(ip6_addr_t *dst, ip6_addr_t *src)
{
    struct netif *netif;
    int i;

    LWIP_ASSERT1(dst != NULL);
    LWIP_ASSERT1(src != NULL);

    for (netif = netif_list; netif != NULL; netif = netif->next) {
        if (!netif_is_up(netif) /* || this is not a proxy netif */) {
            continue;
        }

        for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
            if (ip6_addr_ispreferred(netif_ip6_addr_state(netif, i))
                && ip6_addr_isuniquelocal(netif_ip6_addr(netif, i)))
            {
                ip6_addr_t *ifaddr = netif_ip6_addr(netif, i);
                if (memcmp(src, ifaddr, sizeof(ip6_addr_t) - 1) == 0
                    && ((IP6_ADDR_BLOCK8(src) & 0xff)
                        == (IP6_ADDR_BLOCK8(ifaddr) & 0xff) + 1))
                {
                    ip6_addr_set_loopback(dst);
                    return PXREMAP_MAPPED;
                }
            }
        }
    }

    /* not remapped, just copy src */
    ip6_addr_set(dst, src);
    return PXREMAP_ASIS;
}


/**
 * Mapping from loopback to local network for inbound (port-forwarded)
 * connections.
 *
 * Copy "src" to "dst" with ip6_addr_set(dst, src), but if "src" is a
 * host's loopback address, copy local network address that maps it to
 * "dst".
 */
int
pxremap_inbound_ip6(ip6_addr_t *dst, ip6_addr_t *src)
{
    ip6_addr_t loopback;
    struct netif *netif;
    int i;

    ip6_addr_set_loopback(&loopback);
    if (!ip6_addr_cmp(src, &loopback)) {
        ip6_addr_set(dst, src);
        return PXREMAP_ASIS;
    }

#if 0  /* ?TODO: with multiple interfaces we need to consider fwspec::dst */
    netif = ip6_route_fwd(target);
    if (netif == NULL) {
        return PXREMAP_FAILED;
    }
#else
    netif = netif_list;
    LWIP_ASSERT1(netif != NULL);
    LWIP_ASSERT1(netif->next == NULL);
#endif

    for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; ++i) {
        ip6_addr_t *ifaddr = netif_ip6_addr(netif, i);
        if (ip6_addr_ispreferred(netif_ip6_addr_state(netif, i))
            && ip6_addr_isuniquelocal(ifaddr))
        {
            ip6_addr_set(dst, ifaddr);
            ++((u8_t *)&dst->addr[3])[3];
            return PXREMAP_MAPPED;
        }
    }

    return PXREMAP_FAILED;
}
