inetmib1: Support the MIB2 TCP statistics.
[wine] / dlls / inetmib1 / main.c
1 /*
2  * Copyright 2008 Juan Lang
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  */
18
19 #include "config.h"
20 #include <assert.h>
21 #include <stdarg.h>
22 #include <limits.h>
23 #include "windef.h"
24 #include "winbase.h"
25 #include "snmp.h"
26 #include "iphlpapi.h"
27 #include "wine/debug.h"
28
29 WINE_DEFAULT_DEBUG_CHANNEL(inetmib1);
30
31 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
32 {
33         TRACE("(0x%p, %d, %p)\n", hinstDLL, fdwReason, lpvReserved);
34
35         switch (fdwReason)
36         {
37                 case DLL_WINE_PREATTACH:
38                         return FALSE;    /* prefer native version */
39                 case DLL_PROCESS_ATTACH:
40                         DisableThreadLibraryCalls(hinstDLL);
41                         break;
42                 case DLL_PROCESS_DETACH:
43                         break;
44                 default:
45                         break;
46         }
47
48         return TRUE;
49 }
50
51 /**
52  * Utility functions
53  */
54 static void copyInt(AsnAny *value, void *src)
55 {
56     value->asnType = ASN_INTEGER;
57     value->asnValue.number = *(DWORD *)src;
58 }
59
60 static void setStringValue(AsnAny *value, BYTE type, DWORD len, BYTE *str)
61 {
62     AsnAny strValue;
63
64     strValue.asnType = type;
65     strValue.asnValue.string.stream = str;
66     strValue.asnValue.string.length = len;
67     strValue.asnValue.string.dynamic = TRUE;
68     SnmpUtilAsnAnyCpy(value, &strValue);
69 }
70
71 static void copyLengthPrecededString(AsnAny *value, void *src)
72 {
73     DWORD len = *(DWORD *)src;
74
75     setStringValue(value, ASN_OCTETSTRING, len, (BYTE *)src + sizeof(DWORD));
76 }
77
78 typedef void (*copyValueFunc)(AsnAny *value, void *src);
79
80 struct structToAsnValue
81 {
82     size_t        offset;
83     copyValueFunc copy;
84 };
85
86 static AsnInteger32 mapStructEntryToValue(struct structToAsnValue *map,
87     UINT mapLen, void *record, UINT id, BYTE bPduType, SnmpVarBind *pVarBind)
88 {
89     /* OIDs are 1-based */
90     if (!id)
91         return SNMP_ERRORSTATUS_NOSUCHNAME;
92     --id;
93     if (id >= mapLen)
94         return SNMP_ERRORSTATUS_NOSUCHNAME;
95     if (!map[id].copy)
96         return SNMP_ERRORSTATUS_NOSUCHNAME;
97     map[id].copy(&pVarBind->value, (BYTE *)record + map[id].offset);
98     return SNMP_ERRORSTATUS_NOERROR;
99 }
100
101 static void copyIpAddr(AsnAny *value, void *src)
102 {
103     setStringValue(value, ASN_IPADDRESS, sizeof(DWORD), src);
104 }
105
106 static UINT mib2[] = { 1,3,6,1,2,1 };
107 static UINT mib2System[] = { 1,3,6,1,2,1,1 };
108
109 typedef BOOL (*varqueryfunc)(BYTE bPduType, SnmpVarBind *pVarBind,
110     AsnInteger32 *pErrorStatus);
111
112 struct mibImplementation
113 {
114     AsnObjectIdentifier name;
115     void              (*init)(void);
116     varqueryfunc        query;
117 };
118
119 static UINT mib2IfNumber[] = { 1,3,6,1,2,1,2,1 };
120 static PMIB_IFTABLE ifTable;
121
122 static void mib2IfNumberInit(void)
123 {
124     DWORD size = 0, ret = GetIfTable(NULL, &size, FALSE);
125
126     if (ret == ERROR_INSUFFICIENT_BUFFER)
127     {
128         ifTable = HeapAlloc(GetProcessHeap(), 0, size);
129         if (ifTable)
130             GetIfTable(ifTable, &size, FALSE);
131     }
132 }
133
134 static BOOL mib2IfNumberQuery(BYTE bPduType, SnmpVarBind *pVarBind,
135     AsnInteger32 *pErrorStatus)
136 {
137     AsnObjectIdentifier numberOid = DEFINE_OID(mib2IfNumber);
138
139     TRACE("(0x%02x, %s, %p)\n", bPduType, SnmpUtilOidToA(&pVarBind->name),
140         pErrorStatus);
141
142     switch (bPduType)
143     {
144     case SNMP_PDU_GET:
145     case SNMP_PDU_GETNEXT:
146         if ((bPduType == SNMP_PDU_GET &&
147             !SnmpUtilOidNCmp(&pVarBind->name, &numberOid, numberOid.idLength))
148             || SnmpUtilOidNCmp(&pVarBind->name, &numberOid, numberOid.idLength)
149             < 0)
150         {
151             DWORD numIfs = ifTable ? ifTable->dwNumEntries : 0;
152
153             copyInt(&pVarBind->value, &numIfs);
154             if (bPduType == SNMP_PDU_GETNEXT)
155                 SnmpUtilOidCpy(&pVarBind->name, &numberOid);
156             *pErrorStatus = SNMP_ERRORSTATUS_NOERROR;
157         }
158         else
159         {
160             *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
161             /* Caller deals with OID if bPduType == SNMP_PDU_GETNEXT, so don't
162              * need to set it here.
163              */
164         }
165         break;
166     case SNMP_PDU_SET:
167         *pErrorStatus = SNMP_ERRORSTATUS_READONLY;
168         break;
169     default:
170         FIXME("0x%02x: unsupported PDU type\n", bPduType);
171         *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
172     }
173     return TRUE;
174 }
175
176 static void copyOperStatus(AsnAny *value, void *src)
177 {
178     value->asnType = ASN_INTEGER;
179     /* The IPHlpApi definition of operational status differs from the MIB2 one,
180      * so map it to the MIB2 value.
181      */
182     switch (*(DWORD *)src)
183     {
184     case MIB_IF_OPER_STATUS_OPERATIONAL:
185         value->asnValue.number = MIB_IF_ADMIN_STATUS_UP;
186         break;
187     case MIB_IF_OPER_STATUS_CONNECTING:
188     case MIB_IF_OPER_STATUS_CONNECTED:
189         value->asnValue.number = MIB_IF_ADMIN_STATUS_TESTING;
190         break;
191     default:
192         value->asnValue.number = MIB_IF_ADMIN_STATUS_DOWN;
193     };
194 }
195
196 /* Given an OID and a base OID that it must begin with, finds the item and
197  * integer instance from the OID.  E.g., given an OID foo.1.2 and a base OID
198  * foo, returns item 1 and instance 2.
199  * If bPduType is not SNMP_PDU_GETNEXT and either the item or instance is
200  * missing, returns SNMP_ERRORSTATUS_NOSUCHNAME.
201  * If bPduType is SNMP_PDU_GETNEXT, returns the successor to the item and
202  * instance, or item 1, instance 1 if either is missing.
203  */
204 static AsnInteger32 getItemAndIntegerInstanceFromOid(AsnObjectIdentifier *oid,
205     AsnObjectIdentifier *base, BYTE bPduType, UINT *item, UINT *instance)
206 {
207     AsnInteger32 ret = SNMP_ERRORSTATUS_NOERROR;
208
209     switch (bPduType)
210     {
211     case SNMP_PDU_GETNEXT:
212         if (SnmpUtilOidNCmp(oid, base, base->idLength) < 0)
213         {
214             *item = 1;
215             *instance = 1;
216         }
217         else if (!SnmpUtilOidNCmp(oid, base, base->idLength))
218         {
219             if (oid->idLength == base->idLength ||
220                 oid->idLength == base->idLength + 1)
221             {
222                 /* Either the table or an item within the table is specified,
223                  * but the instance is not.  Get the first instance.
224                  */
225                 *instance = 1;
226                 if (oid->idLength == base->idLength + 1)
227                     *item = oid->ids[base->idLength];
228                 else
229                     *item = 1;
230             }
231             else
232             {
233                 *item = oid->ids[base->idLength];
234                 *instance = oid->ids[base->idLength + 1] + 1;
235             }
236         }
237         else
238             ret = SNMP_ERRORSTATUS_NOSUCHNAME;
239         break;
240     default:
241         if (!SnmpUtilOidNCmp(oid, base, base->idLength))
242         {
243             if (oid->idLength == base->idLength ||
244                 oid->idLength == base->idLength + 1)
245             {
246                 /* Either the table or an item within the table is specified,
247                  * but the instance is not.
248                  */
249                 ret = SNMP_ERRORSTATUS_NOSUCHNAME;
250             }
251             else
252             {
253                 *item = oid->ids[base->idLength];
254                 *instance = oid->ids[base->idLength + 1];
255             }
256         }
257         else
258             ret = SNMP_ERRORSTATUS_NOSUCHNAME;
259     }
260     return ret;
261 }
262
263 /* Given an OID and a base OID that it must begin with, finds the item from the
264  * OID.  E.g., given an OID foo.1 and a base OID foo, returns item 1.
265  * If bPduType is not SNMP_PDU_GETNEXT and the item is missing, returns
266  * SNMP_ERRORSTATUS_NOSUCHNAME.
267  * If bPduType is SNMP_PDU_GETNEXT, returns the successor to the item, or item
268  * 1 if the item is missing.
269  */
270 static AsnInteger32 getItemFromOid(AsnObjectIdentifier *oid,
271     AsnObjectIdentifier *base, BYTE bPduType, UINT *item)
272 {
273     AsnInteger32 ret = SNMP_ERRORSTATUS_NOERROR;
274
275     switch (bPduType)
276     {
277     case SNMP_PDU_GETNEXT:
278         if (SnmpUtilOidNCmp(oid, base, base->idLength) < 0)
279             *item = 1;
280         else if (!SnmpUtilOidNCmp(oid, base, base->idLength))
281         {
282             if (oid->idLength == base->idLength)
283             {
284                 /* The item is missing, assume the first item */
285                 *item = 1;
286             }
287             else
288                 *item = oid->ids[base->idLength] + 1;
289         }
290         else
291             ret = SNMP_ERRORSTATUS_NOSUCHNAME;
292         break;
293     default:
294         if (!SnmpUtilOidNCmp(oid, base, base->idLength))
295         {
296             if (oid->idLength == base->idLength)
297             {
298                 /* The item is missing */
299                 ret = SNMP_ERRORSTATUS_NOSUCHNAME;
300             }
301             else
302             {
303                 *item = oid->ids[base->idLength];
304                 if (!*item)
305                     ret = SNMP_ERRORSTATUS_NOSUCHNAME;
306             }
307         }
308         else
309             ret = SNMP_ERRORSTATUS_NOSUCHNAME;
310     }
311     return ret;
312 }
313
314 struct GenericTable
315 {
316     DWORD numEntries;
317     BYTE  entries[1];
318 };
319
320 /* Finds the index in table whose IP address (at offset addressOffset within the
321  * entry) matches that given the OID, which is assumed to have at least 4 IDs.
322  */
323 static UINT findOIDIPAddressInTable(AsnObjectIdentifier *oid,
324     struct GenericTable *table, size_t tableEntrySize, size_t addressOffset)
325 {
326     DWORD addr;
327     UINT i, index = 0;
328
329     /* Map the IDs to an IP address in little-endian order */
330     addr = (BYTE)oid->ids[3] << 24 | (BYTE)oid->ids[2] << 16 |
331         (BYTE)oid->ids[1] << 8 | (BYTE)oid->ids[0];
332     /* Find the item whose address matches */
333     for (i = 0; !index && i < table->numEntries; i++)
334     {
335         DWORD tableAddr =
336             *(DWORD *)(table->entries + i * tableEntrySize + addressOffset);
337
338         if (addr == tableAddr)
339             index = i + 1;
340     }
341     return index;
342 }
343
344 /* Given an OID and a base OID that it must begin with, finds the item and
345  * element of the table whose IP address matches the instance from the OID.
346  * E.g., given an OID foo.1.2.3.4.5 and a base OID foo, returns item 1 and the
347  * index of the entry in the table whose IP address is 2.3.4.5.
348  * If bPduType is not SNMP_PDU_GETNEXT and either the item or instance is
349  * missing, returns SNMP_ERRORSTATUS_NOSUCHNAME.
350  * If bPduType is SNMP_PDU_GETNEXT, returns the successor to the item and
351  * instance, or item 1, instance 1 if either is missing.
352  */
353 static AsnInteger32 getItemAndIpAddressInstanceFromOid(AsnObjectIdentifier *oid,
354     AsnObjectIdentifier *base, BYTE bPduType, struct GenericTable *table,
355     size_t tableEntrySize, size_t addressOffset, UINT *item, UINT *instance)
356 {
357     AsnInteger32 ret = SNMP_ERRORSTATUS_NOERROR;
358
359     if (!table)
360         return SNMP_ERRORSTATUS_NOSUCHNAME;
361
362     switch (bPduType)
363     {
364     case SNMP_PDU_GETNEXT:
365         if (SnmpUtilOidNCmp(oid, base, base->idLength) < 0)
366         {
367             /* Return the first item and instance from the table */
368             *item = 1;
369             *instance = 1;
370         }
371         else if (!SnmpUtilOidNCmp(oid, base, base->idLength) &&
372             oid->idLength < base->idLength + 5)
373         {
374             /* Either the table or an item is specified, but the instance is
375              * not.
376              */
377             *instance = 1;
378             if (oid->idLength >= base->idLength + 1)
379             {
380                 *item = oid->ids[base->idLength];
381                 if (!*item)
382                     *item = 1;
383             }
384             else
385                 *item = 1;
386         }
387         else if (!SnmpUtilOidNCmp(oid, base, base->idLength) &&
388             oid->idLength == base->idLength + 5)
389         {
390             *item = oid->ids[base->idLength];
391             if (!*item)
392             {
393                 *instance = 1;
394                 *item = 1;
395             }
396             else
397             {
398                 AsnObjectIdentifier ipOid = { 4, oid->ids + base->idLength + 1
399                     };
400
401                 *instance = findOIDIPAddressInTable(&ipOid, table,
402                     tableEntrySize, addressOffset) + 1;
403                 if (*instance > table->numEntries)
404                     ret = SNMP_ERRORSTATUS_NOSUCHNAME;
405             }
406         }
407         else
408             ret = SNMP_ERRORSTATUS_NOSUCHNAME;
409         break;
410     default:
411         if (!SnmpUtilOidNCmp(oid, base, base->idLength) &&
412             oid->idLength == base->idLength + 5)
413         {
414             *item = oid->ids[base->idLength];
415             if (!*item)
416                 ret = SNMP_ERRORSTATUS_NOSUCHNAME;
417             else
418             {
419                 AsnObjectIdentifier ipOid = { 4, oid->ids + base->idLength + 1
420                     };
421
422                 *instance = findOIDIPAddressInTable(&ipOid, table,
423                     tableEntrySize, addressOffset);
424                 if (!*instance)
425                     ret = SNMP_ERRORSTATUS_NOSUCHNAME;
426             }
427         }
428         else
429             ret = SNMP_ERRORSTATUS_NOSUCHNAME;
430     }
431     return ret;
432 }
433
434 static struct structToAsnValue mib2IfEntryMap[] = {
435     { FIELD_OFFSET(MIB_IFROW, dwIndex), copyInt },
436     { FIELD_OFFSET(MIB_IFROW, dwDescrLen), copyLengthPrecededString },
437     { FIELD_OFFSET(MIB_IFROW, dwType), copyInt },
438     { FIELD_OFFSET(MIB_IFROW, dwMtu), copyInt },
439     { FIELD_OFFSET(MIB_IFROW, dwSpeed), copyInt },
440     { FIELD_OFFSET(MIB_IFROW, dwPhysAddrLen), copyLengthPrecededString },
441     { FIELD_OFFSET(MIB_IFROW, dwAdminStatus), copyInt },
442     { FIELD_OFFSET(MIB_IFROW, dwOperStatus), copyOperStatus },
443     { FIELD_OFFSET(MIB_IFROW, dwLastChange), copyInt },
444     { FIELD_OFFSET(MIB_IFROW, dwInOctets), copyInt },
445     { FIELD_OFFSET(MIB_IFROW, dwInUcastPkts), copyInt },
446     { FIELD_OFFSET(MIB_IFROW, dwInNUcastPkts), copyInt },
447     { FIELD_OFFSET(MIB_IFROW, dwInDiscards), copyInt },
448     { FIELD_OFFSET(MIB_IFROW, dwInErrors), copyInt },
449     { FIELD_OFFSET(MIB_IFROW, dwInUnknownProtos), copyInt },
450     { FIELD_OFFSET(MIB_IFROW, dwOutOctets), copyInt },
451     { FIELD_OFFSET(MIB_IFROW, dwOutUcastPkts), copyInt },
452     { FIELD_OFFSET(MIB_IFROW, dwOutNUcastPkts), copyInt },
453     { FIELD_OFFSET(MIB_IFROW, dwOutDiscards), copyInt },
454     { FIELD_OFFSET(MIB_IFROW, dwOutErrors), copyInt },
455     { FIELD_OFFSET(MIB_IFROW, dwOutQLen), copyInt },
456 };
457
458 static UINT mib2IfEntry[] = { 1,3,6,1,2,1,2,2,1 };
459
460 static BOOL mib2IfEntryQuery(BYTE bPduType, SnmpVarBind *pVarBind,
461     AsnInteger32 *pErrorStatus)
462 {
463     AsnObjectIdentifier entryOid = DEFINE_OID(mib2IfEntry);
464
465     TRACE("(0x%02x, %s, %p)\n", bPduType, SnmpUtilOidToA(&pVarBind->name),
466         pErrorStatus);
467
468     switch (bPduType)
469     {
470     case SNMP_PDU_GET:
471     case SNMP_PDU_GETNEXT:
472         if (!ifTable)
473         {
474             /* There is no interface present, so let the caller deal
475              * with finding the successor.
476              */
477             *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
478         }
479         else
480         {
481             UINT tableIndex = 0, item = 0;
482
483             *pErrorStatus = getItemAndIntegerInstanceFromOid(&pVarBind->name,
484                 &entryOid, bPduType, &item, &tableIndex);
485             if (!*pErrorStatus)
486             {
487                 assert(tableIndex);
488                 assert(item);
489                 if (tableIndex > ifTable->dwNumEntries)
490                     *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
491                 else
492                 {
493                     *pErrorStatus = mapStructEntryToValue(mib2IfEntryMap,
494                         DEFINE_SIZEOF(mib2IfEntryMap),
495                         &ifTable->table[tableIndex - 1], item, bPduType,
496                         pVarBind);
497                     if (bPduType == SNMP_PDU_GETNEXT)
498                     {
499                         AsnObjectIdentifier oid;
500
501                         SnmpUtilOidCpy(&pVarBind->name, &entryOid);
502                         oid.idLength = 1;
503                         oid.ids = &item;
504                         SnmpUtilOidAppend(&pVarBind->name, &oid);
505                         /* According to RFC1158, the value of the interface
506                          * index must vary between 1 and ifNumber (the number
507                          * of interfaces), so use the 1-based table index
508                          * directly, rather than assuming that IPHlpApi's
509                          * dwIndex will have the correct range.
510                          */
511                         oid.idLength = 1;
512                         oid.ids = &tableIndex;
513                         SnmpUtilOidAppend(&pVarBind->name, &oid);
514                     }
515                 }
516             }
517         }
518         break;
519     case SNMP_PDU_SET:
520         *pErrorStatus = SNMP_ERRORSTATUS_READONLY;
521         break;
522     default:
523         FIXME("0x%02x: unsupported PDU type\n", bPduType);
524         *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
525     }
526     return TRUE;
527 }
528
529 static UINT mib2Ip[] = { 1,3,6,1,2,1,4 };
530 static MIB_IPSTATS ipStats;
531
532 static void mib2IpStatsInit(void)
533 {
534     GetIpStatistics(&ipStats);
535 }
536
537 static struct structToAsnValue mib2IpMap[] = {
538     { FIELD_OFFSET(MIB_IPSTATS, dwForwarding), copyInt }, /* 1 */
539     { FIELD_OFFSET(MIB_IPSTATS, dwDefaultTTL), copyInt }, /* 2 */
540     { FIELD_OFFSET(MIB_IPSTATS, dwInReceives), copyInt }, /* 3 */
541     { FIELD_OFFSET(MIB_IPSTATS, dwInHdrErrors), copyInt }, /* 4 */
542     { FIELD_OFFSET(MIB_IPSTATS, dwInAddrErrors), copyInt }, /* 5 */
543     { FIELD_OFFSET(MIB_IPSTATS, dwForwDatagrams), copyInt }, /* 6 */
544     { FIELD_OFFSET(MIB_IPSTATS, dwInUnknownProtos), copyInt }, /* 7 */
545     { FIELD_OFFSET(MIB_IPSTATS, dwInDiscards), copyInt }, /* 8 */
546     { FIELD_OFFSET(MIB_IPSTATS, dwInDelivers), copyInt }, /* 9 */
547     { FIELD_OFFSET(MIB_IPSTATS, dwOutRequests), copyInt }, /* 10 */
548     { FIELD_OFFSET(MIB_IPSTATS, dwOutDiscards), copyInt }, /* 11 */
549     { FIELD_OFFSET(MIB_IPSTATS, dwOutNoRoutes), copyInt }, /* 12 */
550     { FIELD_OFFSET(MIB_IPSTATS, dwReasmTimeout), copyInt }, /* 13 */
551     { FIELD_OFFSET(MIB_IPSTATS, dwReasmReqds), copyInt }, /* 14 */
552     { FIELD_OFFSET(MIB_IPSTATS, dwReasmOks), copyInt }, /* 15 */
553     { FIELD_OFFSET(MIB_IPSTATS, dwReasmFails), copyInt }, /* 16 */
554     { FIELD_OFFSET(MIB_IPSTATS, dwFragOks), copyInt }, /* 17 */
555     { FIELD_OFFSET(MIB_IPSTATS, dwFragFails), copyInt }, /* 18 */
556     { FIELD_OFFSET(MIB_IPSTATS, dwFragCreates), copyInt }, /* 19 */
557     { 0, NULL }, /* 20: not used, IP addr table */
558     { 0, NULL }, /* 21: not used, route table */
559     { 0, NULL }, /* 22: not used, net to media (ARP) table */
560     { FIELD_OFFSET(MIB_IPSTATS, dwRoutingDiscards), copyInt }, /* 23 */
561 };
562
563 static BOOL mib2IpStatsQuery(BYTE bPduType, SnmpVarBind *pVarBind,
564     AsnInteger32 *pErrorStatus)
565 {
566     AsnObjectIdentifier myOid = DEFINE_OID(mib2Ip);
567     UINT item = 0;
568
569     TRACE("(0x%02x, %s, %p)\n", bPduType, SnmpUtilOidToA(&pVarBind->name),
570         pErrorStatus);
571
572     switch (bPduType)
573     {
574     case SNMP_PDU_GET:
575     case SNMP_PDU_GETNEXT:
576         *pErrorStatus = getItemFromOid(&pVarBind->name, &myOid, bPduType,
577             &item);
578         if (!*pErrorStatus)
579         {
580             *pErrorStatus = mapStructEntryToValue(mib2IpMap,
581                 DEFINE_SIZEOF(mib2IpMap), &ipStats, item, bPduType, pVarBind);
582             if (!*pErrorStatus && bPduType == SNMP_PDU_GETNEXT)
583             {
584                 AsnObjectIdentifier oid;
585
586                 SnmpUtilOidCpy(&pVarBind->name, &myOid);
587                 oid.idLength = 1;
588                 oid.ids = &item;
589                 SnmpUtilOidAppend(&pVarBind->name, &oid);
590             }
591         }
592         break;
593     case SNMP_PDU_SET:
594         *pErrorStatus = SNMP_ERRORSTATUS_READONLY;
595         break;
596     default:
597         FIXME("0x%02x: unsupported PDU type\n", bPduType);
598         *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
599     }
600     return TRUE;
601 }
602
603 static UINT mib2IpAddr[] = { 1,3,6,1,2,1,4,20,1 };
604 static PMIB_IPADDRTABLE ipAddrTable;
605
606 static struct structToAsnValue mib2IpAddrMap[] = {
607     { FIELD_OFFSET(MIB_IPADDRROW, dwAddr), copyIpAddr },
608     { FIELD_OFFSET(MIB_IPADDRROW, dwIndex), copyInt },
609     { FIELD_OFFSET(MIB_IPADDRROW, dwMask), copyIpAddr },
610     { FIELD_OFFSET(MIB_IPADDRROW, dwBCastAddr), copyInt },
611     { FIELD_OFFSET(MIB_IPADDRROW, dwReasmSize), copyInt },
612 };
613
614 static void mib2IpAddrInit(void)
615 {
616     DWORD size = 0, ret = GetIpAddrTable(NULL, &size, FALSE);
617
618     if (ret == ERROR_INSUFFICIENT_BUFFER)
619     {
620         ipAddrTable = HeapAlloc(GetProcessHeap(), 0, size);
621         if (ipAddrTable)
622             GetIpAddrTable(ipAddrTable, &size, FALSE);
623     }
624 }
625
626 static BOOL mib2IpAddrQuery(BYTE bPduType, SnmpVarBind *pVarBind,
627     AsnInteger32 *pErrorStatus)
628 {
629     AsnObjectIdentifier myOid = DEFINE_OID(mib2IpAddr);
630     UINT tableIndex = 0, item = 0;
631
632     TRACE("(0x%02x, %s, %p)\n", bPduType, SnmpUtilOidToA(&pVarBind->name),
633         pErrorStatus);
634
635     switch (bPduType)
636     {
637     case SNMP_PDU_GET:
638     case SNMP_PDU_GETNEXT:
639         *pErrorStatus = getItemAndIpAddressInstanceFromOid(&pVarBind->name,
640             &myOid, bPduType, (struct GenericTable *)ipAddrTable,
641             sizeof(MIB_IPADDRROW), FIELD_OFFSET(MIB_IPADDRROW, dwAddr), &item,
642             &tableIndex);
643         if (!*pErrorStatus)
644         {
645             assert(tableIndex);
646             assert(item);
647             *pErrorStatus = mapStructEntryToValue(mib2IpAddrMap,
648                 DEFINE_SIZEOF(mib2IpAddrMap),
649                 &ipAddrTable->table[tableIndex - 1], item, bPduType, pVarBind);
650             if (!*pErrorStatus && bPduType == SNMP_PDU_GETNEXT)
651             {
652                 UINT id;
653                 BYTE *ptr;
654                 AsnObjectIdentifier oid;
655
656                 SnmpUtilOidCpy(&pVarBind->name, &myOid);
657                 oid.idLength = 1;
658                 oid.ids = &id;
659                 id = item;
660                 SnmpUtilOidAppend(&pVarBind->name, &oid);
661                 for (ptr = (BYTE *)&ipAddrTable->table[tableIndex - 1].dwAddr;
662                     ptr < (BYTE *)&ipAddrTable->table[tableIndex - 1].dwAddr +
663                     sizeof(DWORD); ptr++)
664                 {
665                     id = *ptr;
666                     SnmpUtilOidAppend(&pVarBind->name, &oid);
667                 }
668             }
669         }
670         break;
671     case SNMP_PDU_SET:
672         *pErrorStatus = SNMP_ERRORSTATUS_READONLY;
673         break;
674     default:
675         FIXME("0x%02x: unsupported PDU type\n", bPduType);
676         *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
677     }
678     return TRUE;
679 }
680
681 static UINT mib2Icmp[] = { 1,3,6,1,2,1,5 };
682 static MIB_ICMP icmpStats;
683
684 static void mib2IcmpInit(void)
685 {
686     GetIcmpStatistics(&icmpStats);
687 }
688
689 static struct structToAsnValue mib2IcmpMap[] = {
690     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwMsgs), copyInt },
691     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwErrors), copyInt },
692     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwDestUnreachs), copyInt },
693     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwTimeExcds), copyInt },
694     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwParmProbs), copyInt },
695     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwSrcQuenchs), copyInt },
696     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwRedirects), copyInt },
697     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwEchos), copyInt },
698     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwEchoReps), copyInt },
699     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwTimestamps), copyInt },
700     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwTimestampReps), copyInt },
701     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwAddrMasks), copyInt },
702     { FIELD_OFFSET(MIBICMPINFO, icmpInStats.dwAddrMaskReps), copyInt },
703     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwMsgs), copyInt },
704     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwErrors), copyInt },
705     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwDestUnreachs), copyInt },
706     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwTimeExcds), copyInt },
707     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwParmProbs), copyInt },
708     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwSrcQuenchs), copyInt },
709     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwRedirects), copyInt },
710     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwEchos), copyInt },
711     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwEchoReps), copyInt },
712     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwTimestamps), copyInt },
713     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwTimestampReps), copyInt },
714     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwAddrMasks), copyInt },
715     { FIELD_OFFSET(MIBICMPINFO, icmpOutStats.dwAddrMaskReps), copyInt },
716 };
717
718 static BOOL mib2IcmpQuery(BYTE bPduType, SnmpVarBind *pVarBind,
719     AsnInteger32 *pErrorStatus)
720 {
721     AsnObjectIdentifier myOid = DEFINE_OID(mib2Icmp);
722     UINT item = 0;
723
724     TRACE("(0x%02x, %s, %p)\n", bPduType, SnmpUtilOidToA(&pVarBind->name),
725         pErrorStatus);
726
727     switch (bPduType)
728     {
729     case SNMP_PDU_GET:
730     case SNMP_PDU_GETNEXT:
731         *pErrorStatus = getItemFromOid(&pVarBind->name, &myOid, bPduType,
732             &item);
733         if (!*pErrorStatus)
734         {
735             *pErrorStatus = mapStructEntryToValue(mib2IcmpMap,
736                 DEFINE_SIZEOF(mib2IcmpMap), &icmpStats, item, bPduType,
737                 pVarBind);
738             if (!*pErrorStatus)
739             {
740                 AsnObjectIdentifier oid;
741
742                 SnmpUtilOidCpy(&pVarBind->name, &myOid);
743                 oid.idLength = 1;
744                 oid.ids = &item;
745                 SnmpUtilOidAppend(&pVarBind->name, &oid);
746             }
747         }
748         break;
749     case SNMP_PDU_SET:
750         *pErrorStatus = SNMP_ERRORSTATUS_READONLY;
751         break;
752     default:
753         FIXME("0x%02x: unsupported PDU type\n", bPduType);
754         *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
755     }
756     return TRUE;
757 }
758
759 static UINT mib2Tcp[] = { 1,3,6,1,2,1,6 };
760 static MIB_TCPSTATS tcpStats;
761
762 static void mib2TcpInit(void)
763 {
764     GetTcpStatistics(&tcpStats);
765 }
766
767 static struct structToAsnValue mib2TcpMap[] = {
768     { FIELD_OFFSET(MIB_TCPSTATS, dwRtoAlgorithm), copyInt },
769     { FIELD_OFFSET(MIB_TCPSTATS, dwRtoMin), copyInt },
770     { FIELD_OFFSET(MIB_TCPSTATS, dwRtoMax), copyInt },
771     { FIELD_OFFSET(MIB_TCPSTATS, dwMaxConn), copyInt },
772     { FIELD_OFFSET(MIB_TCPSTATS, dwActiveOpens), copyInt },
773     { FIELD_OFFSET(MIB_TCPSTATS, dwPassiveOpens), copyInt },
774     { FIELD_OFFSET(MIB_TCPSTATS, dwAttemptFails), copyInt },
775     { FIELD_OFFSET(MIB_TCPSTATS, dwEstabResets), copyInt },
776     { FIELD_OFFSET(MIB_TCPSTATS, dwCurrEstab), copyInt },
777     { FIELD_OFFSET(MIB_TCPSTATS, dwInSegs), copyInt },
778     { FIELD_OFFSET(MIB_TCPSTATS, dwOutSegs), copyInt },
779     { FIELD_OFFSET(MIB_TCPSTATS, dwRetransSegs), copyInt },
780     { FIELD_OFFSET(MIB_TCPSTATS, dwInErrs), copyInt },
781     { FIELD_OFFSET(MIB_TCPSTATS, dwOutRsts), copyInt },
782     { FIELD_OFFSET(MIB_TCPSTATS, dwNumConns), copyInt },
783 };
784
785 static BOOL mib2TcpQuery(BYTE bPduType, SnmpVarBind *pVarBind,
786     AsnInteger32 *pErrorStatus)
787 {
788     AsnObjectIdentifier myOid = DEFINE_OID(mib2Tcp);
789     UINT item = 0;
790
791     TRACE("(0x%02x, %s, %p)\n", bPduType, SnmpUtilOidToA(&pVarBind->name),
792         pErrorStatus);
793
794     switch (bPduType)
795     {
796     case SNMP_PDU_GET:
797     case SNMP_PDU_GETNEXT:
798         *pErrorStatus = getItemFromOid(&pVarBind->name, &myOid, bPduType,
799             &item);
800         if (!*pErrorStatus)
801         {
802             *pErrorStatus = mapStructEntryToValue(mib2TcpMap,
803                 DEFINE_SIZEOF(mib2TcpMap), &tcpStats, item, bPduType, pVarBind);
804             if (!*pErrorStatus && bPduType == SNMP_PDU_GETNEXT)
805             {
806                 AsnObjectIdentifier oid;
807
808                 SnmpUtilOidCpy(&pVarBind->name, &myOid);
809                 oid.idLength = 1;
810                 oid.ids = &item;
811                 SnmpUtilOidAppend(&pVarBind->name, &oid);
812             }
813         }
814         break;
815     case SNMP_PDU_SET:
816         *pErrorStatus = SNMP_ERRORSTATUS_READONLY;
817         break;
818     default:
819         FIXME("0x%02x: unsupported PDU type\n", bPduType);
820         *pErrorStatus = SNMP_ERRORSTATUS_NOSUCHNAME;
821     }
822     return TRUE;
823 }
824
825 /* This list MUST BE lexicographically sorted */
826 static struct mibImplementation supportedIDs[] = {
827     { DEFINE_OID(mib2IfNumber), mib2IfNumberInit, mib2IfNumberQuery },
828     { DEFINE_OID(mib2IfEntry), NULL, mib2IfEntryQuery },
829     { DEFINE_OID(mib2Ip), mib2IpStatsInit, mib2IpStatsQuery },
830     { DEFINE_OID(mib2IpAddr), mib2IpAddrInit, mib2IpAddrQuery },
831     { DEFINE_OID(mib2Icmp), mib2IcmpInit, mib2IcmpQuery },
832     { DEFINE_OID(mib2Tcp), mib2TcpInit, mib2TcpQuery },
833 };
834 static UINT minSupportedIDLength;
835
836 BOOL WINAPI SnmpExtensionInit(DWORD dwUptimeReference,
837     HANDLE *phSubagentTrapEvent, AsnObjectIdentifier *pFirstSupportedRegion)
838 {
839     AsnObjectIdentifier myOid = DEFINE_OID(mib2System);
840     UINT i;
841
842     TRACE("(%d, %p, %p)\n", dwUptimeReference, phSubagentTrapEvent,
843         pFirstSupportedRegion);
844
845     minSupportedIDLength = UINT_MAX;
846     for (i = 0; i < sizeof(supportedIDs) / sizeof(supportedIDs[0]); i++)
847     {
848         if (supportedIDs[i].init)
849             supportedIDs[i].init();
850         if (supportedIDs[i].name.idLength < minSupportedIDLength)
851             minSupportedIDLength = supportedIDs[i].name.idLength;
852     }
853     *phSubagentTrapEvent = NULL;
854     SnmpUtilOidCpy(pFirstSupportedRegion, &myOid);
855     return TRUE;
856 }
857
858 static struct mibImplementation *findSupportedQuery(UINT *ids, UINT idLength,
859     UINT *matchingIndex)
860 {
861     int indexHigh = DEFINE_SIZEOF(supportedIDs) - 1, indexLow = 0, i;
862     struct mibImplementation *impl = NULL;
863     AsnObjectIdentifier oid1 = { idLength, ids};
864
865     if (!idLength)
866         return NULL;
867     for (i = (indexLow + indexHigh) / 2; !impl && indexLow <= indexHigh;
868          i = (indexLow + indexHigh) / 2)
869     {
870         INT cmp;
871
872         cmp = SnmpUtilOidNCmp(&oid1, &supportedIDs[i].name, idLength);
873         if (!cmp)
874         {
875             impl = &supportedIDs[i];
876             *matchingIndex = i;
877         }
878         else if (cmp > 0)
879             indexLow = i + 1;
880         else
881             indexHigh = i - 1;
882     }
883     return impl;
884 }
885
886 BOOL WINAPI SnmpExtensionQuery(BYTE bPduType, SnmpVarBindList *pVarBindList,
887     AsnInteger32 *pErrorStatus, AsnInteger32 *pErrorIndex)
888 {
889     AsnObjectIdentifier mib2oid = DEFINE_OID(mib2);
890     AsnInteger32 error = SNMP_ERRORSTATUS_NOERROR, errorIndex = 0;
891     UINT i;
892
893     TRACE("(0x%02x, %p, %p, %p)\n", bPduType, pVarBindList,
894         pErrorStatus, pErrorIndex);
895
896     for (i = 0; !error && i < pVarBindList->len; i++)
897     {
898         /* Ignore any OIDs not in MIB2 */
899         if (!SnmpUtilOidNCmp(&pVarBindList->list[i].name, &mib2oid,
900             mib2oid.idLength))
901         {
902             struct mibImplementation *impl = NULL;
903             UINT len, matchingIndex = 0;
904
905             TRACE("%s\n", SnmpUtilOidToA(&pVarBindList->list[i].name));
906             /* Search for an implementation matching as many octets as possible
907              */
908             for (len = pVarBindList->list[i].name.idLength;
909                 len >= minSupportedIDLength && !impl; len--)
910                 impl = findSupportedQuery(pVarBindList->list[i].name.ids, len,
911                     &matchingIndex);
912             if (impl && impl->query)
913                 impl->query(bPduType, &pVarBindList->list[i], &error);
914             else
915                 error = SNMP_ERRORSTATUS_NOSUCHNAME;
916             if (error == SNMP_ERRORSTATUS_NOSUCHNAME &&
917                 bPduType == SNMP_PDU_GETNEXT)
918             {
919                 /* GetNext is special: it finds the successor to the given OID,
920                  * so we have to continue until an implementation handles the
921                  * query or we exhaust the table of supported OIDs.
922                  */
923                 for (; error == SNMP_ERRORSTATUS_NOSUCHNAME &&
924                     matchingIndex < DEFINE_SIZEOF(supportedIDs);
925                     matchingIndex++)
926                 {
927                     error = SNMP_ERRORSTATUS_NOERROR;
928                     impl = &supportedIDs[matchingIndex];
929                     if (impl->query)
930                         impl->query(bPduType, &pVarBindList->list[i], &error);
931                     else
932                         error = SNMP_ERRORSTATUS_NOSUCHNAME;
933                 }
934                 /* If the query still isn't resolved, set the OID to the
935                  * successor to the last entry in the table.
936                  */
937                 if (error == SNMP_ERRORSTATUS_NOSUCHNAME)
938                 {
939                     SnmpUtilOidFree(&pVarBindList->list[i].name);
940                     SnmpUtilOidCpy(&pVarBindList->list[i].name,
941                         &supportedIDs[matchingIndex - 1].name);
942                     pVarBindList->list[i].name.ids[
943                         pVarBindList->list[i].name.idLength - 1] += 1;
944                 }
945             }
946             if (error)
947                 errorIndex = i + 1;
948         }
949     }
950     *pErrorStatus = error;
951     *pErrorIndex = errorIndex;
952     return TRUE;
953 }