public void Refresh() { lock (this.m_pOwner.VirtualServer.Server.LockSynchronizer) { this.m_pOwner.VirtualServer.Server.TCP_Client.TcpStream.WriteLine("GetSipRegistration " + TextUtils.QuoteString(this.m_pOwner.VirtualServer.VirtualServerID) + " " + TextUtils.QuoteString(this.m_AddressOfRecord)); string text = this.m_pOwner.VirtualServer.Server.ReadLine(); if (!text.ToUpper().StartsWith("+OK")) { throw new Exception(text); } int num = Convert.ToInt32(text.Split(new char[] { ' ' }, 2)[1]); MemoryStream memoryStream = new MemoryStream(); this.m_pOwner.VirtualServer.Server.TCP_Client.TcpStream.ReadFixedCount(memoryStream, (long)num); DataSet dataSet = Utils.DecompressDataSet(memoryStream); if (dataSet.Tables.Contains("Contacts")) { List <SipRegistrationContact> list = new List <SipRegistrationContact>(); foreach (DataRow dataRow in dataSet.Tables["Contacts"].Rows) { SIP_t_ContactParam sIP_t_ContactParam = new SIP_t_ContactParam(); sIP_t_ContactParam.Parse(new System.NetworkToolkit.StringReader(dataRow["Value"].ToString())); list.Add(new SipRegistrationContact(sIP_t_ContactParam.Address.Uri.Value, sIP_t_ContactParam.Expires, sIP_t_ContactParam.QValue)); } this.m_pContacts = list.ToArray(); } else { this.m_pContacts = new SipRegistrationContact[0]; } } }
/// <summary> /// Sends specified request to the specified data flow. /// </summary> /// <param name="flow">SIP data flow.</param> /// <param name="request">SIP request to send.</param> /// <exception cref="ArgumentNullException">Is raised when <b>flow</b> or <b>request</b> is null reference.</exception> private void SendToFlow(SIP_Flow flow, SIP_Request request) { if (flow == null) { throw new ArgumentNullException("flow"); } if (request == null) { throw new ArgumentNullException("request"); } #region Contact (RFC 3261 8.1.1.8) /* * The Contact header field provides a SIP or SIPS URI that can be used * to contact that specific instance of the UA for subsequent requests. * The Contact header field MUST be present and contain exactly one SIP * or SIPS URI in any request that can result in the establishment of a * dialog. For the methods defined in this specification, that includes * only the INVITE request. For these requests, the scope of the * Contact is global. That is, the Contact header field value contains * the URI at which the UA would like to receive requests, and this URI * MUST be valid even if used in subsequent requests outside of any * dialogs. * * If the Request-URI or top Route header field value contains a SIPS * URI, the Contact header field MUST contain a SIPS URI as well. */ SIP_t_ContactParam contact = request.Contact.GetTopMostValue(); // Add contact header If request-Method can establish dialog and contact header not present. if (SIP_Utils.MethodCanEstablishDialog(request.RequestLine.Method) && contact == null) { SIP_Uri from = (SIP_Uri)request.From.Address.Uri; request.Contact.Add((flow.IsSecure ? "sips:" : "sip:") + from.User + "@" + flow.LocalPublicEP.ToString()); // REMOVE ME: 22.10.2010 //request.Contact.Add((flow.IsSecure ? "sips:" : "sip:" ) + from.User + "@" + m_pStack.TransportLayer.GetContactHost(flow).ToString()); } // If contact SIP URI and host = auto-allocate, allocate it as needed. else if (contact != null && contact.Address.Uri is SIP_Uri && ((SIP_Uri)contact.Address.Uri).Host == "auto-allocate") { ((SIP_Uri)contact.Address.Uri).Host = flow.LocalPublicEP.ToString(); // REMOVE ME: 22.10.2010 //((SIP_Uri)contact.Address.Uri).Host = m_pStack.TransportLayer.GetContactHost(flow).ToString(); } #endregion m_pTransaction = m_pStack.TransactionLayer.CreateClientTransaction(flow, request, true); m_pTransaction.ResponseReceived += new EventHandler <SIP_ResponseReceivedEventArgs>(ClientTransaction_ResponseReceived); m_pTransaction.TimedOut += new EventHandler(ClientTransaction_TimedOut); m_pTransaction.TransportError += new EventHandler <ExceptionEventArgs>(ClientTransaction_TransportError); // Start transaction processing. m_pTransaction.Start(); }
private void addVirtualServer() { Gateway gw = InfoService.Instance.CurrentGateway; //当前网关配置。 SIP_t_ContactParam[] contacts = new SIP_t_ContactParam[1]; SIP_t_ContactParam c = new SIP_t_ContactParam(); StringReader sr = new StringReader($"<sip:{gw.SipNumber}@{_localIP}:{gw.Port}>;expires={Expires};qvalue=1.00"); c.Parse(sr); contacts[0] = c; string aor = $"{gw.SipNumber}@{_localIP}"; _sipProxy.Registrar.SetRegistration(aor, contacts); if (_regTimer == null) { _regTimer = new System.Threading.Timer(timer_Callback, null, (int)(Expires * 0.9) * 1000, Timeout.Infinite); } else { _regTimer.Change((int)(Expires * 0.9) * 1000, Timeout.Infinite); } }
/// <summary> /// Converts <b>ContactUri</b> to valid Contact header value. /// </summary> /// <returns>Returns contact header value.</returns> public string ToContactValue() { SIP_t_ContactParam retVal = new SIP_t_ContactParam(); retVal.Parse(new StringReader(m_ContactURI.ToString())); retVal.Expires = m_Expires; return(retVal.ToStringValue()); }
/// <summary> /// Gets server events and binds them to this. /// </summary> private void Bind() { /* GetSipRegistrations "<virtualServerID>" * Responses: +OK <sizeOfData> * <data> * * -ERR <errorText> */ lock (m_pOwner.Server.LockSynchronizer){ m_pOwner.Server.TcpClient.TcpStream.WriteLine("GetSipRegistrations " + TextUtils.QuoteString(m_pOwner.VirtualServerID) ); string response = m_pOwner.Server.ReadLine(); if (!response.ToUpper().StartsWith("+OK")) { throw new Exception(response); } int sizeOfData = Convert.ToInt32(response.Split(new char[] { ' ' }, 2)[1]); MemoryStream ms = new MemoryStream(); m_pOwner.Server.TcpClient.TcpStream.ReadFixedCount(ms, sizeOfData); // Decompress dataset DataSet ds = Utils.DecompressDataSet(ms); if (ds.Tables.Contains("SipRegistrations")) { foreach (DataRow dr in ds.Tables["SipRegistrations"].Rows) { //--- Parse contact -------------------------------------------------------------// List <SipRegistrationContact> contacts = new List <SipRegistrationContact>(); foreach (string contact in dr["Contacts"].ToString().Split('\t')) { if (!string.IsNullOrEmpty(contact)) { SIP_t_ContactParam c = new SIP_t_ContactParam(); c.Parse(new LumiSoft.Net.StringReader(contact)); contacts.Add(new SipRegistrationContact(c.Address.Uri.Value, c.Expires, c.QValue)); } } //--------------------------------------------------------------------------------// m_pRegistrations.Add(new SipRegistration( this, dr["UserName"].ToString(), dr["AddressOfRecord"].ToString(), contacts.ToArray() )); } } } }
/// <summary> /// Refreshes specified registration info. /// </summary> public void Refresh() { /* GetSipRegistration "<virtualServerID>" "<addressOfRecord>" * Responses: +OK <sizeOfData> * <data> * * -ERR <errorText> */ lock (m_pOwner.VirtualServer.Server.LockSynchronizer){ m_pOwner.VirtualServer.Server.TcpClient.TcpStream.WriteLine("GetSipRegistration " + TextUtils.QuoteString(m_pOwner.VirtualServer.VirtualServerID) + " " + TextUtils.QuoteString(m_AddressOfRecord) ); string response = m_pOwner.VirtualServer.Server.ReadLine(); if (!response.ToUpper().StartsWith("+OK")) { throw new Exception(response); } int sizeOfData = Convert.ToInt32(response.Split(new char[] { ' ' }, 2)[1]); MemoryStream ms = new MemoryStream(); m_pOwner.VirtualServer.Server.TcpClient.TcpStream.ReadFixedCount(ms, sizeOfData); // Decompress dataset DataSet ds = Utils.DecompressDataSet(ms); if (ds.Tables.Contains("Contacts")) { List <SipRegistrationContact> contacts = new List <SipRegistrationContact>(); foreach (DataRow dr in ds.Tables["Contacts"].Rows) { SIP_t_ContactParam c = new SIP_t_ContactParam(); c.Parse(new LumiSoft.Net.StringReader(dr["Value"].ToString())); contacts.Add(new SipRegistrationContact(c.Address.Uri.Value, c.Expires, c.QValue)); } m_pContacts = contacts.ToArray(); } else { m_pContacts = new SipRegistrationContact[0]; } } }
private void Bind() { lock (this.m_pOwner.Server.LockSynchronizer) { this.m_pOwner.Server.TCP_Client.TcpStream.WriteLine("GetSipRegistrations " + TextUtils.QuoteString(this.m_pOwner.VirtualServerID)); string text = this.m_pOwner.Server.ReadLine(); if (!text.ToUpper().StartsWith("+OK")) { throw new Exception(text); } int num = Convert.ToInt32(text.Split(new char[] { ' ' }, 2)[1]); MemoryStream memoryStream = new MemoryStream(); this.m_pOwner.Server.TCP_Client.TcpStream.ReadFixedCount(memoryStream, (long)num); DataSet dataSet = Utils.DecompressDataSet(memoryStream); if (dataSet.Tables.Contains("SipRegistrations")) { foreach (DataRow dataRow in dataSet.Tables["SipRegistrations"].Rows) { List <SipRegistrationContact> list = new List <SipRegistrationContact>(); string[] array = dataRow["Contacts"].ToString().Split(new char[] { '\t' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i]; if (!string.IsNullOrEmpty(text2)) { SIP_t_ContactParam sIP_t_ContactParam = new SIP_t_ContactParam(); sIP_t_ContactParam.Parse(new System.NetworkToolkit.StringReader(text2)); list.Add(new SipRegistrationContact(sIP_t_ContactParam.Address.Uri.Value, sIP_t_ContactParam.Expires, sIP_t_ContactParam.QValue)); } } this.m_pRegistrations.Add(new SipRegistration(this, dataRow["UserName"].ToString(), dataRow["AddressOfRecord"].ToString(), list.ToArray())); } } } }
private void register() { SIP_t_ContactParam[] contacts = new SIP_t_ContactParam[1]; SIP_t_ContactParam c = new SIP_t_ContactParam(); StringReader sr = new StringReader($"<sip:{_localAOR}:{_gateway.Port}>;expires={Expires};qvalue=1.00"); c.Parse(sr); contacts[0] = c; _registrar.SetRegistration(_localAOR, contacts); if (_regTimer == null) { _regTimer = new Timer(timer_Callback, null, (int)(Expires * 0.9) * 1000, Timeout.Infinite); } else { _regTimer.Change((int)(Expires * 0.9) * 1000, Timeout.Infinite); } }
/// <summary> /// Handles REGISTER method. /// </summary> /// <param name="e">Request event arguments.</param> internal void Register(SIP_RequestReceivedEventArgs e) { /* RFC 3261 10.3 Processing REGISTER Requests. * 1. The registrar inspects the Request-URI to determine whether it * has access to bindings for the domain identified in the * Request-URI. If not, and if the server also acts as a proxy * server, the server SHOULD forward the request to the addressed * domain, following the general behavior for proxying messages * described in Section 16. * * 2. To guarantee that the registrar supports any necessary extensions, * the registrar MUST process the Require header field. * * 3. A registrar SHOULD authenticate the UAC. * * 4. The registrar SHOULD determine if the authenticated user is * authorized to modify registrations for this address-of-record. * For example, a registrar might consult an authorization * database that maps user names to a list of addresses-of-record * for which that user has authorization to modify bindings. If * the authenticated user is not authorized to modify bindings, * the registrar MUST return a 403 (Forbidden) and skip the * remaining steps. * * 5. The registrar extracts the address-of-record from the To header * field of the request. If the address-of-record is not valid * for the domain in the Request-URI, the registrar MUST send a * 404 (Not Found) response and skip the remaining steps. The URI * MUST then be converted to a canonical form. To do that, all * URI parameters MUST be removed (including the user-param), and * any escaped characters MUST be converted to their unescaped * form. The result serves as an index into the list of bindings. * * 6. The registrar checks whether the request contains the Contact * header field. If not, it skips to the last step. If the * Contact header field is present, the registrar checks if there * is one Contact field value that contains the special value "*" * and an Expires field. If the request has additional Contact * fields or an expiration time other than zero, the request is * invalid, and the server MUST return a 400 (Invalid Request) and * skip the remaining steps. If not, the registrar checks whether * the Call-ID agrees with the value stored for each binding. If * not, it MUST remove the binding. If it does agree, it MUST * remove the binding only if the CSeq in the request is higher * than the value stored for that binding. Otherwise, the update * MUST be aborted and the request fails. * * 7. The registrar now processes each contact address in the Contact * header field in turn. For each address, it determines the * expiration interval as follows: * * - If the field value has an "expires" parameter, that value * MUST be taken as the requested expiration. * * - If there is no such parameter, but the request has an * Expires header field, that value MUST be taken as the requested expiration. * * - If there is neither, a locally-configured default value MUST * be taken as the requested expiration. * * The registrar MAY choose an expiration less than the requested * expiration interval. If and only if the requested expiration * interval is greater than zero AND smaller than one hour AND * less than a registrar-configured minimum, the registrar MAY * reject the registration with a response of 423 (Interval Too * Brief). This response MUST contain a Min-Expires header field * that states the minimum expiration interval the registrar is * willing to honor. It then skips the remaining steps. * * For each address, the registrar then searches the list of * current bindings using the URI comparison rules. If the * binding does not exist, it is tentatively added. If the * binding does exist, the registrar checks the Call-ID value. If * the Call-ID value in the existing binding differs from the * Call-ID value in the request, the binding MUST be removed if * the expiration time is zero and updated otherwise. If they are * the same, the registrar compares the CSeq value. If the value * is higher than that of the existing binding, it MUST update or * remove the binding as above. If not, the update MUST be * aborted and the request fails. * * This algorithm ensures that out-of-order requests from the same * UA are ignored. * * Each binding record records the Call-ID and CSeq values from * the request. * * The binding updates MUST be committed (that is, made visible to * the proxy or redirect server) if and only if all binding * updates and additions succeed. If any one of them fails (for * example, because the back-end database commit failed), the * request MUST fail with a 500 (Server Error) response and all * tentative binding updates MUST be removed. * * 8. The registrar returns a 200 (OK) response. The response MUST * contain Contact header field values enumerating all current * bindings. Each Contact value MUST feature an "expires" * parameter indicating its expiration interval chosen by the * registrar. The response SHOULD include a Date header field. */ SIP_ServerTransaction transaction = e.ServerTransaction; SIP_Request request = e.Request; SIP_Uri to = null; string userName = ""; // Probably we need to do validate in SIP stack. #region Validate request if (SIP_Utils.IsSipOrSipsUri(request.To.Address.Uri.ToString())) { to = (SIP_Uri)request.To.Address.Uri; } else { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": To: value must be SIP or SIPS URI.", request)); return; } #endregion #region 1. Check if we are responsible for Request-URI domain // if(m_pProxy.OnIsLocalUri(e.Request.Uri)){ // } // TODO: #endregion #region 2. Check that all required extentions supported #endregion #region 3. Authenticate request if (!m_pProxy.AuthenticateRequest(e, out userName)) { return; } #endregion #region 4. Check if user user is authorized to modify registrations // We do this in next step(5.). #endregion #region 5. Check if address of record exists if (!m_pProxy.OnAddressExists(to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x404_Not_Found, request)); return; } else if (!OnCanRegister(userName, to.Address)) { transaction.SendResponse(m_pStack.CreateResponse(SIP_ResponseCodes.x403_Forbidden, request)); return; } #endregion #region 6. Process * Contact if exists // Check if we have star contact. SIP_t_ContactParam starContact = null; foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.IsStarContact) { starContact = c; break; } } // We have star contact. if (starContact != null) { if (request.Contact.GetAllValues().Length > 1) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> If star(*) present, only 1 contact allowed.", request)); return; } else if (starContact.Expires != 0) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": RFC 3261 10.3.6 -> star(*) contact parameter 'expires' value must be always '0'.", request)); return; } // Remove bindings. SIP_Registration reg = m_pRegistrations[to.Address]; if (reg != null) { foreach (SIP_RegistrationBinding b in reg.Bindings) { if (request.CallID != b.CallID || request.CSeq.SequenceNumber > b.CSeqNo) { b.Remove(); } } } } #endregion #region 7. Process Contact values if (starContact == null) { SIP_Registration reg = m_pRegistrations[to.Address]; if (reg == null) { reg = new SIP_Registration(userName, to.Address); m_pRegistrations.Add(reg); } // We may do updates in batch only. // We just validate all values then do update(this ensures that update doesn't fail). // Check expires and CSeq. foreach (SIP_t_ContactParam c in request.Contact.GetAllValues()) { if (c.Expires == -1) { c.Expires = request.Expires; } if (c.Expires == -1) { c.Expires = m_pProxy.Stack.MinimumExpireTime; } // We must accept 0 values - means remove contact. if (c.Expires != 0 && c.Expires < m_pProxy.Stack.MinimumExpireTime) { SIP_Response resp = m_pStack.CreateResponse( SIP_ResponseCodes.x423_Interval_Too_Brief, request); resp.MinExpires = m_pProxy.Stack.MinimumExpireTime; transaction.SendResponse(resp); return; } SIP_RegistrationBinding currentBinding = reg.GetBinding(c.Address.Uri); if (currentBinding != null && currentBinding.CallID == request.CallID && request.CSeq.SequenceNumber < currentBinding.CSeqNo) { transaction.SendResponse( m_pStack.CreateResponse( SIP_ResponseCodes.x400_Bad_Request + ": CSeq value out of order.", request)); return; } } // Do binding updates. reg.AddOrUpdateBindings(e.ServerTransaction.Flow, request.CallID, request.CSeq.SequenceNumber, request.Contact.GetAllValues()); } #endregion #region 8. Create 200 OK response and return all current bindings SIP_Response response = m_pStack.CreateResponse(SIP_ResponseCodes.x200_Ok, request); response.Date = DateTime.Now; SIP_Registration registration = m_pRegistrations[to.Address]; if (registration != null) { foreach (SIP_RegistrationBinding b in registration.Bindings) { // Don't list expired bindings what wait to be disposed. if (b.TTL > 1) { response.Header.Add("Contact:", b.ToContactValue()); } } } // Add Authentication-Info:, then client knows next nonce. response.AuthenticationInfo.Add("qop=\"auth\",nextnonce=\"" + m_pStack.DigestNonceManager.CreateNonce() + "\""); transaction.SendResponse(response); #endregion }
/// <summary> /// Converts <b>ContactUri</b> to valid Contact header value. /// </summary> /// <returns>Returns contact header value.</returns> public string ToContactValue() { SIP_t_ContactParam retVal = new SIP_t_ContactParam(); retVal.Parse(new StringReader(m_ContactURI.ToString())); retVal.Expires = m_Expires; return retVal.ToStringValue(); }
/// <summary> /// Redirects incoming call to speified contact(s). /// </summary> /// <param name="contacts">Redirection targets.</param> /// <exception cref="ObjectDisposedException">Is raised when this object is disposed and and this method is accessed.</exception> /// <exception cref="InvalidOperationException">Is raised when call is not in valid state and this method is called.</exception> /// <exception cref="ArgumentNullException">Is raised when <b>contacts</b> is null.</exception> /// <exception cref="ArgumentException">Is raised when any of the arguments has invalid value.</exception> public void Redirect(SIP_t_ContactParam[] contacts) { lock (m_pLock) { if (State == SIP_UA_CallState.Disposed) { throw new ObjectDisposedException(GetType().Name); } if (State != SIP_UA_CallState.WaitingToAccept) { throw new InvalidOperationException("Call is not in valid state."); } if (contacts == null) { throw new ArgumentNullException("contacts"); } if (contacts.Length == 0) { throw new ArgumentException("Arguments 'contacts' must contain at least 1 value."); } // TODO: //m_pUA.Stack.CreateResponse(SIP_ResponseCodes.,m_pInitialInviteTransaction); throw new NotImplementedException(); } }
/// <summary> /// Add or updates specified SIP registration info. /// </summary> /// <param name="aor">Registration address of record.</param> /// <param name="contacts">Registration address of record contacts to update.</param> /// <param name="flow">SIP proxy local data flow what accpeted this contact registration.</param> public void SetRegistration(string aor, SIP_t_ContactParam[] contacts, SIP_Flow flow) { lock (m_pRegistrations) { SIP_Registration registration = m_pRegistrations[aor]; if (registration == null) { registration = new SIP_Registration("system", aor); m_pRegistrations.Add(registration); OnAorRegistered(registration); } registration.AddOrUpdateBindings(flow, "", 1, contacts); } }
/// <summary> /// Add or updates specified SIP registration info. /// </summary> /// <param name="aor">Registration address of record.</param> /// <param name="contacts">Registration address of record contacts to update.</param> public void SetRegistration(string aor, SIP_t_ContactParam[] contacts) { SetRegistration(aor, contacts, null); }
/// <summary> /// Adds or updates matching bindings. /// </summary> /// <param name="flow">SIP data flow what updates this binding. This value is null if binding was not added through network or /// flow has disposed.</param> /// <param name="callID">Call-ID header field value.</param> /// <param name="cseqNo">CSeq header field sequence number value.</param> /// <param name="contacts">Contacts to add or update.</param> /// <exception cref="ArgumentNullException">Is raised when <b>callID</b> or <b>contacts</b> is null reference.</exception> public void AddOrUpdateBindings(SIP_Flow flow, string callID, int cseqNo, SIP_t_ContactParam[] contacts) { if (callID == null) { throw new ArgumentNullException("callID"); } if (cseqNo < 0) { throw new ArgumentException("Argument 'cseqNo' value must be >= 0."); } if (contacts == null) { throw new ArgumentNullException("contacts"); } lock (m_pLock) { foreach (SIP_t_ContactParam contact in contacts) { SIP_RegistrationBinding binding = GetBinding(contact.Address.Uri); // Add binding. if (binding == null) { binding = new SIP_RegistrationBinding(this, contact.Address.Uri); m_pBindings.Add(binding); } // Update binding. binding.Update(flow, contact.Expires == -1 ? 3600 : contact.Expires, contact.QValue == -1 ? 1.0 : contact.QValue, callID, cseqNo); } } }