001: package examples.subsnotify;
002:
003: import javax.sip.*;
004: import javax.sip.address.*;
005: import javax.sip.header.*;
006: import javax.sip.message.*;
007:
008: import org.apache.log4j.ConsoleAppender;
009: import org.apache.log4j.FileAppender;
010: import org.apache.log4j.Level;
011: import org.apache.log4j.Logger;
012: import org.apache.log4j.SimpleLayout;
013:
014: import java.text.ParseException;
015:
016: import java.util.*;
017:
018: /**
019: * This implements a simple forking proxy to test proper handling of multiple
020: * NOTIFYs. An initial SUBSCRIBE request (i.e. without to-tag) is forked two
021: * times to the same destination. Each response should have a different to-tag;
022: * this proxy only passes the first 2xx response through, and discards the
023: * second
024: *
025: * NOTIFYs should go directly to the Contact announced in the SUBSCRIBE, hence
026: * this proxy won't see them
027: *
028: * @author Jeroen van Bemmel
029: */
030:
031: public class Forker implements SipListener {
032:
033: private static AddressFactory addressFactory;
034:
035: private static MessageFactory messageFactory;
036:
037: private static HeaderFactory headerFactory;
038:
039: private static SipStack sipStack;
040:
041: private SipProvider udpProvider;
042:
043: /**
044: * Flag to test UAC behavior for non-RFC3261 proxies. In particular, they dont
045: * set the 'lr' flag and perform strict routing, ie replace the request URI
046: * with the topmost Route header
047: */
048: private static boolean nonRFC3261Proxy;
049:
050: private static Logger logger = Logger.getLogger(Forker.class);
051: static {
052: try {
053: logger.setLevel(Level.INFO);
054: logger.addAppender(new ConsoleAppender(new SimpleLayout()));
055: logger.addAppender(new FileAppender(new SimpleLayout(),
056: "forkeroutputlog.txt"));
057: } catch (Exception ex) {
058: throw new RuntimeException(ex);
059: }
060: }
061:
062: private static final String usageString = "java "
063: + "examples.subsnotify.Forker \n"
064: + ">>>> is your class path set to the root?";
065:
066: private static void usage() {
067: logger.info(usageString);
068: System.exit(0);
069:
070: }
071:
072: /**
073: * Adds a suitable Record-Route header to the given request or response
074: *
075: * @param r
076: * @throws ParseException
077: * @throws SipException
078: * @throws
079: */
080: private void recordRoute(Message m, String uniqueId)
081: throws ParseException, SipException {
082: Address me = addressFactory
083: .createAddress("<sip:127.0.0.1:5065;id=" + uniqueId
084: + '>');
085: if (!nonRFC3261Proxy)
086: ((SipURI) me.getURI()).setLrParam();
087: RecordRouteHeader rr = headerFactory
088: .createRecordRouteHeader(me);
089: m.addFirst(rr);
090: }
091:
092: public void processRequest(RequestEvent re) {
093: Request request = re.getRequest();
094: ServerTransaction st = re.getServerTransaction();
095:
096: logger.info("\n\nRequest " + request.getMethod()
097: + " received at " + sipStack.getStackName()
098: + " with server transaction id " + st);
099:
100: try {
101: if (request.getMethod().equals(Request.SUBSCRIBE)) {
102: processSubscribe(re, st);
103: } else if (request.getMethod().equals(Request.NOTIFY)) { // because
104: // of
105: // Record-Routing
106:
107: logger.info("Got NOTIFY, forwarding statelessly...");
108:
109: // Forward it without creating a transaction
110:
111: // RFC3265 says: "proxy MUST record-route the initial SUBSCRIBE
112: // and
113: // any dialog-establishing NOTIFY requests
114: // Use from tag as unique id, for debugging
115: FromHeader from = (FromHeader) request
116: .getHeader(FromHeader.NAME);
117: recordRoute(request, from.getTag());
118:
119: doForwardStateless(request, st);
120: } else {
121: Response notImplemented = messageFactory
122: .createResponse(Response.NOT_IMPLEMENTED,
123: request);
124: ((SipProvider) re.getSource())
125: .sendResponse(notImplemented);
126: }
127: } catch (Exception e) {
128: e.printStackTrace();
129: }
130: }
131:
132: /**
133: * Process the invite request.
134: */
135: public void processSubscribe(RequestEvent re, ServerTransaction st) {
136: Request request = re.getRequest();
137: try {
138: logger
139: .info("forker: got an Subscribe -> forking or forwarding");
140:
141: // Check if it is in-dialog or not
142: ToHeader to = (ToHeader) request.getHeader(ToHeader.NAME);
143: if (to.getTag() == null) {
144: logger
145: .info("forker: got a dialog-creating Subscribe forking twice");
146:
147: if (st == null) {
148: st = ((SipProvider) re.getSource())
149: .getNewServerTransaction(request);
150: }
151:
152: // Subscriber added a Route to us; remove it could check its 'id' here)
153: request.removeFirst(RouteHeader.NAME);
154:
155: doFork(request, st, 5070);
156: doFork(request, st, 5071);
157: } else {
158: System.out
159: .println("forker: got a mid-dialog Subscribe, forwarding statelessly...");
160:
161: // Forward it statelessly
162: doForwardStateless(request, st);
163: }
164: } catch (Exception ex) {
165: ex.printStackTrace();
166: }
167: }
168:
169: /**
170: * Mapping of Via branch IDs to the corresponding ServerTransaction, used
171: * for forwarding responses
172: */
173: private final Map CTtoST = new HashMap();
174:
175: private void doFork(Request orig, ServerTransaction st, int port)
176: throws Exception {
177: ViaHeader myVia = headerFactory.createViaHeader("127.0.0.1",
178: udpProvider.getListeningPoint("udp").getPort(), "udp",
179: null);
180: Request forked = (Request) orig.clone();
181: forked.addHeader(myVia);
182:
183: // Note: BIG Gotcha: Need to do this before creating the
184: // ClientTransaction!
185: if (nonRFC3261Proxy) {
186: SipURI suri = addressFactory
187: .createSipURI(null, "127.0.0.1");
188: suri.setPort(port);
189: forked.setRequestURI(suri);
190: } else {
191: RouteHeader route = headerFactory
192: .createRouteHeader(addressFactory
193: .createAddress("<sip:127.0.0.1;lr>"));
194: ((SipURI) route.getAddress().getURI()).setPort(port);
195: forked.addHeader(route);
196: }
197:
198: // Add a Record-Route header, to test that separate dialog instances are
199: // correctly created at the subscriber
200: // This causes us to receive NOTIFYs too
201: recordRoute(forked, Integer.toString(port));
202:
203: ClientTransaction ct = udpProvider
204: .getNewClientTransaction(forked);
205: CTtoST.put(ct, st);
206: ct.sendRequest(); // gets sent to the outbound proxy == Notifier
207: }
208:
209: private void doForwardStateless(Request orig, ServerTransaction st)
210: throws ParseException, InvalidArgumentException,
211: SipException {
212: // To forward statelessly, we need to keep the stack from
213: // creating a ST for us.
214: // Internally a dialog is created for the SUBSCRIBE, unless
215: // dialog support
216: // XXX bug: if I use branch==null here, the stack assigns a random int
217: // without magic cookie
218: //
219: // Latest wisdom from RFC3261 says to simply copy branch from current top via
220: // when forwarding statelessly
221: //
222: ViaHeader top = (ViaHeader) orig.getHeader(ViaHeader.NAME);
223: ViaHeader myVia = headerFactory.createViaHeader("127.0.0.1",
224: 5065, "udp", top.getBranch());
225: orig.addFirst(myVia);
226:
227: if (nonRFC3261Proxy) {
228: RouteHeader route = (RouteHeader) orig.getHeader("Route");
229: if (route != null) {
230: orig.removeFirst("Route");
231: orig.setRequestURI(route.getAddress().getURI());
232: }
233: } else {
234: orig.removeFirst(RouteHeader.NAME); // points at us
235: }
236:
237: // To forward statelessly, we need to keep the stack from creating a ST for us.
238: // Internally a dialog is created for the SUBSCRIBE, unless dialog support
239: // is switched off (see initialization)
240: if (st != null) {
241: logger
242: .info("Would like to forward statelessly, but ST!=null! Problem...");
243: logger.info("st == " + st);
244:
245: }
246: udpProvider.sendRequest(orig);
247: }
248:
249: public void processResponse(ResponseEvent responseReceivedEvent) {
250: logger.info("Got a response");
251: Response response = (Response) responseReceivedEvent
252: .getResponse();
253: ClientTransaction ct = responseReceivedEvent
254: .getClientTransaction();
255:
256: logger.info("Dialog = " + responseReceivedEvent.getDialog());
257:
258: logger.info("Response received with client transaction id "
259: + ct + ": " + response.getStatusCode());
260:
261: if (ct == null) {
262: logger.info("Assuming NOTIFY response, forwarding...");
263: // NOTIFYs are forwarded without transaction, do the same for their
264: // responses
265: response.removeFirst(ViaHeader.NAME);
266: try {
267: udpProvider.sendResponse(response);
268: } catch (SipException e) {
269: e.printStackTrace();
270: }
271: } else {
272: ServerTransaction st = (ServerTransaction) CTtoST.get(ct);
273: if (st != null) {
274: // only forward the first response
275: synchronized (st) {
276: if (st.getState() == TransactionState.TRYING) {
277: response.removeFirst(ViaHeader.NAME);
278: try {
279: st.sendResponse(response);
280: } catch (SipException e) {
281: e.printStackTrace();
282: } catch (InvalidArgumentException e) {
283: e.printStackTrace();
284: }
285: } else {
286: logger.info("Discarding second response");
287: }
288: CTtoST.remove(ct);
289: }
290: } else {
291: logger.info("No ST found");
292: }
293: }
294: }
295:
296: public void processTimeout(javax.sip.TimeoutEvent timeoutEvent) {
297: Transaction transaction;
298: if (timeoutEvent.isServerTransaction()) {
299: transaction = timeoutEvent.getServerTransaction();
300: } else {
301: transaction = timeoutEvent.getClientTransaction();
302: }
303: logger.info("state = " + transaction.getState());
304: logger.info("Transaction Time out");
305: }
306:
307: private static void initFactories() {
308: SipFactory sipFactory = null;
309: sipStack = null;
310: sipFactory = SipFactory.getInstance();
311: sipFactory.setPathName("gov.nist");
312: Properties properties = new Properties();
313:
314: String name = "forker";
315: if (nonRFC3261Proxy)
316: name += "_nonRFC3261";
317:
318: properties.setProperty("javax.sip.STACK_NAME", name);
319: // You need 16 for logging traces. 32 for debug + traces.
320: // Your code will limp at 32 but it is best for debugging.
321: properties.setProperty("gov.nist.javax.sip.TRACE_LEVEL", "32");
322: properties.setProperty("gov.nist.javax.sip.DEBUG_LOG",
323: "forkerdebug.txt");
324: properties.setProperty("gov.nist.javax.sip.SERVER_LOG",
325: "forkerlog.txt");
326:
327: // Switch OFF automatic dialog support. We dont want dialogs in a proxy!
328: properties.setProperty("javax.sip.AUTOMATIC_DIALOG_SUPPORT",
329: "off");
330: try {
331: // Create SipStack object
332: sipStack = sipFactory.createSipStack(properties);
333: logger.info("sipStack = " + sipStack);
334: } catch (PeerUnavailableException e) {
335: // could not find
336: // gov.nist.jain.protocol.ip.sip.SipStackImpl
337: // in the classpath
338: e.printStackTrace();
339: System.err.println(e.getMessage());
340: if (e.getCause() != null)
341: e.getCause().printStackTrace();
342: System.exit(0);
343: }
344:
345: try {
346: headerFactory = sipFactory.createHeaderFactory();
347: addressFactory = sipFactory.createAddressFactory();
348: messageFactory = sipFactory.createMessageFactory();
349: } catch (Exception ex) {
350: ex.printStackTrace();
351: }
352: }
353:
354: public void init() {
355:
356: try {
357:
358: ListeningPoint lp = sipStack.createListeningPoint(
359: "127.0.0.1", 5065, "udp");
360:
361: this .udpProvider = sipStack.createSipProvider(lp);
362: logger.info("udp provider " + udpProvider);
363:
364: } catch (Exception ex) {
365: logger.error(ex.getMessage());
366: ex.printStackTrace();
367: usage();
368: }
369:
370: }
371:
372: public static void main(String args[]) throws Exception {
373:
374: nonRFC3261Proxy = args.length > 0;
375:
376: initFactories();
377: Forker f = new Forker();
378: f.init();
379: f.udpProvider.addSipListener(f);
380: }
381:
382: public void processIOException(IOExceptionEvent exceptionEvent) {
383: // TODO Auto-generated method stub
384:
385: }
386:
387: public void processTransactionTerminated(
388: TransactionTerminatedEvent transactionTerminatedEvent) {
389: // TODO Auto-generated method stub
390:
391: }
392:
393: public void processDialogTerminated(
394: DialogTerminatedEvent dialogTerminatedEvent) {
395: // TODO Auto-generated method stub
396:
397: }
398:
399: }
|