Здравствуйте,
Эдуард Ковалев wrote:
>> -----Original Message-----
>> From: CommuniGate Pro Russian Discussions [mailto:CGatePro@mx.ru]
>> Sent: Monday, July 13, 2009 5:13 PM
>> To: CommuniGate Pro Russian Discussions
>> Subject: Re: [CGP] регистрация PSTN шлюза linksys SPA-3102 в CGP
>>
>> Здравствуйте,
>>
>> Эдуард Ковалев wrote:
>>> Добрый день!
>>>
>>> Есть железка Linksys SPA-3102 в удаленном офисе. Имеет белый IP, но
>> он динамический.
>>
>> А регистрацию в DNS сервере каком-нибудь для этого динамического IP она
>> имеет?
> > Если имеется ввиду DynDNS, то железка этого не умеет... >
> > . Но можно обойтись и без регистрации,
> > Да, именно таким образом приходят входящие с железки >
> > Мы в свое время тестировали 3CX Phone System, железку подключали по этой инструкции: > http://www.3cx.com/voip-gateways/linksys-3102.html > так вот в 3CX pstn линия имеет вид экстеншена и необходимо чтобы железка портом FXO регистрировалась на сервере, и тот использует данную регистрацию для исходящих звонков > правда логи подключения и звонка мы не смотрели...
Я вот проямо сейчас попробовал перенаправлять исходящие звонки на аккаунт регистрации SPA - но она тогда требует аутентификации. Если ее настроить, чтобы аутентификация не требовалась, то вместо донабора номера из заголовка To она просто выдает гудок и завершает звонок при донаборе номера.
> > Сможет ли она извлечь PSTN номер из,
> > Согласен >
> > У железки есть еще один порт FXS (аналоговый телефон) и соответственно он подключается к экстеншену. Т.е. в принципе IP железки видно в подключенных (Status - Registered Devices) > Можно ли это использовать для построения менее громоздкой схемы?
Нет, не получается. При звонках на эту линию она даже выдает ответы 180 (типа, звонит) - но в PSTN звонок при этом не идет. Он идет в fxs порт, к которому может быть подсоединен аналоговый телефон.
> Вообще железка умеет следующее: > > Overview > The SPA3102 has the following ports: > *FXS port (Phone)-Connected to a standard analog telephone or fax machine, configured using the Line tab > *FXO port (Line)-.Connected to a standard telephone wall jack for connectivity to the PSTN, configured using the PSTN Line tab > The FXO port lets the SPA3102 act as a SIP-PSTN gateway, which bridges PSTN and VoIP service. > Line 1 does not provide a gateway because it provides only VoIP service. The VoIP-To-PSTN calling function is referred to as a PSTN gateway, and PSTN-To-VoIP calling function as a VoIP gateway. Note the following definitions: > *VoIP caller-One who calls the SPA3102 via VoIP to obtain PSTN service > *VoIP user-VoIP caller that has a user account (user-id and password) on the SPA3102 > *PSTN caller-One who calls the SPA3102 from the PSTN to obtain VoIP service > Line 1 can be configured with a regular VoIP account and can be used in the same way as the Line 1 of any Linksys ATA. > A second VoIP account can be configured in the SPA3102 to support PSTN gateway calls exclusively. A different SIP port should be assigned to Line 1 and the PSTN Line. The same VoIP account may be used for both Line 1 and the PSTN Line if a different SIP port is assigned to each. > VoIP callers can be authenticated by one of the following methods: > *No Authentication-All callers are accepted for service
Вот это я попробовал - не работает.
> *PIN-Caller is prompted to enter a PIN right after the call is answered > *HTTP digest-SIP INVITE must contain a valid authorization header
То есть, звонящий сначала авторизуется на сервере, а потом железка просит еще раз авторизоваться, без B2BUA такая схема работать не будет.
> PSTN callers can be authenticated by one of the following methods: > *No authentication-All callers are accepted for service > *PIN-Caller is prompted to enter a PIN right after the call is answered > > How VoIP-To-PSTN Calls Work > To obtain PSTN services through the SPA3102, the VoIP caller establishes a connection with the PSTN Line by way of a standard SIP INVITE request addressed to the PSTN Line. The PSTN Line can be configured to support one-stage and two-stage dialing as described in the following sections. > > One-Stage Dialing > The Request-URI of the INVITE to the PSTN Line should have the form <Dialed-Number>@<SPA-Address>,
То есть, просто форкать на регистрацию железки не получится (в этом случае в Request URI будет Contact из регистрации, не телефонный номер.)
> where <Dialed-Number> is the number dialed by the VoIP caller, and <SPA-Address> is a valid address of the SPA3102, such as 10.0.0.100:5061. > If the FXO port is currently in use (off-hook) or the PSTN line is being used by another extension, the SPA3102 replies to the INVITE with a 503 response. Otherwise, it compares the <Dialed-Number> with the <User ID> of the PSTN Line. If they are the same, the SPA3102 interprets this as a request for two-stage dialing (see the "Two-Stage Dialing" section on page 4-3). If they are different, the SPA3102 processes the <Dialed-Number> using the corresponding <Dial Plan>. > If dial plan processing fails, the SPA3102 replies with a 403 response. Otherwise, it replies with a 200 and at the same time takes the FXO port off hook and dials the target number returned after processing the dial plan. > ---- > Note: If <User ID> on the PSTN Line is blank, <Registration> should be disabled for the PSTN Line. > --- > If HTTP Digest Authentication is enabled, the SPA3102 challenges the INVITE with a 401 response if it does not have a valid Authorization header. The Authorization header should include a <User ID n> parameter, where n refers to one of eight VoIP user accounts that can be configured on the SPA3102. The credentials are computed based on the corresponding password using Message Digest 5 (MD5). The <User ID n> must match one of the VoIP accounts stored on the SPA3102. Each VoIP user account contains the information listed in Table 4-1. > > Two-Stage Dialing
Это совсем неудобно.
> In two-stage dialing, the SPA3102 takes the FXO port off-hook but does not automatically dial any digits after accepting the call. To invoke two-stage dialing, the VoIP caller should INVITE the PSTN Line without the user-id in the Request-URI or with a user-id that matches exactly the <User ID n> of the PSTN Line. A different user-id in the Request-URI is treated as a request for one-stage dialing if one-stage dialing is enabled, or dropped by the SPA3102 (as if no user-id is given) if one-stage dialing is disabled. > --- > Note: If Authentication is disabled, a default dial plan is assigned to all VoIP callers. > --- > HTTP Digest Authentication can be also used for two-stage dialing, as in one-stage dialing. If using HTTP Digest Authentication or Authentication is disabled, the VoIP caller should hear the PSTN dial tone right after the call is answered (by a SIP 200 response). > If PIN Authentication is enabled, the VoIP caller is prompted to enter a PIN number after the SPA3102 answers the call. The PIN number must end with a # key. The inter-PIN-digit timeout is 10 seconds (not configurable). Up to eight VoIP caller PIN numbers can be configured on the SPA3102. A dial plan can be selected for each PIN number. If the caller enters a wrong PIN or the SPA3102 times out waiting for more PIN digits, the SPA3102 tears down the call immediately with a BYE request. > --- > Note: When the source address of the INVITE is 127.0.0.1, authentication is automatically disabled because this is a call by the local user. This applies to both one-stage and two-stage dialing. > ---
Да, не умеет.
Остается извлекать адрес из регистрации. Небольшое добавление в стандартный скрипт gatewaycaller позволяети отрабатывать ситуацию, когда параметр SipGatewayDomain задан в виде имени аккаунта, на который регистрируется шлюз:
if FindSubstring(sipDomain, "@") >= 0 then
result = ExecuteCLI("SIPContacts " + sipDomain); if result then syslog("CLI failed: " + result); rejectCall("501-" + result); stop; end if; registration = vars().executeCLIResult[0].(""); syslog("Reg: " + ObjecttoString(vars().executeCLIResult)); syslog("URI: " + ObjecttoString(registration)); offset = FindSubstring(registration, "@"); if offset >= 0 then sipDomain = SubString(registration, offset+1, 1000); else rejectCall("501-registration not found"); stop; end if;
Этот кусок достает первую регистрацию для этого аккаунта (а надо бы отсортировать по Expires) и использует ее для посылки запроса. Для простых случаев работает.
-- Best regards, Dmitry Akindinov -- Stalker Labs. --cut here-- // ================================================== // // Gateway Caller Application // // // // Version 1.7 // // Copyright (c) 2006-2007, Stalker Software, Inc. // // ================================================== // // // All PSTN* settings can be specified as // dictionaries: {gw1=value; gw2=value;} // Settings used: // PSTNGatewayDomain - the gateway domain -- or account name the gateway is registered to // PSTNGatewayVia - [optional] gateway address // PSTNFromName - [optional] From: name to use // PSTNGatewayAuthName - auth name to use with gateway // PSTNGatewayPassword - password to use with gateway // PSTNBillingPlan - parameters for billing // Preferences used: // CallOutPrivacy - if YES, then hide the From: header // // // Router records can specify the gw to use as // the second parameter: // S:<+1(10d)@telnum> = gatewaycaller{1*,gw1}#postmaster // If gwN to use is not specified, and a setting // is a dictionary, the first dictionary item is used. // // If the number is specified as nnnn-clip or nnnn-clir // the prefix overrides the "CallOutPrivacy" preference. // function callerLeg(parameters,callPending) external; function bridgedLoopHash(peerLeg,finishTime) external; function getKeyedSetting(settings, settingName, gwKey) forward; procedure bookCall(accountName,phoneNumber,callerIP,startTime,resultCode,priceInCents) forward; function isPhoneNumber(theAddr) is firstSymbol = Substring(theAddr,0,1); return IsDigit(firstSymbol) or else firstSymbol == "+"; end function; entry Main is // // All calls to gateways must be authenticated // Request AUTH if there is no AUTH, or reject if AUTH is wrong // callerEmail = RemoteRedirector(); if callerEmail == null then callerEmail = RemoteAuthentication(); end if; if callerEmail == null then rejectCall(401); stop; end if; // Read caller's Account Settings. If failed -> reject callerSettings = GetAccountSettings(null,callerEmail); if callerSettings == null then rejectCall("500-Failed to read settings"); stop; end if; if IsString(Vars().startParameter) then phoneNumber = Vars().startParameter; else phoneNumber = Vars().startParameter[0]; gwKey = Vars().startParameter[1]; end if; SysLog("calling '" + phoneNumber + "'..."); callParams = NewDictionary(); // if the PSTNCallPlan setting is set, consult the plan database if callerSettings.PSTNBillingPlan != null and callerSettings.PSTNBillingPlan != "" then end if; if SubString(phoneNumber,-1,5) == "-clir" then callParams.Privacy = "id"; phoneNumber = SubString(phoneNumber,0,Length(phoneNumber)-5); elif SubString(phoneNumber,-1,5) == "-clip" then phoneNumber = SubString(phoneNumber,0,Length(phoneNumber)-5); elif GetAccountPreferences("~" + callerEmail + "/CallOutPrivacy") == "YES" then callParams.Privacy = "id"; end if; callParams.activeSide = true; callParams.useMixer = false; callParams.callBridged = true; callParams.("Call-ID") = PendingRequestData("Call-ID") + ".gwout"; callParams.("Max-Forwards") = PendingRequestData("Max-Forwards")-1; callParams.impersonate = callerEmail; fromAddress = getKeyedSetting(callerSettings, "PSTNFromName", gwKey); if fromAddress == "*" then fromAddress = ReadTelnums(callerEmail)[0]; end if; if fromAddress == null or else fromAddress == "" then fromAddress = SIPURIToEmail(RemoteURI()); end if; sipDomain = getKeyedSetting(callerSettings, "PSTNGatewayDomain", gwKey); if SubString(sipDomain, 0, 1) == "*" then // *domainName -> use media Proxy sipDomain = SubString(sipDomain, 1, 1000); callParams.mediaRelay = true; elif SubString(sipDomain, 0, 1) == "#" then // #domainName -> use media Mixer sipDomain = SubString(sipDomain, 1, 1000); callParams.useMixer = true; callParams.callBridged = false; end if; if SubString(sipDomain, 0, 1) == "$" then // $domainName -> impersonate as From: sipDomain = SubString(sipDomain, 1, 1000); if FindSubString(fromAddress,"@") >= 0 then callParams.impersonate = fromAddress; else callParams.impersonate = fromAddress + "@" + (isPhoneNumber(fromAddress) ? "telnum" : sipDomain); fromAddress = fromAddress + "@" + sipDomain; end if; end if; if SubString(sipDomain, 0, 1) == "&" then // &domainName -> custom-impersonate as From: sipDomain = SubString(sipDomain, 1, 1000); if FindSubString(fromAddress,"@") < 0 then fromAddress = fromAddress + "@" + sipDomain; end if; callParams.customIdentity = fromAddress; end if; // if the PSTNGatewayDomain setting is empty, PSTN calls are prohibited if sipDomain == null or else sipDomain == "" then SysLog("'" + callerEmail + "' has an empty PSTNGatewayDomain setting"); rejectCall("403-PSTN gateway is not specified"); stop; end if; callParams.From = EmailToSIPURI(fromAddress); callPrefix = getKeyedSetting(callerSettings, "PSTNPrefix", gwKey); if IsString(callPrefix) then phoneNumber = callPrefix + phoneNumber; end if; if FindSubstring(sipDomain, "@") >= 0 then result = ExecuteCLI("SIPContacts " + sipDomain); if result then syslog("CLI failed: " + result); rejectCall("501-" + result); stop; end if; registration = vars().executeCLIResult[0].(""); syslog("Reg: " + ObjecttoString(vars().executeCLIResult)); syslog("URI: " + ObjecttoString(registration)); offset = FindSubstring(registration, "@"); if offset >= 0 then sipDomain = SubString(registration, offset+1, 1000); else rejectCall("501-registration not found"); stop; end if; end if; callParams.("") = "sip:" + phoneNumber + "@" + sipDomain; callParams.Via = getKeyedSetting(callerSettings, "PSTNGatewayVia", gwKey); if callParams.Via == "" then callParams.Via = null; end if; callParams.authUsername = getKeyedSetting(callerSettings, "PSTNGatewayAuthName", gwKey); if callParams.authUsername == "test-*domain*" then callParams.authUsername = "test-" + EMailDomainPart(callerEmail) + "@" + sipDomain; end if; callParams.authPassword = getKeyedSetting(callerSettings, "PSTNGatewayPassword", gwKey); SetReferMode("peer"); SetBridgeBreakMode("disconnect"); callerIP = RemoteIPAddress(); refreshParams = newDictionary(); refreshParams.customIdentity = ""; // we do not want to send our PAI to SIP clients SetCallParameters(refreshParams); peerLeg = callerLeg(callParams,true); callStarted = GMTTime(); if not IsTask(peerLeg) then bookCall(callerEmail,phoneNumber,callerIP,callStarted,peerLeg,0); rejectCall(peerLeg); stop; end if; while IsConnected() loop input = bridgedLoopHash(peerLeg,null); exitif input != "#"; end loop; bookCall(callerEmail,phoneNumber,callerIP,callStarted,null,0); end entry; // // This function takes the "settingName" setting from settings // if the setting text is "{...}, it converts it into a dictionary // if gwKey is specified, it uses it as dictionary key, // otherwise, the first dictionary keyed value is used. // function getKeyedSetting(settings, settingName, gwKey) is setting = settings.(settingName); if SubString(setting,0,1) == "{" and then SubString(setting,-1,1) == "}" then setting = TextToObject(setting); end if; if IsDictionary(setting) then setting = setting.(gwKey != null ? gwKey : setting[0]); end if; result = null; if IsString(setting) then if setting != "" then result = setting; end if; end if; return result; end function; function durationTimeString(duration) is result = String(duration % 60); if duration >= 60 then if Length(result) < 2 then result = "0" + result; end if; result = String(duration / 60 % 60) + ":" + result; if duration >= 3600 then if Length(result) < 5 then result = "0" + result; end if; result = String(duration / 3600) + ":" + result; end if; end if; return result; end function; function timeOfDayString(timeInSeconds) is return SubString("0"+String(timeInSeconds / 3600),-1,2) + ":" + SubString("0"+String(timeInSeconds / 60 % 60),-1,2) + ":" + SubString("0"+String(timeInSeconds % 60),-1,2); end function; function moneyString(cents) is result = String(cents % 100); if length(result) < 2 then result = "0" + result; end if; result = String(cents / 100) + "." + result; return(result); end function; procedure bookCall(accountName,phoneNumber,callerIP,startTime,resultCode,priceInCents) is duration = GMTTime() - startTime; localStart = GMTToLocal(startTime); logFileName= "~" + accountName + "/private/logs/PSTNOut-" + Month(localStart) + "-" + String(Year(localStart)) + ".txt"; writeError = AppendSiteFile(logFileName, String(MonthDay(localStart)) + "\t" + timeOfDayString(TimeOfDay(localStart)) + "\t" + phoneNumber + "\t" + durationTimeString(duration) + "\t" + moneyString(priceInCents) + "\t" + String(callerIP) + "\t" + (resultCode == null ? "OK" : String(resultCode)) + "\e"); if writeError != null then SysLog("failed to write to " + logFileName + ": " + writeError); end if; end procedure;Получено Tue Jul 14 07:28:01 2009
Этот архив был сгенерирован hypermail 2.1.8 : Tue 14 Jul 2009 - 12:16:04 MSD