TR-181 Diagnostics: Ping, TraceRoute, and NSLookup
Setup
Mix.install([
{:caretaker, path: "."}
])
alias Caretaker.CWMP.SOAP
alias Caretaker.TR069.Diagnostics.{Ping, TraceRoute, NSLookup}
require Logger
Overview
TR-181 defines several diagnostic tools that an ACS can trigger on a CPE device:
- Ping - ICMP echo request to test connectivity and measure latency
- TraceRoute - Trace the network path to a destination host
- NSLookup - DNS resolution diagnostic
Each diagnostic follows the same pattern:
-
ACS sends a
SetParameterValuesrequest to configure the diagnostic parameters -
ACS sets
DiagnosticsStateto"Requested"to trigger the diagnostic - CPE runs the diagnostic and updates result parameters
-
ACS polls with
GetParameterValuesto retrieve results
Building a Ping Diagnostic Request
The Ping diagnostic is defined under Device.IP.Diagnostics.PingDiagnostics.*
# Create a basic Ping diagnostic configuration
# Only `host` is required; other parameters have sensible defaults
ping_config = Ping.new(host: "8.8.8.8")
Logger.info("Ping config: #{inspect(ping_config)}")
# Build the SetParameterValues body
{:ok, ping_spv_body} = Ping.build_spv(ping_config)
Logger.info("Ping SPV body fragment:\n#{IO.iodata_to_binary(ping_spv_body)}")
Building a TraceRoute Diagnostic Request
The TraceRoute diagnostic is defined under Device.IP.Diagnostics.TraceRouteDiagnostics.*
# Create a TraceRoute diagnostic configuration
traceroute_config = TraceRoute.new(host: "cloudflare.com")
Logger.info("TraceRoute config: #{inspect(traceroute_config)}")
# Build the SetParameterValues body
{:ok, traceroute_spv_body} = TraceRoute.build_spv(traceroute_config)
Logger.info("TraceRoute SPV body fragment:\n#{IO.iodata_to_binary(traceroute_spv_body)}")
Building an NSLookup Diagnostic Request
The NSLookup diagnostic is defined under Device.DNS.Diagnostics.NSLookupDiagnostics.*
# Create an NSLookup diagnostic configuration
# Note: uses `host_name` instead of `host` to match TR-181 naming
nslookup_config = NSLookup.new(host_name: "example.com")
Logger.info("NSLookup config: #{inspect(nslookup_config)}")
# Build the SetParameterValues body
{:ok, nslookup_spv_body} = NSLookup.build_spv(nslookup_config)
Logger.info("NSLookup SPV body fragment:\n#{IO.iodata_to_binary(nslookup_spv_body)}")
Customizing Diagnostic Parameters
Each diagnostic supports various optional parameters for fine-grained control.
Ping Options
# Customize Ping with all available options
custom_ping = Ping.new(
host: "1.1.1.1",
repetitions: 10, # Number of ping requests (default: 4)
timeout: 2000, # Timeout in ms per ping (default: 1000)
data_block_size: 128, # ICMP payload size in bytes (default: 56)
interface: "Device.IP.Interface.1", # Source interface (optional)
dscp: 46, # DiffServ Code Point for QoS (optional)
parameter_key: "ping-job-123" # Key echoed in SetParameterValuesResponse
)
Logger.info("Custom Ping config:")
Logger.info(" Host: #{custom_ping.host}")
Logger.info(" Repetitions: #{custom_ping.repetitions}")
Logger.info(" Timeout: #{custom_ping.timeout}ms")
Logger.info(" Data Block Size: #{custom_ping.data_block_size} bytes")
Logger.info(" Interface: #{custom_ping.interface || "(default)"}")
Logger.info(" DSCP: #{custom_ping.dscp || "(default)"}")
{:ok, custom_ping_body} = Ping.build_spv(custom_ping)
IO.iodata_to_binary(custom_ping_body)
TraceRoute Options
# Customize TraceRoute with all available options
custom_traceroute = TraceRoute.new(
host: "google.com",
timeout: 10000, # Timeout in ms (default: 5000)
data_block_size: 64, # Probe packet size (default: 56)
max_hop_count: 15, # Maximum hops to trace (default: 30)
interface: "Device.IP.Interface.2", # Source interface (optional)
dscp: 0, # DiffServ Code Point (optional)
parameter_key: "trace-job-456"
)
Logger.info("Custom TraceRoute config:")
Logger.info(" Host: #{custom_traceroute.host}")
Logger.info(" Timeout: #{custom_traceroute.timeout}ms")
Logger.info(" Max Hop Count: #{custom_traceroute.max_hop_count}")
Logger.info(" Data Block Size: #{custom_traceroute.data_block_size} bytes")
{:ok, custom_traceroute_body} = TraceRoute.build_spv(custom_traceroute)
IO.iodata_to_binary(custom_traceroute_body)
NSLookup Options
# Customize NSLookup with all available options
custom_nslookup = NSLookup.new(
host_name: "anthropic.com",
dns_server: "8.8.4.4", # Specific DNS server to query (optional)
timeout: 5000, # Timeout in ms (default: 2000)
number_of_repetitions: 3, # Number of queries (default: 1)
interface: "Device.IP.Interface.1", # Source interface (optional)
parameter_key: "nslookup-job-789"
)
Logger.info("Custom NSLookup config:")
Logger.info(" Host Name: #{custom_nslookup.host_name}")
Logger.info(" DNS Server: #{custom_nslookup.dns_server || "(system default)"}")
Logger.info(" Timeout: #{custom_nslookup.timeout}ms")
Logger.info(" Repetitions: #{custom_nslookup.number_of_repetitions}")
{:ok, custom_nslookup_body} = NSLookup.build_spv(custom_nslookup)
IO.iodata_to_binary(custom_nslookup_body)
Encoding as Complete SOAP Envelopes
The diagnostic request bodies must be wrapped in a SOAP envelope before sending to the CPE.
# Build a complete SOAP envelope for a Ping diagnostic
ping_config = Ping.new(host: "8.8.8.8", repetitions: 5)
{:ok, ping_body} = Ping.build_spv(ping_config)
# Wrap in SOAP envelope with a message ID
{:ok, ping_envelope} = SOAP.encode_envelope(ping_body, %{
id: "diag-ping-001",
cwmp_ns: "urn:dslforum-org:cwmp-1-0"
})
Logger.info("Complete Ping SOAP envelope:")
IO.puts(ping_envelope)
# Build a complete SOAP envelope for a TraceRoute diagnostic
traceroute_config = TraceRoute.new(host: "example.org", max_hop_count: 20)
{:ok, traceroute_body} = TraceRoute.build_spv(traceroute_config)
{:ok, traceroute_envelope} = SOAP.encode_envelope(traceroute_body, %{
id: "diag-trace-002",
cwmp_ns: "urn:dslforum-org:cwmp-1-0"
})
Logger.info("Complete TraceRoute SOAP envelope:")
IO.puts(traceroute_envelope)
# Build a complete SOAP envelope for an NSLookup diagnostic
nslookup_config = NSLookup.new(host_name: "github.com", dns_server: "1.1.1.1")
{:ok, nslookup_body} = NSLookup.build_spv(nslookup_config)
{:ok, nslookup_envelope} = SOAP.encode_envelope(nslookup_body, %{
id: "diag-nslookup-003",
cwmp_ns: "urn:dslforum-org:cwmp-1-0"
})
Logger.info("Complete NSLookup SOAP envelope:")
IO.puts(nslookup_envelope)
Diagnostic Workflow: How Results are Retrieved
Diagnostics in TR-069/TR-181 follow an asynchronous pattern:
1. Send SetParameterValues to Start Diagnostic
The ACS sends the SetParameterValues request (as shown above) which includes:
- Configuration parameters (host, timeout, etc.)
-
DiagnosticsStateset to"Requested"
2. CPE Acknowledges and Runs Diagnostic
The CPE responds with SetParameterValuesResponse and begins executing the diagnostic.
The DiagnosticsState transitions through:
-
"Requested"- Diagnostic has been requested -
"Complete"- Diagnostic finished successfully -
"Error_*"- Diagnostic encountered an error (e.g.,Error_CannotResolveHostName)
3. Poll for Results with GetParameterValues
The ACS must poll the CPE to check when the diagnostic is complete and retrieve results.
# Example: Parameters to poll for Ping results
ping_result_params = [
"Device.IP.Diagnostics.PingDiagnostics.DiagnosticsState",
"Device.IP.Diagnostics.PingDiagnostics.SuccessCount",
"Device.IP.Diagnostics.PingDiagnostics.FailureCount",
"Device.IP.Diagnostics.PingDiagnostics.AverageResponseTime",
"Device.IP.Diagnostics.PingDiagnostics.MinimumResponseTime",
"Device.IP.Diagnostics.PingDiagnostics.MaximumResponseTime"
]
Logger.info("Ping result parameters to poll:")
Enum.each(ping_result_params, fn p -> Logger.info(" #{p}") end)
# Example: Parameters to poll for TraceRoute results
# Note: TraceRoute results are in a RouteHops table
traceroute_result_params = [
"Device.IP.Diagnostics.TraceRouteDiagnostics.DiagnosticsState",
"Device.IP.Diagnostics.TraceRouteDiagnostics.ResponseTime",
"Device.IP.Diagnostics.TraceRouteDiagnostics.RouteHopsNumberOfEntries",
# Each hop has its own entry:
"Device.IP.Diagnostics.TraceRouteDiagnostics.RouteHops.1.Host",
"Device.IP.Diagnostics.TraceRouteDiagnostics.RouteHops.1.HostAddress",
"Device.IP.Diagnostics.TraceRouteDiagnostics.RouteHops.1.RTTimes"
]
Logger.info("TraceRoute result parameters to poll:")
Enum.each(traceroute_result_params, fn p -> Logger.info(" #{p}") end)
# Example: Parameters to poll for NSLookup results
# Note: NSLookup results are in a Result table
nslookup_result_params = [
"Device.DNS.Diagnostics.NSLookupDiagnostics.DiagnosticsState",
"Device.DNS.Diagnostics.NSLookupDiagnostics.SuccessCount",
"Device.DNS.Diagnostics.NSLookupDiagnostics.ResultNumberOfEntries",
# Each result has its own entry:
"Device.DNS.Diagnostics.NSLookupDiagnostics.Result.1.Status",
"Device.DNS.Diagnostics.NSLookupDiagnostics.Result.1.AnswerType",
"Device.DNS.Diagnostics.NSLookupDiagnostics.Result.1.HostNameReturned",
"Device.DNS.Diagnostics.NSLookupDiagnostics.Result.1.IPAddresses"
]
Logger.info("NSLookup result parameters to poll:")
Enum.each(nslookup_result_params, fn p -> Logger.info(" #{p}") end)
4. Alternative: Use Passive Notification
Instead of polling, the ACS can set up passive notifications on diagnostic parameters. The CPE will then send an Inform with the results when the diagnostic completes.
# DiagnosticsState values and their meanings
diagnostic_states = %{
"None" => "No diagnostic has been requested",
"Requested" => "Diagnostic has been requested but not started",
"Complete" => "Diagnostic completed successfully",
"Error_CannotResolveHostName" => "Could not resolve the target hostname",
"Error_MaxHopCountExceeded" => "TraceRoute exceeded max hops without reaching target",
"Error_Internal" => "Internal error occurred during diagnostic",
"Error_Other" => "Other unspecified error"
}
Logger.info("DiagnosticsState values:")
Enum.each(diagnostic_states, fn {state, desc} ->
Logger.info(" #{state}: #{desc}")
end)
Summary
This livebook demonstrated how to:
-
Create diagnostic configurations using
Ping.new/1,TraceRoute.new/1, andNSLookup.new/1 -
Build
SetParameterValuesrequest bodies withbuild_spv/1 - Customize diagnostic parameters (host, timeout, repetitions, etc.)
- Wrap diagnostic requests in complete SOAP envelopes
- Understand the asynchronous diagnostic workflow in TR-069
The diagnostic modules handle all the TR-181 parameter naming and type conversions, making it easy to trigger network diagnostics on CPE devices.