license changed to LGPL for libnxcl, libnxsnmp, libnxlp, libnxsl, and libnxmap
[public/netxms.git] / src / snmp / libnxsnmp / transport.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** SNMP support library
4 ** Copyright (C) 2003-2010 Victor Kirhenshtein
5 **
6 ** This program is free software; you can redistribute it and/or modify
7 ** it under the terms of the GNU Lesser General Public License as published by
8 ** the Free Software Foundation; either version 3 of the License, or
9 ** (at your option) any later version.
10 **
11 ** This program is distributed in the hope that it will be useful,
12 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
13 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 ** GNU General Public License for more details.
15 **
16 ** You should have received a copy of the GNU Lesser General Public License
17 ** along with this program; if not, write to the Free Software
18 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 **
20 ** File: transport.cpp
21 **
22 **/
23
24 #include "libnxsnmp.h"
25
26
27 //
28 // Report to SNMP error mapping
29 //
30
31 static struct
32 {
33 const TCHAR *oid;
34 DWORD errorCode;
35 } m_oidToErrorMap[] =
36 {
37 { _T(".1.3.6.1.6.3.15.1.1.1.0"), SNMP_ERR_UNSUPP_SEC_LEVEL },
38 { _T(".1.3.6.1.6.3.15.1.1.2.0"), SNMP_ERR_TIME_WINDOW },
39 { _T(".1.3.6.1.6.3.15.1.1.3.0"), SNMP_ERR_SEC_NAME },
40 { _T(".1.3.6.1.6.3.15.1.1.4.0"), SNMP_ERR_ENGINE_ID },
41 { _T(".1.3.6.1.6.3.15.1.1.5.0"), SNMP_ERR_AUTH_FAILURE },
42 { _T(".1.3.6.1.6.3.15.1.1.6.0"), SNMP_ERR_DECRYPTION },
43 { NULL, 0 }
44 };
45
46
47 //
48 // Constructor
49 //
50
51 SNMP_Transport::SNMP_Transport()
52 {
53 m_authoritativeEngine = NULL;
54 m_contextEngine = NULL;
55 m_securityContext = NULL;
56 m_enableEngineIdAutoupdate = false;
57 }
58
59
60 //
61 // Destructor
62 //
63
64 SNMP_Transport::~SNMP_Transport()
65 {
66 delete m_authoritativeEngine;
67 delete m_contextEngine;
68 delete m_securityContext;
69 }
70
71
72 //
73 // Set security context
74 //
75
76 void SNMP_Transport::setSecurityContext(SNMP_SecurityContext *ctx)
77 {
78 delete m_securityContext;
79 m_securityContext = ctx;
80 }
81
82
83 //
84 // Send a request and wait for respone
85 // with respect for timeouts and retransmissions
86 //
87
88 DWORD SNMP_Transport::doRequest(SNMP_PDU *request, SNMP_PDU **response,
89 DWORD timeout, int numRetries)
90 {
91 DWORD rc;
92 int bytes;
93
94 if ((request == NULL) || (response == NULL) || (numRetries == 0))
95 return SNMP_ERR_PARAM;
96
97 *response = NULL;
98
99 // Create dummy context
100 if (m_securityContext == NULL)
101 m_securityContext = new SNMP_SecurityContext();
102
103
104 // Update SNMP V3 request with cached context engine id
105 if (request->getVersion() == SNMP_VERSION_3)
106 {
107 if ((request->getContextEngineIdLength() == 0) && (m_contextEngine != NULL))
108 {
109 request->setContextEngineId(m_contextEngine->getId(), m_contextEngine->getIdLen());
110 }
111 }
112
113 while(numRetries-- >= 0)
114 {
115 int timeSyncRetries = 3;
116
117 retry:
118 rc = SNMP_ERR_SUCCESS;
119 if (sendMessage(request) <= 0)
120 {
121 rc = SNMP_ERR_COMM;
122 break;
123 }
124
125 bytes = readMessage(response, timeout);
126 if (bytes > 0)
127 {
128 if (*response != NULL)
129 {
130 if (request->getVersion() == SNMP_VERSION_3)
131 {
132 if ((*response)->getMessageId() == request->getMessageId())
133 {
134 // Cache authoritative engine ID
135 if ((m_authoritativeEngine == NULL) && ((*response)->getAuthoritativeEngine().getIdLen() != 0))
136 {
137 m_authoritativeEngine = new SNMP_Engine((*response)->getAuthoritativeEngine());
138 m_securityContext->setAuthoritativeEngine(*m_authoritativeEngine);
139 }
140
141 // Cache context engine ID
142 if ((m_contextEngine == NULL) && ((*response)->getContextEngineIdLength() != 0))
143 {
144 m_contextEngine = new SNMP_Engine((*response)->getContextEngineId(), (*response)->getContextEngineIdLength());
145 }
146
147 if ((*response)->getCommand() == SNMP_REPORT)
148 {
149 SNMP_Variable *var = (*response)->getVariable(0);
150 const TCHAR *oid = var->GetName()->GetValueAsText();
151 rc = SNMP_ERR_AGENT;
152 for(int i = 0; m_oidToErrorMap[i].oid != NULL; i++)
153 {
154 if (!_tcscmp(oid, m_oidToErrorMap[i].oid))
155 {
156 rc = m_oidToErrorMap[i].errorCode;
157 break;
158 }
159 }
160
161 // Engine ID discovery - if request contains empty engine ID,
162 // replace it with correct one and retry
163 if (rc == SNMP_ERR_ENGINE_ID)
164 {
165 bool canRetry = false;
166
167 if (request->getContextEngineIdLength() == 0)
168 {
169 request->setContextEngineId((*response)->getContextEngineId(), (*response)->getContextEngineIdLength());
170 canRetry = true;
171 }
172 if (m_securityContext->getAuthoritativeEngine().getIdLen() == 0)
173 {
174 m_securityContext->setAuthoritativeEngine((*response)->getAuthoritativeEngine());
175 canRetry = true;
176 }
177 if (canRetry)
178 goto retry;
179 }
180 else if (rc == SNMP_ERR_TIME_WINDOW)
181 {
182 // Update cached authoritative engine with new boots and time
183 if ((timeSyncRetries > 0) &&
184 (((*response)->getAuthoritativeEngine().getBoots() != m_authoritativeEngine->getBoots()) ||
185 ((*response)->getAuthoritativeEngine().getTime() != m_authoritativeEngine->getTime())))
186 {
187 m_authoritativeEngine->setBoots((*response)->getAuthoritativeEngine().getBoots());
188 m_authoritativeEngine->setTime((*response)->getAuthoritativeEngine().getTime());
189 m_securityContext->setAuthoritativeEngine(*m_authoritativeEngine);
190 timeSyncRetries--;
191 goto retry;
192 }
193 }
194 }
195 else if ((*response)->getCommand() != SNMP_RESPONSE)
196 {
197 rc = SNMP_ERR_BAD_RESPONSE;
198 }
199 break;
200 }
201 }
202 else
203 {
204 if ((*response)->getRequestId() == request->getRequestId())
205 break;
206 }
207 rc = SNMP_ERR_TIMEOUT;
208 }
209 else
210 {
211 rc = SNMP_ERR_PARSE;
212 break;
213 }
214 }
215 else
216 {
217 rc = (bytes == 0) ? SNMP_ERR_TIMEOUT : SNMP_ERR_COMM;
218 }
219 }
220
221 return rc;
222 }
223
224
225 //
226 // SNMP_UDPTransport default constructor
227 //
228
229 SNMP_UDPTransport::SNMP_UDPTransport()
230 :SNMP_Transport()
231 {
232 m_hSocket = -1;
233 m_dwBufferSize = SNMP_DEFAULT_MSG_MAX_SIZE;
234 m_dwBufferPos = 0;
235 m_dwBytesInBuffer = 0;
236 m_pBuffer = (BYTE *)malloc(m_dwBufferSize);
237 }
238
239
240 //
241 // Create SNMP_UDPTransport for existing socket
242 //
243
244 SNMP_UDPTransport::SNMP_UDPTransport(SOCKET hSocket)
245 :SNMP_Transport()
246 {
247 m_hSocket = hSocket;
248 m_dwBufferSize = SNMP_DEFAULT_MSG_MAX_SIZE;
249 m_dwBufferPos = 0;
250 m_dwBytesInBuffer = 0;
251 m_pBuffer = (BYTE *)malloc(m_dwBufferSize);
252 }
253
254
255 //
256 // Create SNMP_UDPTransport transport connected to given host
257 // Will try to resolve host name if it's not null, otherwise
258 // IP address will be used
259 //
260
261 DWORD SNMP_UDPTransport::createUDPTransport(TCHAR *pszHostName, DWORD dwHostAddr, WORD wPort)
262 {
263 struct sockaddr_in addr;
264 DWORD dwResult;
265
266 #ifdef UNICODE
267 char szHostName[256];
268
269 if (pszHostName != NULL)
270 {
271 WideCharToMultiByte(CP_ACP, WC_COMPOSITECHECK | WC_DEFAULTCHAR,
272 pszHostName, -1, szHostName, 256, NULL, NULL);
273 }
274 #define HOSTNAME_VAR szHostName
275 #else
276 #define HOSTNAME_VAR pszHostName
277 #endif
278
279 // Fill in remote address structure
280 memset(&addr, 0, sizeof(struct sockaddr_in));
281 addr.sin_family = AF_INET;
282 addr.sin_port = htons(wPort);
283
284 // Resolve hostname
285 if (pszHostName != NULL)
286 {
287 struct hostent *hs;
288
289 hs = gethostbyname(HOSTNAME_VAR);
290 if (hs != NULL)
291 {
292 memcpy(&addr.sin_addr.s_addr, hs->h_addr, sizeof(DWORD));
293 }
294 else
295 {
296 addr.sin_addr.s_addr = inet_addr(HOSTNAME_VAR);
297 }
298 }
299 else
300 {
301 addr.sin_addr.s_addr = dwHostAddr;
302 }
303
304 // Create and connect socket
305 if ((addr.sin_addr.s_addr != INADDR_ANY) &&
306 (addr.sin_addr.s_addr != INADDR_NONE))
307 {
308 m_hSocket = socket(AF_INET, SOCK_DGRAM, 0);
309 if (m_hSocket != -1)
310 {
311 // Pseudo-connect socket
312 if (connect(m_hSocket, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == 0)
313 {
314 // Set non-blocking mode
315 #ifdef _WIN32
316 u_long one = 1;
317 ioctlsocket(m_hSocket, FIONBIO, &one);
318 #endif
319 dwResult = SNMP_ERR_SUCCESS;
320 }
321 else
322 {
323 closesocket(m_hSocket);
324 m_hSocket = -1;
325 dwResult = SNMP_ERR_SOCKET;
326 }
327 }
328 else
329 {
330 dwResult = SNMP_ERR_SOCKET;
331 }
332 }
333 else
334 {
335 dwResult = SNMP_ERR_HOSTNAME;
336 }
337
338 return dwResult;
339 }
340
341 #undef HOSTNAME_VAR
342
343
344 //
345 // Destructor for SNMP_Transport
346 //
347
348 SNMP_UDPTransport::~SNMP_UDPTransport()
349 {
350 safe_free(m_pBuffer);
351 if (m_hSocket != -1)
352 closesocket(m_hSocket);
353 }
354
355
356 //
357 // Clear buffer
358 //
359
360 void SNMP_UDPTransport::clearBuffer(void)
361 {
362 m_dwBytesInBuffer = 0;
363 m_dwBufferPos = 0;
364 }
365
366
367 //
368 // Receive data from socket
369 //
370
371 int SNMP_UDPTransport::recvData(DWORD dwTimeout, struct sockaddr *pSender, socklen_t *piAddrSize)
372 {
373 fd_set rdfs;
374 struct timeval tv;
375
376 if (dwTimeout != INFINITE)
377 {
378 #ifndef _WIN32
379 int iErr;
380 QWORD qwTime;
381
382 do
383 {
384 #endif
385 FD_ZERO(&rdfs);
386 FD_SET(m_hSocket, &rdfs);
387 tv.tv_sec = dwTimeout / 1000;
388 tv.tv_usec = (dwTimeout % 1000) * 1000;
389 #ifdef _WIN32
390 if (select(1, &rdfs, NULL, NULL, &tv) <= 0)
391 return 0;
392 #else
393 qwTime = GetCurrentTimeMs();
394 if ((iErr = select(m_hSocket + 1, &rdfs, NULL, NULL, &tv)) <= 0)
395 {
396 if (((iErr == -1) && (errno != EINTR)) ||
397 (iErr == 0))
398 {
399 return 0;
400 }
401 }
402 qwTime = GetCurrentTimeMs() - qwTime; // Elapsed time
403 dwTimeout -= min(((DWORD)qwTime), dwTimeout);
404 } while(iErr < 0);
405 #endif
406 }
407 return recvfrom(m_hSocket, (char *)&m_pBuffer[m_dwBufferPos + m_dwBytesInBuffer],
408 m_dwBufferSize - (m_dwBufferPos + m_dwBytesInBuffer), 0,
409 pSender, piAddrSize);
410 }
411
412
413 //
414 // Pre-parse PDU
415 //
416
417 DWORD SNMP_UDPTransport::preParsePDU(void)
418 {
419 DWORD dwType, dwLength, dwIdLength;
420 BYTE *pbCurrPos;
421
422 if (!BER_DecodeIdentifier(&m_pBuffer[m_dwBufferPos], m_dwBytesInBuffer,
423 &dwType, &dwLength, &pbCurrPos, &dwIdLength))
424 return 0;
425 if (dwType != ASN_SEQUENCE)
426 return 0; // Packet should start with SEQUENCE
427
428 return dwLength + dwIdLength;
429 }
430
431
432 //
433 // Read PDU from socket
434 //
435
436 int SNMP_UDPTransport::readMessage(SNMP_PDU **ppData, DWORD dwTimeout,
437 struct sockaddr *pSender, socklen_t *piAddrSize,
438 SNMP_SecurityContext* (*contextFinder)(struct sockaddr *, socklen_t))
439 {
440 int iBytes;
441 DWORD dwPDULength;
442
443 if (m_dwBytesInBuffer < 2)
444 {
445 iBytes = recvData(dwTimeout, pSender, piAddrSize);
446 if (iBytes <= 0)
447 {
448 clearBuffer();
449 return iBytes;
450 }
451 m_dwBytesInBuffer += iBytes;
452 }
453
454 dwPDULength = preParsePDU();
455 if (dwPDULength == 0)
456 {
457 // Clear buffer
458 clearBuffer();
459 return 0;
460 }
461
462 // Move existing data to the beginning of buffer if there are not enough space at the end
463 if (dwPDULength > m_dwBufferSize - m_dwBufferPos)
464 {
465 memmove(m_pBuffer, &m_pBuffer[m_dwBufferPos], m_dwBytesInBuffer);
466 m_dwBufferPos = 0;
467 }
468
469 // Read entire PDU into buffer
470 while(m_dwBytesInBuffer < dwPDULength)
471 {
472 iBytes = recvData(dwTimeout, pSender, piAddrSize);
473 if (iBytes <= 0)
474 {
475 clearBuffer();
476 return iBytes;
477 }
478 m_dwBytesInBuffer += iBytes;
479 }
480
481 // Change security context if needed
482 if (contextFinder != NULL)
483 {
484 setSecurityContext(contextFinder(pSender, *piAddrSize));
485 }
486
487 // Create new PDU object and remove parsed data from buffer
488 *ppData = new SNMP_PDU;
489 if (!(*ppData)->parse(&m_pBuffer[m_dwBufferPos], dwPDULength, m_securityContext, m_enableEngineIdAutoupdate))
490 {
491 delete *ppData;
492 *ppData = NULL;
493 }
494 m_dwBytesInBuffer -= dwPDULength;
495 if (m_dwBytesInBuffer == 0)
496 m_dwBufferPos = 0;
497
498 return dwPDULength;
499 }
500
501
502 //
503 // Send PDU to socket
504 //
505
506 int SNMP_UDPTransport::sendMessage(SNMP_PDU *pPDU)
507 {
508 BYTE *pBuffer;
509 DWORD dwSize;
510 int nBytes = 0;
511
512 dwSize = pPDU->encode(&pBuffer, m_securityContext);
513 if (dwSize != 0)
514 {
515 nBytes = send(m_hSocket, (char *)pBuffer, dwSize, 0);
516 free(pBuffer);
517 }
518
519 return nBytes;
520 }