/// <summary> /// Asynchronously transmits the message passed to the destination /// indicated by the <see paramref="remoteEP" /> parameter. /// </summary> /// <param name="remoteEP">The destination SIP endpoint's <see cref="NetworkBinding" />.</param> /// <param name="message">The <see cref="SipMessage" /> to be transmitted.</param> /// <exception cref="SipTransportException">Thrown if the remote endpoint rejected the message or timed out.</exception> public void Send(NetworkBinding remoteEP, SipMessage message) { if (disabled) { return; } if ((traceMode & SipTraceMode.Send) != 0) { SipHelper.Trace(string.Format("UDP: sending to {0}", remoteEP), message); } try { sock.SendTo(message.ToArray(), remoteEP); } catch (SocketException e) { // $todo(jeff.lill): // // This is just copied from the TCP transport. It probably // doesn't apply here. switch ((SocketError)e.ErrorCode) { case SocketError.ConnectionAborted: case SocketError.ConnectionRefused: case SocketError.ConnectionReset: case SocketError.HostDown: case SocketError.HostNotFound: case SocketError.HostUnreachable: throw new SipTransportException(SipTransportException.ErrorType.Rejected, e.Message, e); case SocketError.TimedOut: throw new SipTransportException(SipTransportException.ErrorType.Timeout, e.Message, e); default: throw; } } }
/// <summary> /// Asynchronously transmits the message passed to the destination /// indicated by the <see paramref="remoteEP" /> parameter. /// </summary> /// <param name="remoteEP">The destination SIP endpoint's <see cref="NetworkBinding" />.</param> /// <param name="message">The <see cref="SipMessage" /> to be transmitted.</param> /// <remarks> /// Note that this method will go to some lengths to send the message /// down an existing connection to this endpoint. /// </remarks> /// <exception cref="SipTransportException">Thrown if the remote endpoint rejected the message or timed out.</exception> public void Send(NetworkBinding remoteEP, SipMessage message) { string key = remoteEP.ToString(); EnhancedSocket sock; Connection con; using (TimedLock.Lock(this)) { if (listener == null) { throw new ObjectDisposedException("Transport is closed."); } if (disabled) { return; } if ((traceMode & SipTraceMode.Send) != 0) { SipHelper.Trace(string.Format("TCP: sending to {0}", remoteEP), message); } // Send the message down an existing connection to this // endpoint (if there is one). if (connections.TryGetValue(key, out con)) { con.Send(message); return; } } // Otherwise establish a connection to the endpoint and transmit // the message. Note that I'm establishing the connection outside // of the lock so processing on other connections can continue // while the connection is established. try { sock = new EnhancedSocket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); sock.Connect(remoteEP); } catch (SocketException e) { switch ((SocketError)e.ErrorCode) { case SocketError.ConnectionAborted: case SocketError.ConnectionRefused: case SocketError.ConnectionReset: case SocketError.HostDown: case SocketError.HostNotFound: case SocketError.HostUnreachable: throw new SipTransportException(SipTransportException.ErrorType.Rejected, e.Message, e); case SocketError.TimedOut: throw new SipTransportException(SipTransportException.ErrorType.Timeout, e.Message, e); default: throw; } } using (TimedLock.Lock(this)) { if (listener == null) { // Transport must have been closed while we were outside of the lock. sock.ShutdownAndClose(); throw new ObjectDisposedException("Transport is closed."); } if (connections.TryGetValue(key, out con)) { // Another connection to this endpoint must have been established // while we were outside of the lock. Close the new socket and // use the existing connection. sock.ShutdownAndClose(); con.Send(message); return; } // Add the new connection to the collection and then // send the message. con = new Connection(this, sock, remoteEP); connections.Add(key, con); con.Send(message); } }
private void OnReceive(IAsyncResult ar) { List <SipMessage> received = null; try { using (TimedLock.Lock(transport)) { int cb; int pos; int cbContents; SipHeader contentLength; byte[] packet; try { if (sock == null) { return; } if (contentBuf == null) { // We're reading a message envelope. // Read packets into headerBuf until we can find the CRLFCRLF // sequence marking the end of the message headers. cb = sock.EndReceive(ar); if (cb == 0) { // The socket has been closed on by the remote element. CloseAndRemove(); return; } cbRecv += cb; tryAgain: // Remove any leading CR or LF characters by shifting the // buffer contents. I know this isn't super efficient but // we'll probably never actually see packets with this // in the wild. for (pos = 0; pos < cbRecv; pos++) { if (headerBuf[pos] != 0x0D && headerBuf[pos] != 0x0A) { break; } } if (pos != 0) { if (pos == cbRecv) { // No data remaining in the buffer cbRecv = 0; sock.BeginReceive(headerBuf, 0, headerBuf.Length, SocketFlags.None, onRecv, null); return; } Array.Copy(headerBuf, pos, headerBuf, 0, headerBuf.Length - pos); cbRecv -= pos; } // Scan the message for the CRLFCRLF sequence terminating the // message envelope. pos = Helper.IndexOf(headerBuf, CRLFCRLF, 0, cbRecv); if (pos != -1) { // We've got the message envelope pos += 4; // Advance past the CRLFCRLF // Parse the message headers and then get the Content-Length header packet = Helper.Extract(headerBuf, 0, pos); try { message = SipMessage.Parse(packet, false); } catch (Exception e) { SipHelper.Trace(string.Format("TCP: UNPARSABLE message received from {0}: [{1}]", remoteEP, e.Message), Helper.FromUTF8(packet)); throw; } contentLength = message[SipHeader.ContentLength]; if (contentLength == null || !int.TryParse(contentLength.Text, out cbContents) || cbContents < 0) { var e = new SipException("Malformed SIP message: Invalid or missing [Content-Length] header from streaming transport."); e.Transport = "TCP"; e.BadPacket = packet; e.SourceEndpoint = remoteEP; throw e; } if (cbContents > MaxContentSize) { var e = new SipException("Invalid SIP message: [Content-Length={0}] exceeds [{1}].", cbContents, MaxContentSize); e.Transport = "TCP"; e.BadPacket = packet; e.SourceEndpoint = remoteEP; throw e; } if (pos + cbContents <= cbRecv) { // We already have the message contents, so extract the contents, // add them to the message, and then queue the message for delivery // once we leave the lock. message.Contents = Helper.Extract(headerBuf, pos, cbContents); if (received == null) { received = new List <SipMessage>(); } received.Add(message); message = null; // Shift any remaining data to the left in headerBuf, // adjust cbRecv, and the loop to look for another // message. pos += cbContents; cb = cbRecv - pos; // Bytes remaining in the buffer if (cb == 0) { // No more data left in the buffer cbRecv = 0; sock.BeginReceive(headerBuf, 0, headerBuf.Length, SocketFlags.None, onRecv, null); return; } Array.Copy(headerBuf, pos, headerBuf, 0, cb); cbRecv = cb; goto tryAgain; } // We don't have all of the message contents, so allocate a buffer for // the contents, copy what we have already into this buffer, and then // initiate a receive operation to read the remaining data. contentBuf = new byte[cbContents]; cbRecv = cbRecv - pos; // Content bytes remaining in the buffer Array.Copy(headerBuf, pos, contentBuf, 0, cbRecv); sock.BeginReceiveAll(contentBuf, cbRecv, cbContents - cbRecv, SocketFlags.None, onRecv, null); return; } // Throw an error if the header buffer is full and we still haven't // found the end of the envelope. if (cbRecv >= headerBuf.Length) { var e = new SipException("Malformed SIP message: Read [{0}] bytes and have not yet encountered end of headers.", headerBuf.Length); e.Transport = "TCP"; e.SourceEndpoint = remoteEP; throw e; } // Continue receiving header data. sock.BeginReceive(headerBuf, cbRecv, headerBuf.Length - cbRecv, SocketFlags.None, onRecv, null); } else { // We're in the process of reading the message contents. // Complete the contents receive operation and queue the // message for delivery after we leave the lock. sock.EndReceiveAll(ar); message.Contents = contentBuf; if (received == null) { received = new List <SipMessage>(); } received.Add(message); // Reset and start reading the next message envelope. message = null; contentBuf = null; cbRecv = 0; sock.BeginReceive(headerBuf, 0, headerBuf.Length, SocketFlags.None, onRecv, null); } } catch (SocketException) { CloseAndRemove(); } catch (Exception e) { SysLog.LogException(e); CloseAndRemove(); } } } finally { // Deliver any queued messages (outside of the lock) if (received != null) { foreach (var message in received) { message.SourceTransport = transport; message.RemoteEndpoint = remoteEP; if ((transport.traceMode & SipTraceMode.Receive) != 0) { SipHelper.Trace(string.Format("TCP: received from {0}", remoteEP), message); } transport.router.Route(transport, message); } } } }
/// <summary> /// Called when a packet is received by the socket from a remote endpoint. /// </summary> /// <param name="ar">The <see cref="IAsyncResult" />.</param> private void OnReceive(IAsyncResult ar) { byte[] packet = null; IPEndPoint remoteEP = new IPEndPoint(IPAddress.Any, 0); int cb; SipMessage message; recvPending = false; if (sock == null || recvBuf == null) { return; } try { cb = sock.EndReceiveFrom(ar, ref recvEP); } catch (Exception e) { // Log the exception if the socket appears to be open and then submit // another receive request. if (sock.IsOpen) { SysLog.LogException(e); try { recvEP = new IPEndPoint(IPAddress.Any, 0); sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref recvEP, onRecv, null); } catch (Exception e2) { SysLog.LogException(e2, "SIP UDP transport is no longer able to receive packets."); } } return; } // $todo(jeff.lill): This is where I need to add source filtering. // Make a copy of what we received before initiating the next packet receive. try { packet = Helper.Extract(recvBuf, 0, cb); remoteEP = (IPEndPoint)recvEP; } catch (Exception e) { SysLog.LogException(e); } // Initiate the receive of the next message lock (syncLock) { if (sock == null || !sock.IsOpen) { return; } try { recvEP = new IPEndPoint(IPAddress.Any, 0); recvPending = true; sock.BeginReceiveFrom(recvBuf, 0, recvBuf.Length, SocketFlags.None, ref recvEP, onRecv, null); } catch (Exception e) { SysLog.LogException(e); } } // Parse and dispatch the message if (packet == null) { return; } // It looks like SIP clients like X-Lite send 4 byte CRLF CRLF messages // periodically over UDP to keep NAT mappings alive. I'm going to // ignore these messages. if (packet.Length == 4) { return; } // Looks like we have a real message. try { try { message = SipMessage.Parse(packet, true); } catch (Exception e) { SipHelper.Trace(string.Format("UDP: UNPARSABLE message received from {0}: [{1}]", remoteEP, e.Message), Helper.FromUTF8(packet)); throw; } message.SourceTransport = this; message.RemoteEndpoint = remoteEP; } catch (SipException e) { e.BadPacket = packet; e.SourceEndpoint = remoteEP; SysLog.LogException(e); return; } catch (Exception e) { SipException sipException; sipException = new SipException("Error parsing SIP message.", e); sipException.Transport = string.Format("UDP [{0}]", localEP); sipException.BadPacket = packet; sipException.SourceEndpoint = remoteEP; SysLog.LogException(sipException); return; } if (disabled) { return; } if ((traceMode & SipTraceMode.Receive) != 0) { SipHelper.Trace(string.Format("UDP: received from {0}", remoteEP), message); } router.Route(this, message); }