Source code: com/opencloud/slee/services/sip/common/AbstractSipProxy.java
1 package com.opencloud.slee.services.sip.common;
2
3
4 import javax.sip.message.Request;
5 import javax.sip.message.Response;
6 import javax.sip.address.URI;
7 import javax.sip.address.Address;
8 import javax.sip.address.SipURI;
9 import javax.sip.*;
10 import javax.sip.header.*;
11
12 import com.opencloud.slee.services.sip.registrar.LocationServiceException;
13 import com.opencloud.slee.services.sip.registrar.RegistrationBinding;
14
15 import java.util.*;
16
17 /**
18 * A template class that does much of what a typical SIP proxy would do,
19 * according to RFC3261 section 16.
20 *
21 * Applications should override the methods called by processRequest and
22 * processResponse to implement specific features.
23 */
24 public abstract class AbstractSipProxy extends MessageHandler {
25
26 public AbstractSipProxy(SipServerConfig config) {
27 super(config);
28 }
29
30 /**
31 * This method defines the default behaviour for a proxy procesing a request.
32 * Implementations should override as appropriate to customize proxy behaviour.
33 *
34 * Steps in processing a request, from RFC3261:
35 * 16.3 Request Validation
36 * 16.4 Route Information Preprocessing
37 * 16.5 Determining Request Targets
38 * 16.6 Request Forwarding
39 * 1. Copy request
40 * 2. Set Request-URI
41 * 3. Decrement Max-Forwards
42 * 4. Add Record-Route
43 * 5. Add Additional Header Fields
44 * 6. Postprocess routing information
45 * 7. Determine Next-Hop Address, Port and Transport
46 * 8. Add a Via header field value
47 * 9. Add a Content-Leangth header field if necessary
48 * 10. Forward Request
49 * 11. Set timer C
50 */
51 public void processRequest(ServerTransaction txn, Request request) {
52 //System.out.println("processRequest: tid = " + tid + ", request: \n" + request.toString());
53 try {
54 Request newRequest = (Request)request.clone();
55
56 // 16.3 Request Validation
57 validateRequest(txn, newRequest);
58
59 // 16.4 Route Information Preprocessing
60 routePreProcess(newRequest);
61
62 // 16.5 Determining Request Targets
63 List targets = determineRequestTargets(newRequest);
64
65 Iterator it = targets.iterator();
66 while (it.hasNext()) {
67 URI target = (URI)it.next();
68 System.err.println("SIP Proxy Forwarding: " + request.getMethod() + " to URI target: "+target);
69 // 16.6 Request Forwarding
70 // 1. Copy request
71
72 // 2. Request-URI
73 newRequest.setRequestURI(target);
74
75 // *NEW* CANCEL processing
76 // CANCELs are hop-by-hop, so here must remove any existing Via headers,
77 // Record-Route headers. We insert Via header below so we will get response.
78 if (newRequest.getMethod().equals(Request.CANCEL)) {
79 newRequest.removeHeader(ViaHeader.NAME);
80 newRequest.removeHeader(RecordRouteHeader.NAME);
81 }
82 else {
83 // 3. Max-Forwards
84 decrementMaxForwards(newRequest);
85 // 4. Record-Route
86 addRecordRouteHeader(newRequest);
87 }
88
89 // 5. Add Additional Header Fields
90 // TBD
91 // 6. Postprocess routing information
92 // TBD
93 // 7. Determine Next-Hop Address, Port and Transport
94 // TBD
95
96 // 8. Add a Via header field value
97 addViaHeader(newRequest);
98
99 // 9. Add a Content-Leangth header field if necessary
100 //TBD
101
102 // 10. Forward Request
103 forwardRequest(txn, newRequest);
104
105 // 11. Set timer C
106 // TBD
107 }
108
109 } catch (SipSendErrorResponseException se) {
110 int statusCode = se.getStatusCode();
111 sendErrorResponse(txn, request, statusCode);
112 } catch (Exception e) {
113 e.printStackTrace();
114 }
115 }
116
117 public void sendErrorResponse(ServerTransaction txn, Request request, int statusCode) {
118 try {
119 Response response = config.getMessageFactory().createResponse(statusCode, request);
120 txn.sendResponse(response);
121 } catch (Exception e) {
122 e.printStackTrace();
123 }
124 }
125
126 /**
127 * Forwards request on to destination. This method should be overridden to
128 * preserve state (eg. client txn -> server txn mapping) by proxy implementations
129 */
130 public void forwardRequest(ServerTransaction txn, Request request) {
131 // simplest case - just send request - no state saved
132 try {
133 config.getSipProvider().sendRequest(request);
134 } catch (Exception e) {
135 e.printStackTrace();
136 }
137 }
138
139 /**
140 * Performs request validation as per RFC 3261 section 16.3. If a request fails
141 * validation, throw exception to cause appropriate error response to client.
142 *
143 * @param txn the server transaction of the request.
144 * @param request the SIP request to be validated.
145 */
146 public void validateRequest(ServerTransaction txn, Request request) throws SipSendErrorResponseException {
147 // 1. Reasonable syntax
148
149 // 2. URI scheme
150 URI requestURI = null;
151 requestURI = request.getRequestURI();
152
153 boolean supportedURIScheme = false;
154 supportedURIScheme = isSupportedURIScheme(requestURI);
155
156 if (!supportedURIScheme) {
157 throw new SipSendErrorResponseException("Unsupported URI scheme", Response.UNSUPPORTED_URI_SCHEME);
158 }
159
160 // 3. Max-Forwards
161 checkMaxForwards(txn, request);
162
163 // 4. Loop Detection - TBD
164 // 5. Proxy-Require - TBD
165 // 6. Proxy-Authorization - TBD
166 }
167
168 /**
169 * Validate the max-forwards header throw a user error exception (too many hops) if max-forwards reaches 0.
170 * @param txn
171 * @param request
172 * @throws SipSendErrorResponseException
173 */
174 public void checkMaxForwards(ServerTransaction txn, Request request) throws SipSendErrorResponseException {
175 MaxForwardsHeader mfh = (MaxForwardsHeader) request.getHeader(MaxForwardsHeader.NAME);
176 if (mfh == null) return;
177
178 int maxForwards = 0;
179 maxForwards = ((MaxForwardsHeader) request.getHeader(MaxForwardsHeader.NAME)).getMaxForwards();
180
181 if (maxForwards > 0) {
182 return;
183 }
184 else {
185 // MAY respond to OPTIONS, otherwise return 483 Too Many Hops
186 throw new SipSendErrorResponseException("Too many hops", Response.TOO_MANY_HOPS);
187 }
188
189 }
190
191 /**
192 * Attempts to find a locally registered contact address for the given URI, using
193 * the location service interface.
194 */
195 public URI findLocalTarget(URI uri) throws SipSendErrorResponseException {
196 String addressOfRecord = uri.toString();
197
198 Map bindings = null;
199 try {
200 bindings = config.getLocationService().getBindings(addressOfRecord);
201 } catch (LocationServiceException lse) {
202 lse.printStackTrace();
203 }
204
205 if (bindings == null) {
206 throw new SipSendErrorResponseException("User not found", Response.NOT_FOUND);
207 }
208 if (bindings.isEmpty()) {
209 throw new SipSendErrorResponseException("User temporarily unavailable", Response.TEMPORARILY_UNAVAILABLE);
210 }
211
212 Iterator it = bindings.values().iterator();
213 URI target = null;
214 while (it.hasNext()) {
215 RegistrationBinding binding = (RegistrationBinding)it.next();
216 System.out.println("BINDINGS: " + binding);
217 ContactHeader header = binding.getContactHeader(config.getAddressFactory(), config.getHeaderFactory());
218 System.out.println("CONTACT HEADER: " + header);
219 if (header == null) { // entry expired
220 continue; // see if there are any more contacts...
221 }
222 Address na = header.getAddress();
223 System.out.println("Address: " + na);
224 target = na.getURI();
225 break;
226 }
227 if (target == null) {
228 System.err.println("findLocalTarget: No contacts for " + addressOfRecord + " found.");
229 throw new SipSendErrorResponseException("User temporarily unavailable", Response.TEMPORARILY_UNAVAILABLE);
230 }
231 return target;
232 }
233
234 /**
235 * Adds a default Via header to the request. Override to provide a different
236 * Via header.
237 */
238 public void addViaHeader(Request request) {
239 try {
240 ViaHeader via = config.getHeaderFactory().createViaHeader(config.getHostname(), config.getPort(), "UDP", null);
241 request.addHeader(via);
242
243 } catch (Exception e) {
244 e.printStackTrace();
245 }
246
247 }
248
249 /**
250 * Adds a default Record-Route header to the request. Override to provide a different
251 * Record-Route header.
252 */
253 public void addRecordRouteHeader(Request request) {
254 try {
255 // Add our record-route header to list...
256 SipURI myURI = config.getAddressFactory().createSipURI(null, config.getHostname());
257 myURI.setPort(config.getPort());
258 myURI.setMAddrParam(config.getHostname());
259 myURI.setTransportParam(config.getTransport());
260 Address myName = config.getAddressFactory().createAddress(myURI);
261 RecordRouteHeader myHeader = config.getHeaderFactory().createRecordRouteHeader(myName);
262 request.addHeader(myHeader);
263
264 } catch (Exception e) {
265 e.printStackTrace();
266 }
267
268 }
269
270 /**
271 * Decrement the value of max-forwards. If no max-forwards header present, create a max-forwards header with the
272 * RFC3261 recommended default value
273 * @param request
274 * @throws SipSendErrorResponseException
275 */
276 public void decrementMaxForwards(Request request) throws SipSendErrorResponseException {
277 MaxForwardsHeader max = (MaxForwardsHeader) request.getHeader(MaxForwardsHeader.NAME);
278 try {
279 if (max == null) {
280 // add max-forwards with default 70 hops
281 max = config.getHeaderFactory().createMaxForwardsHeader(70);
282 request.setHeader(max);
283 }
284 else {
285 // decrement max-forwards
286 int maxForwards = max.getMaxForwards();
287 maxForwards--;
288 max.setMaxForwards(maxForwards);
289 request.setHeader(max);
290 }
291 } catch (Exception e) {
292 throw new SipSendErrorResponseException("Error updating max-forwards", Response.SERVER_INTERNAL_ERROR);
293 }
294 }
295
296 /**
297 * Check for strict-routing style route headers and swap with Request-URI if
298 * applicable.
299 */
300 public void routePreProcess(Request request) throws SipSendErrorResponseException {
301 URI requestURI = null;
302 requestURI = request.getRequestURI();
303
304 if ((requestURI.isSipURI()) &&
305 (((SipURI)requestURI).getUser() == null) &&
306 (((SipURI)requestURI).getHost().equalsIgnoreCase(config.getHostname()))) {
307 // client is a strict router, replace request-URI with last
308 // value in Route header field...
309 try {
310 ListIterator it = request.getHeaders(RouteHeader.NAME);
311 LinkedList l = new LinkedList();
312 // need last value in list
313 while (it.hasNext()) {
314 RouteHeader r = (RouteHeader)it.next();
315 l.add(r);
316 }
317 RouteHeader route = (RouteHeader)l.getLast();
318
319 l.removeLast(); // Remove the last route header from the list, possibly leaving an empty list
320
321 request.removeHeader(RouteHeader.NAME); // Remove all route headers from the message
322
323 // Re-add the remaining headers to the message
324 for (int i = 0; i < l.size(); i++) {
325 RouteHeader routeHeader = (RouteHeader) l.get(i);
326 request.addHeader(routeHeader);
327 }
328
329 URI newURI = route.getAddress().getURI();
330 request.setRequestURI(newURI);
331
332 } catch (Exception e) {
333 e.printStackTrace();
334 throw new SipSendErrorResponseException("Error updating route headers", Response.SERVER_INTERNAL_ERROR);
335
336 }
337 }
338 // From RFC3261 16.4:
339 // If the first value in the Route header field indicates this proxy,
340 // the proxy MUST remove that value from the request.
341 Iterator routeHeaders = request.getHeaders(RouteHeader.NAME);
342 if (routeHeaders.hasNext()) {
343 RouteHeader r = (RouteHeader) routeHeaders.next();
344 // is this route header for our hostname & port?
345 URI uri = r.getAddress().getURI();
346 if (uri.isSipURI()) {
347 SipURI sipURI = (SipURI) uri;
348 int uriPort = sipURI.getPort();
349 if (uriPort <= 0) uriPort = 5060; // WARNING: Assumes stack impl returns <= 0 if port not specified in URI
350 // JAIN SIP does not specify but NIST SIP returns -1
351 if ((sipURI.getHost().equalsIgnoreCase(config.getHostname())) &&
352 (uriPort == config.getPort())) {
353 // remove this route header
354 routeHeaders.remove();
355 // if this was the last one, remove the header entirely (why isn't this automatic?)
356 if (!routeHeaders.hasNext()) request.removeHeader(RouteHeader.NAME);
357 }
358 }
359 }
360
361
362 }
363
364 /**
365 * Determines target SIP URI(s) for request, using location service or
366 * other criteria.
367 *
368 * TODO: Forking (return more than one target)
369 *
370 * @param request the SIP request being forwarded
371 * @return a list of URIs
372 */
373 public List determineRequestTargets(Request request) throws SipSendErrorResponseException {
374 LinkedList targets = new LinkedList();
375
376 URI requestURI = null;
377 URI target = null;
378 boolean localDomain = false;
379 requestURI = request.getRequestURI();
380 localDomain = isLocalDomain(requestURI);
381
382 if (localDomain) {
383 // determine local SIP target(s) using location service etc
384 target = findLocalTarget(requestURI);
385 if (target == null) { // not found (or not currently registered)
386 throw new SipSendErrorResponseException("User not registered", Response.TEMPORARILY_UNAVAILABLE);
387 }
388 }
389 else {
390 // destination addr is outside our domain
391 target = requestURI;
392 }
393 targets.add(target);
394 return targets;
395 }
396
397 /**
398 * Generic proxy response processing
399 */
400 public void processResponse(ServerTransaction serverTransaction, Response response) {
401
402 try {
403
404 Response newResponse = (Response)response.clone();
405
406 // 16.7 Response Processing
407 // 1. Find appropriate response context
408
409 // 2. Update timer C for provisional responses
410
411 // 3. Remove topmost via
412 Iterator viaHeaderIt = newResponse.getHeaders(ViaHeader.NAME);
413 viaHeaderIt.next();
414 viaHeaderIt.remove();
415 if (!viaHeaderIt.hasNext()) return; // response was meant for this proxy
416
417 // 4. Add the response to the response context
418
419 // 5. Check to see if this response should be forwarded immediately
420 if (newResponse.getStatusCode() == Response.TRYING) {
421 return;
422 }
423
424 // 6. When necessary, choose the best final response from the response context
425
426 // 7. Aggregate authorization header fields if necessary
427
428 // 8. Optionally rewrite Record-Route header field values
429
430 // 9. Forward the response
431 forwardResponse(serverTransaction, newResponse);
432
433 // 10. Generate any necessary CANCEL requests
434
435 } catch (Exception e) {
436 e.printStackTrace();
437 }
438 }
439
440 public void forwardResponse(ServerTransaction txn, Response response) {
441 try {
442 if (txn != null) {
443 txn.sendResponse(response);
444 }
445 else {
446 // forward statelessly anyway
447 config.getSipProvider().sendResponse(response);
448 }
449 } catch (Exception e) {
450 e.printStackTrace();
451 }
452 }
453
454
455 }