/* * 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.CommonLogger; import gov.nist.core.Host; import gov.nist.core.HostPort; import gov.nist.core.InternalErrorHandler; import gov.nist.core.LogWriter; import gov.nist.core.ServerLogger; import gov.nist.core.StackLogger; 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.40 $ $Date: 2010-12-02 22:44:53 $ * * */ public abstract class MessageChannel { private static StackLogger logger = CommonLogger.getLogger(MessageChannel.class); // 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; /** * The client transaction that this message channel points to. */ private SIPClientTransaction encapsulatedClientTransaction; /** * 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 for a viaHeader. */ 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(final 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().equalsIgnoreCase(hop.getTransport())) { MessageChannel messageChannel = messageProcessor.createMessageChannel( hopAddr, hop.getPort()); if (messageChannel instanceof RawMessageChannel) { final RawMessageChannel channel = (RawMessageChannel) messageChannel; Runnable processMessageTask = new Runnable() { public void run() { try { ((RawMessageChannel) channel).processMessage((SIPMessage) sipMessage.clone()); } catch (Exception ex) { if (logger.isLoggingEnabled(ServerLogger.TRACE_ERROR)) { logger.logError("Error self routing message cause by: ", ex); } } } }; getSIPStack().getSelfRoutingThreadpoolExecutor().execute(processMessageTask); if (logger.isLoggingEnabled(LogWriter.TRACE_DEBUG)) logger.logDebug("Self routing message"); return; } } } byte[] msg = sipMessage.encodeAsBytes(this.getTransport()); this.sendMessage(msg, hopAddr, hop.getPort(), sipMessage instanceof SIPRequest); // we successfully sent the message without an exception so let's // now set port and address sipMessage.setRemoteAddress(hopAddr); sipMessage.setRemotePort(hop.getPort()); sipMessage.setLocalPort(this.getPort()); sipMessage.setLocalAddress(this.getMessageProcessor().getIpAddress()); } catch (IOException ioe) { throw ioe; } catch (Exception ex) { if (this.logger.isLoggingEnabled(ServerLogger.TRACE_ERROR)) { this.logger.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.logger.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); // we successfully sent the message without an exception so let's // set port and address before we feed it to the logger. sipMessage.setRemoteAddress(receiverAddress); sipMessage.setRemotePort(receiverPort); sipMessage.setLocalPort(this.getPort()); sipMessage.setLocalAddress(this.getMessageProcessor().getIpAddress()); //ready to log 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) { // http://java.net/jira/browse/JSIP-413 Concurrency issue within MessageChannel.java when using IPv6 addresses return (transport + ":" + inetAddr.getHostAddress().replaceAll("[\\[\\]]", "") + ":" + port).toLowerCase(); } /** * Generate a key given host and port. */ public static String getKey(HostPort hostPort, String transport) { // http://java.net/jira/browse/JSIP-413 Concurrency issue within MessageChannel.java when using IPv6 addresses String ipAddress = hostPort.getHost().getIpAddress(); if (ipAddress == null) { ipAddress = hostPort.getHost().getHostname(); } return (transport + ":" + ipAddress.replaceAll("[\\[\\]]", "") + ":" + 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. */ public void logMessage(SIPMessage sipMessage, InetAddress address, int port, long time) { if (!logger.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) { StringBuilder buf = new StringBuilder(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, StringBuilder 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, StringBuilder 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; } public SIPClientTransaction getEncapsulatedClientTransaction() { return this.encapsulatedClientTransaction; } public void setEncapsulatedClientTransaction(SIPClientTransaction transaction) { this.encapsulatedClientTransaction = transaction; } }