root/sys/netinet/ip_ecn.c
/*      $KAME: ip_ecn.c,v 1.12 2002/01/07 11:34:47 kjc Exp $    */

/*-
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Copyright (C) 1999 WIDE Project.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the project nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include "opt_inet.h"
#include "opt_inet6.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/mbuf.h>
#include <sys/errno.h>

#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#ifdef INET6
#include <netinet/ip6.h>
#endif

#include <netinet/ip_ecn.h>
#ifdef INET6
#include <netinet6/ip6_ecn.h>
#endif

/*
 * ECN and TOS (or TCLASS) processing rules at tunnel encapsulation and
 * decapsulation from RFC6040:
 *
 *                      Outer Hdr at                 Inner Hdr at
 *                      Encapsulator                 Decapsulator
 *   Header fields:     --------------------         ------------
 *     DS Field         copied from inner hdr        no change
 *     ECN Field        constructed by (I)           constructed by (E)
 *
 * ECN_ALLOWED (normal mode):
 *    (I) copy the ECN field to the outer header.
 *
 *    (E) if the ECN field in the outer header is set to CE and the ECN
 *    field of the inner header is not-ECT, drop the packet.
 *    If the ECN field in the inner header is set to ECT(0) and the ECN
 *    field in the outer header is set to ECT(1), copy ECT(1) to
 *    the inner header. If the ECN field in the inner header is set
 *    to ECT(0) or ECT(1) and the ECN field in the outer header is set to
 *    CE, copy CE to the inner header.
 *    Otherwise, make no change to the inner header. This behaviour can be
 *    summarized in the table below:
 *
 *                      Outer Header at Decapsulator
 *               +---------+------------+------------+------------+
 *               | Not-ECT | ECT(0)     | ECT(1)     |     CE     |
 *    Inner Hdr: +---------+------------+------------+------------+
 *      Not-ECT  | Not-ECT |Not-ECT(!!!)|Not-ECT(!!!)| <drop>(!!!)|
 *       ECT(0)  |  ECT(0) | ECT(0)     | ECT(1)     |     CE     |
 *       ECT(1)  |  ECT(1) | ECT(1) (!) | ECT(1)     |     CE     |
 *         CE    |      CE |     CE     |     CE(!!!)|     CE     |
 *               +---------+------------+------------+------------+
 *
 * ECN_COMPLETE (normal mode with security log):
 *    certain combinations indicated in table by '(!!!)' or '(!)',
 *    where '(!!!)' means the combination always potentially dangerous which
 *    returns 3, while '(!)' means possibly dangerous in which returns 2.
 *    These combinations are unsed by previous ECN tunneling specifications
 *    and could be logged. Also, in case of more dangerous ones, the
 *    decapsulator SHOULD log the event and MAY also raise an alarm.
 *
 *    Note: Caller SHOULD use rate-limited alarms so that the anomalous
 *    combinations will not amplify into a flood of alarm messages.
 *    Also, it MUST be possible to suppress alarms or logging.
 *
 * ECN_FORBIDDEN (compatibility mode):
 *    (I) set the ECN field to not-ECT in the outer header.
 *
 *    (E) if the ECN field in the outer header is set to CE, drop the packet.
 *    otherwise, make no change to the ECN field in the inner header.
 *
 * the drop rule is for backward compatibility and protection against
 * erasure of CE.
 */

/*
 * modify outer ECN (TOS) field on ingress operation (tunnel encapsulation).
 */
void
ip_ecn_ingress(int mode, uint8_t *outer, const uint8_t *inner)
{

        KASSERT(outer != NULL && inner != NULL,
            ("NULL pointer passed to %s", __func__));

        *outer = *inner;
        switch (mode) {
        case ECN_COMPLETE:
        case ECN_ALLOWED:
                /* normal mode: always copy the ECN field. */
                break;

        case ECN_FORBIDDEN:
                /* compatibility mode: set not-ECT to the outer */
                *outer &= ~IPTOS_ECN_MASK;
                break;

        case ECN_NOCARE:
                break;
        }
}

/*
 * modify inner ECN (TOS) field on egress operation (tunnel decapsulation).
 * the caller should drop the packet if the return value is 0.
 */
int
ip_ecn_egress(int mode, const uint8_t *outer, uint8_t *inner)
{

        KASSERT(outer != NULL && inner != NULL,
            ("NULL pointer passed to %s", __func__));

        switch (mode) {
        case ECN_COMPLETE:
                if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0) {
                        /* if the outer is ECT(0) and inner is ECT(1) raise a warning */
                        if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1)
                                return (ECN_WARN);
                        /* if the inner is not-ECT and outer is ECT(0) raise an alarm */
                        if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT)
                                return (ECN_ALARM);
                        return (ECN_SUCCESS);
                } else if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1) {
                        /* if the outer is ECT(1) and inner is CE or ECT(1), raise an alarm */
                        if (((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_CE) ||
                            ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT))
                                return (ECN_ALARM);
                        /* if the outer is ECT(1) and inner is ECT(0), copy ECT(1) */
                        if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0)
                                *inner = IPTOS_ECN_ECT1;
                        return (ECN_SUCCESS);
                }
                /* fallthrough */
        case ECN_ALLOWED:
                if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_CE) {
                        /* if the outer is CE and the inner is not-ECT, drop it. */
                        if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_NOTECT)
                                return (ECN_DROP);
                        /* otherwise, copy CE */
                        *inner |= IPTOS_ECN_CE;
                } else if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_ECT1) {
                        /* if the outer is ECT(1) and inner is ECT(0), copy ECT(1) */
                        if ((*inner & IPTOS_ECN_MASK) == IPTOS_ECN_ECT0)
                                *inner = IPTOS_ECN_ECT1;
                }
                break;

        case ECN_FORBIDDEN:
                /*
                 * compatibility mode: if the outer is CE, should drop it.
                 * otherwise, leave the inner.
                 */
                if ((*outer & IPTOS_ECN_MASK) == IPTOS_ECN_CE)
                        return (ECN_DROP);
                break;

        case ECN_NOCARE:
                break;
        }
        return (ECN_SUCCESS);
}

#ifdef INET6
void
ip6_ecn_ingress(int mode, uint32_t *outer, const uint32_t *inner)
{
        uint8_t outer8, inner8;

        KASSERT(outer != NULL && inner != NULL,
            ("NULL pointer passed to %s", __func__));

        inner8 = (ntohl(*inner) >> IPV6_FLOWLABEL_LEN) & 0xff;
        ip_ecn_ingress(mode, &outer8, &inner8);
        *outer &= ~htonl(0xff << IPV6_FLOWLABEL_LEN);
        *outer |= htonl((uint32_t)outer8 << IPV6_FLOWLABEL_LEN);
}

int
ip6_ecn_egress(int mode, const uint32_t *outer, uint32_t *inner)
{
        uint8_t outer8, inner8, oinner8;
        int ret;

        KASSERT(outer != NULL && inner != NULL,
            ("NULL pointer passed to %s", __func__));

        outer8 = (ntohl(*outer) >> IPV6_FLOWLABEL_LEN) & 0xff;
        inner8 = oinner8 = (ntohl(*inner) >> IPV6_FLOWLABEL_LEN) & 0xff;

        ret = ip_ecn_egress(mode, &outer8, &inner8);
        if (ret == ECN_DROP)
                return (ECN_DROP);
        if (inner8 != oinner8) {
                *inner &= ~htonl(0xff << IPV6_FLOWLABEL_LEN);
                *inner |= htonl((uint32_t)inner8 << IPV6_FLOWLABEL_LEN);
        }
        return (ret);
}
#endif