TIM VoIP: risolvere l'Outbound Proxy in IP (SRV -> A) per Fritz!Box, Grandstream e router liberi

Perché serve

In alcune configurazioni VoIP TIM l’Outbound Proxy viene fornito come hostname (testo alfanumerico, ad es. d835s1.co.imsw.telecomitalia.it). Alcuni dispositivi/firmware (FRITZ!Box, Grandstream, ATA, router VoIP “liberi”, ecc.) possono non risolvere correttamente quell’hostname (o non usarlo come outbound proxy), mentre funzionano se al suo posto si inserisce l’indirizzo IP.

L’idea è semplice:

  1. dal tuo outbound TIM ricavi il record SRV _sip._udp.<outbound>
  2. scegli il server “preferito” (priority più bassa)
  3. risolvi il target in record A (IPv4) → ottieni l’IP da usare come outbound proxy

Sintomi tipici

  • registrazione SIP che fallisce solo quando l’outbound è un hostname
  • con lo stesso profilo, inserendo l’IP dell’outbound proxy la registrazione va a buon fine
  • errori del tipo “DNS error / resolve failed / server non raggiungibile”

Nota su SRV: Priority vs Weight

I record SRV hanno due numeri importanti:

  • Priority: più è basso, più è preferito
  • Weight: usato come bilanciamento tra record con la stessa priority

Gli script sotto scelgono automaticamente: Priority più bassa, poi Weight più basso.

Soluzione automatica (consigliata)

Ho preparato due script:

Entrambi:

  • stampano una descrizione iniziale
  • ti chiedono l’hostname dell’outbound proxy TIM
  • fanno SRV → scelgono il record migliore → risolvono A → stampano l’IP
  • mostrano anche i DNS configurati sul sistema (e provano a riconoscere TIM via reverse DNS)

Windows (PowerShell)

Esecuzione

Apri una console CMD o PowerShell nella cartella dove hai lo script e lancia:

powershell -NoProfile -ExecutionPolicy Bypass -File .\tim_proxy_ip.ps1

Lo script ti chiede:

  1. l’outbound proxy TIM (hostname)
  2. opzionalmente un DNS specifico (utile per test)

Alla fine, nella sezione [3] trovi uno o più IP: sono quelli da mettere come Outbound Proxy.

Output dello script PowerShell

Codice (tim_proxy_ip.ps1)

# tim_proxy_ip.ps1

$intro = @"
===============================================================================
TIM Outbound Proxy -> IP (SRV -> A)
===============================================================================

OBIETTIVO
Alcuni dispositivi (FRITZ!Box, Grandstream, ATA VoIP, router VoIP "liberi", ecc.)
possono avere problemi quando l'Outbound Proxy TIM viene inserito come hostname
(alfanumerico). In molti casi invece funziona correttamente se si inserisce
direttamente l'indirizzo IP.

COSA FA QUESTO SCRIPT
1) Interroga il DNS con una query SRV su:
     _sip._udp.<tuo_outbound_proxy_TIM>
   e ottiene i server reali (target) del servizio SIP.
2) Sceglie automaticamente il record SRV preferito:
     Priority piu bassa, poi Weight piu basso
3) Risolve il target scelto con una query A per ottenere uno o piu IP (IPv4).
4) Mostra anche i DNS configurati sul PC e prova a riconoscere TIM via reverse DNS.

COME USARE IL RISULTATO
Gli IP stampati nella sezione [3] sono quelli da inserire come:
  OUTBOUND PROXY / SIP OUTBOUND PROXY
sul tuo dispositivo (Fritz!Box, Grandstream, ATA, router, ecc.).
Se escono piu IP e il primo non va, prova gli altri.
===============================================================================
"@

Write-Host $intro

function Normalize-SrvName([string]$v) {
  $v = $v.Trim().TrimEnd(".")
  if ($v.ToLower().StartsWith("_sip._udp.")) { return $v }
  if ($v.ToLower().StartsWith("sip.udp."))  { return "_sip._udp." + $v.Substring(8) }
  return "_sip._udp." + $v
}

$outbound = Read-Host "Inserisci il tuo outbound proxy TIM (es: d835s1.co.imsw.telecomitalia.it)"
$srvName = Normalize-SrvName $outbound

$useDns = Read-Host "Vuoi interrogare un DNS specifico? (s/N)"
$dnsServer = $null
if ($useDns.ToLower().StartsWith("s")) {
  $dnsServer = Read-Host "Inserisci IP del DNS (es: 85.37.17.51)"
}

Write-Host "`n[1] Query SRV: $srvName" -ForegroundColor Cyan
try {
  $srv = if ($dnsServer) {
    Resolve-DnsName -Type SRV -Name $srvName -Server $dnsServer -ErrorAction Stop
  } else {
    Resolve-DnsName -Type SRV -Name $srvName -ErrorAction Stop
  }
} catch {
  Write-Host "ERRORE: query SRV fallita. Dettaglio: $($_.Exception.Message)" -ForegroundColor Red
  exit 2
}

$records = $srv | Where-Object { $_.QueryType -eq "SRV" } |
  Select-Object NameTarget, Priority, Weight, Port

if (-not $records) {
  Write-Host "Nessun record SRV trovato nell'output." -ForegroundColor Red
  exit 3
}

Write-Host "`nRecord SRV trovati (ordinati per Priority poi Weight):" -ForegroundColor Cyan
$records | Sort-Object Priority, Weight, Port, NameTarget | Format-Table -AutoSize

$best = $records | Sort-Object Priority, Weight | Select-Object -First 1
$target = ($best.NameTarget).TrimEnd(".")

Write-Host "`n[2] Scelto automaticamente (Priority piu bassa, poi Weight): $target (port $($best.Port))" -ForegroundColor Cyan
Write-Host "`n[3] IP da inserire come OUTBOUND PROXY (Fritz!Box / Grandstream / altri):" -ForegroundColor Cyan

try {
  $a = if ($dnsServer) {
    Resolve-DnsName -Type A -Name $target -Server $dnsServer -ErrorAction Stop
  } else {
    Resolve-DnsName -Type A -Name $target -ErrorAction Stop
  }
  $ips = $a | Where-Object { $_.QueryType -eq "A" } | Select-Object -ExpandProperty IPAddress
  $ips | ForEach-Object { "  - $_" }
  # riga pronta da copiare
  if ($ips -and $ips.Count -gt 0) {
    Write-Host "`nCopia/Incolla:" -ForegroundColor Yellow
    Write-Host ("Outbound Proxy = {0}" -f $ips[0]) -ForegroundColor Yellow
  }
} catch {
  Write-Host "ERRORE: risoluzione A fallita. Dettaglio: $($_.Exception.Message)" -ForegroundColor Red
  exit 4
}

Write-Host "`n[4] DNS configurati sul PC (IPv4):" -ForegroundColor Cyan
$localDns = @()
try {
  $localDns = Get-DnsClientServerAddress -AddressFamily IPv4 |
    Select-Object -ExpandProperty ServerAddresses -Unique
} catch {
  $localDns = @()
}

if (-not $localDns -or $localDns.Count -eq 0) {
  Write-Host "  (non rilevati)"
} else {
  foreach ($ip in $localDns) {
    $rdns = $null
    try {
      $ptr = Resolve-DnsName -Type PTR -Name $ip -ErrorAction Stop
      $rdns = ($ptr | Select-Object -First 1).NameHost
    } catch { }
    $tag = ""
    if ($rdns) {
      $low = $rdns.ToLower()
      if ($low.Contains("telecomitalia") -or $low.Contains("tim") -or $low.Contains("alice")) {
        $tag = "  <-- sembra TIM (reverse DNS)"
      }
      Write-Host ("  - {0} ({1}){2}" -f $ip, $rdns, $tag)
    } else {
      Write-Host ("  - {0}" -f $ip)
    }
  }
}

Write-Host "`n[5] DNS TIM comuni (se vuoi provarli manualmente):" -ForegroundColor Cyan
Write-Host "  - 85.37.17.51"
Write-Host "  - 85.38.28.97"
Write-Host "`nFatto."

Linux (Debian/Ubuntu ecc.) – Python

Requisiti

  • python3
  • consigliato avere dig o nslookup disponibili (di solito nel pacchetto dnsutils)

Se non hai permessi sudo non è un problema: lo script non richiede root. Se dig non c’è, proverà con nslookup. Se c’è dig, userà quello.

Esecuzione

python3 tim_proxy_ip.py

Codice (tim_proxy_ip.py)

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import platform
import re
import socket
import subprocess
from dataclasses import dataclass
from typing import List, Optional

INTRO = r"""
===============================================================================
TIM Outbound Proxy -> IP (SRV -> A)
===============================================================================

OBIETTIVO
Alcuni dispositivi (FRITZ!Box, Grandstream, ATA VoIP, router VoIP "liberi", ecc.)
possono avere problemi quando l'Outbound Proxy TIM viene inserito come hostname
(alfanumerico). In molti casi invece funziona correttamente se si inserisce
direttamente l'indirizzo IP.

COSA FA QUESTO SCRIPT
1) Query SRV su:
     _sip._udp.<tuo_outbound_proxy_TIM>
   e ottiene i target reali del servizio SIP.
2) Sceglie automaticamente il record SRV preferito:
     Priority piu bassa, poi Weight piu basso
3) Query A sul target scelto per ottenere uno o piu IP (IPv4).
4) Mostra anche i DNS configurati sul sistema (Linux/Windows) e prova a
   riconoscere TIM via reverse DNS quando possibile.

COME USARE IL RISULTATO
Gli IP stampati nella sezione [3] sono quelli da inserire come:
  OUTBOUND PROXY / SIP OUTBOUND PROXY
sul tuo dispositivo (Fritz!Box, Grandstream, ATA, router, ecc.).
Se escono piu IP e il primo non va, prova gli altri.
===============================================================================
"""


@dataclass
class SrvRecord:
    priority: int
    weight: int
    port: int
    target: str


def have_cmd(name: str) -> bool:
    from shutil import which
    return which(name) is not None


def run_cmd(cmd: List[str]) -> str:
    p = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="ignore")
    out = (p.stdout or "") + "\n" + (p.stderr or "")
    return out.strip()


def normalize_srv_name(user_value: str) -> str:
    v = user_value.strip().rstrip(".")
    if v.lower().startswith("_sip._udp."):
        return v
    if v.lower().startswith("sip.udp."):
        return "_sip._udp." + v[len("sip.udp."):]
    return "_sip._udp." + v


def parse_srv_records(text: str) -> List[SrvRecord]:
    records: List[SrvRecord] = []

    # 1) DIG:
    # _sip._udp.xxx. 36352 IN SRV 10 0 5060 target.
    dig_pat = re.compile(
        r"^\S+\s+\d+\s+\S+\s+SRV\s+(\d+)\s+(\d+)\s+(\d+)\s+([^\s]+)\.?\s*$",
        flags=re.IGNORECASE | re.MULTILINE,
    )
    for m in dig_pat.finditer(text):
        records.append(SrvRecord(
            priority=int(m.group(1)),
            weight=int(m.group(2)),
            port=int(m.group(3)),
            target=m.group(4).rstrip("."),
        ))

    # 2) NSLOOKUP Linux:
    # ... service = 10 0 5060 target.
    ns_linux_pat = re.compile(
        r"^\S+\s+service\s*=\s*(\d+)\s+(\d+)\s+(\d+)\s+([^\s]+)\.?\s*$",
        flags=re.IGNORECASE | re.MULTILINE,
    )
    for m in ns_linux_pat.finditer(text):
        records.append(SrvRecord(
            priority=int(m.group(1)),
            weight=int(m.group(2)),
            port=int(m.group(3)),
            target=m.group(4).rstrip("."),
        ))

    # 3) NSLOOKUP Windows:
    priority_terms = r"(?:priority|priorit[aà])"
    weight_terms = r"(?:weight|peso)"
    port_terms = r"(?:port|porta)"
    target_terms = r"(?:svr hostname|server hostname|host(?:name)? del server|target)"

    ns_win_pat = re.compile(
        rf"{priority_terms}\s*=\s*(\d+).*?"
        rf"{weight_terms}\s*=\s*(\d+).*?"
        rf"{port_terms}\s*=\s*(\d+).*?"
        rf"{target_terms}\s*=\s*([^\s]+)",
        flags=re.IGNORECASE | re.DOTALL,
    )
    for m in ns_win_pat.finditer(text):
        records.append(SrvRecord(
            priority=int(m.group(1)),
            weight=int(m.group(2)),
            port=int(m.group(3)),
            target=m.group(4).rstrip("."),
        ))

    # Dedup stabile
    seen = set()
    out: List[SrvRecord] = []
    for r in records:
        key = (r.priority, r.weight, r.port, r.target)
        if key not in seen:
            seen.add(key)
            out.append(r)
    return out


def query_srv(name: str, dns_server: Optional[str]) -> str:
    if have_cmd("dig"):
        cmd = ["dig"]
        if dns_server:
            cmd.append(f"@{dns_server}")
        cmd += ["+noall", "+answer", "SRV", name]
        return run_cmd(cmd)
    cmd = ["nslookup", "-type=SRV", name]
    if dns_server:
        cmd.append(dns_server)
    return run_cmd(cmd)


def query_a(name: str, dns_server: Optional[str]) -> str:
    if have_cmd("dig"):
        cmd = ["dig"]
        if dns_server:
            cmd.append(f"@{dns_server}")
        cmd += ["+short", "A", name]
        return run_cmd(cmd)
    cmd = ["nslookup", "-type=A", name]
    if dns_server:
        cmd.append(dns_server)
    return run_cmd(cmd)


def parse_a_ipv4(text: str, dns_server: Optional[str]) -> List[str]:
    ips = re.findall(r"\b(?:\d{1,3}\.){3}\d{1,3}\b", text)
    if dns_server:
        ips = [ip for ip in ips if ip != dns_server]
    seen, out = set(), []
    for ip in ips:
        if ip not in seen:
            seen.add(ip)
            out.append(ip)
    return out


def reverse_dns(ip: str) -> Optional[str]:
    try:
        name, _, _ = socket.gethostbyaddr(ip)
        return name.rstrip(".")
    except Exception:
        return None


def get_local_dns_servers_linux() -> List[str]:
    dns: List[str] = []
    try:
        with open("/etc/resolv.conf", "r", encoding="utf-8", errors="ignore") as f:
            for line in f:
                line = line.strip()
                if line.startswith("nameserver"):
                    parts = line.split()
                    if len(parts) >= 2 and re.match(r"^(?:\d{1,3}\.){3}\d{1,3}$", parts[1]):
                        dns.append(parts[1])
    except Exception:
        pass
    seen, out = set(), []
    for ip in dns:
        if ip not in seen:
            seen.add(ip)
            out.append(ip)
    return out


def get_local_dns_servers_windows() -> List[str]:
    try:
        ps_cmd = [
            "powershell", "-NoProfile", "-Command",
            "Get-DnsClientServerAddress -AddressFamily IPv4 | "
            "Select-Object -ExpandProperty ServerAddresses | "
            "Where-Object { $_ -match '^(\\d{1,3}\\.){3}\\d{1,3}$' }"
        ]
        txt = run_cmd(ps_cmd)
        lines = [x.strip() for x in txt.splitlines() if x.strip()]
        seen, out = set(), []
        for ip in lines:
            if ip not in seen:
                seen.add(ip)
                out.append(ip)
        return out
    except Exception:
        return []


def get_local_dns_servers() -> List[str]:
    sysname = platform.system().lower()
    if "windows" in sysname:
        return get_local_dns_servers_windows()
    return get_local_dns_servers_linux()


def is_ipv4(s: str) -> bool:
    return bool(re.match(r"^(?:\d{1,3}\.){3}\d{1,3}$", s.strip()))


def main() -> None:
    print(INTRO)
    outbound = ""
    while not outbound:
        outbound = input("Inserisci il tuo outbound proxy TIM (es: d835s1.co.imsw.telecomitalia.it): ").strip()

    srv_name = normalize_srv_name(outbound)

    use_dns = input("Vuoi interrogare un DNS specifico? (s/N): ").strip().lower().startswith("s")
    dns_server = None
    if use_dns:
        dns_server = ""
        while not is_ipv4(dns_server):
            dns_server = input("Inserisci IP del DNS (es: 85.37.17.51): ").strip()

    print(f"\n[1] Query SRV: {srv_name}" + (f" (DNS: {dns_server})" if dns_server else " (DNS: sistema)"))

    srv_txt = query_srv(srv_name, dns_server=dns_server)
    records = parse_srv_records(srv_txt)

    if not records:
        print("\nERRORE: non ho trovato record SRV nell'output.")
        print("\n--- Output (debug) ---\n")
        print(srv_txt)
        return

    records_sorted = sorted(records, key=lambda r: (r.priority, r.weight, r.port, r.target))
    print("\nRecord SRV trovati (ordinati per Priority poi Weight):")
    for i, r in enumerate(records_sorted, 1):
        print(f"  {i:02d}) priority={r.priority}  weight={r.weight}  port={r.port}  target={r.target}")

    best = min(records, key=lambda r: (r.priority, r.weight))
    target = best.target.rstrip(".")

    print(f"\n[2] Scelto automaticamente (Priority piu bassa, poi Weight): {target} (port {best.port})")

    print("\n[3] IP da inserire come OUTBOUND PROXY (Fritz!Box / Grandstream / altri):")
    a_txt = query_a(target, dns_server=dns_server)
    ips = parse_a_ipv4(a_txt, dns_server=dns_server)

    if not ips and not dns_server:
        try:
            _, _, ip2 = socket.gethostbyname_ex(target)
            ips = sorted(set(ip2))
        except Exception:
            ips = []

    if not ips:
        print("  (nessun IPv4 trovato)")
        print("\n--- Output A (debug) ---\n")
        print(a_txt)
        return

    for ip in ips:
        print(f"  - {ip}")

    # riga pronta da copiare
    print("\nCopia/Incolla:")
    print(f"Outbound Proxy = {ips[0]}")

    local_dns = get_local_dns_servers()
    print("\n[4] DNS configurati sul sistema (IPv4):")
    if not local_dns:
        print("  (non rilevati)")
    else:
        for d in local_dns:
            rdns = reverse_dns(d)
            tag = ""
            if rdns:
                low = rdns.lower()
                if ("telecomitalia" in low) or ("tim" in low) or ("alice" in low):
                    tag = "  <-- sembra TIM (reverse DNS)"
                print(f"  - {d} ({rdns}){tag}")
            else:
                print(f"  - {d}")

    print("\n[5] DNS TIM comuni (se vuoi provarli manualmente):")
    print("  - 85.37.17.51")
    print("  - 85.38.28.97")
    print("\nFatto.")


if __name__ == "__main__":
    main()

Dove inserire l’IP ottenuto

Dipende dal dispositivo, ma in genere è un campo chiamato:

  • Outbound Proxy
  • SIP Outbound Proxy
  • Proxy Server (in alcuni ATA/router)

Esempi:

  • FRITZ!Box: inserisci l’IP nel campo Outbound proxy al posto dell’hostname
  • Grandstream: campo Outbound Proxy (o impostazioni SIP equivalenti)

Grandstream HT802V2 — impostazioni SIP con il campo Outbound Proxy

Una volta configurato correttamente, lo stato della porta mostra Registered:

Grandstream HT802V2 — Port Status con registrazione riuscita

Se ottieni più IP, e il primo non funziona, prova gli altri (possono essere nodi diversi).


Troubleshooting

  • Cache DNS: se sospetti cache “sporche”, su Windows puoi fare ipconfig /flushdns.
  • DNS del router: spesso il PC usa come DNS il router (es. 192.168.1.1). Va bene, ma per test puoi interrogare direttamente un DNS TIM.
  • IP cambiato: TIM può cambiare target/IP nel tempo. In quel caso basta rilanciare lo script e aggiornare l’IP configurato.
  • SIP/TLS/porte: questo script risolve solo l’IP (record A) e non cambia altri parametri (porta, trasporto, ecc.). Mantieni i parametri TIM che già usi.

Disclaimer

Questo contenuto è fornito “così com’è”. Usalo solo sulla tua linea/contratto e nel rispetto dei termini del tuo operatore.