942 lines
35 KiB
Java
942 lines
35 KiB
Java
/*
|
|
* Conditions Of Use
|
|
*
|
|
* This software was developed by employees of the National Institute of
|
|
* Standards and Technology (NIST), an agency of the Federal Government.
|
|
* Pursuant to title 15 Untied States Code Section 105, works of NIST
|
|
* employees are not subject to copyright protection in the United States
|
|
* and are considered to be in the public domain. As a result, a formal
|
|
* license is not needed to use the software.
|
|
*
|
|
* This software is provided by NIST as a service and is expressly
|
|
* provided "AS IS." NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
|
|
* OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
|
|
* AND DATA ACCURACY. NIST does not warrant or make any representations
|
|
* regarding the use of the software or the results thereof, including but
|
|
* not limited to the correctness, accuracy, reliability or usefulness of
|
|
* the software.
|
|
*
|
|
* Permission to use this software is contingent upon your acceptance
|
|
* of the terms of this agreement
|
|
*
|
|
* .
|
|
*
|
|
*/
|
|
/*****************************************************************************
|
|
* Product of NIST/ITL Advanced Networking Technologies Division (ANTD). *
|
|
*****************************************************************************/
|
|
|
|
package gov.nist.javax.sip.stack;
|
|
|
|
import gov.nist.core.InternalErrorHandler;
|
|
import gov.nist.core.ServerLogger;
|
|
import gov.nist.core.StackLogger;
|
|
import gov.nist.core.ThreadAuditor;
|
|
import gov.nist.javax.sip.SIPConstants;
|
|
import gov.nist.javax.sip.header.CSeq;
|
|
import gov.nist.javax.sip.header.CallID;
|
|
import gov.nist.javax.sip.header.From;
|
|
import gov.nist.javax.sip.header.RequestLine;
|
|
import gov.nist.javax.sip.header.StatusLine;
|
|
import gov.nist.javax.sip.header.To;
|
|
import gov.nist.javax.sip.header.Via;
|
|
import gov.nist.javax.sip.header.ViaList;
|
|
import gov.nist.javax.sip.message.SIPMessage;
|
|
import gov.nist.javax.sip.message.SIPRequest;
|
|
import gov.nist.javax.sip.message.SIPResponse;
|
|
import gov.nist.javax.sip.parser.ParseExceptionListener;
|
|
import gov.nist.javax.sip.parser.StringMsgParser;
|
|
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.net.DatagramPacket;
|
|
import java.net.DatagramSocket;
|
|
import java.net.InetAddress;
|
|
import java.net.Socket;
|
|
import java.text.ParseException;
|
|
import java.util.HashSet;
|
|
import java.util.Hashtable;
|
|
import java.util.TimerTask;
|
|
|
|
import javax.sip.address.Hop;
|
|
|
|
/*
|
|
* Kim Kirby (Keyvoice) suggested that duplicate checking should be added to the
|
|
* stack (later removed). Lamine Brahimi suggested a single threaded behavior
|
|
* flag be added to this. Niklas Uhrberg suggested that thread pooling support
|
|
* be added to this for performance and resource management. Peter Parnes found
|
|
* a bug with this code that was sending it into an infinite loop when a bad
|
|
* incoming message was parsed. Bug fix by viswashanti.kadiyala@antepo.com.
|
|
* Hagai Sela addded fixes for NAT traversal. Jeroen van Bemmel fixed up for
|
|
* buggy clients (such as windows messenger) and added code to return
|
|
* BAD_REQUEST. David Alique fixed an address recording bug. Jeroen van Bemmel
|
|
* fixed a performance issue where the stack was doing DNS lookups (potentially
|
|
* unnecessary). Ricardo Bora (Natural Convergence ) added code that prevents
|
|
* the stack from exitting when an exception is encountered.
|
|
*
|
|
*/
|
|
|
|
/**
|
|
* This is the UDP Message handler that gets created when a UDP message needs to
|
|
* be processed. The message is processed by creating a String Message parser
|
|
* and invoking it on the message read from the UDP socket. The parsed structure
|
|
* is handed off via a SIP stack request for further processing. This stack
|
|
* structure isolates the message handling logic from the mechanics of sending
|
|
* and recieving messages (which could be either udp or tcp.
|
|
*
|
|
*
|
|
* @author M. Ranganathan <br/>
|
|
*
|
|
*
|
|
*
|
|
* @version 1.2 $Revision: 1.66 $ $Date: 2010/01/14 05:15:49 $
|
|
*/
|
|
public class UDPMessageChannel extends MessageChannel implements
|
|
ParseExceptionListener, Runnable, RawMessageChannel {
|
|
|
|
|
|
/**
|
|
* SIP Stack structure for this channel.
|
|
*/
|
|
protected SIPTransactionStack sipStack;
|
|
|
|
/**
|
|
* The parser we are using for messages received from this channel.
|
|
*/
|
|
protected StringMsgParser myParser;
|
|
|
|
/**
|
|
* Where we got the stuff from
|
|
*/
|
|
private InetAddress peerAddress;
|
|
|
|
private String myAddress;
|
|
|
|
private int peerPacketSourcePort;
|
|
|
|
private InetAddress peerPacketSourceAddress;
|
|
|
|
/**
|
|
* Reciever port -- port of the destination.
|
|
*/
|
|
private int peerPort;
|
|
|
|
/**
|
|
* Protocol to use when talking to receiver (i.e. when sending replies).
|
|
*/
|
|
private String peerProtocol;
|
|
|
|
protected int myPort;
|
|
|
|
private DatagramPacket incomingPacket;
|
|
|
|
private long receptionTime;
|
|
|
|
/*
|
|
* A table that keeps track of when the last pingback was sent to a given remote IP address
|
|
* and port. This is for NAT compensation. This stays in the table for 1 seconds and prevents
|
|
* infinite loop. If a second pingback happens in that period of time, it will be dropped.
|
|
*/
|
|
private Hashtable<String,PingBackTimerTask> pingBackRecord = new Hashtable<String,PingBackTimerTask>();
|
|
|
|
class PingBackTimerTask extends TimerTask {
|
|
String ipAddress;
|
|
int port;
|
|
|
|
public PingBackTimerTask(String ipAddress, int port) {
|
|
this.ipAddress = ipAddress;
|
|
this.port = port;
|
|
pingBackRecord.put(ipAddress + ":" + port, this);
|
|
}
|
|
@Override
|
|
public void run() {
|
|
pingBackRecord.remove(ipAddress + ":" + port);
|
|
}
|
|
@Override
|
|
public int hashCode() {
|
|
return (ipAddress + ":" + port).hashCode();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Constructor - takes a datagram packet and a stack structure Extracts the
|
|
* address of the other from the datagram packet and stashes away the
|
|
* pointer to the passed stack structure.
|
|
*
|
|
* @param stack
|
|
* is the shared SIPStack structure
|
|
* @param messageProcessor
|
|
* is the creating message processor.
|
|
*/
|
|
protected UDPMessageChannel(SIPTransactionStack stack,
|
|
UDPMessageProcessor messageProcessor) {
|
|
super.messageProcessor = messageProcessor;
|
|
this.sipStack = stack;
|
|
|
|
Thread mythread = new Thread(this);
|
|
|
|
this.myAddress = messageProcessor.getIpAddress().getHostAddress();
|
|
this.myPort = messageProcessor.getPort();
|
|
|
|
mythread.setName("UDPMessageChannelThread");
|
|
mythread.setDaemon(true);
|
|
mythread.start();
|
|
|
|
}
|
|
|
|
/**
|
|
* Constructor. We create one of these in order to process an incoming
|
|
* message.
|
|
*
|
|
* @param stack
|
|
* is the SIP sipStack.
|
|
* @param messageProcessor
|
|
* is the creating message processor.
|
|
* @param packet
|
|
* is the incoming datagram packet.
|
|
*/
|
|
protected UDPMessageChannel(SIPTransactionStack stack,
|
|
UDPMessageProcessor messageProcessor, DatagramPacket packet) {
|
|
|
|
this.incomingPacket = packet;
|
|
super.messageProcessor = messageProcessor;
|
|
this.sipStack = stack;
|
|
|
|
this.myAddress = messageProcessor.getIpAddress().getHostAddress();
|
|
this.myPort = messageProcessor.getPort();
|
|
Thread mythread = new Thread(this);
|
|
mythread.setDaemon(true);
|
|
mythread.setName("UDPMessageChannelThread");
|
|
|
|
mythread.start();
|
|
|
|
}
|
|
|
|
/**
|
|
* Constructor. We create one of these when we send out a message.
|
|
*
|
|
* @param targetAddr
|
|
* INET address of the place where we want to send messages.
|
|
* @param port
|
|
* target port (where we want to send the message).
|
|
* @param sipStack
|
|
* our SIP Stack.
|
|
*/
|
|
protected UDPMessageChannel(InetAddress targetAddr, int port,
|
|
SIPTransactionStack sipStack, UDPMessageProcessor messageProcessor) {
|
|
peerAddress = targetAddr;
|
|
peerPort = port;
|
|
peerProtocol = "UDP";
|
|
super.messageProcessor = messageProcessor;
|
|
this.myAddress = messageProcessor.getIpAddress().getHostAddress();
|
|
this.myPort = messageProcessor.getPort();
|
|
this.sipStack = sipStack;
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug("Creating message channel "
|
|
+ targetAddr.getHostAddress() + "/" + port);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run method specified by runnnable.
|
|
*/
|
|
public void run() {
|
|
// Assume no thread pooling (bug fix by spierhj)
|
|
ThreadAuditor.ThreadHandle threadHandle = null;
|
|
|
|
while (true) {
|
|
// Create a new string message parser to parse the list of messages.
|
|
if (myParser == null) {
|
|
myParser = new StringMsgParser();
|
|
myParser.setParseExceptionListener(this);
|
|
}
|
|
// messages that we write out to him.
|
|
DatagramPacket packet;
|
|
|
|
if (sipStack.threadPoolSize != -1) {
|
|
synchronized (((UDPMessageProcessor) messageProcessor).messageQueue) {
|
|
while (((UDPMessageProcessor) messageProcessor).messageQueue
|
|
.isEmpty()) {
|
|
// Check to see if we need to exit.
|
|
if (!((UDPMessageProcessor) messageProcessor).isRunning)
|
|
return;
|
|
try {
|
|
// We're part of a thread pool. Ask the auditor to
|
|
// monitor this thread.
|
|
if (threadHandle == null) {
|
|
threadHandle = sipStack.getThreadAuditor()
|
|
.addCurrentThread();
|
|
}
|
|
|
|
// Send a heartbeat to the thread auditor
|
|
threadHandle.ping();
|
|
|
|
// Wait for packets
|
|
// Note: getPingInterval returns 0 (infinite) if the
|
|
// thread auditor is disabled.
|
|
((UDPMessageProcessor) messageProcessor).messageQueue
|
|
.wait(threadHandle
|
|
.getPingIntervalInMillisecs());
|
|
} catch (InterruptedException ex) {
|
|
if (!((UDPMessageProcessor) messageProcessor).isRunning)
|
|
return;
|
|
}
|
|
}
|
|
packet = (DatagramPacket) ((UDPMessageProcessor) messageProcessor).messageQueue
|
|
.removeFirst();
|
|
|
|
}
|
|
this.incomingPacket = packet;
|
|
} else {
|
|
packet = this.incomingPacket;
|
|
}
|
|
|
|
// Process the packet. Catch and log any exception we may throw.
|
|
try {
|
|
processIncomingDataPacket(packet);
|
|
} catch (Exception e) {
|
|
|
|
sipStack.getStackLogger().logError(
|
|
"Error while processing incoming UDP packet", e);
|
|
}
|
|
|
|
if (sipStack.threadPoolSize == -1) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process an incoming datagram
|
|
*
|
|
* @param packet
|
|
* is the incoming datagram packet.
|
|
*/
|
|
private void processIncomingDataPacket(DatagramPacket packet)
|
|
throws Exception {
|
|
this.peerAddress = packet.getAddress();
|
|
int packetLength = packet.getLength();
|
|
// Read bytes and put it in a eueue.
|
|
byte[] bytes = packet.getData();
|
|
byte[] msgBytes = new byte[packetLength];
|
|
System.arraycopy(bytes, 0, msgBytes, 0, packetLength);
|
|
|
|
// Do debug logging.
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger()
|
|
.logDebug("UDPMessageChannel: processIncomingDataPacket : peerAddress = "
|
|
+ peerAddress.getHostAddress() + "/"
|
|
+ packet.getPort() + " Length = " + packetLength);
|
|
|
|
}
|
|
|
|
SIPMessage sipMessage = null;
|
|
try {
|
|
this.receptionTime = System.currentTimeMillis();
|
|
sipMessage = myParser.parseSIPMessage(msgBytes);
|
|
myParser = null;
|
|
} catch (ParseException ex) {
|
|
myParser = null; // let go of the parser reference.
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug("Rejecting message ! "
|
|
+ new String(msgBytes));
|
|
this.sipStack.getStackLogger().logDebug("error message "
|
|
+ ex.getMessage());
|
|
this.sipStack.getStackLogger().logException(ex);
|
|
}
|
|
|
|
|
|
// JvB: send a 400 response for requests (except ACK)
|
|
// Currently only UDP, @todo also other transports
|
|
String msgString = new String(msgBytes, 0, packetLength);
|
|
if (!msgString.startsWith("SIP/") && !msgString.startsWith("ACK ")) {
|
|
|
|
String badReqRes = createBadReqRes(msgString, ex);
|
|
if (badReqRes != null) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logDebug(
|
|
"Sending automatic 400 Bad Request:");
|
|
sipStack.getStackLogger().logDebug(badReqRes);
|
|
}
|
|
try {
|
|
this.sendMessage(badReqRes.getBytes(), peerAddress,
|
|
packet.getPort(), "UDP", false);
|
|
} catch (IOException e) {
|
|
this.sipStack.getStackLogger().logException(e);
|
|
}
|
|
} else {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack
|
|
.getStackLogger()
|
|
.logDebug(
|
|
"Could not formulate automatic 400 Bad Request");
|
|
}
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
// No parse exception but null message - reject it and
|
|
// march on (or return).
|
|
// exit this message processor if the message did not parse.
|
|
|
|
if (sipMessage == null) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug("Rejecting message ! + Null message parsed.");
|
|
}
|
|
if (pingBackRecord.get(packet.getAddress().getHostAddress() + ":" + packet.getPort()) == null ) {
|
|
byte[] retval = "\r\n\r\n".getBytes();
|
|
DatagramPacket keepalive = new DatagramPacket(retval,0,retval.length,packet.getAddress(),packet.getPort());
|
|
((UDPMessageProcessor)this.messageProcessor).sock.send(keepalive);
|
|
this.sipStack.getTimer().schedule(new PingBackTimerTask(packet.getAddress().getHostAddress(),
|
|
packet.getPort()), 1000);
|
|
}
|
|
return;
|
|
}
|
|
ViaList viaList = sipMessage.getViaHeaders();
|
|
// Check for the required headers.
|
|
if (sipMessage.getFrom() == null || sipMessage.getTo() == null
|
|
|| sipMessage.getCallId() == null
|
|
|| sipMessage.getCSeq() == null
|
|
|| sipMessage.getViaHeaders() == null) {
|
|
String badmsg = new String(msgBytes);
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logError("bad message " + badmsg);
|
|
this.sipStack.getStackLogger().logError(">>> Dropped Bad Msg "
|
|
+ "From = " + sipMessage.getFrom() + "To = "
|
|
+ sipMessage.getTo() + "CallId = "
|
|
+ sipMessage.getCallId() + "CSeq = "
|
|
+ sipMessage.getCSeq() + "Via = "
|
|
+ sipMessage.getViaHeaders());
|
|
}
|
|
return;
|
|
}
|
|
// For a request first via header tells where the message
|
|
// is coming from.
|
|
// For response, just get the port from the packet.
|
|
if (sipMessage instanceof SIPRequest) {
|
|
Via v = (Via) viaList.getFirst();
|
|
Hop hop = sipStack.addressResolver.resolveAddress(v.getHop());
|
|
this.peerPort = hop.getPort();
|
|
this.peerProtocol = v.getTransport();
|
|
|
|
this.peerPacketSourceAddress = packet.getAddress();
|
|
this.peerPacketSourcePort = packet.getPort();
|
|
try {
|
|
this.peerAddress = packet.getAddress();
|
|
// Check to see if the received parameter matches
|
|
// the peer address and tag it appropriately.
|
|
|
|
|
|
boolean hasRPort = v.hasParameter(Via.RPORT);
|
|
if (hasRPort
|
|
|| !hop.getHost().equals(
|
|
this.peerAddress.getHostAddress())) {
|
|
v.setParameter(Via.RECEIVED, this.peerAddress
|
|
.getHostAddress());
|
|
}
|
|
|
|
if (hasRPort) {
|
|
v.setParameter(Via.RPORT, Integer
|
|
.toString(this.peerPacketSourcePort));
|
|
}
|
|
} catch (java.text.ParseException ex1) {
|
|
InternalErrorHandler.handleException(ex1);
|
|
}
|
|
|
|
} else {
|
|
|
|
this.peerPacketSourceAddress = packet.getAddress();
|
|
this.peerPacketSourcePort = packet.getPort();
|
|
this.peerAddress = packet.getAddress();
|
|
this.peerPort = packet.getPort();
|
|
this.peerProtocol = ((Via) viaList.getFirst()).getTransport();
|
|
}
|
|
|
|
this.processMessage(sipMessage);
|
|
|
|
}
|
|
|
|
/**
|
|
* Actually proces the parsed message.
|
|
*
|
|
* @param sipMessage
|
|
*/
|
|
public void processMessage(SIPMessage sipMessage) {
|
|
|
|
if (sipMessage instanceof SIPRequest) {
|
|
SIPRequest sipRequest = (SIPRequest) sipMessage;
|
|
|
|
// This is a request - process it.
|
|
// So far so good -- we will commit this message if
|
|
// all processing is OK.
|
|
if (sipStack.getStackLogger().isLoggingEnabled(ServerLogger.TRACE_MESSAGES)) {
|
|
|
|
this.sipStack.serverLogger.logMessage(sipMessage, this
|
|
.getPeerHostPort().toString(), this.getHost() + ":"
|
|
+ this.myPort, false, receptionTime);
|
|
|
|
}
|
|
ServerRequestInterface sipServerRequest = sipStack
|
|
.newSIPServerRequest(sipRequest, this);
|
|
// Drop it if there is no request returned
|
|
if (sipServerRequest == null) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger()
|
|
.logWarning("Null request interface returned -- dropping request");
|
|
}
|
|
|
|
|
|
return;
|
|
}
|
|
if (sipStack.isLoggingEnabled())
|
|
this.sipStack.getStackLogger().logDebug("About to process "
|
|
+ sipRequest.getFirstLine() + "/" + sipServerRequest);
|
|
try {
|
|
sipServerRequest.processRequest(sipRequest, this);
|
|
} finally {
|
|
if (sipServerRequest instanceof SIPTransaction) {
|
|
SIPServerTransaction sipServerTx = (SIPServerTransaction) sipServerRequest;
|
|
if (!sipServerTx.passToListener()) {
|
|
((SIPTransaction) sipServerRequest).releaseSem();
|
|
}
|
|
}
|
|
}
|
|
if (sipStack.isLoggingEnabled())
|
|
this.sipStack.getStackLogger().logDebug("Done processing "
|
|
+ sipRequest.getFirstLine() + "/" + sipServerRequest);
|
|
|
|
// So far so good -- we will commit this message if
|
|
// all processing is OK.
|
|
|
|
} else {
|
|
// Handle a SIP Reply message.
|
|
SIPResponse sipResponse = (SIPResponse) sipMessage;
|
|
try {
|
|
sipResponse.checkHeaders();
|
|
} catch (ParseException ex) {
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger()
|
|
.logError("Dropping Badly formatted response message >>> "
|
|
+ sipResponse);
|
|
return;
|
|
}
|
|
ServerResponseInterface sipServerResponse = sipStack
|
|
.newSIPServerResponse(sipResponse, this);
|
|
if (sipServerResponse != null) {
|
|
try {
|
|
if (sipServerResponse instanceof SIPClientTransaction
|
|
&& !((SIPClientTransaction) sipServerResponse)
|
|
.checkFromTag(sipResponse)) {
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger()
|
|
.logError("Dropping response message with invalid tag >>> "
|
|
+ sipResponse);
|
|
return;
|
|
}
|
|
|
|
sipServerResponse.processResponse(sipResponse, this);
|
|
} finally {
|
|
if (sipServerResponse instanceof SIPTransaction
|
|
&& !((SIPTransaction) sipServerResponse)
|
|
.passToListener())
|
|
((SIPTransaction) sipServerResponse).releaseSem();
|
|
}
|
|
|
|
// Normal processing of message.
|
|
} else {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug("null sipServerResponse!");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
/**
|
|
* JvB: added method to check for known buggy clients (Windows Messenger) to
|
|
* fix the port to which responses are sent
|
|
*
|
|
* checks for User-Agent: RTC/1.3.5470 (Messenger 5.1.0701)
|
|
*
|
|
* JvB 22/7/2006 better to take this out for the moment, it is only a
|
|
* problem in rare cases (unregister)
|
|
*
|
|
* private final boolean isBuggyClient( SIPRequest r ) { UserAgent uah =
|
|
* (UserAgent) r.getHeader( UserAgent.NAME ); if (uah!=null) {
|
|
* java.util.ListIterator i = uah.getProduct(); if (i.hasNext()) { String p =
|
|
* (String) uah.getProduct().next(); return p.startsWith( "RTC" ); } }
|
|
* return false; }
|
|
*/
|
|
|
|
/**
|
|
* Implementation of the ParseExceptionListener interface.
|
|
*
|
|
* @param ex
|
|
* Exception that is given to us by the parser.
|
|
* @throws ParseException
|
|
* If we choose to reject the header or message.
|
|
*/
|
|
public void handleException(ParseException ex, SIPMessage sipMessage,
|
|
Class hdrClass, String header, String message)
|
|
throws ParseException {
|
|
if (sipStack.isLoggingEnabled())
|
|
this.sipStack.getStackLogger().logException(ex);
|
|
// Log the bad message for later reference.
|
|
if ((hdrClass != null)
|
|
&& (hdrClass.equals(From.class) || hdrClass.equals(To.class)
|
|
|| hdrClass.equals(CSeq.class)
|
|
|| hdrClass.equals(Via.class)
|
|
|| hdrClass.equals(CallID.class)
|
|
|| hdrClass.equals(RequestLine.class) || hdrClass
|
|
.equals(StatusLine.class))) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
sipStack.getStackLogger().logError("BAD MESSAGE!");
|
|
sipStack.getStackLogger().logError(message);
|
|
}
|
|
throw ex;
|
|
} else {
|
|
sipMessage.addUnparsed(header);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return a reply from a pre-constructed reply. This sends the message back
|
|
* to the entity who caused us to create this channel in the first place.
|
|
*
|
|
* @param sipMessage
|
|
* Message string to send.
|
|
* @throws IOException
|
|
* If there is a problem with sending the message.
|
|
*/
|
|
public void sendMessage(SIPMessage sipMessage) throws IOException {
|
|
if (sipStack.isLoggingEnabled() && this.sipStack.isLogStackTraceOnMessageSend()) {
|
|
if ( sipMessage instanceof SIPRequest &&
|
|
((SIPRequest)sipMessage).getRequestLine() != null) {
|
|
/*
|
|
* We dont want to log empty trace messages.
|
|
*/
|
|
this.sipStack.getStackLogger().logStackTrace(StackLogger.TRACE_INFO);
|
|
} else {
|
|
this.sipStack.getStackLogger().logStackTrace(StackLogger.TRACE_INFO);
|
|
}
|
|
}
|
|
|
|
// Test and see where we are going to send the messsage. If the message
|
|
// is sent back to oursleves, just
|
|
// shortcircuit processing.
|
|
long time = System.currentTimeMillis();
|
|
try {
|
|
for (MessageProcessor messageProcessor : sipStack
|
|
.getMessageProcessors()) {
|
|
if (messageProcessor.getIpAddress().equals(this.peerAddress)
|
|
&& messageProcessor.getPort() == this.peerPort
|
|
&& messageProcessor.getTransport().equals(
|
|
this.peerProtocol)) {
|
|
MessageChannel messageChannel = messageProcessor
|
|
.createMessageChannel(this.peerAddress,
|
|
this.peerPort);
|
|
if (messageChannel instanceof RawMessageChannel) {
|
|
((RawMessageChannel) messageChannel)
|
|
.processMessage(sipMessage);
|
|
if (sipStack.isLoggingEnabled())
|
|
sipStack.getStackLogger().logDebug("Self routing message");
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
byte[] msg = sipMessage.encodeAsBytes( this.getTransport() );
|
|
|
|
sendMessage(msg, peerAddress, peerPort, peerProtocol,
|
|
sipMessage instanceof SIPRequest);
|
|
|
|
} catch (IOException ex) {
|
|
throw ex;
|
|
} catch (Exception ex) {
|
|
sipStack.getStackLogger().logError("An exception occured while sending message",ex);
|
|
throw new IOException(
|
|
"An exception occured while sending message");
|
|
} finally {
|
|
if (sipStack.getStackLogger().isLoggingEnabled(ServerLogger.TRACE_MESSAGES) && !sipMessage.isNullRequest())
|
|
logMessage(sipMessage, peerAddress, peerPort, time);
|
|
else if (sipStack.getStackLogger().isLoggingEnabled(ServerLogger.TRACE_DEBUG))
|
|
sipStack.getStackLogger().logDebug("Sent EMPTY Message");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a message to a specified receiver address.
|
|
*
|
|
* @param msg
|
|
* string to send.
|
|
* @param peerAddress
|
|
* Address of the place to send it to.
|
|
* @param peerPort
|
|
* the port to send it to.
|
|
* @throws IOException
|
|
* If there is trouble sending this message.
|
|
*/
|
|
protected void sendMessage(byte[] msg, InetAddress peerAddress,
|
|
int peerPort, boolean reConnect) throws IOException {
|
|
// Via is not included in the request so silently drop the reply.
|
|
if (sipStack.isLoggingEnabled() && this.sipStack.isLogStackTraceOnMessageSend() ) {
|
|
this.sipStack.getStackLogger().logStackTrace(StackLogger.TRACE_INFO);
|
|
}
|
|
if (peerPort == -1) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug(getClass().getName()
|
|
+ ":sendMessage: Dropping reply!");
|
|
}
|
|
throw new IOException("Receiver port not set ");
|
|
} else {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug("sendMessage " + peerAddress.getHostAddress() + "/"
|
|
+ peerPort + "\n" + "messageSize = " + msg.length + " message = " + new String(msg)) ;
|
|
this.sipStack.getStackLogger().logDebug("*******************\n");
|
|
}
|
|
|
|
}
|
|
DatagramPacket reply = new DatagramPacket(msg, msg.length, peerAddress,
|
|
peerPort);
|
|
try {
|
|
DatagramSocket sock;
|
|
boolean created = false;
|
|
|
|
if (sipStack.udpFlag) {
|
|
// Use the socket from the message processor (for firewall
|
|
// support use the same socket as the message processor
|
|
// socket -- feature request # 18 from java.net). This also
|
|
// makes the whole thing run faster!
|
|
sock = ((UDPMessageProcessor) messageProcessor).sock;
|
|
|
|
// Bind the socket to the stack address in case there
|
|
// are multiple interfaces on the machine (feature reqeust
|
|
// by Will Scullin) 0 binds to an ephemeral port.
|
|
// sock = new DatagramSocket(0,sipStack.stackInetAddress);
|
|
} else {
|
|
// bind to any interface and port.
|
|
sock = new DatagramSocket();
|
|
created = true;
|
|
}
|
|
sock.send(reply);
|
|
if (created)
|
|
sock.close();
|
|
} catch (IOException ex) {
|
|
throw ex;
|
|
} catch (Exception ex) {
|
|
InternalErrorHandler.handleException(ex);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a message to a specified receiver address.
|
|
*
|
|
* @param msg
|
|
* message string to send.
|
|
* @param peerAddress
|
|
* Address of the place to send it to.
|
|
* @param peerPort
|
|
* the port to send it to.
|
|
* @param peerProtocol
|
|
* protocol to use to send.
|
|
* @throws IOException
|
|
* If there is trouble sending this message.
|
|
*/
|
|
protected void sendMessage(byte[] msg, InetAddress peerAddress,
|
|
int peerPort, String peerProtocol, boolean retry)
|
|
throws IOException {
|
|
// Via is not included in the request so silently drop the reply.
|
|
if (peerPort == -1) {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug(getClass().getName()
|
|
+ ":sendMessage: Dropping reply!");
|
|
}
|
|
throw new IOException("Receiver port not set ");
|
|
} else {
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug( ":sendMessage " + peerAddress.getHostAddress() + "/"
|
|
+ peerPort + "\n" + " messageSize = " + msg.length);
|
|
}
|
|
}
|
|
if (peerProtocol.compareToIgnoreCase("UDP") == 0) {
|
|
DatagramPacket reply = new DatagramPacket(msg, msg.length,
|
|
peerAddress, peerPort);
|
|
|
|
try {
|
|
DatagramSocket sock;
|
|
if (sipStack.udpFlag) {
|
|
sock = ((UDPMessageProcessor) messageProcessor).sock;
|
|
|
|
} else {
|
|
// bind to any interface and port.
|
|
sock = sipStack.getNetworkLayer().createDatagramSocket();
|
|
}
|
|
if (sipStack.isLoggingEnabled()) {
|
|
this.sipStack.getStackLogger().logDebug("sendMessage "
|
|
+ peerAddress.getHostAddress() + "/" + peerPort
|
|
+ "\n" + new String(msg));
|
|
}
|
|
sock.send(reply);
|
|
if (!sipStack.udpFlag)
|
|
sock.close();
|
|
} catch (IOException ex) {
|
|
throw ex;
|
|
} catch (Exception ex) {
|
|
InternalErrorHandler.handleException(ex);
|
|
}
|
|
|
|
} else {
|
|
// Use TCP to talk back to the sender.
|
|
Socket outputSocket = sipStack.ioHandler.sendBytes(
|
|
this.messageProcessor.getIpAddress(), peerAddress,
|
|
peerPort, "tcp", msg, retry,this);
|
|
OutputStream myOutputStream = outputSocket.getOutputStream();
|
|
myOutputStream.write(msg, 0, msg.length);
|
|
myOutputStream.flush();
|
|
// The socket is cached (dont close it!);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* get the stack pointer.
|
|
*
|
|
* @return The sip stack for this channel.
|
|
*/
|
|
public SIPTransactionStack getSIPStack() {
|
|
return sipStack;
|
|
}
|
|
|
|
/**
|
|
* Return a transport string.
|
|
*
|
|
* @return the string "udp" in this case.
|
|
*/
|
|
public String getTransport() {
|
|
return SIPConstants.UDP;
|
|
}
|
|
|
|
/**
|
|
* get the stack address for the stack that received this message.
|
|
*
|
|
* @return The stack address for our sipStack.
|
|
*/
|
|
public String getHost() {
|
|
return messageProcessor.getIpAddress().getHostAddress();
|
|
}
|
|
|
|
/**
|
|
* get the port.
|
|
*
|
|
* @return Our port (on which we are getting datagram packets).
|
|
*/
|
|
public int getPort() {
|
|
return ((UDPMessageProcessor) messageProcessor).getPort();
|
|
}
|
|
|
|
/**
|
|
* get the name (address) of the host that sent me the message
|
|
*
|
|
* @return The name of the sender (from the datagram packet).
|
|
*/
|
|
public String getPeerName() {
|
|
return peerAddress.getHostName();
|
|
}
|
|
|
|
/**
|
|
* get the address of the host that sent me the message
|
|
*
|
|
* @return The senders ip address.
|
|
*/
|
|
public String getPeerAddress() {
|
|
return peerAddress.getHostAddress();
|
|
}
|
|
|
|
protected InetAddress getPeerInetAddress() {
|
|
return peerAddress;
|
|
}
|
|
|
|
/**
|
|
* Compare two UDP Message channels for equality.
|
|
*
|
|
* @param other
|
|
* The other message channel with which to compare oursleves.
|
|
*/
|
|
public boolean equals(Object other) {
|
|
|
|
if (other == null)
|
|
return false;
|
|
boolean retval;
|
|
if (!this.getClass().equals(other.getClass())) {
|
|
retval = false;
|
|
} else {
|
|
UDPMessageChannel that = (UDPMessageChannel) other;
|
|
retval = this.getKey().equals(that.getKey());
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
public String getKey() {
|
|
return getKey(peerAddress, peerPort, "UDP");
|
|
}
|
|
|
|
public int getPeerPacketSourcePort() {
|
|
return peerPacketSourcePort;
|
|
}
|
|
|
|
public InetAddress getPeerPacketSourceAddress() {
|
|
return peerPacketSourceAddress;
|
|
}
|
|
|
|
/**
|
|
* Get the logical originator of the message (from the top via header).
|
|
*
|
|
* @return topmost via header sentby field
|
|
*/
|
|
public String getViaHost() {
|
|
return this.myAddress;
|
|
}
|
|
|
|
/**
|
|
* Get the logical port of the message orginator (from the top via hdr).
|
|
*
|
|
* @return the via port from the topmost via header.
|
|
*/
|
|
public int getViaPort() {
|
|
return this.myPort;
|
|
}
|
|
|
|
/**
|
|
* Returns "false" as this is an unreliable transport.
|
|
*/
|
|
public boolean isReliable() {
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* UDP is not a secure protocol.
|
|
*/
|
|
public boolean isSecure() {
|
|
return false;
|
|
}
|
|
|
|
public int getPeerPort() {
|
|
return peerPort;
|
|
}
|
|
|
|
public String getPeerProtocol() {
|
|
return this.peerProtocol;
|
|
}
|
|
|
|
/**
|
|
* Close the message channel.
|
|
*/
|
|
public void close() {
|
|
}
|
|
|
|
|
|
}
|