490 lines
16 KiB
Java
490 lines
16 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.Host;
|
|
import gov.nist.core.HostPort;
|
|
import gov.nist.core.InternalErrorHandler;
|
|
import gov.nist.core.ServerLogger;
|
|
import gov.nist.javax.sip.address.AddressImpl;
|
|
import gov.nist.javax.sip.header.ContentLength;
|
|
import gov.nist.javax.sip.header.ContentType;
|
|
import gov.nist.javax.sip.header.Via;
|
|
import gov.nist.javax.sip.message.MessageFactoryImpl;
|
|
import gov.nist.javax.sip.message.SIPMessage;
|
|
import gov.nist.javax.sip.message.SIPRequest;
|
|
import gov.nist.javax.sip.message.SIPResponse;
|
|
|
|
import java.io.IOException;
|
|
import java.net.InetAddress;
|
|
import java.text.ParseException;
|
|
|
|
import javax.sip.address.Hop;
|
|
import javax.sip.header.CSeqHeader;
|
|
import javax.sip.header.CallIdHeader;
|
|
import javax.sip.header.ContactHeader;
|
|
import javax.sip.header.ContentLengthHeader;
|
|
import javax.sip.header.ContentTypeHeader;
|
|
import javax.sip.header.FromHeader;
|
|
import javax.sip.header.ServerHeader;
|
|
import javax.sip.header.ToHeader;
|
|
import javax.sip.header.ViaHeader;
|
|
|
|
/**
|
|
* Message channel abstraction for the SIP stack.
|
|
*
|
|
* @author M. Ranganathan <br/> Contains additions for support of symmetric NAT contributed by
|
|
* Hagai.
|
|
*
|
|
* @version 1.2 $Revision: 1.28 $ $Date: 2009/11/14 20:06:18 $
|
|
*
|
|
*
|
|
*/
|
|
public abstract class MessageChannel {
|
|
|
|
// Incremented whenever a transaction gets assigned
|
|
// to the message channel and decremented when
|
|
// a transaction gets freed from the message channel.
|
|
protected int useCount;
|
|
|
|
/**
|
|
* Hook method, overridden by subclasses
|
|
*/
|
|
protected void uncache() {}
|
|
|
|
/**
|
|
* Message processor to whom I belong (if set).
|
|
*/
|
|
protected transient MessageProcessor messageProcessor;
|
|
|
|
/**
|
|
* Close the message channel.
|
|
*/
|
|
public abstract void close();
|
|
|
|
/**
|
|
* Get the SIPStack object from this message channel.
|
|
*
|
|
* @return SIPStack object of this message channel
|
|
*/
|
|
public abstract SIPTransactionStack getSIPStack();
|
|
|
|
/**
|
|
* Get transport string of this message channel.
|
|
*
|
|
* @return Transport string of this message channel.
|
|
*/
|
|
public abstract String getTransport();
|
|
|
|
/**
|
|
* Get whether this channel is reliable or not.
|
|
*
|
|
* @return True if reliable, false if not.
|
|
*/
|
|
public abstract boolean isReliable();
|
|
|
|
/**
|
|
* Return true if this is a secure channel.
|
|
*/
|
|
public abstract boolean isSecure();
|
|
|
|
/**
|
|
* Send the message (after it has been formatted)
|
|
*
|
|
* @param sipMessage Message to send.
|
|
*/
|
|
public abstract void sendMessage(SIPMessage sipMessage) throws IOException;
|
|
|
|
/**
|
|
* Get the peer address of the machine that sent us this message.
|
|
*
|
|
* @return a string contianing the ip address or host name of the sender of the message.
|
|
*/
|
|
public abstract String getPeerAddress();
|
|
|
|
protected abstract InetAddress getPeerInetAddress();
|
|
|
|
protected abstract String getPeerProtocol();
|
|
|
|
/**
|
|
* Get the sender port ( the port of the other end that sent me the message).
|
|
*/
|
|
public abstract int getPeerPort();
|
|
|
|
public abstract int getPeerPacketSourcePort();
|
|
|
|
public abstract InetAddress getPeerPacketSourceAddress();
|
|
|
|
/**
|
|
* Generate a key which identifies the message channel. This allows us to cache the message
|
|
* channel.
|
|
*/
|
|
public abstract String getKey();
|
|
|
|
/**
|
|
* Get the host to assign for an outgoing Request via header.
|
|
*/
|
|
public abstract String getViaHost();
|
|
|
|
/**
|
|
* Get the port to assign for the via header of an outgoing message.
|
|
*/
|
|
public abstract int getViaPort();
|
|
|
|
/**
|
|
* Send the message (after it has been formatted), to a specified address and a specified port
|
|
*
|
|
* @param message Message to send.
|
|
* @param receiverAddress Address of the receiver.
|
|
* @param receiverPort Port of the receiver.
|
|
*/
|
|
protected abstract void sendMessage(byte[] message, InetAddress receiverAddress,
|
|
int receiverPort, boolean reconnectFlag) throws IOException;
|
|
|
|
/**
|
|
* Get the host of this message channel.
|
|
*
|
|
* @return host of this messsage channel.
|
|
*/
|
|
public String getHost() {
|
|
return this.getMessageProcessor().getIpAddress().getHostAddress();
|
|
}
|
|
|
|
/**
|
|
* Get port of this message channel.
|
|
*
|
|
* @return Port of this message channel.
|
|
*/
|
|
public int getPort() {
|
|
if (this.messageProcessor != null)
|
|
return messageProcessor.getPort();
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Send a formatted message to the specified target.
|
|
*
|
|
* @param sipMessage Message to send.
|
|
* @param hop hop to send it to.
|
|
* @throws IOException If there is an error sending the message
|
|
*/
|
|
public void sendMessage(SIPMessage sipMessage, Hop hop) throws IOException {
|
|
long time = System.currentTimeMillis();
|
|
InetAddress hopAddr = InetAddress.getByName(hop.getHost());
|
|
|
|
try {
|
|
|
|
for (MessageProcessor messageProcessor : getSIPStack().getMessageProcessors()) {
|
|
if (messageProcessor.getIpAddress().equals(hopAddr)
|
|
&& messageProcessor.getPort() == hop.getPort()
|
|
&& messageProcessor.getTransport().equals(hop.getTransport())) {
|
|
MessageChannel messageChannel = messageProcessor.createMessageChannel(
|
|
hopAddr, hop.getPort());
|
|
if (messageChannel instanceof RawMessageChannel) {
|
|
((RawMessageChannel) messageChannel).processMessage(sipMessage);
|
|
if (getSIPStack().isLoggingEnabled())
|
|
getSIPStack().getStackLogger().logDebug("Self routing message");
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
byte[] msg = sipMessage.encodeAsBytes(this.getTransport());
|
|
|
|
this.sendMessage(msg, hopAddr, hop.getPort(), sipMessage instanceof SIPRequest);
|
|
|
|
} catch (IOException ioe) {
|
|
throw ioe;
|
|
} catch (Exception ex) {
|
|
if (this.getSIPStack().getStackLogger().isLoggingEnabled(ServerLogger.TRACE_ERROR)) {
|
|
this.getSIPStack().getStackLogger().logError("Error self routing message cause by: ", ex);
|
|
}
|
|
// TODO: When moving to Java 6, use the IOExcpetion(message, exception) constructor
|
|
throw new IOException("Error self routing message");
|
|
} finally {
|
|
|
|
if (this.getSIPStack().getStackLogger().isLoggingEnabled(ServerLogger.TRACE_MESSAGES))
|
|
logMessage(sipMessage, hopAddr, hop.getPort(), time);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a message given SIP message.
|
|
*
|
|
* @param sipMessage is the messge to send.
|
|
* @param receiverAddress is the address to which we want to send
|
|
* @param receiverPort is the port to which we want to send
|
|
*/
|
|
public void sendMessage(SIPMessage sipMessage, InetAddress receiverAddress, int receiverPort)
|
|
throws IOException {
|
|
long time = System.currentTimeMillis();
|
|
byte[] bytes = sipMessage.encodeAsBytes(this.getTransport());
|
|
sendMessage(bytes, receiverAddress, receiverPort, sipMessage instanceof SIPRequest);
|
|
logMessage(sipMessage, receiverAddress, receiverPort, time);
|
|
}
|
|
|
|
/**
|
|
* Convenience function to get the raw IP source address of a SIP message as a String.
|
|
*/
|
|
public String getRawIpSourceAddress() {
|
|
String sourceAddress = getPeerAddress();
|
|
String rawIpSourceAddress = null;
|
|
try {
|
|
InetAddress sourceInetAddress = InetAddress.getByName(sourceAddress);
|
|
rawIpSourceAddress = sourceInetAddress.getHostAddress();
|
|
} catch (Exception ex) {
|
|
InternalErrorHandler.handleException(ex);
|
|
}
|
|
return rawIpSourceAddress;
|
|
}
|
|
|
|
/**
|
|
* generate a key given the inet address port and transport.
|
|
*/
|
|
public static String getKey(InetAddress inetAddr, int port, String transport) {
|
|
return (transport + ":" + inetAddr.getHostAddress() + ":" + port).toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Generate a key given host and port.
|
|
*/
|
|
public static String getKey(HostPort hostPort, String transport) {
|
|
return (transport + ":" + hostPort.getHost().getHostname() + ":" + hostPort.getPort())
|
|
.toLowerCase();
|
|
}
|
|
|
|
/**
|
|
* Get the hostport structure of this message channel.
|
|
*/
|
|
public HostPort getHostPort() {
|
|
HostPort retval = new HostPort();
|
|
retval.setHost(new Host(this.getHost()));
|
|
retval.setPort(this.getPort());
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Get the peer host and port.
|
|
*
|
|
* @return a HostPort structure for the peer.
|
|
*/
|
|
public HostPort getPeerHostPort() {
|
|
HostPort retval = new HostPort();
|
|
retval.setHost(new Host(this.getPeerAddress()));
|
|
retval.setPort(this.getPeerPort());
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Get the Via header for this transport. Note that this does not set a branch identifier.
|
|
*
|
|
* @return a via header for outgoing messages sent from this channel.
|
|
*/
|
|
public Via getViaHeader() {
|
|
Via channelViaHeader;
|
|
|
|
channelViaHeader = new Via();
|
|
try {
|
|
channelViaHeader.setTransport(getTransport());
|
|
} catch (ParseException ex) {
|
|
}
|
|
channelViaHeader.setSentBy(getHostPort());
|
|
return channelViaHeader;
|
|
}
|
|
|
|
/**
|
|
* Get the via header host:port structure. This is extracted from the topmost via header of
|
|
* the request.
|
|
*
|
|
* @return a host:port structure
|
|
*/
|
|
public HostPort getViaHostPort() {
|
|
HostPort retval = new HostPort();
|
|
retval.setHost(new Host(this.getViaHost()));
|
|
retval.setPort(this.getViaPort());
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* Log a message sent to an address and port via the default interface.
|
|
*
|
|
* @param sipMessage is the message to log.
|
|
* @param address is the inet address to which the message is sent.
|
|
* @param port is the port to which the message is directed.
|
|
*/
|
|
protected void logMessage(SIPMessage sipMessage, InetAddress address, int port, long time) {
|
|
if (!getSIPStack().getStackLogger().isLoggingEnabled(ServerLogger.TRACE_MESSAGES))
|
|
return;
|
|
|
|
// Default port.
|
|
if (port == -1)
|
|
port = 5060;
|
|
getSIPStack().serverLogger.logMessage(sipMessage, this.getHost() + ":" + this.getPort(),
|
|
address.getHostAddress().toString() + ":" + port, true, time);
|
|
}
|
|
|
|
/**
|
|
* Log a response received at this message channel. This is used for processing incoming
|
|
* responses to a client transaction.
|
|
*
|
|
* @param receptionTime is the time at which the response was received.
|
|
* @param status is the processing status of the message.
|
|
*
|
|
*/
|
|
public void logResponse(SIPResponse sipResponse, long receptionTime, String status) {
|
|
int peerport = getPeerPort();
|
|
if (peerport == 0 && sipResponse.getContactHeaders() != null) {
|
|
ContactHeader contact = (ContactHeader) sipResponse.getContactHeaders().getFirst();
|
|
peerport = ((AddressImpl) contact.getAddress()).getPort();
|
|
|
|
}
|
|
String from = getPeerAddress().toString() + ":" + peerport;
|
|
String to = this.getHost() + ":" + getPort();
|
|
this.getSIPStack().serverLogger.logMessage(sipResponse, from, to, status, false,
|
|
receptionTime);
|
|
}
|
|
|
|
/**
|
|
* Creates a response to a bad request (ie one that causes a ParseException)
|
|
*
|
|
* @param badReq
|
|
* @return message bytes, null if unable to formulate response
|
|
*/
|
|
protected final String createBadReqRes(String badReq, ParseException pe) {
|
|
|
|
StringBuffer buf = new StringBuffer(512);
|
|
buf.append("SIP/2.0 400 Bad Request (" + pe.getLocalizedMessage() + ')');
|
|
|
|
// We need the following headers: all Vias, CSeq, Call-ID, From, To
|
|
if (!copyViaHeaders(badReq, buf))
|
|
return null;
|
|
if (!copyHeader(CSeqHeader.NAME, badReq, buf))
|
|
return null;
|
|
if (!copyHeader(CallIdHeader.NAME, badReq, buf))
|
|
return null;
|
|
if (!copyHeader(FromHeader.NAME, badReq, buf))
|
|
return null;
|
|
if (!copyHeader(ToHeader.NAME, badReq, buf))
|
|
return null;
|
|
|
|
// Should add a to-tag if not already present...
|
|
int toStart = buf.indexOf(ToHeader.NAME);
|
|
if (toStart != -1 && buf.indexOf("tag", toStart) == -1) {
|
|
buf.append(";tag=badreq");
|
|
}
|
|
|
|
// Let's add a Server header too..
|
|
ServerHeader s = MessageFactoryImpl.getDefaultServerHeader();
|
|
if ( s != null ) {
|
|
buf.append("\r\n" + s.toString());
|
|
}
|
|
int clength = badReq.length();
|
|
if (! (this instanceof UDPMessageChannel) ||
|
|
clength + buf.length() + ContentTypeHeader.NAME.length()
|
|
+ ": message/sipfrag\r\n".length() +
|
|
ContentLengthHeader.NAME.length() < 1300) {
|
|
|
|
/*
|
|
* Check to see we are within one UDP packet.
|
|
*/
|
|
ContentTypeHeader cth = new ContentType("message", "sipfrag");
|
|
buf.append("\r\n" + cth.toString());
|
|
ContentLength clengthHeader = new ContentLength(clength);
|
|
buf.append("\r\n" + clengthHeader.toString());
|
|
buf.append("\r\n\r\n" + badReq);
|
|
} else {
|
|
ContentLength clengthHeader = new ContentLength(0);
|
|
buf.append("\r\n" + clengthHeader.toString());
|
|
}
|
|
|
|
return buf.toString();
|
|
}
|
|
|
|
/**
|
|
* Copies a header from a request
|
|
*
|
|
* @param name
|
|
* @param fromReq
|
|
* @param buf
|
|
* @return
|
|
*
|
|
* Note: some limitations here: does not work for short forms of headers, or continuations;
|
|
* problems when header names appear in other parts of the request
|
|
*/
|
|
private static final boolean copyHeader(String name, String fromReq, StringBuffer buf) {
|
|
int start = fromReq.indexOf(name);
|
|
if (start != -1) {
|
|
int end = fromReq.indexOf("\r\n", start);
|
|
if (end != -1) {
|
|
// XX Assumes no continuation here...
|
|
buf.append(fromReq.subSequence(start - 2, end)); // incl CRLF
|
|
// in front
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Copies all via headers from a request
|
|
*
|
|
* @param fromReq
|
|
* @param buf
|
|
* @return
|
|
*
|
|
* Note: some limitations here: does not work for short forms of headers, or continuations
|
|
*/
|
|
private static final boolean copyViaHeaders(String fromReq, StringBuffer buf) {
|
|
int start = fromReq.indexOf(ViaHeader.NAME);
|
|
boolean found = false;
|
|
while (start != -1) {
|
|
int end = fromReq.indexOf("\r\n", start);
|
|
if (end != -1) {
|
|
// XX Assumes no continuation here...
|
|
buf.append(fromReq.subSequence(start - 2, end)); // incl CRLF
|
|
// in front
|
|
found = true;
|
|
start = fromReq.indexOf(ViaHeader.NAME, end);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
return found;
|
|
}
|
|
|
|
/**
|
|
* Get the message processor.
|
|
*/
|
|
public MessageProcessor getMessageProcessor() {
|
|
return this.messageProcessor;
|
|
}
|
|
}
|