/*++

Copyright (c) 1997 Microsoft Corporation

Module Name:

    lookup.c

Abstract:

    This module contains routines for a wrapper
    that integrates the trie lookup into TCPIP.

Author:

    Chaitanya Kodeboyina (chaitk)   11-Dec-1997

Revision History:

--*/

#include "precomp.h"
#include "lookup.h"
#include "info.h"

// Wrapper Constants

// MaskBitsArr[i] = First 'i' bits set to 1 in an unsigned long
const ULONG MaskBitsArr[] =
{
 0x00000000, 0x80000000, 0xC0000000, 0xE0000000,
 0xF0000000, 0xF8000000, 0xFC000000, 0xFE000000,
 0xFF000000, 0xFF800000, 0xFFC00000, 0xFFE00000,
 0xFFF00000, 0xFFF80000, 0xFFFC0000, 0xFFFE0000,
 0xFFFF0000, 0xFFFF8000, 0xFFFFC000, 0xFFFFE000,
 0xFFFFF000, 0xFFFFF800, 0xFFFFFC00, 0xFFFFFE00,
 0xFFFFFF00, 0xFFFFFF80, 0xFFFFFFC0, 0xFFFFFFE0,
 0xFFFFFFF0, 0xFFFFFFF8, 0xFFFFFFFC, 0xFFFFFFFE,
 0xFFFFFFFF
};

// Wrapper Globals

// IP Route Table
Trie *RouteTable;

// Eq Cost Routes
USHORT MaxEqualCostRoutes = 0;

extern uint DefGWActive;
extern uint DefGWConfigured;
extern uint ValidateDefaultGWs(IPAddr Addr);

UINT
InsRoute(IPAddr Dest, IPMask Mask, IPAddr FirstHop, VOID * OutIF,
         UINT Metric, ULONG MatchFlags, RouteTableEntry ** ppInsRTE,
         RouteTableEntry ** ppOldBestRTE, RouteTableEntry ** ppNewBestRTE)
/*++

Routine Description:

    Inserts a route into the route table

    We update only
        1) Dest Addr,
        2) Dest Mask,
        3) Priority,
        4) Route Metric

    The rest of the RTE fields are left
    untouched - to enable the caller to
    read the old values (if this route
    already existed in the route table)

Arguments:

IN -
    Dest        - Destination IP Addr
    Mask        - Destination IP Mask
    FirstHop    - IP Addr of Next Hop
    OutIF       - Outgoing Interface
    Metric      - Metric for the route
    MatchFlags  - RTE Fields to match
OUT -
    ppInsRTE     - Ptr to Ptr to new/updated RTE
    ppOldBestRTE - Ptr to Ptr to old best RTE
    ppNewBestRTE - Ptr to Ptr to new best RTE

Return Value:

    STATUS_SUCCESS or Error Code

--*/
{
    Route route;
    ULONG temp;

    DEST(&route) = Dest;
    MASK(&route) = Mask;
    NHOP(&route) = FirstHop;
    IF(&route) = OutIF;

    temp = RtlConvertEndianLong(Mask);
    LEN(&route) = 0;
    while (temp != 0) {
        LEN(&route)++;
        temp <<= 1;
    }

    METRIC(&route) = Metric;

    switch (InsertIntoTrie(RouteTable, &route, MatchFlags,
                           ppInsRTE, ppOldBestRTE, ppNewBestRTE)) {
    case TRIE_SUCCESS:
        return IP_SUCCESS;
    case ERROR_TRIE_BAD_PARAM:
        return IP_BAD_REQ;
    case ERROR_TRIE_RESOURCES:
        return IP_NO_RESOURCES;
    }

    Assert(FALSE);
    return IP_GENERAL_FAILURE;
}

UINT
DelRoute(IPAddr Dest, IPMask Mask, IPAddr FirstHop, VOID * OutIF,
         ULONG MatchFlags, RouteTableEntry ** ppDelRTE,
         RouteTableEntry ** ppOldBestRTE, RouteTableEntry ** ppNewBestRTE)
/*++

Routine Description:

    Deletes a route from the route table

    The memory for the route(allocated
    on the heap) should be deallocated
    upon return, after all information
    required is read and processed.

Arguments:

IN -
    Dest        - Destination IP Addr
    Mask        - Destination IP Mask
    FirstHop    - IP Addr of Next Hop
    OutIF       - Outgoing Interface
    Metric      - Metric for the route
    MatchFlags  - RTE Fields to match
OUT -
    ppDelRTE     - Ptr to Ptr to the deleted RTE
    ppOldBestRTE - Ptr to Ptr to old best RTE
    ppNewBestRTE - Ptr to Ptr to new best RTE

Return Value:

    STATUS_SUCCESS or Error Code

--*/
{
    Route route;
    ULONG temp;

    DEST(&route) = Dest;
    MASK(&route) = Mask;
    NHOP(&route) = FirstHop;
    IF(&route) = OutIF;

    temp = RtlConvertEndianLong(Mask);
    LEN(&route) = 0;
    while (temp != 0) {
        LEN(&route)++;
        temp <<= 1;
    }

    switch (DeleteFromTrie(RouteTable, &route, MatchFlags,
                           ppDelRTE, ppOldBestRTE, ppNewBestRTE)) {
    case TRIE_SUCCESS:
        return IP_SUCCESS;
    case ERROR_TRIE_NO_ROUTES:
        return IP_BAD_ROUTE;
    case ERROR_TRIE_BAD_PARAM:
        return IP_BAD_REQ;
    case ERROR_TRIE_RESOURCES:
        return IP_NO_RESOURCES;
    }

    Assert(FALSE);
    return IP_GENERAL_FAILURE;
}

RouteTableEntry *
FindRTE(IPAddr Dest, IPAddr Source, UINT Index, UINT MaxPri, UINT MinPri, UINT UnicastIf)
/*++

Routine Description:

    Searches for a route given a prefix,
    with a mask len between the given
    minimum and maximum values.

    The route returned is a Semi-Read-
    Only-Version. The following fields
    should be changed only by calling
    the InsRoute function -
        1) Next,
        2) Dest,
        3) Mask,
        4) Priority, &
        5) Route Metric.

    Remaining fields can be changed by
    directly modifying returned route.

Arguments:

IN -
    Dest        - Destination IP Addr
    Source      - Source to match IF
    Index       - *Value is ignored*
    MaxPri      - Max mask len of RTE
    MinPri      - Min mask len of RTE

Return Value:

    Matching RTE or NULL if not match

--*/
{
    RouteTableEntry *pBestRoute;
    RouteTableEntry *pCurrRoute;
    ULONG addr;
    ULONG mask;
    INT lookupPri;

    UNREFERENCED_PARAMETER(Index);
    
    // Start looking for most specific match
    lookupPri = MaxPri;

    do {
        // Use lookupPri to mask xs bits
        addr = RtlConvertEndianLong(Dest);
        addr = ShowMostSigNBits(addr, lookupPri);
        Dest = RtlConvertEndianLong(addr);

        addr = ShowMostSigNBits(~0, lookupPri);
        mask = RtlConvertEndianLong(addr);

        // Try to match destination
        SearchRouteInTrie(RouteTable,
                          Dest,
                          mask,
                          0, NULL,
                          MATCH_NONE,
                          &pBestRoute);

        if ((NULL_ROUTE(pBestRoute)) || (LEN(pBestRoute) < MinPri)) {
            return NULL;
        }
        // Just in case we need to loop
        lookupPri = LEN(pBestRoute) - 1;

        // Search for a valid route
        while (pBestRoute) {
            if ((FLAGS(pBestRoute) & RTE_VALID) && (!(FLAGS(pBestRoute) & RTE_DEADGW)))
                break;

            pBestRoute = NEXT(pBestRoute);
        }

        // Do we match source too ?
        if (!IP_ADDR_EQUAL(Source, NULL_IP_ADDR) || UnicastIf) {
            // Dest match - Match source
            pCurrRoute = pBestRoute;
            while (pCurrRoute) {
                if (!UnicastIf) {
                    if (METRIC(pCurrRoute) > METRIC(pBestRoute)) {
                        // No Source match
                        break;
                    }
                }
                // Get next valid route
                if (((FLAGS(pCurrRoute) & RTE_VALID) && (!(FLAGS(pCurrRoute) & RTE_DEADGW))) &&
                    ((!IP_ADDR_EQUAL(Source, NULL_IP_ADDR) &&
                      AddrOnIF(IF(pCurrRoute), Source)) ||
                     (UnicastIf &&
                      IF(pCurrRoute)->if_index == UnicastIf))) {
                    // Source match too
                    pBestRoute = pCurrRoute;
                    break;
                }
                pCurrRoute = NEXT(pCurrRoute);
            }

            if (UnicastIf && (pCurrRoute == NULL)) {
                pBestRoute = NULL;
            }
        }
    }
    while ((NULL_ROUTE(pBestRoute)) && (lookupPri >= (INT) MinPri));

    return pBestRoute;
}

RouteTableEntry *
LookupRTE(IPAddr Dest, IPAddr Source, UINT MaxPri, UINT UnicastIf)
/*++

Routine Description:

    Searches for a best route for IP addr.

    The route returned is a Semi-Read-
    Only-Version. The following fields
    should be changed only by calling
    the InsRoute function -
        1) Next,
        2) Dest,
        3) Mask,
        4) Priority, &
        5) Route Metric.

    Remaining fields can be changed by
    directly modifying returned route.

Comments:
    *LookupRTE* assumes that VALID flag
    can be set on/off only for default
    routes. Because in case we find a
    chain with all invalid routes we do
    not enough information to go up in
    the F-trie for less specific routes

Arguments:

IN -
    Dest        - Destination IP Addr
    Source      - Source to match IF
    MaxPri      - *Value is ignored*

Return Value:

    Matching RTE or NULL if not match

--*/
{
    DestinationEntry *pBestDest;
    RouteTableEntry *pBestRoute;
    RouteTableEntry *pCurrRoute = NULL;

    UNREFERENCED_PARAMETER(MaxPri);
    
    // Try to match destination
    pBestDest = SearchAddrInTrie(RouteTable, Dest);

    // No prefix match - quit
    if (pBestDest == NULL) {
        return NULL;
    }
    // Search for a valid route
    pBestRoute = pBestDest->firstRoute;

    while (pBestRoute) {
        if ((FLAGS(pBestRoute) & RTE_VALID) && (!(FLAGS(pBestRoute) & RTE_DEADGW)))
            break;

        pBestRoute = NEXT(pBestRoute);
    }

    // Do we match source too ?
    if (!IP_ADDR_EQUAL(Source, NULL_IP_ADDR) || UnicastIf) {
        // Dest match - Match source
        pCurrRoute = pBestRoute;
        while (pCurrRoute) {
            // Are we doing a weak host lookup ?
            if (!UnicastIf) {
                if (METRIC(pCurrRoute) > METRIC(pBestRoute)) {
                    // No Source match
                    break;
                }
            }
            // Get next valid route
            if (((FLAGS(pCurrRoute) & RTE_VALID) && (!(FLAGS(pCurrRoute) & RTE_DEADGW))) &&
                ((!IP_ADDR_EQUAL(Source, NULL_IP_ADDR) &&
                  AddrOnIF(IF(pCurrRoute), Source)) ||
                 (UnicastIf &&
                  IF(pCurrRoute)->if_index == UnicastIf))) {
                // Source match too
                pBestRoute = pCurrRoute;
                break;
            }
            pCurrRoute = NEXT(pCurrRoute);
        }
    }
    // All routes on the list might be invalid
    // Fault to the slow path that backtracks
    // Or we want to do a strong host routing and haven't found the match
    if ((pBestRoute == NULL) || (UnicastIf && (pCurrRoute == NULL))) {
        return FindRTE(Dest, Source, 0, HOST_ROUTE_PRI, DEFAULT_ROUTE_PRI, UnicastIf);
    }
    return pBestRoute;
}

RouteTableEntry *
LookupForwardRTE(IPAddr Dest, IPAddr Source, BOOLEAN Multipath)
/*++

Routine Description:

    Searches for a best route for IP addr
    on the forward path. If Multipath is
    TRUE, it does a hash on the source and
    dest addr. to pick one of the best
    routes to the destination. This enables
    the network to be utilized effectively
    by providing load balancing.

Comments:
    *LookupRTE* assumes that VALID flag
    can be set on/off only for default
    routes. Because in case we find a
    chain with all invalid routes we do
    not enough information to go up in
    the F-trie for less specific routes

Arguments:

IN -
    Dest        - Destination IP Addr
    Source      - Source IP Addr
    Multipath   - To do a equal cost multipath lookup or not

Return Value:

    Matching RTE or NULL if not match

--*/
{
    DestinationEntry *pBestDest;
    RouteTableEntry *pBestRoute;
    UINT hashValue;
    UINT routeIndex;
    
    // Try to match destination
    pBestDest = SearchAddrInTrie(RouteTable, Dest);

    // No prefix match - quit
    if (pBestDest == NULL) {
        return NULL;
    }
    // Search for a valid route
    pBestRoute = pBestDest->firstRoute;

    if (Multipath) {
        KdPrintEx((DPFLTR_TCPIP_ID, DPFLTR_INFO_LEVEL,"\nIn Fwd RTE:\n Max = %d, Num = %d\n",
                 pBestDest->maxBestRoutes,
                 pBestDest->numBestRoutes));

        // Get best route on dest from best routes' cache

        if (pBestDest->numBestRoutes > 1) {
            // Hash on the src, dest to get best route
            hashValue = Source + Dest;
            hashValue += (hashValue >> 16);
            hashValue += (hashValue >> 8);

            routeIndex = ((USHORT) hashValue) % pBestDest->numBestRoutes;

            pBestRoute = pBestDest->bestRoutes[routeIndex];

            KdPrintEx((DPFLTR_TCPIP_ID, DPFLTR_INFO_LEVEL,"S = %08x, D = %08x\nH = %08x, I = %d\nR = %p, N = %08x\n\n",
                     Source,
                     Dest,
                     hashValue,
                     routeIndex,
                     pBestRoute,
                     NHOP(pBestRoute)));

            if ((FLAGS(pBestRoute) & RTE_VALID) && (!(FLAGS(pBestRoute) & RTE_DEADGW))) {
                return pBestRoute;
            }
        }
        // We do not want to match the source addr below
        Source = NULL_IP_ADDR;
    }
    // Search for a valid route
    pBestRoute = pBestDest->firstRoute;

    while (pBestRoute) {

        if ((FLAGS(pBestRoute) & RTE_VALID) &&
            (!(FLAGS(pBestRoute) & RTE_DEADGW)))
            break;

        pBestRoute = NEXT(pBestRoute);
    }

    // All routes on the list might be invalid
    // Fault to the slow path that backtracks
    if (pBestRoute == NULL) {
        return FindRTE(Dest, Source, 0, HOST_ROUTE_PRI, DEFAULT_ROUTE_PRI, 0);
    }
    return pBestRoute;
}

/*++

Routine Description:

    Gets next route in the route-table.

    The route returned is a Semi-Read-
    Only-Version. The following fields
    should be changed only by calling
    the InsRoute function -
        1) Next,
        2) Dest,
        3) Mask,
        4) Priority, &
        5) Route Metric.

    Remaining fields can be changed by
    directly modifying returned route.

Arguments:

IN -
    Context     - Iterator Context,
OUT -
    ppRoute     - To return route

Return Value:

    TRUE if more routes, FALSE if not

--*/

UINT
GetNextRoute(VOID * Context, Route ** ppRoute)
{
    UINT retVal;

    // Get Next Route
    retVal = IterateOverTrie(RouteTable, Context, ppRoute, NULL);

    // We have routes
    Assert(retVal != ERROR_TRIE_NO_ROUTES);

    // Return Status
    return (retVal == ERROR_TRIE_NOT_EMPTY) ? TRUE : FALSE;
}

/*++

Routine Description:

    Enumerates all destinations in the route-table.
    Assumes RouteTableLock is held by the caller.

Arguments:

IN -
    Context     - iterator context, zeroed to begin enumeration.
OUT -
    ppDest      - receives enumerated destination, if any.

Return Value:

    TRUE if more destinations, FALSE otherwise.

--*/

UINT
GetNextDest(VOID * Context, Dest ** ppDest)
{
    UINT retVal;

    // Get Next Destination
    retVal = IterateOverTrie(RouteTable, Context, NULL, ppDest);

    return (retVal == ERROR_TRIE_NOT_EMPTY) ? TRUE : FALSE;
}

/*++

Routine Description:

    Re-orders all routes in a destination's route-list.
    Assumes RouteTableLock is held by the caller.

Arguments:

IN -
    pDest       - destination whose route-list is to be sorted

Return Value:

    none.

--*/

VOID
SortRoutesInDest(Dest* pDest)
{
    Route* pFirstRoute;
    Route** ppCurrRoute;

    // Pick up the current head of the route list, and replace it with NULL.
    // We'll then rebuild the list by reinserting each item in order.
    pFirstRoute = pDest->firstRoute;
    
    if (!pFirstRoute) {
        return;
    }

    pDest->firstRoute = NULL;

    while (pFirstRoute) {
        Route* pNextRoute;
        uint FirstOrder, CurrOrder;

        if (FLAGS(pFirstRoute) & RTE_IF_VALID) {
            FirstOrder = IF(pFirstRoute)->if_order;
        } else {
            FirstOrder = MAXLONG;
        }

        for (ppCurrRoute = &pDest->firstRoute; *ppCurrRoute;
             ppCurrRoute = &NEXT(*ppCurrRoute)) {
            if (FLAGS(*ppCurrRoute) & RTE_IF_VALID) {
                CurrOrder = IF(*ppCurrRoute)->if_order;
            } else {
                CurrOrder = MAXLONG;
            }

            // N.B. The following sequence of comparisons ensure a *stable*
            // sort, which is important to minimize the impact of this routine
            // on ongoing sessions.

            if (METRIC(pFirstRoute) > METRIC(*ppCurrRoute)) {
                continue;
            } else if (METRIC(pFirstRoute) < METRIC(*ppCurrRoute)) {
                break;
            }

            if (FirstOrder < CurrOrder) {
                break;
            }
        }

        pNextRoute = NEXT(pFirstRoute);
        NEXT(pFirstRoute) = *ppCurrRoute;
        *ppCurrRoute = pFirstRoute;

        pFirstRoute = pNextRoute;
    }

    // Finally, reconstruct the array of best routes cached in the destination

    if (pDest->firstRoute) {
        CacheBestRoutesInDest(pDest);
    }
}

/*++

Routine Description:

    Re-orders all routes in the route-list of the destination
    corresponding to a given route.
    Assumes RouteTableLock is held by the caller.

Arguments:

IN -
    pRTE        - route whose destination's route-list is to be sorted

Return Value:

    none.

--*/

VOID
SortRoutesInDestByRTE(Route *pRTE)
{
    Dest* pDest = SearchAddrInTrie(RouteTable, DEST(pRTE));
    if (pDest) {
        SortRoutesInDest(pDest);
    }
}

UINT
RTValidateContext(VOID * Context, UINT * Valid)
{
    UINT retVal;

    retVal = IsTrieIteratorValid(RouteTable, Context);

    switch (retVal) {
    case ERROR_TRIE_BAD_PARAM:
        *Valid = FALSE;
        return FALSE;

    case ERROR_TRIE_NO_ROUTES:
        *Valid = TRUE;
        return FALSE;

    case TRIE_SUCCESS:
        *Valid = TRUE;
        return TRUE;
    }

    return FALSE;
}

