125 lines
3.3 KiB
Go
125 lines
3.3 KiB
Go
// 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
|
||
|
|
}
|