// Package digip runs "dig" to find the public IP address of the local host. // // Each function sends a DNS query directly to a third-party name server that // returns the source IP address of the query. Because the query does not go // through the local DNS resolver, the answer is the public IP address of the // host (or its NAT gateway), not the IP address of the local resolver. // // The "dig" executable must be in PATH. package digip import ( "context" "fmt" "net" "os/exec" "strings" ) // Version is a dig flag that selects IPv4 or IPv6. type Version string const ( // V4 is the dig flag for IPv4. V4 = "-4" // V6 is the dig flag for IPv6. V6 = "-6" ) // Record is a DNS record type to ask for. type Record string const ( // A is the DNS record type for an IPv4 address. A = "A" // AAAA is the DNS record type for an IPv6 address. AAAA = "AAAA" ) const ( // googleDomain is a hostname that resolves to the client's IP address. // A TXT query for this name returns the IP address as a quoted string. googleDomain = "o-o.myaddr.l.google.com" // googleServer is the name server that answers queries for // googleDomain. // The "@" prefix tells dig to query this server directly. googleServer = "@ns1.google.com" // opendnsDomain is a hostname that resolves to the client's IP address. // An A query returns the IPv4 address, and an AAAA query returns the // IPv6 address. opendnsDomain = "myip.opendns.com" // opendnsServer is the name server that answers queries for // opendnsDomain. // The "@" prefix tells dig to query this server directly. opendnsServer = "@resolver1.opendns.com" ) // Google queries the Google name server for the host's public IP address. // Because Google returns the address in a TXT record, v selects IPv4 (V4) or // IPv6 (V6) transport. // // dig TXT +short -4 o-o.myaddr.l.google.com @ns1.google.com // dig TXT +short -6 o-o.myaddr.l.google.com @ns1.google.com func Google(ctx context.Context, v Version) (net.IP, error) { cmd := exec.CommandContext( ctx, "dig", "TXT", "+short", string(v), googleDomain, googleServer, ) output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("exec dig: %v", err) } if string(output) == "" { return nil, fmt.Errorf("exec dig: no output") } if len(output) < 2 { return nil, fmt.Errorf( "exec dig: unexpected output: %q", string(output), ) } // Remove the quotes from the Google TXT answer. trimmed := strings.TrimSpace(string(output)) ip := net.ParseIP(trimmed[1 : len(trimmed)-1]) return ip, nil } // OpenDNS queries the OpenDNS name server for the host's public IP address. // Use r to request the IPv4 address (A) or the IPv6 address (AAAA). // // dig +short A myip.opendns.com @resolver1.opendns.com // dig +short AAAA myip.opendns.com @resolver1.opendns.com func OpenDNS(ctx context.Context, r Record) (net.IP, error) { cmd := exec.CommandContext( ctx, "dig", "+short", string(r), opendnsDomain, opendnsServer, ) output, err := cmd.CombinedOutput() if err != nil { return nil, fmt.Errorf("exec dig: %v", err) } if string(output) == "" { return nil, fmt.Errorf("exec dig: no output") } if len(output) < 2 { return nil, fmt.Errorf( "exec dig: unexpected output: %q", string(output), ) } ip := net.ParseIP(strings.TrimSpace(string(output))) return ip, nil }