View Javadoc
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 &lt;jbers@bbn.com&gt;
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 }