|
|
#include "allinc.h"
DWORD RtmEventCallback ( IN RTM_ENTITY_HANDLE hRtmHandle, IN RTM_EVENT_TYPE retEvent, IN PVOID pContext1, IN PVOID pContext2 )
/*++
Routine Description:
This callback is given by RTM when we have changed dests to process. We just queue a work item to process changed destinations.
Arguments:
hRtmHandle - Handle that we got during registration retEvent - Event type - we only handle events of type "more changes available" for now pContext1 - Notification handle on which changes are available
pContext2 - Context supplied during notification registration time Return Value:
Status of the operation. --*/
{ DWORD dwResult; // Only "change notifications available" is supported
if (retEvent != RTM_CHANGE_NOTIFICATION) { return ERROR_NOT_SUPPORTED; }
return ((HANDLE) pContext1) == g_hNotification ? ProcessChanges(g_hNotification) : ProcessDefaultRouteChanges( g_hDefaultRouteNotification ); }
DWORD WINAPI ProcessChanges ( IN HANDLE hNotifyHandle )
/*++
Routine Description:
Upon learning that we have changed destinations to process, this function gets called. We retrieve all destinations to process and take appropriate action.
Arguments:
hRtmHandle - RTM registration handle hNotifyHandle - Handle correponding to the change notification that is being signalled Return Value:
Status of the operation. --*/
{ PRTM_DEST_INFO pDestInfo; PRTM_ROUTE_INFO pRouteInfo; DWORD dwDests; DWORD dwResult; BOOL bMark = FALSE;
TraceEnter("ProcessChanges");
pRouteInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) );
if (pRouteInfo == NULL) { Trace1( ERR, "ProcessChanges : error allocating %d bytes for " "route info", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) );
TraceLeave("ProcessChanges");
return ERROR_NOT_ENOUGH_MEMORY; } pDestInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_DEST_INFO(g_rtmProfile.NumberOfViews) );
if (pDestInfo == NULL) { Trace1( ERR, "ProcessChanges : error allocating %d bytes for " "dest. info", RTM_SIZE_OF_DEST_INFO(g_rtmProfile.NumberOfViews) );
HeapFree(IPRouterHeap, 0, pRouteInfo); TraceLeave("ProcessChanges");
return ERROR_NOT_ENOUGH_MEMORY; }
dwDests = 1; // Get each changed dest from the table
do { RtmGetChangedDests(g_hLocalRoute, hNotifyHandle, &dwDests, pDestInfo); if (dwDests < 1) { break; }
//
// For default routes, mark the route so that future changes
// are managed by ProcessDefaultRouteChanges.
//
// We need to do this here so that default routes added by
// routing protocols RIP/OSPF are marked for change notification
// These default routes are added by entities other than
// RouterManager. Default routes added by RM i.e STATIC,
// AUTO-STATIC and NETMGMT default routes are already marked for
// change notification when they are added by RM.
//
// By marking routing protocol default routes here we make sure
// that all default routes are subsequently handled by marked changed
// mechanism (ProcessDefaultRouteChanges).
//
if (pDestInfo->DestAddress.NumBits is 0) { TraceRoute2( ROUTE, "Checking dest %d.%d.%d.%d/%d is marked", PRINT_IPADDR(*(ULONG *)pDestInfo->DestAddress.AddrBits), PRINT_IPADDR(pDestInfo->DestAddress.NumBits) );
dwResult = RtmIsMarkedForChangeNotification( g_hNetMgmtRoute, g_hDefaultRouteNotification, pDestInfo->DestHandle, &bMark );
if (dwResult is NO_ERROR) { if (bMark) { //
// default route is already marked, nothing further
// to do here. This default route change will be
// handled by ProcessDefaultRouteChanges
//
TraceRoute0( ROUTE, "ProcessChanges : Route 0/0 is already marked" );
RtmReleaseChangedDests(g_hLocalRoute, hNotifyHandle, dwDests, pDestInfo);
continue; }
//
// Default route is not marked, mark it
//
dwResult = RtmMarkDestForChangeNotification( g_hNetMgmtRoute, g_hDefaultRouteNotification, pDestInfo->DestHandle, TRUE );
if (dwResult isnot NO_ERROR) { //
// Failed to mark 0/0 route. The consequence is that
// only best route changes are processed. We will
// have to live with the fact that we cannot
// install multiple NETMGMT default routes since
// this is performed by the mark dest. change
// processing (in ProcessDefaultRouteChanges)
//
Trace1( ERR, "ProcessChanges: error %d marking default route", dwResult ); } }
else { //
// Failed to check is 0/0 destination has been
// marked for change notification
// - Refer previous comment
//
Trace1( ERR, "ProcessChanges: error %d checking if default route " "marked", dwResult ); } }
// Check if we have a route in Unicast view
if (pDestInfo->BelongsToViews & RTM_VIEW_MASK_UCAST) { // This is either a new or update route
// Update the same route in KM Frwder
ASSERT(pDestInfo->ViewInfo[0].ViewId is RTM_VIEW_ID_UCAST);
dwResult = RtmGetRouteInfo(g_hLocalRoute, pDestInfo->ViewInfo[0].Route, pRouteInfo, NULL);
// An error mean route just got deleted
// Ignore this change as it is obsolete
if (dwResult is NO_ERROR) { ChangeRouteWithForwarder(&pDestInfo->DestAddress, pRouteInfo, TRUE, TRUE);
RtmReleaseRouteInfo(g_hLocalRoute, pRouteInfo); } } else { // The last UCAST route has been deleted
// Delete the same route from KM Frwder
//
// Check to make sure that the route was added to
// the forwarder. If not there is no need to
// ChangeRouteWithForwarder.
//
// This rather kludgy fix was done for 446075
// The problem here is really a fallout of
// of RTMv2 behavior and how IPRTRMGR uses
// RTM.
//
// Routes added to RTM and not to the TCP/IP
// stack are marked as such in the Flags1 field
// of the RTM_ROUTE_INFO struture. (they have
// the IP_STACK_ROUTE bit turned off).
//
// The problem arises when a particular destination
// is no longer reachable i.e. all routes to it have
// been deleted. When a change notification for this
// is processed by IPRTRMGR, it no longer has access
// to the RTM_ROUTE_INFO STUCTURE for the last route
// to the destination and consequently no access to
// the flags field mentioned above. Hence routes
// were being deleted from the the TCP/IP stack that
// had not been added by IPRTRMGR.
//
// In the normal case this is not a problem. However
// for host routes added by PPTP directly to TCP/IP stack
// a problem arises as follows:
//
// 1. Host route to VPN server Route added by PPTP to TCP
// 2. IPRTRMGR notified of route by TCP.
// 3. IPRTRMGR added route to RTM with stack bit cleared
// as we do not want to readd this route to stack.
// 4. RTM notifies IPRTRMGR to new best route.
// 5. IPRTRMGR skips adding this route to the stack as
// its stack bit is cleared.
//
// 6. Host route to VPN server Route deleted by PPTP to TCP
// 7. IPRTRMGR notified of route deletion by TCP
// 8. IPRTRMGR deleted route from RTM
// 9. RTM notified IPRTRMGR of route/dest deletion
// 10. IPRTRMGR having no idea if this was added to TCP
// deletes this route from TCP.
//
// Somewhere between steps 7 and 10, PPTP adds the route
// to TCP again. Step 10 deleted the new route in TCP in
// response to the old route being deleted thereby
// disabling the new PPTP connection.
//
// To get around this problem we set state in the
// destination RTM_DEST_INFO to flag this condition. This
// is not a complete fix, allows IPRTRMGR to not delete
// routes from TCP that were added by it in the first
// place.
//
// Refer AddRtmRoute to figure out for which routes this
// state is set.
//
TraceRoute2( ROUTE, "Route delete notification for " "%d.%d.%d.%d/%d", PRINT_IPADDR(*(ULONG *)pDestInfo->DestAddress.AddrBits), pDestInfo->DestAddress.NumBits );
//
// if this is a host route
//
if(pDestInfo->DestAddress.NumBits is HOST_MASK_LENGTH) { PBYTE pbOpaque; do { //
// 1. Get the opaque pointer for NETMGMT
//
dwResult = RtmGetOpaqueInformationPointer( g_hNetMgmtRoute, pDestInfo->DestHandle, &pbOpaque ); if(dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute : error %d retrieving opaque " "info", dwResult );
break; }
//
// 2. Check if this route was set to stack
//
if(*((PDWORD) pbOpaque) isnot RTM_NOT_STACK_ROUTE) { TraceRoute1( ROUTE, "Host route with Stack bit 0x%x", *((PDWORD) pbOpaque) );
//
// 3.1 If so delete it.
//
ChangeRouteWithForwarder( &pDestInfo->DestAddress, NULL, FALSE, TRUE ); }
else { //
// 3.2 Otherwise move on.
//
TraceRoute1( ROUTE, "Stack bit not set on host " "route, Skipping deleting 0x%x", *((PDWORD) pbOpaque) ); } }while( FALSE ); } else { TraceRoute0(ROUTE, "Deleting a subnet route" ); ChangeRouteWithForwarder(&pDestInfo->DestAddress, NULL, FALSE, TRUE); } }
RtmReleaseChangedDests(g_hLocalRoute, hNotifyHandle, dwDests, pDestInfo); } while (TRUE);
HeapFree(IPRouterHeap, 0, pRouteInfo);
HeapFree(IPRouterHeap, 0, pDestInfo); TraceLeave("ProcessChanges");
return NO_ERROR; }
DWORD WINAPI ProcessDefaultRouteChanges( IN HANDLE hNotifyHandle )
/*++
Routine Description:
This function is invoked in response to changes to the default route. If the best default route is owned by protocol PROTO_IP_NETMGMT enumerate all PROTO_IP_NETMGMT routes for default route 0/0 and set them as one multihop route to the forwarder Arguments:
hRtmHandle - RTM registration handle hNotifyHandle - Handle correponding to the change notification that is being signalled
Return Value:
NO_ERROR - Success
System error code - Otherwise --*/ { PRTM_DEST_INFO pDestInfo; PRTM_ROUTE_INFO pRouteInfo; DWORD dwDests; DWORD dwResult;
TraceEnter("ProcessDefaultRouteChanges"); pRouteInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); if (pRouteInfo == NULL) { Trace1( ERR, "ProcessDefaultRouteChanges : error allocating %d bytes for " "route info", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) );
TraceLeave("ProcessDefaultRouteChanges");
return ERROR_NOT_ENOUGH_MEMORY; } pDestInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_DEST_INFO(g_rtmProfile.NumberOfViews) );
if (pDestInfo == NULL) { Trace1( ERR, "ProcessDefaultRouteChanges : error allocating %d bytes for " "dest. info", RTM_SIZE_OF_DEST_INFO(g_rtmProfile.NumberOfViews) );
HeapFree(IPRouterHeap, 0, pRouteInfo); TraceLeave("ProcessDefaultRouteChanges");
return ERROR_NOT_ENOUGH_MEMORY; }
do { //
// retreive changed dests
//
dwDests = 1;
dwResult = RtmGetChangedDests( g_hNetMgmtRoute, hNotifyHandle, &dwDests, pDestInfo );
if ((dwResult isnot NO_ERROR) and (dwResult isnot ERROR_NO_MORE_ITEMS)) { Trace1( ERR, "ProcessDefaultRouteChanges: error %d retrieving changed dests", dwResult );
break; }
if (dwDests < 1) { //
// no more dests to enumerate
//
break; }
do { //
// Make sure this the default route 0/0. This functions
// only processes default route changes.
//
if ((pDestInfo->DestAddress.NumBits isnot 0) or (*((ULONG *)pDestInfo->DestAddress.AddrBits) isnot 0)) { Trace2( ERR, "ProcessDefaultRouteChanges: Not default route %d.%d.%d.%d/%d", PRINT_IPADDR(*((ULONG *)pDestInfo->DestAddress.AddrBits)), pDestInfo->DestAddress.NumBits );
break; }
//
// If all routes to 0/0 have been deleted,
// delete it from the forwarder too.
//
if (!(pDestInfo->BelongsToViews & RTM_VIEW_MASK_UCAST)) { dwResult = ChangeRouteWithForwarder( &(pDestInfo->DestAddress), NULL, FALSE, TRUE );
break; }
//
// A route to 0/0 was added/updated
//
if (pDestInfo->ViewInfo[0].Owner isnot g_hNetMgmtRoute) { //
// Default route is not owned by PROTO_IP_NETMGT
// Add only the best route to forwarder
//
TraceRoute1( ROUTE, "ProcessDefaultRouteChanges: Adding non-NetMgmt" " route to forwarder, owner RTM handle 0x%x", pDestInfo->ViewInfo[0].Owner );
dwResult = RtmGetRouteInfo( g_hNetMgmtRoute, pDestInfo->ViewInfo[0].Route, pRouteInfo, NULL );
if (dwResult is NO_ERROR) { ChangeRouteWithForwarder( &pDestInfo->DestAddress, pRouteInfo, TRUE, TRUE );
dwResult = RtmReleaseRouteInfo( g_hNetMgmtRoute, pRouteInfo ); if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges: Failed " "to release route info", dwResult ); } } break; } //
// Default route owned by PROTO_IP_NETMGMT
//
//
// First delete existing 0/0 from the TCP/IP forwarder
//
dwResult = ChangeRouteWithForwarder( &(pDestInfo->DestAddress), NULL, FALSE, TRUE );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges: error %d deleting " "old NetMgmt default routes from forwarder", dwResult ); // break;
}
//
// Second add all NETMGMT 0/0 to the TCP/IP forwarder
//
AddNetmgmtDefaultRoutesToForwarder(pDestInfo); } while( FALSE );
//
// release handles to changed destinations
//
dwResult = RtmReleaseChangedDests( g_hNetMgmtRoute, hNotifyHandle, dwDests, pDestInfo );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges: error %d releasing dests ", dwResult ); }
} while ( TRUE );
HeapFree(IPRouterHeap, 0, pRouteInfo);
HeapFree(IPRouterHeap, 0, pDestInfo); TraceLeave("ProcessDefaultRouteChanges");
return dwResult; }
DWORD WINAPI AddNetmgmtDefaultRoutesToForwarder( PRTM_DEST_INFO pDestInfo ) /*++
Routine Description:
This routine enumerates the routes to 0/0 added by protocol PROTO_IP_NETMGT and adds them to the forwarder. This routine is invoked in response to any change to the default route If the best default route is owned by PROTO_IP_NETMGMT, all PROTO_IP_NETMGMT default routes are added to the TCP/IP forwarder.
This is required since the TCP/IP stack does dead gateway detection and that required multiple default routes if present to be installed in the stack.
An implicit assumption here is that PROTO_IP_NETMGMT routes alone merit this treatment. In case of static or other protocol generated 0/0 routes, only the best route is added to the stack. It is assumed that in the later case(s) the administrator (for static routes) or the protocol has a better idea of routing and so the dead gateway detection is suppressed in the stack by the addition of the best route to 0/0 alone.
Arguments:
pDestInfo - RTM destination info structure of 0/0 route
Return Value :
NO_ERROR - Sucess
Win32 error code - Otherwise
--*/ { DWORD dwResult, dwNumHandles = 0, i; BOOL bRelEnum = FALSE, bRelRoutes = FALSE; PRTM_ROUTE_INFO pRouteInfo; PRTM_ROUTE_HANDLE pHandles; RTM_ENUM_HANDLE hRouteEnum;
dwNumHandles = pDestInfo->ViewInfo[0].NumRoutes; pHandles = HeapAlloc( IPRouterHeap, 0, dwNumHandles * sizeof(RTM_ROUTE_HANDLE) );
if (pHandles == NULL) { Trace1( ERR, "AddNetmgmtDefaultRoutesToForwarder: error allocating %d bytes" "for route handles", dwNumHandles * sizeof(RTM_ROUTE_HANDLE) ); return ERROR_NOT_ENOUGH_MEMORY; }
pRouteInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); if (pRouteInfo == NULL) { Trace1( ERR, "AddNetmgmtDefaultRoutesToForwarder: error allocating %d bytes" "for route info", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); HeapFree(IPRouterHeap, 0, pHandles); return ERROR_NOT_ENOUGH_MEMORY; }
do { //
// Enumerate and add all NETMGMT routes to the forwarder
//
dwResult = RtmCreateRouteEnum( g_hNetMgmtRoute, pDestInfo->DestHandle, RTM_VIEW_MASK_UCAST, RTM_ENUM_OWN_ROUTES, NULL, 0, NULL, 0, &hRouteEnum );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddNetmgmtDefaultRoutesToForwarder: error %d creating route " "enumeration", dwResult );
break; }
bRelEnum = TRUE; dwResult = RtmGetEnumRoutes( g_hNetMgmtRoute, hRouteEnum, &dwNumHandles, pHandles );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges:error %d enumerating " "routes", dwResult );
break; }
bRelRoutes = TRUE;
//
// Change route with the forwarder
//
for (i = 0; i < dwNumHandles; i++) { dwResult = RtmGetRouteInfo( g_hNetMgmtRoute, pHandles[i], pRouteInfo, NULL );
if (dwResult is NO_ERROR) { ChangeRouteWithForwarder( &(pDestInfo->DestAddress), pRouteInfo, TRUE, FALSE );
dwResult = RtmReleaseRouteInfo( g_hNetMgmtRoute, pRouteInfo );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges: error %d releasing " "route info ", dwResult ); } } else { Trace2( ERR, "ProcessDefaultRouteChanges: error %d getting route " "info for route %d", dwResult, i ); } } } while( FALSE );
//
// Release handles
//
if (bRelRoutes) { Trace0(ROUTE, "Releasing routes to 0/0");
dwResult = RtmReleaseRoutes( g_hNetMgmtRoute, dwNumHandles, pHandles );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges: error %d deleting enum " "handle", dwResult ); } }
if (bRelEnum) { Trace0(ROUTE, "Releasing route enum for 0/0"); dwResult = RtmDeleteEnumHandle( g_hNetMgmtRoute, hRouteEnum );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "ProcessDefaultRouteChanges: error %d deleting enum " "handle", dwResult ); } }
HeapFree(IPRouterHeap, 0, pHandles); HeapFree(IPRouterHeap, 0, pRouteInfo);
return dwResult; }
DWORD AddRtmRoute ( IN HANDLE hRtmHandle, IN PINTERFACE_ROUTE_INFO pRtInfo, IN DWORD dwRouteFlags, IN DWORD dwNextHopMask, IN DWORD dwTimeToLive, OUT HANDLE *phRtmRoute )
/*++
Routine Description:
Adds a route to RTM with the specified route information.
Arguments:
hRtmHandle - RTM registration handle used in RTM calls
pRtInfo -
dwNextHopMask -
dwTimeToLive - Time for which the route is kept in RTM before being deleted (value is seconds).
Return Value:
Status of the operation.
--*/
{ PRTM_NET_ADDRESS pDestAddr; PRTM_ROUTE_INFO pRouteInfo; RTM_NEXTHOP_INFO rniInfo; DWORD dwFlags; DWORD dwResult; HANDLE hNextHopHandle; PADAPTER_INFO pBinding;
// Initialize output before caling ops
if (ARGUMENT_PRESENT(phRtmRoute)) { *phRtmRoute = NULL; } pDestAddr = HeapAlloc( IPRouterHeap, 0, sizeof(RTM_NET_ADDRESS) );
if (pDestAddr == NULL) { Trace1( ERR, "AddRtmRoute : error allocating %d bytes" "for dest. address", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); return ERROR_NOT_ENOUGH_MEMORY; }
pRouteInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) );
if (pRouteInfo == NULL) { Trace1( ERR, "AddRtmRoute : error allocating %d bytes" "for route info", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); HeapFree(IPRouterHeap, 0, pDestAddr); return ERROR_NOT_ENOUGH_MEMORY; }
//
// Add a next hop if not already present
//
RTM_IPV4_MAKE_NET_ADDRESS(&rniInfo.NextHopAddress, pRtInfo->dwRtInfoNextHop, 32); rniInfo.InterfaceIndex = pRtInfo->dwRtInfoIfIndex; rniInfo.EntitySpecificInfo = (PVOID) (ULONG_PTR)dwNextHopMask; rniInfo.Flags = 0; rniInfo.RemoteNextHop = NULL;
hNextHopHandle = NULL; dwResult = RtmAddNextHop(hRtmHandle, &rniInfo, &hNextHopHandle, &dwFlags);
if (dwResult is NO_ERROR) { TraceRoute2( ROUTE, "Route to %d.%d.%d.%d/%d.%d.%d.%d", PRINT_IPADDR(pRtInfo->dwRtInfoDest), PRINT_IPADDR(pRtInfo->dwRtInfoMask) ); TraceRoute4( ROUTE, "Next Hop %d.%d.%d.%d/%d.%d.%d.%d, If 0x%x, handle is 0x%x", PRINT_IPADDR(pRtInfo->dwRtInfoNextHop), PRINT_IPADDR(dwNextHopMask), pRtInfo->dwRtInfoIfIndex, hNextHopHandle );
dwResult = ConvertRouteInfoToRtm(hRtmHandle, pRtInfo, hNextHopHandle, dwRouteFlags, pDestAddr, pRouteInfo); if (dwResult is NO_ERROR) { //
// If we are adding a non-dod route we should
// adjust the state of the route to match
// that of the interface it is being added on
//
if ((hRtmHandle == g_hNonDodRoute) || (hRtmHandle == g_hNetMgmtRoute)) { //
// Find the binding given the interface id
//
ENTER_READER(BINDING_LIST);
pBinding = GetInterfaceBinding(pRtInfo->dwRtInfoIfIndex);
if ((!pBinding) || (!pBinding->bBound)) { // Interface has been deleted meanwhile
// or is not bound at this point - quit
EXIT_LOCK(BINDING_LIST); return ERROR_INVALID_PARAMETER; } } //
// Convert TimeToLive from secs to millisecs
//
if (dwTimeToLive != INFINITE) { if (dwTimeToLive < (INFINITE / 1000)) { dwTimeToLive *= 1000; } else { dwTimeToLive = INFINITE; } }
dwFlags = 0;
//
// Add the new route using the RTMv2 API call
//
dwResult = RtmAddRouteToDest(hRtmHandle, phRtmRoute, pDestAddr, pRouteInfo, dwTimeToLive, NULL, 0, NULL, &dwFlags);
if ((hRtmHandle == g_hNonDodRoute) || (hRtmHandle == g_hNetMgmtRoute)) { EXIT_LOCK(BINDING_LIST); }
//
// check if route is 0/0 and route protocol is
// PROTO_IP_NETMGMT. If so mark for change notification
//
if ((pRtInfo->dwRtInfoDest is 0) and (pRtInfo->dwRtInfoMask is 0)) { RTM_DEST_INFO rdi; BOOL bMark; BOOL bRelDest = FALSE; do { TraceRoute2( ROUTE, "Checking dest %d.%d.%d.%d/%d for mark", PRINT_IPADDR(*(ULONG *)pDestAddr->AddrBits), PRINT_IPADDR(pDestAddr->NumBits) );
dwResult = RtmGetExactMatchDestination( g_hNetMgmtRoute, pDestAddr, RTM_THIS_PROTOCOL, RTM_VIEW_MASK_UCAST, &rdi );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute: error %d failed to get " "destination 0/0 for change notification", dwResult );
break; }
bRelDest = TRUE;
dwResult = RtmIsMarkedForChangeNotification( g_hNetMgmtRoute, g_hDefaultRouteNotification, rdi.DestHandle, &bMark );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute: error %d failed to check " "destination 0/0 for change notification", dwResult );
break; }
if (!bMark) { dwResult = RtmMarkDestForChangeNotification( g_hNetMgmtRoute, g_hDefaultRouteNotification, rdi.DestHandle, TRUE ); if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute: error %d failed to nark " "destination 0/0 for change notification", dwResult ); break; } //
// Add route once more, to force marked dest
// change notifications to be issued for this
// change
//
dwFlags = 0; dwResult = RtmAddRouteToDest( hRtmHandle, phRtmRoute, pDestAddr, pRouteInfo, dwTimeToLive, NULL, 0, NULL, &dwFlags );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute: error %d added route after " "marking destination", dwResult );
break; }
TraceRoute2( ROUTE, "Marked dest %d.%d.%d.%d/%d and added", PRINT_IPADDR(*(ULONG *)pDestAddr->AddrBits), pDestAddr->NumBits );
} } while (FALSE);
if (bRelDest) { RtmReleaseDestInfo( g_hNetMgmtRoute, &rdi ); } }
//
// for host routes, added by NETMGMT
// if they are not added to the stack
//
if((pRtInfo->dwRtInfoMask is HOST_ROUTE_MASK) and (pRtInfo->dwRtInfoProto is PROTO_IP_NETMGMT) and (!(dwRouteFlags & IP_STACK_ROUTE))) { RTM_DEST_INFO rdi; BOOL bRelDest = FALSE; PBYTE pbOpaque = NULL;
TraceRoute2( ROUTE, "Non-stack Netmgmt host route " "%d.%d.%d.%d/%d", PRINT_IPADDR(*(ULONG *)pDestAddr->AddrBits), pDestAddr->NumBits ); do { //
// Retrieve destination
//
dwResult = RtmGetExactMatchDestination( g_hNetMgmtRoute, pDestAddr, RTM_THIS_PROTOCOL, RTM_VIEW_MASK_UCAST, &rdi ); if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute : error %d retriveing host route " "destination", dwResult );
break; }
bRelDest = TRUE;
//
// get opaque info ptr.
//
dwResult = RtmLockDestination( g_hNetMgmtRoute, rdi.DestHandle, TRUE, TRUE ); if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute : error %d locking host route " "destination", dwResult );
break; }
dwResult = RtmGetOpaqueInformationPointer( g_hNetMgmtRoute, rdi.DestHandle, &pbOpaque );
if(dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute : error %d retrieving opaque " "info", dwResult );
break; }
*((PDWORD) pbOpaque) = RTM_NOT_STACK_ROUTE; dwResult = RtmLockDestination( g_hNetMgmtRoute, rdi.DestHandle, TRUE, FALSE );
if (dwResult isnot NO_ERROR) { Trace1( ERR, "AddRtmRoute : error %d unlocking dest", dwResult );
break; } }while( FALSE );
if (bRelDest) { RtmReleaseDestInfo( g_hNetMgmtRoute, &rdi ); } TraceRoute3( ROUTE, "Non-stack Netmgmt host route " "%d.%d.%d.%d/%d opaq info set, res == %d", PRINT_IPADDR(*(ULONG *)pDestAddr->AddrBits), pDestAddr->NumBits, dwResult ); } } // Release the next hop handle obtained above
RtmReleaseNextHops(hRtmHandle, 1, &hNextHopHandle); }
HeapFree(IPRouterHeap, 0, pDestAddr); HeapFree(IPRouterHeap, 0, pRouteInfo); return dwResult; }
DWORD DeleteRtmRoute ( IN HANDLE hRtmHandle, IN PINTERFACE_ROUTE_INFO pRtInfo )
/*++
Routine Description:
Deletes an RTM route with the specified route information.
Arguments:
hRtmHandle - RTM registration handle used in RTM calls
pRtInfo -
Return Value:
Status of the operation.
--*/
{ PRTM_NET_ADDRESS pDestAddr; PRTM_ROUTE_INFO pRouteInfo; RTM_NEXTHOP_INFO rniInfo; DWORD dwFlags; DWORD dwResult; HANDLE hRouteHandle; HANDLE hNextHopHandle; pRouteInfo = HeapAlloc( IPRouterHeap, 0, RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) );
if (pRouteInfo == NULL) { Trace1( ERR, "DeleteRtmRoute : error allocating %d bytes" "for route info", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); return ERROR_NOT_ENOUGH_MEMORY; }
pDestAddr = HeapAlloc( IPRouterHeap, 0, sizeof(RTM_NET_ADDRESS) );
if (pDestAddr == NULL) { Trace1( ERR, "AddRtmRoute : error allocating %d bytes" "for dest. address", RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute) ); HeapFree(IPRouterHeap, 0, pRouteInfo); return ERROR_NOT_ENOUGH_MEMORY; }
//
// Obtain a handle to the next hop in the route
//
RTM_IPV4_MAKE_NET_ADDRESS(&rniInfo.NextHopAddress, pRtInfo->dwRtInfoNextHop, 32); rniInfo.InterfaceIndex = pRtInfo->dwRtInfoIfIndex;
rniInfo.NextHopOwner = hRtmHandle; dwResult = RtmFindNextHop(hRtmHandle, &rniInfo, &hNextHopHandle, NULL);
if (dwResult isnot NO_ERROR) { HeapFree(IPRouterHeap, 0, pDestAddr); HeapFree(IPRouterHeap, 0, pRouteInfo); return dwResult; }
//
// We can get this route by matching the route's
// net addr, its owner and neighbour learnt from
//
ConvertRouteInfoToRtm(hRtmHandle, pRtInfo, hNextHopHandle, 0, pDestAddr, pRouteInfo);
dwResult = RtmGetExactMatchRoute(hRtmHandle, pDestAddr, RTM_MATCH_OWNER | RTM_MATCH_NEIGHBOUR, pRouteInfo, 0, 0, &hRouteHandle); if (dwResult is NO_ERROR) { //
// Delete the route found above using the handle
//
dwResult = RtmDeleteRouteToDest(hRtmHandle, hRouteHandle, &dwFlags);
if (dwResult isnot NO_ERROR) { // If delete successful, deref is automatic
RtmReleaseRoutes(hRtmHandle, 1, &hRouteHandle); }
// Release the route information obtained above
RtmReleaseRouteInfo(hRtmHandle, pRouteInfo); }
// Release the next hop handle obtained above
RtmReleaseNextHops(hRtmHandle, 1, &hNextHopHandle); HeapFree(IPRouterHeap, 0, pDestAddr); HeapFree(IPRouterHeap, 0, pRouteInfo);
return dwResult; }
DWORD ConvertRouteInfoToRtm( IN HANDLE hRtmHandle, IN PINTERFACE_ROUTE_INFO pRtInfo, IN HANDLE hNextHopHandle, IN DWORD dwRouteFlags, OUT PRTM_NET_ADDRESS pDestAddr, OUT PRTM_ROUTE_INFO pRouteInfo ) { DWORD dwAddrLen; // Fill the destination addr structure
RTM_IPV4_LEN_FROM_MASK(dwAddrLen, pRtInfo->dwRtInfoMask);
RTM_IPV4_MAKE_NET_ADDRESS(pDestAddr, pRtInfo->dwRtInfoDest, dwAddrLen);
// Fill in the route information now
ZeroMemory(pRouteInfo, sizeof(RTM_ROUTE_INFO));
pRouteInfo->RouteOwner = hRtmHandle; pRouteInfo->Neighbour = hNextHopHandle; pRouteInfo->PrefInfo.Metric = pRtInfo->dwRtInfoMetric1; pRouteInfo->PrefInfo.Preference = pRtInfo->dwRtInfoPreference; pRouteInfo->BelongsToViews = pRtInfo->dwRtInfoViewSet;
//
// BUG BUG BUG BUG :
// This is broken for future references
//
if(g_pLoopbackInterfaceCb && pRtInfo->dwRtInfoIfIndex is g_pLoopbackInterfaceCb->dwIfIndex) { pRouteInfo->BelongsToViews &= ~RTM_VIEW_MASK_MCAST; }
pRouteInfo->NextHopsList.NumNextHops = 1; pRouteInfo->NextHopsList.NextHops[0] = hNextHopHandle;
// an unsigned integer is converted to a shorter
// unsigned integer by truncating the high-order bits!
pRouteInfo->Flags1 = (UCHAR) dwRouteFlags; pRouteInfo->Flags = (USHORT) (dwRouteFlags >> 16); // Get the preference for this route
return ValidateRouteForProtocol(pRtInfo->dwRtInfoProto, pRouteInfo, pDestAddr);
// The following information is lost
//
// dwForwardMetric2,3
// dwForwardPolicy
// dwForwardType
// dwForwardAge
// dwForwardNextHopAS
}
VOID ConvertRtmToRouteInfo ( IN DWORD ownerProtocol, IN PRTM_NET_ADDRESS pDestAddr, IN PRTM_ROUTE_INFO pRouteInfo, IN PRTM_NEXTHOP_INFO pNextHop, OUT PINTERFACE_ROUTE_INFO pRtInfo ) { pRtInfo->dwRtInfoDest = *(ULONG *)pDestAddr->AddrBits; pRtInfo->dwRtInfoMask = RTM_IPV4_MASK_FROM_LEN(pDestAddr->NumBits);
pRtInfo->dwRtInfoIfIndex = pNextHop->InterfaceIndex; pRtInfo->dwRtInfoNextHop = *(ULONG *)pNextHop->NextHopAddress.AddrBits;
pRtInfo->dwRtInfoProto = ownerProtocol;
pRtInfo->dwRtInfoMetric1 = pRtInfo->dwRtInfoMetric2 = pRtInfo->dwRtInfoMetric3 = pRouteInfo->PrefInfo.Metric; pRtInfo->dwRtInfoPreference = pRouteInfo->PrefInfo.Preference; pRtInfo->dwRtInfoViewSet = pRouteInfo->BelongsToViews; pRtInfo->dwRtInfoPolicy = 0; pRtInfo->dwRtInfoType = 0; pRtInfo->dwRtInfoAge = 0; pRtInfo->dwRtInfoNextHopAS = 0;
return; }
PINTERFACE_ROUTE_INFO ConvertMibRouteToRouteInfo( IN PMIB_IPFORWARDROW pMibRow ) { PINTERFACE_ROUTE_INFO pRouteInfo = (PINTERFACE_ROUTE_INFO)pMibRow;
pMibRow->dwForwardMetric2 = 0; pMibRow->dwForwardMetric3 = 0; pMibRow->dwForwardMetric4 = 0; pMibRow->dwForwardMetric5 = 0;
// Make sure Metric1 isn't 0
if (pRouteInfo->dwRtInfoMetric1 is 0) { pRouteInfo->dwRtInfoMetric1 = 1; }
// By default put it in both views
pRouteInfo->dwRtInfoViewSet = RTM_VIEW_MASK_UCAST | RTM_VIEW_MASK_MCAST;
return pRouteInfo; }
PMIB_IPFORWARDROW ConvertRouteInfoToMibRoute( IN PINTERFACE_ROUTE_INFO pRouteInfo ) { PMIB_IPFORWARDROW pMibRoute = (PMIB_IPFORWARDROW) pRouteInfo;
pMibRoute->dwForwardMetric2 = pMibRoute->dwForwardMetric3 = pMibRoute->dwForwardMetric4 = pMibRoute->dwForwardMetric5 = IRE_METRIC_UNUSED;
pMibRoute->dwForwardAge = INFINITE; pMibRoute->dwForwardPolicy = 0; pMibRoute->dwForwardNextHopAS = 0; pMibRoute->dwForwardType = IRE_TYPE_INDIRECT;
return pMibRoute; }
VOID ConvertRouteNotifyOutputToRouteInfo( IN PIPRouteNotifyOutput pirno, OUT PINTERFACE_ROUTE_INFO pRtInfo ) {
ZeroMemory(pRtInfo, sizeof(INTERFACE_ROUTE_INFO));
pRtInfo->dwRtInfoDest = pirno->irno_dest; pRtInfo->dwRtInfoMask = pirno->irno_mask; pRtInfo->dwRtInfoIfIndex = pirno->irno_ifindex; pRtInfo->dwRtInfoNextHop = pirno->irno_nexthop;
pRtInfo->dwRtInfoProto = pirno->irno_proto;
pRtInfo->dwRtInfoMetric1 = pRtInfo->dwRtInfoMetric2 = pRtInfo->dwRtInfoMetric3 = pirno->irno_metric;
pRtInfo->dwRtInfoPreference = ComputeRouteMetric(pirno->irno_proto);
pRtInfo->dwRtInfoViewSet = RTM_VIEW_MASK_UCAST | RTM_VIEW_MASK_MCAST;
pRtInfo->dwRtInfoType = (pirno->irno_proto == PROTO_IP_LOCAL) ? MIB_IPROUTE_TYPE_DIRECT : MIB_IPROUTE_TYPE_OTHER;
pRtInfo->dwRtInfoAge = INFINITE; pRtInfo->dwRtInfoNextHopAS = 0; pRtInfo->dwRtInfoPolicy = 0;
return; }
DWORD BlockConvertRoutesToStatic ( IN HANDLE hRtmHandle, IN DWORD dwIfIndex, IN DWORD dwProtocolId ) { HANDLE hRtmEnum; RTM_ENTITY_INFO reiInfo; RTM_NET_ADDRESS rnaDest; PRTM_ROUTE_INFO pRouteInfo1; PRTM_ROUTE_INFO pRouteInfo2; RTM_NEXTHOP_INFO nhiInfo; RTM_NEXTHOP_HANDLE hNextHop; PHANDLE hRoutes; DWORD dwHandles; DWORD dwFlags; DWORD dwNumBytes; DWORD i, j, k; BOOL fDeleted; DWORD dwResult;
hRoutes = HeapAlloc( IPRouterHeap, 0, g_rtmProfile.MaxHandlesInEnum * sizeof(HANDLE) );
if (hRoutes == NULL) { Trace1( ERR, "BlockConvertRoutesToStatic : error allocating %d bytes" "for route handes", g_rtmProfile.MaxHandlesInEnum * sizeof(HANDLE) ); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return ERROR_NOT_ENOUGH_MEMORY; }
dwNumBytes = RTM_SIZE_OF_ROUTE_INFO(g_rtmProfile.MaxNextHopsInRoute); pRouteInfo1 = HeapAlloc( IPRouterHeap, 0, dwNumBytes );
if (pRouteInfo1 == NULL) { Trace1( ERR, "BlockConvertRoutesToStatic : error allocating %d bytes" "for route info", dwNumBytes ); HeapFree(IPRouterHeap, 0, hRoutes); SetLastError(ERROR_NOT_ENOUGH_MEMORY); return ERROR_NOT_ENOUGH_MEMORY; }
pRouteInfo2 = HeapAlloc( IPRouterHeap, 0, dwNumBytes );
if (pRouteInfo2 == NULL) { Trace1( ERR, "BlockConvertRoutesToStatic : error allocating %d bytes" "for route info", dwNumBytes ); HeapFree(IPRouterHeap, 0, hRoutes); HeapFree(IPRouterHeap, 0, pRouteInfo1); SetLastError(ERROR_NOT_ENOUGH_MEMORY);
return ERROR_NOT_ENOUGH_MEMORY; }
//
// Enum all routes on the interface that we need
//
dwResult = RtmCreateRouteEnum(hRtmHandle, NULL, RTM_VIEW_MASK_ANY, RTM_ENUM_ALL_ROUTES, NULL, RTM_MATCH_INTERFACE, NULL, dwIfIndex, &hRtmEnum);
if(dwResult isnot NO_ERROR) { Trace2(ERR, "BlockConvertRoutesToStatic: Error %d creating handle for %d\n", dwResult, hRtmHandle); HeapFree(IPRouterHeap, 0, hRoutes); HeapFree(IPRouterHeap, 0, pRouteInfo1);
HeapFree(IPRouterHeap, 0, pRouteInfo2);
return dwResult; }
do { dwHandles = g_rtmProfile.MaxHandlesInEnum; dwResult = RtmGetEnumRoutes(hRtmHandle, hRtmEnum, &dwHandles, hRoutes);
for (i = 0; i < dwHandles; i++) { fDeleted = FALSE; // Get the route info from the handle
if (RtmGetRouteInfo(hRtmHandle, hRoutes[i], pRouteInfo1, &rnaDest) is NO_ERROR) { // Does this match the routing protocol we want ?
if ((RtmGetEntityInfo(hRtmHandle, pRouteInfo1->RouteOwner, &reiInfo) is NO_ERROR) && (reiInfo.EntityId.EntityProtocolId is dwProtocolId)) { //
// Add new static route with same information
//
CopyMemory(pRouteInfo2, pRouteInfo1, sizeof(RTM_ROUTE_INFO));
// Adjust the preference to confirm to protocol
pRouteInfo2->PrefInfo.Preference = ComputeRouteMetric(PROTO_IP_NT_AUTOSTATIC);
// Adjust the neighbour to corr to new protocol
if (pRouteInfo1->Neighbour) { // In case we cant get convert the neighbour
pRouteInfo2->Neighbour = NULL; if (RtmGetNextHopInfo(hRtmHandle, pRouteInfo1->Neighbour, &nhiInfo) is NO_ERROR) { // Add the same neigbour using new protocol
hNextHop = NULL;
if (RtmAddNextHop(hRtmHandle, &nhiInfo, &hNextHop, &dwFlags) is NO_ERROR) { pRouteInfo2->Neighbour = hNextHop; }
RtmReleaseNextHopInfo(hRtmHandle, &nhiInfo); } }
// Adjust the next hops to corr to new protocol
for (j = k = 0; j < pRouteInfo1->NextHopsList.NumNextHops; j++) { if (RtmGetNextHopInfo(hRtmHandle, pRouteInfo1->NextHopsList.NextHops[j], &nhiInfo) is NO_ERROR) { // Add the same nexthop using new protocol
hNextHop = NULL;
if (RtmAddNextHop(hRtmHandle, &nhiInfo, &hNextHop, &dwFlags) is NO_ERROR) { pRouteInfo2->NextHopsList.NextHops[k++] = hNextHop; }
RtmReleaseNextHopInfo(hRtmHandle, &nhiInfo); } }
pRouteInfo2->NextHopsList.NumNextHops = (USHORT) k;
// Add the new route with the next hop information
if (k > 0) { dwFlags = 0; if (RtmAddRouteToDest(hRtmHandle, NULL, &rnaDest, pRouteInfo2, INFINITE, NULL, 0, NULL, &dwFlags) is NO_ERROR) { // Route add is successful - delete old route
if (RtmDeleteRouteToDest(pRouteInfo1->RouteOwner, hRoutes[i], &dwFlags) is NO_ERROR) { fDeleted = TRUE; } }
RtmReleaseNextHops(hRtmHandle, k, pRouteInfo2->NextHopsList.NextHops); } }
RtmReleaseRouteInfo(hRtmHandle, pRouteInfo1); }
if (!fDeleted) { RtmReleaseRoutes(hRtmHandle, 1, &hRoutes[i]); } } } while (dwResult is NO_ERROR);
RtmDeleteEnumHandle(hRtmHandle, hRtmEnum);
HeapFree(IPRouterHeap, 0, hRoutes); HeapFree(IPRouterHeap, 0, pRouteInfo1);
HeapFree(IPRouterHeap, 0, pRouteInfo2);
return NO_ERROR; }
DWORD DeleteRtmRoutes ( IN HANDLE hRtmHandle, IN DWORD dwIfIndex, IN BOOL fDeleteAll ) { HANDLE hRtmEnum; PHANDLE hRoutes; DWORD dwHandles; DWORD dwFlags; DWORD i; DWORD dwResult;
hRoutes = HeapAlloc( IPRouterHeap, 0, g_rtmProfile.MaxHandlesInEnum * sizeof(HANDLE) );
if (hRoutes == NULL) { Trace1(ERR, "DeleteRtmRoutes: Error allocating %d bytes", g_rtmProfile.MaxHandlesInEnum * sizeof(HANDLE)); return ERROR_NOT_ENOUGH_MEMORY; }
dwFlags = fDeleteAll ? 0: RTM_MATCH_INTERFACE; dwResult = RtmCreateRouteEnum(hRtmHandle, NULL, RTM_VIEW_MASK_ANY, RTM_ENUM_OWN_ROUTES, NULL, dwFlags, NULL, dwIfIndex, &hRtmEnum);
if(dwResult isnot NO_ERROR) { Trace2(ERR, "DeleteRtmRoutes: Error %d creating handle for %d\n", dwResult, hRtmHandle); HeapFree(IPRouterHeap, 0, hRoutes); return dwResult; }
do { dwHandles = g_rtmProfile.MaxHandlesInEnum; dwResult = RtmGetEnumRoutes(hRtmHandle, hRtmEnum, &dwHandles, hRoutes);
for (i = 0; i < dwHandles; i++) { if (RtmDeleteRouteToDest(hRtmHandle, hRoutes[i], &dwFlags) isnot NO_ERROR) { // If delete is successful, this is automatic
RtmReleaseRoutes(hRtmHandle, 1, &hRoutes[i]); } } } while (dwResult is NO_ERROR);
RtmDeleteEnumHandle(hRtmHandle, hRtmEnum);
HeapFree(IPRouterHeap, 0, hRoutes); return NO_ERROR; }
DWORD DeleteRtmNexthops ( IN HANDLE hRtmHandle, IN DWORD dwIfIndex, IN BOOL fDeleteAll ) { PRTM_NEXTHOP_INFO pNexthop; PHANDLE hNexthops; HANDLE hRtmEnum; DWORD dwHandles; DWORD i; DWORD dwResult;
hNexthops = HeapAlloc( IPRouterHeap, 0, g_rtmProfile.MaxHandlesInEnum * sizeof(HANDLE) );
if (hNexthops == NULL) { Trace1(ERR, "DeleteRtmNextHops: Error allocating %d bytes", g_rtmProfile.MaxHandlesInEnum * sizeof(HANDLE)); return ERROR_NOT_ENOUGH_MEMORY; }
dwResult = RtmCreateNextHopEnum(hRtmHandle, 0, NULL, &hRtmEnum);
if(dwResult isnot NO_ERROR) { Trace2(ERR, "DeleteAllNexthops: Error %d creating handle for %d\n", dwResult, hRtmHandle); HeapFree(IPRouterHeap, 0, hNexthops); return dwResult; }
do { dwHandles = g_rtmProfile.MaxHandlesInEnum; dwResult = RtmGetEnumNextHops(hRtmHandle, hRtmEnum, &dwHandles, hNexthops);
for (i = 0; i < dwHandles; i++) { if (!fDeleteAll) { //
// Make sure that the interface matches
//
if ((RtmGetNextHopPointer(hRtmHandle, hNexthops[i], &pNexthop) isnot NO_ERROR) || (pNexthop->InterfaceIndex != dwIfIndex)) { RtmReleaseNextHops(hRtmHandle, 1, &hNexthops[i]); continue; } }
// We need to delete this next hop here
if (RtmDeleteNextHop(hRtmHandle, hNexthops[i], NULL) isnot NO_ERROR) { // If delete is successful, this is automatic
RtmReleaseNextHops(hRtmHandle, 1, &hNexthops[i]); } } } while (dwResult is NO_ERROR);
RtmDeleteEnumHandle(hRtmHandle, hRtmEnum);
HeapFree(IPRouterHeap, 0, hNexthops);
return NO_ERROR; }
|