summaryrefslogblamecommitdiffstats
path: root/private/ntos/tdi/tcpip/ip/info.c
blob: a866c0cba3539b8572e151cbbdee0cd437276c6e (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
































































































































































































































































































































































































































































































































































































































                                                                                                    
/********************************************************************/
/**                     Microsoft LAN Manager                      **/
/**               Copyright(c) Microsoft Corp., 1990-1993          **/
/********************************************************************/
/* :ts=4 */

//** INFO.C - Routines for querying and setting IP information.
//
//  This file contains the code for dealing with Query/Set information
//  calls.
//

#include    "oscfg.h"
#include    "ndis.h"
#include    "cxport.h"
#include    "ip.h"
#include    "ipdef.h"
#include    "info.h"
#include    "tdi.h"
#include    "tdiinfo.h"
#include    "llinfo.h"
#include    "tdistat.h"
#include	"iproute.h"
#include	"igmp.h"
#include	"ipfilter.h"
#include	"iprtdef.h"
	
extern Interface		*IFList;
extern NetTableEntry	*NetTableList;
extern uint				LoopIndex;			// Index of loopback I/F.
extern uint				DefaultTTL;
extern uint				NumIF;
extern uint				NumNTE;
extern RouteInterface	DummyInterface;		// Dummy interface.

EXTERNAL_LOCK(RouteTableLock)

extern uint             RTEReadNext(void *Context, void *Buffer);
extern uint             RTValidateContext(void *Context, uint *Valid);
extern uint             RTReadNext(void *Context, void *Buffer);

uint	IPInstance;
uint	ICMPInstance;

//* CopyToNdis - Copy a flat buffer to an NDIS_BUFFER chain.
//
//  A utility function to copy a flat buffer to an NDIS buffer chain. We
//  assume that the NDIS_BUFFER chain is big enough to hold the copy amount;
//  in a debug build we'll  debugcheck if this isn't true. We return a pointer
//  to the buffer where we stopped copying, and an offset into that buffer.
//  This is useful for copying in pieces into the chain.
//
//  Input:  DestBuf     - Destination NDIS_BUFFER chain.
//          SrcBuf      - Src flat buffer.
//          Size        - Size in bytes to copy.
//          StartOffset - Pointer to start of offset into first buffer in
//                          chain. Filled in on return with the offset to
//                          copy into next.
//
//  Returns: Pointer to next buffer in chain to copy into.
//
PNDIS_BUFFER
CopyToNdis(PNDIS_BUFFER DestBuf, uchar *SrcBuf, uint Size,
    uint *StartOffset)
{
    uint        CopySize;
    uchar       *DestPtr;
    uint        DestSize;
    uint        Offset = *StartOffset;
    uchar      *VirtualAddress;
    uint        Length;

    CTEAssert(DestBuf != NULL);
    CTEAssert(SrcBuf != NULL);

    NdisQueryBuffer(DestBuf, &VirtualAddress, &Length);
    CTEAssert(Length >= Offset);
    DestPtr = VirtualAddress + Offset;
    DestSize = Length - Offset;

    for (;;) {
        CopySize = MIN(Size, DestSize);
        CTEMemCopy(DestPtr, SrcBuf, CopySize);

        DestPtr += CopySize;
        SrcBuf += CopySize;

        if ((Size -= CopySize) == 0)
            break;

        if ((DestSize -= CopySize) == 0) {
            DestBuf = NDIS_BUFFER_LINKAGE(DestBuf);
            CTEAssert(DestBuf != NULL);
            NdisQueryBuffer(DestBuf, &VirtualAddress, &Length);
            DestPtr = VirtualAddress;
            DestSize = Length;
        }
    }

    *StartOffset = DestPtr - VirtualAddress;

    return DestBuf;

}


//* IPQueryInfo - IP query information handler.
//
//  Called by the upper layer when it wants to query information about us.
//  We take in an ID, a buffer and length, and a context value, and return
//  whatever information we can.
//
//  Input:  ID          - Pointer to ID structure.
//          Buffer      - Pointer to buffer chain.
//          Size        - Pointer to size in bytes of buffer. On return, filled
//                          in with bytes read.
//          Context     - Pointer to context value.
//
//  Returns: TDI_STATUS of attempt to read information.
//
long
IPQueryInfo(TDIObjectID *ID, PNDIS_BUFFER Buffer, uint *Size, void *Context)
{
    uint            BufferSize = *Size;
    uint            BytesCopied = 0;
    uint            Offset = 0;
    TDI_STATUS      Status;
    ushort          NTEContext;
    uchar           InfoBuff[sizeof(IPRouteEntry)];
    IPAddrEntry     *AddrEntry;
    NetTableEntry   *CurrentNTE;
    uint            Valid, DataLeft;
    CTELockHandle   Handle;
    Interface       *LowerIF;
    IPInterfaceInfo *IIIPtr;
    uint            LLID = 0;
	uint			Entity;
	uint			Instance;
    IPAddr          IFAddr;

	
	Entity = ID->toi_entity.tei_entity;
	Instance = ID->toi_entity.tei_instance;

	// See if it's something we might handle.

	if (Entity != CL_NL_ENTITY && Entity != ER_ENTITY) {
		// We need to pass this down to the lower layer. Loop through until
		// we find one that takes it. If noone does, error out.
		for (LowerIF = IFList; LowerIF != NULL; LowerIF = LowerIF->if_next) {
			Status = (*LowerIF->if_qinfo)(LowerIF->if_lcontext, ID, Buffer,
				Size, Context);
			if (Status != TDI_INVALID_REQUEST)
				return Status;
		}
		// If we get here, noone took it. Return an error.
		return TDI_INVALID_REQUEST;

	}
	
	if ((Entity == CL_NL_ENTITY && Instance != IPInstance) ||
		Instance != ICMPInstance)
		return TDI_INVALID_REQUEST;

	// The request is for us.
	*Size = 0;					// Set to 0 in case of an error.

	// Make sure it's something we support.
	if (ID->toi_class == INFO_CLASS_GENERIC) {
		if (ID->toi_type == INFO_TYPE_PROVIDER && ID->toi_id == ENTITY_TYPE_ID) {
			// He's trying to see what type we are.
			if (BufferSize >= sizeof(uint)) {
				*(uint *)&InfoBuff[0] = (Entity == CL_NL_ENTITY) ? CL_NL_IP :
					ER_ICMP;
				(void)CopyToNdis(Buffer, InfoBuff, sizeof(uint), &Offset);
				return TDI_SUCCESS;
			} else
				return TDI_BUFFER_TOO_SMALL;
		}
		return TDI_INVALID_PARAMETER;	
	} else
		if (ID->toi_class != INFO_CLASS_PROTOCOL ||
			ID->toi_type != INFO_TYPE_PROVIDER)
			return TDI_INVALID_PARAMETER;

	// If it's ICMP, just copy the statistics.
	if (Entity == ER_ENTITY) {

		// It is ICMP. Make sure the ID is valid.
		if (ID->toi_id != ICMP_MIB_STATS_ID)
			return TDI_INVALID_PARAMETER;

        // He wants the stats. Copy what we can.
        if (BufferSize < sizeof(ICMPSNMPInfo))
            return TDI_BUFFER_TOO_SMALL;

        Buffer = CopyToNdis(Buffer, (uchar *)&ICMPInStats, sizeof(ICMPStats),
            &Offset);
        (void)CopyToNdis(Buffer, (uchar *)&ICMPOutStats, sizeof(ICMPStats),
            &Offset);
		
		*Size = sizeof(ICMPSNMPInfo);
        return TDI_SUCCESS;
    }

    // It's not ICMP. We need to figure out what it is, and take the
    // appropriate action.

	switch (ID->toi_id) {
		
		case IP_MIB_STATS_ID:
			if (BufferSize < sizeof(IPSNMPInfo))
				return TDI_BUFFER_TOO_SMALL;
			IPSInfo.ipsi_numif = NumIF;
			IPSInfo.ipsi_numaddr = NumNTE;
            IPSInfo.ipsi_defaultttl = DefaultTTL;
            IPSInfo.ipsi_forwarding = ForwardPackets ? IP_FORWARDING :
                                                       IP_NOT_FORWARDING;
			CopyToNdis(Buffer, (uchar *)&IPSInfo, sizeof(IPSNMPInfo), &Offset);
			BytesCopied = sizeof(IPSNMPInfo);
			Status = TDI_SUCCESS;
			break;
		case IP_MIB_ADDRTABLE_ENTRY_ID:
			// He wants to read the address table. Figure out where we're
			// starting from, and if it's valid begin copying from there.
			NTEContext = *(ushort *)Context;
			CurrentNTE = NetTableList;
			
			if (NTEContext != 0) {
				for (;CurrentNTE != NULL; CurrentNTE = CurrentNTE->nte_next)
					if (CurrentNTE->nte_context == NTEContext)
						break;
				if (CurrentNTE == NULL)
					return TDI_INVALID_PARAMETER;
			}

			AddrEntry = (IPAddrEntry *)InfoBuff;
			for (; CurrentNTE != NULL; CurrentNTE = CurrentNTE->nte_next) {
				if ((int)(BufferSize - BytesCopied) >= (int)sizeof(IPAddrEntry)) {
					// We have room to copy it. Build the entry, and copy
					// it.
					if (CurrentNTE->nte_flags & NTE_ACTIVE) {
						if (CurrentNTE->nte_flags & NTE_VALID) {
							AddrEntry->iae_addr = CurrentNTE->nte_addr;
							AddrEntry->iae_mask = CurrentNTE->nte_mask;
						} else {
							AddrEntry->iae_addr = NULL_IP_ADDR;
							AddrEntry->iae_mask = NULL_IP_ADDR;
						}

						AddrEntry->iae_index = CurrentNTE->nte_if->if_index;
						AddrEntry->iae_bcastaddr =
							*(int *)&(CurrentNTE->nte_if->if_bcast) & 1;
						AddrEntry->iae_reasmsize = 0xffff;
						AddrEntry->iae_context = CurrentNTE->nte_context;
						Buffer = CopyToNdis(Buffer, (uchar *)AddrEntry,
							sizeof(IPAddrEntry), &Offset);
						BytesCopied += sizeof(IPAddrEntry);
					}
				} else
					break;
			}
			
			if (CurrentNTE == NULL)
				Status = TDI_SUCCESS;
			else {
				Status = TDI_BUFFER_OVERFLOW;
				**(ushort **)&Context = CurrentNTE->nte_context;
			}
				
			break;
		case IP_MIB_RTTABLE_ENTRY_ID:
			// Make sure we have a valid context.
			CTEGetLock(&RouteTableLock, &Handle);
			DataLeft = RTValidateContext(Context, &Valid);

            // If the context is valid, we'll continue trying to read.
            if (!Valid) {
                CTEFreeLock(&RouteTableLock, Handle);
                return TDI_INVALID_PARAMETER;
            }

            while (DataLeft)  {
                // The invariant here is that there is data in the table to
                // read. We may or may not have room for it. So DataLeft
                // is TRUE, and BufferSize - BytesCopied is the room left
                // in the buffer.
                if ((int)(BufferSize - BytesCopied) >= (int)sizeof(IPRouteEntry)) {
                    DataLeft = RTReadNext(Context, InfoBuff);
                    BytesCopied += sizeof(IPRouteEntry);
                    Buffer = CopyToNdis(Buffer, InfoBuff, sizeof(IPRouteEntry),
                        &Offset);
                } else
                    break;

            }

			CTEFreeLock(&RouteTableLock, Handle);
			Status = (!DataLeft ? TDI_SUCCESS : TDI_BUFFER_OVERFLOW);
			break;
        case IP_INTFC_INFO_ID:

            IFAddr = *(IPAddr *)Context;
            // Loop through the NTE table, looking for a match.
            for (CurrentNTE = NetTableList; CurrentNTE != NULL; CurrentNTE = CurrentNTE->nte_next) {
                if ((CurrentNTE->nte_flags & NTE_VALID) &&
                    IP_ADDR_EQUAL(CurrentNTE->nte_addr, IFAddr))
                    break;
            }
            if (CurrentNTE == NULL) {
                Status = TDI_INVALID_PARAMETER;
                break;
            }

            if (BufferSize < offsetof(IPInterfaceInfo, iii_addr)) {
                Status = TDI_BUFFER_TOO_SMALL;
                break;
            }

            // We have the NTE. Get the interface, fill in a structure,
            // and we're done.
            LowerIF = CurrentNTE->nte_if;
            IIIPtr = (IPInterfaceInfo *)InfoBuff;
            IIIPtr->iii_flags = LowerIF->if_flags & IF_FLAGS_P2P ?
               IP_INTFC_FLAG_P2P : 0;
            IIIPtr->iii_mtu = LowerIF->if_mtu;
            IIIPtr->iii_speed = LowerIF->if_speed;
            IIIPtr->iii_addrlength = LowerIF->if_addrlen;
            BytesCopied = offsetof(IPInterfaceInfo, iii_addr);
            if (BufferSize >= (offsetof(IPInterfaceInfo, iii_addr) +
                LowerIF->if_addrlen)) {
                Status = TDI_SUCCESS;
                Buffer = CopyToNdis(Buffer, InfoBuff,
                    offsetof(IPInterfaceInfo, iii_addr), &Offset);
                CopyToNdis(Buffer, LowerIF->if_addr, LowerIF->if_addrlen,
                    &Offset);
                BytesCopied += LowerIF->if_addrlen;
             } else {
                Status = TDI_BUFFER_TOO_SMALL;
             }
             break;

		default:
			return TDI_INVALID_PARAMETER;
			break;
	}

    *Size = BytesCopied;
    return Status;
}

//*	IPSetInfo - IP set information handler.
//
//	Called by the upper layer when it wants to set an object, which could
//	be a route table entry, an ARP table entry, or something else.
//
//	Input:	ID			- Pointer to ID structure.
//			Buffer		- Pointer to buffer containing element to set..
//			Size		- Pointer to size in bytes of buffer.
//
//	Returns: TDI_STATUS of attempt to read information.
//
long
IPSetInfo(TDIObjectID *ID, void *Buffer, uint Size)
{
	uint			Entity;
	uint			Instance;
	Interface		*LowerIF;
	Interface		*OutIF;
	uint			MTU;
	IPRouteEntry	*IRE;
	NetTableEntry	*OutNTE, *LocalNTE;
	IP_STATUS		Status;
	IPAddr			FirstHop, Dest, NextHop;

	Entity = ID->toi_entity.tei_entity;
	Instance = ID->toi_entity.tei_instance;

	// If it's not for us, pass it down.
	if (Entity != CL_NL_ENTITY) {
		// We need to pass this down to the lower layer. Loop through until
		// we find one that takes it. If noone does, error out.
		for (LowerIF = IFList; LowerIF != NULL; LowerIF = LowerIF->if_next) {
			Status = (*LowerIF->if_setinfo)(LowerIF->if_lcontext, ID, Buffer,
				Size);
			if (Status != TDI_INVALID_REQUEST)
				return Status;
		}
		// If we get here, noone took it. Return an error.
		return TDI_INVALID_REQUEST;
	}
	
	if (Instance != IPInstance)
		return TDI_INVALID_REQUEST;

	// We're identified as the entity. Make sure the ID is correct.
	if (ID->toi_id == IP_MIB_RTTABLE_ENTRY_ID) {
		NetTableEntry			*TempNTE;
		
		// This is an attempt to set a route table entry. Make sure the
		// size if correct.
		if (Size < sizeof(IPRouteEntry))
			return TDI_INVALID_PARAMETER;

		IRE = (IPRouteEntry *)Buffer;

		OutNTE = NULL;
		LocalNTE = NULL;

		Dest = IRE->ire_dest;
		NextHop = IRE->ire_nexthop;

		// Make sure that the nexthop is sensible. We don't allow nexthops
		// to be broadcast or invalid or loopback addresses.
		if (IP_ADDR_EQUAL(NextHop, NULL_IP_ADDR) || IP_LOOPBACK(NextHop) ||
			CLASSD_ADDR(NextHop) || CLASSE_ADDR(NextHop))
			return TDI_INVALID_PARAMETER;

		// Also make sure that the destination we're routing to is sensible.
		// Don't allow routes to be added to Class D or E or loopback
		// addresses.
		if (IP_LOOPBACK(Dest) || CLASSD_ADDR(Dest) || CLASSE_ADDR(Dest))
			return TDI_INVALID_PARAMETER;

		if (IRE->ire_index == LoopIndex)
			return TDI_INVALID_PARAMETER;

		if (IRE->ire_index  != INVALID_IF_INDEX) {

			// First thing to do is to find the outgoing NTE for specified
			// interface, and also make sure that it matches the destination
			// if the destination is one of my addresses.
			for (TempNTE = NetTableList; TempNTE != NULL;
				TempNTE = TempNTE->nte_next) {
				if (OutNTE == NULL && IRE->ire_index == TempNTE->nte_if->if_index)
					OutNTE = TempNTE;
				if (IP_ADDR_EQUAL(NextHop, TempNTE->nte_addr) &&
					(TempNTE->nte_flags & NTE_VALID))
					LocalNTE = TempNTE;
	
				// Don't let a route be set through a broadcast address.
				if (IsBCastOnNTE(NextHop, TempNTE) != DEST_LOCAL)
					return TDI_INVALID_PARAMETER;
				
				// Don't let a route to a broadcast address be added or deleted.
				if (IsBCastOnNTE(Dest, TempNTE) != DEST_LOCAL)
					return TDI_INVALID_PARAMETER;
			}
	
			// At this point OutNTE points to the outgoing NTE, and LocalNTE
			// points to the NTE for the local address, if this is a direct route.
			// Make sure they point to the same interface, and that the type is
			// reasonable.
			if (OutNTE == NULL)
				return TDI_INVALID_PARAMETER;
	
			if (LocalNTE != NULL) {
				// He's routing straight out a local interface. The interface for
				// the local address must match the interface passed in, and the
				// type must be DIRECT (if we're adding) or INVALID (if we're
				// deleting).
				if (LocalNTE->nte_if->if_index != IRE->ire_index)
					return TDI_INVALID_PARAMETER;
	
				if (IRE->ire_type != IRE_TYPE_DIRECT &&
					IRE->ire_type != IRE_TYPE_INVALID)
					return TDI_INVALID_PARAMETER;
				
				OutNTE = LocalNTE;
			}
	
	
			// Figure out what the first hop should be. If he's routing straight
			// through a local interface, or the next hop is equal to the
			// destination, then the first hop is IPADDR_LOCAL. Otherwise it's the
			// address of the gateway.
			if (LocalNTE != NULL)
				FirstHop = IPADDR_LOCAL;
			else
				if (IP_ADDR_EQUAL(Dest, NextHop))
					FirstHop = IPADDR_LOCAL;
				else
					FirstHop = NextHop;

		    MTU = OutNTE->nte_mss;
			OutIF = OutNTE->nte_if;

		} else {
			OutIF = (Interface *)&DummyInterface;
			MTU = DummyInterface.ri_if.if_mtu - sizeof(IPHeader);
			if (IP_ADDR_EQUAL(Dest, NextHop))
				FirstHop = IPADDR_LOCAL;
			else
				FirstHop = NextHop;
		}

		// We've done the validation. See if he's adding or deleting a route.
		if (IRE->ire_type != IRE_TYPE_INVALID) {
			// He's adding a route.
			Status = AddRoute(Dest, IRE->ire_mask, FirstHop, OutIF,
				MTU, IRE->ire_metric1, IRE->ire_proto,
				ATYPE_OVERRIDE, IRE->ire_context);

		} else {
			// He's deleting a route.
			Status = DeleteRoute(Dest, IRE->ire_mask, FirstHop, OutIF);
		}
		
		if (Status == IP_SUCCESS)
			return TDI_SUCCESS;
		else
			if (Status == IP_NO_RESOURCES)
				return TDI_NO_RESOURCES;
			else
				return TDI_INVALID_PARAMETER;
					
	} else {
		if (ID->toi_id == IP_MIB_STATS_ID) {
			IPSNMPInfo		*Info = (IPSNMPInfo *)Buffer;
			
			// Setting information about TTL and/or routing.
			if (Info->ipsi_defaultttl > 255 || (!RouterConfigured &&
				Info->ipsi_forwarding == IP_FORWARDING)) {
				return TDI_INVALID_PARAMETER;
			}
			
			DefaultTTL = Info->ipsi_defaultttl;
			ForwardPackets = Info->ipsi_forwarding == IP_FORWARDING ? TRUE :
				FALSE;
			
			return TDI_SUCCESS;
		}
		return TDI_INVALID_PARAMETER;
	}
	
}
#ifndef CHICAGO
#pragma BEGIN_INIT
#endif

//*	IPGetEList - Get the entity list.
//
//	Called at init time to get an entity list. We fill our stuff in, and
//	then call the interfaces below us to allow them to do the same.
//
//	Input:	EntityList		- Pointer to entity list to be filled in.
//			Count			- Pointer to number of entries in the list.
//
//	Returns Status of attempt to get the info.
//
long
IPGetEList(TDIEntityID *EList, uint *Count)
{
	uint		ECount;
	uint		MyIPBase;
	uint		MyERBase;
	int			Status;
	uint		i;
	Interface	*LowerIF;
	TDIEntityID *EntityList;

	ECount = *Count;
	EntityList = EList;

	// Walk down the list, looking for existing CL_NL or ER entities, and
	// adjust our base instance accordingly.

	MyIPBase = 0;
	MyERBase = 0;
	for (i = 0; i < ECount; i++, EntityList++) {
		if (EntityList->tei_entity == CL_NL_ENTITY)
			MyIPBase = MAX(MyIPBase, EntityList->tei_instance + 1);
		else
			if (EntityList->tei_entity == ER_ENTITY)
				MyERBase = MAX(MyERBase, EntityList->tei_instance + 1);
	}
	
	// At this point we've figure out our base instance. Save for later use.
	IPInstance = MyIPBase;
	ICMPInstance = MyERBase;

	// EntityList points to the start of where we want to begin filling in.
	// Make sure we have enough room. We need one for the ICMP instance,
	// and one for the CL_NL instance.

	if ((ECount + 2) > MAX_TDI_ENTITIES)
		return TDI_REQ_ABORTED;

	// Now fill it in.
	EntityList->tei_entity = CL_NL_ENTITY;
	EntityList->tei_instance = IPInstance;
	EntityList++;
	EntityList->tei_entity = ER_ENTITY;
	EntityList->tei_instance = ICMPInstance;
	*Count += 2;

	// Loop through the interfaces, querying each of them.
	for (LowerIF = IFList; LowerIF != NULL; LowerIF = LowerIF->if_next) {
		Status = (*LowerIF->if_getelist)(LowerIF->if_lcontext, EList, Count);
		if (!Status)
			return TDI_BUFFER_TOO_SMALL;
	}
	
	return TDI_SUCCESS;
}
			
#ifndef CHICAGO
#pragma END_INIT
#endif