1 // NAME
2 // $RCSfile: MultiResponsePdu.java,v $
3 // DESCRIPTION
4 // [given below in javadoc format]
5 // DELTA
6 // $Revision: 3.3 $
7 // CREATED
8 // $Date: 2007/10/17 10:44:09 $
9 // COPYRIGHT
10 // Westhawk Ltd
11 // TO DO
12 //
13
14 /*
15 * Copyright (C) 2006 by Westhawk Ltd
16 * <a href="www.westhawk.co.uk">www.westhawk.co.uk</a>
17 *
18 * Permission to use, copy, modify, and distribute this software
19 * for any purpose and without fee is hereby granted, provided
20 * that the above copyright notices appear in all copies and that
21 * both the copyright notice and this permission notice appear in
22 * supporting documentation.
23 * This software is provided "as is" without express or implied
24 * warranty.
25 * author <a href="mailto:snmp@westhawk.co.uk">Tim Panton</a>
26 */
27
28 package uk.co.westhawk.snmp.stack;
29
30 /*-
31 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
32 * SNMP Java Client
33 * ჻჻჻჻჻჻
34 * Copyright 2023 MetricsHub, Westhawk
35 * ჻჻჻჻჻჻
36 * This program is free software: you can redistribute it and/or modify
37 * it under the terms of the GNU Lesser General Public License as
38 * published by the Free Software Foundation, either version 3 of the
39 * License, or (at your option) any later version.
40 *
41 * This program is distributed in the hope that it will be useful,
42 * but WITHOUT ANY WARRANTY; without even the implied warranty of
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
44 * GNU General Lesser Public License for more details.
45 *
46 * You should have received a copy of the GNU General Lesser Public
47 * License along with this program. If not, see
48 * <http://www.gnu.org/licenses/lgpl-3.0.html>.
49 * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
50 */
51
52 import java.util.*;
53
54
55 /**
56 * This class can receive multiple responses.
57 * Typical usage includes sending a single PDU to a multicast /
58 * broadcast address so that multiple responses can be received from
59 * different sources.
60 *
61 * <p>
62 * This class sets a single long timeout for the retry, so it sends the
63 * request only once.
64 * Opposite to its parent class, this class does not ignore the duplicate
65 * responses, and it will timeout by nature.
66 * </p>
67 *
68 * <p>Note:</p>
69 * <ul>
70 * <li>Please realise that you might choke the stack and your network, when
71 * you use this class, even on a small subnet</li>
72 * <li>This PDU will eat up transmit and receive resources, until it times out</li>
73 * <li>This PDU cannot be used to receive traps</li>
74 * <li>Authentication (and privacy) is by definition a unicast activity.
75 * You can find unauthenticated SNMPv3 engines, by broadcasting this PDU
76 * with a SnmpContextv3(Pool) with no authentication.
77 * Then you need to continue an authentication/privacy context and a (normal)
78 * PDU.<br/>
79 * In other words, finding SNMPv3 engines that only support
80 * authentication and/or privacy cannot be done via broadcasting.
81 * </li>
82 * </ul>
83 *
84 *
85 * <p>
86 * Thanks to Josh Bers <jbers@bbn.com>
87 * </p>
88 *
89 * @since 4_14
90 * @author <a href="mailto:snmp@westhawk.co.uk">Birgit Arkesteijn</a>
91 * @version $Revision: 3.3 $ $Date: 2007/10/17 10:44:09 $
92 */
93 public class MultiResponsePdu extends Pdu {
94 private static final String version_id = "@(#)$Id: MultiResponsePdu.java,v 3.3 2007/10/17 10:44:09 birgita Exp $ Copyright Westhawk Ltd";
95
96 /**
97 * Hashtable to hold responses received from agents.
98 */
99 private Hashtable responses = new Hashtable();
100
101 /**
102 * IP address of current response
103 */
104 private String thisIP = null;
105
106 /**
107 * By default create a MultiResponsePdu that will wait for 3
108 * seconds for responses to come in from multiple sources. If you
109 * want to wait longer set the RetryInterval to a longer first
110 * timeout. To make the request more reliable, add more timeouts.
111 *
112 * @param con The context
113 */
114 public MultiResponsePdu(SnmpContextBasisFace con) {
115 super(con);
116 setRetryIntervals(new int[] { 3000 });
117 }
118
119 /**
120 * Gets the IP address of the host of the most recent response received.
121 *
122 * @return The sourceAgent value
123 */
124 public String getSourceAgent() {
125 return thisIP;
126 }
127
128 /**
129 * Gets the number of responses so far received
130 * to this request.
131 *
132 * @return The number of responses
133 */
134 public int getNumResponses() {
135 return responses.size();
136 }
137
138 /**
139 * Prints out the list of received responses and their source IP
140 * addressses. Results will be ommitted if not yet received.
141 *
142 * @return String representation of this PDU and all its received
143 * responses
144 */
145 public String toString() {
146 // loop over vector of responses and use Pdu.toString(boolean)
147 // to print out the received varbinds.
148 StringBuffer buffer = new StringBuffer();
149 if (!answered) {
150 buffer.append(toString(false));
151 } else {
152 Enumeration ipaddrs = responses.keys();
153 String ipaddr = (String) ipaddrs.nextElement();
154 // set respVarbinds to each response in turn calling toString(true)
155 respVarbinds = (Vector) responses.get(ipaddr);
156
157 buffer.append(toString(true));
158 buffer.append(" rhost=").append(ipaddr);
159 int i = 2;
160 while (ipaddrs.hasMoreElements()) {
161 ipaddr = (String) ipaddrs.nextElement();
162 respVarbinds = (Vector) responses.get(ipaddr);
163
164 buffer.append("\n\t");
165 buffer.append(printVars("respVarbinds" + i, respVarbinds));
166 buffer.append(" rhost=").append(ipaddr);
167 i++;
168 }
169 }
170 return buffer.toString();
171 }
172
173 /**
174 * Lets the observers know which source we received a response from.
175 */
176 protected void tell_them() {
177 String sender = thisIP;
178 // if timed out then we are done waiting for replies.
179 if (isTimedOut()) {
180 sender = null;
181 } else {
182 // record this response for posterity
183 responses.put(sender, respVarbinds);
184 }
185
186 // tell all interested parties
187 notifyObservers(sender);
188
189 // free up space for next result
190 respVarbinds = null;
191 thisIP = null;
192 }
193
194 /**
195 * Fills in the received response.
196 *
197 * Now override fillin to fetch source ip address and not set answered
198 * you can get multiple responses by setting the timeout period long
199 * do this in the constructor.
200 *
201 * @param seq Description of Parameter
202 * @see Pdu#getResponseVarbinds()
203 */
204 void fillin(AsnPduSequence seq) {
205 // this will be set to true (eventually) in handleNoAnswer()
206 if (answered) {
207 if (AsnObject.debug > 6) {
208 System.out.println(getClass().getName() + ".fillin(): "
209 + "Got a second answer to request " + getReqId());
210 }
211 return;
212 }
213
214 // check that we haven't already heard from this host before:
215 thisIP = getContext().getReceivedFromHostAddress();
216 if (responses.containsKey(thisIP)) {
217 if (AsnObject.debug > 6) {
218 System.out.println(getClass().getName() + ".fillin(): "
219 + "Got a second answer from " + thisIP
220 + " to request " + getReqId());
221 }
222 return;
223 }
224
225 // fillin(null) can be called in case of a Decoding exception
226 if (seq != null) {
227 if (seq.isCorrect == true) {
228 int n = -1;
229 try {
230 // Fill in the request id
231 this.req_id = seq.getReqId();
232 setErrorStatus(seq.getWhatError());
233 setErrorIndex(seq.getWhereError());
234
235 // The varbinds from the response/report are set in a
236 // new Vector.
237 AsnSequence varBind = seq.getVarBind();
238 int size = varBind.getObjCount();
239 respVarbinds = new Vector(size, 1);
240 for (n = 0; n < size; n++) {
241 Object obj = varBind.getObj(n);
242 if (obj instanceof AsnSequence) {
243 AsnSequence varSeq = (AsnSequence) obj;
244 try {
245 varbind vb = new varbind(varSeq);
246 respVarbinds.addElement(vb);
247 new_value(n, vb);
248 } catch (IllegalArgumentException exc) {
249 }
250 }
251 }
252
253 // At this point, I don't know whether I received a
254 // response and should fill in only the respVarbind or
255 // whether I received a request (via ListeningContext)
256 // and I should fill in the reqVarbinds.
257 // So when reqVarbinds is empty, I clone the
258 // respVarbinds.
259 if (reqVarbinds.isEmpty()) {
260 reqVarbinds = (Vector) respVarbinds.clone();
261 }
262 } catch (Exception e) {
263 // it happens that an agent does not encode the varbind
264 // list properly. Since we try do decode as much as
265 // possible there may be wrong elements in this list.
266
267 DecodingException exc = new DecodingException(
268 "Incorrect varbind list, element " + n);
269 setErrorStatus(AsnObject.SNMP_ERR_DECODINGASN_EXC, exc);
270 }
271 } else {
272 // we couldn't read the whole message
273 // see AsnObject.AsnReadHeader, isCorrect
274
275 DecodingException exc = new DecodingException(
276 "Incorrect packet. No of bytes received less than packet length.");
277 setErrorStatus(AsnObject.SNMP_ERR_DECODINGPKTLNGTH_EXC, exc);
278 }
279 }
280
281 // always do 'setChanged', even if there are no varbinds.
282 setChanged();
283 tell_them();
284 clearChanged();
285
286 // don't want to tell trans to stop since this will remove
287 // the PDU from the context. Instead depend on timeouts to
288 // free up the transmitter and remove PDU from context.
289 /*
290 * synchronized(this)
291 * {
292 * got = true;
293 * answered = true;
294 * notify(); // see also handleNoAnswer()
295 * if (trans != null)
296 * {
297 * // free up the transmitter, since
298 * // we are happy with the answer.
299 * // trans may be null if we are receiving a trap.
300 * trans.interruptMe();
301 * }
302 * }
303 */
304 }
305
306 }