mirror of https://github.com/tongzx/nt5src
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2178 lines
58 KiB
2178 lines
58 KiB
/*++
|
|
|
|
Copyright (c) 1995-1998 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
apitest.c
|
|
|
|
Abstract:
|
|
Contains routines for testing the RTMv2 API.
|
|
|
|
Author:
|
|
Chaitanya Kodeboyina (chaitk) 26-Jun-1998
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "apitest.h"
|
|
|
|
MY_ENTITY_EXPORT_METHODS
|
|
Rip2Methods =
|
|
{
|
|
5,
|
|
|
|
{
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
NULL,
|
|
EntityExportMethod,
|
|
EntityExportMethod
|
|
}
|
|
};
|
|
|
|
MY_ENTITY_EXPORT_METHODS
|
|
OspfMethods =
|
|
{
|
|
6,
|
|
|
|
{
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
EntityExportMethod
|
|
}
|
|
};
|
|
|
|
MY_ENTITY_EXPORT_METHODS
|
|
Bgp4Methods =
|
|
{
|
|
4,
|
|
|
|
{
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
EntityExportMethod,
|
|
EntityExportMethod
|
|
}
|
|
};
|
|
|
|
|
|
ENTITY_CHARS
|
|
GlobalEntityChars [] =
|
|
{
|
|
//
|
|
// {
|
|
// Rtmv2Registration,
|
|
// { RtmInstanceId, AddressFamily, { EntityProtocolId, EntityInstanceId } },
|
|
// EntityEventCallback, ExportMethods,
|
|
// RoutesFileName,
|
|
// }
|
|
//
|
|
/*
|
|
{
|
|
FALSE,
|
|
{ 0, RTM_PROTOCOL_FAMILY_IP, { PROTO_IP_RIP, 1 } },
|
|
EntityEventCallback, &Rip2Methods,
|
|
"test.out"
|
|
},
|
|
*/
|
|
{
|
|
TRUE,
|
|
{ 1, AF_INET, { PROTO_IP_RIP, 1 } },
|
|
EntityEventCallback, &Rip2Methods,
|
|
"test.out"
|
|
},
|
|
|
|
{
|
|
TRUE,
|
|
{ 1, AF_INET, { PROTO_IP_OSPF, 1 } },
|
|
EntityEventCallback, &OspfMethods,
|
|
"test.out"
|
|
},
|
|
|
|
{
|
|
TRUE,
|
|
{ 1, AF_INET, { PROTO_IP_BGP, 1 } },
|
|
EntityEventCallback, &Bgp4Methods,
|
|
"test.out"
|
|
},
|
|
|
|
{
|
|
TRUE,
|
|
{ 0, 0, { 0, 0 } },
|
|
NULL, NULL,
|
|
""
|
|
}
|
|
};
|
|
|
|
const RTM_VIEW_SET VIEW_MASK_ARR[]
|
|
= {
|
|
0,
|
|
RTM_VIEW_MASK_UCAST,
|
|
RTM_VIEW_MASK_MCAST,
|
|
RTM_VIEW_MASK_UCAST | RTM_VIEW_MASK_MCAST
|
|
};
|
|
|
|
// Disable warnings for unreferenced params and local variables
|
|
#pragma warning(disable: 4100)
|
|
#pragma warning(disable: 4101)
|
|
|
|
//
|
|
// Main
|
|
//
|
|
|
|
#if !LOOKUP_TESTING
|
|
|
|
int
|
|
__cdecl
|
|
main (void)
|
|
{
|
|
PENTITY_CHARS Entity;
|
|
DWORD Status;
|
|
LPTHREAD_START_ROUTINE EntityThreadProc;
|
|
#if MT
|
|
UINT NumThreads;
|
|
HANDLE Threads[64];
|
|
#endif
|
|
|
|
Entity = &GlobalEntityChars[0];
|
|
|
|
#if MT
|
|
NumThreads = 0;
|
|
#endif
|
|
|
|
while (Entity->EntityInformation.EntityId.EntityId != 0)
|
|
{
|
|
if (Entity->Rtmv2Registration)
|
|
{
|
|
EntityThreadProc = Rtmv2EntityThreadProc;
|
|
}
|
|
else
|
|
{
|
|
EntityThreadProc = Rtmv1EntityThreadProc;
|
|
}
|
|
|
|
#if MT
|
|
Threads[NumThreads] = CreateThread(NULL,
|
|
0,
|
|
EntityThreadProc,
|
|
Entity++,
|
|
0,
|
|
NULL);
|
|
|
|
Print("Thread ID %d: %p\n", NumThreads, Threads[NumThreads]);
|
|
|
|
NumThreads++;
|
|
#else
|
|
Status = EntityThreadProc(Entity++);
|
|
#endif
|
|
}
|
|
|
|
#if MT
|
|
WaitForMultipleObjects(NumThreads, Threads, TRUE, INFINITE);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// A general state machine for a protocol thread (RTMv1)
|
|
//
|
|
|
|
DWORD Rtmv1EntityThreadProc (LPVOID ThreadParameters)
|
|
{
|
|
RTM_PROTOCOL_FAMILY_CONFIG FamilyConfig;
|
|
PENTITY_CHARS EntityChars;
|
|
PRTM_ENTITY_INFO EntityInfo;
|
|
HANDLE Event;
|
|
HANDLE V1RegnHandle;
|
|
DWORD ProtocolId;
|
|
UINT i;
|
|
FILE *FilePtr;
|
|
IP_NETWORK Network;
|
|
UINT NumDests;
|
|
UINT NumRoutes;
|
|
Route ThisRoute;
|
|
Route Routes[MAXROUTES];
|
|
RTM_IP_ROUTE V1Route;
|
|
RTM_IP_ROUTE V1Route2;
|
|
HANDLE V1EnumHandle;
|
|
BOOL Exists;
|
|
DWORD Flags;
|
|
DWORD Status;
|
|
|
|
//
|
|
// Get all characteristics of this entity
|
|
//
|
|
|
|
EntityChars = (PENTITY_CHARS) ThreadParameters;
|
|
|
|
EntityInfo = &EntityChars->EntityInformation;
|
|
|
|
Assert(EntityInfo->AddressFamily == RTM_PROTOCOL_FAMILY_IP);
|
|
|
|
Print("\n--------------------------------------------------------\n");
|
|
|
|
#if WRAPPER
|
|
FamilyConfig.RPFC_Validate = ValidateRouteCallback;
|
|
FamilyConfig.RPFC_Change = RouteChangeCallback;
|
|
|
|
Status = RtmCreateRouteTable(EntityInfo->AddressFamily, &FamilyConfig);
|
|
|
|
if (Status != ERROR_ALREADY_EXISTS)
|
|
{
|
|
Check(Status, 50);
|
|
}
|
|
else
|
|
{
|
|
Print("Protocol Family's Route Table already created\n");
|
|
}
|
|
|
|
Event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
|
|
|
Assert(Event != NULL);
|
|
|
|
ProtocolId = EntityInfo->EntityId.EntityProtocolId,
|
|
|
|
V1RegnHandle = RtmRegisterClient(EntityInfo->AddressFamily,
|
|
ProtocolId,
|
|
NULL,
|
|
NULL); // RTM_PROTOCOL_SINGLE_ROUTE);
|
|
if (V1RegnHandle == NULL)
|
|
{
|
|
Status = GetLastError();
|
|
|
|
Check(Status, 52);
|
|
}
|
|
|
|
if ((FilePtr = fopen(EntityChars->RoutesFileName, "r")) == NULL)
|
|
{
|
|
Fatal("Failed open route database with status = %p\n",
|
|
ERROR_OPENING_DATABASE);
|
|
}
|
|
|
|
NumRoutes = ReadRoutesFromFile(FilePtr, MAX_ROUTES, Routes);
|
|
|
|
fclose(FilePtr);
|
|
|
|
//
|
|
// How many destinations do we have in table ?
|
|
//
|
|
|
|
NumDests = RtmGetNetworkCount(EntityInfo->AddressFamily);
|
|
|
|
if (NumDests == 0)
|
|
{
|
|
Check(Status, 63);
|
|
}
|
|
|
|
Print("Number of destinations = %lu\n\n", NumDests);
|
|
|
|
//
|
|
// Add a bunch of routes from the input file
|
|
//
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
// Print("Add Route: Addr = %08x, Mask = %08x\n",
|
|
// Routes[i].addr,
|
|
// Routes[i].mask);
|
|
|
|
ConvertRouteToV1Route(&Routes[i], &V1Route);
|
|
|
|
V1Route.RR_ProtocolSpecificData.PSD_Data[0] =
|
|
V1Route.RR_ProtocolSpecificData.PSD_Data[1] =
|
|
V1Route.RR_ProtocolSpecificData.PSD_Data[2] =
|
|
V1Route.RR_ProtocolSpecificData.PSD_Data[3] = i;
|
|
|
|
V1Route.RR_FamilySpecificData.FSD_Priority = ProtocolId;
|
|
|
|
V1Route.RR_FamilySpecificData.FSD_Flags = (ULONG) ~0;
|
|
|
|
Status = RtmAddRoute(V1RegnHandle,
|
|
&V1Route,
|
|
INFINITE,
|
|
&Flags,
|
|
NULL,
|
|
NULL);
|
|
|
|
Check(Status, 54);
|
|
}
|
|
|
|
//
|
|
// Check if routes exist to the destination
|
|
//
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
Network.N_NetNumber = Routes[i].addr;
|
|
Network.N_NetMask = Routes[i].mask;
|
|
|
|
Exists = RtmIsRoute(EntityInfo->AddressFamily,
|
|
&Network,
|
|
&V1Route);
|
|
if (!Exists)
|
|
{
|
|
Check(Status, 64);
|
|
|
|
continue;
|
|
}
|
|
|
|
// ConvertV1RouteToRoute(&V1Route, &ThisRoute);
|
|
|
|
// PrintRoute(&ThisRoute);
|
|
|
|
Exists = RtmLookupIPDestination(Routes[i].addr,
|
|
&V1Route2);
|
|
|
|
if (Exists)
|
|
{
|
|
if (!RtlEqualMemory(&V1Route, &V1Route2, sizeof(RTM_IP_ROUTE)))
|
|
{
|
|
Print("Routes different: \n");
|
|
|
|
ConvertV1RouteToRoute(&V1Route, &ThisRoute);
|
|
// PrintRoute(&ThisRoute);
|
|
|
|
ConvertV1RouteToRoute(&V1Route2, &ThisRoute);
|
|
// PrintRoute(&ThisRoute);
|
|
|
|
Print("\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Status = GetLastError();
|
|
|
|
PrintIPAddr(&Routes[i].addr); Print("\n");
|
|
|
|
Check(Status, 65);
|
|
}
|
|
}
|
|
|
|
//
|
|
// How many destinations do we have in table ?
|
|
//
|
|
|
|
NumDests = RtmGetNetworkCount(EntityInfo->AddressFamily);
|
|
|
|
if (NumDests == 0)
|
|
{
|
|
Check(Status, 63);
|
|
}
|
|
|
|
Print("Number of destinations = %lu\n\n", NumDests);
|
|
|
|
// Try using RtmGetFirstRoute and RtmGetNextRoute
|
|
|
|
NumRoutes = 0;
|
|
|
|
Status = RtmGetFirstRoute(EntityInfo->AddressFamily,
|
|
NULL,
|
|
&V1Route);
|
|
|
|
// Check(Status, 59);
|
|
|
|
while (SUCCESS(Status))
|
|
{
|
|
NumRoutes++;
|
|
|
|
// Print the V1 Route that is next in the enum
|
|
|
|
// ConvertV1RouteToRoute(&V1Route, &ThisRoute);
|
|
|
|
// PrintRoute(&ThisRoute);
|
|
|
|
Status = RtmGetNextRoute(EntityInfo->AddressFamily,
|
|
NULL,
|
|
&V1Route);
|
|
|
|
// Check(Status, 60);
|
|
}
|
|
|
|
|
|
// Print("Num of routes in table : %lu\n", NumRoutes);
|
|
|
|
|
|
//
|
|
// Disable and reenable all routes that match criteria
|
|
//
|
|
|
|
V1Route.RR_InterfaceID = 3;
|
|
|
|
Status = RtmBlockSetRouteEnable(V1RegnHandle,
|
|
RTM_ONLY_THIS_INTERFACE,
|
|
&V1Route,
|
|
FALSE);
|
|
Check(Status, 66);
|
|
|
|
/*
|
|
V1Route.RR_InterfaceID = 3;
|
|
|
|
Status = RtmBlockSetRouteEnable(V1RegnHandle,
|
|
RTM_ONLY_THIS_INTERFACE,
|
|
&V1Route,
|
|
TRUE);
|
|
Check(Status, 66);
|
|
|
|
//
|
|
// Convert all routes that match criteria to static
|
|
//
|
|
|
|
V1Route.RR_InterfaceID = 3;
|
|
|
|
Status = RtmBlockConvertRoutesToStatic(V1RegnHandle,
|
|
RTM_ONLY_THIS_INTERFACE,
|
|
&V1Route);
|
|
|
|
Check(Status, 62);
|
|
*/
|
|
|
|
//
|
|
// Delete all routes that match the criteria
|
|
//
|
|
|
|
ZeroMemory(&V1Route, sizeof(RTM_IP_ROUTE));
|
|
|
|
V1Route.RR_InterfaceID = 2;
|
|
|
|
Status= RtmBlockDeleteRoutes(V1RegnHandle,
|
|
RTM_ONLY_THIS_INTERFACE|RTM_ONLY_THIS_NETWORK,
|
|
&V1Route);
|
|
|
|
Check(Status, 61);
|
|
|
|
//
|
|
// Enum and del this regn's routes in the table
|
|
//
|
|
|
|
V1Route.RR_RoutingProtocol = ProtocolId;
|
|
|
|
V1EnumHandle = RtmCreateEnumerationHandle(EntityInfo->AddressFamily,
|
|
RTM_ONLY_THIS_PROTOCOL,
|
|
&V1Route);
|
|
if (V1EnumHandle == NULL)
|
|
{
|
|
Status = GetLastError();
|
|
|
|
Check(Status, 56);
|
|
}
|
|
|
|
NumRoutes = 0;
|
|
|
|
do
|
|
{
|
|
Status = RtmEnumerateGetNextRoute(V1EnumHandle,
|
|
&V1Route);
|
|
|
|
// Check(Status, 58);
|
|
|
|
if (!SUCCESS(Status))
|
|
{
|
|
break;
|
|
}
|
|
|
|
NumRoutes++;
|
|
|
|
// Print the V1 Route that is next in the enum
|
|
|
|
// ConvertV1RouteToRoute(&V1Route, &ThisRoute);
|
|
|
|
// PrintRoute(&ThisRoute);
|
|
|
|
// Delete this route from the table forever
|
|
|
|
Status = RtmDeleteRoute(V1RegnHandle,
|
|
&V1Route,
|
|
&Flags,
|
|
NULL);
|
|
|
|
Check(Status, 55);
|
|
}
|
|
while (TRUE);
|
|
|
|
Print("Num of routes in table : %lu\n", NumRoutes);
|
|
|
|
Status = RtmCloseEnumerationHandle(V1EnumHandle);
|
|
|
|
Check(Status, 57);
|
|
|
|
//
|
|
// Enumerate all routes in table once again
|
|
//
|
|
|
|
V1EnumHandle = RtmCreateEnumerationHandle(EntityInfo->AddressFamily,
|
|
RTM_INCLUDE_DISABLED_ROUTES,
|
|
NULL);
|
|
if (V1EnumHandle == NULL)
|
|
{
|
|
Status = GetLastError();
|
|
|
|
Check(Status, 56);
|
|
}
|
|
|
|
NumRoutes = 0;
|
|
|
|
do
|
|
{
|
|
Status = RtmEnumerateGetNextRoute(V1EnumHandle,
|
|
&V1Route);
|
|
|
|
// Check(Status, 58);
|
|
|
|
if (!SUCCESS(Status))
|
|
{
|
|
break;
|
|
}
|
|
|
|
NumRoutes++;
|
|
|
|
// Print the V1 Route that is next in the enum
|
|
|
|
ConvertV1RouteToRoute(&V1Route, &ThisRoute);
|
|
|
|
// PrintRoute(&ThisRoute);
|
|
|
|
UNREFERENCED_PARAMETER(Flags);
|
|
|
|
Status = RtmDeleteRoute(V1RegnHandle,
|
|
&V1Route,
|
|
&Flags,
|
|
NULL);
|
|
|
|
Check(Status, 55);
|
|
}
|
|
while (TRUE);
|
|
|
|
Print("Num of routes in table : %lu\n", NumRoutes);
|
|
|
|
Status = RtmCloseEnumerationHandle(V1EnumHandle);
|
|
|
|
Check(Status, 57);
|
|
|
|
//
|
|
// Deregister the entity and clean up now
|
|
//
|
|
|
|
Status = RtmDeregisterClient(V1RegnHandle);
|
|
|
|
Check(Status, 53);
|
|
|
|
if (Event)
|
|
{
|
|
CloseHandle(Event);
|
|
}
|
|
|
|
Status = RtmDeleteRouteTable(EntityInfo->AddressFamily);
|
|
|
|
Check(Status, 51);
|
|
#endif
|
|
|
|
Print("\n--------------------------------------------------------\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
VOID
|
|
ConvertRouteToV1Route(Route *ThisRoute, RTM_IP_ROUTE *V1Route)
|
|
{
|
|
ZeroMemory(V1Route, sizeof(RTM_IP_ROUTE));
|
|
|
|
V1Route->RR_Network.N_NetNumber = ThisRoute->addr;
|
|
V1Route->RR_Network.N_NetMask = ThisRoute->mask;
|
|
|
|
V1Route->RR_InterfaceID = PtrToUlong(ThisRoute->interface);
|
|
|
|
V1Route->RR_NextHopAddress.N_NetNumber = ThisRoute->nexthop;
|
|
V1Route->RR_NextHopAddress.N_NetMask = 0xFFFFFFFF;
|
|
|
|
V1Route->RR_FamilySpecificData.FSD_Metric = ThisRoute->metric;
|
|
|
|
return;
|
|
}
|
|
|
|
VOID
|
|
ConvertV1RouteToRoute(RTM_IP_ROUTE *V1Route, Route *ThisRoute)
|
|
{
|
|
DWORD Mask;
|
|
|
|
ZeroMemory(ThisRoute, sizeof(Route));
|
|
|
|
ThisRoute->addr = V1Route->RR_Network.N_NetNumber;
|
|
ThisRoute->mask = V1Route->RR_Network.N_NetMask;
|
|
|
|
ThisRoute->len = 0;
|
|
|
|
// No checking for contiguous masks
|
|
|
|
Mask = ThisRoute->mask;
|
|
while (Mask)
|
|
{
|
|
if (Mask & 1)
|
|
{
|
|
ThisRoute->len++;
|
|
}
|
|
|
|
Mask >>= 1;
|
|
}
|
|
|
|
ThisRoute->interface = ULongToPtr(V1Route->RR_InterfaceID);
|
|
|
|
ThisRoute->nexthop = V1Route->RR_NextHopAddress.N_NetNumber;
|
|
Assert(V1Route->RR_NextHopAddress.N_NetMask == 0xFFFFFFFF);
|
|
|
|
ThisRoute->metric = V1Route->RR_FamilySpecificData.FSD_Metric;
|
|
|
|
Print("Owner = %08x, ", V1Route->RR_RoutingProtocol);
|
|
PrintRoute(ThisRoute);
|
|
|
|
return;
|
|
}
|
|
|
|
DWORD
|
|
ValidateRouteCallback(
|
|
IN PVOID Route
|
|
)
|
|
{
|
|
UNREFERENCED_PARAMETER(Route);
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
VOID
|
|
RouteChangeCallback(
|
|
IN DWORD Flags,
|
|
IN PVOID CurrBestRoute,
|
|
IN PVOID PrevBestRoute
|
|
)
|
|
{
|
|
Route ThisRoute;
|
|
|
|
Print("Route Change Notification:\n");
|
|
|
|
Print("Flags = %08x\n", Flags);
|
|
|
|
Print("Prev Route = ");
|
|
if (Flags & RTM_PREVIOUS_BEST_ROUTE)
|
|
{
|
|
ConvertV1RouteToRoute((RTM_IP_ROUTE *) PrevBestRoute, &ThisRoute);
|
|
// PrintRoute(ThisRoute);
|
|
}
|
|
else
|
|
{
|
|
Print("NULL Route\n");
|
|
}
|
|
|
|
// Print("Curr Route = ");
|
|
if (Flags & RTM_CURRENT_BEST_ROUTE)
|
|
{
|
|
ConvertV1RouteToRoute((RTM_IP_ROUTE *) CurrBestRoute, &ThisRoute);
|
|
// PrintRoute(ThisRoute);
|
|
}
|
|
else
|
|
{
|
|
Print("NULL Route\n");
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
|
|
//
|
|
// A general state machine for a protocol thread (RTMv2)
|
|
//
|
|
|
|
DWORD Rtmv2EntityThreadProc (LPVOID ThreadParameters)
|
|
{
|
|
PENTITY_CHARS EntityChars;
|
|
PRTM_ENTITY_INFO EntityInfo;
|
|
RTM_INSTANCE_CONFIG InstanceConfig;
|
|
RTM_ADDRESS_FAMILY_CONFIG AddrFamConfig;
|
|
RTM_ADDRESS_FAMILY_INFO AddrFamilyInfo;
|
|
RTM_ENTITY_HANDLE RtmRegHandle;
|
|
RTM_VIEW_SET ViewSet;
|
|
UINT NumViews;
|
|
UINT NumInstances;
|
|
RTM_INSTANCE_INFO Instances[MAX_INSTANCES];
|
|
RTM_ADDRESS_FAMILY_INFO AddrFams[MAX_ADDR_FAMS];
|
|
UINT NumEntities;
|
|
RTM_ENTITY_HANDLE EntityHandles[MAX_ENTITIES];
|
|
RTM_ENTITY_INFO EntityInfos[MAX_ENTITIES];
|
|
UINT NumMethods;
|
|
RTM_ENTITY_EXPORT_METHOD ExportMethods[MAX_METHODS];
|
|
RTM_ENTITY_METHOD_INPUT Input;
|
|
UINT OutputHdrSize;
|
|
UINT OutputSize;
|
|
RTM_ENTITY_METHOD_OUTPUT Output[MAX_METHODS];
|
|
UINT i, j, k, l, m;
|
|
UCHAR *p;
|
|
FILE *FilePtr;
|
|
UINT NumRoutes;
|
|
Route Routes[MAXROUTES];
|
|
RTM_NEXTHOP_INFO NextHopInfo;
|
|
RTM_NEXTHOP_HANDLE NextHopHandle;
|
|
PRTM_NEXTHOP_INFO NextHopPointer;
|
|
DWORD ChangeFlags;
|
|
RTM_ENUM_HANDLE EnumHandle;
|
|
RTM_ENUM_HANDLE EnumHandle1;
|
|
RTM_ENUM_HANDLE EnumHandle2;
|
|
UINT TotalHandles;
|
|
UINT NumHandles;
|
|
HANDLE Handles[MAX_HANDLES];
|
|
RTM_NET_ADDRESS NetAddress;
|
|
UINT DestInfoSize;
|
|
PRTM_DEST_INFO DestInfo1;
|
|
PRTM_DEST_INFO DestInfo2;
|
|
UINT NumInfos;
|
|
PRTM_DEST_INFO DestInfos;
|
|
RTM_ROUTE_INFO RouteInfo;
|
|
RTM_ROUTE_HANDLE RouteHandle;
|
|
PRTM_ROUTE_INFO RoutePointer;
|
|
RTM_PREF_INFO PrefInfo;
|
|
RTM_ROUTE_LIST_HANDLE RouteListHandle1;
|
|
RTM_ROUTE_LIST_HANDLE RouteListHandle2;
|
|
RTM_NOTIFY_HANDLE NotifyHandle;
|
|
BOOL Marked;
|
|
DWORD Status;
|
|
DWORD Status1;
|
|
DWORD Status2;
|
|
|
|
//
|
|
// Test the mask to len conversion macros in rtmv2.h
|
|
//
|
|
|
|
for (i = 0; i < 33; i++)
|
|
{
|
|
j = RTM_IPV4_MASK_FROM_LEN(i);
|
|
|
|
p = (PUCHAR) &j;
|
|
|
|
RTM_IPV4_LEN_FROM_MASK(k, j);
|
|
|
|
Assert(i == k);
|
|
|
|
printf("Len %2d: %08x: %02x.%02x.%02x.%02x: %2d\n",
|
|
i, j, p[0], p[1], p[2], p[3], k);
|
|
}
|
|
|
|
//
|
|
// Get all characteristics of this entity
|
|
//
|
|
|
|
EntityChars = (PENTITY_CHARS) ThreadParameters;
|
|
|
|
EntityInfo = &EntityChars->EntityInformation;
|
|
|
|
Print("\n--------------------------------------------------------\n");
|
|
|
|
//
|
|
// -00- Is this addr family config in registry
|
|
//
|
|
|
|
Status = RtmReadAddressFamilyConfig(EntityInfo->RtmInstanceId,
|
|
EntityInfo->AddressFamily,
|
|
&AddrFamConfig);
|
|
|
|
DBG_UNREFERENCED_LOCAL_VARIABLE(InstanceConfig);
|
|
|
|
if (!SUCCESS(Status))
|
|
{
|
|
// Fill in the instance config
|
|
|
|
Status = RtmWriteInstanceConfig(EntityInfo->RtmInstanceId,
|
|
&InstanceConfig);
|
|
|
|
Check(Status, 0);
|
|
|
|
// Fill in the address family config
|
|
|
|
AddrFamConfig.AddressSize = sizeof(DWORD);
|
|
|
|
AddrFamConfig.MaxChangeNotifyRegns = 10;
|
|
AddrFamConfig.MaxOpaqueInfoPtrs = 10;
|
|
AddrFamConfig.MaxNextHopsInRoute = 5;
|
|
AddrFamConfig.MaxHandlesInEnum = 100;
|
|
|
|
AddrFamConfig.ViewsSupported = RTM_VIEW_MASK_UCAST|RTM_VIEW_MASK_MCAST;
|
|
|
|
Status = RtmWriteAddressFamilyConfig(EntityInfo->RtmInstanceId,
|
|
EntityInfo->AddressFamily,
|
|
&AddrFamConfig);
|
|
Check(Status, 0);
|
|
}
|
|
|
|
//
|
|
// -01- Register with an AF on an RTM instance
|
|
//
|
|
|
|
Status = RtmRegisterEntity(EntityInfo,
|
|
(PRTM_ENTITY_EXPORT_METHODS)
|
|
EntityChars->ExportMethods,
|
|
EntityChars->EventCallback,
|
|
TRUE,
|
|
&EntityChars->RegnProfile,
|
|
&RtmRegHandle);
|
|
|
|
Check(Status, 1);
|
|
|
|
//
|
|
// Count the number of views for later use
|
|
//
|
|
|
|
NumViews = EntityChars->RegnProfile.NumberOfViews;
|
|
|
|
//
|
|
// Test all the management APIs before others
|
|
//
|
|
|
|
NumInstances = MAX_INSTANCES;
|
|
|
|
Status = RtmGetInstances(&NumInstances,
|
|
&Instances[0]);
|
|
|
|
Check(Status, 100);
|
|
|
|
for (i = 0; i < NumInstances; i++)
|
|
{
|
|
Status = RtmGetInstanceInfo(Instances[i].RtmInstanceId,
|
|
&Instances[i],
|
|
&Instances[i].NumAddressFamilies,
|
|
AddrFams);
|
|
|
|
Check(Status, 101);
|
|
}
|
|
|
|
//
|
|
// Query the appropriate table to check regn
|
|
//
|
|
|
|
NumEntities = MAX_ENTITIES;
|
|
|
|
Status = RtmGetAddressFamilyInfo(EntityInfo->RtmInstanceId,
|
|
EntityInfo->AddressFamily,
|
|
&AddrFamilyInfo,
|
|
&NumEntities,
|
|
EntityInfos);
|
|
Check(Status, 102);
|
|
|
|
//
|
|
// -03- Get all currently registered entities
|
|
//
|
|
|
|
NumEntities = MAX_ENTITIES;
|
|
|
|
Status = RtmGetRegisteredEntities(RtmRegHandle,
|
|
&NumEntities,
|
|
EntityHandles,
|
|
EntityInfos);
|
|
|
|
Print("\n");
|
|
for (i = 0; i < NumEntities; i++)
|
|
{
|
|
Print("%02d: Handle: %p\n", i, EntityHandles[i]);
|
|
}
|
|
Print("\n");
|
|
|
|
Check(Status, 3);
|
|
|
|
//
|
|
// -04- Get all exports methods of each entity
|
|
//
|
|
|
|
for (i = 0; i < NumEntities; i++)
|
|
{
|
|
NumMethods = 0;
|
|
|
|
Status = RtmGetEntityMethods(RtmRegHandle,
|
|
EntityHandles[i],
|
|
&NumMethods,
|
|
NULL);
|
|
|
|
Check(Status, 4);
|
|
|
|
Print("\n");
|
|
Print("Number of methods for %p = %2d\n",
|
|
EntityHandles[i],
|
|
NumMethods);
|
|
Print("\n");
|
|
|
|
Status = RtmGetEntityMethods(RtmRegHandle,
|
|
EntityHandles[i],
|
|
&NumMethods,
|
|
ExportMethods);
|
|
|
|
Check(Status, 4);
|
|
|
|
/*
|
|
//
|
|
// -06- Try blocking methods & then calling invoke
|
|
// Wont block as thread owns Critical Section
|
|
//
|
|
|
|
Status = RtmBlockMethods(RtmRegHandle,
|
|
NULL,
|
|
0,
|
|
RTM_BLOCK_METHODS);
|
|
|
|
Check(Status, 6);
|
|
*/
|
|
|
|
// for (j = 0; j < NumMethods; j++)
|
|
{
|
|
//
|
|
// -05- Invoke all exports methods of an entity
|
|
//
|
|
|
|
Input.MethodType = METHOD_TYPE_ALL_METHODS; // 1 << j;
|
|
|
|
Input.InputSize = 0;
|
|
|
|
OutputHdrSize = FIELD_OFFSET(RTM_ENTITY_METHOD_OUTPUT, OutputData);
|
|
|
|
OutputSize = OutputHdrSize * MAX_METHODS;
|
|
|
|
Status = RtmInvokeMethod(RtmRegHandle,
|
|
EntityHandles[i],
|
|
&Input,
|
|
&OutputSize,
|
|
Output);
|
|
|
|
Print("\n");
|
|
Print("Num Methods Called = %d\n", OutputSize / OutputHdrSize);
|
|
Print("\n");
|
|
|
|
Check(Status, 5);
|
|
}
|
|
}
|
|
|
|
//
|
|
// -44- Release handles we have on the entities
|
|
//
|
|
|
|
Status = RtmReleaseEntities(RtmRegHandle,
|
|
NumEntities,
|
|
EntityHandles);
|
|
|
|
Check(Status, 44);
|
|
|
|
//
|
|
// -07- Add next hops to the table (from the info file)
|
|
//
|
|
|
|
if ((FilePtr = fopen(EntityChars->RoutesFileName, "r")) == NULL)
|
|
{
|
|
Fatal("Failed open route database with status = %x\n",
|
|
ERROR_OPENING_DATABASE);
|
|
}
|
|
|
|
NumRoutes = ReadRoutesFromFile(FilePtr,
|
|
MAX_ROUTES,
|
|
Routes);
|
|
|
|
fclose(FilePtr);
|
|
|
|
// For each route, add its next-hop to the next-hop table
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NextHopInfo.NextHopAddress,
|
|
Routes[i].nexthop,
|
|
ADDRSIZE);
|
|
|
|
NextHopInfo.RemoteNextHop = NULL;
|
|
NextHopInfo.Flags = 0;
|
|
NextHopInfo.EntitySpecificInfo = UIntToPtr(i);
|
|
NextHopInfo.InterfaceIndex = PtrToUlong(Routes[i].interface);
|
|
|
|
NextHopHandle = NULL;
|
|
|
|
Status = RtmAddNextHop(RtmRegHandle,
|
|
&NextHopInfo,
|
|
&NextHopHandle,
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 7);
|
|
|
|
// Print("Add Next Hop %lu: %p\n", i, NextHopHandle);
|
|
|
|
if (!(ChangeFlags & RTM_NEXTHOP_CHANGE_NEW))
|
|
{
|
|
Status = RtmReleaseNextHops(RtmRegHandle,
|
|
1,
|
|
&NextHopHandle);
|
|
Check(Status, 15);
|
|
}
|
|
#if _DBG_
|
|
else
|
|
{
|
|
Status = RtmDeleteNextHop(RtmRegHandle,
|
|
NextHopHandle,
|
|
NULL);
|
|
Check(Status, 14);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
|
|
//
|
|
// 08 - Find the next-hops added using RtmFindNextHop
|
|
//
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NextHopInfo.NextHopAddress,
|
|
Routes[i].nexthop,
|
|
ADDRSIZE);
|
|
|
|
NextHopInfo.NextHopOwner = RtmRegHandle;
|
|
|
|
NextHopInfo.InterfaceIndex = PtrToUlong(Routes[i].interface);
|
|
|
|
NextHopHandle = NULL;
|
|
|
|
Status = RtmFindNextHop(RtmRegHandle,
|
|
&NextHopInfo,
|
|
&NextHopHandle,
|
|
&NextHopPointer);
|
|
|
|
// Print("NextHop: Handle: %p,\n\t Addr: ", NextHopHandle);
|
|
// Print("%3d.", (UINT) NextHopPointer->NextHopAddress.AddrBits[0]);
|
|
// Print("%3d.", (UINT) NextHopPointer->NextHopAddress.AddrBits[1]);
|
|
// Print("%3d.", (UINT) NextHopPointer->NextHopAddress.AddrBits[2]);
|
|
// Print("%3d ", (UINT) NextHopPointer->NextHopAddress.AddrBits[3]);
|
|
// Print("\n\tInterface = %lu\n", NextHopPointer->InterfaceIndex);
|
|
|
|
Check(Status, 8);
|
|
|
|
Status = RtmReleaseNextHops(RtmRegHandle,
|
|
1,
|
|
&NextHopHandle);
|
|
|
|
Check(Status, 15);
|
|
}
|
|
|
|
//
|
|
// -40- Register with RTM for getting change notifications
|
|
//
|
|
|
|
Status = RtmRegisterForChangeNotification(RtmRegHandle,
|
|
RTM_VIEW_MASK_MCAST,
|
|
RTM_CHANGE_TYPE_BEST,
|
|
// RTM_NOTIFY_ONLY_MARKED_DESTS,
|
|
EntityChars,
|
|
&NotifyHandle);
|
|
|
|
Check(Status, 40);
|
|
|
|
Print("Change Notification Registration Successful\n\n");
|
|
|
|
//
|
|
// -35- Create an entity specific list to add routes to
|
|
//
|
|
|
|
Status = RtmCreateRouteList(RtmRegHandle,
|
|
&RouteListHandle1);
|
|
|
|
Check(Status, 35);
|
|
|
|
//
|
|
// -17- Add routes to RIB with approprate next-hops
|
|
//
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
// Get the next hop handle using next hop address
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NextHopInfo.NextHopAddress,
|
|
Routes[i].nexthop,
|
|
ADDRSIZE);
|
|
|
|
NextHopInfo.NextHopOwner = RtmRegHandle;
|
|
|
|
NextHopInfo.InterfaceIndex = PtrToUlong(Routes[i].interface);
|
|
|
|
NextHopHandle = NULL;
|
|
|
|
Status = RtmFindNextHop(RtmRegHandle,
|
|
&NextHopInfo,
|
|
&NextHopHandle,
|
|
NULL);
|
|
Check(Status, 8);
|
|
|
|
// Now do the route add with the right information
|
|
|
|
RouteHandle = NULL;
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NetAddress,
|
|
Routes[i].addr,
|
|
Routes[i].len);
|
|
|
|
// Print("Add Route: Len : %08x, Addr = %3d.%3d.%3d.%3d\n",
|
|
// NetAddress.NumBits,
|
|
// NetAddress.AddrBits[0],
|
|
// NetAddress.AddrBits[1],
|
|
// NetAddress.AddrBits[2],
|
|
// NetAddress.AddrBits[3]);
|
|
|
|
ZeroMemory(&RouteInfo, sizeof(RTM_ROUTE_INFO));
|
|
|
|
// Assume 'neighbour learnt from' is the 1st nexthop
|
|
RouteInfo.Neighbour = NextHopHandle;
|
|
|
|
RouteInfo.PrefInfo.Preference = EntityInfo->EntityId.EntityProtocolId;
|
|
RouteInfo.PrefInfo.Metric = Routes[i].metric;
|
|
|
|
RouteInfo.BelongsToViews = VIEW_MASK_ARR[1 + (i % 3)];
|
|
|
|
RouteInfo.EntitySpecificInfo = UIntToPtr(i);
|
|
|
|
RouteInfo.NextHopsList.NumNextHops = 1;
|
|
RouteInfo.NextHopsList.NextHops[0] = NextHopHandle;
|
|
|
|
ChangeFlags = RTM_ROUTE_CHANGE_NEW;
|
|
|
|
Status = RtmAddRouteToDest(RtmRegHandle,
|
|
&RouteHandle,
|
|
&NetAddress,
|
|
&RouteInfo,
|
|
INFINITE,
|
|
RouteListHandle1,
|
|
0,
|
|
NULL,
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 17);
|
|
|
|
// Update the same route using the handle
|
|
|
|
ChangeFlags = 0;
|
|
|
|
RouteInfo.Flags = RTM_ROUTE_FLAGS_DISCARD;
|
|
|
|
Status = RtmAddRouteToDest(RtmRegHandle,
|
|
&RouteHandle,
|
|
&NetAddress,
|
|
&RouteInfo,
|
|
INFINITE,
|
|
RouteListHandle1,
|
|
0,
|
|
NULL,
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 17);
|
|
|
|
// Print("Add Route %lu: %p\n", i, RouteHandle);
|
|
|
|
Status = RtmLockRoute(RtmRegHandle,
|
|
RouteHandle,
|
|
TRUE,
|
|
TRUE,
|
|
&RoutePointer);
|
|
|
|
Check(Status, 46);
|
|
|
|
// Update route parameters in place
|
|
|
|
RoutePointer->PrefInfo.Metric = 1000 - RoutePointer->PrefInfo.Metric;
|
|
|
|
RoutePointer->BelongsToViews = VIEW_MASK_ARR[i % 3];
|
|
|
|
RoutePointer->EntitySpecificInfo = UIntToPtr(1000 - i);
|
|
|
|
Status = RtmUpdateAndUnlockRoute(RtmRegHandle,
|
|
RouteHandle,
|
|
10, // INFINITE,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 47);
|
|
|
|
// Print("Update Route %lu: %p\n", i, RouteHandle);
|
|
|
|
if (!SUCCESS(Status))
|
|
{
|
|
Status = RtmLockRoute(RtmRegHandle,
|
|
RouteHandle,
|
|
TRUE,
|
|
FALSE,
|
|
NULL);
|
|
|
|
Check(Status, 46);
|
|
}
|
|
|
|
// Try doing a add specifying the route handle
|
|
|
|
RouteInfo.PrefInfo.Metric = Routes[i].metric;
|
|
|
|
RouteInfo.BelongsToViews = VIEW_MASK_ARR[1 + (i % 3)];
|
|
|
|
RouteInfo.EntitySpecificInfo = UIntToPtr(i);
|
|
|
|
ChangeFlags = 0;
|
|
|
|
Status = RtmAddRouteToDest(RtmRegHandle,
|
|
&RouteHandle,
|
|
&NetAddress,
|
|
&RouteInfo,
|
|
INFINITE,
|
|
RouteListHandle1,
|
|
0,
|
|
NULL,
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 17);
|
|
|
|
Status = RtmReleaseNextHops(RtmRegHandle,
|
|
1,
|
|
&NextHopHandle);
|
|
|
|
// Check(Status, 15);
|
|
|
|
if (!SUCCESS(Status))
|
|
{
|
|
// Print("%p %p\n", RtmRegHandle,NextHopHandle);
|
|
|
|
Status = RtmReleaseNextHops(RtmRegHandle,
|
|
1,
|
|
&NextHopHandle);
|
|
|
|
Check(Status, 15);
|
|
}
|
|
}
|
|
|
|
Status = RtmCreateRouteList(RtmRegHandle,
|
|
&RouteListHandle2);
|
|
|
|
Check(Status, 35);
|
|
|
|
//
|
|
// -38- Create an enumeration on the route list
|
|
//
|
|
|
|
Status = RtmCreateRouteListEnum(RtmRegHandle,
|
|
RouteListHandle1,
|
|
&EnumHandle);
|
|
|
|
Check(Status, 38);
|
|
|
|
TotalHandles = 0;
|
|
|
|
do
|
|
{
|
|
//
|
|
// -39- Get next set of routes on the enum
|
|
//
|
|
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status = RtmGetListEnumRoutes(RtmRegHandle,
|
|
EnumHandle,
|
|
&NumHandles,
|
|
Handles);
|
|
Check(Status, 39);
|
|
|
|
TotalHandles += NumHandles;
|
|
|
|
for (i = 0; i < NumHandles; i++)
|
|
{
|
|
; // Print("Route Handle %5lu: %p\n", i, Handles[i]);
|
|
}
|
|
|
|
//
|
|
// -37- Move all routes in one route list to another
|
|
//
|
|
|
|
Status = RtmInsertInRouteList(RtmRegHandle,
|
|
RouteListHandle2,
|
|
NumHandles,
|
|
Handles);
|
|
|
|
Check(Status, 37);
|
|
|
|
//
|
|
// Release the routes that have been enum'ed
|
|
//
|
|
|
|
Status = RtmReleaseRoutes(RtmRegHandle, NumHandles, Handles);
|
|
|
|
Check(Status, 27);
|
|
}
|
|
while (NumHandles == MAX_HANDLES);
|
|
|
|
Print("\nTotal Num of handles in list: %lu\n", TotalHandles);
|
|
|
|
|
|
//
|
|
// -36- Destroy all the entity specific lists
|
|
//
|
|
|
|
Status = RtmDeleteRouteList(RtmRegHandle, RouteListHandle1);
|
|
|
|
Check(Status, 36);
|
|
|
|
|
|
Status = RtmDeleteRouteList(RtmRegHandle, RouteListHandle2);
|
|
|
|
Check(Status, 36);
|
|
|
|
|
|
DestInfoSize = RTM_SIZE_OF_DEST_INFO(NumViews);
|
|
|
|
DestInfo1 = ALLOC_RTM_DEST_INFO(NumViews, 1);
|
|
|
|
DestInfo2 = ALLOC_RTM_DEST_INFO(NumViews, 1);
|
|
|
|
//
|
|
// -18- Get dests from the table using exact match
|
|
//
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
// Query for the route with the right information
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NetAddress, Routes[i].addr, Routes[i].len);
|
|
|
|
Status = RtmGetExactMatchDestination(RtmRegHandle,
|
|
&NetAddress,
|
|
RTM_BEST_PROTOCOL,
|
|
0,
|
|
DestInfo1);
|
|
Check(Status, 18);
|
|
|
|
//
|
|
// For each destination in table, mark the dest
|
|
//
|
|
|
|
Status = RtmMarkDestForChangeNotification(RtmRegHandle,
|
|
NotifyHandle,
|
|
DestInfo1->DestHandle,
|
|
TRUE);
|
|
Check(Status, 48);
|
|
|
|
Status = RtmIsMarkedForChangeNotification(RtmRegHandle,
|
|
NotifyHandle,
|
|
DestInfo1->DestHandle,
|
|
&Marked);
|
|
Check(Status, 49);
|
|
|
|
Assert(Marked == TRUE);
|
|
|
|
Status = RtmReleaseDestInfo(RtmRegHandle, DestInfo1);
|
|
|
|
Check(Status, 22);
|
|
}
|
|
|
|
DestInfo1 = ALLOC_RTM_DEST_INFO(NumViews, 1);
|
|
|
|
DestInfo2 = ALLOC_RTM_DEST_INFO(NumViews, 1);
|
|
|
|
//
|
|
// -29- Get routes from the table using exact match
|
|
//
|
|
|
|
for (i = 0; i < NumRoutes; i++)
|
|
{
|
|
// Get the next hop handle using next hop address
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NextHopInfo.NextHopAddress,
|
|
Routes[i].nexthop,
|
|
ADDRSIZE);
|
|
|
|
NextHopInfo.NextHopOwner = RtmRegHandle;
|
|
|
|
NextHopInfo.InterfaceIndex = PtrToUlong(Routes[i].interface);
|
|
|
|
NextHopHandle = NULL;
|
|
|
|
Status = RtmFindNextHop(RtmRegHandle,
|
|
&NextHopInfo,
|
|
&NextHopHandle,
|
|
NULL);
|
|
Check(Status, 8);
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NetAddress,
|
|
Routes[i].addr,
|
|
Routes[i].len);
|
|
|
|
// Query for the route with the right information
|
|
|
|
RouteInfo.Neighbour = NextHopHandle;
|
|
|
|
RouteInfo.RouteOwner = RtmRegHandle;
|
|
|
|
RouteInfo.PrefInfo.Preference = EntityInfo->EntityId.EntityProtocolId;
|
|
RouteInfo.PrefInfo.Metric = Routes[i].metric;
|
|
|
|
RouteInfo.NextHopsList.NumNextHops = 1;
|
|
RouteInfo.NextHopsList.NextHops[0] = NextHopHandle;
|
|
|
|
Status = RtmGetExactMatchRoute(RtmRegHandle,
|
|
&NetAddress,
|
|
RTM_MATCH_FULL,
|
|
&RouteInfo,
|
|
PtrToUlong(Routes[i].interface),
|
|
0,
|
|
&RouteHandle);
|
|
|
|
Check(Status, 29);
|
|
|
|
Status = RtmReleaseNextHops(RtmRegHandle,
|
|
1,
|
|
&NextHopHandle);
|
|
|
|
Check(Status, 15);
|
|
|
|
Status = RtmReleaseRoutes(RtmRegHandle, 1, &RouteHandle);
|
|
|
|
Check(Status, 27);
|
|
|
|
Status = RtmReleaseRouteInfo(RtmRegHandle, &RouteInfo);
|
|
|
|
Check(Status, 31);
|
|
}
|
|
|
|
|
|
//
|
|
// -19- Get dests from the table using prefix match
|
|
//
|
|
// -20- Do a prefix walk up the tree for each dest
|
|
//
|
|
|
|
for (i = j = 0; i < NumRoutes; i++)
|
|
{
|
|
// Query for the route with the right information
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NetAddress,
|
|
Routes[i].addr,
|
|
Routes[i].len);
|
|
|
|
Status = RtmGetMostSpecificDestination(RtmRegHandle,
|
|
&NetAddress,
|
|
RTM_BEST_PROTOCOL,
|
|
RTM_VIEW_MASK_UCAST,
|
|
DestInfo1);
|
|
|
|
// Check(Status, 19);
|
|
|
|
if (DestInfo1->DestAddress.NumBits != NetAddress.NumBits)
|
|
{
|
|
; // Print("No Exact Match : %5lu\n", j++);
|
|
}
|
|
|
|
while (SUCCESS(Status))
|
|
{
|
|
Status = RtmGetLessSpecificDestination(RtmRegHandle,
|
|
DestInfo1->DestHandle,
|
|
RTM_BEST_PROTOCOL,
|
|
RTM_VIEW_MASK_UCAST,
|
|
DestInfo2);
|
|
|
|
// Check(Status, 20);
|
|
|
|
Check(RtmReleaseDestInfo(RtmRegHandle, DestInfo1), 22);
|
|
|
|
if (!SUCCESS(Status)) break;
|
|
|
|
// Print("NumBits: %d\n", DestInfo2->DestAddress.NumBits);
|
|
|
|
Status = RtmGetLessSpecificDestination(RtmRegHandle,
|
|
DestInfo2->DestHandle,
|
|
RTM_BEST_PROTOCOL,
|
|
RTM_VIEW_MASK_UCAST,
|
|
DestInfo1);
|
|
|
|
// Check(Status, 20);
|
|
|
|
Check(RtmReleaseDestInfo(RtmRegHandle, DestInfo2), 20);
|
|
|
|
if (!SUCCESS(Status)) break;
|
|
|
|
// Print("NumBits: %d\n", DestInfo1->DestAddress.NumBits);
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
for (i = 0; i < 100000000; i++) { ; }
|
|
#endif
|
|
|
|
|
|
//
|
|
// Just do a "route enum" over the whole table
|
|
//
|
|
|
|
Status2 = RtmCreateRouteEnum(RtmRegHandle,
|
|
NULL,
|
|
0, // RTM_VIEW_MASK_UCAST|RTM_VIEW_MASK_MCAST,
|
|
RTM_ENUM_OWN_ROUTES,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
0,
|
|
&EnumHandle2);
|
|
|
|
Check(Status2, 25);
|
|
|
|
l = 0;
|
|
|
|
do
|
|
{
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status2 = RtmGetEnumRoutes(RtmRegHandle,
|
|
EnumHandle2,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
// Check(Status2, 26);
|
|
|
|
for (k = 0; k < NumHandles; k++)
|
|
{
|
|
; // Print("Route %d: %p\n", l++, Handles[k]);
|
|
}
|
|
|
|
Check(RtmReleaseRoutes(RtmRegHandle,
|
|
NumHandles,
|
|
Handles), 27);
|
|
}
|
|
while (SUCCESS(Status2));
|
|
|
|
//
|
|
// Just try a enum query after ERROR_NO_MORE_ITEMS is retd
|
|
//
|
|
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status2 = RtmGetEnumRoutes(RtmRegHandle,
|
|
EnumHandle2,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
Assert((NumHandles == 0) && (Status2 == ERROR_NO_MORE_ITEMS));
|
|
|
|
Status2 = RtmDeleteEnumHandle(RtmRegHandle,
|
|
EnumHandle2);
|
|
|
|
Check(Status2, 16);
|
|
|
|
//
|
|
// Get dests from the table using an enumeration
|
|
// -23- Open a new dest enumeration
|
|
// -24- Get dests in enum
|
|
// -16- Close destination enum.
|
|
//
|
|
|
|
DestInfos = ALLOC_RTM_DEST_INFO(NumViews, MAX_HANDLES);
|
|
|
|
#if MCAST_ENUM
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NetAddress,
|
|
0x000000E0,
|
|
4);
|
|
|
|
#else
|
|
|
|
RTM_IPV4_MAKE_NET_ADDRESS(&NetAddress,
|
|
0x00000000,
|
|
0);
|
|
|
|
#endif
|
|
|
|
Status = RtmCreateDestEnum(RtmRegHandle,
|
|
RTM_VIEW_MASK_UCAST | RTM_VIEW_MASK_MCAST,
|
|
RTM_ENUM_RANGE | RTM_ENUM_OWN_DESTS,
|
|
&NetAddress,
|
|
RTM_THIS_PROTOCOL,
|
|
&EnumHandle1);
|
|
|
|
Check(Status, 23);
|
|
|
|
m = j = 0;
|
|
|
|
do
|
|
{
|
|
NumInfos = MAX_HANDLES;
|
|
|
|
Status1 = RtmGetEnumDests(RtmRegHandle,
|
|
EnumHandle1,
|
|
&NumInfos,
|
|
DestInfos);
|
|
|
|
// Check(Status1, 24);
|
|
|
|
for (i = 0; i < NumInfos; i++)
|
|
{
|
|
DestInfo1 = (PRTM_DEST_INFO) ((PUCHAR)DestInfos+(i*DestInfoSize));
|
|
|
|
// Print("Dest %d: %p\n", j++, DestInfo1->DestHandle);
|
|
|
|
// PrintDestInfo(DestInfo1);
|
|
|
|
Status2 = RtmCreateRouteEnum(RtmRegHandle,
|
|
DestInfo1->DestHandle,
|
|
RTM_VIEW_MASK_UCAST |
|
|
RTM_VIEW_MASK_MCAST,
|
|
RTM_ENUM_OWN_ROUTES,
|
|
NULL,
|
|
0, // RTM_MATCH_INTERFACE,
|
|
NULL,
|
|
0,
|
|
&EnumHandle2);
|
|
|
|
Check(Status2, 25);
|
|
/*
|
|
Check(RtmHoldDestination(RtmRegHandle,
|
|
DestInfo1->DestHandle,
|
|
RTM_VIEW_MASK_UCAST,
|
|
100), 33);
|
|
*/
|
|
l = 0;
|
|
|
|
PrefInfo.Preference = (ULONG) ~0;
|
|
PrefInfo.Metric = (ULONG) 0;
|
|
|
|
do
|
|
{
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status2 = RtmGetEnumRoutes(RtmRegHandle,
|
|
EnumHandle2,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
// Check(Status2, 26);
|
|
|
|
for (k = 0; k < NumHandles; k++)
|
|
{
|
|
// Print("\tRoute %d: %p\t", l++, Handles[k]);
|
|
|
|
// PrintRouteInfo(Handles[k]);
|
|
|
|
Status = RtmIsBestRoute(RtmRegHandle,
|
|
Handles[k],
|
|
&ViewSet);
|
|
|
|
Check(Status, 28);
|
|
|
|
// Print("Best In Views: %08x\n", ViewSet);
|
|
|
|
|
|
Status = RtmGetRouteInfo(RtmRegHandle,
|
|
Handles[k],
|
|
&RouteInfo,
|
|
&NetAddress);
|
|
|
|
Check(Status, 30);
|
|
|
|
Print("RouteDest: Len : %08x," \
|
|
" Addr = %3d.%3d.%3d.%3d," \
|
|
" Pref = %08x, %08x\n",
|
|
NetAddress.NumBits,
|
|
NetAddress.AddrBits[0],
|
|
NetAddress.AddrBits[1],
|
|
NetAddress.AddrBits[2],
|
|
NetAddress.AddrBits[3],
|
|
RouteInfo.PrefInfo.Preference,
|
|
RouteInfo.PrefInfo.Metric);
|
|
|
|
//
|
|
// Make sure that list is ordered by PrefInfo
|
|
//
|
|
|
|
if ((PrefInfo.Preference < RouteInfo.PrefInfo.Preference)||
|
|
((PrefInfo.Preference == RouteInfo.PrefInfo.Preference)
|
|
&& (PrefInfo.Metric > RouteInfo.PrefInfo.Metric)))
|
|
{
|
|
Check(ERROR_INVALID_DATA, 150);
|
|
}
|
|
|
|
Status = RtmReleaseRouteInfo(RtmRegHandle,
|
|
&RouteInfo);
|
|
|
|
Check(Status, 31);
|
|
|
|
// Print("Del Route %lu: %p\n", m++, Handles[k]);
|
|
|
|
Status = RtmDeleteRouteToDest(RtmRegHandle,
|
|
Handles[k],
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 32);
|
|
}
|
|
|
|
Check(RtmReleaseRoutes(RtmRegHandle,
|
|
NumHandles,
|
|
Handles), 27);
|
|
}
|
|
while (SUCCESS(Status2));
|
|
|
|
//
|
|
// Just try a enum query after ERROR_NO_MORE_ITEMS is retd
|
|
//
|
|
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status2 = RtmGetEnumRoutes(RtmRegHandle,
|
|
EnumHandle2,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
Assert((NumHandles == 0) && (Status2 == ERROR_NO_MORE_ITEMS));
|
|
/*
|
|
Check(RtmHoldDestination(RtmRegHandle,
|
|
DestInfo1->DestHandle,
|
|
RTM_VIEW_MASK_MCAST,
|
|
100), 33);
|
|
*/
|
|
Status2 = RtmDeleteEnumHandle(RtmRegHandle,
|
|
EnumHandle2);
|
|
|
|
Check(Status2, 16);
|
|
|
|
// Check(RtmReleaseDestInfo(RtmRegHandle,
|
|
// DestInfo1), 22);
|
|
}
|
|
|
|
Check(RtmReleaseDests(RtmRegHandle,
|
|
NumInfos,
|
|
DestInfos), 34);
|
|
}
|
|
while (SUCCESS(Status1));
|
|
|
|
//
|
|
// Just try a enum query after ERROR_NO_MORE_ITEMS is retd
|
|
//
|
|
|
|
NumInfos = MAX_HANDLES;
|
|
|
|
Status1 = RtmGetEnumDests(RtmRegHandle,
|
|
EnumHandle1,
|
|
&NumInfos,
|
|
DestInfos);
|
|
|
|
Assert((NumInfos == 0) && (Status1 == ERROR_NO_MORE_ITEMS));
|
|
|
|
Status1 = RtmDeleteEnumHandle(RtmRegHandle,
|
|
EnumHandle1);
|
|
|
|
Check(Status1, 16);
|
|
|
|
|
|
//
|
|
// -10- Enumerate all the next-hops in table,
|
|
//
|
|
// -11- For each next-hop in table
|
|
// -12- Get the next hop info,
|
|
// -14- Delete the next-hop,
|
|
// -13- Release next hop info.
|
|
//
|
|
// -15- Release all the next-hops in table,
|
|
//
|
|
// -16- Close the next hop enumeration handle.
|
|
//
|
|
|
|
Status = RtmCreateNextHopEnum(RtmRegHandle,
|
|
0,
|
|
NULL,
|
|
&EnumHandle);
|
|
|
|
Check(Status, 10);
|
|
|
|
j = 0;
|
|
|
|
do
|
|
{
|
|
NumHandles = 5; // MAX_HANDLES;
|
|
|
|
Status = RtmGetEnumNextHops(RtmRegHandle,
|
|
EnumHandle,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
// Check(Status, 11);
|
|
|
|
for (i = 0; i < NumHandles; i++)
|
|
{
|
|
Check(RtmGetNextHopInfo(RtmRegHandle,
|
|
Handles[i],
|
|
&NextHopInfo), 12);
|
|
|
|
// Print("Deleting NextHop %lu: %p\n", j++, Handles[i]);
|
|
// Print("State: %04x, Interface: %d\n",
|
|
// NextHopInfo.State,
|
|
// NextHopInfo.InterfaceIndex);
|
|
|
|
Check(RtmDeleteNextHop(RtmRegHandle,
|
|
Handles[i],
|
|
NULL), 14);
|
|
|
|
Check(RtmReleaseNextHopInfo(RtmRegHandle,
|
|
&NextHopInfo), 13);
|
|
}
|
|
|
|
Check(RtmReleaseNextHops(RtmRegHandle,
|
|
NumHandles,
|
|
Handles), 15);
|
|
}
|
|
while (SUCCESS(Status));
|
|
|
|
//
|
|
// Just try a enum query after ERROR_NO_MORE_ITEMS is retd
|
|
//
|
|
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status = RtmGetEnumNextHops(RtmRegHandle,
|
|
EnumHandle,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
Assert((NumHandles == 0) && (Status == ERROR_NO_MORE_ITEMS));
|
|
|
|
Status = RtmDeleteEnumHandle(RtmRegHandle,
|
|
EnumHandle);
|
|
|
|
Check(Status, 16);
|
|
|
|
//
|
|
// Make sure that the next hop table is empty now
|
|
//
|
|
|
|
Status = RtmCreateNextHopEnum(RtmRegHandle,
|
|
0,
|
|
NULL,
|
|
&EnumHandle);
|
|
|
|
NumHandles = MAX_HANDLES;
|
|
|
|
Status = RtmGetEnumNextHops(RtmRegHandle,
|
|
EnumHandle,
|
|
&NumHandles,
|
|
Handles);
|
|
|
|
|
|
if ((Status != ERROR_NO_MORE_ITEMS) || (NumHandles != 0))
|
|
{
|
|
Check(Status, 11);
|
|
}
|
|
|
|
Status = RtmDeleteEnumHandle(RtmRegHandle,
|
|
EnumHandle);
|
|
|
|
Check(Status, 16);
|
|
|
|
Sleep(1000);
|
|
|
|
//
|
|
// -41- Deregister all existing change notif registrations
|
|
//
|
|
|
|
Status = RtmDeregisterFromChangeNotification(RtmRegHandle,
|
|
NotifyHandle);
|
|
Check(Status, 41);
|
|
|
|
Print("Change Notification Deregistration Successful\n\n");
|
|
|
|
//
|
|
// -02- De-register with the RTM before exiting
|
|
//
|
|
|
|
Status = RtmDeregisterEntity(RtmRegHandle);
|
|
|
|
Check(Status, 2);
|
|
|
|
#if _DBG_
|
|
Status = RtmDeregisterEntity(RtmRegHandle);
|
|
|
|
Check(Status, 2);
|
|
#endif
|
|
|
|
Print("\n--------------------------------------------------------\n");
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
|
|
DWORD
|
|
EntityEventCallback (
|
|
IN RTM_ENTITY_HANDLE RtmRegHandle,
|
|
IN RTM_EVENT_TYPE EventType,
|
|
IN PVOID Context1,
|
|
IN PVOID Context2
|
|
)
|
|
{
|
|
RTM_ENTITY_HANDLE EntityHandle;
|
|
PENTITY_CHARS EntityChars;
|
|
PRTM_ENTITY_INFO EntityInfo;
|
|
RTM_NOTIFY_HANDLE NotifyHandle;
|
|
PRTM_DEST_INFO DestInfos;
|
|
UINT NumDests;
|
|
UINT NumViews;
|
|
RTM_ROUTE_HANDLE RouteHandle;
|
|
PRTM_ROUTE_INFO RoutePointer;
|
|
DWORD ChangeFlags;
|
|
DWORD Status;
|
|
|
|
Print("\nEvent callback called for %p :", RtmRegHandle);
|
|
|
|
Status = NO_ERROR;
|
|
|
|
Print("\n\tEntity Event = ");
|
|
|
|
switch (EventType)
|
|
{
|
|
case RTM_ENTITY_REGISTERED:
|
|
|
|
EntityHandle = (RTM_ENTITY_HANDLE) Context1;
|
|
EntityInfo = (PRTM_ENTITY_INFO) Context2;
|
|
|
|
Print("Registration\n\tEntity Handle = %p\n\tEntity IdInst = %p\n\n",
|
|
EntityHandle,
|
|
EntityInfo->EntityId);
|
|
|
|
/*
|
|
//
|
|
// -45- Make a copy of the handle of new entity
|
|
//
|
|
|
|
Status = RtmReferenceHandles(RtmRegHandle,
|
|
1,
|
|
&EntityHandle);
|
|
|
|
Check(Status, 45);
|
|
*/
|
|
break;
|
|
|
|
case RTM_ENTITY_DEREGISTERED:
|
|
|
|
EntityHandle = (RTM_ENTITY_HANDLE) Context1;
|
|
EntityInfo = (PRTM_ENTITY_INFO) Context2;
|
|
|
|
Print("Deregistration\n\tEntity Handle = %p\n\tEntity IdInst = %p\n\n",
|
|
EntityHandle,
|
|
EntityInfo->EntityId);
|
|
|
|
/*
|
|
//
|
|
// -44- Release the handle we have on the entity
|
|
//
|
|
|
|
Status = RtmReleaseEntities(RtmRegHandle,
|
|
1,
|
|
&EntityHandle);
|
|
|
|
Check(Status, 44);
|
|
*/
|
|
break;
|
|
|
|
case RTM_CHANGE_NOTIFICATION:
|
|
|
|
NotifyHandle = (RTM_NOTIFY_HANDLE) Context1;
|
|
|
|
EntityChars = (PENTITY_CHARS) Context2;
|
|
|
|
Print("Changes Available\n\tNotify Handle = %p\n\tEntity Ch = %p\n\n",
|
|
NotifyHandle,
|
|
EntityChars);
|
|
|
|
//
|
|
// Count the number of view for later use
|
|
//
|
|
|
|
NumViews = EntityChars->RegnProfile.NumberOfViews;
|
|
|
|
//
|
|
// -43- Get all changes to destinations
|
|
//
|
|
|
|
DestInfos = ALLOC_RTM_DEST_INFO(NumViews, MAX_HANDLES);
|
|
|
|
do
|
|
{
|
|
NumDests = MAX_HANDLES;
|
|
|
|
Status = RtmGetChangedDests(RtmRegHandle,
|
|
NotifyHandle,
|
|
&NumDests,
|
|
DestInfos);
|
|
// Check(Status, 42);
|
|
|
|
printf("Status = %lu\n", Status);
|
|
|
|
Print("Num Changed Dests = %d\n", NumDests);
|
|
|
|
EntityChars->TotalChangedDests += NumDests;
|
|
|
|
Status = RtmReleaseChangedDests(RtmRegHandle,
|
|
NotifyHandle,
|
|
NumDests,
|
|
DestInfos);
|
|
Check(Status, 43);
|
|
}
|
|
while (NumDests > 0);
|
|
|
|
Print("Total Changed Dests = %d\n", EntityChars->TotalChangedDests);
|
|
|
|
break;
|
|
|
|
case RTM_ROUTE_EXPIRED:
|
|
|
|
RouteHandle = (RTM_ROUTE_HANDLE) Context1;
|
|
|
|
RoutePointer = (PRTM_ROUTE_INFO) Context2;
|
|
|
|
Print("Route Aged Out\n\tRoute Handle = %p\n\tRoute Pointer = %p\n\n",
|
|
RouteHandle,
|
|
RoutePointer);
|
|
|
|
// Refresh the route by doing dummy update in place
|
|
|
|
Status = RtmLockRoute(RtmRegHandle,
|
|
RouteHandle,
|
|
TRUE,
|
|
TRUE,
|
|
NULL);
|
|
|
|
// Check(Status, 46);
|
|
|
|
if (Status == NO_ERROR)
|
|
{
|
|
Status = RtmUpdateAndUnlockRoute(RtmRegHandle,
|
|
RouteHandle,
|
|
INFINITE,
|
|
NULL,
|
|
0,
|
|
NULL,
|
|
&ChangeFlags);
|
|
|
|
Check(Status, 47);
|
|
|
|
if (!SUCCESS(Status))
|
|
{
|
|
Status = RtmLockRoute(RtmRegHandle,
|
|
RouteHandle,
|
|
TRUE,
|
|
FALSE,
|
|
NULL);
|
|
|
|
Check(Status, 46);
|
|
}
|
|
}
|
|
|
|
Check(RtmReleaseRoutes(RtmRegHandle,
|
|
1,
|
|
&RouteHandle), 27);
|
|
|
|
break;
|
|
|
|
default:
|
|
Status = ERROR_NOT_SUPPORTED;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
VOID
|
|
EntityExportMethod (
|
|
IN RTM_ENTITY_HANDLE CallerHandle,
|
|
IN RTM_ENTITY_HANDLE CalleeHandle,
|
|
IN RTM_ENTITY_METHOD_INPUT *Input,
|
|
OUT RTM_ENTITY_METHOD_OUTPUT *Output
|
|
)
|
|
{
|
|
Print("Export Function %2d called on %p: Caller = %p\n\n",
|
|
Input->MethodType,
|
|
CalleeHandle,
|
|
CallerHandle);
|
|
|
|
Output->MethodStatus = NO_ERROR;
|
|
|
|
return;
|
|
}
|
|
|
|
// Default warnings for unreferenced params and local variables
|
|
#pragma warning(default: 4100)
|
|
#pragma warning(default: 4101)
|
|
|
|
#if _DBG_
|
|
|
|
00 RtmReadAddrFamilyConfig
|
|
00 RtmWriteAddrFamilyConfig
|
|
00 RtmWriteInstanceConfig
|
|
|
|
01 RtmRegisterEntity
|
|
02 RtmDeregisterEntity
|
|
03 RtmGetRegdEntities
|
|
04 RtmGetEntityMethods
|
|
05 RtmInvokeMethod
|
|
06 RtmBlockMethods
|
|
07 RtmAddNextHop
|
|
08 RtmFindNextHop
|
|
09 RtmLockNextHop
|
|
10 RtmCreateNextHopEnum
|
|
11 RtmGetEnumNextHops
|
|
12 RtmGetNextHopInfo
|
|
13 RtmReleaseNextHopInfo
|
|
14 RtmDelNextHop
|
|
15 RtmReleaseNextHops
|
|
16 RtmDeleteEnumHandle
|
|
17 RtmAddRouteToDest
|
|
18 RtmGetExactMatchDestination
|
|
19 RtmGetMostSpecificDestination
|
|
20 RtmGetLessSpecificDestination
|
|
21 RtmGetDestInfo
|
|
22 RtmReleaseDestInfo
|
|
23 RtmCreateDestEnum
|
|
24 RtmGetEnumDests
|
|
25 RtmCreateRouteEnum
|
|
26 RtmGetEnumRoutes
|
|
27 RtmReleaseRoutes
|
|
28 RtmIsBestRoute
|
|
29 RtmGetExactMatchRoute
|
|
30 RtmGetRouteInfo
|
|
31 RtmReleaseRouteInfo
|
|
32 RtmDelRoute
|
|
33 RtmHoldDestination
|
|
34 RtmReleaseDests
|
|
35 RtmCreateRouteList
|
|
36 RtmDeleteRouteList
|
|
37 RtmInsertInRouteList
|
|
38 RtmCreateRouteListEnum
|
|
39 RtmGetListEnumRoutes
|
|
40 RtmRegisterForChangeNotification
|
|
41 RtmDeregisterFromChangeNotification
|
|
42 RtmGetChangedDests
|
|
43 RtmReleaseChangedDests
|
|
44 RtmReleaseEntities
|
|
45 RtmReferenceHandles
|
|
46 RtmLockRoute
|
|
47 RtmUpdateAndUnlockRoute
|
|
48 RtmMarkDestForChangeNotification
|
|
49 RtmIsMarkedForChangeNotification
|
|
|
|
50 RtmCreateRouteTable
|
|
51 RtmDeleteRouteTable
|
|
52 RtmRegisterClient
|
|
53 RtmDeregisterClient
|
|
54 RtmAddRoute
|
|
55 RtmDeleteRoute
|
|
56 RtmCreateEnumerationHandle
|
|
57 RtmCloseEnumerationHandle
|
|
58 RtmEnumerateGetNextRoute
|
|
59 RtmGetFirstRoute
|
|
60 RtmGetNextRoute
|
|
61 RtmBlockDeleteRoutes
|
|
62 RtmBlockConvertRoutesToStatic
|
|
63 RtmGetNetworkCount
|
|
64 RtmIsRoute
|
|
65 RtmLookupIPDestination
|
|
66 RtmBlockSetRouteEnable
|
|
|
|
#endif
|