TIM VoIP: turn Outbound Proxy hostname into an IP (SRV -> A) for Fritz!Box, Grandstream and open routers

Why this is useful

With some TIM VoIP setups the Outbound Proxy is provided as a hostname (e.g. d835s1.co.imsw.telecomitalia.it). Some devices/firmware (FRITZ!Box, Grandstream, ATA gateways, “open” VoIP routers, etc.) may fail when you enter that hostname, but work if you enter the IP address instead.

The idea:

  1. query the SRV record _sip._udp.<outbound>
  2. pick the preferred target (lowest priority)
  3. resolve the target to an A record (IPv4) → that IP is what you enter as outbound proxy

Common symptoms

  • SIP registration fails only when outbound proxy is a hostname
  • the same account works if you replace the outbound proxy with the resolved IP
  • “DNS error / resolve failed / server unreachable” style errors

SRV quick note: Priority vs Weight

SRV records carry:

  • Priority: lower = preferred
  • Weight: load-balancing for targets with the same priority

The scripts below pick automatically: lowest Priority, then lowest Weight.

Two scripts:

Both scripts:

  • print an intro at startup
  • ask for your TIM outbound proxy hostname
  • do SRV → pick best record → resolve A → print the IP(s)
  • also show the DNS servers configured on the system (and attempt to detect TIM via reverse DNS)

Windows (PowerShell)

Run

Open CMD or PowerShell in the folder where the script is and run:

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

The script will ask:

  1. your TIM outbound proxy hostname
  2. optionally, a specific DNS server to query

In section [3] you’ll get one or more IPs: those are the ones to enter as Outbound Proxy.

PowerShell script output

Code (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 etc.) – Python

Requirements

  • python3
  • it helps to have dig or nslookup installed (often from the dnsutils package)

No root is required. If dig is available the script will use it, otherwise it falls back to nslookup.

Run

python3 tim_proxy_ip.py

Code (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()

Where to paste the resolved IP

Device UI labels differ, but you typically want:

  • Outbound Proxy
  • SIP Outbound Proxy
  • sometimes Proxy Server (on certain ATA/router UIs)

Examples:

  • FRITZ!Box: set the Outbound proxy field to the resolved IP
  • Grandstream: set Outbound Proxy (or equivalent SIP settings)

Grandstream HT802V2 — SIP settings with the Outbound Proxy field

Once configured correctly, the port status shows Registered:

Grandstream HT802V2 — Port Status showing successful registration

If you see multiple IPs and the first one doesn’t work, try the others.


Troubleshooting

  • DNS cache: on Windows you can run ipconfig /flushdns.
  • Router DNS: your PC often uses the router as DNS (e.g. 192.168.1.1). That’s fine, but for testing you can query a TIM DNS directly.
  • IPs can change: TIM may change targets/IPs over time; just re-run the script and update your configuration.
  • Transport/ports: this script only resolves the IP (A record). Keep the other SIP parameters you already have from TIM.

Disclaimer

Provided as-is. Use it only on your own line/contract and in compliance with your provider’s terms.